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