From: yasuoka Date: Tue, 2 Jul 2024 00:33:51 +0000 (+0000) Subject: Add support for RADIUS accounting. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=747da5e94edd8f4f54b21b349193f5796ebff89d;p=openbsd Add support for RADIUS accounting. --- diff --git a/usr.sbin/radiusd/parse.y b/usr.sbin/radiusd/parse.y index 56a0f7b0aaa..6513ca5c4b2 100644 --- a/usr.sbin/radiusd/parse.y +++ b/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 @@ -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 STRING %token NUMBER -%type optport +%type optport optacct %type listen_addr %type str_l optdeco %type prefix -%type yesno +%type yesno optquick %type strnum %type key %type 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; } ; @@ -476,6 +488,52 @@ authopt : AUTHENTICATE_BY STRING { 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; diff --git a/usr.sbin/radiusd/radiusd.c b/usr.sbin/radiusd/radiusd.c index 821df8f0bb2..e9202ba28f0 100644 --- a/usr.sbin/radiusd/radiusd.c +++ b/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 *listn, 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) @@ -439,9 +447,19 @@ radiusd_listen_handle_packet(struct radiusd_listen *listn, 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 *listn, 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 *listn, 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( @@ -627,6 +693,53 @@ 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_module *module, 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) diff --git a/usr.sbin/radiusd/radiusd.conf.5 b/usr.sbin/radiusd/radiusd.conf.5 index 5d02722c678..4d8604fc17a 100644 --- a/usr.sbin/radiusd/radiusd.conf.5 +++ b/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 configuration file. 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_standard" { authenticate *@local by bsdauth decorate-by strip-realm authenticate * by radius + +account * to standard .Ed .Sh SEE ALSO .Xr radiusd 8 , diff --git a/usr.sbin/radiusd/radiusd.h b/usr.sbin/radiusd/radiusd.h index 6cc56b64a85..3d724176047 100644 --- a/usr.sbin/radiusd/radiusd.h +++ b/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 { diff --git a/usr.sbin/radiusd/radiusd_local.h b/usr.sbin/radiusd/radiusd_local.h index 24e4f74074f..a36373c827c 100644 --- a/usr.sbin/radiusd/radiusd_local.h +++ b/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) diff --git a/usr.sbin/radiusd/radiusd_module.c b/usr.sbin/radiusd/radiusd_module.c index 85236db2cd3..9a2b6360632 100644 --- a/usr.sbin/radiusd/radiusd_module.c +++ b/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 @@ -50,6 +50,8 @@ static void (*module_request_decoration) (void *, u_int, const u_char *, 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_handlers *handler) 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 imsg *imsg) 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 imsg *imsg) 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 imsg *imsg) } 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; } } diff --git a/usr.sbin/radiusd/radiusd_module.h b/usr.sbin/radiusd/radiusd_module.h index 5fb44513fd6..9b3b8476052 100644 --- a/usr.sbin/radiusd/radiusd_module.h +++ b/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 diff --git a/usr.sbin/radiusd/radiusd_standard.8 b/usr.sbin/radiusd/radiusd_standard.8 index a75c9da3e97..d434c7c22c7 100644 --- a/usr.sbin/radiusd/radiusd_standard.8 +++ b/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 diff --git a/usr.sbin/radiusd/radiusd_standard.c b/usr.sbin/radiusd/radiusd_standard.c index 68d5f8e0f95..b925ec4c152 100644 --- a/usr.sbin/radiusd/radiusd_standard.c +++ b/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 #include +#include +#include #include #include @@ -49,11 +51,34 @@ 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; @@ -298,3 +324,286 @@ module_standard_resdeco(void *ctx, u_int q_id, const u_char *req, size_t reqlen, 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 } +};