aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile27
-rw-r--r--ge.187
-rw-r--r--ge.c296
-rw-r--r--server.c7
5 files changed, 413 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index fbd205a..012b7b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
TAGS
gmid
gg
+ge
*.d
*.o
*.swp
diff --git a/Makefile b/Makefile
index 60de30f..be1a744 100644
--- a/Makefile
+++ b/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:
diff --git a/ge.1 b/ge.1
new file mode 100644
index 0000000..b3eb562
--- /dev/null
+++ b/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 .
diff --git a/ge.c b/ge.c
new file mode 100644
index 0000000..ec35184
--- /dev/null
+++ b/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);
+}
diff --git a/server.c b/server.c
index b87974e..f9e9dbe 100644
--- a/server.c
+++ b/server.c
@@ -1367,8 +1367,11 @@ loop(struct tls *ctx_, int sock4, int sock6, struct imsgbuf *ibuf)
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;