/* * Scan for indexable paths. */ static void pathgen(struct req *req) { FILE *fp; char *dp; size_t dpsz; ssize_t len; 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); } }
static void format(const struct req *req, const char *file) { struct manoutput conf; struct mparse *mp; struct roff_man *man; void *vp; int fd; int usepath; if (-1 == (fd = open(file, O_RDONLY, 0))) { puts("<P>You specified an invalid manual file.</P>"); return; } mchars_alloc(); mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, req->q.manpath); mparse_readfd(mp, fd, file); close(fd); 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, &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(&conf); if (man->macroset == MACROSET_MDOC) { mdoc_validate(man); html_mdoc(vp, man); } else { man_validate(man); html_man(vp, man); } html_free(vp); mparse_free(mp); mchars_free(); free(conf.man); }
/* * Scan for indexable paths. */ static void pathgen(struct req *req) { FILE *fp; char *dp; size_t dpsz; if (NULL == (fp = fopen("manpath.conf", "r"))) { fprintf(stderr, "%s/manpath.conf: %s\n", MAN_DIR, strerror(errno)); pg_error_internal(); exit(EXIT_FAILURE); } 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; } if ( req->p == NULL ) { fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR); pg_error_internal(); exit(EXIT_FAILURE); } }
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; } manpath = mandoc_strndup(fullpath, file - fullpath); file++; if ( ! validate_manpath(req, manpath)) { pg_error_badrequest( "You specified an invalid manpath."); free(manpath); return; } /* * Begin by chdir()ing into the manpath. * This way we can pick up the database files, which are * relative to the manpath root. */ if (chdir(manpath) == -1) { fprintf(stderr, "chdir %s: %s\n", manpath, strerror(errno)); pg_error_internal(); free(manpath); return; } 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; } resp_begin_html(200, NULL); resp_searchform(req); resp_show(req, file); resp_end_html(); }
static void pg_search(const struct req *req) { struct mansearch search; struct manpaths paths; struct manpage *res; char **argv; char *query, *rp, *wp; size_t ressz; int argc; /* * 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. */ if (-1 == (chdir(req->q.manpath))) { fprintf(stderr, "chdir %s: %s\n", req->q.manpath, strerror(errno)); pg_error_internal(); return; } 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("."); /* * Break apart at spaces with backslash-escaping. */ 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, 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); free(query); mansearch_free(res, ressz); free(paths.paths[0]); free(paths.paths); }
static void format(const struct req *req, const char *file) { struct mparse *mp; struct mchars *mchars; struct mdoc *mdoc; struct man *man; void *vp; char *opts; enum mandoclevel rc; int fd; int usepath; if (-1 == (fd = open(file, O_RDONLY, 0))) { puts("<P>You specified an invalid manual file.</P>"); return; } mchars = mchars_alloc(); mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL, mchars, req->q.manpath); rc = 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; } usepath = strcmp(req->q.manpath, req->p[0]); mandoc_asprintf(&opts, "fragment,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; } vp = html_alloc(mchars, opts); if (NULL != mdoc) html_mdoc(vp, mdoc); else html_man(vp, man); html_free(vp); mparse_free(mp); mchars_free(mchars); free(opts); }
static void pg_searchres(const struct req *req, struct manpage *r, size_t sz) { char *arch, *archend; size_t i, iuse, isec; int archprio, archpriouse; int prio, priouse; char sec; for (i = 0; i < sz; i++) { if (validate_filename(r[i].file)) continue; fprintf(stderr, "invalid filename %s in %s database\n", r[i].file, req->q.manpath); pg_error_internal(); return; } if (1 == sz) { /* * If we have just one result, then jump there now * without any delay. */ 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, "&"); printf("\r\n" "Content-Type: text/html; charset=utf-8\r\n" "\r\n"); return; } resp_begin_html(200, NULL); resp_searchform(req); puts("<DIV CLASS=\"results\">"); puts("<TABLE>"); for (i = 0; i < sz; i++) { printf("<TR>\n" "<TD CLASS=\"title\">\n" "<A HREF=\"%s/%s/%s?", scriptname, req->q.manpath, r[i].file); http_printquery(req, "&"); printf("\">"); html_print(r[i].names); printf("</A>\n" "</TD>\n" "<TD CLASS=\"desc\">"); html_print(r[i].output); puts("</TD>\n" "</TR>"); } puts("</TABLE>\n" "</DIV>"); /* * In man(1) mode, show one of the pages * even if more than one is found. */ if (req->q.equal) { puts("<HR>"); 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; } resp_show(req, r[iuse].file); } resp_end_html(); }
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"))) scriptname = ""; if ( ! validate_urifrag(scriptname)) { fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n", scriptname); pg_error_internal(); return(EXIT_FAILURE); } /* * 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(MAN_DIR)) { fprintf(stderr, "MAN_DIR: %s: %s\n", MAN_DIR, strerror(errno)); pg_error_internal(); return(EXIT_FAILURE); } memset(&req, 0, sizeof(struct req)); pathgen(&req); /* Next parse out the query string. */ if (NULL != (querystring = getenv("QUERY_STRING"))) http_parse(&req, querystring); 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); } if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { pg_error_badrequest( "You specified an invalid architecture."); return(EXIT_FAILURE); } /* Dispatch to the three different pages. */ path = getenv("PATH_INFO"); if (NULL == path) path = ""; else if ('/' == *path) path++; if ('\0' != *path) pg_show(&req, path); else if (NULL != req.q.query) pg_search(&req); else pg_index(&req); 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); }
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) { warn("setitimer"); pg_error_internal(); return EXIT_FAILURE; } /* * First we change directory into the MAN_DIR so that * subsequent scanning for manpath directories is rooted * relative to the same position. */ if (chdir(MAN_DIR) == -1) { warn("MAN_DIR: %s", MAN_DIR); pg_error_internal(); return EXIT_FAILURE; } memset(&req, 0, sizeof(struct req)); req.q.equal = 1; parse_manpath_conf(&req); /* Parse the path info and the query string. */ if ((path = getenv("PATH_INFO")) == NULL) path = ""; else if (*path == '/') path++; if (*path != '\0') { parse_path_info(&req, path); if (req.q.manpath == NULL || access(path, F_OK) == -1) path = ""; } else if ((querystring = getenv("QUERY_STRING")) != NULL) parse_query_string(&req, querystring); /* Validate parsed data and add defaults. */ 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; } if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { pg_error_badrequest( "You specified an invalid architecture."); return EXIT_FAILURE; } /* Dispatch to the three different pages. */ if ('\0' != *path) pg_show(&req, path); else if (NULL != req.q.query) pg_search(&req); else pg_index(&req); 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; }
static void pg_searchres(const struct req *req, struct manpage *r, size_t sz) { char *arch, *archend; const char *sec; size_t i, iuse; int archprio, archpriouse; int prio, priouse; for (i = 0; i < sz; i++) { if (validate_filename(r[i].file)) continue; warnx("invalid filename %s in %s database", r[i].file, req->q.manpath); pg_error_internal(); return; } if (req->isquery && sz == 1) { /* * If we have just one result, then jump there now * without any delay. */ printf("Status: 303 See Other\r\n"); printf("Location: http://%s/%s%s%s/%s", HTTP_HOST, scriptname, *scriptname == '\0' ? "" : "/", req->q.manpath, r[0].file); printf("\r\n" "Content-Type: text/html; charset=utf-8\r\n" "\r\n"); return; } resp_begin_html(200, NULL); resp_searchform(req, req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); if (sz > 1) { puts("<div class=\"results\">"); puts("<table>"); for (i = 0; i < sz; i++) { printf("<tr>\n" "<td class=\"title\">\n" "<a href=\"/%s%s%s/%s", scriptname, *scriptname == '\0' ? "" : "/", req->q.manpath, r[i].file); printf("\">"); html_print(r[i].names); printf("</a>\n" "</td>\n" "<td class=\"desc\">"); html_print(r[i].output); puts("</td>\n" "</tr>"); } puts("</table>\n" "</div>"); } /* * In man(1) mode, show one of the pages * even if more than one is found. */ if (req->q.equal || sz == 1) { puts("<hr>"); 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; } resp_show(req, r[iuse].file); } resp_end_html(); }