Add support for RADIUS accounting.
authoryasuoka <yasuoka@openbsd.org>
Tue, 2 Jul 2024 00:33:51 +0000 (00:33 +0000)
committeryasuoka <yasuoka@openbsd.org>
Tue, 2 Jul 2024 00:33:51 +0000 (00:33 +0000)
usr.sbin/radiusd/parse.y
usr.sbin/radiusd/radiusd.c
usr.sbin/radiusd/radiusd.conf.5
usr.sbin/radiusd/radiusd.h
usr.sbin/radiusd/radiusd_local.h
usr.sbin/radiusd/radiusd_module.c
usr.sbin/radiusd/radiusd_module.h
usr.sbin/radiusd/radiusd_standard.8
usr.sbin/radiusd/radiusd_standard.c

index 56a0f7b..6513ca5 100644 (file)
@@ -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; }
                ;
@@ -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;
index 821df8f..e9202ba 100644 (file)
@@ -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)
index 5d02722..4d8604f 100644 (file)
@@ -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 ,
index 6cc56b6..3d72417 100644 (file)
@@ -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 {
index 24e4f74..a36373c 100644 (file)
@@ -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)
index 85236db..9a2b636 100644 (file)
@@ -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_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;
            }
        }
index 5fb4451..9b3b847 100644 (file)
@@ -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
 
index a75c9da..d434c7c 100644 (file)
@@ -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.
 .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
index 68d5f8e..b925ec4 100644 (file)
@@ -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,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 }
+};