diff options
author | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2024-03-24 00:05:02 +0000 |
---|---|---|
committer | YASUOKA Masahiko <yasuoka@cvs.openbsd.org> | 2024-03-24 00:05:02 +0000 |
commit | 6412064657fe036644466d12ac1379c687d871b5 (patch) | |
tree | 60e33e9f122e0c9813822a967a10e517e0537ac8 /sbin | |
parent | ee7b21a221091aaa69eff37963151de7630ac938 (diff) |
Allow zero-length identity response
ok tobhe
Diffstat (limited to 'sbin')
-rw-r--r-- | sbin/iked/eap.c | 9 | ||||
-rw-r--r-- | sbin/iked/radius.c | 752 |
2 files changed, 759 insertions, 2 deletions
diff --git a/sbin/iked/eap.c b/sbin/iked/eap.c index 137398d91f4..40cbe627da0 100644 --- a/sbin/iked/eap.c +++ b/sbin/iked/eap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: eap.c,v 1.25 2023/07/18 15:07:41 claudio Exp $ */ +/* $OpenBSD: eap.c,v 1.26 2024/03/24 00:05:01 yasuoka Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org> @@ -71,7 +71,12 @@ eap_validate_id_response(struct eap_message *eap) len = betoh16(eap->eap_length) - sizeof(*eap); ptr += sizeof(*eap); - if (len == 0 || (str = get_string(ptr, len)) == NULL) { + if (len == 0) { + if ((str = strdup("")) == NULL) { + log_warn("%s: strdup failed", __func__); + return (NULL); + } + } else if ((str = get_string(ptr, len)) == NULL) { log_info("%s: invalid identity response, length %zu", __func__, len); return (NULL); diff --git a/sbin/iked/radius.c b/sbin/iked/radius.c new file mode 100644 index 00000000000..1614093fa38 --- /dev/null +++ b/sbin/iked/radius.c @@ -0,0 +1,752 @@ +/* $OpenBSD: radius.c,v 1.1 2024/03/24 00:05:01 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_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; + } + + /* 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; + 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); + 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"); + + /* NAS Port Type = Virtual */ + radius_put_uint32_attr(req->rr_reqpkt, + RADIUS_TYPE_NAS_PORT_TYPE, RADIUS_NAS_PORT_TYPE_VIRTUAL); + /* Service Type = Framed */ + radius_put_uint32_attr(req->rr_reqpkt, + RADIUS_TYPE_SERVICE_TYPE, RADIUS_SERVICE_TYPE_FRAMED); + + 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_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; + } + + 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; + } + } + 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); + 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)); + break; + } + req->rr_reqpkt = pkt; + req->rr_ntry = 0; + iked_radius_request_send(env, req); +} |