From: yasuoka Date: Sat, 13 Jul 2024 12:22:46 +0000 (+0000) Subject: Add RADIUS support. Authentication, accounting, and "Dynamic X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=f36db9c4e75d6f4b6e40410c8005109158705028;p=openbsd Add RADIUS support. Authentication, accounting, and "Dynamic Authorization Extensions"(DAE) are supported. feedback markus stu ok tobhe --- diff --git a/sbin/iked/Makefile b/sbin/iked/Makefile index 2d0f5365898..783e2eb0705 100644 --- a/sbin/iked/Makefile +++ b/sbin/iked/Makefile @@ -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 diff --git a/sbin/iked/config.c b/sbin/iked/config.c index ff24c4bcc93..d4204509522 100644 --- a/sbin/iked/config.c +++ b/sbin/iked/config.c @@ -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 @@ -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); +} diff --git a/sbin/iked/eap.c b/sbin/iked/eap.c index 40cbe627da0..24d1a898be3 100644 --- a/sbin/iked/eap.c +++ b/sbin/iked/eap.c @@ -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 @@ -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); diff --git a/sbin/iked/eap.h b/sbin/iked/eap.h index 509da7ac6f3..ef617c2f695 100644 --- a/sbin/iked/eap.h +++ b/sbin/iked/eap.h @@ -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 @@ -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[]; diff --git a/sbin/iked/iked.c b/sbin/iked/iked.c index 00bd3f6e2d6..b69a354438a 100644 --- a/sbin/iked/iked.c +++ b/sbin/iked/iked.c @@ -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 @@ -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); diff --git a/sbin/iked/iked.conf.5 b/sbin/iked/iked.conf.5 index 053aee34f81..753a84ef062 100644 --- a/sbin/iked/iked.conf.5 +++ b/sbin/iked/iked.conf.5 @@ -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 .\" 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 diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h index 41720d39787..5d95dd92908 100644 --- a/sbin/iked/iked.h +++ b/sbin/iked/iked.h @@ -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 @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -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 *); diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c index c0add7c3894..ccbab9de1cb 100644 --- a/sbin/iked/ikev2.c +++ b/sbin/iked/ikev2.c @@ -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 @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -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) diff --git a/sbin/iked/ikev2_msg.c b/sbin/iked/ikev2_msg.c index 15ef2825197..12d801f4557 100644 --- a/sbin/iked/ikev2_msg.c +++ b/sbin/iked/ikev2_msg.c @@ -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 @@ -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; diff --git a/sbin/iked/ikev2_pld.c b/sbin/iked/ikev2_pld.c index 27605039aa9..ac8635e79fc 100644 --- a/sbin/iked/ikev2_pld.c +++ b/sbin/iked/ikev2_pld.c @@ -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 @@ -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; } diff --git a/sbin/iked/parse.y b/sbin/iked/parse.y index 613eb8fb49a..970a9e33482 100644 --- a/sbin/iked/parse.y +++ b/sbin/iked/parse.y @@ -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 @@ -38,9 +38,12 @@ #include #include #include +#include #include #include +#include #include +#include #include #include #include @@ -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 STRING %token NUMBER %type string @@ -453,7 +467,7 @@ typedef struct { %type proto proto_list protoval %type hosts hosts_list %type port -%type portval af rdomain +%type portval af rdomain hexdecnumber %type peers %type anyhost %type host host_spec @@ -470,6 +484,8 @@ typedef struct { %type name iface %type cfg ikecfg ikecfgvals %type transform_esn +%type accounting +%type 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", diff --git a/sbin/iked/pfkey.c b/sbin/iked/pfkey.c index ac9e79c20df..02b6cd8973d 100644 --- a/sbin/iked/pfkey.c +++ b/sbin/iked/pfkey.c @@ -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 @@ -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); } diff --git a/sbin/iked/policy.c b/sbin/iked/policy.c index a9e783c82a4..836a454dc1a 100644 --- a/sbin/iked/policy.c +++ b/sbin/iked/policy.c @@ -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 @@ -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 index 00000000000..a9e7d3a9d84 --- /dev/null +++ b/sbin/iked/radius.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/sbin/iked/types.h b/sbin/iked/types.h index 6690a4ab528..2f5b8b3930e 100644 --- a/sbin/iked/types.h +++ b/sbin/iked/types.h @@ -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 @@ -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