xref: /openbsd-src/usr.sbin/radiusd/control.c (revision 882428cdbdd2944d8f59bc8621c131fd814fb6ee)
1*882428cdSclaudio /*	$OpenBSD: control.c,v 1.7 2024/11/21 13:43:10 claudio Exp $ */
2842565f2Syasuoka 
3842565f2Syasuoka /*
4842565f2Syasuoka  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
5842565f2Syasuoka  *
6842565f2Syasuoka  * Permission to use, copy, modify, and distribute this software for any
7842565f2Syasuoka  * purpose with or without fee is hereby granted, provided that the above
8842565f2Syasuoka  * copyright notice and this permission notice appear in all copies.
9842565f2Syasuoka  *
10842565f2Syasuoka  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11842565f2Syasuoka  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12842565f2Syasuoka  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13842565f2Syasuoka  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14842565f2Syasuoka  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15842565f2Syasuoka  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16842565f2Syasuoka  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17842565f2Syasuoka  */
18842565f2Syasuoka 
19842565f2Syasuoka #include <sys/types.h>
20842565f2Syasuoka #include <sys/queue.h>
21842565f2Syasuoka #include <sys/socket.h>
22842565f2Syasuoka #include <sys/stat.h>
23842565f2Syasuoka #include <sys/time.h>
24842565f2Syasuoka #include <sys/un.h>
25842565f2Syasuoka 
26842565f2Syasuoka #include <errno.h>
27842565f2Syasuoka #include <event.h>
28842565f2Syasuoka #include <imsg.h>
29842565f2Syasuoka #include <stdlib.h>
30842565f2Syasuoka #include <stdio.h>
31842565f2Syasuoka #include <string.h>
32842565f2Syasuoka #include <unistd.h>
33842565f2Syasuoka 
34842565f2Syasuoka #include "radiusd.h"
35842565f2Syasuoka #include "radiusd_local.h"
36842565f2Syasuoka #include "log.h"
37842565f2Syasuoka #include "control.h"
38842565f2Syasuoka 
39842565f2Syasuoka static TAILQ_HEAD(, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
40842565f2Syasuoka 
41842565f2Syasuoka #define	CONTROL_BACKLOG	5
42842565f2Syasuoka static int	 idseq = 0;
43842565f2Syasuoka 
44842565f2Syasuoka struct ctl_conn	*control_connbyfd(int);
45842565f2Syasuoka struct ctl_conn	*control_connbyid(uint32_t);
46842565f2Syasuoka void		 control_close(int);
47842565f2Syasuoka void		 control_connfree(struct ctl_conn *);
48842565f2Syasuoka void		 control_event_add(struct ctl_conn *);
49842565f2Syasuoka 
50842565f2Syasuoka struct {
51842565f2Syasuoka 	struct event	ev;
52842565f2Syasuoka 	struct event	evt;
53842565f2Syasuoka 	int		fd;
54842565f2Syasuoka } control_state;
55842565f2Syasuoka 
56842565f2Syasuoka int
57842565f2Syasuoka control_init(const char *path)
58842565f2Syasuoka {
59842565f2Syasuoka 	struct sockaddr_un	 sun;
60842565f2Syasuoka 	int			 fd;
61842565f2Syasuoka 	mode_t			 old_umask;
62842565f2Syasuoka 
63842565f2Syasuoka 	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
64842565f2Syasuoka 	    0)) == -1) {
65842565f2Syasuoka 		log_warn("control_init: socket");
66842565f2Syasuoka 		return (-1);
67842565f2Syasuoka 	}
68842565f2Syasuoka 
69842565f2Syasuoka 	memset(&sun, 0, sizeof(sun));
70842565f2Syasuoka 	sun.sun_family = AF_UNIX;
71842565f2Syasuoka 	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
72842565f2Syasuoka 
73842565f2Syasuoka 	if (unlink(path) == -1)
74842565f2Syasuoka 		if (errno != ENOENT) {
75842565f2Syasuoka 			log_warn("control_init: unlink %s", path);
76842565f2Syasuoka 			close(fd);
77842565f2Syasuoka 			return (-1);
78842565f2Syasuoka 		}
79842565f2Syasuoka 
80842565f2Syasuoka 	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
81842565f2Syasuoka 	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
82842565f2Syasuoka 		log_warn("control_init: bind: %s", path);
83842565f2Syasuoka 		close(fd);
84842565f2Syasuoka 		umask(old_umask);
85842565f2Syasuoka 		return (-1);
86842565f2Syasuoka 	}
87842565f2Syasuoka 	umask(old_umask);
88842565f2Syasuoka 
89842565f2Syasuoka 	if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
90842565f2Syasuoka 		log_warn("control_init: chmod");
91842565f2Syasuoka 		close(fd);
92842565f2Syasuoka 		(void)unlink(path);
93842565f2Syasuoka 		return (-1);
94842565f2Syasuoka 	}
95842565f2Syasuoka 
96842565f2Syasuoka 	control_state.fd = fd;
97842565f2Syasuoka 
98842565f2Syasuoka 	return (0);
99842565f2Syasuoka }
100842565f2Syasuoka 
101842565f2Syasuoka int
102842565f2Syasuoka control_listen(void)
103842565f2Syasuoka {
104842565f2Syasuoka 
105842565f2Syasuoka 	if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
106842565f2Syasuoka 		log_warn("control_listen: listen");
107842565f2Syasuoka 		return (-1);
108842565f2Syasuoka 	}
109842565f2Syasuoka 
110842565f2Syasuoka 	event_set(&control_state.ev, control_state.fd, EV_READ,
111842565f2Syasuoka 	    control_accept, NULL);
112842565f2Syasuoka 	event_add(&control_state.ev, NULL);
113842565f2Syasuoka 	evtimer_set(&control_state.evt, control_accept, NULL);
114842565f2Syasuoka 
115842565f2Syasuoka 	return (0);
116842565f2Syasuoka }
117842565f2Syasuoka 
118842565f2Syasuoka void
119842565f2Syasuoka control_cleanup(void)
120842565f2Syasuoka {
121842565f2Syasuoka 	struct ctl_conn	*c, *t;
122842565f2Syasuoka 
123842565f2Syasuoka 	TAILQ_FOREACH_SAFE(c, &ctl_conns, entry, t) {
124842565f2Syasuoka 		TAILQ_REMOVE(&ctl_conns, c, entry);
125842565f2Syasuoka 		control_connfree(c);
126842565f2Syasuoka 	}
127842565f2Syasuoka 	event_del(&control_state.ev);
128842565f2Syasuoka 	event_del(&control_state.evt);
129842565f2Syasuoka }
130842565f2Syasuoka 
131842565f2Syasuoka /* ARGSUSED */
132842565f2Syasuoka void
133842565f2Syasuoka control_accept(int listenfd, short event, void *bula)
134842565f2Syasuoka {
135842565f2Syasuoka 	int			 connfd;
136842565f2Syasuoka 	socklen_t		 len;
137842565f2Syasuoka 	struct sockaddr_un	 sun;
138842565f2Syasuoka 	struct ctl_conn		*c;
139842565f2Syasuoka 
140842565f2Syasuoka 	event_add(&control_state.ev, NULL);
141842565f2Syasuoka 	if ((event & EV_TIMEOUT))
142842565f2Syasuoka 		return;
143842565f2Syasuoka 
144842565f2Syasuoka 	len = sizeof(sun);
145842565f2Syasuoka 	if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
146842565f2Syasuoka 	    SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
147842565f2Syasuoka 		/*
148842565f2Syasuoka 		 * Pause accept if we are out of file descriptors, or
149842565f2Syasuoka 		 * libevent will haunt us here too.
150842565f2Syasuoka 		 */
151842565f2Syasuoka 		if (errno == ENFILE || errno == EMFILE) {
152842565f2Syasuoka 			struct timeval evtpause = { 1, 0 };
153842565f2Syasuoka 
154842565f2Syasuoka 			event_del(&control_state.ev);
155842565f2Syasuoka 			evtimer_add(&control_state.evt, &evtpause);
156842565f2Syasuoka 		} else if (errno != EWOULDBLOCK && errno != EINTR &&
157842565f2Syasuoka 		    errno != ECONNABORTED)
158842565f2Syasuoka 			log_warn("control_accept: accept");
159842565f2Syasuoka 		return;
160842565f2Syasuoka 	}
161842565f2Syasuoka 
162842565f2Syasuoka 	if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
163842565f2Syasuoka 		log_warn("control_accept");
164842565f2Syasuoka 		close(connfd);
165842565f2Syasuoka 		return;
166842565f2Syasuoka 	}
167842565f2Syasuoka 
168*882428cdSclaudio 	if (imsgbuf_init(&c->iev.ibuf, connfd) == -1) {
169*882428cdSclaudio 		log_warn("control_accept");
170*882428cdSclaudio 		close(connfd);
171*882428cdSclaudio 		free(c);
172*882428cdSclaudio 		return;
173*882428cdSclaudio 	}
174842565f2Syasuoka 	if (idseq == 0)	/* don't use zero.  See radiusd_module_imsg */
175842565f2Syasuoka 		++idseq;
176842565f2Syasuoka 	c->id = idseq++;
177842565f2Syasuoka 	c->iev.handler = control_dispatch_imsg;
178842565f2Syasuoka 	c->iev.events = EV_READ;
179842565f2Syasuoka 	event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, c->iev.handler, c);
180842565f2Syasuoka 	event_add(&c->iev.ev, NULL);
181842565f2Syasuoka 
182842565f2Syasuoka 	TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
183842565f2Syasuoka }
184842565f2Syasuoka 
185842565f2Syasuoka struct ctl_conn *
186842565f2Syasuoka control_connbyfd(int fd)
187842565f2Syasuoka {
188842565f2Syasuoka 	struct ctl_conn	*c;
189842565f2Syasuoka 
190842565f2Syasuoka 	TAILQ_FOREACH(c, &ctl_conns, entry) {
191842565f2Syasuoka 		if (c->iev.ibuf.fd == fd)
192842565f2Syasuoka 			break;
193842565f2Syasuoka 	}
194842565f2Syasuoka 
195842565f2Syasuoka 	return (c);
196842565f2Syasuoka }
197842565f2Syasuoka 
198842565f2Syasuoka struct ctl_conn *
199842565f2Syasuoka control_connbyid(uint32_t id)
200842565f2Syasuoka {
201842565f2Syasuoka 	struct ctl_conn	*c;
202842565f2Syasuoka 
203842565f2Syasuoka 	TAILQ_FOREACH(c, &ctl_conns, entry) {
204842565f2Syasuoka 		if (c->id == id)
205842565f2Syasuoka 			break;
206842565f2Syasuoka 	}
207842565f2Syasuoka 
208842565f2Syasuoka 	return (c);
209842565f2Syasuoka }
210842565f2Syasuoka 
211842565f2Syasuoka void
212842565f2Syasuoka control_close(int fd)
213842565f2Syasuoka {
214842565f2Syasuoka 	struct ctl_conn	*c;
215842565f2Syasuoka 
216842565f2Syasuoka 	if ((c = control_connbyfd(fd)) == NULL) {
217842565f2Syasuoka 		log_warn("control_close: fd %d: not found", fd);
218842565f2Syasuoka 		return;
219842565f2Syasuoka 	}
220842565f2Syasuoka 	if (c->modulename[0] != '\0')
221842565f2Syasuoka 		radiusd_imsg_compose_module(radiusd_s, c->modulename,
222842565f2Syasuoka 		    IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0);
223842565f2Syasuoka 
224842565f2Syasuoka 	control_connfree(c);
225842565f2Syasuoka }
226842565f2Syasuoka 
227842565f2Syasuoka void
228842565f2Syasuoka control_connfree(struct ctl_conn *c)
229842565f2Syasuoka {
2309cbf9e90Sclaudio 	imsgbuf_clear(&c->iev.ibuf);
231842565f2Syasuoka 	TAILQ_REMOVE(&ctl_conns, c, entry);
232842565f2Syasuoka 
233842565f2Syasuoka 	event_del(&c->iev.ev);
234842565f2Syasuoka 	close(c->iev.ibuf.fd);
235842565f2Syasuoka 
236842565f2Syasuoka 	/* Some file descriptors are available again. */
237842565f2Syasuoka 	if (evtimer_pending(&control_state.evt, NULL)) {
238842565f2Syasuoka 		evtimer_del(&control_state.evt);
239842565f2Syasuoka 		event_add(&control_state.ev, NULL);
240842565f2Syasuoka 	}
241842565f2Syasuoka 
242842565f2Syasuoka 	free(c);
243842565f2Syasuoka }
244842565f2Syasuoka 
245842565f2Syasuoka /* ARGSUSED */
246842565f2Syasuoka void
247842565f2Syasuoka control_dispatch_imsg(int fd, short event, void *bula)
248842565f2Syasuoka {
249842565f2Syasuoka 	struct ctl_conn	*c;
250842565f2Syasuoka 	struct imsg	 imsg;
251842565f2Syasuoka 	ssize_t		 n, datalen;
252842565f2Syasuoka 	char		 modulename[RADIUSD_MODULE_NAME_LEN + 1], msg[128];
253842565f2Syasuoka 
254842565f2Syasuoka 	if ((c = control_connbyfd(fd)) == NULL) {
255842565f2Syasuoka 		log_warn("control_dispatch_imsg: fd %d: not found", fd);
256842565f2Syasuoka 		return;
257842565f2Syasuoka 	}
258842565f2Syasuoka 
259842565f2Syasuoka 	if (event & EV_READ) {
2604f3fb1ffSclaudio 		if (imsgbuf_read(&c->iev.ibuf) != 1) {
261842565f2Syasuoka 			control_close(fd);
262842565f2Syasuoka 			return;
263842565f2Syasuoka 		}
264842565f2Syasuoka 	}
265842565f2Syasuoka 	if (event & EV_WRITE) {
266dd7efffeSclaudio 		if (imsgbuf_write(&c->iev.ibuf) == -1) {
267842565f2Syasuoka 			control_close(fd);
268842565f2Syasuoka 			return;
269842565f2Syasuoka 		}
270842565f2Syasuoka 	}
271842565f2Syasuoka 
272842565f2Syasuoka 	for (;;) {
273842565f2Syasuoka 		if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
274842565f2Syasuoka 			control_close(fd);
275842565f2Syasuoka 			return;
276842565f2Syasuoka 		}
277842565f2Syasuoka 
278842565f2Syasuoka 		if (n == 0)
279842565f2Syasuoka 			break;
280842565f2Syasuoka 
281842565f2Syasuoka 		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
282842565f2Syasuoka 		switch (imsg.hdr.type) {
283842565f2Syasuoka 		default:
284842565f2Syasuoka 			if (imsg.hdr.type >= IMSG_RADIUSD_MODULE_MIN) {
285842565f2Syasuoka 				if (datalen < RADIUSD_MODULE_NAME_LEN) {
286842565f2Syasuoka 					log_warnx( "%s: received an invalid "
287842565f2Syasuoka 					    "imsg %d: too small", __func__,
288842565f2Syasuoka 					    imsg.hdr.type);
289842565f2Syasuoka 					break;
290842565f2Syasuoka 				}
291842565f2Syasuoka 				memset(modulename, 0, sizeof(modulename));
292842565f2Syasuoka 				memcpy(modulename, imsg.data,
293842565f2Syasuoka 				    RADIUSD_MODULE_NAME_LEN);
294842565f2Syasuoka 				if (radiusd_imsg_compose_module(radiusd_s,
295842565f2Syasuoka 				    modulename, imsg.hdr.type, c->id, -1, -1,
296842565f2Syasuoka 				    (caddr_t)imsg.data +
297842565f2Syasuoka 				    RADIUSD_MODULE_NAME_LEN, datalen -
298842565f2Syasuoka 				    RADIUSD_MODULE_NAME_LEN) != 0) {
299842565f2Syasuoka 					snprintf(msg, sizeof(msg),
300842565f2Syasuoka 					    "module `%s' is not loaded or not "
301842565f2Syasuoka 					    "capable for control command",
302842565f2Syasuoka 					    modulename);
303842565f2Syasuoka 					imsg_compose_event(&c->iev,
304842565f2Syasuoka 					    IMSG_NG, c->id, -1, -1, msg,
305842565f2Syasuoka 					    strlen(msg) + 1);
306842565f2Syasuoka 				}
307842565f2Syasuoka 			} else
308842565f2Syasuoka 				log_debug("control_dispatch_imsg: "
309842565f2Syasuoka 				    "error handling imsg %d", imsg.hdr.type);
310842565f2Syasuoka 			break;
311842565f2Syasuoka 		}
312842565f2Syasuoka 		imsg_free(&imsg);
313842565f2Syasuoka 	}
314842565f2Syasuoka 	imsg_event_add(&c->iev);
315842565f2Syasuoka }
316842565f2Syasuoka 
317842565f2Syasuoka int
318842565f2Syasuoka control_imsg_relay(struct imsg *imsg)
319842565f2Syasuoka {
320842565f2Syasuoka 	struct ctl_conn	*c;
321842565f2Syasuoka 
322842565f2Syasuoka 	if ((c = control_connbyid(imsg->hdr.peerid)) == NULL)
323842565f2Syasuoka 		return (0);
324842565f2Syasuoka 
325842565f2Syasuoka 	return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
326842565f2Syasuoka 	    -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
327842565f2Syasuoka }
328842565f2Syasuoka 
329842565f2Syasuoka void
330842565f2Syasuoka control_conn_bind(uint32_t peerid, const char *modulename)
331842565f2Syasuoka {
332842565f2Syasuoka 	struct ctl_conn	*c;
333842565f2Syasuoka 
334842565f2Syasuoka 	if ((c = control_connbyid(peerid)) == NULL)
335842565f2Syasuoka 		return;
336842565f2Syasuoka 
337842565f2Syasuoka 	if (c->modulename[0] != '\0')
338842565f2Syasuoka 		radiusd_imsg_compose_module(radiusd_s, c->modulename,
339842565f2Syasuoka 		    IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0);
340842565f2Syasuoka 	strlcpy(c->modulename, modulename, sizeof(c->modulename));
341842565f2Syasuoka }
342