commit - cd1db57e49723e38c55e6b86cc8123fecd389ee7
commit + b148e63d1f09f60fc25d5a6e2c76e126c9827c2d
blob - 5195f23ace2cd3b0f5e41c202214651aece284a8
blob + 23faa509f27428d3149445b6a2102fb502e52ceb
--- gitwrapper/gitwrapper.c
+++ gitwrapper/gitwrapper.c
#include <err.h>
#include <errno.h>
#include <event.h>
+#include <fcntl.h>
+#include <imsg.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
{
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 */
* 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)
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
.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
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/stat.h>
#include <err.h>
+#include <errno.h>
#include <event.h>
+#include <fcntl.h>
#include <imsg.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <syslog.h>
#include <getopt.h>
#include <unistd.h>
#include "got_lib_gitproto.h"
#include "gotd.h"
+#include "secrets.h"
+#include "log.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
__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
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");
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) {
}
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;
}
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");
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;
}
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':
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
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);
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)
free(client->username);
free(client);
client_cnt--;
+
+ if (listener_halted && client_cnt == 0)
+ event_loopexit(NULL);
}
static void
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;
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)
{
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);
}
}
- while (err == NULL) {
+ while (err == NULL && !do_disconnect) {
n = imsg_get(ibuf, &imsg);
if (n == -1) {
err = got_error_from_errno("imsg_get");
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;
if (err) {
disconnect_on_error(client, err);
+ } else if (do_disconnect) {
+ disconnect(client);
} else {
gotd_imsg_event_add(&client->iev);
}
"gitwrapper",
"notify",
"gotsys",
+ "reload",
+ "gotctl",
};
static void
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:
{
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,
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;
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);
argv[argc++] = "-f";
argv[argc++] = confpath;
+
+ if (secretspath) {
+ argv[argc++] = "-s";
+ argv[argc++] = secretspath;
+ }
if (repo_path) {
argv[argc++] = "-P";
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");
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");
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);
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);
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);
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;
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))
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) {
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;
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) {
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
GOTD_PROC_GITWRAPPER,
GOTD_PROC_NOTIFY,
GOTD_PROC_GOTSYS,
+ GOTD_PROC_RELOAD,
+ GOTD_PROC_GOTCTL,
GOTD_PROC_MAX,
};
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;
GOTD_IMSG_INFO_REPO,
GOTD_IMSG_INFO_CLIENT,
GOTD_IMSG_STOP,
+ GOTD_IMSG_RELOAD,
/* Request a list of references. */
GOTD_IMSG_LIST_REFS,
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,
/* 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
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;
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;
}
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;
} 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
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);
}
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) {
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);
}
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;
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;