commit 0126d91d1d80d7d8e794b2176556fce969f165cd from: Omar Polo date: Wed Sep 07 20:47:33 2022 UTC add ge: gemini export! 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 +.\" +.\" 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 + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +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;