commit - 84bc347c349f09dc692e8d760ba9af3d21b0f5af
commit + a596b9579655b53f1549498aff25ae1d2c9dfc81
blob - 8a7d489ac1ca0b7fd0cf744dfdea4257efcda1c9
blob + 8b6c6977676250da107793984432e332c86e1251
--- Makefile
+++ Makefile
.endif
.if make(clean) || make(obj) || make(release)
-SUBDIR += gotweb
+SUBDIR += gotweb gotwebd
.endif
.if make(tags) || make(cleandir)
web-install:
${MAKE} -C gotweb install
+webd:
+ ${MAKE} -C gotwebd
+
+webd-install:
+ ${MAKE} -C gotwebd install
+
.include <bsd.subdir.mk>
blob - /dev/null
blob + 4c0096efb52bb79e4a8f7e5f09c1b1d953009636 (mode 644)
--- /dev/null
+++ gotwebd/Makefile
+.PATH:${.CURDIR}/../lib
+
+SUBDIR = libexec
+
+.include "../got-version.mk"
+.include "Makefile.inc"
+
+PROG = gotwebd
+SRCS = config.c sockets.c log.c gotwebd.c parse.y proc.c \
+ fcgi.c gotweb.c got_operations.c
+SRCS += blame.c commit_graph.c delta.c diff.c \
+ diffreg.c error.c fileindex.c object.c object_cache.c \
+ object_idset.c object_parse.c opentemp.c path.c pack.c \
+ privsep.c reference.c repository.c sha1.c worktree.c \
+ utf8.c inflate.c buf.c rcsutil.c diff3.c \
+ lockfile.c deflate.c object_create.c delta_cache.c \
+ gotconfig.c diff_main.c diff_atomize_text.c diff_myers.c \
+ diff_output.c diff_output_plain.c diff_output_unidiff.c \
+ diff_output_edscript.c diff_patience.c bloom.c murmurhash2.c \
+ worktree_open.c patch.c sigs.c date.c
+
+MAN = ${PROG}.conf.5 ${PROG}.8
+
+CPPFLAGS += -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}
+LDADD += -lz -levent -lutil -lm
+YFLAGS =
+DPADD = ${LIBEVENT} ${LIBUTIL}
+#CFLAGS += -DGOT_NO_OBJ_CACHE
+
+.if ${GOT_RELEASE} != "Yes"
+NOMAN = Yes
+.endif
+
+.if defined(PROFILE)
+CPPFLAGS += -DPROFILE
+DEBUG = -O0 -pg -g -static
+.else
+DEBUG = -O0 -g
+.endif
+
+realinstall:
+ if [ ! -d ${DESTDIR}${PUB_REPOS_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${DESTDIR}${PUB_REPOS_DIR}; \
+ fi
+ ${INSTALL} -c -o root -g daemon -m 0755 ${PROG} ${BINDIR}/${PROG}
+ if [ ! -d ${DESTDIR}${HTTPD_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${DESTDIR}${HTTPD_DIR}; \
+ fi
+ if [ ! -d ${DESTDIR}${PROG_DIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 ${DESTDIR}${PROG_DIR}; \
+ fi
+ ${INSTALL} -c -o ${WWWUSR} -g ${WWWGRP} -m 0755 \
+ ${.CURDIR}/files/htdocs/${PROG}/* ${DESTDIR}${PROG_DIR}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 8e5361495585907a0f12661cad8b8b9fbfb0b503 (mode 644)
--- /dev/null
+++ gotwebd/Makefile.inc
+LDADD += -lz -lutil
+PREFIX ?= /usr/local
+BINDIR ?= ${PREFIX}/sbin
+CHROOT_DIR ?= /var/www
+GOTWEB_DIR = /bin/gotwebd
+LIBEXECDIR = ${GOTWEB_DIR}/libexec
+LIBEXEC_DIR = ${CHROOT_DIR}${LIBEXECDIR}
+HTTPD_DIR = ${CHROOT_DIR}/htdocs
+PROG_DIR = ${HTTPD_DIR}/${PROG}
+WWWUSR ?= www
+WWWGRP ?= www
blob - /dev/null
blob + 4a8874a87e47d8824f253f466514c16f8ce8c881 (mode 644)
--- /dev/null
+++ gotwebd/config.c
+/*
+ * Copyright (c) 2020-2021 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <event.h>
+#include <fcntl.h>
+#include <util.h>
+#include <errno.h>
+#include <imsg.h>
+
+#include "got_opentemp.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+int
+config_init(struct gotwebd *env)
+{
+ struct privsep *ps = env->gotwebd_ps;
+ unsigned int what;
+
+ /* Global configuration. */
+ if (privsep_process == PROC_GOTWEBD)
+ env->prefork_gotwebd = GOTWEBD_NUMPROC;
+
+ ps->ps_what[PROC_GOTWEBD] = CONFIG_ALL;
+ ps->ps_what[PROC_SOCKS] = CONFIG_SOCKS;
+
+ /* Other configuration. */
+ what = ps->ps_what[privsep_process];
+ if (what & CONFIG_SOCKS) {
+ env->server_cnt = 0;
+ env->servers = calloc(1, sizeof(*env->servers));
+ if (env->servers == NULL)
+ fatalx("%s: calloc", __func__);
+ env->sockets = calloc(1, sizeof(*env->sockets));
+ if (env->sockets == NULL)
+ fatalx("%s: calloc", __func__);
+ TAILQ_INIT(env->servers);
+ TAILQ_INIT(env->sockets);
+ }
+ return 0;
+}
+
+int
+config_getcfg(struct gotwebd *env, struct imsg *imsg)
+{
+ /* nothing to do but tell gotwebd configuration is done */
+ if (privsep_process != PROC_GOTWEBD)
+ proc_compose(env->gotwebd_ps, PROC_GOTWEBD,
+ IMSG_CFG_DONE, NULL, 0);
+
+ return 0;
+}
+
+int
+config_setserver(struct gotwebd *env, struct server *srv)
+{
+ struct server ssrv;
+ struct privsep *ps = env->gotwebd_ps;
+
+ memcpy(&ssrv, srv, sizeof(ssrv));
+ proc_compose(ps, PROC_SOCKS, IMSG_CFG_SRV, &ssrv, sizeof(ssrv));
+ return 0;
+}
+
+int
+config_getserver(struct gotwebd *env, struct imsg *imsg)
+{
+ struct server *srv;
+ uint8_t *p = imsg->data;
+
+ IMSG_SIZE_CHECK(imsg, &srv);
+
+ srv = calloc(1, sizeof(*srv));
+ if (srv == NULL)
+ fatalx("%s: calloc", __func__);
+ memcpy(srv, p, sizeof(*srv));
+
+ if (IMSG_DATA_SIZE(imsg) != sizeof(*srv)) {
+ log_debug("%s: imsg size error", __func__);
+ free(srv);
+ return 1;
+ }
+
+ /* log server info */
+ log_debug("%s: server=%s fcgi_socket=%s unix_socket=%s", __func__,
+ srv->name, srv->fcgi_socket ? "yes" : "no", srv->unix_socket ?
+ "yes" : "no");
+
+ TAILQ_INSERT_TAIL(env->servers, srv, entry);
+
+ return 0;
+}
+
+int
+config_setsock(struct gotwebd *env, struct socket *sock)
+{
+ struct privsep *ps = env->gotwebd_ps;
+ struct socket_conf s;
+ int id;
+ int fd = -1, n, m;
+ struct iovec iov[6];
+ size_t c;
+ unsigned int what;
+
+ /* open listening sockets */
+ if (sockets_privinit(env, sock) == -1)
+ return -1;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ what = ps->ps_what[id];
+
+ if ((what & CONFIG_SOCKS) == 0 || id == privsep_process)
+ continue;
+
+ memcpy(&s, &sock->conf, sizeof(s));
+
+ c = 0;
+ iov[c].iov_base = &s;
+ iov[c++].iov_len = sizeof(s);
+
+ if (id == PROC_SOCKS) {
+ /* XXX imsg code will close the fd after 1st call */
+ n = -1;
+ proc_range(ps, id, &n, &m);
+ for (n = 0; n < m; n++) {
+ if (sock->fd == -1)
+ fd = -1;
+ else if ((fd = dup(sock->fd)) == -1)
+ return 1;
+ if (proc_composev_imsg(ps, id, n, IMSG_CFG_SOCK,
+ -1, fd, iov, c) != 0) {
+ log_warn("%s: failed to compose "
+ "IMSG_CFG_SOCK imsg",
+ __func__);
+ return 1;
+ }
+ if (proc_flush_imsg(ps, id, n) == -1) {
+ log_warn("%s: failed to flush "
+ "IMSG_CFG_SOCK imsg",
+ __func__);
+ return 1;
+ }
+ }
+ }
+ }
+
+ /* Close socket early to prevent fd exhaustion in gotwebd. */
+ if (sock->fd != -1) {
+ close(sock->fd);
+ sock->fd = -1;
+ }
+
+ return 0;
+}
+
+int
+config_getsock(struct gotwebd *env, struct imsg *imsg)
+{
+ struct socket *sock = NULL;
+ struct socket_conf sock_conf;
+ uint8_t *p = imsg->data;
+ int i;
+
+ IMSG_SIZE_CHECK(imsg, &sock_conf);
+ memcpy(&sock_conf, p, sizeof(sock_conf));
+
+ if (IMSG_DATA_SIZE(imsg) != sizeof(sock_conf)) {
+ log_debug("%s: imsg size error", __func__);
+ return 1;
+ }
+
+ /* create a new socket */
+ if ((sock = calloc(1, sizeof(*sock))) == NULL) {
+ if (imsg->fd != -1)
+ close(imsg->fd);
+ return 1;
+ }
+
+ memcpy(&sock->conf, &sock_conf, sizeof(sock->conf));
+ sock->fd = imsg->fd;
+
+ TAILQ_INSERT_TAIL(env->sockets, sock, entry);
+
+ for (i = 0; i < PRIV_FDS__MAX; i++)
+ sock->priv_fd[i] = -1;
+
+ for (i = 0; i < GOT_PACK_NUM_TEMPFILES; i++)
+ sock->pack_fds[i] = -1;
+
+ /* log new socket info */
+ log_debug("%s: name=%s id=%d server=%s child_id=%d parent_id=%d "
+ "type=%s ipv4=%d ipv6=%d socket_path=%s",
+ __func__, sock->conf.name, sock->conf.id, sock->conf.srv_name,
+ sock->conf.child_id, sock->conf.parent_id, sock->conf.type ?
+ "fcgi" : "unix", sock->conf.ipv4, sock->conf.ipv6,
+ strlen(sock->conf.unix_socket_name) ?
+ sock->conf.unix_socket_name : "none");
+
+ return 0;
+}
+
+int
+config_setfd(struct gotwebd *env, struct socket *sock)
+{
+ struct privsep *ps = env->gotwebd_ps;
+ int id, s;
+ int fd = -1, n, m, j;
+ struct iovec iov[6];
+ size_t c;
+ unsigned int what;
+
+ log_debug("%s: Allocating %d file descriptors",
+ __func__, PRIV_FDS__MAX + GOT_PACK_NUM_TEMPFILES);
+
+ for (j = 0; j < PRIV_FDS__MAX + GOT_PACK_NUM_TEMPFILES; j++) {
+ for (id = 0; id < PROC_MAX; id++) {
+ what = ps->ps_what[id];
+
+ if ((what & CONFIG_SOCKS) == 0 || id == privsep_process)
+ continue;
+
+ s = sock->conf.id;
+ c = 0;
+ iov[c].iov_base = &s;
+ iov[c++].iov_len = sizeof(s);
+
+ if (id == PROC_SOCKS) {
+ /*
+ * XXX imsg code will close the fd
+ * after 1st call
+ */
+ n = -1;
+ proc_range(ps, id, &n, &m);
+ for (n = 0; n < m; n++) {
+ fd = got_opentempfd();
+ if (fd == -1)
+ return 1;
+ if (proc_composev_imsg(ps, id, n,
+ IMSG_CFG_FD, -1, fd, iov, c) != 0) {
+ log_warn("%s: failed to compose "
+ "IMSG_CFG_FD imsg",
+ __func__);
+ return 1;
+ }
+ if (proc_flush_imsg(ps, id, n) == -1) {
+ log_warn("%s: failed to flush "
+ "IMSG_CFG_FD imsg",
+ __func__);
+ return 1;
+ }
+ }
+ }
+ }
+
+ /* Close fd early to prevent fd exhaustion in gotwebd. */
+ if (fd != -1)
+ close(fd);
+ }
+ return 0;
+}
+
+int
+config_getfd(struct gotwebd *env, struct imsg *imsg)
+{
+ struct socket *sock;
+ uint8_t *p = imsg->data;
+ int sock_id, match = 0, i;
+
+ IMSG_SIZE_CHECK(imsg, &sock_id);
+ memcpy(&sock_id, p, sizeof(sock_id));
+
+ TAILQ_FOREACH(sock, env->sockets, entry) {
+ for (i = 0; i < (GOT_PACK_NUM_TEMPFILES + PRIV_FDS__MAX); i++) {
+ if (i < PRIV_FDS__MAX && sock->priv_fd[i] == -1) {
+ log_debug("%s: assigning socket %d priv_fd %d",
+ __func__, sock_id, imsg->fd);
+ sock->priv_fd[i] = imsg->fd;
+ match = 1;
+ break;
+ }
+ if (sock->pack_fds[i - PRIV_FDS__MAX] == -1) {
+ log_debug("%s: assigning socket %d pack_fd %d",
+ __func__, sock_id, imsg->fd);
+ sock->pack_fds[i - PRIV_FDS__MAX] = imsg->fd;
+ match = 1;
+ break;
+ }
+ }
+ }
+
+ if (match)
+ return 0;
+ else
+ return 1;
+}
blob - /dev/null
blob + 1581cf938674fc59b27476119bfcc54b5b4417b5 (mode 644)
--- /dev/null
+++ gotwebd/fcgi.c
+/*
+ * Copyright (c) 2020-2022 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
+ *
+ * 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 <arpa/inet.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "got_error.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+size_t fcgi_parse_record(uint8_t *, size_t, struct request *);
+void fcgi_parse_begin_request(uint8_t *, uint16_t, struct request *,
+ uint16_t);
+void fcgi_parse_params(uint8_t *, uint16_t, struct request *, uint16_t);
+void fcgi_send_response(struct request *, struct fcgi_response *);
+
+void dump_fcgi_record_header(const char *, struct fcgi_record_header *);
+void dump_fcgi_begin_request_body(const char *,
+ struct fcgi_begin_request_body *);
+void dump_fcgi_end_request_body(const char *,
+ struct fcgi_end_request_body *);
+
+extern int cgi_inflight;
+extern volatile int client_cnt;
+
+void
+fcgi_request(int fd, short events, void *arg)
+{
+ struct request *c = arg;
+ ssize_t n;
+ size_t parsed = 0;
+
+ n = read(fd, c->buf + c->buf_pos + c->buf_len,
+ FCGI_RECORD_SIZE - c->buf_pos-c->buf_len);
+
+ switch (n) {
+ case -1:
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ return;
+ default:
+ goto fail;
+ }
+ break;
+
+ case 0:
+ log_debug("closed connection");
+ goto fail;
+ default:
+ break;
+ }
+
+ c->buf_len += n;
+
+ /*
+ * Parse the records as they are received. Per the FastCGI
+ * specification, the server need only receive the FastCGI
+ * parameter records in full; it is free to begin execution
+ * at that point, which is what happens here.
+ */
+ do {
+ parsed = fcgi_parse_record(c->buf + c->buf_pos, c->buf_len, c);
+ if (parsed != 0) {
+ c->buf_pos += parsed;
+ c->buf_len -= parsed;
+ }
+ } while (parsed > 0 && c->buf_len > 0);
+
+ /* Make space for further reads */
+ if (parsed != 0)
+ if (c->buf_len > 0) {
+ bcopy(c->buf + c->buf_pos, c->buf, c->buf_len);
+ c->buf_pos = 0;
+ }
+ return;
+fail:
+ fcgi_cleanup_request(c);
+}
+
+size_t
+fcgi_parse_record(uint8_t *buf, size_t n, struct request *c)
+{
+ struct fcgi_record_header *h;
+
+ if (n < sizeof(struct fcgi_record_header))
+ return 0;
+
+ h = (struct fcgi_record_header*) buf;
+
+ dump_fcgi_record("", h);
+
+ if (n < sizeof(struct fcgi_record_header) + ntohs(h->content_len)
+ + h->padding_len)
+ return 0;
+
+ if (h->version != 1)
+ log_warn("wrong version");
+
+ switch (h->type) {
+ case FCGI_BEGIN_REQUEST:
+ fcgi_parse_begin_request(buf +
+ sizeof(struct fcgi_record_header),
+ ntohs(h->content_len), c, ntohs(h->id));
+ break;
+ case FCGI_PARAMS:
+ fcgi_parse_params(buf + sizeof(struct fcgi_record_header),
+ ntohs(h->content_len), c, ntohs(h->id));
+ break;
+ case FCGI_STDIN:
+ case FCGI_ABORT_REQUEST:
+ fcgi_create_end_record(c);
+ fcgi_cleanup_request(c);
+ return 0;
+ default:
+ log_warn("unimplemented type %d", h->type);
+ break;
+ }
+
+ return (sizeof(struct fcgi_record_header) + ntohs(h->content_len)
+ + h->padding_len);
+}
+
+void
+fcgi_parse_begin_request(uint8_t *buf, uint16_t n,
+ struct request *c, uint16_t id)
+{
+ /* XXX -- FCGI_CANT_MPX_CONN */
+ if (c->request_started) {
+ log_warn("unexpected FCGI_BEGIN_REQUEST, ignoring");
+ return;
+ }
+
+ if (n != sizeof(struct fcgi_begin_request_body)) {
+ log_warn("wrong size %d != %lu", n,
+ sizeof(struct fcgi_begin_request_body));
+ return;
+ }
+
+ c->request_started = 1;
+
+ c->id = id;
+ SLIST_INIT(&c->env);
+ c->env_count = 0;
+}
+
+void
+fcgi_parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
+{
+ struct env_val *env_entry;
+ uint32_t name_len, val_len;
+ uint8_t *sd, *dr_buf;
+
+ if (!c->request_started) {
+ log_warn("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring");
+ return;
+ }
+
+ if (c->id != id) {
+ log_warn("unexpected id, ignoring");
+ return;
+ }
+
+ if (n == 0) {
+ gotweb_process_request(c);
+ return;
+ }
+
+ while (n > 0) {
+ if (buf[0] >> 7 == 0) {
+ name_len = buf[0];
+ n--;
+ buf++;
+ } else {
+ if (n > 3) {
+ name_len = ((buf[0] & 0x7f) << 24) +
+ (buf[1] << 16) + (buf[2] << 8) + buf[3];
+ n -= 4;
+ buf += 4;
+ } else
+ return;
+ }
+
+ if (n > 0) {
+ if (buf[0] >> 7 == 0) {
+ val_len = buf[0];
+ n--;
+ buf++;
+ } else {
+ if (n > 3) {
+ val_len = ((buf[0] & 0x7f) << 24) +
+ (buf[1] << 16) + (buf[2] << 8) +
+ buf[3];
+ n -= 4;
+ buf += 4;
+ } else
+ return;
+ }
+ } else
+ return;
+
+ if (n < name_len + val_len)
+ return;
+
+ if ((env_entry = malloc(sizeof(struct env_val))) == NULL) {
+ log_warn("cannot malloc env_entry");
+ return;
+ }
+
+ if ((env_entry->val = calloc(sizeof(char), name_len + val_len +
+ 2)) == NULL) {
+ log_warn("cannot allocate env_entry->val");
+ free(env_entry);
+ return;
+ }
+
+ bcopy(buf, env_entry->val, name_len);
+ buf += name_len;
+ n -= name_len;
+
+ env_entry->val[name_len] = '\0';
+ if (val_len < MAX_QUERYSTRING && strcmp(env_entry->val,
+ "QUERY_STRING") == 0 && c->querystring[0] == '\0') {
+ bcopy(buf, c->querystring, val_len);
+ c->querystring[val_len] = '\0';
+ }
+ if (val_len < GOTWEBD_MAXTEXT && strcmp(env_entry->val,
+ "HTTP_HOST") == 0 && c->http_host[0] == '\0') {
+
+ /*
+ * lazily get subdomain
+ * will only get domain if no subdomain exists
+ * this can still work if gotweb server name is the same
+ */
+ sd = strchr(buf, '.');
+ if (sd)
+ *sd = '\0';
+
+ bcopy(buf, c->http_host, val_len);
+ c->http_host[val_len] = '\0';
+ }
+ if (val_len < MAX_DOCUMENT_ROOT && strcmp(env_entry->val,
+ "DOCUMENT_ROOT") == 0 && c->document_root[0] == '\0') {
+
+ /* drop first char, as it's always / */
+ dr_buf = &buf[1];
+
+ bcopy(dr_buf, c->document_root, val_len - 1);
+ c->document_root[val_len] = '\0';
+ }
+ if (val_len < MAX_SERVER_NAME && strcmp(env_entry->val,
+ "SERVER_NAME") == 0 && c->server_name[0] == '\0') {
+ /* drop first char, as it's always / */
+
+ bcopy(buf, c->server_name, val_len);
+ c->server_name[val_len] = '\0';
+ }
+ env_entry->val[name_len] = '=';
+
+ bcopy(buf, (env_entry->val) + name_len + 1, val_len);
+ buf += val_len;
+ n -= val_len;
+
+ SLIST_INSERT_HEAD(&c->env, env_entry, entry);
+ log_debug("env[%d], %s", c->env_count, env_entry->val);
+ c->env_count++;
+ }
+}
+
+void
+fcgi_timeout(int fd, short events, void *arg)
+{
+ fcgi_cleanup_request((struct request*) arg);
+}
+
+int
+fcgi_gen_binary_response(struct request *c, const uint8_t *data, int len)
+{
+ struct fcgi_response *resp;
+ struct fcgi_record_header *header;
+ ssize_t n = 0;
+ int i;
+
+ if (c->sock->client_status == CLIENT_DISCONNECT)
+ return -1;
+
+ if (data == NULL)
+ return 0;
+
+ if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+ log_warn("%s: cannot calloc fcgi_response", __func__);
+ return -1;
+ }
+
+ header = (struct fcgi_record_header*) resp->data;
+ header->version = 1;
+ header->type = FCGI_STDOUT;
+ header->id = htons(c->id);
+ header->padding_len = 0;
+ header->reserved = 0;
+
+ for (i = 0; i < len; i++) {
+ resp->data[i+8] = data[i];
+ n++;
+ }
+
+ header->content_len = htons(n);
+ resp->data_pos = 0;
+ resp->data_len = n + sizeof(struct fcgi_record_header);
+ fcgi_send_response(c, resp);
+
+ return 0;
+}
+
+int
+fcgi_gen_response(struct request *c, const char *data)
+{
+ struct fcgi_response *resp;
+ struct fcgi_record_header *header;
+ ssize_t n = 0;
+ int i;
+
+ if (c->sock->client_status == CLIENT_DISCONNECT)
+ return -1;
+
+ if (data == NULL)
+ return 0;
+
+ if (strlen(data) == 0)
+ return 0;
+
+ if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+ log_warn("%s: cannot calloc fcgi_response", __func__);
+ return -1;
+ }
+
+ header = (struct fcgi_record_header*) resp->data;
+ header->version = 1;
+ header->type = FCGI_STDOUT;
+ header->id = htons(c->id);
+ header->padding_len = 0;
+ header->reserved = 0;
+
+ for (i = 0; i < strlen(data); i++) {
+ resp->data[i+8] = data[i];
+ n++;
+ }
+
+ header->content_len = htons(n);
+ resp->data_pos = 0;
+ resp->data_len = n + sizeof(struct fcgi_record_header);
+ fcgi_send_response(c, resp);
+
+ return 0;
+}
+
+void
+fcgi_send_response(struct request *c, struct fcgi_response *resp)
+{
+ struct fcgi_record_header *header;
+ struct timespec ts;
+ size_t padded_len;
+ int err = 0, th = 2000;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 50;
+
+ header = (struct fcgi_record_header*)resp->data;
+
+ /* The FastCGI spec suggests to align the output buffer */
+ padded_len = FCGI_ALIGN(resp->data_len);
+ if (padded_len > resp->data_len) {
+ /* There should always be FCGI_PADDING_SIZE bytes left */
+ if (padded_len > FCGI_RECORD_SIZE)
+ log_warn("response too long");
+ header->padding_len = padded_len - resp->data_len;
+ resp->data_len = padded_len;
+ }
+
+ dump_fcgi_record("resp ", header);
+
+ /*
+ * XXX: add some simple write heuristics here
+ * On slower VMs, spotty connections, etc., we don't want to go right to
+ * disconnect. Let's at least try to write the data a few times before
+ * giving up.
+ */
+ while ((write(c->fd, resp->data + resp->data_pos,
+ resp->data_len)) == -1) {
+ nanosleep(&ts, NULL);
+ err++;
+ if (err == th) {
+ c->sock->client_status = CLIENT_DISCONNECT;
+ break;
+ }
+ }
+
+ free(resp);
+}
+
+void
+fcgi_create_end_record(struct request *c)
+{
+ struct fcgi_response *resp;
+ struct fcgi_record_header *header;
+ struct fcgi_end_request_body *end_request;
+
+ if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+ log_warn("cannot calloc fcgi_response");
+ return;
+ }
+ header = (struct fcgi_record_header*) resp->data;
+ header->version = 1;
+ header->type = FCGI_END_REQUEST;
+ header->id = htons(c->id);
+ header->content_len = htons(sizeof(struct
+ fcgi_end_request_body));
+ header->padding_len = 0;
+ header->reserved = 0;
+ end_request = (struct fcgi_end_request_body *) (resp->data +
+ sizeof(struct fcgi_record_header));
+ end_request->app_status = htonl(0); /* script_status */
+ end_request->protocol_status = FCGI_REQUEST_COMPLETE;
+ end_request->reserved[0] = 0;
+ end_request->reserved[1] = 0;
+ end_request->reserved[2] = 0;
+ resp->data_pos = 0;
+ resp->data_len = sizeof(struct fcgi_end_request_body) +
+ sizeof(struct fcgi_record_header);
+ fcgi_send_response(c, resp);
+}
+
+void
+fcgi_cleanup_request(struct request *c)
+{
+ cgi_inflight--;
+ client_cnt--;
+
+ evtimer_del(&c->tmo);
+ if (event_initialized(&c->ev))
+ event_del(&c->ev);
+
+ close(c->fd);
+ gotweb_free_transport(c->t);
+ free(c);
+}
+
+void
+dump_fcgi_record(const char *p, struct fcgi_record_header *h)
+{
+ dump_fcgi_record_header(p, h);
+
+ if (h->type == FCGI_BEGIN_REQUEST)
+ dump_fcgi_begin_request_body(p,
+ (struct fcgi_begin_request_body *)(h + 1));
+ else if (h->type == FCGI_END_REQUEST)
+ dump_fcgi_end_request_body(p,
+ (struct fcgi_end_request_body *)(h + 1));
+}
+
+void
+dump_fcgi_record_header(const char* p, struct fcgi_record_header *h)
+{
+ log_debug("%sversion: %d", p, h->version);
+ log_debug("%stype: %d", p, h->type);
+ log_debug("%srequestId: %d", p, ntohs(h->id));
+ log_debug("%scontentLength: %d", p, ntohs(h->content_len));
+ log_debug("%spaddingLength: %d", p, h->padding_len);
+ log_debug("%sreserved: %d", p, h->reserved);
+}
+
+void
+dump_fcgi_begin_request_body(const char *p, struct fcgi_begin_request_body *b)
+{
+ log_debug("%srole %d", p, ntohs(b->role));
+ log_debug("%sflags %d", p, b->flags);
+}
+
+void
+dump_fcgi_end_request_body(const char *p, struct fcgi_end_request_body *b)
+{
+ log_debug("%sappStatus: %d", p, ntohl(b->app_status));
+ log_debug("%sprotocolStatus: %d", p, b->protocol_status);
+}
blob - /dev/null
blob + f841f054bc2941b0cdca7e496ea69621671d6766 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/android-chrome-192x192.png differ
blob - /dev/null
blob + 653a1510ce933f7fe9fbab2fcd171f04fa0b24cc (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/android-chrome-384x384.png differ
blob - /dev/null
blob + 460aa1299f8e9f37773618bcab2619794416fb49 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/apple-touch-icon.png differ
blob - /dev/null
blob + b3930d0f047184047cb81d620436d91653438b8b (mode 755)
--- /dev/null
+++ gotwebd/files/htdocs/gotwebd/browserconfig.xml
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+ <msapplication>
+ <tile>
+ <square150x150logo src="/mstile-150x150.png"/>
+ <TileColor>#da532c</TileColor>
+ </tile>
+ </msapplication>
+</browserconfig>
blob - /dev/null
blob + f6c1a7c289faa4a48e03c97e68b1ba7a11dfddd1 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/favicon-16x16.png differ
blob - /dev/null
blob + 0ceea8c0eabe73e8d12cf106d73c34abb1999cb2 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/favicon-32x32.png differ
blob - /dev/null
blob + ee414573031ea5b310539196d2530a1e52d49b64 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/favicon.ico differ
blob - /dev/null
blob + 33933f80ee46217039804bc96672ede12b352b93 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/got.png differ
blob - /dev/null
blob + 97ace786464b193baf1cd51e54016aea3016e62f (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/got_large.png differ
blob - /dev/null
blob + 4b9d5d8b3a18a88189f1c92d789387f654bd25ac (mode 755)
--- /dev/null
+++ gotwebd/files/htdocs/gotwebd/gotweb.css
+/*
+ * Copyright (c) 2019 Jerome Kasper <neon.king.fr@gmail.com>
+ * Copyright (c) 2019, 2020 Tracey Emery <tracey@traceyemery.net>
+ *
+ * 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.
+ */
+
+/* general sections */
+
+a {
+ color: #444444;
+ text-decoration: none;
+}
+a:hover {
+ color: Gold;
+ text-decoration: none;
+}
+body {
+ background-color: #ffffff;
+ color: #000000;
+ margin: 0;
+ padding: 0;
+ font-family: Arial, sans-serif;
+}
+
+.diff_minus, .diff_submodule {
+ color: magenta;
+}
+.diff_plus, .diff_symlink, .diff_author {
+ color: darkcyan;
+}
+.diff_chunk_header, .diff_date {
+ background-color: LightSlateGray;
+ color: yellow;
+}
+.diff_meta, .diff_executable, .diff_commit {
+ color: green;
+}
+.diff_directory {
+ color: blue;
+}
+
+.back_white {
+ background-color: #ffffff;
+}
+.back_lightgray {
+ background-color: #d8f3ef;
+}
+
+#logo {
+ height: 50px;
+}
+#refs_str {
+ background-color: #243647;
+ color: #ffffff;
+ font-style: italic;
+}
+#dotted_line {
+ clear: left;
+ float: left;
+ width: 100%;
+ border-top: 1px dotted #444444;
+}
+#header {
+ overflow: auto;
+ width: 100%;
+ background-image: linear-gradient(to right, White, LightSlateGray);
+}
+#header a {
+ color: #ffffff;
+ font-size: 1.2em;
+ text-decoration: none;
+}
+#header a:hover {
+ color: Gold;
+ font-size: 1.2em;
+ text-decoration: none;
+}
+#site_path {
+ clear: left;
+ float: left;
+ overflow: auto;
+ width: 100%;
+ background-color: #243647;
+}
+#site_link {
+ float: left;
+ width: 40%;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ color: #ffffff;
+ overflow: hidden;
+}
+#site_link a {
+ color: #ffffff;
+ text-decoration: none;
+}
+#search {
+ float: right;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#got_link {
+ float: left;
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+#content {
+ width: 100%;
+}
+#np_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ border-bottom: 1px dotted #444444;
+ background-color: #f5fcfb;
+ overflow: hidden;
+}
+#nav_prev {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ overflow: visible;
+}
+#nav_next {
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ text-align: right;
+ overflow: hidden;
+}
+#navs_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: #ced7e0;
+}
+#navs {
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ font-size: .8em;
+}
+#site_owner_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#site_owner {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#description_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#description {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#repo_owner_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#repo_owner {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#last_change_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#last_change {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#cloneurl_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#cloneurl {
+ float: left;
+ width: 72%;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ overflow: auto;
+ white-space: pre-wrap;
+}
+
+#header_commit_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_commit {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_diff_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_diff {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_author_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_author {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_committer_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_committer {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_age_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_age {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_commit_msg_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_commit_msg {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ white-space: pre-wrap;
+}
+#header_tree_title {
+ clear: left;
+ float: left;
+ width: 6.5em;
+ padding-left: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+#header_tree {
+ float: left;
+ width: 72%;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+#err_content {
+ clear: left;
+ float: left;
+ width: 100%;
+ padding-left: 20px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+#briefs_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#briefs_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#briefs_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#briefs_age {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ float: left;
+ width: 7.5em;
+ overflow: auto;
+}
+#briefs_author {
+ float: left;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ width: 8.5em;
+ font-style: italic;
+ overflow: auto;
+}
+#briefs_log {
+ float: left;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ width: 65%;
+}
+
+#tags_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#tags_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#tags_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#tag_age {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ float: left;
+ width: 7.5em;
+ overflow: auto;
+}
+#tags_log {
+ float: left;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ width: 65%;
+}
+
+#tag_header_wrapper {
+ clear: left;
+ float: left;
+ background-color: #f5fcfb;
+ width: 100%;
+}
+#tag_header {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 2px;
+ width: 80%;
+}
+#tag {
+ float: left;
+ width: 8.5em;
+ font-style: italic;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#tag_commit {
+ clear: left;
+ float: left;
+ padding-left: 20px;
+ padding-bottom: 20px;
+ white-space: pre-wrap;
+}
+
+#index_header {
+ clear: left;
+ float: left;
+ overflow: auto;
+ width: 100%;
+ background-color: Khaki;
+}
+#index_header_project {
+ clear: left;
+ float: left;
+ width: 20%;
+ padding: 10px;
+}
+#index_header_description {
+ float: left;
+ width: 30%;
+ padding: 10px;
+}
+#index_header_owner {
+ float: left;
+ width: 12%;
+ padding: 10px;
+}
+#index_header_age {
+ padding: 10px;
+ overflow: hidden;
+}
+#index_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#index_project {
+ float: left;
+ width: 20%;
+ padding: 10px;
+ overflow: hidden;
+}
+#index_project_description {
+ float: left;
+ width: 30%;
+ padding: 10px;
+ overflow: auto;
+}
+#index_project_owner {
+ float: left;
+ width: 12%;
+ padding: 10px;
+ overflow: hidden;
+}
+#index_project_age {
+ float: left;
+ width: 14%;
+ padding: 10px;
+ overflow: visible;
+}
+#index_project a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_project a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_project_navs a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_project_navs a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_next a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_next a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+#index_prev a {
+ color: #444444;
+ text-decoration: none;
+}
+#index_prev a:hover {
+ color: SteelBlue;
+ text-decoration: none;
+}
+
+#commits_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#commits_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#commits_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#commits_header_wrapper {
+ float: left;
+ background-color: #f5fcfb;
+ width: 100%;
+}
+#commits_header {
+ float: left;
+ padding-top: 5px;
+ padding-bottom: 2px;
+ width: 80%;
+}
+#commit {
+ clear: left;
+ float: left;
+ padding-left: 20px;
+ padding-bottom: 20px;
+ white-space: pre-wrap;
+}
+#commits_line {
+ clear: left;
+ float: left;
+}
+
+#blame_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#blame_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#blame_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#blame_header_wrapper {
+ float: left;
+ background-color: #f5fcfb;
+ width: 100%;
+}
+#blame_header {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 2px;
+ width: 80%;
+}
+#blame {
+ clear: left;
+ float: left;
+ margin-left: 20px;
+ margin-bottom: 20px;
+ font-family: monospace;
+ white-space: pre;
+ overflow: auto;
+}
+#blame_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#blame_number {
+ float: left;
+ width: 6em;
+ overflow: hidden;
+}
+#blame_hash {
+ float: left;
+ width: 6em;
+ overflow: auto;
+}
+#blame_date {
+ float: left;
+ width: 7em;
+ overflow: auto;
+}
+#blame_author {
+ float: left;
+ width: 6em;
+ overflow: hidden;
+}
+#blame_code {
+ float:left;
+ width: 50%;
+ overflow: visible;
+}
+
+#tree_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#tree_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#tree_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#tree_header_wrapper {
+ clear: left;
+ float: left;
+ background-color: #f5fcfb;
+ width: 100%;
+}
+#tree_header {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 2px;
+ width: 80%;
+}
+#tree {
+ clear: left;
+ float: left;
+ margin-left: 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ font-family: monospace;
+}
+#tree_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#tree_line {
+ clear: left;
+ float: left;
+ width: 20em;
+ padding: 1px;
+}
+#tree_line_blank {
+ float: left;
+ padding: 1px;
+ width: 9.5em;
+}
+#tree_line_navs {
+ float: left;
+ text-align: right;
+ padding: 1px;
+}
+
+#diff_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#diff_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#diff_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#diff_header_wrapper {
+ float: left;
+ background-color: #f5fcfb;
+ width: 100%;
+}
+#diff_header {
+ float: left;
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 2px;
+ width: 80%;
+}
+#diff {
+ clear: left;
+ float: left;
+ margin-left: 20px;
+ margin-bottom: 20px;
+ font-family: monospace;
+ white-space: pre;
+}
+#diff_line {
+ clear: left;
+ float: left;
+}
+
+#summary_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: Khaki;
+}
+
+#branches_title_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+ background-color: LightSlateGray;
+ color: #ffffff;
+}
+#branches_title {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+#branches_content {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+
+#branches_wrapper {
+ clear: left;
+ float: left;
+ width: 100%;
+}
+#branches_age {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ float: left;
+ width: 7.5em;
+ overflow: auto;
+}
+#branches_space {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ float: left;
+ width: 8.5em;
+ overflow: auto;
+}
+#branch {
+ float: left;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
blob - /dev/null
blob + 0c47027971e9e0a5060e23fe73e7cb0399eacea8 (mode 755)
Binary files /dev/null and gotwebd/files/htdocs/gotwebd/mstile-150x150.png differ
blob - /dev/null
blob + 96e67c7c4b7cb9b1b395281fae8d7cffa834a991 (mode 755)
--- /dev/null
+++ gotwebd/files/htdocs/gotwebd/safari-pinned-tab.svg
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="400.000000pt" height="400.000000pt" viewBox="0 0 400.000000 400.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M0 1995 l0 -1215 2000 0 2000 0 0 1215 0 1215 -2000 0 -2000 0 0
+-1215z"/>
+</g>
+</svg>
blob - /dev/null
blob + a1553eb86b573da072c732c9aabac5a80968461f (mode 755)
--- /dev/null
+++ gotwebd/files/htdocs/gotwebd/site.webmanifest
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-384x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
blob - /dev/null
blob + 6437bf550c8e246193623b0bed1c1356161f0b2e (mode 644)
--- /dev/null
+++ gotwebd/got_operations.c
+/*
+ * Copyright (c) 2020-2022 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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 <sys/socket.h>
+#include <sys/stat.h>
+
+#include <event.h>
+#include <imsg.h>
+#include <sha1.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_reference.h"
+#include "got_repository.h"
+#include "got_path.h"
+#include "got_cancel.h"
+#include "got_diff.h"
+#include "got_commit_graph.h"
+#include "got_blame.h"
+#include "got_privsep.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+static const struct got_error *got_init_repo_commit(struct repo_commit **);
+static const struct got_error *got_init_repo_tag(struct repo_tag **);
+static const struct got_error *got_get_repo_commit(struct request *,
+ struct repo_commit *, struct got_commit_object *, struct got_reflist_head *,
+ struct got_object_id *);
+static const struct got_error *got_gotweb_dupfd(int *, int *);
+static const struct got_error *got_gotweb_openfile(FILE **, int *, int *);
+static const struct got_error *got_gotweb_flushfile(FILE *, int);
+static const struct got_error *got_gotweb_blame_cb(void *, int, int,
+ struct got_commit_object *,struct got_object_id *);
+
+static int
+isbinary(const uint8_t *buf, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ if (buf[i] == 0)
+ return 1;
+ return 0;
+}
+
+
+static const struct got_error *
+got_gotweb_flushfile(FILE *f, int fd)
+{
+ if (fseek(f, 0, SEEK_SET) == -1)
+ return got_error_from_errno("fseek");
+
+ if (ftruncate(fd, 0) == -1)
+ return got_error_from_errno("ftruncate");
+
+ if (fsync(fd) == -1)
+ return got_error_from_errno("fsync");
+
+ if (f && fclose(f) == EOF)
+ return got_error_from_errno("fclose");
+
+ if (fd != -1 && close(fd) != -1)
+ return got_error_from_errno("close");
+
+ return NULL;
+}
+
+static const struct got_error *
+got_gotweb_openfile(FILE **f, int *priv_fd, int *fd)
+{
+ const struct got_error *error = NULL;
+
+ *fd = dup(*priv_fd);
+
+ if (*fd < 0)
+ return NULL;
+
+ *f = fdopen(*fd, "w+");
+ if (*f == NULL) {
+ close(*fd);
+ error = got_error(GOT_ERR_PRIVSEP_NO_FD);
+ }
+
+ return error;
+}
+
+static const struct got_error *
+got_gotweb_dupfd(int *priv_fd, int *fd)
+{
+ const struct got_error *error = NULL;
+
+ *fd = dup(*priv_fd);
+
+ if (*fd < 0)
+ return NULL;
+
+ return error;
+}
+
+const struct got_error *
+got_get_repo_owner(char **owner, struct request *c, char *dir)
+{
+ const struct got_error *error = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ const char *gitconfig_owner;
+
+ *owner = NULL;
+
+ if (srv->show_repo_owner == 0)
+ return NULL;
+
+ gitconfig_owner = got_repo_get_gitconfig_owner(repo);
+ if (gitconfig_owner) {
+ *owner = strdup(gitconfig_owner);
+ if (*owner == NULL)
+ return got_error_from_errno("strdup");
+ }
+ return error;
+}
+
+const struct got_error *
+got_get_repo_age(char **repo_age, struct request *c, char *dir,
+ const char *refname, int ref_tm)
+{
+ const struct got_error *error = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct got_commit_object *commit = NULL;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ time_t committer_time = 0, cmp_time = 0;
+
+ *repo_age = NULL;
+ TAILQ_INIT(&refs);
+
+ if (srv->show_repo_age == 0)
+ return NULL;
+
+ error = got_ref_list(&refs, repo, "refs/heads",
+ got_ref_cmp_by_name, NULL);
+ if (error)
+ goto done;
+
+ /*
+ * Find the youngest branch tip in the repository, or the age of
+ * the a specific branch tip if a name was provided by the caller.
+ */
+ TAILQ_FOREACH(re, &refs, entry) {
+ struct got_object_id *id = NULL;
+
+ if (refname && strcmp(got_ref_get_name(re->ref), refname) != 0)
+ continue;
+
+ error = got_ref_resolve(&id, repo, re->ref);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, id);
+ free(id);
+ if (error)
+ goto done;
+
+ committer_time =
+ got_object_commit_get_committer_time(commit);
+ got_object_commit_close(commit);
+ if (cmp_time < committer_time)
+ cmp_time = committer_time;
+
+ if (refname)
+ break;
+ }
+
+ if (cmp_time != 0) {
+ committer_time = cmp_time;
+ error = gotweb_get_time_str(repo_age, committer_time, ref_tm);
+ }
+done:
+ got_ref_list_free(&refs);
+ return error;
+}
+
+static const struct got_error *
+got_get_repo_commit(struct request *c, struct repo_commit *repo_commit,
+ struct got_commit_object *commit, struct got_reflist_head *refs,
+ struct got_object_id *id)
+{
+ const struct got_error *error = NULL;
+ struct got_reflist_entry *re;
+ struct got_object_id *id2 = NULL;
+ struct got_object_qid *parent_id;
+ struct transport *t = c->t;
+ struct querystring *qs = c->t->qs;
+ char *commit_msg = NULL, *commit_msg0;
+
+ TAILQ_FOREACH(re, refs, entry) {
+ char *s;
+ const char *name;
+ struct got_tag_object *tag = NULL;
+ struct got_object_id *ref_id;
+ int cmp;
+
+ if (got_ref_is_symbolic(re->ref))
+ continue;
+
+ name = got_ref_get_name(re->ref);
+ if (strncmp(name, "refs/", 5) == 0)
+ name += 5;
+ if (strncmp(name, "got/", 4) == 0)
+ continue;
+ if (strncmp(name, "heads/", 6) == 0)
+ name += 6;
+ if (strncmp(name, "remotes/", 8) == 0) {
+ name += 8;
+ s = strstr(name, "/" GOT_REF_HEAD);
+ if (s != NULL && s[strlen(s)] == '\0')
+ continue;
+ }
+ error = got_ref_resolve(&ref_id, t->repo, re->ref);
+ if (error)
+ return error;
+ if (strncmp(name, "tags/", 5) == 0) {
+ error = got_object_open_as_tag(&tag, t->repo, ref_id);
+ if (error) {
+ if (error->code != GOT_ERR_OBJ_TYPE) {
+ free(ref_id);
+ continue;
+ }
+ /*
+ * Ref points at something other
+ * than a tag.
+ */
+ error = NULL;
+ tag = NULL;
+ }
+ }
+ cmp = got_object_id_cmp(tag ?
+ got_object_tag_get_object_id(tag) : ref_id, id);
+ free(ref_id);
+ if (tag)
+ got_object_tag_close(tag);
+ if (cmp != 0)
+ continue;
+ s = repo_commit->refs_str;
+ if (asprintf(&repo_commit->refs_str, "%s%s%s", s ? s : "",
+ s ? ", " : "", name) == -1) {
+ error = got_error_from_errno("asprintf");
+ free(s);
+ repo_commit->refs_str = NULL;
+ return error;
+ }
+ free(s);
+ }
+
+ error = got_object_id_str(&repo_commit->commit_id, id);
+ if (error)
+ return error;
+
+ error = got_object_id_str(&repo_commit->tree_id,
+ got_object_commit_get_tree_id(commit));
+ if (error)
+ return error;
+
+ if (qs->action == DIFF) {
+ parent_id = STAILQ_FIRST(
+ got_object_commit_get_parent_ids(commit));
+ if (parent_id != NULL) {
+ id2 = got_object_id_dup(&parent_id->id);
+ error = got_object_id_str(&repo_commit->parent_id, id2);
+ if (error)
+ return error;
+ free(id2);
+ } else {
+ repo_commit->parent_id = strdup("/dev/null");
+ if (repo_commit->parent_id == NULL) {
+ error = got_error_from_errno("strdup");
+ return error;
+ }
+ }
+ }
+
+ repo_commit->committer_time =
+ got_object_commit_get_committer_time(commit);
+
+ repo_commit->author =
+ strdup(got_object_commit_get_author(commit));
+ if (repo_commit->author == NULL) {
+ error = got_error_from_errno("strdup");
+ return error;
+ }
+ repo_commit->committer =
+ strdup(got_object_commit_get_committer(commit));
+ if (repo_commit->committer == NULL) {
+ error = got_error_from_errno("strdup");
+ return error;
+ }
+ error = got_object_commit_get_logmsg(&commit_msg0, commit);
+ if (error)
+ return error;
+
+ commit_msg = commit_msg0;
+ while (*commit_msg == '\n')
+ commit_msg++;
+
+ repo_commit->commit_msg = strdup(commit_msg);
+ if (repo_commit->commit_msg == NULL)
+ error = got_error_from_errno("strdup");
+ free(commit_msg0);
+ return error;
+}
+
+const struct got_error *
+got_get_repo_commits(struct request *c, int limit)
+{
+ const struct got_error *error = NULL;
+ struct got_object_id *id = NULL;
+ struct got_commit_graph *graph = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_reflist_head refs;
+ struct got_reference *ref;
+ struct repo_commit *repo_commit = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *in_repo_path = NULL, *repo_path = NULL, *file_path = NULL;
+ int chk_next = 0, chk_multi = 0, commit_found = 0;
+ int obj_type, limit_chk = 0;
+
+ TAILQ_INIT(&refs);
+
+ if (qs->file != NULL && strlen(qs->file) > 0)
+ if (asprintf(&file_path, "%s/%s", qs->folder ? qs->folder : "",
+ qs->file) == -1)
+ return got_error_from_errno("asprintf");
+
+ if (asprintf(&repo_path, "%s/%s", srv->repos_path,
+ repo_dir->name) == -1)
+ return got_error_from_errno("asprintf");
+
+ error = got_init_repo_commit(&repo_commit);
+ if (error)
+ return error;
+
+ /*
+ * XXX: jumping directly to a commit id via
+ * got_repo_match_object_id_prefix significantly improves performance,
+ * but does not allow us to create a PREVIOUS button, since commits can
+ * only be itereated forward. So, we have to match as we iterate from
+ * the headref.
+ */
+ if (qs->action == BRIEFS || qs->action == COMMITS ||
+ (qs->action == TREE && qs->commit == NULL)) {
+ error = got_ref_open(&ref, repo, qs->headref, 0);
+ if (error)
+ goto done;
+
+ error = got_ref_resolve(&id, repo, ref);
+ got_ref_close(ref);
+ if (error)
+ goto done;
+ } else if (qs->commit != NULL) {
+ error = got_ref_open(&ref, repo, qs->commit, 0);
+ if (error == NULL) {
+ error = got_ref_resolve(&id, repo, ref);
+ if (error)
+ goto done;
+ error = got_object_get_type(&obj_type, repo, id);
+ got_ref_close(ref);
+ if (error)
+ goto done;
+ if (obj_type == GOT_OBJ_TYPE_TAG) {
+ struct got_tag_object *tag;
+ error = got_object_open_as_tag(&tag, repo, id);
+ if (error)
+ goto done;
+ if (got_object_tag_get_object_type(tag) !=
+ GOT_OBJ_TYPE_COMMIT) {
+ got_object_tag_close(tag);
+ error = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+ free(id);
+ id = got_object_id_dup(
+ got_object_tag_get_object_id(tag));
+ if (id == NULL)
+ error = got_error_from_errno(
+ "got_object_id_dup");
+ got_object_tag_close(tag);
+ if (error)
+ goto done;
+ } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
+ error = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+ }
+ error = got_repo_match_object_id_prefix(&id, qs->commit,
+ GOT_OBJ_TYPE_COMMIT, repo);
+ if (error)
+ goto done;
+ }
+
+ error = got_repo_map_path(&in_repo_path, repo, repo_path);
+ if (error)
+ goto done;
+
+ error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
+ if (error)
+ goto done;
+
+ if (qs->file != NULL && strlen(qs->file) > 0) {
+ error = got_commit_graph_open(&graph, file_path, 0);
+ if (error)
+ goto done;
+ } else {
+ error = got_commit_graph_open(&graph, in_repo_path, 0);
+ if (error)
+ goto done;
+ }
+
+ error = got_commit_graph_iter_start(graph, id, repo, NULL, NULL);
+ if (error)
+ goto done;
+
+ for (;;) {
+ if (limit_chk == ((limit * qs->page) - (limit - 1)) &&
+ commit_found == 0 && repo_commit->commit_id != NULL) {
+ t->prev_id = strdup(repo_commit->commit_id);
+ if (t->prev_id == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ }
+
+ error = got_commit_graph_iter_next(&id, graph, repo, NULL,
+ NULL);
+ if (error) {
+ if (error->code == GOT_ERR_ITER_COMPLETED)
+ error = NULL;
+ goto done;
+ }
+ if (id == NULL)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, id);
+ if (error)
+ goto done;
+
+ error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name,
+ NULL);
+ if (error)
+ goto done;
+
+ error = got_get_repo_commit(c, repo_commit, commit,
+ &refs, id);
+ if (error)
+ goto done;
+
+ if (qs->commit != NULL && commit_found == 0 && limit != 1) {
+ if (strcmp(qs->commit, repo_commit->commit_id) == 0)
+ commit_found = 1;
+ else if (qs->file != NULL && strlen(qs->file) > 0 &&
+ qs->page == 0)
+ commit_found = 1;
+ else {
+ limit_chk++;
+ free(id);
+ id = NULL;
+ continue;
+ }
+ }
+
+ struct repo_commit *new_repo_commit = NULL;
+ error = got_init_repo_commit(&new_repo_commit);
+ if (error)
+ goto done;
+
+ TAILQ_INSERT_TAIL(&t->repo_commits, new_repo_commit, entry);
+
+ error = got_get_repo_commit(c, new_repo_commit, commit,
+ &refs, id);
+ if (error)
+ goto done;
+
+ free(id);
+ id = NULL;
+
+ if (limit == 1 && chk_multi == 0 &&
+ srv->max_commits_display != 1)
+ commit_found = 1;
+ else {
+ chk_multi = 1;
+
+ /*
+ * check for one more commit before breaking,
+ * so we know whether to navigate through briefs
+ * commits and summary
+ */
+ if (chk_next && (qs->action == BRIEFS ||
+ qs->action == COMMITS || qs->action == SUMMARY)) {
+ t->next_id = strdup(new_repo_commit->commit_id);
+ if (t->next_id == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ if (commit) {
+ got_object_commit_close(commit);
+ commit = NULL;
+ }
+ if (t->next_id == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ TAILQ_REMOVE(&t->repo_commits, new_repo_commit,
+ entry);
+ gotweb_free_repo_commit(new_repo_commit);
+ goto done;
+ }
+ }
+ got_ref_list_free(&refs);
+ if (error || (limit && --limit == 0)) {
+ if (commit_found || (qs->file != NULL &&
+ strlen(qs->file) > 0))
+ if (chk_multi == 0)
+ break;
+ chk_next = 1;
+ }
+ if (commit) {
+ got_object_commit_close(commit);
+ commit = NULL;
+ }
+ }
+done:
+ gotweb_free_repo_commit(repo_commit);
+ if (commit)
+ got_object_commit_close(commit);
+ if (graph)
+ got_commit_graph_close(graph);
+ got_ref_list_free(&refs);
+ free(file_path);
+ free(repo_path);
+ free(id);
+ return error;
+}
+
+const struct got_error *
+got_get_repo_tags(struct request *c, int limit)
+{
+ const struct got_error *error = NULL;
+ struct got_object_id *id = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_reflist_head refs;
+ struct got_reference *ref;
+ struct got_reflist_entry *re;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ struct got_tag_object *tag = NULL;
+ struct repo_tag *rt = NULL, *trt = NULL;
+ char *in_repo_path = NULL, *repo_path = NULL, *id_str = NULL;
+ char *commit_msg = NULL, *commit_msg0 = NULL;
+ int chk_next = 0, chk_multi = 1, commit_found = 0, c_cnt = 0;
+
+ TAILQ_INIT(&refs);
+
+ if (asprintf(&repo_path, "%s/%s", srv->repos_path,
+ repo_dir->name) == -1)
+ return got_error_from_errno("asprintf");
+
+ if (error)
+ return error;
+
+ if (qs->commit == NULL && qs->action == TAGS) {
+ error = got_ref_open(&ref, repo, qs->headref, 0);
+ if (error)
+ goto err;
+ error = got_ref_resolve(&id, repo, ref);
+ got_ref_close(ref);
+ if (error)
+ goto err;
+ } else if (qs->commit == NULL && qs->action == TAG) {
+ error = got_error_msg(GOT_ERR_EOF, "commit id missing");
+ goto err;
+ } else {
+ error = got_repo_match_object_id_prefix(&id, qs->commit,
+ GOT_OBJ_TYPE_COMMIT, repo);
+ if (error)
+ goto err;
+ }
+
+ if (qs->action != SUMMARY && qs->action != TAGS) {
+ error = got_object_open_as_commit(&commit, repo, id);
+ if (error)
+ goto err;
+ error = got_object_commit_get_logmsg(&commit_msg0, commit);
+ if (error)
+ goto err;
+ if (commit) {
+ got_object_commit_close(commit);
+ commit = NULL;
+ }
+ }
+
+ error = got_repo_map_path(&in_repo_path, repo, repo_path);
+ if (error)
+ goto err;
+
+ error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags,
+ repo);
+ if (error)
+ goto err;
+
+ if (limit == 1)
+ chk_multi = 0;
+
+ /*
+ * XXX: again, see previous message about caching
+ */
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ struct repo_tag *new_repo_tag = NULL;
+ error = got_init_repo_tag(&new_repo_tag);
+ if (error)
+ goto err;
+
+ TAILQ_INSERT_TAIL(&t->repo_tags, new_repo_tag, entry);
+
+ new_repo_tag->tag_name = strdup(got_ref_get_name(re->ref));
+ if (new_repo_tag->tag_name == NULL) {
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+
+ error = got_ref_resolve(&id, repo, re->ref);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_tag(&tag, repo, id);
+ if (error) {
+ if (error->code != GOT_ERR_OBJ_TYPE) {
+ free(id);
+ id = NULL;
+ goto done;
+ }
+ /* "lightweight" tag */
+ error = got_object_open_as_commit(&commit, repo, id);
+ if (error) {
+ free(id);
+ id = NULL;
+ goto done;
+ }
+ new_repo_tag->tagger =
+ strdup(got_object_commit_get_committer(commit));
+ if (new_repo_tag->tagger == NULL) {
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+ new_repo_tag->tagger_time =
+ got_object_commit_get_committer_time(commit);
+ error = got_object_id_str(&id_str, id);
+ if (error)
+ goto err;
+ free(id);
+ id = NULL;
+ } else {
+ free(id);
+ id = NULL;
+ new_repo_tag->tagger =
+ strdup(got_object_tag_get_tagger(tag));
+ if (new_repo_tag->tagger == NULL) {
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+ new_repo_tag->tagger_time =
+ got_object_tag_get_tagger_time(tag);
+ error = got_object_id_str(&id_str,
+ got_object_tag_get_object_id(tag));
+ if (error)
+ goto err;
+ }
+
+ new_repo_tag->commit_id = strdup(id_str);
+ if (new_repo_tag->commit_id == NULL)
+ goto err;
+
+ if (commit_found == 0 && qs->commit != NULL &&
+ strncmp(id_str, qs->commit, strlen(id_str)) != 0)
+ continue;
+ else
+ commit_found = 1;
+
+ t->tag_count++;
+
+ /*
+ * check for one more commit before breaking,
+ * so we know whether to navigate through briefs
+ * commits and summary
+ */
+ if (chk_next) {
+ t->next_id = strdup(new_repo_tag->commit_id);
+ if (t->next_id == NULL) {
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+ if (commit) {
+ got_object_commit_close(commit);
+ commit = NULL;
+ }
+ if (t->next_id == NULL) {
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+ TAILQ_REMOVE(&t->repo_tags, new_repo_tag, entry);
+ gotweb_free_repo_tag(new_repo_tag);
+ goto done;
+ }
+
+ if (commit) {
+ error = got_object_commit_get_logmsg(&new_repo_tag->
+ tag_commit, commit);
+ if (error)
+ goto done;
+ got_object_commit_close(commit);
+ commit = NULL;
+ } else {
+ new_repo_tag->tag_commit =
+ strdup(got_object_tag_get_message(tag));
+ if (new_repo_tag->tag_commit == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ }
+
+ while (*new_repo_tag->tag_commit == '\n')
+ new_repo_tag->tag_commit++;
+
+ if (qs->action != SUMMARY && qs->action != TAGS) {
+ commit_msg = commit_msg0;
+ while (*commit_msg == '\n')
+ commit_msg++;
+
+ new_repo_tag->commit_msg = strdup(commit_msg);
+ if (new_repo_tag->commit_msg == NULL) {
+ error = got_error_from_errno("strdup");
+ free(commit_msg0);
+ goto err;
+ }
+ free(commit_msg0);
+ }
+
+ if (limit && --limit == 0) {
+ if (chk_multi == 0)
+ break;
+ chk_next = 1;
+ }
+ free(id);
+ id = NULL;
+ }
+
+done:
+ /*
+ * we have tailq populated, so find previous commit id
+ * for navigation through briefs and commits
+ */
+ if (t->tag_count == 0) {
+ TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
+ TAILQ_REMOVE(&t->repo_tags, rt, entry);
+ gotweb_free_repo_tag(rt);
+ }
+ }
+ if (t->tag_count > 0 && t->prev_id == NULL && qs->commit != NULL) {
+ commit_found = 0;
+ TAILQ_FOREACH_REVERSE(rt, &t->repo_tags, repo_tags_head,
+ entry) {
+ if (commit_found == 0 && rt->commit_id != NULL &&
+ strcmp(qs->commit, rt->commit_id) != 0) {
+ continue;
+ } else
+ commit_found = 1;
+ if (c_cnt == srv->max_commits_display ||
+ rt == TAILQ_FIRST(&t->repo_tags)) {
+ t->prev_id = strdup(rt->commit_id);
+ if (t->prev_id == NULL)
+ error = got_error_from_errno("strdup");
+ break;
+ }
+ c_cnt++;
+ }
+ }
+err:
+ if (commit)
+ got_object_commit_close(commit);
+ got_ref_list_free(&refs);
+ free(repo_path);
+ free(id);
+ return error;
+}
+
+const struct got_error *
+got_output_repo_tree(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct got_commit_object *commit = NULL;
+ struct got_repository *repo = t->repo;
+ struct querystring *qs = t->qs;
+ struct repo_commit *rc = NULL;
+ struct got_object_id *tree_id = NULL, *commit_id = NULL;
+ struct got_reflist_head refs;
+ struct got_tree_object *tree = NULL;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *id_str = NULL;
+ char *path = NULL, *in_repo_path = NULL, *build_folder = NULL;
+ char *modestr = NULL, *name = NULL, *class = NULL;
+ int nentries, i, class_flip = 0;
+
+ TAILQ_INIT(&refs);
+
+ rc = TAILQ_FIRST(&t->repo_commits);
+
+ if (qs->folder != NULL) {
+ path = strdup(qs->folder);
+ if (path == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else {
+ error = got_repo_map_path(&in_repo_path, repo, repo_dir->path);
+ if (error)
+ goto done;
+ free(path);
+ path = in_repo_path;
+ }
+
+ error = got_repo_match_object_id(&commit_id, NULL, rc->commit_id,
+ GOT_OBJ_TYPE_COMMIT, &refs, repo);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, commit_id);
+ if (error)
+ goto done;
+
+ error = got_object_id_by_path(&tree_id, repo, commit, path);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_tree(&tree, repo, tree_id);
+ if (error)
+ goto done;
+
+ nentries = got_object_tree_get_nentries(tree);
+
+ for (i = 0; i < nentries; i++) {
+ struct got_tree_entry *te;
+ mode_t mode;
+
+ te = got_object_tree_get_entry(tree, i);
+
+ error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
+ if (error)
+ goto done;
+
+ modestr = strdup("");
+ if (modestr == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ mode = got_tree_entry_get_mode(te);
+ if (got_object_tree_entry_is_submodule(te)) {
+ free(modestr);
+ modestr = strdup("$");
+ if (modestr == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (S_ISLNK(mode)) {
+ free(modestr);
+ modestr = strdup("@");
+ if (modestr == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (S_ISDIR(mode)) {
+ free(modestr);
+ modestr = strdup("/");
+ if (modestr == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (mode & S_IXUSR) {
+ free(modestr);
+ modestr = strdup("*");
+ if (modestr == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ }
+
+ if (class_flip == 0) {
+ class = strdup("back_lightgray");
+ if (class == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ class_flip = 1;
+ } else {
+ class = strdup("back_white");
+ if (class == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ class_flip = 0;
+ }
+
+ name = strdup(got_tree_entry_get_name(te));
+ if (name == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ if (S_ISDIR(mode)) {
+ if (asprintf(&build_folder, "%s/%s",
+ qs->folder ? qs->folder : "",
+ got_tree_entry_get_name(te)) == -1) {
+ error = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (fcgi_gen_response(c,
+ "<div id='tree_wrapper'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_line' "
+ "class='") == -1)
+ goto done;
+ if (fcgi_gen_response(c, class) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a class='diff_directory' "
+ "href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tree") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&folder=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, build_folder) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, modestr) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_line_blank' "
+ "class='") == -1)
+ goto done;
+ if (fcgi_gen_response(c, class) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, " ") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ } else {
+ free(name);
+ name = strdup(got_tree_entry_get_name(te));
+ if (name == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+
+ if (fcgi_gen_response(c,
+ "<div id='tree_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tree_line' "
+ "class='") == -1)
+ goto done;
+ if (fcgi_gen_response(c, class) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<a href='?index_page=") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&action=blob") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&folder=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->folder) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&file=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, name) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, modestr) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_line_blank' "
+ "class='") == -1)
+ goto done;
+ if (fcgi_gen_response(c, class) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<a href='?index_page=") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&action=commits") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&folder=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->folder) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&file=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, name) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "commits") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | \n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<a href='?index_page=") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&action=blame") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&folder=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->folder) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "&file=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, name) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "blame") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ }
+ free(id_str);
+ id_str = NULL;
+ free(build_folder);
+ build_folder = NULL;
+ free(name);
+ name = NULL;
+ free(modestr);
+ modestr = NULL;
+ free(class);
+ class = NULL;
+ }
+done:
+ free(id_str);
+ free(build_folder);
+ free(modestr);
+ free(path);
+ free(name);
+ free(class);
+ got_ref_list_free(&refs);
+ if (commit)
+ got_object_commit_close(commit);
+ free(commit_id);
+ free(tree_id);
+ return error;
+}
+
+const struct got_error *
+got_output_file_blob(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct querystring *qs = c->t->qs;
+ struct got_commit_object *commit = NULL;
+ struct got_object_id *commit_id = NULL;
+ struct got_reflist_head refs;
+ struct got_blob_object *blob = NULL;
+ char *path = NULL, *in_repo_path = NULL;
+ int obj_type, set_mime = 0, type = 0, fd = -1;
+ char *buf_output = NULL;
+ size_t len, hdrlen;
+ const uint8_t *buf;
+
+ TAILQ_INIT(&refs);
+
+ if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
+ qs->folder ? "/" : "", qs->file) == -1) {
+ error = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ error = got_repo_map_path(&in_repo_path, repo, path);
+ if (error)
+ goto done;
+
+ error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
+ GOT_OBJ_TYPE_COMMIT, &refs, repo);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, commit_id);
+ if (error)
+ goto done;
+
+ error = got_object_id_by_path(&commit_id, repo, commit, in_repo_path);
+ if (error)
+ goto done;
+
+ if (commit_id == NULL) {
+ error = got_error(GOT_ERR_NO_OBJ);
+ goto done;
+ }
+
+ error = got_object_get_type(&obj_type, repo, commit_id);
+ if (error)
+ goto done;
+
+ if (obj_type != GOT_OBJ_TYPE_BLOB) {
+ error = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], &fd);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_blob(&blob, repo, commit_id, BUF, fd);
+ if (error)
+ goto done;
+ hdrlen = got_object_blob_get_hdrlen(blob);
+ do {
+ error = got_object_blob_read_block(&len, blob);
+ if (error)
+ goto done;
+ buf = got_object_blob_get_read_buf(blob);
+
+ /*
+ * Skip blob object header first time around,
+ * which also contains a zero byte.
+ */
+ buf += hdrlen;
+ if (set_mime == 0) {
+ if (isbinary(buf, len - hdrlen)) {
+ error = gotweb_render_content_type_file(c,
+ "application/octet-stream",
+ qs->file);
+ if (error) {
+ log_warnx("%s: %s", __func__,
+ error->msg);
+ goto done;
+ }
+ type = 0;
+ } else {
+ error = gotweb_render_content_type(c,
+ "text/text");
+ if (error) {
+ log_warnx("%s: %s", __func__,
+ error->msg);
+ goto done;
+ }
+ type = 1;
+ }
+ }
+ set_mime = 1;
+ if (type) {
+ buf_output = calloc(len - hdrlen + 1,
+ sizeof(*buf_output));
+ if (buf_output == NULL) {
+ error = got_error_from_errno("calloc");
+ goto done;
+ }
+ memcpy(buf_output, buf, len - hdrlen);
+ fcgi_gen_response(c, buf_output);
+ free(buf_output);
+ buf_output = NULL;
+ } else
+ fcgi_gen_binary_response(c, buf, len - hdrlen);
+
+ hdrlen = 0;
+ } while (len != 0);
+done:
+ if (commit)
+ got_object_commit_close(commit);
+ if (fd != -1 && close(fd) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (blob)
+ got_object_blob_close(blob);
+ free(buf_output);
+ free(in_repo_path);
+ free(commit_id);
+ free(path);
+ return error;
+}
+
+struct blame_line {
+ int annotated;
+ char *id_str;
+ char *committer;
+ char datebuf[11]; /* YYYY-MM-DD + NUL */
+};
+
+struct blame_cb_args {
+ struct blame_line *lines;
+ int nlines;
+ int nlines_prec;
+ int lineno_cur;
+ off_t *line_offsets;
+ FILE *f;
+ struct got_repository *repo;
+ struct request *c;
+};
+
+static const struct got_error *
+got_gotweb_blame_cb(void *arg, int nlines, int lineno,
+ struct got_commit_object *commit, struct got_object_id *id)
+{
+ const struct got_error *err = NULL;
+ struct blame_cb_args *a = arg;
+ struct blame_line *bline;
+ struct request *c = a->c;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *line = NULL, *eline = NULL;
+ size_t linesize = 0;
+ off_t offset;
+ struct tm tm;
+ time_t committer_time;
+
+ if (nlines != a->nlines ||
+ (lineno != -1 && lineno < 1) || lineno > a->nlines)
+ return got_error(GOT_ERR_RANGE);
+
+ if (lineno == -1)
+ return NULL; /* no change in this commit */
+
+ /* Annotate this line. */
+ bline = &a->lines[lineno - 1];
+ if (bline->annotated)
+ return NULL;
+ err = got_object_id_str(&bline->id_str, id);
+ if (err)
+ return err;
+
+ bline->committer = strdup(got_object_commit_get_committer(commit));
+ if (bline->committer == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+
+ committer_time = got_object_commit_get_committer_time(commit);
+ if (gmtime_r(&committer_time, &tm) == NULL)
+ return got_error_from_errno("gmtime_r");
+ if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
+ &tm) == 0) {
+ err = got_error(GOT_ERR_NO_SPACE);
+ goto done;
+ }
+ bline->annotated = 1;
+
+ /* Print lines annotated so far. */
+ bline = &a->lines[a->lineno_cur - 1];
+ if (!bline->annotated)
+ goto done;
+
+ offset = a->line_offsets[a->lineno_cur - 1];
+ if (fseeko(a->f, offset, SEEK_SET) == -1) {
+ err = got_error_from_errno("fseeko");
+ goto done;
+ }
+
+ while (bline->annotated) {
+ int out_buff_size = 100;
+ char *smallerthan, *at, *nl, *committer;
+ char out_buff[out_buff_size];
+ size_t len;
+
+ if (getline(&line, &linesize, a->f) == -1) {
+ if (ferror(a->f))
+ err = got_error_from_errno("getline");
+ break;
+ }
+
+ committer = bline->committer;
+ smallerthan = strchr(committer, '<');
+ if (smallerthan && smallerthan[1] != '\0')
+ committer = smallerthan + 1;
+ at = strchr(committer, '@');
+ if (at)
+ *at = '\0';
+ len = strlen(committer);
+ if (len >= 9)
+ committer[8] = '\0';
+
+ nl = strchr(line, '\n');
+ if (nl)
+ *nl = '\0';
+
+ if (fcgi_gen_response(c, "<div id='blame_wrapper'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='blame_number'>") == -1)
+ goto done;
+ if (snprintf(out_buff, strlen(out_buff), "%.*d", a->nlines_prec,
+ a->lineno_cur) < 0)
+ goto done;
+ if (fcgi_gen_response(c, out_buff) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_hash'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, bline->id_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (snprintf(out_buff, 10, "%.8s", bline->id_str) < 0)
+ goto done;
+ if (fcgi_gen_response(c, out_buff) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a></div>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_date'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, bline->datebuf) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_author'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, committer) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_code'>") == -1)
+ goto done;
+ err = gotweb_escape_html(&eline, line);
+ if (err)
+ goto done;
+ if (fcgi_gen_response(c, eline) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+ a->lineno_cur++;
+ bline = &a->lines[a->lineno_cur - 1];
+ }
+done:
+ free(line);
+ free(eline);
+ return err;
+}
+
+const struct got_error *
+got_output_file_blame(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct querystring *qs = c->t->qs;
+ struct got_object_id *obj_id = NULL, *commit_id = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_reflist_head refs;
+ struct got_blob_object *blob = NULL;
+ char *path = NULL, *in_repo_path = NULL;
+ struct blame_cb_args bca;
+ int i, obj_type, fd1 = -1, fd2 = -1, fd3 = -1, fd4 = -1, fd5 = -1;
+ int fd6 = -1;
+ off_t filesize;
+ FILE *f1 = NULL, *f2 = NULL;
+
+ TAILQ_INIT(&refs);
+ bca.f = NULL;
+ bca.lines = NULL;
+
+ if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
+ qs->folder ? "/" : "", qs->file) == -1) {
+ error = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ error = got_repo_map_path(&in_repo_path, repo, path);
+ if (error)
+ goto done;
+
+ error = got_repo_match_object_id(&commit_id, NULL, qs->commit,
+ GOT_OBJ_TYPE_COMMIT, &refs, repo);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, commit_id);
+ if (error)
+ goto done;
+
+ error = got_object_id_by_path(&obj_id, repo, commit, in_repo_path);
+ if (error)
+ goto done;
+
+ if (commit_id == NULL) {
+ error = got_error(GOT_ERR_NO_OBJ);
+ goto done;
+ }
+
+ error = got_object_get_type(&obj_type, repo, obj_id);
+ if (error)
+ goto done;
+
+ if (obj_type != GOT_OBJ_TYPE_BLOB) {
+ error = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ error = got_gotweb_openfile(&bca.f, &c->priv_fd[BLAME_FD_1], &fd1);
+ if (error)
+ goto done;
+
+ error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_2], &fd2);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_blob(&blob, repo, obj_id, BUF, fd2);
+ if (error)
+ goto done;
+
+ error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
+ &bca.line_offsets, bca.f, blob);
+ if (error || bca.nlines == 0)
+ goto done;
+
+ /* Don't include \n at EOF in the blame line count. */
+ if (bca.line_offsets[bca.nlines - 1] == filesize)
+ bca.nlines--;
+
+ bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
+ if (bca.lines == NULL) {
+ error = got_error_from_errno("calloc");
+ goto done;
+ }
+ bca.lineno_cur = 1;
+ bca.nlines_prec = 0;
+ i = bca.nlines;
+ while (i > 0) {
+ i /= 10;
+ bca.nlines_prec++;
+ }
+ bca.repo = repo;
+ bca.c = c;
+
+ error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_3], &fd3);
+ if (error)
+ goto done;
+
+ error = got_gotweb_dupfd(&c->priv_fd[BLAME_FD_4], &fd4);
+ if (error)
+ goto done;
+
+ error = got_gotweb_openfile(&f1, &c->priv_fd[BLAME_FD_5], &fd5);
+ if (error)
+ goto done;
+
+ error = got_gotweb_openfile(&f2, &c->priv_fd[BLAME_FD_6], &fd6);
+ if (error)
+ goto done;
+
+ error = got_blame(in_repo_path, commit_id, repo,
+ GOT_DIFF_ALGORITHM_MYERS, got_gotweb_blame_cb, &bca, NULL, NULL,
+ fd3, fd4, f1, f2);
+
+ if (blob) {
+ free(bca.line_offsets);
+ for (i = 0; i < bca.nlines; i++) {
+ struct blame_line *bline = &bca.lines[i];
+ free(bline->id_str);
+ free(bline->committer);
+ }
+ }
+done:
+ free(bca.lines);
+ if (fd2 != -1 && close(fd2) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (fd3 != -1 && close(fd3) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (fd4 != -1 && close(fd4) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (bca.f) {
+ const struct got_error *bca_err =
+ got_gotweb_flushfile(bca.f, fd1);
+ if (error == NULL)
+ error = bca_err;
+ }
+ if (f1) {
+ const struct got_error *f1_err =
+ got_gotweb_flushfile(f1, fd5);
+ if (error == NULL)
+ error = f1_err;
+ }
+ if (f2) {
+ const struct got_error *f2_err =
+ got_gotweb_flushfile(f2, fd6);
+ if (error == NULL)
+ error = f2_err;
+ }
+ if (commit)
+ got_object_commit_close(commit);
+ if (blob)
+ got_object_blob_close(blob);
+ free(in_repo_path);
+ free(commit_id);
+ free(path);
+ return error;
+}
+
+const struct got_error *
+got_output_repo_diff(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct got_repository *repo = t->repo;
+ struct repo_commit *rc = NULL;
+ struct got_object_id *id1 = NULL, *id2 = NULL;
+ struct got_reflist_head refs;
+ FILE *f1 = NULL, *f2 = NULL, *f3 = NULL;
+ char *label1 = NULL, *label2 = NULL, *line = NULL;
+ char *newline, *eline = NULL, *color = NULL;
+ int obj_type, fd1, fd2, fd3, fd4 = -1, fd5 = -1;
+ size_t linesize = 0;
+ ssize_t linelen;
+ int wrlen = 0;
+
+ TAILQ_INIT(&refs);
+
+ error = got_gotweb_openfile(&f1, &c->priv_fd[DIFF_FD_1], &fd1);
+ if (error)
+ return error;
+
+ error = got_gotweb_openfile(&f2, &c->priv_fd[DIFF_FD_2], &fd2);
+ if (error)
+ return error;
+
+ error = got_gotweb_openfile(&f3, &c->priv_fd[DIFF_FD_3], &fd3);
+ if (error)
+ return error;
+
+ rc = TAILQ_FIRST(&t->repo_commits);
+
+ if (rc->parent_id != NULL &&
+ strncmp(rc->parent_id, "/dev/null", 9) != 0) {
+ error = got_repo_match_object_id(&id1, &label1,
+ rc->parent_id, GOT_OBJ_TYPE_ANY,
+ &refs, repo);
+ if (error)
+ goto done;
+ }
+
+ error = got_repo_match_object_id(&id2, &label2, rc->commit_id,
+ GOT_OBJ_TYPE_ANY, &refs, repo);
+ if (error)
+ goto done;
+
+ error = got_object_get_type(&obj_type, repo, id2);
+ if (error)
+ goto done;
+
+ error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_4], &fd4);
+ if (error)
+ goto done;
+
+ error = got_gotweb_dupfd(&c->priv_fd[DIFF_FD_5], &fd5);
+ if (error)
+ goto done;
+
+ switch (obj_type) {
+ case GOT_OBJ_TYPE_BLOB:
+ error = got_diff_objects_as_blobs(NULL, NULL, f1, f2, fd4, fd5,
+ id1, id2, NULL, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
+ repo, f3);
+ break;
+ case GOT_OBJ_TYPE_TREE:
+ error = got_diff_objects_as_trees(NULL, NULL, f1, f2, fd4, fd5,
+ id1, id2, NULL, "", "", GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
+ repo, f3);
+ break;
+ case GOT_OBJ_TYPE_COMMIT:
+ error = got_diff_objects_as_commits(NULL, NULL, f1, f2, fd4,
+ fd5, id1, id2, NULL, GOT_DIFF_ALGORITHM_MYERS, 3, 0, 0,
+ repo, f3);
+ break;
+ default:
+ error = got_error(GOT_ERR_OBJ_TYPE);
+ }
+ if (error)
+ goto done;
+
+ if (fseek(f1, 0, SEEK_SET) == -1) {
+ error = got_ferror(f1, GOT_ERR_IO);
+ goto done;
+ }
+
+ if (fseek(f2, 0, SEEK_SET) == -1) {
+ error = got_ferror(f2, GOT_ERR_IO);
+ goto done;
+ }
+
+ if (fseek(f3, 0, SEEK_SET) == -1) {
+ error = got_ferror(f3, GOT_ERR_IO);
+ goto done;
+ }
+
+ while ((linelen = getline(&line, &linesize, f3)) != -1) {
+ if (strncmp(line, "-", 1) == 0) {
+ color = strdup("diff_minus");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "+", 1) == 0) {
+ color = strdup("diff_plus");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "@@", 2) == 0) {
+ color = strdup("diff_chunk_header");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "@@", 2) == 0) {
+ color = strdup("diff_chunk_header");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "commit +", 8) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "commit -", 8) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "blob +", 6) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "blob -", 6) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "file +", 6) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "file -", 6) == 0) {
+ color = strdup("diff_meta");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "from:", 5) == 0) {
+ color = strdup("diff_author");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "via:", 4) == 0) {
+ color = strdup("diff_author");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ } else if (strncmp(line, "date:", 5) == 0) {
+ color = strdup("diff_date");
+ if (color == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ }
+ if (fcgi_gen_response(c, "<div id='diff_line' class='") == -1)
+ goto done;
+ if (fcgi_gen_response(c, color ? color : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ newline = strchr(line, '\n');
+ if (newline)
+ *newline = '\0';
+
+ error = gotweb_escape_html(&eline, line);
+ if (error)
+ goto done;
+ if (fcgi_gen_response(c, eline) == -1)
+ goto done;
+ free(eline);
+ eline = NULL;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (linelen > 0)
+ wrlen = wrlen + linelen;
+ free(color);
+ color = NULL;
+ }
+ if (linelen == -1 && ferror(f3))
+ error = got_error_from_errno("getline");
+done:
+ free(color);
+ if (fd4 != -1 && close(fd4) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (fd5 != -1 && close(fd5) == -1 && error == NULL)
+ error = got_error_from_errno("close");
+ if (f1) {
+ const struct got_error *f1_err =
+ got_gotweb_flushfile(f1, fd1);
+ if (error == NULL)
+ error = f1_err;
+ }
+ if (f2) {
+ const struct got_error *f2_err =
+ got_gotweb_flushfile(f2, fd2);
+ if (error == NULL)
+ error = f2_err;
+ }
+ if (f3) {
+ const struct got_error *f3_err =
+ got_gotweb_flushfile(f3, fd3);
+ if (error == NULL)
+ error = f3_err;
+ }
+ got_ref_list_free(&refs);
+ free(line);
+ free(eline);
+ free(label1);
+ free(label2);
+ free(id1);
+ free(id2);
+ return error;
+}
+
+static const struct got_error *
+got_init_repo_commit(struct repo_commit **rc)
+{
+ const struct got_error *error = NULL;
+
+ *rc = calloc(1, sizeof(**rc));
+ if (*rc == NULL)
+ return got_error_from_errno2("%s: calloc", __func__);
+
+ (*rc)->path = NULL;
+ (*rc)->refs_str = NULL;
+ (*rc)->commit_id = NULL;
+ (*rc)->committer = NULL;
+ (*rc)->author = NULL;
+ (*rc)->parent_id = NULL;
+ (*rc)->tree_id = NULL;
+ (*rc)->commit_msg = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+got_init_repo_tag(struct repo_tag **rt)
+{
+ const struct got_error *error = NULL;
+
+ *rt = calloc(1, sizeof(**rt));
+ if (*rt == NULL)
+ return got_error_from_errno2("%s: calloc", __func__);
+
+ (*rt)->commit_id = NULL;
+ (*rt)->tag_name = NULL;
+ (*rt)->tag_commit = NULL;
+ (*rt)->commit_msg = NULL;
+ (*rt)->tagger = NULL;
+
+ return error;
+}
blob - /dev/null
blob + 5de75e9906e8db76b4f47ea4e47f97bbff344227 (mode 644)
--- /dev/null
+++ gotwebd/gotweb.c
+/*
+ * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
+ *
+ * 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 <net/if.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <sha1.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_reference.h"
+#include "got_repository.h"
+#include "got_path.h"
+#include "got_cancel.h"
+#include "got_worktree.h"
+#include "got_diff.h"
+#include "got_commit_graph.h"
+#include "got_blame.h"
+#include "got_privsep.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+enum gotweb_ref_tm {
+ TM_DIFF,
+ TM_LONG,
+};
+
+static const struct querystring_keys querystring_keys[] = {
+ { "action", ACTION },
+ { "commit", COMMIT },
+ { "file", RFILE },
+ { "folder", FOLDER },
+ { "headref", HEADREF },
+ { "index_page", INDEX_PAGE },
+ { "path", PATH },
+ { "page", PAGE },
+};
+
+static const struct action_keys action_keys[] = {
+ { "blame", BLAME },
+ { "blob", BLOB },
+ { "briefs", BRIEFS },
+ { "commits", COMMITS },
+ { "diff", DIFF },
+ { "error", ERR },
+ { "index", INDEX },
+ { "summary", SUMMARY },
+ { "tag", TAG },
+ { "tags", TAGS },
+ { "tree", TREE },
+};
+
+static const struct got_error *gotweb_init_querystring(struct querystring **);
+static const struct got_error *gotweb_parse_querystring(struct querystring **,
+ char *);
+static const struct got_error *gotweb_assign_querystring(struct querystring **,
+ char *, char *);
+static const struct got_error *gotweb_render_header(struct request *);
+static const struct got_error *gotweb_render_footer(struct request *);
+static const struct got_error *gotweb_render_index(struct request *);
+static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
+ const char *);
+static const struct got_error *gotweb_load_got_path(struct request *c,
+ struct repo_dir *);
+static const struct got_error *gotweb_get_repo_description(char **,
+ struct server *, char *);
+static const struct got_error *gotweb_get_clone_url(char **, struct server *,
+ char *);
+static const struct got_error *gotweb_render_navs(struct request *);
+static const struct got_error *gotweb_render_blame(struct request *);
+static const struct got_error *gotweb_render_briefs(struct request *);
+static const struct got_error *gotweb_render_commits(struct request *);
+static const struct got_error *gotweb_render_diff(struct request *);
+static const struct got_error *gotweb_render_summary(struct request *);
+static const struct got_error *gotweb_render_tag(struct request *);
+static const struct got_error *gotweb_render_tags(struct request *);
+static const struct got_error *gotweb_render_tree(struct request *);
+static const struct got_error *gotweb_render_branches(struct request *);
+
+static void gotweb_free_querystring(struct querystring *);
+static void gotweb_free_repo_dir(struct repo_dir *);
+
+struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
+
+void
+gotweb_process_request(struct request *c)
+{
+ const struct got_error *error = NULL, *error2 = NULL;
+ struct server *srv = NULL;
+ struct querystring *qs = NULL;
+ struct repo_dir *repo_dir = NULL;
+ uint8_t err[] = "gotwebd experienced an error: ";
+ int html = 0;
+
+ /* init the transport */
+ error = gotweb_init_transport(&c->t);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ /* don't process any further if client disconnected */
+ if (c->sock->client_status == CLIENT_DISCONNECT)
+ return;
+ /* get the gotwebd server */
+ srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
+ if (srv == NULL) {
+ log_warnx("%s: error server is NULL", __func__);
+ goto err;
+ }
+ c->srv = srv;
+ /* parse our querystring */
+ error = gotweb_init_querystring(&qs);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ c->t->qs = qs;
+ error = gotweb_parse_querystring(&qs, c->querystring);
+ if (error) {
+ gotweb_free_querystring(qs);
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+
+ /*
+ * certain actions require a commit id in the querystring. this stops
+ * bad actors from exploiting this by manually manipulating the
+ * querystring.
+ */
+
+ if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
+ qs->action == DIFF)) {
+ error2 = got_error(GOT_ERR_QUERYSTRING);
+ goto render;
+ }
+
+ if (qs->action != INDEX) {
+ error = gotweb_init_repo_dir(&repo_dir, qs->path);
+ if (error)
+ goto done;
+ error = gotweb_load_got_path(c, repo_dir);
+ c->t->repo_dir = repo_dir;
+ if (error && error->code != GOT_ERR_LONELY_PACKIDX)
+ goto err;
+ }
+
+ /* render top of page */
+ if (qs != NULL && qs->action == BLOB) {
+ error = got_get_repo_commits(c, 1);
+ if (error)
+ goto done;
+ error = gotweb_render_content_type(c, "text/plain");
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ error = got_output_file_blob(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ goto done;
+ } else {
+render:
+ error = gotweb_render_content_type(c, "text/html");
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ html = 1;
+ }
+
+ error = gotweb_render_header(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+
+ if (error2) {
+ error = error2;
+ goto err;
+ }
+
+ switch(qs->action) {
+ case BLAME:
+ error = gotweb_render_blame(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case BRIEFS:
+ error = gotweb_render_briefs(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case COMMITS:
+ error = gotweb_render_commits(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case DIFF:
+ error = gotweb_render_diff(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case INDEX:
+ error = gotweb_render_index(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case SUMMARY:
+ error = gotweb_render_summary(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case TAG:
+ error = gotweb_render_tag(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case TAGS:
+ error = gotweb_render_tags(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case TREE:
+ error = gotweb_render_tree(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto err;
+ }
+ break;
+ case ERR:
+ default:
+ if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
+ goto err;
+ if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
+ goto err;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto err;
+ break;
+ }
+
+ goto done;
+err:
+ if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
+ return;
+ if (fcgi_gen_response(c, err) == -1)
+ return;
+ if (error) {
+ if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
+ return;
+ } else {
+ if (fcgi_gen_response(c, "see daemon logs for details") == -1)
+ return;
+ }
+ if (html && fcgi_gen_response(c, "</div>\n") == -1)
+ return;
+done:
+ if (c->t->repo != NULL && qs->action != INDEX)
+ got_repo_close(c->t->repo);
+ if (html && srv != NULL)
+ gotweb_render_footer(c);
+}
+
+struct server *
+gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
+ uint8_t *subdomain)
+{
+ struct server *srv = NULL;
+
+ /* check against document_root first */
+ if (strlen(server_name) > 0)
+ TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
+ if (strcmp(srv->name, server_name) == 0)
+ goto done;
+
+ /* check against document_root second */
+ if (strlen(document_root) > 0)
+ TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
+ if (strcmp(srv->name, document_root) == 0)
+ goto done;
+
+ /* check against subdomain third */
+ if (strlen(subdomain) > 0)
+ TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
+ if (strcmp(srv->name, subdomain) == 0)
+ goto done;
+
+ /* if those fail, send first server */
+ TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
+ if (srv != NULL)
+ break;
+done:
+ return srv;
+};
+
+const struct got_error *
+gotweb_init_transport(struct transport **t)
+{
+ const struct got_error *error = NULL;
+
+ *t = calloc(1, sizeof(**t));
+ if (*t == NULL)
+ return got_error_from_errno2("%s: calloc", __func__);
+
+ TAILQ_INIT(&(*t)->repo_commits);
+ TAILQ_INIT(&(*t)->repo_tags);
+
+ (*t)->repo = NULL;
+ (*t)->repo_dir = NULL;
+ (*t)->qs = NULL;
+ (*t)->next_id = NULL;
+ (*t)->prev_id = NULL;
+ (*t)->next_disp = 0;
+ (*t)->prev_disp = 0;
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_init_querystring(struct querystring **qs)
+{
+ const struct got_error *error = NULL;
+
+ *qs = calloc(1, sizeof(**qs));
+ if (*qs == NULL)
+ return got_error_from_errno2("%s: calloc", __func__);
+
+ (*qs)->action = INDEX;
+ (*qs)->commit = NULL;
+ (*qs)->file = NULL;
+ (*qs)->folder = NULL;
+ (*qs)->headref = strdup("HEAD");
+ if ((*qs)->headref == NULL) {
+ return got_error_from_errno2("%s: strdup", __func__);
+ }
+ (*qs)->index_page = 0;
+ (*qs)->index_page_str = NULL;
+ (*qs)->path = NULL;
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_parse_querystring(struct querystring **qs, char *qst)
+{
+ const struct got_error *error = NULL;
+ char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
+ char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
+
+ if (qst == NULL)
+ return error;
+
+ tok1 = strdup(qst);
+ if (tok1 == NULL)
+ return got_error_from_errno2("%s: strdup", __func__);
+
+ tok1_pair = tok1;
+ tok1_end = tok1;
+
+ while (tok1_pair != NULL) {
+ strsep(&tok1_end, "&");
+
+ tok2 = strdup(tok1_pair);
+ if (tok2 == NULL) {
+ free(tok1);
+ return got_error_from_errno2("%s: strdup", __func__);
+ }
+
+ tok2_pair = tok2;
+ tok2_end = tok2;
+
+ while (tok2_pair != NULL) {
+ strsep(&tok2_end, "=");
+ if (tok2_end) {
+ error = gotweb_assign_querystring(qs, tok2_pair,
+ tok2_end);
+ if (error)
+ goto err;
+ }
+ tok2_pair = tok2_end;
+ }
+ free(tok2);
+ tok1_pair = tok1_end;
+ }
+ free(tok1);
+ return error;
+err:
+ free(tok2);
+ free(tok1);
+ return error;
+}
+
+static const struct got_error *
+gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
+{
+ const struct got_error *error = NULL;
+ const char *errstr;
+ int a_cnt, el_cnt;
+
+ for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
+ if (strcmp(key, querystring_keys[el_cnt].name) != 0)
+ continue;
+
+ switch (querystring_keys[el_cnt].element) {
+ case ACTION:
+ for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
+ if (strcmp(value, action_keys[a_cnt].name) != 0)
+ continue;
+ else if (strcmp(value,
+ action_keys[a_cnt].name) == 0){
+ (*qs)->action =
+ action_keys[a_cnt].action;
+ goto qa_found;
+ }
+ }
+ (*qs)->action = ERR;
+qa_found:
+ break;
+ case COMMIT:
+ (*qs)->commit = strdup(value);
+ if ((*qs)->commit == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ break;
+ case RFILE:
+ (*qs)->file = strdup(value);
+ if ((*qs)->file == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ break;
+ case FOLDER:
+ (*qs)->folder = strdup(value);
+ if ((*qs)->folder == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ break;
+ case HEADREF:
+ (*qs)->headref = strdup(value);
+ if ((*qs)->headref == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ break;
+ case INDEX_PAGE:
+ if (strlen(value) == 0)
+ break;
+ (*qs)->index_page_str = strdup(value);
+ if ((*qs)->index_page_str == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ (*qs)->index_page = strtonum(value, INT64_MIN,
+ INT64_MAX, &errstr);
+ if (errstr) {
+ error = got_error_from_errno3("%s: strtonum %s",
+ __func__, errstr);
+ goto done;
+ }
+ if ((*qs)->index_page < 0) {
+ (*qs)->index_page = 0;
+ sprintf((*qs)->index_page_str, "%d", 0);
+ }
+ break;
+ case PATH:
+ (*qs)->path = strdup(value);
+ if ((*qs)->path == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ break;
+ case PAGE:
+ if (strlen(value) == 0)
+ break;
+ (*qs)->page_str = strdup(value);
+ if ((*qs)->page_str == NULL) {
+ error = got_error_from_errno2("%s: strdup",
+ __func__);
+ goto done;
+ }
+ (*qs)->page = strtonum(value, INT64_MIN,
+ INT64_MAX, &errstr);
+ if (errstr) {
+ error = got_error_from_errno3("%s: strtonum %s",
+ __func__, errstr);
+ goto done;
+ }
+ if ((*qs)->page < 0) {
+ (*qs)->page = 0;
+ sprintf((*qs)->page_str, "%d", 0);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+done:
+ return error;
+}
+
+void
+gotweb_free_repo_tag(struct repo_tag *rt)
+{
+ if (rt != NULL) {
+ free(rt->commit_msg);
+ free(rt->commit_id);
+ free(rt->tagger);
+ }
+ free(rt);
+}
+
+void
+gotweb_free_repo_commit(struct repo_commit *rc)
+{
+ if (rc != NULL) {
+ free(rc->path);
+ free(rc->refs_str);
+ free(rc->commit_id);
+ free(rc->parent_id);
+ free(rc->tree_id);
+ free(rc->author);
+ free(rc->committer);
+ free(rc->commit_msg);
+ }
+ free(rc);
+}
+
+static void
+gotweb_free_querystring(struct querystring *qs)
+{
+ if (qs != NULL) {
+ free(qs->commit);
+ free(qs->file);
+ free(qs->folder);
+ free(qs->headref);
+ free(qs->index_page_str);
+ free(qs->path);
+ free(qs->page_str);
+ }
+ free(qs);
+}
+
+static void
+gotweb_free_repo_dir(struct repo_dir *repo_dir)
+{
+ if (repo_dir != NULL) {
+ free(repo_dir->name);
+ free(repo_dir->owner);
+ free(repo_dir->description);
+ free(repo_dir->url);
+ free(repo_dir->age);
+ free(repo_dir->path);
+ }
+ free(repo_dir);
+}
+
+void
+gotweb_free_transport(struct transport *t)
+{
+ struct repo_commit *rc = NULL, *trc = NULL;
+ struct repo_tag *rt = NULL, *trt = NULL;
+
+ TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
+ TAILQ_REMOVE(&t->repo_commits, rc, entry);
+ gotweb_free_repo_commit(rc);
+ }
+ TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
+ TAILQ_REMOVE(&t->repo_tags, rt, entry);
+ gotweb_free_repo_tag(rt);
+ }
+ gotweb_free_repo_dir(t->repo_dir);
+ gotweb_free_querystring(t->qs);
+ if (t != NULL) {
+ free(t->next_id);
+ free(t->prev_id);
+ }
+ free(t);
+}
+
+const struct got_error *
+gotweb_render_content_type(struct request *c, const uint8_t *type)
+{
+ const struct got_error *error = NULL;
+ char *h = NULL;
+
+ if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+
+ fcgi_gen_response(c, h);
+done:
+ free(h);
+
+ return error;
+}
+
+const struct got_error *
+gotweb_render_content_type_file(struct request *c, const uint8_t *type,
+ char *file)
+{
+ const struct got_error *error = NULL;
+ char *h = NULL;
+
+ if (asprintf(&h, "Content-type: %s\r\n"
+ "Content-disposition: attachment; filename=%s\r\n\r\n",
+ type, file) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+
+ fcgi_gen_response(c, h);
+done:
+ free(h);
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_header(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct server *srv = c->srv;
+ struct querystring *qs = c->t->qs;
+ char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
+ char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
+
+ if (strlen(c->document_root) > 0) {
+ if (asprintf(&droot, "/%s/", c->document_root) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ } else {
+ if (asprintf(&droot, "/") == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ }
+
+ if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ if (asprintf(&css,
+ "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
+ droot, srv->custom_css) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
+ srv->logo_url) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
+ droot, srv->logo) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
+ "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
+ srv->site_link) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+ if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
+ "&action=summary' alt='summlink'>%s</a>", c->document_root,
+ qs->index_page, qs->path, qs->path) == -1) {
+ error = got_error_from_errno2("%s: asprintf", __func__);
+ goto done;
+ }
+
+ if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, title) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<meta name='viewport' "
+ "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
+ "content='#da532c'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<meta name='theme-color' content='#ffffff'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
+ "href='/apple-touch-icon.png'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<link rel='icon' type='image/png' sizes='32x32' "
+ "href='/favicon-32x32.png'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
+ "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<link rel='manifest' "
+ "href='/site.webmanifest'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<link rel='mask-icon' "
+ "href='/safari-pinned-tab.svg'/>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, css) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='header'>\n<div id='got_link'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, gotlink) == -1)
+ goto done;
+ if (fcgi_gen_response(c, gotimg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='site_path'>\n<div id='site_link'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, sitelink) == -1)
+ goto done;
+ if (qs != NULL) {
+ if (qs->path != NULL) {
+ if (fcgi_gen_response(c, " / ") == -1)
+ goto done;
+ if (fcgi_gen_response(c, summlink) == -1)
+ goto done;
+ }
+ if (qs->action != INDEX) {
+ if (fcgi_gen_response(c, " / ") == -1)
+ goto done;
+ switch(qs->action) {
+ case(BLAME):
+ if (fcgi_gen_response(c, "blame") == -1)
+ goto done;
+ break;
+ case(BRIEFS):
+ if (fcgi_gen_response(c, "briefs") == -1)
+ goto done;
+ break;
+ case(COMMITS):
+ if (fcgi_gen_response(c, "commits") == -1)
+ goto done;
+ break;
+ case(DIFF):
+ if (fcgi_gen_response(c, "diff") == -1)
+ goto done;
+ break;
+ case(SUMMARY):
+ if (fcgi_gen_response(c, "summary") == -1)
+ goto done;
+ break;
+ case(TAG):
+ if (fcgi_gen_response(c, "tag") == -1)
+ goto done;
+ break;
+ case(TAGS):
+ if (fcgi_gen_response(c, "tags") == -1)
+ goto done;
+ break;
+ case(TREE):
+ if (fcgi_gen_response(c, "tree") == -1)
+ goto done;
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+ fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
+done:
+ free(title);
+ free(droot);
+ free(css);
+ free(gotlink);
+ free(gotimg);
+ free(sitelink);
+ free(summlink);
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_footer(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct server *srv = c->srv;
+ char *siteowner = NULL;
+
+ if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
+ goto done;
+ if (srv->show_site_owner) {
+ error = gotweb_escape_html(&siteowner, srv->site_owner);
+ if (error)
+ goto done;
+ if (fcgi_gen_response(c, siteowner) == -1)
+ goto done;
+ } else
+ if (fcgi_gen_response(c, " ") == -1)
+ goto done;
+ fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
+done:
+ free(siteowner);
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_navs(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct server *srv = c->srv;
+ char *nhref = NULL, *phref = NULL;
+ int disp = 0;
+
+ if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
+ goto done;
+
+ switch(qs->action) {
+ case INDEX:
+ if (qs->index_page > 0) {
+ if (asprintf(&phref, "index_page=%d",
+ qs->index_page - 1) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case BRIEFS:
+ if (t->prev_id && qs->commit != NULL &&
+ strcmp(qs->commit, t->prev_id) != 0) {
+ if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
+ "&action=briefs&commit=%s&headref=%s",
+ qs->index_page, qs->path, qs->page - 1, t->prev_id,
+ qs->headref) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case COMMITS:
+ if (t->prev_id && qs->commit != NULL &&
+ strcmp(qs->commit, t->prev_id) != 0) {
+ if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
+ "&action=commits&commit=%s&headref=%s&folder=%s"
+ "&file=%s",
+ qs->index_page, qs->path, qs->page - 1, t->prev_id,
+ qs->headref, qs->folder ? qs->folder : "",
+ qs->file ? qs->file : "") == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case TAGS:
+ if (t->prev_id && qs->commit != NULL &&
+ strcmp(qs->commit, t->prev_id) != 0) {
+ if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
+ "&action=tags&commit=%s&headref=%s",
+ qs->index_page, qs->path, qs->page - 1, t->prev_id,
+ qs->headref) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ default:
+ disp = 0;
+ break;
+ }
+
+ if (disp) {
+ if (fcgi_gen_response(c, "<a href='?") == -1)
+ goto done;
+ if (fcgi_gen_response(c, phref) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>Previous</a>") == -1)
+ goto done;
+ }
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
+ goto done;
+
+ disp = 0;
+
+ switch(qs->action) {
+ case INDEX:
+ if (t->next_disp == srv->max_repos_display &&
+ t->repos_total != (qs->index_page + 1) *
+ srv->max_repos_display) {
+ if (asprintf(&nhref, "index_page=%d",
+ qs->index_page + 1) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case BRIEFS:
+ if (t->next_id) {
+ if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
+ "&action=briefs&commit=%s&headref=%s",
+ qs->index_page, qs->path, qs->page + 1, t->next_id,
+ qs->headref) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case COMMITS:
+ if (t->next_id) {
+ if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
+ "&action=commits&commit=%s&headref=%s&folder=%s"
+ "&file=%s",
+ qs->index_page, qs->path, qs->page + 1, t->next_id,
+ qs->headref, qs->folder ? qs->folder : "",
+ qs->file ? qs->file : "") == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ case TAGS:
+ if (t->next_id) {
+ if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
+ "&action=tags&commit=%s&headref=%s",
+ qs->index_page, qs->path, qs->page + 1, t->next_id,
+ qs->headref) == -1) {
+ error = got_error_from_errno2("%s: asprintf",
+ __func__);
+ goto done;
+ }
+ disp = 1;
+ }
+ break;
+ default:
+ disp = 0;
+ break;
+ }
+ if (disp) {
+ if (fcgi_gen_response(c, "<a href='?") == -1)
+ goto done;
+ if (fcgi_gen_response(c, nhref) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>Next</a>") == -1)
+ goto done;
+ }
+ fcgi_gen_response(c, "</div>\n");
+done:
+ free(t->next_id);
+ t->next_id = NULL;
+ free(t->prev_id);
+ t->prev_id = NULL;
+ free(phref);
+ free(nhref);
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_index(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = NULL;
+ DIR *d;
+ struct dirent **sd_dent;
+ char *c_path = NULL;
+ struct stat st;
+ unsigned int d_cnt, d_i, d_disp = 0;
+
+ d = opendir(srv->repos_path);
+ if (d == NULL) {
+ error = got_error_from_errno2("opendir", srv->repos_path);
+ return error;
+ }
+
+ d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
+ if (d_cnt == -1) {
+ error = got_error_from_errno2("scandir", srv->repos_path);
+ goto done;
+ }
+
+ /* get total count of repos */
+ for (d_i = 0; d_i < d_cnt; d_i++) {
+ if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
+ strcmp(sd_dent[d_i]->d_name, "..") == 0)
+ continue;
+
+ if (asprintf(&c_path, "%s/%s", srv->repos_path,
+ sd_dent[d_i]->d_name) == -1) {
+ error = got_error_from_errno("asprintf");
+ return error;
+ }
+
+ if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
+ !got_path_dir_is_empty(c_path))
+ t->repos_total++;
+ free(c_path);
+ c_path = NULL;
+ }
+
+ if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='index_header_project'>Project</div>\n") == -1)
+ goto done;
+ if (srv->show_repo_description)
+ if (fcgi_gen_response(c, "<div id='index_header_description'>"
+ "Description</div>\n") == -1)
+ goto done;
+ if (srv->show_repo_owner)
+ if (fcgi_gen_response(c, "<div id='index_header_owner'>"
+ "Owner</div>\n") == -1)
+ goto done;
+ if (srv->show_repo_age)
+ if (fcgi_gen_response(c, "<div id='index_header_age'>"
+ "Last Change</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ for (d_i = 0; d_i < d_cnt; d_i++) {
+ if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
+ break; /* account for parent and self */
+
+ if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
+ strcmp(sd_dent[d_i]->d_name, "..") == 0)
+ continue;
+
+ if (qs->index_page > 0 && (qs->index_page *
+ srv->max_repos_display) > t->prev_disp) {
+ t->prev_disp++;
+ continue;
+ }
+
+ error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
+ if (error)
+ goto done;
+
+ error = gotweb_load_got_path(c, repo_dir);
+ if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
+ error = NULL;
+ continue;
+ }
+ else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
+ goto done;
+
+ if (lstat(repo_dir->path, &st) == 0 &&
+ S_ISDIR(st.st_mode) &&
+ !got_path_dir_is_empty(repo_dir->path))
+ goto render;
+ else {
+ gotweb_free_repo_dir(repo_dir);
+ repo_dir = NULL;
+ continue;
+ }
+render:
+ d_disp++;
+ t->prev_disp++;
+ if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=summary'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (srv->show_repo_description) {
+ if (fcgi_gen_response(c,
+ "<div id='index_project_description'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->description) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ }
+
+ if (srv->show_repo_owner) {
+ if (fcgi_gen_response(c,
+ "<div id='index_project_owner'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->owner) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ }
+
+ if (srv->show_repo_age) {
+ if (fcgi_gen_response(c,
+ "<div id='index_project_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->age) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ }
+
+ if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='navs'>") == -1)
+ goto done;;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=summary'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "summary") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a> | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=briefs'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commit briefs") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a> | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=commits'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commits") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a> | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tags'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "tags") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a> | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tree'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "tree") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ gotweb_free_repo_dir(repo_dir);
+ repo_dir = NULL;
+ error = got_repo_close(t->repo);
+ if (error)
+ goto done;
+ t->next_disp++;
+ if (d_disp == srv->max_repos_display)
+ break;
+ }
+ if (srv->max_repos_display == 0)
+ goto div;
+ if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
+ goto div;
+ if (t->repos_total <= srv->max_repos ||
+ t->repos_total <= srv->max_repos_display)
+ goto div;
+
+ error = gotweb_render_navs(c);
+ if (error)
+ goto done;
+div:
+ fcgi_gen_response(c, "</div>\n");
+done:
+ if (d != NULL && closedir(d) == EOF && error == NULL)
+ error = got_error_from_errno("closedir");
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_blame(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct repo_commit *rc = NULL;
+ char *age = NULL;
+
+ error = got_get_repo_commits(c, 1);
+ if (error)
+ return error;
+
+ rc = TAILQ_FIRST(&t->repo_commits);
+
+ error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_msg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
+ goto done;
+
+ error = got_output_file_blame(c);
+ if (error)
+ goto done;
+
+ fcgi_gen_response(c, "</div>\n");
+done:
+ fcgi_gen_response(c, "</div>\n");
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_briefs(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct repo_commit *rc = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *smallerthan, *newline;
+ char *age = NULL;
+
+ if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
+ goto done;
+
+ if (qs->action == SUMMARY) {
+ qs->action = BRIEFS;
+ error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
+ } else
+ error = got_get_repo_commits(c, srv->max_commits_display);
+ if (error)
+ goto done;
+
+ TAILQ_FOREACH(rc, &t->repo_commits, entry) {
+ error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
+ if (error)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
+ goto done;
+ smallerthan = strchr(rc->author, '<');
+ if (smallerthan)
+ *smallerthan = '\0';
+ if (fcgi_gen_response(c, rc->author) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
+ goto done;
+ newline = strchr(rc->commit_msg, '\n');
+ if (newline)
+ *newline = '\0';
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->headref) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_msg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+ if (rc->refs_str) {
+ if (fcgi_gen_response(c,
+ " <span id='refs_str'>(") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->refs_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, ")</span>") == -1)
+ goto done;
+ }
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='navs'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->headref) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "diff") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->headref) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "tree") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+
+ free(age);
+ age = NULL;
+ }
+
+ if (t->next_id || t->prev_id) {
+ error = gotweb_render_navs(c);
+ if (error)
+ goto done;
+ }
+ fcgi_gen_response(c, "</div>\n");
+done:
+ free(age);
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_commits(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct repo_commit *rc = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *age = NULL, *author = NULL;
+ /* int commit_found = 0; */
+
+ if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='commits_title'>Commits</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
+ goto done;
+
+ error = got_get_repo_commits(c, srv->max_commits_display);
+ if (error)
+ goto done;
+
+ TAILQ_FOREACH(rc, &t->repo_commits, entry) {
+ error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
+ if (error)
+ goto done;
+ error = gotweb_escape_html(&author, rc->author);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<div id='commits_header_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
+ goto done;
+
+
+ if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, author ? author : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, rc->commit_msg) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='navs'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "diff") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "tree") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ free(age);
+ age = NULL;
+ free(author);
+ author = NULL;
+ }
+
+ if (t->next_id || t->prev_id) {
+ error = gotweb_render_navs(c);
+ if (error)
+ goto done;
+ }
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ fcgi_gen_response(c, "</div>\n");
+done:
+ free(age);
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_branches(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct got_repository *repo = t->repo;
+ char *age = NULL;
+
+ TAILQ_INIT(&refs);
+
+ error = got_ref_list(&refs, repo, "refs/heads",
+ got_ref_cmp_by_name, NULL);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='branches_title'>Branches</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
+ goto done;
+
+ TAILQ_FOREACH(re, &refs, entry) {
+ char *refname = NULL;
+
+ if (got_ref_is_symbolic(re->ref))
+ continue;
+
+ refname = strdup(got_ref_get_name(re->ref));
+ if (refname == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ if (strncmp(refname, "refs/heads/", 11) != 0)
+ continue;
+
+ error = got_get_repo_age(&age, c, qs->path, refname,
+ TM_DIFF);
+ if (error)
+ goto done;
+
+ if (strncmp(refname, "refs/heads/", 11) == 0)
+ refname += 11;
+
+ if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, " ") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='branch'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, refname) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, refname) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='navs'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, refname) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "summary") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, refname) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commit briefs") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->path) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, refname) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commits") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+
+ free(age);
+ age = NULL;
+
+ }
+ fcgi_gen_response(c, "</div>\n");
+done:
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_tree(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct repo_commit *rc = NULL;
+ char *age = NULL;
+
+ error = got_get_repo_commits(c, 1);
+ if (error)
+ return error;
+
+ rc = TAILQ_FIRST(&t->repo_commits);
+
+ error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->tree_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_msg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
+ goto done;
+
+ error = got_output_repo_tree(c);
+ if (error)
+ goto done;
+
+ fcgi_gen_response(c, "</div>\n");
+ fcgi_gen_response(c, "</div>\n");
+done:
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_diff(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct repo_commit *rc = NULL;
+ char *age = NULL, *author = NULL;
+
+ error = got_get_repo_commits(c, 1);
+ if (error)
+ return error;
+
+ rc = TAILQ_FIRST(&t->repo_commits);
+
+ error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
+ if (error)
+ goto done;
+ error = gotweb_escape_html(&author, rc->author);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='diff_title'>Commit Diff</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->parent_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<br />") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->tree_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, author ? author : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rc->commit_msg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
+ goto done;
+
+ error = got_output_repo_diff(c);
+ if (error)
+ goto done;
+
+ fcgi_gen_response(c, "</div>\n");
+done:
+ fcgi_gen_response(c, "</div>\n");
+ free(age);
+ free(author);
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_summary(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct transport *t = c->t;
+ struct server *srv = c->srv;
+
+ if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
+ goto done;
+
+ if (!srv->show_repo_description)
+ goto owner;
+
+ if (fcgi_gen_response(c, "<div id='description_title'>"
+ "Description:</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='description'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, t->repo_dir->description) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+owner:
+ if (!srv->show_repo_owner)
+ goto last_change;
+
+ if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
+ "Owner:</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+last_change:
+ if (!srv->show_repo_age)
+ goto clone_url;
+
+ if (fcgi_gen_response(c, "<div id='last_change_title'>"
+ "Last Change:</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, t->repo_dir->age) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+clone_url:
+ if (!srv->show_repo_cloneurl)
+ goto content;
+
+ if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
+ "Clone URL:</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, t->repo_dir->url) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+content:
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ error = gotweb_render_briefs(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto done;
+ }
+
+ error = gotweb_render_tags(c);
+ if (error) {
+ log_warnx("%s: %s", __func__, error->msg);
+ goto done;
+ }
+
+ error = gotweb_render_branches(c);
+ if (error)
+ log_warnx("%s: %s", __func__, error->msg);
+done:
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_tag(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct repo_tag *rt = NULL;
+ struct transport *t = c->t;
+ char *age = NULL, *author = NULL;
+
+ error = got_get_repo_tags(c, 1);
+ if (error)
+ goto done;
+
+ if (t->tag_count == 0) {
+ error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
+ "bad commit id");
+ goto done;
+ }
+
+ rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
+
+ error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
+ if (error)
+ goto done;
+ error = gotweb_escape_html(&author, rt->tagger);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_id) == -1)
+ goto done;
+
+ if (strncmp(rt->tag_name, "refs/", 5) == 0)
+ rt->tag_name += 5;
+
+ if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->tag_name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, ")</span>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, author ? author : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
+ "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_msg) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, rt->tag_commit) == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ fcgi_gen_response(c, "</div>\n");
+done:
+ free(age);
+ free(author);
+ return error;
+}
+
+static const struct got_error *
+gotweb_render_tags(struct request *c)
+{
+ const struct got_error *error = NULL;
+ struct repo_tag *rt = NULL;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ struct querystring *qs = t->qs;
+ struct repo_dir *repo_dir = t->repo_dir;
+ char *newline;
+ char *age = NULL;
+ int commit_found = 0;
+
+ if (qs->action == BRIEFS) {
+ qs->action = TAGS;
+ error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
+ } else
+ error = got_get_repo_tags(c, srv->max_commits_display);
+ if (error)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='tags_title'>Tags</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
+ goto done;
+
+ if (t->tag_count == 0) {
+ if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "This repository contains no tags\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ }
+
+ TAILQ_FOREACH(rt, &t->repo_tags, entry) {
+ if (commit_found == 0 && qs->commit != NULL) {
+ if (strcmp(qs->commit, rt->commit_id) != 0)
+ continue;
+ else
+ commit_found = 1;
+ }
+ error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
+ if (error)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, age ? age : "") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tag'>") == -1)
+ goto done;
+ if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
+ rt->tag_name += 10;
+ if (fcgi_gen_response(c, rt->tag_name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
+ goto done;
+ if (rt->tag_commit != NULL) {
+ newline = strchr(rt->tag_commit, '\n');
+ if (newline)
+ *newline = '\0';
+ }
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (rt->tag_commit != NULL &&
+ fcgi_gen_response(c, rt->tag_commit) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "<div id='navs'>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "tag") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commit briefs") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, " | ") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, qs->index_page_str) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&path=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, repo_dir->name) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
+ goto done;
+ if (fcgi_gen_response(c, rt->commit_id) == -1)
+ goto done;
+ if (fcgi_gen_response(c, "'>") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "commits") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</a>") == -1)
+ goto done;
+
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c, "</div>\n") == -1)
+ goto done;
+ if (fcgi_gen_response(c,
+ "<div id='dotted_line'></div>\n") == -1)
+ goto done;
+
+ free(age);
+ age = NULL;
+ }
+ if (t->next_id || t->prev_id) {
+ error = gotweb_render_navs(c);
+ if (error)
+ goto done;
+ }
+ fcgi_gen_response(c, "</div>\n");
+done:
+ free(age);
+ return error;
+}
+
+const struct got_error *
+gotweb_escape_html(char **escaped_html, const char *orig_html)
+{
+ const struct got_error *error = NULL;
+ struct escape_pair {
+ char c;
+ const char *s;
+ } esc[] = {
+ { '>', ">" },
+ { '<', "<" },
+ { '&', "&" },
+ { '"', """ },
+ { '\'', "'" },
+ { '\n', "<br />" },
+ };
+ size_t orig_len, len;
+ int i, j, x;
+
+ orig_len = strlen(orig_html);
+ len = orig_len;
+ for (i = 0; i < orig_len; i++) {
+ for (j = 0; j < nitems(esc); j++) {
+ if (orig_html[i] != esc[j].c)
+ continue;
+ len += strlen(esc[j].s) - 1 /* escaped char */;
+ }
+ }
+
+ *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
+ if (*escaped_html == NULL)
+ return got_error_from_errno("calloc");
+
+ x = 0;
+ for (i = 0; i < orig_len; i++) {
+ int escaped = 0;
+ for (j = 0; j < nitems(esc); j++) {
+ if (orig_html[i] != esc[j].c)
+ continue;
+
+ if (strlcat(*escaped_html, esc[j].s, len + 1)
+ >= len + 1) {
+ error = got_error(GOT_ERR_NO_SPACE);
+ goto done;
+ }
+ x += strlen(esc[j].s);
+ escaped = 1;
+ break;
+ }
+ if (!escaped) {
+ (*escaped_html)[x] = orig_html[i];
+ x++;
+ }
+ }
+done:
+ if (error) {
+ free(*escaped_html);
+ *escaped_html = NULL;
+ } else {
+ (*escaped_html)[x] = '\0';
+ }
+
+ return error;
+}
+
+static const struct got_error *
+gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
+{
+ const struct got_error *error = NULL;
+ struct socket *sock = c->sock;
+ struct server *srv = c->srv;
+ struct transport *t = c->t;
+ DIR *dt;
+ char *dir_test;
+ int opened = 0;
+
+ if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
+ GOTWEB_GIT_DIR) == -1)
+ return got_error_from_errno("asprintf");
+
+ dt = opendir(dir_test);
+ if (dt == NULL) {
+ free(dir_test);
+ } else {
+ repo_dir->path = strdup(dir_test);
+ if (repo_dir->path == NULL) {
+ opened = 1;
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+ opened = 1;
+ goto done;
+ }
+
+ if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
+ GOTWEB_GOT_DIR) == -1) {
+ dir_test = NULL;
+ error = got_error_from_errno("asprintf");
+ goto err;
+ }
+
+ dt = opendir(dir_test);
+ if (dt == NULL)
+ free(dir_test);
+ else {
+ opened = 1;
+ error = got_error(GOT_ERR_NOT_GIT_REPO);
+ goto err;
+ }
+
+ if (asprintf(&dir_test, "%s/%s", srv->repos_path,
+ repo_dir->name) == -1) {
+ error = got_error_from_errno("asprintf");
+ dir_test = NULL;
+ goto err;
+ }
+
+ repo_dir->path = strdup(dir_test);
+ if (repo_dir->path == NULL) {
+ opened = 1;
+ error = got_error_from_errno("strdup");
+ goto err;
+ }
+
+ dt = opendir(dir_test);
+ if (dt == NULL) {
+ error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
+ goto err;
+ } else
+ opened = 1;
+done:
+ error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
+ if (error)
+ goto err;
+ error = gotweb_get_repo_description(&repo_dir->description, srv,
+ repo_dir->path);
+ if (error)
+ goto err;
+ error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
+ if (error)
+ goto err;
+ error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
+ NULL, TM_DIFF);
+ if (error)
+ goto err;
+ error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
+err:
+ free(dir_test);
+ if (opened)
+ if (dt != NULL && closedir(dt) == EOF && error == NULL)
+ error = got_error_from_errno("closedir");
+ return error;
+}
+
+static const struct got_error *
+gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
+{
+ const struct got_error *error;
+
+ *repo_dir = calloc(1, sizeof(**repo_dir));
+ if (*repo_dir == NULL)
+ return got_error_from_errno("calloc");
+
+ if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
+ error = got_error_from_errno("asprintf");
+ free(*repo_dir);
+ *repo_dir = NULL;
+ return error;
+ }
+ (*repo_dir)->owner = NULL;
+ (*repo_dir)->description = NULL;
+ (*repo_dir)->url = NULL;
+ (*repo_dir)->age = NULL;
+ (*repo_dir)->path = NULL;
+
+ return NULL;
+}
+
+static const struct got_error *
+gotweb_get_repo_description(char **description, struct server *srv, char *dir)
+{
+ const struct got_error *error = NULL;
+ FILE *f = NULL;
+ char *d_file = NULL;
+ unsigned int len;
+ size_t n;
+
+ *description = NULL;
+ if (srv->show_repo_description == 0)
+ return NULL;
+
+ if (asprintf(&d_file, "%s/description", dir) == -1)
+ return got_error_from_errno("asprintf");
+
+ f = fopen(d_file, "r");
+ if (f == NULL) {
+ if (errno == ENOENT || errno == EACCES)
+ return NULL;
+ error = got_error_from_errno2("fopen", d_file);
+ goto done;
+ }
+
+ if (fseek(f, 0, SEEK_END) == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+ len = ftell(f);
+ if (len == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+
+ if (len == 0)
+ goto done;
+
+ if (fseek(f, 0, SEEK_SET) == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+ *description = calloc(len + 1, sizeof(**description));
+ if (*description == NULL) {
+ error = got_error_from_errno("calloc");
+ goto done;
+ }
+
+ n = fread(*description, 1, len, f);
+ if (n == 0 && ferror(f))
+ error = got_ferror(f, GOT_ERR_IO);
+done:
+ if (f != NULL && fclose(f) == EOF && error == NULL)
+ error = got_error_from_errno("fclose");
+ free(d_file);
+ return error;
+}
+
+static const struct got_error *
+gotweb_get_clone_url(char **url, struct server *srv, char *dir)
+{
+ const struct got_error *error = NULL;
+ FILE *f;
+ char *d_file = NULL;
+ unsigned int len;
+ size_t n;
+
+ *url = NULL;
+
+ if (srv->show_repo_cloneurl == 0)
+ return NULL;
+
+ if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
+ return got_error_from_errno("asprintf");
+
+ f = fopen(d_file, "r");
+ if (f == NULL) {
+ if (errno != ENOENT && errno != EACCES)
+ error = got_error_from_errno2("fopen", d_file);
+ goto done;
+ }
+
+ if (fseek(f, 0, SEEK_END) == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+ len = ftell(f);
+ if (len == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+ if (len == 0)
+ goto done;
+
+ if (fseek(f, 0, SEEK_SET) == -1) {
+ error = got_ferror(f, GOT_ERR_IO);
+ goto done;
+ }
+
+ *url = calloc(len + 1, sizeof(**url));
+ if (*url == NULL) {
+ error = got_error_from_errno("calloc");
+ goto done;
+ }
+
+ n = fread(*url, 1, len, f);
+ if (n == 0 && ferror(f))
+ error = got_ferror(f, GOT_ERR_IO);
+done:
+ if (f != NULL && fclose(f) == EOF && error == NULL)
+ error = got_error_from_errno("fclose");
+ free(d_file);
+ return error;
+}
+
+const struct got_error *
+gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
+{
+ struct tm tm;
+ time_t diff_time;
+ const char *years = "years ago", *months = "months ago";
+ const char *weeks = "weeks ago", *days = "days ago";
+ const char *hours = "hours ago", *minutes = "minutes ago";
+ const char *seconds = "seconds ago", *now = "right now";
+ char *s;
+ char datebuf[29];
+
+ *repo_age = NULL;
+
+ switch (ref_tm) {
+ case TM_DIFF:
+ diff_time = time(NULL) - committer_time;
+ if (diff_time > 60 * 60 * 24 * 365 * 2) {
+ if (asprintf(repo_age, "%lld %s",
+ (diff_time / 60 / 60 / 24 / 365), years) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
+ if (asprintf(repo_age, "%lld %s",
+ (diff_time / 60 / 60 / 24 / (365 / 12)),
+ months) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
+ if (asprintf(repo_age, "%lld %s",
+ (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 60 * 60 * 24 * 2) {
+ if (asprintf(repo_age, "%lld %s",
+ (diff_time / 60 / 60 / 24), days) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 60 * 60 * 2) {
+ if (asprintf(repo_age, "%lld %s",
+ (diff_time / 60 / 60), hours) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 60 * 2) {
+ if (asprintf(repo_age, "%lld %s", (diff_time / 60),
+ minutes) == -1)
+ return got_error_from_errno("asprintf");
+ } else if (diff_time > 2) {
+ if (asprintf(repo_age, "%lld %s", diff_time,
+ seconds) == -1)
+ return got_error_from_errno("asprintf");
+ } else {
+ if (asprintf(repo_age, "%s", now) == -1)
+ return got_error_from_errno("asprintf");
+ }
+ break;
+ case TM_LONG:
+ if (gmtime_r(&committer_time, &tm) == NULL)
+ return got_error_from_errno("gmtime_r");
+
+ s = asctime_r(&tm, datebuf);
+ if (s == NULL)
+ return got_error_from_errno("asctime_r");
+
+ if (asprintf(repo_age, "%s UTC", datebuf) == -1)
+ return got_error_from_errno("asprintf");
+ break;
+ }
+ return NULL;
+}
\ No newline at end of file
blob - /dev/null
blob + d73977323f12761149928c250d279753f884a9b3 (mode 644)
--- /dev/null
+++ gotwebd/gotwebd.8
+.\"
+.\" Copyright (c) 2020 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 GOTWEB 8
+.Os
+.Sh NAME
+.Nm gotweb
+.Nd Game of Trees Git repository server for web browsers -- which obviously
+needs to be updated to gotwebd
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+provides a web interface allowing Git repository contents to be viewed
+with a web browser.
+.Pp
+.Nm
+is a CGI program based on
+.Xr got 1
+and
+.Xr kcgi 3
+which is intended to run in a
+.Xr chroot 2
+environment in
+.Pa /var/www .
+The program has been designed to work out of the box with
+the
+.Xr httpd 8
+web server in conjunction with
+.Xr slowcgi 8 .
+.Pp
+Enabling
+.Nm
+requires the following steps:
+.Bl -enum
+.It
+The
+.Xr httpd.conf 5
+configuration file must be adjusted to run
+.Nm
+as a CGI program with
+.Xr slowcgi 8 .
+The
+.Sx EXAMPLES
+section below contains an appropriate configuration file sample.
+.It
+httpd(8) and slowcgi(8) must be enabled and started:
+.Bd -literal -offset indent
+ # rcctl enable httpd slowcgi
+ # rcctl start httpd slowcgi
+.Ed
+.It
+Optionally, the run-time behaviour of
+.Nm
+can be configured via the
+.Xr gotweb.conf 5
+configuration file.
+.It
+Git repositories must be created at a suitable location inside the
+web server's
+.Xr chroot 2
+environment.
+These repositories should
+.Em not
+be writable by the user ID of the
+.Xr httpd 8
+server.
+The default location for repositories published by
+.Nm
+is
+.Pa /var/www/got/public .
+.It
+Git repositories served by
+.Nm
+should be kept up-to-date with a mechanism such as
+.Cm got fetch ,
+.Xr git-fetch 1 ,
+or
+.Xr rsync 1 ,
+scheduled by
+.Xr cron 8 .
+.El
+.Sh FILES
+.Bl -tag -width /var/www/got/public/ -compact
+.It Pa /var/www/got/public/
+Default location for Git repositories served by
+.Nm .
+This location can be adjusted in the
+.Xr gotweb.conf 5
+configuration file.
+.It Pa /var/www/cgi-bin/gotweb/gotweb
+The
+.Nm
+CGI program, statically linked for use in a
+.Xr chroot 2
+environment.
+.It Pa /var/www/cgi-bin/gotweb/gw_tmpl/
+Directory for template files used by
+.Nm .
+.It Pa /var/www/cgi-bin/gotweb/libexec/
+Directory containing statically linked
+.Xr got 1
+helper programs which are run by
+.Nm
+to read Git repositories.
+.It Pa /var/www/htdocs/gotweb/
+Directory containing HTML, CSS, and image files used by
+.Nm .
+.It Pa /var/www/got/tmp/
+Directory for temporary files created by
+.Nm .
+.El
+.Sh EXAMPLES
+Example configuration for httpd.conf:
+.Bd -literal -offset indent
+
+ types { include "/usr/share/misc/mime.types" }
+ server "gotweb.example.com" {
+ listen on * port 80
+ root "/htdocs/gotweb"
+ location "/cgi-bin/*" {
+ root "/"
+ fastcgi
+ }
+ location "/*" {
+ directory index "index.html"
+ }
+ }
+.Ed
+.Sh SEE ALSO
+.Xr got 1 ,
+.Xr kcgi 3 ,
+.Xr git-repository 5 ,
+.Xr gotweb.conf 5 ,
+.Xr httpd 8 ,
+.Xr slowcgi 8
+.Sh AUTHORS
+.An Tracey Emery Aq Mt tracey@traceyemery.net
+.An Stefan Sperling Aq Mt stsp@openbsd.org
blob - /dev/null
blob + c6930e3119c2231e948970c004f3f21d33c34c16 (mode 644)
--- /dev/null
+++ gotwebd/gotwebd.c
+/*
+ * Copyright (c) 2016, 2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/cdefs.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <imsg.h>
+#include <pwd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <util.h>
+
+#include "got_opentemp.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+__dead void usage(void);
+
+int main(int, char **);
+int gotwebd_configure(struct gotwebd *);
+void gotwebd_configure_done(struct gotwebd *);
+void gotwebd_sighdlr(int sig, short event, void *arg);
+void gotwebd_shutdown(void);
+int gotwebd_dispatch_sockets(int, struct privsep_proc *, struct imsg *);
+
+struct gotwebd *gotwebd_env;
+
+static struct privsep_proc procs[] = {
+ { "sockets", PROC_SOCKS, gotwebd_dispatch_sockets, sockets,
+ sockets_shutdown },
+};
+
+int
+gotwebd_dispatch_sockets(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct privsep *ps = p->p_ps;
+ struct gotwebd *env = ps->ps_env;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CFG_DONE:
+ gotwebd_configure_done(env);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+gotwebd_sighdlr(int sig, short event, void *arg)
+{
+ /* struct privsep *ps = arg; */
+
+ if (privsep_process != PROC_GOTWEBD)
+ return;
+
+ switch (sig) {
+ case SIGHUP:
+ log_info("%s: ignoring SIGHUP", __func__);
+ break;
+ case SIGPIPE:
+ log_info("%s: ignoring SIGPIPE", __func__);
+ break;
+ case SIGUSR1:
+ log_info("%s: ignoring SIGUSR1", __func__);
+ break;
+ case SIGTERM:
+ case SIGINT:
+ gotwebd_shutdown();
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+__dead void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+ getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct gotwebd *env;
+ struct privsep *ps;
+ unsigned int proc;
+ int ch;
+ const char *conffile = GOTWEBD_CONF;
+ enum privsep_procid proc_id = PROC_GOTWEBD;
+ int proc_instance = 0;
+ const char *errp, *title = NULL;
+ int argc0 = argc;
+
+ env = calloc(1, sizeof(*env));
+ if (env == NULL)
+ fatal("%s: calloc", __func__);
+
+ /* XXX: add s and S for both sockets */
+ while ((ch = getopt(argc, argv, "D:P:I:df:vn")) != -1) {
+ switch (ch) {
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'd':
+ env->gotwebd_debug = 2;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ env->gotwebd_verbose++;
+ break;
+ case 'n':
+ env->gotwebd_debug = 2;
+ env->gotwebd_noaction = 1;
+ break;
+ case 'P':
+ title = optarg;
+ proc_id = proc_getid(procs, nitems(procs), title);
+ if (proc_id == PROC_MAX)
+ fatalx("invalid process name");
+ break;
+ case 'I':
+ proc_instance = strtonum(optarg, 0,
+ PROC_MAX_INSTANCES, &errp);
+ if (errp)
+ fatalx("invalid process instance");
+ break;
+ default:
+ usage();
+ }
+ }
+
+ /* log to stderr until daemonized */
+ log_init(env->gotwebd_debug ? env->gotwebd_debug : 1, LOG_DAEMON);
+
+ argc -= optind;
+ if (argc > 0)
+ usage();
+
+ ps = calloc(1, sizeof(*ps));
+ if (ps == NULL)
+ fatal("%s: calloc:", __func__);
+
+ gotwebd_env = env;
+ env->gotwebd_ps = ps;
+ ps->ps_env = env;
+ env->gotwebd_conffile = conffile;
+
+ if (parse_config(env->gotwebd_conffile, env) == -1)
+ exit(1);
+
+ if (env->gotwebd_noaction && !env->gotwebd_debug)
+ env->gotwebd_debug = 1;
+
+ /* check for root privileges */
+ if (env->gotwebd_noaction == 0) {
+ if (geteuid())
+ fatalx("need root privileges");
+ }
+
+ ps->ps_pw = getpwnam(GOTWEBD_USER);
+ if (ps->ps_pw == NULL)
+ fatalx("unknown user %s", GOTWEBD_USER);
+
+ log_init(env->gotwebd_debug, LOG_DAEMON);
+ log_setverbose(env->gotwebd_verbose);
+
+ if (env->gotwebd_noaction)
+ ps->ps_noaction = 1;
+
+ ps->ps_instances[PROC_SOCKS] = env->prefork_gotwebd;
+ ps->ps_instance = proc_instance;
+ if (title != NULL)
+ ps->ps_title[proc_id] = title;
+
+ for (proc = 0; proc < nitems(procs); proc++)
+ procs[proc].p_chroot = strlen(env->httpd_chroot) ?
+ env->httpd_chroot : D_HTTPD_CHROOT;
+
+ /* only the gotwebd returns */
+ proc_init(ps, procs, nitems(procs), argc0, argv, proc_id);
+
+ log_procinit("gotwebd");
+ if (!env->gotwebd_debug && daemon(0, 0) == -1)
+ fatal("can't daemonize");
+
+ if (ps->ps_noaction == 0)
+ log_info("%s startup", getprogname());
+
+ event_init();
+
+ signal_set(&ps->ps_evsigint, SIGINT, gotwebd_sighdlr, ps);
+ signal_set(&ps->ps_evsigterm, SIGTERM, gotwebd_sighdlr, ps);
+ signal_set(&ps->ps_evsighup, SIGHUP, gotwebd_sighdlr, ps);
+ signal_set(&ps->ps_evsigpipe, SIGPIPE, gotwebd_sighdlr, ps);
+ signal_set(&ps->ps_evsigusr1, SIGUSR1, gotwebd_sighdlr, ps);
+
+ signal_add(&ps->ps_evsigint, NULL);
+ signal_add(&ps->ps_evsigterm, NULL);
+ signal_add(&ps->ps_evsighup, NULL);
+ signal_add(&ps->ps_evsigpipe, NULL);
+ signal_add(&ps->ps_evsigusr1, NULL);
+
+ if (!env->gotwebd_noaction)
+ proc_connect(ps);
+
+ if (gotwebd_configure(env) == -1)
+ fatalx("configuration failed");
+
+#ifdef PROFILE
+ if (unveil("gmon.out", "rwc") != 0)
+ err(1, "gmon.out");
+#endif
+
+ if (unveil(strlen(env->httpd_chroot) > 0 ? env->httpd_chroot :
+ D_HTTPD_CHROOT, "rwc") == -1)
+ err(1, "unveil");
+
+ if (unveil(GOT_TMPDIR_STR, "rw") == -1)
+ err(1, "unveil");
+
+ if (unveil(GOTWEBD_CONF, "r") == -1)
+ err(1, "unveil");
+
+ if (unveil(NULL, NULL) != 0)
+ err(1, "unveil");
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath inet unix", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ event_dispatch();
+
+ log_debug("%s gotwebd exiting", getprogname());
+
+ return (0);
+}
+
+int
+gotwebd_configure(struct gotwebd *env)
+{
+ struct server *srv;
+ struct socket *sock;
+ int id;
+
+ if (env->gotwebd_noaction) {
+ fprintf(stderr, "configuration OK\n");
+ proc_kill(env->gotwebd_ps);
+ exit(0);
+ }
+
+ /* gotweb need to reload its config. */
+ env->gotwebd_reload = env->prefork_gotwebd;
+
+ /* send our gotweb servers */
+ TAILQ_FOREACH(srv, env->servers, entry) {
+ if (config_setserver(env, srv) == -1)
+ fatalx("%s: send server error", __func__);
+ }
+
+ /* send our sockets */
+ TAILQ_FOREACH(sock, env->sockets, entry) {
+ if (config_setsock(env, sock) == -1)
+ fatalx("%s: send socket error", __func__);
+ if (config_setfd(env, sock) == -1)
+ fatalx("%s: send priv_fd error", __func__);
+ }
+
+ for (id = 0; id < PROC_MAX; id++) {
+ if (id == privsep_process)
+ continue;
+ proc_compose(env->gotwebd_ps, id, IMSG_CFG_DONE, NULL, 0);
+ }
+
+ return (0);
+}
+
+void
+gotwebd_configure_done(struct gotwebd *env)
+{
+ int id;
+
+ if (env->gotwebd_reload == 0) {
+ log_warnx("%s: configuration already finished", __func__);
+ return;
+ }
+
+ env->gotwebd_reload--;
+ if (env->gotwebd_reload == 0) {
+ for (id = 0; id < PROC_MAX; id++) {
+ if (id == privsep_process)
+ continue;
+ proc_compose(env->gotwebd_ps, id, IMSG_CTL_START,
+ NULL, 0);
+ }
+ }
+}
+
+void
+gotwebd_shutdown(void)
+{
+ proc_kill(gotwebd_env->gotwebd_ps);
+
+ /* unlink(gotwebd_env->gotweb->gotweb_conf.gotweb_unix_socket_name); */
+ /* free(gotwebd_env->gotweb); */
+ free(gotwebd_env);
+
+ log_warnx("gotwebd terminating");
+ exit(0);
+}
blob - /dev/null
blob + 7dba54734ea8a323a1f5597f698f84a790722531 (mode 644)
--- /dev/null
+++ gotwebd/gotwebd.conf.5
+.\"
+.\" Copyright (c) 2020 Tracey Emery <tracey@traceyemery.net>
+.\"
+.\" 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 GOTWEB.CONF 5
+.Os
+.Sh NAME
+.Nm gotweb.conf
+.Nd gotweb configuration file
+.Sh DESCRIPTION
+.Nm
+is the run-time configuration file for
+.Xr gotweb 8 .
+.Pp
+The file format is line-based, with one configuration directive per line.
+Any lines beginning with a
+.Sq #
+are treated as comments and ignored.
+.Pp
+Paths mentioned in
+.Nm
+must be relative to
+.Pa /var/www ,
+the
+.Xr chroot 2
+environment of
+.Xr httpd 8 .
+.Sh GLOBAL CONFIGURATION
+The available configuration directives are as follows:
+.Bl -tag -width Ds
+.It Ic got_max_commits_display Ar number
+Set the maximum amount of commits displayed per page.
+.It Ic got_logo Ar path
+Set the path to an image file containing a logo to be displayed.
+.It Ic got_logo_url Ar url
+Set a hyperlink for the logo.
+.It Ic got_max_repos Ar number
+Set the maximum amount of repositories
+.Xr gotweb 8
+will work with.
+.It Ic got_max_repos_display Ar number
+Set the maximum amount of repositories displayed on the index screen.
+.It Ic got_show_repo_age Ar on | off
+Toggle display of last repository modification date.
+.It Ic got_show_repo_cloneurl Ar on | off
+Toggle display of clone URLs for a repository.
+This requires the creation of a
+.Pa cloneurl
+file inside the repository which contains one URL per line.
+.It Ic got_show_repo_description Ar on | off
+Toggle display of the repository description.
+The
+.Pa description
+file in the repository should be updated with an appropriate description.
+.It Ic got_repos_path Ar path
+Set the path to the directory which contains Git repositories that
+.Xr gotweb 8
+should publish.
+.It Ic got_show_repo_owner Ar on | off
+Set whether to display the repository owner.
+Displaying the owner requires owner information to be added to the
+.Pa config
+file in the repository.
+.Xr gotweb 8
+will parse owner information from either a [gotweb] or a [gitweb] section.
+For example:
+.Bd -literal -offset indent
+[gotweb]
+owner = "Your Name"
+.Ed
+.It Ic got_site_link Ar string
+Set the displayed site link name for the index page.
+.It Ic got_site_name Ar string
+Set the displayed site name title.
+.It Ic got_site_owner Ar string
+Set the displayed site owner.
+.It Ic got_show_site_owner Ar on | off
+Toggle display of the site owner.
+.It Ic got_www_path Ar string
+Set the public gotweb httpd path.
+.El
+.Sh EXAMPLES
+These are the currently configurable items for
+.Xr gotweb 8
+with their default values.
+.Bd -literal -offset indent
+
+#
+# gotweb options
+# all paths relative to /var/www (httpd chroot jail)
+#
+
+got_repos_path "/got/public"
+got_www_path "/gotweb"
+
+#got_max_repos 100
+#got_max_repos_display 25
+got_max_commits_display 50
+
+got_site_name "my public repos"
+got_site_owner "Got Owner"
+got_site_link "repos"
+
+got_logo "got.png"
+got_logo_url "https://gameoftrees.org"
+
+# on by default
+#got_show_site_owner off
+#got_show_repo_owner off
+#got_show_repo_age false
+#got_show_repo_description no
+#got_show_repo_cloneurl off
+.Ed
+.Sh FILES
+.Bl -tag -width Ds -compact
+.It Pa /var/www/etc/gotweb.conf
+Location of the
+.Nm
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr got 1 ,
+.Xr gotweb 8
blob - /dev/null
blob + 38c0c930eedf474fcef860b7a5336ec4ae64724d (mode 644)
--- /dev/null
+++ gotwebd/gotwebd.h
+/*
+ * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
+ *
+ * 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 <netinet/in.h>
+#include <net/if.h>
+#include <sys/queue.h>
+
+#include <limits.h>
+#include <stdio.h>
+
+#ifdef DEBUG
+#define dprintf(x...) do { log_debug(x); } while(0)
+#else
+#define dprintf(x...)
+#endif /* DEBUG */
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+/* GOTWEBD DEFAULTS */
+#define GOTWEBD_CONF "/etc/gotwebd.conf"
+
+#define GOTWEBD_USER "www"
+
+#define GOTWEBD_MAXCLIENTS 1024
+#define GOTWEBD_MAXTEXT 511
+#define GOTWEBD_MAXNAME 64
+#define GOTWEBD_MAXPORT 6
+#define GOTWEBD_NUMPROC 3
+#define GOTWEBD_MAXIFACE 16
+
+/* GOTWEB DEFAULTS */
+#define MAX_QUERYSTRING 2048
+#define MAX_DOCUMENT_ROOT 255
+#define MAX_SERVER_NAME 255
+
+#define GOTWEB_GOT_DIR ".got"
+#define GOTWEB_GIT_DIR ".git"
+
+#define D_HTTPD_CHROOT "/var/www"
+#define D_UNIX_SOCKET "/run/gotweb.sock"
+#define D_FCGI_PORT "9000"
+#define D_GOTPATH "/got/public"
+#define D_SITENAME "Gotweb"
+#define D_SITEOWNER "Got Owner"
+#define D_SITELINK "Repos"
+#define D_GOTLOGO "got.png"
+#define D_GOTURL "https://gameoftrees.org"
+#define D_GOTWEBCSS "gotweb.css"
+
+#define D_SHOWROWNER 1
+#define D_SHOWSOWNER 1
+#define D_SHOWAGE 1
+#define D_SHOWDESC 1
+#define D_SHOWURL 1
+#define D_MAXREPO 0
+#define D_MAXREPODISP 25
+#define D_MAXSLCOMMDISP 10
+#define D_MAXCOMMITDISP 25
+
+#define BUF 8192
+
+#define TIMEOUT_DEFAULT 120
+
+#define FCGI_CONTENT_SIZE 65535
+#define FCGI_PADDING_SIZE 255
+#define FCGI_RECORD_SIZE \
+ (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
+
+#define FCGI_ALIGNMENT 8
+#define FCGI_ALIGN(n) \
+ (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1))
+
+#define FD_RESERVE 5
+#define FD_NEEDED 6
+
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+/* XXX: move this later after ig */
+#define GOT_PACK_NUM_TEMPFILES 32
+
+enum imsg_type {
+ IMSG_CFG_SRV = IMSG_PROC_MAX,
+ IMSG_CFG_SOCK,
+ IMSG_CFG_FD,
+ IMSG_CFG_DONE,
+ IMSG_CTL_START,
+};
+
+struct env_val {
+ SLIST_ENTRY(env_val) entry;
+ char *val;
+};
+SLIST_HEAD(env_head, env_val);
+
+struct fcgi_record_header {
+ uint8_t version;
+ uint8_t type;
+ uint16_t id;
+ uint16_t content_len;
+ uint8_t padding_len;
+ uint8_t reserved;
+}__packed;
+
+struct fcgi_response {
+ TAILQ_ENTRY(fcgi_response) entry;
+ uint8_t data[FCGI_RECORD_SIZE];
+ size_t data_pos;
+ size_t data_len;
+};
+
+struct repo_dir {
+ char *name;
+ char *owner;
+ char *description;
+ char *url;
+ char *age;
+ char *path;
+};
+
+struct repo_tag {
+ TAILQ_ENTRY(repo_tag) entry;
+ char *commit_id;
+ char *tag_name;
+ char *tag_commit;
+ char *commit_msg;
+ char *tagger;
+ time_t tagger_time;
+};
+
+struct repo_commit {
+ TAILQ_ENTRY(repo_commit) entry;
+ char *path;
+ char *refs_str;
+ char *commit_id; /* id_str1 */
+ char *parent_id; /* id_str2 */
+ char *tree_id;
+ char *author;
+ char *committer;
+ char *commit_msg;
+ time_t committer_time;
+};
+
+struct got_repository;
+struct transport {
+ TAILQ_HEAD(repo_commits_head, repo_commit) repo_commits;
+ TAILQ_HEAD(repo_tags_head, repo_tag) repo_tags;
+ struct got_repository *repo;
+ struct repo_dir *repo_dir;
+ struct querystring *qs;
+ char *next_id;
+ char *prev_id;
+ unsigned int repos_total;
+ unsigned int next_disp;
+ unsigned int prev_disp;
+ unsigned int tag_count;
+};
+
+enum socket_priv_fds {
+ DIFF_FD_1,
+ DIFF_FD_2,
+ DIFF_FD_3,
+ DIFF_FD_4,
+ DIFF_FD_5,
+ BLAME_FD_1,
+ BLAME_FD_2,
+ BLAME_FD_3,
+ BLAME_FD_4,
+ BLAME_FD_5,
+ BLAME_FD_6,
+ BLOB_FD_1,
+ BLOB_FD_2,
+ PRIV_FDS__MAX,
+};
+
+struct request {
+ struct socket *sock;
+ struct server *srv;
+ struct transport *t;
+ struct event ev;
+ struct event tmo;
+
+ uint16_t id;
+ int fd;
+ int priv_fd[PRIV_FDS__MAX];
+
+ uint8_t buf[FCGI_RECORD_SIZE];
+ size_t buf_pos;
+ size_t buf_len;
+
+ char querystring[MAX_QUERYSTRING];
+ char http_host[GOTWEBD_MAXTEXT];
+ char document_root[MAX_DOCUMENT_ROOT];
+ char server_name[MAX_SERVER_NAME];
+
+ struct env_head env;
+ int env_count;
+
+ uint8_t request_started;
+};
+
+struct fcgi_begin_request_body {
+ uint16_t role;
+ uint8_t flags;
+ uint8_t reserved[5];
+}__packed;
+
+struct fcgi_end_request_body {
+ uint32_t app_status;
+ uint8_t protocol_status;
+ uint8_t reserved[3];
+}__packed;
+
+struct address {
+ TAILQ_ENTRY(address) entry;
+ struct sockaddr_storage ss;
+ int ipproto;
+ int prefixlen;
+ in_port_t port;
+ char ifname[IFNAMSIZ];
+};
+TAILQ_HEAD(addresslist, address);
+
+struct server {
+ TAILQ_ENTRY(server) entry;
+ struct addresslist *al;
+
+ char name[GOTWEBD_MAXTEXT];
+
+ char repos_path[PATH_MAX];
+ char site_name[GOTWEBD_MAXNAME];
+ char site_owner[GOTWEBD_MAXNAME];
+ char site_link[GOTWEBD_MAXTEXT];
+ char logo[GOTWEBD_MAXTEXT];
+ char logo_url[GOTWEBD_MAXTEXT];
+ char custom_css[PATH_MAX];
+
+ size_t max_repos;
+ size_t max_repos_display;
+ size_t max_commits_display;
+
+ int show_site_owner;
+ int show_repo_owner;
+ int show_repo_age;
+ int show_repo_description;
+ int show_repo_cloneurl;
+
+ int unix_socket;
+ char unix_socket_name[PATH_MAX];
+
+ int fcgi_socket;
+ char fcgi_socket_bind[GOTWEBD_MAXTEXT];
+ in_port_t fcgi_socket_port;
+};
+TAILQ_HEAD(serverlist, server);
+
+enum client_action {
+ CLIENT_CONNECT,
+ CLIENT_DISCONNECT,
+};
+
+enum sock_type {
+ UNIX,
+ FCGI,
+};
+
+struct socket_conf {
+ struct addresslist *al;
+
+ char name[GOTWEBD_MAXTEXT];
+ char srv_name[GOTWEBD_MAXTEXT];
+
+ int id;
+ int child_id;
+ int parent_id;
+
+ int ipv4;
+ int ipv6;
+
+ int type;
+ char unix_socket_name[PATH_MAX];
+ in_port_t fcgi_socket_port;
+};
+
+struct socket {
+ TAILQ_ENTRY(socket) entry;
+ struct socket_conf conf;
+
+ int fd;
+ int pack_fds[GOT_PACK_NUM_TEMPFILES];
+ int priv_fd[PRIV_FDS__MAX];
+
+ struct event evt;
+ struct event ev;
+ struct event pause;
+
+ int client_status;
+};
+TAILQ_HEAD(socketlist, socket);
+
+struct gotwebd {
+ struct serverlist *servers;
+ struct socketlist *sockets;
+
+ struct privsep *gotwebd_ps;
+ const char *gotwebd_conffile;
+
+ int gotwebd_debug;
+ int gotwebd_verbose;
+ int gotwebd_noaction;
+
+ uint16_t prefork_gotwebd;
+ int gotwebd_reload;
+
+ int server_cnt;
+
+ char httpd_chroot[PATH_MAX];
+
+ int unix_socket;
+ char unix_socket_name[PATH_MAX];
+
+ int fcgi_socket;
+ char fcgi_socket_bind[GOTWEBD_MAXTEXT];
+ in_port_t fcgi_socket_port;
+};
+
+struct querystring {
+ uint8_t action;
+ char *commit;
+ char *previd;
+ char *prevset;
+ char *file;
+ char *folder;
+ char *headref;
+ int index_page;
+ char *index_page_str;
+ char *path;
+ int page;
+ char *page_str;
+};
+
+struct querystring_keys {
+ const char *name;
+ int element;
+};
+
+struct action_keys {
+ const char *name;
+ int action;
+};
+
+enum querystring_elements {
+ ACTION,
+ COMMIT,
+ RFILE,
+ FOLDER,
+ HEADREF,
+ INDEX_PAGE,
+ PATH,
+ PAGE,
+ PREVID,
+ QSELEM__MAX,
+};
+
+enum query_actions {
+ BLAME,
+ BLOB,
+ BRIEFS,
+ COMMITS,
+ DIFF,
+ ERR,
+ INDEX,
+ SUMMARY,
+ TAG,
+ TAGS,
+ TREE,
+ ACTIONS__MAX,
+};
+
+extern struct gotwebd *gotwebd_env;
+
+/* sockets.c */
+void sockets(struct privsep *, struct privsep_proc *);
+void sockets_shutdown(void);
+void sockets_parse_sockets(struct gotwebd *);
+void sockets_socket_accept(int, short, void *);
+int sockets_privinit(struct gotwebd *, struct socket *);
+
+/* gotweb.c */
+const struct got_error *gotweb_render_content_type(struct request *,
+ const uint8_t *);
+const struct got_error
+ *gotweb_render_content_type_file(struct request *, const uint8_t *, char *);
+const struct got_error *gotweb_get_time_str(char **, time_t, int);
+const struct got_error *gotweb_init_transport(struct transport **);
+const struct got_error *gotweb_escape_html(char **, const char *);
+void gotweb_free_repo_commit(struct repo_commit *);
+void gotweb_free_repo_tag(struct repo_tag *);
+void gotweb_process_request(struct request *);
+void gotweb_free_transport(struct transport *);
+
+/* parse.y */
+int parse_config(const char *, struct gotwebd *);
+int cmdline_symset(char *);
+
+/* fcgi.c */
+void fcgi_request(int, short, void *);
+void fcgi_timeout(int, short, void *);
+void fcgi_cleanup_request(struct request *);
+void fcgi_create_end_record(struct request *);
+void dump_fcgi_record(const char *, struct fcgi_record_header *);
+int fcgi_gen_response(struct request *, const char *);
+int fcgi_gen_binary_response(struct request *, const uint8_t *, int);
+
+/* got_operations.c */
+const struct got_error *got_get_repo_owner(char **, struct request *, char *);
+const struct got_error *got_get_repo_age(char **, struct request *, char *,
+ const char *, int);
+const struct got_error *got_get_repo_commits(struct request *, int);
+const struct got_error *got_get_repo_tags(struct request *, int);
+const struct got_error *got_get_repo_heads(struct request *);
+const struct got_error *got_output_repo_diff(struct request *);
+const struct got_error *got_output_repo_tree(struct request *);
+const struct got_error *got_output_file_blob(struct request *);
+const struct got_error *got_output_file_blame(struct request *);
+
+/* config.c */
+int config_setserver(struct gotwebd *, struct server *);
+int config_getserver(struct gotwebd *, struct imsg *);
+int config_setsock(struct gotwebd *, struct socket *);
+int config_getsock(struct gotwebd *, struct imsg *);
+int config_setfd(struct gotwebd *, struct socket *);
+int config_getfd(struct gotwebd *, struct imsg *);
+int config_getcfg(struct gotwebd *, struct imsg *);
+int config_init(struct gotwebd *);
blob - /dev/null
blob + 5fd34708bd3654bc05060446ff5d55557747cfd3 (mode 644)
--- /dev/null
+++ gotwebd/libexec/Makefile
+SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \
+ got-read-tag got-read-pack got-read-gitconfig got-read-gotconfig
+
+.include <bsd.subdir.mk>
blob - /dev/null
blob + 85bee26728643214f5d4570f003572ac1fc36d05 (mode 644)
--- /dev/null
+++ gotwebd/libexec/Makefile.inc
+.include "../Makefile.inc"
+
+realinstall:
+ if [ ! -d ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/. ]; then \
+ ${INSTALL} -d -o root -g daemon -m 755 \
+ ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}; \
+ fi
+ ${INSTALL} ${INSTALL_COPY} -o root -g daemon -m 755 ${PROG} \
+ ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/${PROG}
+
+NOMAN = Yes
blob - /dev/null
blob + 8a3f38ce4c45ce1386bdc180e342b043f8abe809 (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-blob/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-blob
+SRCS= got-read-blob.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-blob
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 0996068f0f3d0bb71779d03c1c1a7ec355644166 (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-commit/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-commit
+SRCS= got-read-commit.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-commit
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 77cc7852cae9199c991f2908f2d9e8a27da650ee (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-gitconfig/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-gitconfig
+SRCS= got-read-gitconfig.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c gitconfig.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gitconfig
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 29605918a07b9e1969e3a2722605c3424e77d13f (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-gotconfig/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-gotconfig
+SRCS= got-read-gotconfig.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c parse.y
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib \
+ -I${.CURDIR}/../../../libexec/got-read-gotconfig
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gotconfig
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 4889fe0bab46bbcdb20610ea9255916cd11d0e0d (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-object/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-object
+SRCS= got-read-object.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-object
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + a28b9cfd2d1c5d17ffcbe771790c2183e406f924 (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-pack/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-pack
+SRCS= got-read-pack.c delta.c error.c inflate.c object_cache.c \
+ object_idset.c object_parse.c opentemp.c pack.c path.c \
+ privsep.c sha1.c delta_cache.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-pack
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 3a0b798c57ea849bde67efe66e3b721f7486e287 (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-tag/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-tag
+SRCS= got-read-tag.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tag
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 19a4c9cfa3379ca5fec4fafdc691d38c80c996e8 (mode 644)
--- /dev/null
+++ gotwebd/libexec/got-read-tree/Makefile
+
+.include "../../../got-version.mk"
+.include "../Makefile.inc"
+
+PROG= got-read-tree
+SRCS= got-read-tree.c error.c inflate.c object_parse.c \
+ path.c privsep.c sha1.c
+
+CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
+LDADD = -lutil -lz
+DPADD = ${LIBZ} ${LIBUTIL}
+LDSTATIC = ${STATIC}
+
+.PATH: ${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tree
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + 79d3d334581eddd8ffcc0f02f067e5c64d0be72e (mode 644)
--- /dev/null
+++ gotwebd/log.c
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+static int debug;
+static int verbose;
+const char *log_procname;
+
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+void
+log_init(int n_debug, int facility)
+{
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(getprogname());
+
+ if (!debug)
+ openlog(getprogname(), LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose > 1) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "%s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "%s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
blob - /dev/null
blob + c7b300955ba979db3cd4d8308802a8179f9d7ac4 (mode 644)
--- /dev/null
+++ gotwebd/parse.y
+/*
+ * Copyright (c) 2016-2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <ifaddrs.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <tls.h>
+#include <unistd.h>
+
+#include "proc.h"
+#include "gotwebd.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file;
+struct file *newfile(const char *, int);
+static void closefile(struct file *);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)))
+ __attribute__((__nonnull__ (1)));
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+static int errors;
+
+static struct gotwebd *gotwebd;
+static struct server *new_srv;
+static struct server *conf_new_server(const char *);
+int getservice(const char *);
+int n;
+
+int get_addrs(const char *, struct addresslist *, in_port_t);
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, in_port_t, const char *, int);
+int host_if(const char *, struct addresslist *,
+ int, in_port_t, const char *, int);
+int host(const char *, struct addresslist *,
+ int, in_port_t, const char *, int);
+int is_if_in_group(const char *, const char *);
+
+typedef struct {
+ union {
+ long long number;
+ char *string;
+ in_port_t port;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token BIND INTERFACE WWW_PATH MAX_REPOS SITE_NAME SITE_OWNER SITE_LINK LOGO
+%token LOGO_URL SHOW_REPO_OWNER SHOW_REPO_AGE SHOW_REPO_DESCRIPTION
+%token MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR
+%token SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK FCGI_SOCKET
+%token UNIX_SOCKET UNIX_SOCKET_NAME SERVER CHROOT CUSTOM_CSS
+
+%token <v.string> STRING
+%type <v.port> fcgiport
+%token <v.number> NUMBER
+%type <v.number> boolean
+
+%%
+
+grammar :
+ | grammar '\n'
+ | grammar main '\n'
+ | grammar server '\n'
+ ;
+
+boolean : STRING {
+ if (strcasecmp($1, "1") == 0 ||
+ strcasecmp($1, "yes") == 0 ||
+ strcasecmp($1, "on") == 0)
+ $$ = 1;
+ else if (strcasecmp($1, "0") == 0 ||
+ strcasecmp($1, "off") == 0 ||
+ strcasecmp($1, "no") == 0)
+ $$ = 0;
+ else {
+ yyerror("invalid boolean value '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | ON { $$ = 1; }
+ | NUMBER { $$ = $1; }
+ ;
+
+fcgiport : NUMBER {
+ if ($1 <= 0 || $1 > (int)USHRT_MAX) {
+ yyerror("invalid port: %lld", $1);
+ YYERROR;
+ }
+ $$ = htons($1);
+ }
+ | STRING {
+ int val;
+
+ if ((val = getservice($1)) == -1) {
+ yyerror("invalid port: %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+
+ $$ = val;
+ }
+ ;
+
+main : PREFORK NUMBER {
+ gotwebd->prefork_gotwebd = $2;
+ }
+ | CHROOT STRING {
+ n = strlcpy(gotwebd->httpd_chroot, $2,
+ sizeof(gotwebd->httpd_chroot));
+ if (n >= sizeof(gotwebd->httpd_chroot)) {
+ yyerror("%s: httpd_chroot truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | FCGI_SOCKET boolean {
+ gotwebd->fcgi_socket = $2;
+ }
+ | FCGI_SOCKET boolean {
+ gotwebd->fcgi_socket = $2;
+ } '{' optnl socketopts4 '}'
+ | UNIX_SOCKET boolean {
+ gotwebd->unix_socket = $2;
+ }
+ | UNIX_SOCKET_NAME STRING {
+ n = snprintf(gotwebd->unix_socket_name,
+ sizeof(gotwebd->unix_socket_name), "%s%s",
+ strlen(gotwebd->httpd_chroot) ?
+ gotwebd->httpd_chroot : D_HTTPD_CHROOT, $2);
+ if (n < 0) {
+ yyerror("%s: unix_socket_name truncated",
+ __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+server : SERVER STRING {
+ struct server *srv;
+
+ TAILQ_FOREACH(srv, gotwebd->servers, entry) {
+ if (strcmp(srv->name, $2) == 0) {
+ yyerror("server name exists '%s'", $2);
+ free($2);
+ YYERROR;
+ }
+ }
+
+ new_srv = conf_new_server($2);
+ if (new_srv->fcgi_socket)
+ if (get_addrs(new_srv->fcgi_socket_bind,
+ new_srv->al,
+ new_srv->fcgi_socket_port) == -1) {
+ yyerror("could not get tcp iface "
+ "addrs");
+ YYERROR;
+ }
+ log_debug("adding server %s", $2);
+ free($2);
+ }
+ | SERVER STRING {
+ struct server *srv;
+
+ TAILQ_FOREACH(srv, gotwebd->servers, entry) {
+ if (strcmp(srv->name, $2) == 0) {
+ yyerror("server name exists '%s'", $2);
+ free($2);
+ YYERROR;
+ }
+ }
+
+ new_srv = conf_new_server($2);
+ log_debug("adding server %s", $2);
+ free($2);
+ } '{' optnl serveropts2 '}' {
+ if (get_addrs(new_srv->fcgi_socket_bind,
+ new_srv->al, new_srv->fcgi_socket_port) == -1) {
+ yyerror("could not get tcp iface addrs");
+ YYERROR;
+ }
+ }
+ ;
+
+serveropts1 : REPOS_PATH STRING {
+ n = strlcpy(new_srv->repos_path, $2,
+ sizeof(new_srv->repos_path));
+ if (n >= sizeof(new_srv->repos_path)) {
+ yyerror("%s: repos_path truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | SITE_NAME STRING {
+ n = strlcpy(new_srv->site_name, $2,
+ sizeof(new_srv->site_name));
+ if (n >= sizeof(new_srv->site_name)) {
+ yyerror("%s: site_name truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | SITE_OWNER STRING {
+ n = strlcpy(new_srv->site_owner, $2,
+ sizeof(new_srv->site_owner));
+ if (n >= sizeof(new_srv->site_owner)) {
+ yyerror("%s: site_owner truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | SITE_LINK STRING {
+ n = strlcpy(new_srv->site_link, $2,
+ sizeof(new_srv->site_link));
+ if (n >= sizeof(new_srv->site_link)) {
+ yyerror("%s: site_link truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | LOGO STRING {
+ n = strlcpy(new_srv->logo, $2, sizeof(new_srv->logo));
+ if (n >= sizeof(new_srv->logo)) {
+ yyerror("%s: logo truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | LOGO_URL STRING {
+ n = strlcpy(new_srv->logo_url, $2,
+ sizeof(new_srv->logo_url));
+ if (n >= sizeof(new_srv->logo_url)) {
+ yyerror("%s: logo_url truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | CUSTOM_CSS STRING {
+ n = strlcpy(new_srv->custom_css, $2,
+ sizeof(new_srv->custom_css));
+ if (n >= sizeof(new_srv->custom_css)) {
+ yyerror("%s: custom_css truncated", __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | MAX_REPOS NUMBER {
+ if ($2 > 0)
+ new_srv->max_repos = $2;
+ }
+ | SHOW_SITE_OWNER boolean {
+ new_srv->show_site_owner = $2;
+ }
+ | SHOW_REPO_OWNER boolean {
+ new_srv->show_repo_owner = $2;
+ }
+ | SHOW_REPO_AGE boolean {
+ new_srv->show_repo_age = $2;
+ }
+ | SHOW_REPO_DESCRIPTION boolean {
+ new_srv->show_repo_description = $2;
+ }
+ | SHOW_REPO_CLONEURL boolean {
+ new_srv->show_repo_cloneurl = $2;
+ }
+ | MAX_REPOS_DISPLAY NUMBER {
+ new_srv->max_repos_display = $2;
+ }
+ | MAX_COMMITS_DISPLAY NUMBER {
+ if ($2 > 0)
+ new_srv->max_commits_display = $2;
+ }
+ | FCGI_SOCKET boolean {
+ new_srv->fcgi_socket = $2;
+ }
+ | FCGI_SOCKET boolean {
+ new_srv->fcgi_socket = $2;
+ } '{' optnl socketopts2 '}'
+ | UNIX_SOCKET boolean {
+ new_srv->unix_socket = $2;
+ }
+ | UNIX_SOCKET_NAME STRING {
+ n = snprintf(new_srv->unix_socket_name,
+ sizeof(new_srv->unix_socket_name), "%s%s",
+ strlen(gotwebd->httpd_chroot) ?
+ gotwebd->httpd_chroot : D_HTTPD_CHROOT, $2);
+ if (n < 0) {
+ yyerror("%s: unix_socket_name truncated",
+ __func__);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+serveropts2 : serveropts2 serveropts1 nl
+ | serveropts1 optnl
+ ;
+
+socketopts1 : BIND INTERFACE STRING {
+ n = strlcpy(new_srv->fcgi_socket_bind, $3,
+ sizeof(new_srv->fcgi_socket_bind));
+ if (n >= sizeof(new_srv->fcgi_socket_bind)) {
+ yyerror("%s: fcgi_socket_bind truncated",
+ __func__);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | PORT fcgiport {
+ struct server *srv;
+
+ TAILQ_FOREACH(srv, gotwebd->servers, entry) {
+ if (srv->fcgi_socket_port == $2) {
+ yyerror("port already assigned");
+ YYERROR;
+ }
+ }
+ new_srv->fcgi_socket_port = $2;
+ }
+ ;
+
+socketopts2 : socketopts2 socketopts1 nl
+ | socketopts1 optnl
+ ;
+
+socketopts3 : BIND INTERFACE STRING {
+ n = strlcpy(gotwebd->fcgi_socket_bind, $3,
+ sizeof(gotwebd->fcgi_socket_bind));
+ if (n >= sizeof(gotwebd->fcgi_socket_bind)) {
+ yyerror("%s: fcgi_socket_bind truncated",
+ __func__);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | PORT fcgiport {
+ gotwebd->fcgi_socket_port = $2;
+ }
+ ;
+
+socketopts4 : socketopts4 socketopts3 nl
+ | socketopts3 optnl
+ ;
+
+nl : '\n' optnl
+ ;
+
+optnl : '\n' optnl /* zero or more newlines */
+ | /* empty */
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ fatalx("yyerror vasprintf");
+ va_end(ap);
+ logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+ free(msg);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* This has to be sorted always. */
+ static const struct keywords keywords[] = {
+ { "bind", BIND },
+ { "chroot", CHROOT },
+ { "custom_css", CUSTOM_CSS },
+ { "fcgi_socket", FCGI_SOCKET },
+ { "interface", INTERFACE },
+ { "logo", LOGO },
+ { "logo_url" , LOGO_URL },
+ { "max_commits_display", MAX_COMMITS_DISPLAY },
+ { "max_repos", MAX_REPOS },
+ { "max_repos_display", MAX_REPOS_DISPLAY },
+ { "port", PORT },
+ { "prefork", PREFORK },
+ { "repos_path", REPOS_PATH },
+ { "server", SERVER },
+ { "show_repo_age", SHOW_REPO_AGE },
+ { "show_repo_cloneurl", SHOW_REPO_CLONEURL },
+ { "show_repo_description", SHOW_REPO_DESCRIPTION },
+ { "show_repo_owner", SHOW_REPO_OWNER },
+ { "show_site_owner", SHOW_SITE_OWNER },
+ { "site_link", SITE_LINK },
+ { "site_name", SITE_NAME },
+ { "site_owner", SITE_OWNER },
+ { "unix_socket", UNIX_SOCKET },
+ { "unix_socket_name", UNIX_SOCKET_NAME },
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+unsigned char *parsebuf;
+int parseindex;
+unsigned char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ c = getc(file->stream);
+ if (c == EOF)
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ return (c);
+ }
+
+ c = getc(file->stream);
+ while (c == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ c = getc(file->stream);
+ }
+
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* Skip to either EOF or the first real EOL. */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ unsigned char buf[8096];
+ unsigned char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ c = lgetc(0);
+ while (c == ' ' || c == '\t')
+ c = lgetc(0); /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#') {
+ c = lgetc(0);
+ while (c != '\n' && c != EOF)
+ c = lgetc(0); /* nothing */
+ }
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ c = lgetc(0);
+ if (c == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ c = lgetc(quotec);
+ if (c == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ next = lgetc(quotec);
+ if (next == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n') {
+ file->lineno++;
+ continue;
+ } else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ c = lgetc(0);
+ } while (c != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ c = lgetc(0);
+ } while (c != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ token = lookup(buf);
+ if (token == STRING) {
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ }
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ log_warnx("%s: group writable or world read/writable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+newfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ nfile = calloc(1, sizeof(struct file));
+ if (nfile == NULL) {
+ log_warn("calloc");
+ return (NULL);
+ }
+ nfile->name = strdup(name);
+ if (nfile->name == NULL) {
+ log_warn("strdup");
+ free(nfile);
+ return (NULL);
+ }
+ nfile->stream = fopen(nfile->name, "r");
+ if (nfile->stream == NULL) {
+ /* no warning, we don't require a conf file */
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ return (nfile);
+}
+
+static void
+closefile(struct file *xfile)
+{
+ fclose(xfile->stream);
+ free(xfile->name);
+ free(xfile);
+}
+
+int
+parse_config(const char *filename, struct gotwebd *env)
+{
+ struct sym *sym, *next;
+
+ file = newfile(filename, 0);
+ if (file == NULL)
+ /* just return, as we don't require a conf file */
+ return (0);
+
+ if (config_init(env) == -1)
+ fatalx("failed to initialize configuration");
+
+ gotwebd = env;
+
+ yyparse();
+ errors = file->errors;
+ closefile(file);
+
+ /* Free macros and check which have not been used. */
+ TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
+ if ((gotwebd->gotwebd_verbose > 1) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not used\n",
+ sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (errors)
+ return (-1);
+
+ /* just add default server if no config specified */
+ if (gotwebd->server_cnt == 0) {
+ new_srv = conf_new_server(D_SITENAME);
+ log_debug("%s: adding default server %s", __func__, D_SITENAME);
+ }
+
+ /* setup our listening sockets */
+ sockets_parse_sockets(env);
+
+ return (0);
+}
+
+struct server *
+conf_new_server(const char *name)
+{
+ struct server *srv = NULL;
+ int val;
+
+ srv = calloc(1, sizeof(*srv));
+ if (srv == NULL)
+ fatalx("%s: calloc", __func__);
+
+ n = strlcpy(srv->name, name, sizeof(srv->name));
+ if (n >= sizeof(srv->name))
+ fatalx("%s: strlcpy", __func__);
+ n = snprintf(srv->unix_socket_name,
+ sizeof(srv->unix_socket_name), "%s%s", D_HTTPD_CHROOT,
+ D_UNIX_SOCKET);
+ if (n < 0)
+ fatalx("%s: snprintf", __func__);
+ n = strlcpy(srv->repos_path, D_GOTPATH,
+ sizeof(srv->repos_path));
+ if (n >= sizeof(srv->repos_path))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->site_name, D_SITENAME,
+ sizeof(srv->site_name));
+ if (n >= sizeof(srv->site_name))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->site_owner, D_SITEOWNER,
+ sizeof(srv->site_owner));
+ if (n >= sizeof(srv->site_owner))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->site_link, D_SITELINK,
+ sizeof(srv->site_link));
+ if (n >= sizeof(srv->site_link))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->logo, D_GOTLOGO,
+ sizeof(srv->logo));
+ if (n >= sizeof(srv->logo))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->logo_url, D_GOTURL, sizeof(srv->logo_url));
+ if (n >= sizeof(srv->logo_url))
+ fatalx("%s: strlcpy", __func__);
+ n = strlcpy(srv->custom_css, D_GOTWEBCSS, sizeof(srv->custom_css));
+ if (n >= sizeof(srv->custom_css))
+ fatalx("%s: strlcpy", __func__);
+
+ val = getservice(D_FCGI_PORT);
+ srv->fcgi_socket_port = gotwebd->fcgi_socket_port ?
+ gotwebd->fcgi_socket_port: htons(val);
+
+ srv->show_site_owner = D_SHOWSOWNER;
+ srv->show_repo_owner = D_SHOWROWNER;
+ srv->show_repo_age = D_SHOWAGE;
+ srv->show_repo_description = D_SHOWDESC;
+ srv->show_repo_cloneurl = D_SHOWURL;
+
+ srv->max_repos_display = D_MAXREPODISP;
+ srv->max_commits_display = D_MAXCOMMITDISP;
+ srv->max_repos = D_MAXREPO;
+
+ srv->unix_socket = 1;
+ srv->fcgi_socket = gotwebd->fcgi_socket ? gotwebd->fcgi_socket : 0;
+
+ if ((srv->al = calloc(1, sizeof(*srv->al))) == NULL)
+ fatalx("%s: calloc", __func__);
+
+ TAILQ_INIT(srv->al);
+ TAILQ_INSERT_TAIL(gotwebd->servers, srv, entry);
+ gotwebd->server_cnt++;
+
+ return srv;
+};
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0)
+ break;
+ }
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ sym = calloc(1, sizeof(*sym));
+ if (sym == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ val = strrchr(s, '=');
+ if (val == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ sym = malloc(len);
+ if (sym == NULL)
+ fatal("%s: malloc", __func__);
+
+ memcpy(&sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ }
+ return (NULL);
+}
+
+int
+getservice(const char *n)
+{
+ struct servent *s;
+ const char *errstr;
+ long long llval;
+
+ llval = strtonum(n, 0, UINT16_MAX, &errstr);
+ if (errstr) {
+ s = getservbyname(n, "tcp");
+ if (s == NULL)
+ s = getservbyname(n, "udp");
+ if (s == NULL)
+ return (-1);
+ return (s->s_port);
+ }
+
+ return (htons((unsigned short)llval));
+}
+
+struct address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ memset(&ina, 0, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(__func__);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+ if (sain->sin_addr.s_addr == INADDR_ANY)
+ h->prefixlen = 0; /* 0.0.0.0 address */
+ else
+ h->prefixlen = -1; /* host address */
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct addrinfo hints, *res;
+ struct sockaddr_in6 *sa_in6;
+ struct address *h = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM; /* dummy */
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(s, "0", &hints, &res) == 0) {
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(__func__);
+ sa_in6 = (struct sockaddr_in6 *)&h->ss;
+ sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+ sa_in6->sin6_family = AF_INET6;
+ memcpy(&sa_in6->sin6_addr,
+ &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+ sizeof(sa_in6->sin6_addr));
+ sa_in6->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ if (memcmp(&sa_in6->sin6_addr, &in6addr_any,
+ sizeof(sa_in6->sin6_addr)) == 0)
+ h->prefixlen = 0; /* any address */
+ else
+ h->prefixlen = -1; /* host address */
+ freeaddrinfo(res);
+ }
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname, int ipproto)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ if ((cnt = host_if(s, al, max, port, ifname, ipproto)) != 0)
+ return (cnt);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ hints.ai_flags = AI_ADDRCONFIG;
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("%s: could not parse \"%s\": %s", __func__, s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(__func__);
+
+ if (port)
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("%s: interface name truncated",
+ __func__);
+ freeaddrinfo(res0);
+ free(h);
+ return (-1);
+ }
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = res->ai_family;
+ h->prefixlen = -1; /* host address */
+
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+host_if(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname, int ipproto)
+{
+ struct ifaddrs *ifap, *p;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+ int cnt = 0, af;
+
+ if (getifaddrs(&ifap) == -1)
+ fatal("getifaddrs");
+
+ /* First search for IPv4 addresses */
+ af = AF_INET;
+
+ nextaf:
+ for (p = ifap; p != NULL && cnt < max; p = p->ifa_next) {
+ if (p->ifa_addr == NULL ||
+ p->ifa_addr->sa_family != af ||
+ (strcmp(s, p->ifa_name) != 0 &&
+ !is_if_in_group(p->ifa_name, s)))
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal("calloc");
+
+ if (port)
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("%s: interface name truncated",
+ __func__);
+ free(h);
+ freeifaddrs(ifap);
+ return (-1);
+ }
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = af;
+ h->prefixlen = -1; /* host address */
+
+ if (af == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ p->ifa_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_scope_id = ((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_scope_id;
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (af == AF_INET) {
+ /* Next search for IPv6 addresses */
+ af = AF_INET6;
+ goto nextaf;
+ }
+
+ if (cnt > max) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeifaddrs(ifap);
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname, int ipproto)
+{
+ struct address *h;
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ if (port)
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("%s: interface name truncated",
+ __func__);
+ free(h);
+ return (-1);
+ }
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, ifname, ipproto));
+}
+
+int
+is_if_in_group(const char *ifname, const char *groupname)
+{
+ unsigned int len;
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+ int s;
+ int ret = 0;
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ err(1, "socket");
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ)
+ err(1, "IFNAMSIZ");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+ if (errno == EINVAL || errno == ENOTTY)
+ goto end;
+ err(1, "SIOCGIFGROUP");
+ }
+
+ len = ifgr.ifgr_len;
+ ifgr.ifgr_groups = calloc(len / sizeof(struct ifg_req),
+ sizeof(struct ifg_req));
+ if (ifgr.ifgr_groups == NULL)
+ err(1, "getifgroups");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ ifg = ifgr.ifgr_groups;
+ for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+ len -= sizeof(struct ifg_req);
+ if (strcmp(ifg->ifgrq_group, groupname) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ free(ifgr.ifgr_groups);
+
+end:
+ close(s);
+ return (ret);
+}
+
+int
+get_addrs(const char *addr, struct addresslist *al, in_port_t port)
+{
+ if (strcmp("", addr) == 0) {
+ if (host("0.0.0.0", al, 1, port, "0.0.0.0", -1) <= 0) {
+ yyerror("invalid listen ip: %s",
+ "0.0.0.0");
+ return (-1);
+ }
+ if (host("::", al, 1, port, "::", -1) <= 0) {
+ yyerror("invalid listen ip: %s", "::");
+ return (-1);
+ }
+ } else {
+ if (host(addr, al, GOTWEBD_MAXIFACE, port, addr,
+ -1) <= 0) {
+ yyerror("invalid listen ip: %s", addr);
+ return (-1);
+ }
+ }
+ return (0);
+}
blob - /dev/null
blob + 037993ed0667b68af3bc09186201595197a814c9 (mode 644)
--- /dev/null
+++ gotwebd/proc.c
+/*
+ * Copyright (c) 2010 - 2016 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <event.h>
+#include <imsg.h>
+
+#include "proc.h"
+
+void proc_exec(struct privsep *, struct privsep_proc *, unsigned int,
+ int, char **);
+void proc_setup(struct privsep *, struct privsep_proc *, unsigned int);
+void proc_open(struct privsep *, int, int);
+void proc_accept(struct privsep *, int, enum privsep_procid,
+ unsigned int);
+void proc_close(struct privsep *);
+int proc_ispeer(struct privsep_proc *, unsigned int, enum privsep_procid);
+void proc_shutdown(struct privsep_proc *);
+void proc_sig_handler(int, short, void *);
+int proc_dispatch_null(int, struct privsep_proc *, struct imsg *);
+
+enum privsep_procid privsep_process;
+
+int
+proc_ispeer(struct privsep_proc *procs, unsigned int nproc,
+ enum privsep_procid type)
+{
+ unsigned int i;
+
+ for (i = 0; i < nproc; i++)
+ if (procs[i].p_id == type)
+ return (1);
+
+ return (0);
+}
+
+enum privsep_procid
+proc_getid(struct privsep_proc *procs, unsigned int nproc,
+ const char *proc_name)
+{
+ struct privsep_proc *p;
+ unsigned int proc;
+
+ for (proc = 0; proc < nproc; proc++) {
+ p = &procs[proc];
+ if (strcmp(p->p_title, proc_name))
+ continue;
+
+ return (p->p_id);
+ }
+
+ return (PROC_MAX);
+}
+
+void
+proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+ int argc, char **argv)
+{
+ unsigned int proc, nargc, i, proc_i;
+ char **nargv;
+ struct privsep_proc *p;
+ char num[32];
+ int fd;
+
+ /* Prepare the new process argv. */
+ nargv = calloc(argc + 5, sizeof(char *));
+ if (nargv == NULL)
+ fatal("%s: calloc", __func__);
+
+ /* Copy call argument first. */
+ nargc = 0;
+ nargv[nargc++] = argv[0];
+
+ /* Set process name argument and save the position. */
+ nargv[nargc] = strdup("-P");
+ if (nargv[nargc] == NULL)
+ fatal("%s: strdup", __func__);
+ nargc++;
+ proc_i = nargc;
+ nargc++;
+
+ /* Point process instance arg to stack and copy the original args. */
+ nargv[nargc] = strdup("-I");
+ if (nargv[nargc] == NULL)
+ fatal("%s: strdup", __func__);
+ nargc++;
+ nargv[nargc++] = num;
+ for (i = 1; i < (unsigned int) argc; i++)
+ nargv[nargc++] = argv[i];
+
+ nargv[nargc] = NULL;
+
+ for (proc = 0; proc < nproc; proc++) {
+ p = &procs[proc];
+
+ /* Update args with process title. */
+ nargv[proc_i] = (char *)(uintptr_t)p->p_title;
+
+ /* Fire children processes. */
+ for (i = 0; i < ps->ps_instances[p->p_id]; i++) {
+ /* Update the process instance number. */
+ snprintf(num, sizeof(num), "%u", i);
+
+ fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_GOTWEBD][0];
+ ps->ps_pipes[p->p_id][i].pp_pipes[PROC_GOTWEBD][0] = -1;
+
+ switch (fork()) {
+ case -1:
+ fatal("%s: fork", __func__);
+ break;
+ case 0:
+ /* First create a new session */
+ if (setsid() == -1)
+ fatal("setsid");
+
+ /* Prepare parent socket. */
+ if (fd != PROC_GOTWEBD_SOCK_FILENO) {
+ if (dup2(fd, PROC_GOTWEBD_SOCK_FILENO)
+ == -1)
+ fatal("dup2");
+ } else if (fcntl(fd, F_SETFD, 0) == -1)
+ fatal("fcntl");
+
+ execvp(argv[0], nargv);
+ fatal("%s: execvp", __func__);
+ break;
+ default:
+ /* Close child end. */
+ close(fd);
+ break;
+ }
+ }
+ }
+
+ free(nargv);
+}
+
+void
+proc_connect(struct privsep *ps)
+{
+ struct imsgev *iev;
+ unsigned int src, dst, inst;
+
+ /* Don't distribute any sockets if we are not really going to run. */
+ if (ps->ps_noaction)
+ return;
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ /* We don't communicate with ourselves. */
+ if (dst == PROC_GOTWEBD)
+ continue;
+
+ for (inst = 0; inst < ps->ps_instances[dst]; inst++) {
+ iev = &ps->ps_ievs[dst][inst];
+ imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events,
+ iev->handler, iev->data);
+ event_add(&iev->ev, NULL);
+ }
+ }
+
+ /* Distribute the socketpair()s for everyone. */
+ for (src = 0; src < PROC_MAX; src++)
+ for (dst = src; dst < PROC_MAX; dst++) {
+ /* Parent already distributed its fds. */
+ if (src == PROC_GOTWEBD || dst == PROC_GOTWEBD)
+ continue;
+
+ proc_open(ps, src, dst);
+ }
+}
+
+void
+proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc,
+ int argc, char **argv, enum privsep_procid proc_id)
+{
+ struct privsep_proc *p = NULL;
+ struct privsep_pipes *pa, *pb;
+ unsigned int proc;
+ unsigned int dst;
+ int fds[2];
+
+ /* Don't initiate anything if we are not really going to run. */
+ if (ps->ps_noaction)
+ return;
+
+ if (proc_id == PROC_GOTWEBD) {
+ privsep_process = PROC_GOTWEBD;
+ proc_setup(ps, procs, nproc);
+
+ /*
+ * Create the children sockets so we can use them
+ * to distribute the rest of the socketpair()s using
+ * proc_connect() later.
+ */
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ /* Don't create socket for ourselves. */
+ if (dst == PROC_GOTWEBD)
+ continue;
+
+ for (proc = 0; proc < ps->ps_instances[dst]; proc++) {
+ pa = &ps->ps_pipes[PROC_GOTWEBD][0];
+ pb = &ps->ps_pipes[dst][proc];
+ if (socketpair(AF_UNIX,
+ SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+ PF_UNSPEC, fds) == -1)
+ fatal("%s: socketpair", __func__);
+
+ pa->pp_pipes[dst][proc] = fds[0];
+ pb->pp_pipes[PROC_GOTWEBD][0] = fds[1];
+ }
+ }
+
+ /* Engage! */
+ proc_exec(ps, procs, nproc, argc, argv);
+ return;
+ }
+
+ /* Initialize a child */
+ for (proc = 0; proc < nproc; proc++) {
+ if (procs[proc].p_id != proc_id)
+ continue;
+ p = &procs[proc];
+ break;
+ }
+ if (p == NULL || p->p_init == NULL)
+ fatalx("%s: process %d missing process initialization",
+ __func__, proc_id);
+
+ p->p_init(ps, p);
+
+ fatalx("failed to initiate child process");
+}
+
+void
+proc_accept(struct privsep *ps, int fd, enum privsep_procid dst,
+ unsigned int n)
+{
+ struct privsep_pipes *pp = ps->ps_pp;
+ struct imsgev *iev;
+
+ if (ps->ps_ievs[dst] == NULL) {
+#if DEBUG > 1
+ log_debug("%s: %s src %d %d to dst %d %d not connected",
+ __func__, ps->ps_title[privsep_process],
+ privsep_process, ps->ps_instance + 1,
+ dst, n + 1);
+#endif
+ close(fd);
+ return;
+ }
+
+ if (pp->pp_pipes[dst][n] != -1) {
+ log_warnx("%s: duplicated descriptor", __func__);
+ close(fd);
+ return;
+ } else
+ pp->pp_pipes[dst][n] = fd;
+
+ iev = &ps->ps_ievs[dst][n];
+ imsg_init(&iev->ibuf, fd);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+ event_add(&iev->ev, NULL);
+}
+
+void
+proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc)
+{
+ unsigned int i, j, src, dst, id;
+ struct privsep_pipes *pp;
+
+ /* Initialize parent title, ps_instances and procs. */
+ ps->ps_title[PROC_GOTWEBD] = "parent";
+
+ for (src = 0; src < PROC_MAX; src++)
+ /* Default to 1 process instance */
+ if (ps->ps_instances[src] < 1)
+ ps->ps_instances[src] = 1;
+
+ for (src = 0; src < nproc; src++) {
+ procs[src].p_ps = ps;
+ if (procs[src].p_cb == NULL)
+ procs[src].p_cb = proc_dispatch_null;
+
+ id = procs[src].p_id;
+ ps->ps_title[id] = procs[src].p_title;
+ ps->ps_ievs[id] = calloc(ps->ps_instances[id],
+ sizeof(struct imsgev));
+ if (ps->ps_ievs[id] == NULL)
+ fatal("%s: calloc", __func__);
+
+ /* With this set up, we are ready to call imsg_init(). */
+ for (i = 0; i < ps->ps_instances[id]; i++) {
+ ps->ps_ievs[id][i].handler = proc_dispatch;
+ ps->ps_ievs[id][i].events = EV_READ;
+ ps->ps_ievs[id][i].proc = &procs[src];
+ ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i];
+ }
+ }
+
+ /*
+ * Allocate pipes for all process instances (incl. parent)
+ *
+ * - ps->ps_pipes: N:M mapping
+ * N source processes connected to M destination processes:
+ * [src][instances][dst][instances], for example
+ * [PROC_RELAY][3][PROC_CA][3]
+ *
+ * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
+ * Each process instance has a destination array of socketpair fds:
+ * [dst][instances], for example
+ * [PROC_GOTWEBD][0]
+ */
+ for (src = 0; src < PROC_MAX; src++) {
+ /* Allocate destination array for each process */
+ ps->ps_pipes[src] = calloc(ps->ps_instances[src],
+ sizeof(struct privsep_pipes));
+ if (ps->ps_pipes[src] == NULL)
+ fatal("%s: calloc", __func__);
+
+ for (i = 0; i < ps->ps_instances[src]; i++) {
+ pp = &ps->ps_pipes[src][i];
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ /* Allocate maximum fd integers */
+ pp->pp_pipes[dst] =
+ calloc(ps->ps_instances[dst],
+ sizeof(int));
+ if (pp->pp_pipes[dst] == NULL)
+ fatal("%s: calloc", __func__);
+
+ /* Mark fd as unused */
+ for (j = 0; j < ps->ps_instances[dst]; j++)
+ pp->pp_pipes[dst][j] = -1;
+ }
+ }
+ }
+
+ ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance];
+}
+
+void
+proc_kill(struct privsep *ps)
+{
+ char *cause;
+ pid_t pid;
+ int len, status;
+
+ if (privsep_process != PROC_GOTWEBD)
+ return;
+
+ proc_close(ps);
+
+ do {
+ pid = waitpid(WAIT_ANY, &status, 0);
+ if (pid <= 0)
+ continue;
+
+ if (WIFSIGNALED(status)) {
+ len = asprintf(&cause, "terminated; signal %d",
+ WTERMSIG(status));
+ } else if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0)
+ len = asprintf(&cause, "exited abnormally");
+ else
+ len = 0;
+ } else
+ len = -1;
+
+ if (len == 0) {
+ /* child exited OK, don't print a warning message */
+ } else if (len != -1) {
+ log_warnx("lost child: pid %u %s", pid, cause);
+ free(cause);
+ } else
+ log_warnx("lost child: pid %u", pid);
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+}
+
+void
+proc_open(struct privsep *ps, int src, int dst)
+{
+ struct privsep_pipes *pa, *pb;
+ struct privsep_fd pf;
+ int fds[2];
+ unsigned int i, j;
+
+ /* Exchange pipes between process. */
+ for (i = 0; i < ps->ps_instances[src]; i++) {
+ for (j = 0; j < ps->ps_instances[dst]; j++) {
+ /* Don't create sockets for ourself. */
+ if (src == dst && i == j)
+ continue;
+
+ pa = &ps->ps_pipes[src][i];
+ pb = &ps->ps_pipes[dst][j];
+ if (socketpair(AF_UNIX,
+ SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+ PF_UNSPEC, fds) == -1)
+ fatal("%s: socketpair", __func__);
+
+ pa->pp_pipes[dst][j] = fds[0];
+ pb->pp_pipes[src][i] = fds[1];
+
+ pf.pf_procid = src;
+ pf.pf_instance = i;
+ if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD,
+ -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1)
+ fatal("%s: proc_compose_imsg", __func__);
+
+ pf.pf_procid = dst;
+ pf.pf_instance = j;
+ if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD,
+ -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1)
+ fatal("%s: proc_compose_imsg", __func__);
+
+ /*
+ * We have to flush to send the descriptors and close
+ * them to avoid the fd ramp on startup.
+ */
+ if (proc_flush_imsg(ps, src, i) == -1 ||
+ proc_flush_imsg(ps, dst, j) == -1)
+ fatal("%s: imsg_flush", __func__);
+ }
+ }
+}
+
+void
+proc_close(struct privsep *ps)
+{
+ unsigned int dst, n;
+ struct privsep_pipes *pp;
+
+ if (ps == NULL)
+ return;
+
+ pp = ps->ps_pp;
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ if (ps->ps_ievs[dst] == NULL)
+ continue;
+
+ for (n = 0; n < ps->ps_instances[dst]; n++) {
+ if (pp->pp_pipes[dst][n] == -1)
+ continue;
+
+ /* Cancel the fd, close and invalidate the fd */
+ event_del(&(ps->ps_ievs[dst][n].ev));
+ imsg_clear(&(ps->ps_ievs[dst][n].ibuf));
+ close(pp->pp_pipes[dst][n]);
+ pp->pp_pipes[dst][n] = -1;
+ }
+ free(ps->ps_ievs[dst]);
+ }
+}
+
+void
+proc_shutdown(struct privsep_proc *p)
+{
+ struct privsep *ps = p->p_ps;
+
+ if (p->p_shutdown != NULL)
+ (*p->p_shutdown)();
+
+ proc_close(ps);
+
+ log_info("%s, %s exiting, pid %d", getprogname(), p->p_title, getpid());
+
+ exit(0);
+}
+
+void
+proc_sig_handler(int sig, short event, void *arg)
+{
+ struct privsep_proc *p = arg;
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ proc_shutdown(p);
+ break;
+ case SIGCHLD:
+ case SIGHUP:
+ case SIGPIPE:
+ case SIGUSR1:
+ /* ignore */
+ break;
+ default:
+ fatalx("proc_sig_handler: unexpected signal");
+ /* NOTREACHED */
+ }
+}
+
+void
+proc_run(struct privsep *ps, struct privsep_proc *p,
+ struct privsep_proc *procs, unsigned int nproc,
+ void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg)
+{
+ struct passwd *pw;
+ const char *root;
+
+ log_procinit(p->p_title);
+
+ /* Set the process group of the current process */
+ setpgid(0, 0);
+
+ /* Use non-standard user */
+ if (p->p_pw != NULL)
+ pw = p->p_pw;
+ else
+ pw = ps->ps_pw;
+
+ /* Change root directory */
+ if (p->p_chroot != NULL)
+ root = p->p_chroot;
+ else
+ root = pw->pw_dir;
+
+ if (chroot(root) == -1)
+ fatal("proc_run: chroot");
+ if (chdir("/") == -1)
+ fatal("proc_run: chdir(\"/\")");
+
+ privsep_process = p->p_id;
+
+ setproctitle("%s", p->p_title);
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("proc_run: cannot drop privileges");
+
+ event_init();
+
+ signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p);
+ signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p);
+
+ signal_add(&ps->ps_evsigint, NULL);
+ signal_add(&ps->ps_evsigterm, NULL);
+ signal_add(&ps->ps_evsigchld, NULL);
+ signal_add(&ps->ps_evsighup, NULL);
+ signal_add(&ps->ps_evsigpipe, NULL);
+ signal_add(&ps->ps_evsigusr1, NULL);
+
+ proc_setup(ps, procs, nproc);
+ proc_accept(ps, PROC_GOTWEBD_SOCK_FILENO, PROC_GOTWEBD, 0);
+
+ DPRINTF("%s: %s %d/%d, pid %d", __func__, p->p_title,
+ ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
+
+ if (run != NULL)
+ run(ps, p, arg);
+
+ event_dispatch();
+
+ proc_shutdown(p);
+}
+
+void
+proc_dispatch(int fd, short event, void *arg)
+{
+ struct imsgev *iev = arg;
+ struct privsep_proc *p = iev->proc;
+ struct privsep *ps = p->p_ps;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int verbose;
+ const char *title;
+ struct privsep_fd pf;
+
+ title = ps->ps_title[privsep_process];
+ ibuf = &iev->ibuf;
+
+ if (event & EV_READ) {
+ n = imsg_read(ibuf);
+ if (n == -1 && errno != EAGAIN)
+ fatal("%s: imsg_read", __func__);
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ }
+
+ if (event & EV_WRITE) {
+ n = msgbuf_write(&ibuf->w);
+ if (n == -1 && errno != EAGAIN)
+ fatal("%s: msgbuf_write", __func__);
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ }
+
+ for (;;) {
+ n = imsg_get(ibuf, &imsg);
+ if (n == -1)
+ fatal("%s: imsg_get", __func__);
+ if (n == 0)
+ break;
+
+#if DEBUG > 1
+ log_debug("%s: %s %d got imsg %d peerid %d from %s %d",
+ __func__, title, ps->ps_instance + 1,
+ imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid);
+#endif
+
+ /*
+ * Check the message with the program callback
+ */
+ if ((p->p_cb)(fd, p, &imsg) == 0) {
+ /* Message was handled by the callback, continue */
+ imsg_free(&imsg);
+ continue;
+ }
+
+ /*
+ * Generic message handling
+ */
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_VERBOSE:
+ log_info("%s", __func__);
+ IMSG_SIZE_CHECK(&imsg, &verbose);
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ case IMSG_CTL_PROCFD:
+ IMSG_SIZE_CHECK(&imsg, &pf);
+ memcpy(&pf, imsg.data, sizeof(pf));
+ proc_accept(ps, imsg.fd, pf.pf_procid,
+ pf.pf_instance);
+ break;
+ default:
+ log_warnx("%s: %s %d got invalid imsg %d peerid %d "
+ "from %s %d",
+ __func__, title, ps->ps_instance + 1,
+ imsg.hdr.type, imsg.hdr.peerid,
+ p->p_title, imsg.hdr.pid);
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(iev);
+}
+
+int
+proc_dispatch_null(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ return (-1);
+}
+
+/*
+ * imsg helper functions
+ */
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ if (iev->handler == NULL) {
+ imsg_flush(&iev->ibuf);
+ return;
+ }
+
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+ pid_t pid, int fd, void *data, uint16_t datalen)
+{
+ int ret;
+
+ ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
+ if (ret == -1)
+ return (ret);
+ imsg_event_add(iev);
+ return (ret);
+}
+
+int
+imsg_composev_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+ pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+ int ret;
+
+ ret = imsg_composev(&iev->ibuf, type, peerid, pid, fd, iov, iovcnt);
+ if (ret == -1)
+ return (ret);
+ imsg_event_add(iev);
+ return (ret);
+}
+
+void
+proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m)
+{
+ if (*n == -1) {
+ /* Use a range of all target instances */
+ *n = 0;
+ *m = ps->ps_instances[id];
+ } else {
+ /* Use only a single slot of the specified peer process */
+ *m = *n + 1;
+ }
+}
+
+int
+proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n,
+ uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ for (; n < m; n++) {
+ if (imsg_compose_event(&ps->ps_ievs[id][n],
+ type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+proc_compose(struct privsep *ps, enum privsep_procid id,
+ uint16_t type, void *data, uint16_t datalen)
+{
+ return (proc_compose_imsg(ps, id, -1, type, -1, -1, data, datalen));
+}
+
+int
+proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n,
+ uint16_t type, uint32_t peerid, int fd, const struct iovec *iov, int iovcnt)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ for (; n < m; n++)
+ if (imsg_composev_event(&ps->ps_ievs[id][n],
+ type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+proc_composev(struct privsep *ps, enum privsep_procid id,
+ uint16_t type, const struct iovec *iov, int iovcnt)
+{
+ return (proc_composev_imsg(ps, id, -1, type, -1, -1, iov, iovcnt));
+}
+
+int
+proc_forward_imsg(struct privsep *ps, struct imsg *imsg,
+ enum privsep_procid id, int n)
+{
+ return (proc_compose_imsg(ps, id, n, imsg->hdr.type,
+ imsg->hdr.peerid, imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg)));
+}
+
+struct imsgbuf *
+proc_ibuf(struct privsep *ps, enum privsep_procid id, int n)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ return (&ps->ps_ievs[id][n].ibuf);
+}
+
+struct imsgev *
+proc_iev(struct privsep *ps, enum privsep_procid id, int n)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ return (&ps->ps_ievs[id][n]);
+}
+
+/* This function should only be called with care as it breaks async I/O */
+int
+proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n)
+{
+ struct imsgbuf *ibuf;
+ int m, ret = 0;
+
+ proc_range(ps, id, &n, &m);
+ for (; n < m; n++) {
+ ibuf = proc_ibuf(ps, id, n);
+ if (ibuf == NULL)
+ return (-1);
+ do {
+ ret = imsg_flush(ibuf);
+ } while (ret == -1 && errno == EAGAIN);
+ if (ret == -1)
+ break;
+ imsg_event_add(&ps->ps_ievs[id][n]);
+ }
+
+ return (ret);
+}
blob - /dev/null
blob + 524112fcf4b0c72a25c6f5a4cb85c16bd33ef3a9 (mode 644)
--- /dev/null
+++ gotwebd/proc.h
+/*
+ * Copyright (c) 2010-2015 Reyk Floeter <reyk@openbsd.org>
+ *
+ * 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.
+ */
+
+enum {
+ IMSG_NONE,
+ IMSG_CTL_OK,
+ IMSG_CTL_FAIL,
+ IMSG_CTL_VERBOSE,
+ IMSG_CTL_NOTIFY,
+ IMSG_CTL_RESET,
+ IMSG_CTL_PROCFD,
+ IMSG_PROC_MAX
+};
+
+/* imsg */
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ struct privsep_proc *proc;
+ void *data;
+ short events;
+};
+
+#define IMSG_SIZE_CHECK(imsg, p) do { \
+ if (IMSG_DATA_SIZE(imsg) < sizeof(*p)) \
+ fatalx("bad length imsg received (%s)", #p); \
+} while (0)
+#define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE)
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ uint8_t flags;
+ unsigned int waiting;
+#define CTL_CONN_NOTIFY 0x01
+ struct imsgev iev;
+ uid_t uid;
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+extern struct ctl_connlist ctl_conns;
+
+/* privsep */
+enum privsep_procid {
+ PROC_GOTWEBD = 0,
+ PROC_SOCKS,
+ PROC_MAX,
+};
+extern enum privsep_procid privsep_process;
+
+#define CONFIG_RELOAD 0x00
+#define CONFIG_SOCKS 0x01
+#define CONFIG_ALL 0xff
+
+struct privsep_pipes {
+ int *pp_pipes[PROC_MAX];
+};
+
+struct privsep {
+ struct privsep_pipes *ps_pipes[PROC_MAX];
+ struct privsep_pipes *ps_pp;
+
+ struct imsgev *ps_ievs[PROC_MAX];
+ const char *ps_title[PROC_MAX];
+ uint8_t ps_what[PROC_MAX];
+
+ struct passwd *ps_pw;
+ int ps_noaction;
+
+ unsigned int ps_instances[PROC_MAX];
+ unsigned int ps_instance;
+
+ /* Event and signal handlers */
+ struct event ps_evsigint;
+ struct event ps_evsigterm;
+ struct event ps_evsigchld;
+ struct event ps_evsighup;
+ struct event ps_evsigpipe;
+ struct event ps_evsigusr1;
+
+ void *ps_env;
+};
+
+struct privsep_proc {
+ const char *p_title;
+ enum privsep_procid p_id;
+ int (*p_cb)(int, struct privsep_proc *,
+ struct imsg *);
+ void (*p_init)(struct privsep *,
+ struct privsep_proc *);
+ void (*p_shutdown)(void);
+ const char *p_chroot;
+ struct passwd *p_pw;
+ struct privsep *p_ps;
+};
+
+struct privsep_fd {
+ enum privsep_procid pf_procid;
+ unsigned int pf_instance;
+};
+
+#if DEBUG
+#define DPRINTF log_debug
+#else
+#define DPRINTF(x...) do {} while(0)
+#endif
+
+#define PROC_GOTWEBD_SOCK_FILENO 3
+#define PROC_MAX_INSTANCES 32
+
+/* proc.c */
+void proc_init(struct privsep *, struct privsep_proc *, unsigned int,
+ int, char **, enum privsep_procid);
+void proc_kill(struct privsep *);
+void proc_connect(struct privsep *ps);
+void proc_dispatch(int, short event, void *);
+void proc_range(struct privsep *, enum privsep_procid, int *, int *);
+void proc_run(struct privsep *, struct privsep_proc *,
+ struct privsep_proc *, unsigned int,
+ void (*)(struct privsep *, struct privsep_proc *, void *), void *);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
+ pid_t, int, void *, uint16_t);
+int imsg_composev_event(struct imsgev *, uint16_t, uint32_t,
+ pid_t, int, const struct iovec *, int);
+int proc_compose_imsg(struct privsep *, enum privsep_procid, int,
+ uint16_t, uint32_t, int, void *, uint16_t);
+int proc_compose(struct privsep *, enum privsep_procid,
+ uint16_t, void *data, uint16_t);
+int proc_composev_imsg(struct privsep *, enum privsep_procid, int,
+ uint16_t, uint32_t, int, const struct iovec *, int);
+int proc_composev(struct privsep *, enum privsep_procid,
+ uint16_t, const struct iovec *, int);
+int proc_forward_imsg(struct privsep *, struct imsg *,
+ enum privsep_procid, int);
+struct imsgbuf *
+ proc_ibuf(struct privsep *, enum privsep_procid, int);
+struct imsgev *
+ proc_iev(struct privsep *, enum privsep_procid, int);
+enum privsep_procid
+ proc_getid(struct privsep_proc *, unsigned int, const char *);
+int proc_flush_imsg(struct privsep *, enum privsep_procid, int);
+
+/* log.c */
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
blob - /dev/null
blob + f07a3be3b97818f5ddfc016d4f5b93952636de34 (mode 644)
--- /dev/null
+++ gotwebd/sockets.c
+/*
+ * Copyright (c) 2016, 2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
+ * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netdb.h>
+#include <poll.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "got_error.h"
+#include "got_opentemp.h"
+
+#include "proc.h"
+#include "gotwebd.h"
+
+#define SOCKS_BACKLOG 5
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
+
+volatile int client_cnt;
+
+struct timeval timeout = { TIMEOUT_DEFAULT, 0 };
+
+void sockets_sighdlr(int, short, void *);
+void sockets_run(struct privsep *, struct privsep_proc *, void *);
+void sockets_launch(void);
+void sockets_purge(struct gotwebd *);
+void sockets_accept_paused(int, short, void *);
+void sockets_dup_new_socket(struct socket *, struct socket *);
+void sockets_rlimit(int);
+
+
+int sockets_dispatch_gotwebd(int, struct privsep_proc *, struct imsg *);
+int sockets_unix_socket_listen(struct privsep *, struct socket *);
+int sockets_create_socket(struct addresslist *, in_port_t);
+int sockets_socket_af(struct sockaddr_storage *, in_port_t);
+int sockets_accept_reserve(int, struct sockaddr *, socklen_t *, int,
+ volatile int *);
+
+struct socket
+ *sockets_conf_new_socket(struct gotwebd *, struct server *, int, int,
+ int);
+
+int cgi_inflight = 0;
+
+static struct privsep_proc procs[] = {
+ { "gotwebd", PROC_GOTWEBD, sockets_dispatch_gotwebd },
+};
+
+void
+sockets(struct privsep *ps, struct privsep_proc *p)
+{
+ proc_run(ps, p, procs, nitems(procs), sockets_run, NULL);
+}
+
+void
+sockets_run(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+ if (config_init(ps->ps_env) == -1)
+ fatal("failed to initialize configuration");
+
+ p->p_shutdown = sockets_shutdown;
+
+ sockets_rlimit(-1);
+
+ signal_del(&ps->ps_evsigchld);
+ signal_set(&ps->ps_evsigchld, SIGCHLD, sockets_sighdlr, ps);
+ signal_add(&ps->ps_evsigchld, NULL);
+
+#ifndef PROFILE
+ if (pledge("stdio rpath wpath cpath inet recvfd proc exec sendfd",
+ NULL) == -1)
+ fatal("pledge");
+#endif
+}
+
+void
+sockets_parse_sockets(struct gotwebd *env)
+{
+ struct server *srv;
+ struct socket *sock, *new_sock = NULL;
+ struct address *a;
+ int sock_id = 0, ipv4 = 0, ipv6 = 0;
+
+ TAILQ_FOREACH(srv, env->servers, entry) {
+ if (srv->unix_socket) {
+ sock_id++;
+ new_sock = sockets_conf_new_socket(env, srv,
+ sock_id, UNIX, 0);
+ TAILQ_INSERT_TAIL(env->sockets, new_sock, entry);
+ }
+
+ if (srv->fcgi_socket) {
+ sock_id++;
+ new_sock = sockets_conf_new_socket(env, srv,
+ sock_id, FCGI, 0);
+ TAILQ_INSERT_TAIL(env->sockets, new_sock, entry);
+
+ /* add ipv6 children */
+ TAILQ_FOREACH(sock, env->sockets, entry) {
+ ipv4 = ipv6 = 0;
+
+ TAILQ_FOREACH(a, sock->conf.al, entry) {
+ if (a->ss.ss_family == AF_INET)
+ ipv4 = 1;
+ if (a->ss.ss_family == AF_INET6)
+ ipv6 = 1;
+ }
+
+ /* create ipv6 sock */
+ if (ipv4 == 1 && ipv6 == 1) {
+ sock_id++;
+ sock->conf.child_id = sock_id;
+ new_sock = sockets_conf_new_socket(env,
+ srv, sock_id, FCGI, 1);
+ sockets_dup_new_socket(sock, new_sock);
+ TAILQ_INSERT_TAIL(env->sockets,
+ new_sock, entry);
+ continue;
+ }
+ }
+ }
+ }
+}
+
+void
+sockets_dup_new_socket(struct socket *p_sock, struct socket *sock)
+{
+ struct address *a, *acp;
+ int n;
+
+ sock->conf.parent_id = p_sock->conf.id;
+ sock->conf.ipv4 = 0;
+ sock->conf.ipv6 = 1;
+
+ memcpy(&sock->conf.srv_name, p_sock->conf.srv_name,
+ sizeof(sock->conf.srv_name));
+
+ n = snprintf(sock->conf.name, GOTWEBD_MAXTEXT, "%s_child",
+ p_sock->conf.srv_name);
+ if (n < 0) {
+ free(p_sock->conf.al);
+ free(p_sock);
+ free(sock->conf.al);
+ free(sock);
+ fatalx("%s: snprintf", __func__);
+ }
+
+ TAILQ_FOREACH(a, p_sock->conf.al, entry) {
+ if (a->ss.ss_family == AF_INET)
+ continue;
+
+ if ((acp = calloc(1, sizeof(*acp))) == NULL)
+ fatal("%s: calloc", __func__);
+ memcpy(&acp->ss, &a->ss, sizeof(acp->ss));
+ acp->ipproto = a->ipproto;
+ acp->prefixlen = a->prefixlen;
+ acp->port = a->port;
+ if (strlen(a->ifname) != 0) {
+ if (strlcpy(acp->ifname, a->ifname,
+ sizeof(acp->ifname)) >= sizeof(acp->ifname)) {
+ fatalx("%s: interface name truncated",
+ __func__);
+ }
+ }
+
+ TAILQ_INSERT_TAIL(sock->conf.al, acp, entry);
+ }
+}
+
+struct socket *
+sockets_conf_new_socket(struct gotwebd *env, struct server *srv, int id,
+ int type, int is_dup)
+{
+ struct socket *sock;
+ struct address *a, *acp;
+ int n;
+
+ if ((sock = calloc(1, sizeof(*sock))) == NULL)
+ fatalx("%s: calloc", __func__);
+
+ if ((sock->conf.al = calloc(1, sizeof(*sock->conf.al))) == NULL) {
+ free(sock);
+ fatalx("%s: calloc", __func__);
+ }
+ TAILQ_INIT(sock->conf.al);
+
+ sock->conf.parent_id = 0;
+ sock->conf.id = id;
+
+ sock->fd = -1;
+
+ sock->conf.type = type;
+
+ if (type == UNIX) {
+ if (strlcpy(sock->conf.unix_socket_name,
+ srv->unix_socket_name,
+ sizeof(sock->conf.unix_socket_name)) >=
+ sizeof(sock->conf.unix_socket_name)) {
+ free(sock->conf.al);
+ free(sock);
+ fatalx("%s: strlcpy", __func__);
+ }
+ } else
+ sock->conf.ipv4 = 1;
+
+ sock->conf.fcgi_socket_port = srv->fcgi_socket_port;
+
+ if (is_dup)
+ goto done;
+
+ n = snprintf(sock->conf.name, GOTWEBD_MAXTEXT, "%s_parent",
+ srv->name);
+ if (n < 0) {
+ free(sock->conf.al);
+ free(sock);
+ fatalx("%s: snprintf", __func__);
+ }
+
+ if (strlcpy(sock->conf.srv_name, srv->name,
+ sizeof(sock->conf.srv_name)) >= sizeof(sock->conf.srv_name)) {
+ free(sock->conf.al);
+ free(sock);
+ fatalx("%s: strlcpy", __func__);
+ }
+
+ TAILQ_FOREACH(a, srv->al, entry) {
+ if ((acp = calloc(1, sizeof(*acp))) == NULL) {
+ free(sock->conf.al);
+ free(sock);
+ fatal("%s: calloc", __func__);
+ }
+ memcpy(&acp->ss, &a->ss, sizeof(acp->ss));
+ acp->ipproto = a->ipproto;
+ acp->prefixlen = a->prefixlen;
+ acp->port = a->port;
+ if (strlen(a->ifname) != 0) {
+ if (strlcpy(acp->ifname, a->ifname,
+ sizeof(acp->ifname)) >= sizeof(acp->ifname)) {
+ fatalx("%s: interface name truncated",
+ __func__);
+ }
+ }
+
+ TAILQ_INSERT_TAIL(sock->conf.al, acp, entry);
+ }
+done:
+ return (sock);
+}
+
+int
+sockets_socket_af(struct sockaddr_storage *ss, in_port_t port)
+{
+ switch (ss->ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)ss)->sin_port = port;
+ ((struct sockaddr_in *)ss)->sin_len =
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)ss)->sin6_port = port;
+ ((struct sockaddr_in6 *)ss)->sin6_len =
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+sockets_launch(void)
+{
+ struct socket *sock;
+
+ TAILQ_FOREACH(sock, gotwebd_env->sockets, entry) {
+ log_debug("%s: configuring socket %d (%d)", __func__,
+ sock->conf.id, sock->fd);
+
+ event_set(&sock->ev, sock->fd, EV_READ | EV_PERSIST,
+ sockets_socket_accept, sock);
+
+ if (event_add(&sock->ev, NULL))
+ fatalx("event add sock");
+
+ evtimer_set(&sock->pause, sockets_accept_paused, sock);
+
+ log_debug("%s: running socket listener %d", __func__,
+ sock->conf.id);
+ }
+}
+
+void
+sockets_purge(struct gotwebd *env)
+{
+ struct socket *sock, *tsock;
+
+ /* shutdown and remove sockets */
+ TAILQ_FOREACH_SAFE(sock, env->sockets, entry, tsock) {
+ if (event_initialized(&sock->ev))
+ event_del(&sock->ev);
+ if (evtimer_initialized(&sock->evt))
+ evtimer_del(&sock->evt);
+ if (evtimer_initialized(&sock->pause))
+ evtimer_del(&sock->pause);
+ if (sock->fd != -1)
+ close(sock->fd);
+ TAILQ_REMOVE(env->sockets, sock, entry);
+ }
+}
+
+int
+sockets_dispatch_gotwebd(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct privsep *ps = p->p_ps;
+ int res = 0, cmd = 0, verbose;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CFG_SRV:
+ config_getserver(gotwebd_env, imsg);
+ break;
+ case IMSG_CFG_SOCK:
+ config_getsock(gotwebd_env, imsg);
+ break;
+ case IMSG_CFG_FD:
+ config_getfd(gotwebd_env, imsg);
+ break;
+ case IMSG_CFG_DONE:
+ config_getcfg(gotwebd_env, imsg);
+ break;
+ case IMSG_CTL_START:
+ sockets_launch();
+ break;
+ case IMSG_CTL_VERBOSE:
+ IMSG_SIZE_CHECK(imsg, &verbose);
+ memcpy(&verbose, imsg->data, sizeof(verbose));
+ log_setverbose(verbose);
+ break;
+ default:
+ return -1;
+ }
+
+ switch (cmd) {
+ case 0:
+ break;
+ default:
+ if (proc_compose_imsg(ps, PROC_GOTWEBD, -1, cmd,
+ imsg->hdr.peerid, -1, &res, sizeof(res)) == -1)
+ return -1;
+ break;
+ }
+
+ return 0;
+}
+
+void
+sockets_sighdlr(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGHUP:
+ log_info("%s: ignoring SIGHUP", __func__);
+ break;
+ case SIGPIPE:
+ log_info("%s: ignoring SIGPIPE", __func__);
+ break;
+ case SIGUSR1:
+ log_info("%s: ignoring SIGUSR1", __func__);
+ break;
+ case SIGCHLD:
+ break;
+ default:
+ log_info("SIGNAL: %d", sig);
+ fatalx("unexpected signal");
+ }
+}
+
+void
+sockets_shutdown(void)
+{
+ struct server *srv, *tsrv;
+ struct socket *sock, *tsock;
+
+ sockets_purge(gotwebd_env);
+
+ /* clean sockets */
+ TAILQ_FOREACH_SAFE(sock, gotwebd_env->sockets, entry, tsock) {
+ TAILQ_REMOVE(gotwebd_env->sockets, sock, entry);
+ close(sock->fd);
+ free(sock);
+ }
+
+ /* clean servers */
+ TAILQ_FOREACH_SAFE(srv, gotwebd_env->servers, entry, tsrv)
+ free(srv);
+
+ free(gotwebd_env->sockets);
+ free(gotwebd_env->servers);
+ free(gotwebd_env);
+}
+
+int
+sockets_privinit(struct gotwebd *env, struct socket *sock)
+{
+ struct privsep *ps = env->gotwebd_ps;
+
+ if (sock->conf.type == UNIX) {
+ log_debug("%s: initializing unix socket %s", __func__,
+ sock->conf.unix_socket_name);
+ sock->fd = sockets_unix_socket_listen(ps, sock);
+ if (sock->fd == -1) {
+ log_warnx("%s: create unix socket failed", __func__);
+ return -1;
+ }
+ }
+
+ if (sock->conf.type == FCGI) {
+ log_debug("%s: initializing fcgi socket for %s", __func__,
+ sock->conf.name);
+ sock->fd = sockets_create_socket(sock->conf.al,
+ sock->conf.fcgi_socket_port);
+ if (sock->fd == -1) {
+ log_warnx("%s: create unix socket failed", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+sockets_unix_socket_listen(struct privsep *ps, struct socket *sock)
+{
+ struct gotwebd *env = ps->ps_env;
+ struct sockaddr_un sun;
+ struct socket *tsock;
+ int u_fd = -1;
+ mode_t old_umask, mode;
+
+ TAILQ_FOREACH(tsock, env->sockets, entry) {
+ if (strcmp(tsock->conf.unix_socket_name,
+ sock->conf.unix_socket_name) == 0 &&
+ tsock->fd != -1)
+ return (tsock->fd);
+ }
+
+ u_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK| SOCK_CLOEXEC, 0);
+ if (u_fd == -1) {
+ log_warn("%s: socket", __func__);
+ return -1;
+ }
+
+ sun.sun_family = AF_UNIX;
+ if (strlcpy(sun.sun_path, sock->conf.unix_socket_name,
+ sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
+ log_warn("%s: %s name too long", __func__,
+ sock->conf.unix_socket_name);
+ close(u_fd);
+ return -1;
+ }
+
+ if (unlink(sock->conf.unix_socket_name) == -1) {
+ if (errno != ENOENT) {
+ log_warn("%s: unlink %s", __func__,
+ 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;
+
+ if (bind(u_fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("%s: bind: %s", __func__, 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("%s: chmod", __func__);
+ close(u_fd);
+ (void)unlink(sock->conf.unix_socket_name);
+ return -1;
+ }
+
+ if (chown(sock->conf.unix_socket_name, ps->ps_pw->pw_uid,
+ ps->ps_pw->pw_gid) == -1) {
+ log_warn("%s: chown", __func__);
+ close(u_fd);
+ (void)unlink(sock->conf.unix_socket_name);
+ return -1;
+ }
+
+ if (listen(u_fd, SOCKS_BACKLOG) == -1) {
+ log_warn("%s: listen", __func__);
+ return -1;
+ }
+
+ return u_fd;
+}
+
+int
+sockets_create_socket(struct addresslist *al, in_port_t port)
+{
+ struct addrinfo hints;
+ struct address *a;
+ int fd = -1, o_val = 1, flags;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= AI_PASSIVE;
+
+ TAILQ_FOREACH(a, al, entry) {
+ if (sockets_socket_af(&a->ss, port) == -1) {
+ log_warnx("%s: sockets_socket_af", __func__);
+ goto fail;
+ }
+
+ fd = socket(a->ss.ss_family, hints.ai_socktype,
+ a->ipproto);
+ log_debug("%s: opening socket (%d) for %s", __func__,
+ fd, a->ifname);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &o_val,
+ sizeof(int)) == -1) {
+ log_warn("%s: setsockopt error", __func__);
+ return -1;
+ }
+
+ /* non-blocking */
+ flags = fcntl(fd, F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(fd, F_SETFL, flags);
+
+ if (bind(fd, (struct sockaddr *)&a->ss, a->ss.ss_len) == -1) {
+ close(fd);
+ log_info("%s: can't bind to port %d", __func__,
+ ntohs(port));
+ goto fail;
+ }
+
+ if (listen(fd, SOMAXCONN) == -1) {
+ log_warn("%s, unable to listen on socket", __func__);
+ goto fail;
+ }
+ }
+
+ free(a);
+ return (fd);
+fail:
+ free(a);
+ return -1;
+}
+
+int
+sockets_accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+ int reserve, volatile int *counter)
+{
+ int ret;
+
+ if (getdtablecount() + reserve +
+ ((*counter + 1) * FD_NEEDED) >= getdtablesize()) {
+ log_debug("inflight fds exceeded");
+ errno = EMFILE;
+ return -1;
+ }
+
+ if ((ret = accept4(sockfd, addr, addrlen,
+ SOCK_NONBLOCK | SOCK_CLOEXEC)) > -1) {
+ (*counter)++;
+ log_debug("inflight incremented, now %d", *counter);
+ }
+
+ return ret;
+}
+
+void
+sockets_accept_paused(int fd, short events, void *arg)
+{
+ struct socket *sock = (struct socket *)arg;
+
+ event_add(&sock->ev, NULL);
+}
+
+void
+sockets_socket_accept(int fd, short event, void *arg)
+{
+ struct socket *sock = (struct socket *)arg;
+ struct sockaddr_storage ss;
+ struct timeval backoff;
+ struct request *c = NULL;
+ socklen_t len;
+ int s;
+
+ backoff.tv_sec = 1;
+ backoff.tv_usec = 0;
+
+ event_add(&sock->ev, NULL);
+ if (event & EV_TIMEOUT)
+ return;
+
+ len = sizeof(ss);
+
+ s = sockets_accept_reserve(fd, (struct sockaddr *)&ss, &len,
+ FD_RESERVE, &cgi_inflight);
+
+ if (s == -1) {
+ switch (errno) {
+ case EINTR:
+ case EWOULDBLOCK:
+ case ECONNABORTED:
+ return;
+ case EMFILE:
+ case ENFILE:
+ event_del(&sock->ev);
+ evtimer_add(&sock->pause, &backoff);
+ return;
+ default:
+ log_warn("%s: accept", __func__);
+ }
+ }
+
+ if (client_cnt > GOTWEBD_MAXCLIENTS)
+ goto err;
+
+ c = calloc(1, sizeof(struct request));
+ if (c == NULL) {
+ log_warn("%s", __func__);
+ close(s);
+ cgi_inflight--;
+ return;
+ }
+
+ c->fd = s;
+ c->sock = sock;
+ memcpy(c->priv_fd, sock->priv_fd, sizeof(c->priv_fd));
+ c->buf_pos = 0;
+ c->buf_len = 0;
+ c->request_started = 0;
+ c->sock->client_status = CLIENT_CONNECT;
+
+ event_set(&c->ev, s, EV_READ, fcgi_request, c);
+ event_add(&c->ev, NULL);
+
+ evtimer_set(&c->tmo, fcgi_timeout, c);
+ evtimer_add(&c->tmo, &timeout);
+
+ client_cnt++;
+
+ return;
+err:
+ cgi_inflight--;
+ close(s);
+ if (c != NULL)
+ free(c);
+}
+
+void
+sockets_rlimit(int maxfd)
+{
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
+ fatal("%s: failed to get resource limit", __func__);
+ log_debug("%s: max open files %llu", __func__, rl.rlim_max);
+
+ /*
+ * Allow the maximum number of open file descriptors for this
+ * login class (which should be the class "daemon" by default).
+ */
+ if (maxfd == -1)
+ rl.rlim_cur = rl.rlim_max;
+ else
+ rl.rlim_cur = MAXIMUM(rl.rlim_max, (rlim_t)maxfd);
+ if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
+ fatal("%s: failed to set resource limit", __func__);
+}
\ No newline at end of file