1*93d3e4e1Sderaadt /* $OpenBSD: cgi.c,v 1.85 2017/01/25 03:19:56 deraadt Exp $ */ 2c6c22f12Sschwarze /* 3c6c22f12Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4ce781f36Sschwarze * Copyright (c) 2014, 2015, 2016, 2017 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> 22c976f0e2Sschwarze #include <err.h> 23c6c22f12Sschwarze #include <errno.h> 24c6c22f12Sschwarze #include <fcntl.h> 25c6c22f12Sschwarze #include <limits.h> 26c2235d37Sschwarze #include <stdint.h> 27c6c22f12Sschwarze #include <stdio.h> 28c6c22f12Sschwarze #include <stdlib.h> 29c6c22f12Sschwarze #include <string.h> 30c6c22f12Sschwarze #include <unistd.h> 31c6c22f12Sschwarze 32c6c22f12Sschwarze #include "mandoc_aux.h" 33f2d5c709Sschwarze #include "mandoc.h" 34f2d5c709Sschwarze #include "roff.h" 35396853b5Sschwarze #include "mdoc.h" 36fec2846bSschwarze #include "man.h" 37c6c22f12Sschwarze #include "main.h" 384de77decSschwarze #include "manconf.h" 39c6c22f12Sschwarze #include "mansearch.h" 406fdade3eSschwarze #include "cgi.h" 41c6c22f12Sschwarze 42c6c22f12Sschwarze /* 43c6c22f12Sschwarze * A query as passed to the search function. 44c6c22f12Sschwarze */ 45c6c22f12Sschwarze struct query { 4631e689c3Sschwarze char *manpath; /* desired manual directory */ 4731e689c3Sschwarze char *arch; /* architecture */ 4831e689c3Sschwarze char *sec; /* manual section */ 49e89321abSschwarze char *query; /* unparsed query expression */ 504477fbfaSschwarze int equal; /* match whole names, not substrings */ 51c6c22f12Sschwarze }; 52c6c22f12Sschwarze 53c6c22f12Sschwarze struct req { 54c6c22f12Sschwarze struct query q; 55c6c22f12Sschwarze char **p; /* array of available manpaths */ 56c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 5752b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 58c6c22f12Sschwarze }; 59c6c22f12Sschwarze 6084f05c93Sschwarze enum focus { 6184f05c93Sschwarze FOCUS_NONE = 0, 6284f05c93Sschwarze FOCUS_QUERY 6384f05c93Sschwarze }; 6484f05c93Sschwarze 65c6c22f12Sschwarze static void html_print(const char *); 66c6c22f12Sschwarze static void html_putchar(char); 67c6c22f12Sschwarze static int http_decode(char *); 68941df026Sschwarze static void parse_manpath_conf(struct req *); 69941df026Sschwarze static void parse_path_info(struct req *req, const char *path); 70941df026Sschwarze static void parse_query_string(struct req *, const char *); 71facea411Sschwarze static void pg_error_badrequest(const char *); 72facea411Sschwarze static void pg_error_internal(void); 73facea411Sschwarze static void pg_index(const struct req *); 74facea411Sschwarze static void pg_noresult(const struct req *, const char *); 7557482ef4Sschwarze static void pg_search(const struct req *); 76facea411Sschwarze static void pg_searchres(const struct req *, 77facea411Sschwarze struct manpage *, size_t); 7881060b1aSschwarze static void pg_show(struct req *, const char *); 79c6c22f12Sschwarze static void resp_begin_html(int, const char *); 80c6c22f12Sschwarze static void resp_begin_http(int, const char *); 81941df026Sschwarze static void resp_catman(const struct req *, const char *); 82711661c7Sschwarze static void resp_copy(const char *); 83c6c22f12Sschwarze static void resp_end_html(void); 84941df026Sschwarze static void resp_format(const struct req *, const char *); 8584f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 8646723f19Sschwarze static void resp_show(const struct req *, const char *); 87e89321abSschwarze static void set_query_attr(char **, char **); 88e89321abSschwarze static int validate_filename(const char *); 89e89321abSschwarze static int validate_manpath(const struct req *, const char *); 90e89321abSschwarze static int validate_urifrag(const char *); 91c6c22f12Sschwarze 923b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 93c6c22f12Sschwarze 9446723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 9528e449d6Sschwarze static const char *const sec_numbers[] = { 9628e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 9728e449d6Sschwarze }; 9828e449d6Sschwarze static const char *const sec_names[] = { 9928e449d6Sschwarze "All Sections", 10028e449d6Sschwarze "1 - General Commands", 10128e449d6Sschwarze "2 - System Calls", 1024e6618fdSschwarze "3 - Library Functions", 1034e6618fdSschwarze "3p - Perl Library", 1044e6618fdSschwarze "4 - Device Drivers", 10528e449d6Sschwarze "5 - File Formats", 10628e449d6Sschwarze "6 - Games", 1074e6618fdSschwarze "7 - Miscellaneous Information", 1084e6618fdSschwarze "8 - System Manager\'s Manual", 1094e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11028e449d6Sschwarze }; 11128e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 11228e449d6Sschwarze 11328e449d6Sschwarze static const char *const arch_names[] = { 114*93d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64", 115766ef059Sschwarze "hppa", "i386", "landisk", 1163bcd4815Sschwarze "loongson", "luna88k", "macppc", "mips64", 1176250a715Sschwarze "octeon", "sgi", "socppc", "sparc64", 118766ef059Sschwarze "amiga", "arc", "armish", "arm32", 119766ef059Sschwarze "atari", "aviion", "beagle", "cats", 120766ef059Sschwarze "hppa64", "hp300", 1213bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1223bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 1230c245db5Sschwarze "pmax", "powerpc", "solbourne", "sparc", 1246250a715Sschwarze "sun3", "vax", "wgrisc", "x68k", 1256250a715Sschwarze "zaurus" 12628e449d6Sschwarze }; 12728e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 12828e449d6Sschwarze 129c6c22f12Sschwarze /* 130c6c22f12Sschwarze * Print a character, escaping HTML along the way. 131c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 132c6c22f12Sschwarze */ 133c6c22f12Sschwarze static void 134c6c22f12Sschwarze html_putchar(char c) 135c6c22f12Sschwarze { 136c6c22f12Sschwarze 137c6c22f12Sschwarze switch (c) { 138c6c22f12Sschwarze case ('"'): 139f765a656Sbentley printf("""); 140c6c22f12Sschwarze break; 141c6c22f12Sschwarze case ('&'): 142c6c22f12Sschwarze printf("&"); 143c6c22f12Sschwarze break; 144c6c22f12Sschwarze case ('>'): 145c6c22f12Sschwarze printf(">"); 146c6c22f12Sschwarze break; 147c6c22f12Sschwarze case ('<'): 148c6c22f12Sschwarze printf("<"); 149c6c22f12Sschwarze break; 150c6c22f12Sschwarze default: 151c6c22f12Sschwarze putchar((unsigned char)c); 152c6c22f12Sschwarze break; 153c6c22f12Sschwarze } 154c6c22f12Sschwarze } 155c6c22f12Sschwarze 156c6c22f12Sschwarze /* 157c6c22f12Sschwarze * Call through to html_putchar(). 158c6c22f12Sschwarze * Accepts NULL strings. 159c6c22f12Sschwarze */ 160c6c22f12Sschwarze static void 161c6c22f12Sschwarze html_print(const char *p) 162c6c22f12Sschwarze { 163c6c22f12Sschwarze 164c6c22f12Sschwarze if (NULL == p) 165c6c22f12Sschwarze return; 166c6c22f12Sschwarze while ('\0' != *p) 167c6c22f12Sschwarze html_putchar(*p++); 168c6c22f12Sschwarze } 169c6c22f12Sschwarze 170c6c22f12Sschwarze /* 17131e689c3Sschwarze * Transfer the responsibility for the allocated string *val 17231e689c3Sschwarze * to the query structure. 173c6c22f12Sschwarze */ 174c6c22f12Sschwarze static void 17531e689c3Sschwarze set_query_attr(char **attr, char **val) 17631e689c3Sschwarze { 17731e689c3Sschwarze 17831e689c3Sschwarze free(*attr); 17931e689c3Sschwarze if (**val == '\0') { 18031e689c3Sschwarze *attr = NULL; 18131e689c3Sschwarze free(*val); 18231e689c3Sschwarze } else 18331e689c3Sschwarze *attr = *val; 18431e689c3Sschwarze *val = NULL; 18531e689c3Sschwarze } 18631e689c3Sschwarze 18731e689c3Sschwarze /* 18831e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 18931e689c3Sschwarze * and store the values into the query structure. 19031e689c3Sschwarze */ 19131e689c3Sschwarze static void 192941df026Sschwarze parse_query_string(struct req *req, const char *qs) 193c6c22f12Sschwarze { 194c6c22f12Sschwarze char *key, *val; 19531e689c3Sschwarze size_t keysz, valsz; 196c6c22f12Sschwarze 19752b413c1Sschwarze req->isquery = 1; 19831e689c3Sschwarze req->q.manpath = NULL; 19931e689c3Sschwarze req->q.arch = NULL; 20031e689c3Sschwarze req->q.sec = NULL; 201e89321abSschwarze req->q.query = NULL; 2024477fbfaSschwarze req->q.equal = 1; 203c6c22f12Sschwarze 20431e689c3Sschwarze key = val = NULL; 20531e689c3Sschwarze while (*qs != '\0') { 206c6c22f12Sschwarze 20731e689c3Sschwarze /* Parse one key. */ 208c6c22f12Sschwarze 20931e689c3Sschwarze keysz = strcspn(qs, "=;&"); 21031e689c3Sschwarze key = mandoc_strndup(qs, keysz); 21131e689c3Sschwarze qs += keysz; 21231e689c3Sschwarze if (*qs != '=') 21331e689c3Sschwarze goto next; 214c6c22f12Sschwarze 21531e689c3Sschwarze /* Parse one value. */ 216c6c22f12Sschwarze 21731e689c3Sschwarze valsz = strcspn(++qs, ";&"); 21831e689c3Sschwarze val = mandoc_strndup(qs, valsz); 21931e689c3Sschwarze qs += valsz; 220c6c22f12Sschwarze 22131e689c3Sschwarze /* Decode and catch encoding errors. */ 22231e689c3Sschwarze 22331e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 22431e689c3Sschwarze goto next; 22531e689c3Sschwarze 22631e689c3Sschwarze /* Handle key-value pairs. */ 22731e689c3Sschwarze 22831e689c3Sschwarze if ( ! strcmp(key, "query")) 229e89321abSschwarze set_query_attr(&req->q.query, &val); 23031e689c3Sschwarze 23131e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 23231e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 23331e689c3Sschwarze 23431e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 235aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 23631e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 237aabcc9a1Sschwarze val[7] = '-'; 238aabcc9a1Sschwarze if ('C' == val[8]) 239aabcc9a1Sschwarze val[8] = 'c'; 240aabcc9a1Sschwarze } 241aabcc9a1Sschwarze #endif 24231e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 24331e689c3Sschwarze } 24431e689c3Sschwarze 24531e689c3Sschwarze else if ( ! (strcmp(key, "sec") 246aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24731e689c3Sschwarze && strcmp(key, "sektion") 248aabcc9a1Sschwarze #endif 24931e689c3Sschwarze )) { 25031e689c3Sschwarze if ( ! strcmp(val, "0")) 25131e689c3Sschwarze *val = '\0'; 25231e689c3Sschwarze set_query_attr(&req->q.sec, &val); 253c6c22f12Sschwarze } 25431e689c3Sschwarze 25531e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 25631e689c3Sschwarze if ( ! strcmp(val, "default")) 25731e689c3Sschwarze *val = '\0'; 25831e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2594477fbfaSschwarze } 26031e689c3Sschwarze 26131e689c3Sschwarze /* 26231e689c3Sschwarze * The key must be freed in any case. 26331e689c3Sschwarze * The val may have been handed over to the query 26431e689c3Sschwarze * structure, in which case it is now NULL. 26531e689c3Sschwarze */ 26631e689c3Sschwarze next: 26731e689c3Sschwarze free(key); 26831e689c3Sschwarze key = NULL; 26931e689c3Sschwarze free(val); 27031e689c3Sschwarze val = NULL; 27131e689c3Sschwarze 27231e689c3Sschwarze if (*qs != '\0') 27331e689c3Sschwarze qs++; 27431e689c3Sschwarze } 275c6c22f12Sschwarze } 276c6c22f12Sschwarze 277c6c22f12Sschwarze /* 278c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 279c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 280c6c22f12Sschwarze * over the allocated string. 281c6c22f12Sschwarze */ 282c6c22f12Sschwarze static int 283c6c22f12Sschwarze http_decode(char *p) 284c6c22f12Sschwarze { 285c6c22f12Sschwarze char hex[3]; 2861f69f32bStedu char *q; 287c6c22f12Sschwarze int c; 288c6c22f12Sschwarze 289c6c22f12Sschwarze hex[2] = '\0'; 290c6c22f12Sschwarze 2911f69f32bStedu q = p; 2921f69f32bStedu for ( ; '\0' != *p; p++, q++) { 293c6c22f12Sschwarze if ('%' == *p) { 294c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 295526e306bSschwarze return 0; 296c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 297526e306bSschwarze return 0; 298c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 299526e306bSschwarze return 0; 300c6c22f12Sschwarze if ('\0' == c) 301526e306bSschwarze return 0; 302c6c22f12Sschwarze 3031f69f32bStedu *q = (char)c; 3041f69f32bStedu p += 2; 305c6c22f12Sschwarze } else 3061f69f32bStedu *q = '+' == *p ? ' ' : *p; 307c6c22f12Sschwarze } 308c6c22f12Sschwarze 3091f69f32bStedu *q = '\0'; 310526e306bSschwarze return 1; 311c6c22f12Sschwarze } 312c6c22f12Sschwarze 313c6c22f12Sschwarze static void 314c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 315c6c22f12Sschwarze { 316c6c22f12Sschwarze 317c6c22f12Sschwarze if (200 != code) 318fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 319c6c22f12Sschwarze 320fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 321fa9c540aStedu "Cache-Control: no-cache\r\n" 322fa9c540aStedu "Pragma: no-cache\r\n" 323fa9c540aStedu "\r\n"); 324c6c22f12Sschwarze 325c6c22f12Sschwarze fflush(stdout); 326c6c22f12Sschwarze } 327c6c22f12Sschwarze 328c6c22f12Sschwarze static void 329711661c7Sschwarze resp_copy(const char *filename) 330711661c7Sschwarze { 331711661c7Sschwarze char buf[4096]; 332711661c7Sschwarze ssize_t sz; 333711661c7Sschwarze int fd; 334711661c7Sschwarze 335711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 336711661c7Sschwarze fflush(stdout); 337711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 338711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 339fd3cdd86Sjsg close(fd); 340711661c7Sschwarze } 341711661c7Sschwarze } 342711661c7Sschwarze 343711661c7Sschwarze static void 344c6c22f12Sschwarze resp_begin_html(int code, const char *msg) 345c6c22f12Sschwarze { 346c6c22f12Sschwarze 347c6c22f12Sschwarze resp_begin_http(code, msg); 348c6c22f12Sschwarze 349d649d931Sschwarze printf("<!DOCTYPE html>\n" 350735516bdSschwarze "<html>\n" 351735516bdSschwarze "<head>\n" 352735516bdSschwarze " <meta charset=\"UTF-8\"/>\n" 353735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 354735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 355735516bdSschwarze " <title>%s</title>\n" 356735516bdSschwarze "</head>\n" 357ce781f36Sschwarze "<body>\n", 35806bcd913Sschwarze CSS_DIR, CUSTOMIZE_TITLE); 359711661c7Sschwarze 360711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 361c6c22f12Sschwarze } 362c6c22f12Sschwarze 363c6c22f12Sschwarze static void 364c6c22f12Sschwarze resp_end_html(void) 365c6c22f12Sschwarze { 366c6c22f12Sschwarze 367711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 368711661c7Sschwarze 369735516bdSschwarze puts("</body>\n" 370735516bdSschwarze "</html>"); 371c6c22f12Sschwarze } 372c6c22f12Sschwarze 373c6c22f12Sschwarze static void 37484f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 375c6c22f12Sschwarze { 376c6c22f12Sschwarze int i; 377c6c22f12Sschwarze 378ce781f36Sschwarze printf("<form action=\"/%s\" method=\"get\">\n" 379735516bdSschwarze " <fieldset>\n" 380735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n", 381c6c22f12Sschwarze scriptname); 38228e449d6Sschwarze 38328e449d6Sschwarze /* Write query input box. */ 38428e449d6Sschwarze 385784a63d6Sschwarze printf(" <input type=\"text\" name=\"query\" value=\""); 38684f05c93Sschwarze if (req->q.query != NULL) 387e89321abSschwarze html_print(req->q.query); 38884f05c93Sschwarze printf( "\" size=\"40\""); 38984f05c93Sschwarze if (focus == FOCUS_QUERY) 39084f05c93Sschwarze printf(" autofocus"); 39184f05c93Sschwarze puts(">"); 39228e449d6Sschwarze 393784a63d6Sschwarze /* Write submission buttons. */ 39428e449d6Sschwarze 395784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 396784a63d6Sschwarze "man</button>\n" 397784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">" 398542ee4bfSschwarze "apropos</button>\n" 399542ee4bfSschwarze " <br/>\n"); 40028e449d6Sschwarze 40128e449d6Sschwarze /* Write section selector. */ 40228e449d6Sschwarze 403784a63d6Sschwarze puts(" <select name=\"sec\">"); 40428e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 405735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]); 40628e449d6Sschwarze if (NULL != req->q.sec && 40728e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 408735516bdSschwarze printf(" selected=\"selected\""); 409735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 41028e449d6Sschwarze } 411735516bdSschwarze puts(" </select>"); 41228e449d6Sschwarze 41328e449d6Sschwarze /* Write architecture selector. */ 41428e449d6Sschwarze 415735516bdSschwarze printf( " <select name=\"arch\">\n" 416735516bdSschwarze " <option value=\"default\""); 417be14a32aSschwarze if (NULL == req->q.arch) 418735516bdSschwarze printf(" selected=\"selected\""); 419735516bdSschwarze puts(">All Architectures</option>"); 42028e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 421735516bdSschwarze printf(" <option value=\"%s\"", arch_names[i]); 42228e449d6Sschwarze if (NULL != req->q.arch && 42328e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 424735516bdSschwarze printf(" selected=\"selected\""); 425735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 42628e449d6Sschwarze } 427735516bdSschwarze puts(" </select>"); 42828e449d6Sschwarze 42928e449d6Sschwarze /* Write manpath selector. */ 43028e449d6Sschwarze 431c6c22f12Sschwarze if (req->psz > 1) { 432735516bdSschwarze puts(" <select name=\"manpath\">"); 433c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 434735516bdSschwarze printf(" <option "); 43550eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 436735516bdSschwarze printf("selected=\"selected\" "); 437735516bdSschwarze printf("value=\""); 438c6c22f12Sschwarze html_print(req->p[i]); 439c6c22f12Sschwarze printf("\">"); 440c6c22f12Sschwarze html_print(req->p[i]); 441735516bdSschwarze puts("</option>"); 442c6c22f12Sschwarze } 443735516bdSschwarze puts(" </select>"); 444c6c22f12Sschwarze } 44528e449d6Sschwarze 446784a63d6Sschwarze puts(" </fieldset>\n" 447ce781f36Sschwarze "</form>"); 448c6c22f12Sschwarze } 449c6c22f12Sschwarze 45081475784Sschwarze static int 451cf3a545cSschwarze validate_urifrag(const char *frag) 452cf3a545cSschwarze { 453cf3a545cSschwarze 454cf3a545cSschwarze while ('\0' != *frag) { 455cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 456cf3a545cSschwarze '-' == *frag || '.' == *frag || 457cf3a545cSschwarze '/' == *frag || '_' == *frag)) 458526e306bSschwarze return 0; 459cf3a545cSschwarze frag++; 460cf3a545cSschwarze } 461526e306bSschwarze return 1; 462cf3a545cSschwarze } 463cf3a545cSschwarze 464cf3a545cSschwarze static int 465631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 466631ce2c6Sschwarze { 467631ce2c6Sschwarze size_t i; 468631ce2c6Sschwarze 469631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 470631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 471526e306bSschwarze return 1; 472631ce2c6Sschwarze 473526e306bSschwarze return 0; 474631ce2c6Sschwarze } 475631ce2c6Sschwarze 476631ce2c6Sschwarze static int 47781475784Sschwarze validate_filename(const char *file) 47881475784Sschwarze { 47981475784Sschwarze 48081475784Sschwarze if ('.' == file[0] && '/' == file[1]) 48181475784Sschwarze file += 2; 48281475784Sschwarze 483526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 484526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 48581475784Sschwarze } 48681475784Sschwarze 487c6c22f12Sschwarze static void 488facea411Sschwarze pg_index(const struct req *req) 489c6c22f12Sschwarze { 490c6c22f12Sschwarze 491c6c22f12Sschwarze resp_begin_html(200, NULL); 49284f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 493735516bdSschwarze printf("<p>\n" 494d56ca219Sschwarze "This web interface is documented in the\n" 4950a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 496d56ca219Sschwarze "manual, and the\n" 4970a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n" 4982a43838fSschwarze "manual explains the query syntax.\n" 499735516bdSschwarze "</p>\n", 5003b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5013b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 502c6c22f12Sschwarze resp_end_html(); 503c6c22f12Sschwarze } 504c6c22f12Sschwarze 505c6c22f12Sschwarze static void 506facea411Sschwarze pg_noresult(const struct req *req, const char *msg) 507c6c22f12Sschwarze { 508c6c22f12Sschwarze resp_begin_html(200, NULL); 50984f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 510735516bdSschwarze puts("<p>"); 511c6c22f12Sschwarze puts(msg); 512735516bdSschwarze puts("</p>"); 513c6c22f12Sschwarze resp_end_html(); 514c6c22f12Sschwarze } 515c6c22f12Sschwarze 516c6c22f12Sschwarze static void 517facea411Sschwarze pg_error_badrequest(const char *msg) 518c6c22f12Sschwarze { 519c6c22f12Sschwarze 520c6c22f12Sschwarze resp_begin_html(400, "Bad Request"); 521735516bdSschwarze puts("<h1>Bad Request</h1>\n" 522735516bdSschwarze "<p>\n"); 523c6c22f12Sschwarze puts(msg); 524c6c22f12Sschwarze printf("Try again from the\n" 525735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 526735516bdSschwarze "</p>", scriptname); 527c6c22f12Sschwarze resp_end_html(); 528c6c22f12Sschwarze } 529c6c22f12Sschwarze 530c6c22f12Sschwarze static void 531facea411Sschwarze pg_error_internal(void) 532c6c22f12Sschwarze { 533c6c22f12Sschwarze resp_begin_html(500, "Internal Server Error"); 534735516bdSschwarze puts("<p>Internal Server Error</p>"); 535c6c22f12Sschwarze resp_end_html(); 536c6c22f12Sschwarze } 537c6c22f12Sschwarze 538c6c22f12Sschwarze static void 539facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 540c6c22f12Sschwarze { 541be14a32aSschwarze char *arch, *archend; 542db26164eSschwarze const char *sec; 543db26164eSschwarze size_t i, iuse; 544be14a32aSschwarze int archprio, archpriouse; 54546723f19Sschwarze int prio, priouse; 546c6c22f12Sschwarze 54781475784Sschwarze for (i = 0; i < sz; i++) { 54881475784Sschwarze if (validate_filename(r[i].file)) 54981475784Sschwarze continue; 550c976f0e2Sschwarze warnx("invalid filename %s in %s database", 55181475784Sschwarze r[i].file, req->q.manpath); 55281475784Sschwarze pg_error_internal(); 55381475784Sschwarze return; 55481475784Sschwarze } 55581475784Sschwarze 55652b413c1Sschwarze if (req->isquery && sz == 1) { 557c6c22f12Sschwarze /* 558c6c22f12Sschwarze * If we have just one result, then jump there now 559c6c22f12Sschwarze * without any delay. 560c6c22f12Sschwarze */ 561fa9c540aStedu printf("Status: 303 See Other\r\n"); 5623b9cfc6fSschwarze printf("Location: http://%s/%s%s%s/%s", 5633b9cfc6fSschwarze HTTP_HOST, scriptname, 5643b9cfc6fSschwarze *scriptname == '\0' ? "" : "/", 5653b9cfc6fSschwarze req->q.manpath, r[0].file); 566fa9c540aStedu printf("\r\n" 567fa9c540aStedu "Content-Type: text/html; charset=utf-8\r\n" 568fa9c540aStedu "\r\n"); 569c6c22f12Sschwarze return; 570c6c22f12Sschwarze } 571c6c22f12Sschwarze 572c6c22f12Sschwarze resp_begin_html(200, NULL); 57384f05c93Sschwarze resp_searchform(req, 57484f05c93Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 5754592dbf1Sschwarze 5764592dbf1Sschwarze if (sz > 1) { 5770a282dffSschwarze puts("<table class=\"results\">"); 578c6c22f12Sschwarze for (i = 0; i < sz; i++) { 579735516bdSschwarze printf(" <tr>\n" 5800a282dffSschwarze " <td>" 5810a282dffSschwarze "<a class=\"Xr\" href=\"/%s%s%s/%s\">", 5823b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5833b9cfc6fSschwarze req->q.manpath, r[i].file); 584c6c22f12Sschwarze html_print(r[i].names); 585542ee4bfSschwarze printf("</a></td>\n" 5860a282dffSschwarze " <td><span class=\"Nd\">"); 587c6c22f12Sschwarze html_print(r[i].output); 5880a282dffSschwarze puts("</span></td>\n" 589735516bdSschwarze " </tr>"); 590c6c22f12Sschwarze } 5910a282dffSschwarze puts("</table>"); 5924592dbf1Sschwarze } 59346723f19Sschwarze 59446723f19Sschwarze /* 59546723f19Sschwarze * In man(1) mode, show one of the pages 59646723f19Sschwarze * even if more than one is found. 59746723f19Sschwarze */ 59846723f19Sschwarze 5994592dbf1Sschwarze if (req->q.equal || sz == 1) { 600735516bdSschwarze puts("<hr>"); 60146723f19Sschwarze iuse = 0; 602db26164eSschwarze priouse = 20; 603be14a32aSschwarze archpriouse = 3; 60446723f19Sschwarze for (i = 0; i < sz; i++) { 605db26164eSschwarze sec = r[i].file; 606db26164eSschwarze sec += strcspn(sec, "123456789"); 607db26164eSschwarze if (sec[0] == '\0') 60846723f19Sschwarze continue; 609db26164eSschwarze prio = sec_prios[sec[0] - '1']; 610db26164eSschwarze if (sec[1] != '/') 611db26164eSschwarze prio += 10; 612db26164eSschwarze if (req->q.arch == NULL) { 613be14a32aSschwarze archprio = 614db26164eSschwarze ((arch = strchr(sec + 1, '/')) 615db26164eSschwarze == NULL) ? 3 : 616db26164eSschwarze ((archend = strchr(arch + 1, '/')) 617db26164eSschwarze == NULL) ? 0 : 618be14a32aSschwarze strncmp(arch, "amd64/", 619be14a32aSschwarze archend - arch) ? 2 : 1; 620be14a32aSschwarze if (archprio < archpriouse) { 621be14a32aSschwarze archpriouse = archprio; 622be14a32aSschwarze priouse = prio; 623be14a32aSschwarze iuse = i; 624be14a32aSschwarze continue; 625be14a32aSschwarze } 626be14a32aSschwarze if (archprio > archpriouse) 627be14a32aSschwarze continue; 628be14a32aSschwarze } 62946723f19Sschwarze if (prio >= priouse) 63046723f19Sschwarze continue; 63146723f19Sschwarze priouse = prio; 63246723f19Sschwarze iuse = i; 63346723f19Sschwarze } 63446723f19Sschwarze resp_show(req, r[iuse].file); 63546723f19Sschwarze } 63646723f19Sschwarze 637c6c22f12Sschwarze resp_end_html(); 638c6c22f12Sschwarze } 639c6c22f12Sschwarze 640c6c22f12Sschwarze static void 641941df026Sschwarze resp_catman(const struct req *req, const char *file) 642c6c22f12Sschwarze { 643c6c22f12Sschwarze FILE *f; 644c6c22f12Sschwarze char *p; 64531f93c25Sschwarze size_t sz; 64631f93c25Sschwarze ssize_t len; 64731f93c25Sschwarze int i; 648c6c22f12Sschwarze int italic, bold; 649c6c22f12Sschwarze 65031f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 651735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 652c6c22f12Sschwarze return; 653c6c22f12Sschwarze } 654c6c22f12Sschwarze 655735516bdSschwarze puts("<div class=\"catman\">\n" 656735516bdSschwarze "<pre>"); 657c6c22f12Sschwarze 65831f93c25Sschwarze p = NULL; 65931f93c25Sschwarze sz = 0; 66031f93c25Sschwarze 66131f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 662c6c22f12Sschwarze bold = italic = 0; 66331f93c25Sschwarze for (i = 0; i < len - 1; i++) { 664c6c22f12Sschwarze /* 665c6c22f12Sschwarze * This means that the catpage is out of state. 666c6c22f12Sschwarze * Ignore it and keep going (although the 667c6c22f12Sschwarze * catpage is bogus). 668c6c22f12Sschwarze */ 669c6c22f12Sschwarze 670c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 671c6c22f12Sschwarze continue; 672c6c22f12Sschwarze 673c6c22f12Sschwarze /* 674c6c22f12Sschwarze * Print a regular character. 675c6c22f12Sschwarze * Close out any bold/italic scopes. 676c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 677c6c22f12Sschwarze * have something to enter when we backspace. 678c6c22f12Sschwarze */ 679c6c22f12Sschwarze 680c6c22f12Sschwarze if ('\b' != p[i + 1]) { 681c6c22f12Sschwarze if (italic) 682735516bdSschwarze printf("</i>"); 683c6c22f12Sschwarze if (bold) 684735516bdSschwarze printf("</b>"); 685c6c22f12Sschwarze italic = bold = 0; 686c6c22f12Sschwarze html_putchar(p[i]); 687c6c22f12Sschwarze continue; 68831f93c25Sschwarze } else if (i + 2 >= len) 689c6c22f12Sschwarze continue; 690c6c22f12Sschwarze 691c6c22f12Sschwarze /* Italic mode. */ 692c6c22f12Sschwarze 693c6c22f12Sschwarze if ('_' == p[i]) { 694c6c22f12Sschwarze if (bold) 695735516bdSschwarze printf("</b>"); 696c6c22f12Sschwarze if ( ! italic) 697735516bdSschwarze printf("<i>"); 698c6c22f12Sschwarze bold = 0; 699c6c22f12Sschwarze italic = 1; 700c6c22f12Sschwarze i += 2; 701c6c22f12Sschwarze html_putchar(p[i]); 702c6c22f12Sschwarze continue; 703c6c22f12Sschwarze } 704c6c22f12Sschwarze 705c6c22f12Sschwarze /* 706c6c22f12Sschwarze * Handle funny behaviour troff-isms. 707c6c22f12Sschwarze * These grok'd from the original man2html.c. 708c6c22f12Sschwarze */ 709c6c22f12Sschwarze 710c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 711c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 712c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 713c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 714c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 715c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 716c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 717c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 718c6c22f12Sschwarze if (italic) 719735516bdSschwarze printf("</i>"); 720c6c22f12Sschwarze if (bold) 721735516bdSschwarze printf("</b>"); 722c6c22f12Sschwarze italic = bold = 0; 723c6c22f12Sschwarze putchar('*'); 724c6c22f12Sschwarze i += 2; 725c6c22f12Sschwarze continue; 726c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 727c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 728c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 729c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 730c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 731c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 732c6c22f12Sschwarze if (italic) 733735516bdSschwarze printf("</i>"); 734c6c22f12Sschwarze if (bold) 735735516bdSschwarze printf("</b>"); 736c6c22f12Sschwarze italic = bold = 0; 737c6c22f12Sschwarze putchar('+'); 738c6c22f12Sschwarze i += 2; 739c6c22f12Sschwarze continue; 740c6c22f12Sschwarze } 741c6c22f12Sschwarze 742c6c22f12Sschwarze /* Bold mode. */ 743c6c22f12Sschwarze 744c6c22f12Sschwarze if (italic) 745735516bdSschwarze printf("</i>"); 746c6c22f12Sschwarze if ( ! bold) 747735516bdSschwarze printf("<b>"); 748c6c22f12Sschwarze bold = 1; 749c6c22f12Sschwarze italic = 0; 750c6c22f12Sschwarze i += 2; 751c6c22f12Sschwarze html_putchar(p[i]); 752c6c22f12Sschwarze } 753c6c22f12Sschwarze 754c6c22f12Sschwarze /* 755c6c22f12Sschwarze * Clean up the last character. 756c6c22f12Sschwarze * We can get to a newline; don't print that. 757c6c22f12Sschwarze */ 758c6c22f12Sschwarze 759c6c22f12Sschwarze if (italic) 760735516bdSschwarze printf("</i>"); 761c6c22f12Sschwarze if (bold) 762735516bdSschwarze printf("</b>"); 763c6c22f12Sschwarze 76431f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 765c6c22f12Sschwarze html_putchar(p[i]); 766c6c22f12Sschwarze 767c6c22f12Sschwarze putchar('\n'); 768c6c22f12Sschwarze } 76931f93c25Sschwarze free(p); 770c6c22f12Sschwarze 771735516bdSschwarze puts("</pre>\n" 772735516bdSschwarze "</div>"); 773c6c22f12Sschwarze 774c6c22f12Sschwarze fclose(f); 775c6c22f12Sschwarze } 776c6c22f12Sschwarze 777c6c22f12Sschwarze static void 778941df026Sschwarze resp_format(const struct req *req, const char *file) 779c6c22f12Sschwarze { 7802ccd0917Sschwarze struct manoutput conf; 781c6c22f12Sschwarze struct mparse *mp; 782ede1b9d0Sschwarze struct roff_man *man; 783c6c22f12Sschwarze void *vp; 784f74d674aSschwarze int fd; 785f74d674aSschwarze int usepath; 786c6c22f12Sschwarze 787c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 788735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 789c6c22f12Sschwarze return; 790c6c22f12Sschwarze } 791c6c22f12Sschwarze 79216536faaSschwarze mchars_alloc(); 793f139d5f6Sschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, 794f139d5f6Sschwarze MANDOCLEVEL_BADARG, NULL, req->q.manpath); 795df927bb6Sschwarze mparse_readfd(mp, fd, file); 796c6c22f12Sschwarze close(fd); 797c6c22f12Sschwarze 7982ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 7992ccd0917Sschwarze conf.fragment = 1; 8007c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 801f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 802f211c215Sschwarze mandoc_asprintf(&conf.man, "/%s%s%%N.%%S", 803f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 804c6c22f12Sschwarze 805f2d5c709Sschwarze mparse_result(mp, &man, NULL); 806f2d5c709Sschwarze if (man == NULL) { 807c976f0e2Sschwarze warnx("fatal mandoc error: %s/%s", req->q.manpath, file); 808facea411Sschwarze pg_error_internal(); 809c6c22f12Sschwarze mparse_free(mp); 81016536faaSschwarze mchars_free(); 811c6c22f12Sschwarze return; 812c6c22f12Sschwarze } 813c6c22f12Sschwarze 81416536faaSschwarze vp = html_alloc(&conf); 815c6c22f12Sschwarze 816396853b5Sschwarze if (man->macroset == MACROSET_MDOC) { 817396853b5Sschwarze mdoc_validate(man); 818f2d5c709Sschwarze html_mdoc(vp, man); 819fec2846bSschwarze } else { 820fec2846bSschwarze man_validate(man); 821c6c22f12Sschwarze html_man(vp, man); 822fec2846bSschwarze } 823c6c22f12Sschwarze 824c6c22f12Sschwarze html_free(vp); 825c6c22f12Sschwarze mparse_free(mp); 82616536faaSschwarze mchars_free(); 8272ccd0917Sschwarze free(conf.man); 8287c6e1b3aSschwarze free(conf.style); 829c6c22f12Sschwarze } 830c6c22f12Sschwarze 831c6c22f12Sschwarze static void 83246723f19Sschwarze resp_show(const struct req *req, const char *file) 83346723f19Sschwarze { 83481475784Sschwarze 83581475784Sschwarze if ('.' == file[0] && '/' == file[1]) 8362f7bef27Sschwarze file += 2; 83746723f19Sschwarze 83846723f19Sschwarze if ('c' == *file) 839941df026Sschwarze resp_catman(req, file); 84046723f19Sschwarze else 841941df026Sschwarze resp_format(req, file); 84246723f19Sschwarze } 84346723f19Sschwarze 84446723f19Sschwarze static void 845b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 846c6c22f12Sschwarze { 847b53c14c3Sschwarze char *manpath; 848b53c14c3Sschwarze const char *file; 849c6c22f12Sschwarze 850b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 851facea411Sschwarze pg_error_badrequest( 852c6c22f12Sschwarze "You did not specify a page to show."); 853c6c22f12Sschwarze return; 854c6c22f12Sschwarze } 855b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 856b53c14c3Sschwarze file++; 857c6c22f12Sschwarze 858b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 859631ce2c6Sschwarze pg_error_badrequest( 860631ce2c6Sschwarze "You specified an invalid manpath."); 861b53c14c3Sschwarze free(manpath); 862631ce2c6Sschwarze return; 863631ce2c6Sschwarze } 864631ce2c6Sschwarze 865c6c22f12Sschwarze /* 866c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 867c6c22f12Sschwarze * This way we can pick up the database files, which are 868c6c22f12Sschwarze * relative to the manpath root. 869c6c22f12Sschwarze */ 870c6c22f12Sschwarze 871b53c14c3Sschwarze if (chdir(manpath) == -1) { 872c976f0e2Sschwarze warn("chdir %s", manpath); 873631ce2c6Sschwarze pg_error_internal(); 874b53c14c3Sschwarze free(manpath); 875c6c22f12Sschwarze return; 876c6c22f12Sschwarze } 877b53c14c3Sschwarze free(manpath); 878b53c14c3Sschwarze 879b53c14c3Sschwarze if ( ! validate_filename(file)) { 88081475784Sschwarze pg_error_badrequest( 88181475784Sschwarze "You specified an invalid manual file."); 88281475784Sschwarze return; 88381475784Sschwarze } 88481475784Sschwarze 88546723f19Sschwarze resp_begin_html(200, NULL); 88684f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 887b53c14c3Sschwarze resp_show(req, file); 88846723f19Sschwarze resp_end_html(); 889c6c22f12Sschwarze } 890c6c22f12Sschwarze 891c6c22f12Sschwarze static void 89257482ef4Sschwarze pg_search(const struct req *req) 893c6c22f12Sschwarze { 894c6c22f12Sschwarze struct mansearch search; 895c6c22f12Sschwarze struct manpaths paths; 896c6c22f12Sschwarze struct manpage *res; 897fbeeb774Sschwarze char **argv; 898fbeeb774Sschwarze char *query, *rp, *wp; 899c6c22f12Sschwarze size_t ressz; 900fbeeb774Sschwarze int argc; 901c6c22f12Sschwarze 902c6c22f12Sschwarze /* 903c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 904c6c22f12Sschwarze * This way we can pick up the database files, which are 905c6c22f12Sschwarze * relative to the manpath root. 906c6c22f12Sschwarze */ 907c6c22f12Sschwarze 908c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 909c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 910631ce2c6Sschwarze pg_error_internal(); 911c6c22f12Sschwarze return; 912c6c22f12Sschwarze } 913c6c22f12Sschwarze 914c6c22f12Sschwarze search.arch = req->q.arch; 915c6c22f12Sschwarze search.sec = req->q.sec; 9160f10154cSschwarze search.outkey = "Nd"; 9170f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 918fea71919Sschwarze search.firstmatch = 1; 919c6c22f12Sschwarze 920c6c22f12Sschwarze paths.sz = 1; 921c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 922c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 923c6c22f12Sschwarze 924c6c22f12Sschwarze /* 925fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 926c6c22f12Sschwarze */ 927c6c22f12Sschwarze 928fbeeb774Sschwarze argc = 0; 929fbeeb774Sschwarze argv = NULL; 930fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 931fbeeb774Sschwarze for (;;) { 932fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 933fbeeb774Sschwarze rp++; 934fbeeb774Sschwarze if (*rp == '\0') 935fbeeb774Sschwarze break; 936fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 937fbeeb774Sschwarze argv[argc++] = wp = rp; 938fbeeb774Sschwarze for (;;) { 939fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 940fbeeb774Sschwarze *wp = '\0'; 941fbeeb774Sschwarze rp++; 942fbeeb774Sschwarze break; 943fbeeb774Sschwarze } 944fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 945fbeeb774Sschwarze rp++; 946fbeeb774Sschwarze if (wp != rp) 947fbeeb774Sschwarze *wp = *rp; 948fbeeb774Sschwarze if (*rp == '\0') 949fbeeb774Sschwarze break; 950fbeeb774Sschwarze wp++; 951fbeeb774Sschwarze rp++; 952fbeeb774Sschwarze } 953c6c22f12Sschwarze } 954c6c22f12Sschwarze 955fbeeb774Sschwarze if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz)) 956facea411Sschwarze pg_noresult(req, "You entered an invalid query."); 957c6c22f12Sschwarze else if (0 == ressz) 958facea411Sschwarze pg_noresult(req, "No results found."); 959c6c22f12Sschwarze else 960facea411Sschwarze pg_searchres(req, res, ressz); 961c6c22f12Sschwarze 962fbeeb774Sschwarze free(query); 963fbeeb774Sschwarze mansearch_free(res, ressz); 964c6c22f12Sschwarze free(paths.paths[0]); 965c6c22f12Sschwarze free(paths.paths); 966c6c22f12Sschwarze } 967c6c22f12Sschwarze 968c6c22f12Sschwarze int 969c6c22f12Sschwarze main(void) 970c6c22f12Sschwarze { 971c6c22f12Sschwarze struct req req; 972136c26b8Sschwarze struct itimerval itimer; 97357482ef4Sschwarze const char *path; 97431e689c3Sschwarze const char *querystring; 97557482ef4Sschwarze int i; 976c6c22f12Sschwarze 977136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 978136c26b8Sschwarze 9792935aafcSschwarze itimer.it_value.tv_sec = 2; 980136c26b8Sschwarze itimer.it_value.tv_usec = 0; 9812935aafcSschwarze itimer.it_interval.tv_sec = 2; 982136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 983136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 984c976f0e2Sschwarze warn("setitimer"); 985136c26b8Sschwarze pg_error_internal(); 986526e306bSschwarze return EXIT_FAILURE; 987136c26b8Sschwarze } 988136c26b8Sschwarze 989c6c22f12Sschwarze /* 9906fdade3eSschwarze * First we change directory into the MAN_DIR so that 991c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 992c6c22f12Sschwarze * relative to the same position. 993c6c22f12Sschwarze */ 994c6c22f12Sschwarze 995c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 996c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 997facea411Sschwarze pg_error_internal(); 998526e306bSschwarze return EXIT_FAILURE; 999c6c22f12Sschwarze } 1000c6c22f12Sschwarze 1001c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1002abf19dc9Sschwarze req.q.equal = 1; 1003941df026Sschwarze parse_manpath_conf(&req); 1004c6c22f12Sschwarze 100502b1b494Sschwarze /* Parse the path info and the query string. */ 1006c6c22f12Sschwarze 100702b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 100802b1b494Sschwarze path = ""; 100902b1b494Sschwarze else if (*path == '/') 101002b1b494Sschwarze path++; 101102b1b494Sschwarze 1012aa16a3a8Sschwarze if (*path != '\0') { 1013941df026Sschwarze parse_path_info(&req, path); 101486f4328bSschwarze if (req.q.manpath == NULL || access(path, F_OK) == -1) 101502b1b494Sschwarze path = ""; 101602b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1017941df026Sschwarze parse_query_string(&req, querystring); 1018c6c22f12Sschwarze 101902b1b494Sschwarze /* Validate parsed data and add defaults. */ 102002b1b494Sschwarze 102150eaed2bSschwarze if (req.q.manpath == NULL) 102250eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 102350eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1024631ce2c6Sschwarze pg_error_badrequest( 1025631ce2c6Sschwarze "You specified an invalid manpath."); 1026526e306bSschwarze return EXIT_FAILURE; 1027631ce2c6Sschwarze } 1028631ce2c6Sschwarze 1029cf3a545cSschwarze if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { 1030cf3a545cSschwarze pg_error_badrequest( 1031cf3a545cSschwarze "You specified an invalid architecture."); 1032526e306bSschwarze return EXIT_FAILURE; 1033cf3a545cSschwarze } 1034cf3a545cSschwarze 103557482ef4Sschwarze /* Dispatch to the three different pages. */ 1036c6c22f12Sschwarze 103757482ef4Sschwarze if ('\0' != *path) 103857482ef4Sschwarze pg_show(&req, path); 1039e89321abSschwarze else if (NULL != req.q.query) 104057482ef4Sschwarze pg_search(&req); 104157482ef4Sschwarze else 1042facea411Sschwarze pg_index(&req); 1043c6c22f12Sschwarze 104431e689c3Sschwarze free(req.q.manpath); 104531e689c3Sschwarze free(req.q.arch); 104631e689c3Sschwarze free(req.q.sec); 1047e89321abSschwarze free(req.q.query); 1048c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1049c6c22f12Sschwarze free(req.p[i]); 1050c6c22f12Sschwarze free(req.p); 1051526e306bSschwarze return EXIT_SUCCESS; 1052c6c22f12Sschwarze } 1053c6c22f12Sschwarze 1054c6c22f12Sschwarze /* 105502b1b494Sschwarze * If PATH_INFO is not a file name, translate it to a query. 105602b1b494Sschwarze */ 105702b1b494Sschwarze static void 1058941df026Sschwarze parse_path_info(struct req *req, const char *path) 105902b1b494Sschwarze { 1060daf4c292Sschwarze char *dir[4]; 1061daf4c292Sschwarze int i; 106202b1b494Sschwarze 106352b413c1Sschwarze req->isquery = 0; 106402b1b494Sschwarze req->q.equal = 1; 106502b1b494Sschwarze req->q.manpath = mandoc_strdup(path); 1066daf4c292Sschwarze req->q.arch = NULL; 106702b1b494Sschwarze 106802b1b494Sschwarze /* Mandatory manual page name. */ 106902b1b494Sschwarze if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) { 107002b1b494Sschwarze req->q.query = req->q.manpath; 107102b1b494Sschwarze req->q.manpath = NULL; 107202b1b494Sschwarze } else 107302b1b494Sschwarze *req->q.query++ = '\0'; 107402b1b494Sschwarze 107502b1b494Sschwarze /* Optional trailing section. */ 107602b1b494Sschwarze if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) { 107702b1b494Sschwarze if(isdigit((unsigned char)req->q.sec[1])) { 107802b1b494Sschwarze *req->q.sec++ = '\0'; 107902b1b494Sschwarze req->q.sec = mandoc_strdup(req->q.sec); 108002b1b494Sschwarze } else 108102b1b494Sschwarze req->q.sec = NULL; 108202b1b494Sschwarze } 108302b1b494Sschwarze 108402b1b494Sschwarze /* Handle the case of name[.section] only. */ 1085daf4c292Sschwarze if (req->q.manpath == NULL) 108602b1b494Sschwarze return; 108702b1b494Sschwarze req->q.query = mandoc_strdup(req->q.query); 108802b1b494Sschwarze 1089daf4c292Sschwarze /* Split directory components. */ 1090daf4c292Sschwarze dir[i = 0] = req->q.manpath; 1091daf4c292Sschwarze while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) { 1092daf4c292Sschwarze if (++i == 3) { 1093daf4c292Sschwarze pg_error_badrequest( 1094daf4c292Sschwarze "You specified too many directory components."); 1095daf4c292Sschwarze exit(EXIT_FAILURE); 109602b1b494Sschwarze } 1097daf4c292Sschwarze *dir[i]++ = '\0'; 1098daf4c292Sschwarze } 1099daf4c292Sschwarze 1100daf4c292Sschwarze /* Optional manpath. */ 1101daf4c292Sschwarze if ((i = validate_manpath(req, req->q.manpath)) == 0) 1102daf4c292Sschwarze req->q.manpath = NULL; 1103daf4c292Sschwarze else if (dir[1] == NULL) 1104daf4c292Sschwarze return; 1105daf4c292Sschwarze 1106daf4c292Sschwarze /* Optional section. */ 1107daf4c292Sschwarze if (strncmp(dir[i], "man", 3) == 0) { 1108daf4c292Sschwarze free(req->q.sec); 1109daf4c292Sschwarze req->q.sec = mandoc_strdup(dir[i++] + 3); 1110daf4c292Sschwarze } 1111daf4c292Sschwarze if (dir[i] == NULL) { 1112daf4c292Sschwarze if (req->q.manpath == NULL) 1113daf4c292Sschwarze free(dir[0]); 1114daf4c292Sschwarze return; 1115daf4c292Sschwarze } 1116daf4c292Sschwarze if (dir[i + 1] != NULL) { 1117daf4c292Sschwarze pg_error_badrequest( 1118daf4c292Sschwarze "You specified an invalid directory component."); 1119daf4c292Sschwarze exit(EXIT_FAILURE); 1120daf4c292Sschwarze } 1121daf4c292Sschwarze 1122daf4c292Sschwarze /* Optional architecture. */ 1123daf4c292Sschwarze if (i) { 1124daf4c292Sschwarze req->q.arch = mandoc_strdup(dir[i]); 1125daf4c292Sschwarze if (req->q.manpath == NULL) 1126daf4c292Sschwarze free(dir[0]); 1127daf4c292Sschwarze } else 1128daf4c292Sschwarze req->q.arch = dir[0]; 112902b1b494Sschwarze } 113002b1b494Sschwarze 113102b1b494Sschwarze /* 1132c6c22f12Sschwarze * Scan for indexable paths. 1133c6c22f12Sschwarze */ 1134c6c22f12Sschwarze static void 1135941df026Sschwarze parse_manpath_conf(struct req *req) 1136c6c22f12Sschwarze { 1137c6c22f12Sschwarze FILE *fp; 1138c6c22f12Sschwarze char *dp; 1139c6c22f12Sschwarze size_t dpsz; 114031f93c25Sschwarze ssize_t len; 1141c6c22f12Sschwarze 1142c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1143c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1144de651747Sschwarze pg_error_internal(); 1145de651747Sschwarze exit(EXIT_FAILURE); 1146de651747Sschwarze } 1147c6c22f12Sschwarze 114831f93c25Sschwarze dp = NULL; 114931f93c25Sschwarze dpsz = 0; 115031f93c25Sschwarze 115131f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 115231f93c25Sschwarze if (dp[len - 1] == '\n') 115331f93c25Sschwarze dp[--len] = '\0'; 1154c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1155c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1156cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1157c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1158c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1159cf3a545cSschwarze pg_error_internal(); 1160cf3a545cSschwarze exit(EXIT_FAILURE); 1161cf3a545cSschwarze } 1162c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1163c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1164c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1165cf3a545cSschwarze pg_error_internal(); 1166cf3a545cSschwarze exit(EXIT_FAILURE); 1167cf3a545cSschwarze } 1168cf3a545cSschwarze req->p[req->psz++] = dp; 116931f93c25Sschwarze dp = NULL; 117031f93c25Sschwarze dpsz = 0; 1171c6c22f12Sschwarze } 117231f93c25Sschwarze free(dp); 1173de651747Sschwarze 1174de651747Sschwarze if (req->p == NULL) { 1175c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1176de651747Sschwarze pg_error_internal(); 1177de651747Sschwarze exit(EXIT_FAILURE); 1178de651747Sschwarze } 1179c6c22f12Sschwarze } 1180