/* $OpenBSD: radiusd_ipcp.c,v 1.17 2024/09/15 05:31:23 yasuoka Exp $ */ /* * Copyright (c) 2024 Internet Initiative Japan Inc. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "radiusd.h" #include "radiusd_module.h" #include "radiusd_ipcp.h" #include "log.h" #define RADIUSD_IPCP_START_WAIT 60 enum ipcp_address_type { ADDRESS_TYPE_POOL, ADDRESS_TYPE_STATIC }; struct ipcp_address { enum ipcp_address_type type; struct in_addr start; struct in_addr end; int naddrs; TAILQ_ENTRY(ipcp_address) next; }; struct user { TAILQ_HEAD(, assigned_ipv4) ipv4s; RB_ENTRY(user) tree; char name[0]; }; struct radiusctl_client { int peerid; TAILQ_ENTRY(radiusctl_client) entry; }; struct module_ipcp_dae; struct assigned_ipv4 { struct in_addr ipv4; unsigned seq; char session_id[256]; char auth_method[16]; struct user *user; uint32_t session_timeout; struct timespec start; struct timespec timeout; struct in_addr nas_ipv4; struct in6_addr nas_ipv6; char nas_id[256]; const char *tun_type; union { struct sockaddr_in sin4; struct sockaddr_in6 sin6; } tun_client; struct timespec authtime; RB_ENTRY(assigned_ipv4) tree; TAILQ_ENTRY(assigned_ipv4) next; /* RFC 5176 Dynamic Authorization Extensions for RADIUS */ struct module_ipcp_dae *dae; RADIUS_PACKET *dae_reqpkt; TAILQ_ENTRY(assigned_ipv4) dae_next; int dae_ntry; struct event dae_evtimer; TAILQ_HEAD(, radiusctl_client) dae_clients; }; struct module_ipcp_ctrlconn { uint32_t peerid; TAILQ_ENTRY(module_ipcp_ctrlconn) next; }; struct module_ipcp_dae { struct module_ipcp *ipcp; int sock; char nas_id[256]; char secret[80]; union { struct sockaddr_in sin4; struct sockaddr_in6 sin6; } nas_addr; struct event ev_sock; struct event ev_reqs; TAILQ_ENTRY(module_ipcp_dae) next; TAILQ_HEAD(, assigned_ipv4) reqs; int ninflight; }; struct module_ipcp { struct module_base *base; int nsessions; unsigned seq; int max_sessions; int user_max_sessions; int start_wait; int session_timeout; bool no_session_timeout; struct timespec uptime; struct in_addr name_server[2]; struct in_addr netbios_server[2]; RB_HEAD(assigned_ipv4_tree, assigned_ipv4) ipv4s; RB_HEAD(user_tree, user) users; int npools; TAILQ_HEAD(,ipcp_address) addrs; TAILQ_HEAD(,module_ipcp_ctrlconn) ctrls; TAILQ_HEAD(,module_ipcp_dae) daes; struct event ev_timer; }; #ifndef nitems #define nitems(_x) (sizeof((_x)) / sizeof((_x)[0])) #endif #ifndef MAXIMUM #define MAXIMUM(_a, _b) (((_a) > (_b))? (_a) : (_b)) #endif static void ipcp_init(struct module_ipcp *); static void ipcp_start(void *); static void ipcp_stop(void *); static void ipcp_fini(struct module_ipcp *); static void ipcp_config_set(void *, const char *, int, char * const *); static void ipcp_dispatch_control(void *, struct imsg *); static int ipcp_notice_startstop(struct module_ipcp *, struct assigned_ipv4 *, int, struct radiusd_ipcp_statistics *); static void ipcp_resdeco(void *, u_int, const u_char *, size_t reqlen, const u_char *, size_t reslen); static void ipcp_reject(struct module_ipcp *, RADIUS_PACKET *, unsigned int, RADIUS_PACKET *, int); static void ipcp_accounting_request(void *, u_int, const u_char *, size_t); struct assigned_ipv4 *ipcp_ipv4_assign(struct module_ipcp *, struct user *, struct in_addr); static struct assigned_ipv4 *ipcp_ipv4_find(struct module_ipcp *, struct in_addr); static void ipcp_ipv4_delete(struct module_ipcp *, struct assigned_ipv4 *, const char *); static void ipcp_ipv4_release(struct module_ipcp *, struct assigned_ipv4 *); static int assigned_ipv4_compar(struct assigned_ipv4 *, struct assigned_ipv4 *); static struct user *ipcp_user_get(struct module_ipcp *, const char *); static int user_compar(struct user *, struct user *); static int ipcp_prepare_db(void); static int ipcp_restore_from_db(struct module_ipcp *); static void ipcp_put_db(struct module_ipcp *, struct assigned_ipv4 *); static void ipcp_del_db(struct module_ipcp *, struct assigned_ipv4 *); static void ipcp_db_dump_fill_record(struct radiusd_ipcp_db_dump *, int, struct assigned_ipv4 *); static void ipcp_update_time(struct module_ipcp *); static void ipcp_on_timer(int, short, void *); static void ipcp_schedule_timer(struct module_ipcp *); static void ipcp_dae_send_disconnect_request(struct assigned_ipv4 *); static void ipcp_dae_request_on_timeout(int, short, void *); static void ipcp_dae_on_event(int, short, void *); static void ipcp_dae_reset_request(struct assigned_ipv4 *); static void ipcp_dae_send_pending_requests(int, short, void *); static struct ipcp_address *parse_address_range(const char *); static const char *radius_tunnel_type_string(unsigned, const char *); static const char *radius_terminate_cause_string(unsigned); static const char *radius_error_cause_string(unsigned); static int parse_addr(const char *, int, struct sockaddr *, socklen_t); static const char *print_addr(struct sockaddr *, char *, size_t); RB_PROTOTYPE_STATIC(assigned_ipv4_tree, assigned_ipv4, tree, assigned_ipv4_compar); RB_PROTOTYPE_STATIC(user_tree, user, tree, user_compar); int main(int argc, char *argv[]) { struct module_ipcp module_ipcp; struct module_handlers handlers = { .start = ipcp_start, .stop = ipcp_stop, .config_set = ipcp_config_set, .response_decoration = ipcp_resdeco, .accounting_request = ipcp_accounting_request, .dispatch_control = ipcp_dispatch_control }; ipcp_init(&module_ipcp); if ((module_ipcp.base = module_create(STDIN_FILENO, &module_ipcp, &handlers)) == NULL) err(1, "Could not create a module instance"); if (ipcp_prepare_db() == -1) err(1, "ipcp_prepare_db"); module_drop_privilege(module_ipcp.base, 1); if (unveil(_PATH_RADIUSD_IPCP_DB, "rw") == -1) err(1, "unveil"); if (pledge("stdio inet rpath wpath flock", NULL) == -1) err(1, "pledge"); setproctitle("[main]"); module_load(module_ipcp.base); log_init(0); event_init(); module_start(module_ipcp.base); event_loop(0); ipcp_fini(&module_ipcp); event_loop(0); event_base_free(NULL); exit(EXIT_SUCCESS); } void ipcp_init(struct module_ipcp *self) { memset(self, 0, sizeof(struct module_ipcp)); TAILQ_INIT(&self->addrs); RB_INIT(&self->ipv4s); RB_INIT(&self->users); TAILQ_INIT(&self->ctrls); TAILQ_INIT(&self->daes); self->seq = 1; self->no_session_timeout = true; ipcp_update_time(self); } void ipcp_start(void *ctx) { struct module_ipcp *self = ctx; struct ipcp_address *addr; struct module_ipcp_dae *dae; int sock; ipcp_update_time(self); if (self->start_wait == 0) self->start_wait = RADIUSD_IPCP_START_WAIT; /* count pool address*/ TAILQ_FOREACH(addr, &self->addrs, next) { if (addr->type == ADDRESS_TYPE_POOL) self->npools += addr->naddrs; } log_info("number of pooled IP addresses = %d", self->npools); if (ipcp_restore_from_db(self) == -1) { module_send_message(self->base, IMSG_NG, "Restoring the database failed: %s", strerror(errno)); module_stop(self->base); return; } ipcp_schedule_timer(self); /* prepare socket for DAE */ TAILQ_FOREACH(dae, &self->daes, next) { if ((sock = socket(dae->nas_addr.sin4.sin_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { log_warn("%s: could not start dae: socket()", __func__); return; } if (connect(sock, (struct sockaddr *)&dae->nas_addr, dae->nas_addr.sin4.sin_len) == -1) { log_warn("%s: could not start dae: connect()", __func__); return; } dae->sock = sock; event_set(&dae->ev_sock, sock, EV_READ | EV_PERSIST, ipcp_dae_on_event, dae); event_add(&dae->ev_sock, NULL); evtimer_set(&dae->ev_reqs, ipcp_dae_send_pending_requests, dae); } module_send_message(self->base, IMSG_OK, NULL); } void ipcp_stop(void *ctx) { struct module_ipcp *self = ctx; struct module_ipcp_dae *dae; ipcp_update_time(self); /* stop the sockets for DAE */ TAILQ_FOREACH(dae, &self->daes, next) { if (dae->sock >= 0) { event_del(&dae->ev_sock); close(dae->sock); dae->sock = -1; } if (evtimer_pending(&dae->ev_reqs, NULL)) event_del(&dae->ev_reqs); } if (evtimer_pending(&self->ev_timer, NULL)) evtimer_del(&self->ev_timer); } void ipcp_fini(struct module_ipcp *self) { struct assigned_ipv4 *assign, *assignt; struct user *user, *usert; struct module_ipcp_ctrlconn *ctrl, *ctrlt; struct module_ipcp_dae *dae, *daet; struct ipcp_address *addr, *addrt; RB_FOREACH_SAFE(assign, assigned_ipv4_tree, &self->ipv4s, assignt) ipcp_ipv4_release(self, assign); RB_FOREACH_SAFE(user, user_tree, &self->users, usert) { RB_REMOVE(user_tree, &self->users, user); free(user); } TAILQ_FOREACH_SAFE(ctrl, &self->ctrls, next, ctrlt) free(ctrl); TAILQ_FOREACH_SAFE(dae, &self->daes, next, daet) { if (dae->sock >= 0) { event_del(&dae->ev_sock); close(dae->sock); } free(dae); } TAILQ_FOREACH_SAFE(addr, &self->addrs, next, addrt) free(addr); if (evtimer_pending(&self->ev_timer, NULL)) evtimer_del(&self->ev_timer); module_destroy(self->base); } void ipcp_config_set(void *ctx, const char *name, int argc, char * const * argv) { struct module_ipcp *module = ctx; const char *errmsg = "none"; int i; struct ipcp_address *addr; struct in_addr ina; struct module_ipcp_dae dae, *dae0; if (strcmp(name, "address") == 0) { SYNTAX_ASSERT(argc >= 1, "specify one of pool, server, nas-select, or user-select"); if (strcmp(argv[0], "pool") == 0) { SYNTAX_ASSERT(argc >= 2, "`address pool' must have one address range at " "least"); addr = TAILQ_FIRST(&module->addrs); for (i = 0; i < argc - 1; i++) { if ((addr = parse_address_range(argv[i + 1])) == NULL) { module_send_message(module->base, IMSG_NG, "Invalid address range: " "%s", argv[i + 1]); return; } addr->type = ADDRESS_TYPE_POOL; TAILQ_INSERT_TAIL(&module->addrs, addr, next); } } else if (strcmp(argv[0], "static") == 0) { SYNTAX_ASSERT(argc >= 2, "`address static' must have one address range at " "least"); addr = TAILQ_FIRST(&module->addrs); for (i = 0; i < argc - 1; i++) { if ((addr = parse_address_range(argv[i + 1])) == NULL) { module_send_message(module->base, IMSG_NG, "Invalid address range: " "%s", argv[i + 1]); return; } addr->type = ADDRESS_TYPE_STATIC; TAILQ_INSERT_TAIL(&module->addrs, addr, next); } } else SYNTAX_ASSERT(0, "specify pool or static"); } else if (strcmp(name, "max-sessions") == 0) { SYNTAX_ASSERT(argc == 1, "`max-sessions' must have an argument"); module->max_sessions = strtonum(argv[0], 0, INT_MAX, &errmsg); if (errmsg != NULL) { module_send_message(module->base, IMSG_NG, "could not parse `max-sessions': %s", errmsg); return; } } else if (strcmp(name, "user-max-sessions") == 0) { SYNTAX_ASSERT(argc == 1, "`max-session' must have an argument"); module->user_max_sessions = strtonum(argv[0], 0, INT_MAX, &errmsg); if (errmsg != NULL) { module_send_message(module->base, IMSG_NG, "could not parse `user-max-session': %s", errmsg); return; } } else if (strcmp(name, "start-wait") == 0) { SYNTAX_ASSERT(argc == 1, "`start-wait' must have an argument"); module->start_wait = strtonum(argv[0], 1, INT_MAX, &errmsg); if (errmsg != NULL) { module_send_message(module->base, IMSG_NG, "could not parse `start-wait': %s", errmsg); return; } } else if (strcmp(name, "name-server") == 0) { SYNTAX_ASSERT(argc == 1 || argc == 2, "specify 1 or 2 addresses for `name-server'"); for (i = 0; i < argc; i++) { if (inet_pton(AF_INET, argv[i], &ina) != 1) { module_send_message(module->base, IMSG_NG, "Invalid IP address: %s", argv[i]); return; } if (module->name_server[0].s_addr == 0) module->name_server[0] = ina; else if (module->name_server[1].s_addr == 0) module->name_server[1] = ina; else SYNTAX_ASSERT(0, "too many `name-server' is configured"); } } else if (strcmp(name, "netbios-server") == 0) { SYNTAX_ASSERT(argc == 1 || argc == 2, "specify 1 or 2 addresses for `name-server'"); for (i = 0; i < argc; i++) { if (inet_pton(AF_INET, argv[i], &ina) != 1) { module_send_message(module->base, IMSG_NG, "Invalid IP address: %s", argv[i]); return; } if (module->netbios_server[0].s_addr == 0) module->netbios_server[0] = ina; else if (module->netbios_server[1].s_addr == 0) module->netbios_server[1] = ina; else SYNTAX_ASSERT(0, "too many `name-server' is configured"); } } else if (strcmp(name, "session-timeout") == 0) { SYNTAX_ASSERT(argc == 1, "`session-timeout' must have an argument"); if (strcmp(argv[0], "radius") == 0) { module->no_session_timeout = false; module->session_timeout = 0; } else { module->no_session_timeout = false; module->session_timeout = strtonum(argv[0], 1, INT_MAX, &errmsg); if (errmsg != NULL) { module_send_message(module->base, IMSG_NG, "could not parse `session-timeout': %s", errmsg); return; } } } else if (strcmp(name, "dae") == 0) { memset(&dae, 0, sizeof(dae)); dae.sock = -1; if (!(argc >= 1 || strcmp(argv[1], "server") == 0)) { module_send_message(module->base, IMSG_NG, "`%s' is unknown", argv[1]); return; } i = 1; SYNTAX_ASSERT(i < argc, "no address[:port] for dae server"); if (i < argc && parse_addr(argv[i], AF_UNSPEC, (struct sockaddr *) &dae.nas_addr, sizeof(dae.nas_addr)) == -1) { module_send_message(module->base, IMSG_NG, "failed to parse dae server's address, %s", argv[i]); return; } if (ntohs(dae.nas_addr.sin4.sin_port) == 0) dae.nas_addr.sin4.sin_port = htons(RADIUS_DAE_DEFAULT_PORT); i++; SYNTAX_ASSERT(i < argc, "no secret for dae server"); if (strlcpy(dae.secret, argv[i++], sizeof(dae.secret)) >= sizeof(dae.secret)) { module_send_message(module->base, IMSG_NG, "dae server's secret must be < %d bytes", (int)sizeof(dae.secret) - 1); return; } if (i < argc) strlcpy(dae.nas_id, argv[i++], sizeof(dae.nas_id)); if ((dae0 = calloc(1, sizeof(struct module_ipcp_dae))) == NULL) { module_send_message(module->base, IMSG_NG, "%s", strerror(errno)); return; } *dae0 = dae; TAILQ_INIT(&dae0->reqs); TAILQ_INSERT_TAIL(&module->daes, dae0, next); dae0->ipcp = module; } else if (strcmp(name, "_debug") == 0) log_init(1); else if (strncmp(name, "_", 1) == 0) /* ignore */; else { module_send_message(module->base, IMSG_NG, "Unknown config parameter name `%s'", name); return; } module_send_message(module->base, IMSG_OK, NULL); return; syntax_error: module_send_message(module->base, IMSG_NG, "%s", errmsg); } void ipcp_dispatch_control(void *ctx, struct imsg *imsg) { struct module_ipcp *self = ctx; struct assigned_ipv4 *assign; struct radiusd_ipcp_db_dump *dump; struct module_ipcp_ctrlconn *ctrl, *ctrlt; int i; size_t dumpsiz; u_int datalen; unsigned seq; struct radiusctl_client *client; const char *cause; ipcp_update_time(self); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { case IMSG_RADIUSD_MODULE_CTRL_UNBIND: TAILQ_FOREACH_SAFE(ctrl, &self->ctrls, next, ctrlt) { if (ctrl->peerid == imsg->hdr.peerid) { TAILQ_REMOVE(&self->ctrls, ctrl, next); free(ctrl); break; } } break; case IMSG_RADIUSD_MODULE_IPCP_MONITOR: case IMSG_RADIUSD_MODULE_IPCP_DUMP_AND_MONITOR: if ((ctrl = calloc(1, sizeof(struct module_ipcp_ctrlconn))) == NULL) { log_warn("%s: calloc()", __func__); goto fail; } ctrl->peerid = imsg->hdr.peerid; TAILQ_INSERT_TAIL(&self->ctrls, ctrl, next); module_imsg_compose(self->base, IMSG_RADIUSD_MODULE_CTRL_BIND, imsg->hdr.peerid, 0, -1, NULL, 0); if (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_MONITOR) break; /* FALLTHROUGH */ case IMSG_RADIUSD_MODULE_IPCP_DUMP: dumpsiz = MAX_IMSGSIZE; if ((dump = calloc(1, dumpsiz)) == NULL) { log_warn("%s: calloc()", __func__); goto fail; } i = 0; RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { if (!timespecisset(&assign->start)) /* not started yet */ continue; ipcp_db_dump_fill_record(dump, i++, assign); if (RB_NEXT(assigned_ipv4_tree, &self->ipv4s, assign) == NULL) break; if (offsetof(struct radiusd_ipcp_db_dump, records[i + 1]) >= dumpsiz) { module_imsg_compose(self->base, IMSG_RADIUSD_MODULE_IPCP_DUMP, imsg->hdr.peerid, 0, -1, dump, offsetof(struct radiusd_ipcp_db_dump, records[i])); i = 0; } } dump->islast = 1; module_imsg_compose(self->base, IMSG_RADIUSD_MODULE_IPCP_DUMP, imsg->hdr.peerid, 0, -1, dump, offsetof( struct radiusd_ipcp_db_dump, records[i])); freezero(dump ,dumpsiz); break; case IMSG_RADIUSD_MODULE_IPCP_DISCONNECT: case IMSG_RADIUSD_MODULE_IPCP_DELETE: if (datalen < sizeof(unsigned)) { log_warn("%s: received " "%s message size is wrong", __func__, (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_DISCONNECT) ? "IMSG_RADIUSD_MODULE_IPCP_DISCONNECT" : "IMSG_RADIUSD_MODULE_IPCP_DELETE"); goto fail; } seq = *(unsigned *)imsg->data; RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { if (!timespecisset(&assign->start)) /* not started yet */ continue; if (assign->seq == seq) break; } if (assign == NULL) { cause = "session not found"; log_warnx("%s seq=%u requested, but the " "session is not found", (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_DISCONNECT)? "Disconnect" : "Delete", seq); module_imsg_compose(self->base, IMSG_NG, imsg->hdr.peerid, 0, -1, cause, strlen(cause) + 1); } else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_DELETE) { log_info("Delete seq=%u by request", assign->seq); ipcp_ipv4_delete(self, assign, "By control"); module_imsg_compose(self->base, IMSG_OK, imsg->hdr.peerid, 0, -1, NULL, 0); } else { if (assign->dae == NULL) log_warnx("Disconnect seq=%u requested, but " "DAE is not configured", assign->seq); else { log_info("Disconnect seq=%u requested", assign->seq); if ((client = calloc(1, sizeof(struct radiusctl_client))) == NULL) { log_warn("%s: calloc: %m", __func__); goto fail; } client->peerid = imsg->hdr.peerid; if (assign->dae_ntry == 0) ipcp_dae_send_disconnect_request( assign); TAILQ_INSERT_TAIL(&assign->dae_clients, client, entry); } } break; } return; fail: module_stop(self->base); } int ipcp_notice_startstop(struct module_ipcp *self, struct assigned_ipv4 *assign, int start, struct radiusd_ipcp_statistics *stat) { struct module_ipcp_ctrlconn *ctrl; struct radiusd_ipcp_db_dump *dump; size_t dumpsiz; struct iovec iov[2]; int niov = 0; dumpsiz = offsetof(struct radiusd_ipcp_db_dump, records[1]); if ((dump = calloc(1, dumpsiz)) == NULL) { log_warn("%s: calloc()", __func__); return (-1); } dump->islast = 1; ipcp_db_dump_fill_record(dump, 0, assign); iov[niov].iov_base = dump; iov[niov].iov_len = dumpsiz; if (start == 0) { iov[++niov].iov_base = stat; iov[niov].iov_len = sizeof(struct radiusd_ipcp_statistics); } TAILQ_FOREACH(ctrl, &self->ctrls, next) module_imsg_composev(self->base, (start)? IMSG_RADIUSD_MODULE_IPCP_START : IMSG_RADIUSD_MODULE_IPCP_STOP, ctrl->peerid, 0, -1, iov, niov + 1); freezero(dump, dumpsiz); return (0); } void ipcp_resdeco(void *ctx, u_int q_id, const u_char *req, size_t reqlen, const u_char *res, size_t reslen) { struct module_ipcp *self = ctx; RADIUS_PACKET *radres = NULL, *radreq = NULL; struct in_addr addr4; const struct in_addr mask4 = { .s_addr = 0xffffffffUL }; int res_code, msraserr = 935; struct ipcp_address *addr; int i, j, n; bool found = false; char username[256], buf[128]; struct user *user = NULL; struct assigned_ipv4 *assigned = NULL, *assign; ipcp_update_time(self); if ((radres = radius_convert_packet(res, reslen)) == NULL) { log_warn("%s: radius_convert_packet() failed", __func__); goto fatal; } res_code = radius_get_code(radres); if (res_code != RADIUS_CODE_ACCESS_ACCEPT) goto accept; if ((radreq = radius_convert_packet(req, reqlen)) == NULL) { log_warn("%s: radius_convert_packet() failed", __func__); goto fatal; } /* * prefer User-Name of the response rather than the request, * since it must be the authenticated user. */ if (radius_get_string_attr(radres, RADIUS_TYPE_USER_NAME, username, sizeof(username)) != 0 && radius_get_string_attr(radreq, RADIUS_TYPE_USER_NAME, username, sizeof(username)) != 0) { log_warnx("q=%u unexpected request: no user-name", q_id); goto fatal; } if ((addr = TAILQ_FIRST(&self->addrs)) != NULL) { /* The address assignment is configured */ if ((user = ipcp_user_get(self, username)) == NULL) { log_warn("%s: ipcp_user_get()", __func__); goto fatal; } msraserr = 935; if (self->max_sessions != 0) { if (self->nsessions >= self->max_sessions) { log_info("q=%u rejected: number of " "sessions reached the limit(%d)", q_id, self->max_sessions); goto reject; } } if (self->user_max_sessions != 0) { n = 0; TAILQ_FOREACH(assign, &user->ipv4s, next) n++; if (n >= self->user_max_sessions) { log_info("q=%u rejected: number of " "sessions per a user reached the limit(%d)", q_id, self->user_max_sessions); goto reject; } } msraserr = 716; if (radius_get_ipv4_attr(radres, RADIUS_TYPE_FRAMED_IP_ADDRESS, &addr4) == 0) { if (ipcp_ipv4_find(self, addr4) != NULL) log_info("q=%u rejected: server requested IP " "address is busy", q_id); else { /* compare in host byte order */ addr4.s_addr = ntohl(addr4.s_addr); TAILQ_FOREACH(addr, &self->addrs, next) { if (addr->type != ADDRESS_TYPE_STATIC && addr->type != ADDRESS_TYPE_POOL) continue; if (addr->start.s_addr <= addr4.s_addr && addr4.s_addr <= addr->end.s_addr) break; } if (addr == NULL) log_info("q=%u rejected: server " "requested IP address is out of " "the range", q_id); else found = true; /* revert the addr to the network byte order */ addr4.s_addr = htonl(addr4.s_addr); } if (!found) goto reject; } else { n = arc4random_uniform(self->npools); i = 0; TAILQ_FOREACH(addr, &self->addrs, next) { if (addr->type == ADDRESS_TYPE_POOL) { if (i <= n && n < i + addr->naddrs) { j = n - i; break; } i += addr->naddrs; } } for (i = 0; i < self->npools; i++, j++) { if (addr == NULL) break; if (j >= addr->naddrs) { /* next pool */ if ((addr = TAILQ_NEXT(addr, next)) == NULL) addr = TAILQ_FIRST( &self->addrs); j = 0; } addr4.s_addr = htonl(addr->start.s_addr + j); if (ipcp_ipv4_find(self, addr4) == NULL) { found = true; break; } } if (!found) { log_info("q=%u rejected: ran out of the " "address pool", q_id); goto reject; } } if ((assigned = ipcp_ipv4_assign(self, user, addr4)) == NULL) { log_warn("%s: ipcp_ipv4_assign()", __func__); goto fatal; } radius_set_ipv4_attr(radres, RADIUS_TYPE_FRAMED_IP_NETMASK, mask4); radius_del_attr_all(radres, RADIUS_TYPE_FRAMED_IP_ADDRESS); radius_put_ipv4_attr(radres, RADIUS_TYPE_FRAMED_IP_ADDRESS, addr4); log_info("q=%u Assign %s for %s", q_id, inet_ntop(AF_INET, &addr4, buf, sizeof(buf)), username); if (radius_has_attr(radreq, RADIUS_TYPE_USER_PASSWORD)) strlcpy(assigned->auth_method, "PAP", sizeof(assigned->auth_method)); else if (radius_has_attr(radreq, RADIUS_TYPE_CHAP_PASSWORD)) strlcpy(assigned->auth_method, "CHAP", sizeof(assigned->auth_method)); else if (radius_has_vs_attr(radreq, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_CHAP_RESPONSE)) strlcpy(assigned->auth_method, "MS-CHAP", sizeof(assigned->auth_method)); else if (radius_has_vs_attr(radreq, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_CHAP2_RESPONSE)) strlcpy(assigned->auth_method, "MS-CHAP-V2", sizeof(assigned->auth_method)); else if (radius_has_attr(radreq, RADIUS_TYPE_EAP_MESSAGE)) strlcpy(assigned->auth_method, "EAP", sizeof(assigned->auth_method)); } if (self->name_server[0].s_addr != 0) { addr4.s_addr = htonl(self->name_server[0].s_addr); radius_del_vs_attr_all(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER); radius_put_vs_ipv4_attr(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER, self->name_server[0]); } if (self->name_server[1].s_addr != 0) { addr4.s_addr = htonl(self->name_server[1].s_addr); radius_del_vs_attr_all(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER); radius_put_vs_ipv4_attr(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER, self->name_server[1]); } if (self->netbios_server[0].s_addr != 0) { addr4.s_addr = htonl(self->netbios_server[0].s_addr); radius_del_vs_attr_all(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER); radius_put_vs_ipv4_attr(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER, self->netbios_server[0]); } if (self->netbios_server[1].s_addr != 0) { addr4.s_addr = htonl(self->netbios_server[1].s_addr); radius_del_vs_attr_all(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER); radius_put_vs_ipv4_attr(radres, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER, self->netbios_server[1]); } if (!self->no_session_timeout && radius_has_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT)) { radius_get_uint32_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT, &assigned->session_timeout); /* we handle this session-timeout */ radius_del_attr_all(radres, RADIUS_TYPE_SESSION_TIMEOUT); } accept: if (module_resdeco_done(self->base, q_id, radius_get_data(radres), radius_get_length(radres)) == -1) { log_warn("%s: module_resdeco_done() failed", __func__); module_stop(self->base); } if (radreq != NULL) radius_delete_packet(radreq); radius_delete_packet(radres); return; reject: ipcp_reject(self, radreq, q_id, radres, msraserr); radius_delete_packet(radreq); radius_delete_packet(radres); return; fatal: if (radreq != NULL) radius_delete_packet(radreq); if (radres != NULL) radius_delete_packet(radres); module_stop(self->base); } void ipcp_reject(struct module_ipcp *self, RADIUS_PACKET *reqp, unsigned int q_id, RADIUS_PACKET *orig_resp, int mserr) { bool is_eap, is_mschap, is_mschap2; uint8_t attr[256]; size_t attrlen; RADIUS_PACKET *resp; struct { uint8_t code; uint8_t id; uint16_t length; } __packed eap; resp = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, reqp); if (resp == NULL) { log_warn("%s: radius_new_response_packet() failed", __func__); module_accsreq_aborted(self->base, q_id); return; } is_eap = radius_has_attr(reqp, RADIUS_TYPE_EAP_MESSAGE); if (radius_get_vs_raw_attr(reqp, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_CHAP_RESPONSE, attr, &attrlen) == 0) is_mschap = true; else if (radius_get_vs_raw_attr(reqp, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_CHAP2_RESPONSE, attr, &attrlen) == 0) is_mschap2 = true; if (is_eap) { memset(&eap, 0, sizeof(eap)); /* just in case */ eap.code = 1; /* EAP Request */ attrlen = sizeof(attr); if (orig_resp != NULL && radius_get_raw_attr(orig_resp, RADIUS_TYPE_EAP_MESSAGE, &attr, &attrlen) == 0) eap.id = attr[1]; else eap.id = 0; eap.length = htons(sizeof(eap)); radius_put_raw_attr(resp, RADIUS_TYPE_EAP_MESSAGE, &eap, ntohs(eap.length)); } else if (is_mschap || is_mschap2) { attr[0] = attr[1]; /* Copy the ident of the request */ snprintf(attr + 1, sizeof(attr) - 1, "E=%d R=0 V=3", mserr); radius_put_vs_raw_attr(resp, RADIUS_VENDOR_MICROSOFT, RADIUS_VTYPE_MS_CHAP_ERROR, attr, strlen(attr + 1) + 1); } module_resdeco_done(self->base, q_id, radius_get_data(resp), radius_get_length(resp)); radius_delete_packet(resp); } /*********************************************************************** * RADIUS Accounting ***********************************************************************/ void ipcp_accounting_request(void *ctx, u_int q_id, const u_char *pkt, size_t pktlen) { RADIUS_PACKET *radpkt = NULL; int code, af; uint32_t type, delay, uval; struct in_addr addr4, nas_ipv4; struct in6_addr nas_ipv6, ipv6_zero; struct module_ipcp *self = ctx; struct assigned_ipv4 *assign, *assignt; char username[256], nas_id[256], buf[256], buf1[384]; struct timespec dur; struct radiusd_ipcp_statistics stat; struct module_ipcp_dae *dae; ipcp_update_time(self); if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) { log_warn("%s: radius_convert_packet() failed", __func__); module_stop(self->base); return; } code = radius_get_code(radpkt); if (code != RADIUS_CODE_ACCOUNTING_REQUEST && code != RADIUS_CODE_ACCOUNTING_RESPONSE) goto out; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_STATUS_TYPE, &type) != 0) goto out; /* identifier for the NAS */ memset(&ipv6_zero, 0, sizeof(ipv6_zero)); memset(&nas_ipv4, 0, sizeof(nas_ipv4)); memset(&nas_ipv6, 0, sizeof(nas_ipv6)); memset(&nas_id, 0, sizeof(nas_id)); radius_get_ipv4_attr(radpkt, RADIUS_TYPE_NAS_IP_ADDRESS, &nas_ipv4); radius_get_ipv6_attr(radpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, &nas_ipv6); radius_get_string_attr(radpkt, RADIUS_TYPE_NAS_IDENTIFIER, nas_id, sizeof(nas_id)); if (nas_ipv4.s_addr == 0 && IN6_ARE_ADDR_EQUAL(&nas_ipv6, &ipv6_zero) && nas_id[0] == '\0') { log_warnx("q=%u no NAS-IP-Address, NAS-IPV6-Address, or " "NAS-Identifier", q_id); goto out; } if (type == RADIUS_ACCT_STATUS_TYPE_ACCT_ON || type == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) { /* * NAS or daemon is restarted. Delete all assigned records * from it */ RB_FOREACH_SAFE(assign, assigned_ipv4_tree, &self->ipv4s, assignt) { if (assign->nas_ipv4.s_addr != nas_ipv4.s_addr || !IN6_ARE_ADDR_EQUAL(&assign->nas_ipv6, &nas_ipv6) || strcmp(assign->nas_id, nas_id) != 0) continue; log_info("q=%u Delete record for %s", q_id, inet_ntop(AF_INET, &assign->ipv4, buf, sizeof(buf))); ipcp_ipv4_delete(self, assign, (type == RADIUS_ACCT_STATUS_TYPE_ACCT_ON) ? "Receive Acct-On" : "Receive Acct-Off"); } return; } if (radius_get_ipv4_attr(radpkt, RADIUS_TYPE_FRAMED_IP_ADDRESS, &addr4) != 0) goto out; if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username, sizeof(username)) != 0) goto out; if ((assign = ipcp_ipv4_find(self, addr4)) == NULL) /* not assigned by this */ goto out; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_DELAY_TIME, &delay) != 0) delay = 0; if (type == RADIUS_ACCT_STATUS_TYPE_START) { assign->start = self->uptime; assign->start.tv_sec -= delay; if (!self->no_session_timeout && (self->session_timeout > 0 || assign->session_timeout > 0)) { assign->timeout = assign->start; if (self->session_timeout > 0) assign->timeout.tv_sec += self->session_timeout; else assign->timeout.tv_sec += assign->session_timeout; } assign->nas_ipv4 = nas_ipv4; assign->nas_ipv6 = nas_ipv6; strlcpy(assign->nas_id, nas_id, sizeof(assign->nas_id)); if (radius_get_string_attr(radpkt, RADIUS_TYPE_ACCT_SESSION_ID, assign->session_id, sizeof(assign->session_id)) != 0) assign->session_id[0] = '\0'; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_TUNNEL_TYPE, &uval) == 0) assign->tun_type = radius_tunnel_type_string(uval, NULL); if (assign->tun_type == NULL) assign->tun_type = ""; /* * Get "tunnel from" from Tunnel-Client-Endpoint or Calling- * Station-Id */ af = AF_UNSPEC; if (radius_get_string_attr(radpkt, RADIUS_TYPE_TUNNEL_CLIENT_ENDPOINT, buf, sizeof(buf)) == 0) { if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_TUNNEL_MEDIUM_TYPE, &uval) == 0) { if (uval == RADIUS_TUNNEL_MEDIUM_TYPE_IPV4) af = AF_INET; else if (uval == RADIUS_TUNNEL_MEDIUM_TYPE_IPV6) af = AF_INET6; } parse_addr(buf, af, (struct sockaddr *) &assign->tun_client, sizeof(assign->tun_client)); } if (assign->tun_client.sin4.sin_family == 0 && radius_get_string_attr(radpkt, RADIUS_TYPE_CALLING_STATION_ID, buf, sizeof(buf)) == 0) parse_addr(buf, af, (struct sockaddr *) &assign->tun_client, sizeof(assign->tun_client)); TAILQ_FOREACH(dae, &self->daes, next) { if (dae->nas_id[0] == '\0' || strcmp(dae->nas_id, assign->nas_id) == 0) break; } assign->dae = dae; ipcp_put_db(self, assign); ipcp_schedule_timer(self); if (ipcp_notice_startstop(self, assign, 1, NULL) != 0) goto fail; log_info("q=%u Start seq=%u user=%s duration=%dsec " "session=%s tunnel=%s from=%s auth=%s ip=%s", q_id, assign->seq, assign->user->name, delay, assign->session_id, assign->tun_type, print_addr((struct sockaddr *) &assign->tun_client, buf1, sizeof(buf1)), assign->auth_method, inet_ntop(AF_INET, &addr4, buf, sizeof(buf))); } else if (type == RADIUS_ACCT_STATUS_TYPE_STOP) { memset(&stat, 0, sizeof(stat)); dur = self->uptime; dur.tv_sec -= delay; timespecsub(&dur, &assign->start, &dur); if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_INPUT_OCTETS, &uval) == 0) stat.ibytes = uval; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_INPUT_GIGAWORDS, &uval) == 0) stat.ibytes = ((uint64_t)uval << 32) | stat.ibytes; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_OUTPUT_OCTETS, &uval) == 0) stat.obytes = uval; if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS, &uval) == 0) stat.obytes = ((uint64_t)uval << 32) | stat.obytes; radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_INPUT_PACKETS, &stat.ipackets); radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS, &stat.opackets); if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_TERMINATE_CAUSE, &uval) == 0) strlcpy(stat.cause, radius_terminate_cause_string(uval), sizeof(stat.cause)); log_info("q=%u Stop seq=%u user=%s duration=%lldsec " "session=%s tunnel=%s from=%s auth=%s ip=%s " "datain=%"PRIu64"bytes,%" PRIu32"packets dataout=%"PRIu64 "bytes,%"PRIu32"packets cause=\"%s\"", q_id, assign->seq, assign->user->name, dur.tv_sec, assign->session_id, assign->tun_type, print_addr( (struct sockaddr *)&assign->tun_client, buf1, sizeof(buf1)), assign->auth_method, inet_ntop(AF_INET, &addr4, buf, sizeof(buf)), stat.ibytes, stat.ipackets, stat.obytes, stat.opackets, stat.cause); ipcp_del_db(self, assign); if (ipcp_notice_startstop(self, assign, 0, &stat) != 0) goto fail; ipcp_ipv4_release(self, ipcp_ipv4_find(self, addr4)); } out: radius_delete_packet(radpkt); return; fail: module_stop(self->base); radius_delete_packet(radpkt); return; } /*********************************************************************** * On memory database to manage IP address assignment ***********************************************************************/ struct assigned_ipv4 * ipcp_ipv4_assign(struct module_ipcp *self, struct user *user, struct in_addr ina) { struct assigned_ipv4 *ip; ip = calloc(1, sizeof(struct assigned_ipv4)); if (ip == NULL) { log_warn("%s: calloc()", __func__); return (NULL); } ip->ipv4 = ina; ip->user = user; ip->authtime = self->uptime; RB_INSERT(assigned_ipv4_tree, &self->ipv4s, ip); TAILQ_INSERT_TAIL(&user->ipv4s, ip, next); TAILQ_INIT(&ip->dae_clients); self->nsessions++; ip->seq = self->seq++; return (ip); } struct assigned_ipv4 * ipcp_ipv4_find(struct module_ipcp *self, struct in_addr ina) { struct assigned_ipv4 key, *ret; struct timespec dif; key.ipv4 = ina; ret = RB_FIND(assigned_ipv4_tree, &self->ipv4s, &key); if (ret != NULL && ret->start.tv_sec == 0) { /* not yet assigned */ timespecsub(&self->uptime, &ret->authtime, &dif); if (dif.tv_sec >= self->start_wait) { /* assumed NAS finally didn't use the address */ TAILQ_REMOVE(&ret->user->ipv4s, ret, next); RB_REMOVE(assigned_ipv4_tree, &self->ipv4s, ret); free(ret); ret = NULL; self->nsessions--; } } return (ret); } void ipcp_ipv4_delete(struct module_ipcp *self, struct assigned_ipv4 *assign, const char *cause) { static struct radiusd_ipcp_statistics stat = { 0 }; memset(stat.cause, 0, sizeof(stat.cause)); strlcpy(stat.cause, cause, sizeof(stat.cause)); ipcp_del_db(self, assign); ipcp_notice_startstop(self, assign, 0, &stat); ipcp_ipv4_release(self, assign); } void ipcp_ipv4_release(struct module_ipcp *self, struct assigned_ipv4 *assign) { if (assign != NULL) { TAILQ_REMOVE(&assign->user->ipv4s, assign, next); RB_REMOVE(assigned_ipv4_tree, &self->ipv4s, assign); self->nsessions--; ipcp_dae_reset_request(assign); free(assign); } } int assigned_ipv4_compar(struct assigned_ipv4 *a, struct assigned_ipv4 *b) { return (b->ipv4.s_addr - a->ipv4.s_addr); } struct user * ipcp_user_get(struct module_ipcp *self, const char *username) { struct { struct user user; char name[256]; } key; struct user *elm; strlcpy(key.user.name, username, 256); elm = RB_FIND(user_tree, &self->users, &key.user); if (elm == NULL) { if ((elm = calloc(1, offsetof(struct user, name[ strlen(username) + 1]))) == NULL) return (NULL); memcpy(elm->name, username, strlen(username)); RB_INSERT(user_tree, &self->users, elm); TAILQ_INIT(&elm->ipv4s); } return (elm); } int user_compar(struct user *a, struct user *b) { return (strcmp(a->name, b->name)); } RB_GENERATE_STATIC(assigned_ipv4_tree, assigned_ipv4, tree, assigned_ipv4_compar); RB_GENERATE_STATIC(user_tree, user, tree, user_compar); /*********************************************************************** * DB for the persistent over processes ***********************************************************************/ int ipcp_prepare_db(void) { struct passwd *pw; DB *db; if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_CREAT | O_RDWR | O_EXLOCK, 0600, DB_BTREE, NULL)) == NULL) return (-1); if ((pw = getpwnam(RADIUSD_USER)) == NULL) return (-1); fchown(db->fd(db), pw->pw_uid, pw->pw_gid); db->close(db); return (0); } int ipcp_restore_from_db(struct module_ipcp *self) { DB *db; DBT key, val; char keybuf[128]; struct user *user; struct radiusd_ipcp_db_record *record; struct assigned_ipv4 *assigned; struct in_addr ipv4; struct module_ipcp_dae *dae; if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDONLY | O_SHLOCK, 0600, DB_BTREE, NULL)) == NULL) return (-1); key.data = "ipv4/"; key.size = 5; if (db->seq(db, &key, &val, R_CURSOR) == 0) { do { if (key.size >= sizeof(keybuf)) break; memcpy(keybuf, key.data, key.size); keybuf[key.size] = '\0'; if (strncmp(keybuf, "ipv4/", 5) != 0) break; inet_pton(AF_INET, keybuf + 5, &ipv4); record = (struct radiusd_ipcp_db_record *)val.data; if ((user = ipcp_user_get(self, record->username)) == NULL) return (-1); if ((assigned = ipcp_ipv4_assign(self, user, ipv4)) == NULL) return (-1); assigned->seq = record->seq; self->seq = MAXIMUM(assigned->seq + 1, self->seq); strlcpy(assigned->auth_method, record->auth_method, sizeof(assigned->auth_method)); strlcpy(assigned->session_id, record->session_id, sizeof(assigned->session_id)); assigned->start = record->start; assigned->timeout = record->timeout; assigned->nas_ipv4 = record->nas_ipv4; assigned->nas_ipv6 = record->nas_ipv6; strlcpy(assigned->nas_id, record->nas_id, sizeof(assigned->nas_id)); assigned->tun_type = radius_tunnel_type_string(0, record->tun_type); memcpy(&assigned->tun_client, &record->tun_client, sizeof(assigned->tun_client)); TAILQ_FOREACH(dae, &self->daes, next) { if (dae->nas_id[0] == '\0' || strcmp(dae->nas_id, assigned->nas_id) == 0) break; } assigned->dae = dae; } while (db->seq(db, &key, &val, R_NEXT) == 0); } db->close(db); return (0); } void ipcp_put_db(struct module_ipcp *self, struct assigned_ipv4 *assigned) { DB *db; DBT key, val; char keybuf[128]; struct radiusd_ipcp_db_record record; strlcpy(keybuf, "ipv4/", sizeof(keybuf)); inet_ntop(AF_INET, &assigned->ipv4, keybuf + 5, sizeof(keybuf) - 5); key.data = keybuf; key.size = strlen(keybuf); strlcpy(record.session_id, assigned->session_id, sizeof(record.session_id)); strlcpy(record.auth_method, assigned->auth_method, sizeof(record.auth_method)); strlcpy(record.username, assigned->user->name, sizeof(record.username)); record.seq = assigned->seq; record.start = assigned->start; record.timeout = assigned->timeout; record.nas_ipv4 = assigned->nas_ipv4; record.nas_ipv6 = assigned->nas_ipv6; strlcpy(record.nas_id, assigned->nas_id, sizeof(record.nas_id)); if (assigned->tun_type != NULL) strlcpy(record.tun_type, assigned->tun_type, sizeof(record.tun_type)); memcpy(&record.tun_client, &assigned->tun_client, sizeof(record.tun_client)); val.data = &record; val.size = sizeof(record); if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDWR | O_EXLOCK, 0600, DB_BTREE, NULL)) == NULL) return; db->put(db, &key, &val, 0); db->close(db); } void ipcp_del_db(struct module_ipcp *self, struct assigned_ipv4 *assigned) { DB *db; DBT key; char keybuf[128]; strlcpy(keybuf, "ipv4/", sizeof(keybuf)); inet_ntop(AF_INET, &assigned->ipv4, keybuf + 5, sizeof(keybuf) - 5); key.data = keybuf; key.size = strlen(keybuf); if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDWR | O_EXLOCK, 0600, DB_BTREE, NULL)) == NULL) return; db->del(db, &key, 0); db->close(db); } void ipcp_db_dump_fill_record(struct radiusd_ipcp_db_dump *dump, int idx, struct assigned_ipv4 *assign) { dump->records[idx].af = AF_INET; dump->records[idx].addr.ipv4 = assign->ipv4; dump->records[idx].rec.seq = assign->seq; strlcpy(dump->records[idx].rec.session_id, assign->session_id, sizeof(dump->records[idx].rec.session_id)); strlcpy(dump->records[idx].rec.auth_method, assign->auth_method, sizeof(dump->records[idx].rec.auth_method)); strlcpy(dump->records[idx].rec.username, assign->user->name, sizeof(dump->records[idx].rec.username)); dump->records[idx].rec.start = assign->start; dump->records[idx].rec.timeout = assign->timeout; dump->records[idx].rec.nas_ipv4 = assign->nas_ipv4; dump->records[idx].rec.nas_ipv6 = assign->nas_ipv6; strlcpy(dump->records[idx].rec.nas_id, assign->nas_id, sizeof(dump->records[idx].rec.nas_id)); if (assign->tun_type != NULL) strlcpy(dump->records[idx].rec.tun_type, assign->tun_type, sizeof(dump->records[idx].rec.tun_type)); memcpy(&dump->records[idx].rec.tun_client, &assign->tun_client, sizeof(dump->records[idx].rec.tun_client)); } /*********************************************************************** * Timer ***********************************************************************/ void ipcp_update_time(struct module_ipcp *self) { clock_gettime(CLOCK_BOOTTIME, &self->uptime); } void ipcp_on_timer(int fd, short ev, void *ctx) { struct module_ipcp *self = ctx; ipcp_update_time(self); ipcp_schedule_timer(self); } void ipcp_schedule_timer(struct module_ipcp *self) { struct assigned_ipv4 *assign, *min_assign = NULL; struct timespec tsd; struct timeval tv; /* check session timeout */ RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { if (assign->timeout.tv_sec == 0) continue; if (timespeccmp(&assign->timeout, &self->uptime, <=)) { log_info("Reached session timeout seq=%u", assign->seq); ipcp_dae_send_disconnect_request(assign); memset(&assign->timeout, 0, sizeof(assign->timeout)); ipcp_put_db(self, assign); } if (min_assign == NULL || timespeccmp(&min_assign->timeout, &assign->timeout, >)) min_assign = assign; } if (evtimer_pending(&self->ev_timer, NULL)) evtimer_del(&self->ev_timer); if (min_assign != NULL) { timespecsub(&min_assign->timeout, &self->uptime, &tsd); TIMESPEC_TO_TIMEVAL(&tv, &tsd); evtimer_set(&self->ev_timer, ipcp_on_timer, self); evtimer_add(&self->ev_timer, &tv); } } /*********************************************************************** * Dynamic Authorization Extension for RAIDUS (RFC 5176) ***********************************************************************/ static const int dae_request_timeouts[] = { 2, 4, 8, 8 }; void ipcp_dae_send_disconnect_request(struct assigned_ipv4 *assign) { RADIUS_PACKET *reqpkt = NULL; struct timeval tv; char buf[80]; if (assign->dae == NULL) return; /* DAE is not configured */ if (assign->dae_reqpkt == NULL) { if ((reqpkt = radius_new_request_packet( RADIUS_CODE_DISCONNECT_REQUEST)) == NULL) { log_warn("%s: radius_new_request_packet(): %m", __func__); return; } radius_put_string_attr(reqpkt, RADIUS_TYPE_ACCT_SESSION_ID, assign->session_id); /* * RFC 5176 Section 3, "either the User-Name or * Chargeable-User-Identity attribute SHOULD be present in * Disconnect-Request and CoA-Request packets." */ radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME, assign->user->name); if (assign->nas_id[0] != '\0') radius_put_string_attr(reqpkt, RADIUS_TYPE_NAS_IDENTIFIER, assign->nas_id); if (ntohl(assign->nas_ipv4.s_addr) != 0) radius_put_ipv4_attr(reqpkt, RADIUS_TYPE_NAS_IP_ADDRESS, assign->nas_ipv4); if (!IN6_IS_ADDR_UNSPECIFIED(&assign->nas_ipv6)) radius_put_ipv6_attr(reqpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, &assign->nas_ipv6); radius_set_accounting_request_authenticator(reqpkt, assign->dae->secret); assign->dae_reqpkt = reqpkt; TAILQ_INSERT_TAIL(&assign->dae->reqs, assign, dae_next); } if (assign->dae_ntry == 0) { if (assign->dae->ninflight >= RADIUSD_IPCP_DAE_MAX_INFLIGHT) return; log_info("Sending Disconnect-Request seq=%u to %s", assign->seq, print_addr((struct sockaddr *) &assign->dae->nas_addr, buf, sizeof(buf))); } if (radius_send(assign->dae->sock, assign->dae_reqpkt, 0) < 0) log_warn("%s: sendto: %m", __func__); tv.tv_sec = dae_request_timeouts[assign->dae_ntry]; tv.tv_usec = 0; evtimer_set(&assign->dae_evtimer, ipcp_dae_request_on_timeout, assign); evtimer_add(&assign->dae_evtimer, &tv); if (assign->dae_ntry == 0) assign->dae->ninflight++; assign->dae_ntry++; } void ipcp_dae_request_on_timeout(int fd, short ev, void *ctx) { struct assigned_ipv4 *assign = ctx; char buf[80]; struct radiusctl_client *client; if (assign->dae_ntry >= (int)nitems(dae_request_timeouts)) { log_warnx("No answer for Disconnect-Request seq=%u from %s", assign->seq, print_addr((struct sockaddr *) &assign->dae->nas_addr, buf, sizeof(buf))); TAILQ_FOREACH(client, &assign->dae_clients, entry) module_imsg_compose(assign->dae->ipcp->base, IMSG_NG, client->peerid, 0, -1, NULL, 0); ipcp_dae_reset_request(assign); } else ipcp_dae_send_disconnect_request(assign); } void ipcp_dae_on_event(int fd, short ev, void *ctx) { struct module_ipcp_dae *dae = ctx; struct module_ipcp *self = dae->ipcp; RADIUS_PACKET *radres = NULL; int code; uint32_t u32; struct assigned_ipv4 *assign; char buf[80], causestr[80]; const char *cause = ""; struct radiusctl_client *client; ipcp_update_time(self); if ((ev & EV_READ) == 0) return; if ((radres = radius_recv(dae->sock, 0)) == NULL) { if (errno == EAGAIN) return; log_warn("%s: Failed to receive from %s", __func__, print_addr( (struct sockaddr *)&dae->nas_addr, buf, sizeof(buf))); return; } TAILQ_FOREACH(assign, &dae->reqs, dae_next) { if (radius_get_id(assign->dae_reqpkt) == radius_get_id(radres)) break; } if (assign == NULL) { log_warnx("%s: Received RADIUS packet from %s has unknown " "id=%d", __func__, print_addr((struct sockaddr *) &dae->nas_addr, buf, sizeof(buf)), radius_get_id(radres)); goto out; } radius_set_request_packet(radres, assign->dae_reqpkt); if ((radius_check_response_authenticator(radres, dae->secret)) != 0) { log_warnx("%s: Received RADIUS packet for seq=%u from %s has " "a bad authenticator", __func__, assign->seq, print_addr( (struct sockaddr *)&dae->nas_addr, buf, sizeof(buf))); goto out; } causestr[0] = '\0'; if (radius_get_uint32_attr(radres, RADIUS_TYPE_ERROR_CAUSE, &u32) == 0){ cause = radius_error_cause_string(u32); if (cause != NULL) snprintf(causestr, sizeof(causestr), " cause=%u(%s)", u32, cause); else snprintf(causestr, sizeof(causestr), " cause=%u", u32); cause = causestr; } code = radius_get_code(radres); switch (code) { case RADIUS_CODE_DISCONNECT_ACK: log_info("Received Disconnect-ACK for seq=%u from %s%s", assign->seq, print_addr((struct sockaddr *) &dae->nas_addr, buf, sizeof(buf)), cause); break; case RADIUS_CODE_DISCONNECT_NAK: log_info("Received Disconnect-NAK for seq=%u from %s%s", assign->seq, print_addr((struct sockaddr *) &dae->nas_addr, buf, sizeof(buf)), cause); break; default: log_warn("%s: Received unknown code=%d for id=%u from %s", __func__, code, assign->seq, print_addr((struct sockaddr *) &dae->nas_addr, buf, sizeof(buf))); break; } TAILQ_FOREACH(client, &assign->dae_clients, entry) { if (*cause != '\0') module_imsg_compose(self->base, (code == RADIUS_CODE_DISCONNECT_ACK) ? IMSG_OK : IMSG_NG, client->peerid, 0, -1, cause + 1, strlen(cause + 1) + 1); else module_imsg_compose(self->base, (code == RADIUS_CODE_DISCONNECT_ACK) ? IMSG_OK : IMSG_NG, client->peerid, 0, -1, NULL, 0); } ipcp_dae_reset_request(assign); out: if (radres != NULL) radius_delete_packet(radres); } void ipcp_dae_reset_request(struct assigned_ipv4 *assign) { struct radiusctl_client *client, *clientt; const struct timeval zero = { 0, 0 }; if (assign->dae != NULL) { if (assign->dae_reqpkt != NULL) TAILQ_REMOVE(&assign->dae->reqs, assign, dae_next); if (assign->dae_ntry > 0) { assign->dae->ninflight--; if (!evtimer_pending(&assign->dae->ev_reqs, NULL)) evtimer_add(&assign->dae->ev_reqs, &zero); } } if (assign->dae_reqpkt != NULL) radius_delete_packet(assign->dae_reqpkt); assign->dae_reqpkt = NULL; if (evtimer_pending(&assign->dae_evtimer, NULL)) evtimer_del(&assign->dae_evtimer); TAILQ_FOREACH_SAFE(client, &assign->dae_clients, entry, clientt) { TAILQ_REMOVE(&assign->dae_clients, client, entry); free(client); } assign->dae_ntry = 0; } void ipcp_dae_send_pending_requests(int fd, short ev, void *ctx) { struct module_ipcp_dae *dae = ctx; struct module_ipcp *self = dae->ipcp; struct assigned_ipv4 *assign, *assignt; ipcp_update_time(self); TAILQ_FOREACH_SAFE(assign, &dae->reqs, dae_next, assignt) { if (dae->ninflight >= RADIUSD_IPCP_DAE_MAX_INFLIGHT) break; if (assign->dae_ntry == 0) /* pending */ ipcp_dae_send_disconnect_request(assign); } } /*********************************************************************** * Miscellaneous functions ***********************************************************************/ struct ipcp_address * parse_address_range(const char *range) { char *buf, *sep; int masklen; uint32_t mask; struct in_addr start, end; struct ipcp_address *ret; const char *errstr; buf = strdup(range); if (buf == NULL) goto error; if ((sep = strchr(buf, '-')) != NULL) { *sep = '\0'; if (inet_pton(AF_INET, buf, &start) != 1) goto error; else if (inet_pton(AF_INET, ++sep, &end) != 1) goto error; start.s_addr = ntohl(start.s_addr); end.s_addr = ntohl(end.s_addr); } else { if ((sep = strchr(buf, '/')) != NULL) { *sep = '\0'; if (inet_pton(AF_INET, buf, &start) != 1) goto error; masklen = strtonum(++sep, 0, 32, &errstr); if (errstr != NULL) goto error; } else { if (inet_pton(AF_INET, buf, &start) != 1) goto error; masklen = 32; } mask = 0xFFFFFFFFUL; if (masklen < 32) mask <<= (32 - masklen); start.s_addr = ntohl(start.s_addr) & mask; if (masklen == 32) end = start; else if (masklen == 31) end.s_addr = start.s_addr + 1; else { end.s_addr = start.s_addr + (1 << (32 - masklen)) - 2; start.s_addr = start.s_addr + 1; } } free(buf); if ((ret = calloc(1, sizeof(struct ipcp_address))) == NULL) return (NULL); ret->start = start; ret->end = end; ret->naddrs = end.s_addr - start.s_addr + 1; return (ret); error: free(buf); return (NULL); } const char * radius_tunnel_type_string(unsigned val, const char *label) { unsigned int i; struct { const unsigned constval; const char *label; } tunnel_types[] = { { RADIUS_TUNNEL_TYPE_PPTP, "PPTP" }, { RADIUS_TUNNEL_TYPE_L2F, "L2F" }, { RADIUS_TUNNEL_TYPE_L2TP, "L2TP" }, { RADIUS_TUNNEL_TYPE_ATMP, "ATMP" }, { RADIUS_TUNNEL_TYPE_VTP, "VTP" }, { RADIUS_TUNNEL_TYPE_AH, "AH" }, { RADIUS_TUNNEL_TYPE_IP, "IP" }, { RADIUS_TUNNEL_TYPE_MOBILE, "MIN-IP-IP" }, { RADIUS_TUNNEL_TYPE_ESP, "ESP" }, { RADIUS_TUNNEL_TYPE_GRE, "GRE" }, { RADIUS_TUNNEL_TYPE_VDS, "DVS" }, /* [MS-RNAS] 3.3.5.1.9 Tunnel-Type */ { RADIUS_VENDOR_MICROSOFT << 8 | 1, "SSTP" } }; if (label != NULL) { /* for conversion to the const value */ for (i = 0; i < nitems(tunnel_types); i++) { if (strcmp(tunnel_types[i].label, label) == 0) return (tunnel_types[i].label); } } for (i = 0; i < nitems(tunnel_types); i++) { if (tunnel_types[i].constval == val) return (tunnel_types[i].label); } return (NULL); } const char * radius_terminate_cause_string(unsigned val) { unsigned int i; struct { const unsigned constval; const char *label; } terminate_causes[] = { { RADIUS_TERMNATE_CAUSE_USER_REQUEST, "User Request" }, { RADIUS_TERMNATE_CAUSE_LOST_CARRIER, "Lost Carrier" }, { RADIUS_TERMNATE_CAUSE_LOST_SERVICE, "Lost Service" }, { RADIUS_TERMNATE_CAUSE_IDLE_TIMEOUT, "Idle Timeout" }, { RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT, "Session Timeout" }, { RADIUS_TERMNATE_CAUSE_ADMIN_RESET, "Admin Reset" }, { RADIUS_TERMNATE_CAUSE_ADMIN_REBOOT, "Admin Reboot" }, { RADIUS_TERMNATE_CAUSE_PORT_ERROR, "Port Error" }, { RADIUS_TERMNATE_CAUSE_NAS_ERROR, "NAS Error" }, { RADIUS_TERMNATE_CAUSE_NAS_RESET, "NAS Request" }, { RADIUS_TERMNATE_CAUSE_NAS_REBOOT, "NAS Reboot" }, { RADIUS_TERMNATE_CAUSE_PORT_UNNEEDED, "Port Unneeded" }, { RADIUS_TERMNATE_CAUSE_PORT_PREEMPTED, "Port Preempted" }, { RADIUS_TERMNATE_CAUSE_PORT_SUSPENDED, "Port Suspended" }, { RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL, "Service Unavailable" }, { RADIUS_TERMNATE_CAUSE_CALLBACK, "Callback" }, { RADIUS_TERMNATE_CAUSE_USER_ERROR, "User Error" }, { RADIUS_TERMNATE_CAUSE_HOST_REQUEST, "Host Request" }, }; for (i = 0; i < nitems(terminate_causes); i++) { if (terminate_causes[i].constval == val) return (terminate_causes[i].label); } return (NULL); } const char * radius_error_cause_string(unsigned val) { unsigned int i; struct { const unsigned constval; const char *label; } error_causes[] = { { RADIUS_ERROR_CAUSE_RESIDUAL_SESSION_REMOVED, "Residual Session Context Removed" }, { RADIUS_ERROR_CAUSE_INVALID_EAP_PACKET, "Invalid EAP Packet (Ignored)" }, { RADIUS_ERROR_CAUSE_UNSUPPORTED_ATTRIBUTE, "Unsupported Attribute" }, { RADIUS_ERROR_CAUSE_MISSING_ATTRIBUTE, "Missing Attribute" }, { RADIUS_ERROR_CAUSE_NAS_IDENTIFICATION_MISMATCH, "NAS Identification Mismatch" }, { RADIUS_ERROR_CAUSE_INVALID_REQUEST, "Invalid Request" }, { RADIUS_ERROR_CAUSE_UNSUPPORTED_SERVICE, "Unsupported Service" }, { RADIUS_ERROR_CAUSE_UNSUPPORTED_EXTENSION, "Unsupported Extension" }, { RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE, "Invalid Attribute Value" }, { RADIUS_ERROR_CAUSE_ADMINISTRATIVELY_PROHIBITED, "Administratively Prohibited" }, { RADIUS_ERROR_CAUSE_REQUEST_NOT_ROUTABLE, "Request Not Routable (Proxy)" }, { RADIUS_ERROR_CAUSE_SESSION_NOT_FOUND, "Session Context Not Found" }, { RADIUS_ERROR_CAUSE_SESSION_NOT_REMOVABLE, "Session Context Not Removable" }, { RADIUS_ERROR_CAUSE_OTHER_PROXY_PROCESSING_ERROR, "Other Proxy Processing Error" }, { RADIUS_ERROR_CAUSE_RESOURCES_UNAVAILABLE, "Resources Unavailable" }, { RADIUS_ERROR_CAUSE_REQUEST_INITIATED, "equest Initiated" }, { RADIUS_ERROR_CAUSE_MULTI_SELECTION_UNSUPPORTED, "Multiple Session Selection Unsupported" } }; for (i = 0; i < nitems(error_causes); i++) { if (error_causes[i].constval == val) return (error_causes[i].label); } return (NULL); } int parse_addr(const char *str0, int af, struct sockaddr *sa, socklen_t salen) { int error; char *str, *end, *colon, *colon0, *addr = NULL, *port = NULL; char *sb, *sb0; struct addrinfo hints, *ai; if ((str = strdup(str0)) == NULL) return (-1); if (*str == '[' && (end = strchr(str + 1, ']')) != NULL) { addr = str + 1; *end = '\0'; if (*(end + 1) == ':') port = end + 2; else if (*(end + 1) == '[' && (sb = strrchr(end + 2, ']')) != NULL) { port = end + 2; *sb = '\0'; } } else if ((sb0 = strchr(str, '[')) != NULL && (sb = strrchr(sb0 + 1, ']')) != NULL && sb0 < sb) { addr = str; *sb0 = '\0'; port = sb0 + 1; *sb = '\0'; } else if ((colon0 = strchr(str, ':')) != NULL && (colon = strrchr(str, ':')) != NULL && colon0 == colon) { /* has one : */ addr = str; *colon = '\0'; port = colon + 1; } else { addr = str; port = NULL; } memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST; if (port != NULL) hints.ai_flags |= AI_NUMERICSERV; if ((error = getaddrinfo(addr, port, &hints, &ai)) != 0) { free(str); return (-1); } free(str); if (salen < ai->ai_addrlen) { freeaddrinfo(ai); return (-1); } memcpy(sa, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); return (0); } const char * print_addr(struct sockaddr *sa, char *buf, size_t bufsiz) { int noport, ret; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; if (ntohs(((struct sockaddr_in *)sa)->sin_port) == 0) { noport = 1; ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); } else { noport = 0; ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); } if (ret != 0) return ""; if (noport) strlcpy(buf, hbuf, bufsiz); else if (sa->sa_family == AF_INET6) snprintf(buf, bufsiz, "[%s]:%s", hbuf, sbuf); else snprintf(buf, bufsiz, "%s:%s", hbuf, sbuf); return (buf); }