# We check for this prefix to determine whether the class
# value was generated by this server.  It should be changed
# so that it is globally unique.
class_value_prefix = 'ai:'

#
#	Replacement for the old rlm_acct_unique module
#
acct_unique {
	#
	#  If we have a class attribute in the format
	#  'auth_id:[0-9a-f]{32}' it'll have a local value
	#  (defined by insert_acct_class), this ensures
	#  uniqueness and suitability.
	#
	#  We could just use the Class attribute as
	#  Acct-Unique-Session-Id, but this may cause problems
	#  with NAS that carry Class values across between
	#  multiple linked sessions.  So we rehash class with
	#  Acct-Session-ID to provide a truely unique session
	#  identifier.
	#
	#  Using a Class/Session-ID combination is more robust
	#  than using elements in the Accounting-Request,
	#  which may be subject to change, such as
	#  NAS-IP-Address, Client-IP-Address and
	#  NAS-Port-ID/NAS-Port.
	#
	#  This policy should ensure that session data is not
	#  affected if NAS IP addresses change, or the client
	#  roams to a different 'port' whilst maintaining its
	#  initial authentication session (Common in a
	#  wireless environment).
	#
	update request {
	       &Tmp-String-9 := "${policy.class_value_prefix}"
	}

	if (("%{hex:&Class}" =~ /^%{hex:&Tmp-String-9}/) && \
	    ("%{string:&Class}" =~ /^${policy.class_value_prefix}([0-9a-f]{32})/i)) {
		update request {
			&Acct-Unique-Session-Id := "%{md5:%{1},%{Acct-Session-ID}}"
		}
	}

	#
	#  Not All devices respect RFC 2865 when dealing with
	#  the class attribute, so be prepared to use the
	#  older style of hashing scheme if a class attribute
	#  is not included
	#
	else {
		update request {
			&Acct-Unique-Session-Id := "%{md5:%{User-Name},%{Acct-Session-ID},%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port}}"
		 }
	}
}

#
#	Insert a (hopefully unique) value into class
#
insert_acct_class {
	update reply {
		&Class = "${policy.class_value_prefix}%{md5:%t,%I,%{Packet-Src-Port},%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}},%{NAS-IP-Address},%{Calling-Station-ID},%{User-Name}}"
	}
}

#
#	Merges Acct-[Input|Output]-Octets and Acct-[Input|Output]-Gigawords into Acct-[Input|Output]-Octets64
#
#	If the &Attr-Foo doesn't exist, it's value is taken as zero.
#
acct_counters64.preacct {
	update request {
		&Acct-Input-Octets64 = "%{expr:(&Acct-Input-Gigawords << 32) | &Acct-Input-Octets}"
		&Acct-Output-Octets64 = "%{expr:(&Acct-Output-Gigawords << 32) | &Acct-Output-Octets}"
	}
}

#
#  There is a delay between sending the Access-Accept and receiving
#  the corresponding Accounting-Request "start" packet.  This delay
#  can be leveraged by a user to bypass Simultaneous-Use checks.
#
#  The user can start up multiple sessions at the same time.  When
#  that happens, both Simultaneous-Use checks are performed before any
#  Accounting-Request packet is received.  Both Simultaneous-Use
#  checks will result in "no user session" in the radacct table, and
#  both sessions will be allowed.  At some point later in time, the
#  Accounting-Request packets are received.  But by then it's too
#  late.
#
#  The solution is to insert a temporary session into the "radacct"
#  table, during the "post-auth" section.  This is done by
#  uncommenting the "sql_session_start" entry in
#  sites-enabled/default.  Then, reading
#  raddb/mods-config/sql/main/*/queries.conf, and looking for the
#  "sql_session_start" comments.  Follow the instructions there to
#  finalize the configuration.
#
#  The server will then create a temporary entry in "radacct" before
#  it returns the Access-Request.  Any other Access-Request which is
#  received at the same time will then have it's Simultaneous-Use
#  check see that entry, and will be rejected.
#
#  Subsequent Accounting-Request packets for the first session will
#  then UPDATE (not INSERT) the data for the session.
#
#  There is still a small race condition as the Simultaneous-Use
#  checks are not done at the same time as updating radacct.  But the
#  window of opportunity is much smaller.  i.e. milliseconds, instead
#  of seconds.
#
sql_session_start.post-auth {
	acct_unique
	sql.accounting
}
