加载中…
个人资料
qhqh
qhqh
  • 博客等级:
  • 博客积分:0
  • 博客访问:1,680
  • 关注人气:28
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

用python实现一个轻量级RADIUS服务器(WINDOWS平台)

(2014-11-28 09:41:28)
标签:

python

radius

windows

分类: python

近日有需求要实现一个RADIUS服务器。因为需要灵活的控制属性下发。所以不打算使用FREERADIUS,想要自己实现一个。因自身条件所限,我选择的语言为python,平台为WINDWOS 2003 SERVER。

一、首先要安装几个模块

1、twisted:实现UDP服务器框架

2、pywin32 :这个主要是为了在支持IOCP,提高系统吞吐效率

3、pyrad :用python实现的对RADISU的支持

 

二、准备一下字典文件

我直接使用了freeradius的字典文件,由于我用到华为的设备,我把其它的INCLUDE都删除,只把华为的厂商自定义属性直接加到dictionary文件里面

 

三、编写代码

pyrad里面有个实现RADIUS服务器的示例,但是在linux平台上的。有个WINDOWS平台的示例,但非常不完整,根本无法工作。但这毕竟给了我参考的东西,根据这两个示例整合一下。

#-* -coding: gbk -* -

'''
Created on 2014-10-16

@author: qh
'''

from twisted.internet import iocpreactor as iocpreactor

try:
    iocpreactor.install()
except Exception, e:
    print "iocp install failed:%s" % str(e)
   
from twisted.internet import reactor
from twisted.internet import protocol
from twisted.python import log
import sys
import traceback
from pyrad import dictionary
from pyrad import host
from pyrad import packet
from pyrad.client import Client
import pyrad
import socket

import random
import time
import binascii
import traceback

 

class PacketError(Exception):
    """Exception class for bogus packets

    PacketError exceptions are only used inside the Server class to
    abort processing of a packet.
    """

#RADUYS是一个基类,认证和计帐都是基于这个基类的

class RADIUS(host.Host, protocol.DatagramProtocol):
    def __init__(self, hosts={},key='', dict=dictionary.Dictionary()):
        host.Host.__init__(self, dict=dict)
        self.hosts = hosts    #hosts是一个服务器地址列表,其实就是你RADIUS服务器监听的地址
        self.key=key  #radius的KEY

        self.mysrv=Client(server="1.1.1.1",  #这是另一台RADIUS server,我们要把认证和计费请求发给它
           secret="xxxxxxx",
           dict=dict)

    def processPacket(self, pkt):
        pass

    def createPacket(self, **kwargs):  #这个函数子类一定要实现,进行实际的业务处理
        print "error"
        raise NotImplementedError('Attempted to use a pure base class')
   
    def CreateReplyPacket(self, pkt, **attributes):
        reply = pkt.CreateReply(**attributes)
        reply.source = pkt.source
       
       
        return reply
   
    def datagramReceived(self, datagram, (host, port)):  #这个是用来进行实际报文接收和发送的函数
        try:
            #print "create packet"
            pkt = self.createPacket(packet=datagram)
        except packet.PacketError as err:
            log.msg('Dropping invalid packet: ' + str(err))
            return

        #if host not in self.hosts:
          log.msg('Dropping packet from unknown host ' + host)
          return

        pkt.source = (host, port)
        try:
            reply=self.processPacket(pkt)
           
        except PacketError as err:
            log.msg('Dropping packet from %s: %s' % (host, str(err)))
        try:
            self.transport.write(reply.ReplyPacket(), (host, port))
        except Exception, e:
            log.msg("transport.write:%s" % str(e))
            traceback.print_exc() 

#这是用来进行认证的类,我们这个实现比较简单,接到认证请求后,改变或增减一些属性后,转发给另一个radius SERVER,    

       
class RADIUSAccess(RADIUS):
    def createPacket(self, **kwargs):
        #print "create auth packet"
        return self.CreateAuthPacket(**kwargs)

 

    def checkUser(self,myattrb,userName,userPass,myreply): #这个函数用于我们自己对RADIUS属性进行处理
        mypos=userName.find('@')
        myuserName=userName
        if mypos>0:
            myuserName=userName[:mypos]
        req=self.mysrv.CreateAuthPacket(code=packet.AccessRequest,
                         User_Name=myuserName
                         )
        req["NAS-IP-Address"]     = "a.b.c.d" #转发时,我们是作为RADIUS客户端的,所以这个地址应该设置为我们RADIUS服务器的地址
        req["User-Password"]=req.PwCrypt(userPass)
        req["NAS-Identifier"]     = "qh"
        for mykey in myattrb:
            #print mykey,myattrb[mykey]
            req[mykey]=myattrb[mykey]
        msg='unknow'
        try:
            #print "Sending authentication request"
            reply=self.mysrv.SendPacket(req)
            msg=reply['Reply-Message'][0]
        except pyrad.client.Timeout:
            print "RADIUS server does not reply"
            return False
        except socket.error, error:
            print "Network error: " + error[1]
            return False
       
        #print "Attributes returned by server:"
        #for i in reply.keys():
            #print "%s: %s" % (i, reply[i])
        if reply.code==pyrad.packet.AccessAccept:
            print "Access accepted:%s->%s" % (myuserName,msg)
            if msg=='SUCCESS':  //转发的那台RADIUS服务器有点特殊,它返回AccessAccept且msg为SUCCESS才是真正的认证成功
                myattrb={}
                print "radius return value:"
                for attr in reply.keys():
                    print "%s: %s" % (attr, reply[attr])
                    if attr in('User-Password','User-Name','NAS-Identifier','NAS-IP-Address','Huawei-Domain-Name'):#除了这几个属性外,其它属性都不经修改直接转发
                        continue
                    myreply[attr]=reply[attr][0]
                return True
            else:
                return False
        else:
            print "Access denied:%s->%s" % (myuserName,msg)
            return False
        return False

 

    def AddAttrib(self,reply,username,nas_ip): //这个函数会根据我们的要求,加上一些属性,具体实现就看看你自己的需求了,这里只给出了一个示例,加上了一个华为厂商属性(2011),属性为域名(138)

         val='pppoe'

         attrib=(2011,138) #不知道是什么原因,我虽然已经在diction文件里面加上了华为的厂商属性,而且也可以在程序里面print这些属性了,但设置这些属性时却不能使用属性名,只能使用这种形式

         reply[attrib]=val

 

    def processPacket(self, pkt):
        pkt.secret=self.key
        if pkt.code != packet.AccessRequest:
            raise PacketError(
                    'non-AccessRequest packet on authentication socket')

        myattrb={}
        for attr in pkt.keys():
            if attr in('User-Password','User-Name','NAS-Identifier','NAS-IP-Address'):
                continue
            myattrb[attr]=pkt[attr][0]

        reply=self.CreateReplyPacket(pkt)
        try:
            userName=pkt['User-Name'][0]
            nas_ip=pkt['NAS-IP-Address']
            userPass=pkt.PwDecrypt(pkt[2][0])  //注意如何把密码提取出来,这个纠结了我很久

             reply.code=packet.AccessAccept
             self.AddAttrib(reply,userName,nas_ip)
            else:
                reply.code=packet.AccessReject
                print "reject:%s" % userName
        except Exception, e:
            reply.code=packet.AccessReject
            print "found Excption:%s" % str(e)
            traceback.print_exc()
       
        print "send reply to bas:"
        for attr in reply.keys():
            print "%s: %s" % (attr, reply[attr])
        return reply

 

#这个是计帐的子类,这里我们的实现非常简单,直接把NAS-IP-Address和NAS-Identifier属性改了后,直接转发给另一台RADIUS SERVER

class RADIUSAccounting(RADIUS):
    def createPacket(self, **kwargs):
        return self.CreateAcctPacket(**kwargs)
   
    def sendAccount(self,userName,myattrib):
        mypos=userName.find('@')
        myuserName=userName
        if mypos>0:
            myuserName=userName[:mypos]
        req=self.mysrv.CreateAcctPacket(User_Name=myuserName)
        req["NAS-IP-Address"]="a.b.c.d"
        req["NAS-Identifier"]="gl_155"
        for mykey in myattrib:
            print mykey,myattrib[mykey]
            req[mykey]=myattrib[mykey]
        try:
            self.mysrv.SendPacket(req)
        except pyrad.client.Timeout:
            print "RADIUS server does not reply"
       
        except socket.error, error:
            print "Network error: " + error[1]
       
    def processPacket(self, pkt):
        if pkt.code != packet.AccountingRequest :
            raise PacketError(
                    'non-AccountingRequest packet on authentication socket')
        #print "Received an accounting request"
        #print "Attributes: "
        pkt.secret=self.key
        myattrb={}
        for attr in pkt.keys():
            #print "%s: %s" % (attr, pkt[attr])
            if attr in('User-Password','User-Name','NAS-Identifier','NAS-IP-Address'):
                continue
            myattrb[attr]=pkt[attr][0]
        userName=pkt['User-Name'][0]
        try:
            self.sendAccount(userName,myattrb)
        except Exception, e:
            print "found Excption:%s" % str(e)
           
        reply=self.CreateReplyPacket(pkt)
        reply.code=packet.AccountingResponse
        return reply

 

if __name__ == '__main__':
    log.startLogging(sys.stdout, 0)
    myhosts=[]
    myhosts.append('a.b.c.d')
    mydict=dictionary.Dictionary("dictionary")
    reactor.listenUDP(1812, RADIUSAccess(hosts=myhosts,key='yyyyyyyy',dict=mydict))
    reactor.listenUDP(1813, RADIUSAccounting(hosts=myhosts,key='yyyyyyyy',dict=mydict))
    reactor.run()

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有