Blob


1 /*
2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/types.h>
18 #include <sys/queue.h>
19 #include <sys/socket.h>
20 #include <sys/uio.h>
22 #include <errno.h>
23 #include <event.h>
24 #include <siphash.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <imsg.h>
30 #include <limits.h>
31 #include <sha1.h>
32 #include <signal.h>
33 #include <unistd.h>
35 #include "got_error.h"
37 #include "gotd.h"
38 #include "log.h"
39 #include "listen.h"
41 #ifndef nitems
42 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
43 #endif
45 struct gotd_listen_client {
46 STAILQ_ENTRY(gotd_listen_client) entry;
47 uint32_t id;
48 int fd;
49 };
50 STAILQ_HEAD(gotd_listen_clients, gotd_listen_client);
52 static struct gotd_listen_clients gotd_listen_clients[GOTD_CLIENT_TABLE_SIZE];
53 static SIPHASH_KEY clients_hash_key;
54 static volatile int listen_client_cnt;
55 static int inflight;
57 static struct {
58 pid_t pid;
59 const char *title;
60 int fd;
61 struct gotd_imsgev iev;
62 struct gotd_imsgev pause;
63 } gotd_listen;
65 static int inflight;
67 static void listen_shutdown(void);
69 static void
70 listen_sighdlr(int sig, short event, void *arg)
71 {
72 /*
73 * Normal signal handler rules don't apply because libevent
74 * decouples for us.
75 */
77 switch (sig) {
78 case SIGHUP:
79 break;
80 case SIGUSR1:
81 break;
82 case SIGTERM:
83 case SIGINT:
84 listen_shutdown();
85 /* NOTREACHED */
86 break;
87 default:
88 fatalx("unexpected signal");
89 }
90 }
92 static uint64_t
93 client_hash(uint32_t client_id)
94 {
95 return SipHash24(&clients_hash_key, &client_id, sizeof(client_id));
96 }
98 static void
99 add_client(struct gotd_listen_client *client)
101 uint64_t slot = client_hash(client->id) % nitems(gotd_listen_clients);
102 STAILQ_INSERT_HEAD(&gotd_listen_clients[slot], client, entry);
103 listen_client_cnt++;
106 static struct gotd_listen_client *
107 find_client(uint32_t client_id)
109 uint64_t slot;
110 struct gotd_listen_client *c;
112 slot = client_hash(client_id) % nitems(gotd_listen_clients);
113 STAILQ_FOREACH(c, &gotd_listen_clients[slot], entry) {
114 if (c->id == client_id)
115 return c;
118 return NULL;
121 static uint32_t
122 get_client_id(void)
124 int duplicate = 0;
125 uint32_t id;
127 do {
128 id = arc4random();
129 duplicate = (find_client(id) != NULL);
130 } while (duplicate || id == 0);
132 return id;
135 static const struct got_error *
136 disconnect(struct gotd_listen_client *client)
138 uint64_t slot;
139 int client_fd;
141 log_debug("client on fd %d disconnecting", client->fd);
143 slot = client_hash(client->id) % nitems(gotd_listen_clients);
144 STAILQ_REMOVE(&gotd_listen_clients[slot], client,
145 gotd_listen_client, entry);
146 client_fd = client->fd;
147 free(client);
148 inflight--;
149 listen_client_cnt--;
150 if (close(client_fd) == -1)
151 return got_error_from_errno("close");
153 return NULL;
156 static int
157 accept_reserve(int fd, struct sockaddr *addr, socklen_t *addrlen,
158 int reserve, volatile int *counter)
160 int ret;
162 if (getdtablecount() + reserve +
163 ((*counter + 1) * GOTD_FD_NEEDED) >= getdtablesize()) {
164 log_debug("inflight fds exceeded");
165 errno = EMFILE;
166 return -1;
169 if ((ret = accept4(fd, addr, addrlen,
170 SOCK_NONBLOCK | SOCK_CLOEXEC)) > -1) {
171 (*counter)++;
174 return ret;
177 static void
178 gotd_accept_paused(int fd, short event, void *arg)
180 event_add(&gotd_listen.iev.ev, NULL);
183 static void
184 gotd_accept(int fd, short event, void *arg)
186 struct gotd_imsgev *iev = arg;
187 struct sockaddr_storage ss;
188 struct timeval backoff;
189 socklen_t len;
190 int s = -1;
191 struct gotd_listen_client *client = NULL;
192 struct gotd_imsg_connect iconn;
193 uid_t euid;
194 gid_t egid;
196 backoff.tv_sec = 1;
197 backoff.tv_usec = 0;
199 if (event_add(&gotd_listen.iev.ev, NULL) == -1) {
200 log_warn("event_add");
201 return;
203 if (event & EV_TIMEOUT)
204 return;
206 len = sizeof(ss);
208 /* Other backoff conditions apart from EMFILE/ENFILE? */
209 s = accept_reserve(fd, (struct sockaddr *)&ss, &len, GOTD_FD_RESERVE,
210 &inflight);
211 if (s == -1) {
212 switch (errno) {
213 case EINTR:
214 case EWOULDBLOCK:
215 case ECONNABORTED:
216 return;
217 case EMFILE:
218 case ENFILE:
219 event_del(&gotd_listen.iev.ev);
220 evtimer_add(&gotd_listen.pause.ev, &backoff);
221 return;
222 default:
223 log_warn("accept");
224 return;
228 if (listen_client_cnt >= GOTD_MAXCLIENTS)
229 goto err;
231 if (getpeereid(s, &euid, &egid) == -1) {
232 log_warn("getpeerid");
233 goto err;
236 client = calloc(1, sizeof(*client));
237 if (client == NULL) {
238 log_warn("%s: calloc", __func__);
239 goto err;
241 client->id = get_client_id();
242 client->fd = s;
243 s = -1;
244 add_client(client);
245 log_debug("%s: new client connected on fd %d uid %d gid %d", __func__,
246 client->fd, euid, egid);
248 memset(&iconn, 0, sizeof(iconn));
249 iconn.client_id = client->id;
250 iconn.euid = euid;
251 iconn.egid = egid;
252 s = dup(client->fd);
253 if (s == -1) {
254 log_warn("%s: dup", __func__);
255 goto err;
257 if (gotd_imsg_compose_event(iev, GOTD_IMSG_CONNECT, PROC_LISTEN, s,
258 &iconn, sizeof(iconn)) == -1) {
259 log_warn("imsg compose CONNECT");
260 goto err;
263 return;
264 err:
265 inflight--;
266 if (client)
267 disconnect(client);
268 if (s != -1)
269 close(s);
272 static const struct got_error *
273 recv_disconnect(struct imsg *imsg)
275 struct gotd_imsg_disconnect idisconnect;
276 size_t datalen;
277 struct gotd_listen_client *client = NULL;
279 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
280 if (datalen != sizeof(idisconnect))
281 return got_error(GOT_ERR_PRIVSEP_LEN);
282 memcpy(&idisconnect, imsg->data, sizeof(idisconnect));
284 log_debug("client disconnecting");
286 client = find_client(idisconnect.client_id);
287 if (client == NULL)
288 return got_error(GOT_ERR_CLIENT_ID);
290 return disconnect(client);
293 static void
294 listen_dispatch(int fd, short event, void *arg)
296 const struct got_error *err = NULL;
297 struct gotd_imsgev *iev = arg;
298 struct imsgbuf *ibuf = &iev->ibuf;
299 struct imsg imsg;
300 ssize_t n;
301 int shut = 0;
303 if (event & EV_READ) {
304 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
305 fatal("imsg_read error");
306 if (n == 0) /* Connection closed. */
307 shut = 1;
310 if (event & EV_WRITE) {
311 n = msgbuf_write(&ibuf->w);
312 if (n == -1 && errno != EAGAIN)
313 fatal("msgbuf_write");
314 if (n == 0) /* Connection closed. */
315 shut = 1;
318 for (;;) {
319 if ((n = imsg_get(ibuf, &imsg)) == -1)
320 fatal("%s: imsg_get", __func__);
321 if (n == 0) /* No more messages. */
322 break;
324 switch (imsg.hdr.type) {
325 case GOTD_IMSG_DISCONNECT:
326 err = recv_disconnect(&imsg);
327 if (err)
328 log_warnx("%s: disconnect: %s",
329 gotd_listen.title, err->msg);
330 break;
331 default:
332 log_debug("%s: unexpected imsg %d", gotd_listen.title,
333 imsg.hdr.type);
334 break;
337 imsg_free(&imsg);
340 if (!shut) {
341 gotd_imsg_event_add(iev);
342 } else {
343 /* This pipe is dead. Remove its event handler */
344 event_del(&iev->ev);
345 event_loopexit(NULL);
349 void
350 listen_main(const char *title, int gotd_socket)
352 struct gotd_imsgev iev;
353 struct event evsigint, evsigterm, evsighup, evsigusr1;
355 arc4random_buf(&clients_hash_key, sizeof(clients_hash_key));
357 gotd_listen.title = title;
358 gotd_listen.pid = getpid();
359 gotd_listen.fd = gotd_socket;
361 signal_set(&evsigint, SIGINT, listen_sighdlr, NULL);
362 signal_set(&evsigterm, SIGTERM, listen_sighdlr, NULL);
363 signal_set(&evsighup, SIGHUP, listen_sighdlr, NULL);
364 signal_set(&evsigusr1, SIGUSR1, listen_sighdlr, NULL);
365 signal(SIGPIPE, SIG_IGN);
367 signal_add(&evsigint, NULL);
368 signal_add(&evsigterm, NULL);
369 signal_add(&evsighup, NULL);
370 signal_add(&evsigusr1, NULL);
372 imsg_init(&iev.ibuf, GOTD_FILENO_MSG_PIPE);
373 iev.handler = listen_dispatch;
374 iev.events = EV_READ;
375 iev.handler_arg = NULL;
376 event_set(&iev.ev, iev.ibuf.fd, EV_READ, listen_dispatch, &iev);
377 if (event_add(&iev.ev, NULL) == -1)
378 fatalx("event add");
380 event_set(&gotd_listen.iev.ev, gotd_listen.fd, EV_READ | EV_PERSIST,
381 gotd_accept, &iev);
382 if (event_add(&gotd_listen.iev.ev, NULL))
383 fatalx("event add");
384 evtimer_set(&gotd_listen.pause.ev, gotd_accept_paused, NULL);
386 event_dispatch();
388 listen_shutdown();
391 static void
392 listen_shutdown(void)
394 log_debug("%s: shutting down", gotd_listen.title);
396 if (gotd_listen.fd != -1)
397 close(gotd_listen.fd);
399 exit(0);