1*f211c215Sschwarze /* $OpenBSD: cgi.c,v 1.61 2016/04/14 23:48:06 schwarze Exp $ */ 2c6c22f12Sschwarze /* 3c6c22f12Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4abf19dc9Sschwarze * Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@usta.de> 5c6c22f12Sschwarze * 6c6c22f12Sschwarze * Permission to use, copy, modify, and distribute this software for any 7c6c22f12Sschwarze * purpose with or without fee is hereby granted, provided that the above 8c6c22f12Sschwarze * copyright notice and this permission notice appear in all copies. 9c6c22f12Sschwarze * 104de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 11c6c22f12Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 124de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 13c6c22f12Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14c6c22f12Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15c6c22f12Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16c6c22f12Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17c6c22f12Sschwarze */ 18136c26b8Sschwarze #include <sys/types.h> 19136c26b8Sschwarze #include <sys/time.h> 20136c26b8Sschwarze 21c6c22f12Sschwarze #include <ctype.h> 22c6c22f12Sschwarze #include <errno.h> 23c6c22f12Sschwarze #include <fcntl.h> 24c6c22f12Sschwarze #include <limits.h> 25c2235d37Sschwarze #include <stdint.h> 26c6c22f12Sschwarze #include <stdio.h> 27c6c22f12Sschwarze #include <stdlib.h> 28c6c22f12Sschwarze #include <string.h> 29c6c22f12Sschwarze #include <unistd.h> 30c6c22f12Sschwarze 31c6c22f12Sschwarze #include "mandoc_aux.h" 32f2d5c709Sschwarze #include "mandoc.h" 33f2d5c709Sschwarze #include "roff.h" 34396853b5Sschwarze #include "mdoc.h" 35fec2846bSschwarze #include "man.h" 36c6c22f12Sschwarze #include "main.h" 374de77decSschwarze #include "manconf.h" 38c6c22f12Sschwarze #include "mansearch.h" 396fdade3eSschwarze #include "cgi.h" 40c6c22f12Sschwarze 41c6c22f12Sschwarze /* 42c6c22f12Sschwarze * A query as passed to the search function. 43c6c22f12Sschwarze */ 44c6c22f12Sschwarze struct query { 4531e689c3Sschwarze char *manpath; /* desired manual directory */ 4631e689c3Sschwarze char *arch; /* architecture */ 4731e689c3Sschwarze char *sec; /* manual section */ 48e89321abSschwarze char *query; /* unparsed query expression */ 494477fbfaSschwarze int equal; /* match whole names, not substrings */ 50c6c22f12Sschwarze }; 51c6c22f12Sschwarze 52c6c22f12Sschwarze struct req { 53c6c22f12Sschwarze struct query q; 54c6c22f12Sschwarze char **p; /* array of available manpaths */ 55c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 5652b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 57c6c22f12Sschwarze }; 58c6c22f12Sschwarze 59c6c22f12Sschwarze static void catman(const struct req *, const char *); 60c6c22f12Sschwarze static void format(const struct req *, const char *); 61c6c22f12Sschwarze static void html_print(const char *); 62c6c22f12Sschwarze static void html_putchar(char); 63c6c22f12Sschwarze static int http_decode(char *); 6431e689c3Sschwarze static void http_parse(struct req *, const char *); 65c6c22f12Sschwarze static void pathgen(struct req *); 6602b1b494Sschwarze static void path_parse(struct req *req, const char *path); 67facea411Sschwarze static void pg_error_badrequest(const char *); 68facea411Sschwarze static void pg_error_internal(void); 69facea411Sschwarze static void pg_index(const struct req *); 70facea411Sschwarze static void pg_noresult(const struct req *, const char *); 7157482ef4Sschwarze static void pg_search(const struct req *); 72facea411Sschwarze static void pg_searchres(const struct req *, 73facea411Sschwarze struct manpage *, size_t); 7481060b1aSschwarze static void pg_show(struct req *, const char *); 75c6c22f12Sschwarze static void resp_begin_html(int, const char *); 76c6c22f12Sschwarze static void resp_begin_http(int, const char *); 77711661c7Sschwarze static void resp_copy(const char *); 78c6c22f12Sschwarze static void resp_end_html(void); 79c6c22f12Sschwarze static void resp_searchform(const struct req *); 8046723f19Sschwarze static void resp_show(const struct req *, const char *); 81e89321abSschwarze static void set_query_attr(char **, char **); 82e89321abSschwarze static int validate_filename(const char *); 83e89321abSschwarze static int validate_manpath(const struct req *, const char *); 84e89321abSschwarze static int validate_urifrag(const char *); 85c6c22f12Sschwarze 863b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 87c6c22f12Sschwarze 8846723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 8928e449d6Sschwarze static const char *const sec_numbers[] = { 9028e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 9128e449d6Sschwarze }; 9228e449d6Sschwarze static const char *const sec_names[] = { 9328e449d6Sschwarze "All Sections", 9428e449d6Sschwarze "1 - General Commands", 9528e449d6Sschwarze "2 - System Calls", 964e6618fdSschwarze "3 - Library Functions", 974e6618fdSschwarze "3p - Perl Library", 984e6618fdSschwarze "4 - Device Drivers", 9928e449d6Sschwarze "5 - File Formats", 10028e449d6Sschwarze "6 - Games", 1014e6618fdSschwarze "7 - Miscellaneous Information", 1024e6618fdSschwarze "8 - System Manager\'s Manual", 1034e6618fdSschwarze "9 - Kernel Developer\'s Manual" 10428e449d6Sschwarze }; 10528e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 10628e449d6Sschwarze 10728e449d6Sschwarze static const char *const arch_names[] = { 10828e449d6Sschwarze "amd64", "alpha", "armish", "armv7", 10928e449d6Sschwarze "aviion", "hppa", "hppa64", "i386", 11028e449d6Sschwarze "ia64", "landisk", "loongson", "luna88k", 11128e449d6Sschwarze "macppc", "mips64", "octeon", "sgi", 11228e449d6Sschwarze "socppc", "solbourne", "sparc", "sparc64", 11328e449d6Sschwarze "vax", "zaurus", 11428e449d6Sschwarze "amiga", "arc", "arm32", "atari", 11528e449d6Sschwarze "beagle", "cats", "hp300", "mac68k", 11628e449d6Sschwarze "mvme68k", "mvme88k", "mvmeppc", "palm", 11728e449d6Sschwarze "pc532", "pegasos", "pmax", "powerpc", 11828e449d6Sschwarze "sun3", "wgrisc", "x68k" 11928e449d6Sschwarze }; 12028e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 12128e449d6Sschwarze 122c6c22f12Sschwarze /* 123c6c22f12Sschwarze * Print a character, escaping HTML along the way. 124c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 125c6c22f12Sschwarze */ 126c6c22f12Sschwarze static void 127c6c22f12Sschwarze html_putchar(char c) 128c6c22f12Sschwarze { 129c6c22f12Sschwarze 130c6c22f12Sschwarze switch (c) { 131c6c22f12Sschwarze case ('"'): 132c6c22f12Sschwarze printf(""e;"); 133c6c22f12Sschwarze break; 134c6c22f12Sschwarze case ('&'): 135c6c22f12Sschwarze printf("&"); 136c6c22f12Sschwarze break; 137c6c22f12Sschwarze case ('>'): 138c6c22f12Sschwarze printf(">"); 139c6c22f12Sschwarze break; 140c6c22f12Sschwarze case ('<'): 141c6c22f12Sschwarze printf("<"); 142c6c22f12Sschwarze break; 143c6c22f12Sschwarze default: 144c6c22f12Sschwarze putchar((unsigned char)c); 145c6c22f12Sschwarze break; 146c6c22f12Sschwarze } 147c6c22f12Sschwarze } 148c6c22f12Sschwarze 149c6c22f12Sschwarze /* 150c6c22f12Sschwarze * Call through to html_putchar(). 151c6c22f12Sschwarze * Accepts NULL strings. 152c6c22f12Sschwarze */ 153c6c22f12Sschwarze static void 154c6c22f12Sschwarze html_print(const char *p) 155c6c22f12Sschwarze { 156c6c22f12Sschwarze 157c6c22f12Sschwarze if (NULL == p) 158c6c22f12Sschwarze return; 159c6c22f12Sschwarze while ('\0' != *p) 160c6c22f12Sschwarze html_putchar(*p++); 161c6c22f12Sschwarze } 162c6c22f12Sschwarze 163c6c22f12Sschwarze /* 16431e689c3Sschwarze * Transfer the responsibility for the allocated string *val 16531e689c3Sschwarze * to the query structure. 166c6c22f12Sschwarze */ 167c6c22f12Sschwarze static void 16831e689c3Sschwarze set_query_attr(char **attr, char **val) 16931e689c3Sschwarze { 17031e689c3Sschwarze 17131e689c3Sschwarze free(*attr); 17231e689c3Sschwarze if (**val == '\0') { 17331e689c3Sschwarze *attr = NULL; 17431e689c3Sschwarze free(*val); 17531e689c3Sschwarze } else 17631e689c3Sschwarze *attr = *val; 17731e689c3Sschwarze *val = NULL; 17831e689c3Sschwarze } 17931e689c3Sschwarze 18031e689c3Sschwarze /* 18131e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 18231e689c3Sschwarze * and store the values into the query structure. 18331e689c3Sschwarze */ 18431e689c3Sschwarze static void 18531e689c3Sschwarze http_parse(struct req *req, const char *qs) 186c6c22f12Sschwarze { 187c6c22f12Sschwarze char *key, *val; 18831e689c3Sschwarze size_t keysz, valsz; 189c6c22f12Sschwarze 19052b413c1Sschwarze req->isquery = 1; 19131e689c3Sschwarze req->q.manpath = NULL; 19231e689c3Sschwarze req->q.arch = NULL; 19331e689c3Sschwarze req->q.sec = NULL; 194e89321abSschwarze req->q.query = NULL; 1954477fbfaSschwarze req->q.equal = 1; 196c6c22f12Sschwarze 19731e689c3Sschwarze key = val = NULL; 19831e689c3Sschwarze while (*qs != '\0') { 199c6c22f12Sschwarze 20031e689c3Sschwarze /* Parse one key. */ 201c6c22f12Sschwarze 20231e689c3Sschwarze keysz = strcspn(qs, "=;&"); 20331e689c3Sschwarze key = mandoc_strndup(qs, keysz); 20431e689c3Sschwarze qs += keysz; 20531e689c3Sschwarze if (*qs != '=') 20631e689c3Sschwarze goto next; 207c6c22f12Sschwarze 20831e689c3Sschwarze /* Parse one value. */ 209c6c22f12Sschwarze 21031e689c3Sschwarze valsz = strcspn(++qs, ";&"); 21131e689c3Sschwarze val = mandoc_strndup(qs, valsz); 21231e689c3Sschwarze qs += valsz; 213c6c22f12Sschwarze 21431e689c3Sschwarze /* Decode and catch encoding errors. */ 21531e689c3Sschwarze 21631e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 21731e689c3Sschwarze goto next; 21831e689c3Sschwarze 21931e689c3Sschwarze /* Handle key-value pairs. */ 22031e689c3Sschwarze 22131e689c3Sschwarze if ( ! strcmp(key, "query")) 222e89321abSschwarze set_query_attr(&req->q.query, &val); 22331e689c3Sschwarze 22431e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 22531e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 22631e689c3Sschwarze 22731e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 228aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 22931e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 230aabcc9a1Sschwarze val[7] = '-'; 231aabcc9a1Sschwarze if ('C' == val[8]) 232aabcc9a1Sschwarze val[8] = 'c'; 233aabcc9a1Sschwarze } 234aabcc9a1Sschwarze #endif 23531e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 23631e689c3Sschwarze } 23731e689c3Sschwarze 23831e689c3Sschwarze else if ( ! (strcmp(key, "sec") 239aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24031e689c3Sschwarze && strcmp(key, "sektion") 241aabcc9a1Sschwarze #endif 24231e689c3Sschwarze )) { 24331e689c3Sschwarze if ( ! strcmp(val, "0")) 24431e689c3Sschwarze *val = '\0'; 24531e689c3Sschwarze set_query_attr(&req->q.sec, &val); 246c6c22f12Sschwarze } 24731e689c3Sschwarze 24831e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 24931e689c3Sschwarze if ( ! strcmp(val, "default")) 25031e689c3Sschwarze *val = '\0'; 25131e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2524477fbfaSschwarze } 25331e689c3Sschwarze 25431e689c3Sschwarze /* 25531e689c3Sschwarze * The key must be freed in any case. 25631e689c3Sschwarze * The val may have been handed over to the query 25731e689c3Sschwarze * structure, in which case it is now NULL. 25831e689c3Sschwarze */ 25931e689c3Sschwarze next: 26031e689c3Sschwarze free(key); 26131e689c3Sschwarze key = NULL; 26231e689c3Sschwarze free(val); 26331e689c3Sschwarze val = NULL; 26431e689c3Sschwarze 26531e689c3Sschwarze if (*qs != '\0') 26631e689c3Sschwarze qs++; 26731e689c3Sschwarze } 268c6c22f12Sschwarze } 269c6c22f12Sschwarze 270c6c22f12Sschwarze /* 271c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 272c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 273c6c22f12Sschwarze * over the allocated string. 274c6c22f12Sschwarze */ 275c6c22f12Sschwarze static int 276c6c22f12Sschwarze http_decode(char *p) 277c6c22f12Sschwarze { 278c6c22f12Sschwarze char hex[3]; 2791f69f32bStedu char *q; 280c6c22f12Sschwarze int c; 281c6c22f12Sschwarze 282c6c22f12Sschwarze hex[2] = '\0'; 283c6c22f12Sschwarze 2841f69f32bStedu q = p; 2851f69f32bStedu for ( ; '\0' != *p; p++, q++) { 286c6c22f12Sschwarze if ('%' == *p) { 287c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 288526e306bSschwarze return 0; 289c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 290526e306bSschwarze return 0; 291c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 292526e306bSschwarze return 0; 293c6c22f12Sschwarze if ('\0' == c) 294526e306bSschwarze return 0; 295c6c22f12Sschwarze 2961f69f32bStedu *q = (char)c; 2971f69f32bStedu p += 2; 298c6c22f12Sschwarze } else 2991f69f32bStedu *q = '+' == *p ? ' ' : *p; 300c6c22f12Sschwarze } 301c6c22f12Sschwarze 3021f69f32bStedu *q = '\0'; 303526e306bSschwarze return 1; 304c6c22f12Sschwarze } 305c6c22f12Sschwarze 306c6c22f12Sschwarze static void 307c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 308c6c22f12Sschwarze { 309c6c22f12Sschwarze 310c6c22f12Sschwarze if (200 != code) 311fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 312c6c22f12Sschwarze 313fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 314fa9c540aStedu "Cache-Control: no-cache\r\n" 315fa9c540aStedu "Pragma: no-cache\r\n" 316fa9c540aStedu "\r\n"); 317c6c22f12Sschwarze 318c6c22f12Sschwarze fflush(stdout); 319c6c22f12Sschwarze } 320c6c22f12Sschwarze 321c6c22f12Sschwarze static void 322711661c7Sschwarze resp_copy(const char *filename) 323711661c7Sschwarze { 324711661c7Sschwarze char buf[4096]; 325711661c7Sschwarze ssize_t sz; 326711661c7Sschwarze int fd; 327711661c7Sschwarze 328711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 329711661c7Sschwarze fflush(stdout); 330711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 331711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 332711661c7Sschwarze } 333711661c7Sschwarze } 334711661c7Sschwarze 335711661c7Sschwarze static void 336c6c22f12Sschwarze resp_begin_html(int code, const char *msg) 337c6c22f12Sschwarze { 338c6c22f12Sschwarze 339c6c22f12Sschwarze resp_begin_http(code, msg); 340c6c22f12Sschwarze 341d649d931Sschwarze printf("<!DOCTYPE html>\n" 342c6c22f12Sschwarze "<HTML>\n" 343c6c22f12Sschwarze "<HEAD>\n" 344d649d931Sschwarze "<META CHARSET=\"UTF-8\" />\n" 34506bcd913Sschwarze "<LINK REL=\"stylesheet\" HREF=\"%s/mandoc.css\"" 346c6c22f12Sschwarze " TYPE=\"text/css\" media=\"all\">\n" 3476fdade3eSschwarze "<TITLE>%s</TITLE>\n" 348c6c22f12Sschwarze "</HEAD>\n" 349c6c22f12Sschwarze "<BODY>\n" 350c6c22f12Sschwarze "<!-- Begin page content. //-->\n", 35106bcd913Sschwarze CSS_DIR, CUSTOMIZE_TITLE); 352711661c7Sschwarze 353711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 354c6c22f12Sschwarze } 355c6c22f12Sschwarze 356c6c22f12Sschwarze static void 357c6c22f12Sschwarze resp_end_html(void) 358c6c22f12Sschwarze { 359c6c22f12Sschwarze 360711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 361711661c7Sschwarze 362c6c22f12Sschwarze puts("</BODY>\n" 363c6c22f12Sschwarze "</HTML>"); 364c6c22f12Sschwarze } 365c6c22f12Sschwarze 366c6c22f12Sschwarze static void 367c6c22f12Sschwarze resp_searchform(const struct req *req) 368c6c22f12Sschwarze { 369c6c22f12Sschwarze int i; 370c6c22f12Sschwarze 371c6c22f12Sschwarze puts("<!-- Begin search form. //-->"); 372c6c22f12Sschwarze printf("<DIV ID=\"mancgi\">\n" 3733b9cfc6fSschwarze "<FORM ACTION=\"/%s\" METHOD=\"get\">\n" 374c6c22f12Sschwarze "<FIELDSET>\n" 37528e449d6Sschwarze "<LEGEND>Manual Page Search Parameters</LEGEND>\n", 376c6c22f12Sschwarze scriptname); 37728e449d6Sschwarze 37828e449d6Sschwarze /* Write query input box. */ 37928e449d6Sschwarze 38028e449d6Sschwarze printf( "<TABLE><TR><TD>\n" 3814477fbfaSschwarze "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\""); 382e89321abSschwarze if (NULL != req->q.query) 383e89321abSschwarze html_print(req->q.query); 38428e449d6Sschwarze puts("\" SIZE=\"40\">"); 38528e449d6Sschwarze 38628e449d6Sschwarze /* Write submission and reset buttons. */ 38728e449d6Sschwarze 38828e449d6Sschwarze printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n" 38928e449d6Sschwarze "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"); 39028e449d6Sschwarze 39128e449d6Sschwarze /* Write show radio button */ 39228e449d6Sschwarze 39328e449d6Sschwarze printf( "</TD><TD>\n" 39428e449d6Sschwarze "<INPUT TYPE=\"radio\" "); 39528e449d6Sschwarze if (req->q.equal) 396d56ca219Sschwarze printf("CHECKED=\"checked\" "); 39728e449d6Sschwarze printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n" 39828e449d6Sschwarze "<LABEL FOR=\"show\">Show named manual page</LABEL>\n"); 39928e449d6Sschwarze 40028e449d6Sschwarze /* Write section selector. */ 40128e449d6Sschwarze 402d56ca219Sschwarze puts( "</TD></TR><TR><TD>\n" 40328e449d6Sschwarze "<SELECT NAME=\"sec\">"); 40428e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 40528e449d6Sschwarze printf("<OPTION VALUE=\"%s\"", sec_numbers[i]); 40628e449d6Sschwarze if (NULL != req->q.sec && 40728e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 408d56ca219Sschwarze printf(" SELECTED=\"selected\""); 40928e449d6Sschwarze printf(">%s</OPTION>\n", sec_names[i]); 41028e449d6Sschwarze } 41128e449d6Sschwarze puts("</SELECT>"); 41228e449d6Sschwarze 41328e449d6Sschwarze /* Write architecture selector. */ 41428e449d6Sschwarze 415be14a32aSschwarze printf( "<SELECT NAME=\"arch\">\n" 416be14a32aSschwarze "<OPTION VALUE=\"default\""); 417be14a32aSschwarze if (NULL == req->q.arch) 418d56ca219Sschwarze printf(" SELECTED=\"selected\""); 419be14a32aSschwarze puts(">All Architectures</OPTION>"); 42028e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 42128e449d6Sschwarze printf("<OPTION VALUE=\"%s\"", arch_names[i]); 42228e449d6Sschwarze if (NULL != req->q.arch && 42328e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 424d56ca219Sschwarze printf(" SELECTED=\"selected\""); 42528e449d6Sschwarze printf(">%s</OPTION>\n", arch_names[i]); 42628e449d6Sschwarze } 42728e449d6Sschwarze puts("</SELECT>"); 42828e449d6Sschwarze 42928e449d6Sschwarze /* Write manpath selector. */ 43028e449d6Sschwarze 431c6c22f12Sschwarze if (req->psz > 1) { 43228e449d6Sschwarze puts("<SELECT NAME=\"manpath\">"); 433c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 434c6c22f12Sschwarze printf("<OPTION "); 43550eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 436d56ca219Sschwarze printf("SELECTED=\"selected\" "); 437c6c22f12Sschwarze printf("VALUE=\""); 438c6c22f12Sschwarze html_print(req->p[i]); 439c6c22f12Sschwarze printf("\">"); 440c6c22f12Sschwarze html_print(req->p[i]); 441c6c22f12Sschwarze puts("</OPTION>"); 442c6c22f12Sschwarze } 443c6c22f12Sschwarze puts("</SELECT>"); 444c6c22f12Sschwarze } 44528e449d6Sschwarze 44628e449d6Sschwarze /* Write search radio button */ 44728e449d6Sschwarze 44828e449d6Sschwarze printf( "</TD><TD>\n" 44928e449d6Sschwarze "<INPUT TYPE=\"radio\" "); 45028e449d6Sschwarze if (0 == req->q.equal) 451d56ca219Sschwarze printf("CHECKED=\"checked\" "); 45228e449d6Sschwarze printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n" 45328e449d6Sschwarze "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n"); 45428e449d6Sschwarze 45528e449d6Sschwarze puts("</TD></TR></TABLE>\n" 456c6c22f12Sschwarze "</FIELDSET>\n" 457c6c22f12Sschwarze "</FORM>\n" 458c6c22f12Sschwarze "</DIV>"); 459c6c22f12Sschwarze puts("<!-- End search form. //-->"); 460c6c22f12Sschwarze } 461c6c22f12Sschwarze 46281475784Sschwarze static int 463cf3a545cSschwarze validate_urifrag(const char *frag) 464cf3a545cSschwarze { 465cf3a545cSschwarze 466cf3a545cSschwarze while ('\0' != *frag) { 467cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 468cf3a545cSschwarze '-' == *frag || '.' == *frag || 469cf3a545cSschwarze '/' == *frag || '_' == *frag)) 470526e306bSschwarze return 0; 471cf3a545cSschwarze frag++; 472cf3a545cSschwarze } 473526e306bSschwarze return 1; 474cf3a545cSschwarze } 475cf3a545cSschwarze 476cf3a545cSschwarze static int 477631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 478631ce2c6Sschwarze { 479631ce2c6Sschwarze size_t i; 480631ce2c6Sschwarze 481631ce2c6Sschwarze if ( ! strcmp(manpath, "mandoc")) 482526e306bSschwarze return 1; 483631ce2c6Sschwarze 484631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 485631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 486526e306bSschwarze return 1; 487631ce2c6Sschwarze 488526e306bSschwarze return 0; 489631ce2c6Sschwarze } 490631ce2c6Sschwarze 491631ce2c6Sschwarze static int 49281475784Sschwarze validate_filename(const char *file) 49381475784Sschwarze { 49481475784Sschwarze 49581475784Sschwarze if ('.' == file[0] && '/' == file[1]) 49681475784Sschwarze file += 2; 49781475784Sschwarze 498526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 499526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 50081475784Sschwarze } 50181475784Sschwarze 502c6c22f12Sschwarze static void 503facea411Sschwarze pg_index(const struct req *req) 504c6c22f12Sschwarze { 505c6c22f12Sschwarze 506c6c22f12Sschwarze resp_begin_html(200, NULL); 507c6c22f12Sschwarze resp_searchform(req); 508f267f4feSschwarze printf("<P>\n" 509d56ca219Sschwarze "This web interface is documented in the\n" 5103b9cfc6fSschwarze "<A HREF=\"/%s%smandoc/man8/man.cgi.8\">man.cgi</A>\n" 511d56ca219Sschwarze "manual, and the\n" 5123b9cfc6fSschwarze "<A HREF=\"/%s%smandoc/man1/apropos.1\">apropos</A>\n" 5132a43838fSschwarze "manual explains the query syntax.\n" 514f267f4feSschwarze "</P>\n", 5153b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5163b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 517c6c22f12Sschwarze resp_end_html(); 518c6c22f12Sschwarze } 519c6c22f12Sschwarze 520c6c22f12Sschwarze static void 521facea411Sschwarze pg_noresult(const struct req *req, const char *msg) 522c6c22f12Sschwarze { 523c6c22f12Sschwarze resp_begin_html(200, NULL); 524c6c22f12Sschwarze resp_searchform(req); 525c6c22f12Sschwarze puts("<P>"); 526c6c22f12Sschwarze puts(msg); 527c6c22f12Sschwarze puts("</P>"); 528c6c22f12Sschwarze resp_end_html(); 529c6c22f12Sschwarze } 530c6c22f12Sschwarze 531c6c22f12Sschwarze static void 532facea411Sschwarze pg_error_badrequest(const char *msg) 533c6c22f12Sschwarze { 534c6c22f12Sschwarze 535c6c22f12Sschwarze resp_begin_html(400, "Bad Request"); 536c6c22f12Sschwarze puts("<H1>Bad Request</H1>\n" 537c6c22f12Sschwarze "<P>\n"); 538c6c22f12Sschwarze puts(msg); 539c6c22f12Sschwarze printf("Try again from the\n" 5403b9cfc6fSschwarze "<A HREF=\"/%s\">main page</A>.\n" 541c6c22f12Sschwarze "</P>", scriptname); 542c6c22f12Sschwarze resp_end_html(); 543c6c22f12Sschwarze } 544c6c22f12Sschwarze 545c6c22f12Sschwarze static void 546facea411Sschwarze pg_error_internal(void) 547c6c22f12Sschwarze { 548c6c22f12Sschwarze resp_begin_html(500, "Internal Server Error"); 549c6c22f12Sschwarze puts("<P>Internal Server Error</P>"); 550c6c22f12Sschwarze resp_end_html(); 551c6c22f12Sschwarze } 552c6c22f12Sschwarze 553c6c22f12Sschwarze static void 554facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 555c6c22f12Sschwarze { 556be14a32aSschwarze char *arch, *archend; 557db26164eSschwarze const char *sec; 558db26164eSschwarze size_t i, iuse; 559be14a32aSschwarze int archprio, archpriouse; 56046723f19Sschwarze int prio, priouse; 561c6c22f12Sschwarze 56281475784Sschwarze for (i = 0; i < sz; i++) { 56381475784Sschwarze if (validate_filename(r[i].file)) 56481475784Sschwarze continue; 56581475784Sschwarze fprintf(stderr, "invalid filename %s in %s database\n", 56681475784Sschwarze r[i].file, req->q.manpath); 56781475784Sschwarze pg_error_internal(); 56881475784Sschwarze return; 56981475784Sschwarze } 57081475784Sschwarze 57152b413c1Sschwarze if (req->isquery && sz == 1) { 572c6c22f12Sschwarze /* 573c6c22f12Sschwarze * If we have just one result, then jump there now 574c6c22f12Sschwarze * without any delay. 575c6c22f12Sschwarze */ 576fa9c540aStedu printf("Status: 303 See Other\r\n"); 5773b9cfc6fSschwarze printf("Location: http://%s/%s%s%s/%s", 5783b9cfc6fSschwarze HTTP_HOST, scriptname, 5793b9cfc6fSschwarze *scriptname == '\0' ? "" : "/", 5803b9cfc6fSschwarze req->q.manpath, r[0].file); 581fa9c540aStedu printf("\r\n" 582fa9c540aStedu "Content-Type: text/html; charset=utf-8\r\n" 583fa9c540aStedu "\r\n"); 584c6c22f12Sschwarze return; 585c6c22f12Sschwarze } 586c6c22f12Sschwarze 587c6c22f12Sschwarze resp_begin_html(200, NULL); 588c6c22f12Sschwarze resp_searchform(req); 589c6c22f12Sschwarze puts("<DIV CLASS=\"results\">"); 590c6c22f12Sschwarze puts("<TABLE>"); 591c6c22f12Sschwarze 592c6c22f12Sschwarze for (i = 0; i < sz; i++) { 593c6c22f12Sschwarze printf("<TR>\n" 594c6c22f12Sschwarze "<TD CLASS=\"title\">\n" 5953b9cfc6fSschwarze "<A HREF=\"/%s%s%s/%s", 5963b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5973b9cfc6fSschwarze req->q.manpath, r[i].file); 598c6c22f12Sschwarze printf("\">"); 599c6c22f12Sschwarze html_print(r[i].names); 600c6c22f12Sschwarze printf("</A>\n" 601c6c22f12Sschwarze "</TD>\n" 602c6c22f12Sschwarze "<TD CLASS=\"desc\">"); 603c6c22f12Sschwarze html_print(r[i].output); 604c6c22f12Sschwarze puts("</TD>\n" 605c6c22f12Sschwarze "</TR>"); 606c6c22f12Sschwarze } 607c6c22f12Sschwarze 608c6c22f12Sschwarze puts("</TABLE>\n" 609c6c22f12Sschwarze "</DIV>"); 61046723f19Sschwarze 61146723f19Sschwarze /* 61246723f19Sschwarze * In man(1) mode, show one of the pages 61346723f19Sschwarze * even if more than one is found. 61446723f19Sschwarze */ 61546723f19Sschwarze 61646723f19Sschwarze if (req->q.equal) { 61746723f19Sschwarze puts("<HR>"); 61846723f19Sschwarze iuse = 0; 619db26164eSschwarze priouse = 20; 620be14a32aSschwarze archpriouse = 3; 62146723f19Sschwarze for (i = 0; i < sz; i++) { 622db26164eSschwarze sec = r[i].file; 623db26164eSschwarze sec += strcspn(sec, "123456789"); 624db26164eSschwarze if (sec[0] == '\0') 62546723f19Sschwarze continue; 626db26164eSschwarze prio = sec_prios[sec[0] - '1']; 627db26164eSschwarze if (sec[1] != '/') 628db26164eSschwarze prio += 10; 629db26164eSschwarze if (req->q.arch == NULL) { 630be14a32aSschwarze archprio = 631db26164eSschwarze ((arch = strchr(sec + 1, '/')) 632db26164eSschwarze == NULL) ? 3 : 633db26164eSschwarze ((archend = strchr(arch + 1, '/')) 634db26164eSschwarze == NULL) ? 0 : 635be14a32aSschwarze strncmp(arch, "amd64/", 636be14a32aSschwarze archend - arch) ? 2 : 1; 637be14a32aSschwarze if (archprio < archpriouse) { 638be14a32aSschwarze archpriouse = archprio; 639be14a32aSschwarze priouse = prio; 640be14a32aSschwarze iuse = i; 641be14a32aSschwarze continue; 642be14a32aSschwarze } 643be14a32aSschwarze if (archprio > archpriouse) 644be14a32aSschwarze continue; 645be14a32aSschwarze } 64646723f19Sschwarze if (prio >= priouse) 64746723f19Sschwarze continue; 64846723f19Sschwarze priouse = prio; 64946723f19Sschwarze iuse = i; 65046723f19Sschwarze } 65146723f19Sschwarze resp_show(req, r[iuse].file); 65246723f19Sschwarze } 65346723f19Sschwarze 654c6c22f12Sschwarze resp_end_html(); 655c6c22f12Sschwarze } 656c6c22f12Sschwarze 657c6c22f12Sschwarze static void 658c6c22f12Sschwarze catman(const struct req *req, const char *file) 659c6c22f12Sschwarze { 660c6c22f12Sschwarze FILE *f; 661c6c22f12Sschwarze char *p; 66231f93c25Sschwarze size_t sz; 66331f93c25Sschwarze ssize_t len; 66431f93c25Sschwarze int i; 665c6c22f12Sschwarze int italic, bold; 666c6c22f12Sschwarze 66731f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 66846723f19Sschwarze puts("<P>You specified an invalid manual file.</P>"); 669c6c22f12Sschwarze return; 670c6c22f12Sschwarze } 671c6c22f12Sschwarze 672c6c22f12Sschwarze puts("<DIV CLASS=\"catman\">\n" 673c6c22f12Sschwarze "<PRE>"); 674c6c22f12Sschwarze 67531f93c25Sschwarze p = NULL; 67631f93c25Sschwarze sz = 0; 67731f93c25Sschwarze 67831f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 679c6c22f12Sschwarze bold = italic = 0; 68031f93c25Sschwarze for (i = 0; i < len - 1; i++) { 681c6c22f12Sschwarze /* 682c6c22f12Sschwarze * This means that the catpage is out of state. 683c6c22f12Sschwarze * Ignore it and keep going (although the 684c6c22f12Sschwarze * catpage is bogus). 685c6c22f12Sschwarze */ 686c6c22f12Sschwarze 687c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 688c6c22f12Sschwarze continue; 689c6c22f12Sschwarze 690c6c22f12Sschwarze /* 691c6c22f12Sschwarze * Print a regular character. 692c6c22f12Sschwarze * Close out any bold/italic scopes. 693c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 694c6c22f12Sschwarze * have something to enter when we backspace. 695c6c22f12Sschwarze */ 696c6c22f12Sschwarze 697c6c22f12Sschwarze if ('\b' != p[i + 1]) { 698c6c22f12Sschwarze if (italic) 699c6c22f12Sschwarze printf("</I>"); 700c6c22f12Sschwarze if (bold) 701c6c22f12Sschwarze printf("</B>"); 702c6c22f12Sschwarze italic = bold = 0; 703c6c22f12Sschwarze html_putchar(p[i]); 704c6c22f12Sschwarze continue; 70531f93c25Sschwarze } else if (i + 2 >= len) 706c6c22f12Sschwarze continue; 707c6c22f12Sschwarze 708c6c22f12Sschwarze /* Italic mode. */ 709c6c22f12Sschwarze 710c6c22f12Sschwarze if ('_' == p[i]) { 711c6c22f12Sschwarze if (bold) 712c6c22f12Sschwarze printf("</B>"); 713c6c22f12Sschwarze if ( ! italic) 714c6c22f12Sschwarze printf("<I>"); 715c6c22f12Sschwarze bold = 0; 716c6c22f12Sschwarze italic = 1; 717c6c22f12Sschwarze i += 2; 718c6c22f12Sschwarze html_putchar(p[i]); 719c6c22f12Sschwarze continue; 720c6c22f12Sschwarze } 721c6c22f12Sschwarze 722c6c22f12Sschwarze /* 723c6c22f12Sschwarze * Handle funny behaviour troff-isms. 724c6c22f12Sschwarze * These grok'd from the original man2html.c. 725c6c22f12Sschwarze */ 726c6c22f12Sschwarze 727c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 728c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 729c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 730c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 731c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 732c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 733c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 734c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 735c6c22f12Sschwarze if (italic) 736c6c22f12Sschwarze printf("</I>"); 737c6c22f12Sschwarze if (bold) 738c6c22f12Sschwarze printf("</B>"); 739c6c22f12Sschwarze italic = bold = 0; 740c6c22f12Sschwarze putchar('*'); 741c6c22f12Sschwarze i += 2; 742c6c22f12Sschwarze continue; 743c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 744c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 745c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 746c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 747c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 748c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 749c6c22f12Sschwarze if (italic) 750c6c22f12Sschwarze printf("</I>"); 751c6c22f12Sschwarze if (bold) 752c6c22f12Sschwarze printf("</B>"); 753c6c22f12Sschwarze italic = bold = 0; 754c6c22f12Sschwarze putchar('+'); 755c6c22f12Sschwarze i += 2; 756c6c22f12Sschwarze continue; 757c6c22f12Sschwarze } 758c6c22f12Sschwarze 759c6c22f12Sschwarze /* Bold mode. */ 760c6c22f12Sschwarze 761c6c22f12Sschwarze if (italic) 762c6c22f12Sschwarze printf("</I>"); 763c6c22f12Sschwarze if ( ! bold) 764c6c22f12Sschwarze printf("<B>"); 765c6c22f12Sschwarze bold = 1; 766c6c22f12Sschwarze italic = 0; 767c6c22f12Sschwarze i += 2; 768c6c22f12Sschwarze html_putchar(p[i]); 769c6c22f12Sschwarze } 770c6c22f12Sschwarze 771c6c22f12Sschwarze /* 772c6c22f12Sschwarze * Clean up the last character. 773c6c22f12Sschwarze * We can get to a newline; don't print that. 774c6c22f12Sschwarze */ 775c6c22f12Sschwarze 776c6c22f12Sschwarze if (italic) 777c6c22f12Sschwarze printf("</I>"); 778c6c22f12Sschwarze if (bold) 779c6c22f12Sschwarze printf("</B>"); 780c6c22f12Sschwarze 78131f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 782c6c22f12Sschwarze html_putchar(p[i]); 783c6c22f12Sschwarze 784c6c22f12Sschwarze putchar('\n'); 785c6c22f12Sschwarze } 78631f93c25Sschwarze free(p); 787c6c22f12Sschwarze 788c6c22f12Sschwarze puts("</PRE>\n" 78946723f19Sschwarze "</DIV>"); 790c6c22f12Sschwarze 791c6c22f12Sschwarze fclose(f); 792c6c22f12Sschwarze } 793c6c22f12Sschwarze 794c6c22f12Sschwarze static void 795c6c22f12Sschwarze format(const struct req *req, const char *file) 796c6c22f12Sschwarze { 7972ccd0917Sschwarze struct manoutput conf; 798c6c22f12Sschwarze struct mparse *mp; 799ede1b9d0Sschwarze struct roff_man *man; 800c6c22f12Sschwarze void *vp; 801f74d674aSschwarze int fd; 802f74d674aSschwarze int usepath; 803c6c22f12Sschwarze 804c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 80546723f19Sschwarze puts("<P>You specified an invalid manual file.</P>"); 806c6c22f12Sschwarze return; 807c6c22f12Sschwarze } 808c6c22f12Sschwarze 80916536faaSschwarze mchars_alloc(); 81016536faaSschwarze mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, req->q.manpath); 811df927bb6Sschwarze mparse_readfd(mp, fd, file); 812c6c22f12Sschwarze close(fd); 813c6c22f12Sschwarze 8142ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 8152ccd0917Sschwarze conf.fragment = 1; 816f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 817*f211c215Sschwarze mandoc_asprintf(&conf.man, "/%s%s%%N.%%S", 818*f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 819c6c22f12Sschwarze 820f2d5c709Sschwarze mparse_result(mp, &man, NULL); 821f2d5c709Sschwarze if (man == NULL) { 822c6c22f12Sschwarze fprintf(stderr, "fatal mandoc error: %s/%s\n", 823c6c22f12Sschwarze req->q.manpath, file); 824facea411Sschwarze pg_error_internal(); 825c6c22f12Sschwarze mparse_free(mp); 82616536faaSschwarze mchars_free(); 827c6c22f12Sschwarze return; 828c6c22f12Sschwarze } 829c6c22f12Sschwarze 83016536faaSschwarze vp = html_alloc(&conf); 831c6c22f12Sschwarze 832396853b5Sschwarze if (man->macroset == MACROSET_MDOC) { 833396853b5Sschwarze mdoc_validate(man); 834f2d5c709Sschwarze html_mdoc(vp, man); 835fec2846bSschwarze } else { 836fec2846bSschwarze man_validate(man); 837c6c22f12Sschwarze html_man(vp, man); 838fec2846bSschwarze } 839c6c22f12Sschwarze 840c6c22f12Sschwarze html_free(vp); 841c6c22f12Sschwarze mparse_free(mp); 84216536faaSschwarze mchars_free(); 8432ccd0917Sschwarze free(conf.man); 844c6c22f12Sschwarze } 845c6c22f12Sschwarze 846c6c22f12Sschwarze static void 84746723f19Sschwarze resp_show(const struct req *req, const char *file) 84846723f19Sschwarze { 84981475784Sschwarze 85081475784Sschwarze if ('.' == file[0] && '/' == file[1]) 8512f7bef27Sschwarze file += 2; 85246723f19Sschwarze 85346723f19Sschwarze if ('c' == *file) 85446723f19Sschwarze catman(req, file); 85546723f19Sschwarze else 85646723f19Sschwarze format(req, file); 85746723f19Sschwarze } 85846723f19Sschwarze 85946723f19Sschwarze static void 860b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 861c6c22f12Sschwarze { 862b53c14c3Sschwarze char *manpath; 863b53c14c3Sschwarze const char *file; 864c6c22f12Sschwarze 865b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 866facea411Sschwarze pg_error_badrequest( 867c6c22f12Sschwarze "You did not specify a page to show."); 868c6c22f12Sschwarze return; 869c6c22f12Sschwarze } 870b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 871b53c14c3Sschwarze file++; 872c6c22f12Sschwarze 873b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 874631ce2c6Sschwarze pg_error_badrequest( 875631ce2c6Sschwarze "You specified an invalid manpath."); 876b53c14c3Sschwarze free(manpath); 877631ce2c6Sschwarze return; 878631ce2c6Sschwarze } 879631ce2c6Sschwarze 880c6c22f12Sschwarze /* 881c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 882c6c22f12Sschwarze * This way we can pick up the database files, which are 883c6c22f12Sschwarze * relative to the manpath root. 884c6c22f12Sschwarze */ 885c6c22f12Sschwarze 886b53c14c3Sschwarze if (chdir(manpath) == -1) { 887631ce2c6Sschwarze fprintf(stderr, "chdir %s: %s\n", 888b53c14c3Sschwarze manpath, strerror(errno)); 889631ce2c6Sschwarze pg_error_internal(); 890b53c14c3Sschwarze free(manpath); 891c6c22f12Sschwarze return; 892c6c22f12Sschwarze } 893c6c22f12Sschwarze 894b53c14c3Sschwarze if (strcmp(manpath, "mandoc")) { 895b53c14c3Sschwarze free(req->q.manpath); 896b53c14c3Sschwarze req->q.manpath = manpath; 897b53c14c3Sschwarze } else 898b53c14c3Sschwarze free(manpath); 899b53c14c3Sschwarze 900b53c14c3Sschwarze if ( ! validate_filename(file)) { 90181475784Sschwarze pg_error_badrequest( 90281475784Sschwarze "You specified an invalid manual file."); 90381475784Sschwarze return; 90481475784Sschwarze } 90581475784Sschwarze 90646723f19Sschwarze resp_begin_html(200, NULL); 90746723f19Sschwarze resp_searchform(req); 908b53c14c3Sschwarze resp_show(req, file); 90946723f19Sschwarze resp_end_html(); 910c6c22f12Sschwarze } 911c6c22f12Sschwarze 912c6c22f12Sschwarze static void 91357482ef4Sschwarze pg_search(const struct req *req) 914c6c22f12Sschwarze { 915c6c22f12Sschwarze struct mansearch search; 916c6c22f12Sschwarze struct manpaths paths; 917c6c22f12Sschwarze struct manpage *res; 918fbeeb774Sschwarze char **argv; 919fbeeb774Sschwarze char *query, *rp, *wp; 920c6c22f12Sschwarze size_t ressz; 921fbeeb774Sschwarze int argc; 922c6c22f12Sschwarze 923c6c22f12Sschwarze /* 924c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 925c6c22f12Sschwarze * This way we can pick up the database files, which are 926c6c22f12Sschwarze * relative to the manpath root. 927c6c22f12Sschwarze */ 928c6c22f12Sschwarze 929c6c22f12Sschwarze if (-1 == (chdir(req->q.manpath))) { 930631ce2c6Sschwarze fprintf(stderr, "chdir %s: %s\n", 931631ce2c6Sschwarze req->q.manpath, strerror(errno)); 932631ce2c6Sschwarze pg_error_internal(); 933c6c22f12Sschwarze return; 934c6c22f12Sschwarze } 935c6c22f12Sschwarze 936c6c22f12Sschwarze search.arch = req->q.arch; 937c6c22f12Sschwarze search.sec = req->q.sec; 9380f10154cSschwarze search.outkey = "Nd"; 9390f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 940fea71919Sschwarze search.firstmatch = 1; 941c6c22f12Sschwarze 942c6c22f12Sschwarze paths.sz = 1; 943c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 944c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 945c6c22f12Sschwarze 946c6c22f12Sschwarze /* 947fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 948c6c22f12Sschwarze */ 949c6c22f12Sschwarze 950fbeeb774Sschwarze argc = 0; 951fbeeb774Sschwarze argv = NULL; 952fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 953fbeeb774Sschwarze for (;;) { 954fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 955fbeeb774Sschwarze rp++; 956fbeeb774Sschwarze if (*rp == '\0') 957fbeeb774Sschwarze break; 958fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 959fbeeb774Sschwarze argv[argc++] = wp = rp; 960fbeeb774Sschwarze for (;;) { 961fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 962fbeeb774Sschwarze *wp = '\0'; 963fbeeb774Sschwarze rp++; 964fbeeb774Sschwarze break; 965fbeeb774Sschwarze } 966fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 967fbeeb774Sschwarze rp++; 968fbeeb774Sschwarze if (wp != rp) 969fbeeb774Sschwarze *wp = *rp; 970fbeeb774Sschwarze if (*rp == '\0') 971fbeeb774Sschwarze break; 972fbeeb774Sschwarze wp++; 973fbeeb774Sschwarze rp++; 974fbeeb774Sschwarze } 975c6c22f12Sschwarze } 976c6c22f12Sschwarze 977fbeeb774Sschwarze if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz)) 978facea411Sschwarze pg_noresult(req, "You entered an invalid query."); 979c6c22f12Sschwarze else if (0 == ressz) 980facea411Sschwarze pg_noresult(req, "No results found."); 981c6c22f12Sschwarze else 982facea411Sschwarze pg_searchres(req, res, ressz); 983c6c22f12Sschwarze 984fbeeb774Sschwarze free(query); 985fbeeb774Sschwarze mansearch_free(res, ressz); 986c6c22f12Sschwarze free(paths.paths[0]); 987c6c22f12Sschwarze free(paths.paths); 988c6c22f12Sschwarze } 989c6c22f12Sschwarze 990c6c22f12Sschwarze int 991c6c22f12Sschwarze main(void) 992c6c22f12Sschwarze { 993c6c22f12Sschwarze struct req req; 994136c26b8Sschwarze struct itimerval itimer; 99557482ef4Sschwarze const char *path; 99631e689c3Sschwarze const char *querystring; 99757482ef4Sschwarze int i; 998c6c22f12Sschwarze 999136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 1000136c26b8Sschwarze 10012935aafcSschwarze itimer.it_value.tv_sec = 2; 1002136c26b8Sschwarze itimer.it_value.tv_usec = 0; 10032935aafcSschwarze itimer.it_interval.tv_sec = 2; 1004136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 1005136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1006136c26b8Sschwarze fprintf(stderr, "setitimer: %s\n", strerror(errno)); 1007136c26b8Sschwarze pg_error_internal(); 1008526e306bSschwarze return EXIT_FAILURE; 1009136c26b8Sschwarze } 1010136c26b8Sschwarze 1011c6c22f12Sschwarze /* 10126fdade3eSschwarze * First we change directory into the MAN_DIR so that 1013c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 1014c6c22f12Sschwarze * relative to the same position. 1015c6c22f12Sschwarze */ 1016c6c22f12Sschwarze 10176fdade3eSschwarze if (-1 == chdir(MAN_DIR)) { 1018c6c22f12Sschwarze fprintf(stderr, "MAN_DIR: %s: %s\n", 10196fdade3eSschwarze MAN_DIR, strerror(errno)); 1020facea411Sschwarze pg_error_internal(); 1021526e306bSschwarze return EXIT_FAILURE; 1022c6c22f12Sschwarze } 1023c6c22f12Sschwarze 1024c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1025abf19dc9Sschwarze req.q.equal = 1; 1026c6c22f12Sschwarze pathgen(&req); 1027c6c22f12Sschwarze 102802b1b494Sschwarze /* Parse the path info and the query string. */ 1029c6c22f12Sschwarze 103002b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 103102b1b494Sschwarze path = ""; 103202b1b494Sschwarze else if (*path == '/') 103302b1b494Sschwarze path++; 103402b1b494Sschwarze 103502b1b494Sschwarze if (*path != '\0' && access(path, F_OK) == -1) { 103602b1b494Sschwarze path_parse(&req, path); 103702b1b494Sschwarze path = ""; 103802b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1039c6c22f12Sschwarze http_parse(&req, querystring); 1040c6c22f12Sschwarze 104102b1b494Sschwarze /* Validate parsed data and add defaults. */ 104202b1b494Sschwarze 104350eaed2bSschwarze if (req.q.manpath == NULL) 104450eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 104550eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1046631ce2c6Sschwarze pg_error_badrequest( 1047631ce2c6Sschwarze "You specified an invalid manpath."); 1048526e306bSschwarze return EXIT_FAILURE; 1049631ce2c6Sschwarze } 1050631ce2c6Sschwarze 1051cf3a545cSschwarze if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { 1052cf3a545cSschwarze pg_error_badrequest( 1053cf3a545cSschwarze "You specified an invalid architecture."); 1054526e306bSschwarze return EXIT_FAILURE; 1055cf3a545cSschwarze } 1056cf3a545cSschwarze 105757482ef4Sschwarze /* Dispatch to the three different pages. */ 1058c6c22f12Sschwarze 105957482ef4Sschwarze if ('\0' != *path) 106057482ef4Sschwarze pg_show(&req, path); 1061e89321abSschwarze else if (NULL != req.q.query) 106257482ef4Sschwarze pg_search(&req); 106357482ef4Sschwarze else 1064facea411Sschwarze pg_index(&req); 1065c6c22f12Sschwarze 106631e689c3Sschwarze free(req.q.manpath); 106731e689c3Sschwarze free(req.q.arch); 106831e689c3Sschwarze free(req.q.sec); 1069e89321abSschwarze free(req.q.query); 1070c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1071c6c22f12Sschwarze free(req.p[i]); 1072c6c22f12Sschwarze free(req.p); 1073526e306bSschwarze return EXIT_SUCCESS; 1074c6c22f12Sschwarze } 1075c6c22f12Sschwarze 1076c6c22f12Sschwarze /* 107702b1b494Sschwarze * If PATH_INFO is not a file name, translate it to a query. 107802b1b494Sschwarze */ 107902b1b494Sschwarze static void 108002b1b494Sschwarze path_parse(struct req *req, const char *path) 108102b1b494Sschwarze { 108202b1b494Sschwarze int dir_done; 108302b1b494Sschwarze 108452b413c1Sschwarze req->isquery = 0; 108502b1b494Sschwarze req->q.equal = 1; 108602b1b494Sschwarze req->q.manpath = mandoc_strdup(path); 108702b1b494Sschwarze 108802b1b494Sschwarze /* Mandatory manual page name. */ 108902b1b494Sschwarze if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) { 109002b1b494Sschwarze req->q.query = req->q.manpath; 109102b1b494Sschwarze req->q.manpath = NULL; 109202b1b494Sschwarze } else 109302b1b494Sschwarze *req->q.query++ = '\0'; 109402b1b494Sschwarze 109502b1b494Sschwarze /* Optional trailing section. */ 109602b1b494Sschwarze if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) { 109702b1b494Sschwarze if(isdigit((unsigned char)req->q.sec[1])) { 109802b1b494Sschwarze *req->q.sec++ = '\0'; 109902b1b494Sschwarze req->q.sec = mandoc_strdup(req->q.sec); 110002b1b494Sschwarze } else 110102b1b494Sschwarze req->q.sec = NULL; 110202b1b494Sschwarze } 110302b1b494Sschwarze 110402b1b494Sschwarze /* Handle the case of name[.section] only. */ 110502b1b494Sschwarze if (req->q.manpath == NULL) { 110602b1b494Sschwarze req->q.arch = NULL; 110702b1b494Sschwarze return; 110802b1b494Sschwarze } 110902b1b494Sschwarze req->q.query = mandoc_strdup(req->q.query); 111002b1b494Sschwarze 111102b1b494Sschwarze /* Optional architecture. */ 111202b1b494Sschwarze dir_done = 0; 111302b1b494Sschwarze for (;;) { 111402b1b494Sschwarze if ((req->q.arch = strrchr(req->q.manpath, '/')) == NULL) 111502b1b494Sschwarze break; 111602b1b494Sschwarze *req->q.arch++ = '\0'; 111702b1b494Sschwarze if (dir_done || strncmp(req->q.arch, "man", 3)) { 111802b1b494Sschwarze req->q.arch = mandoc_strdup(req->q.arch); 111902b1b494Sschwarze break; 112002b1b494Sschwarze } 112102b1b494Sschwarze 112202b1b494Sschwarze /* Optional directory name. */ 112302b1b494Sschwarze req->q.arch += 3; 112402b1b494Sschwarze if (*req->q.arch != '\0') { 112502b1b494Sschwarze free(req->q.sec); 112602b1b494Sschwarze req->q.sec = mandoc_strdup(req->q.arch); 112702b1b494Sschwarze } 112802b1b494Sschwarze dir_done = 1; 112902b1b494Sschwarze } 113002b1b494Sschwarze } 113102b1b494Sschwarze 113202b1b494Sschwarze /* 1133c6c22f12Sschwarze * Scan for indexable paths. 1134c6c22f12Sschwarze */ 1135c6c22f12Sschwarze static void 1136c6c22f12Sschwarze pathgen(struct req *req) 1137c6c22f12Sschwarze { 1138c6c22f12Sschwarze FILE *fp; 1139c6c22f12Sschwarze char *dp; 1140c6c22f12Sschwarze size_t dpsz; 114131f93c25Sschwarze ssize_t len; 1142c6c22f12Sschwarze 1143de651747Sschwarze if (NULL == (fp = fopen("manpath.conf", "r"))) { 1144de651747Sschwarze fprintf(stderr, "%s/manpath.conf: %s\n", 1145de651747Sschwarze MAN_DIR, strerror(errno)); 1146de651747Sschwarze pg_error_internal(); 1147de651747Sschwarze exit(EXIT_FAILURE); 1148de651747Sschwarze } 1149c6c22f12Sschwarze 115031f93c25Sschwarze dp = NULL; 115131f93c25Sschwarze dpsz = 0; 115231f93c25Sschwarze 115331f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 115431f93c25Sschwarze if (dp[len - 1] == '\n') 115531f93c25Sschwarze dp[--len] = '\0'; 1156c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1157c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1158cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1159cf3a545cSschwarze fprintf(stderr, "%s/manpath.conf contains " 1160cf3a545cSschwarze "unsafe path \"%s\"\n", MAN_DIR, dp); 1161cf3a545cSschwarze pg_error_internal(); 1162cf3a545cSschwarze exit(EXIT_FAILURE); 1163cf3a545cSschwarze } 1164cf3a545cSschwarze if (NULL != strchr(dp, '/')) { 1165cf3a545cSschwarze fprintf(stderr, "%s/manpath.conf contains " 1166cf3a545cSschwarze "path with slash \"%s\"\n", MAN_DIR, dp); 1167cf3a545cSschwarze pg_error_internal(); 1168cf3a545cSschwarze exit(EXIT_FAILURE); 1169cf3a545cSschwarze } 1170cf3a545cSschwarze req->p[req->psz++] = dp; 117131f93c25Sschwarze dp = NULL; 117231f93c25Sschwarze dpsz = 0; 1173c6c22f12Sschwarze } 117431f93c25Sschwarze free(dp); 1175de651747Sschwarze 1176de651747Sschwarze if ( req->p == NULL ) { 1177de651747Sschwarze fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR); 1178de651747Sschwarze pg_error_internal(); 1179de651747Sschwarze exit(EXIT_FAILURE); 1180de651747Sschwarze } 1181c6c22f12Sschwarze } 1182