Download raw body.
iked: RADIUS support
On Fri, Jul 12, 2024 at 05:42:43PM +0200, YASUOKA Masahiko wrote:
> Hello,
>
> On Mon, 26 Feb 2024 22:48:15 +0900 (JST)
> YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> > On Mon, 29 Jan 2024 09:43:55 +0900 (JST)
> > YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> >> On Thu, 25 Jan 2024 18:50:38 +0900 (JST)
> >> YASUOKA Masahiko <yasuoka@openbsd.org> wrote:
> >>> The diff adds RADIUS support for iked(8).
> >>>
> >>> ---
> >>> ikev2 RAS passive esp \
> >>> from 0.0.0.0/0 to 0.0.0.0 \
> >>> local any peer any \
> >>> srcid (FQDN) \
> >>> eap radius \
> >>> config address 192.168.0.0/24
> >>>
> >>> radius server 192.168.0.4 secret testing123
> >>> # radius accounting server 192.168.0.4 secret testing123
> >>> ---
> >>>
> >>> We can ask EAP for a RADIUS server which supports EAP. Unfortunetely
> >>> radiusd(8) has no config which terminates EAP yet, so freeradius,
> >>> Windows AD, or other is needed for test.
> >>>
> >>> Also
> >>>
> >>> - Use RADIUS attriubutes for configurations
> >>> - RADIUS accouting is also supported
> >>>
> >>> comments? test? ok?
> >>
> >> Let me update the diff. Now I think it works with EAP methods other
> >> than MSCHAP-V2.
> >>
> >> - feedbacks from markus
> >> - support MSK which legnth != 16
> >> - give "iked_" for the functions in radiusd
> >> - pass EAP messages which type isn't support eap.c
> >
> > Let me share the latest updated diff. There is no big difference.
> > The followings are about the changes from the previous diff.
> >
> > - Fix wording in comments. Found by stu.
> > - Check vendor-id and attribute-type in the config more strictly and
> > accept hex.
> > - Use imsg_get_fd().
> > - Use radius_get_eap_msk() in libradius (it's fixed now).
> > - Use RADIUS_TYPE_FRAMED_IPV6_ADDRESS in <radius.h> (it's defined now).
> > - Include NAS-Identifier in all RADIUS requests.
> > - Send an Accounting-On message when RADIUS accouting is configured
> > for the first time.
>
> Let me update the diff. Changes from the previous.
>
> - Add Dynamic Authorization Extensions (DAE) for RADIUS server
> feature. (radiusd_ipcp(8) recently support DAE)
> - When EAP is used without Identifier, overwrite eapid by
> authenticated username. This is needed for accouting.
> - Rearrange how to add the RADIUS attributes.
>
> The diff is getting bigger. How about committing the diff and
> continue to work on the tree?
I think you are right. let's get it in and continue working on it in-tree.
ok tobhe@
>
>
> Index: sbin/iked/Makefile
> ===================================================================
> RCS file: /cvs/src/sbin/iked/Makefile,v
> diff -u -p -u -p -r1.22 Makefile
> --- sbin/iked/Makefile 28 May 2021 18:01:39 -0000 1.22
> +++ sbin/iked/Makefile 12 Jul 2024 15:22:40 -0000
> @@ -4,15 +4,15 @@ 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: sbin/iked/config.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/config.c,v
> diff -u -p -u -p -r1.97 config.c
> --- sbin/iked/config.c 15 Feb 2024 19:11:00 -0000 1.97
> +++ sbin/iked/config.c 12 Jul 2024 15:22:41 -0000
> @@ -123,6 +123,8 @@ config_free_sa(struct iked *env, struct
> 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
> 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, unsigne
> }
> }
>
> + 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);
> }
>
> @@ -1089,6 +1137,285 @@ config_getkey(struct iked *env, struct i
>
> explicit_bzero(imsg->data, len);
> ca_getkey(&env->sc_ps, &id, imsg->hdr.type);
> +
> + 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: sbin/iked/eap.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/eap.c,v
> diff -u -p -u -p -r1.26 eap.c
> --- sbin/iked/eap.c 24 Mar 2024 00:05:01 -0000 1.26
> +++ sbin/iked/eap.c 12 Jul 2024 15:22:41 -0000
> @@ -583,9 +583,12 @@ eap_parse(struct iked *env, const struct
>
> 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: sbin/iked/eap.h
> ===================================================================
> RCS file: /cvs/src/sbin/iked/eap.h,v
> diff -u -p -u -p -r1.6 eap.h
> --- sbin/iked/eap.h 16 Sep 2020 21:37:35 -0000 1.6
> +++ sbin/iked/eap.h 12 Jul 2024 15:22:41 -0000
> @@ -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: sbin/iked/iked.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/iked.c,v
> diff -u -p -u -p -r1.70 iked.c
> --- sbin/iked/iked.c 15 Feb 2024 20:10:45 -0000 1.70
> +++ sbin/iked/iked.c 12 Jul 2024 15:22:42 -0000
> @@ -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 rese
>
> 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: sbin/iked/iked.conf.5
> ===================================================================
> RCS file: /cvs/src/sbin/iked/iked.conf.5,v
> diff -u -p -u -p -r1.96 iked.conf.5
> --- sbin/iked/iked.conf.5 13 Apr 2024 12:11:08 -0000 1.96
> +++ sbin/iked/iked.conf.5 12 Jul 2024 15:22:42 -0000
> @@ -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
> @@ -779,6 +786,118 @@ for filtering and monitoring.
> 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
> Index: sbin/iked/iked.h
> ===================================================================
> RCS file: /cvs/src/sbin/iked/iked.h,v
> diff -u -p -u -p -r1.230 iked.h
> --- sbin/iked/iked.h 2 Mar 2024 16:16:07 -0000 1.230
> +++ sbin/iked/iked.h 12 Jul 2024 15:22:42 -0000
> @@ -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 *);
> @@ -1156,6 +1261,17 @@ int eap_mschap_challenge(struct iked *,
> uint8_t *, size_t);
> 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);
> Index: sbin/iked/ikev2.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/ikev2.c,v
> diff -u -p -u -p -r1.386 ikev2.c
> --- sbin/iked/ikev2.c 21 Mar 2024 22:08:49 -0000 1.386
> +++ sbin/iked/ikev2.c 12 Jul 2024 15:22:44 -0000
> @@ -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 pri
> 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 pri
> 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
> 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 ik
> 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 ik
> 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, str
> 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, st
> 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, str
> 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, str
> 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, struc
> 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, struc
> 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, struc
> 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,
> 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,
> 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,
> *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,
> 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,
> 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);
> }
>
> @@ -7627,6 +7686,8 @@ void
> 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));
> Index: sbin/iked/ikev2_msg.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/ikev2_msg.c,v
> diff -u -p -u -p -r1.101 ikev2_msg.c
> --- sbin/iked/ikev2_msg.c 2 Mar 2024 16:16:07 -0000 1.101
> +++ sbin/iked/ikev2_msg.c 12 Jul 2024 15:22:45 -0000
> @@ -203,6 +203,7 @@ ikev2_msg_cleanup(struct iked *env, stru
> 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, stru
> 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: sbin/iked/ikev2_pld.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/ikev2_pld.c,v
> diff -u -p -u -p -r1.135 ikev2_pld.c
> --- sbin/iked/ikev2_pld.c 2 Apr 2024 19:58:28 -0000 1.135
> +++ sbin/iked/ikev2_pld.c 12 Jul 2024 15:22:45 -0000
> @@ -2104,6 +2104,15 @@ ikev2_pld_eap(struct iked *env, struct i
>
> 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: sbin/iked/parse.y
> ===================================================================
> RCS file: /cvs/src/sbin/iked/parse.y,v
> diff -u -p -u -p -r1.146 parse.y
> --- sbin/iked/parse.y 25 Apr 2024 14:24:54 -0000 1.146
> +++ sbin/iked/parse.y 12 Jul 2024 15:22:45 -0000
> @@ -38,9 +38,12 @@
> #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_po
> 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, struc
> 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, struc
> 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: sbin/iked/pfkey.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/pfkey.c,v
> diff -u -p -u -p -r1.84 pfkey.c
> --- sbin/iked/pfkey.c 14 Aug 2023 12:02:02 -0000 1.84
> +++ sbin/iked/pfkey.c 12 Jul 2024 15:22:46 -0000
> @@ -111,8 +111,11 @@ int pfkey_write(struct iked *, struct sa
> 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 satyp
> }
>
> 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
> 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
> *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 *e
> 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
> 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: sbin/iked/policy.c
> ===================================================================
> RCS file: /cvs/src/sbin/iked/policy.c,v
> diff -u -p -u -p -r1.98 policy.c
> --- sbin/iked/policy.c 3 Feb 2024 00:54:14 -0000 1.98
> +++ sbin/iked/policy.c 12 Jul 2024 15:22:46 -0000
> @@ -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);
> Index: sbin/iked/radius.c
> ===================================================================
> RCS file: sbin/iked/radius.c
> diff -N sbin/iked/radius.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ sbin/iked/radius.c 12 Jul 2024 15:22:46 -0000
> @@ -0,0 +1,937 @@
> +/* $OpenBSD$ */
> +
> +/*
> + * 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);
> +}
> Index: sbin/iked/types.h
> ===================================================================
> RCS file: /cvs/src/sbin/iked/types.h,v
> diff -u -p -u -p -r1.54 types.h
> --- sbin/iked/types.h 15 Feb 2024 20:10:45 -0000 1.54
> +++ sbin/iked/types.h 12 Jul 2024 15:22:46 -0000
> @@ -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
>
iked: RADIUS support