First rework of path handling. - geomyidae - A small C-based gopherd. (gopher://bitreich.org/1/scm/geomyidae)
 (HTM) git clone git://r-36.net/geomyidae
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit b12a77acd24fc170b1ad047986ffaf13592fb326
 (DIR) parent f66a8a67b9471909016d6f24ce93f39584130a67
 (HTM) Author: Christoph Lohmann <20h@r-36.net>
       Date:   Thu, 20 Jul 2023 06:30:24 +0200
       
       First rework of path handling.
       
       * Renaming the gph functions.
       * Beware, still full of debug functions.
       
       Diffstat:
         M handlr.c                            |     112 +++++++++++++++++--------------
         M handlr.h                            |      25 ++++++++++++++++++++-----
         M ind.c                               |     133 ++++++++++++++++---------------
         M ind.h                               |      41 ++++++++++++++++---------------
         M index.gph                           |       4 +++-
         M main.c                              |     144 ++++++++++++++++++++++---------
       
       6 files changed, 277 insertions(+), 182 deletions(-)
       ---
 (DIR) diff --git a/handlr.c b/handlr.c
       @@ -17,6 +17,7 @@
        #include <dirent.h>
        #include <sys/wait.h>
        #include <errno.h>
       +#include <libgen.h>
        
        #include "ind.h"
        #include "arg.h"
       @@ -25,7 +26,7 @@ void
        handledir(int sock, char *path, char *port, char *base, char *args,
                        char *sear, char *ohost, char *chost, char *bhost, int istls)
        {
       -        char *pa, *file, *e, *par, *b;
       +        char *pa, *file, *e, *par;
                struct dirent **dirent;
                int ndir, i, ret = 0;
                struct stat st;
       @@ -35,22 +36,25 @@ handledir(int sock, char *path, char *port, char *base, char *args,
                USED(sear);
                USED(bhost);
        
       -        pa = xstrdup(path);
       -        e = pa + strlen(pa) - 1;
       -        if (e > pa && e[0] == '/')
       -                *e = '\0';
       +        printf("handledir:\n");
       +        printf("sock = %d; path = %s; port = %s; base = %s; args = %s;\n",
       +                        sock, path, port, base, args);
       +        printf("sear = %s; ohost = %s; chost = %s; bhost = %s; istls = %d;\n",
       +                        sear, ohost, chost, bhost, istls);
        
       -        par = xstrdup(pa);
       +        pa = xstrdup(path);
        
       -        b = strrchr(makebasepath(par, base), '/');
       -        if (b != NULL) {
       -                *b = '\0';
       +        /* Is there any directory below the request? */
       +        if (strlen(pa+strlen(base)) > 1) {
       +                par = xstrdup(pa+strlen(base));
       +                e = strrchr(par, '/');
       +                *e = '\0';
                        dprintf(sock, "1..\t%s\t%s\t%s\r\n",
       -                        makebasepath(par, base), ohost, port);
       +                        par, ohost, port);
       +                free(par);
                }
       -        free(par);
        
       -        ndir = scandir(pa[0] ? pa : ".", &dirent, 0, alphasort);
       +        ndir = scandir(pa, &dirent, 0, alphasort);
                if (ndir < 0) {
                        perror("scandir");
                        free(pa);
       @@ -61,19 +65,21 @@ handledir(int sock, char *path, char *port, char *base, char *args,
                                        continue;
        
                                type = gettype(dirent[i]->d_name);
       -                        file = smprintf("%s%s%s", pa,
       -                                        pa[0] == '/' && pa[1] == '\0' ? "" : "/",
       +
       +                        file = smprintf("%s%s%s",
       +                                        pa,
       +                                        pa[strlen(pa)-1] == '/'? "" : "/",
                                                dirent[i]->d_name);
       +                        printf("handledir: smprintf file = %s\n", file);
                                if (stat(file, &st) >= 0 && S_ISDIR(st.st_mode))
                                        type = gettype("index.gph");
       -                        e = makebasepath(file, base);
                                ret = dprintf(sock,
                                                "%c%-50.50s %10s %16s\t%s\t%s\t%s\r\n",
                                                *type->type,
                                                dirent[i]->d_name,
                                                humansize(st.st_size),
                                                humantime(&(st.st_mtime)),
       -                                        e, ohost, port);
       +                                        file + strlen(base), ohost, port);
                                free(file);
                        }
                        for (i = 0; i < ndir; i++)
       @@ -89,24 +95,30 @@ void
        handlegph(int sock, char *file, char *port, char *base, char *args,
                        char *sear, char *ohost, char *chost, char *bhost, int istls)
        {
       -        Indexs *act;
       +        gphindex *act;
                int i, ret = 0;
        
                USED(args);
                USED(sear);
                USED(bhost);
        
       -        act = scanfile(file);
       +        printf("handlegph:\n");
       +        printf("sock = %d; file = %s; port = %s; base = %s; args = %s;\n",
       +                        sock, file, port, base, args);
       +        printf("sear = %s; ohost = %s; chost = %s; bhost = %s; istls = %d;\n",
       +                        sear, ohost, chost, bhost, istls);
       +
       +        act = gph_scanfile(file);
                if (act != NULL) {
                        for (i = 0; i < act->num && ret >= 0; i++)
       -                        ret = printelem(sock, act->n[i], file, base, ohost, port);
       +                        ret = gph_printelem(sock, act->n[i], file, base, ohost, port);
                        dprintf(sock, ".\r\n");
        
                        for (i = 0; i < act->num; i++) {
       -                        freeelem(act->n[i]);
       +                        gph_freeelem(act->n[i]);
                                act->n[i] = NULL;
                        }
       -                freeindex(act);
       +                gph_freeindex(act);
                }
        }
        
       @@ -135,21 +147,22 @@ void
        handlecgi(int sock, char *file, char *port, char *base, char *args,
                        char *sear, char *ohost, char *chost, char *bhost, int istls)
        {
       -        char *p, *path;
       +        char *script, *path;
        
                USED(base);
                USED(port);
        
       -        path = xstrdup(file);
       -        p = strrchr(path, '/');
       -        if (p != NULL)
       -                p[1] = '\0';
       -        else {
       -                free(path);
       -                path = NULL;
       -        }
       +        printf("handlecgi:\n");
       +        printf("sock = %d; file = %s; port = %s; base = %s; args = %s;\n",
       +                        sock, file, port, base, args);
       +        printf("sear = %s; ohost = %s; chost = %s; bhost = %s; istls = %d;\n",
       +                        sear, ohost, chost, bhost, istls);
        
       -        p = makebasepath(file, base);
       +        path = xstrdup(file);
       +        path = dirname(path);
       +        script = path + strlen(path) + 1;
       +        printf("path = %s\n", path);
       +        printf("script = %s\n", script);
        
                if (sear == NULL)
                        sear = "";
       @@ -166,10 +179,10 @@ handlecgi(int sock, char *file, char *port, char *base, char *args,
                                        break;
                        }
        
       -                setcgienviron(p, file, port, base, args, sear, ohost, chost,
       +                setcgienviron(script, file, port, base, args, sear, ohost, chost,
                                        bhost, istls);
        
       -                if (execl(file, p, sear, args, ohost, port,
       +                if (execl(file, script, sear, args, ohost, port,
                                        (char *)NULL) == -1) {
                                perror("execl");
                                _exit(1);
       @@ -189,25 +202,24 @@ handledcgi(int sock, char *file, char *port, char *base, char *args,
                        char *sear, char *ohost, char *chost, char *bhost, int istls)
        {
                FILE *fp;
       -        char *p, *path, *ln = NULL;
       +        char *script, *path, *ln = NULL;
                size_t linesiz = 0;
                ssize_t n;
                int outsocks[2], ret = 0;
       -        Elems *el;
       +        gphelem *el;
       +
       +        printf("handledcgi:\n");
       +        printf("sock = %d; file = %s; port = %s; base = %s; args = %s;\n",
       +                        sock, file, port, base, args);
       +        printf("sear = %s; ohost = %s; chost = %s; bhost = %s; istls = %d;\n",
       +                        sear, ohost, chost, bhost, istls);
        
                if (socketpair(AF_LOCAL, SOCK_STREAM, 0, outsocks) < 0)
                        return;
        
                path = xstrdup(file);
       -        p = strrchr(path, '/');
       -        if (p != NULL)
       -                p[1] = '\0';
       -        else {
       -                free(path);
       -                path = NULL;
       -        }
       -
       -        p = makebasepath(file, base);
       +        path = dirname(path);
       +        script = path + strlen(path) + 1;
        
                if (sear == NULL)
                        sear = "";
       @@ -225,10 +237,10 @@ handledcgi(int sock, char *file, char *port, char *base, char *args,
                                        break;
                        }
        
       -                setcgienviron(p, file, port, base, args, sear, ohost, chost,
       +                setcgienviron(script, file, port, base, args, sear, ohost, chost,
                                        bhost, istls);
        
       -                if (execl(file, p, sear, args, ohost, port,
       +                if (execl(file, script, sear, args, ohost, port,
                                        (char *)NULL) == -1) {
                                perror("execl");
                                _exit(1);
       @@ -251,21 +263,21 @@ handledcgi(int sock, char *file, char *port, char *base, char *args,
                                if (ln[n - 1] == '\n')
                                        ln[--n] = '\0';
        
       -                        el = getadv(ln);
       +                        el = gph_getadv(ln);
                                if (el == NULL)
                                        continue;
        
       -                        ret = printelem(sock, el, file, base, ohost, port);
       -                        freeelem(el);
       +                        ret = gph_printelem(sock, el, file, base, ohost, port);
       +                        gph_freeelem(el);
                        }
                        if (ferror(fp))
                                perror("getline");
                        dprintf(sock, ".\r\n");
        
                        free(ln);
       -                free(path);
                        fclose(fp);
                        wait(NULL);
       +                free(path);
                        break;
                }
        }
 (DIR) diff --git a/handlr.h b/handlr.h
       @@ -9,16 +9,31 @@
        /*
         * Handler API definition
         *
       - * path .... absolute path to the script
       + *   Sample: /get/some/script/with/dirs////?key=value\tsearch what?\r\n
       + *         * in /get/some/script is a file "index.dcgi"
       + *         * request to bitreich.org on port 70 using TLS
       + *         * base is in /var/gopher
       + *         * client from 85.65.4.2
       + *
       + * path/file absolute path to the script/directory, always starts with '/'
       + *   Sample: /var/gopher/get/some/script/index.dcgi
         * port .... port which the script should use when defining menu items
         *             (See -o and -p in geomyidae(8))
       - * base .... base path of geomyidae ("" in case of chroot)
       - * args .... query string parsed after »script?query«
       - * sear .... search part of request (»selector\tsearch\r\n«)
       - * ohost ... host of geomiydae (See -h in geomyidae(8))
       + *   Sample: 70
       + * base .... base path of geomyidae, never ends in '/', so chroot is ''
       + *   Sample: /var/gopher
       + * args .... Gives all variable input from the selector in some way.
       + *   Sample: /with/dirs////?key=value
       + * sear .... search part of request
       + *   Sample: search what?
       + * ohost ... host of geomyidae (See -h in geomyidae(8))
       + *   Sample: bitreich.org
         * chost ... IP of the client sending a request
       + *   Sample: 85.65.4.2
         * bhost ... server IP the server received the connection to
       + *   Sample: 78.46.175.99
         * istls ... set to 1, if TLS was used for thr request
       + *   Sample: 1
         */
        
        void handledir(int sock, char *path, char *port, char *base, char *args,
 (DIR) diff --git a/ind.c b/ind.c
       @@ -24,6 +24,7 @@
        #include <arpa/inet.h>
        #include <sys/ioctl.h>
        #include <limits.h>
       +#include <errno.h>
        
        #define PAGE_SHIFT 12
        #define PAGE_SIZE (1UL << PAGE_SHIFT)
       @@ -258,7 +259,7 @@ gettype(char *filename)
        }
        
        void
       -freeelem(Elems *e)
       +gph_freeelem(gphelem *e)
        {
                if (e != NULL) {
                        if (e->e != NULL) {
       @@ -273,12 +274,12 @@ freeelem(Elems *e)
        }
        
        void
       -freeindex(Indexs *i)
       +gph_freeindex(gphindex *i)
        {
                if (i != NULL) {
                        if (i->n != NULL) {
                                for (;i->num > 0; i->num--)
       -                                freeelem(i->n[i->num - 1]);
       +                                gph_freeelem(i->n[i->num - 1]);
                                free(i->n);
                        }
                        free(i);
       @@ -288,7 +289,7 @@ freeindex(Indexs *i)
        }
        
        void
       -addelem(Elems *e, char *s)
       +gph_addelem(gphelem *e, char *s)
        {
                e->num++;
                e->e = xrealloc(e->e, sizeof(char *) * e->num);
       @@ -297,23 +298,23 @@ addelem(Elems *e, char *s)
                return;
        }
        
       -Elems *
       -getadv(char *str)
       +gphelem *
       +gph_getadv(char *str)
        {
                char *b, *e, *o, *bo;
       -        Elems *ret;
       +        gphelem *ret;
        
       -        ret = xcalloc(1, sizeof(Elems));
       +        ret = xcalloc(1, sizeof(gphelem));
        
                if (strchr(str, '\t')) {
       -                addelem(ret, "i");
       -                addelem(ret, "Happy helping ☃ here: You tried to "
       +                gph_addelem(ret, "i");
       +                gph_addelem(ret, "Happy helping ☃ here: You tried to "
                                "output a spurious TAB character. This will "
                                "break gopher. Please review your scripts. "
                                "Have a nice day!");
       -                addelem(ret, "Err");
       -                addelem(ret, "server");
       -                addelem(ret, "port");
       +                gph_addelem(ret, "Err");
       +                gph_addelem(ret, "server");
       +                gph_addelem(ret, "port");
        
                        return ret;
                }
       @@ -331,7 +332,7 @@ getadv(char *str)
                                }
                                *e = '\0';
                                e++;
       -                        addelem(ret, b);
       +                        gph_addelem(ret, b);
                                b = e;
                                bo = b;
                        }
       @@ -339,7 +340,7 @@ getadv(char *str)
                        e = strchr(b, ']');
                        if (e != NULL) {
                                *e = '\0';
       -                        addelem(ret, b);
       +                        gph_addelem(ret, b);
                        }
                        free(o);
        
       @@ -349,55 +350,55 @@ getadv(char *str)
                        }
        
                        /* Invalid entry: Give back the whole line. */
       -                freeelem(ret);
       -                ret = xcalloc(1, sizeof(Elems));
       +                gph_freeelem(ret);
       +                ret = xcalloc(1, sizeof(gphelem));
                }
        
       -        addelem(ret, "i");
       +        gph_addelem(ret, "i");
                /* Jump over escape sequence. */
                if (str[0] == '[' && str[1] == '|')
                        str += 2;
       -        addelem(ret, str);
       -        addelem(ret, "Err");
       -        addelem(ret, "server");
       -        addelem(ret, "port");
       +        gph_addelem(ret, str);
       +        gph_addelem(ret, "Err");
       +        gph_addelem(ret, "server");
       +        gph_addelem(ret, "port");
        
                return ret;
        }
        
        void
       -addindexs(Indexs *idx, Elems *el)
       +gph_addindex(gphindex *idx, gphelem *el)
        {
                idx->num++;
       -        idx->n = xrealloc(idx->n, sizeof(Elems) * idx->num);
       +        idx->n = xrealloc(idx->n, sizeof(gphelem *) * idx->num);
                idx->n[idx->num - 1] = el;
        
                return;
        }
        
       -Indexs *
       -scanfile(char *fname)
       +gphindex *
       +gph_scanfile(char *fname)
        {
                char *ln = NULL;
                size_t linesiz = 0;
                ssize_t n;
                FILE *fp;
       -        Indexs *ret;
       -        Elems *el;
       +        gphindex *ret;
       +        gphelem *el;
        
                if (!(fp = fopen(fname, "r")))
                        return NULL;
        
       -        ret = xcalloc(1, sizeof(Indexs));
       +        ret = xcalloc(1, sizeof(gphindex));
        
                while ((n = getline(&ln, &linesiz, fp)) > 0) {
                        if (ln[n - 1] == '\n')
                                ln[--n] = '\0';
       -                el = getadv(ln);
       +                el = gph_getadv(ln);
                        if (el == NULL)
                                continue;
        
       -                addindexs(ret, el);
       +                gph_addindex(ret, el);
                }
                if (ferror(fp))
                        perror("getline");
       @@ -413,9 +414,9 @@ scanfile(char *fname)
        }
        
        int
       -printelem(int fd, Elems *el, char *file, char *base, char *addr, char *port)
       +gph_printelem(int fd, gphelem *el, char *file, char *base, char *addr, char *port)
        {
       -        char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase;
       +        char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase, *rpath;
                int len, blen;
        
                if (!strcmp(el->e[3], "server")) {
       @@ -432,10 +433,6 @@ printelem(int fd, Elems *el, char *file, char *base, char *addr, char *port)
                 * some URL and ignore various types that have different semantics,
                 * do not point to some file or directory.
                 */
       -        /*
       -         * FUTURE: If ever special requests with no beginning '/' are used in
       -         * geomyidae, this is the place to control this.
       -         */
                if ((el->e[2][0] != '\0'
                    && el->e[2][0] != '/' /* Absolute Request. */
                    && el->e[0][0] != 'i' /* Informational item. */
       @@ -446,45 +443,50 @@ printelem(int fd, Elems *el, char *file, char *base, char *addr, char *port)
                    && el->e[0][0] != 'T') && /* tn3270 */
                    !(el->e[0][0] == 'h' && !strncmp(el->e[2], "URL:", 4))) {
                        path = file + strlen(base);
       -                if ((p = strrchr(path, '/')))
       -                        len = p - path;
       -                else
       +
       +                /* Strip off original gph file name. */
       +                if ((p = strrchr(path, '/'))) {
       +                        len = strlen(path) - strlen(basename(path));
       +                } else {
                                len = strlen(path);
       +                }
        
                        /* Strip off arguments for realpath. */
                        argbase = strchr(el->e[2], '?');
       -                if (argbase != NULL)
       +                if (argbase != NULL) {
                                blen = argbase - el->e[2];
       -                else
       +                } else {
                                blen = strlen(el->e[2]);
       +                }
        
       -                snprintf(buf, sizeof(buf), "%s%.*s/%.*s", base, len,
       +                /*
       +                 * Print everything together. Realpath will resolve it.
       +                 */
       +                snprintf(buf, sizeof(buf), "%s%.*s%.*s", base, len,
                                path, blen, el->e[2]);
        
       -                if ((path = realpath(buf, NULL)) &&
       -                                (realbase = realpath(base, NULL)) &&
       -                                !strncmp(realbase, path, strlen(realbase))) {
       -                        p = path + strlen(realbase);
       +                if ((rpath = realpath(buf, NULL)) &&
       +                                (realbase = realpath(*base? base : "/", NULL)) &&
       +                                !strncmp(realbase, rpath, strlen(realbase))) {
       +                        p = rpath + (*base? strlen(realbase) : 0);
        
                                /*
       -                         * Do not forget to readd arguments which were
       +                         * Do not forget to re-add arguments which were
                                 * stripped off.
                                 */
       -                        if (argbase != NULL)
       -                                argp = smprintf("%s%s", p[0]? p : "/", argbase);
       -                        else
       -                                argp = xstrdup(p[0]? p : "/");
       +                        argp = smprintf("%s%s", *p? p : "/", argbase? argbase : "");
        
                                free(el->e[2]);
                                el->e[2] = argp;
                                free(realbase);
                        }
       -                free(path);
       +                if (rpath != NULL)
       +                        free(rpath);
                }
        
                if (dprintf(fd, "%.1s%s\t%s\t%s\t%s\r\n", el->e[0], el->e[1], el->e[2],
                                el->e[3], el->e[4]) < 0) {
       -                perror("printelem: dprintf");
       +                perror("printgphelem: dprintf");
                        return -1;
                }
                return 0;
       @@ -545,19 +547,26 @@ setcgienviron(char *file, char *path, char *port, char *base, char *args,
                setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
                /* TODO: Separate, if run like rest.dcgi. */
                setenv("PATH_INFO", file, 1);
       +        printf("PATH_INFO = %s\n", file);
                setenv("PATH_TRANSLATED", path, 1);
       +        printf("PATH_TRANSLATED = %s\n", path);
        
                setenv("QUERY_STRING", args, 1);
       +        printf("QUERY_STRING = %s\n", args);
                /* legacy compatibility */
                setenv("SELECTOR", args, 1);
       +        printf("SELECTOR = %s\n", args);
                setenv("REQUEST", args, 1);
       +        printf("REQUEST = %s\n", args);
        
                setenv("REMOTE_ADDR", chost, 1);
       +        printf("REMOTE_ADDR = %s\n", chost);
                /*
                 * Don't do a reverse lookup on every call. Only do when needed, in
                 * the script. The RFC allows us to set the IP to the value.
                 */
                setenv("REMOTE_HOST", chost, 1);
       +        printf("REMOTE_HOST = %s\n", chost);
                /* Please do not implement identd here. */
                unsetenv("REMOTE_IDENT");
                unsetenv("REMOTE_USER");
       @@ -569,9 +578,12 @@ setcgienviron(char *file, char *path, char *port, char *base, char *args,
                 */
                setenv("REQUEST_METHOD", "GET", 1);
                setenv("SCRIPT_NAME", file, 1);
       +        printf("SCRIPT_NAME = %s\n", file);
                setenv("SERVER_NAME", ohost, 1);
       +        printf("SERVER_PORT = %s\n", port);
                setenv("SERVER_PORT", port, 1);
                setenv("SERVER_LISTEN_NAME", bhost, 1);
       +        printf("SERVER_LISTEN_NAME = %s\n", bhost);
                if (istls) {
                        setenv("SERVER_PROTOCOL", "gophers/1.0", 1);
                } else {
       @@ -580,8 +592,10 @@ setcgienviron(char *file, char *path, char *port, char *base, char *args,
                setenv("SERVER_SOFTWARE", "geomyidae", 1);
        
                setenv("X_GOPHER_SEARCH", sear, 1);
       +        printf("X_GOPHER_SEARCH = %s\n", sear);
                /* legacy compatibility */
                setenv("SEARCHREQUEST", sear, 1);
       +        printf("SEARCHREQUEST = %s\n", sear);
        
                if (istls) {
                        setenv("GOPHERS", "on", 1);
       @@ -626,14 +640,3 @@ humantime(const time_t *clock)
                return buf;
        }
        
       -char *
       -makebasepath(char *path, char *base)
       -{
       -        if (!(base[0] == '/' && base[1] == '\0') &&
       -                        strlen(path) > strlen(base)) {
       -                return path + strlen(base);
       -        } else {
       -                return path;
       -        }
       -}
       -
 (DIR) diff --git a/ind.h b/ind.h
       @@ -10,18 +10,6 @@
        
        extern int glfd;
        
       -typedef struct Elems Elems;
       -struct Elems {
       -        char **e;
       -        int num;
       -};
       -
       -typedef struct Indexs Indexs;
       -struct Indexs {
       -        Elems **n;
       -        int num;
       -};
       -
        typedef struct filetype filetype;
        struct filetype {
                char *end;
       @@ -31,20 +19,34 @@ struct filetype {
        };
        
        filetype *gettype(char *filename);
       +
       +typedef struct gphelem gphelem;
       +struct gphelem {
       +        char **e;
       +        int num;
       +};
       +
       +typedef struct gphindex gphindex;
       +struct gphindex {
       +        gphelem **n;
       +        int num;
       +};
       +
       +gphindex *gph_scanfile(char *fname);
       +gphelem *gph_getadv(char *str);
       +int gph_printelem(int fd, gphelem *el, char *file, char *base, char *addr, char *port);
       +void gph_addindex(gphindex *idx, gphelem *el);
       +void gph_addelem(gphelem *e, char *s);
       +void gph_freeindex(gphindex *i);
       +void gph_freeelem(gphelem *e);
       +
        void *xcalloc(size_t, size_t);
        void *xmalloc(size_t);
        void *xrealloc(void *, size_t);
        char *xstrdup(const char *str);
        int xsendfile(int, int);
       -Indexs *scanfile(char *fname);
       -Elems *getadv(char *str);
        int pendingbytes(int sock);
        void waitforpendingbytes(int sock);
       -int printelem(int fd, Elems *el, char *file, char *base, char *addr, char *port);
       -void addindexs(Indexs *idx, Elems *el);
       -void addelem(Elems *e, char *s);
       -void freeindex(Indexs *i);
       -void freeelem(Elems *e);
        char *smprintf(char *fmt, ...);
        char *reverselookup(char *host);
        void setcgienviron(char *file, char *path, char *port, char *base,
       @@ -52,7 +54,6 @@ void setcgienviron(char *file, char *path, char *port, char *base,
                        char *bhost, int istls);
        char *humansize(off_t n);
        char *humantime(const time_t *clock);
       -char *makebasepath(char *path, char *base);
        
        #endif
        
 (DIR) diff --git a/index.gph b/index.gph
       @@ -3,9 +3,11 @@ tcomment (old style comment)
        [1|R-36|/|server|port]
        [0|file - comment|/file.txt|server|port]
        [h|http://www.heise.de|URL:http://www.heise.de|server|port]
       -[0|some \| escape and [ special characters ] test|error|server|port]
       +[0|some \| escape and [ special characters ] test|Err|server|port]
        [9|binary data file|/file.dat|server|port]
        [9|unclosed entry|/file.dat|server|port
       +[9|some relative path with args|./legacy/caps.txt?key=value|server|port]
       +[9|some other relative path|legacy/caps.txt|server|port]
        [|empty type||server|port]
        [|Escape something, [| is skipped.
        some        invalid line
 (DIR) diff --git a/main.c b/main.c
       @@ -59,7 +59,7 @@ int nlistfds = 0;
        char *argv0;
        char stdbase[] = "/var/gopher";
        char *stdport = "70";
       -char *indexf[] = {"/index.gph", "/index.cgi", "/index.dcgi", "/index.bob", "/index.bin"};
       +char *indexf[] = {"index.gph", "index.cgi", "index.dcgi", "index.bob", "index.bin"};
        char *nocgierr = "3Sorry, execution of the token '%s' was requested, but this "
                    "is disabled in the server configuration.\tErr"
                    "\tlocalhost\t70\r\n";
       @@ -83,7 +83,7 @@ char *htredir = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                        "  </body>\n"
                        "</html>\n";
        char *selinval ="3Happy helping ☃ here: "
       -                "Sorry, your selector does not start with / or contains '..'. "
       +                "Sorry, your selector does contains '..'. "
                        "That's illegal here.\tErr\tlocalhost\t70\r\n.\r\n\r\n";
        
        int
       @@ -142,12 +142,20 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                int len = 0, fd, i, maxrecv, pathfallthrough = 0;
                filetype *type;
        
       +        printf("handlerequest:\n");
       +        printf("sock = %d; req = '%s';\n", sock, req);
       +        printf("rlen = %d; base = '%s'; ohost = '%s'; port = %s;\n", rlen,
       +                        base, ohost, port);
       +        printf("clienth = %s; clientp = %s; serverh = %s; serverp = %s;\n",
       +                        clienth, clientp, serverh, serverp);
       +        printf("nocgi = %d; istls = %d;\n", nocgi, istls);
       +
                if (!istls) {
                        /*
                         * If sticky bit is set on base dir and encryption is not
                         * used, do not serve.
                         */
       -                if (stat(base, &dir) == -1)
       +                if (stat(*base? base : "/", &dir) == -1)
                                return;
                        if (dir.st_mode & S_ISVTX) {
                                dprintf(sock, tlserr, recvc);
       @@ -176,6 +184,7 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                c = strchr(recvb, '\n');
                if (c)
                        c[0] = '\0';
       +
                sear = strchr(recvb, '\t');
                if (sear != NULL) {
                        *sear++ = '\0';
       @@ -228,29 +237,32 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                 * selectors.
                 */
        
       +        /* Strip off the arguments of req?args style. */
                c = strchr(recvb, '?');
                if (c != NULL) {
                        *c++ = '\0';
       -                snprintf(args, sizeof(args), "%s", c);
       +                snprintf(args, sizeof(args), "?%s", c);
                }
       +        printf("args = %s\n", args);
       +        printf("recvb = %s\n", recvb);
        
       -        if (recvb[0] == '\0') {
       -                recvb[0] = '/';
       -                recvb[1] = '\0';
       +        /* Strip '/' at the end of the request. */
       +        for (c = recvb + strlen(recvb) - 1; c >= recvb && c[0] == '/'; c--) {
       +                /* Prepend to args. */
       +                snprintf(args, sizeof(args), "/%s", args);
       +                c[0] = '\0';
                }
       +        printf("args = %s\n", args);
        
       -        /*
       -         * Do not allow requests not beginning with '/' or which contain
       -         * "..".
       -         */
       -        if (recvb[0] != '/' || strstr(recvb, "..")){
       +        /* Do not allow requests including "..". */
       +        if (strstr(recvb, "..")) {
                        dprintf(sock, "%s", selinval);
                        return;
                }
        
       -        /* append base to request path (always starting with /), if base is a chroot don't append '/' */
       -        if (snprintf(path, sizeof(path), "%s%s",
       -            base[0] == '/' && base[1] == '\0' ? "" : base,
       +        printf("recvb = %s\n", recvb);
       +        if (snprintf(path, sizeof(path), "%s%s%s", base,
       +            (*recvb != '/')? "/" : "",
                    recvb) > sizeof(path)) {
                        if (loglvl & ERRORS) {
                                logentry(clienth, clientp, recvc,
       @@ -259,6 +271,8 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                        dprintf(sock, toolongerr, recvc);
                        return;
                }
       +        /* path is now always at least '/' */
       +        printf("path = %s\n", path);
        
                fd = -1;
                /*
       @@ -270,33 +284,54 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                 *        $args = $rest_of_path + "?" + $args
                 */
                if (stat(path, &dir) == -1) {
       +                printf("Not found. Try backtraversal.\n");
                        memmove(argsc, args, strlen(args));
                        snprintf(path, sizeof(path), "%s", base);
       -                recvbp = recvb + 1;
       +                recvbp = recvb;
       +
       +                /*
       +                 * Walk into the selector until some directory or file
       +                 * does not exist. Then reconstruct the args, selector
       +                 * etc.
       +                 */
                        while (recvbp != NULL) {
       -                        sep = strsep(&recvbp, "/");
       +                        /* Traverse multiple / in selector. */
       +                        for (sep = recvbp; sep != recvbp && sep != recvbp+1;
       +                                sep = strsep(&recvbp, "/"));
       +                        printf("traversal directory = %s\n", sep);
       +
       +                        /* Append found directory to path. */
                                snprintf(path+strlen(path), sizeof(path)-strlen(path),
                                        "/%s", sep);
       +                        /* path is now always at least '/' */
       +                        printf("full traversal path = %s\n", path);
       +
                                if (stat(path, &dir) == -1) {
       +                                /*
       +                                 * Current try was not found. Go back one
       +                                 * step and finish.
       +                                 */
                                        c = strrchr(path, '/');
                                        if (c != NULL) {
                                                *c++ = '\0';
                                                snprintf(args, sizeof(args),
       -                                                "/%s%s%s%s%s",
       +                                                "/%s%s%s%s",
                                                        c,
                                                        (recvbp != NULL)? "/" : "",
                                                        (recvbp != NULL)? recvbp : "",
       -                                                (argsc[0] != '\0')? "?" : "",
                                                        (argsc[0] != '\0')? argsc : ""
                                                );
       +                                        printf("args = %s\n", args);
                                        }
                                        /* path fallthrough */
                                        pathfallthrough = 1;
       +                                printf("pathfallthrough = 1\n");
                                        break;
                                }
                        }
                }
        
       +        printf("path = %s\n", path);
                if (stat(path, &dir) != -1) {
                        /*
                         * If sticky bit is set, only serve if this is encrypted.
       @@ -311,9 +346,11 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                        }
        
                        if (S_ISDIR(dir.st_mode)) {
       +                        printf("S_ISDIR\n");
                                for (i = 0; i < sizeof(indexf)/sizeof(indexf[0]);
                                                i++) {
       -                                if (strlen(path) + strlen(indexf[i])
       +                                len = strlen(path);
       +                                if (len + strlen(indexf[i]) + (path[len-1] == '/')? 0 : 1
                                                        >= sizeof(path)) {
                                                if (loglvl & ERRORS) {
                                                        logentry(clienth, clientp,
       @@ -322,12 +359,18 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                                                }
                                                return;
                                        }
       -                                strncat(path, indexf[i],
       -                                                sizeof(path)-strlen(path)-1);
       +                                sprintf(path, "%s%s%s",
       +                                        path,
       +                                        (path[len-1] == '/')? "" : "/",
       +                                        indexf[i]);
       +                                printf("path index = %s\n", path);
                                        fd = open(path, O_RDONLY);
                                        if (fd >= 0)
                                                break;
       -                                path[strlen(path)-strlen(indexf[i])] = '\0';
       +
       +                                /* Not found. Clear path from indexf. */
       +                                printf("len = %d\n", len);
       +                                path[len] = '\0';
                                }
                        } else {
                                fd = open(path, O_RDONLY);
       @@ -342,10 +385,9 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                        }
                }
        
       +        /* Some file was opened. Serve it. */
                if (fd >= 0) {
                        close(fd);
       -                if (loglvl & FILES)
       -                        logentry(clienth, clientp, recvc, "serving");
        
                        c = strrchr(path, '/');
                        if (c == NULL)
       @@ -359,8 +401,10 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                        if (pathfallthrough &&
                                        !(type->f == handledcgi || type->f == handlecgi)) {
                                dprintf(sock, notfounderr, recvc);
       -                        if (loglvl & ERRORS)
       -                                logentry(clienth, clientp, recvc, "not found");
       +                        if (loglvl & ERRORS) {
       +                                logentry(clienth, clientp, recvc,
       +                                        "handler in path fallthrough not allowed");
       +                        }
                                return;
                        }
        
       @@ -369,14 +413,21 @@ handlerequest(int sock, char *req, int rlen, char *base, char *ohost,
                                if (loglvl & ERRORS)
                                        logentry(clienth, clientp, recvc, "nocgi error");
                        } else {
       +                        if (loglvl & FILES)
       +                                logentry(clienth, clientp, recvc, "serving");
       +
                                type->f(sock, path, port, base, args, sear, ohost,
                                        clienth, serverh, istls);
                        }
                } else {
       -                /*
       -                 * If we had to traverse the path, do not allow directory
       -                 * listings, only dynamic content.
       -                 */
       +                if (pathfallthrough && S_ISDIR(dir.st_mode)) {
       +                        if (loglvl & ERRORS) {
       +                                logentry(clienth, clientp, recvc,
       +                                        "directory listing in traversal not allowed");
       +                        }
       +                        return;
       +                }
       +
                        if (!pathfallthrough && S_ISDIR(dir.st_mode)) {
                                handledir(sock, path, port, base, args, sear, ohost,
                                        clienth, serverh, istls);
       @@ -791,7 +842,7 @@ main(int argc, char *argv[])
                                perror("chdir");
                                return 1;
                        }
       -                base = "/";
       +                base = "";
                        if (chroot(".") < 0) {
                                perror("chroot");
                                return 1;
       @@ -801,9 +852,9 @@ main(int argc, char *argv[])
                        return 1;
                }
        
       -        /* strip / at the end, except if it is "/" */
       -        for (p = base + strlen(base); p > base + 1 && p[-1] == '/'; --p)
       -                p[-1] = '\0';
       +        /* strip / at the end of base */
       +        for (p = base + strlen(base) - 1; p >= base && p[0] == '/'; --p)
       +                p[0] = '\0';
        
                if (dropprivileges(gr, us) < 0) {
                        perror("dropprivileges");
       @@ -1063,21 +1114,32 @@ read_selector_again:
                                                close(tlssocks[tlsclientreader? 1 : 0]);
                                                do {
                                                        if (tlsclientreader) {
       -                                                        shuflen = read(tlssocks[0], shufbuf, sizeof(shufbuf)-1);
       +                                                        shuflen = read(tlssocks[0],
       +                                                                shufbuf,
       +                                                                sizeof(shufbuf)-1);
                                                        } else {
       -                                                        shuflen = tls_read(tlsclientctx, shufbuf, sizeof(shufbuf)-1);
       +                                                        shuflen = tls_read(tlsclientctx,
       +                                                                shufbuf,
       +                                                                sizeof(shufbuf)-1);
                                                        }
                                                        if (shuflen == -1 && errno == EINTR)
                                                                continue;
       -                                                for (shufpos = 0; shufpos < shuflen; shufpos += wlen) {
       +                                                for (shufpos = 0; shufpos < shuflen;
       +                                                                shufpos += wlen) {
                                                                if (tlsclientreader) {
       -                                                                wlen = tls_write(tlsclientctx, shufbuf+shufpos, shuflen-shufpos);
       +                                                                wlen = tls_write(tlsclientctx,
       +                                                                        shufbuf+shufpos,
       +                                                                        shuflen-shufpos);
                                                                        if (wlen < 0) {
       -                                                                        fprintf(stderr, "tls_write failed: %s\n", tls_error(tlsclientctx));
       +                                                                        fprintf(stderr,
       +                                                                                "tls_write failed: %s\n",
       +                                                                                tls_error(tlsclientctx));
                                                                                return 1;
                                                                        }
                                                                } else {
       -                                                                wlen = write(tlssocks[1], shufbuf+shufpos, shuflen-shufpos);
       +                                                                wlen = write(tlssocks[1],
       +                                                                        shufbuf+shufpos,
       +                                                                        shuflen-shufpos);
                                                                        if (wlen < 0) {
                                                                                perror("write");
                                                                                return 1;