Add RADIUS support. Authentication, accounting, and "Dynamic
authoryasuoka <yasuoka@openbsd.org>
Sat, 13 Jul 2024 12:22:46 +0000 (12:22 +0000)
committeryasuoka <yasuoka@openbsd.org>
Sat, 13 Jul 2024 12:22:46 +0000 (12:22 +0000)
Authorization Extensions"(DAE) are supported.
feedback markus stu

ok tobhe

15 files changed:
sbin/iked/Makefile
sbin/iked/config.c
sbin/iked/eap.c
sbin/iked/eap.h
sbin/iked/iked.c
sbin/iked/iked.conf.5
sbin/iked/iked.h
sbin/iked/ikev2.c
sbin/iked/ikev2_msg.c
sbin/iked/ikev2_pld.c
sbin/iked/parse.y
sbin/iked/pfkey.c
sbin/iked/policy.c
sbin/iked/radius.c [new file with mode: 0644]
sbin/iked/types.h

index 2d0f536..783e2eb 100644 (file)
@@ -1,18 +1,18 @@
-# $OpenBSD: Makefile,v 1.22 2021/05/28 18:01:39 tobhe Exp $
+# $OpenBSD: Makefile,v 1.23 2024/07/13 12:22:46 yasuoka Exp $
 
 PROG=          iked
 SRCS=          ca.c chap_ms.c config.c control.c crypto.c dh.c \
                eap.c iked.c ikev2.c ikev2_msg.c ikev2_pld.c \
                log.c ocsp.c pfkey.c policy.c print.c proc.c timer.c util.c \
-               imsg_util.c smult_curve25519_ref.c vroute.c
+               imsg_util.c radius.c smult_curve25519_ref.c vroute.c
 SRCS+=         eap_map.c ikev2_map.c
 SRCS+=         crypto_hash.c sntrup761.c
 SRCS+=         parse.y
 MAN=           iked.conf.5 iked.8
 #NOMAN=                yes
 
-LDADD=         -lutil -levent -lcrypto
-DPADD=         ${LIBUTIL} ${LIBEVENT} ${LIBCRYPTO}
+LDADD=         -lutil -levent -lcrypto -lradius
+DPADD=         ${LIBUTIL} ${LIBEVENT} ${LIBCRYPTO} ${LIBRADIUS}
 CFLAGS+=       -Wall -I${.CURDIR}
 CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+=       -Wmissing-declarations
index ff24c4b..d420450 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: config.c,v 1.97 2024/02/15 19:11:00 tobhe Exp $       */
+/*     $OpenBSD: config.c,v 1.98 2024/07/13 12:22:46 yasuoka Exp $     */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -123,6 +123,8 @@ config_free_sa(struct iked *env, struct iked_sa *sa)
        sa_configure_iface(env, sa, 0);
        sa_free_flows(env, &sa->sa_flows);
 
+       iked_radius_acct_stop(env, sa);
+
        if (sa->sa_addrpool) {
                (void)RB_REMOVE(iked_addrpool, &env->sc_addrpool, sa);
                free(sa->sa_addrpool);
@@ -187,6 +189,10 @@ config_free_sa(struct iked *env, struct iked_sa *sa)
                ikestat_dec(env, ikes_sa_established_current);
        ikestat_inc(env, ikes_sa_removed);
 
+       free(sa->sa_rad_addr);
+       free(sa->sa_rad_addr6);
+       iked_radius_request_free(env, sa->sa_radreq);
+
        free(sa);
 }
 
@@ -591,6 +597,48 @@ config_doreset(struct iked *env, unsigned int mode)
                }
        }
 
+       if (mode == RESET_ALL || mode == RESET_RADIUS) {
+               struct iked_radserver_req       *req;
+               struct iked_radserver           *rad, *radt;
+               struct iked_radcfgmap           *cfg, *cfgt;
+               struct iked_raddae              *dae, *daet;
+               struct iked_radclient           *client, *clientt;
+
+               TAILQ_FOREACH_SAFE(rad, &env->sc_radauthservers, rs_entry,
+                   radt) {
+                       close(rad->rs_sock);
+                       event_del(&rad->rs_ev);
+                       TAILQ_REMOVE(&env->sc_radauthservers, rad, rs_entry);
+                       while ((req = TAILQ_FIRST(&rad->rs_reqs)) != NULL)
+                               iked_radius_request_free(env, req);
+                       freezero(rad, sizeof(*rad));
+               }
+               TAILQ_FOREACH_SAFE(rad, &env->sc_radacctservers, rs_entry,
+                   radt) {
+                       close(rad->rs_sock);
+                       event_del(&rad->rs_ev);
+                       TAILQ_REMOVE(&env->sc_radacctservers, rad, rs_entry);
+                       while ((req = TAILQ_FIRST(&rad->rs_reqs)) != NULL)
+                               iked_radius_request_free(env, req);
+                       freezero(rad, sizeof(*rad));
+               }
+               TAILQ_FOREACH_SAFE(cfg, &env->sc_radcfgmaps, entry, cfgt) {
+                       TAILQ_REMOVE(&env->sc_radcfgmaps, cfg, entry);
+                       free(cfg);
+               }
+               TAILQ_FOREACH_SAFE(dae, &env->sc_raddaes, rd_entry, daet) {
+                       close(dae->rd_sock);
+                       event_del(&dae->rd_ev);
+                       TAILQ_REMOVE(&env->sc_raddaes, dae, rd_entry);
+                       free(dae);
+               }
+               TAILQ_FOREACH_SAFE(client, &env->sc_raddaeclients, rc_entry,
+                   clientt) {
+                       TAILQ_REMOVE(&env->sc_raddaeclients, client, rc_entry);
+                       free(client);
+               }
+       }
+
        return (0);
 }
 
@@ -1092,3 +1140,282 @@ config_getkey(struct iked *env, struct imsg *imsg)
 
        return (0);
 }
+
+int
+config_setradauth(struct iked *env)
+{
+       proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CFG_RADAUTH,
+           &env->sc_radauth, sizeof(env->sc_radauth));
+       return (0);
+}
+
+int
+config_getradauth(struct iked *env, struct imsg *imsg)
+{
+       if (IMSG_DATA_SIZE(imsg) < sizeof(struct iked_radopts))
+               fatalx("%s: invalid radauth message", __func__);
+
+       memcpy(&env->sc_radauth, imsg->data, sizeof(struct iked_radopts));
+
+       return (0);
+}
+
+int
+config_setradacct(struct iked *env)
+{
+       proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CFG_RADACCT,
+           &env->sc_radacct, sizeof(env->sc_radacct));
+       return (0);
+}
+
+int
+config_getradacct(struct iked *env, struct imsg *imsg)
+{
+       if (IMSG_DATA_SIZE(imsg) < sizeof(struct iked_radopts))
+               fatalx("%s: invalid radacct message", __func__);
+
+       memcpy(&env->sc_radacct, imsg->data, sizeof(struct iked_radopts));
+
+       return (0);
+}
+
+int
+config_setradserver(struct iked *env, struct sockaddr *sa, socklen_t salen,
+    char *secret, int isaccounting)
+{
+       int                      sock = -1;
+       struct iovec             iov[2];
+       struct iked_radserver    server;
+
+       if (env->sc_opts & IKED_OPT_NOACTION)
+               return (0);
+       memset(&server, 0, sizeof(server));
+       memcpy(&server.rs_sockaddr, sa, salen);
+       server.rs_accounting = isaccounting;
+       if ((sock = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+               log_warn("%s: socket() failed", __func__);
+               goto error;
+       }
+       if (connect(sock, sa, salen) == -1) {
+               log_warn("%s: connect() failed", __func__);
+               goto error;
+       }
+       iov[0].iov_base = &server;
+       iov[0].iov_len = offsetof(struct iked_radserver, rs_secret[0]);
+       iov[1].iov_base = secret;
+       iov[1].iov_len = strlen(secret) + 1;
+
+       proc_composev_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADSERVER, -1,
+           sock, iov, 2);
+
+       return (0);
+ error:
+       if (sock >= 0)
+               close(sock);
+       return (-1);
+}
+
+int
+config_getradserver(struct iked *env, struct imsg *imsg)
+{
+       size_t                   len;
+       struct iked_radserver   *server;
+
+       len = IMSG_DATA_SIZE(imsg);
+       if (len <= sizeof(*server))
+               fatalx("%s: invalid IMSG_CFG_RADSERVER message", __func__);
+
+       if ((server = calloc(1, len)) == NULL) {
+               log_warn("%s: calloc() failed", __func__);
+               return (-1);
+       }
+       memcpy(server, imsg->data, len);
+       explicit_bzero(imsg->data, len);
+       TAILQ_INIT(&server->rs_reqs);
+       server->rs_sock = imsg_get_fd(imsg);
+       server->rs_env = env;
+
+       if (!server->rs_accounting)
+               TAILQ_INSERT_TAIL(&env->sc_radauthservers, server, rs_entry);
+       else
+               TAILQ_INSERT_TAIL(&env->sc_radacctservers, server, rs_entry);
+       event_set(&server->rs_ev, server->rs_sock, EV_READ | EV_PERSIST,
+           iked_radius_on_event, server);
+       event_add(&server->rs_ev, NULL);
+
+       return (0);
+}
+
+int
+config_setradcfgmap(struct iked *env, int cfg_type, uint32_t vendor_id,
+    uint8_t attr_type)
+{
+       struct iked_radcfgmap cfgmap;
+
+       if (env->sc_opts & IKED_OPT_NOACTION)
+               return (0);
+       memset(&cfgmap, 0, sizeof(cfgmap));
+       cfgmap.cfg_type = cfg_type;
+       cfgmap.vendor_id = vendor_id;
+       cfgmap.attr_type = attr_type;
+
+       proc_compose_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADCFGMAP, -1,
+           -1, &cfgmap, sizeof(cfgmap));
+
+       return (0);
+}
+
+int
+config_getradcfgmap(struct iked *env, struct imsg *imsg)
+{
+       int                      i;
+       size_t                   len;
+       struct iked_radcfgmap   *cfgmap, *cfgmap0;
+       struct iked_radcfgmaps   cfgmaps = TAILQ_HEAD_INITIALIZER(cfgmaps);
+
+       len = IMSG_DATA_SIZE(imsg);
+       if (len < sizeof(*cfgmap))
+               fatalx("%s: invalid IMSG_CFG_RADCFGMAP message", __func__);
+
+       if (TAILQ_EMPTY(&env->sc_radcfgmaps)) {
+               /* no customized config map yet */
+               for (i = 0; radius_cfgmaps[i].cfg_type != 0; i++) {
+                       if ((cfgmap = calloc(1, len)) == NULL) {
+                               while ((cfgmap = TAILQ_FIRST(&cfgmaps))
+                                   != NULL) {
+                                       TAILQ_REMOVE(&cfgmaps, cfgmap, entry);
+                                       free(cfgmap);
+                               }
+                               return (-1);
+                       }
+                       *cfgmap = radius_cfgmaps[i];
+                       TAILQ_INSERT_TAIL(&cfgmaps, cfgmap, entry);
+               }
+               TAILQ_CONCAT(&env->sc_radcfgmaps, &cfgmaps, entry);
+       }
+
+       cfgmap0 = (struct iked_radcfgmap *)imsg->data;
+       TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry) {
+               if (cfgmap->vendor_id == cfgmap0->vendor_id &&
+                   cfgmap->attr_type == cfgmap0->attr_type) {
+                       /* override existing config map */
+                       cfgmap->cfg_type = cfgmap0->cfg_type;
+                       break;
+               }
+       }
+       if (cfgmap == NULL) {
+               if ((cfgmap = calloc(1, len)) == NULL) {
+                       log_warn("%s: calloc() failed", __func__);
+                       return (-1);
+               }
+               memcpy(cfgmap, imsg->data, len);
+               TAILQ_INSERT_TAIL(&env->sc_radcfgmaps, cfgmap, entry);
+       }
+       return (0);
+}
+
+int
+config_setraddae(struct iked *env, struct sockaddr *sa, socklen_t salen)
+{
+       int                      sock, on;
+       struct iked_raddae       dae;
+
+       if (env->sc_opts & IKED_OPT_NOACTION)
+               return (0);
+       memset(&dae, 0, sizeof(dae));
+       memcpy(&dae.rd_sockaddr, sa, salen);
+       if ((sock = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+               log_warn("%s: socket() failed", __func__);
+               goto error;
+       }
+       on = 1;
+       if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
+               log_warn("%s: setsockopt(,,SO_REUSEADDR) failed", __func__);
+       /* REUSEPORT is needed because the old sockets may not be closed yet */
+       on = 1;
+       if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) == -1)
+               log_warn("%s: setsockopt(,,SO_REUSEPORT) failed", __func__);
+       if (bind(sock, sa, salen) == -1) {
+               log_warn("%s: bind() failed", __func__);
+               goto error;
+       }
+
+       proc_compose_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADDAE, -1,
+           sock, &dae, sizeof(dae));
+
+       return (0);
+ error:
+       if (sock >= 0)
+               close(sock);
+       return (-1);
+}
+
+int
+config_getraddae(struct iked *env, struct imsg *imsg)
+{
+       struct iked_raddae      *dae;
+
+       if (IMSG_DATA_SIZE(imsg) < sizeof(*dae))
+               fatalx("%s: invalid IMSG_CFG_RADDAE message", __func__);
+
+       if ((dae = calloc(1, sizeof(*dae))) == NULL) {
+               log_warn("%s: calloc() failed", __func__);
+               return (-1);
+       }
+       memcpy(dae, imsg->data, sizeof(*dae));
+       dae->rd_sock = imsg_get_fd(imsg);
+       dae->rd_env = env;
+
+       event_set(&dae->rd_ev, dae->rd_sock, EV_READ | EV_PERSIST,
+           iked_radius_dae_on_event, dae);
+       event_add(&dae->rd_ev, NULL);
+
+       TAILQ_INSERT_TAIL(&env->sc_raddaes, dae, rd_entry);
+
+       return (0);
+}
+
+int
+config_setradclient(struct iked *env, struct sockaddr *sa, socklen_t salen,
+    char *secret)
+{
+       struct iovec             iov[2];
+       struct iked_radclient    client;
+
+       if (salen > sizeof(client.rc_sockaddr))
+               fatal("%s: invalid salen", __func__);
+
+       memcpy(&client.rc_sockaddr, sa, salen);
+
+       iov[0].iov_base = &client;
+       iov[0].iov_len = offsetof(struct iked_radclient, rc_secret[0]);
+       iov[1].iov_base = secret;
+       iov[1].iov_len = strlen(secret);
+
+       proc_composev_imsg(&env->sc_ps, PROC_IKEV2, -1, IMSG_CFG_RADDAECLIENT,
+           -1, -1, iov, 2);
+
+       return (0);
+}
+
+int
+config_getradclient(struct iked *env, struct imsg *imsg)
+{
+       struct iked_radclient   *client;
+       u_int                    len;
+
+       len = IMSG_DATA_SIZE(imsg);
+
+       if (len < sizeof(*client))
+               fatalx("%s: invalid IMSG_CFG_RADDAE message", __func__);
+
+       if ((client = calloc(1, len + 1)) == NULL) {
+               log_warn("%s: calloc() failed", __func__);
+               return (-1);
+       }
+       memcpy(client, imsg->data, len);
+
+       TAILQ_INSERT_TAIL(&env->sc_raddaeclients, client, rc_entry);
+
+       return (0);
+}
index 40cbe62..24d1a89 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: eap.c,v 1.26 2024/03/24 00:05:01 yasuoka Exp $        */
+/*     $OpenBSD: eap.c,v 1.27 2024/07/13 12:22:46 yasuoka Exp $        */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -583,9 +583,12 @@ eap_parse(struct iked *env, const struct iked_sa *sa, struct iked_message *msg,
 
                return (eap_mschap(env, sa, msg, eap));
        default:
-               log_debug("%s: unsupported EAP type %s", __func__,
-                   print_map(eap->eap_type, eap_type_map));
-               return (-1);
+               if (sa->sa_policy->pol_auth.auth_eap != EAP_TYPE_RADIUS) {
+                       log_debug("%s: unsupported EAP type %s", __func__,
+                           print_map(eap->eap_type, eap_type_map));
+                       return (-1);
+               } /* else, when RADIUS, pass it to the client */
+               break;
        }
 
        return (0);
index 509da7a..ef617c2 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: eap.h,v 1.6 2020/09/16 21:37:35 tobhe Exp $   */
+/*     $OpenBSD: eap.h,v 1.7 2024/07/13 12:22:46 yasuoka Exp $ */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -93,6 +93,7 @@ extern struct iked_constmap eap_code_map[];
 #define EAP_TYPE_PWD           52      /* RFC-harkins-emu-eap-pwd-12.txt */
 #define EAP_TYPE_EXPANDED_TYPE 254     /* RFC3748 */
 #define EAP_TYPE_EXPERIMENTAL  255     /* RFC3748 */
+#define EAP_TYPE_RADIUS                10001   /* internal use for EAP RADIUS */
 
 extern struct iked_constmap eap_type_map[];
 
index 00bd3f6..b69a354 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.c,v 1.70 2024/02/15 20:10:45 tobhe Exp $ */
+/*     $OpenBSD: iked.c,v 1.71 2024/07/13 12:22:46 yasuoka Exp $       */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -307,6 +307,8 @@ parent_configure(struct iked *env)
        config_setstatic(env);
        config_setcoupled(env, env->sc_decoupled ? 0 : 1);
        config_setocsp(env);
+       config_setradauth(env);
+       config_setradacct(env);
        /* Must be last */
        config_setmode(env, env->sc_passive ? 1 : 0);
 
@@ -324,6 +326,7 @@ parent_reload(struct iked *env, int reset, const char *filename)
 
        if (reset == RESET_RELOAD) {
                config_setreset(env, RESET_POLICY, PROC_IKEV2);
+               config_setreset(env, RESET_RADIUS, PROC_IKEV2);
                if (config_setkeys(env) == -1)
                        fatalx("%s: failed to send keys", __func__);
                config_setreset(env, RESET_CA, PROC_CERT);
index 053aee3..753a84e 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: iked.conf.5,v 1.96 2024/04/13 12:11:08 jmc Exp $
+.\" $OpenBSD: iked.conf.5,v 1.97 2024/07/13 12:22:46 yasuoka Exp $
 .\"
 .\" Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org>
 .\" Copyright (c) 2004 Mathieu Sauve-Frankel  All rights reserved.
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: April 13 2024 $
+.Dd $Mdocdate: July 13 2024 $
 .Dt IKED.CONF 5
 .Os
 .Sh NAME
@@ -648,11 +648,18 @@ for more information.
 .Bl -tag -width $domain -compact -offset indent
 .It Ic eap Ar type
 Use EAP to authenticate the initiator.
-The only supported EAP
-.Ar type
-is currently
-.Ar MSCHAP-V2 .
+Currently
+.Ar MSCHAP-V2
+or
+.Ar RADIUS
+is supported for EAP
+.Ar type .
 The responder will use RSA public key authentication.
+To use RADIUS for EAP,
+at least one RADIUS server should be configured.
+See
+.Sx RADIUS
+section for the RADIUS support.
 .It Ic ecdsa256
 Use ECDSA with a 256-bit elliptic curve key and SHA2-256 for authentication.
 .It Ic ecdsa384
@@ -780,6 +787,118 @@ The traffic will be blocked if the specified
 .Ar interface
 does not exist.
 .El
+.Sh RADIUS CONFIGURATION
+.Pp
+The configuration options for RADIUS are as follows:
+.Bl -tag -width xxxx
+.It Ic radius config Oo Ar af Oc Ar option Oo Ar vendor Oc Ar attr
+When the RADIUS authentication succeeded,
+.Xr iked 8
+uses the RADIUS attributes contained the response from the RADIUS server to
+construct IKEv2 configuration payloads (CP).
+This configuration option defines a mapping from a RADIUS attribute to an IKE
+CP with the following parameters:
+.Pp
+.Bl -tag -width "vendor attr" -compact
+.It Op Ar af
+Specify either
+.Ar inet
+or
+.Ar inet6
+for the address family of the IKE CP option.
+.It Ar option
+Specify an IKE CP option.
+Choose from
+.Sx AUTOMATIC KEYING POLICIES
+config options
+.Po
+.Ic address ,
+.Ic netmask ,
+.Ic name-server ,
+.Ic netbios-server ,
+.Ic dhcp-server ,
+and
+.Ic access-server
+.Pc ,
+or use
+.Ic none
+to disable the existing or default mapping.
+.It Ar attr
+For a standard RADIUS attribute,
+specify its Attribute-Type for
+.Ar attr .
+.It Ar vendor Ar attr
+For a vendor specific RADIUS attribute,
+specify its Vendor-ID for
+.Ar vendor
+and the Attribute-Type for
+.Ar attr .
+.El
+.Pp
+By default,
+.Xr iked 8
+uses the following attributes for the options:
+.Bl -column "inet6 netbios-server" "Vendor" "Type" "MS-Secondary-NBNS-Server" \
+-offset "XX"
+.It Em "Option" Ta Em "Vendor" Ta Em "Type" Ta Em "Attribute Name"
+.It Li "inet address" Ta "" Ta "8" Ta "Framed-IP-Address"
+.It Li "inet netmask" Ta "" Ta "9" Ta "Framed-IP-Netmask"
+.It Li "inet name-server" Ta "0x137" Ta "28" Ta "MS-Primary-DNS-Server"
+.It Li "inet name-server" Ta "0x137" Ta "29" Ta "MS-Secondary-DNS-Server"
+.It Li "inet netbios-server" Ta "0x137" Ta "30" Ta "MS-Primary-NBNS-Server"
+.It Li "inet netbios-server" Ta "0x137" Ta "31" Ta "MS-Secondary-NBNS-Server"
+.El
+.It Ic radius Oo Ic accounting Oc Ic server Ar address Oo port Ar number Oc \
+secret Ar secret
+Specify the RADIUS server's IP address and the shared secret with the server.
+For a RADIUS accounting server,
+specify optional
+.Ic accounting
+keyword.
+Optionally specify the port number,
+otherwise the default port number,
+1812 for authentication or
+1813 for accounting,
+is used as the default.
+.It Ic radius Oo Ic accounting Oc Ic max-tries Ar number
+Specify the maximum number of retransmissions for a server.
+.Xr iked 8
+will retransmit 2, 6, 14, 22, 30 seconds after the first transmission
+and subsequent retransmissions will occur every 8 seconds.
+If the number of retransmissions per server reaches this value,
+the current server is marked as failed,
+and the next server is used for subsequent requests.
+For RADIUS accounting requests,
+specify optional
+.Ic accounting
+keyword.
+The default value is 3.
+.It Ic radius Oo Ic accounting Oc Ic max-failovers Ar number
+If a positive number is specified,
+.Xr iked 8
+will failover to the next server when the current server is marked
+.Dq fail .
+This key and value specifies the maximum number of failovers.
+For RADIUS accounting requests,
+specify optional
+.Ic accounting
+keyword.
+The default value is 0.
+.It Ic radius dae listen on Ar address Oo port Ar number Oc
+Specify the local
+.Ar address
+.Xr iked 8
+should listen on for the Dynamic Authorization Extensions
+.Po DAE, RFC 5176 Pc requests,
+Optionally specify a port
+.Ar number,
+the default port number is 3799.
+.It Ic radius dae client Ar address Ic secret Ar secret
+Specify
+.Ar address
+for a DAE client and
+.Ar secret .
+.El
 .Sh PACKET FILTERING
 IPsec traffic appears unencrypted on the
 .Xr enc 4
index 41720d3..5d95dd9 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: iked.h,v 1.230 2024/03/02 16:16:07 tobhe Exp $        */
+/*     $OpenBSD: iked.h,v 1.231 2024/07/13 12:22:46 yasuoka Exp $      */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/tree.h>
 #include <sys/queue.h>
+#include <netinet/in.h>
 #include <arpa/inet.h>
 #include <limits.h>
 #include <imsg.h>
@@ -217,8 +218,8 @@ struct iked_static_id {
 
 struct iked_auth {
        uint8_t         auth_method;
-       uint8_t         auth_eap;                       /* optional EAP */
        uint8_t         auth_length;                    /* zero if EAP */
+       uint16_t        auth_eap;                       /* optional EAP */
        uint8_t         auth_data[IKED_PSK_SIZE];
 };
 
@@ -403,6 +404,15 @@ struct iked_ipcomp {
        uint8_t                          ic_transform;  /* transform */
 };
 
+struct iked_sastats {
+       uint64_t                         sas_ipackets;
+       uint64_t                         sas_opackets;
+       uint64_t                         sas_ibytes;
+       uint64_t                         sas_obytes;
+       uint64_t                         sas_idrops;
+       uint64_t                         sas_odrops;
+};
+
 struct iked_sa {
        struct iked_sahdr                sa_hdr;
        uint32_t                         sa_msgid;      /* Last request rcvd */
@@ -485,6 +495,7 @@ struct iked_sa {
        struct iked_proposals            sa_proposals;  /* SA proposals */
        struct iked_childsas             sa_childsas;   /* IPsec Child SAs */
        struct iked_saflows              sa_flows;      /* IPsec flows */
+       struct iked_sastats              sa_stats;
 
        struct iked_sa                  *sa_nexti;      /* initiated IKE SA */
        struct iked_sa                  *sa_previ;      /* matching back pointer */
@@ -533,6 +544,11 @@ struct iked_sa {
        RB_ENTRY(iked_sa)                sa_addrpool6_entry;    /* pool entries */
        time_t                           sa_last_recvd;
 #define IKED_IKE_SA_LAST_RECVD_TIMEOUT  300            /* 5 minutes */
+       struct timespec                  sa_starttime;
+
+       struct iked_radserver_req       *sa_radreq;
+       struct iked_addr                *sa_rad_addr;   /* requested address */
+       struct iked_addr                *sa_rad_addr6;  /* requested address */
 };
 RB_HEAD(iked_sas, iked_sa);
 RB_HEAD(iked_dstid_sas, iked_sa);
@@ -648,6 +664,7 @@ struct iked_message {
        uint8_t                  msg_transform;
        uint16_t                 msg_flags;
        struct eap_msg           msg_eap;
+       struct ibuf             *msg_eapmsg;
        size_t                   msg_del_spisize;
        size_t                   msg_del_cnt;
        struct ibuf             *msg_del_buf;
@@ -702,6 +719,72 @@ struct iked_user {
 };
 RB_HEAD(iked_users, iked_user);
 
+struct iked_radserver_req;
+
+struct iked_radserver {
+       int                              rs_sock;
+       int                              rs_accounting;
+       struct event                     rs_ev;
+       struct iked                     *rs_env;
+       struct sockaddr_storage          rs_sockaddr;
+       TAILQ_ENTRY(iked_radserver)      rs_entry;
+       struct in_addr                   rs_nas_ipv4;
+       struct in6_addr                  rs_nas_ipv6;
+       unsigned int                     rs_reqseq;
+       TAILQ_HEAD(, iked_radserver_req) rs_reqs;
+       char                             rs_secret[];
+};
+TAILQ_HEAD(iked_radservers, iked_radserver);
+
+struct iked_raddae {
+       int                              rd_sock;
+       struct event                     rd_ev;
+       struct iked                     *rd_env;
+       struct sockaddr_storage          rd_sockaddr;
+       TAILQ_ENTRY(iked_raddae)         rd_entry;
+};
+TAILQ_HEAD(iked_raddaes, iked_raddae);
+
+struct iked_radclient {
+       struct iked                     *rc_env;
+       struct sockaddr_storage          rc_sockaddr;
+       TAILQ_ENTRY(iked_radclient)      rc_entry;
+       char                             rc_secret[];
+};
+TAILQ_HEAD(iked_radclients , iked_radclient);
+
+struct iked_radopts {
+       int                              max_tries;
+       int                              max_failovers;
+};
+
+struct iked_radcfgmap {
+       uint16_t                         cfg_type;
+       uint32_t                         vendor_id;
+       uint8_t                          attr_type;
+       TAILQ_ENTRY(iked_radcfgmap)      entry;
+};
+TAILQ_HEAD(iked_radcfgmaps, iked_radcfgmap);
+
+extern const struct iked_radcfgmap radius_cfgmaps[];
+
+struct iked_radserver_req {
+       struct iked_radserver           *rr_server;
+       struct iked_sa                  *rr_sa;
+       struct iked_timer                rr_timer;
+       int                              rr_reqid;
+       int                              rr_accounting;
+       struct timespec                  rr_accttime;
+       void                            *rr_reqpkt;
+       struct ibuf                     *rr_state;
+       char                            *rr_user;
+       int                              rr_ntry;
+       int                              rr_nfailover;
+       struct iked_cfg                  rr_cfg[IKED_CFG_MAX];
+       unsigned int                     rr_ncfg;
+       TAILQ_ENTRY(iked_radserver_req)  rr_entry;
+};
+
 struct privsep_pipes {
        int                             *pp_pipes[PROC_MAX];
 };
@@ -810,6 +893,14 @@ struct iked {
        struct iked_activesas            sc_activesas;
        struct iked_flows                sc_activeflows;
        struct iked_users                sc_users;
+       struct iked_radopts              sc_radauth;
+       struct iked_radopts              sc_radacct;
+       int                              sc_radaccton;
+       struct iked_radservers           sc_radauthservers;
+       struct iked_radservers           sc_radacctservers;
+       struct iked_radcfgmaps           sc_radcfgmaps;
+       struct iked_raddaes              sc_raddaes;
+       struct iked_radclients           sc_raddaeclients;
 
        struct iked_stats                sc_stats;
 
@@ -941,6 +1032,20 @@ int        config_setkeys(struct iked *);
 int     config_getkey(struct iked *, struct imsg *);
 int     config_setstatic(struct iked *);
 int     config_getstatic(struct iked *, struct imsg *);
+int     config_setradauth(struct iked *);
+int     config_getradauth(struct iked *, struct imsg *);
+int     config_setradacct(struct iked *);
+int     config_getradacct(struct iked *, struct imsg *);
+int     config_setradserver(struct iked *, struct sockaddr *, socklen_t,
+           char *, int);
+int     config_getradserver(struct iked *, struct imsg *);
+int     config_setradcfgmap(struct iked *, int, uint32_t, uint8_t);
+int     config_getradcfgmap(struct iked *, struct imsg *);
+int     config_setraddae(struct iked *, struct sockaddr *, socklen_t);
+int     config_getraddae(struct iked *, struct imsg *);
+int     config_setradclient(struct iked *, struct sockaddr *, socklen_t,
+           char *);
+int     config_getradclient(struct iked *, struct imsg *);
 
 /* policy.c */
 void    policy_init(struct iked *);
@@ -1157,6 +1262,17 @@ int       eap_mschap_challenge(struct iked *, struct iked_sa *, int, int,
 int     eap_mschap_success(struct iked *, struct iked_sa *, int);
 int     eap_challenge_request(struct iked *, struct iked_sa *, int);
 
+/* radius.c */
+int     iked_radius_request(struct iked *, struct iked_sa *,
+           struct iked_message *);
+void    iked_radius_request_free(struct iked *, struct iked_radserver_req *);
+void    iked_radius_on_event(int, short, void *);
+void    iked_radius_acct_on(struct iked *);
+void    iked_radius_acct_off(struct iked *);
+void    iked_radius_acct_start(struct iked *, struct iked_sa *);
+void    iked_radius_acct_stop(struct iked *, struct iked_sa *);
+void    iked_radius_dae_on_event(int, short, void *);
+
 /* pfkey.c */
 int     pfkey_couple(struct iked *, struct iked_sas *, int);
 int     pfkey_flow_add(struct iked *, struct iked_flow *);
index c0add7c..ccbab9d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2.c,v 1.386 2024/03/21 22:08:49 tobhe Exp $       */
+/*     $OpenBSD: ikev2.c,v 1.387 2024/07/13 12:22:46 yasuoka Exp $     */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -36,6 +36,7 @@
 #include <errno.h>
 #include <err.h>
 #include <event.h>
+#include <time.h>
 
 #include <openssl/sha.h>
 #include <openssl/evp.h>
@@ -284,6 +285,7 @@ ikev2_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
                        timer_add(env, &env->sc_inittmr,
                            IKED_INITIATOR_INITIAL);
                }
+               iked_radius_acct_on(env);
                return (0);
        case IMSG_UDP_SOCKET:
                return (config_getsocket(env, imsg, ikev2_msg_cb));
@@ -295,6 +297,18 @@ ikev2_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
                return (config_getflow(env, imsg));
        case IMSG_CFG_USER:
                return (config_getuser(env, imsg));
+       case IMSG_CFG_RADAUTH:
+               return (config_getradauth(env, imsg));
+       case IMSG_CFG_RADACCT:
+               return (config_getradacct(env, imsg));
+       case IMSG_CFG_RADSERVER:
+               return (config_getradserver(env, imsg));
+       case IMSG_CFG_RADCFGMAP:
+               return (config_getradcfgmap(env, imsg));
+       case IMSG_CFG_RADDAE:
+               return (config_getraddae(env, imsg));
+       case IMSG_CFG_RADDAECLIENT:
+               return (config_getradclient(env, imsg));
        case IMSG_COMPILE:
                return (config_getcompile(env));
        case IMSG_CTL_STATIC:
@@ -1782,6 +1796,7 @@ ikev2_init_done(struct iked *env, struct iked_sa *sa)
                ret = ikev2_childsa_enable(env, sa);
        if (ret == 0) {
                sa_state(env, sa, IKEV2_STATE_ESTABLISHED);
+               iked_radius_acct_start(env, sa);
                /* Delete exchange timeout. */
                timer_del(env, &sa->sa_timer);
                ikev2_enable_timer(env, sa);
@@ -2456,7 +2471,7 @@ ikev2_add_cp(struct iked *env, struct iked_sa *sa, int type, struct ibuf *buf)
        struct ikev2_cp         *cp;
        struct ikev2_cfg        *cfg;
        struct iked_cfg         *ikecfg;
-       unsigned int             i;
+       unsigned int             i, rad_ncfg = 0;
        uint32_t                 mask4;
        size_t                   len;
        struct sockaddr_in      *in4;
@@ -2479,8 +2494,15 @@ ikev2_add_cp(struct iked *env, struct iked_sa *sa, int type, struct ibuf *buf)
                return (-1);
        }
 
-       for (i = 0; i < pol->pol_ncfg; i++) {
-               ikecfg = &pol->pol_cfg[i];
+       if (sa->sa_radreq != NULL)
+               rad_ncfg = sa->sa_radreq->rr_ncfg;
+
+       for (i = 0; i < pol->pol_ncfg + rad_ncfg; i++) {
+               if (i < pol->pol_ncfg)
+                       ikecfg = &pol->pol_cfg[i];
+               else
+                       ikecfg = &sa->sa_radreq->rr_cfg[i - pol->pol_ncfg];
+
                if (ikecfg->cfg_action != cp->cp_type)
                        continue;
                /* only return one address in case of multiple pools */
@@ -3857,6 +3879,8 @@ ikev2_resp_ike_eap(struct iked *env, struct iked_sa *sa,
        switch (sa->sa_policy->pol_auth.auth_eap) {
        case EAP_TYPE_MSCHAP_V2:
                return ikev2_resp_ike_eap_mschap(env, sa, msg);
+       case EAP_TYPE_RADIUS:
+               return iked_radius_request(env, sa, msg);
        }
        return -1;
 }
@@ -4012,6 +4036,7 @@ ikev2_resp_ike_auth(struct iked *env, struct iked_sa *sa)
                ret = ikev2_childsa_enable(env, sa);
        if (ret == 0) {
                sa_state(env, sa, IKEV2_STATE_ESTABLISHED);
+               iked_radius_acct_start(env, sa);
                /* Delete exchange timeout. */
                timer_del(env, &sa->sa_timer);
                ikev2_enable_timer(env, sa);
@@ -4746,10 +4771,10 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa)
                nsa->sa_tag = sa->sa_tag;
                sa->sa_tag = NULL;
        }
-       if (sa->sa_eapid) {
-               nsa->sa_eapid = sa->sa_eapid;
-               sa->sa_eapid = NULL;
-       }
+       /* sa_eapid needs to be set on both for radius accounting */
+       if (sa->sa_eapid)
+               nsa->sa_eapid = strdup(sa->sa_eapid);
+
        log_info("%srekeyed as new IKESA %s (enc %s%s%s group %s prf %s)",
            SPI_SA(sa, NULL), print_spi(nsa->sa_hdr.sh_ispi, 8),
            print_xf(nsa->sa_encr->encr_id, cipher_keylength(nsa->sa_encr) -
@@ -4760,6 +4785,8 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa)
            print_xf(nsa->sa_dhgroup->id, 0, groupxfs),
            print_xf(nsa->sa_prf->hash_id, hash_keylength(sa->sa_prf), prfxfs));
        sa_state(env, nsa, IKEV2_STATE_ESTABLISHED);
+       clock_gettime(CLOCK_MONOTONIC, &nsa->sa_starttime);
+       iked_radius_acct_start(env, nsa);
        ikev2_enable_timer(env, nsa);
 
        ikestat_inc(env, ikes_sa_rekeyed);
@@ -7028,6 +7055,7 @@ ikev2_cp_setaddr(struct iked *env, struct iked_sa *sa, sa_family_t family)
        const char              *errstr = NULL;
        int                      ret, pass, passes;
        size_t                   i;
+       struct sockaddr_in      *in4;
 
        switch (family) {
        case AF_INET:
@@ -7045,8 +7073,23 @@ ikev2_cp_setaddr(struct iked *env, struct iked_sa *sa, sa_family_t family)
                return (0);
        /* default if no pool configured */
        ret = 0;
+
+       /* handle the special addresses from RADIUS */
+       if (sa->sa_rad_addr != NULL) {
+               in4 = (struct sockaddr_in *)&sa->sa_rad_addr->addr;
+               /* 0xFFFFFFFF allows the user to select an address (RFC 2865) */
+               if (in4->sin_addr.s_addr == htonl(0xFFFFFFFF))
+                       ;/* this is  default behavior if the user selects */
+               /* 0xFFFFFFFE indicated the NAS should select (RFC 2865) */
+               else if (in4->sin_addr.s_addr == htonl(0xFFFFFFFE)) {
+                       free(sa->sa_cp_addr);
+                       sa->sa_cp_addr = NULL;
+               }
+       }
+
        /* two passes if client requests from specific pool */
-       passes = (sa->sa_cp_addr != NULL || sa->sa_cp_addr6 != NULL) ? 2 : 1;
+       passes = (sa->sa_cp_addr != NULL || sa->sa_cp_addr6 != NULL ||
+           sa->sa_rad_addr != NULL || sa->sa_rad_addr6 != NULL) ? 2 : 1;
        for (pass = 0; pass < passes; pass++) {
                /* loop over all address pool configs (addr_net) */
                for (i = 0; i < pol->pol_ncfg; i++) {
@@ -7062,13 +7105,16 @@ ikev2_cp_setaddr(struct iked *env, struct iked_sa *sa, sa_family_t family)
                                        return (0);
                        }
                }
-               if (sa->sa_cp_addr != NULL) {
+               if (family == AF_INET) {
                        free(sa->sa_cp_addr);
                        sa->sa_cp_addr = NULL;
-               }
-               if (sa->sa_cp_addr6 != NULL) {
+                       free(sa->sa_rad_addr);
+                       sa->sa_rad_addr = NULL;
+               } else {
                        free(sa->sa_cp_addr6);
                        sa->sa_cp_addr6 = NULL;
+                       free(sa->sa_rad_addr6);
+                       sa->sa_rad_addr6 = NULL;
                }
        }
 
@@ -7088,7 +7134,7 @@ ikev2_cp_setaddr_pool(struct iked *env, struct iked_sa *sa,
        char                     idstr[IKED_ID_SIZE];
        struct iked_addr         addr;
        uint32_t                 mask, host, lower, upper, start, nhost;
-       int                      requested = 0;
+       int                      requested = 0, rad_requested = 0;
 
        /*
         * failure: pool configured, but not requested.
@@ -7165,8 +7211,14 @@ ikev2_cp_setaddr_pool(struct iked *env, struct iked_sa *sa,
        case AF_INET:
                cfg4 = (struct sockaddr_in *)&ikecfg->cfg.address.addr;
                mask = prefixlen2mask(ikecfg->cfg.address.addr_mask);
-               if (sa->sa_cp_addr != NULL) {
-                       memcpy(&addr, sa->sa_cp_addr, sizeof(addr));
+               if (sa->sa_cp_addr != NULL || sa->sa_rad_addr != NULL) {
+                       if (sa->sa_rad_addr != NULL) {
+                               rad_requested = 1;
+                               memcpy(&addr, sa->sa_rad_addr, sizeof(addr));
+                       } else {
+                               requested = 1;
+                               memcpy(&addr, sa->sa_cp_addr, sizeof(addr));
+                       }
                        key.sa_addrpool = &addr;
                        in4 = (struct sockaddr_in *)&addr.addr;
                        if ((in4->sin_addr.s_addr & mask) !=
@@ -7179,10 +7231,16 @@ ikev2_cp_setaddr_pool(struct iked *env, struct iked_sa *sa,
                                *errstr = "requested addr in use";
                                return (-1);
                        }
-                       sa->sa_addrpool = sa->sa_cp_addr;
-                       sa->sa_cp_addr = NULL;
+                       if (sa->sa_rad_addr != NULL) {
+                               sa->sa_addrpool = sa->sa_rad_addr;
+                               sa->sa_rad_addr = NULL;
+                       } else {
+                               sa->sa_addrpool = sa->sa_cp_addr;
+                               sa->sa_cp_addr = NULL;
+                       }
+                       free(sa->sa_cp_addr);
+                       free(sa->sa_rad_addr);
                        RB_INSERT(iked_addrpool, &env->sc_addrpool, sa);
-                       requested = 1;
                        goto done;
                }
                in4 = (struct sockaddr_in *)&addr.addr;
@@ -7194,7 +7252,7 @@ ikev2_cp_setaddr_pool(struct iked *env, struct iked_sa *sa,
        case AF_INET6:
                cfg6 = (struct sockaddr_in6 *)&ikecfg->cfg.address.addr;
                in6 = (struct sockaddr_in6 *)&addr.addr;
-               if (sa->sa_cp_addr6 != NULL) {
+               if (sa->sa_cp_addr6 != NULL || sa->sa_rad_addr6 != NULL) {
                        /* XXX not yet supported */
                }
                in6->sin6_family = AF_INET6;
@@ -7280,9 +7338,10 @@ ikev2_cp_setaddr_pool(struct iked *env, struct iked_sa *sa,
  done:
        if (ikev2_print_id(IKESA_DSTID(sa), idstr, sizeof(idstr)) == -1)
                bzero(idstr, sizeof(idstr));
-       log_info("%sassigned address %s to %s%s", SPI_SA(sa, NULL),
+       log_info("%sassigned address %s to %s%s%s", SPI_SA(sa, NULL),
            print_addr(&addr.addr),
-           idstr, requested ? " (requested by peer)" : "");
+           idstr, requested ? " (requested by peer)" : "",
+           rad_requested? "(requested by RADIUS)" : "");
        return (0);
 }
 
@@ -7628,6 +7687,8 @@ ikev2_log_established(struct iked_sa *sa)
 {
        char dstid[IKED_ID_SIZE], srcid[IKED_ID_SIZE];
 
+       clock_gettime(CLOCK_MONOTONIC, &sa->sa_starttime);
+
        if (ikev2_print_id(IKESA_DSTID(sa), dstid, sizeof(dstid)) == -1)
                bzero(dstid, sizeof(dstid));
        if (ikev2_print_id(IKESA_SRCID(sa), srcid, sizeof(srcid)) == -1)
index 15ef282..12d801f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_msg.c,v 1.101 2024/03/02 16:16:07 tobhe Exp $   */
+/*     $OpenBSD: ikev2_msg.c,v 1.102 2024/07/13 12:22:46 yasuoka Exp $ */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -203,6 +203,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                ibuf_free(msg->msg_cookie);
                ibuf_free(msg->msg_cookie2);
                ibuf_free(msg->msg_del_buf);
+               ibuf_free(msg->msg_eapmsg);
                free(msg->msg_eap.eam_user);
                free(msg->msg_cp_addr);
                free(msg->msg_cp_addr6);
@@ -219,6 +220,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message *msg)
                msg->msg_cookie = NULL;
                msg->msg_cookie2 = NULL;
                msg->msg_del_buf = NULL;
+               msg->msg_eapmsg = NULL;
                msg->msg_eap.eam_user = NULL;
                msg->msg_cp_addr = NULL;
                msg->msg_cp_addr6 = NULL;
index 2760503..ac8635e 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ikev2_pld.c,v 1.135 2024/04/02 19:58:28 tobhe Exp $   */
+/*     $OpenBSD: ikev2_pld.c,v 1.136 2024/07/13 12:22:46 yasuoka Exp $ */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -2104,6 +2104,15 @@ ikev2_pld_eap(struct iked *env, struct ikev2_payload *pld,
 
                if (eap_parse(env, sa, msg, eap, msg->msg_response) == -1)
                        return (-1);
+               if (msg->msg_parent->msg_eapmsg != NULL) {
+                       log_info("%s: duplicate EAP in payload", __func__);
+                       return (-1);
+               }
+               if ((msg->msg_parent->msg_eapmsg = ibuf_new(eap, eap_len))
+                   == NULL) {
+                       log_debug("%s: failed to save eap", __func__);
+                       return (-1);
+               }
                msg->msg_parent->msg_eap.eam_found = 1;
        }
 
index 613eb8f..970a9e3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.146 2024/04/25 14:24:54 jsg Exp $ */
+/*     $OpenBSD: parse.y,v 1.147 2024/07/13 12:22:46 yasuoka Exp $     */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
 #include <errno.h>
 #include <fcntl.h>
 #include <ifaddrs.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <netdb.h>
+#include <radius.h>
 #include <stdarg.h>
+#include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -107,6 +110,8 @@ static char         *ocsp_url = NULL;
 static long             ocsp_tolerate = 0;
 static long             ocsp_maxage = -1;
 static int              cert_partial_chain = 0;
+static struct iked_radopts
+                        radauth, radacct;
 
 struct iked_transform ikev2_default_ike_transforms[] = {
        { IKEV2_XFORMTYPE_ENCR, IKEV2_XFORMENCR_AES_CBC, 256 },
@@ -394,6 +399,8 @@ static int           expand_flows(struct iked_policy *, int, struct ipsec_addr_wrap *,
                            struct ipsec_addr_wrap *);
 static struct ipsec_addr_wrap *
                         expand_keyword(struct ipsec_addr_wrap *);
+struct iked_radserver *
+                        create_radserver(const char *, u_short, const char *);
 
 struct ipsec_transforms *ipsec_transforms;
 struct ipsec_filters *ipsec_filters;
@@ -407,6 +414,7 @@ typedef struct {
                uint8_t                  ikemode;
                uint8_t                  dir;
                uint8_t                  satype;
+               uint8_t                  accounting;
                char                    *string;
                uint16_t                 port;
                struct ipsec_hosts      *hosts;
@@ -427,6 +435,10 @@ typedef struct {
                struct ipsec_transforms *transforms;
                struct ipsec_filters    *filters;
                struct ipsec_mode       *mode;
+               struct {
+                       uint32_t         vendorid;
+                       uint8_t          attrtype;
+               } radattr;
        } v;
        int lineno;
 } YYSTYPE;
@@ -446,6 +458,8 @@ typedef struct {
 %token TOLERATE MAXAGE DYNAMIC
 %token CERTPARTIALCHAIN
 %token REQUEST IFACE
+%token RADIUS ACCOUNTING SERVER SECRET MAX_TRIES MAX_FAILOVERS
+%token CLIENT DAE LISTEN ON
 %token <v.string>              STRING
 %token <v.number>              NUMBER
 %type  <v.string>              string
@@ -453,7 +467,7 @@ typedef struct {
 %type  <v.proto>               proto proto_list protoval
 %type  <v.hosts>               hosts hosts_list
 %type  <v.port>                port
-%type  <v.number>              portval af rdomain
+%type  <v.number>              portval af rdomain hexdecnumber
 %type  <v.peers>               peers
 %type  <v.anyhost>             anyhost
 %type  <v.host>                host host_spec
@@ -470,6 +484,8 @@ typedef struct {
 %type  <v.string>              name iface
 %type  <v.cfg>                 cfg ikecfg ikecfgvals
 %type  <v.string>              transform_esn
+%type  <v.accounting>          accounting
+%type  <v.radattr>             radattr
 %%
 
 grammar                : /* empty */
@@ -478,6 +494,7 @@ grammar             : /* empty */
                | grammar set '\n'
                | grammar user '\n'
                | grammar ikev2rule '\n'
+               | grammar radius '\n'
                | grammar varset '\n'
                | grammar otherrule skipline '\n'
                | grammar error '\n'            { file->errors++; }
@@ -1039,6 +1056,11 @@ ikeauth          : /* empty */                   {
                        $$.auth_eap = 0;
                        explicit_bzero(&$2, sizeof($2));
                }
+               | EAP RADIUS                    {
+                       $$.auth_method = IKEV2_AUTH_SIG_ANY;
+                       $$.auth_eap = EAP_TYPE_RADIUS;
+                       $$.auth_length = 0;
+               }
                | EAP STRING                    {
                        unsigned int i;
 
@@ -1046,7 +1068,11 @@ ikeauth          : /* empty */                   {
                                if ($2[i] == '-')
                                        $2[i] = '_';
 
-                       if (strcasecmp("mschap_v2", $2) != 0) {
+                       if (strcasecmp("mschap_v2", $2) == 0)
+                               $$.auth_eap = EAP_TYPE_MSCHAP_V2;
+                       else if (strcasecmp("radius", $2) == 0)
+                               $$.auth_eap = EAP_TYPE_RADIUS;
+                       else {
                                yyerror("unsupported EAP method: %s", $2);
                                free($2);
                                YYERROR;
@@ -1054,7 +1080,6 @@ ikeauth           : /* empty */                   {
                        free($2);
 
                        $$.auth_method = IKEV2_AUTH_SIG_ANY;
-                       $$.auth_eap = EAP_TYPE_MSCHAP_V2;
                        $$.auth_length = 0;
                }
                | STRING                        {
@@ -1245,6 +1270,202 @@ string          : string STRING
                | STRING
                ;
 
+radius         : RADIUS accounting SERVER STRING port SECRET STRING
+               {
+                       int              ret, gai_err;
+                       struct addrinfo  hints, *ai;
+                       u_short          port;
+
+                       memset(&hints, 0, sizeof(hints));
+                       hints.ai_family = PF_UNSPEC;
+                       hints.ai_socktype = SOCK_DGRAM;
+                       hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+                       if ((gai_err = getaddrinfo($4, NULL, &hints, &ai))
+                           != 0) {
+                               yyerror("could not parse the address: %s: %s",
+                                   $4, gai_strerror(gai_err));
+                               free($4);
+                               explicit_bzero($7, strlen($7));
+                               free($7);
+                               YYERROR;
+                       }
+                       port = $5;
+                       if (port == 0)
+                               port = htons((!$2)? RADIUS_DEFAULT_PORT :
+                                   RADIUS_ACCT_DEFAULT_PORT);
+                       socket_af(ai->ai_addr, port);
+                       if ((ret = config_setradserver(env, ai->ai_addr,
+                           ai->ai_addrlen, $7, $2)) != 0) {
+                               yyerror("could not set radius server");
+                               free($4);
+                               explicit_bzero($7, strlen($7));
+                               free($7);
+                               YYERROR;
+                       }
+                       explicit_bzero($7, strlen($7));
+                       freeaddrinfo(ai);
+                       free($4);
+                       free($7);
+               }
+               | RADIUS accounting MAX_TRIES NUMBER {
+                       if ($4 <= 0) {
+                               yyerror("max-tries must a positive value");
+                               YYERROR;
+                       }
+                       if ($2)
+                               radacct.max_tries = $4;
+                       else
+                               radauth.max_tries = $4;
+               }
+               | RADIUS accounting MAX_FAILOVERS NUMBER {
+                       if ($4 < 0) {
+                               yyerror("max-failovers must be 0 or a "
+                                   "positive value");
+                               YYERROR;
+                       }
+                       if ($2)
+                               radacct.max_failovers = $4;
+                       else
+                               radauth.max_failovers = $4;
+               }
+               | RADIUS CONFIG af STRING radattr {
+                       const struct ipsec_xf   *xf;
+                       int                      af, cfgtype;
+
+                       af = $3;
+                       if (af == AF_UNSPEC)
+                               af = AF_INET;
+                       if (strcmp($4, "none") == 0)
+                               cfgtype = 0;
+                       else {
+                               if ((xf = parse_xf($4, af, cpxfs)) == NULL ||
+                                   xf->id == IKEV2_CFG_INTERNAL_IP4_SUBNET ||
+                                   xf->id == IKEV2_CFG_INTERNAL_IP6_SUBNET) {
+                                       yyerror("not a valid ikecfg option");
+                                       free($4);
+                                       YYERROR;
+                               }
+                               cfgtype = xf->id;
+                       }
+                       free($4);
+                       config_setradcfgmap(env, cfgtype, $5.vendorid,
+                           $5.attrtype);
+               }
+               | RADIUS DAE LISTEN ON STRING port {
+                       int              ret, gai_err;
+                       struct addrinfo  hints, *ai;
+                       u_short          port;
+
+                       memset(&hints, 0, sizeof(hints));
+                       hints.ai_family = PF_UNSPEC;
+                       hints.ai_socktype = SOCK_DGRAM;
+                       hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+                       if ((gai_err = getaddrinfo($5, NULL, &hints, &ai))
+                           != 0) {
+                               yyerror("could not parse the address: %s: %s",
+                                   $5, gai_strerror(gai_err));
+                               free($5);
+                               YYERROR;
+                       }
+                       port = $6;
+                       if (port == 0)
+                               port = htons(RADIUS_DAE_DEFAULT_PORT);
+                       socket_af(ai->ai_addr, port);
+                       if ((ret = config_setraddae(env, ai->ai_addr,
+                           ai->ai_addrlen)) != 0) {
+                               yyerror("could not set radius server");
+                               free($5);
+                               YYERROR;
+                       }
+                       freeaddrinfo(ai);
+                       free($5);
+               }
+               | RADIUS DAE CLIENT STRING SECRET STRING {
+                       int              gai_err;
+                       struct addrinfo  hints, *ai;
+
+                       memset(&hints, 0, sizeof(hints));
+                       hints.ai_family = PF_UNSPEC;
+                       hints.ai_socktype = SOCK_DGRAM;
+                       hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+                       if ((gai_err = getaddrinfo($4, NULL, &hints, &ai))
+                           != 0) {
+                               yyerror("could not parse the address: %s: %s",
+                                   $4, gai_strerror(gai_err));
+                               free($4);
+                               explicit_bzero($6, strlen($6));
+                               free($6);
+                               YYERROR;
+                       }
+                       config_setradclient(env, ai->ai_addr, ai->ai_addrlen,
+                           $6);
+                       free($4);
+                       explicit_bzero($6, strlen($6));
+                       free($6);
+                       freeaddrinfo(ai);
+               }
+               ;
+
+radattr                : hexdecnumber hexdecnumber {
+                       if ($1 < 0 || 0xffffffL < $1) {
+                               yyerror("vendor-id must be in 0-0xffffff");
+                               YYERROR;
+                       }
+                       if ($2 < 0 || 256 <= $2) {
+                               yyerror("attribute type must be in 0-255");
+                               YYERROR;
+                       }
+                       $$.vendorid = $1;
+                       $$.attrtype = $2;
+               }
+               | hexdecnumber {
+                       if ($1 < 0 || 256 <= $1) {
+                               yyerror("attribute type must be in 0-255");
+                               YYERROR;
+                       }
+                       $$.vendorid = 0;
+                       $$.attrtype = $1;
+               }
+
+hexdecnumber   : STRING {
+                       const char      *errstr;
+                       char            *ep;
+                       uintmax_t        ul;
+
+                       if ($1[0] == '0' && $1[1] == 'x' && isxdigit($1[2])) {
+                               ul = strtoumax($1 + 2, &ep, 16);
+                               if (*ep != '\0') {
+                                       yyerror("`%s' is not a number", $1);
+                                       free($1);
+                                       YYERROR;
+                               }
+                               if (ul == UINTMAX_MAX || ul > UINT64_MAX) {
+                                       yyerror("`%s' is out-of-range", $1);
+                                       free($1);
+                                       YYERROR;
+                               }
+                               $$ = ul;
+                       } else {
+                               $$ = strtonum($1, 0, UINT64_MAX, &errstr);
+                               if (errstr != NULL) {
+                                       yyerror("`%s' is %s", $1, errstr);
+                                       free($1);
+                                       YYERROR;
+                               }
+                       }
+                       free($1);
+               }
+               | NUMBER
+               ;
+
+accounting     : {
+                       $$ = 0;
+               }
+               | ACCOUNTING {
+                       $$ = 1;
+               }
+               ;
+
 varset         : STRING '=' string
                {
                        char *s = $1;
@@ -1336,6 +1557,7 @@ lookup(char *s)
 {
        /* this has to be sorted always */
        static const struct keywords keywords[] = {
+               { "accounting",         ACCOUNTING },
                { "active",             ACTIVE },
                { "ah",                 AH },
                { "any",                ANY },
@@ -1343,8 +1565,10 @@ lookup(char *s)
                { "bytes",              BYTES },
                { "cert_partial_chain", CERTPARTIALCHAIN },
                { "childsa",            CHILDSA },
+               { "client",             CLIENT },
                { "config",             CONFIG },
                { "couple",             COUPLE },
+               { "dae",                DAE },
                { "decouple",           DECOUPLE },
                { "default",            DEFAULT },
                { "dpd_check_interval", DPD_CHECK_INTERVAL },
@@ -1370,7 +1594,10 @@ lookup(char *s)
                { "inet6",              INET6 },
                { "ipcomp",             IPCOMP },
                { "lifetime",           LIFETIME },
+               { "listen",             LISTEN },
                { "local",              LOCAL },
+               { "max-failovers",      MAX_FAILOVERS},
+               { "max-tries",          MAX_TRIES },
                { "maxage",             MAXAGE },
                { "mobike",             MOBIKE },
                { "name",               NAME },
@@ -1381,6 +1608,7 @@ lookup(char *s)
                { "nostickyaddress",    NOSTICKYADDRESS },
                { "novendorid",         NOVENDORID },
                { "ocsp",               OCSP },
+               { "on",                 ON },
                { "passive",            PASSIVE },
                { "peer",               PEER },
                { "port",               PORT },
@@ -1388,9 +1616,12 @@ lookup(char *s)
                { "proto",              PROTO },
                { "psk",                PSK },
                { "quick",              QUICK },
+               { "radius",             RADIUS },
                { "rdomain",            RDOMAIN },
                { "request",            REQUEST },
                { "sa",                 SA },
+               { "secret",             SECRET },
+               { "server",             SERVER },
                { "set",                SET },
                { "skip",               SKIP },
                { "srcid",              SRCID },
@@ -1792,6 +2023,10 @@ parse_config(const char *filename, struct iked *x_env)
        dpd_interval = IKED_IKE_SA_ALIVE_TIMEOUT;
        decouple = passive = 0;
        ocsp_url = NULL;
+       radauth.max_tries = 3;
+       radauth.max_failovers = 0;
+       radacct.max_tries = 3;
+       radacct.max_failovers = 0;
 
        if (env->sc_opts & IKED_OPT_PASSIVE)
                passive = 1;
@@ -1812,6 +2047,8 @@ parse_config(const char *filename, struct iked *x_env)
        env->sc_ocsp_maxage = ocsp_maxage;
        env->sc_cert_partial_chain = cert_partial_chain;
        env->sc_vendorid = vendorid;
+       env->sc_radauth = radauth;
+       env->sc_radacct = radacct;
 
        if (!rules)
                log_warnx("%s: no valid configuration rules found",
index ac9e79c..02b6cd8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pfkey.c,v 1.84 2023/08/14 12:02:02 tobhe Exp $        */
+/*     $OpenBSD: pfkey.c,v 1.85 2024/07/13 12:22:46 yasuoka Exp $      */
 
 /*
  * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
@@ -111,8 +111,11 @@ int        pfkey_write(struct iked *, struct sadb_msg *, struct iovec *, int,
            uint8_t **, ssize_t *);
 int    pfkey_reply(int, uint8_t **, ssize_t *);
 void   pfkey_dispatch(int, short, void *);
-int    pfkey_sa_lookup(struct iked *, struct iked_childsa *, uint64_t *);
+int    pfkey_sa_lookup(struct iked *, struct iked_childsa *, uint64_t *,
+           struct iked_sastats *);
 int    pfkey_sa_check_exists(struct iked *, struct iked_childsa *);
+int    pfkey_sa_sastats(struct iked *, struct iked_childsa *,
+           struct iked_sastats *);
 
 struct sadb_ident *
        pfkey_id2ident(struct iked_id *, unsigned int);
@@ -872,7 +875,8 @@ pfkey_sa(struct iked *env, uint8_t satype, uint8_t action, struct iked_childsa *
 }
 
 int
-pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
+pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used,
+    struct iked_sastats *stats)
 {
        struct iked_policy      *pol = sa->csa_ikesa->sa_policy;
        struct sadb_msg         *msg, smsg;
@@ -880,6 +884,7 @@ pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
        struct sadb_sa           sadb;
        struct sadb_x_rdomain    sa_rdomain;
        struct sadb_lifetime    *sa_life;
+       struct sadb_x_counter   *sa_counter;
        struct sockaddr_storage  ssrc, sdst;
        struct iovec             iov[IOV_CNT];
        uint64_t                 pad = 0;
@@ -1012,6 +1017,20 @@ pfkey_sa_lookup(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
                *last_used = sa_life->sadb_lifetime_usetime;
                log_debug("%s: last_used %llu", __func__, *last_used);
        }
+       if (stats) {
+               if ((sa_counter = pfkey_find_ext(data, n,
+                   SADB_X_EXT_COUNTER)) == NULL) {
+                       /* has never been used */
+                       ret = -1;
+                       goto done;
+               }
+               stats->sas_ibytes = sa_counter->sadb_x_counter_ibytes;
+               stats->sas_obytes = sa_counter->sadb_x_counter_obytes;
+               stats->sas_ipackets = sa_counter->sadb_x_counter_ipackets;
+               stats->sas_opackets = sa_counter->sadb_x_counter_opackets;
+               stats->sas_idrops = sa_counter->sadb_x_counter_idrops;
+               stats->sas_odrops = sa_counter->sadb_x_counter_odrops;
+       }
 
 #undef PAD
 done:
@@ -1022,13 +1041,20 @@ done:
 int
 pfkey_sa_last_used(struct iked *env, struct iked_childsa *sa, uint64_t *last_used)
 {
-       return pfkey_sa_lookup(env, sa, last_used);
+       return pfkey_sa_lookup(env, sa, last_used, NULL);
 }
 
 int
 pfkey_sa_check_exists(struct iked *env, struct iked_childsa *sa)
 {
-       return pfkey_sa_lookup(env, sa, NULL);
+       return pfkey_sa_lookup(env, sa, NULL, NULL);
+}
+
+int
+pfkey_sa_sastats(struct iked *env, struct iked_childsa *sa,
+    struct iked_sastats *stats)
+{
+       return pfkey_sa_lookup(env, sa, NULL, stats);
 }
 
 int
@@ -1582,7 +1608,8 @@ pfkey_sa_update_addresses(struct iked *env, struct iked_childsa *sa)
 int
 pfkey_sa_delete(struct iked *env, struct iked_childsa *sa)
 {
-       uint8_t         satype;
+       uint8_t                 satype;
+       struct iked_sastats     sas;
 
        if (!sa->csa_loaded || sa->csa_spi.spi == 0)
                return (0);
@@ -1590,11 +1617,23 @@ pfkey_sa_delete(struct iked *env, struct iked_childsa *sa)
        if (pfkey_map(pfkey_satype, sa->csa_saproto, &satype) == -1)
                return (-1);
 
+       /* preserve the statistics */
+       memset(&sas, 0, sizeof(sas));
+       pfkey_sa_sastats(env, sa, &sas);
+
        if (pfkey_sa(env, satype, SADB_DELETE, sa) == -1 &&
            pfkey_sa_check_exists(env, sa) == 0)
                return (-1);
 
        sa->csa_loaded = 0;
+
+       sa->csa_ikesa->sa_stats.sas_ipackets += sas.sas_ipackets;
+       sa->csa_ikesa->sa_stats.sas_opackets += sas.sas_opackets;
+       sa->csa_ikesa->sa_stats.sas_ibytes += sas.sas_ibytes;
+       sa->csa_ikesa->sa_stats.sas_obytes += sas.sas_obytes;
+       sa->csa_ikesa->sa_stats.sas_idrops += sas.sas_idrops;
+       sa->csa_ikesa->sa_stats.sas_odrops += sas.sas_odrops;
+
        return (0);
 }
 
index a9e783c..836a454 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: policy.c,v 1.98 2024/02/03 00:54:14 jsg Exp $ */
+/*     $OpenBSD: policy.c,v 1.99 2024/07/13 12:22:46 yasuoka Exp $     */
 
 /*
  * Copyright (c) 2020-2021 Tobias Heider <tobhe@openbsd.org>
@@ -60,6 +60,11 @@ policy_init(struct iked *env)
 {
        TAILQ_INIT(&env->sc_policies);
        TAILQ_INIT(&env->sc_ocsp);
+       TAILQ_INIT(&env->sc_radauthservers);
+       TAILQ_INIT(&env->sc_radacctservers);
+       TAILQ_INIT(&env->sc_radcfgmaps);
+       TAILQ_INIT(&env->sc_raddaes);
+       TAILQ_INIT(&env->sc_raddaeclients);
        RB_INIT(&env->sc_users);
        RB_INIT(&env->sc_sas);
        RB_INIT(&env->sc_dstid_sas);
diff --git a/sbin/iked/radius.c b/sbin/iked/radius.c
new file mode 100644 (file)
index 0000000..a9e7d3a
--- /dev/null
@@ -0,0 +1,1871 @@
+/*     $OpenBSD: radius.c,v 1.3 2024/07/13 12:22:46 yasuoka Exp $      */
+
+/*
+ * Copyright (c) 2024 Internet Initiative Japan Inc.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <netinet/ip_ipsp.h>
+
+#include <endian.h>
+#include <event.h>
+#include <errno.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <radius.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+
+#include "iked.h"
+#include "eap.h"
+#include "ikev2.h"
+#include "types.h"
+
+void    iked_radius_request_send(struct iked *, void *);
+void    iked_radius_fill_attributes(struct iked_sa *, RADIUS_PACKET *);
+void    iked_radius_config(struct iked_radserver_req *, const RADIUS_PACKET *,
+           int, uint32_t, uint8_t);
+void    iked_radius_acct_request(struct iked *, struct iked_sa *, uint8_t);
+
+const struct iked_radcfgmap radius_cfgmaps[] = {
+    { IKEV2_CFG_INTERNAL_IP4_ADDRESS, 0, RADIUS_TYPE_FRAMED_IP_ADDRESS },
+    { IKEV2_CFG_INTERNAL_IP4_NETMASK, 0, RADIUS_TYPE_FRAMED_IP_NETMASK },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_PRIMARY_NBNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER },
+    { 0 }
+};
+
+int
+iked_radius_request(struct iked *env, struct iked_sa *sa,
+    struct iked_message *msg)
+{
+       struct eap_message              *eap;
+       RADIUS_PACKET                   *pkt;
+       size_t                           len;
+
+       eap = ibuf_data(msg->msg_eapmsg);
+       len = betoh16(eap->eap_length);
+       if (eap->eap_code != EAP_CODE_RESPONSE) {
+               log_debug("%s: eap_code is not response %u", __func__,
+                   (unsigned)eap->eap_code);
+               return -1;
+       }
+
+       if (eap->eap_type == EAP_TYPE_IDENTITY) {
+               if ((sa->sa_radreq = calloc(1,
+                   sizeof(struct iked_radserver_req))) == NULL) {
+                       log_debug(
+                           "%s: calloc failed for iked_radserver_req: %s",
+                           __func__, strerror(errno));
+                       return (-1);
+               }
+               timer_set(env, &sa->sa_radreq->rr_timer,
+                   iked_radius_request_send, sa->sa_radreq);
+               sa->sa_radreq->rr_user = strdup(msg->msg_eap.eam_identity);
+       }
+
+       if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST))
+           == NULL) {
+               log_debug("%s: radius_new_request_packet failed %s", __func__,
+                   strerror(errno));
+               return -1;
+       }
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+           sa->sa_radreq->rr_user);
+       if (sa->sa_radreq->rr_state != NULL)
+               radius_put_raw_attr(pkt, RADIUS_TYPE_STATE,
+                   ibuf_data(sa->sa_radreq->rr_state),
+                   ibuf_size(sa->sa_radreq->rr_state));
+
+       if (radius_put_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+           (uint8_t *)eap, len) == -1) {
+               log_debug("%s: radius_put_raw_attr_cat failed %s", __func__,
+                   strerror(errno));
+               return -1;
+       }
+
+       iked_radius_fill_attributes(sa, pkt);
+
+       /* save the request, it'll be needed for message authentication */
+       if (sa->sa_radreq->rr_reqpkt != NULL)
+               radius_delete_packet(sa->sa_radreq->rr_reqpkt);
+       sa->sa_radreq->rr_reqpkt = pkt;
+       sa->sa_radreq->rr_sa = sa;
+       sa->sa_radreq->rr_ntry = 0;
+
+       iked_radius_request_send(env, sa->sa_radreq);
+
+       return 0;
+}
+
+void
+iked_radius_request_free(struct iked *env, struct iked_radserver_req *req)
+{
+       if (req == NULL)
+               return;
+       timer_del(env, &req->rr_timer);
+       free(req->rr_user);
+       ibuf_free(req->rr_state);
+       if (req->rr_reqpkt)
+               radius_delete_packet(req->rr_reqpkt);
+       if (req->rr_sa)
+               req->rr_sa->sa_radreq = NULL;
+       if (req->rr_server)
+               TAILQ_REMOVE(&req->rr_server->rs_reqs, req, rr_entry);
+       free(req);
+}
+
+void
+iked_radius_on_event(int fd, short ev, void *ctx)
+{
+       struct iked                     *env;
+       struct iked_radserver           *server = ctx;
+       struct iked_radserver_req       *req;
+       const struct iked_radcfgmap     *cfgmap;
+       RADIUS_PACKET                   *pkt;
+       int                              i, resid;
+       struct ibuf                     *e;
+       const void                      *attrval;
+       size_t                           attrlen;
+       uint8_t                          code;
+       char                             username[256];
+       u_char                           eapmsk[128];
+       /* RFC 3748 defines the MSK minimum size is 64 bytes */
+       size_t                           eapmsksiz = sizeof(eapmsk);
+
+       env = server->rs_env;
+       pkt = radius_recv(server->rs_sock, 0);
+       if (pkt == NULL) {
+               log_info("%s: receiving a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+               return;
+       }
+       resid = radius_get_id(pkt);
+
+       TAILQ_FOREACH(req, &server->rs_reqs, rr_entry) {
+               if (req->rr_reqid == resid)
+                       break;
+       }
+       if (req == NULL) {
+               log_debug("%s: received an unknown RADIUS message: id=%u",
+                   __func__, (unsigned)resid);
+               return;
+       }
+
+       radius_set_request_packet(pkt, req->rr_reqpkt);
+       if (radius_check_response_authenticator(pkt, server->rs_secret) != 0) {
+               log_info("%s: received an invalid RADIUS message: bad "
+                   "response authenticator", __func__);
+               return;
+       }
+       if (req->rr_accounting) {
+               /* accounting */
+               code = radius_get_code(pkt);
+               switch (code) {
+               case RADIUS_CODE_ACCOUNTING_RESPONSE: /* Expected */
+                       break;
+               default:
+                       log_info("%s: received an invalid RADIUS message: "
+                           "code %u", __func__, (unsigned)code);
+               }
+               timer_del(env, &req->rr_timer);
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               free(req);
+               return;
+       }
+
+       /* authentication */
+       if (radius_check_message_authenticator(pkt, server->rs_secret) != 0) {
+               log_info("%s: received an invalid RADIUS message: bad "
+                   "message authenticator", __func__);
+               return;
+       }
+
+       timer_del(env, &req->rr_timer);
+       req->rr_ntry = 0;
+
+       if (req->rr_sa == NULL)
+               goto fail;
+
+       code = radius_get_code(pkt);
+       switch (code) {
+       case RADIUS_CODE_ACCESS_CHALLENGE:
+               if (radius_get_raw_attr_ptr(pkt, RADIUS_TYPE_STATE, &attrval,
+                   &attrlen) != 0) {
+                       log_info("%s: received an invalid RADIUS message: no "
+                           "state attribute", __func__);
+                       goto fail;
+               }
+               if ((req->rr_state != NULL &&
+                   ibuf_set(req->rr_state, 0, attrval, attrlen) != 0) ||
+                   (req->rr_state = ibuf_new(attrval, attrlen)) == NULL) {
+                       log_info("%s: ibuf_new() failed: %s", __func__,
+                           strerror(errno));
+                       goto fail;
+               }
+               break;
+       case RADIUS_CODE_ACCESS_ACCEPT:
+               log_info("%s: received Access-Accept for %s",
+                   SPI_SA(req->rr_sa, __func__), req->rr_user);
+               /* Try to retrieve the EAP MSK from the RADIUS response */
+               if (radius_get_eap_msk(pkt, eapmsk, &eapmsksiz,
+                   server->rs_secret) == 0) {
+                       ibuf_free(req->rr_sa->sa_eapmsk);
+                       if ((req->rr_sa->sa_eapmsk = ibuf_new(eapmsk,
+                           eapmsksiz)) == NULL) {
+                               log_info("%s: ibuf_new() failed: %s", __func__,
+                                   strerror(errno));
+                               goto fail;
+                       }
+               } else
+                       log_debug("Could not retrieve the EAP MSK from the "
+                           "RADIUS message");
+
+               free(req->rr_sa->sa_eapid);
+               /* The EAP identity might be protected (RFC 3748 7.3) */
+               if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+                   username, sizeof(username)) == 0 &&
+                   strcmp(username, req->rr_user) != 0) {
+                       /*
+                        * The Access-Accept might have a User-Name.  It
+                        * should be used for Accouting (RFC 2865 5.1).
+                        */
+                       free(req->rr_user);
+                       req->rr_sa->sa_eapid = strdup(username);
+               } else
+                       req->rr_sa->sa_eapid = req->rr_user;
+               req->rr_user = NULL;
+
+               sa_state(env, req->rr_sa, IKEV2_STATE_AUTH_SUCCESS);
+
+               /* Map RADIUS attributes to cp */
+               if (TAILQ_EMPTY(&env->sc_radcfgmaps)) {
+                       for (i = 0; radius_cfgmaps[i].cfg_type != 0; i++) {
+                               cfgmap = &radius_cfgmaps[i];
+                               iked_radius_config(req, pkt, cfgmap->cfg_type,
+                                   cfgmap->vendor_id, cfgmap->attr_type);
+                       }
+               } else {
+                       TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry)
+                               iked_radius_config(req, pkt, cfgmap->cfg_type,
+                                   cfgmap->vendor_id, cfgmap->attr_type);
+               }
+
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               break;
+       case RADIUS_CODE_ACCESS_REJECT:
+               log_info("%s: received Access-Reject for %s",
+                   SPI_SA(req->rr_sa, __func__), req->rr_user);
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               break;
+       default:
+               log_debug("%s: received an invalid RADIUS message: code %u",
+                   __func__, (unsigned)code);
+               break;
+       }
+
+       /* get the length first */
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, NULL,
+           &attrlen) != 0) {
+               log_info("%s: failed to retrieve the EAP message", __func__);
+               goto fail;
+       }
+       /* allocate a buffer */
+       if ((e = ibuf_new(NULL, attrlen)) == NULL) {
+               log_info("%s: ibuf_new() failed: %s", __func__,
+                   strerror(errno));
+               goto fail;
+       }
+       /* copy the message to the buffer */
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+           ibuf_data(e), &attrlen) != 0) {
+               ibuf_free(e);
+               log_info("%s: failed to retrieve the EAP message", __func__);
+               goto fail;
+       }
+       ikev2_send_ike_e(env, req->rr_sa, e, IKEV2_PAYLOAD_EAP,
+           IKEV2_EXCHANGE_IKE_AUTH, 1);
+       return;
+ fail:
+       if (req->rr_server != NULL)
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+       req->rr_server = NULL;
+       if (req->rr_sa != NULL) {
+               ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+               sa_free(env, req->rr_sa);
+       }
+}
+
+void
+iked_radius_request_send(struct iked *env, void *ctx)
+{
+       struct iked_radserver_req       *req = ctx, *req0;
+       struct iked_radserver           *server = req->rr_server;
+       const int                        timeouts[] = { 2, 4, 8 };
+       uint8_t                          seq;
+       int                              i, max_tries, max_failovers;
+       struct sockaddr_storage          ss;
+       socklen_t                        sslen;
+       struct iked_radservers          *radservers;
+       struct timespec                  now;
+
+       if (!req->rr_accounting) {
+               max_tries = env->sc_radauth.max_tries;
+               max_failovers = env->sc_radauth.max_failovers;
+               radservers = &env->sc_radauthservers;
+       } else {
+               max_tries = env->sc_radacct.max_tries;
+               max_failovers = env->sc_radacct.max_failovers;
+               radservers = &env->sc_radacctservers;
+       }
+
+       if (req->rr_ntry > max_tries) {
+               req->rr_ntry = 0;
+               log_info("%s: RADIUS server %s failed", __func__,
+                   print_addr(&server->rs_sockaddr));
+ next_server:
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               if (req->rr_nfailover >= max_failovers ||
+                   TAILQ_NEXT(server, rs_entry) == NULL) {
+                       log_info("%s: No more RADIUS server", __func__);
+                       goto fail;
+               } else if (req->rr_state != NULL) {
+                       log_info("%s: Can't change RADIUS server: "
+                           "client has a state already", __func__);
+                       goto fail;
+               } else {
+                       TAILQ_REMOVE(radservers, server, rs_entry);
+                       TAILQ_INSERT_TAIL(radservers, server, rs_entry);
+                       server = TAILQ_FIRST(radservers);
+                       log_info("%s: RADIUS server %s is active",
+                           __func__, print_addr(&server->rs_sockaddr));
+               }
+               req->rr_nfailover++;
+       }
+
+       if (req->rr_server != NULL &&
+           req->rr_server != TAILQ_FIRST(radservers)) {
+               /* Current server is marked fail */
+               if (req->rr_state != NULL || req->rr_nfailover >= max_failovers)
+                       goto fail; /* can't fail over */
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               req->rr_nfailover++;
+       }
+
+       if (req->rr_server == NULL) {
+               /* Select a new server */
+               server = TAILQ_FIRST(radservers);
+               if (server == NULL) {
+                       log_info("%s: No RADIUS server is configured",
+                           __func__);
+                       goto fail;
+               }
+               TAILQ_INSERT_TAIL(&server->rs_reqs, req, rr_entry);
+               req->rr_server = server;
+
+               /* Prepare NAS-IP-Address */
+               if (server->rs_nas_ipv4.s_addr == INADDR_ANY &&
+                   IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6)) {
+                       sslen = sizeof(ss);
+                       if (getsockname(server->rs_sock, (struct sockaddr *)&ss,
+                           &sslen) == 0) {
+                               if (ss.ss_family == AF_INET)
+                                       server->rs_nas_ipv4 =
+                                           ((struct sockaddr_in *)&ss)
+                                           ->sin_addr;
+                               else
+                                       server->rs_nas_ipv6 =
+                                           ((struct sockaddr_in6 *)&ss)
+                                           ->sin6_addr;
+                       }
+               }
+       }
+       if (req->rr_ntry == 0) {
+               /* decide the ID */
+               seq = ++server->rs_reqseq;
+               for (i = 0; i < UCHAR_MAX; i++) {
+                       TAILQ_FOREACH(req0, &server->rs_reqs, rr_entry) {
+                               if (req0->rr_reqid == seq)
+                                       break;
+                       }
+                       if (req0 == NULL)
+                               break;
+                       seq++;
+               }
+               if (i >= UCHAR_MAX) {
+                       log_info("%s: RADIUS server %s failed.  Too many "
+                           "pending requests", __func__,
+                           print_addr(&server->rs_sockaddr));
+                       if (TAILQ_NEXT(server, rs_entry) != NULL)
+                               goto next_server;
+                       goto fail;
+               }
+               req->rr_reqid = seq;
+               radius_set_id(req->rr_reqpkt, req->rr_reqid);
+       }
+
+       if (server->rs_nas_ipv4.s_addr != INADDR_ANY)
+               radius_put_ipv4_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
+                   server->rs_nas_ipv4);
+       else if (!IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6))
+               radius_put_ipv6_attr(req->rr_reqpkt,
+                   RADIUS_TYPE_NAS_IPV6_ADDRESS, &server->rs_nas_ipv6);
+       /* Identifier */
+       radius_put_string_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IDENTIFIER,
+           IKED_NAS_ID);
+
+       if (req->rr_accounting) {
+               if (req->rr_ntry == 0 && req->rr_nfailover == 0)
+                       radius_put_uint32_attr(req->rr_reqpkt,
+                           RADIUS_TYPE_ACCT_DELAY_TIME, 0);
+               else {
+                       clock_gettime(CLOCK_MONOTONIC, &now);
+                       timespecsub(&now, &req->rr_accttime, &now);
+                       radius_put_uint32_attr(req->rr_reqpkt,
+                           RADIUS_TYPE_ACCT_DELAY_TIME, now.tv_sec);
+               }
+               radius_set_accounting_request_authenticator(req->rr_reqpkt,
+                   server->rs_secret);
+       } else {
+               radius_put_message_authenticator(req->rr_reqpkt,
+                   server->rs_secret);
+       }
+
+       if (radius_send(server->rs_sock, req->rr_reqpkt, 0) < 0)
+               log_info("%s: sending a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+
+       if (req->rr_ntry >= (int)nitems(timeouts))
+               timer_add(env, &req->rr_timer, timeouts[nitems(timeouts) - 1]);
+       else
+               timer_add(env, &req->rr_timer, timeouts[req->rr_ntry]);
+       req->rr_ntry++;
+       return;
+ fail:
+       if (req->rr_server != NULL)
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+       req->rr_server = NULL;
+       if (req->rr_sa != NULL) {
+               ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+               sa_free(env, req->rr_sa);
+       }
+}
+
+void
+iked_radius_fill_attributes(struct iked_sa *sa, RADIUS_PACKET *pkt)
+{
+       /* NAS Port Type = Virtual */
+       radius_put_uint32_attr(pkt,
+           RADIUS_TYPE_NAS_PORT_TYPE, RADIUS_NAS_PORT_TYPE_VIRTUAL);
+       /* Service Type =  Framed */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_SERVICE_TYPE,
+           RADIUS_SERVICE_TYPE_FRAMED);
+       /* Tunnel Type = EAP */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_TUNNEL_TYPE,
+           RADIUS_TUNNEL_TYPE_ESP);
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_CALLED_STATION_ID,
+           print_addr(&sa->sa_local.addr));
+       radius_put_string_attr(pkt, RADIUS_TYPE_CALLING_STATION_ID,
+           print_addr(&sa->sa_peer.addr));
+}
+
+void
+iked_radius_config(struct iked_radserver_req *req, const RADIUS_PACKET *pkt,
+    int cfg_type, uint32_t vendor_id, uint8_t attr_type)
+{
+       unsigned int             i;
+       struct iked_sa          *sa = req->rr_sa;
+       struct in_addr           ia4;
+       struct in6_addr          ia6;
+       struct sockaddr_in      *sin4;
+       struct sockaddr_in6     *sin6;
+       struct iked_addr        *addr;
+       struct iked_cfg         *ikecfg;
+
+       for (i = 0; i < sa->sa_policy->pol_ncfg; i++) {
+               ikecfg = &sa->sa_policy->pol_cfg[i];
+               if (ikecfg->cfg_type == cfg_type &&
+                   ikecfg->cfg_type != IKEV2_CFG_INTERNAL_IP4_ADDRESS)
+                       return; /* use config rather than radius */
+       }
+       switch (cfg_type) {
+       case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+       case IKEV2_CFG_INTERNAL_IP4_NETMASK:
+       case IKEV2_CFG_INTERNAL_IP4_DNS:
+       case IKEV2_CFG_INTERNAL_IP4_NBNS:
+       case IKEV2_CFG_INTERNAL_IP4_DHCP:
+       case IKEV2_CFG_INTERNAL_IP4_SERVER:
+               if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+                       radius_get_ipv4_attr(pkt, attr_type, &ia4);
+               else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+                   attr_type))
+                       radius_get_vs_ipv4_attr(pkt, vendor_id, attr_type,
+                           &ia4);
+               else
+                       break; /* no attribute contained */
+
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP4_NETMASK) {
+                       /*
+                        * This assumes IKEV2_CFG_INTERNAL_IP4_ADDRESS is
+                        * called before IKEV2_CFG_INTERNAL_IP4_NETMASK
+                        */
+                       if (sa->sa_rad_addr == NULL) {
+                               /*
+                                * RFC 7296, IKEV2_CFG_INTERNAL_IP4_NETMASK
+                                * must be used with
+                                * IKEV2_CFG_INTERNAL_IP4_ADDRESS
+                                */
+                               break;
+                       }
+                       if (ia4.s_addr == 0) {
+                               log_debug("%s: netmask is wrong", __func__);
+                               break;
+                       }
+                       if (ia4.s_addr == htonl(0))
+                               sa->sa_rad_addr->addr_mask = 0;
+                       else
+                               sa->sa_rad_addr->addr_mask =
+                                   33 - ffs(ntohl(ia4.s_addr));
+                       if (sa->sa_rad_addr->addr_mask < 32)
+                               sa->sa_rad_addr->addr_net = 1;
+               }
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP4_ADDRESS) {
+                       if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+                               log_warn("%s: calloc", __func__);
+                               return;
+                       }
+                       sa->sa_rad_addr = addr;
+               } else {
+                       req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+                       req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+                       addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+                       req->rr_ncfg++;
+               }
+               addr->addr_af = AF_INET;
+               sin4 = (struct sockaddr_in *)&addr->addr;
+               sin4->sin_family = AF_INET;
+               sin4->sin_len = sizeof(struct sockaddr_in);
+               sin4->sin_addr = ia4;
+               break;
+       case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+       case IKEV2_CFG_INTERNAL_IP6_DNS:
+       case IKEV2_CFG_INTERNAL_IP6_NBNS:
+       case IKEV2_CFG_INTERNAL_IP6_DHCP:
+       case IKEV2_CFG_INTERNAL_IP6_SERVER:
+               if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+                       radius_get_ipv6_attr(pkt, attr_type, &ia6);
+               else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+                   attr_type))
+                       radius_get_vs_ipv6_attr(pkt, vendor_id, attr_type,
+                           &ia6);
+               else
+                       break; /* no attribute contained */
+
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP6_ADDRESS) {
+                       if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+                               log_warn("%s: calloc", __func__);
+                               return;
+                       }
+                       sa->sa_rad_addr = addr;
+               } else {
+                       req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+                       req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+                       addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+                       req->rr_ncfg++;
+               }
+               addr->addr_af = AF_INET;
+               sin6 = (struct sockaddr_in6 *)&addr->addr;
+               sin6->sin6_family = AF_INET6;
+               sin6->sin6_len = sizeof(struct sockaddr_in6);
+               sin6->sin6_addr = ia6;
+               break;
+       }
+       return;
+}
+
+void
+iked_radius_acct_on(struct iked *env)
+{
+       if (TAILQ_EMPTY(&env->sc_radacctservers))
+               return;
+       if (env->sc_radaccton == 0) {   /* trigger once */
+               iked_radius_acct_request(env, NULL,
+                   RADIUS_ACCT_STATUS_TYPE_ACCT_ON);
+               env->sc_radaccton = 1;
+       }
+}
+
+void
+iked_radius_acct_off(struct iked *env)
+{
+       iked_radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_OFF);
+}
+
+void
+iked_radius_acct_start(struct iked *env, struct iked_sa *sa)
+{
+       iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_START);
+}
+
+void
+iked_radius_acct_stop(struct iked *env, struct iked_sa *sa)
+{
+       iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_STOP);
+}
+
+void
+iked_radius_acct_request(struct iked *env, struct iked_sa *sa, uint8_t stype)
+{
+       struct iked_radserver_req       *req;
+       RADIUS_PACKET                   *pkt;
+       struct iked_addr                *addr4 = NULL;
+       struct iked_addr                *addr6 = NULL;
+       struct in_addr                   mask4;
+       char                             sa_id[IKED_ID_SIZE];
+       char                             sid[16 + 1];
+       struct timespec                  now;
+       int                              cause;
+
+       if (TAILQ_EMPTY(&env->sc_radacctservers))
+               return;
+       /*
+        * In RFC2866 5.6, "Users who are delivered service without
+        * being authenticated SHOULD NOT generate Accounting records
+        */
+       if (sa != NULL && sa->sa_eapid == NULL) {
+               /* fallback to IKEID for accounting */
+               if (ikev2_print_id(IKESA_DSTID(sa), sa_id, sizeof(sa_id)) != -1)
+                       sa->sa_eapid = strdup(sa_id);
+               if (sa->sa_eapid == NULL)
+                       return;
+       }
+
+       if ((req = calloc(1, sizeof(struct iked_radserver_req))) == NULL) {
+               log_debug("%s: calloc faile for iked_radserver_req: %s",
+                   __func__, strerror(errno));
+               return;
+       }
+       req->rr_accounting = 1;
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       req->rr_accttime = now;
+       timer_set(env, &req->rr_timer, iked_radius_request_send, req);
+
+       if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCOUNTING_REQUEST))
+           == NULL) {
+               log_debug("%s: radius_new_request_packet failed %s", __func__,
+                   strerror(errno));
+               return;
+       }
+
+       /* RFC 2866  5.1. Acct-Status-Type */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE, stype);
+
+       if (sa == NULL) {
+               /* ASSERT(stype == RADIUS_ACCT_STATUS_TYPE_ACCT_ON ||
+                   stype == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) */
+               req->rr_reqpkt = pkt;
+               req->rr_ntry = 0;
+               iked_radius_request_send(env, req);
+               return;
+       }
+
+       iked_radius_fill_attributes(sa, pkt);
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME, sa->sa_eapid);
+
+       /* RFC 2866  5.5. Acct-Session-Id */
+       snprintf(sid, sizeof(sid), "%016llx",
+           (unsigned long long)sa->sa_hdr.sh_ispi);
+       radius_put_string_attr(pkt, RADIUS_TYPE_ACCT_SESSION_ID, sid);
+
+       /* Accounting Request must have Framed-IP-Address */
+       addr4 = sa->sa_addrpool;
+       if (addr4 != NULL) {
+               radius_put_ipv4_attr(pkt, RADIUS_TYPE_FRAMED_IP_ADDRESS,
+                   ((struct sockaddr_in *)&addr4->addr)->sin_addr);
+               if (addr4->addr_mask != 0) {
+                       mask4.s_addr = htonl(
+                           0xFFFFFFFFUL << (32 - addr4->addr_mask));
+                       radius_put_ipv4_attr(pkt,
+                           RADIUS_TYPE_FRAMED_IP_NETMASK, mask4);
+               }
+       }
+       addr6 = sa->sa_addrpool6;
+       if (addr6 != NULL)
+               radius_put_ipv6_attr(pkt, RADIUS_TYPE_FRAMED_IPV6_ADDRESS,
+                   &((struct sockaddr_in6 *)&addr6->addr)->sin6_addr);
+
+       /* RFC2866 5.6 Acct-Authentic */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_AUTHENTIC,
+           (sa->sa_radreq != NULL)? RADIUS_ACCT_AUTHENTIC_RADIUS :
+           RADIUS_ACCT_AUTHENTIC_LOCAL);
+
+       switch (stype) {
+       case RADIUS_ACCT_STATUS_TYPE_START:
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE,
+                   RADIUS_ACCT_STATUS_TYPE_START);
+               break;
+       case RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE:
+       case RADIUS_ACCT_STATUS_TYPE_STOP:
+               /* RFC 2866 5.7.  Acct-Session-Time */
+               timespecsub(&now, &sa->sa_starttime, &now);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_SESSION_TIME,
+                   now.tv_sec);
+               /* RFC 2866 5.10 Acct-Terminate-Cause */
+               cause = RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL;
+               if (sa->sa_reason) {
+                       if (strcmp(sa->sa_reason, "received delete") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_USER_REQUEST;
+                       } else if (strcmp(sa->sa_reason, "SA rekeyed") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT;
+                       } else if (strncmp(sa->sa_reason, "retransmit",
+                           strlen("retransmit")) == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_LOST_SERVICE;
+                       } else if (strcmp(sa->sa_reason,
+                           "disconnect requested") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_ADMIN_RESET;
+                       }
+               }
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE,
+                   cause);
+               /* I/O statistics {Input,Output}-{Packets,Octets,Gigawords} */
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_PACKETS,
+                   sa->sa_stats.sas_ipackets);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS,
+                   sa->sa_stats.sas_opackets);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_OCTETS,
+                   sa->sa_stats.sas_ibytes & 0xffffffffUL);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_OCTETS,
+                   sa->sa_stats.sas_obytes & 0xffffffffUL);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_GIGAWORDS,
+                   sa->sa_stats.sas_ibytes >> 32);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS,
+                   sa->sa_stats.sas_obytes >> 32);
+               break;
+       }
+       req->rr_reqpkt = pkt;
+       req->rr_ntry = 0;
+       iked_radius_request_send(env, req);
+}
+
+void
+iked_radius_dae_on_event(int fd, short ev, void *ctx)
+{
+       struct iked_raddae      *dae = ctx;
+       struct iked             *env = dae->rd_env;
+       RADIUS_PACKET           *req = NULL, *res = NULL;
+       struct sockaddr_storage  ss;
+       socklen_t                sslen;
+       struct iked_radclient   *client;
+       struct iked_sa          *sa = NULL;
+       char                     attr[256], username[256];
+       char                    *endp, *reason, *nakcause = NULL;
+       int                      code, n = 0;
+       uint64_t                 ispi = 0;
+       uint32_t                 u32, cause = 0;
+       struct iked_addr        *addr4 = NULL;
+
+       reason = "disconnect requested";
+
+       sslen = sizeof(ss);
+       req = radius_recvfrom(dae->rd_sock, 0, (struct sockaddr *)&ss, &sslen);
+       if (req == NULL) {
+               log_warn("%s: receiving a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+               return;
+       }
+       TAILQ_FOREACH(client, &env->sc_raddaeclients, rc_entry) {
+               if (sockaddr_cmp((struct sockaddr *)&client->rc_sockaddr,
+                   (struct sockaddr *)&ss, -1) == 0)
+                       break;
+       }
+       if (client == NULL) {
+               log_warnx("%s: received RADIUS message from %s: "
+                   "unknown client", __func__, print_addr(&ss));
+               goto out;
+       }
+
+       if (radius_check_accounting_request_authenticator(req,
+           client->rc_secret) != 0) {
+               log_warnx("%s: received an invalid RADIUS message from %s: bad "
+                   "response authenticator", __func__, print_addr(&ss));
+               goto out;
+       }
+
+       if ((code = radius_get_code(req)) != RADIUS_CODE_DISCONNECT_REQUEST) {
+               /* Code other than Disconnect-Request is not supported */
+               if (code == RADIUS_CODE_COA_REQUEST) {
+                       code = RADIUS_CODE_COA_NAK;
+                       cause = RADIUS_ERROR_CAUSE_ADMINISTRATIVELY_PROHIBITED;
+                       nakcause = "Coa-Request is not supprted";
+                       goto send;
+               }
+               log_warnx("%s: received an invalid RADIUS message "
+                   "from %s: unknown code %d", __func__,
+                   print_addr(&ss), code);
+               goto out;
+       }
+
+       log_info("received Disconnect-Request from %s", print_addr(&ss));
+
+       if (radius_get_string_attr(req, RADIUS_TYPE_NAS_IDENTIFIER, attr,
+           sizeof(attr)) == 0 && strcmp(attr, IKED_NAS_ID) != 0) {
+               cause = RADIUS_ERROR_CAUSE_NAS_IDENTIFICATION_MISMATCH;
+               nakcause = "NAS-Identifier is not matched";
+               goto search_done;
+       }
+
+       /* prepare User-Name attribute */
+       memset(username, 0, sizeof(username));
+       radius_get_string_attr(req, RADIUS_TYPE_USER_NAME, username,
+           sizeof(username));
+
+       if (radius_get_string_attr(req, RADIUS_TYPE_ACCT_SESSION_ID, attr,
+           sizeof(attr)) == 0) {
+               /* the client is to disconnect a session */
+               ispi = strtoull(attr, &endp, 16);
+               if (attr[0] == '\0' || *endp != '\0' || errno == ERANGE ||
+                   ispi == ULLONG_MAX) {
+                       cause = RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE;
+                       nakcause = "Session-Id is wrong";
+                       goto search_done;
+
+               }
+               RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                       if (sa->sa_hdr.sh_ispi == ispi)
+                               break;
+               }
+               if (sa == NULL)
+                       goto search_done;
+               if (username[0] != '\0' && (sa->sa_eapid == NULL ||
+                   strcmp(username, sa->sa_eapid) != 0)) {
+                       /* specified User-Name attribute is mismatched */
+                       cause = RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE;
+                       nakcause = "User-Name is not matched";
+                       goto search_done;
+               }
+               ikev2_ike_sa_setreason(sa, reason);
+               ikev2_ike_sa_delete(env, sa);
+               n++;
+       } else if (username[0] != '\0') {
+               RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                       if (sa->sa_eapid != NULL &&
+                           strcmp(sa->sa_eapid, username) == 0) {
+                               ikev2_ike_sa_setreason(sa, reason);
+                               ikev2_ike_sa_delete(env, sa);
+                               n++;
+                       }
+               }
+       } else if (radius_get_uint32_attr(req, RADIUS_TYPE_FRAMED_IP_ADDRESS,
+           &u32) == 0) {
+               addr4 = sa->sa_addrpool;
+               if (addr4 != NULL) {
+                       RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                               if (u32 == ((struct sockaddr_in *)&addr4->addr)
+                                   ->sin_addr.s_addr) {
+                                       ikev2_ike_sa_setreason(sa, reason);
+                                       ikev2_ike_sa_delete(env, sa);
+                                       n++;
+                               }
+                       }
+               }
+       }
+ search_done:
+       if (n > 0)
+               code = RADIUS_CODE_DISCONNECT_ACK;
+       else {
+               if (nakcause == NULL)
+                       nakcause = "session not found";
+               if (cause == 0)
+                       cause = RADIUS_ERROR_CAUSE_SESSION_NOT_FOUND;
+               code = RADIUS_CODE_DISCONNECT_NAK;
+       }
+ send:
+       res = radius_new_response_packet(code, req);
+       if (res == NULL) {
+               log_warn("%s: radius_new_response_packet", __func__);
+               goto out;
+       }
+       if (cause != 0)
+               radius_put_uint32_attr(res, RADIUS_TYPE_ERROR_CAUSE, cause);
+       radius_set_response_authenticator(res, client->rc_secret);
+       if (radius_sendto(dae->rd_sock, res, 0, (struct sockaddr *)&ss, sslen)
+           == -1)
+               log_warn("%s: sendto", __func__);
+       log_info("send %s for %s%s%s",
+           (code == RADIUS_CODE_DISCONNECT_ACK)? "Disconnect-ACK" :
+           (code == RADIUS_CODE_DISCONNECT_NAK)? "Disconnect-NAK" : "CoA-NAK",
+           print_addr(&ss), (nakcause)? ": " : "", (nakcause)? nakcause : "");
+ out:
+       radius_delete_packet(req);
+       if (res != NULL)
+               radius_delete_packet(res);
+}
+/*     $OpenBSD: radius.c,v 1.3 2024/07/13 12:22:46 yasuoka Exp $      */
+
+/*
+ * Copyright (c) 2024 Internet Initiative Japan Inc.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <netinet/ip_ipsp.h>
+
+#include <endian.h>
+#include <event.h>
+#include <errno.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <radius.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+
+#include "iked.h"
+#include "eap.h"
+#include "ikev2.h"
+#include "types.h"
+
+void    iked_radius_request_send(struct iked *, void *);
+void    iked_radius_fill_attributes(struct iked_sa *, RADIUS_PACKET *);
+void    iked_radius_config(struct iked_radserver_req *, const RADIUS_PACKET *,
+           int, uint32_t, uint8_t);
+void    iked_radius_acct_request(struct iked *, struct iked_sa *, uint8_t);
+
+const struct iked_radcfgmap radius_cfgmaps[] = {
+    { IKEV2_CFG_INTERNAL_IP4_ADDRESS, 0, RADIUS_TYPE_FRAMED_IP_ADDRESS },
+    { IKEV2_CFG_INTERNAL_IP4_NETMASK, 0, RADIUS_TYPE_FRAMED_IP_NETMASK },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_DNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_PRIMARY_NBNS_SERVER },
+    { IKEV2_CFG_INTERNAL_IP4_NBNS, RADIUS_VENDOR_MICROSOFT,
+       RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER },
+    { 0 }
+};
+
+int
+iked_radius_request(struct iked *env, struct iked_sa *sa,
+    struct iked_message *msg)
+{
+       struct eap_message              *eap;
+       RADIUS_PACKET                   *pkt;
+       size_t                           len;
+
+       eap = ibuf_data(msg->msg_eapmsg);
+       len = betoh16(eap->eap_length);
+       if (eap->eap_code != EAP_CODE_RESPONSE) {
+               log_debug("%s: eap_code is not response %u", __func__,
+                   (unsigned)eap->eap_code);
+               return -1;
+       }
+
+       if (eap->eap_type == EAP_TYPE_IDENTITY) {
+               if ((sa->sa_radreq = calloc(1,
+                   sizeof(struct iked_radserver_req))) == NULL) {
+                       log_debug(
+                           "%s: calloc failed for iked_radserver_req: %s",
+                           __func__, strerror(errno));
+                       return (-1);
+               }
+               timer_set(env, &sa->sa_radreq->rr_timer,
+                   iked_radius_request_send, sa->sa_radreq);
+               sa->sa_radreq->rr_user = strdup(msg->msg_eap.eam_identity);
+       }
+
+       if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCESS_REQUEST))
+           == NULL) {
+               log_debug("%s: radius_new_request_packet failed %s", __func__,
+                   strerror(errno));
+               return -1;
+       }
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+           sa->sa_radreq->rr_user);
+       if (sa->sa_radreq->rr_state != NULL)
+               radius_put_raw_attr(pkt, RADIUS_TYPE_STATE,
+                   ibuf_data(sa->sa_radreq->rr_state),
+                   ibuf_size(sa->sa_radreq->rr_state));
+
+       if (radius_put_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+           (uint8_t *)eap, len) == -1) {
+               log_debug("%s: radius_put_raw_attr_cat failed %s", __func__,
+                   strerror(errno));
+               return -1;
+       }
+
+       iked_radius_fill_attributes(sa, pkt);
+
+       /* save the request, it'll be needed for message authentication */
+       if (sa->sa_radreq->rr_reqpkt != NULL)
+               radius_delete_packet(sa->sa_radreq->rr_reqpkt);
+       sa->sa_radreq->rr_reqpkt = pkt;
+       sa->sa_radreq->rr_sa = sa;
+       sa->sa_radreq->rr_ntry = 0;
+
+       iked_radius_request_send(env, sa->sa_radreq);
+
+       return 0;
+}
+
+void
+iked_radius_request_free(struct iked *env, struct iked_radserver_req *req)
+{
+       if (req == NULL)
+               return;
+       timer_del(env, &req->rr_timer);
+       free(req->rr_user);
+       ibuf_free(req->rr_state);
+       if (req->rr_reqpkt)
+               radius_delete_packet(req->rr_reqpkt);
+       if (req->rr_sa)
+               req->rr_sa->sa_radreq = NULL;
+       if (req->rr_server)
+               TAILQ_REMOVE(&req->rr_server->rs_reqs, req, rr_entry);
+       free(req);
+}
+
+void
+iked_radius_on_event(int fd, short ev, void *ctx)
+{
+       struct iked                     *env;
+       struct iked_radserver           *server = ctx;
+       struct iked_radserver_req       *req;
+       const struct iked_radcfgmap     *cfgmap;
+       RADIUS_PACKET                   *pkt;
+       int                              i, resid;
+       struct ibuf                     *e;
+       const void                      *attrval;
+       size_t                           attrlen;
+       uint8_t                          code;
+       char                             username[256];
+       u_char                           eapmsk[128];
+       /* RFC 3748 defines the MSK minimum size is 64 bytes */
+       size_t                           eapmsksiz = sizeof(eapmsk);
+
+       env = server->rs_env;
+       pkt = radius_recv(server->rs_sock, 0);
+       if (pkt == NULL) {
+               log_info("%s: receiving a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+               return;
+       }
+       resid = radius_get_id(pkt);
+
+       TAILQ_FOREACH(req, &server->rs_reqs, rr_entry) {
+               if (req->rr_reqid == resid)
+                       break;
+       }
+       if (req == NULL) {
+               log_debug("%s: received an unknown RADIUS message: id=%u",
+                   __func__, (unsigned)resid);
+               return;
+       }
+
+       radius_set_request_packet(pkt, req->rr_reqpkt);
+       if (radius_check_response_authenticator(pkt, server->rs_secret) != 0) {
+               log_info("%s: received an invalid RADIUS message: bad "
+                   "response authenticator", __func__);
+               return;
+       }
+       if (req->rr_accounting) {
+               /* accounting */
+               code = radius_get_code(pkt);
+               switch (code) {
+               case RADIUS_CODE_ACCOUNTING_RESPONSE: /* Expected */
+                       break;
+               default:
+                       log_info("%s: received an invalid RADIUS message: "
+                           "code %u", __func__, (unsigned)code);
+               }
+               timer_del(env, &req->rr_timer);
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               free(req);
+               return;
+       }
+
+       /* authentication */
+       if (radius_check_message_authenticator(pkt, server->rs_secret) != 0) {
+               log_info("%s: received an invalid RADIUS message: bad "
+                   "message authenticator", __func__);
+               return;
+       }
+
+       timer_del(env, &req->rr_timer);
+       req->rr_ntry = 0;
+
+       if (req->rr_sa == NULL)
+               goto fail;
+
+       code = radius_get_code(pkt);
+       switch (code) {
+       case RADIUS_CODE_ACCESS_CHALLENGE:
+               if (radius_get_raw_attr_ptr(pkt, RADIUS_TYPE_STATE, &attrval,
+                   &attrlen) != 0) {
+                       log_info("%s: received an invalid RADIUS message: no "
+                           "state attribute", __func__);
+                       goto fail;
+               }
+               if ((req->rr_state != NULL &&
+                   ibuf_set(req->rr_state, 0, attrval, attrlen) != 0) ||
+                   (req->rr_state = ibuf_new(attrval, attrlen)) == NULL) {
+                       log_info("%s: ibuf_new() failed: %s", __func__,
+                           strerror(errno));
+                       goto fail;
+               }
+               break;
+       case RADIUS_CODE_ACCESS_ACCEPT:
+               log_info("%s: received Access-Accept for %s",
+                   SPI_SA(req->rr_sa, __func__), req->rr_user);
+               /* Try to retrieve the EAP MSK from the RADIUS response */
+               if (radius_get_eap_msk(pkt, eapmsk, &eapmsksiz,
+                   server->rs_secret) == 0) {
+                       ibuf_free(req->rr_sa->sa_eapmsk);
+                       if ((req->rr_sa->sa_eapmsk = ibuf_new(eapmsk,
+                           eapmsksiz)) == NULL) {
+                               log_info("%s: ibuf_new() failed: %s", __func__,
+                                   strerror(errno));
+                               goto fail;
+                       }
+               } else
+                       log_debug("Could not retrieve the EAP MSK from the "
+                           "RADIUS message");
+
+               free(req->rr_sa->sa_eapid);
+               /* The EAP identity might be protected (RFC 3748 7.3) */
+               if (radius_get_string_attr(pkt, RADIUS_TYPE_USER_NAME,
+                   username, sizeof(username)) == 0 &&
+                   strcmp(username, req->rr_user) != 0) {
+                       /*
+                        * The Access-Accept might have a User-Name.  It
+                        * should be used for Accouting (RFC 2865 5.1).
+                        */
+                       free(req->rr_user);
+                       req->rr_sa->sa_eapid = strdup(username);
+               } else
+                       req->rr_sa->sa_eapid = req->rr_user;
+               req->rr_user = NULL;
+
+               sa_state(env, req->rr_sa, IKEV2_STATE_AUTH_SUCCESS);
+
+               /* Map RADIUS attributes to cp */
+               if (TAILQ_EMPTY(&env->sc_radcfgmaps)) {
+                       for (i = 0; radius_cfgmaps[i].cfg_type != 0; i++) {
+                               cfgmap = &radius_cfgmaps[i];
+                               iked_radius_config(req, pkt, cfgmap->cfg_type,
+                                   cfgmap->vendor_id, cfgmap->attr_type);
+                       }
+               } else {
+                       TAILQ_FOREACH(cfgmap, &env->sc_radcfgmaps, entry)
+                               iked_radius_config(req, pkt, cfgmap->cfg_type,
+                                   cfgmap->vendor_id, cfgmap->attr_type);
+               }
+
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               break;
+       case RADIUS_CODE_ACCESS_REJECT:
+               log_info("%s: received Access-Reject for %s",
+                   SPI_SA(req->rr_sa, __func__), req->rr_user);
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               break;
+       default:
+               log_debug("%s: received an invalid RADIUS message: code %u",
+                   __func__, (unsigned)code);
+               break;
+       }
+
+       /* get the length first */
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE, NULL,
+           &attrlen) != 0) {
+               log_info("%s: failed to retrieve the EAP message", __func__);
+               goto fail;
+       }
+       /* allocate a buffer */
+       if ((e = ibuf_new(NULL, attrlen)) == NULL) {
+               log_info("%s: ibuf_new() failed: %s", __func__,
+                   strerror(errno));
+               goto fail;
+       }
+       /* copy the message to the buffer */
+       if (radius_get_raw_attr_cat(pkt, RADIUS_TYPE_EAP_MESSAGE,
+           ibuf_data(e), &attrlen) != 0) {
+               ibuf_free(e);
+               log_info("%s: failed to retrieve the EAP message", __func__);
+               goto fail;
+       }
+       ikev2_send_ike_e(env, req->rr_sa, e, IKEV2_PAYLOAD_EAP,
+           IKEV2_EXCHANGE_IKE_AUTH, 1);
+       return;
+ fail:
+       if (req->rr_server != NULL)
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+       req->rr_server = NULL;
+       if (req->rr_sa != NULL) {
+               ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+               sa_free(env, req->rr_sa);
+       }
+}
+
+void
+iked_radius_request_send(struct iked *env, void *ctx)
+{
+       struct iked_radserver_req       *req = ctx, *req0;
+       struct iked_radserver           *server = req->rr_server;
+       const int                        timeouts[] = { 2, 4, 8 };
+       uint8_t                          seq;
+       int                              i, max_tries, max_failovers;
+       struct sockaddr_storage          ss;
+       socklen_t                        sslen;
+       struct iked_radservers          *radservers;
+       struct timespec                  now;
+
+       if (!req->rr_accounting) {
+               max_tries = env->sc_radauth.max_tries;
+               max_failovers = env->sc_radauth.max_failovers;
+               radservers = &env->sc_radauthservers;
+       } else {
+               max_tries = env->sc_radacct.max_tries;
+               max_failovers = env->sc_radacct.max_failovers;
+               radservers = &env->sc_radacctservers;
+       }
+
+       if (req->rr_ntry > max_tries) {
+               req->rr_ntry = 0;
+               log_info("%s: RADIUS server %s failed", __func__,
+                   print_addr(&server->rs_sockaddr));
+ next_server:
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               if (req->rr_nfailover >= max_failovers ||
+                   TAILQ_NEXT(server, rs_entry) == NULL) {
+                       log_info("%s: No more RADIUS server", __func__);
+                       goto fail;
+               } else if (req->rr_state != NULL) {
+                       log_info("%s: Can't change RADIUS server: "
+                           "client has a state already", __func__);
+                       goto fail;
+               } else {
+                       TAILQ_REMOVE(radservers, server, rs_entry);
+                       TAILQ_INSERT_TAIL(radservers, server, rs_entry);
+                       server = TAILQ_FIRST(radservers);
+                       log_info("%s: RADIUS server %s is active",
+                           __func__, print_addr(&server->rs_sockaddr));
+               }
+               req->rr_nfailover++;
+       }
+
+       if (req->rr_server != NULL &&
+           req->rr_server != TAILQ_FIRST(radservers)) {
+               /* Current server is marked fail */
+               if (req->rr_state != NULL || req->rr_nfailover >= max_failovers)
+                       goto fail; /* can't fail over */
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+               req->rr_server = NULL;
+               req->rr_nfailover++;
+       }
+
+       if (req->rr_server == NULL) {
+               /* Select a new server */
+               server = TAILQ_FIRST(radservers);
+               if (server == NULL) {
+                       log_info("%s: No RADIUS server is configured",
+                           __func__);
+                       goto fail;
+               }
+               TAILQ_INSERT_TAIL(&server->rs_reqs, req, rr_entry);
+               req->rr_server = server;
+
+               /* Prepare NAS-IP-Address */
+               if (server->rs_nas_ipv4.s_addr == INADDR_ANY &&
+                   IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6)) {
+                       sslen = sizeof(ss);
+                       if (getsockname(server->rs_sock, (struct sockaddr *)&ss,
+                           &sslen) == 0) {
+                               if (ss.ss_family == AF_INET)
+                                       server->rs_nas_ipv4 =
+                                           ((struct sockaddr_in *)&ss)
+                                           ->sin_addr;
+                               else
+                                       server->rs_nas_ipv6 =
+                                           ((struct sockaddr_in6 *)&ss)
+                                           ->sin6_addr;
+                       }
+               }
+       }
+       if (req->rr_ntry == 0) {
+               /* decide the ID */
+               seq = ++server->rs_reqseq;
+               for (i = 0; i < UCHAR_MAX; i++) {
+                       TAILQ_FOREACH(req0, &server->rs_reqs, rr_entry) {
+                               if (req0->rr_reqid == seq)
+                                       break;
+                       }
+                       if (req0 == NULL)
+                               break;
+                       seq++;
+               }
+               if (i >= UCHAR_MAX) {
+                       log_info("%s: RADIUS server %s failed.  Too many "
+                           "pending requests", __func__,
+                           print_addr(&server->rs_sockaddr));
+                       if (TAILQ_NEXT(server, rs_entry) != NULL)
+                               goto next_server;
+                       goto fail;
+               }
+               req->rr_reqid = seq;
+               radius_set_id(req->rr_reqpkt, req->rr_reqid);
+       }
+
+       if (server->rs_nas_ipv4.s_addr != INADDR_ANY)
+               radius_put_ipv4_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS,
+                   server->rs_nas_ipv4);
+       else if (!IN6_IS_ADDR_UNSPECIFIED(&server->rs_nas_ipv6))
+               radius_put_ipv6_attr(req->rr_reqpkt,
+                   RADIUS_TYPE_NAS_IPV6_ADDRESS, &server->rs_nas_ipv6);
+       /* Identifier */
+       radius_put_string_attr(req->rr_reqpkt, RADIUS_TYPE_NAS_IDENTIFIER,
+           "OpenIKED");
+
+       if (req->rr_accounting) {
+               if (req->rr_ntry == 0 && req->rr_nfailover == 0)
+                       radius_put_uint32_attr(req->rr_reqpkt,
+                           RADIUS_TYPE_ACCT_DELAY_TIME, 0);
+               else {
+                       clock_gettime(CLOCK_MONOTONIC, &now);
+                       timespecsub(&now, &req->rr_accttime, &now);
+                       radius_put_uint32_attr(req->rr_reqpkt,
+                           RADIUS_TYPE_ACCT_DELAY_TIME, now.tv_sec);
+               }
+               radius_set_accounting_request_authenticator(req->rr_reqpkt,
+                   server->rs_secret);
+       } else {
+               radius_put_message_authenticator(req->rr_reqpkt,
+                   server->rs_secret);
+       }
+
+       if (radius_send(server->rs_sock, req->rr_reqpkt, 0) < 0)
+               log_info("%s: sending a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+
+       if (req->rr_ntry >= (int)nitems(timeouts))
+               timer_add(env, &req->rr_timer, timeouts[nitems(timeouts) - 1]);
+       else
+               timer_add(env, &req->rr_timer, timeouts[req->rr_ntry]);
+       req->rr_ntry++;
+       return;
+ fail:
+       if (req->rr_server != NULL)
+               TAILQ_REMOVE(&server->rs_reqs, req, rr_entry);
+       req->rr_server = NULL;
+       if (req->rr_sa != NULL) {
+               ikev2_ike_sa_setreason(req->rr_sa, "RADIUS request failed");
+               sa_free(env, req->rr_sa);
+       }
+}
+
+void
+iked_radius_fill_attributes(struct iked_sa *sa, RADIUS_PACKET *pkt)
+{
+       /* NAS Port Type = Virtual */
+       radius_put_uint32_attr(pkt,
+           RADIUS_TYPE_NAS_PORT_TYPE, RADIUS_NAS_PORT_TYPE_VIRTUAL);
+       /* Service Type =  Framed */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_SERVICE_TYPE,
+           RADIUS_SERVICE_TYPE_FRAMED);
+       /* Tunnel Type = EAP */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_TUNNEL_TYPE,
+           RADIUS_TUNNEL_TYPE_ESP);
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_CALLED_STATION_ID,
+           print_addr(&sa->sa_local.addr));
+       radius_put_string_attr(pkt, RADIUS_TYPE_CALLING_STATION_ID,
+           print_addr(&sa->sa_peer.addr));
+}
+
+void
+iked_radius_config(struct iked_radserver_req *req, const RADIUS_PACKET *pkt,
+    int cfg_type, uint32_t vendor_id, uint8_t attr_type)
+{
+       unsigned int             i;
+       struct iked_sa          *sa = req->rr_sa;
+       struct in_addr           ia4;
+       struct in6_addr          ia6;
+       struct sockaddr_in      *sin4;
+       struct sockaddr_in6     *sin6;
+       struct iked_addr        *addr;
+       struct iked_cfg         *ikecfg;
+
+       for (i = 0; i < sa->sa_policy->pol_ncfg; i++) {
+               ikecfg = &sa->sa_policy->pol_cfg[i];
+               if (ikecfg->cfg_type == cfg_type &&
+                   ikecfg->cfg_type != IKEV2_CFG_INTERNAL_IP4_ADDRESS)
+                       return; /* use config rather than radius */
+       }
+       switch (cfg_type) {
+       case IKEV2_CFG_INTERNAL_IP4_ADDRESS:
+       case IKEV2_CFG_INTERNAL_IP4_NETMASK:
+       case IKEV2_CFG_INTERNAL_IP4_DNS:
+       case IKEV2_CFG_INTERNAL_IP4_NBNS:
+       case IKEV2_CFG_INTERNAL_IP4_DHCP:
+       case IKEV2_CFG_INTERNAL_IP4_SERVER:
+               if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+                       radius_get_ipv4_attr(pkt, attr_type, &ia4);
+               else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+                   attr_type))
+                       radius_get_vs_ipv4_attr(pkt, vendor_id, attr_type,
+                           &ia4);
+               else
+                       break; /* no attribute contained */
+
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP4_NETMASK) {
+                       /*
+                        * This assumes IKEV2_CFG_INTERNAL_IP4_ADDRESS is
+                        * called before IKEV2_CFG_INTERNAL_IP4_NETMASK
+                        */
+                       if (sa->sa_rad_addr == NULL) {
+                               /*
+                                * RFC 7296, IKEV2_CFG_INTERNAL_IP4_NETMASK
+                                * must be used with
+                                * IKEV2_CFG_INTERNAL_IP4_ADDRESS
+                                */
+                               break;
+                       }
+                       if (ia4.s_addr == 0) {
+                               log_debug("%s: netmask is wrong", __func__);
+                               break;
+                       }
+                       if (ia4.s_addr == htonl(0))
+                               sa->sa_rad_addr->addr_mask = 0;
+                       else
+                               sa->sa_rad_addr->addr_mask =
+                                   33 - ffs(ntohl(ia4.s_addr));
+                       if (sa->sa_rad_addr->addr_mask < 32)
+                               sa->sa_rad_addr->addr_net = 1;
+               }
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP4_ADDRESS) {
+                       if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+                               log_warn("%s: calloc", __func__);
+                               return;
+                       }
+                       sa->sa_rad_addr = addr;
+               } else {
+                       req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+                       req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+                       addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+                       req->rr_ncfg++;
+               }
+               addr->addr_af = AF_INET;
+               sin4 = (struct sockaddr_in *)&addr->addr;
+               sin4->sin_family = AF_INET;
+               sin4->sin_len = sizeof(struct sockaddr_in);
+               sin4->sin_addr = ia4;
+               break;
+       case IKEV2_CFG_INTERNAL_IP6_ADDRESS:
+       case IKEV2_CFG_INTERNAL_IP6_DNS:
+       case IKEV2_CFG_INTERNAL_IP6_NBNS:
+       case IKEV2_CFG_INTERNAL_IP6_DHCP:
+       case IKEV2_CFG_INTERNAL_IP6_SERVER:
+               if (vendor_id == 0 && radius_has_attr(pkt, attr_type))
+                       radius_get_ipv6_attr(pkt, attr_type, &ia6);
+               else if (vendor_id != 0 && radius_has_vs_attr(pkt, vendor_id,
+                   attr_type))
+                       radius_get_vs_ipv6_attr(pkt, vendor_id, attr_type,
+                           &ia6);
+               else
+                       break; /* no attribute contained */
+
+               if (cfg_type == IKEV2_CFG_INTERNAL_IP6_ADDRESS) {
+                       if ((addr = calloc(1, sizeof(*addr))) == NULL) {
+                               log_warn("%s: calloc", __func__);
+                               return;
+                       }
+                       sa->sa_rad_addr = addr;
+               } else {
+                       req->rr_cfg[req->rr_ncfg].cfg_action = IKEV2_CP_REPLY;
+                       req->rr_cfg[req->rr_ncfg].cfg_type = cfg_type;
+                       addr = &req->rr_cfg[req->rr_ncfg].cfg.address;
+                       req->rr_ncfg++;
+               }
+               addr->addr_af = AF_INET;
+               sin6 = (struct sockaddr_in6 *)&addr->addr;
+               sin6->sin6_family = AF_INET6;
+               sin6->sin6_len = sizeof(struct sockaddr_in6);
+               sin6->sin6_addr = ia6;
+               break;
+       }
+       return;
+}
+
+void
+iked_radius_acct_on(struct iked *env)
+{
+       if (TAILQ_EMPTY(&env->sc_radacctservers))
+               return;
+       if (env->sc_radaccton == 0) {   /* trigger once */
+               iked_radius_acct_request(env, NULL,
+                   RADIUS_ACCT_STATUS_TYPE_ACCT_ON);
+               env->sc_radaccton = 1;
+       }
+}
+
+void
+iked_radius_acct_off(struct iked *env)
+{
+       iked_radius_acct_request(env, NULL, RADIUS_ACCT_STATUS_TYPE_ACCT_OFF);
+}
+
+void
+iked_radius_acct_start(struct iked *env, struct iked_sa *sa)
+{
+       iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_START);
+}
+
+void
+iked_radius_acct_stop(struct iked *env, struct iked_sa *sa)
+{
+       iked_radius_acct_request(env, sa, RADIUS_ACCT_STATUS_TYPE_STOP);
+}
+
+void
+iked_radius_acct_request(struct iked *env, struct iked_sa *sa, uint8_t stype)
+{
+       struct iked_radserver_req       *req;
+       RADIUS_PACKET                   *pkt;
+       struct iked_addr                *addr4 = NULL;
+       struct iked_addr                *addr6 = NULL;
+       struct in_addr                   mask4;
+       char                             sa_id[IKED_ID_SIZE];
+       char                             sid[16 + 1];
+       struct timespec                  now;
+       int                              cause;
+
+       if (TAILQ_EMPTY(&env->sc_radacctservers))
+               return;
+       /*
+        * In RFC2866 5.6, "Users who are delivered service without
+        * being authenticated SHOULD NOT generate Accounting records
+        */
+       if (sa != NULL && sa->sa_eapid == NULL) {
+               /* fallback to IKEID for accounting */
+               if (ikev2_print_id(IKESA_DSTID(sa), sa_id, sizeof(sa_id)) != -1)
+                       sa->sa_eapid = strdup(sa_id);
+               if (sa->sa_eapid == NULL)
+                       return;
+       }
+
+       if ((req = calloc(1, sizeof(struct iked_radserver_req))) == NULL) {
+               log_debug("%s: calloc faile for iked_radserver_req: %s",
+                   __func__, strerror(errno));
+               return;
+       }
+       req->rr_accounting = 1;
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       req->rr_accttime = now;
+       timer_set(env, &req->rr_timer, iked_radius_request_send, req);
+
+       if ((pkt = radius_new_request_packet(RADIUS_CODE_ACCOUNTING_REQUEST))
+           == NULL) {
+               log_debug("%s: radius_new_request_packet failed %s", __func__,
+                   strerror(errno));
+               return;
+       }
+
+       /* RFC 2866  5.1. Acct-Status-Type */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE, stype);
+
+       if (sa == NULL) {
+               /* ASSERT(stype == RADIUS_ACCT_STATUS_TYPE_ACCT_ON ||
+                   stype == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) */
+               req->rr_reqpkt = pkt;
+               req->rr_ntry = 0;
+               iked_radius_request_send(env, req);
+               return;
+       }
+
+       iked_radius_fill_attributes(sa, pkt);
+
+       radius_put_string_attr(pkt, RADIUS_TYPE_USER_NAME, sa->sa_eapid);
+
+       /* RFC 2866  5.5. Acct-Session-Id */
+       snprintf(sid, sizeof(sid), "%016llx",
+           (unsigned long long)sa->sa_hdr.sh_ispi);
+       radius_put_string_attr(pkt, RADIUS_TYPE_ACCT_SESSION_ID, sid);
+
+       /* Accounting Request must have Framed-IP-Address */
+       addr4 = sa->sa_addrpool;
+       if (addr4 != NULL) {
+               radius_put_ipv4_attr(pkt, RADIUS_TYPE_FRAMED_IP_ADDRESS,
+                   ((struct sockaddr_in *)&addr4->addr)->sin_addr);
+               if (addr4->addr_mask != 0) {
+                       mask4.s_addr = htonl(
+                           0xFFFFFFFFUL << (32 - addr4->addr_mask));
+                       radius_put_ipv4_attr(pkt,
+                           RADIUS_TYPE_FRAMED_IP_NETMASK, mask4);
+               }
+       }
+       addr6 = sa->sa_addrpool6;
+       if (addr6 != NULL)
+               radius_put_ipv6_attr(pkt, RADIUS_TYPE_FRAMED_IPV6_ADDRESS,
+                   &((struct sockaddr_in6 *)&addr6->addr)->sin6_addr);
+
+       /* RFC2866 5.6 Acct-Authentic */
+       radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_AUTHENTIC,
+           (sa->sa_radreq != NULL)? RADIUS_ACCT_AUTHENTIC_RADIUS :
+           RADIUS_ACCT_AUTHENTIC_LOCAL);
+
+       switch (stype) {
+       case RADIUS_ACCT_STATUS_TYPE_START:
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_STATUS_TYPE,
+                   RADIUS_ACCT_STATUS_TYPE_START);
+               break;
+       case RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE:
+       case RADIUS_ACCT_STATUS_TYPE_STOP:
+               /* RFC 2866 5.7.  Acct-Session-Time */
+               timespecsub(&now, &sa->sa_starttime, &now);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_SESSION_TIME,
+                   now.tv_sec);
+               /* RFC 2866 5.10 Acct-Terminate-Cause */
+               cause = RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL;
+               if (sa->sa_reason) {
+                       if (strcmp(sa->sa_reason, "received delete") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_USER_REQUEST;
+                       } else if (strcmp(sa->sa_reason, "SA rekeyed") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT;
+                       } else if (strncmp(sa->sa_reason, "retransmit",
+                           strlen("retransmit")) == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_LOST_SERVICE;
+                       } else if (strcmp(sa->sa_reason,
+                           "disconnect requested") == 0) {
+                               cause = RADIUS_TERMNATE_CAUSE_ADMIN_RESET;
+                       }
+               }
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE,
+                   cause);
+               /* I/O statistics {Input,Output}-{Packets,Octets,Gigawords} */
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_PACKETS,
+                   sa->sa_stats.sas_ipackets);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS,
+                   sa->sa_stats.sas_opackets);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_OCTETS,
+                   sa->sa_stats.sas_ibytes & 0xffffffffUL);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_OCTETS,
+                   sa->sa_stats.sas_obytes & 0xffffffffUL);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_INPUT_GIGAWORDS,
+                   sa->sa_stats.sas_ibytes >> 32);
+               radius_put_uint32_attr(pkt, RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS,
+                   sa->sa_stats.sas_obytes >> 32);
+               break;
+       }
+       req->rr_reqpkt = pkt;
+       req->rr_ntry = 0;
+       iked_radius_request_send(env, req);
+}
+
+void
+iked_radius_dae_on_event(int fd, short ev, void *ctx)
+{
+       struct iked_raddae      *dae = ctx;
+       struct iked             *env = dae->rd_env;
+       RADIUS_PACKET           *req = NULL, *res = NULL;
+       struct sockaddr_storage  ss;
+       socklen_t                sslen;
+       struct iked_radclient   *client;
+       struct iked_sa          *sa = NULL;
+       char                     attr[256], *endp, *reason;
+       const char              *cp;
+       int                      code, n = 0;
+       uint64_t                 ispi = 0;
+       uint32_t                 u32, cause = 0;
+       struct iked_addr        *addr4 = NULL;
+
+       reason = "disconnect requested";
+
+       sslen = sizeof(ss);
+       req = radius_recvfrom(dae->rd_sock, 0, (struct sockaddr *)&ss, &sslen);
+       if (req == NULL) {
+               log_warn("%s: receiving a RADIUS message failed: %s", __func__,
+                   strerror(errno));
+               return;
+       }
+       TAILQ_FOREACH(client, &env->sc_raddaeclients, rc_entry) {
+               if (sockaddr_cmp((struct sockaddr *)&client->rc_sockaddr,
+                   (struct sockaddr *)&ss, -1) == 0)
+                       break;
+       }
+       if (client == NULL) {
+               log_warnx("%s: received RADIUS message from %s: "
+                   "unknown client", __func__, print_addr(&ss));
+               goto out;
+       }
+
+       if (radius_check_accounting_request_authenticator(req,
+           client->rc_secret) != 0) {
+               log_warnx("%s: received an invalid RADIUS message from %s: bad "
+                   "response authenticator", __func__, print_addr(&ss));
+               goto out;
+       }
+
+       if ((code = radius_get_code(req)) != RADIUS_CODE_DISCONNECT_REQUEST) {
+               /* Code other than Disconnect-Request is not supported */
+               if (code == RADIUS_CODE_COA_REQUEST) {
+                       log_info("received CoA-Request from %s",
+                           print_addr(&ss));
+                       code = RADIUS_CODE_COA_NAK;
+                       cause = RADIUS_ERROR_CAUSE_ADMINISTRATIVELY_PROHIBITED;
+                       goto send;
+               }
+               log_warnx("%s: received an invalid RADIUS message "
+                   "from %s: unknown code %d", __func__,
+                   print_addr(&ss), code);
+               goto out;
+       }
+
+       log_info("received Disconnect-Request from %s", print_addr(&ss));
+
+       if (radius_get_string_attr(req, RADIUS_TYPE_ACCT_SESSION_ID, attr,
+           sizeof(attr)) == 0) {
+               ispi = strtoull(attr, &endp, 16);
+               if (attr[0] != '\0' && *endp == '\0' && errno != ERANGE &&
+                   ispi != ULLONG_MAX) {
+                       RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                               if (sa->sa_hdr.sh_ispi == ispi) {
+                                       ikev2_ike_sa_setreason(sa, reason);
+                                       ikev2_ike_sa_delete(env, sa);
+                                       n++;
+                               }
+                       }
+               }
+       }
+       if (radius_get_string_attr(req, RADIUS_TYPE_USER_NAME, attr,
+           sizeof(attr)) == 0) {
+               RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                       if (sa->sa_eapid != NULL &&
+                           strcmp(sa->sa_eapid, attr) == 0) {
+                               ikev2_ike_sa_setreason(sa, reason);
+                               ikev2_ike_sa_delete(env, sa);
+                               n++;
+                       }
+               }
+       }
+       if (radius_get_uint32_attr(req, RADIUS_TYPE_FRAMED_IP_ADDRESS, &u32)
+            == 0) {
+               addr4 = sa->sa_addrpool;
+               if (addr4 != NULL) {
+                       RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                               if (u32 == ((struct sockaddr_in *)&addr4->addr)
+                                   ->sin_addr.s_addr) {
+                                       ikev2_ike_sa_setreason(sa, reason);
+                                       ikev2_ike_sa_delete(env, sa);
+                                       n++;
+                               }
+                       }
+               }
+       }
+       if (radius_get_string_attr(req, RADIUS_TYPE_CALLED_STATION_ID, attr,
+           sizeof(attr)) != 0) {
+               RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                       cp = print_addr(&sa->sa_local.addr);
+                       if (strcmp(cp, attr) == 0) {
+                               ikev2_ike_sa_setreason(sa, reason);
+                               ikev2_ike_sa_delete(env, sa);
+                               n++;
+                       }
+               }
+       }
+       if (radius_get_string_attr(req, RADIUS_TYPE_CALLING_STATION_ID, attr,
+           sizeof(attr)) != 0) {
+               RB_FOREACH(sa, iked_sas, &env->sc_sas) {
+                       cp = print_addr(&sa->sa_peer.addr);
+                       if (strcmp(cp, attr) == 0) {
+                               ikev2_ike_sa_setreason(sa, reason);
+                               ikev2_ike_sa_delete(env, sa);
+                               n++;
+                       }
+               }
+       }
+
+       if (n > 0)
+               code = RADIUS_CODE_DISCONNECT_ACK;
+       else {
+               code = RADIUS_CODE_DISCONNECT_ACK;
+               cause = RADIUS_ERROR_CAUSE_SESSION_NOT_FOUND;
+       }
+ send:
+       res = radius_new_response_packet(code, req);
+       if (res == NULL) {
+               log_warn("%s: radius_new_response_packet", __func__);
+               goto out;
+       }
+       radius_set_response_authenticator(res, client->rc_secret);
+       if (cause != 0)
+               radius_put_uint32_attr(res, RADIUS_TYPE_ERROR_CAUSE, cause);
+       if (radius_sendto(dae->rd_sock, res, 0, (struct sockaddr *)&ss, sslen)
+           == -1)
+               log_warn("%s: sendto", __func__);
+       log_info("send %s for %s",
+           (code == RADIUS_CODE_DISCONNECT_ACK)? "Disconnect-ACK" :
+           (code == RADIUS_CODE_DISCONNECT_NAK)? "Disconnect-NAK" : "CoA-NAK",
+           print_addr(&ss));
+ out:
+       radius_delete_packet(req);
+       if (res != NULL)
+               radius_delete_packet(res);
+}
index 6690a4a..2f5b8b3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: types.h,v 1.54 2024/02/15 20:10:45 tobhe Exp $        */
+/*     $OpenBSD: types.h,v 1.55 2024/07/13 12:22:46 yasuoka Exp $      */
 
 /*
  * Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
@@ -42,6 +42,7 @@
 #define IKED_PUBKEY            "local.pub"
 
 #define IKED_VENDOR_ID         "OpenIKED-"
+#define IKED_NAS_ID            "OpenIKED"
 
 #define IKED_OCSP_RESPCERT     "ocsp/responder.crt"
 
@@ -112,6 +113,12 @@ enum imsg_type {
        IMSG_CFG_POLICY,
        IMSG_CFG_FLOW,
        IMSG_CFG_USER,
+       IMSG_CFG_RADAUTH,
+       IMSG_CFG_RADACCT,
+       IMSG_CFG_RADSERVER,
+       IMSG_CFG_RADCFGMAP,
+       IMSG_CFG_RADDAE,
+       IMSG_CFG_RADDAECLIENT,
        IMSG_CERTREQ,
        IMSG_CERT,
        IMSG_CERTVALID,
@@ -150,6 +157,7 @@ enum flushmode {
        RESET_POLICY,
        RESET_SA,
        RESET_USER,
+       RESET_RADIUS,
 };
 
 #ifndef nitems