=================================================================== RCS file: /cvs/mandoc/cgi.c,v retrieving revision 1.83 retrieving revision 1.110 diff -u -p -r1.83 -r1.110 --- mandoc/cgi.c 2014/07/25 16:07:13 1.83 +++ mandoc/cgi.c 2015/10/13 22:59:54 1.110 @@ -1,37 +1,40 @@ -/* $Id: cgi.c,v 1.83 2014/07/25 16:07:13 schwarze Exp $ */ +/* $Id: cgi.c,v 1.110 2015/10/13 22:59:54 schwarze Exp $ */ /* * Copyright (c) 2011, 2012 Kristaps Dzonsons - * Copyright (c) 2014 Ingo Schwarze + * Copyright (c) 2014, 2015 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef HAVE_CONFIG_H #include "config.h" -#endif +#include +#include + #include #include #include #include +#include #include #include #include #include -#include "mandoc.h" #include "mandoc_aux.h" +#include "mandoc.h" +#include "roff.h" #include "main.h" -#include "manpath.h" +#include "manconf.h" #include "mansearch.h" #include "cgi.h" @@ -42,7 +45,7 @@ struct query { char *manpath; /* desired manual directory */ char *arch; /* architecture */ char *sec; /* manual section */ - char *expr; /* unparsed expression string */ + char *query; /* unparsed query expression */ int equal; /* match whole names, not substrings */ }; @@ -55,13 +58,12 @@ struct req { static void catman(const struct req *, const char *); static void format(const struct req *, const char *); static void html_print(const char *); -static void html_printquery(const struct req *); static void html_putchar(char); -static int http_decode(char *); +static int http_decode(char *); static void http_parse(struct req *, const char *); static void http_print(const char *); -static void http_putchar(char); -static void http_printquery(const struct req *); +static void http_putchar(char); +static void http_printquery(const struct req *, const char *); static void pathgen(struct req *); static void pg_error_badrequest(const char *); static void pg_error_internal(void); @@ -76,6 +78,10 @@ static void resp_begin_http(int, const char *); static void resp_end_html(void); static void resp_searchform(const struct req *); static void resp_show(const struct req *, const char *); +static void set_query_attr(char **, char **); +static int validate_filename(const char *); +static int validate_manpath(const struct req *, const char *); +static int validate_urifrag(const char *); static const char *scriptname; /* CGI script name */ @@ -87,14 +93,14 @@ static const char *const sec_names[] = { "All Sections", "1 - General Commands", "2 - System Calls", - "3 - Subroutines", - "3p - Perl Subroutines", - "4 - Special Files", + "3 - Library Functions", + "3p - Perl Library", + "4 - Device Drivers", "5 - File Formats", "6 - Games", - "7 - Macros and Conventions", - "8 - Maintenance Commands", - "9 - Kernel Interface" + "7 - Miscellaneous Information", + "8 - System Manager\'s Manual", + "9 - Kernel Developer\'s Manual" }; static const int sec_MAX = sizeof(sec_names) / sizeof(char *); @@ -141,54 +147,30 @@ html_putchar(char c) } static void -http_printquery(const struct req *req) +http_printquery(const struct req *req, const char *sep) { - if (NULL != req->q.manpath) { - printf("&manpath="); - http_print(req->q.manpath); + if (NULL != req->q.query) { + printf("query="); + http_print(req->q.query); } + if (0 == req->q.equal) + printf("%sapropos=1", sep); if (NULL != req->q.sec) { - printf("&sec="); + printf("%ssec=", sep); http_print(req->q.sec); } if (NULL != req->q.arch) { - printf("&arch="); + printf("%sarch=", sep); http_print(req->q.arch); } - if (NULL != req->q.expr) { - printf("&query="); - http_print(req->q.expr); + if (strcmp(req->q.manpath, req->p[0])) { + printf("%smanpath=", sep); + http_print(req->q.manpath); } - if (0 == req->q.equal) - printf("&apropos=1"); } static void -html_printquery(const struct req *req) -{ - - if (NULL != req->q.manpath) { - printf("&manpath="); - html_print(req->q.manpath); - } - if (NULL != req->q.sec) { - printf("&sec="); - html_print(req->q.sec); - } - if (NULL != req->q.arch) { - printf("&arch="); - html_print(req->q.arch); - } - if (NULL != req->q.expr) { - printf("&query="); - html_print(req->q.expr); - } - if (0 == req->q.equal) - printf("&apropos=1"); -} - -static void http_print(const char *p) { @@ -205,7 +187,7 @@ http_print(const char *p) static void html_print(const char *p) { - + if (NULL == p) return; while ('\0' != *p) @@ -242,7 +224,7 @@ http_parse(struct req *req, const char *qs) req->q.manpath = NULL; req->q.arch = NULL; req->q.sec = NULL; - req->q.expr = NULL; + req->q.query = NULL; req->q.equal = 1; key = val = NULL; @@ -270,7 +252,7 @@ http_parse(struct req *req, const char *qs) /* Handle key-value pairs. */ if ( ! strcmp(key, "query")) - set_query_attr(&req->q.expr, &val); + set_query_attr(&req->q.query, &val); else if ( ! strcmp(key, "apropos")) req->q.equal = !strcmp(val, "0"); @@ -316,11 +298,6 @@ next: if (*qs != '\0') qs++; } - - /* Fall back to the default manpath. */ - - if (req->q.manpath == NULL) - req->q.manpath = mandoc_strdup(req->p[0]); } static void @@ -355,13 +332,13 @@ http_decode(char *p) for ( ; '\0' != *p; p++, q++) { if ('%' == *p) { if ('\0' == (hex[0] = *(p + 1))) - return(0); + return 0; if ('\0' == (hex[1] = *(p + 2))) - return(0); + return 0; if (1 != sscanf(hex, "%x", &c)) - return(0); + return 0; if ('\0' == c) - return(0); + return 0; *q = (char)c; p += 2; @@ -370,7 +347,7 @@ http_decode(char *p) } *q = '\0'; - return(1); + return 1; } static void @@ -394,13 +371,10 @@ resp_begin_html(int code, const char *msg) resp_begin_http(code, msg); - printf("\n" + printf("\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "q.expr) - html_print(req->q.expr); + if (NULL != req->q.query) + html_print(req->q.query); puts("\" SIZE=\"40\">"); /* Write submission and reset buttons. */ @@ -451,19 +425,19 @@ resp_searchform(const struct req *req) printf( "\n" "q.equal) - printf("CHECKED "); + printf("CHECKED=\"checked\" "); printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n" "\n"); /* Write section selector. */ - printf( "\n" + puts( "\n" ""); @@ -473,13 +447,13 @@ resp_searchform(const struct req *req) printf( ""); @@ -490,9 +464,8 @@ resp_searchform(const struct req *req) puts("q.equal) - printf("CHECKED "); + printf("CHECKED=\"checked\" "); printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n" "\n"); @@ -526,10 +499,10 @@ validate_urifrag(const char *frag) if ( ! (isalnum((unsigned char)*frag) || '-' == *frag || '.' == *frag || '/' == *frag || '_' == *frag)) - return(0); + return 0; frag++; } - return(1); + return 1; } static int @@ -538,13 +511,13 @@ validate_manpath(const struct req *req, const char* ma size_t i; if ( ! strcmp(manpath, "mandoc")) - return(1); + return 1; for (i = 0; i < req->psz; i++) if ( ! strcmp(manpath, req->p[i])) - return(1); + return 1; - return(0); + return 0; } static int @@ -554,8 +527,8 @@ validate_filename(const char *file) if ('.' == file[0] && '/' == file[1]) file += 2; - return ( ! (strstr(file, "../") || strstr(file, "/..") || - (strncmp(file, "man", 3) && strncmp(file, "cat", 3)))); + return ! (strstr(file, "../") || strstr(file, "/..") || + (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); } static void @@ -565,10 +538,10 @@ pg_index(const struct req *req) resp_begin_html(200, NULL); resp_searchform(req); printf("

\n" - "This web interface is documented in the " - "man.cgi " - "manual, and the " - "apropos " + "This web interface is documented in the\n" + "man.cgi\n" + "manual, and the\n" + "apropos\n" "manual explains the query syntax.\n" "

\n", scriptname, scriptname); @@ -634,7 +607,7 @@ pg_searchres(const struct req *req, struct manpage *r, printf("Status: 303 See Other\r\n"); printf("Location: http://%s%s/%s/%s?", HTTP_HOST, scriptname, req->q.manpath, r[0].file); - http_printquery(req); + http_printquery(req, "&"); printf("\r\n" "Content-Type: text/html; charset=utf-8\r\n" "\r\n"); @@ -649,9 +622,9 @@ pg_searchres(const struct req *req, struct manpage *r, for (i = 0; i < sz; i++) { printf("\n" "\n" - "q.manpath, r[i].file); - html_printquery(req); + http_printquery(req, "&"); printf("\">"); html_print(r[i].names); printf("\n" @@ -729,7 +702,7 @@ catman(const struct req *req, const char *file) while (NULL != (p = fgetln(f, &len))) { bold = italic = 0; for (i = 0; i < (int)len - 1; i++) { - /* + /* * This means that the catpage is out of state. * Ignore it and keep going (although the * catpage is bogus). @@ -770,7 +743,7 @@ catman(const struct req *req, const char *file) continue; } - /* + /* * Handle funny behaviour troff-isms. * These grok'd from the original man2html.c. */ @@ -808,7 +781,7 @@ catman(const struct req *req, const char *file) } /* Bold mode. */ - + if (italic) printf(""); if ( ! bold) @@ -819,9 +792,9 @@ catman(const struct req *req, const char *file) html_putchar(p[i]); } - /* + /* * Clean up the last character. - * We can get to a newline; don't print that. + * We can get to a newline; don't print that. */ if (italic) @@ -844,54 +817,54 @@ catman(const struct req *req, const char *file) static void format(const struct req *req, const char *file) { + struct manoutput conf; struct mparse *mp; - int fd; - struct mdoc *mdoc; - struct man *man; + struct roff_man *man; void *vp; - enum mandoclevel rc; - char opts[PATH_MAX + 128]; + int fd; + int usepath; if (-1 == (fd = open(file, O_RDONLY, 0))) { puts("

You specified an invalid manual file.

"); return; } - mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL, - req->q.manpath); - rc = mparse_readfd(mp, fd, file); + mchars_alloc(); + mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, req->q.manpath); + mparse_readfd(mp, fd, file); close(fd); - if (rc >= MANDOCLEVEL_FATAL) { - fprintf(stderr, "fatal mandoc error: %s/%s\n", - req->q.manpath, file); - pg_error_internal(); - return; - } + memset(&conf, 0, sizeof(conf)); + conf.fragment = 1; + usepath = strcmp(req->q.manpath, req->p[0]); + mandoc_asprintf(&conf.man, "%s?query=%%N&sec=%%S%s%s%s%s", + scriptname, + req->q.arch ? "&arch=" : "", + req->q.arch ? req->q.arch : "", + usepath ? "&manpath=" : "", + usepath ? req->q.manpath : ""); - snprintf(opts, sizeof(opts), "fragment,man=%s?" - "manpath=%s&query=%%N&sec=%%S&arch=%s", - scriptname, req->q.manpath, - req->q.arch ? req->q.arch : ""); - - mparse_result(mp, &mdoc, &man, NULL); - if (NULL == man && NULL == mdoc) { + mparse_result(mp, &man, NULL); + if (man == NULL) { fprintf(stderr, "fatal mandoc error: %s/%s\n", req->q.manpath, file); pg_error_internal(); mparse_free(mp); + mchars_free(); return; } - vp = html_alloc(opts); + vp = html_alloc(&conf); - if (NULL != mdoc) - html_mdoc(vp, mdoc); + if (man->macroset == MACROSET_MDOC) + html_mdoc(vp, man); else html_man(vp, man); html_free(vp); mparse_free(mp); + mchars_free(); + free(conf.man); } static void @@ -908,20 +881,23 @@ resp_show(const struct req *req, const char *file) } static void -pg_show(struct req *req, const char *path) +pg_show(struct req *req, const char *fullpath) { - char *sub; + char *manpath; + const char *file; - if (NULL == path || NULL == (sub = strchr(path, '/'))) { + if ((file = strchr(fullpath, '/')) == NULL) { pg_error_badrequest( "You did not specify a page to show."); return; - } - *sub++ = '\0'; + } + manpath = mandoc_strndup(fullpath, file - fullpath); + file++; - if ( ! validate_manpath(req, path)) { + if ( ! validate_manpath(req, manpath)) { pg_error_badrequest( "You specified an invalid manpath."); + free(manpath); return; } @@ -931,27 +907,29 @@ pg_show(struct req *req, const char *path) * relative to the manpath root. */ - if (-1 == chdir(path)) { + if (chdir(manpath) == -1) { fprintf(stderr, "chdir %s: %s\n", - path, strerror(errno)); + manpath, strerror(errno)); pg_error_internal(); + free(manpath); return; } - if ( ! validate_filename(sub)) { + if (strcmp(manpath, "mandoc")) { + free(req->q.manpath); + req->q.manpath = manpath; + } else + free(manpath); + + if ( ! validate_filename(file)) { pg_error_badrequest( "You specified an invalid manual file."); return; } - if (strcmp(path, "mandoc")) { - free(req->q.manpath); - req->q.manpath = mandoc_strdup(path); - } - resp_begin_html(200, NULL); resp_searchform(req); - resp_show(req, sub); + resp_show(req, file); resp_end_html(); } @@ -961,10 +939,10 @@ pg_search(const struct req *req) struct mansearch search; struct manpaths paths; struct manpage *res; - char **cp; - const char *ep, *start; + char **argv; + char *query, *rp, *wp; size_t ressz; - int i, sz; + int argc; /* * Begin by chdir()ing into the root of the manpath. @@ -981,54 +959,54 @@ pg_search(const struct req *req) search.arch = req->q.arch; search.sec = req->q.sec; - search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd); - search.flags = req->q.equal ? MANSEARCH_MAN : 0; + search.outkey = "Nd"; + search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; + search.firstmatch = 1; paths.sz = 1; paths.paths = mandoc_malloc(sizeof(char *)); paths.paths[0] = mandoc_strdup("."); /* - * Poor man's tokenisation: just break apart by spaces. - * Yes, this is half-ass. But it works for now. + * Break apart at spaces with backslash-escaping. */ - ep = req->q.expr; - while (ep && isspace((unsigned char)*ep)) - ep++; - - sz = 0; - cp = NULL; - while (ep && '\0' != *ep) { - cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *)); - start = ep; - while ('\0' != *ep && ! isspace((unsigned char)*ep)) - ep++; - cp[sz] = mandoc_malloc((ep - start) + 1); - memcpy(cp[sz], start, ep - start); - cp[sz++][ep - start] = '\0'; - while (isspace((unsigned char)*ep)) - ep++; + argc = 0; + argv = NULL; + rp = query = mandoc_strdup(req->q.query); + for (;;) { + while (isspace((unsigned char)*rp)) + rp++; + if (*rp == '\0') + break; + argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); + argv[argc++] = wp = rp; + for (;;) { + if (isspace((unsigned char)*rp)) { + *wp = '\0'; + rp++; + break; + } + if (rp[0] == '\\' && rp[1] != '\0') + rp++; + if (wp != rp) + *wp = *rp; + if (*rp == '\0') + break; + wp++; + rp++; + } } - if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz)) + if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz)) pg_noresult(req, "You entered an invalid query."); else if (0 == ressz) pg_noresult(req, "No results found."); else pg_searchres(req, res, ressz); - for (i = 0; i < sz; i++) - free(cp[i]); - free(cp); - - for (i = 0; i < (int)ressz; i++) { - free(res[i].file); - free(res[i].names); - free(res[i].output); - } - free(res); - + free(query); + mansearch_free(res, ressz); free(paths.paths[0]); free(paths.paths); } @@ -1037,10 +1015,23 @@ int main(void) { struct req req; + struct itimerval itimer; const char *path; const char *querystring; int i; + /* Poor man's ReDoS mitigation. */ + + itimer.it_value.tv_sec = 2; + itimer.it_value.tv_usec = 0; + itimer.it_interval.tv_sec = 2; + itimer.it_interval.tv_usec = 0; + if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { + fprintf(stderr, "setitimer: %s\n", strerror(errno)); + pg_error_internal(); + return EXIT_FAILURE; + } + /* Scan our run-time environment. */ if (NULL == (scriptname = getenv("SCRIPT_NAME"))) @@ -1050,7 +1041,7 @@ main(void) fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n", scriptname); pg_error_internal(); - return(EXIT_FAILURE); + return EXIT_FAILURE; } /* @@ -1063,8 +1054,8 @@ main(void) fprintf(stderr, "MAN_DIR: %s: %s\n", MAN_DIR, strerror(errno)); pg_error_internal(); - return(EXIT_FAILURE); - } + return EXIT_FAILURE; + } memset(&req, 0, sizeof(struct req)); pathgen(&req); @@ -1074,16 +1065,18 @@ main(void) if (NULL != (querystring = getenv("QUERY_STRING"))) http_parse(&req, querystring); - if ( ! validate_manpath(&req, req.q.manpath)) { + if (req.q.manpath == NULL) + req.q.manpath = mandoc_strdup(req.p[0]); + else if ( ! validate_manpath(&req, req.q.manpath)) { pg_error_badrequest( "You specified an invalid manpath."); - return(EXIT_FAILURE); + return EXIT_FAILURE; } if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { pg_error_badrequest( "You specified an invalid architecture."); - return(EXIT_FAILURE); + return EXIT_FAILURE; } /* Dispatch to the three different pages. */ @@ -1096,7 +1089,7 @@ main(void) if ('\0' != *path) pg_show(&req, path); - else if (NULL != req.q.expr) + else if (NULL != req.q.query) pg_search(&req); else pg_index(&req); @@ -1104,11 +1097,11 @@ main(void) free(req.q.manpath); free(req.q.arch); free(req.q.sec); - free(req.q.expr); + free(req.q.query); for (i = 0; i < (int)req.psz; i++) free(req.p[i]); free(req.p); - return(EXIT_SUCCESS); + return EXIT_SUCCESS; } /*