From: "Omar Polo" Subject: Re: add gotwebctl(8) To: Stefan Sperling Cc: gameoftrees@openbsd.org Date: Sun, 18 Jan 2026 17:41:30 +0100 Hello, Sorry for the delay. Stefan Sperling wrote: > This patch adds a new utility called gotwebctl(8). It communicates with > gotwebd(8) via a new control socket which only accepts connections from > the root user. > > In this initial version only 2 commands are supported: info and stop > > The info command reports the process ID of the gotwebd parent process > and the current verbosity level. > > The stop command stops gotwebd gracefully. > Earlier today I committed a change which makes gotwebd stop gracefully > when a TERM signal is received, closing listening sockets while still > allowing existing connections to process to completion before terminating. > > I will need this functionality in order to control gotwebd from gotsysd. > Making this command available via a unix socket avoids having to find > the process ID of the running gotwebd instance. And allowing gotwebd > to stop gracefully minimizes the changes of service disruption whenever > gotsysd needs to restart gotwebd to apply configuration changes. > > And I believe having this new tool is valuable in itself. > We could extend it with more functionality later. The tool could report > additional run-time information, change the verbository level on the fly, > or whatever else we come up with. > > ok? looks good to me. ok op@ maybe just remove > [...] > --- /dev/null > +++ gotwebctl/gotwebctl.c > [...] > +#define GOTCTL_CMD_INFO "info" > +#define GOTCTL_CMD_STOP "stop" because they seem unused. Also, this was missing as well i think: --- Makefile +++ Makefile @@ -7,7 +7,7 @@ SUBDIR += regress .endif .if make(clean) || make(obj) || make(release) -SUBDIR += gotwebd gotd gotsh gotctl template gitwrapper +SUBDIR += gotwebd gotwebctl gotd gotsh gotctl template gitwrapper SUBDIR += gotsysd gotsys gotsysctl .endif Oh, there is a conflict in gotwebd/parse.y now but was trivial to fix :P If it saves you some minutes, I'm reattaching here your diff rebased and with the points above addressed. diff /home/op/w/got path + /home/op/w/got commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - 59785cb66e3647ab727137e1f1d919208c5848c9 file + Makefile --- Makefile +++ Makefile @@ -7,7 +7,7 @@ SUBDIR += regress .endif .if make(clean) || make(obj) || make(release) -SUBDIR += gotwebd gotd gotsh gotctl template gitwrapper +SUBDIR += gotwebd gotwebctl gotd gotsh gotctl template gitwrapper SUBDIR += gotsysd gotsys gotsysctl .endif @@ -45,9 +45,11 @@ tmpl-regress: ${MAKE} -C regress/template webd: tmpl + ${MAKE} -C gotwebctl ${MAKE} -C gotwebd webd-install: + ${MAKE} -C gotwebctl install ${MAKE} -C gotwebd install server: commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - /dev/null file + gotwebctl/Makefile (mode 644) --- /dev/null +++ gotwebctl/Makefile @@ -0,0 +1,29 @@ +.PATH:${.CURDIR}/../lib ${.CURDIR}/../gotwebd + +.include "../got-version.mk" + +PROG= gotwebctl +SRCS= gotwebctl.c error.c hash.c + +MAN = ${PROG}.8 + +CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotwebd + +.if defined(PROFILE) +LDADD = -lutil_p -levent_p +.else +LDADD = -lutil -levent +.endif +DPADD = ${LIBUTIL} ${LIBEVENT} + +.if ${GOT_RELEASE} != "Yes" +NOMAN = Yes +.else +BINDIR = ${PREFIX}/sbin +.endif + +realinstall: + ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \ + -m ${BINMODE} ${PROG} ${BINDIR}/${PROG} + +.include commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - /dev/null file + gotwebctl/gotwebctl.8 (mode 644) --- /dev/null +++ gotwebctl/gotwebctl.8 @@ -0,0 +1,85 @@ +.\" +.\" Copyright (c) 2026 Stefan Sperling +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate$ +.Dt GOTWEBCTL 8 +.Os +.Sh NAME +.Nm gotwebctl +.Nd control the Game of Trees Web Daemon +.Sh SYNOPSIS +.Nm +.Op Fl hV +.Op Fl f Ar path +.Ar command +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +controls the +.Xr gotwebd 8 +daemon. +.Pp +Access to the +.Xr gotwebd 8 +control socket requires root privileges. +.Pp +The options for +.Nm +are as follows: +.Bl -tag -width Ds +.It Fl h +Display usage information and exit immediately. +.It Fl f Ar path +Set the +.Ar path +to the control socket which +.Xr gotwebd 8 +is listening on. +If not specified, the default path +.Pa /var/run/gotwebd.sock +will be used. +.It Fl V , -version +Display program version and exit immediately. +.El +.Pp +The commands for +.Nm +are as follows: +.Bl -tag -width Ds +.It Cm info +Display information about a running +.Xr gotwebd 8 +instance. +.It Cm stop +Ask a running +.Xr gotwebd 8 +instance to gracefully stop itself. +This is equivalent to sending a TERM signal to +.Xr gotwebd 8 +without requiring knowledge of the process ID to send the signal to. +.Pp +The control socket will be closed by +.Xr gotwebd 8 , +allowing a new instance of +.Xr gotwebd 8 +to take over. +New FastCGI socket connections will no longer be accepted, but FastCGI +requests currently in progress are still processed to completion. +.Sh SEE ALSO +.Xr got 1 , +.Xr gotwebd.conf 5 , +.Xr gotwebd 8 +.Sh AUTHORS +.An Stefan Sperling Aq Mt stsp@openbsd.org commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - /dev/null file + gotwebctl/gotwebctl.c (mode 644) --- /dev/null +++ gotwebctl/gotwebctl.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2026 Stefan Sperling + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_error.h" +#include "got_object.h" +#include "got_version.h" +#include "got_path.h" +#include "got_reference.h" + +#include "media.h" +#include "gotwebd.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +struct gotwebctl_cmd { + const char *cmd_name; + const struct got_error *(*cmd_main)(int, char *[], int); + void (*cmd_usage)(int); +}; + +__dead static void usage(int, int); + +__dead static void usage_info(int); +__dead static void usage_stop(int); + +static const struct got_error* cmd_info(int, char *[], int); +static const struct got_error* cmd_stop(int, char *[], int); + +static const struct gotwebctl_cmd gotwebctl_commands[] = { + { "info", cmd_info, usage_info }, + { "stop", cmd_stop, usage_stop }, +}; + +__dead static void +usage_info(int status) +{ + FILE *fp = (status == 0) ? stdout : stderr; + fprintf(fp, "usage: %s info\n", getprogname()); + exit(status); +} + +static const struct got_error * +show_info(struct imsg *imsg) +{ + struct gotwebd_imsg_info info; + size_t datalen; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen != sizeof(info)) + return got_error(GOT_ERR_PRIVSEP_LEN); + memcpy(&info, imsg->data, sizeof(info)); + + printf("gotwebd PID: %d\n", info.pid); + printf("verbosity: %d\n", info.verbosity); + return NULL; +} + +static const struct got_error * +cmd_info(int argc, char *argv[], int gotwebd_sock) +{ + const struct got_error *err = NULL; + struct imsgbuf ibuf; + struct imsg imsg; + ssize_t n; + int done = 0; + + 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, gotwebd_sock) == -1) + return got_error_from_errno("imsgbuf_init"); + + if (imsg_compose(&ibuf, GOTWEBD_IMSG_CTL_INFO, 0, 0, -1, + NULL, 0) == -1) { + imsgbuf_clear(&ibuf); + return got_error_from_errno("imsg_compose INFO"); + } + + if (imsgbuf_flush(&ibuf) == -1) { + imsgbuf_clear(&ibuf); + return got_error_from_errno("imsgbuf_flush"); + } + + while (!done && err == NULL) { + n = imsgbuf_read(&ibuf); + if (n == -1) { + if (errno != EAGAIN) { + err = got_error_from_errno("imsgbuf_read"); + break; + } + + sleep(1); + continue; + } + if (n == 0) + break; + + n = imsg_get(&ibuf, &imsg); + if (n == -1) { + err = got_error_from_errno("imsg_get"); + break; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_CTL_INFO: + err = show_info(&imsg); + done = 1; + break; + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + imsg_free(&imsg); + } + + imsgbuf_clear(&ibuf); + return err; +} + +__dead static void +usage_stop(int status) +{ + FILE *fp = (status == 0) ? stdout : stderr; + fprintf(fp, "usage: %s stop\n", getprogname()); + exit(status); +} + +static const struct got_error * +cmd_stop(int argc, char *argv[], int gotwebd_sock) +{ + const struct got_error *err; + struct imsgbuf ibuf; + struct imsg imsg; + ssize_t n; + + 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, gotwebd_sock) == -1) + return got_error_from_errno("imsgbuf_init"); + + if (imsg_compose(&ibuf, GOTWEBD_IMSG_CTL_STOP, 0, 0, -1, + NULL, 0) == -1) { + imsgbuf_clear(&ibuf); + return got_error_from_errno("imsg_compose STOP"); + } + + if (imsgbuf_flush(&ibuf) == -1) { + imsgbuf_clear(&ibuf); + return got_error_from_errno("imsgbuf_flush"); + } + + for (;;) { + n = imsg_get(&ibuf, &imsg); + if (n == -1) { + err = got_error_from_errno("imsg_get"); + break; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + + imsg_free(&imsg); + } + + imsgbuf_clear(&ibuf); + return err; +} + +static void +list_commands(FILE *fp) +{ + size_t i; + + fprintf(fp, "commands:"); + for (i = 0; i < nitems(gotwebctl_commands); i++) { + const struct gotwebctl_cmd *cmd = &gotwebctl_commands[i]; + fprintf(fp, " %s", cmd->cmd_name); + } + fputc('\n', fp); +} + +__dead static void +usage(int hflag, int status) +{ + FILE *fp = (status == 0) ? stdout : stderr; + + fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n", + getprogname()); + if (hflag) + list_commands(fp); + exit(status); +} + +static int +connect_gotwebd(const char *socket_path) +{ + int gotwebd_sock = -1; + struct sockaddr_un sun; + + if (unveil(socket_path, "w") != 0) + err(1, "unveil %s", socket_path); + + if ((gotwebd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >= + sizeof(sun.sun_path)) + errx(1, "gotd socket path too long"); + if (connect(gotwebd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", socket_path); + + return gotwebd_sock; +} + +int +main(int argc, char *argv[]) +{ + const struct gotwebctl_cmd *cmd; + int gotwebd_sock = -1, i; + int ch; + int hflag = 0, Vflag = 0; + static const struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + const char *socket_path = GOTWEBD_CONTROL_SOCKET; + + setlocale(LC_CTYPE, ""); + +#ifndef PROFILE + if (pledge("stdio rpath unix unveil", NULL) == -1) + err(1, "pledge"); +#endif + while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) { + switch (ch) { + case 'h': + hflag = 1; + break; + case 'f': + socket_path = optarg; + break; + case 'V': + Vflag = 1; + break; + default: + usage(hflag, 1); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + optind = 1; + optreset = 1; + + if (Vflag) { + got_version_print_str(); + return 0; + } + + if (argc <= 0) + usage(hflag, hflag ? 0 : 1); + + for (i = 0; i < nitems(gotwebctl_commands); i++) { + const struct got_error *error; + + cmd = &gotwebctl_commands[i]; + + if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0) + continue; + + if (hflag) + cmd->cmd_usage(0); +#ifdef PROFILE + if (unveil("gmon.out", "rwc") != 0) + err(1, "unveil", "gmon.out"); +#endif + gotwebd_sock = connect_gotwebd(socket_path); + if (gotwebd_sock == -1) + return 1; + error = cmd->cmd_main(argc, argv, gotwebd_sock); + close(gotwebd_sock); + if (error && error->msg[0] != '\0') { + fprintf(stderr, "%s: %s\n", getprogname(), error->msg); + return 1; + } + + return 0; + } + + fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]); + list_commands(stderr); + return 1; +} commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - 4c6c4cdca05841e58fab572e2bdc7fd2221e01eb file + gotwebd/gotwebd.8 --- gotwebd/gotwebd.8 +++ gotwebd/gotwebd.8 @@ -133,6 +133,18 @@ Directory containing HTML, CSS, and image files used b Default location for the .Nm listening socket. +.It Pa /var/run/gotweb-login.sock +Default location for the +.Nm +login socket. +.It Pa /var/run/gotwebd.sock +Default location for the +.Nm +control socket. +.Xr gotwebctl 8 +can be used to send commands to +.Nm +via this socket. .It Pa /tmp/ Directory for temporary files created by .Nm . @@ -219,6 +231,7 @@ server "example.com" { .Xr git-repository 5 , .Xr gotwebd.conf 5 , .Xr httpd.conf 5 , +.Xr gotwebctl 8 , .Xr httpd 8 .Sh AUTHORS .An Omar Polo Aq Mt op@openbsd.org commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - f868bed8cfb869337fbd6d4150b9ca347187f066 file + gotwebd/gotwebd.c --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,9 @@ void gotwebd_dispatch_gotweb(int, short, void *); struct gotwebd *gotwebd_env; +static volatile int client_cnt; +static volatile int stopping; + void imsg_event_add(struct imsgev *iev) { @@ -424,6 +428,26 @@ gotwebd_dispatch_gotweb(int fd, short event, void *arg } static void +control_socket_destroy(void) +{ + struct gotwebd *env = gotwebd_env; + + if (env->iev_control == NULL) + return; + + event_del(&env->iev_control->ev); + imsgbuf_clear(&env->iev_control->ibuf); + if (env->iev_control->ibuf.fd != -1) + close(env->iev_control->ibuf.fd); + + free(env->iev_control); + env->iev_control = NULL; + + free(env->control_sock); + env->control_sock = NULL; +} + +static void gotwebd_stop(void) { if (main_compose_sockets(gotwebd_env, GOTWEBD_IMSG_CTL_STOP, @@ -433,6 +457,9 @@ gotwebd_stop(void) if (main_compose_login(gotwebd_env, GOTWEBD_IMSG_CTL_STOP, -1, NULL, 0) == -1) fatal("send_imsg GOTWEBD_IMSG_CTL_STOP"); + + control_socket_destroy(); + stopping = 1; } void @@ -451,10 +478,14 @@ gotwebd_sighdlr(int sig, short event, void *arg) log_info("%s: ignoring SIGUSR1", __func__); break; case SIGTERM: + if (stopping) + break; gotwebd_stop(); break; case SIGINT: gotwebd_shutdown(); + exit(0); + /* NOTREACHED */ break; default: log_warn("unexpected signal %d", sig); @@ -577,6 +608,293 @@ get_usernames(const char **gotwebd_username, const cha *www_username = colon + 1; } +static int +control_socket_listen(struct gotwebd *env, struct socket *sock, + uid_t uid, gid_t gid) +{ + int u_fd = -1; + mode_t old_umask, mode; + + u_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK| SOCK_CLOEXEC, 0); + if (u_fd == -1) { + log_warn("socket"); + return -1; + } + + if (unlink(sock->conf.unix_socket_name) == -1) { + if (errno != ENOENT) { + log_warn("unlink %s", sock->conf.unix_socket_name); + close(u_fd); + return -1; + } + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + + if (bind(u_fd, (struct sockaddr *)&sock->conf.addr.ss, + sock->conf.addr.slen) == -1) { + log_warn("bind: %s", sock->conf.unix_socket_name); + close(u_fd); + (void)umask(old_umask); + return -1; + } + + (void)umask(old_umask); + + if (chmod(sock->conf.unix_socket_name, mode) == -1) { + log_warn("chmod: %s", sock->conf.unix_socket_name); + close(u_fd); + (void)unlink(sock->conf.unix_socket_name); + return -1; + } + + if (chown(sock->conf.unix_socket_name, uid, gid) == -1) { + log_warn("chown: %s", sock->conf.unix_socket_name); + close(u_fd); + (void)unlink(sock->conf.unix_socket_name); + return -1; + } + + if (listen(u_fd, 1) == -1) { + log_warn("listen: %s", sock->conf.unix_socket_name); + return -1; + } + + return u_fd; +} + +struct gotwebd_control_client { + int fd; + struct imsgev iev; + struct event tmo; +}; + +static void +disconnect(struct gotwebd_control_client *client) +{ + if (client->fd != -1) + close(client->fd); + free(client); + client_cnt--; +} + +static void +control_timeout(int fd, short events, void *arg) +{ + struct gotwebd_control_client *client = arg; + + log_debug("disconnecting control socket due to timeout"); + + disconnect(client); +} + +static void +send_info(struct gotwebd_control_client *client) +{ + struct gotwebd_imsg_info info; + + info.pid = gotwebd_env->pid; + info.verbosity = gotwebd_env->gotwebd_verbose; + + if (imsg_compose_event(&client->iev, GOTWEBD_IMSG_CTL_INFO, 0, + gotwebd_env->pid, -1, &info, sizeof(info)) == -1) + log_warn("imsg compose INFO"); +} + +static void +control_request(int fd, short event, void *arg) +{ + struct gotwebd_control_client *client = arg; + struct imsg imsg; + ssize_t n; + + if (event & EV_WRITE) { + if (imsgbuf_write(&client->iev.ibuf) == -1) { + log_warn("imsgbuf_write"); + disconnect(client); + return; + } + + if (stopping) { + disconnect(client); + return; + } + } + + if (event & EV_READ) { + if ((n = imsgbuf_read(&client->iev.ibuf)) == -1) + fatal("imsgbuf_read error"); + if (n == 0) { + /* Connection closed. */ + disconnect(client); + return; + } + } + + for (;;) { + n = imsg_get(&client->iev.ibuf, &imsg); + if (n == -1) { + disconnect(client); + return; + } + + if (n == 0) + break; + + evtimer_del(&client->tmo); + + switch (imsg.hdr.type) { + case GOTWEBD_IMSG_CTL_INFO: + send_info(client); + break; + case GOTWEBD_IMSG_CTL_STOP: + if (!stopping) + gotwebd_stop(); + disconnect(client); + imsg_free(&imsg); + return; + default: + log_warnx("unexpected imsg %d", imsg.hdr.type); + break; + } + + imsg_free(&imsg); + } + + imsg_event_add(&client->iev); +} + +static void +control_accept(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct gotwebd *env = gotwebd_env; + struct sockaddr_storage ss; + struct timeval backoff; + socklen_t len; + int s = -1; + struct gotwebd_control_client *client = NULL; + uid_t euid; + gid_t egid; + + backoff.tv_sec = 1; + backoff.tv_usec = 0; + + if (stopping) + return; + + if (event_add(&iev->ev, NULL) == -1) { + log_warn("event_add"); + return; + } + if (event & EV_TIMEOUT) + return; + + len = sizeof(ss); + + s = accept4(fd, (struct sockaddr *)&ss, &len, + SOCK_NONBLOCK | SOCK_CLOEXEC); + if (s == -1) { + switch (errno) { + case EINTR: + case EWOULDBLOCK: + case ECONNABORTED: + return; + case EMFILE: + case ENFILE: + event_del(&iev->ev); + if (!stopping) + evtimer_add(&env->control_pause_ev, &backoff); + return; + default: + log_warn("accept"); + return; + } + } + + if (client_cnt >= 1) + goto err; + + if (getpeereid(s, &euid, &egid) == -1) { + log_warn("getpeerid"); + goto err; + } + + if (euid != 0) { + log_warnx("control connection from UID %d denied", euid); + goto err; + } + + client = calloc(1, sizeof(*client)); + if (client == NULL) { + log_warn("%s: calloc", __func__); + goto err; + } + client->fd = s; + s = -1; + + client->iev.handler = control_request; + client->iev.events = EV_READ; + client->iev.data = client; + + imsgbuf_init(&client->iev.ibuf, client->fd); + event_set(&client->iev.ev, client->fd, EV_READ, control_request, + client); + imsg_event_add(&client->iev); + + evtimer_set(&client->tmo, control_timeout, client); + + log_debug("%s: control connection on fd %d uid %d gid %d", __func__, + client->fd, euid, egid); + client_cnt++; + return; +err: + if (client) { + close(client->fd); + free(client); + } + if (s != -1) + close(s); +} + +static void +accept_paused(int fd, short event, void *arg) +{ + struct gotwebd *env = gotwebd_env; + + if (!stopping) + event_add(&env->iev_control->ev, NULL); +} + +static void +control_socket_init(struct gotwebd *env, uid_t uid, gid_t gid) +{ + struct imsgev *iev; + int fd; + + log_info("initializing control socket %s", + env->control_sock->conf.unix_socket_name); + + iev = calloc(1, sizeof(*iev)); + if (iev == NULL) + fatal("calloc"); + + fd = control_socket_listen(env, env->control_sock, uid, gid); + if (fd == -1) + exit(1); + + if (imsgbuf_init(&iev->ibuf, fd) == -1) + fatal("imsgbuf_init"); + + iev->data = iev; + event_set(&iev->ev, fd, EV_READ, control_accept, iev); + event_add(&iev->ev, NULL); + evtimer_set(&env->control_pause_ev, accept_paused, NULL); + + env->iev_control = iev; +} + int main(int argc, char **argv) { @@ -783,11 +1101,15 @@ main(int argc, char **argv) break; } + env->pid = getpid(); + if (!env->gotwebd_debug && daemon(1, 0) == -1) fatal("daemon"); evb = event_init(); + control_socket_init(env, pw->pw_uid, pw->pw_gid); + env->iev_sockets = calloc(1, sizeof(*env->iev_sockets)); if (env->iev_sockets == NULL) fatal("calloc"); @@ -867,15 +1189,16 @@ main(int argc, char **argv) err(1, "unveil"); #ifndef PROFILE - if (pledge("stdio", NULL) == -1) + if (pledge("stdio unix", NULL) == -1) err(1, "pledge"); #endif event_dispatch(); + + gotwebd_shutdown(); + event_base_free(evb); - log_debug("%s gotwebd exiting", getprogname()); - return (0); } @@ -1151,6 +1474,8 @@ gotwebd_shutdown(void) free(env->login_sock); + control_socket_destroy(); + do { pid = waitpid(WAIT_ANY, &status, 0); if (pid <= 0) @@ -1188,5 +1513,4 @@ gotwebd_shutdown(void) free(gotwebd_env); log_warnx("gotwebd terminating"); - exit(0); } commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - 0e88f461cc2d5660929306c6218098113ead9fbd file + gotwebd/gotwebd.conf.5 --- gotwebd/gotwebd.conf.5 +++ gotwebd/gotwebd.conf.5 @@ -218,6 +218,17 @@ commands. By default the path .Pa /var/run/gotweb-login.sock will be used. +.It Ic control socket Ar path +Set the +.Ar path +to the +.Ux Ns -domain +socket for +.Xr gotwebctl 8 +commands. +By default the path +.Pa /var/run/gotwebd.sock +will be used. .It Ic prefork Ar number Spawn enough processes such that .Ar number @@ -927,5 +938,6 @@ server "secure.example.com" { .Xr got 1 , .Xr httpd.conf 5 , .Xr services 5 , +.Xr gotwebctl 8 , .Xr gotwebd 8 , .Xr httpd 8 commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - d33a473211f9b6d1f97dac15ca9b453a9ec4035b file + gotwebd/gotwebd.h --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -43,6 +43,8 @@ #define GOTWEBD_LOGIN_SOCKET "/var/run/gotweb-login.sock" #define GOTWEBD_LOGIN_TIMEOUT 300 /* in seconds */ +#define GOTWEBD_CONTROL_SOCKET "/var/run/gotwebd.sock" + #define GOTWEBD_MAXDESCRSZ 1024 #define GOTWEBD_MAXCLONEURLSZ 1024 #define GOTWEBD_CACHESIZE 1024 @@ -150,6 +152,7 @@ enum imsg_type { GOTWEBD_IMSG_CTL_PIPE, GOTWEBD_IMSG_CTL_START, GOTWEBD_IMSG_CTL_STOP, + GOTWEBD_IMSG_CTL_INFO, GOTWEBD_IMSG_LOGIN_SECRET, GOTWEBD_IMSG_AUTH_SECRET, GOTWEBD_IMSG_AUTH_CONF, @@ -170,6 +173,12 @@ struct imsgev { #define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE) +/* Structure for GOTWEBD_IMSG_CTL_INFO. */ +struct gotwebd_imsg_info { + pid_t pid; + int verbosity; +}; + struct env_val { SLIST_ENTRY(env_val) entry; char *val; @@ -477,6 +486,8 @@ TAILQ_HEAD(socketlist, socket); struct passwd; struct gotwebd { + pid_t pid; + struct serverlist servers; struct socketlist sockets; struct addresslist addresses; @@ -484,6 +495,10 @@ struct gotwebd { struct socket *login_sock; struct event login_pause_ev; + struct socket *control_sock; + struct event control_pause_ev; + struct imsgev *iev_control; + enum gotwebd_auth_config auth_config; struct gotwebd_access_rule_list access_rules; commit - f5e64618f74a49a9167bec1f7db97000c2b8557f blob - d7ad16d93f68bb3f742baac27bc72c4373600b62 file + gotwebd/parse.y --- gotwebd/parse.y +++ gotwebd/parse.y @@ -156,7 +156,7 @@ mediatype_ok(const char *s) %token SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER AUTHENTICATION %token ENABLE DISABLE INSECURE REPOSITORY REPOSITORIES PERMIT DENY HIDE %token WEBSITE PATH BRANCH REPOS_URL_PATH DESCRIPTION -%token TYPES INCLUDE +%token TYPES INCLUDE GOTWEBD_CONTROL %token STRING %token NUMBER @@ -481,6 +481,20 @@ main : PREFORK NUMBER { free($2); } + | GOTWEBD_CONTROL SOCKET STRING { + struct address *h; + h = get_unix_addr($3); + if (h == NULL) { + yyerror("can't listen on %s", $3); + free($3); + YYERROR; + } + if (gotwebd->control_sock != NULL) + free(gotwebd->control_sock); + gotwebd->control_sock = sockets_conf_new_socket(-1, h); + free(h); + free($3); + } ; server : SERVER STRING { @@ -1065,6 +1079,7 @@ lookup(char *s) { "authentication", AUTHENTICATION }, { "branch", BRANCH }, { "chroot", CHROOT }, + { "control", GOTWEBD_CONTROL }, { "custom_css", CUSTOM_CSS }, { "deny", DENY }, { "description", DESCRIPTION }, @@ -1635,6 +1650,19 @@ parse_config(const char *filename, struct gotwebd *env free(h); } + /* Add implicit control socket */ + if (gotwebd->control_sock == NULL) { + struct address *h; + h = get_unix_addr(GOTWEBD_CONTROL_SOCKET); + if (h == NULL) { + fprintf(stderr, "cannot listen on %s", + GOTWEBD_CONTROL_SOCKET); + return (-1); + } + gotwebd->control_sock = sockets_conf_new_socket(-1, h); + free(h); + } + /* * Disable authentication if not explicitly configured. * Authentication requires access rules to be configured, and we want