[BACK]Return to radiusd_module.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / radiusd

File: [local] / src / usr.sbin / radiusd / radiusd_module.c (download)

Revision 1.16, Fri Feb 9 07:41:32 2024 UTC (3 months, 4 weeks ago) by yasuoka
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.15: +3 -3 lines

Add nochroot parameter to module_drop_privilege() so that modules can
use unveil(2) instead of chroot(2) if need.

/*	$OpenBSD: radiusd_module.c,v 1.16 2024/02/09 07:41:32 yasuoka Exp $	*/

/*
 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
 *
 * 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 <sys/types.h>
#include <sys/queue.h>
#include <sys/uio.h>

#include <err.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <imsg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <pwd.h>

#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, int nochroot)
{
	struct passwd	*pw;

	tzset();

	/* Drop the privilege */
	if ((pw = getpwnam(RADIUSD_USER)) == NULL)
		goto on_fail;
	if (nochroot == 0 && 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
}