1*6a6803e4Sschwarze /* $OpenBSD: cgi.c,v 1.110 2020/04/03 11:34:19 schwarze Exp $ */ 2c6c22f12Sschwarze /* 318ccf011Sschwarze * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@usta.de> 4*6a6803e4Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 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. 17*6a6803e4Sschwarze * 18*6a6803e4Sschwarze * Implementation of the man.cgi(8) program. 19c6c22f12Sschwarze */ 20136c26b8Sschwarze #include <sys/types.h> 21136c26b8Sschwarze #include <sys/time.h> 22136c26b8Sschwarze 23c6c22f12Sschwarze #include <ctype.h> 24c976f0e2Sschwarze #include <err.h> 25c6c22f12Sschwarze #include <errno.h> 26c6c22f12Sschwarze #include <fcntl.h> 27c6c22f12Sschwarze #include <limits.h> 28c2235d37Sschwarze #include <stdint.h> 29c6c22f12Sschwarze #include <stdio.h> 30c6c22f12Sschwarze #include <stdlib.h> 31c6c22f12Sschwarze #include <string.h> 32c6c22f12Sschwarze #include <unistd.h> 33c6c22f12Sschwarze 34c6c22f12Sschwarze #include "mandoc_aux.h" 35f2d5c709Sschwarze #include "mandoc.h" 36f2d5c709Sschwarze #include "roff.h" 37396853b5Sschwarze #include "mdoc.h" 38fec2846bSschwarze #include "man.h" 3999acaf1eSschwarze #include "mandoc_parse.h" 40c6c22f12Sschwarze #include "main.h" 414de77decSschwarze #include "manconf.h" 42c6c22f12Sschwarze #include "mansearch.h" 436fdade3eSschwarze #include "cgi.h" 44c6c22f12Sschwarze 45c6c22f12Sschwarze /* 46c6c22f12Sschwarze * A query as passed to the search function. 47c6c22f12Sschwarze */ 48c6c22f12Sschwarze struct query { 4931e689c3Sschwarze char *manpath; /* desired manual directory */ 5031e689c3Sschwarze char *arch; /* architecture */ 5131e689c3Sschwarze char *sec; /* manual section */ 52e89321abSschwarze char *query; /* unparsed query expression */ 534477fbfaSschwarze int equal; /* match whole names, not substrings */ 54c6c22f12Sschwarze }; 55c6c22f12Sschwarze 56c6c22f12Sschwarze struct req { 57c6c22f12Sschwarze struct query q; 58c6c22f12Sschwarze char **p; /* array of available manpaths */ 59c6c22f12Sschwarze size_t psz; /* number of available manpaths */ 6052b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */ 61c6c22f12Sschwarze }; 62c6c22f12Sschwarze 6384f05c93Sschwarze enum focus { 6484f05c93Sschwarze FOCUS_NONE = 0, 6584f05c93Sschwarze FOCUS_QUERY 6684f05c93Sschwarze }; 6784f05c93Sschwarze 68c6c22f12Sschwarze static void html_print(const char *); 69c6c22f12Sschwarze static void html_putchar(char); 70c6c22f12Sschwarze static int http_decode(char *); 71*6a6803e4Sschwarze static void http_encode(const char *); 72941df026Sschwarze static void parse_manpath_conf(struct req *); 73*6a6803e4Sschwarze static void parse_path_info(struct req *, const char *); 74941df026Sschwarze static void parse_query_string(struct req *, const char *); 75facea411Sschwarze static void pg_error_badrequest(const char *); 76facea411Sschwarze static void pg_error_internal(void); 77facea411Sschwarze static void pg_index(const struct req *); 7818ccf011Sschwarze static void pg_noresult(const struct req *, int, const char *, 7918ccf011Sschwarze const char *); 80e1beff2aSschwarze static void pg_redirect(const struct req *, const char *); 8157482ef4Sschwarze static void pg_search(const struct req *); 82facea411Sschwarze static void pg_searchres(const struct req *, 83facea411Sschwarze struct manpage *, size_t); 8481060b1aSschwarze static void pg_show(struct req *, const char *); 85fdef72b0Sschwarze static void resp_begin_html(int, const char *, const char *); 86c6c22f12Sschwarze static void resp_begin_http(int, const char *); 87941df026Sschwarze static void resp_catman(const struct req *, const char *); 88711661c7Sschwarze static void resp_copy(const char *); 89c6c22f12Sschwarze static void resp_end_html(void); 90941df026Sschwarze static void resp_format(const struct req *, const char *); 9184f05c93Sschwarze static void resp_searchform(const struct req *, enum focus); 9246723f19Sschwarze static void resp_show(const struct req *, const char *); 93e89321abSschwarze static void set_query_attr(char **, char **); 94f7a12365Sschwarze static int validate_arch(const char *); 95e89321abSschwarze static int validate_filename(const char *); 96e89321abSschwarze static int validate_manpath(const struct req *, const char *); 97e89321abSschwarze static int validate_urifrag(const char *); 98c6c22f12Sschwarze 993b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME; 100c6c22f12Sschwarze 10146723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 10228e449d6Sschwarze static const char *const sec_numbers[] = { 10328e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 10428e449d6Sschwarze }; 10528e449d6Sschwarze static const char *const sec_names[] = { 10628e449d6Sschwarze "All Sections", 10728e449d6Sschwarze "1 - General Commands", 10828e449d6Sschwarze "2 - System Calls", 1094e6618fdSschwarze "3 - Library Functions", 1104e6618fdSschwarze "3p - Perl Library", 1114e6618fdSschwarze "4 - Device Drivers", 11228e449d6Sschwarze "5 - File Formats", 11328e449d6Sschwarze "6 - Games", 1144e6618fdSschwarze "7 - Miscellaneous Information", 1154e6618fdSschwarze "8 - System Manager\'s Manual", 1164e6618fdSschwarze "9 - Kernel Developer\'s Manual" 11728e449d6Sschwarze }; 11828e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 11928e449d6Sschwarze 12028e449d6Sschwarze static const char *const arch_names[] = { 12193d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64", 122766ef059Sschwarze "hppa", "i386", "landisk", 1233bcd4815Sschwarze "loongson", "luna88k", "macppc", "mips64", 1246250a715Sschwarze "octeon", "sgi", "socppc", "sparc64", 125766ef059Sschwarze "amiga", "arc", "armish", "arm32", 126766ef059Sschwarze "atari", "aviion", "beagle", "cats", 127766ef059Sschwarze "hppa64", "hp300", 1283bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k", 1293bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos", 1300c245db5Sschwarze "pmax", "powerpc", "solbourne", "sparc", 1316250a715Sschwarze "sun3", "vax", "wgrisc", "x68k", 1326250a715Sschwarze "zaurus" 13328e449d6Sschwarze }; 13428e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 13528e449d6Sschwarze 136c6c22f12Sschwarze /* 137c6c22f12Sschwarze * Print a character, escaping HTML along the way. 138c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned! 139c6c22f12Sschwarze */ 140c6c22f12Sschwarze static void 141c6c22f12Sschwarze html_putchar(char c) 142c6c22f12Sschwarze { 143c6c22f12Sschwarze 144c6c22f12Sschwarze switch (c) { 145e74fa2aeSschwarze case '"': 146f765a656Sbentley printf("""); 147c6c22f12Sschwarze break; 148e74fa2aeSschwarze case '&': 149c6c22f12Sschwarze printf("&"); 150c6c22f12Sschwarze break; 151e74fa2aeSschwarze case '>': 152c6c22f12Sschwarze printf(">"); 153c6c22f12Sschwarze break; 154e74fa2aeSschwarze case '<': 155c6c22f12Sschwarze printf("<"); 156c6c22f12Sschwarze break; 157c6c22f12Sschwarze default: 158c6c22f12Sschwarze putchar((unsigned char)c); 159c6c22f12Sschwarze break; 160c6c22f12Sschwarze } 161c6c22f12Sschwarze } 162c6c22f12Sschwarze 163c6c22f12Sschwarze /* 164c6c22f12Sschwarze * Call through to html_putchar(). 165c6c22f12Sschwarze * Accepts NULL strings. 166c6c22f12Sschwarze */ 167c6c22f12Sschwarze static void 168c6c22f12Sschwarze html_print(const char *p) 169c6c22f12Sschwarze { 170c6c22f12Sschwarze 171c6c22f12Sschwarze if (NULL == p) 172c6c22f12Sschwarze return; 173c6c22f12Sschwarze while ('\0' != *p) 174c6c22f12Sschwarze html_putchar(*p++); 175c6c22f12Sschwarze } 176c6c22f12Sschwarze 177c6c22f12Sschwarze /* 17831e689c3Sschwarze * Transfer the responsibility for the allocated string *val 17931e689c3Sschwarze * to the query structure. 180c6c22f12Sschwarze */ 181c6c22f12Sschwarze static void 18231e689c3Sschwarze set_query_attr(char **attr, char **val) 18331e689c3Sschwarze { 18431e689c3Sschwarze 18531e689c3Sschwarze free(*attr); 18631e689c3Sschwarze if (**val == '\0') { 18731e689c3Sschwarze *attr = NULL; 18831e689c3Sschwarze free(*val); 18931e689c3Sschwarze } else 19031e689c3Sschwarze *attr = *val; 19131e689c3Sschwarze *val = NULL; 19231e689c3Sschwarze } 19331e689c3Sschwarze 19431e689c3Sschwarze /* 19531e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs 19631e689c3Sschwarze * and store the values into the query structure. 19731e689c3Sschwarze */ 19831e689c3Sschwarze static void 199941df026Sschwarze parse_query_string(struct req *req, const char *qs) 200c6c22f12Sschwarze { 201c6c22f12Sschwarze char *key, *val; 20231e689c3Sschwarze size_t keysz, valsz; 203c6c22f12Sschwarze 20452b413c1Sschwarze req->isquery = 1; 20531e689c3Sschwarze req->q.manpath = NULL; 20631e689c3Sschwarze req->q.arch = NULL; 20731e689c3Sschwarze req->q.sec = NULL; 208e89321abSschwarze req->q.query = NULL; 2094477fbfaSschwarze req->q.equal = 1; 210c6c22f12Sschwarze 21131e689c3Sschwarze key = val = NULL; 21231e689c3Sschwarze while (*qs != '\0') { 213c6c22f12Sschwarze 21431e689c3Sschwarze /* Parse one key. */ 215c6c22f12Sschwarze 21631e689c3Sschwarze keysz = strcspn(qs, "=;&"); 21731e689c3Sschwarze key = mandoc_strndup(qs, keysz); 21831e689c3Sschwarze qs += keysz; 21931e689c3Sschwarze if (*qs != '=') 22031e689c3Sschwarze goto next; 221c6c22f12Sschwarze 22231e689c3Sschwarze /* Parse one value. */ 223c6c22f12Sschwarze 22431e689c3Sschwarze valsz = strcspn(++qs, ";&"); 22531e689c3Sschwarze val = mandoc_strndup(qs, valsz); 22631e689c3Sschwarze qs += valsz; 227c6c22f12Sschwarze 22831e689c3Sschwarze /* Decode and catch encoding errors. */ 22931e689c3Sschwarze 23031e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val))) 23131e689c3Sschwarze goto next; 23231e689c3Sschwarze 23331e689c3Sschwarze /* Handle key-value pairs. */ 23431e689c3Sschwarze 23531e689c3Sschwarze if ( ! strcmp(key, "query")) 236e89321abSschwarze set_query_attr(&req->q.query, &val); 23731e689c3Sschwarze 23831e689c3Sschwarze else if ( ! strcmp(key, "apropos")) 23931e689c3Sschwarze req->q.equal = !strcmp(val, "0"); 24031e689c3Sschwarze 24131e689c3Sschwarze else if ( ! strcmp(key, "manpath")) { 242aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 24331e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) { 244aabcc9a1Sschwarze val[7] = '-'; 245aabcc9a1Sschwarze if ('C' == val[8]) 246aabcc9a1Sschwarze val[8] = 'c'; 247aabcc9a1Sschwarze } 248aabcc9a1Sschwarze #endif 24931e689c3Sschwarze set_query_attr(&req->q.manpath, &val); 25031e689c3Sschwarze } 25131e689c3Sschwarze 25231e689c3Sschwarze else if ( ! (strcmp(key, "sec") 253aabcc9a1Sschwarze #ifdef COMPAT_OLDURI 25431e689c3Sschwarze && strcmp(key, "sektion") 255aabcc9a1Sschwarze #endif 25631e689c3Sschwarze )) { 25731e689c3Sschwarze if ( ! strcmp(val, "0")) 25831e689c3Sschwarze *val = '\0'; 25931e689c3Sschwarze set_query_attr(&req->q.sec, &val); 260c6c22f12Sschwarze } 26131e689c3Sschwarze 26231e689c3Sschwarze else if ( ! strcmp(key, "arch")) { 26331e689c3Sschwarze if ( ! strcmp(val, "default")) 26431e689c3Sschwarze *val = '\0'; 26531e689c3Sschwarze set_query_attr(&req->q.arch, &val); 2664477fbfaSschwarze } 26731e689c3Sschwarze 26831e689c3Sschwarze /* 26931e689c3Sschwarze * The key must be freed in any case. 27031e689c3Sschwarze * The val may have been handed over to the query 27131e689c3Sschwarze * structure, in which case it is now NULL. 27231e689c3Sschwarze */ 27331e689c3Sschwarze next: 27431e689c3Sschwarze free(key); 27531e689c3Sschwarze key = NULL; 27631e689c3Sschwarze free(val); 27731e689c3Sschwarze val = NULL; 27831e689c3Sschwarze 27931e689c3Sschwarze if (*qs != '\0') 28031e689c3Sschwarze qs++; 28131e689c3Sschwarze } 282c6c22f12Sschwarze } 283c6c22f12Sschwarze 284c6c22f12Sschwarze /* 285c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns 286c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place 287c6c22f12Sschwarze * over the allocated string. 288c6c22f12Sschwarze */ 289c6c22f12Sschwarze static int 290c6c22f12Sschwarze http_decode(char *p) 291c6c22f12Sschwarze { 292c6c22f12Sschwarze char hex[3]; 2931f69f32bStedu char *q; 294c6c22f12Sschwarze int c; 295c6c22f12Sschwarze 296c6c22f12Sschwarze hex[2] = '\0'; 297c6c22f12Sschwarze 2981f69f32bStedu q = p; 2991f69f32bStedu for ( ; '\0' != *p; p++, q++) { 300c6c22f12Sschwarze if ('%' == *p) { 301c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1))) 302526e306bSschwarze return 0; 303c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2))) 304526e306bSschwarze return 0; 305c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c)) 306526e306bSschwarze return 0; 307c6c22f12Sschwarze if ('\0' == c) 308526e306bSschwarze return 0; 309c6c22f12Sschwarze 3101f69f32bStedu *q = (char)c; 3111f69f32bStedu p += 2; 312c6c22f12Sschwarze } else 3131f69f32bStedu *q = '+' == *p ? ' ' : *p; 314c6c22f12Sschwarze } 315c6c22f12Sschwarze 3161f69f32bStedu *q = '\0'; 317526e306bSschwarze return 1; 318c6c22f12Sschwarze } 319c6c22f12Sschwarze 320c6c22f12Sschwarze static void 321f7a12365Sschwarze http_encode(const char *p) 322f7a12365Sschwarze { 323f7a12365Sschwarze for (; *p != '\0'; p++) { 324f7a12365Sschwarze if (isalnum((unsigned char)*p) == 0 && 325f7a12365Sschwarze strchr("-._~", *p) == NULL) 3261ecf8c4fSschwarze printf("%%%2.2X", (unsigned char)*p); 327f7a12365Sschwarze else 328f7a12365Sschwarze putchar(*p); 329f7a12365Sschwarze } 330f7a12365Sschwarze } 331f7a12365Sschwarze 332f7a12365Sschwarze static void 333c6c22f12Sschwarze resp_begin_http(int code, const char *msg) 334c6c22f12Sschwarze { 335c6c22f12Sschwarze 336c6c22f12Sschwarze if (200 != code) 337fa9c540aStedu printf("Status: %d %s\r\n", code, msg); 338c6c22f12Sschwarze 339fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n" 340fa9c540aStedu "Cache-Control: no-cache\r\n" 34153459fdfSbentley "Content-Security-Policy: default-src 'none'; " 34253459fdfSbentley "style-src 'self' 'unsafe-inline'\r\n" 343fa9c540aStedu "Pragma: no-cache\r\n" 344fa9c540aStedu "\r\n"); 345c6c22f12Sschwarze 346c6c22f12Sschwarze fflush(stdout); 347c6c22f12Sschwarze } 348c6c22f12Sschwarze 349c6c22f12Sschwarze static void 350711661c7Sschwarze resp_copy(const char *filename) 351711661c7Sschwarze { 352711661c7Sschwarze char buf[4096]; 353711661c7Sschwarze ssize_t sz; 354711661c7Sschwarze int fd; 355711661c7Sschwarze 356711661c7Sschwarze if ((fd = open(filename, O_RDONLY)) != -1) { 357711661c7Sschwarze fflush(stdout); 358711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0) 359711661c7Sschwarze write(STDOUT_FILENO, buf, sz); 360fd3cdd86Sjsg close(fd); 361711661c7Sschwarze } 362711661c7Sschwarze } 363711661c7Sschwarze 364711661c7Sschwarze static void 365fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file) 366c6c22f12Sschwarze { 367fdef72b0Sschwarze char *cp; 368c6c22f12Sschwarze 369c6c22f12Sschwarze resp_begin_http(code, msg); 370c6c22f12Sschwarze 371d649d931Sschwarze printf("<!DOCTYPE html>\n" 372735516bdSschwarze "<html>\n" 373735516bdSschwarze "<head>\n" 374735516bdSschwarze " <meta charset=\"UTF-8\"/>\n" 375661a42d8Sschwarze " <meta name=\"viewport\"" 376661a42d8Sschwarze " content=\"width=device-width, initial-scale=1.0\">\n" 377735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\"" 378735516bdSschwarze " type=\"text/css\" media=\"all\">\n" 379fdef72b0Sschwarze " <title>", 380fdef72b0Sschwarze CSS_DIR); 381fdef72b0Sschwarze if (file != NULL) { 382fdef72b0Sschwarze if ((cp = strrchr(file, '/')) != NULL) 383fdef72b0Sschwarze file = cp + 1; 384fdef72b0Sschwarze if ((cp = strrchr(file, '.')) != NULL) { 385fdef72b0Sschwarze printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1); 386fdef72b0Sschwarze } else 387fdef72b0Sschwarze printf("%s - ", file); 388fdef72b0Sschwarze } 389fdef72b0Sschwarze printf("%s</title>\n" 390735516bdSschwarze "</head>\n" 391ce781f36Sschwarze "<body>\n", 392fdef72b0Sschwarze CUSTOMIZE_TITLE); 393711661c7Sschwarze 394711661c7Sschwarze resp_copy(MAN_DIR "/header.html"); 395c6c22f12Sschwarze } 396c6c22f12Sschwarze 397c6c22f12Sschwarze static void 398c6c22f12Sschwarze resp_end_html(void) 399c6c22f12Sschwarze { 400c6c22f12Sschwarze 401711661c7Sschwarze resp_copy(MAN_DIR "/footer.html"); 402711661c7Sschwarze 403735516bdSschwarze puts("</body>\n" 404735516bdSschwarze "</html>"); 405c6c22f12Sschwarze } 406c6c22f12Sschwarze 407c6c22f12Sschwarze static void 40884f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus) 409c6c22f12Sschwarze { 410c6c22f12Sschwarze int i; 411c6c22f12Sschwarze 4127411869fSschwarze printf("<form action=\"/%s\" method=\"get\" " 4137411869fSschwarze "autocomplete=\"off\" autocapitalize=\"none\">\n" 414735516bdSschwarze " <fieldset>\n" 415735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n", 416c6c22f12Sschwarze scriptname); 41728e449d6Sschwarze 41828e449d6Sschwarze /* Write query input box. */ 41928e449d6Sschwarze 4206b824e3bSschwarze printf(" <input type=\"search\" name=\"query\" value=\""); 42184f05c93Sschwarze if (req->q.query != NULL) 422e89321abSschwarze html_print(req->q.query); 42384f05c93Sschwarze printf( "\" size=\"40\""); 42484f05c93Sschwarze if (focus == FOCUS_QUERY) 42584f05c93Sschwarze printf(" autofocus"); 42684f05c93Sschwarze puts(">"); 42728e449d6Sschwarze 428784a63d6Sschwarze /* Write submission buttons. */ 42928e449d6Sschwarze 430784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">" 431784a63d6Sschwarze "man</button>\n" 432784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">" 433542ee4bfSschwarze "apropos</button>\n" 434542ee4bfSschwarze " <br/>\n"); 43528e449d6Sschwarze 43628e449d6Sschwarze /* Write section selector. */ 43728e449d6Sschwarze 438784a63d6Sschwarze puts(" <select name=\"sec\">"); 43928e449d6Sschwarze for (i = 0; i < sec_MAX; i++) { 440735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]); 44128e449d6Sschwarze if (NULL != req->q.sec && 44228e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec)) 443735516bdSschwarze printf(" selected=\"selected\""); 444735516bdSschwarze printf(">%s</option>\n", sec_names[i]); 44528e449d6Sschwarze } 446735516bdSschwarze puts(" </select>"); 44728e449d6Sschwarze 44828e449d6Sschwarze /* Write architecture selector. */ 44928e449d6Sschwarze 450735516bdSschwarze printf( " <select name=\"arch\">\n" 451735516bdSschwarze " <option value=\"default\""); 452be14a32aSschwarze if (NULL == req->q.arch) 453735516bdSschwarze printf(" selected=\"selected\""); 454735516bdSschwarze puts(">All Architectures</option>"); 45528e449d6Sschwarze for (i = 0; i < arch_MAX; i++) { 4566b824e3bSschwarze printf(" <option"); 45728e449d6Sschwarze if (NULL != req->q.arch && 45828e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch)) 459735516bdSschwarze printf(" selected=\"selected\""); 460735516bdSschwarze printf(">%s</option>\n", arch_names[i]); 46128e449d6Sschwarze } 462735516bdSschwarze puts(" </select>"); 46328e449d6Sschwarze 46428e449d6Sschwarze /* Write manpath selector. */ 46528e449d6Sschwarze 466c6c22f12Sschwarze if (req->psz > 1) { 467735516bdSschwarze puts(" <select name=\"manpath\">"); 468c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) { 469735516bdSschwarze printf(" <option"); 47050eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0) 471735516bdSschwarze printf(" selected=\"selected\""); 4726b824e3bSschwarze printf(">"); 473c6c22f12Sschwarze html_print(req->p[i]); 474735516bdSschwarze puts("</option>"); 475c6c22f12Sschwarze } 476735516bdSschwarze puts(" </select>"); 477c6c22f12Sschwarze } 47828e449d6Sschwarze 479784a63d6Sschwarze puts(" </fieldset>\n" 480ce781f36Sschwarze "</form>"); 481c6c22f12Sschwarze } 482c6c22f12Sschwarze 48381475784Sschwarze static int 484cf3a545cSschwarze validate_urifrag(const char *frag) 485cf3a545cSschwarze { 486cf3a545cSschwarze 487cf3a545cSschwarze while ('\0' != *frag) { 488cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) || 489cf3a545cSschwarze '-' == *frag || '.' == *frag || 490cf3a545cSschwarze '/' == *frag || '_' == *frag)) 491526e306bSschwarze return 0; 492cf3a545cSschwarze frag++; 493cf3a545cSschwarze } 494526e306bSschwarze return 1; 495cf3a545cSschwarze } 496cf3a545cSschwarze 497cf3a545cSschwarze static int 498631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath) 499631ce2c6Sschwarze { 500631ce2c6Sschwarze size_t i; 501631ce2c6Sschwarze 502631ce2c6Sschwarze for (i = 0; i < req->psz; i++) 503631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i])) 504526e306bSschwarze return 1; 505631ce2c6Sschwarze 506526e306bSschwarze return 0; 507631ce2c6Sschwarze } 508631ce2c6Sschwarze 509631ce2c6Sschwarze static int 510f7a12365Sschwarze validate_arch(const char *arch) 511f7a12365Sschwarze { 512f7a12365Sschwarze int i; 513f7a12365Sschwarze 514f7a12365Sschwarze for (i = 0; i < arch_MAX; i++) 515f7a12365Sschwarze if (strcmp(arch, arch_names[i]) == 0) 516f7a12365Sschwarze return 1; 517f7a12365Sschwarze 518f7a12365Sschwarze return 0; 519f7a12365Sschwarze } 520f7a12365Sschwarze 521f7a12365Sschwarze static int 52281475784Sschwarze validate_filename(const char *file) 52381475784Sschwarze { 52481475784Sschwarze 52581475784Sschwarze if ('.' == file[0] && '/' == file[1]) 52681475784Sschwarze file += 2; 52781475784Sschwarze 528526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") || 529526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3))); 53081475784Sschwarze } 53181475784Sschwarze 532c6c22f12Sschwarze static void 533facea411Sschwarze pg_index(const struct req *req) 534c6c22f12Sschwarze { 535c6c22f12Sschwarze 536fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 53784f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 538735516bdSschwarze printf("<p>\n" 539d56ca219Sschwarze "This web interface is documented in the\n" 5400a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n" 541d56ca219Sschwarze "manual, and the\n" 5420a282dffSschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n" 5432a43838fSschwarze "manual explains the query syntax.\n" 544735516bdSschwarze "</p>\n", 5453b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/", 5463b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/"); 547c6c22f12Sschwarze resp_end_html(); 548c6c22f12Sschwarze } 549c6c22f12Sschwarze 550c6c22f12Sschwarze static void 55118ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg, 55218ccf011Sschwarze const char *user_msg) 553c6c22f12Sschwarze { 55418ccf011Sschwarze resp_begin_html(code, http_msg, NULL); 55584f05c93Sschwarze resp_searchform(req, FOCUS_QUERY); 556735516bdSschwarze puts("<p>"); 55718ccf011Sschwarze puts(user_msg); 558735516bdSschwarze puts("</p>"); 559c6c22f12Sschwarze resp_end_html(); 560c6c22f12Sschwarze } 561c6c22f12Sschwarze 562c6c22f12Sschwarze static void 563facea411Sschwarze pg_error_badrequest(const char *msg) 564c6c22f12Sschwarze { 565c6c22f12Sschwarze 566fdef72b0Sschwarze resp_begin_html(400, "Bad Request", NULL); 567735516bdSschwarze puts("<h1>Bad Request</h1>\n" 568735516bdSschwarze "<p>\n"); 569c6c22f12Sschwarze puts(msg); 570c6c22f12Sschwarze printf("Try again from the\n" 571735516bdSschwarze "<a href=\"/%s\">main page</a>.\n" 572735516bdSschwarze "</p>", scriptname); 573c6c22f12Sschwarze resp_end_html(); 574c6c22f12Sschwarze } 575c6c22f12Sschwarze 576c6c22f12Sschwarze static void 577facea411Sschwarze pg_error_internal(void) 578c6c22f12Sschwarze { 579fdef72b0Sschwarze resp_begin_html(500, "Internal Server Error", NULL); 580735516bdSschwarze puts("<p>Internal Server Error</p>"); 581c6c22f12Sschwarze resp_end_html(); 582c6c22f12Sschwarze } 583c6c22f12Sschwarze 584c6c22f12Sschwarze static void 585e1beff2aSschwarze pg_redirect(const struct req *req, const char *name) 586e1beff2aSschwarze { 587e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 588e0d9a108Sschwarze "Location: /"); 589e1beff2aSschwarze if (*scriptname != '\0') 590e1beff2aSschwarze printf("%s/", scriptname); 591e1beff2aSschwarze if (strcmp(req->q.manpath, req->p[0])) 592e1beff2aSschwarze printf("%s/", req->q.manpath); 593e1beff2aSschwarze if (req->q.arch != NULL) 594e1beff2aSschwarze printf("%s/", req->q.arch); 595f7a12365Sschwarze http_encode(name); 596f7a12365Sschwarze if (req->q.sec != NULL) { 597f7a12365Sschwarze putchar('.'); 598f7a12365Sschwarze http_encode(req->q.sec); 599f7a12365Sschwarze } 600e1beff2aSschwarze printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"); 601e1beff2aSschwarze } 602e1beff2aSschwarze 603e1beff2aSschwarze static void 604facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz) 605c6c22f12Sschwarze { 606be14a32aSschwarze char *arch, *archend; 607db26164eSschwarze const char *sec; 608db26164eSschwarze size_t i, iuse; 609be14a32aSschwarze int archprio, archpriouse; 61046723f19Sschwarze int prio, priouse; 611c6c22f12Sschwarze 61281475784Sschwarze for (i = 0; i < sz; i++) { 61381475784Sschwarze if (validate_filename(r[i].file)) 61481475784Sschwarze continue; 615c976f0e2Sschwarze warnx("invalid filename %s in %s database", 61681475784Sschwarze r[i].file, req->q.manpath); 61781475784Sschwarze pg_error_internal(); 61881475784Sschwarze return; 61981475784Sschwarze } 62081475784Sschwarze 62152b413c1Sschwarze if (req->isquery && sz == 1) { 622c6c22f12Sschwarze /* 623c6c22f12Sschwarze * If we have just one result, then jump there now 624c6c22f12Sschwarze * without any delay. 625c6c22f12Sschwarze */ 626e0d9a108Sschwarze printf("Status: 303 See Other\r\n" 627e0d9a108Sschwarze "Location: /"); 628e0d9a108Sschwarze if (*scriptname != '\0') 629e0d9a108Sschwarze printf("%s/", scriptname); 630e0d9a108Sschwarze if (strcmp(req->q.manpath, req->p[0])) 631e0d9a108Sschwarze printf("%s/", req->q.manpath); 632e0d9a108Sschwarze printf("%s\r\n" 633e0d9a108Sschwarze "Content-Type: text/html; charset=utf-8\r\n\r\n", 634e0d9a108Sschwarze r[0].file); 635c6c22f12Sschwarze return; 636c6c22f12Sschwarze } 637c6c22f12Sschwarze 63846723f19Sschwarze /* 63946723f19Sschwarze * In man(1) mode, show one of the pages 64046723f19Sschwarze * even if more than one is found. 64146723f19Sschwarze */ 64246723f19Sschwarze 64346723f19Sschwarze iuse = 0; 644fdef72b0Sschwarze if (req->q.equal || sz == 1) { 645db26164eSschwarze priouse = 20; 646be14a32aSschwarze archpriouse = 3; 64746723f19Sschwarze for (i = 0; i < sz; i++) { 648db26164eSschwarze sec = r[i].file; 649db26164eSschwarze sec += strcspn(sec, "123456789"); 650db26164eSschwarze if (sec[0] == '\0') 65146723f19Sschwarze continue; 652db26164eSschwarze prio = sec_prios[sec[0] - '1']; 653db26164eSschwarze if (sec[1] != '/') 654db26164eSschwarze prio += 10; 655db26164eSschwarze if (req->q.arch == NULL) { 656be14a32aSschwarze archprio = 657db26164eSschwarze ((arch = strchr(sec + 1, '/')) 658db26164eSschwarze == NULL) ? 3 : 659db26164eSschwarze ((archend = strchr(arch + 1, '/')) 660db26164eSschwarze == NULL) ? 0 : 661be14a32aSschwarze strncmp(arch, "amd64/", 662be14a32aSschwarze archend - arch) ? 2 : 1; 663be14a32aSschwarze if (archprio < archpriouse) { 664be14a32aSschwarze archpriouse = archprio; 665be14a32aSschwarze priouse = prio; 666be14a32aSschwarze iuse = i; 667be14a32aSschwarze continue; 668be14a32aSschwarze } 669be14a32aSschwarze if (archprio > archpriouse) 670be14a32aSschwarze continue; 671be14a32aSschwarze } 67246723f19Sschwarze if (prio >= priouse) 67346723f19Sschwarze continue; 67446723f19Sschwarze priouse = prio; 67546723f19Sschwarze iuse = i; 67646723f19Sschwarze } 677fdef72b0Sschwarze resp_begin_html(200, NULL, r[iuse].file); 678fdef72b0Sschwarze } else 679fdef72b0Sschwarze resp_begin_html(200, NULL, NULL); 680fdef72b0Sschwarze 681fdef72b0Sschwarze resp_searchform(req, 682fdef72b0Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); 683fdef72b0Sschwarze 684fdef72b0Sschwarze if (sz > 1) { 685fdef72b0Sschwarze puts("<table class=\"results\">"); 686fdef72b0Sschwarze for (i = 0; i < sz; i++) { 687fdef72b0Sschwarze printf(" <tr>\n" 688fdef72b0Sschwarze " <td>" 6897b3dbe16Sschwarze "<a class=\"Xr\" href=\"/"); 6907b3dbe16Sschwarze if (*scriptname != '\0') 6917b3dbe16Sschwarze printf("%s/", scriptname); 6927b3dbe16Sschwarze if (strcmp(req->q.manpath, req->p[0])) 6937b3dbe16Sschwarze printf("%s/", req->q.manpath); 6947b3dbe16Sschwarze printf("%s\">", r[i].file); 695fdef72b0Sschwarze html_print(r[i].names); 696fdef72b0Sschwarze printf("</a></td>\n" 697fdef72b0Sschwarze " <td><span class=\"Nd\">"); 698fdef72b0Sschwarze html_print(r[i].output); 699fdef72b0Sschwarze puts("</span></td>\n" 700fdef72b0Sschwarze " </tr>"); 701fdef72b0Sschwarze } 702fdef72b0Sschwarze puts("</table>"); 703fdef72b0Sschwarze } 704fdef72b0Sschwarze 705fdef72b0Sschwarze if (req->q.equal || sz == 1) { 706fdef72b0Sschwarze puts("<hr>"); 70746723f19Sschwarze resp_show(req, r[iuse].file); 70846723f19Sschwarze } 70946723f19Sschwarze 710c6c22f12Sschwarze resp_end_html(); 711c6c22f12Sschwarze } 712c6c22f12Sschwarze 713c6c22f12Sschwarze static void 714941df026Sschwarze resp_catman(const struct req *req, const char *file) 715c6c22f12Sschwarze { 716c6c22f12Sschwarze FILE *f; 717c6c22f12Sschwarze char *p; 71831f93c25Sschwarze size_t sz; 71931f93c25Sschwarze ssize_t len; 72031f93c25Sschwarze int i; 721c6c22f12Sschwarze int italic, bold; 722c6c22f12Sschwarze 72331f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) { 724735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 725c6c22f12Sschwarze return; 726c6c22f12Sschwarze } 727c6c22f12Sschwarze 728735516bdSschwarze puts("<div class=\"catman\">\n" 729735516bdSschwarze "<pre>"); 730c6c22f12Sschwarze 73131f93c25Sschwarze p = NULL; 73231f93c25Sschwarze sz = 0; 73331f93c25Sschwarze 73431f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) { 735c6c22f12Sschwarze bold = italic = 0; 73631f93c25Sschwarze for (i = 0; i < len - 1; i++) { 737c6c22f12Sschwarze /* 738c6c22f12Sschwarze * This means that the catpage is out of state. 739c6c22f12Sschwarze * Ignore it and keep going (although the 740c6c22f12Sschwarze * catpage is bogus). 741c6c22f12Sschwarze */ 742c6c22f12Sschwarze 743c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i]) 744c6c22f12Sschwarze continue; 745c6c22f12Sschwarze 746c6c22f12Sschwarze /* 747c6c22f12Sschwarze * Print a regular character. 748c6c22f12Sschwarze * Close out any bold/italic scopes. 749c6c22f12Sschwarze * If we're in back-space mode, make sure we'll 750c6c22f12Sschwarze * have something to enter when we backspace. 751c6c22f12Sschwarze */ 752c6c22f12Sschwarze 753c6c22f12Sschwarze if ('\b' != p[i + 1]) { 754c6c22f12Sschwarze if (italic) 755735516bdSschwarze printf("</i>"); 756c6c22f12Sschwarze if (bold) 757735516bdSschwarze printf("</b>"); 758c6c22f12Sschwarze italic = bold = 0; 759c6c22f12Sschwarze html_putchar(p[i]); 760c6c22f12Sschwarze continue; 76131f93c25Sschwarze } else if (i + 2 >= len) 762c6c22f12Sschwarze continue; 763c6c22f12Sschwarze 764c6c22f12Sschwarze /* Italic mode. */ 765c6c22f12Sschwarze 766c6c22f12Sschwarze if ('_' == p[i]) { 767c6c22f12Sschwarze if (bold) 768735516bdSschwarze printf("</b>"); 769c6c22f12Sschwarze if ( ! italic) 770735516bdSschwarze printf("<i>"); 771c6c22f12Sschwarze bold = 0; 772c6c22f12Sschwarze italic = 1; 773c6c22f12Sschwarze i += 2; 774c6c22f12Sschwarze html_putchar(p[i]); 775c6c22f12Sschwarze continue; 776c6c22f12Sschwarze } 777c6c22f12Sschwarze 778c6c22f12Sschwarze /* 779c6c22f12Sschwarze * Handle funny behaviour troff-isms. 780c6c22f12Sschwarze * These grok'd from the original man2html.c. 781c6c22f12Sschwarze */ 782c6c22f12Sschwarze 783c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) || 784c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) || 785c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) || 786c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) || 787c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) || 788c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) || 789c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) || 790c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) { 791c6c22f12Sschwarze if (italic) 792735516bdSschwarze printf("</i>"); 793c6c22f12Sschwarze if (bold) 794735516bdSschwarze printf("</b>"); 795c6c22f12Sschwarze italic = bold = 0; 796c6c22f12Sschwarze putchar('*'); 797c6c22f12Sschwarze i += 2; 798c6c22f12Sschwarze continue; 799c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) || 800c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) || 801c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) || 802c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) || 803c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) || 804c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) { 805c6c22f12Sschwarze if (italic) 806735516bdSschwarze printf("</i>"); 807c6c22f12Sschwarze if (bold) 808735516bdSschwarze printf("</b>"); 809c6c22f12Sschwarze italic = bold = 0; 810c6c22f12Sschwarze putchar('+'); 811c6c22f12Sschwarze i += 2; 812c6c22f12Sschwarze continue; 813c6c22f12Sschwarze } 814c6c22f12Sschwarze 815c6c22f12Sschwarze /* Bold mode. */ 816c6c22f12Sschwarze 817c6c22f12Sschwarze if (italic) 818735516bdSschwarze printf("</i>"); 819c6c22f12Sschwarze if ( ! bold) 820735516bdSschwarze printf("<b>"); 821c6c22f12Sschwarze bold = 1; 822c6c22f12Sschwarze italic = 0; 823c6c22f12Sschwarze i += 2; 824c6c22f12Sschwarze html_putchar(p[i]); 825c6c22f12Sschwarze } 826c6c22f12Sschwarze 827c6c22f12Sschwarze /* 828c6c22f12Sschwarze * Clean up the last character. 829c6c22f12Sschwarze * We can get to a newline; don't print that. 830c6c22f12Sschwarze */ 831c6c22f12Sschwarze 832c6c22f12Sschwarze if (italic) 833735516bdSschwarze printf("</i>"); 834c6c22f12Sschwarze if (bold) 835735516bdSschwarze printf("</b>"); 836c6c22f12Sschwarze 83731f93c25Sschwarze if (i == len - 1 && p[i] != '\n') 838c6c22f12Sschwarze html_putchar(p[i]); 839c6c22f12Sschwarze 840c6c22f12Sschwarze putchar('\n'); 841c6c22f12Sschwarze } 84231f93c25Sschwarze free(p); 843c6c22f12Sschwarze 844735516bdSschwarze puts("</pre>\n" 845735516bdSschwarze "</div>"); 846c6c22f12Sschwarze 847c6c22f12Sschwarze fclose(f); 848c6c22f12Sschwarze } 849c6c22f12Sschwarze 850c6c22f12Sschwarze static void 851941df026Sschwarze resp_format(const struct req *req, const char *file) 852c6c22f12Sschwarze { 8532ccd0917Sschwarze struct manoutput conf; 854c6c22f12Sschwarze struct mparse *mp; 8556b86842eSschwarze struct roff_meta *meta; 856c6c22f12Sschwarze void *vp; 857f74d674aSschwarze int fd; 858f74d674aSschwarze int usepath; 859c6c22f12Sschwarze 860c6c22f12Sschwarze if (-1 == (fd = open(file, O_RDONLY, 0))) { 861735516bdSschwarze puts("<p>You specified an invalid manual file.</p>"); 862c6c22f12Sschwarze return; 863c6c22f12Sschwarze } 864c6c22f12Sschwarze 86516536faaSschwarze mchars_alloc(); 8666b86842eSschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 | 8676b86842eSschwarze MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath); 868df927bb6Sschwarze mparse_readfd(mp, fd, file); 869c6c22f12Sschwarze close(fd); 8706b86842eSschwarze meta = mparse_result(mp); 871c6c22f12Sschwarze 8722ccd0917Sschwarze memset(&conf, 0, sizeof(conf)); 8732ccd0917Sschwarze conf.fragment = 1; 8747c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css"); 875f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]); 876ac2abdb8Sschwarze mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S", 877ac2abdb8Sschwarze scriptname, *scriptname == '\0' ? "" : "/", 878f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : ""); 879c6c22f12Sschwarze 88016536faaSschwarze vp = html_alloc(&conf); 8816b86842eSschwarze if (meta->macroset == MACROSET_MDOC) 8826b86842eSschwarze html_mdoc(vp, meta); 8836b86842eSschwarze else 8846b86842eSschwarze html_man(vp, meta); 885c6c22f12Sschwarze 886c6c22f12Sschwarze html_free(vp); 887c6c22f12Sschwarze mparse_free(mp); 88816536faaSschwarze mchars_free(); 8892ccd0917Sschwarze free(conf.man); 8907c6e1b3aSschwarze free(conf.style); 891c6c22f12Sschwarze } 892c6c22f12Sschwarze 893c6c22f12Sschwarze static void 89446723f19Sschwarze resp_show(const struct req *req, const char *file) 89546723f19Sschwarze { 89681475784Sschwarze 89781475784Sschwarze if ('.' == file[0] && '/' == file[1]) 8982f7bef27Sschwarze file += 2; 89946723f19Sschwarze 90046723f19Sschwarze if ('c' == *file) 901941df026Sschwarze resp_catman(req, file); 90246723f19Sschwarze else 903941df026Sschwarze resp_format(req, file); 90446723f19Sschwarze } 90546723f19Sschwarze 90646723f19Sschwarze static void 907b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath) 908c6c22f12Sschwarze { 909b53c14c3Sschwarze char *manpath; 910b53c14c3Sschwarze const char *file; 911c6c22f12Sschwarze 912b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) { 913facea411Sschwarze pg_error_badrequest( 914c6c22f12Sschwarze "You did not specify a page to show."); 915c6c22f12Sschwarze return; 916c6c22f12Sschwarze } 917b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath); 918b53c14c3Sschwarze file++; 919c6c22f12Sschwarze 920b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) { 921631ce2c6Sschwarze pg_error_badrequest( 922631ce2c6Sschwarze "You specified an invalid manpath."); 923b53c14c3Sschwarze free(manpath); 924631ce2c6Sschwarze return; 925631ce2c6Sschwarze } 926631ce2c6Sschwarze 927c6c22f12Sschwarze /* 928c6c22f12Sschwarze * Begin by chdir()ing into the manpath. 929c6c22f12Sschwarze * This way we can pick up the database files, which are 930c6c22f12Sschwarze * relative to the manpath root. 931c6c22f12Sschwarze */ 932c6c22f12Sschwarze 933b53c14c3Sschwarze if (chdir(manpath) == -1) { 934c976f0e2Sschwarze warn("chdir %s", manpath); 935631ce2c6Sschwarze pg_error_internal(); 936b53c14c3Sschwarze free(manpath); 937c6c22f12Sschwarze return; 938c6c22f12Sschwarze } 939b53c14c3Sschwarze free(manpath); 940b53c14c3Sschwarze 941b53c14c3Sschwarze if ( ! validate_filename(file)) { 94281475784Sschwarze pg_error_badrequest( 94381475784Sschwarze "You specified an invalid manual file."); 94481475784Sschwarze return; 94581475784Sschwarze } 94681475784Sschwarze 947fdef72b0Sschwarze resp_begin_html(200, NULL, file); 94884f05c93Sschwarze resp_searchform(req, FOCUS_NONE); 949b53c14c3Sschwarze resp_show(req, file); 95046723f19Sschwarze resp_end_html(); 951c6c22f12Sschwarze } 952c6c22f12Sschwarze 953c6c22f12Sschwarze static void 95457482ef4Sschwarze pg_search(const struct req *req) 955c6c22f12Sschwarze { 956c6c22f12Sschwarze struct mansearch search; 957c6c22f12Sschwarze struct manpaths paths; 958c6c22f12Sschwarze struct manpage *res; 959fbeeb774Sschwarze char **argv; 960fbeeb774Sschwarze char *query, *rp, *wp; 961c6c22f12Sschwarze size_t ressz; 962fbeeb774Sschwarze int argc; 963c6c22f12Sschwarze 964c6c22f12Sschwarze /* 965c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath. 966c6c22f12Sschwarze * This way we can pick up the database files, which are 967c6c22f12Sschwarze * relative to the manpath root. 968c6c22f12Sschwarze */ 969c6c22f12Sschwarze 970c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) { 971c976f0e2Sschwarze warn("chdir %s", req->q.manpath); 972631ce2c6Sschwarze pg_error_internal(); 973c6c22f12Sschwarze return; 974c6c22f12Sschwarze } 975c6c22f12Sschwarze 976c6c22f12Sschwarze search.arch = req->q.arch; 977c6c22f12Sschwarze search.sec = req->q.sec; 9780f10154cSschwarze search.outkey = "Nd"; 9790f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 980fea71919Sschwarze search.firstmatch = 1; 981c6c22f12Sschwarze 982c6c22f12Sschwarze paths.sz = 1; 983c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *)); 984c6c22f12Sschwarze paths.paths[0] = mandoc_strdup("."); 985c6c22f12Sschwarze 986c6c22f12Sschwarze /* 987fbeeb774Sschwarze * Break apart at spaces with backslash-escaping. 988c6c22f12Sschwarze */ 989c6c22f12Sschwarze 990fbeeb774Sschwarze argc = 0; 991fbeeb774Sschwarze argv = NULL; 992fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query); 993fbeeb774Sschwarze for (;;) { 994fbeeb774Sschwarze while (isspace((unsigned char)*rp)) 995fbeeb774Sschwarze rp++; 996fbeeb774Sschwarze if (*rp == '\0') 997fbeeb774Sschwarze break; 998fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 999fbeeb774Sschwarze argv[argc++] = wp = rp; 1000fbeeb774Sschwarze for (;;) { 1001fbeeb774Sschwarze if (isspace((unsigned char)*rp)) { 1002fbeeb774Sschwarze *wp = '\0'; 1003fbeeb774Sschwarze rp++; 1004fbeeb774Sschwarze break; 1005fbeeb774Sschwarze } 1006fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0') 1007fbeeb774Sschwarze rp++; 1008fbeeb774Sschwarze if (wp != rp) 1009fbeeb774Sschwarze *wp = *rp; 1010fbeeb774Sschwarze if (*rp == '\0') 1011fbeeb774Sschwarze break; 1012fbeeb774Sschwarze wp++; 1013fbeeb774Sschwarze rp++; 1014fbeeb774Sschwarze } 1015c6c22f12Sschwarze } 1016c6c22f12Sschwarze 1017e1beff2aSschwarze res = NULL; 1018e1beff2aSschwarze ressz = 0; 1019e1beff2aSschwarze if (req->isquery && req->q.equal && argc == 1) 1020e1beff2aSschwarze pg_redirect(req, argv[0]); 1021e1beff2aSschwarze else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) 102218ccf011Sschwarze pg_noresult(req, 400, "Bad Request", 102318ccf011Sschwarze "You entered an invalid query."); 1024e1beff2aSschwarze else if (ressz == 0) 102518ccf011Sschwarze pg_noresult(req, 404, "Not Found", "No results found."); 1026c6c22f12Sschwarze else 1027facea411Sschwarze pg_searchres(req, res, ressz); 1028c6c22f12Sschwarze 1029fbeeb774Sschwarze free(query); 1030fbeeb774Sschwarze mansearch_free(res, ressz); 1031c6c22f12Sschwarze free(paths.paths[0]); 1032c6c22f12Sschwarze free(paths.paths); 1033c6c22f12Sschwarze } 1034c6c22f12Sschwarze 1035c6c22f12Sschwarze int 1036c6c22f12Sschwarze main(void) 1037c6c22f12Sschwarze { 1038c6c22f12Sschwarze struct req req; 1039136c26b8Sschwarze struct itimerval itimer; 104057482ef4Sschwarze const char *path; 104131e689c3Sschwarze const char *querystring; 104257482ef4Sschwarze int i; 1043c6c22f12Sschwarze 1044f80eb964Sschwarze /* 1045f80eb964Sschwarze * The "rpath" pledge could be revoked after mparse_readfd() 1046f80eb964Sschwarze * if the file desciptor to "/footer.html" would be opened 1047f80eb964Sschwarze * up front, but it's probably not worth the complication 1048f80eb964Sschwarze * of the code it would cause: it would require scattering 1049f80eb964Sschwarze * pledge() calls in multiple low-level resp_*() functions. 1050f80eb964Sschwarze */ 1051f80eb964Sschwarze 1052f80eb964Sschwarze if (pledge("stdio rpath", NULL) == -1) { 1053f80eb964Sschwarze warn("pledge"); 1054f80eb964Sschwarze pg_error_internal(); 1055f80eb964Sschwarze return EXIT_FAILURE; 1056f80eb964Sschwarze } 1057f80eb964Sschwarze 1058136c26b8Sschwarze /* Poor man's ReDoS mitigation. */ 1059136c26b8Sschwarze 10602935aafcSschwarze itimer.it_value.tv_sec = 2; 1061136c26b8Sschwarze itimer.it_value.tv_usec = 0; 10622935aafcSschwarze itimer.it_interval.tv_sec = 2; 1063136c26b8Sschwarze itimer.it_interval.tv_usec = 0; 1064136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1065c976f0e2Sschwarze warn("setitimer"); 1066136c26b8Sschwarze pg_error_internal(); 1067526e306bSschwarze return EXIT_FAILURE; 1068136c26b8Sschwarze } 1069136c26b8Sschwarze 1070c6c22f12Sschwarze /* 10716fdade3eSschwarze * First we change directory into the MAN_DIR so that 1072c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted 1073c6c22f12Sschwarze * relative to the same position. 1074c6c22f12Sschwarze */ 1075c6c22f12Sschwarze 1076c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) { 1077c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR); 1078facea411Sschwarze pg_error_internal(); 1079526e306bSschwarze return EXIT_FAILURE; 1080c6c22f12Sschwarze } 1081c6c22f12Sschwarze 1082c6c22f12Sschwarze memset(&req, 0, sizeof(struct req)); 1083abf19dc9Sschwarze req.q.equal = 1; 1084941df026Sschwarze parse_manpath_conf(&req); 1085c6c22f12Sschwarze 108602b1b494Sschwarze /* Parse the path info and the query string. */ 1087c6c22f12Sschwarze 108802b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL) 108902b1b494Sschwarze path = ""; 109002b1b494Sschwarze else if (*path == '/') 109102b1b494Sschwarze path++; 109202b1b494Sschwarze 1093aa16a3a8Sschwarze if (*path != '\0') { 1094941df026Sschwarze parse_path_info(&req, path); 1095a9941855Sschwarze if (req.q.manpath == NULL || req.q.sec == NULL || 1096a9941855Sschwarze *req.q.query == '\0' || access(path, F_OK) == -1) 109702b1b494Sschwarze path = ""; 109802b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL) 1099941df026Sschwarze parse_query_string(&req, querystring); 1100c6c22f12Sschwarze 110102b1b494Sschwarze /* Validate parsed data and add defaults. */ 110202b1b494Sschwarze 110350eaed2bSschwarze if (req.q.manpath == NULL) 110450eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]); 110550eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) { 1106631ce2c6Sschwarze pg_error_badrequest( 1107631ce2c6Sschwarze "You specified an invalid manpath."); 1108526e306bSschwarze return EXIT_FAILURE; 1109631ce2c6Sschwarze } 1110631ce2c6Sschwarze 1111f7a12365Sschwarze if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) { 1112cf3a545cSschwarze pg_error_badrequest( 1113cf3a545cSschwarze "You specified an invalid architecture."); 1114526e306bSschwarze return EXIT_FAILURE; 1115cf3a545cSschwarze } 1116cf3a545cSschwarze 111757482ef4Sschwarze /* Dispatch to the three different pages. */ 1118c6c22f12Sschwarze 111957482ef4Sschwarze if ('\0' != *path) 112057482ef4Sschwarze pg_show(&req, path); 1121e89321abSschwarze else if (NULL != req.q.query) 112257482ef4Sschwarze pg_search(&req); 112357482ef4Sschwarze else 1124facea411Sschwarze pg_index(&req); 1125c6c22f12Sschwarze 112631e689c3Sschwarze free(req.q.manpath); 112731e689c3Sschwarze free(req.q.arch); 112831e689c3Sschwarze free(req.q.sec); 1129e89321abSschwarze free(req.q.query); 1130c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++) 1131c6c22f12Sschwarze free(req.p[i]); 1132c6c22f12Sschwarze free(req.p); 1133526e306bSschwarze return EXIT_SUCCESS; 1134c6c22f12Sschwarze } 1135c6c22f12Sschwarze 1136c6c22f12Sschwarze /* 1137f5938fa6Sschwarze * Translate PATH_INFO to a query. 113802b1b494Sschwarze */ 113902b1b494Sschwarze static void 1140941df026Sschwarze parse_path_info(struct req *req, const char *path) 114102b1b494Sschwarze { 1142f5938fa6Sschwarze const char *name, *sec, *end; 114302b1b494Sschwarze 114452b413c1Sschwarze req->isquery = 0; 114502b1b494Sschwarze req->q.equal = 1; 1146f5938fa6Sschwarze req->q.manpath = NULL; 1147daf4c292Sschwarze req->q.arch = NULL; 114802b1b494Sschwarze 114902b1b494Sschwarze /* Mandatory manual page name. */ 1150f5938fa6Sschwarze if ((name = strrchr(path, '/')) == NULL) 1151f5938fa6Sschwarze name = path; 1152f5938fa6Sschwarze else 1153f5938fa6Sschwarze name++; 115402b1b494Sschwarze 115502b1b494Sschwarze /* Optional trailing section. */ 1156f5938fa6Sschwarze sec = strrchr(name, '.'); 1157f5938fa6Sschwarze if (sec != NULL && isdigit((unsigned char)*++sec)) { 1158f5938fa6Sschwarze req->q.query = mandoc_strndup(name, sec - name - 1); 1159f5938fa6Sschwarze req->q.sec = mandoc_strdup(sec); 1160f5938fa6Sschwarze } else { 1161f5938fa6Sschwarze req->q.query = mandoc_strdup(name); 116202b1b494Sschwarze req->q.sec = NULL; 116302b1b494Sschwarze } 116402b1b494Sschwarze 116502b1b494Sschwarze /* Handle the case of name[.section] only. */ 1166f5938fa6Sschwarze if (name == path) 116702b1b494Sschwarze return; 116802b1b494Sschwarze 1169f5938fa6Sschwarze /* Optional manpath. */ 1170f5938fa6Sschwarze end = strchr(path, '/'); 1171f5938fa6Sschwarze req->q.manpath = mandoc_strndup(path, end - path); 1172f5938fa6Sschwarze if (validate_manpath(req, req->q.manpath)) { 1173f5938fa6Sschwarze path = end + 1; 1174f5938fa6Sschwarze if (name == path) 1175f5938fa6Sschwarze return; 1176f5938fa6Sschwarze } else { 1177f5938fa6Sschwarze free(req->q.manpath); 1178f5938fa6Sschwarze req->q.manpath = NULL; 1179f5938fa6Sschwarze } 1180f5938fa6Sschwarze 1181f5938fa6Sschwarze /* Optional section. */ 1182955967fcSschwarze if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) { 1183f5938fa6Sschwarze path += 3; 1184f5938fa6Sschwarze end = strchr(path, '/'); 1185f5938fa6Sschwarze free(req->q.sec); 1186f5938fa6Sschwarze req->q.sec = mandoc_strndup(path, end - path); 1187f5938fa6Sschwarze path = end + 1; 1188f5938fa6Sschwarze if (name == path) 1189f5938fa6Sschwarze return; 1190f5938fa6Sschwarze } 1191f5938fa6Sschwarze 1192f5938fa6Sschwarze /* Optional architecture. */ 1193f5938fa6Sschwarze end = strchr(path, '/'); 1194f5938fa6Sschwarze if (end + 1 != name) { 1195daf4c292Sschwarze pg_error_badrequest( 1196daf4c292Sschwarze "You specified too many directory components."); 1197daf4c292Sschwarze exit(EXIT_FAILURE); 119802b1b494Sschwarze } 1199f5938fa6Sschwarze req->q.arch = mandoc_strndup(path, end - path); 1200f5938fa6Sschwarze if (validate_arch(req->q.arch) == 0) { 1201daf4c292Sschwarze pg_error_badrequest( 1202daf4c292Sschwarze "You specified an invalid directory component."); 1203daf4c292Sschwarze exit(EXIT_FAILURE); 1204daf4c292Sschwarze } 120502b1b494Sschwarze } 120602b1b494Sschwarze 120702b1b494Sschwarze /* 1208c6c22f12Sschwarze * Scan for indexable paths. 1209c6c22f12Sschwarze */ 1210c6c22f12Sschwarze static void 1211941df026Sschwarze parse_manpath_conf(struct req *req) 1212c6c22f12Sschwarze { 1213c6c22f12Sschwarze FILE *fp; 1214c6c22f12Sschwarze char *dp; 1215c6c22f12Sschwarze size_t dpsz; 121631f93c25Sschwarze ssize_t len; 1217c6c22f12Sschwarze 1218c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) { 1219c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR); 1220de651747Sschwarze pg_error_internal(); 1221de651747Sschwarze exit(EXIT_FAILURE); 1222de651747Sschwarze } 1223c6c22f12Sschwarze 122431f93c25Sschwarze dp = NULL; 122531f93c25Sschwarze dpsz = 0; 122631f93c25Sschwarze 122731f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) { 122831f93c25Sschwarze if (dp[len - 1] == '\n') 122931f93c25Sschwarze dp[--len] = '\0'; 1230c6c22f12Sschwarze req->p = mandoc_realloc(req->p, 1231c6c22f12Sschwarze (req->psz + 1) * sizeof(char *)); 1232cf3a545cSschwarze if ( ! validate_urifrag(dp)) { 1233c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1234c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp); 1235cf3a545cSschwarze pg_error_internal(); 1236cf3a545cSschwarze exit(EXIT_FAILURE); 1237cf3a545cSschwarze } 1238c976f0e2Sschwarze if (strchr(dp, '/') != NULL) { 1239c976f0e2Sschwarze warnx("%s/manpath.conf contains " 1240c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp); 1241cf3a545cSschwarze pg_error_internal(); 1242cf3a545cSschwarze exit(EXIT_FAILURE); 1243cf3a545cSschwarze } 1244cf3a545cSschwarze req->p[req->psz++] = dp; 124531f93c25Sschwarze dp = NULL; 124631f93c25Sschwarze dpsz = 0; 1247c6c22f12Sschwarze } 124831f93c25Sschwarze free(dp); 1249de651747Sschwarze 1250de651747Sschwarze if (req->p == NULL) { 1251c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR); 1252de651747Sschwarze pg_error_internal(); 1253de651747Sschwarze exit(EXIT_FAILURE); 1254de651747Sschwarze } 1255c6c22f12Sschwarze } 1256