# Initialise the reply list RAD-Code = Access-Reject, RAD-Identifier = RAD-Identifier, RAD-Authenticator = "" . RAD-Authenticator, # we need a copy moveall Proxy-State, # Look up secret and other attributes associated with the client Sql(str = "select attribute, value from data where space=? and name=?", str = "str,IP-Source", # attrs used for place holders str = "clients"), delall str, delall REP:int, REP:Secret or ( Log-Line = "Request received on " . IP-Dest . ":" . UDP-Dest . " from unknown client " . IP-Source . " identified as NAS " . (NAS-Identifier or NAS-IP-Address) . " for user " . User-Name, # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "IP-Source,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, abort ), # Create the start of a log line. We add to it as we proceed below. # The response type and RADIUS code are added by the server. Log-Line = "from " . IP-Dest . ":" . UDP-Dest . " for request from NAS " . (NAS-Identifier or NAS-IP-Address) . (NAS-Port exists and (" port " . NAS-Port)) . " via " . IP-Source . " for " . User-Name . (Calling-Station-Id and (" CLI " . Calling-Station-Id)), # If we see a Message-Authenticator, use it to verify the request. # Note: keeping the (dummy) Message-Authenticator on the reply list will # make the server automatically sign the encoded response packet as well. Message-Authenticator and ( REP:Message-Authenticator = Message-Authenticator . "", # get a copy on REP REQ:Message-Authenticator pokedwith "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", REP:Message-Authenticator == RAD-Packet hmacmd5 REP:Secret or ( Log-Line := REP:Log-Line . " - Message authenticator mismatch!", abort ), Log-Line := REP:Log-Line . " [M-A OK]", del str ), # Add data from database: A/V pairs that would be sent in access accepts # as well as data we need to do authentication and to make other decisions. # Uncomment if you want a global default set of values # #Sql(str = "select attribute, value from data where space=? and name=?", # str = "str,str", # str = "defaults", # str = "defaults"), delall str, del REP:int, # Uncomment if you want per-nas values # #Sql(str = "select attribute, value from data where space=? and name=?", # str = "str,str", # str = "nases", # str = (NAS-Identifier or NAS-IP-Address)), delall str, del REP:int, # Uncomment if you want per-realm values, incl. the local-realm and # strip-realm options; the Target-Server attribute also generally # comes from here. # #str = "select attribute, value from data where space=? and name=?", #str = "str,str", #str = "realms", #str = (User-Name beforefirst "/" or User-Name afterlast "@"), #REQ:str and ( # references last str line above # Sql 0, del REP:int, # local-realm and REQ:User-Name := (User-Name afterfirst "/" or # User-Name beforelast "@" or # User-Name) #), #delall str, # Uncomment if you want per-user values # Sql(str = "select attribute, value from data where space=? and name=?", str = "str,User-Name", str = "users"), delall str, del REP:int, # uncomment this if you want data from client groups, based on the # distinct values of the groupname column for this client. MySQL doesn't have # subqueries or CONNECT BY clauses, so we need to do it this way. # #Sql(str = "select distinct g.attribute,g.value " . # " from data c,data g" . # " where c.space='clients' and c.name=? " . # " and g.space='groups' and g.name=c.groupname " . # "order by g.id", # str = "IP-Source"), #delall str, delall REP:int, # We now take one of two separate paths that later join again, # depending on whether we received an accounting request or not. RAD-Code != Accounting-Request and ( # This is for authentication. Decrypt PAP password, if any; # set CHAP-Challenge by copying it from the request authenticator # if we're doing CHAP and it wasn't already there User-Password and ( REQ:User-Password := (User-Password papdecrypt (REP:Secret . RAD-Authenticator) . "\x00") beforefirst "\x00", # Uncomment if you want to log passwords # #Log-Line := REP:Log-Line . " [" . User-Password . "]", 0), CHAP-Password and ( CHAP-Challenge or (REQ:CHAP-Challenge = RAD-Authenticator), # Uncomment if you want to log passwords # #Log-Line := REP:Log-Line . " response [" . hex CHAP-Password . # "] to challenge [" . hex CHAP-Challenge . "]", 0), # Perform database-defined checks based on username on the data we have now. # Uncomment the CheckSql interface in the configuration file as well if you # need this. # #CheckSql(str = "select attribute, op, value from radcheck " . # "where space=? and name=?", # str = "str,User-Name", # str = "users"), delall str, int or reject, # If the data we obtained has auth-type set to Reject or Accept, act now auth-type == Reject and reject, auth-type == Accept and accept, # See if the database gave us a cleartext password; if so, we can do either # PAP or CHAP. clear-password exists and ( # See if we're doing PAP User-Password exists and ( # PAP: Compare and we're done User-Password == clear-password and ( # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - matches [" . clear-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, accept ), # PAP failed # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - does not match [" . clear-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, reject ), # Not doing PAP. See if we're doing CHAP CHAP-Password exists and ( # CHAP: compare given hash to calculated hash (requires cleartext) 16 lastof CHAP-Password == md5 (1 firstof CHAP-Password . clear-password . CHAP-Challenge) and ( # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - response [" . hex CHAP-Password . # "] to challenge [" . hex CHAP-Challenge . # "] matches [" . clear-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, accept ), # CHAP failed # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - response [" . hex CHAP-Password . # "] to challenge [" . hex CHAP-Challenge . # "] does not match [" . clear-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, reject ), # Not doing PAP or CHAP. For now, that means we reject, but you can # add other authentication schemes that require a cleartext password here. # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . # " - no supported credentials received to match [" . # clear-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, reject ), # We don't have a cleartext from the database. See if we do have a MD5-Hex # style hashed password, then we can do PAP. REP:md5-hex-password and User-Password exists and ( # Hash decrypted user-Password with salt and compare to stored hash 32 lastof REP:md5-hex-password == hex md5 (User-Password . 4 firstof REP:md5-hex-password) and ( # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - matches MD5 password [" . # REP:md5-hex-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, accept ), # PAP using stored md5-hex password failed; reject. # Uncomment if you want to log passwords #Log-Line := REP:Log-Line . " - does not match MD5 password [" . # REP:md5-hex-password . "]", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, reject ), # No cleartext and no md5hex password from database. # You can add other authentication schemes that do not require such # passwords here. # If we can't authenticate locally, we fall through to proxying 1) or ( # Handle accounting. First verify the request authenticator. # We log (in the error log) and respond to, but don't process # or proxy incorrectly signed requests. REQ:RAD-Authenticator pokedwith "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", REP:RAD-Authenticator == md5 (RAD-Packet . REP:Secret) or ( Log-Line := REP:Log-Line . " - signature MISMATCH - record ignored", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, acctresp # it's not transient, so we don't want retransmits ), REQ:Acct-Authenticator = Verified, # Create unique database key based on NAS, timestamp and session ID. # No single NAS will reuse the same session ID in the same second. # Useful if your database doesn't automatically assign sequence numbers. # #REQ:Record-Unique-Key = hex (NAS-IP-Address toraw . Timestamp toraw) . # Acct-Session-Id, # Do the transaction. Note: if you use multiple prog= lines for # connection pooling and/or load sharing in the AcctSql interface, # you *must* use the pidattr feature as well, because the next group # of statements must be routed through the same connection. AcctSql(str = "lock table accounting write"), int or abort, del str, del REP:int, AcctSql(str = "select count(acct_id) as 'int' from accounting " . " where acct_nas = ? " . " and acct_status_type = ? " . " and acct_session_id = ? " . " and unix_timestamp(acct_timestamp)+120 >= unix_timestamp()", str = "str,Acct-Status-Type,Acct-Session-Id", str = (NAS-Identifier or NAS-IP-Address)), delall str, del REP:int, # If it's a duplicate, release locks and respond right now; # we don't even want to proxy duplicate requests as we generate our own # retransmissions (to multiple target servers if needed). int and ( AcctSql(str = "unlock tables"), Log-Line := REP:Log-Line . " - duplicate record ignored", acctresp ), # It's not a duplicate. Store the accounting record. AcctSql(str = "insert into accounting (acct_nas, acct_status_type, acct_session_id, acct_timestamp, user_name, nas_ip_address, nas_port, service_type, framed_protocol, framed_ip_address, framed_ip_netmask, login_ip_host, login_service, login_tcp_port, class, called_station_id, calling_station_id, nas_identifier, nas_port_type, port_limit, acct_input_octets, acct_output_octets, acct_session_time, acct_input_packets, acct_output_packets, acct_terminate_cause, acct_multi_session_id, acct_link_count) values (?, ?, ?, now(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", str = "str,Acct-Status-Type,Acct-Session-Id,User-Name,NAS-IP-Address,NAS-Port,Service-Type,Framed-Protocol,Framed-IP-Address,Framed-IP-Netmask,Login-IP-Host,Login-Service,Login-TCP-Port,Class,Called-Station-Id,Calling-Station-Id,NAS-Identifier,NAS-Port-Type,Port-Limit,Acct-Input-Octets,Acct-Output-Octets,Acct-Session-Time,Acct-Input-Packets,Acct-Output-Packets,Acct-Terminate-Cause,Acct-Multi-Session-Id,Acct-Link-Count", str = (NAS-Identifier or NAS-IP-Address)), int or (AcctSql(str = "unlock tables"), abort), delall str, delall REP:int, # (Add, update or delete record in sessions (resources) table here) # Commit transaction; drop request (causing retransmission) if we could not. AcctSql(str = "unlock tables"), int or abort, del str, del REP:int ), # If we weren't able yet to answer an authentication request, and for # all accounting requests, we continue here. # # If somehow we obtained one or more Target-Server attributes, proxy. REP:Target-Server exists and ( # for such things you'd want negative ACLs, but we don't have them yet. delall REQ:RAD-Identifier, delall REQ:RAD-Length, delall REQ:RAD-Authenticator, delall REQ:RAD-Attributes, # strip realm before proxying if asked to strip-realm and REQ:User-Name := (User-Name afterfirst "/" or User-Name beforelast "@" or User-Name), # proxy; drop request if radclient gave us a real error Radiusclient(moveall REP:Target-Server), int < 64 or abort, del REP:int, # for such things you'd want negative ACLs, but we don't have them yet. del F:RAD-Code, # delete our own default, keep value added by proxy del RAD-Identifier, # keep our own, delete values added by proxy del RAD-Length, del RAD-Authenticator, del RAD-Attributes, # Reply if the response was OK, stripping inappropriate attributes for # accounting requests or access rejects according to our own dictionary RAD-Code == Accounting-Request and REP:RAD-Code == Accounting-Response and acctresp, RAD-Code == Access-Request and (REP:RAD-Code == Access-Accept and accept, REP:RAD-Code == Access-Reject and reject) ), # Are you still here? We really don't know what to do, # but these should be some sensible default actions for # access requests and accounting requests. RAD-Code == Accounting-Request and acctresp, RAD-Code == Access-Request and ( # Uncomment if you want to log passwords as well #Log-Line := REP:Log-Line . " - no data available to authenticate user", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, reject ), # If we received a request with an unknown value in the Code field, drop packet Log-Line := REP:Log-Line . " - unknown request type ", # Uncomment if you want to log to SQL as well #Sql(str = "insert into log (log_when, log_who, log_what) " . # "values (now(), ?, ?)", # str = "User-Name,str", # str = REP:Log-Line), delall str, del REP:int, # # Uncomment if you want to log to SQL only, not to stderr/file or syslog #del Log-Line, abort # vim:sw=2:softtabstop=2