#!/usr/bin/python # -*- coding: utf-8 -*- # radius.py: independent worker process as a radius server # MikroWizard.com , Mikrotik router management solution # Author: sepehr.ha@gmail.com from libs.db.db_device import Devices,EXCLUDED,database from libs.db import db_sysconfig import logging import time import asyncio import logging import traceback from pyrad.dictionary import Dictionary from pyrad.server_async import ServerAsync from pyrad.packet import AccessAccept,AccessReject from pyrad.server import RemoteHost from libs.mschap3 import mschap,mppe from libs.db import db,db_user_group_perm,db_device,db_groups,db_device,db_AA,db_sysconfig from libs.util import FourcePermToRouter try: import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) except: pass log = logging.getLogger("Radius") logging.basicConfig(filename="pyrad.log", level="DEBUG", format="%(asctime)s [%(levelname)-8s] %(message)s") class RadServer(ServerAsync): def __init__(self, loop, dictionary): ServerAsync.__init__(self, loop=loop, dictionary=dictionary, debug=True) def verifyMsChapV2(self,pkt,userpwd,group,nthash): ms_chap_response = pkt['MS-CHAP2-Response'][0] authenticator_challenge = pkt['MS-CHAP-Challenge'][0] if len(ms_chap_response)!=50: raise Exception("Invalid MSCHAPV2-Response attribute length") nt_response = ms_chap_response[26:50] peer_challenge = ms_chap_response[2:18] _user_name = pkt.get(1)[0] nt_resp = mschap.generate_nt_response_mschap2( authenticator_challenge, peer_challenge, _user_name, userpwd, nthash ) if nt_resp == nt_response: auth_resp = mschap.generate_authenticator_response( userpwd, nt_response, peer_challenge, authenticator_challenge, _user_name, nthash ) mppeSendKey, mppeRecvKey = mppe.mppe_chap2_gen_keys(userpwd, nt_response,nthash) if group: reply = self.CreateReplyPacket(pkt, **{ "MS-CHAP2-Success": auth_resp.encode(), "Mikrotik-Group": group, }) else: reply = self.CreateReplyPacket(pkt, **{ "MS-CHAP2-Success": auth_resp.encode(), }) reply.code = AccessAccept return reply else: return False def send_auth_reject(self,protocol,pkt,addr): reply = self.CreateReplyPacket(pkt, **{ }) reply.code = AccessReject reply.error_msg = "User password wrong" #log failed attempts protocol.send_response(reply, addr) def handle_auth_packet(self, protocol, pkt, addr): # log.error("Attributes: ") # for attr in pkt.keys(): # log.error("%s: %s" % (attr, pkt[attr])) try: tz=int(time.time()) username = pkt['User-Name'][0] userip=pkt['Calling-Station-Id'][0] devip=pkt['NAS-IP-Address'][0] dev=db_device.query_device_by_ip(devip) if not dev: self.send_auth_reject(protocol,pkt,addr) return u = db.get_user_by_username(username) if not u: self.send_auth_reject(protocol,pkt,addr) db_AA.Auth.add_log(dev.id, 'failed', username , userip , by=None,sessionid=None,timestamp=tz,message="User Not Exist") return else: #get user permision related to device if not dev: self.send_auth_reject(protocol, pkt, addr) db_AA.Auth.add_log(dev.id, 'failed', username, userip, by=None, sessionid=None, timestamp=tz, message="Device Not Exist") return force_perms=True if db_sysconfig.get_sysconfig('force_perms')=="True" else False if force_perms: dev_groups=db_groups.devgroups(dev.id) dev_groups_ids=[group.id for group in dev_groups] dev_groups_ids.append(1) res=False if dev and len(dev_groups_ids)>0: perm=db_user_group_perm.DevUserGroupPermRel.query_permission_by_user_and_device_group(u.id,dev_groups_ids) res2=False if len(list(perm))>0: res2=FourcePermToRouter(dev,perm) if not res2: self.send_auth_reject(protocol,pkt,addr) db_AA.Auth.add_log(dev.id, 'failed', username , userip , by=None,sessionid=None,timestamp=tz,message="Unable to verify group") return nthash=u.hash if force_perms: reply=self.verifyMsChapV2(pkt,"password",perm[0].perm_id.name,nthash) else: reply=self.verifyMsChapV2(pkt,"password",False,nthash) if reply: protocol.send_response(reply, addr) return db_AA.Auth.add_log(dev.id, 'failed', username , userip , by=None,sessionid=None,timestamp=tz,message="Wrong Password") self.send_auth_reject(protocol,pkt,addr) except Exception as e: print(e) self.send_auth_reject(protocol,pkt,addr) #log failed attempts def handle_acct_packet(self, protocol, pkt, addr): try: ts = int(time.time()) dev_ip=pkt['NAS-IP-Address'][0] dev=db_device.query_device_by_ip(dev_ip) type=pkt['Acct-Status-Type'][0] user=pkt['User-Name'][0] userip=pkt['Calling-Station-Id'][0] sessionid=pkt['Acct-Session-Id'][0] if type == 'Start': db_AA.Auth.add_log(dev.id, 'loggedin', user , userip , None,timestamp=ts,sessionid=sessionid) elif type == 'Stop': db_AA.Auth.add_log(dev.id, 'loggedout', user , userip , None,timestamp=ts,sessionid=sessionid) except Exception as e: log.error("Error in accounting: ") log.error(e) log.error("Received an accounting request") log.error("Attributes: ") log.error(pkt.keys()) # for attr in pkt.keys(): # log.error("%s: %s" % (attr, pkt[attr])) reply = self.CreateReplyPacket(pkt) protocol.send_response(reply, addr) def handle_coa_packet(self, protocol, pkt, addr): log.error("Received an coa request") log.error("Attributes: ") for attr in pkt.keys(): log.error("%s: %s" % (attr, pkt[attr])) reply = self.CreateReplyPacket(pkt) protocol.send_response(reply, addr) def handle_disconnect_packet(self, protocol, pkt, addr): log.error("Received an disconnect request") log.error("Attributes: ") for attr in pkt.keys(): log.error("%s: %s" % (attr, pkt[attr])) reply = self.CreateReplyPacket(pkt) # COA NAK reply.code = 45 protocol.send_response(reply, addr) def main(): # create server and read dictionary loop = asyncio.get_event_loop() server = RadServer(loop=loop, dictionary=Dictionary('py/libs/raddic/dictionary')) secret = db_sysconfig.get_sysconfig('rad_secret') server.hosts["0.0.0.0"] = RemoteHost("0.0.0.0", secret.encode(), "localhost") try: # Initialize transports loop.run_until_complete( asyncio.ensure_future( server.initialize_transports(enable_auth=True, enable_acct=True, enable_coa=False, addresses=['0.0.0.0']))) try: # start server loop.run_forever() except KeyboardInterrupt as k: pass # Close transports loop.run_until_complete(asyncio.ensure_future( server.deinitialize_transports())) except Exception as exc: log.error('Error: ', exc) log.error('\n'.join(traceback.format_exc().splitlines())) # Close transports loop.run_until_complete(asyncio.ensure_future( server.deinitialize_transports())) loop.close() if __name__ == '__main__': main()