First rework of path handling. - geomyidae - A small C-based gopherd. HTML git clone git://bitreich.org/geomyidae/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/geomyidae/ DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit b12a77acd24fc170b1ad047986ffaf13592fb326 DIR parent f66a8a67b9471909016d6f24ce93f39584130a67 HTML 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;