===================================================================
RCS file: /cvs/mandoc/cgi.c,v
retrieving revision 1.8
retrieving revision 1.106
diff -u -p -r1.8 -r1.106
--- mandoc/cgi.c 2011/11/27 11:46:44 1.8
+++ mandoc/cgi.c 2015/03/27 21:33:20 1.106
@@ -1,247 +1,334 @@
-/* $Id: cgi.c,v 1.8 2011/11/27 11:46:44 kristaps Exp $ */
+/* $Id: cgi.c,v 1.106 2015/03/27 21:33:20 schwarze Exp $ */
/*
- * Copyright (c) 2011 Kristaps Dzonsons Requested manual not found. Your search didn't work. \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.Bad Request
\n"
+ "");
- if (0 == sz)
- puts("
\n"
+ "\n"
+ " ");
+ }
- for (i = 0; i < (int)sz; i++) {
- printf("\n"
+ "q.manpath, r[i].file);
+ http_printquery(req, "&");
+ printf("\">");
+ html_print(r[i].names);
+ printf("\n"
+ " \n"
+ "");
+ html_print(r[i].output);
+ puts(" \n"
+ "
");
+ iuse = 0;
+ priouse = 10;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ isec = strcspn(r[i].file, "123456789");
+ sec = r[i].file[isec];
+ if ('\0' == sec)
+ continue;
+ prio = sec_prios[sec - '1'];
+ if (NULL == req->q.arch) {
+ archprio =
+ (NULL == (arch = strchr(
+ r[i].file + isec, '/'))) ? 3 :
+ (NULL == (archend = strchr(
+ arch + 1, '/'))) ? 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; + } + + puts(""); + + 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). + */ + + if ('\b' == p[i] || '\n' == p[i]) + continue; + + /* + * Print a regular character. + * Close out any bold/italic scopes. + * If we're in back-space mode, make sure we'll + * have something to enter when we backspace. + */ + + if ('\b' != p[i + 1]) { + if (italic) + printf(""); + if (bold) + printf(""); + italic = bold = 0; + html_putchar(p[i]); + continue; + } else if (i + 2 >= (int)len) + continue; + + /* Italic mode. */ + + if ('_' == p[i]) { + if (bold) + printf(""); + if ( ! italic) + printf(""); + bold = 0; + italic = 1; + i += 2; + html_putchar(p[i]); + continue; + } + + /* + * Handle funny behaviour troff-isms. + * These grok'd from the original man2html.c. + */ + + if (('+' == p[i] && 'o' == p[i + 2]) || + ('o' == p[i] && '+' == p[i + 2]) || + ('|' == p[i] && '=' == p[i + 2]) || + ('=' == p[i] && '|' == p[i + 2]) || + ('*' == p[i] && '=' == p[i + 2]) || + ('=' == p[i] && '*' == p[i + 2]) || + ('*' == p[i] && '|' == p[i + 2]) || + ('|' == p[i] && '*' == p[i + 2])) { + if (italic) + printf(""); + if (bold) + printf(""); + italic = bold = 0; + putchar('*'); + i += 2; + continue; + } else if (('|' == p[i] && '-' == p[i + 2]) || + ('-' == p[i] && '|' == p[i + 1]) || + ('+' == p[i] && '-' == p[i + 1]) || + ('-' == p[i] && '+' == p[i + 1]) || + ('+' == p[i] && '|' == p[i + 1]) || + ('|' == p[i] && '+' == p[i + 1])) { + if (italic) + printf(""); + if (bold) + printf(""); + italic = bold = 0; + putchar('+'); + i += 2; + continue; + } + + /* Bold mode. */ + + if (italic) + printf(""); + if ( ! bold) + 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. + */ + + if (italic) + printf(""); + if (bold) + printf(""); + + if (i == (int)len - 1 && '\n' != p[i]) + html_putchar(p[i]); + + putchar('\n'); + } + + puts("\n" + "
You specified an invalid manual file.
"); return; } - mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); - rc = mparse_readfd(mp, fd, file); + mchars = mchars_alloc(); + mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, + mchars, 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?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 : ""); + + mparse_result(mp, &mdoc, &man, NULL); + if (NULL == man && NULL == mdoc) { + fprintf(stderr, "fatal mandoc error: %s/%s\n", + req->q.manpath, file); + pg_error_internal(); + mparse_free(mp); + mchars_free(mchars); return; } - mparse_result(mp, &mdoc, &man); - vp = html_alloc(NULL); + vp = html_alloc(mchars, &conf); - if (NULL != mdoc) { - resp_begin_http(200, NULL); + if (NULL != mdoc) html_mdoc(vp, mdoc); - } else if (NULL != man) { - resp_begin_http(200, NULL); + else html_man(vp, man); - } else - resp_baddb(); html_free(vp); mparse_free(mp); + mchars_free(mchars); + 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]; - int rc; - unsigned int vol, rec; - DB *db; - DBT key, val; - if (NULL == path) { - resp_badmanual(); + 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_badmanual(); - return; - } else - *sub++ = '\0'; + } + manpath = mandoc_strndup(fullpath, file - fullpath); + file++; - if ( ! (atou(path, &vol) && atou(sub, &rec))) { - resp_badmanual(); + if ( ! validate_manpath(req, manpath)) { + pg_error_badrequest( + "You specified an invalid manpath."); + free(manpath); return; - } else if (vol >= (unsigned int)ps->sz) { - resp_badmanual(); - 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. */ - - db = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL); - if (NULL == db) { - 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 = (*db->get)(db, &key, &val, 0))) { - rc < 0 ? resp_baddb() : resp_badmanual(); - (*db->close)(db); + if ( ! validate_filename(file)) { + pg_error_badrequest( + "You specified an invalid manual file."); return; - } + } - /* Extra filename: the first nil-terminated entry. */ - - strlcpy(file, ps->paths[vol], MAXPATHLEN); - strlcat(file, "/", MAXPATHLEN); - strlcat(file, (char *)val.data, MAXPATHLEN); - - (*db->close)(db); - - format(file); + 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_badexpr(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 = ""; - - cache = getenv("CACHE_DIR"); - if (NULL == cache) - cache = "/cache/man.cgi"; - - if (-1 == chdir(cache)) { - resp_bad(); + 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); } - host = getenv("HTTP_HOST"); - if (NULL == host) - host = "localhost"; + /* Scan our run-time environment. */ - memset(&req, 0, sizeof(struct req)); + if (NULL == (scriptname = getenv("SCRIPT_NAME"))) + scriptname = ""; - if (NULL != (p = getenv("QUERY_STRING"))) - kval_parse(&req.fields, &req.fieldsz, p); + if ( ! validate_urifrag(scriptname)) { + fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n", + scriptname); + pg_error_internal(); + return(EXIT_FAILURE); + } - /* Resolve leading subpath component. */ + /* + * First we change directory into the MAN_DIR so that + * subsequent scanning for manpath directories is rooted + * relative to the same position. + */ - subpath = path = NULL; - req.page = PAGE__MAX; + if (-1 == chdir(MAN_DIR)) { + fprintf(stderr, "MAN_DIR: %s: %s\n", + MAN_DIR, strerror(errno)); + pg_error_internal(); + return(EXIT_FAILURE); + } - if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path) - req.page = PAGE_INDEX; + memset(&req, 0, sizeof(struct req)); + pathgen(&req); - if (NULL != path && '/' == *path && '\0' == *++path) - req.page = PAGE_INDEX; + /* Next parse out the query string. */ - /* Strip file suffix. */ + if (NULL != (querystring = getenv("QUERY_STRING"))) + http_parse(&req, querystring); - if (NULL != path && NULL != (p = strrchr(path, '.'))) - if (NULL != p && NULL == strchr(p, '/')) - *p++ = '\0'; + 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); + } - /* Resolve subpath component. */ + 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 != (subpath = strchr(path, '/'))) - *subpath++ = '\0'; + /* Dispatch to the three different pages. */ - /* Map path into one we recognise. */ + path = getenv("PATH_INFO"); + if (NULL == path) + path = ""; + else if ('/' == *path) + path++; - 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; - } + if ('\0' != *path) + pg_show(&req, path); + else if (NULL != req.q.query) + pg_search(&req); + else + pg_index(&req); - /* Initialise MANPATH. */ + 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); +} - memset(&paths, 0, sizeof(struct manpaths)); - manpath_manconf("etc/man.conf", &paths); +/* + * Scan for indexable paths. + */ +static void +pathgen(struct req *req) +{ + FILE *fp; + char *dp; + size_t dpsz; - /* Route pages. */ + if (NULL == (fp = fopen("manpath.conf", "r"))) { + fprintf(stderr, "%s/manpath.conf: %s\n", + MAN_DIR, strerror(errno)); + pg_error_internal(); + exit(EXIT_FAILURE); + } - 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: - break; + while (NULL != (dp = fgetln(fp, &dpsz))) { + if ('\n' == dp[dpsz - 1]) + dpsz--; + req->p = mandoc_realloc(req->p, + (req->psz + 1) * sizeof(char *)); + dp = mandoc_strndup(dp, dpsz); + 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; } - manpath_free(&paths); - kval_free(req.fields, req.fieldsz); - - return(EXIT_SUCCESS); + if ( req->p == NULL ) { + fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR); + pg_error_internal(); + exit(EXIT_FAILURE); + } }