===================================================================
RCS file: /cvs/mandoc/cgi.c,v
retrieving revision 1.11
retrieving revision 1.127
diff -u -p -r1.11 -r1.127
--- mandoc/cgi.c 2011/12/07 11:52:36 1.11
+++ mandoc/cgi.c 2016/04/15 15:13:07 1.127
@@ -1,128 +1,130 @@
-/* $Id: cgi.c,v 1.11 2011/12/07 11:52:36 kristaps Exp $ */
+/* $Id: cgi.c,v 1.127 2016/04/15 15:13:07 schwarze Exp $ */
/*
- * Copyright (c) 2011 Kristaps Dzonsons \n"
- " The query your entered was malformed.\n"
- " Try again from the\n"
- " main page\n"
- " \n"
- " The page you're looking for, ");
- printf(" ");
- html_print(page);
- puts(",\n"
- " could not be found.\n"
- " Try searching from the\n"
- " main page\n"
- " \n"
+ "This web interface is documented in the\n"
+ "man.cgi\n"
+ "manual, and the\n"
+ "apropos\n"
+ "manual explains the query syntax.\n"
+ " Generic badness happened. ");
+ puts(msg);
+ puts(" \n");
+ puts(msg);
+ printf("Try again from the\n"
+ "main page.\n"
+ " Your database is broken. Internal Server Error No results found.Malformed Query
\n"
- "Page Not Found
\n"
- "Bad Request
\n"
+ "");
- if (0 == sz)
- puts("
\n"
+ "\n"
+ " ");
+ }
- for (i = 0; i < (int)sz; i++) {
- printf("\n"
+ "q.manpath, r[i].file);
+ printf("\">");
+ html_print(r[i].names);
+ printf("\n"
+ " \n"
+ "");
+ html_print(r[i].output);
+ puts(" \n"
+ "
");
+ iuse = 0;
+ priouse = 20;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ sec = r[i].file;
+ sec += strcspn(sec, "123456789");
+ if (sec[0] == '\0')
+ continue;
+ prio = sec_prios[sec[0] - '1'];
+ if (sec[1] != '/')
+ prio += 10;
+ if (req->q.arch == NULL) {
+ archprio =
+ ((arch = strchr(sec + 1, '/'))
+ == NULL) ? 3 :
+ ((archend = strchr(arch + 1, '/'))
+ == NULL) ? 0 :
+ strncmp(arch, "amd64/",
+ archend - arch) ? 2 : 1;
+ if (archprio < archpriouse) {
+ archpriouse = archprio;
+ priouse = prio;
+ iuse = i;
+ continue;
+ }
+ if (archprio > archpriouse)
+ continue;
+ }
+ if (prio >= priouse)
+ continue;
+ priouse = prio;
+ iuse = i;
}
- printf(") ");
- html_print(r[i].desc);
- puts("
You specified an invalid manual file.
"); return; } - resp_begin_html(200, NULL); + puts(""); - puts("\n" + ""); - while (NULL != (p = fgetln(f, &len))) { + p = NULL; + sz = 0; + + while ((len = getline(&p, &sz, f)) != -1) { bold = italic = 0; - for (i = 0; i < (int)len - 1; i++) { - /* + for (i = 0; i < len - 1; i++) { + /* * This means that the catpage is out of state. * Ignore it and keep going (although the * catpage is bogus). @@ -508,22 +701,22 @@ catman(const char *file) if ('\b' != p[i + 1]) { if (italic) - printf(""); + printf(""); if (bold) - printf(""); + printf(""); italic = bold = 0; html_putchar(p[i]); continue; - } else if (i + 2 >= (int)len) + } else if (i + 2 >= len) continue; /* Italic mode. */ if ('_' == p[i]) { if (bold) - printf(""); + printf(""); if ( ! italic) - printf(""); + printf(""); bold = 0; italic = 1; i += 2; @@ -531,7 +724,7 @@ catman(const char *file) continue; } - /* + /* * Handle funny behaviour troff-isms. * These grok'd from the original man2html.c. */ @@ -545,9 +738,9 @@ catman(const char *file) ('*' == p[i] && '|' == p[i + 2]) || ('|' == p[i] && '*' == p[i + 2])) { if (italic) - printf(""); + printf(""); if (bold) - printf(""); + printf(""); italic = bold = 0; putchar('*'); i += 2; @@ -559,9 +752,9 @@ catman(const char *file) ('+' == p[i] && '|' == p[i + 1]) || ('|' == p[i] && '+' == p[i + 1])) { if (italic) - printf(""); + printf(""); if (bold) - printf(""); + printf(""); italic = bold = 0; putchar('+'); i += 2; @@ -569,313 +762,422 @@ catman(const char *file) } /* Bold mode. */ - + if (italic) - printf(""); + printf(""); if ( ! bold) - printf(""); + printf(""); bold = 1; italic = 0; i += 2; 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) - printf(""); + printf(""); if (bold) - printf(""); + printf(""); - if (i == (int)len - 1 && '\n' != p[i]) + if (i == len - 1 && p[i] != '\n') html_putchar(p[i]); putchar('\n'); } + free(p); - puts("\n" - "\n" - ""); + puts("
You specified an invalid manual file.
"); return; } - mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); - 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) { - resp_baddb(); + memset(&conf, 0, sizeof(conf)); + conf.fragment = 1; + usepath = strcmp(req->q.manpath, req->p[0]); + mandoc_asprintf(&conf.man, "/%s%s%%N.%%S", + usepath ? req->q.manpath : "", usepath ? "/" : ""); + + 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; } - snprintf(opts, sizeof(opts), "style=/man.css," - "man=%s/search.html?sec=%%S&expr=%%N," - /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/, - progname); + vp = html_alloc(&conf); - mparse_result(mp, &mdoc, &man); - vp = html_alloc(opts); - - if (NULL != mdoc) { - resp_begin_http(200, NULL); - html_mdoc(vp, mdoc); - } else if (NULL != man) { - resp_begin_http(200, NULL); + if (man->macroset == MACROSET_MDOC) { + mdoc_validate(man); + html_mdoc(vp, man); + } else { + man_validate(man); html_man(vp, man); - } else - resp_baddb(); + } html_free(vp); mparse_free(mp); + mchars_free(); + free(conf.man); } static void -pg_show(const struct manpaths *ps, const struct req *req, char *path) +resp_show(const struct req *req, const char *file) { - char *sub; - char file[MAXPATHLEN]; - const char *fn, *cp; - int rc; - unsigned int vol, rec; - DB *idx; - DBT key, val; - if (NULL == path) { - resp_error400(); + if ('.' == file[0] && '/' == file[1]) + file += 2; + + if ('c' == *file) + catman(req, file); + else + format(req, file); +} + +static void +pg_show(struct req *req, const char *fullpath) +{ + char *manpath; + const char *file; + + if ((file = strchr(fullpath, '/')) == NULL) { + pg_error_badrequest( + "You did not specify a page to show."); return; - } else if (NULL == (sub = strrchr(path, '/'))) { - resp_error400(); - return; - } else - *sub++ = '\0'; + } + manpath = mandoc_strndup(fullpath, file - fullpath); + file++; - if ( ! (atou(path, &vol) && atou(sub, &rec))) { - resp_error400(); + if ( ! validate_manpath(req, manpath)) { + pg_error_badrequest( + "You specified an invalid manpath."); + free(manpath); return; - } else if (vol >= (unsigned int)ps->sz) { - resp_error400(); - return; } - strlcpy(file, ps->paths[vol], MAXPATHLEN); - strlcat(file, "/mandoc.index", MAXPATHLEN); + /* + * Begin by chdir()ing into the manpath. + * This way we can pick up the database files, which are + * relative to the manpath root. + */ - /* Open the index recno(3) database. */ - - idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL); - if (NULL == idx) { - resp_baddb(); + if (chdir(manpath) == -1) { + fprintf(stderr, "chdir %s: %s\n", + manpath, strerror(errno)); + pg_error_internal(); + free(manpath); return; } - key.data = &rec; - key.size = 4; + if (strcmp(manpath, "mandoc")) { + free(req->q.manpath); + req->q.manpath = manpath; + } else + free(manpath); - if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) { - rc < 0 ? resp_baddb() : resp_error400(); - goto out; - } - - cp = (char *)val.data; - - if (NULL == (fn = memchr(cp, '\0', val.size))) - resp_baddb(); - else if (++fn - cp >= (int)val.size) - resp_baddb(); - else if (NULL == memchr(fn, '\0', val.size - (fn - cp))) - resp_baddb(); - else { - strlcpy(file, ps->paths[vol], MAXPATHLEN); - strlcat(file, "/", MAXPATHLEN); - strlcat(file, fn, MAXPATHLEN); - if (0 == strcmp(cp, "cat")) - catman(file); - else - format(file); + if ( ! validate_filename(file)) { + pg_error_badrequest( + "You specified an invalid manual file."); + return; } -out: - (*idx->close)(idx); + + resp_begin_html(200, NULL); + resp_searchform(req); + resp_show(req, file); + resp_end_html(); } static void -pg_search(const struct manpaths *ps, const struct req *req, char *path) +pg_search(const struct req *req) { - size_t tt; - int i, sz, rc; - const char *ep, *start; - char **cp; - struct opts opt; - struct expr *expr; + struct mansearch search; + struct manpaths paths; + struct manpage *res; + char **argv; + char *query, *rp, *wp; + size_t ressz; + int argc; - expr = NULL; - cp = NULL; - ep = NULL; - sz = 0; - - memset(&opt, 0, sizeof(struct opts)); - - for (sz = i = 0; i < (int)req->fieldsz; i++) - if (0 == strcmp(req->fields[i].key, "expr")) - ep = req->fields[i].val; - else if (0 == strcmp(req->fields[i].key, "sec")) - opt.cat = req->fields[i].val; - else if (0 == strcmp(req->fields[i].key, "arch")) - opt.arch = req->fields[i].val; - /* - * Poor man's tokenisation. - * Just break apart by spaces. - * Yes, this is half-ass. But it works for now. + * Begin by chdir()ing into the root of the manpath. + * This way we can pick up the database files, which are + * relative to the manpath root. */ - while (ep && isspace((unsigned char)*ep)) - ep++; - - while (ep && '\0' != *ep) { - cp = mandoc_realloc(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++; + if (-1 == (chdir(req->q.manpath))) { + fprintf(stderr, "chdir %s: %s\n", + req->q.manpath, strerror(errno)); + pg_error_internal(); + return; } - rc = -1; + search.arch = req->q.arch; + search.sec = req->q.sec; + 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("."); + /* - * Pump down into apropos backend. - * The resp_search() function is called with the results. + * Break apart at spaces with backslash-escaping. */ - if (NULL != (expr = exprcomp(sz, cp, &tt))) - rc = apropos_search - (ps->sz, ps->paths, &opt, - expr, tt, (void *)req, resp_search); + 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++; + } + } - /* ...unless errors occured. */ + 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); - if (0 == rc) - resp_baddb(); - else if (-1 == rc) - resp_search(NULL, 0, (void *)req); - - for (i = 0; i < sz; i++) - free(cp[i]); - - free(cp); - exprfree(expr); + free(query); + mansearch_free(res, ressz); + free(paths.paths[0]); + free(paths.paths); } int main(void) { - int i; struct req req; - char *p, *path, *subpath; - struct manpaths paths; + struct itimerval itimer; + const char *path; + const char *querystring; + int i; - /* HTTP init: read and parse the query string. */ + /* Poor man's ReDoS mitigation. */ - progname = getenv("SCRIPT_NAME"); - if (NULL == progname) - progname = ""; + 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; + } - cache = getenv("CACHE_DIR"); - if (NULL == cache) - cache = "/cache/man.cgi"; + /* + * First we change directory into the MAN_DIR so that + * subsequent scanning for manpath directories is rooted + * relative to the same position. + */ - if (-1 == chdir(cache)) { - resp_bad(); - return(EXIT_FAILURE); + if (-1 == chdir(MAN_DIR)) { + fprintf(stderr, "MAN_DIR: %s: %s\n", + MAN_DIR, strerror(errno)); + pg_error_internal(); + return EXIT_FAILURE; } - host = getenv("HTTP_HOST"); - if (NULL == host) - host = "localhost"; - memset(&req, 0, sizeof(struct req)); + req.q.equal = 1; + pathgen(&req); - if (NULL != (p = getenv("QUERY_STRING"))) - kval_parse(&req.fields, &req.fieldsz, p); + /* Parse the path info and the query string. */ - /* Resolve leading subpath component. */ + if ((path = getenv("PATH_INFO")) == NULL) + path = ""; + else if (*path == '/') + path++; - subpath = path = NULL; - req.page = PAGE__MAX; + if (*path != '\0') { + path_parse(&req, path); + if (access(path, F_OK) == -1) + path = ""; + } else if ((querystring = getenv("QUERY_STRING")) != NULL) + http_parse(&req, querystring); - if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path) - req.page = PAGE_INDEX; + /* Validate parsed data and add defaults. */ - if (NULL != path && '/' == *path && '\0' == *++path) - req.page = PAGE_INDEX; + 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; + } - /* Strip file suffix. */ + if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { + pg_error_badrequest( + "You specified an invalid architecture."); + return EXIT_FAILURE; + } - if (NULL != path && NULL != (p = strrchr(path, '.'))) - if (NULL != p && NULL == strchr(p, '/')) - *p++ = '\0'; + /* Dispatch to the three different pages. */ - /* Resolve subpath component. */ + if ('\0' != *path) + pg_show(&req, path); + else if (NULL != req.q.query) + pg_search(&req); + else + pg_index(&req); - if (NULL != path && NULL != (subpath = strchr(path, '/'))) - *subpath++ = '\0'; + free(req.q.manpath); + free(req.q.arch); + free(req.q.sec); + free(req.q.query); + for (i = 0; i < (int)req.psz; i++) + free(req.p[i]); + free(req.p); + return EXIT_SUCCESS; +} - /* Map path into one we recognise. */ +/* + * If PATH_INFO is not a file name, translate it to a query. + */ +static void +path_parse(struct req *req, const char *path) +{ + char *dir; - if (NULL != path && '\0' != *path) - for (i = 0; i < (int)PAGE__MAX; i++) - if (0 == strcmp(pages[i], path)) { - req.page = (enum page)i; - break; - } + req->isquery = 0; + req->q.equal = 1; + req->q.manpath = mandoc_strdup(path); - /* Initialise MANPATH. */ + /* Mandatory manual page name. */ + if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) { + req->q.query = req->q.manpath; + req->q.manpath = NULL; + } else + *req->q.query++ = '\0'; - memset(&paths, 0, sizeof(struct manpaths)); - manpath_manconf("etc/catman.conf", &paths); + /* Optional trailing section. */ + if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) { + if(isdigit((unsigned char)req->q.sec[1])) { + *req->q.sec++ = '\0'; + req->q.sec = mandoc_strdup(req->q.sec); + } else + req->q.sec = NULL; + } - /* Route pages. */ + /* Handle the case of name[.section] only. */ + if (req->q.manpath == NULL) { + req->q.arch = NULL; + return; + } + req->q.query = mandoc_strdup(req->q.query); - switch (req.page) { - case (PAGE_INDEX): - pg_index(&paths, &req, subpath); - break; - case (PAGE_SEARCH): - pg_search(&paths, &req, subpath); - break; - case (PAGE_SHOW): - pg_show(&paths, &req, subpath); - break; - default: - resp_error404(path); - break; + /* Optional architecture. */ + dir = strrchr(req->q.manpath, '/'); + if (dir != NULL && strncmp(dir + 1, "man", 3) != 0) { + *dir++ = '\0'; + req->q.arch = mandoc_strdup(dir); + dir = strrchr(req->q.manpath, '/'); + } else + req->q.arch = NULL; + + /* Optional directory name. */ + if (dir != NULL && strncmp(dir + 1, "man", 3) == 0) { + *dir++ = '\0'; + free(req->q.sec); + req->q.sec = mandoc_strdup(dir + 3); } +} - manpath_free(&paths); - kval_free(req.fields, req.fieldsz); +/* + * Scan for indexable paths. + */ +static void +pathgen(struct req *req) +{ + FILE *fp; + char *dp; + size_t dpsz; + ssize_t len; - return(EXIT_SUCCESS); + if (NULL == (fp = fopen("manpath.conf", "r"))) { + fprintf(stderr, "%s/manpath.conf: %s\n", + MAN_DIR, strerror(errno)); + pg_error_internal(); + exit(EXIT_FAILURE); + } + + dp = NULL; + dpsz = 0; + + while ((len = getline(&dp, &dpsz, fp)) != -1) { + if (dp[len - 1] == '\n') + dp[--len] = '\0'; + req->p = mandoc_realloc(req->p, + (req->psz + 1) * sizeof(char *)); + if ( ! validate_urifrag(dp)) { + fprintf(stderr, "%s/manpath.conf contains " + "unsafe path \"%s\"\n", MAN_DIR, dp); + pg_error_internal(); + exit(EXIT_FAILURE); + } + if (NULL != strchr(dp, '/')) { + fprintf(stderr, "%s/manpath.conf contains " + "path with slash \"%s\"\n", MAN_DIR, dp); + pg_error_internal(); + exit(EXIT_FAILURE); + } + req->p[req->psz++] = dp; + dp = NULL; + dpsz = 0; + } + free(dp); + + if ( req->p == NULL ) { + fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR); + pg_error_internal(); + exit(EXIT_FAILURE); + } }