#!/usr/bin/env python import os import shutil import re import tempfile import copy from n4d.server.core import Core from jinja2 import Environment from jinja2.loaders import FileSystemLoader import n4d.responses class FreeRadiusManager: def __init__(self): self.radius_path="/etc/freeradius/3.0/" self.templates_path="/usr/share/n4d/templates/n4d-freeradius/" self.groups_file=self.radius_path + "/mods-config/files/authorize.lliurex_groups" self.groups={} self.diversions={} self.variable_list=["LDAP_BASE_DN","INTERNAL_NETWORK","INTERNAL_MASK"] self.diversions["/etc/freeradius/3.0/clients.conf"]="/etc/freeradius/3.0/clients.conf.diverted" #self.diversions["/etc/freeradius/3.0/eap.conf"]="/etc/freeradius/3.0/eap.conf.diverted" self.diversions["/etc/freeradius/3.0/mods-config/files/authorize"]="/etc/freeradius/3.0/mods-config/files/authorize.diverted" self.diversions["/etc/freeradius/3.0/radiusd.conf"]="/etc/freeradius/3.0/radiusd.conf.diverted" self.diversions["/etc/freeradius/3.0/mods-available/ldap"]="/etc/freeradius/3.0/mods-available/ldap.diverted" self.diversions["/etc/freeradius/3.0/mods-available/mschap"]="/etc/freeradius/3.0/mods-available/mschap.diverted" self.diversions["/etc/freeradius/3.0/sites-available/default"]="/etc/freeradius/3.0/sites-available/default.diverted" self.diversions["/etc/freeradius/3.0/sites-available/inner-tunnel"]="/etc/freeradius/3.0/sites-available/inner-tunnel.diverted" self.n4d = Core.get_core() #def init def startup(self,options): self.variable=copy.deepcopy(self.n4d.get_variable("FREERADIUS").get('return')) if self.variable==None: self.variable={} self.variable["configured"]=False self.variable["groups_filter"]={} self.variable["groups_filter"]["enabled"]=False self.variable["groups_filter"]["groups"]={} self.variable["groups_filter"]["default_auth"]=None try: self.n4d.set_variable("FREERADIUS",copy.deepcopy(self.variable),{'description':"Freeradius service variable",'package':"n4d-freeradius"}) except: # If this fails, something is horribly wrong and we have bigger problems than this variable not being saved # Hopefully we'll be able to do it later, anyway pass if self.variable["groups_filter"]["enabled"]: groups=self.parse_groups_file() if groups != self.variable["groups_filter"]["groups"]: self.variable["groups_filter"]["groups"]=groups self.save_variable() #def startup def is_configured(self): return n4d.responses.build_successful_call_response(self.variable["configured"]) #def is_configured def parse_groups_file(self): group_pattern='^DEFAULT\s+Ldap-Group\s*==\s*"(\w+)"\s*(,\s*Auth-Type\s*:=\s*\w+\s*)?$' groups={} if os.path.exists(self.groups_file): f=open(self.groups_file) for line in f.readlines(): ret=re.match(group_pattern,line) if ret!=None: group,auth_type=ret.groups() groups[group]=auth_type f.close() return groups #def parse_groups_file def get_allowed_groups(self): groups=self.parse_groups_file() if len(groups) < 1: try: groups=self.variable["groups_filter"]["groups"] except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) return n4d.responses.build_successful_call_response(list(groups.keys())) #def get_allowed_groups def generate_groups_file(self,groups=None): header = "# FILE GENERATED BY n4d-freeradius PLUGIN\n\n" final_rule="DEFAULT Auth-Type := Reject\n\n" group_skel='DEFAULT Ldap-Group == "%s"' extra_auth=", Auth-Type := EAP" if not groups: try: groups=self.variable["groups_filter"]["groups"] except Exception as e: return False fd,tmpfile=tempfile.mkstemp() f=open(tmpfile,"w") f.write(header) for group in groups: f.write(group_skel%group) if groups[group]!=None: f.write(extra_auth) f.write("\n") f.write("\n") f.write(final_rule) f.close() os.close(fd) shutil.copy(tmpfile,self.groups_file) self.fix_perms(self.groups_file) return True #def generate_groups_file def clean_groups_file(self): f=open(self.groups_file,"w") f.write("# FILE GENERATED BY n4d-freeradius PLUGIN\n\n") f.close() #def clean_groups_file def set_filter_default_auth(self,auth_type=None): try: if not self.variable["groups_filter"]["enabled"]: return n4d.responses.build_successful_call_response(ret_value=False,ret_msg="Filter not enabled") # return {"msg":False,"status":"Filter not enabled"} except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) try: self.variable["groups_filter"]["default_auth"]=auth_type for group in self.variable["groups_filter"]["groups"]: self.variable["groups_filter"]["groups"][group]=auth_type except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) self.generate_groups_file() self.restart_service() self.save_variable() return n4d.responses.build_successful_call_response(ret_value=True,ret_msg="auth configured") # return {"status":True,"msg":"auth configured"} #def set_filter_default_auth def add_group_to_filter(self,group,extra_auth=None): try: if not self.variable["groups_filter"]["enabled"]: return n4d.responses.build_successful_call_response(ret_value=False,ret_msg="Filter not enabled") # return {"status":False,"msg":"Filter not enabled"} if extra_auth==None: extra_auth=self.variable["groups_filter"]["default_auth"] except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) groups=self.parse_groups_file() if group not in groups or groups[group]!=extra_auth: self.variable["groups_filter"]["groups"][group]=extra_auth self.generate_groups_file() self.save_variable() self.restart_service() return n4d.responses.build_successful_call_response(ret_value=True,ret_msg="Group added") # return {"status":True,"msg":"Group added"} #def add_group_to_filter def remove_group_from_filter(self,group): try: if not self.variable["groups_filter"]["enabled"]: return n4d.responses.build_successful_call_response(ret_value=False,ret_msg="Filter not enabled") except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) groups=self.parse_groups_file() if group in groups: groups.pop(group) self.variable["groups_filter"]["groups"]=groups self.generate_groups_file() self.restart_service() self.save_variable() return n4d.responses.build_successful_call_response(ret_value=True,ret_msg="Group removed") # return {"status":True, "msg": "Group removed"} #def remove_group_from_filter def enable_group_filtering(self): self.variable["groups_filter"]["enabled"]=True self.generate_groups_file() self.save_variable() self.restart_service() return n4d.responses.build_successful_call_response(True) #return {"status":True,"msg":""} #def enable_filtering def disable_group_filtering(self): # get groups currently written in case user did it manually groups=self.parse_groups_file() # save groups in local variable only if it has content # rely on variable groups value if it doesn't try: if len(groups)>0: self.variable["groups_filter"]["groups"]=groups self.clean_groups_file() self.variable["groups_filter"]["enabled"]=False except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) self.save_variable() self.restart_service() return n4d.responses.build_successful_call_response(True) #def empty_groups_file def save_variable(self): self.n4d.set_variable("FREERADIUS",copy.deepcopy(self.variable),{'description':"Freeradius service variable",'package':"n4d-freeradius"}) #def save_variable def restart_service(self): os.system("systemctl restart freeradius") #def restart_service def render_templates(self,server,radius_secret,ldap_user,ldap_pwd,router_ip): vars=self.n4d.get_variable_list(self.variable_list).get('return') vars["RADIUS_SECRET"]=radius_secret vars["LDAP_USER"]=ldap_user vars["LDAP_PASSWORD"]=ldap_pwd vars["SERVER"]=server vars["ROUTER_IP"]=router_ip env = Environment(loader=FileSystemLoader(self.templates_path)) template=env.get_template("clients.conf") str_template=template.render(vars) clients_str=str_template f=open(self.templates_path+"mods-available/ldap") lines=f.readlines() f.close() str_template="" for line in lines: if "%%LDAP_USER%%" in line: line=line.replace("%%LDAP_USER%%",vars["LDAP_USER"]) if "%%LDAP_PASSWORD%%" in line: line=line.replace("%%LDAP_PASSWORD%%",vars["LDAP_PASSWORD"]) if "%%LDAP_BASE_DN%%" in line: line=line.replace("%%LDAP_BASE_DN%%",vars["LDAP_BASE_DN"]) if "%%SERVER%%" in line: line=line.replace("%%SERVER%%",vars["SERVER"]) str_template+=line ldap_str=str_template return (clients_str,ldap_str) #def render_template def install_conf_files(self,server,radius_secret,ldap_user,ldap_pwd,router_ip): try: clients_str,ldap_str=self.render_templates(server,radius_secret,ldap_user,ldap_pwd,router_ip) if not os.path.exists(self.radius_path): os.makedirs(self.radius_path) # clients.conf f=open(self.radius_path+"clients.conf.lliurex","w") f.write(clients_str) f.close() self.fix_perms(self.radius_path+"clients.conf.lliurex") # modules/ldap if not os.path.exists(self.radius_path+"mods-available"): os.makedirs(self.radius_path+"mods-available/") f=open(self.radius_path+"mods-available/ldap.lliurex","w") f.write(ldap_str) f.close() self.fix_perms(self.radius_path+"/mods-available/ldap.lliurex") if not os.path.exists(self.radius_path+"mods-enabled/ldap"): os.symlink(self.radius_path+"mods-available/ldap",self.radius_path+"mods-enabled/ldap") # default if not os.path.exists(self.radius_path+"sites-available"): os.makedirs(self.radius_path+"sites-available") shutil.copy(self.templates_path+"sites-available/default",self.radius_path+"sites-available/default.lliurex") self.fix_perms(self.radius_path+"sites-available/default.lliurex") # inner-tunnel shutil.copy(self.templates_path+"sites-available/inner-tunnel",self.radius_path+"sites-available/inner-tunnel.lliurex") self.fix_perms(self.radius_path+"sites-available/inner-tunnel.lliurex") # radiusd.conf shutil.copy(self.templates_path+"radiusd.conf",self.radius_path+"radiusd.conf.lliurex") self.fix_perms(self.radius_path+"radiusd.conf.lliurex") # eap.conf #shutil.copy(self.templates_path+"eap.conf",self.radius_path+"eap.conf.lliurex") #self.fix_perms(self.radius_path+"eap.conf.lliurex") # authorize shutil.copy(self.templates_path+"/mods-config/files/authorize",self.radius_path+"/mods-config/files/authorize.lliurex") self.fix_perms(self.radius_path+"/mods-config/files/authorize.lliurex") # user.lliurex_groups shutil.copy(self.templates_path+"/mods-config/files/authorize.lliurex_groups",self.radius_path+"/mods-config/files") self.fix_perms(self.radius_path+"/mods-config/files/authorize.lliurex_groups") # modules/mschap shutil.copy(self.templates_path+"/mods-available/mschap",self.radius_path+"mods-available/mschap.lliurex") self.fix_perms(self.radius_path+"/mods-available/mschap.lliurex") if not os.path.exists(self.radius_path+"mods-enabled/mschap"): os.symlink(self.radius_path+"mods-available/mschap",self.radius_path+"mods-enabled/mschap") self.enable_diversions() os.system("systemctl restart freeradius") self.variable["groups_filter"]["enabled"]=False self.variable["configured"]=True self.enable_systemd() self.save_variable() return n4d.responses.build_successful_call_response(True) # return {"status":True,"msg":str(True)} except Exception as e: return n4d.responses.build_failed_call_response(ret_msg=str(e)) # return {"status":False,"msg":str(e)} #def install_conf_files def enable_systemd(self): os.system("systemctl enable freeradius") def fix_perms(self,f): os.system("chown freerad:freerad %s"%f) os.system("chmod 640 %s"%f) #def fix_perms def enable_diversions(self): for original_file in self.diversions: lliurex_file=original_file+".lliurex" if not os.path.exists(self.diversions[original_file]): command="dpkg-divert --add --package n4d-freeradius --rename --divert '%s' '%s'"%(self.diversions[original_file],original_file) os.system(command) if not os.path.islink(original_file): os.symlink(lliurex_file, original_file) #def enable_diversions def disable_diversions(self): pass #def disable_diversions #class RadiusManager if __name__=="__main__": r=RadiusManager() r.install_conf_files("server","myradius1","cn=roadmin...","2","")