1/*	$OpenBSD: control.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */
2
3/*
4 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/queue.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <sys/time.h>
24#include <sys/un.h>
25
26#include <errno.h>
27#include <event.h>
28#include <imsg.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <string.h>
32#include <unistd.h>
33
34#include "radiusd.h"
35#include "radiusd_local.h"
36#include "log.h"
37#include "control.h"
38
39static TAILQ_HEAD(, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
40
41#define	CONTROL_BACKLOG	5
42static int	 idseq = 0;
43
44struct ctl_conn	*control_connbyfd(int);
45struct ctl_conn	*control_connbyid(uint32_t);
46void		 control_close(int);
47void		 control_connfree(struct ctl_conn *);
48void		 control_event_add(struct ctl_conn *);
49
50struct {
51	struct event	ev;
52	struct event	evt;
53	int		fd;
54} control_state;
55
56int
57control_init(const char *path)
58{
59	struct sockaddr_un	 sun;
60	int			 fd;
61	mode_t			 old_umask;
62
63	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
64	    0)) == -1) {
65		log_warn("control_init: socket");
66		return (-1);
67	}
68
69	memset(&sun, 0, sizeof(sun));
70	sun.sun_family = AF_UNIX;
71	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
72
73	if (unlink(path) == -1)
74		if (errno != ENOENT) {
75			log_warn("control_init: unlink %s", path);
76			close(fd);
77			return (-1);
78		}
79
80	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
81	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
82		log_warn("control_init: bind: %s", path);
83		close(fd);
84		umask(old_umask);
85		return (-1);
86	}
87	umask(old_umask);
88
89	if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
90		log_warn("control_init: chmod");
91		close(fd);
92		(void)unlink(path);
93		return (-1);
94	}
95
96	control_state.fd = fd;
97
98	return (0);
99}
100
101int
102control_listen(void)
103{
104
105	if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
106		log_warn("control_listen: listen");
107		return (-1);
108	}
109
110	event_set(&control_state.ev, control_state.fd, EV_READ,
111	    control_accept, NULL);
112	event_add(&control_state.ev, NULL);
113	evtimer_set(&control_state.evt, control_accept, NULL);
114
115	return (0);
116}
117
118void
119control_cleanup(void)
120{
121	struct ctl_conn	*c, *t;
122
123	TAILQ_FOREACH_SAFE(c, &ctl_conns, entry, t) {
124		TAILQ_REMOVE(&ctl_conns, c, entry);
125		control_connfree(c);
126	}
127	event_del(&control_state.ev);
128	event_del(&control_state.evt);
129}
130
131/* ARGSUSED */
132void
133control_accept(int listenfd, short event, void *bula)
134{
135	int			 connfd;
136	socklen_t		 len;
137	struct sockaddr_un	 sun;
138	struct ctl_conn		*c;
139
140	event_add(&control_state.ev, NULL);
141	if ((event & EV_TIMEOUT))
142		return;
143
144	len = sizeof(sun);
145	if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
146	    SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
147		/*
148		 * Pause accept if we are out of file descriptors, or
149		 * libevent will haunt us here too.
150		 */
151		if (errno == ENFILE || errno == EMFILE) {
152			struct timeval evtpause = { 1, 0 };
153
154			event_del(&control_state.ev);
155			evtimer_add(&control_state.evt, &evtpause);
156		} else if (errno != EWOULDBLOCK && errno != EINTR &&
157		    errno != ECONNABORTED)
158			log_warn("control_accept: accept");
159		return;
160	}
161
162	if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
163		log_warn("control_accept");
164		close(connfd);
165		return;
166	}
167
168	if (idseq == 0)	/* don't use zero.  See radiusd_module_imsg */
169		++idseq;
170	c->id = idseq++;
171	imsg_init(&c->iev.ibuf, connfd);
172	c->iev.handler = control_dispatch_imsg;
173	c->iev.events = EV_READ;
174	event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, c->iev.handler, c);
175	event_add(&c->iev.ev, NULL);
176
177	TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
178}
179
180struct ctl_conn *
181control_connbyfd(int fd)
182{
183	struct ctl_conn	*c;
184
185	TAILQ_FOREACH(c, &ctl_conns, entry) {
186		if (c->iev.ibuf.fd == fd)
187			break;
188	}
189
190	return (c);
191}
192
193struct ctl_conn *
194control_connbyid(uint32_t id)
195{
196	struct ctl_conn	*c;
197
198	TAILQ_FOREACH(c, &ctl_conns, entry) {
199		if (c->id == id)
200			break;
201	}
202
203	return (c);
204}
205
206void
207control_close(int fd)
208{
209	struct ctl_conn	*c;
210
211	if ((c = control_connbyfd(fd)) == NULL) {
212		log_warn("control_close: fd %d: not found", fd);
213		return;
214	}
215	if (c->modulename[0] != '\0')
216		radiusd_imsg_compose_module(radiusd_s, c->modulename,
217		    IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0);
218
219	control_connfree(c);
220}
221
222void
223control_connfree(struct ctl_conn *c)
224{
225	msgbuf_clear(&c->iev.ibuf.w);
226	TAILQ_REMOVE(&ctl_conns, c, entry);
227
228	event_del(&c->iev.ev);
229	close(c->iev.ibuf.fd);
230
231	/* Some file descriptors are available again. */
232	if (evtimer_pending(&control_state.evt, NULL)) {
233		evtimer_del(&control_state.evt);
234		event_add(&control_state.ev, NULL);
235	}
236
237	free(c);
238}
239
240/* ARGSUSED */
241void
242control_dispatch_imsg(int fd, short event, void *bula)
243{
244	struct ctl_conn	*c;
245	struct imsg	 imsg;
246	ssize_t		 n, datalen;
247	char		 modulename[RADIUSD_MODULE_NAME_LEN + 1], msg[128];
248
249	if ((c = control_connbyfd(fd)) == NULL) {
250		log_warn("control_dispatch_imsg: fd %d: not found", fd);
251		return;
252	}
253
254	if (event & EV_READ) {
255		if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
256		    n == 0) {
257			control_close(fd);
258			return;
259		}
260	}
261	if (event & EV_WRITE) {
262		if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
263			control_close(fd);
264			return;
265		}
266	}
267
268	for (;;) {
269		if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
270			control_close(fd);
271			return;
272		}
273
274		if (n == 0)
275			break;
276
277		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
278		switch (imsg.hdr.type) {
279		default:
280			if (imsg.hdr.type >= IMSG_RADIUSD_MODULE_MIN) {
281				if (datalen < RADIUSD_MODULE_NAME_LEN) {
282					log_warnx( "%s: received an invalid "
283					    "imsg %d: too small", __func__,
284					    imsg.hdr.type);
285					break;
286				}
287				memset(modulename, 0, sizeof(modulename));
288				memcpy(modulename, imsg.data,
289				    RADIUSD_MODULE_NAME_LEN);
290				if (radiusd_imsg_compose_module(radiusd_s,
291				    modulename, imsg.hdr.type, c->id, -1, -1,
292				    (caddr_t)imsg.data +
293				    RADIUSD_MODULE_NAME_LEN, datalen -
294				    RADIUSD_MODULE_NAME_LEN) != 0) {
295					snprintf(msg, sizeof(msg),
296					    "module `%s' is not loaded or not "
297					    "capable for control command",
298					    modulename);
299					imsg_compose_event(&c->iev,
300					    IMSG_NG, c->id, -1, -1, msg,
301					    strlen(msg) + 1);
302				}
303			} else
304				log_debug("control_dispatch_imsg: "
305				    "error handling imsg %d", imsg.hdr.type);
306			break;
307		}
308		imsg_free(&imsg);
309	}
310	imsg_event_add(&c->iev);
311}
312
313int
314control_imsg_relay(struct imsg *imsg)
315{
316	struct ctl_conn	*c;
317
318	if ((c = control_connbyid(imsg->hdr.peerid)) == NULL)
319		return (0);
320
321	return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
322	    -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
323}
324
325void
326control_conn_bind(uint32_t peerid, const char *modulename)
327{
328	struct ctl_conn	*c;
329
330	if ((c = control_connbyid(peerid)) == NULL)
331		return;
332
333	if (c->modulename[0] != '\0')
334		radiusd_imsg_compose_module(radiusd_s, c->modulename,
335		    IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0);
336	strlcpy(c->modulename, modulename, sizeof(c->modulename));
337}
338