add ge: gemini export!

This commit is contained in:
Omar Polo 2022-09-07 20:47:33 +00:00
parent 7600099513
commit 0126d91d1d
5 changed files with 413 additions and 5 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
TAGS
gmid
gg
ge
*.d
*.o
*.swp

View File

@ -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:

87
ge.1 Normal file
View File

@ -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 .

296
ge.c Normal file
View File

@ -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);
}

View File

@ -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;