###########################################################################
#
# BEHAVIOUR - Expression that defines the server's operating rules
#
# This is compiled at startup and ran for every request that comes in.
# Upon entry, the REQUEST list of A/V pairs is already populated with
# information from and about the request. Upon exit, the REPLY list is
# used to build a response to send to the client.
#
# Other than the attributes / fixed fields you want to send, you need
# to set the first instance of the attribute 'Secret' (see subdicts/dict.
# internal) to the shared secret to be used for signing the response.
# You also need to set the first instance of the RAD-Authenticator
# attribute to the value that this attribute had in the original request; 
# i.e. copying the attribute from the REQUEST list to the REPLY list.
# The same goes for the RAD-Identifier attribute and all instances of
# the Proxy-State attribute. See RFC 2865 why.
#
# Then, when the expression completes (that is without being aborted),
# the server will first build the packet only based on the attributes
# on the REPLY list, so at first also putting in the original request
# authenticator as the response authenticator. It will then sign the
# packet using the shared secret provided on the reply list, putting the
# signature over the original response authenticator, to create a valid
# RADIUS response.
#
# See openradius-language.html for a list neatly showing all operators, 
# with contexts, precedence, association and auto-conversion properties.  
#
# The && and || operators do short-circuit boolean evaluation as they do
# in C, Perl and shell scripts - that's how conditional subexpressions
# are implemented.
#
# This language has hardly any syntax, other than that a term may be
# not be directly followed by another term. 
#
##
#
# This is a simpler example expression file that goes together with the 
# distributed 'configuration' file. It allows RADIUS clients to be listed 
# in an ASCII file .../raddb/legacy/clients, and uses the standard Unix
# password database.
#
# (paths are dependent on what's specified in the configuration file).
#
# More specifically, this file provides the following rules:
#
# 1.  look up the secret for the radius client by IP address, using the
#     Oldclients interface that uses the ascfile module to return
#     information from a flat file that lists the IP addresses and their 
#     secrets next to each other, normally .../raddb/legacy/clients;
#
# 2.  if not found, log the event using the Errorlogger interface that
#     uses the radlogger shell script module to log the message, normally
#     in /var/log/raderr.log and drops the request. Otherwise continue:
#
# 3.  initialise the reply list by copying the Identifier, Authenticator 
#     and all Proxy-State attributes from the request;
#
# 4.  see if we're accounting. If so, verify the request authenticator
#     and put the result in Acct-Authentic, log the information using
#     the Acctlogger interface that uses the radlogger module. If that
#     didn't succeed, drop the request; otherwise, set RAD-Code to
#     Accounting-Response and halt the expression so that the client is
#     answered. But if we're authenticating:
#
# 5.  set RAD-Code to Access-Reject and Reply-Message to something nasty,
#     so we can send a valid response that rejects users by simply 
#     halting the expression at any point;
#
# 6.  if a PAP password was supplied in the request, decrypt that
#     using the shared secret that was found for this client.
#     Otherwise we're already done.
#
# 7.  log the request using the Stdlogger interface that uses the
#     radlogger shell script to log the username, password, CHAP
#     challenge and password, NAS IP and port, as per the send ACL
#     defined in the configuration file for the Stdlogger interface;
#
      

###########################################################################
#
# Step 1 & 2: Look up client's secret by packet's source IP address; log
# error and drop request if not found

# Set the first 'str' instance to IP-Source (type conversion is
# automatic), call Oldclients and evaluate the returned 'str' instance
# as a boolean (false if nonexistant or empty).

Oldclients(str=IP-Source), str || (

	Errorlogger(str=Timestamp as "%c" . ": Request from unknown client " . 
			IP-Source . " identified as NAS " . (NAS-Identifier || 
							     NAS-IP-Address) . 
			" for user " . User-Name . " - ignored."),
	abort
), 

# Save returned string attribute in REP:Secret and delete the parameter
# and the returned attributes. Note that the 'del' operator has the
# REQUEST list as default for lowercase attributes because of rule c.

Secret = str, del str, del REP:int, del REP:str,


###########################################################################
#
# Step 3: Initialise reply list

RAD-Identifier = RAD-Identifier, 
RAD-Authenticator = RAD-Authenticator,
RAD-Length = 20,			# Dirty hack. Is overwritten by
					# encode() if there are any real
					# attributes.

RAD-Code = Access-Reject,		# Another dirty hack, but needed
					# now to make sure all RAD-PKT
					# attributes appear in the reply
					# list before the ones from
					# RAD-ATR. buildtree() does not
					# yet sort on encapsulation
					# depth... :(
moveall Proxy-State,


###########################################################################
#
# Step 4: Handle accounting

RAD-Code == Accounting-Request && (

	# Verify request authenticator. Note that the ',1' in the
	# subexpression after the '&&' makes the '||' following it
	# behave like a logical 'else'.

	RAD-Authenticator == md5 (RAD-Code asraw . 
				  RAD-Identifier asraw .
				  RAD-Length asraw . 
				  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" .
				  RAD-Attributes . 
				  REP:Secret) && (

		REQ:Acct-Authentic = Verified, 
	1) || (
		REQ:Acct-Authentic = Mismatch
	),

	# Always log it; drop request if that fails

	Acctlogger(str=Timestamp as "%c",
		   REQ:Record-Unique-Key=NAS-IP-Address asraw ashex .
					 Timestamp asraw ashex .
					 Acct-Session-Id), int || abort,
	del str, del REP:int,
	
	# Set response code and halt the expression

	RAD-Code := Accounting-Response,
	halt
),


###########################################################################
#
# Step 5: Now we're authenticating, set defaults to reject when halted,
# so we can do so at anytime

# RAD-Code = Access-Reject, # Not needed anymore - already done 
Reply-Message = "I don't know you. Go away.\n",


###########################################################################
#
# Step 6: Decrypt PAP password.
# CHAP-Challenge by copying it from the request authenticator if it
# wasn't already present 

User-Password || halt,
REQ:User-Password := User-Password ^ md5 (REP:Secret . 
					  RAD-Authenticator)
		     beforefirst "\x00",

###########################################################################
#
# Step 7: Log the request using the Stdlogger interface

Stdlogger(str=Timestamp as "%c"), 
del str, del REP:int,


###########################################################################
#
# Step 9: Check unix password database; set some standard attrs. if OK.

delall REP:int,				# to be *very* sure
Unixpasswd(str:=User-Name), int && (

	RAD-Code := Access-Accept,
	Reply-Message := "Welcome, " . User-Name . ". You're coming in on NAS ".
			 (NAS-Identifier || NAS-IP-Address) . " on port " .
			 NAS-Port . ".\nStarting PPP; enjoy.\n",
	Service-Type = Framed,
	Framed-Protocol = PPP,
	halt
)
