portable: fix-up merge conflict
[got-portable.git] / gotwebd / gotwebd.c
blobca4b89457c125d13575600d13e48d7f6aca87648
1 /*
2 * Copyright (c) 2016, 2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include "got_compat.h"
20 #include <sys/param.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/wait.h>
25 #include <net/if.h>
26 #include <netinet/in.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <termios.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <event.h>
35 #include <fcntl.h>
36 #include <pwd.h>
37 #include <signal.h>
38 #include <syslog.h>
39 #include <unistd.h>
40 #include <ctype.h>
42 #include "got_opentemp.h"
43 #include "got_reference.h"
45 #include "gotwebd.h"
46 #include "log.h"
48 __dead void usage(void);
50 int main(int, char **);
51 int gotwebd_configure(struct gotwebd *);
52 void gotwebd_configure_done(struct gotwebd *);
53 void gotwebd_sighdlr(int sig, short event, void *arg);
54 void gotwebd_shutdown(void);
55 void gotwebd_dispatch_sockets(int, short, void *);
57 struct gotwebd *gotwebd_env;
59 void
60 imsg_event_add(struct imsgev *iev)
62 if (iev->handler == NULL) {
63 imsgbuf_flush(&iev->ibuf);
64 return;
67 iev->events = EV_READ;
68 if (imsgbuf_queuelen(&iev->ibuf))
69 iev->events |= EV_WRITE;
71 event_del(&iev->ev);
72 event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
73 event_add(&iev->ev, NULL);
76 int
77 imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
78 pid_t pid, int fd, const void *data, uint16_t datalen)
80 int ret;
82 ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
83 if (ret == -1)
84 return (ret);
85 imsg_event_add(iev);
86 return (ret);
89 int
90 main_compose_sockets(struct gotwebd *env, uint32_t type, int fd,
91 const void *data, uint16_t len)
93 size_t i;
94 int ret, d;
96 for (i = 0; i < env->nserver; ++i) {
97 d = -1;
98 if (fd != -1 && (d = dup(fd)) == -1)
99 goto err;
101 ret = imsg_compose_event(&env->iev_server[i], type, 0, -1,
102 d, data, len);
103 if (ret == -1)
104 goto err;
106 /* prevent fd exhaustion */
107 if (d != -1) {
108 if (imsgbuf_flush(&env->iev_server[i].ibuf) == -1)
109 goto err;
110 imsg_event_add(&env->iev_server[i]);
114 if (fd != -1)
115 close(fd);
116 return 0;
118 err:
119 if (fd != -1)
120 close(fd);
121 return -1;
125 sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d,
126 uint16_t len)
128 return (imsg_compose_event(env->iev_parent, type, 0, -1, -1, d, len));
131 void
132 gotwebd_dispatch_sockets(int fd, short event, void *arg)
134 struct imsgev *iev = arg;
135 struct imsgbuf *ibuf;
136 struct imsg imsg;
137 struct gotwebd *env = gotwebd_env;
138 ssize_t n;
139 int shut = 0;
141 ibuf = &iev->ibuf;
143 if (event & EV_READ) {
144 if ((n = imsgbuf_read(ibuf)) == -1)
145 fatal("imsg_read error");
146 if (n == 0) /* Connection closed */
147 shut = 1;
149 if (event & EV_WRITE) {
150 if (imsgbuf_write(ibuf) == -1)
151 fatal("msgbuf_write");
154 for (;;) {
155 if ((n = imsg_get(ibuf, &imsg)) == -1)
156 fatal("imsg_get");
157 if (n == 0) /* No more messages. */
158 break;
160 switch (imsg.hdr.type) {
161 case IMSG_CFG_DONE:
162 gotwebd_configure_done(env);
163 break;
164 default:
165 fatalx("%s: unknown imsg type %d", __func__,
166 imsg.hdr.type);
169 imsg_free(&imsg);
172 if (!shut)
173 imsg_event_add(iev);
174 else {
175 /* This pipe is dead. Remove its event handler */
176 event_del(&iev->ev);
177 event_loopexit(NULL);
181 void
182 gotwebd_sighdlr(int sig, short event, void *arg)
184 /* struct privsep *ps = arg; */
186 switch (sig) {
187 case SIGHUP:
188 log_info("%s: ignoring SIGHUP", __func__);
189 break;
190 case SIGPIPE:
191 log_info("%s: ignoring SIGPIPE", __func__);
192 break;
193 case SIGUSR1:
194 log_info("%s: ignoring SIGUSR1", __func__);
195 break;
196 case SIGTERM:
197 case SIGINT:
198 gotwebd_shutdown();
199 break;
200 default:
201 fatalx("unexpected signal");
205 static int
206 spawn_socket_process(struct gotwebd *env, const char *argv0, int n)
208 const char *argv[8];
209 int argc = 0;
210 int p[2];
211 pid_t pid;
213 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
214 fatal("socketpair");
216 switch (pid = fork()) {
217 case -1:
218 fatal("fork");
219 case 0: /* child */
220 break;
221 default: /* parent */
222 close(p[0]);
223 if (imsgbuf_init(&env->iev_server[n].ibuf, p[1]) == -1)
224 fatal("imsgbuf_init");
225 imsgbuf_allow_fdpass(&env->iev_server[n].ibuf);
226 env->iev_server[n].handler = gotwebd_dispatch_sockets;
227 env->iev_server[n].data = &env->iev_server[n];
228 event_set(&env->iev_server[n].ev, p[1], EV_READ,
229 gotwebd_dispatch_sockets, &env->iev_server[n]);
230 event_add(&env->iev_server[n].ev, NULL);
231 return 0;
234 close(p[1]);
236 argv[argc++] = argv0;
237 argv[argc++] = "-S";
238 if (strcmp(env->gotwebd_conffile, GOTWEBD_CONF) != 0) {
239 argv[argc++] = "-f";
240 argv[argc++] = env->gotwebd_conffile;
242 if (env->gotwebd_debug)
243 argv[argc++] = "-d";
244 if (env->gotwebd_verbose > 0)
245 argv[argc++] = "-v";
246 if (env->gotwebd_verbose > 1)
247 argv[argc++] = "-v";
248 argv[argc] = NULL;
250 if (p[0] != GOTWEBD_SOCK_FILENO) {
251 if (dup2(p[0], GOTWEBD_SOCK_FILENO) == -1)
252 fatal("dup2");
253 } else if (fcntl(p[0], F_SETFD, 0) == -1)
254 fatal("fcntl");
256 /* obnoxious cast */
257 execvp(argv0, (char * const *)argv);
258 fatal("execvp %s", argv0);
261 __dead void
262 usage(void)
264 fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
265 getprogname());
266 exit(1);
270 main(int argc, char **argv)
272 struct event sigint, sigterm, sighup, sigpipe, sigusr1;
273 struct gotwebd *env;
274 struct passwd *pw;
275 int ch, i;
276 int no_action = 0;
277 int server_proc = 0;
278 const char *conffile = GOTWEBD_CONF;
279 const char *username = GOTWEBD_DEFAULT_USER;
280 const char *argv0;
282 if ((argv0 = argv[0]) == NULL)
283 argv0 = "gotwebd";
285 /* log to stderr until daemonized */
286 log_init(1, LOG_DAEMON);
288 env = calloc(1, sizeof(*env));
289 if (env == NULL)
290 fatal("%s: calloc", __func__);
291 config_init(env);
293 while ((ch = getopt(argc, argv, "D:df:nSv")) != -1) {
294 switch (ch) {
295 case 'D':
296 if (cmdline_symset(optarg) < 0)
297 log_warnx("could not parse macro definition %s",
298 optarg);
299 break;
300 case 'd':
301 env->gotwebd_debug = 1;
302 break;
303 case 'f':
304 conffile = optarg;
305 break;
306 case 'n':
307 no_action = 1;
308 break;
309 case 'S':
310 server_proc = 1;
311 break;
312 case 'v':
313 if (env->gotwebd_verbose < 3)
314 env->gotwebd_verbose++;
315 break;
316 default:
317 usage();
321 argc -= optind;
322 if (argc > 0)
323 usage();
325 gotwebd_env = env;
326 env->gotwebd_conffile = conffile;
328 if (parse_config(env->gotwebd_conffile, env) == -1)
329 exit(1);
331 if (no_action) {
332 fprintf(stderr, "configuration OK\n");
333 exit(0);
336 /* check for root privileges */
337 if (geteuid())
338 fatalx("need root privileges");
340 if (env->user)
341 username = env->user;
342 pw = getpwnam(username);
343 if (pw == NULL)
344 fatalx("unknown user %s", username);
345 env->pw = pw;
347 log_init(env->gotwebd_debug, LOG_DAEMON);
348 log_setverbose(env->gotwebd_verbose);
350 if (server_proc) {
351 setproctitle("sockets");
352 log_procinit("sockets");
354 if (chroot(env->httpd_chroot) == -1)
355 fatal("chroot %s", env->httpd_chroot);
356 if (chdir("/") == -1)
357 fatal("chdir /");
358 if (setgroups(1, &pw->pw_gid) == -1 ||
359 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
360 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
361 fatal("failed to drop privileges");
363 sockets(env, GOTWEBD_SOCK_FILENO);
364 return 1;
367 if (!env->gotwebd_debug && daemon(1, 0) == -1)
368 fatal("daemon");
370 event_init();
372 env->nserver = env->prefork_gotwebd;
373 env->iev_server = calloc(env->nserver, sizeof(*env->iev_server));
374 if (env->iev_server == NULL)
375 fatal("calloc");
377 for (i = 0; i < env->nserver; ++i) {
378 if (spawn_socket_process(env, argv0, i) == -1)
379 fatal("spawn_socket_process");
382 if (chdir("/") == -1)
383 fatal("chdir /");
385 log_procinit("gotwebd");
387 log_info("%s startup", getprogname());
389 signal_set(&sigint, SIGINT, gotwebd_sighdlr, env);
390 signal_set(&sigterm, SIGTERM, gotwebd_sighdlr, env);
391 signal_set(&sighup, SIGHUP, gotwebd_sighdlr, env);
392 signal_set(&sigpipe, SIGPIPE, gotwebd_sighdlr, env);
393 signal_set(&sigusr1, SIGUSR1, gotwebd_sighdlr, env);
395 signal_add(&sigint, NULL);
396 signal_add(&sigterm, NULL);
397 signal_add(&sighup, NULL);
398 signal_add(&sigpipe, NULL);
399 signal_add(&sigusr1, NULL);
401 if (gotwebd_configure(env) == -1)
402 fatalx("configuration failed");
404 #ifdef PROFILE
405 if (unveil("gmon.out", "rwc") != 0)
406 err(1, "gmon.out");
407 #endif
409 if (unveil(env->httpd_chroot, "r") == -1)
410 err(1, "unveil");
412 if (unveil(GOTWEBD_CONF, "r") == -1)
413 err(1, "unveil");
415 if (unveil(NULL, NULL) != 0)
416 err(1, "unveil");
418 #ifndef PROFILE
419 if (pledge("stdio", NULL) == -1)
420 err(1, "pledge");
421 #endif
423 event_dispatch();
425 log_debug("%s gotwebd exiting", getprogname());
427 return (0);
431 gotwebd_configure(struct gotwebd *env)
433 struct server *srv;
434 struct socket *sock;
436 /* gotweb need to reload its config. */
437 env->gotwebd_reload = env->prefork_gotwebd;
439 /* send our gotweb servers */
440 TAILQ_FOREACH(srv, &env->servers, entry) {
441 if (config_setserver(env, srv) == -1)
442 fatalx("%s: send server error", __func__);
445 /* send our sockets */
446 TAILQ_FOREACH(sock, &env->sockets, entry) {
447 if (config_setsock(env, sock) == -1)
448 fatalx("%s: send socket error", __func__);
451 /* send the temp files */
452 if (config_setfd(env) == -1)
453 fatalx("%s: send priv_fd error", __func__);
455 if (main_compose_sockets(env, IMSG_CFG_DONE, -1, NULL, 0) == -1)
456 fatal("main_compose_sockets IMSG_CFG_DONE");
458 return (0);
461 void
462 gotwebd_configure_done(struct gotwebd *env)
464 if (env->gotwebd_reload == 0) {
465 log_warnx("%s: configuration already finished", __func__);
466 return;
469 env->gotwebd_reload--;
470 if (env->gotwebd_reload == 0 &&
471 main_compose_sockets(env, IMSG_CTL_START, -1, NULL, 0) == -1)
472 fatal("main_compose_sockets IMSG_CTL_START");
475 void
476 gotwebd_shutdown(void)
478 struct gotwebd *env = gotwebd_env;
479 pid_t pid;
480 int i, status;
482 for (i = 0; i < env->nserver; ++i) {
483 event_del(&env->iev_server[i].ev);
484 imsgbuf_clear(&env->iev_server[i].ibuf);
485 close(env->iev_server[i].ibuf.fd);
486 env->iev_server[i].ibuf.fd = -1;
489 do {
490 pid = waitpid(WAIT_ANY, &status, 0);
491 if (pid <= 0)
492 continue;
494 if (WIFSIGNALED(status))
495 log_warnx("lost child: pid %u terminated; signal %d",
496 pid, WTERMSIG(status));
497 else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
498 log_warnx("lost child: pid %u exited abnormally",
499 pid);
500 } while (pid != -1 || (pid == -1 && errno == EINTR));
502 free(gotwebd_env);
504 log_warnx("gotwebd terminating");
505 exit(0);