Compare commits

...

71 Commits

Author SHA1 Message Date
Omar Polo c616a6d6f4 typo in bounds check, sigh... 2024-06-12 15:47:54 +00:00
Omar Polo 96eaf145cb typo in error message 2024-06-12 09:42:37 +00:00
Omar Polo 44e5992664 -current again 2024-06-11 08:21:40 +00:00
Omar Polo cb384f4280 prepare release 2.0.4 2024-06-11 08:18:11 +00:00
Omar Polo a33eaaa925 changelog for 2.0.5 2024-06-11 08:18:10 +00:00
Omar Polo a4f18acde3 disable a test that fails on darwin in the CI 2024-06-10 17:00:45 +00:00
Omar Polo 79f0d2d9a4 regress: add another test in test_gg_n_flag
Related to https://github.com/omar-polo/gmid/issues/12
2024-06-10 16:48:10 +00:00
Omar Polo 036d43dd73 use $gg rather than gg in regress 2024-06-10 11:48:33 +00:00
Omar Polo 5810af408c indent 2024-06-10 11:43:38 +00:00
Omar Polo 7f6c46ae1e iri: don't error on a '..' component at the start of the path
I choose to out of paranoia, but the algorithm defined in RFC3986
allows for them.  So, we should rather remove the leading '..'
component and continue to handle the rest of the path.

Fixes https://github.com/omar-polo/gmid/issues/12
2024-06-10 11:42:22 +00:00
Omar Polo 3d06af043c style 2024-06-10 10:25:54 +00:00
Omar Polo 1001cc1a1f remove stale comment 2024-06-10 10:25:54 +00:00
Omar Polo f2c45fdab0 warn instead of dieing on unknown accept(2) failures 2024-06-10 10:25:54 +00:00
Omar Polo d4955d2891 fmt 2024-06-10 10:25:54 +00:00
Omar Polo 22ce7eb455 use a fatalx() rather than abort()
makes up for a nicer error message, and easier troubleshoot.
2024-06-10 10:25:54 +00:00
Omar Polo 0965162959 another range check 2024-06-10 08:24:16 +00:00
Omar Polo 4a2e39c23b align 2024-06-10 08:22:25 +00:00
Omar Polo 36bdda94c1 detect and reject NUL bytes embedded in the request 2024-06-10 08:20:35 +00:00
Omar Polo 9325f61db0 add regress for gg -n 2024-06-10 08:20:35 +00:00
Omar Polo cf62693495 gg: use snprintf 2024-06-10 08:20:35 +00:00
Omar Polo 62aa1935cc use snprintf 2024-06-10 08:20:35 +00:00
Omar Polo 60c2f58b75 refactor path_clean()
Instead of doing multiple passes over the string use a modified
version canonpath() from kern_pledge.c that does all in a single
go.
2024-06-10 08:20:35 +00:00
Omar Polo bc1190c5d4 shutting_down is no longer used, remove stale extern 2024-06-09 10:08:27 +00:00
Omar Polo 702b5101d3 yyerror is already defined (locally) in parse.y 2024-06-09 10:07:13 +00:00
Omar Polo 345a12c766 remove from gmid.h functions that are used only in gemexp 2024-06-09 09:48:39 +00:00
Omar Polo 68d36b207f check and error on strlcpy truncation 2024-06-09 09:46:04 +00:00
Omar Polo 910fbe8f00 drop vestiges of -DLIBBSD_OPENBSD_VIS
it just isn't worth the pain since I have to check anyway for the
working strnvis.  it's amazing what a shitshow this situation is.
shrug.
2024-06-09 08:48:10 +00:00
Omar Polo 911fd6b6c6 fix previous; it's compat/vis.c, sigh... 2024-06-08 19:05:37 +00:00
Omar Polo 5f31f2410e fix previous; add strnvis to $COMPATS 2024-06-08 19:02:58 +00:00
Omar Polo 401030113e change the approach for strnvis
instead of making things more obscure via gmid_strnvis(), let's
just check for strnvis with -Werror so we can swap the OS broken
implementation with the bundled OpenBSD one.
2024-06-08 18:37:57 +00:00
Omar Polo fcf3f1fa9a ...complete the sentence 2024-06-06 18:10:06 +00:00
Omar Polo 3b2ee15a6b fix previous; was causing a shift/reduce conflict 2024-06-06 18:00:23 +00:00
Omar Polo 235f5e70ee regress: add a test for comments and blanks at the start of the file 2024-06-06 17:38:30 +00:00
Omar Polo 471cae7e8f fix parser: empty lines are allowed 2024-06-06 17:31:36 +00:00
Omar Polo 15acd9c6ec site: log for 2.0.4 2024-06-06 14:49:12 +00:00
Omar Polo a4ffa32f0e -current again 2024-06-06 14:14:27 +00:00
Omar Polo 4864061c16 prepare release 2.0.4 2024-06-06 14:10:58 +00:00
Omar Polo 9089c18ac9 changelog for 2.0.4 2024-06-06 14:10:57 +00:00
Omar Polo a1562e847f sync file list 2024-06-06 14:10:57 +00:00
Omar Polo 69cfd0a304 add a todo 2024-06-06 13:53:09 +00:00
Omar Polo 848189f10a attempt to deal with the portability fiasco of strnvis(3) 2024-06-06 13:43:12 +00:00
Omar Polo 1bc73c3bcb libtls: add missing include 2024-06-06 13:43:12 +00:00
Omar Polo 6174e65dbe add a nice error message in case the `cgi' option is present
was removed with gmid 2.0 but to ease the migration a friendly error
message is more useful than a "syntax error".
2024-06-05 15:18:10 +00:00
Omar Polo c38417e32d site: fix syntax for 2.0.2 changelog 2024-06-04 17:33:16 +00:00
Omar Polo e030968718 site: add changelog for 2.0.3 2024-06-04 17:32:53 +00:00
Omar Polo 5e71e44d26 -current again 2024-06-04 11:38:48 +00:00
Omar Polo 4d207d1050 prepare release 2.0.3 2024-06-04 11:33:54 +00:00
Omar Polo b6eb184a95 changelog for 2.0.3 2024-06-04 11:32:21 +00:00
Omar Polo d08e2664fc bump date 2024-06-03 16:24:52 +00:00
Omar Polo 0a4a979ccb regress: add a check for `fastcgi off' handling across locations
Based on the bug report from Alex, thanks!
2024-06-03 16:14:40 +00:00
Omar Polo da834b5803 fix `fastcgi off' handling
When a matching location has a `fastcgi off' directive, we should
honour that and stop searching for further location which may have
a `fastcgi' directive.

Bug reported by Alex // nytpu, thanks!
2024-06-03 16:14:09 +00:00
Omar Polo c9ea70a36f regress: add test_ipv6_server 2024-05-29 09:06:45 +00:00
Omar Polo 7c723cf05f regress: add a knob to disable test_ipv6_addr
at least on the CI is failing with "can't connect to ::1:10965:
Address not available" which suggests IPv6 is broken there.
2024-05-29 09:05:06 +00:00
Omar Polo b5dd7091ad typo 2024-05-29 08:58:12 +00:00
Omar Polo 5b549c2805 regress: rename ipv4 test and add another with ipv6 2024-05-29 08:56:10 +00:00
Omar Polo b00f71ba97 iri: add support for raw IPv6 addresses 2024-05-29 08:52:25 +00:00
Omar Polo 6ff8de1f8a gg: unbreak -n 2024-05-29 08:37:39 +00:00
Omar Polo 9f675805d0 regress: run test_ip_addr with host=127.0.0.1 2024-05-29 08:33:36 +00:00
Omar Polo a91b0892bf explain why we disable runtime tests on macos 2024-05-29 08:12:51 +00:00
Omar Polo 610a4666cd regress: use the new gg -q to reduce the blabbering 2024-05-29 08:09:25 +00:00
Omar Polo 2f4926259f gg: add -q to avoid printing "Server says" 2024-05-29 08:08:36 +00:00
Omar Polo cd12ad1132 pretty-print the socket address at configuration parsing time
saves a getnameinfo(NI_NUMERICHOST) at runtime, even if it's pretty
cheap.
2024-05-29 08:03:59 +00:00
Omar Polo b2782022c9 add regress that hit gmid via a raw IPv4 address 2024-05-29 07:54:03 +00:00
Omar Polo 1ef0cd0cdb relax the SNI requirement
There are legitimate cases where SNI can't be used, for example
when connecting via an IPv6 address, so don't rejects those requests.
Instead, fill the requested domain with the address (literal) of
the socket they're connected to and attempt to match on it.

This possibly still incur in a "won't proxy" error if the client
then requests a different hostname.

See the github issue https://github.com/omar-polo/gmid/issues/25
2024-05-29 07:52:13 +00:00
Omar Polo 42e2af25ae github: add workflow to build images for ghcr.io 2024-05-27 15:16:38 +00:00
Omar Polo 89dca7ab54 s/MIN/MINIMUM/g 2024-05-25 17:44:35 +00:00
Omar Polo 359c56ce35 contrib/gmid.service: remove User and Group
May cause weird errors (status=216/GROUP) on some distros, and
running as root is already the default, so remove the two lines.
Reported by and debugged together with leandro del Flug, thanks!
2024-04-27 17:12:09 +00:00
Omar Polo c2dcb5fa6e contrib/gmid.service: start as root by default
Various techniques used by gmid are effective only when the daemon
is started as root.  Strongly suggest to do so by switching the
sample configuration.  This way, provided that a local user is
created as well, the chroot configuration will work out-of-the-box
and the TLS certificates can be readable only by root.
2024-04-27 16:17:37 +00:00
Omar Polo 5d12e6a104 improve the description for -f 2024-04-27 16:10:46 +00:00
Omar Polo 0d8eb9b60c typo: semicolors -> semicolons 2024-04-11 09:42:15 +00:00
Omar Polo 5864f3ce3c set next version 2024-04-04 19:28:14 +00:00
29 changed files with 520 additions and 214 deletions

View File

@ -1,6 +1,9 @@
# gcc' -Werror=use-after-free gets tripped by vis.c: it sees a use
# after free where it's not possible and breaks the CI.
# seems that inside the CI it's not currently possible to bind to ::1
# so set HAVE_IPV6=no.
linux_amd64_task:
container:
image: alpine:latest
@ -8,7 +11,7 @@ linux_amd64_task:
- apk add alpine-sdk linux-headers bison libretls-dev libevent-dev
- ./configure CFLAGS='-O2 -pipe -Wno-deprecated-declarations -Wno-use-after-free' -Werror
- make
- make regress REGRESS_HOST="*"
- make regress REGRESS_HOST="*" HAVE_IPV6=no
linux_arm_task:
arm_container:
@ -17,7 +20,7 @@ linux_arm_task:
- apk add alpine-sdk linux-headers bison libretls-dev libevent-dev
- ./configure CFLAGS='-O2 -pipe -Wno-deprecated-declarations -Wno-use-after-free' -Werror
- make
- make regress REGRESS_HOST="*"
- make regress REGRESS_HOST="*" HAVE_IPV6=no
freebsd_14_task:
freebsd_instance:
@ -26,8 +29,13 @@ freebsd_14_task:
script:
- ./configure CFLAGS='-O2 -pipe -Wno-deprecated-declarations' -Werror
- make
- make regress
- make regress HAVE_IPV6=no
#
# There are some issues with imsg fd passing on macos at the moment that
# seem to be triggered only in applications that do a heavy use of them,
# like gmid or opensmtpd. Still, keep macos to ensure gmid builds here.
#
mac_task:
macos_instance:
image: ghcr.io/cirruslabs/macos-sonoma-xcode:latest

33
.github/workflows/alpine-release.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: release docker image
on:
push:
tags:
env:
IMAGE_NAME: "gmid"
jobs:
build:
permissions: write-all
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: build the image
run: docker build -f contrib/Dockerfile -t gmid:alpine .
- name: login to ghcr.io
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: push the image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr A-Z a-z)
# strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag gmid:alpine $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION

View File

@ -1,3 +1,56 @@
2024-06-11 Omar Polo <op@omarpolo.com>
* configure (VERSION): release 2.0.5
2024-06-10 Omar Polo <op@omarpolo.com>
* don't error on a '..' component at the start of the path
* reject NUL bytes embedded in the request
2024-06-09 Omar Polo <op@omarpolo.com>
* check for truncation various strlcpy calls.
* clean up of a few unused prototypes and externs.
2024-06-08 Omar Polo <op@omarpolo.com>
* configure: change how strnvis(3) is handled: on systems
with the broken interface gmid will just use its built-in
version.
2024-06-06 Omar Polo <op@omarpolo.com>
* parse.y: allow again empty lines at the start of the config
* configure (VERSION): release 2.0.4
* portability fix for system with a wrong strnvis(3)
2024-06-05 Omar Polo <op@omarpolo.com>
* parse.y: add a nicer error message if the removed `cgi' option
is still used.
2024-06-04 Omar Polo <op@omarpolo.com>
* configure (VERSION): release 2.0.3
2024-06-03 Omar Polo <op@omarpolo.com>
* server.c (vhost_fastcgi): fix `fastcgi off' handling.
Reported by Alex // nytpu
2024-05-29 Omar Polo <op@omarpolo.com>
* server.c (handle_handshake): relax the SNI requirement. There are
legitimate use-cases where SNI can't be used, like connecting to a
raw IPv6 address.
* gg.c (main): add -q to avoid printing "Server Says:"
* gg.c (main): unbreak -n
* iri.c (parse_authority): add support for raw IPv6 addresses
2024-04-04 Omar Polo <op@omarpolo.com>
* configure (VERSION): release 2.0.2
2024-04-03 Omar Polo <op@omarpolo.com>
* configure: improve function checking in the configure

View File

@ -158,10 +158,10 @@ ${DISTNAME}.tar.gz: ${DISTFILES}
mkdir -p .dist/${DISTNAME}/
${INSTALL} -m 0644 ${DISTFILES} .dist/${DISTNAME}/
cd .dist/${DISTNAME} && chmod 755 configure
${MAKE} -C compat DESTDIR=${PWD}/.dist/${DISTNAME}/compat dist
${MAKE} -C compat DESTDIR=${PWD}/.dist/${DISTNAME}/compat dist
${MAKE} -C contrib DESTDIR=${PWD}/.dist/${DISTNAME}/contrib dist
${MAKE} -C have DESTDIR=${PWD}/.dist/${DISTNAME}/have dist
${MAKE} -C keys DESTDIR=${PWD}/.dist/${DISTNAME}/keys dist
${MAKE} -C have DESTDIR=${PWD}/.dist/${DISTNAME}/have dist
${MAKE} -C keys DESTDIR=${PWD}/.dist/${DISTNAME}/keys dist
${MAKE} -C regress DESTDIR=${PWD}/.dist/${DISTNAME}/regress dist
cd .dist/ && tar zcf ../$@ ${DISTNAME}
rm -rf .dist/

View File

@ -18,6 +18,7 @@
#include "config.h"
#include <limits.h>
#include <string.h>
#include <openssl/ecdsa.h>
#include <openssl/err.h>

View File

@ -183,7 +183,7 @@ config_send_file(struct privsep *ps, enum privsep_procid id, int type,
/* avoid fd rampage */
if (proc_flush_imsg(ps, id, -1) == -1) {
log_warn("%s: proc_fush_imsg", __func__);
log_warn("%s: proc_flush_imsg", __func__);
return -1;
}

23
configure vendored
View File

@ -19,7 +19,7 @@
set -e
RELEASE=no
VERSION=2.0.2
VERSION=2.0.5-current
usage()
{
@ -143,7 +143,6 @@ echo "file config.log: writing..."
NEED_GNU_SOURCE=0
NEED_OPENBSD_SOURCE=0
NEED_LIBBSD_OPENBSD_VIS=0
COMPATS=
COMP="${CC} ${CFLAGS} -Werror=implicit-function-declaration"
@ -216,10 +215,6 @@ runtest() {
NEED_OPENBSD_SOURCE=1
return 0
fi
if [ "$4" = -DLIBBSD_OPENBSD_VIS ]; then
NEED_LIBBSD_OPENBSD_VIS=1
return 0
fi
if [ -n "$3" ]; then
CFLAGS="$CFLAGS $3"
fi
@ -315,7 +310,14 @@ runtest strtonum STRTONUM -D_OPENBSD_SOURCE || true
runtest timingsafe_memcmp TIMINGSAFE_MEMCMP || true
runtest tree_h TREE_H || true
runtest vasprintf VASPRINTF -D_GNU_SOURCE || true
runtest vis VIS -DLIBBSD_OPENBSD_VIS || true
# strnvis is a bit special since NetBSD, FreeBSD and MacOS have
# the broken version with the wrong semantics and arguments.
# Hence the -Wall -Werror check.
if ! singletest strnvis STRNVIS "-Wall -Werror"; then
CFLAGS="-I ${PWD}/compat/vis ${CFLAGS} ${CFLAGS}"
COMPATS="compat/vis.c ${COMPATS}"
fi
if [ ${HAVE_ARC4RANDOM} -eq 1 -a ${HAVE_ARC4RANDOM_BUF} -eq 0 ]; then
COMPATS="compat/arc4random.c ${COMPATS}"
@ -388,10 +390,6 @@ if [ ${HAVE_QUEUE_H} -eq 0 -o ${HAVE_IMSG} -eq 0 -o ${HAVE_TREE_H} -eq 0 ]; then
CFLAGS="${CFLAGS} -I ${PWD}/compat"
fi
if [ ${HAVE_VIS} -eq 0 ]; then
CFLAGS="${CFLAGS} -I ${PWD}/compat/vis"
fi
if [ $HAVE_LIBEVENT2 -eq 1 ]; then
CFLAGS="$CFLAGS -DHAVE_LIBEVENT2=1"
fi
@ -402,9 +400,6 @@ fi
if [ $NEED_OPENBSD_SOURCE = 1 ]; then
CFLAGS="$CFLAGS -D_OPENBSD_SOURCE"
fi
if [ $NEED_LIBBSD_OPENBSD_VIS = 1 ]; then
CFLAGS="$CFLAGS -DLIBBSD_OPENBSD_VIS"
fi
CFLAGS="-I. ${CFLAGS} ${CDIAGFLAGS}"

View File

@ -6,8 +6,6 @@ Wants=network-online.target
[Service]
Type=simple
User=gmid
Group=nobody
ExecStart=/usr/local/bin/gmid -f -c /etc/gmid.conf
ExecStop=/bin/kill -TERM $MAINPID
ExecReload=/bin/kill -HUP $MAINPID

11
ge.c
View File

@ -91,7 +91,7 @@ log_request(struct client *c, int code, const char *meta)
*c->domain == '\0' ? c->iri.host : c->domain, b, code, meta);
}
void
static void
load_local_cert(struct vhost *h, const char *hostname, const char *dir)
{
char *cert, *key;
@ -112,7 +112,9 @@ load_local_cert(struct vhost *h, const char *hostname, const char *dir)
if (h->key == NULL)
fatal("can't load %s", key);
strlcpy(h->domain, hostname, sizeof(h->domain));
if (strlcpy(h->domain, hostname, sizeof(h->domain))
>= sizeof(h->domain))
fatalx("hostname too long: %s", hostname);
}
/* wrapper around dirname(3). dn must be PATH_MAX+1 at least. */
@ -122,7 +124,8 @@ pdirname(const char *path, char *dn)
char p[PATH_MAX+1];
char *t;
strlcpy(p, path, sizeof(p));
if (strlcpy(p, path, sizeof(p)) >= sizeof(p))
fatalx("%s: path too long: %s", __func__, path);
t = dirname(p);
memmove(dn, t, strlen(t)+1);
}
@ -141,7 +144,7 @@ mkdirs(const char *path, mode_t mode)
}
/* $XDG_DATA_HOME/gemexp */
char *
static char *
data_dir(void)
{
const char *home, *xdg;

8
gg.1
View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2021, 2022 Omar Polo <op@omarpolo.com>
.\" Copyright (c) 2021-2024 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
@ -11,7 +11,7 @@
.\" 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: October 19 2023$
.Dd $Mdocdate: May 29 2024$
.Dt GG 1
.Os
.Sh NAME
@ -20,7 +20,7 @@
.Sh SYNOPSIS
.Nm
.Bk -words
.Op Fl 23Nn
.Op Fl 23Nnq
.Op Fl C Ar cert
.Op Fl d Ar mode
.Op Fl H Ar sni
@ -82,6 +82,8 @@ and
to do the request instead of the ones extracted by the IRI.
.Ar port
is by default 1965.
.It Fl q
Don't print server error messages to standard error.
.It Fl T Ar seconds
Kill
.Nm

22
gg.c
View File

@ -41,6 +41,7 @@ int flag3;
int nop;
int redirects = 5;
int timer;
int quiet;
const char *cert;
const char *key;
const char *proxy_host;
@ -212,14 +213,13 @@ get(const char *r)
char req[GEMINI_URL_LEN];
uint8_t buf[2048];
const char *parse_err, *host, *port;
int ret;
if (strlcpy(iribuf, r, sizeof(iribuf)) >= sizeof(iribuf))
errx(1, "iri too long: %s", r);
if (strlcpy(req, r, sizeof(req)) >= sizeof(req))
errx(1, "iri too long: %s", r);
if (strlcat(req, "\r\n", sizeof(req)) >= sizeof(req))
ret = snprintf(req, sizeof(req), "%s\r\n", r);
if (ret < 0 || (size_t)ret >= sizeof(req))
errx(1, "iri too long: %s", r);
if (!parse_iri(iribuf, &iri, &parse_err))
@ -308,8 +308,11 @@ get(const char *r)
assert(t != NULL);
if (code < 20 || code >= 30) {
*t = '\0';
fprintf(stderr, "Server says: ");
safeprint(stderr, buf + 3); /* skip return code */
if (!quiet) {
fprintf(stderr, "Server says: ");
/* skip return code */
safeprint(stderr, buf + 3);
}
}
t += 2; /* skip \r\n */
len -= t - buf;
@ -335,7 +338,7 @@ static void __attribute__((noreturn))
usage(void)
{
fprintf(stderr, "version: " GG_STRING "\n");
fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
fprintf(stderr, "usage: %s [-23Nnq] [-C cert] [-d mode] [-H sni] "
"[-K key] [-P host[:port]]\n",
getprogname());
fprintf(stderr, " [-T seconds] gemini://...\n");
@ -385,7 +388,7 @@ main(int argc, char **argv)
setlocale(LC_CTYPE, "");
while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
while ((ch = getopt(argc, argv, "23C:d:H:K:nNP:qT:")) != -1) {
switch (ch) {
case '2':
flag2 = 1;
@ -415,6 +418,9 @@ main(int argc, char **argv)
parse_proxy(optarg);
dont_verify_name = 1;
break;
case 'q':
quiet = 1;
break;
case 'T':
timer = strtonum(optarg, 1, 1000, &errstr);
if (errstr != NULL)

7
gmid.8
View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2021, 2022, 2023 Omar Polo <op@omarpolo.com>
.\" Copyright (c) 2021, 2022, 2023, 2024 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
@ -11,7 +11,7 @@
.\" 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 October 20, 2023
.Dd April 27, 2024
.Dt GMID 8
.Os
.Sh NAME
@ -52,7 +52,8 @@ Overrides the definition of
.Ar macro
in the config file if present.
.It Fl f
Stays and logs on the foreground.
Do not daemonize.
Stay and log in the foreground.
.It Fl h , Fl -help
Print the usage and exit.
.It Fl n

10
gmid.c
View File

@ -314,10 +314,12 @@ main(int argc, char **argv)
if (*conf->chroot != '\0' && *conf->user == '\0')
fatalx("can't chroot without a user to switch to.");
} else {
if (user)
strlcpy(conf->user, user, sizeof(conf->user));
if (chroot)
strlcpy(conf->chroot, chroot, sizeof(conf->chroot));
if (user && strlcpy(conf->user, user, sizeof(conf->user))
>= sizeof(conf->user))
fatalx("user name too long: %s", user);
if (chroot && strlcpy(conf->chroot, chroot, sizeof(conf->chroot))
>= sizeof(conf->chroot))
fatalx("chroot path too long: %s", chroot);
}
if ((ps = calloc(1, sizeof(*ps))) == NULL)

View File

@ -11,7 +11,7 @@
.\" 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 April 4, 2024
.Dd June 11, 2024
.Dt GMID.CONF 5
.Os
.Sh NAME
@ -384,7 +384,7 @@ The port the server is listening on.
.Dq GEMINI
.It Ev SERVER_SOFTWARE
The name and version of the server, i.e.
.Dq gmid/2.0.2
.Dq gmid/2.0.5
.It Ev REMOTE_USER
The subject of the client certificate if provided, otherwise unset.
.It Ev TLS_CLIENT_ISSUER

9
gmid.h
View File

@ -114,6 +114,9 @@ struct address {
socklen_t slen;
int16_t port;
/* pretty-printed version of `ss' */
char pp[NI_MAXHOST];
/* used in the server */
struct conf *conf;
int sock;
@ -364,10 +367,6 @@ enum imsg_type {
IMSG_CTL_PROCFD,
};
/* gmid.c */
char *data_dir(void);
void load_local_cert(struct vhost*, const char*, const char*);
/* gmid.c / ge.c */
void log_request(struct client *, int, const char *);
@ -383,7 +382,6 @@ void crypto(struct privsep *, struct privsep_proc *);
void crypto_engine_init(struct conf *);
/* parse.y */
void yyerror(const char*, ...);
int parse_conf(struct conf *, const char*);
int cmdline_symset(char *);
@ -396,7 +394,6 @@ const char *mime(struct conf *, struct vhost*, const char*);
void free_mime(struct mime *);
/* server.c */
extern int shutting_down;
const char *vhost_lang(struct vhost*, const char*);
const char *vhost_default_mime(struct vhost*, const char*);
const char *vhost_index(struct vhost*, const char*);

View File

@ -34,12 +34,12 @@ DISTFILES = ASN1_time_parse.c \
setresuid.c \
strlcat.c \
strlcpy.c \
strnvis.c \
strtonum.c \
sys_endian_h.c \
timingsafe_memcmp.c \
tree_h.c \
vasprintf.c \
vis.c \
wait_any.c
all:

155
iri.c
View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, 2022 Omar Polo <op@omarpolo.com>
* Copyright (c) 2020, 2022, 2024 Omar Polo <op@omarpolo.com>
* Copyright (c) 2015 Theo de Raadt <deraadt@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
@ -177,25 +178,47 @@ parse_port(struct parser *p)
return 1;
}
/* TODO: add support for ip-literal and ipv4addr ? */
/* *( unreserved / sub-delims / pct-encoded ) */
static int
parse_authority(struct parser *p)
{
p->parsed->host = p->iri;
struct addrinfo hints, *ai;
char *end;
int err;
while (unreserved(*p->iri)
|| sub_delimiters(*p->iri)
|| parse_pct_encoded(p)
|| valid_multibyte_utf8(p)) {
/* normalize the host name. */
if (*p->iri < 0x7F)
*p->iri = tolower(*p->iri);
if (*p->iri == '[') {
p->iri++;
}
p->parsed->host = p->iri;
if ((end = strchr(p->iri, ']')) == NULL) {
p->err = "invalid IPv6 address";
return 0;
}
*end++ = '\0';
p->iri = end;
if (p->err != NULL)
return 0;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
err = getaddrinfo(p->parsed->host, NULL, &hints, &ai);
if (err != 0) {
p->err = "invalid IPv6 address";
return 0;
}
freeaddrinfo(ai);
} else {
p->parsed->host = p->iri;
while (unreserved(*p->iri)
|| sub_delimiters(*p->iri)
|| parse_pct_encoded(p)
|| valid_multibyte_utf8(p)) {
/* normalize the host name. */
if (*p->iri < 0x7F)
*p->iri = tolower(*p->iri);
p->iri++;
}
if (p->err != NULL)
return 0;
}
if (*p->iri == ':') {
*p->iri = '\0';
@ -218,78 +241,56 @@ parse_authority(struct parser *p)
}
/*
* Routine for path_clean. Elide the pointed .. with the preceding
* element. Return 0 if it's not possible. incr is the length of
* the increment, 3 for ../ and 2 for ..
*/
static int
path_elide_dotdot(char *path, char *i, int incr)
{
char *j;
if (i == path)
return 0;
for (j = i-2; j != path && *j != '/'; j--)
/* noop */ ;
if (*j == '/')
j++;
i += incr;
memmove(j, i, strlen(i)+1);
return 1;
}
/*
* Use an algorithm similar to the one implemented in go' path.Clean:
* Use an algorithm based on canonpath() from kern_pledge.c.
*
* 1. Replace multiple slashes with a single slash
* 2. Eliminate each . path name element
* 3. Eliminate each inner .. along with the non-.. element that precedes it
* 4. Eliminate trailing .. if possible or error (go would only discard)
*
* Unlike path.Clean, this function return the empty string if the
* original path is equivalent to "/".
* It's slightly more complicated since even if your paths are
* absolutely, they don't start with '/'. q == path asserts
* that we're at the start of the path.
*/
static int
path_clean(char *path)
{
char *i;
char *p, *q;
/* 1. replace multiple slashes with a single one */
for (i = path; *i; ++i) {
if (*i == '/' && *(i+1) == '/') {
memmove(i, i+1, strlen(i)); /* move also the \0 */
i--;
p = q = path;
while (*p) {
if (q == path && p[0] == '/') {
/* special case, if path is just "/" trim it */
p++;
} else if (q == path && p[0] == '.' && p[1] == '.' &&
(p[2] == '/' || p[2] == '\0')) {
/* ../ at the start of path */
p += 2;
if (*p == '/')
p++;
} else if (q == path && p[0] == '.' &&
(p[1] == '/' || p[1] == '\0')) {
/* ./ at the start of path */
p++;
if (*p == '/')
p++;
} else if (p[0] == '/' && p[1] == '/') {
/* trim double slashes */
p++;
} else if (p[0] == '/' && p[1] == '.' && p[2] == '.' &&
(p[3] == '/' || p[3] == '\0')) {
/* /../ component */
while (q > path && *--q != '/')
continue;
p += 3;
if (q == path && *p == '/')
p++;
} else if (p[0] == '/' && p[1] == '.' &&
(p[2] == '/' || p[2] == '\0')) {
/* /./ component */
p += 2;
} else {
*q++ = *p++;
}
}
/* 2. eliminate each . path name element */
for (i = path; *i; ++i) {
if ((i == path || *i == '/') &&
*i != '.' && i[1] == '.' && i[2] == '/') {
/* move also the \0 */
memmove(i, i+2, strlen(i)-1);
i--;
}
}
if (!strcmp(path, ".") || !strcmp(path, "/.")) {
*path = '\0';
return 1;
}
/* 3. eliminate each inner .. along with the preceding non-.. */
for (i = strstr(path, "../"); i != NULL; i = strstr(path, "..")) {
/* break if we've found a trailing .. */
if (i[2] == '\0')
break;
if (!path_elide_dotdot(path, i, 3))
return 0;
}
/* 4. eliminate trailing ..*/
if ((i = strstr(path, "..")) != NULL)
if (!path_elide_dotdot(path, i, 2))
return 0;
if (*p != '\0')
return 0;
*q = '\0';
return 1;
}

96
parse.y
View File

@ -123,7 +123,7 @@ typedef struct {
%token ACCESS ALIAS AUTO
%token BLOCK
%token CA CERT CHROOT CLIENT
%token CA CERT CGI CHROOT CLIENT
%token DEFAULT
%token FACILITY FASTCGI FOR_HOST
%token INCLUDE INDEX IPV6
@ -147,6 +147,12 @@ typedef struct {
%%
/*
* Allow empty lines at the start of the configuration.
*/
grammar : nl conf | conf
;
conf : /* empty */
| conf include nl
| conf varset nl
@ -329,7 +335,10 @@ vhost : SERVER string {
TAILQ_INIT(&host->proxies);
(void) strlcpy(loc->match, "*", sizeof(loc->match));
(void) strlcpy(host->domain, $2, sizeof(host->domain));
if (strlcpy(host->domain, $2, sizeof(host->domain))
>= sizeof(host->domain))
yyerror("server name too long: %s", $2);
if (strstr($2, "xn--") != NULL) {
yywarn("\"%s\" looks like punycode: you "
@ -375,7 +384,9 @@ servopt : ALIAS string {
struct alist *a;
a = xcalloc(1, sizeof(*a));
(void) strlcpy(a->alias, $2, sizeof(a->alias));
if (strlcpy(a->alias, $2, sizeof(a->alias))
>= sizeof(a->alias))
yyerror("alias too long: %s", $2);
free($2);
TAILQ_INSERT_TAIL(&host->aliases, a, aliases);
}
@ -384,6 +395,11 @@ servopt : ALIAS string {
free(host->cert_path);
host->cert_path = $2;
}
| CGI string {
free($2);
yyerror("`cgi' was removed in gmid 2.0."
" Please use fastcgi or proxy instead.");
}
| KEY string {
ensure_absolute_path($2);
free(host->key_path);
@ -447,11 +463,17 @@ proxy_port : /* empty */ { $$ = 1965; }
;
proxy_match : PROTO string {
(void) strlcpy(proxy->match_proto, $2, sizeof(proxy->match_proto));
if (strlcpy(proxy->match_proto, $2,
sizeof(proxy->match_proto))
>= sizeof(proxy->match_proto))
yyerror("proto too long: %s", $2);
free($2);
}
| FOR_HOST string proxy_port {
(void) strlcpy(proxy->match_host, $2, sizeof(proxy->match_host));
if (strlcpy(proxy->match_host, $2,
sizeof(proxy->match_host))
>= sizeof(proxy->match_host))
yyerror("for-host too long: %s", $2);
(void) snprintf(proxy->match_port, sizeof(proxy->match_port),
"%d", $3);
free($2);
@ -478,7 +500,9 @@ proxy_opt : CERT string {
free($2);
}
| RELAY_TO string proxy_port {
(void) strlcpy(proxy->host, $2, sizeof(proxy->host));
if (strlcpy(proxy->host, $2, sizeof(proxy->host))
>= sizeof(proxy->host))
yyerror("relay-to host too long: %s", $2);
(void) snprintf(proxy->port, sizeof(proxy->port),
"%d", $3);
free($2);
@ -488,7 +512,9 @@ proxy_opt : CERT string {
proxy->reqca_path = $4;
}
| SNI string {
(void) strlcpy(proxy->sni, $2, sizeof(proxy->sni));
if (strlcpy(proxy->sni, $2, sizeof(proxy->sni))
>= sizeof(proxy->sni))
yyerror("sni hostname too long: %s", $2);
free($2);
}
| USE_TLS bool {
@ -503,7 +529,9 @@ location : LOCATION { advance_loc(); } string '{' optnl locopts '}' {
/* drop the starting '/' if any */
if (*$3 == '/')
memmove($3, $3+1, strlen($3));
(void) strlcpy(loc->match, $3, sizeof(loc->match));
if (strlcpy(loc->match, $3, sizeof(loc->match))
>= sizeof(loc->match))
yyerror("location path too long: %s", $3);
free($3);
}
| error '}'
@ -516,7 +544,9 @@ locopts : /* empty */
locopt : AUTO INDEX bool { loc->auto_index = $3 ? 1 : -1; }
| BLOCK RETURN NUM string {
check_block_fmt($4);
(void) strlcpy(loc->block_fmt, $4, sizeof(loc->block_fmt));
if (strlcpy(loc->block_fmt, $4, sizeof(loc->block_fmt))
>= sizeof(loc->block_fmt))
yyerror("block return meta too long: %s", $4);
loc->block_code = check_block_code($3);
free($4);
}
@ -533,18 +563,23 @@ locopt : AUTO INDEX bool { loc->auto_index = $3 ? 1 : -1; }
loc->block_code = 40;
}
| DEFAULT TYPE string {
(void) strlcpy(loc->default_mime, $3,
sizeof(loc->default_mime));
if (strlcpy(loc->default_mime, $3,
sizeof(loc->default_mime))
>= sizeof(loc->default_mime))
yyerror("default type too long: %s", $3);
free($3);
}
| fastcgi
| INDEX string {
(void) strlcpy(loc->index, $2, sizeof(loc->index));
if (strlcpy(loc->index, $2, sizeof(loc->index))
>= sizeof(loc->index))
yyerror("index string too long: %s", $2);
free($2);
}
| LANG string {
(void) strlcpy(loc->lang, $2,
sizeof(loc->lang));
if (strlcpy(loc->lang, $2, sizeof(loc->lang))
>= sizeof(loc->lang))
yyerror("lang too long: %s", $2);
free($2);
}
| LOG bool { loc->disable_log = !$2; }
@ -553,7 +588,9 @@ locopt : AUTO INDEX bool { loc->auto_index = $3 ? 1 : -1; }
loc->reqca_path = $4;
}
| ROOT string {
(void) strlcpy(loc->dir, $2, sizeof(loc->dir));
if (strlcpy(loc->dir, $2, sizeof(loc->dir))
>= sizeof(loc->dir))
yyerror("root path too long: %s", $2);
free($2);
}
| STRIP NUM { loc->strip = check_strip_no($2); }
@ -651,6 +688,7 @@ static const struct keyword {
{"block", BLOCK},
{"ca", CA},
{"cert", CERT},
{"cgi", CGI},
{"chroot", CHROOT},
{"client", CLIENT},
{"default", DEFAULT},
@ -1239,9 +1277,11 @@ fastcgi_conf(const char *path, const char *port)
f = xcalloc(1, sizeof(*f));
f->id = i;
(void)strlcpy(f->path, path, sizeof(f->path));
if (port != NULL)
(void)strlcpy(f->port, port, sizeof(f->port));
if (strlcpy(f->path, path, sizeof(f->path)) >= sizeof(f->path))
yyerror("fastcgi path is too long: %s", path);
if (port != NULL &&
strlcpy(f->port, port, sizeof(f->port)) >= sizeof(f->port))
yyerror("port too long: %s", port);
TAILQ_INSERT_TAIL(&conf->fcgi, f, fcgi);
return f->id;
@ -1254,8 +1294,10 @@ add_param(char *name, char *val)
struct envhead *h = &loc->params;
e = xcalloc(1, sizeof(*e));
(void) strlcpy(e->name, name, sizeof(e->name));
(void) strlcpy(e->value, val, sizeof(e->value));
if (strlcpy(e->name, name, sizeof(e->name)) >= sizeof(e->name))
yyerror("parameter name too long: %s", name);
if (strlcpy(e->value, val, sizeof(e->value)) >= sizeof(e->value))
yyerror("param value too long: %s", val);
TAILQ_INSERT_TAIL(h, e, envs);
}
@ -1280,7 +1322,7 @@ getservice(const char *n)
}
static void
add_to_addr_queue(struct addrhead *a, struct addrinfo *ai)
add_to_addr_queue(struct addrhead *a, struct addrinfo *ai, const char *pp)
{
struct address *addr;
struct sockaddr_in *sin;
@ -1306,6 +1348,7 @@ add_to_addr_queue(struct addrhead *a, struct addrinfo *ai)
addr->ai_protocol = ai->ai_protocol;
addr->slen = ai->ai_addrlen;
memcpy(&addr->ss, ai->ai_addr, ai->ai_addrlen);
strlcpy(addr->pp, pp, sizeof(addr->pp));
/* for commodity */
switch (addr->ai_family) {
@ -1330,6 +1373,7 @@ void
listen_on(const char *hostname, const char *servname)
{
struct addrinfo hints, *res, *res0;
char pp[NI_MAXHOST];
int error;
memset(&hints, 0, sizeof(hints));
@ -1344,8 +1388,14 @@ listen_on(const char *hostname, const char *servname)
}
for (res = res0; res; res = res->ai_next) {
add_to_addr_queue(&host->addrs, res);
add_to_addr_queue(&conf->addrs, res);
if (getnameinfo(res->ai_addr, res->ai_addrlen, pp, sizeof(pp),
NULL, 0, NI_NUMERICHOST) == -1) {
yyerror("getnameinfo failed: %s", strerror(errno));
break;
}
add_to_addr_queue(&host->addrs, res, pp);
add_to_addr_queue(&conf->addrs, res, pp);
}
freeaddrinfo(res0);

View File

@ -22,7 +22,7 @@
#include "log.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
static const struct timeval handshake_timeout = { 5, 0 };
@ -50,7 +50,7 @@ proxy_tls_readcb(int fd, short event, void *d)
}
if (bufev->wm_read.high != 0)
howmuch = MIN(sizeof(buf), bufev->wm_read.high);
howmuch = MINIMUM(sizeof(buf), bufev->wm_read.high);
switch (ret = tls_read(c->proxyctx, buf, howmuch)) {
case TLS_WANT_POLLIN:

View File

@ -7,6 +7,9 @@ GENCERT_FLAGS=
# host to bind to during regress
REGRESS_HOST = localhost
# set to no if don't have IPv6 working (need to listen on ::1)
HAVE_IPV6 = yes
DISTFILES = Makefile \
env \
err \
@ -39,7 +42,7 @@ IRI_OBJS = ${IRI_SRCS:.c=.o} ${REG_COMPATS}
.PHONY: all data clean dist
all: data puny-test iri_test fcgi-test
env REGRESS_HOST="${REGRESS_HOST}" ./regress ${TESTS}
env HAVE_IPV6="${HAVE_IPV6}" REGRESS_HOST="${REGRESS_HOST}" ./regress ${TESTS}
data: testdata localhost.pem testca.pem valid.crt invalid.pem

View File

@ -62,12 +62,12 @@ int run_test(const char*, int, struct iri);
int
diff_iri(struct iri *p, struct iri *exp)
{
DIFF(p, exp, schema);
DIFF(p, exp, host);
DIFF(p, exp, port);
DIFF(p, exp, path);
DIFF(p, exp, query);
DIFF(p, exp, fragment);
DIFF(p, exp, schema);
DIFF(p, exp, host);
DIFF(p, exp, port);
DIFF(p, exp, path);
DIFF(p, exp, query);
DIFF(p, exp, fragment);
return 1;
}
@ -162,6 +162,14 @@ main(void)
PASS,
IRI("gemini", "naïve.omarpolo.com", "", "", "", ""),
"Can percent decode hostnames");
TEST("gemini://100.64.3.27/",
PASS,
IRI("gemini", "100.64.3.27", "", "", "", ""),
"Accepts IPv4 addresses");
TEST("gemini://[::1]/",
PASS,
IRI("gemini", "::1", "", "", "", ""),
"Accepts IPv6 addresses");
/* path */
TEST("gemini://omarpolo.com/foo/bar/baz",
@ -186,31 +194,31 @@ main(void)
"parse paths with multiple .. elements");
TEST("gemini://omarpolo.com/foo/..",
PASS,
IRI("gemini", "omarpolo.com", "", "", "", ""),
IRI("gemini", "omarpolo.com", "", "", "", ""),
"parse paths with a trailing ..");
TEST("gemini://omarpolo.com/foo/../",
PASS,
IRI("gemini", "omarpolo.com", "", "", "", ""),
IRI("gemini", "omarpolo.com", "", "", "", ""),
"parse paths with a trailing ..");
TEST("gemini://omarpolo.com/foo/../..",
FAIL,
empty,
"reject paths that would escape the root");
PASS,
IRI("gemini", "omarpolo.com", "", "", "", ""),
"parse paths that would escape the root");
TEST("gemini://omarpolo.com/foo/../../",
FAIL,
empty,
"reject paths that would escape the root")
PASS,
IRI("gemini", "omarpolo.com", "", "", "", ""),
"parse paths that would escape the root")
TEST("gemini://omarpolo.com/foo/../foo/../././/bar/baz/.././.././/",
PASS,
IRI("gemini", "omarpolo.com", "", "", "", ""),
IRI("gemini", "omarpolo.com", "", "", "", ""),
"parse path with lots of cleaning available");
TEST("gemini://omarpolo.com//foo",
PASS,
IRI("gemini", "omarpolo.com", "", "foo", "", ""),
IRI("gemini", "omarpolo.com", "", "foo", "", ""),
"Trim initial slashes");
TEST("gemini://omarpolo.com/////foo",
PASS,
IRI("gemini", "omarpolo.com", "", "foo", "", ""),
IRI("gemini", "omarpolo.com", "", "foo", "", ""),
"Trim initial slashes (pt. 2)");
TEST("http://a/b/c/../..",
PASS,
@ -263,8 +271,8 @@ main(void)
IRI("foo", "bar.com", "", "caffè+macchiato.gmi", "", ""),
"can decode");
TEST("foo://bar.com/foo%2F..%2F..",
FAIL,
empty,
PASS,
IRI("foo", "bar.com", "", "", "", ""),
"conversion and checking are done in the correct order");
TEST("foo://bar.com/foo%00?baz",
FAIL,
@ -272,7 +280,7 @@ main(void)
"rejects %00");
/* IRI */
TEST("foo://bar.com/cafè.gmi",
TEST("foo://bar.com/cafè.gmi",
PASS,
IRI("foo", "bar.com", "", "cafè.gmi", "" , ""),
"decode IRI (with a 2-byte utf8 seq)");

View File

@ -6,9 +6,12 @@ gemexp="./../gemexp"
gg="./../gg"
gmid="./../gmid"
current_test=
server_name=
gghost=
run_test() {
ggflags=
host="$REGRESS_HOST"
port=10965
config_common="log syslog off"
hdr=
@ -18,9 +21,15 @@ run_test() {
ran_no=$((ran_no + 1))
current_test=$1
server_name=localhost
gghost=localhost
rm -f reg.conf
if ! $1; then
if [ "$2" = "need_ipv6" -a "$HAVE_IPV6" != "yes" ]; then
echo "$1 skipped (needs HAVE_IPV6='yes')"
return
elif ! $1; then
echo "$1 failed"
failed="$failed $1"
failed_no=$((failed_no + 1))
@ -58,11 +67,11 @@ gen_config() {
cat <<EOF > reg.conf
$config_common
$1
server "localhost" {
server "$server_name" {
cert "$PWD/localhost.pem"
key "$PWD/localhost.key"
root "$PWD/testdata"
listen on $REGRESS_HOST port $port
listen on $host port $port
$2
}
EOF
@ -77,7 +86,7 @@ set_proxy() {
server "localhost.local" {
cert "$PWD/localhost.pem"
key "$PWD/localhost.key"
listen on $REGRESS_HOST port $port
listen on $host port $port
proxy {
relay-to localhost port $port
$1
@ -108,13 +117,13 @@ setup_simple_test() {
# usage: get <path>
# return the body of the request on stdout
get() {
$gg -T10 $ggflags "gemini://localhost:10965/$1" || true
$gg -q -T10 $ggflags "gemini://$gghost:10965/$1" || true
}
# usage: head <path>
# return the meta response line on stdout
head() {
$gg -T10 -d header $ggflags "gemini://localhost:10965/$1" || true
$gg -q -T10 -d header $ggflags "gemini://$gghost:10965/$1" || true
}
# usage: fetch <path>

View File

@ -19,8 +19,10 @@ fi
# Run standalone unit tests.
run_test test_punycode
run_test test_iri
run_test test_gg_n_flag
# Run configuration dumping test.
# Run configuration parsing tests.
run_test test_parse_comments_at_start
run_test test_dump_config
if [ "${SKIP_RUNTIME_TESTS:-0}" -eq 1 ]; then
@ -55,6 +57,7 @@ run_test test_root_inside_location
run_test test_root_inside_location_with_redirect
run_test test_fastcgi
run_test test_fastcgi_inside_location
run_test test_fastcgi_location_match
run_test test_fastcgi_deprecated_syntax
run_test test_macro_expansion
run_test test_proxy_relay_to
@ -62,9 +65,13 @@ run_test test_proxy_with_certs
# run_test test_unknown_host # XXX: breaks on some distro
run_test test_include_mime
run_test test_log_file
run_test test_ipv4_addr
run_test test_ipv6_addr need_ipv6
run_test test_ipv6_server need_ipv6
# TODO: add test that uses only a TLSv1.2 or TLSv1.3
# TODO: add a test that attempt to serve a non-regular file
# TODO: add a test where the index is not a regular file
# TODO: add a test that logs and uses a client cert
tests_done

View File

@ -8,6 +8,35 @@ test_iri() {
./iri_test
}
test_gg_n_flag() {
dont_check_server_alive=yes
$gg -n gemini://omarpolo.com/ || return 1
# XXX this fails on macos in the CI, while in
# test_iri passes successfully. Unfortunately,
# I can't debug stuff on darwin (lacking hardware.)
#$gg -n "foo://bar.com/cafè.gmi" || return 1
$gg -n gemini://omarpolo.com/../ || return 1
}
test_parse_comments_at_start() {
dont_check_server_alive=yes
cat <<EOF >reg.conf
# a comment
server "$server_name" {
cert "$PWD/localhost.pem"
key "$PWD/localhost.key"
root "$PWD/testdata"
listen on $host port $port
}
EOF
$gmid -n -c reg.conf >/dev/null
}
test_dump_config() {
dont_check_server_alive=yes
gen_config '' ''
@ -292,6 +321,35 @@ test_fastcgi_inside_location() {
return 0
}
test_fastcgi_location_match() {
./fcgi-test fcgi.sock &
fcgi_pid=$!
setup_simple_test 'prefork 1' '
location "/dir/*" {
fastcgi off
}
location "/*" {
fastcgi socket "'$PWD'/fcgi.sock"
}'
msg=$(printf "# hello from fastcgi!\nsome more content in the page...")
fetch /foo
if ! check_reply "20 text/gemini" "$msg"; then
kill $fcgi_pid
return 1
fi
fetch /dir/foo.gmi
if ! check_reply "20 text/gemini" "# hello world"; then
kill $fcgi_pid
return 1
fi
kill $fcgi_pid
return 0
}
test_fastcgi_deprecated_syntax() {
./fcgi-test fcgi.sock &
fcgi_pid=$!
@ -322,7 +380,7 @@ server "localhost" {
cert \$pwd "/localhost.pem"
key \$pwd "/localhost.key"
root \$pwd "/testdata"
listen on $REGRESS_HOST port $port
listen on $host port $port
@common
}
EOF
@ -430,3 +488,36 @@ log style legacy'
rm -f log log.edited
return 0
}
test_ipv4_addr() {
server_name="*"
host="127.0.0.1"
gghost=127.0.0.1
ggflags=-N
setup_simple_test
fetch /
check_reply "20 text/gemini" "# hello world" || return 1
}
test_ipv6_addr() {
server_name="*"
host="::1"
gghost="[::1]"
ggflags=-N
setup_simple_test
fetch /
check_reply "20 text/gemini" "# hello world" || return 1
}
test_ipv6_server() {
server_name="::1"
host="::1"
gghost="[::1]"
ggflags=-N
setup_simple_test
fetch /
check_reply "20 text/gemini" "# hello world" || return 1
}

View File

@ -31,7 +31,7 @@
#include "log.h"
#include "proc.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
@ -119,6 +119,15 @@ match_host(struct vhost *v, struct client *c)
if (addr == NULL)
return 0;
if (*c->domain == '\0') {
if (strlcpy(c->domain, addr->pp, sizeof(c->domain))
>= sizeof(c->domain)) {
log_warnx("%s: domain too long: %s", __func__,
addr->pp);
*c->domain = '\0';
}
}
if (matches(v->domain, c->domain))
return 1;
@ -246,7 +255,6 @@ struct location *
vhost_fastcgi(struct vhost *v, const char *path)
{
struct location *loc;
int force_disable = 0;
if (v == NULL || path == NULL)
return NULL;
@ -257,12 +265,9 @@ vhost_fastcgi(struct vhost *v, const char *path)
if (matches(loc->match, path))
return loc;
if (loc->nofcgi && matches(loc->match, path))
force_disable = 1;
return NULL;
}
if (force_disable)
return NULL;
loc = TAILQ_FIRST(&v->locations);
return loc->fcgi == -1 ? NULL : loc;
}
@ -385,7 +390,7 @@ handle_handshake(int fd, short ev, void *d)
return;
default:
/* unreachable */
abort();
fatalx("unexpected return value from tls_handshake");
}
c->bev = bufferevent_new(fd, client_read, client_write,
@ -403,16 +408,19 @@ handle_handshake(int fd, short ev, void *d)
evbuffer_unfreeze(c->bev->output, 1);
#endif
if ((servname = tls_conn_servername(c->ctx)) == NULL) {
if ((servname = tls_conn_servername(c->ctx)) == NULL)
log_debug("handshake: missing SNI");
goto err;
}
if (!puny_decode(servname, c->domain, sizeof(c->domain), &parse_err)) {
log_info("puny_decode: %s", parse_err);
goto err;
start_reply(c, BAD_REQUEST, "Wrong/malformed host");
return;
}
/*
* match_addr will serialize the (matching) address if c->domain
* is empty, so that we can support requests for raw IPv6 address
* that can't have a SNI.
*/
TAILQ_FOREACH(h, &conf->hosts, vhosts)
if (match_host(h, c))
break;
@ -428,8 +436,7 @@ handle_handshake(int fd, short ev, void *d)
return;
}
err:
start_reply(c, BAD_REQUEST, "Wrong/malformed host or missing SNI");
start_reply(c, BAD_REQUEST, "Wrong/malformed host");
}
static void
@ -607,8 +614,7 @@ apply_reverse_proxy(struct client *c)
if (p->reqca != NULL && check_matching_certificate(p->reqca, c))
return 1;
log_debug("opening proxy connection for %s:%s",
p->host, p->port);
log_debug("opening proxy connection for %s:%s", p->host, p->port);
if ((c->pfd = proxy_socket(c, p->host, p->port)) == -1) {
start_reply(c, PROXY_ERROR, "proxy error");
@ -712,8 +718,7 @@ apply_fastcgi(struct client *c)
return 0;
}
log_debug("opening fastcgi connection for (%s,%s)",
f->path, f->port);
log_debug("opening fastcgi connection for (%s,%s)", f->path, f->port);
if (*f->port == '\0')
c->pfd = fcgi_open_sock(f);
@ -812,8 +817,7 @@ open_dir(struct client *c)
return;
}
strlcpy(path, c->iri.path, sizeof(path));
strlcat(path, index, sizeof(path));
snprintf(path, sizeof(path), "%s%s", c->iri.path, index);
close(c->pfd);
c->pfd = fd;
@ -853,7 +857,7 @@ client_tls_readcb(int fd, short event, void *d)
}
if (bufev->wm_read.high != 0)
howmuch = MIN(sizeof(buf), bufev->wm_read.high);
howmuch = MINIMUM(sizeof(buf), bufev->wm_read.high);
switch (ret = tls_read(client->ctx, buf, howmuch)) {
case TLS_WANT_POLLIN:
@ -951,6 +955,8 @@ client_read(struct bufferevent *bev, void *d)
struct evbuffer *src = EVBUFFER_INPUT(bev);
const char *path, *p, *parse_err = "invalid request";
char decoded[DOMAIN_NAME_LEN];
char *nul;
size_t len;
bufferevent_disable(bev, EVBUFFER_READ);
@ -981,6 +987,14 @@ client_read(struct bufferevent *bev, void *d)
return;
}
nul = strchr(c->req, '\0');
len = nul - c->req;
if (len != c->reqlen) {
log_debug("NUL inside the request IRI");
start_reply(c, BAD_REQUEST, "bad request");
return;
}
if (!parse_iri(c->req, &c->iri, &parse_err) ||
!puny_decode(c->iri.host, decoded, sizeof(decoded), &parse_err)) {
log_debug("IRI parse error: %s", parse_err);
@ -1294,7 +1308,8 @@ server_accept(int sock, short et, void *d)
if (errno == EWOULDBLOCK || errno == EAGAIN ||
errno == ECONNABORTED)
return;
fatal("accept");
log_warnx("accept failed");
return;
}
mark_nonblock(fd);

View File

@ -21,7 +21,7 @@ REPOLOGY_URL = https://repology.org/project/gmid/versions
SUBST = ./subst GITHUB=https://github.com/omar-polo/gmid \
SITE=https://ftp.omarpolo.com \
VERS=2.0.2 \
VERS=2.0.5 \
PUBKEY=gmid-2.0.pub \
TREE=https://github.com/omar-polo/gmid/blob/master

View File

@ -1,12 +1,34 @@
# change log
## 2024/06/11 - 2.0.5 “Lady Stardust” security release
This release fixes a logic error that can result in a DoS; therefore is a strongly reccomended update for all users. It's safe to update to it from any version of the 2.0.x series.
* allow again empty lines at the start of the configuration file
* change how strnvis(3) is handled: on systems with the broken interface gmid will just use its own built-in version
* reject requests with NUL bytes in them.
* don't error on a '..' component at the start of the path.
## 2024/06/06 - 2.0.4 “Lady Stardust” bugfix release
* add a nicer error message if the removed `cgi' option is still used. Reported by freezr.
* portability fix for systems with a wrong strnvis(3).
## 2024/06/04 - 2.0.3 “Lady Stardust” bugfix release
* relax the SNI requirements
* gg: add -q to avoid printing the "Server Says:" line
* gg: unbreak -n
* fix parsing of IPv6 addresses
* fix `fastcgi off' handling
## 2024/04/04 - 2.0.2 “Lady Stardust” bugfix release
- fix `log access path' with `chroot' enabled.
- fix config dumping (-nn).
- rework grammar to allow semicolors after top-level statements.
- don't make the log styles reserved keywords.
- contrib/vim: fixed indent, from Anna “CyberTailor”, thanks!
* fix `log access path' with `chroot' enabled.
* fix config dumping (-nn).
* rework grammar to allow semicolons after top-level statements.
* don't make the log styles reserved keywords.
* contrib/vim: fixed indent, from Anna “CyberTailor”, thanks!
## 2024/01/24 - 2.0.1 “Lady Stardust” bugfix release

View File

@ -22,6 +22,7 @@
#include <errno.h>
#include <string.h>
#include <vis.h> /* for gmid_strnvis() */
#include <openssl/ec.h>
#include <openssl/err.h>
@ -247,7 +248,7 @@ gencert(const char *hostname, const char *certpath, const char *keypath,
ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 315360000L); /* 10 years */
X509_set_version(x509, 2); // v3
X509_set_version(x509, 2); /* v3 */
if (!X509_set_pubkey(x509, pkey)) {
log_warnx("couldn't set the public key");