需要将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