commit b148e63d1f09f60fc25d5a6e2c76e126c9827c2d from: Stefan Sperling via: Thomas Adam date: Fri Mar 21 13:23:05 2025 UTC implement reload support in gotd, triggered via gotctl reload Reload must be triggered via 'gotctl reload' rather than SIGHUP because once gotd has dropped root privileges the gotd-secrets.conf file becomes permanently inaccessible. When SIGHUP is received gotd now logs a message which points the user at 'gotctl reload'. commit - cd1db57e49723e38c55e6b86cc8123fecd389ee7 commit + b148e63d1f09f60fc25d5a6e2c76e126c9827c2d blob - 5195f23ace2cd3b0f5e41c202214651aece284a8 blob + 23faa509f27428d3149445b6a2102fb502e52ceb --- gitwrapper/gitwrapper.c +++ gitwrapper/gitwrapper.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -104,6 +106,7 @@ main(int argc, char *argv[]) { const struct got_error *error; const char *confpath = NULL; + int conf_fd = -1; char *command = NULL, *repo_name = NULL; /* for matching gotd.conf */ char *myserver = NULL; const char *repo_path = NULL; /* as passed on the command line */ @@ -133,12 +136,13 @@ main(int argc, char *argv[]) * checks whether repository paths exist on disk. * Parsing errors and warnings will be logged to stderr. * Upon failure we will run Git's native tooling so do not - * bother checking for errors here. + * bother checking for errors here, not even from open(2). */ confpath = getenv("GOTD_CONF_PATH"); if (confpath == NULL) confpath = GOTD_CONF_PATH; - gotd_parse_config(confpath, GOTD_PROC_GITWRAPPER, NULL, &gotd); + conf_fd = open(confpath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + gotd_parse_config(confpath, conf_fd, GOTD_PROC_GITWRAPPER, NULL, &gotd); error = apply_unveil(myserver); if (error) @@ -209,6 +213,8 @@ done: free(repo_name); free(myserver); free(gitcommand); + if (conf_fd != -1 && close(conf_fd) && error == NULL) + error = got_error_from_errno("close"); if (error) { fprintf(stderr, "%s: %s\n", getprogname(), error->msg); return 1; blob - 07c32a0a3163a6d2e93ca633c2b16134f6043ad9 blob + 8a340c0208ad6cab755ff5fdae8113732a85ab6b --- gotctl/gotctl.8 +++ gotctl/gotctl.8 @@ -64,6 +64,35 @@ Stop a running .Xr gotd 8 instance. This operation requires root privileges. +.It Cm reload Oo Fl c Ar config-file Oc Oo Fl n Oc Oo Fl s Ar secrets Oc +Reload a running +.Xr gotd 8 +instance. +.Xr gotd 8 +will relaunch with an updated configuration read from the provided +configuration files. +The previous instance of +.Xr gotd 8 +will continue to serve existing client connections and then exit. +.Pp +This operation requires root privileges. +.Pp +The options for +.Cm gotctl reload +are as follows: +.Bl -tag -width Ds +.It Fl f Ar config-file +Set the path to the configuration file. +If not specified, the file +.Pa /etc/gotd.conf +will be used. +.It Fl n +Only check the configuration files for validity. +.It Fl s Ar secrets +Set the path to the secrets file. +If not specified, the file +.Pa /etc/gotd-secrets.conf +will be used if it exists. .El .Sh SEE ALSO .Xr got 1 , blob - de46b28e1688e0f673242c0fdd78785c32388f95 blob + 4d80fc06bb5915fe9ecf740992bb312660cc36ba --- gotctl/gotctl.c +++ gotctl/gotctl.c @@ -19,15 +19,19 @@ #include #include #include +#include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include @@ -39,6 +43,8 @@ #include "got_lib_gitproto.h" #include "gotd.h" +#include "secrets.h" +#include "log.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) @@ -57,13 +63,16 @@ __dead static void usage(int, int); __dead static void usage_info(void); __dead static void usage_stop(void); +__dead static void usage_reload(void); static const struct got_error* cmd_info(int, char *[], int); static const struct got_error* cmd_stop(int, char *[], int); +static const struct got_error* cmd_reload(int, char *[], int); static const struct gotctl_cmd gotctl_commands[] = { { "info", cmd_info, usage_info }, { "stop", cmd_stop, usage_stop }, + { "reload", cmd_reload, usage_reload }, }; __dead static void @@ -164,6 +173,12 @@ cmd_info(int argc, char *argv[], int gotd_sock) struct imsgbuf ibuf; struct imsg imsg; + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); +#ifndef PROFILE + if (pledge("stdio", NULL) == -1) + return got_error_from_errno("pledge"); +#endif if (imsgbuf_init(&ibuf, gotd_sock) == -1) return got_error_from_errno("imsgbuf_init"); @@ -220,15 +235,262 @@ cmd_stop(int argc, char *argv[], int gotd_sock) struct imsgbuf ibuf; struct imsg imsg; + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); +#ifndef PROFILE + if (pledge("stdio", NULL) == -1) + return got_error_from_errno("pledge"); +#endif if (imsgbuf_init(&ibuf, gotd_sock) == -1) return got_error_from_errno("imsgbuf_init"); if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1) { imsgbuf_clear(&ibuf); return got_error_from_errno("imsg_compose STOP"); + } + + err = gotd_imsg_flush(&ibuf); + while (err == NULL) { + err = gotd_imsg_poll_recv(&imsg, &ibuf, 0); + if (err) { + if (err->code == GOT_ERR_EOF) + err = NULL; + break; + } + + switch (imsg.hdr.type) { + case GOTD_IMSG_ERROR: + err = gotd_imsg_recv_error(NULL, &imsg); + break; + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + imsg_free(&imsg); + } + + imsgbuf_clear(&ibuf); + return err; +} + +__dead static void +usage_reload(void) +{ + fprintf(stderr, "usage: %s reload [-c config-file] [-s secrets]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) + return got_error_from_errno2("stat", fname); + + if (st.st_uid != 0) { + return got_error_fmt(GOT_ERR_UID, + "secrets file %s must be owned by root", fname); + } + + if (st.st_gid != 0) { + return got_error_fmt(GOT_ERR_GID, + "secrets file %s must be owned by group wheel/root", + fname); + } + + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + return got_error_fmt(GOT_ERR_GID, + "secrets file %s must not be group writable or world " + "readable/writable", fname); + } + + return NULL; +} + +static const struct got_error * +cmd_reload(int argc, char *argv[], int gotd_sock) +{ + const struct got_error *err = NULL; + struct imsgbuf ibuf; + struct gotd gotd; + struct gotd_secrets *secrets = NULL; + struct imsg imsg; + char *confpath = NULL, *secretspath = NULL; + int ch, conf_fd = -1, secrets_fd = -1; + int no_action = 0; + + log_init(1, LOG_DAEMON); /* log to stderr . */ + +#ifndef PROFILE + if (pledge("stdio rpath sendfd unveil", NULL) == -1) + return got_error_from_errno("pledge"); +#endif + while ((ch = getopt(argc, argv, "c:ns:")) != -1) { + switch (ch) { + case 'c': + if (unveil(optarg, "r") != 0) + return got_error_from_errno("unveil"); + confpath = realpath(optarg, NULL); + if (confpath == NULL) { + return got_error_from_errno2("realpath", + optarg); + } + break; + case 'n': + no_action = 1; + break; + case 's': + if (unveil(optarg, "r") != 0) + return got_error_from_errno("unveil"); + secretspath = realpath(optarg, NULL); + if (secretspath == NULL) { + return got_error_from_errno2("realpath", + optarg); + } + break; + default: + usage_reload(); + /* NOTREACHED */ + } + } + + if (confpath == NULL) { + confpath = strdup(GOTD_CONF_PATH); + if (confpath == NULL) + return got_error_from_errno("strdup"); + } + + if (unveil(confpath, "r") != 0) + return got_error_from_errno("unveil"); + + if (unveil(secretspath ? secretspath : GOTD_SECRETS_PATH, "r") != 0) + return got_error_from_errno("unveil"); + + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); + + secrets_fd = open(secretspath ? secretspath : GOTD_SECRETS_PATH, + O_RDONLY | O_NOFOLLOW); + if (secrets_fd == -1) { + if (secretspath != NULL || errno != ENOENT) { + return got_error_from_errno2("open", + secretspath ? secretspath : GOTD_SECRETS_PATH); + } + } else if (secretspath == NULL) { + secretspath = strdup(GOTD_SECRETS_PATH); + if (secretspath == NULL) + return got_error_from_errno("strdup"); } + conf_fd = open(confpath, O_RDONLY | O_NOFOLLOW); + if (conf_fd == -1) + return got_error_from_errno2("open", confpath); + + if (secrets_fd != -1) { + int fd; + FILE *fp; + + err = check_file_secrecy(secrets_fd, secretspath); + if (err) + goto done; + + fd = dup(secrets_fd); + if (fd == -1) { + err = got_error_from_errno("dup"); + goto done; + } + + fp = fdopen(fd, "r"); + if (fp == NULL) { + err = got_error_from_errno2("fdopen", secretspath); + close(fd); + goto done; + } + err = gotd_secrets_parse(secretspath, fp, &secrets); + fclose(fp); + if (err) { + err = got_error_fmt(GOT_ERR_PARSE_CONFIG, + "failed to parse secrets file %s: %s", + secretspath, err->msg); + goto done; + } + } + + if (gotd_parse_config(confpath, conf_fd, GOTD_PROC_GOTCTL, + secrets, &gotd) != 0) { + /* Errors were already printed. Silence this one. */ + err = got_error_msg(GOT_ERR_PARSE_CONFIG, ""); + goto done; + } + + if (no_action) { + fprintf(stderr, "configuration OK\n"); + goto done; + } + +#ifndef PROFILE + if (pledge("stdio sendfd", NULL) == -1) { + err = got_error_from_errno("pledge"); + goto done; + } +#endif + if (secrets_fd != -1 && lseek(secrets_fd, 0L, SEEK_SET) == -1) { + err = got_error_from_errno2("lseek", secretspath); + goto done; + } + if (lseek(conf_fd, 0L, SEEK_SET) == -1) { + err = got_error_from_errno2("lseek", confpath); + goto done; + } + + if (imsgbuf_init(&ibuf, gotd_sock) == -1) { + err = got_error_from_errno("imsgbuf_init"); + goto done; + } + imsgbuf_allow_fdpass(&ibuf); + + if (secrets_fd != -1) { + if (imsg_compose(&ibuf, GOTD_IMSG_RELOAD_SECRETS, 0, 0, + secrets_fd, secretspath ? secretspath : GOTD_SECRETS_PATH, + secretspath ? + strlen(secretspath) : strlen(GOTD_SECRETS_PATH)) == -1) { + err = got_error_from_errno("imsg_compose " + "RELOAD_SECRETS"); + imsgbuf_clear(&ibuf); + goto done; + } + secrets_fd = -1; + } else { + if (imsg_compose(&ibuf, GOTD_IMSG_RELOAD_SECRETS, 0, 0, -1, + NULL, 0) == -1) { + err = got_error_from_errno("imsg_compose " + "RELOAD_SECRETS"); + imsgbuf_clear(&ibuf); + goto done; + } + } + + if (imsg_compose(&ibuf, GOTD_IMSG_RELOAD, 0, 0, conf_fd, + confpath, strlen(confpath)) == -1) { + err = got_error_from_errno("imsg_compose RELOAD"); + imsgbuf_clear(&ibuf); + goto done; + + } + conf_fd = -1; + err = gotd_imsg_flush(&ibuf); + if (err) + goto done; +#ifndef PROFILE + if (pledge("stdio", NULL) == -1) { + err = got_error_from_errno("pledge"); + goto done; + } +#endif while (err == NULL) { err = gotd_imsg_poll_recv(&imsg, &ibuf, 0); if (err) { @@ -250,6 +512,13 @@ cmd_stop(int argc, char *argv[], int gotd_sock) } imsgbuf_clear(&ibuf); +done: + free(confpath); + free(secretspath); + if (conf_fd != -1 && close(conf_fd) == -1 && err == NULL) + err = got_error_from_errno("close"); + if (secrets_fd != -1 && close(secrets_fd) == -1 && err == NULL) + err = got_error_from_errno("close"); return err; } @@ -278,37 +547,15 @@ usage(int hflag, int status) exit(status); } -static const struct got_error * -apply_unveil(const char *unix_socket_path) -{ -#ifdef PROFILE - if (unveil("gmon.out", "rwc") != 0) - return got_error_from_errno2("unveil", "gmon.out"); -#endif - if (unveil(unix_socket_path, "w") != 0) - return got_error_from_errno2("unveil", unix_socket_path); - - if (unveil(NULL, NULL) != 0) - return got_error_from_errno("unveil"); - - return NULL; -} - static int connect_gotd(const char *socket_path) { - const struct got_error *error = NULL; int gotd_sock = -1; struct sockaddr_un sun; - error = apply_unveil(socket_path); - if (error) - errx(1, "%s", error->msg); + if (unveil(socket_path, "w") != 0) + err(1, "unveil %s", socket_path); -#ifndef PROFILE - if (pledge("stdio unix", NULL) == -1) - err(1, "pledge"); -#endif if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) err(1, "socket"); @@ -321,10 +568,9 @@ connect_gotd(const char *socket_path) err(1, "connect: %s", socket_path); #ifndef PROFILE - if (pledge("stdio", NULL) == -1) + if (pledge("stdio rpath sendfd unveil", NULL) == -1) err(1, "pledge"); #endif - return gotd_sock; } @@ -344,10 +590,9 @@ main(int argc, char *argv[]) setlocale(LC_CTYPE, ""); #ifndef PROFILE - if (pledge("stdio unix unveil", NULL) == -1) + if (pledge("stdio rpath unix sendfd unveil", NULL) == -1) err(1, "pledge"); #endif - while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) { switch (ch) { case 'h': @@ -388,13 +633,16 @@ main(int argc, char *argv[]) if (hflag) cmd->cmd_usage(); - +#ifdef PROFILE + if (unveil("gmon.out", "rwc") != 0) + err(1, "unveil", "gmon.out"); +#endif gotd_sock = connect_gotd(socket_path); if (gotd_sock == -1) return 1; error = cmd->cmd_main(argc, argv, gotd_sock); close(gotd_sock); - if (error) { + if (error && error->msg[0] != '\0') { fprintf(stderr, "%s: %s\n", getprogname(), error->msg); return 1; } blob - e202efef1d899a13eed237db063dc45818bd74bc blob + 77da458596ecb00f013cf151b97639966bcbe0da --- gotd/gotd.c +++ gotd/gotd.c @@ -118,6 +118,12 @@ volatile int client_cnt; static struct timeval auth_timeout = { 5, 0 }; static struct gotd gotd; static int gotd_socket = -1; +static int gotd_reload_conf_fd = -1; +static int gotd_reload_secrets_fd = -1; +static int have_reload_secrets; +static char *gotd_reload_secrets_path; +static int listener_halted; +static uint32_t reload_client_id; void gotd_sighdlr(int sig, short event, void *arg); static void gotd_shutdown(void); @@ -129,7 +135,9 @@ static const struct got_error *start_auth_child(struct struct gotd_repo *, char *, const char *, int, int); static void kill_proc(struct gotd_child_proc *, int); static void disconnect(struct gotd_client *); -static void drop_privs(struct passwd *); +static pid_t start_child(enum gotd_procid, const char *, char *, + const char *, const char *, int, int, int); +static void kill_proc_timeout(int, short, void *); __dead static void usage(void) @@ -427,6 +435,9 @@ disconnect(struct gotd_client *client) free(client->username); free(client); client_cnt--; + + if (listener_halted && client_cnt == 0) + event_loopexit(NULL); } static void @@ -564,9 +575,229 @@ stop_gotd(struct gotd_client *client) gotd_shutdown(); /* NOTREACHED */ return NULL; +} + +static const struct got_error * +send_reload_config(struct gotd_imsgev *iev) +{ + const struct got_error *err = NULL; + int fd; + + fd = dup(gotd_socket); + if (fd == -1) { + err = got_error_from_errno("dup"); + goto done; + } + + if (imsg_compose(&iev->ibuf, GOTD_IMSG_LISTEN_SOCKET, + GOTD_PROC_GOTD, gotd.pid, fd, NULL, 0) == -1) { + close(fd); + err = got_error_from_errno("imsg compose LISTEN_SOCKET"); + goto done; + } + + if (imsg_compose(&iev->ibuf, GOTD_IMSG_RELOAD_SECRETS, + GOTD_PROC_GOTD, gotd.pid, gotd_reload_secrets_fd, NULL, 0) == -1) { + err = got_error_from_errno("imsg compose RELOAD_SECRETS"); + goto done; + } + + if (imsg_compose(&iev->ibuf, GOTD_IMSG_GOTD_CONF, + GOTD_PROC_GOTD, gotd.pid, gotd_reload_conf_fd, NULL, 0) == -1) { + err = got_error_from_errno("imsg compose GOTD_CONF"); + goto done; + } + + err = gotd_imsg_flush(&iev->ibuf); + if (err) + return err; +done: + if (gotd_reload_conf_fd != -1) { + close(gotd_reload_conf_fd); + gotd_reload_conf_fd = -1; + } + + if (gotd_reload_conf_fd != -1) { + close(gotd_reload_secrets_fd); + gotd_reload_secrets_fd = -1; + } + have_reload_secrets = 0; + + return err; +} + +static const struct got_error * +halt_listener(struct gotd_imsgev *iev) +{ + if (gotd_imsg_compose_event(&gotd.listen_proc->iev, + GOTD_IMSG_HALT, GOTD_PROC_GOTD, -1, + &reload_client_id, sizeof(reload_client_id)) == -1) + return got_error_from_errno("imsg compose HALT"); + + listener_halted = 1; + return NULL; +} + +static void +gotd_dispatch_reload(int fd, short event, void *arg) +{ + const struct got_error *err = NULL; + struct gotd_imsgev *iev = arg; + struct imsgbuf *ibuf = &iev->ibuf; + struct gotd_child_proc *proc = gotd.reload_proc; + ssize_t n; + int shut = 0; + struct imsg imsg; + + if (proc->iev.ibuf.fd != fd) + fatalx("%s: unexpected fd %d", __func__, fd); + + if (event & EV_READ) { + if ((n = imsgbuf_read(ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) { + /* Connection closed. */ + shut = 1; + goto done; + } + } + + if (event & EV_WRITE) { + err = gotd_imsg_flush(ibuf); + if (err) + fatalx("%s", err->msg); + } + + for (;;) { + const struct got_error *err = NULL; + + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case GOTD_IMSG_ERROR: + err = gotd_imsg_recv_error(NULL, &imsg); + break; + case GOTD_IMSG_RELOAD_READY: + err = send_reload_config(iev); + if (err) + break; + err = halt_listener(iev); + if (err) + break; + shut = 1; + break; + default: + log_debug("unexpected imsg %d", imsg.hdr.type); + break; + } + + if (err) { + log_warnx("reloading failed: %s", err->msg); + kill_proc(gotd.reload_proc, 0); + gotd.reload_proc = NULL; + imsg_free(&imsg); + return; + } + + imsg_free(&imsg); + } +done: + if (!shut) { + gotd_imsg_event_add(iev); + } else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + if (listener_halted && client_cnt == 0) + event_loopexit(NULL); + } } static const struct got_error * +reload_gotd(struct gotd_client *client, struct imsg *imsg) +{ + const struct got_error *err = NULL; + size_t datalen; + char *confpath = NULL; + struct gotd_child_proc *proc = NULL; + + if (client->euid != 0) + return got_error_set_errno(EPERM, "reload"); + + if (gotd.reload_proc != NULL || gotd_reload_conf_fd != -1) + return got_error_set_errno(EALREADY, "reload"); + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen == 0) + return got_error(GOT_ERR_PRIVSEP_LEN); + + confpath = strndup(imsg->data, datalen); + if (confpath == NULL) + return got_error_from_errno("strndup"); + + gotd_reload_conf_fd = imsg_get_fd(imsg); + if (gotd_reload_conf_fd == -1) { + err = got_error(GOT_ERR_PRIVSEP_NO_FD); + goto done; + } + + /* TODO: parse provided config for verification */ + + proc = calloc(1, sizeof(*proc)); + if (proc == NULL) { + err = got_error_from_errno("calloc"); + goto done; + } + + proc->type = GOTD_PROC_RELOAD; + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, + PF_UNSPEC, proc->pipe) == -1) { + err = got_error_from_errno("socketpair"); + free(proc); + proc = NULL; + goto done; + } + + if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) { + err = got_error_from_errno("imsgbuf_init"); + goto done; + } + + proc->pid = start_child(GOTD_PROC_RELOAD, NULL, gotd.argv0, + confpath, gotd_reload_secrets_path, proc->pipe[1], + gotd.daemonize, gotd.verbosity); + imsgbuf_allow_fdpass(&proc->iev.ibuf); + proc->iev.handler = gotd_dispatch_reload; + proc->iev.events = EV_READ; + proc->iev.handler_arg = NULL; + event_set(&proc->iev.ev, proc->iev.ibuf.fd, EV_READ, + gotd_dispatch_reload, &proc->iev); + gotd_imsg_event_add(&proc->iev); + evtimer_set(&proc->tmo, kill_proc_timeout, proc); + + TAILQ_INSERT_HEAD(&procs, proc, entry); + + gotd.reload_proc = proc; + + log_info("gotd is reloading with PID %d", proc->pid); + + reload_client_id = client->id; +done: + if (err) { + if (proc) { + close(proc->pipe[0]); + close(proc->pipe[1]); + free(proc); + } + } + free(confpath); + return err; +} + +static const struct got_error * start_client_authentication(struct gotd_client *client, struct imsg *imsg) { const struct got_error *err; @@ -629,6 +860,38 @@ start_client_authentication(struct gotd_client *client return NULL; } +static const struct got_error * +recv_reload_secrets(struct imsg *imsg) +{ + const struct got_error *err = NULL; + size_t datalen; + + gotd_reload_secrets_fd = imsg_get_fd(imsg); + if (gotd_reload_secrets_fd == -1) + return NULL; /* no secrets being used */ + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen == 0) { + err = got_error(GOT_ERR_PRIVSEP_LEN); + goto done; + } + + gotd_reload_secrets_path = strndup(imsg->data, datalen); + if (gotd_reload_secrets_path == NULL) + err = got_error_from_errno("strndup"); +done: + if (err) { + if (gotd_reload_secrets_fd != -1) { + close(gotd_reload_secrets_fd); + gotd_reload_secrets_fd = -1; + } + free(gotd_reload_secrets_path); + gotd_reload_secrets_path = NULL; + } + + return err; +} + static void gotd_request(int fd, short events, void *arg) { @@ -638,6 +901,7 @@ gotd_request(int fd, short events, void *arg) const struct got_error *err = NULL; struct imsg imsg; ssize_t n; + int do_disconnect = 0; if (events & EV_WRITE) { err = gotd_imsg_flush(ibuf); @@ -677,7 +941,7 @@ gotd_request(int fd, short events, void *arg) } } - while (err == NULL) { + while (err == NULL && !do_disconnect) { n = imsg_get(ibuf, &imsg); if (n == -1) { err = got_error_from_errno("imsg_get"); @@ -694,7 +958,23 @@ gotd_request(int fd, short events, void *arg) break; case GOTD_IMSG_STOP: err = stop_gotd(client); + break; + case GOTD_IMSG_RELOAD_SECRETS: + if (have_reload_secrets) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = recv_reload_secrets(&imsg); + if (err) + break; + have_reload_secrets = 1; break; + case GOTD_IMSG_RELOAD: + err = reload_gotd(client, &imsg); + if (err) + break; + do_disconnect = 1; + break; case GOTD_IMSG_LIST_REFS: err = start_client_authentication(client, &imsg); break; @@ -709,6 +989,8 @@ gotd_request(int fd, short events, void *arg) if (err) { disconnect_on_error(client, err); + } else if (do_disconnect) { + disconnect(client); } else { gotd_imsg_event_add(&client->iev); } @@ -821,6 +1103,8 @@ static const char *gotd_proc_names[GOTD_PROC_MAX] = { "gitwrapper", "notify", "gotsys", + "reload", + "gotctl", }; static void @@ -939,10 +1223,10 @@ gotd_sighdlr(int sig, short event, void *arg) switch (sig) { case SIGHUP: - log_info("%s: ignoring SIGHUP", __func__); + log_info("ignoring SIGHUP; run 'gotctl reload' instead"); break; case SIGUSR1: - log_info("%s: ignoring SIGUSR1", __func__); + log_info("ignoring SIGUSR1"); break; case SIGTERM: case SIGINT: @@ -1189,13 +1473,20 @@ setup_listener(struct gotd_imsgev *iev) { struct gotd_imsg_listen_socket isocket; size_t i; + int fd; memset(&isocket, 0, sizeof(isocket)); isocket.nconnection_limits = gotd.nconnection_limits; + fd = dup(gotd_socket); + if (fd == -1) + return got_error_from_errno("dup"); + if (gotd_imsg_compose_event(iev, GOTD_IMSG_LISTEN_SOCKET, - GOTD_PROC_GOTD, gotd_socket, &isocket, sizeof(isocket)) == -1) + GOTD_PROC_GOTD, fd, &isocket, sizeof(isocket)) == -1) { + close(fd); return got_error_from_errno("imsg compose LISTEN_SOCKET"); + } for (i = 0; i < gotd.nconnection_limits; i++) { if (gotd_imsg_compose_event(iev, GOTD_IMSG_CONNECTION_LIMIT, @@ -2521,9 +2812,10 @@ done: static pid_t start_child(enum gotd_procid proc_id, const char *repo_path, - char *argv0, const char *confpath, int fd, int daemonize, int verbosity) + char *argv0, const char *confpath, const char *secretspath, + int fd, int daemonize, int verbosity) { - const char *argv[11]; + const char *argv[13]; int argc = 0; pid_t pid; @@ -2565,6 +2857,9 @@ start_child(enum gotd_procid proc_id, const char *repo break; case GOTD_PROC_NOTIFY: argv[argc++] = "-TN"; + break; + case GOTD_PROC_RELOAD: + argv[argc++] = "-TG"; break; default: fatalx("invalid process id %d", proc_id); @@ -2572,6 +2867,11 @@ start_child(enum gotd_procid proc_id, const char *repo argv[argc++] = "-f"; argv[argc++] = confpath; + + if (secretspath) { + argv[argc++] = "-s"; + argv[argc++] = secretspath; + } if (repo_path) { argv[argc++] = "-P"; @@ -2614,7 +2914,7 @@ start_listener(char *argv0, const char *confpath, int PF_UNSPEC, proc->pipe) == -1) fatal("socketpair"); - proc->pid = start_child(proc->type, NULL, argv0, confpath, + proc->pid = start_child(proc->type, NULL, argv0, confpath, NULL, proc->pipe[1], daemonize, verbosity); if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) fatal("imsgbuf_init"); @@ -2650,7 +2950,7 @@ start_notifier(char *argv0, const char *confpath, int PF_UNSPEC, proc->pipe) == -1) fatal("socketpair"); - proc->pid = start_child(proc->type, NULL, argv0, confpath, + proc->pid = start_child(proc->type, NULL, argv0, confpath, NULL, proc->pipe[1], daemonize, verbosity); if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) fatal("imsgbuf_init"); @@ -2698,7 +2998,7 @@ start_session_child(struct gotd_client *client, struct if (socketpair(AF_UNIX, sock_flags, PF_UNSPEC, proc->pipe) == -1) fatal("socketpair"); proc->pid = start_child(proc->type, proc->repo_path, argv0, - confpath, proc->pipe[1], daemonize, verbosity); + confpath, NULL, proc->pipe[1], daemonize, verbosity); if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) fatal("imsgbuf_init"); imsgbuf_allow_fdpass(&proc->iev.ibuf); @@ -2754,7 +3054,7 @@ start_repo_child(struct gotd_client *client, enum gotd PF_UNSPEC, proc->pipe) == -1) fatal("socketpair"); proc->pid = start_child(proc->type, proc->repo_path, argv0, - confpath, proc->pipe[1], daemonize, verbosity); + confpath, NULL, proc->pipe[1], daemonize, verbosity); if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) fatal("imsgbuf_init"); imsgbuf_allow_fdpass(&proc->iev.ibuf); @@ -2801,7 +3101,7 @@ start_auth_child(struct gotd_client *client, int requi PF_UNSPEC, proc->pipe) == -1) fatal("socketpair"); proc->pid = start_child(proc->type, proc->repo_path, argv0, - confpath, proc->pipe[1], daemonize, verbosity); + confpath, NULL, proc->pipe[1], daemonize, verbosity); if (imsgbuf_init(&proc->iev.ibuf, proc->pipe[0]) == -1) fatal("imsgbuf_init"); imsgbuf_allow_fdpass(&proc->iev.ibuf); @@ -2974,6 +3274,9 @@ main(int argc, char **argv) switch (*optarg) { case 'A': proc_id = GOTD_PROC_AUTH; + break; + case 'G': + proc_id = GOTD_PROC_RELOAD; break; case 'L': proc_id = GOTD_PROC_LISTEN; @@ -3019,6 +3322,7 @@ main(int argc, char **argv) if (proc_id == GOTD_PROC_GOTD) { const char *p = secretspath ? secretspath : GOTD_SECRETS_PATH; + int conf_fd = -1; fp = fopen(p, "r"); if (fp == NULL && (secretspath != NULL || errno != ENOENT)) @@ -3033,8 +3337,15 @@ main(int argc, char **argv) p, error->msg); } - if (gotd_parse_config(confpath, proc_id, secrets, &gotd) != 0) + conf_fd = open(confpath, O_RDONLY | O_NOFOLLOW); + if (conf_fd == -1) + fatal("open %s", confpath); + + if (gotd_parse_config(confpath, conf_fd, proc_id, secrets, + &gotd) != 0) return 1; + close(conf_fd); + conf_fd = -1; pw = getpwnam(gotd.user_name); if (pw == NULL) { @@ -3083,13 +3394,139 @@ main(int argc, char **argv) if (gotd_socket == -1) { fatal("cannot listen on unix socket %s", gotd.unix_socket_path); + } + + if (gethostname(hostname, sizeof(hostname)) == -1) + fatal("gethostname"); + if (asprintf(&gotd.default_sender, "%s@%s", pw->pw_name, + hostname) == -1) + fatal("asprintf"); + } else if (proc_id == GOTD_PROC_RELOAD) { + struct imsgbuf ibuf; + struct imsg imsg; + + if (imsgbuf_init(&ibuf, GOTD_FILENO_MSG_PIPE) == -1) + fatal("imsgbuf_init"); + imsgbuf_allow_fdpass(&ibuf); + + if (imsg_compose(&ibuf, GOTD_IMSG_RELOAD_READY, + GOTD_PROC_RELOAD, getpid(), -1, NULL, 0) == -1) + fatal("imsg compose RELOAD_READY"); + error = gotd_imsg_flush(&ibuf); + if (error) { + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatal("imsg flush: %s", error->msg); } + while (error == NULL && + (gotd_socket == -1 || gotd_reload_conf_fd == -1)) { + error = gotd_imsg_poll_recv(&imsg, &ibuf, 0); + if (error) + break; + switch (imsg.hdr.type) { + case GOTD_IMSG_ERROR: + error = gotd_imsg_recv_error(NULL, &imsg); + break; + case GOTD_IMSG_LISTEN_SOCKET: + if (gotd_socket != -1) { + error = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + gotd_socket = imsg_get_fd(&imsg); + if (gotd_socket != -1) + break; + error = got_error(GOT_ERR_PRIVSEP_NO_FD); + break; + case GOTD_IMSG_RELOAD_SECRETS: + if (have_reload_secrets) { + error = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + error = recv_reload_secrets(&imsg); + if (error) + break; + have_reload_secrets = 1; + break; + case GOTD_IMSG_GOTD_CONF: + if (!have_reload_secrets || + gotd_reload_conf_fd != -1) { + error = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + gotd_reload_conf_fd = imsg_get_fd(&imsg); + if (gotd_reload_conf_fd != -1) + break; + error = got_error(GOT_ERR_PRIVSEP_NO_FD); + break; + } + } + if (error) { + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatal("reloading: %s", error->msg); + } + + if (gotd_reload_secrets_fd != -1) { + char *p = gotd_reload_secrets_path; + + check_file_secrecy(gotd_reload_secrets_fd, p); + fp = fdopen(gotd_reload_secrets_fd, "r"); + if (fp == NULL) { + error = got_error_from_errno2("fdopen", + gotd_reload_secrets_path); + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatal("reloading: %s", error->msg); + } + + error = gotd_secrets_parse(p, fp, &secrets); + fclose(fp); + if (error) { + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatalx("failed to parse secrets file %s: %s", + p, error->msg); + } + } + + if (gotd_parse_config(confpath, gotd_reload_conf_fd, + GOTD_PROC_GOTD, secrets, &gotd) != 0) + return 1; + close(gotd_reload_conf_fd); + gotd_reload_conf_fd = -1; + + pw = getpwnam(gotd.user_name); + if (pw == NULL) { + uid = strtonum(gotd.user_name, 0, UID_MAX - 1, &errstr); + if (errstr == NULL) + pw = getpwuid(uid); + } + if (pw == NULL) { + error = got_error_fmt(GOT_ERR_UID, + "user %s not found", gotd.user_name); + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatalx("%s", error->msg); + } + + if (pw->pw_uid == 0) { + error = got_error_fmt(GOT_ERR_UID, + "cannot run %s as the superuser", getprogname()); + gotd_imsg_send_error(&ibuf, proc_id, 0, error); + fatalx("%s", error->msg); + } + if (gethostname(hostname, sizeof(hostname)) == -1) fatal("gethostname"); if (asprintf(&gotd.default_sender, "%s@%s", pw->pw_name, hostname) == -1) fatal("asprintf"); + + imsgbuf_clear(&ibuf); + close(GOTD_FILENO_MSG_PIPE); + + /* Clear state such that reloading again will work. */ + close(gotd_reload_conf_fd); + gotd_reload_conf_fd = -1; + close(gotd_reload_secrets_fd); + gotd_reload_secrets_fd = -1; + have_reload_secrets = 0; } gotd.argv0 = argv0; @@ -3110,6 +3547,11 @@ main(int argc, char **argv) if (daemonize && daemon(1, 0) == -1) fatal("daemon"); gotd.pid = getpid(); + } else if (proc_id == GOTD_PROC_RELOAD) { + snprintf(title, sizeof(title), "%s", + gotd_proc_names[GOTD_PROC_GOTD]); + arc4random_buf(&clients_hash_key, sizeof(clients_hash_key)); + gotd.pid = getpid(); } else if (proc_id == GOTD_PROC_LISTEN) { snprintf(title, sizeof(title), "%s", gotd_proc_names[proc_id]); } else if (proc_id == GOTD_PROC_AUTH) { @@ -3146,6 +3588,9 @@ main(int argc, char **argv) fatal("setgid %d failed", pw->pw_gid); if (setuid(pw->pw_uid) == -1) fatal("setuid %d failed", pw->pw_uid); + /* FALLTHROUGH */ + case GOTD_PROC_RELOAD: + proc_id = GOTD_PROC_GOTD; if (verbosity) { log_info("socket: %s", gotd.unix_socket_path); log_info("user: %s", pw->pw_name); blob - f04ba7601ab2f6ff8ec1e20c104c69749cacf93a blob + 57c83b55a87d19e7fd97463282757061ded30a78 --- gotd/gotd.h +++ gotd/gotd.h @@ -68,6 +68,8 @@ enum gotd_procid { GOTD_PROC_GITWRAPPER, GOTD_PROC_NOTIFY, GOTD_PROC_GOTSYS, + GOTD_PROC_RELOAD, + GOTD_PROC_GOTCTL, GOTD_PROC_MAX, }; @@ -175,6 +177,7 @@ struct gotd { int nrepos; struct gotd_child_proc *listen_proc; struct gotd_child_proc *notify_proc; + struct gotd_child_proc *reload_proc; int notifications_enabled; struct timeval request_timeout; struct timeval auth_timeout; @@ -198,6 +201,7 @@ enum gotd_imsg_type { GOTD_IMSG_INFO_REPO, GOTD_IMSG_INFO_CLIENT, GOTD_IMSG_STOP, + GOTD_IMSG_RELOAD, /* Request a list of references. */ GOTD_IMSG_LIST_REFS, @@ -259,6 +263,12 @@ enum gotd_imsg_type { GOTD_IMSG_REPO_CHILD_READY, GOTD_IMSG_CONNECT_REPO_CHILD, + /* Reloading. */ + GOTD_IMSG_RELOAD_READY, + GOTD_IMSG_RELOAD_SECRETS, + GOTD_IMSG_GOTD_CONF, + GOTD_IMSG_HALT, + /* Auth child process. */ GOTD_IMSG_AUTH_READY, GOTD_IMSG_AUTH_ACCESS_RULE, @@ -639,9 +649,8 @@ struct gotd_imsg_notify { /* Followed by username_len data bytes. */ }; -int enter_chroot(const char *); -int gotd_parse_config(const char *, enum gotd_procid, struct gotd_secrets *, - struct gotd *); +int gotd_parse_config(const char *, int, enum gotd_procid, + struct gotd_secrets *, struct gotd *); struct gotd_repo *gotd_find_repo_by_name(const char *, struct gotd_repolist *); struct gotd_repo *gotd_find_repo_by_path(const char *, struct gotd *); struct gotd_uid_connection_limit *gotd_find_uid_connection_limit( blob - 24af2498fc5909a23d8edfaec7cbf75e44e24bf9 blob + ec806d3f673b48425a89894e6428720af4b2699a --- gotd/listen.c +++ gotd/listen.c @@ -58,6 +58,7 @@ static struct gotd_listen_clients gotd_listen_clients[ static SIPHASH_KEY clients_hash_key; static volatile int listen_client_cnt; static int inflight; +static int listener_halted; struct gotd_uid_connection_counter { STAILQ_ENTRY(gotd_uid_connection_counter) entry; @@ -215,6 +216,9 @@ disconnect(struct gotd_listen_client *client) listen_client_cnt--; if (close(client_fd) == -1) return got_error_from_errno("close"); + + if (listener_halted && listen_client_cnt == 0) + listen_shutdown(); return NULL; } @@ -543,6 +547,26 @@ listen_dispatch(int fd, short event, void *arg) if (err) log_warnx("disconnect: %s", err->msg); break; + case GOTD_IMSG_HALT: { + uint32_t reload_client_id; + struct gotd_listen_client *client; + + event_del(&gotd_listen.iev.ev); + evtimer_del(&gotd_listen.pause_ev); + listener_halted = 1; + log_debug("gotd reloading; stopped listening for " + "connections"); + + if (imsg_get_data(&imsg, &reload_client_id, + sizeof(reload_client_id)) == -1) { + err = got_error_from_errno("imsg-get_data"); + break; + } + client = find_client(reload_client_id); + if (client) + disconnect(client); + break; + } default: log_debug("unexpected imsg %d", imsg.hdr.type); break; @@ -556,7 +580,8 @@ listen_dispatch(int fd, short event, void *arg) } else { /* This pipe is dead. Remove its event handler */ event_del(&iev->ev); - event_loopexit(NULL); + if (!listener_halted) + event_loopexit(NULL); } } blob - df4c6ae0933e6455023be61dfd1d6524ea69cbb3 blob + eeac71346eaccf9b930bcfdb65a805e2f5d19570 --- gotd/parse.y +++ gotd/parse.y @@ -62,7 +62,7 @@ static struct file { int lineno; int errors; } *file; -struct file *newfile(const char *, int, int); +struct file *newfile(const char *, int, int, int); static void closefile(struct file *); int check_file_secrecy(int, const char *); int yyparse(void); @@ -1084,9 +1084,10 @@ check_file_secrecy(int fd, const char *fname) } struct file * -newfile(const char *name, int secret, int required) +newfile(const char *name, int conf_fd, int secret, int required) { struct file *nfile; + int fd = -1; nfile = calloc(1, sizeof(struct file)); if (nfile == NULL) { @@ -1099,7 +1100,12 @@ newfile(const char *name, int secret, int required) free(nfile); return (NULL); } - nfile->stream = fopen(nfile->name, "r"); + + fd = dup(conf_fd); + if (fd == -1) + return NULL; + + nfile->stream = fdopen(fd, "r"); if (nfile->stream == NULL) { if (required) log_warn("open %s", nfile->name); @@ -1126,7 +1132,7 @@ closefile(struct file *xfile) } int -gotd_parse_config(const char *filename, enum gotd_procid proc_id, +gotd_parse_config(const char *filename, int conf_fd, enum gotd_procid proc_id, struct gotd_secrets *secrets, struct gotd *env) { struct sym *sym, *next; @@ -1155,7 +1161,10 @@ gotd_parse_config(const char *filename, enum gotd_proc gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT; gotd->request_timeout.tv_usec = 0; - file = newfile(filename, 0, require_config_file); + if (conf_fd == -1 && !require_config_file) + return 0; + + file = newfile(filename, conf_fd, 0, require_config_file); if (file == NULL) return require_config_file ? -1 : 0;