2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include <sys/types.h>
18 #include <sys/queue.h>
19 #include <sys/socket.h>
33 #include "got_error.h"
36 #include "got_compat.h"
43 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
46 struct gotd_listen_client {
47 STAILQ_ENTRY(gotd_listen_client) entry;
52 STAILQ_HEAD(gotd_listen_clients, gotd_listen_client);
54 static struct gotd_listen_clients gotd_listen_clients[GOTD_CLIENT_TABLE_SIZE];
55 static SIPHASH_KEY clients_hash_key;
56 static volatile int listen_client_cnt;
59 struct gotd_uid_connection_counter {
60 STAILQ_ENTRY(gotd_uid_connection_counter) entry;
64 STAILQ_HEAD(gotd_client_uids, gotd_uid_connection_counter);
65 static struct gotd_client_uids gotd_client_uids[GOTD_CLIENT_TABLE_SIZE];
66 static SIPHASH_KEY uid_hash_key;
72 struct gotd_imsgev iev;
73 struct gotd_imsgev pause;
74 struct gotd_uid_connection_limit *connection_limits;
75 size_t nconnection_limits;
80 static void listen_shutdown(void);
83 listen_sighdlr(int sig, short event, void *arg)
86 * Normal signal handler rules don't apply because libevent
101 fatalx("unexpected signal");
106 client_hash(uint32_t client_id)
108 return SipHash24(&clients_hash_key, &client_id, sizeof(client_id));
112 add_client(struct gotd_listen_client *client)
114 uint64_t slot = client_hash(client->id) % nitems(gotd_listen_clients);
115 STAILQ_INSERT_HEAD(&gotd_listen_clients[slot], client, entry);
119 static struct gotd_listen_client *
120 find_client(uint32_t client_id)
123 struct gotd_listen_client *c;
125 slot = client_hash(client_id) % nitems(gotd_listen_clients);
126 STAILQ_FOREACH(c, &gotd_listen_clients[slot], entry) {
127 if (c->id == client_id)
142 duplicate = (find_client(id) != NULL);
143 } while (duplicate || id == 0);
151 return SipHash24(&uid_hash_key, &euid, sizeof(euid));
155 add_uid_connection_counter(struct gotd_uid_connection_counter *counter)
157 uint64_t slot = uid_hash(counter->euid) % nitems(gotd_client_uids);
158 STAILQ_INSERT_HEAD(&gotd_client_uids[slot], counter, entry);
162 remove_uid_connection_counter(struct gotd_uid_connection_counter *counter)
164 uint64_t slot = uid_hash(counter->euid) % nitems(gotd_client_uids);
165 STAILQ_REMOVE(&gotd_client_uids[slot], counter,
166 gotd_uid_connection_counter, entry);
169 static struct gotd_uid_connection_counter *
170 find_uid_connection_counter(uid_t euid)
173 struct gotd_uid_connection_counter *c;
175 slot = uid_hash(euid) % nitems(gotd_client_uids);
176 STAILQ_FOREACH(c, &gotd_client_uids[slot], entry) {
184 static const struct got_error *
185 disconnect(struct gotd_listen_client *client)
187 struct gotd_uid_connection_counter *counter;
191 log_debug("client on fd %d disconnecting", client->fd);
193 slot = client_hash(client->id) % nitems(gotd_listen_clients);
194 STAILQ_REMOVE(&gotd_listen_clients[slot], client,
195 gotd_listen_client, entry);
197 counter = find_uid_connection_counter(client->euid);
199 if (counter->nconnections > 0)
200 counter->nconnections--;
201 if (counter->nconnections == 0) {
202 remove_uid_connection_counter(counter);
207 client_fd = client->fd;
211 if (close(client_fd) == -1)
212 return got_error_from_errno("close");
218 accept_reserve(int fd, struct sockaddr *addr, socklen_t *addrlen,
219 int reserve, volatile int *counter)
222 int sock_flags = SOCK_NONBLOCK;
225 sock_flags |= SOCK_CLOEXEC;
228 if (getdtablecount() + reserve +
229 ((*counter + 1) * GOTD_FD_NEEDED) >= getdtablesize()) {
230 log_debug("inflight fds exceeded");
235 /* TA: silence warning from GCC. */
237 ret = accept(fd, addr, addrlen);
239 ret = accept4(fd, addr, addrlen, sock_flags);
250 gotd_accept_paused(int fd, short event, void *arg)
252 event_add(&gotd_listen.iev.ev, NULL);
256 gotd_accept(int fd, short event, void *arg)
258 struct gotd_imsgev *iev = arg;
259 struct sockaddr_storage ss;
260 struct timeval backoff;
263 struct gotd_listen_client *client = NULL;
264 struct gotd_uid_connection_counter *counter = NULL;
265 struct gotd_imsg_connect iconn;
272 if (event_add(&gotd_listen.iev.ev, NULL) == -1) {
273 log_warn("event_add");
276 if (event & EV_TIMEOUT)
281 /* Other backoff conditions apart from EMFILE/ENFILE? */
282 s = accept_reserve(fd, (struct sockaddr *)&ss, &len, GOTD_FD_RESERVE,
292 event_del(&gotd_listen.iev.ev);
293 evtimer_add(&gotd_listen.pause.ev, &backoff);
301 if (listen_client_cnt >= GOTD_MAXCLIENTS)
304 if (getpeereid(s, &euid, &egid) == -1) {
305 log_warn("getpeerid");
309 counter = find_uid_connection_counter(euid);
310 if (counter == NULL) {
311 counter = calloc(1, sizeof(*counter));
312 if (counter == NULL) {
313 log_warn("%s: calloc", __func__);
316 counter->euid = euid;
317 counter->nconnections = 1;
318 add_uid_connection_counter(counter);
320 int max_connections = GOTD_MAX_CONN_PER_UID;
321 struct gotd_uid_connection_limit *limit;
323 limit = gotd_find_uid_connection_limit(
324 gotd_listen.connection_limits,
325 gotd_listen.nconnection_limits, euid);
327 max_connections = limit->max_connections;
329 if (counter->nconnections >= max_connections) {
330 log_warnx("maximum connections exceeded for uid %d",
334 counter->nconnections++;
337 client = calloc(1, sizeof(*client));
338 if (client == NULL) {
339 log_warn("%s: calloc", __func__);
342 client->id = get_client_id();
347 log_debug("%s: new client connected on fd %d uid %d gid %d", __func__,
348 client->fd, euid, egid);
350 memset(&iconn, 0, sizeof(iconn));
351 iconn.client_id = client->id;
356 log_warn("%s: dup", __func__);
359 if (gotd_imsg_compose_event(iev, GOTD_IMSG_CONNECT, PROC_LISTEN, s,
360 &iconn, sizeof(iconn)) == -1) {
361 log_warn("imsg compose CONNECT");
374 static const struct got_error *
375 recv_disconnect(struct imsg *imsg)
377 struct gotd_imsg_disconnect idisconnect;
379 struct gotd_listen_client *client = NULL;
381 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
382 if (datalen != sizeof(idisconnect))
383 return got_error(GOT_ERR_PRIVSEP_LEN);
384 memcpy(&idisconnect, imsg->data, sizeof(idisconnect));
386 log_debug("client disconnecting");
388 client = find_client(idisconnect.client_id);
390 return got_error(GOT_ERR_CLIENT_ID);
392 return disconnect(client);
396 listen_dispatch(int fd, short event, void *arg)
398 const struct got_error *err = NULL;
399 struct gotd_imsgev *iev = arg;
400 struct imsgbuf *ibuf = &iev->ibuf;
405 if (event & EV_READ) {
406 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
407 fatal("imsg_read error");
408 if (n == 0) /* Connection closed. */
412 if (event & EV_WRITE) {
413 n = msgbuf_write(&ibuf->w);
414 if (n == -1 && errno != EAGAIN)
415 fatal("msgbuf_write");
416 if (n == 0) /* Connection closed. */
421 if ((n = imsg_get(ibuf, &imsg)) == -1)
422 fatal("%s: imsg_get", __func__);
423 if (n == 0) /* No more messages. */
426 switch (imsg.hdr.type) {
427 case GOTD_IMSG_DISCONNECT:
428 err = recv_disconnect(&imsg);
430 log_warnx("disconnect: %s", err->msg);
433 log_debug("unexpected imsg %d", imsg.hdr.type);
441 gotd_imsg_event_add(iev);
443 /* This pipe is dead. Remove its event handler */
445 event_loopexit(NULL);
450 listen_main(const char *title, int gotd_socket,
451 struct gotd_uid_connection_limit *connection_limits,
452 size_t nconnection_limits)
454 struct gotd_imsgev iev;
455 struct event evsigint, evsigterm, evsighup, evsigusr1;
457 arc4random_buf(&clients_hash_key, sizeof(clients_hash_key));
458 arc4random_buf(&uid_hash_key, sizeof(uid_hash_key));
460 gotd_listen.title = title;
461 gotd_listen.pid = getpid();
462 gotd_listen.fd = gotd_socket;
463 gotd_listen.connection_limits = connection_limits;
464 gotd_listen.nconnection_limits = nconnection_limits;
466 signal_set(&evsigint, SIGINT, listen_sighdlr, NULL);
467 signal_set(&evsigterm, SIGTERM, listen_sighdlr, NULL);
468 signal_set(&evsighup, SIGHUP, listen_sighdlr, NULL);
469 signal_set(&evsigusr1, SIGUSR1, listen_sighdlr, NULL);
470 signal(SIGPIPE, SIG_IGN);
472 signal_add(&evsigint, NULL);
473 signal_add(&evsigterm, NULL);
474 signal_add(&evsighup, NULL);
475 signal_add(&evsigusr1, NULL);
477 imsg_init(&iev.ibuf, GOTD_FILENO_MSG_PIPE);
478 iev.handler = listen_dispatch;
479 iev.events = EV_READ;
480 iev.handler_arg = NULL;
481 event_set(&iev.ev, iev.ibuf.fd, EV_READ, listen_dispatch, &iev);
482 if (event_add(&iev.ev, NULL) == -1)
485 event_set(&gotd_listen.iev.ev, gotd_listen.fd, EV_READ | EV_PERSIST,
487 if (event_add(&gotd_listen.iev.ev, NULL))
489 evtimer_set(&gotd_listen.pause.ev, gotd_accept_paused, NULL);
497 listen_shutdown(void)
499 log_debug("shutting down");
501 free(gotd_listen.connection_limits);
502 if (gotd_listen.fd != -1)
503 close(gotd_listen.fd);