Commit Diff


commit - 760009951357d4c36991c4c6a62db973289b32d9
commit + 0126d91d1d80d7d8e794b2176556fce969f165cd
blob - fbd205a5ee924d99d4221103c422aeda0a627ca3
blob + 012b7b0cab4f2b3da8c62d1c82892e78d923c0a6
--- .gitignore
+++ .gitignore
@@ -3,6 +3,7 @@
 TAGS
 gmid
 gg
+ge
 *.d
 *.o
 *.swp
blob - 60de30f62c00339378a1517d80cd9c6c074a21c3
blob + be1a7445e98c4877f502b9b9cb3cf912078ec9c5
--- Makefile
+++ Makefile
@@ -78,6 +78,21 @@ GMID_SRCS =	dirs.c \
 
 GMID_OBJS =	${GMID_SRCS:.c=.o} ${COBJS}
 
+GE_SRCS =	dirs.c \
+		fcgi.c \
+		ge.c \
+		iri.c \
+		log.c \
+		mime.c \
+		proxy.c \
+		puny.c \
+		sandbox.c \
+		server.c \
+		utf8.c \
+		utils.c
+
+GE_OBJS =	${GE_SRCS:.c=.o} ${COBJS}
+
 GG_SRCS =	gg.c \
 		iri.c \
 		utf8.c
@@ -88,6 +103,7 @@ SRCS =		gmid.h \
 		landlock_shim.h \
 		parse.y \
 		${GMID_SRCS} \
+		${GE_SRCS} \
 		${GG_SRCS}
 
 REGRESSFILES =	regress/Makefile \
@@ -142,7 +158,7 @@ DISTFILES =	${EXTRAS} \
 
 DISTNAME =	gmid-${VERSION}
 
-all: Makefile.local gmid gg
+all: Makefile.local gmid ge gg
 .PHONY: all static clean cleanall test regress install
 
 Makefile.local config.h: configure ${TESTSRCS}
@@ -158,15 +174,19 @@ y.tab.c: parse.y
 gmid: ${GMID_OBJS}
 	${CC} ${GMID_OBJS} -o $@ ${LDFLAGS}
 
+ge: ${GE_OBJS}
+	${CC} ${GE_OBJS} -o $@ ${LDFLAGS}
+
 gg: ${GG_OBJS}
 	${CC} ${GG_OBJS} -o $@ ${LDFLAGS}
 
-static: ${GMID_OBJS} ${GG_OBJS}
+static: ${GMID_OBJS} ${GE_OBJS} ${GG_OBJS}
 	${CC} ${GMID_OBJS} -o gmid ${LDFLAGS} ${STATIC}
+	${CC} ${GG_OBJS} -o ge ${LDFLAGS} ${STATIC}
 	${CC} ${GG_OBJS} -o gg ${LDFLAGS} ${STATIC}
 
 clean:
-	rm -f *.o compat/*.o y.tab.c y.tab.h y.output gmid gg
+	rm -f *.o compat/*.o y.tab.c y.tab.h y.output gmid ge gg
 	rm -f compile_flags.txt
 	${MAKE} -C regress clean
 
@@ -185,6 +205,7 @@ install: gmid gg
 	${INSTALL_PROGRAM} gg ${DESTDIR}${BINDIR}
 	${INSTALL_MAN} gmid.1 ${DESTDIR}${MANDIR}/man1
 	${INSTALL_MAN} gmid.conf.5 ${DESTDIR}${MANDIR}/man5
+	${INSTALL_MAN} ge.1 ${DESTDIR}${MANDIR}/man1
 	${INSTALL_MAN} gg.1 ${DESTDIR}${MANDIR}/man1
 
 uninstall:
blob - /dev/null
blob + b3eb5620782ad537e3d938806bda094b61d3fbc3 (mode 644)
--- /dev/null
+++ ge.1
@@ -0,0 +1,87 @@
+.\" Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+.\"
+.\" 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 September 7, 2022
+.Dt GE 1
+.Os
+.Sh NAME
+.Nm ge
+.Nd export a directory over Gemini
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl hV
+.Op Fl d Ar certs-dir
+.Op Fl H Ar hostname
+.Op Fl p Ar port
+.Op Ar directory
+.Ek
+.Sh DESCRIPTION
+.Nm
+exports the given
+.Ar directory
+over the Gemini protocol.
+It's intended to be used interactively mostly for testing purposes,
+for a full-fledged daemon look for
+.Xr gmid 8 .
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl d Ar certs-path
+Directory where certificates are stored.
+By default is
+.Pa $XDG_DATA_HOME/gmid ,
+i.e.\&
+.Pa ~/.local/share/gmid .
+.It Fl H Ar hostname
+The
+.Ar hostname
+to use,
+.Ar localhost
+by default.
+Certificates for the given
+.Ar hostname
+are searched inside the
+.Ar certs-dir
+specified with the
+.Fl d
+option.
+The certificate files are named
+.Ar hostname Ns .pem
+and
+.Ar hostname Ns .key
+and are implicitly generated if not found.
+.It Fl h , Fl -help
+Print the usage and exit.
+.It Fl p Ar port
+The port to bind to, 1965 by default.
+.It Fl V , Fl -version
+Print the version and exit.
+.It Ar directory
+The root directory to serve, or the current working directory if not
+specified.
+.El
+.Sh SEE ALSO
+.Xr gmid 8
+.Sh ACKNOWLEDGEMENTS
+.Nm
+uses the
+.Dq Flexible and Economical
+UTF-8 decoder written by
+.An Bjoern Hoehrmann .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Omar Polo Aq Mt op@omarpolo.com .
blob - /dev/null
blob + ec351845b67a362462cb86460ed2403664fff75c (mode 644)
--- /dev/null
+++ ge.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ *
+ * 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 "gmid.h"
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+struct imsgbuf ibuf, logibuf;
+struct conf conf;
+
+struct fcgi fcgi[FCGI_MAX];	/* just because it's referenced */
+struct vhosthead hosts;
+
+
+static const struct option opts[] = {
+	{"help",	no_argument,	NULL,	'h'},
+	{"version",	no_argument,	NULL,	'V'},
+	{NULL,		0,		NULL,	0},
+};
+
+void
+load_local_cert(struct vhost *h, const char *hostname, const char *dir)
+{
+	char *cert, *key;
+
+	if (asprintf(&cert, "%s/%s.cert.pem", dir, hostname) == -1)
+		errx(1, "asprintf");
+	if (asprintf(&key, "%s/%s.key.pem", dir, hostname) == -1)
+		errx(1, "asprintf");
+
+	if (access(cert, R_OK) == -1 || access(key, R_OK) == -1)
+		gen_certificate(hostname, cert, key);
+
+	h->cert = cert;
+	h->key = key;
+	h->domain = hostname;
+}
+
+/* wrapper around dirname(3).  dn must be PATH_MAX+1 at least. */
+static void
+pdirname(const char *path, char *dn)
+{
+	char	 p[PATH_MAX+1];
+	char	*t;
+
+	strlcpy(p, path, sizeof(p));
+	t = dirname(p);
+	memmove(dn, t, strlen(t)+1);
+}
+
+static void
+mkdirs(const char *path, mode_t mode)
+{
+	char	dname[PATH_MAX+1];
+
+	pdirname(path, dname);
+	if (!strcmp(dname, "/"))
+		return;
+	mkdirs(dname, mode);
+	if (mkdir(path, mode) != 0 && errno != EEXIST)
+		fatal("can't mkdir %s: %s", path, strerror(errno));
+}
+
+/* $XDG_DATA_HOME/gmid */
+char *
+data_dir(void)
+{
+	const char *home, *xdg;
+	char *t;
+
+	if ((xdg = getenv("XDG_DATA_HOME")) == NULL) {
+		if ((home = getenv("HOME")) == NULL)
+			errx(1, "XDG_DATA_HOME and HOME both empty");
+		if (asprintf(&t, "%s/.local/share/gmid", home) == -1)
+			err(1, "asprintf");
+	} else {
+		if (asprintf(&t, "%s/gmid", xdg) == -1)
+			err(1, "asprintf");
+	}
+
+	mkdirs(t, 0755);
+	return t;
+}
+
+static void
+logger_init(void)
+{
+	int p[2];
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
+		err(1, "socketpair");
+
+	switch (fork()) {
+	case -1:
+		err(1, "fork");
+	case 0:
+		close(p[0]);
+		setproctitle("logger");
+		imsg_init(&logibuf, p[1]);
+		_exit(logger_main(p[1], &logibuf));
+	default:
+		close(p[1]);
+		imsg_init(&logibuf, p[0]);
+		return;
+	}
+}
+
+static int
+serve(const char *host, int port, const char *dir, struct tls *ctx)
+{
+	struct addrinfo hints, *res, *res0;
+	int error, saved_errno, sock = -1;
+	const char *cause = NULL;
+	char service[32];
+
+	if (snprintf(service, sizeof(service), "%d", port) < 0)
+		fatal("snprintf");
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+	error = getaddrinfo(host, service, &hints, &res0);
+	if (error)
+		fatal("%s", gai_strerror(error));
+	for (res = res0; res; res = res->ai_next) {
+		sock = socket(res->ai_family, res->ai_socktype,
+		    res->ai_protocol);
+		if (sock == -1) {
+			cause = "socket";
+			continue;
+		}
+
+		if (bind(sock, res->ai_addr, res->ai_addrlen) == -1) {
+			cause = "bind";
+			saved_errno = errno;
+			close(sock);
+			errno = saved_errno;
+			continue;
+		}
+
+		if (listen(sock, 5) == -1)
+			fatal("listen");
+
+		/*
+		 * for the time being, we're happy as soon as
+		 * something binds.
+		 */
+		break;
+	}
+
+	if (sock == -1)
+		fatal("%s", cause);
+	freeaddrinfo(res0);
+
+	log_notice(NULL, "serving %s on port %d", dir, port);
+	loop(ctx, sock, -1, NULL);
+	return 0;
+}
+
+static __dead void
+usage(void)
+{
+	fprintf(stderr,
+	    "Version: " GMID_STRING "\n"
+	    "Usage: %s [-hVv] [-d certs-dir] [-H hostname] [-p port] [dir]\n",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct tls_config *tlsconf;
+	struct tls *ctx;
+	struct vhost *host;
+	struct location *loc;
+	const char *errstr, *certs_dir = NULL, *hostname = "localhost";
+	char path[PATH_MAX];
+	int ch;
+
+	logger_init();
+	conf.port = 1965;
+
+	while ((ch = getopt_long(argc, argv, "d:H:hp:Vv", opts, NULL)) != -1) {
+		switch (ch) {
+		case 'd':
+			certs_dir = optarg;
+			break;
+		case 'H':
+			hostname = optarg;
+			break;
+		case 'h':
+			usage();
+			break;
+		case 'p':
+			conf.port = strtonum(optarg, 0, UINT16_MAX, &errstr);
+			if (errstr)
+				fatal("port number is %s: %s", errstr, optarg);
+			break;
+		case 'V':
+			puts("Version: " GMID_STRING);
+			return 0;
+		default:
+			usage();
+			break;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 1)
+		usage();
+
+	/* prepare the configuration */
+	conf.verbose = 1;
+	init_mime(&conf.mime);
+
+	if (certs_dir == NULL)
+		certs_dir = data_dir();
+
+	if (load_default_mime(&conf.mime) == -1)
+		fatal("can't load default mime types");
+	sort_mime(&conf.mime);
+
+	/* set up the implicit vhost and location */
+
+	host = xcalloc(1, sizeof(*host));
+	TAILQ_INSERT_HEAD(&hosts, host, vhosts);
+
+	loc = xcalloc(1, sizeof(*loc));
+	loc->fcgi = -1;
+	TAILQ_INSERT_HEAD(&host->locations, loc, locations);
+
+	load_local_cert(host, hostname, certs_dir);
+
+	host->domain = "*";
+	loc->auto_index = 1;
+	loc->match = "*";
+
+	if (*argv == NULL) {
+		if (getcwd(path, sizeof(path)) == NULL)
+			fatal("getcwd");
+		loc->dir = path;
+	} else
+		loc->dir = absolutify_path(*argv);
+
+	if ((loc->dirfd = open(loc->dir, O_RDONLY|O_DIRECTORY)) == -1)
+		fatal("can't open %s", loc->dir);
+
+	/* setup tls */
+
+	if ((tlsconf = tls_config_new()) == NULL)
+		fatal("tls_config_new"); /* XXX: fatalx */
+
+	/* optionally accept client certs but don't try to verify them */
+	tls_config_verify_client_optional(tlsconf);
+	tls_config_insecure_noverifycert(tlsconf);
+
+	if ((ctx = tls_server()) == NULL)
+		fatal("tls_server failure"); /* XXX: fatalx */
+
+	if (tls_config_set_keypair_file(tlsconf, host->cert, host->key))
+		fatal("can't load the keypair (%s, %s)",
+		    host->cert, host->key);
+
+	if (tls_configure(ctx, tlsconf) == -1)
+		fatal("tls_configure: %s", tls_error(ctx));
+
+	/* start the server */
+	signal(SIGPIPE, SIG_IGN);
+	setproctitle("%s", loc->dir);
+	return serve(hostname, conf.port, loc->dir, ctx);
+}
blob - b87974e60ac299ac783f598b851fa97599ada8b4
blob + f9e9dbe6239e6560d0aea5aa3d366b2ef5a9513a
--- server.c
+++ server.c
@@ -1367,8 +1367,11 @@ loop(struct tls *ctx_, int sock4, int sock6, struct im
 		event_add(&e6, NULL);
 	}
 
-	event_set(&imsgev, ibuf->fd, EV_READ | EV_PERSIST, handle_dispatch_imsg, ibuf);
-	event_add(&imsgev, NULL);
+	if (ibuf) {
+		event_set(&imsgev, ibuf->fd, EV_READ | EV_PERSIST,
+		    handle_dispatch_imsg, ibuf);
+		event_add(&imsgev, NULL);
+	}
 
 #ifdef SIGINFO
 	has_siginfo = 1;