Blob


1 /*
2 * Copyright (c) 2016, 2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
4 *
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.
8 *
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.
16 */
18 #include <sys/param.h>
19 #include <sys/queue.h>
20 #include <sys/socket.h>
21 #include <sys/wait.h>
23 #include <net/if.h>
24 #include <netinet/in.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <termios.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <event.h>
33 #include <fcntl.h>
34 #include <imsg.h>
35 #include <pwd.h>
36 #include <signal.h>
37 #include <syslog.h>
38 #include <unistd.h>
39 #include <ctype.h>
40 #include <util.h>
42 #include "got_opentemp.h"
43 #include "got_reference.h"
45 #include "gotwebd.h"
47 __dead void usage(void);
49 int main(int, char **);
50 int gotwebd_configure(struct gotwebd *);
51 void gotwebd_configure_done(struct gotwebd *);
52 void gotwebd_sighdlr(int sig, short event, void *arg);
53 void gotwebd_shutdown(void);
54 void gotwebd_dispatch_sockets(int, short, void *);
56 struct gotwebd *gotwebd_env;
58 void
59 imsg_event_add(struct imsgev *iev)
60 {
61 if (iev->handler == NULL) {
62 imsg_flush(&iev->ibuf);
63 return;
64 }
66 iev->events = EV_READ;
67 if (iev->ibuf.w.queued)
68 iev->events |= EV_WRITE;
70 event_del(&iev->ev);
71 event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
72 event_add(&iev->ev, NULL);
73 }
75 int
76 imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
77 pid_t pid, int fd, const void *data, uint16_t datalen)
78 {
79 int ret;
81 ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
82 if (ret == -1)
83 return (ret);
84 imsg_event_add(iev);
85 return (ret);
86 }
88 int
89 main_compose_sockets(struct gotwebd *env, uint32_t type, int fd,
90 const void *data, uint16_t len)
91 {
92 size_t i;
93 int ret, d;
95 for (i = 0; i < env->nserver; ++i) {
96 d = -1;
97 if (fd != -1 && (d = dup(fd)) == -1)
98 goto err;
100 ret = imsg_compose_event(&env->iev_server[i], type, 0, -1,
101 d, data, len);
102 if (ret == -1)
103 goto err;
105 /* prevent fd exhaustion */
106 if (d != -1) {
107 do {
108 ret = imsg_flush(&env->iev_server[i].ibuf);
109 } while (ret == -1 && errno == EAGAIN);
110 if (ret == -1)
111 goto err;
112 imsg_event_add(&env->iev_server[i]);
116 if (fd != -1)
117 close(fd);
118 return 0;
120 err:
121 if (fd != -1)
122 close(fd);
123 return -1;
126 int
127 sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d,
128 uint16_t len)
130 return (imsg_compose_event(env->iev_parent, type, 0, -1, -1, d, len));
133 void
134 gotwebd_dispatch_sockets(int fd, short event, void *arg)
136 struct imsgev *iev = arg;
137 struct imsgbuf *ibuf;
138 struct imsg imsg;
139 struct gotwebd *env = gotwebd_env;
140 ssize_t n;
141 int shut = 0;
143 ibuf = &iev->ibuf;
145 if (event & EV_READ) {
146 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
147 fatal("imsg_read error");
148 if (n == 0) /* Connection closed */
149 shut = 1;
151 if (event & EV_WRITE) {
152 if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
153 fatal("msgbuf_write");
154 if (n == 0) /* Connection closed */
155 shut = 1;
158 for (;;) {
159 if ((n = imsg_get(ibuf, &imsg)) == -1)
160 fatal("imsg_get");
161 if (n == 0) /* No more messages. */
162 break;
164 switch (imsg.hdr.type) {
165 case IMSG_CFG_DONE:
166 gotwebd_configure_done(env);
167 break;
168 default:
169 fatalx("%s: unknown imsg type %d", __func__,
170 imsg.hdr.type);
173 imsg_free(&imsg);
176 if (!shut)
177 imsg_event_add(iev);
178 else {
179 /* This pipe is dead. Remove its event handler */
180 event_del(&iev->ev);
181 event_loopexit(NULL);
185 void
186 gotwebd_sighdlr(int sig, short event, void *arg)
188 /* struct privsep *ps = arg; */
190 switch (sig) {
191 case SIGHUP:
192 log_info("%s: ignoring SIGHUP", __func__);
193 break;
194 case SIGPIPE:
195 log_info("%s: ignoring SIGPIPE", __func__);
196 break;
197 case SIGUSR1:
198 log_info("%s: ignoring SIGUSR1", __func__);
199 break;
200 case SIGTERM:
201 case SIGINT:
202 gotwebd_shutdown();
203 break;
204 default:
205 fatalx("unexpected signal");
209 static int
210 spawn_socket_process(struct gotwebd *env, const char *argv0, int n)
212 const char *argv[5];
213 int argc = 0;
214 int p[2];
215 pid_t pid;
217 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
218 fatal("socketpair");
220 switch (pid = fork()) {
221 case -1:
222 fatal("fork");
223 case 0: /* child */
224 break;
225 default: /* parent */
226 close(p[0]);
227 imsg_init(&env->iev_server[n].ibuf, p[1]);
228 env->iev_server[n].handler = gotwebd_dispatch_sockets;
229 env->iev_server[n].data = &env->iev_server[n];
230 event_set(&env->iev_server[n].ev, p[1], EV_READ,
231 gotwebd_dispatch_sockets, &env->iev_server[n]);
232 event_add(&env->iev_server[n].ev, NULL);
233 return 0;
236 close(p[1]);
238 argv[argc++] = argv0;
239 argv[argc++] = "-S";
240 if (env->gotwebd_debug)
241 argv[argc++] = "-d";
242 if (env->gotwebd_verbose)
243 argv[argc++] = "-v";
244 argv[argc] = NULL;
246 if (p[0] != GOTWEBD_SOCK_FILENO) {
247 if (dup2(p[0], GOTWEBD_SOCK_FILENO) == -1)
248 fatal("dup2");
249 } else if (fcntl(p[0], F_SETFD, 0) == -1)
250 fatal("fcntl");
252 /* obnoxious cast */
253 execvp(argv0, (char * const *)argv);
254 fatal("execvp %s", argv0);
257 __dead void
258 usage(void)
260 fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
261 getprogname());
262 exit(1);
265 int
266 main(int argc, char **argv)
268 struct event sigint, sigterm, sighup, sigpipe, sigusr1;
269 struct gotwebd *env;
270 struct passwd *pw;
271 int ch, i;
272 int no_action = 0;
273 int server_proc = 0;
274 const char *conffile = GOTWEBD_CONF;
275 const char *argv0;
277 if ((argv0 = argv[0]) == NULL)
278 argv0 = "gotwebd";
280 /* log to stderr until daemonized */
281 log_init(1, LOG_DAEMON);
283 env = calloc(1, sizeof(*env));
284 if (env == NULL)
285 fatal("%s: calloc", __func__);
286 config_init(env);
288 while ((ch = getopt(argc, argv, "D:df:nSv")) != -1) {
289 switch (ch) {
290 case 'D':
291 if (cmdline_symset(optarg) < 0)
292 log_warnx("could not parse macro definition %s",
293 optarg);
294 break;
295 case 'd':
296 env->gotwebd_debug = 1;
297 break;
298 case 'f':
299 conffile = optarg;
300 break;
301 case 'n':
302 no_action = 1;
303 break;
304 case 'S':
305 server_proc = 1;
306 break;
307 case 'v':
308 env->gotwebd_verbose++;
309 break;
310 default:
311 usage();
315 argc -= optind;
316 if (argc > 0)
317 usage();
319 gotwebd_env = env;
320 env->gotwebd_conffile = conffile;
322 if (parse_config(env->gotwebd_conffile, env) == -1)
323 exit(1);
325 if (no_action) {
326 fprintf(stderr, "configuration OK\n");
327 exit(0);
330 /* check for root privileges */
331 if (geteuid())
332 fatalx("need root privileges");
334 pw = getpwnam(GOTWEBD_USER);
335 if (pw == NULL)
336 fatalx("unknown user %s", GOTWEBD_USER);
337 env->pw = pw;
339 log_init(env->gotwebd_debug, LOG_DAEMON);
340 log_setverbose(env->gotwebd_verbose);
342 if (server_proc) {
343 setproctitle("sockets");
344 log_procinit("sockets");
346 if (chroot(env->httpd_chroot) == -1)
347 fatal("chroot %s", env->httpd_chroot);
348 if (chdir("/") == -1)
349 fatal("chdir /");
350 if (setgroups(1, &pw->pw_gid) == -1 ||
351 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
352 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
353 fatal("failed to drop privileges");
355 sockets(env, GOTWEBD_SOCK_FILENO);
356 return 1;
359 if (!env->gotwebd_debug && daemon(1, 0) == -1)
360 fatal("daemon");
362 event_init();
364 env->nserver = env->prefork_gotwebd;
365 env->iev_server = calloc(env->nserver, sizeof(*env->iev_server));
366 if (env->iev_server == NULL)
367 fatal("calloc");
369 for (i = 0; i < env->nserver; ++i) {
370 if (spawn_socket_process(env, argv0, i) == -1)
371 fatal("spawn_socket_process");
374 if (chdir("/") == -1)
375 fatal("chdir /");
377 log_procinit("gotwebd");
379 log_info("%s startup", getprogname());
381 signal_set(&sigint, SIGINT, gotwebd_sighdlr, env);
382 signal_set(&sigterm, SIGTERM, gotwebd_sighdlr, env);
383 signal_set(&sighup, SIGHUP, gotwebd_sighdlr, env);
384 signal_set(&sigpipe, SIGPIPE, gotwebd_sighdlr, env);
385 signal_set(&sigusr1, SIGUSR1, gotwebd_sighdlr, env);
387 signal_add(&sigint, NULL);
388 signal_add(&sigterm, NULL);
389 signal_add(&sighup, NULL);
390 signal_add(&sigpipe, NULL);
391 signal_add(&sigusr1, NULL);
393 if (gotwebd_configure(env) == -1)
394 fatalx("configuration failed");
396 #ifdef PROFILE
397 if (unveil("gmon.out", "rwc") != 0)
398 err(1, "gmon.out");
399 #endif
401 if (unveil(env->httpd_chroot, "r") == -1)
402 err(1, "unveil");
404 if (unveil(GOTWEBD_CONF, "r") == -1)
405 err(1, "unveil");
407 if (unveil(NULL, NULL) != 0)
408 err(1, "unveil");
410 #ifndef PROFILE
411 if (pledge("stdio", NULL) == -1)
412 err(1, "pledge");
413 #endif
415 event_dispatch();
417 log_debug("%s gotwebd exiting", getprogname());
419 return (0);
422 int
423 gotwebd_configure(struct gotwebd *env)
425 struct server *srv;
426 struct socket *sock;
428 /* gotweb need to reload its config. */
429 env->gotwebd_reload = env->prefork_gotwebd;
431 /* send our gotweb servers */
432 TAILQ_FOREACH(srv, &env->servers, entry) {
433 if (config_setserver(env, srv) == -1)
434 fatalx("%s: send server error", __func__);
437 /* send our sockets */
438 TAILQ_FOREACH(sock, &env->sockets, entry) {
439 if (config_setsock(env, sock) == -1)
440 fatalx("%s: send socket error", __func__);
441 if (config_setfd(env, sock) == -1)
442 fatalx("%s: send priv_fd error", __func__);
445 if (main_compose_sockets(env, IMSG_CFG_DONE, -1, NULL, 0) == -1)
446 fatal("main_compose_sockets IMSG_CFG_DONE");
448 return (0);
451 void
452 gotwebd_configure_done(struct gotwebd *env)
454 if (env->gotwebd_reload == 0) {
455 log_warnx("%s: configuration already finished", __func__);
456 return;
459 env->gotwebd_reload--;
460 if (env->gotwebd_reload == 0 &&
461 main_compose_sockets(env, IMSG_CTL_START, -1, NULL, 0) == -1)
462 fatal("main_compose_sockets IMSG_CTL_START");
465 void
466 gotwebd_shutdown(void)
468 struct gotwebd *env = gotwebd_env;
469 pid_t pid;
470 int i, status;
472 for (i = 0; i < env->nserver; ++i) {
473 event_del(&env->iev_server[i].ev);
474 imsg_clear(&env->iev_server[i].ibuf);
475 close(env->iev_server[i].ibuf.fd);
476 env->iev_server[i].ibuf.fd = -1;
479 do {
480 pid = waitpid(WAIT_ANY, &status, 0);
481 if (pid <= 0)
482 continue;
484 if (WIFSIGNALED(status))
485 log_warnx("lost child: pid %u terminated; signal %d",
486 pid, WTERMSIG(status));
487 else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
488 log_warnx("lost child: pid %u exited abnormally",
489 pid);
490 } while (pid != -1 || (pid == -1 && errno == EINTR));
492 free(gotwebd_env);
494 log_warnx("gotwebd terminating");
495 exit(0);