/* $OpenBSD: radiusd_module.c,v 1.15 2024/01/08 04:16:48 yasuoka Exp $ */ /* * Copyright (c) 2015 YASUOKA Masahiko * * 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. */ /* radiusd_module.c -- helper functions for radiusd modules */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "radiusd.h" #include "radiusd_module.h" #include "imsg_subr.h" static void (*module_config_set) (void *, const char *, int, char * const *) = NULL; static void (*module_start_module) (void *) = NULL; static void (*module_stop_module) (void *) = NULL; static void (*module_userpass) (void *, u_int, const char *, const char *) = NULL; static void (*module_access_request) (void *, u_int, const u_char *, size_t) = NULL; static void (*module_request_decoration) (void *, u_int, const u_char *, size_t) = NULL; static void (*module_response_decoration) (void *, u_int, const u_char *, size_t, const u_char *, size_t) = NULL; struct module_base { void *ctx; struct imsgbuf ibuf; bool priv_dropped; /* Buffer for receiving the RADIUS packet */ u_char *radpkt; int radpktsiz; int radpktoff; u_char *radpkt2; int radpkt2siz; /* allocated size */ int radpkt2len; /* actual size */ #ifdef USE_LIBEVENT struct module_imsgbuf *module_imsgbuf; bool writeready; bool stopped; bool ev_onhandler; struct event ev; #endif }; static int module_common_radpkt(struct module_base *, uint32_t, u_int, const u_char *, size_t); static int module_recv_imsg(struct module_base *); static int module_imsg_handler(struct module_base *, struct imsg *); #ifdef USE_LIBEVENT static void module_on_event(int, short, void *); #endif static void module_reset_event(struct module_base *); struct module_base * module_create(int sock, void *ctx, struct module_handlers *handler) { struct module_base *base; if ((base = calloc(1, sizeof(struct module_base))) == NULL) return (NULL); imsg_init(&base->ibuf, sock); base->ctx = ctx; module_userpass = handler->userpass; module_access_request = handler->access_request; module_config_set = handler->config_set; module_request_decoration = handler->request_decoration; module_response_decoration = handler->response_decoration; module_start_module = handler->start; module_stop_module = handler->stop; return (base); } void module_start(struct module_base *base) { #ifdef USE_LIBEVENT int ival; if ((ival = fcntl(base->ibuf.fd, F_GETFL)) == -1) err(1, "Failed to F_GETFL"); if (fcntl(base->ibuf.fd, F_SETFL, ival | O_NONBLOCK) == -1) err(1, "Failed to setup NONBLOCK"); event_set(&base->ev, base->ibuf.fd, EV_READ, module_on_event, base); event_add(&base->ev, NULL); #endif } int module_run(struct module_base *base) { int ret; ret = module_recv_imsg(base); if (ret == 0) imsg_flush(&base->ibuf); return (ret); } void module_destroy(struct module_base *base) { if (base != NULL) { free(base->radpkt); free(base->radpkt2); imsg_clear(&base->ibuf); } free(base); } void module_load(struct module_base *base) { struct radiusd_module_load_arg load; memset(&load, 0, sizeof(load)); if (module_userpass != NULL) load.cap |= RADIUSD_MODULE_CAP_USERPASS; if (module_access_request != NULL) load.cap |= RADIUSD_MODULE_CAP_ACCSREQ; if (module_request_decoration != NULL) load.cap |= RADIUSD_MODULE_CAP_REQDECO; if (module_response_decoration != NULL) load.cap |= RADIUSD_MODULE_CAP_RESDECO; imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_LOAD, 0, 0, -1, &load, sizeof(load)); imsg_flush(&base->ibuf); } void module_drop_privilege(struct module_base *base) { struct passwd *pw; tzset(); /* Drop the privilege */ if ((pw = getpwnam(RADIUSD_USER)) == NULL) goto on_fail; if (chroot(pw->pw_dir) == -1) goto on_fail; if (chdir("/") == -1) goto on_fail; if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) goto on_fail; base->priv_dropped = true; on_fail: return; } int module_notify_secret(struct module_base *base, const char *secret) { int ret; ret = imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_NOTIFY_SECRET, 0, 0, -1, secret, strlen(secret) + 1); module_reset_event(base); return (ret); } int module_send_message(struct module_base *base, uint32_t cmd, const char *fmt, ...) { char *msg; va_list ap; int ret; if (fmt == NULL) ret = imsg_compose(&base->ibuf, cmd, 0, 0, -1, NULL, 0); else { va_start(ap, fmt); vasprintf(&msg, fmt, ap); va_end(ap); if (msg == NULL) return (-1); ret = imsg_compose(&base->ibuf, cmd, 0, 0, -1, msg, strlen(msg) + 1); free(msg); } module_reset_event(base); return (ret); } int module_userpass_ok(struct module_base *base, u_int q_id, const char *msg) { int ret; struct iovec iov[2]; iov[0].iov_base = &q_id; iov[0].iov_len = sizeof(q_id); iov[1].iov_base = (char *)msg; iov[1].iov_len = strlen(msg) + 1; ret = imsg_composev(&base->ibuf, IMSG_RADIUSD_MODULE_USERPASS_OK, 0, 0, -1, iov, 2); module_reset_event(base); return (ret); } int module_userpass_fail(struct module_base *base, u_int q_id, const char *msg) { int ret; struct iovec iov[2]; iov[0].iov_base = &q_id; iov[0].iov_len = sizeof(q_id); iov[1].iov_base = (char *)msg; iov[1].iov_len = strlen(msg) + 1; ret = imsg_composev(&base->ibuf, IMSG_RADIUSD_MODULE_USERPASS_FAIL, 0, 0, -1, iov, 2); module_reset_event(base); return (ret); } int module_accsreq_answer(struct module_base *base, u_int q_id, const u_char *pkt, size_t pktlen) { return (module_common_radpkt(base, IMSG_RADIUSD_MODULE_ACCSREQ_ANSWER, q_id, pkt, pktlen)); } int module_accsreq_aborted(struct module_base *base, u_int q_id) { int ret; ret = imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_ACCSREQ_ABORTED, 0, 0, -1, &q_id, sizeof(u_int)); module_reset_event(base); return (ret); } int module_reqdeco_done(struct module_base *base, u_int q_id, const u_char *pkt, size_t pktlen) { return (module_common_radpkt(base, IMSG_RADIUSD_MODULE_REQDECO_DONE, q_id, pkt, pktlen)); } int module_resdeco_done(struct module_base *base, u_int q_id, const u_char *pkt, size_t pktlen) { return (module_common_radpkt(base, IMSG_RADIUSD_MODULE_RESDECO_DONE, q_id, pkt, pktlen)); } static int module_common_radpkt(struct module_base *base, uint32_t imsg_type, u_int q_id, const u_char *pkt, size_t pktlen) { int ret = 0, off = 0, len, siz; struct iovec iov[2]; struct radiusd_module_radpkt_arg ans; len = pktlen; ans.q_id = q_id; ans.pktlen = pktlen; ans.final = false; while (!ans.final) { siz = MAX_IMSGSIZE - sizeof(ans); if (len - off <= siz) { ans.final = true; siz = len - off; } iov[0].iov_base = &ans; iov[0].iov_len = sizeof(ans); if (siz > 0) { iov[1].iov_base = (u_char *)pkt + off; iov[1].iov_len = siz; } ret = imsg_composev(&base->ibuf, imsg_type, 0, 0, -1, iov, (siz > 0)? 2 : 1); if (ret == -1) break; off += siz; } module_reset_event(base); return (ret); } static int module_recv_imsg(struct module_base *base) { ssize_t n; struct imsg imsg; if (((n = imsg_read(&base->ibuf)) == -1 && errno != EAGAIN) || n == 0) { if (n != 0) syslog(LOG_ERR, "%s: imsg_read(): %m", __func__); module_stop(base); return (-1); } for (;;) { if ((n = imsg_get(&base->ibuf, &imsg)) == -1) { syslog(LOG_ERR, "%s: imsg_get(): %m", __func__); module_stop(base); return (-1); } if (n == 0) break; module_imsg_handler(base, &imsg); imsg_free(&imsg); } module_reset_event(base); return (0); } static int module_imsg_handler(struct module_base *base, struct imsg *imsg) { ssize_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { case IMSG_RADIUSD_MODULE_SET_CONFIG: { struct radiusd_module_set_arg *arg; struct radiusd_module_object *val; u_int i; size_t off; char **argv; arg = (struct radiusd_module_set_arg *)imsg->data; off = sizeof(struct radiusd_module_set_arg); if ((argv = calloc(sizeof(const char *), arg->nparamval)) == NULL) { module_send_message(base, IMSG_NG, "Out of memory: %s", strerror(errno)); break; } for (i = 0; i < arg->nparamval; i++) { if (datalen - off < sizeof(struct radiusd_module_object)) break; val = (struct radiusd_module_object *) ((caddr_t)imsg->data + off); if (datalen - off < val->size) break; argv[i] = (char *)(val + 1); off += val->size; } if (i >= arg->nparamval) module_config_set(base->ctx, arg->paramname, arg->nparamval, argv); else module_send_message(base, IMSG_NG, "Internal protocol error"); free(argv); break; } case IMSG_RADIUSD_MODULE_START: if (module_start_module != NULL) { module_start_module(base->ctx); if (!base->priv_dropped) { syslog(LOG_ERR, "Module tried to start with " "root privileges"); abort(); } } else { if (!base->priv_dropped) { syslog(LOG_ERR, "Module tried to start with " "root privileges"); abort(); } module_send_message(base, IMSG_OK, NULL); } break; case IMSG_RADIUSD_MODULE_STOP: module_stop(base); break; case IMSG_RADIUSD_MODULE_USERPASS: { struct radiusd_module_userpass_arg *userpass; if (module_userpass == NULL) { syslog(LOG_ERR, "Received USERPASS message, but " "module doesn't support"); break; } if (datalen < (ssize_t)sizeof(struct radiusd_module_userpass_arg)) { syslog(LOG_ERR, "Received USERPASS message, but " "length is wrong"); break; } userpass = (struct radiusd_module_userpass_arg *)imsg->data; module_userpass(base->ctx, userpass->q_id, userpass->user, (userpass->has_pass)? userpass->pass : NULL); explicit_bzero(userpass, sizeof(struct radiusd_module_userpass_arg)); break; } case IMSG_RADIUSD_MODULE_ACCSREQ: case IMSG_RADIUSD_MODULE_REQDECO: case IMSG_RADIUSD_MODULE_RESDECO0_REQ: case IMSG_RADIUSD_MODULE_RESDECO: { struct radiusd_module_radpkt_arg *accessreq; int chunklen; const char *typestr; if (imsg->hdr.type == IMSG_RADIUSD_MODULE_ACCSREQ) { if (module_access_request == NULL) { syslog(LOG_ERR, "Received ACCSREQ message, but " "module doesn't support"); break; } typestr = "ACCSREQ"; } else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_REQDECO) { if (module_request_decoration == NULL) { syslog(LOG_ERR, "Received REQDECO message, but " "module doesn't support"); break; } typestr = "REQDECO"; } else { if (module_response_decoration == NULL) { syslog(LOG_ERR, "Received RESDECO message, but " "module doesn't support"); break; } if (imsg->hdr.type == IMSG_RADIUSD_MODULE_RESDECO0_REQ) typestr = "RESDECO0_REQ"; else typestr = "RESDECO"; } if (datalen < (ssize_t)sizeof(struct radiusd_module_radpkt_arg)) { syslog(LOG_ERR, "Received %s message, but " "length is wrong", typestr); break; } accessreq = (struct radiusd_module_radpkt_arg *)imsg->data; if (base->radpktsiz < accessreq->pktlen) { u_char *nradpkt; if ((nradpkt = realloc(base->radpkt, accessreq->pktlen)) == NULL) { syslog(LOG_ERR, "Could not handle received " "%s message: %m", typestr); base->radpktoff = 0; goto accsreq_out; } base->radpkt = nradpkt; base->radpktsiz = accessreq->pktlen; } chunklen = datalen - sizeof(struct radiusd_module_radpkt_arg); if (chunklen > base->radpktsiz - base->radpktoff){ syslog(LOG_ERR, "Could not handle received %s message: " "received length is too big", typestr); base->radpktoff = 0; goto accsreq_out; } memcpy(base->radpkt + base->radpktoff, (caddr_t)(accessreq + 1), chunklen); base->radpktoff += chunklen; if (!accessreq->final) goto accsreq_out; if (base->radpktoff != accessreq->pktlen) { syslog(LOG_ERR, "Could not handle received %s " "message: length is mismatch", typestr); base->radpktoff = 0; goto accsreq_out; } if (imsg->hdr.type == IMSG_RADIUSD_MODULE_ACCSREQ) module_access_request(base->ctx, accessreq->q_id, base->radpkt, base->radpktoff); else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_REQDECO) module_request_decoration(base->ctx, accessreq->q_id, base->radpkt, base->radpktoff); else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_RESDECO0_REQ) { /* preserve request */ if (base->radpktoff > base->radpkt2siz) { u_char *nradpkt; if ((nradpkt = realloc(base->radpkt2, base->radpktoff)) == NULL) { syslog(LOG_ERR, "Could not handle " "received %s message: %m", typestr); base->radpktoff = 0; goto accsreq_out; } base->radpkt2 = nradpkt; base->radpkt2siz = base->radpktoff; } memcpy(base->radpkt2, base->radpkt, base->radpktoff); base->radpkt2len = base->radpktoff; } else { module_response_decoration(base->ctx, accessreq->q_id, base->radpkt2, base->radpkt2len, base->radpkt, base->radpktoff); base->radpkt2len = 0; } base->radpktoff = 0; accsreq_out: break; } } return (0); } void module_stop(struct module_base *base) { if (module_stop_module != NULL) module_stop_module(base->ctx); #ifdef USE_LIBEVENT event_del(&base->ev); base->stopped = true; #endif close(base->ibuf.fd); } #ifdef USE_LIBEVENT static void module_on_event(int fd, short evmask, void *ctx) { struct module_base *base = ctx; int ret; base->ev_onhandler = true; if (evmask & EV_WRITE) base->writeready = true; if (evmask & EV_READ) { ret = module_recv_imsg(base); if (ret < 0) return; } while (base->writeready && base->ibuf.w.queued) { ret = msgbuf_write(&base->ibuf.w); if (ret > 0) continue; base->writeready = false; if (ret == 0 && errno == EAGAIN) break; syslog(LOG_ERR, "%s: msgbuf_write: %m", __func__); module_stop(base); return; } base->ev_onhandler = false; module_reset_event(base); return; } #endif static void module_reset_event(struct module_base *base) { #ifdef USE_LIBEVENT short evmask = 0; struct timeval *tvp = NULL, tv = { 0, 0 }; if (base->ev_onhandler) return; if (base->stopped) return; event_del(&base->ev); evmask |= EV_READ; if (base->ibuf.w.queued) { if (!base->writeready) evmask |= EV_WRITE; else tvp = &tv; /* fire immediately */ } event_set(&base->ev, base->ibuf.fd, evmask, module_on_event, base); if (event_add(&base->ev, tvp) == -1) syslog(LOG_ERR, "event_add() failed in %s()", __func__); #endif }