Commit Diff


commit - da2270739c798f5d01e9d1afe269d4fa1c5000aa
commit + 747da5e94edd8f4f54b21b349193f5796ebff89d
blob - 56a0f7b0aaa3318a7dc630f8c8decc6ecef6f67b
blob + 6513ca5c4b23cdccf90157e8254e6cbe066a1fa6
--- usr.sbin/radiusd/parse.y
+++ usr.sbin/radiusd/parse.y
@@ -1,4 +1,4 @@
-/*	$OpenBSD: parse.y,v 1.19 2024/07/02 00:00:12 yasuoka Exp $	*/
+/*	$OpenBSD: parse.y,v 1.20 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 /*
  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -92,15 +92,15 @@ typedef struct {
 %}
 
 %token	INCLUDE LISTEN ON PORT CLIENT SECRET LOAD MODULE MSGAUTH_REQUIRED
-%token	AUTHENTICATE AUTHENTICATE_BY BY DECORATE_BY SET
-%token	ERROR YES NO
+%token	ACCOUNT ACCOUNTING AUTHENTICATE AUTHENTICATE_BY BY DECORATE_BY QUICK
+%token	SET TO ERROR YES NO
 %token	<v.string>		STRING
 %token	<v.number>		NUMBER
-%type	<v.number>		optport
+%type	<v.number>		optport optacct
 %type	<v.listen>		listen_addr
 %type	<v.str_l>		str_l optdeco
 %type	<v.prefix>		prefix
-%type	<v.yesno>		yesno
+%type	<v.yesno>		yesno optquick
 %type	<v.string>		strnum
 %type	<v.string>		key
 %type	<v.string>		optstring
@@ -113,6 +113,7 @@ grammar		: /* empty */
 		| grammar client '\n'
 		| grammar module '\n'
 		| grammar authenticate '\n'
+		| grammar account '\n'
 		| grammar error '\n'
 		;
 
@@ -143,7 +144,7 @@ outofmemory:
 			*n = $3;
 			TAILQ_INSERT_TAIL(&conf->listen, n, next);
 		}
-listen_addr	: STRING optport {
+listen_addr	: STRING optacct optport {
 			int		 gai_errno;
 			struct addrinfo hints, *res;
 
@@ -164,11 +165,22 @@ listen_addr	: STRING optport {
 			free($1);
 			$$.stype = res->ai_socktype;
 			$$.sproto = res->ai_protocol;
+			$$.accounting = $2;
 			memcpy(&$$.addr, res->ai_addr, res->ai_addrlen);
-			$$.addr.ipv4.sin_port = ($2 == 0)?
-			    htons(RADIUS_DEFAULT_PORT) : htons($2);
+			if ($3 != 0)
+				$$.addr.ipv4.sin_port = htons($3);
+			else if ($2)
+				$$.addr.ipv4.sin_port =
+				    htons(RADIUS_ACCT_DEFAULT_PORT);
+			else
+				$$.addr.ipv4.sin_port =
+				    htons(RADIUS_DEFAULT_PORT);
+
 			freeaddrinfo(res);
 		}
+optacct		: ACCOUNTING { $$ = 1; }
+		| { $$ = 0; }
+		;
 optport		: { $$ = 0; }
 		| PORT NUMBER	{ $$ = $2; }
 		;
@@ -474,8 +486,54 @@ authopt		: AUTHENTICATE_BY STRING {
 				TAILQ_INSERT_TAIL(&authen.deco, modref, next);
 			}
 			free_str_l(&$2);
+		}
+		;
+
+account		: ACCOUNT optquick str_l TO STRING optdeco {
+			int				 i, error = 1;
+			struct radiusd_accounting	*acct;
+			struct radiusd_module_ref	*modref, *modreft;
+
+			if ((acct = calloc(1,
+			    sizeof(struct radiusd_authentication))) == NULL) {
+				yyerror("Out of memory: %s", strerror(errno));
+				goto account_error;
+			}
+			if ((acct->acct = create_module_ref($5)) == NULL)
+				goto account_error;
+			acct->username = $3.v;
+			acct->quick = $2;
+			TAILQ_INIT(&acct->deco);
+			for (i = 0; i < $6.c; i++) {
+				if ((modref = create_module_ref($6.v[i]))
+				    == NULL)
+					goto account_error;
+				TAILQ_INSERT_TAIL(&acct->deco, modref, next);
+			}
+			TAILQ_INSERT_TAIL(&conf->account, acct, next);
+			acct = NULL;
+			error = 0;
+ account_error:
+			if (acct != NULL) {
+				free(acct->acct);
+				TAILQ_FOREACH_SAFE(modref, &acct->deco, next,
+				    modreft) {
+					TAILQ_REMOVE(&acct->deco, modref, next);
+					free(modref);
+				}
+				free_str_l(&$3);
+			}
+			free(acct);
+			free($5);
+			free_str_l(&$6);
+			if (error > 0)
+				YYERROR;
 		}
 		;
+
+optquick	: { $$ = 0; }
+		| QUICK { $$ = 1; }
+
 str_l		: str_l strnum {
 			int	  i;
 			char	**v;
@@ -548,6 +606,8 @@ lookup(char *s)
 {
 	/* this has to be sorted always */
 	static const struct keywords keywords[] = {
+		{ "account",			ACCOUNT},
+		{ "accounting",			ACCOUNTING},
 		{ "authenticate",		AUTHENTICATE},
 		{ "authenticate-by",		AUTHENTICATE_BY},
 		{ "by",				BY},
@@ -561,8 +621,10 @@ lookup(char *s)
 		{ "no",				NO},
 		{ "on",				ON},
 		{ "port",			PORT},
+		{ "quick",			QUICK},
 		{ "secret",			SECRET},
 		{ "set",			SET},
+		{ "to",				TO},
 		{ "yes",			YES},
 	};
 	const struct keywords	*p;
blob - 821df8f0bb23ced3c2b8774310e8caf71a0ce1b7
blob + e9202ba28f059c96ed0295472646745d69875468
--- usr.sbin/radiusd/radiusd.c
+++ usr.sbin/radiusd/radiusd.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: radiusd.c,v 1.43 2024/07/01 23:53:30 yasuoka Exp $	*/
+/*	$OpenBSD: radiusd.c,v 1.44 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 /*
  * Copyright (c) 2013, 2023 Internet Initiative Japan Inc.
@@ -63,7 +63,11 @@ static void		 radiusd_on_sighup(int, short, void *);
 static void		 radiusd_on_sigchld(int, short, void *);
 static void		 raidus_query_access_request(struct radius_query *);
 static void		 radius_query_access_response(struct radius_query *);
+static void		 raidus_query_accounting_request(
+			    struct radiusd_accounting *, struct radius_query *);
+static void		 radius_query_accounting_response(struct radius_query *);
 static const char	*radius_code_string(int);
+static const char	*radius_acct_status_type_string(uint32_t);
 static int		 radiusd_access_response_fixup (struct radius_query *);
 
 
@@ -89,9 +93,11 @@ static void		 radiusd_module_request_decoration(
 			    struct radiusd_module *, struct radius_query *);
 static void		 radiusd_module_response_decoration(
 			    struct radiusd_module *, struct radius_query *);
-static void		 close_stdio(void);
+static void		 radiusd_module_account_request(struct radiusd_module *,
+			    struct radius_query *);
 static int		 imsg_compose_radius_packet(struct imsgbuf *,
 			    uint32_t, u_int, RADIUS_PACKET *);
+static void		 close_stdio(void);
 
 static u_int		 radius_query_id_seq = 0;
 int			 debug = 0;
@@ -405,8 +411,10 @@ radiusd_listen_handle_packet(struct radiusd_listen *li
 	static char			 username[256];
 	char				 peerstr[NI_MAXHOST + NI_MAXSERV + 30];
 	struct radiusd_authentication	*authen;
+	struct radiusd_accounting	*accounting;
 	struct radiusd_client		*client;
 	struct radius_query		*q = NULL;
+	uint32_t			 acct_status;
 #define in(_x)	(((struct sockaddr_in  *)_x)->sin_addr)
 #define in6(_x)	(((struct sockaddr_in6 *)_x)->sin6_addr)
 
@@ -436,12 +444,22 @@ radiusd_listen_handle_packet(struct radiusd_listen *li
 		log_warnx("Received %s(code=%d) from %s id=%d: no `client' "
 		    "matches", radius_code_string(req_code), req_code, peerstr,
 		    req_id);
+		goto on_error;
+	}
+
+	/* Check the request authenticator if accounting */
+	if ((req_code == RADIUS_CODE_ACCOUNTING_REQUEST ||
+	    listn->accounting) && radius_check_accounting_request_authenticator(
+	    packet, client->secret) != 0) {
+		log_warnx("Received %s(code=%d) from %s id=%d: bad request "
+		    "authenticator", radius_code_string(req_code), req_code,
+		    peerstr, req_id);
 		goto on_error;
 	}
 
 	/* Check the client's Message-Authenticator */
-	if (client->msgauth_required && !radius_has_attr(packet,
-	    RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) {
+	if (client->msgauth_required && !listn->accounting &&
+	    !radius_has_attr(packet, RADIUS_TYPE_MESSAGE_AUTHENTICATOR)) {
 		log_warnx("Received %s(code=%d) from %s id=%d: no message "
 		    "authenticator", radius_code_string(req_code), req_code,
 		    peerstr, req_id);
@@ -503,6 +521,13 @@ radiusd_listen_handle_packet(struct radiusd_listen *li
 
 	switch (req_code) {
 	case RADIUS_CODE_ACCESS_REQUEST:
+		if (listn->accounting) {
+			log_info("Received %s(code=%d) from %s id=%d: "
+			    "ignored because the port is for authentication",
+			    radius_code_string(req_code), req_code, peerstr,
+			    req_id);
+			break;
+		}
 		/*
 		 * Find a matching `authenticate' entry
 		 */
@@ -539,6 +564,47 @@ radiusd_listen_handle_packet(struct radiusd_listen *li
 
 		raidus_query_access_request(q);
 		return;
+	case RADIUS_CODE_ACCOUNTING_REQUEST:
+		if (!listn->accounting) {
+			log_info("Received %s(code=%d) from %s id=%d: "
+			    "ignored because the port is for accounting",
+			    radius_code_string(req_code), req_code, peerstr,
+			    req_id);
+			break;
+		}
+		if (radius_get_uint32_attr(q->req, RADIUS_TYPE_ACCT_STATUS_TYPE,
+		    &acct_status) != 0)
+			acct_status = 0;
+		/*
+		 * Find a matching `accounting' entry
+		 */
+		TAILQ_FOREACH(accounting, &listn->radiusd->account, next) {
+			if (acct_status == RADIUS_ACCT_STATUS_TYPE_ACCT_ON ||
+			    acct_status == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) {
+				raidus_query_accounting_request(accounting, q);
+				continue;
+			}
+			for (i = 0; accounting->username[i] != NULL; i++) {
+				if (fnmatch(accounting->username[i], username,
+				    0) == 0)
+					break;
+			}
+			if (accounting->username[i] == NULL)
+				continue;
+			raidus_query_accounting_request(accounting, q);
+			if (accounting->quick)
+				break;
+		}
+		/* pass NULL to hadnle this self without module */
+		raidus_query_accounting_request(NULL, q);
+
+		if ((q->res = radius_new_response_packet(
+		    RADIUS_CODE_ACCOUNTING_RESPONSE, q->req)) == NULL)
+			log_warn("%s: radius_new_response_packet() failed",
+			    __func__);
+		else
+			radius_query_accounting_response(q);
+		break;
 	default:
 		log_info("Received %s(code=%d) from %s id=%d: %s is not "
 		    "supported in this implementation", radius_code_string(
@@ -625,8 +691,55 @@ radius_query_access_response(struct radius_query *q)
 		log_warn("Sending a RADIUS response failed");
 on_error:
 	radiusd_access_request_aborted(q);
+}
+
+static void
+raidus_query_accounting_request(struct radiusd_accounting *accounting,
+    struct radius_query *q)
+{
+	int		 req_code;
+	uint32_t	 acct_status;
+	char		 buf0[NI_MAXHOST + NI_MAXSERV + 30];
+
+	if (accounting != NULL) {
+		/* handle by the module */
+		if (MODULE_DO_ACCTREQ(accounting->acct->module))
+			radiusd_module_account_request(accounting->acct->module,
+			    q);
+		return;
+	}
+	req_code = radius_get_code(q->req);
+	if (radius_get_uint32_attr(q->req, RADIUS_TYPE_ACCT_STATUS_TYPE,
+	    &acct_status) != 0)
+		acct_status = 0;
+	log_info("Received %s(code=%d) type=%s(%lu) from %s id=%d username=%s "
+	    "q=%u", radius_code_string(req_code), req_code,
+	    radius_acct_status_type_string(acct_status), (unsigned long)
+	    acct_status, addrport_tostring((struct sockaddr *)&q->clientaddr,
+	    q->clientaddrlen, buf0, sizeof(buf0)), q->req_id, q->username,
+	    q->id);
 }
 
+static void
+radius_query_accounting_response(struct radius_query *q)
+{
+	int		 sz, res_id, res_code;
+	char		 buf[NI_MAXHOST + NI_MAXSERV + 30];
+
+	radius_set_response_authenticator(q->res, q->client->secret);
+	res_id = radius_get_id(q->res);
+	res_code = radius_get_code(q->res);
+
+	log_info("Sending %s(code=%d) to %s id=%u q=%u",
+	    radius_code_string(res_code), res_code,
+	    addrport_tostring((struct sockaddr *)&q->clientaddr,
+		    q->clientaddrlen, buf, sizeof(buf)), res_id, q->id);
+
+	if ((sz = sendto(q->listen->sock, radius_get_data(q->res),
+	    radius_get_length(q->res), 0,
+	    (struct sockaddr *)&q->clientaddr, q->clientaddrlen)) <= 0)
+		log_warn("Sending a RADIUS response failed");
+}
 /***********************************************************************
  * Callback functions from the modules
  ***********************************************************************/
@@ -772,6 +885,29 @@ radius_code_string(int code)
 	return ("Unknown");
 }
 
+static const char *
+radius_acct_status_type_string(uint32_t type)
+{
+	int			i;
+	struct _typestrings {
+		uint32_t	 type;
+		const char	*string;
+	} typestrings[] = {
+	    { RADIUS_ACCT_STATUS_TYPE_START,		"Start" },
+	    { RADIUS_ACCT_STATUS_TYPE_STOP,		"Stop" },
+	    { RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE,	"Interim-Update" },
+	    { RADIUS_ACCT_STATUS_TYPE_ACCT_ON,		"Accounting-On" },
+	    { RADIUS_ACCT_STATUS_TYPE_ACCT_OFF,		"Accounting-Off" },
+	    { -1,					NULL }
+	};
+
+	for (i = 0; typestrings[i].string != NULL; i++)
+		if (typestrings[i].type == type)
+			return (typestrings[i].string);
+
+	return ("Unknown");
+}
+
 void
 radiusd_conf_init(struct radiusd *conf)
 {
@@ -779,6 +915,7 @@ radiusd_conf_init(struct radiusd *conf)
 	TAILQ_INIT(&conf->listen);
 	TAILQ_INIT(&conf->module);
 	TAILQ_INIT(&conf->authen);
+	TAILQ_INIT(&conf->account);
 	TAILQ_INIT(&conf->client);
 
 	return;
@@ -1605,6 +1742,29 @@ radiusd_module_response_decoration(struct radiusd_modu
 	radiusd_module_reset_ev_handler(module);
 }
 
+static void
+radiusd_module_account_request(struct radiusd_module *module,
+    struct radius_query *q)
+{
+	RADIUS_PACKET				*radpkt;
+
+	if ((radpkt = radius_convert_packet(radius_get_data(q->req),
+	    radius_get_length(q->req))) == NULL) {
+		log_warn("q=%u Could not send ACCSREQ to `%s'", q->id,
+		    module->name);
+		radiusd_access_request_aborted(q);
+		return;
+	}
+	if (imsg_compose_radius_packet(&module->ibuf,
+	    IMSG_RADIUSD_MODULE_ACCTREQ, q->id, radpkt) == -1) {
+		log_warn("q=%u Could not send ACCTREQ to `%s'", q->id,
+		    module->name);
+		radiusd_access_request_aborted(q);
+	}
+	radiusd_module_reset_ev_handler(module);
+	radius_delete_packet(radpkt);
+}
+
 static int
 imsg_compose_radius_packet(struct imsgbuf *ibuf, uint32_t type, u_int q_id,
     RADIUS_PACKET *radpkt)
blob - 5d02722c678e52a9e47acc48528a0c19ee462de1
blob + 4d8604fc17a7acaf4316adf23a2e76538b92cf28
--- usr.sbin/radiusd/radiusd.conf.5
+++ usr.sbin/radiusd/radiusd.conf.5
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: radiusd.conf.5,v 1.20 2024/07/02 00:00:12 yasuoka Exp $
+.\"	$OpenBSD: radiusd.conf.5,v 1.21 2024/07/02 00:33:51 yasuoka Exp $
 .\"
 .\" Copyright (c) 2014 Esdenera Networks GmbH
 .\" Copyright (c) 2014, 2023 Internet Initiative Japan Inc.
@@ -35,7 +35,7 @@ Keywords may be specified multiple times within the co
 The configuration options are as follows:
 .Bl -tag -width Ds
 .It Xo
-.Ic listen on Ar address
+.Ic listen on Oo Ic accounting Oc Ar address
 .Ic port Ar port
 .Xc
 Specify an
@@ -43,6 +43,11 @@ Specify an
 and a
 .Ar port
 to listen on.
+When
+.Ar accouting
+is specified,
+it is used for waiting for RADIUS accounting messages.
+The default port number is 1812 for authentication and 1813 for accounting.
 .It Ic client Ar address/mask Brq ...
 Allow access to a client with the specified
 .Ar address
@@ -129,6 +134,28 @@ matches an authenticating user is used.
 Optionally decoration modules can be specified by
 .Ar deco .
 The specified modules decorate the RADIUS messages in the configured order.
+.It Ic account Oo Ic quick Oc Ar username-pattern ... Ic to Ar module \
+Oo Ic decoratd by Ar deco ... Oc
+Specify an accounting configuration for the users specified by
+.Ar username-pattern .
+The accounting messages for the users matched by the pattern are handled
+by the module specified by the
+.Ar module .
+Use shell globbing rules for the patterns;
+multiple patterns can be determined by separating them with space characters.
+When multiple
+.Ic account
+lines are selected,
+all account settings whose
+.Ar username-pattern
+matches an accounting users are used.
+until the user matches the setting with the
+.Ar quick
+option.
+.Pp
+Optionally decoration modules can be specified by
+.Ar deco .
+The specified modules decorate the RADIUS messages in the configured order.
 .El
 .Sh FILES
 .Bl -tag -width "/etc/examples/radiusd.conf" -compact
@@ -142,7 +169,9 @@ Example configuration file.
 .Sh EXAMPLES
 .Bd -literal -offset indent
 listen on 0.0.0.0
+listen on 0.0.0.0 accounting
 listen on ::
+listen on :: accounting
 
 client 127.0.0.1/32 {
     secret "secret"
@@ -168,6 +197,8 @@ module strip-realm "/usr/libexec/radiusd/radiusd_stand
 authenticate *@local by bsdauth decorate-by strip-realm
 
 authenticate * by radius
+
+account * to standard
 .Ed
 .Sh SEE ALSO
 .Xr radiusd 8 ,
blob - 6cc56b64a85e491434ef41403356827f09995949
blob + 3d724176047135f8384c59fba3cc10be57559d37
--- usr.sbin/radiusd/radiusd.h
+++ usr.sbin/radiusd/radiusd.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: radiusd.h,v 1.6 2024/01/08 04:16:48 yasuoka Exp $	*/
+/*	$OpenBSD: radiusd.h,v 1.7 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 #ifndef	RADIUSD_H
 #define	RADIUSD_H 1
@@ -45,16 +45,18 @@ enum imsg_type {
 	IMSG_RADIUSD_MODULE_RESDECO0_REQ, /* request pkt for RESDECO */
 	IMSG_RADIUSD_MODULE_RESDECO,
 	IMSG_RADIUSD_MODULE_RESDECO_DONE,
-	IMSG_RADIUSD_MODULE_STOP
+	IMSG_RADIUSD_MODULE_ACCTREQ,
+	IMSG_RADIUSD_MODULE_STOP,
 };
 
 /* Module sends LOAD when it becomes ready */
 struct radiusd_module_load_arg {
 	uint32_t	cap;	/* module capabity bits */
-#define RADIUSD_MODULE_CAP_USERPASS	0x1
-#define RADIUSD_MODULE_CAP_ACCSREQ	0x2
-#define RADIUSD_MODULE_CAP_REQDECO	0x4
-#define RADIUSD_MODULE_CAP_RESDECO	0x8
+#define RADIUSD_MODULE_CAP_USERPASS	0x01
+#define RADIUSD_MODULE_CAP_ACCSREQ	0x02
+#define RADIUSD_MODULE_CAP_REQDECO	0x04
+#define RADIUSD_MODULE_CAP_RESDECO	0x08
+#define RADIUSD_MODULE_CAP_ACCTREQ	0x10
 };
 
 struct radiusd_module_object {
blob - 24e4f74074f64db7fa1b308b508a08a608da9f38
blob + a36373c827cb9b4057db65c0fb8228e6ace5c1fe
--- usr.sbin/radiusd/radiusd_local.h
+++ usr.sbin/radiusd/radiusd_local.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: radiusd_local.h,v 1.10 2024/07/01 05:20:01 yasuoka Exp $	*/
+/*	$OpenBSD: radiusd_local.h,v 1.11 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 /*
  * Copyright (c) 2013 Internet Initiative Japan Inc.
@@ -44,6 +44,7 @@ struct radiusd_listen {
 	struct radiusd				*radiusd;
 	struct event				 ev;
 	int					 sock;
+	int					 accounting;
 	union {
 		struct sockaddr_in		 ipv4;
 		struct sockaddr_in6		 ipv6;
@@ -96,6 +97,15 @@ struct radiusd_authentication {
 	TAILQ_ENTRY(radiusd_authentication)	  next;
 };
 
+struct radiusd_accounting {
+	char					**username;
+	char					 *secret;
+	struct radiusd_module_ref		 *acct;
+	int					  quick;
+	TAILQ_HEAD(,radiusd_module_ref)		  deco;
+	TAILQ_ENTRY(radiusd_accounting)		  next;
+};
+
 struct radiusd {
 	struct radiusd_listen_head		 listen;
 	struct event				 ev_sigterm;
@@ -104,6 +114,7 @@ struct radiusd {
 	struct event				 ev_sigchld;
 	TAILQ_HEAD(,radiusd_module)		 module;
 	TAILQ_HEAD(,radiusd_authentication)	 authen;
+	TAILQ_HEAD(,radiusd_accounting)		 account;
 	TAILQ_HEAD(,radiusd_client)		 client;
 	TAILQ_HEAD(,radius_query)		 query;
 	int					 error;
@@ -151,6 +162,9 @@ struct radius_query {
 #define	MODULE_DO_ACCSREQ(_m)					\
 	((_m)->fd >= 0 &&					\
 	    ((_m)->capabilities & RADIUSD_MODULE_CAP_ACCSREQ) != 0)
+#define	MODULE_DO_ACCTREQ(_m)					\
+	((_m)->fd >= 0 &&					\
+	    ((_m)->capabilities & RADIUSD_MODULE_CAP_ACCTREQ) != 0)
 #define	MODULE_DO_REQDECO(_m)					\
 	((_m)->fd >= 0 &&					\
 	    ((_m)->capabilities & RADIUSD_MODULE_CAP_REQDECO) != 0)
blob - 85236db2cd3569de27d0c2e4923d066b3677525c
blob + 9a2b63606328f906e3ea5ebadc04240122c8cb45
--- usr.sbin/radiusd/radiusd_module.c
+++ usr.sbin/radiusd/radiusd_module.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: radiusd_module.c,v 1.16 2024/02/09 07:41:32 yasuoka Exp $	*/
+/*	$OpenBSD: radiusd_module.c,v 1.17 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 /*
  * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
@@ -50,6 +50,8 @@ static void	(*module_request_decoration) (void *, u_in
 		    size_t) = NULL;
 static void	(*module_response_decoration) (void *, u_int, const u_char *,
 		    size_t, const u_char *, size_t) = NULL;
+static void	(*module_accounting_request) (void *, u_int, const u_char *,
+		    size_t) = NULL;
 
 struct module_base {
 	void			*ctx;
@@ -98,6 +100,7 @@ module_create(int sock, void *ctx, struct module_handl
 	module_config_set = handler->config_set;
 	module_request_decoration = handler->request_decoration;
 	module_response_decoration = handler->response_decoration;
+	module_accounting_request = handler->accounting_request;
 	module_start_module = handler->start;
 	module_stop_module = handler->stop;
 
@@ -156,6 +159,8 @@ module_load(struct module_base *base)
 		load.cap |= RADIUSD_MODULE_CAP_REQDECO;
 	if (module_response_decoration != NULL)
 		load.cap |= RADIUSD_MODULE_CAP_RESDECO;
+	if (module_accounting_request != NULL)
+		load.cap |= RADIUSD_MODULE_CAP_ACCTREQ;
 	imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_LOAD, 0, 0, -1, &load,
 	    sizeof(load));
 	imsg_flush(&base->ibuf);
@@ -447,6 +452,7 @@ module_imsg_handler(struct module_base *base, struct i
 	case IMSG_RADIUSD_MODULE_REQDECO:
 	case IMSG_RADIUSD_MODULE_RESDECO0_REQ:
 	case IMSG_RADIUSD_MODULE_RESDECO:
+	case IMSG_RADIUSD_MODULE_ACCTREQ:
 	    {
 		struct radiusd_module_radpkt_arg	*accessreq;
 		int					 chunklen;
@@ -459,6 +465,13 @@ module_imsg_handler(struct module_base *base, struct i
 				break;
 			}
 			typestr = "ACCSREQ";
+		} else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_ACCTREQ) {
+			if (module_accounting_request == NULL) {
+				syslog(LOG_ERR, "Received ACCTREQ message, but "
+				    "module doesn't support");
+				break;
+			}
+			typestr = "ACCTREQ";
 		} else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_REQDECO) {
 			if (module_request_decoration == NULL) {
 				syslog(LOG_ERR, "Received REQDECO message, but "
@@ -539,14 +552,16 @@ module_imsg_handler(struct module_base *base, struct i
 			}
 			memcpy(base->radpkt2, base->radpkt, base->radpktoff);
 			base->radpkt2len = base->radpktoff;
-		} else {
+		} else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_RESDECO) {
 			module_response_decoration(base->ctx, accessreq->q_id,
 			    base->radpkt2, base->radpkt2len, base->radpkt,
 			    base->radpktoff);
 			base->radpkt2len = 0;
-		}
+		} else
+			module_accounting_request(base->ctx, accessreq->q_id,
+			    base->radpkt, base->radpktoff);
 		base->radpktoff = 0;
-accsreq_out:
+ accsreq_out:
 		break;
 	    }
 	}
blob - 5fb44513fd6e36d4faaf2a33f6d12e6edc943186
blob + 9b3b84760528d85e69e0c9fe15ee1d918af4bc2f
--- usr.sbin/radiusd/radiusd_module.h
+++ usr.sbin/radiusd/radiusd_module.h
@@ -20,6 +20,7 @@
 #include "radiusd.h"
 
 struct module_ctx;
+struct imsg;
 
 struct module_handlers {
 	/* Should send IMSG_OK or IMSG_NG */
@@ -42,6 +43,11 @@ struct module_handlers {
 
 	void (*response_decoration)(void *ctx, u_int query_id,
 	    const u_char *req, size_t reqlen, const u_char *res, size_t reslen);
+
+	void (*accounting_request)(void *ctx, u_int query_id, const u_char *pkt,
+	    size_t pktlen);
+
+	void (*dispatch_control)(void *ctx, struct imsg *);
 };
 
 #define SYNTAX_ASSERT(_cond, _msg)				\
@@ -77,6 +83,10 @@ int			 module_reqdeco_done(struct module_base *, u_int
 			    const u_char *, size_t);
 int			 module_resdeco_done(struct module_base *, u_int,
 			    const u_char *, size_t);
+int			 module_imsg_compose(struct module_base *, uint32_t,
+			    uint32_t, pid_t, int, const void *, size_t);
+int			 module_imsg_composev(struct module_base *, uint32_t,
+			    uint32_t, pid_t, int, const struct iovec *, int);
 
 __END_DECLS
 
blob - a75c9da3e970a8eec6665f41a9f1a88d62cb4df7
blob + d434c7c22c7463910004974f468361caa74b02e7
--- usr.sbin/radiusd/radiusd_standard.8
+++ usr.sbin/radiusd/radiusd_standard.8
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: radiusd_standard.8,v 1.1 2024/07/02 00:00:12 yasuoka Exp $
+.\"	$OpenBSD: radiusd_standard.8,v 1.2 2024/07/02 00:33:51 yasuoka Exp $
 .\"
 .\" Copyright (c) 2014 Esdenera Networks GmbH
 .\" Copyright (c) 2014, 2024 Internet Initiative Japan Inc.
@@ -28,7 +28,14 @@
 .Sh DESCRIPTION
 The
 .Nm
-utility processes files ...
+utility is executed by
+.Xr radiusd 8
+as a module to provide various standard functionalities.
+It can be configured as a module for decoration which modifies request and
+response RADIUS messages.
+Also it can be configured as an accounting module that logs accounting
+information through
+.Xr syslog 3 .
 .Sh CONFIGURATIONS
 The
 .Nm
blob - 68d5f8e0f95b701783adf2b8d74678b013259c42
blob + b925ec4c152117b11c9b2a276128068b8164d864
--- usr.sbin/radiusd/radiusd_standard.c
+++ usr.sbin/radiusd/radiusd_standard.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: radiusd_standard.c,v 1.5 2024/04/23 13:34:51 jsg Exp $	*/
+/*	$OpenBSD: radiusd_standard.c,v 1.6 2024/07/02 00:33:51 yasuoka Exp $	*/
 
 /*
  * Copyright (c) 2013, 2023 Internet Initiative Japan Inc.
@@ -17,6 +17,8 @@
  */
 #include <sys/types.h>
 #include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
 
 #include <err.h>
 #include <errno.h>
@@ -49,12 +51,35 @@ struct module_standard {
 	struct attrs		 remove_resattrs;
 };
 
+struct radius_const_str {
+	const unsigned	 constval;
+	const char	*label;
+};
+
+static void	 radius_const_print(FILE *, RADIUS_PACKET *, uint8_t,
+		    const char *, struct radius_const_str *);
 static void	 module_standard_config_set(void *, const char *, int,
 		    char * const *);
 static void	 module_standard_reqdeco(void *, u_int, const u_char *, size_t);
 static void	 module_standard_resdeco(void *, u_int, const u_char *, size_t,
 		    const u_char *, size_t);
+static void	 module_accounting_request(void *, u_int, const u_char *,
+		    size_t);
+static void	 radius_u32_print(FILE *, RADIUS_PACKET *, uint8_t,
+		    const char *);
+static void	 radius_str_print(FILE *, RADIUS_PACKET *, uint8_t,
+		    const char *);
+static void	 radius_ipv4_print(FILE *, RADIUS_PACKET *, uint8_t,
+		    const char *);
+static void	 radius_ipv6_print(FILE *, RADIUS_PACKET *, uint8_t,
+		    const char *);
 
+static struct radius_const_str
+		 nas_port_type_consts[], tunnel_type_consts[],
+		 service_type_consts[], framed_protocol_consts[],
+		 acct_status_type_consts[], acct_authentic_consts[],
+		 terminate_cause_consts[], tunnel_medium_type_consts[];
+
 int
 main(int argc, char *argv[])
 {
@@ -62,7 +87,8 @@ main(int argc, char *argv[])
 	struct module_handlers handlers = {
 		.config_set = module_standard_config_set,
 		.request_decoration = module_standard_reqdeco,
-		.response_decoration = module_standard_resdeco
+		.response_decoration = module_standard_resdeco,
+		.accounting_request = module_accounting_request
 	};
 	struct attr		*attr;
 
@@ -297,4 +323,287 @@ module_standard_resdeco(void *ctx, u_int q_id, const u
 	}
 	if (radres != NULL)
 		radius_delete_packet(radres);
+}
+
+static void
+module_accounting_request(void *ctx, u_int query_id, const u_char *pkt,
+    size_t pktlen)
+{
+	RADIUS_PACKET		*radpkt = NULL;
+	struct module_standard	*module = ctx;
+	FILE			*fp;
+	char			*buf = NULL;
+	size_t			 size = 0;
+
+	if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
+		syslog(LOG_ERR,
+		    "%s: radius_convert_packet() failed: %m", __func__);
+		module_stop(module->base);
+		return;
+	}
+
+	if ((fp = open_memstream(&buf, &size)) == NULL) {
+		syslog(LOG_ERR, "%s: open_memstream() failed: %m", __func__);
+		module_stop(module->base);
+		goto out;
+	}
+	radius_const_print(fp, radpkt, RADIUS_TYPE_ACCT_STATUS_TYPE,
+	    "Acct-Status-Type", acct_status_type_consts);
+
+	radius_ipv4_print(fp, radpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
+	    "NAS-IP-Address");
+	radius_ipv6_print(fp, radpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS,
+	    "NAS-IPv6-Address");
+	radius_const_print(fp, radpkt, RADIUS_TYPE_NAS_PORT_TYPE,
+	    "NAS-Port-Type",  nas_port_type_consts);
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_NAS_PORT, "NAS-Port");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_NAS_IDENTIFIER,
+	    "NAS-Identifier");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_CALLING_STATION_ID,
+	    "Calling-Station-ID");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_CALLED_STATION_ID,
+	    "Called-Station-ID");
+
+	radius_const_print(fp, radpkt, RADIUS_TYPE_TUNNEL_MEDIUM_TYPE,
+	    "Tunnel-Medium-Type", tunnel_medium_type_consts);
+	radius_str_print(fp, radpkt, RADIUS_TYPE_TUNNEL_CLIENT_ENDPOINT,
+	    "Tunnel-Client-Endpoint");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_TUNNEL_SERVER_ENDPOINT,
+	    "Tunnel-Server-Endpoint");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_TUNNEL_ASSIGNMENT_ID,
+	    "Tunnel-Assignment-ID");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_ACCT_TUNNEL_CONNECTION,
+	    "Acct-Tunnel-Connection");
+
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_SESSION_TIME,
+	    "Acct-Session-Time");
+	radius_const_print(fp, radpkt,
+	    RADIUS_TYPE_TUNNEL_TYPE, "Tunnel-Type", tunnel_type_consts);
+	radius_str_print(fp, radpkt, RADIUS_TYPE_USER_NAME, "User-Name");
+	radius_const_print(fp, radpkt,
+	    RADIUS_TYPE_SERVICE_TYPE, "Service-Type", service_type_consts);
+	radius_const_print(fp, radpkt, RADIUS_TYPE_FRAMED_PROTOCOL,
+	    "Framed-Protocol", framed_protocol_consts);
+	radius_ipv4_print(fp, radpkt, RADIUS_TYPE_FRAMED_IP_ADDRESS,
+	    "Framed-IP-Address");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_DELAY_TIME,
+	    "Acct-Delay-Time");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_INPUT_OCTETS,
+	    "Acct-Input-Octets");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_OUTPUT_OCTETS,
+	    "Acct-Output-Octets");
+	radius_str_print(fp, radpkt, RADIUS_TYPE_ACCT_SESSION_ID,
+	    "Acct-Session-ID");
+	radius_const_print(fp, radpkt, RADIUS_TYPE_ACCT_AUTHENTIC,
+	    "Acct-Authentic", acct_authentic_consts);
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_SESSION_TIME,
+	    "Acct-Sesion-Time");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_INPUT_PACKETS,
+	    "Acct-Input-Packets");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS,
+	    "Acct-Output-Packets");
+	radius_const_print(fp, radpkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE,
+	    "Acct-Terminate-Cause", terminate_cause_consts);
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_INPUT_GIGAWORDS,
+	    "Acct-Input-Gigawords");
+	radius_u32_print(fp, radpkt, RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS,
+	    "Acct-Output-Gigawords");
+
+	fputc('\0', fp);
+	fclose(fp);
+	syslog(LOG_INFO, "Accounting q=%u %s", query_id, buf + 1);
+ out:
+	radius_delete_packet(radpkt);
+	freezero(buf, size);
 }
+
+/***********************************************************************
+ * print RADIUS attribute
+ ***********************************************************************/
+static void
+radius_const_print(FILE *fout, RADIUS_PACKET *radpkt, uint8_t attr_type,
+    const char *attr_name, struct radius_const_str *consts)
+{
+	struct radius_const_str *const_;
+	uint32_t		 u32val;
+
+	if (radius_get_uint32_attr(radpkt, attr_type, &u32val) != 0)
+		return;
+
+	for (const_ = consts; const_->label != NULL; const_++) {
+		if (const_->constval == u32val)
+			break;
+	}
+
+	fprintf(fout, " %s=%s(%u)", attr_name, (const_ != NULL)? const_->label
+	    : "unknown", (unsigned)u32val);
+}
+
+static void
+radius_u32_print(FILE *fout, RADIUS_PACKET *radpkt, uint8_t attr_type,
+    const char *attr_name)
+{
+	uint32_t		 u32val;
+
+	if (radius_get_uint32_attr(radpkt, attr_type, &u32val) != 0)
+		return;
+	fprintf(fout, " %s=%u", attr_name, u32val);
+}
+
+static void
+radius_str_print(FILE *fout, RADIUS_PACKET *radpkt, uint8_t attr_type,
+    const char *attr_name)
+{
+	char			 strval[256];
+
+	if (radius_get_string_attr(radpkt, attr_type, strval, sizeof(strval))
+	    != 0)
+		return;
+	fprintf(fout, " %s=%s", attr_name, strval);
+}
+
+static void
+radius_ipv4_print(FILE *fout, RADIUS_PACKET *radpkt, uint8_t attr_type,
+    const char *attr_name)
+{
+	struct in_addr		 ipv4;
+	char			 buf[128];
+
+	if (radius_get_ipv4_attr(radpkt, attr_type, &ipv4) != 0)
+		return;
+	fprintf(fout, " %s=%s", attr_name,
+	    inet_ntop(AF_INET, &ipv4, buf, sizeof(buf)));
+}
+
+static void
+radius_ipv6_print(FILE *fout, RADIUS_PACKET *radpkt, uint8_t attr_type,
+    const char *attr_name)
+{
+	struct in6_addr		 ipv6;
+	char			 buf[128];
+
+	if (radius_get_ipv6_attr(radpkt, attr_type, &ipv6) != 0)
+		return;
+
+	fprintf(fout, " %s=%s", attr_name,
+	    inet_ntop(AF_INET6, &ipv6, buf, sizeof(buf)));
+}
+
+static struct radius_const_str nas_port_type_consts[] = {
+    { RADIUS_NAS_PORT_TYPE_ASYNC,		"\"Async\"" },
+    { RADIUS_NAS_PORT_TYPE_SYNC,		"\"Sync\"" },
+    { RADIUS_NAS_PORT_TYPE_ISDN_SYNC,		"\"ISDN Sync\"" },
+    { RADIUS_NAS_PORT_TYPE_ISDN_ASYNC_V120,	"\"ISDN Async V.120\"" },
+    { RADIUS_NAS_PORT_TYPE_ISDN_ASYNC_V110,	"\"ISDN Async V.110\"" },
+    { RADIUS_NAS_PORT_TYPE_VIRTUAL,		"\"Virtual\"" },
+    { RADIUS_NAS_PORT_TYPE_PIAFS,		"\"PIAFS\"" },
+    { RADIUS_NAS_PORT_TYPE_HDLC_CLEAR_CHANNEL,	"\"HDLC Clear Channel\"" },
+    { RADIUS_NAS_PORT_TYPE_X_25,		"\"X.25\"" },
+    { RADIUS_NAS_PORT_TYPE_X_75,		"\"X.75\"" },
+    { RADIUS_NAS_PORT_TYPE_G3_FAX,		"\"G.3 Fax\"" },
+    { RADIUS_NAS_PORT_TYPE_SDSL,		"\"SDSL\"" },
+    { RADIUS_NAS_PORT_TYPE_ADSL_CAP,		"\"ADSL-CAP\"" },
+    { RADIUS_NAS_PORT_TYPE_ADSL_DMT,		"\"ADSL-DMT\"" },
+    { RADIUS_NAS_PORT_TYPE_IDSL,		"\"IDSL\"" },
+    { RADIUS_NAS_PORT_TYPE_ETHERNET,		"\"Ethernet\"" },
+    { RADIUS_NAS_PORT_TYPE_XDSL,		"\"xDSL\"" },
+    { RADIUS_NAS_PORT_TYPE_CABLE,		"\"Cable\"" },
+    { RADIUS_NAS_PORT_TYPE_WIRELESS,		"\"Wireless\"" },
+    { RADIUS_NAS_PORT_TYPE_WIRELESS_802_11,	"\"Wireless - IEEE 802.11\"" },
+    { 0, NULL }
+};
+
+static struct radius_const_str tunnel_type_consts[] = {
+    { RADIUS_TUNNEL_TYPE_PPTP,		"PPTP" },
+    { RADIUS_TUNNEL_TYPE_L2F,		"L2F" },
+    { RADIUS_TUNNEL_TYPE_L2TP,		"L2TP" },
+    { RADIUS_TUNNEL_TYPE_ATMP,		"ATMP" },
+    { RADIUS_TUNNEL_TYPE_VTP,		"VTP" },
+    { RADIUS_TUNNEL_TYPE_AH,		"AH" },
+    { RADIUS_TUNNEL_TYPE_IP,		"IP" },
+    { RADIUS_TUNNEL_TYPE_MOBILE,	"MIN-IP-IP" },
+    { RADIUS_TUNNEL_TYPE_ESP,		"ESP" },
+    { RADIUS_TUNNEL_TYPE_GRE,		"GRE" },
+    { RADIUS_TUNNEL_TYPE_VDS,		"DVS" },
+    { 0, NULL }
+};
+
+static struct radius_const_str service_type_consts[] = {
+    { RADIUS_SERVICE_TYPE_LOGIN,		"\"Login\"" },
+    { RADIUS_SERVICE_TYPE_FRAMED,		"\"Framed\"" },
+    { RADIUS_SERVICE_TYPE_CB_LOGIN,		"\"Callback Login\"" },
+    { RADIUS_SERVICE_TYPE_CB_FRAMED,		"\"Callback Framed\"" },
+    { RADIUS_SERVICE_TYPE_OUTBOUND,		"\"Outbound\"" },
+    { RADIUS_SERVICE_TYPE_ADMINISTRATIVE,	"\"Administrative\"" },
+    { RADIUS_SERVICE_TYPE_NAS_PROMPT,		"\"NAS Propmt\"" },
+/* there had been a typo in radius.h */
+#if !defined(RADIUS_SERVICE_TYPE_CB_NAS_PROMPT) && \
+    defined(RADIUS_SERVICE_TYPE_CB_NAS_PROMPTi)
+#define RADIUS_SERVICE_TYPE_CB_NAS_PROMPT RADIUS_SERVICE_TYPE_CB_NAS_PROMPTi
+#endif
+    { RADIUS_SERVICE_TYPE_AUTHENTICAT_ONLY,	"\"Authenticat Only\"" },
+    { RADIUS_SERVICE_TYPE_CB_NAS_PROMPT,	"\"Callback NAS Prompt\"" },
+    { RADIUS_SERVICE_TYPE_CALL_CHECK,		"\"Call Check\"" },
+    { RADIUS_SERVICE_TYPE_CB_ADMINISTRATIVE,	"\"Callback Administrative\"" },
+    { 0, NULL }
+};
+
+static struct radius_const_str framed_protocol_consts[] = {
+    { RADIUS_FRAMED_PROTOCOL_PPP,		"PPP" },
+    { RADIUS_FRAMED_PROTOCOL_SLIP,		"SLIP" },
+    { RADIUS_FRAMED_PROTOCOL_ARAP,		"ARAP" },
+    { RADIUS_FRAMED_PROTOCOL_GANDALF,		"Gandalf" },
+    { RADIUS_FRAMED_PROTOCOL_XYLOGICS,		"Xylogics" },
+    { RADIUS_FRAMED_PROTOCOL_X75,		"X.75" },
+    { 0, NULL }
+};
+
+static struct radius_const_str acct_status_type_consts[] = {
+    { RADIUS_ACCT_STATUS_TYPE_START,		"Start" },
+    { RADIUS_ACCT_STATUS_TYPE_STOP,		"Stop" },
+    { RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE,	"Interim-Update" },
+    { RADIUS_ACCT_STATUS_TYPE_ACCT_ON,		"Accounting-On" },
+    { RADIUS_ACCT_STATUS_TYPE_ACCT_OFF,		"Accounting-Off" },
+    { 0, NULL }
+};
+
+static struct radius_const_str acct_authentic_consts[] = {
+    { RADIUS_ACCT_AUTHENTIC_RADIUS,		"RADIUS" },
+    { RADIUS_ACCT_AUTHENTIC_LOCAL,		"Local" },
+    { RADIUS_ACCT_AUTHENTIC_REMOTE,		"Remote" },
+    { 0, NULL }
+};
+
+static struct radius_const_str terminate_cause_consts[] = {
+    { RADIUS_TERMNATE_CAUSE_USER_REQUEST,	"\"User Request\"" },
+    { RADIUS_TERMNATE_CAUSE_LOST_CARRIER,	"\"Lost Carrier\"" },
+    { RADIUS_TERMNATE_CAUSE_LOST_SERVICE,	"\"Lost Service\"" },
+    { RADIUS_TERMNATE_CAUSE_IDLE_TIMEOUT,	"\"Idle Timeout\"" },
+    { RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT,	"\"Session Timeout\"" },
+    { RADIUS_TERMNATE_CAUSE_ADMIN_RESET,	"\"Admin Reset\"" },
+    { RADIUS_TERMNATE_CAUSE_ADMIN_REBOOT,	"\"Admin Reboot\"" },
+    { RADIUS_TERMNATE_CAUSE_PORT_ERROR,		"\"Port Error\"" },
+    { RADIUS_TERMNATE_CAUSE_NAS_ERROR,		"\"NAS Error\"" },
+    { RADIUS_TERMNATE_CAUSE_NAS_RESET,		"\"NAS Request\"" },
+    { RADIUS_TERMNATE_CAUSE_NAS_REBOOT,		"\"NAS Reboot\"" },
+    { RADIUS_TERMNATE_CAUSE_PORT_UNNEEDED,	"\"Port Unneeded\"" },
+    { RADIUS_TERMNATE_CAUSE_PORT_PREEMPTED,	"\"Port Preempted\"" },
+    { RADIUS_TERMNATE_CAUSE_PORT_SUSPENDED,	"\"Port Suspended\"" },
+    { RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL,	"\"Service Unavailable\"" },
+    { RADIUS_TERMNATE_CAUSE_CALLBACK,		"\"Callback\"" },
+    { RADIUS_TERMNATE_CAUSE_USER_ERROR,		"\"User Error\"" },
+    { RADIUS_TERMNATE_CAUSE_HOST_REQUEST,	"\"Host Request\"" },
+    { 0, NULL }
+};
+
+static struct radius_const_str tunnel_medium_type_consts[] = {
+    { RADIUS_TUNNEL_MEDIUM_TYPE_IPV4,		"IPv4" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_IPV6,		"IPv6" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_NSAP,		"NSAP" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_HDLC,		"HDLC" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_BBN1822,	"BBN1822" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_802,		"802" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_E163,		"E.163" },
+    { RADIUS_TUNNEL_MEDIUM_TYPE_E164,		"E.164" },
+    { 0, NULL }
+};