【python3】简易双因子Radius认证服务器,带UI界面


需要将dictionary.rfc2865放在运行的同级目录下

#_*_coding:utf-8_*_
from tkinter import *
from tkinter.ttk import *
from tkinter import scrolledtext
from tkinter import messagebox
import time
import os
import re
import socket
import threading
from os import environ
from os.path import join, exists
#from ico import img
#import base64
from pyrad import packet
import pyrad.dictionary
import pyrad.host
import random
import string

CHALLENGE_LEN = 6
CHALLENGE_TYPE = 6
CHALLENGE = "BAdPassw0d"
FilterId = ""

class RadiusServer(pyrad.host.Host):
    def __init__(self):
        dict = pyrad.dictionary.Dictionary("dictionary.rfc2865") #从freeradius中搞一个通用的字典使用
        pyrad.host.Host.__init__(self, dict=dict)

    def get_challenge(self):
        if CHALLENGE_TYPE == 1:
            return (''.join(random.choice(string.ascii_letters) for _ in range(CHALLENGE_LEN))).upper()
        if CHALLENGE_TYPE == 2:
            return (''.join(random.choice(string.ascii_letters) for _ in range(CHALLENGE_LEN))).lower()
        if CHALLENGE_TYPE == 4:
            return ''.join(random.choice(string.ascii_letters) for _ in range(CHALLENGE_LEN))
        if CHALLENGE_TYPE == 6:
            return ''.join(random.choice(string.digits) for _ in range(CHALLENGE_LEN))
        if CHALLENGE_TYPE == 8:
            return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(CHALLENGE_LEN))

    def check_pap_pass(self, radpkt):
        global CHALLENGE
        if radpkt.PwCrypt(CHALLENGE) == radpkt["User-Password"][0]:
            radpkt.code = packet.AccessAccept
            if(len(FilterId)>0):
                radpkt['Filter-Id'] = FilterId
                radpkt['Class'] = FilterId  #安盟使用的安全组信息
            return("用户名:%-16s 状态:挑战码认证通过" % radpkt["User-Name"][0])
        elif radpkt.PwCrypt("Authpasswd") == radpkt["User-Password"][0]:
            radpkt.code = packet.AccessChallenge
            CHALLENGE = self.get_challenge()
            radpkt['Reply-Message'] = "Enter Token Code"
            radpkt['State'] = ("CHALLENGE=%s" % CHALLENGE).encode()
            return("用户名:%-16s 状态:待挑战认证 挑战码:%s" % (radpkt["User-Name"][0],CHALLENGE))
        elif radpkt.PwCrypt("password") == radpkt["User-Password"][0]:
            radpkt.code = packet.AccessAccept
            if(len(FilterId)>0):
                radpkt['Filter-Id'] = FilterId
            return("用户名:%-16s 状态:认证通过" % radpkt["User-Name"][0])
        # elif radpkt.PwCrypt(PIN+"password") == radpkt["User-Password"][0]:
        #     radpkt.code = packet.AccessAccept
        #     return("用户名:%-16s 状态:PIN认证通过" % radpkt["User-Name"][0])
        else:
            radpkt.code = packet.AccessReject
            return("用户名:%-16s 状态:认证失败" % radpkt["User-Name"][0])

    def get_pkt(self, pkt):
        get_pw = None
        get_name = None
        radpkt = self.CreateAuthPacket(packet=pkt) #解析请求报文
        radpkt.secret = b"sharepasswd"
        #radpkt.secret = bytes(secret, encoding = "utf8")

        for key in radpkt.keys():
            print(key, radpkt[key])
        print("="*60)
        info = ""
        radpkt.code = packet.AccessAccept
        if "User-Password" in radpkt.keys():
            info = self.check_pap_pass(radpkt)
        else:
            info = "用户名:%-16s 状态:CHAP通过   IP:%s" % (radpkt['User-Name'][0],radpkt['NAS-IP-Address'][0])

        for key in radpkt.keys():
            print(key, radpkt[key])
        print("="*60)
        return radpkt.ReplyPacket(),info


class app_GUI():
    def __init__(self,init_window_name):
        self.init_window_name = init_window_name
        self.service = False  #开启service 监听

    #设置窗口
    def set_init_window(self):
        self.set_frame()
        self.load_config()

################################界面相关###################################
    def set_frame(self):
        frmLT = Frame(width=580,height=800)
        frmLT.grid(row=0, column=0, padx=18)
        row = 1

        self.IPlabel = Label(frmLT, text="服务IP:")
        self.IPlabel.grid(row=row, column=2,sticky=E, pady=(12,0))
        self.IPvar = StringVar()
        self.IPEntry= Entry(frmLT,textvariable=self.IPvar)
        self.IPvar.set("0.0.0.0")
        self.IPEntry.grid(row=row, column=3, pady=(12,0))
        row +=1
        self.portlabel = Label(frmLT, text="服务端口:")
        self.portlabel.grid(row=row, column=2,sticky=E, pady=(6,6))
        self.portvar = StringVar()
        self.portEntry= Entry(frmLT,textvariable=self.portvar)
        self.portvar.set(1812)
        self.portEntry.grid(row=row, column=3)
        row +=1
        self.filteridlabel = Label(frmLT, text="安全组:")
        self.filteridlabel.grid(row=row, column=2,sticky=E)
        self.filteridvar = StringVar()
        self.filteridEntry= Entry(frmLT,textvariable=self.filteridvar)
        self.filteridvar.set("")
        self.filteridEntry.grid(row=row, column=3)
        self.filteridEntry.bind('<Return>', self.change_filter_id)
        row +=1
        self.radioVar = IntVar()
        self.chaLenLabel = Label(frmLT, text="挑战码长度")
        self.chaLenLabel.grid(row=row, column=2,sticky=E, pady=(6,6))
        self.chaLenVar = StringVar()
        self.chaLenEntry= Entry(frmLT,textvariable=self.chaLenVar)
        self.chaLenVar.set(6)
        self.chaLenEntry.grid(row=row, column=3)
        self.chaLenEntry.bind('<Return>', self.change_challenge_len)
        row +=1
        self.cha = Label(frmLT, text="挑战码类型")
        self.cha.grid(row=row, column=4,sticky=E)
        row +=1
        self.R1 = Radiobutton(frmLT, text="大写字母", variable=self.radioVar, value=1, command=self.change_challenge_type)
        self.R1.grid(row=row, column=2)
        self.R2 = Radiobutton(frmLT, text="小写字母", variable=self.radioVar, value=2, command=self.change_challenge_type)
        self.R2.grid(row=row, column=3)
        self.R3 = Radiobutton(frmLT, text="纯字母", variable=self.radioVar, value=4, command=self.change_challenge_type)
        self.R3.grid(row=row, column=4)
        self.R4 = Radiobutton(frmLT, text="纯数字", variable=self.radioVar, value=6, command=self.change_challenge_type)
        self.R4.grid(row=row, column=7)
        self.R5 = Radiobutton(frmLT, text="全组合", variable=self.radioVar, value=8, command=self.change_challenge_type)
        self.R5.grid(row=row, column=9)
        self.radioVar.set(6)
        row +=1
        self.logTextLabel = Label(frmLT, text="运行日志")
        self.logTextLabel.grid(row=row, column=0, columnspan=15, pady=(12,4))
        row +=1
        self.logText = scrolledtext.ScrolledText(frmLT,width=80, height=15,borderwidth=0.1)
        self.logText.grid(row=row, column=0, columnspan=15,sticky=NW)


        self.strStartButton = StringVar()
        self.strStartButton.set("开启服务")
        self.startButton = Button(frmLT, textvariable=self.strStartButton,command=self.start_service)
        self.startButton.grid(row=2, column=7,)
        self.strHelpButton = Button(frmLT, text="使用说明",command=self.show_help_info)
        self.strHelpButton.grid(row=3, column=7)
        frmLT.grid_propagate(0)

################################保存配置相关###################################
    def config_filename(self):
        return join(environ["appdata"], 'radius_server.config')

    def load_config(self):
        if not exists(self.config_filename()):
            return
        try:
            with open(self.config_filename()) as fd:
                config = eval(fd.read())
                self.IPvar.set(config["ip"])
                self.portvar.set(config["port"])
                global CHALLENGE_LEN,CHALLENGE_TYPE,FilterId
                self.chaLenVar.set(config['challengeLen'])
                CHALLENGE_LEN = int(config['challengeLen'])
                self.radioVar.set(config['challengeType'])
                CHALLENGE_TYPE = int(config['challengeType'])
                if "filterId" in config:
                    self.filteridvar.set(config['filterId'])
                    FilterId = config['filterId']
        except Exception as e:
            self.write_log_to_Text("[ERROR]  %s" % e)

    def save_config(self):
        ip = self.IPvar.get()
        port=self.portvar.get()
        challengelen = self.chaLenEntry.get()
        challengetype= self.radioVar.get()
        filterId = FilterId
        dev = {"ip":ip, "port":int(port), "challengeLen":challengelen, "challengeType":challengetype, "filterId":filterId}
        with open(self.config_filename(), 'w') as fd:
            fd.write(str(dev))

################################内部调用###################################
    def api_para_check_failed(self):
        ip = self.IPvar.get()
        port=int(self.portvar.get())
        if not re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",ip.strip()) and "::" not in ip:
            self.write_log_to_Text("[ERROR] 请输入合法IP!")
            return True
        if port<1 or port>65535:
            self.write_log_to_Text("[ERROR] 服务端口非法!")
            return True
        self.IPvar.set(ip.strip())
        return False


    #日志动态打印
    def write_log_to_Text(self,logmsg):
        self.logText.see(END)
        current_time = self.get_current_time()
        logmsg_in = current_time +" " + str(logmsg) + "\n"
        self.logText.insert(END, logmsg_in)
        self.logText.see(END)
        self.logText.update()

    #获取当前时间
    def get_current_time(self):
        current_time = time.strftime('%m-%d %H:%M:%S',time.localtime(time.time()))
        return str(current_time)
################################对外接口调用###################################
    def start_service(self):
        if self.api_para_check_failed():  #参数校验不通过
            return
        self.change_challenge_type()
        self.change_filter_id("start")
        self.save_config()
        if self.service == False:
            self.service_t=threading.Thread(target=self.startup_radius_sevice, args=())
            self.service_t.setDaemon(True)
            self.service_t.start()
        else:
            self.service = False
            self.write_log_to_Text("[INFO] %s:%d停止服务" % (self.ip_port[0],self.ip_port[1]))
            self.strStartButton.set("开启服务")

    def startup_radius_sevice(self):
        try:
            self.ip_port = (self.IPEntry.get(), int(self.portEntry.get()))
            if "::" in self.IPEntry.get():
                self.server = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)  # ipv6 udp协议
            else:
                self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # ipv4 udp协议
            self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server.bind(self.ip_port)
        except Exception as e:
            self.write_log_to_Text("[Error]:%s" % e)
            return
        self.service = True
        self.write_log_to_Text("[INFO] %s:%d开启服务" % (self.ip_port[0],self.ip_port[1]))
        self.strStartButton.set("关闭服务")
        while True:
            if not self.service:
                self.server.close()
                break
            data,client_addr = self.server.recvfrom(1024)
            srv = RadiusServer()
            reply,info = srv.get_pkt(data)
            self.server.sendto(reply, client_addr)
            self.write_log_to_Text("[INFO] %s" % info)
            time.sleep(0.1)

    def change_challenge_len(self,type):
        global CHALLENGE_LEN
        if len(self.chaLenVar.get().strip())==0:
            return
        try:
            a = int(self.chaLenVar.get())
            if a < 1 or a > 64:
                self.write_log_to_Text("[Error] 挑战码长度为1~64")
                return
            if a != CHALLENGE_LEN:
                self.write_log_to_Text("[INFO] 挑战码长度修改为%d" % a)
            CHALLENGE_LEN = a
            self.save_config()
        except:
            self.write_log_to_Text("[Error] 挑战码长度值非法")

    def change_filter_id(self,type):
        global FilterId
        lastv = FilterId
        if lastv == self.filteridvar.get():
            return
        FilterId = self.filteridvar.get()
        if(len(FilterId)==0):
            self.write_log_to_Text("[INFO] 认证回应报文不携带Filter-Id字段")
        else:
            self.write_log_to_Text("[INFO] 安全组名称修改为%s" % FilterId)
        self.save_config()

    def change_challenge_type(self):
        global CHALLENGE_TYPE
        CHALLENGE_TYPE = self.radioVar.get()
        self.save_config()

    def show_help_info(self):
        info="""
服务IP是0.0.0.0  本机所有ipv4地址
服务IP是::           本机所有ipv6地址

共享秘钥为sharepasswd
认证方式PAP
用户名随意
密码是password         认证成功
密码是Authpasswd        需要挑战认证,会随机生成挑战码
密码是其他值                   认证失败

认证方式CHAP
用户名随意
密码随意                           认证成功

ps:开启服务后,修改挑战码长度或安全组名称时,需要按回车键才会生效
        """
        messagebox.showinfo("说明",info)

def app_gui_start(title):
    init_window = Tk()
    init_window.resizable(0,0)
    sw = init_window.winfo_screenwidth()
    sh = init_window.winfo_screenheight()
    ww = 614
    wh = 420
    x = (sw-ww) / 2
    y = (sh-wh) / 2
    init_window.title(title)
    init_window.geometry("%dx%d+%d+%d" %(ww,wh,x,y))
    #tmp = open("tmp.ico","wb+")  
    #tmp.write(base64.b64decode(img))
    #tmp.close()
    #init_window.iconbitmap("tmp.ico")
    #os.remove("tmp.ico") 
    app_PORTAL = app_GUI(init_window)
    app_PORTAL.set_init_window()
    init_window.mainloop()

app_gui_start("Simple Radius Server v22.02.19")

附dictionary.rfc2865字典内容,直接拷贝内容存为文件即可

# -*- text -*-
#
#	Attributes and values defined in RFC 2865.
#	http://www.ietf.org/rfc/rfc2865.txt
#
#	$Id: dictionary.rfc2865,v 1.3.2.1 2005/11/30 22:17:29 aland Exp $
#
ATTRIBUTE	User-Name				1	string
ATTRIBUTE	User-Password				2	string encrypt=1
ATTRIBUTE	CHAP-Password				3	octets
ATTRIBUTE	NAS-IP-Address				4	ipaddr
ATTRIBUTE	NAS-Port				5	integer
ATTRIBUTE	Service-Type				6	integer
ATTRIBUTE	Framed-Protocol				7	integer
ATTRIBUTE	Framed-IP-Address			8	ipaddr
ATTRIBUTE	Framed-IP-Netmask			9	ipaddr
ATTRIBUTE	Framed-Routing				10	integer
ATTRIBUTE	Filter-Id				11	string
ATTRIBUTE	Framed-MTU				12	integer
ATTRIBUTE	Framed-Compression			13	integer
ATTRIBUTE	Login-IP-Host				14	ipaddr
ATTRIBUTE	Login-Service				15	integer
ATTRIBUTE	Login-TCP-Port				16	integer
# Attribute 17 is undefined
ATTRIBUTE	Reply-Message				18	string
ATTRIBUTE	Callback-Number				19	string
ATTRIBUTE	Callback-Id				20	string
# Attribute 21 is undefined
ATTRIBUTE	Framed-Route				22	string
ATTRIBUTE	Framed-IPX-Network			23	ipaddr
ATTRIBUTE	State					24	octets
ATTRIBUTE	Class					25	octets
ATTRIBUTE	Vendor-Specific				26	octets
ATTRIBUTE	Session-Timeout				27	integer
ATTRIBUTE	Idle-Timeout				28	integer
ATTRIBUTE	Termination-Action			29	integer
ATTRIBUTE	Called-Station-Id			30	string
ATTRIBUTE	Calling-Station-Id			31	string
ATTRIBUTE	NAS-Identifier				32	string
ATTRIBUTE	Proxy-State				33	octets
ATTRIBUTE	Login-LAT-Service			34	string
ATTRIBUTE	Login-LAT-Node				35	string
ATTRIBUTE	Login-LAT-Group				36	octets
ATTRIBUTE	Framed-AppleTalk-Link			37	integer
ATTRIBUTE	Framed-AppleTalk-Network		38	integer
ATTRIBUTE	Framed-AppleTalk-Zone			39	string

ATTRIBUTE	CHAP-Challenge				60	octets
ATTRIBUTE	NAS-Port-Type				61	integer
ATTRIBUTE	Port-Limit				62	integer
ATTRIBUTE	Login-LAT-Port				63	integer

#
#	Integer Translations
#

#	Service types

VALUE	Service-Type			Login-User		1
VALUE	Service-Type			Framed-User		2
VALUE	Service-Type			Callback-Login-User	3
VALUE	Service-Type			Callback-Framed-User	4
VALUE	Service-Type			Outbound-User		5
VALUE	Service-Type			Administrative-User	6
VALUE	Service-Type			NAS-Prompt-User		7
VALUE	Service-Type			Authenticate-Only	8
VALUE	Service-Type			Callback-NAS-Prompt	9
VALUE	Service-Type			Call-Check		10
VALUE	Service-Type			Callback-Administrative	11

#	Framed Protocols

VALUE	Framed-Protocol			PPP			1
VALUE	Framed-Protocol			SLIP			2
VALUE	Framed-Protocol			ARAP			3
VALUE	Framed-Protocol			Gandalf-SLML		4
VALUE	Framed-Protocol			Xylogics-IPX-SLIP	5
VALUE	Framed-Protocol			X.75-Synchronous	6

#	Framed Routing Values

VALUE	Framed-Routing			None			0
VALUE	Framed-Routing			Broadcast		1
VALUE	Framed-Routing			Listen			2
VALUE	Framed-Routing			Broadcast-Listen	3

#	Framed Compression Types

VALUE	Framed-Compression		None			0
VALUE	Framed-Compression		Van-Jacobson-TCP-IP	1
VALUE	Framed-Compression		IPX-Header-Compression	2
VALUE	Framed-Compression		Stac-LZS		3

#	Login Services

VALUE	Login-Service			Telnet			0
VALUE	Login-Service			Rlogin			1
VALUE	Login-Service			TCP-Clear		2
VALUE	Login-Service			PortMaster		3
VALUE	Login-Service			LAT			4
VALUE	Login-Service			X25-PAD			5
VALUE	Login-Service			X25-T3POS		6
VALUE	Login-Service			TCP-Clear-Quiet		8

#	Login-TCP-Port		(see /etc/services for more examples)

VALUE	Login-TCP-Port			Telnet			23
VALUE	Login-TCP-Port			Rlogin			513
VALUE	Login-TCP-Port			Rsh			514

#	Termination Options

VALUE	Termination-Action		Default			0
VALUE	Termination-Action		RADIUS-Request		1

#	NAS Port Types

VALUE	NAS-Port-Type			Async			0
VALUE	NAS-Port-Type			Sync			1
VALUE	NAS-Port-Type			ISDN			2
VALUE	NAS-Port-Type			ISDN-V120		3
VALUE	NAS-Port-Type			ISDN-V110		4
VALUE	NAS-Port-Type			Virtual			5
VALUE	NAS-Port-Type			PIAFS			6
VALUE	NAS-Port-Type			HDLC-Clear-Channel	7
VALUE	NAS-Port-Type			X.25			8
VALUE	NAS-Port-Type			X.75			9
VALUE	NAS-Port-Type			G.3-Fax			10
VALUE	NAS-Port-Type			SDSL			11
VALUE	NAS-Port-Type			ADSL-CAP		12
VALUE	NAS-Port-Type			ADSL-DMT		13
VALUE	NAS-Port-Type			IDSL			14
VALUE	NAS-Port-Type			Ethernet		15
VALUE	NAS-Port-Type			xDSL			16
VALUE	NAS-Port-Type			Cable			17
VALUE	NAS-Port-Type			Wireless-Other		18
VALUE	NAS-Port-Type			Wireless-802.11		19

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值