1 /* $Id: cgi.c,v 1.1.1.4 2015/12/17 21:58:48 christos Exp $ */ 2 /* 3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include "config.h" 19 20 #include <sys/types.h> 21 #include <sys/time.h> 22 23 #include <ctype.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <limits.h> 27 #include <stdint.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 33 #include "mandoc.h" 34 #include "mandoc_aux.h" 35 #include "main.h" 36 #include "manpath.h" 37 #include "mansearch.h" 38 #include "cgi.h" 39 40 /* 41 * A query as passed to the search function. 42 */ 43 struct query { 44 char *manpath; /* desired manual directory */ 45 char *arch; /* architecture */ 46 char *sec; /* manual section */ 47 char *query; /* unparsed query expression */ 48 int equal; /* match whole names, not substrings */ 49 }; 50 51 struct req { 52 struct query q; 53 char **p; /* array of available manpaths */ 54 size_t psz; /* number of available manpaths */ 55 }; 56 57 static void catman(const struct req *, const char *); 58 static void format(const struct req *, const char *); 59 static void html_print(const char *); 60 static void html_putchar(char); 61 static int http_decode(char *); 62 static void http_parse(struct req *, const char *); 63 static void http_print(const char *); 64 static void http_putchar(char); 65 static void http_printquery(const struct req *, const char *); 66 static void pathgen(struct req *); 67 static void pg_error_badrequest(const char *); 68 static void pg_error_internal(void); 69 static void pg_index(const struct req *); 70 static void pg_noresult(const struct req *, const char *); 71 static void pg_search(const struct req *); 72 static void pg_searchres(const struct req *, 73 struct manpage *, size_t); 74 static void pg_show(struct req *, const char *); 75 static void resp_begin_html(int, const char *); 76 static void resp_begin_http(int, const char *); 77 static void resp_end_html(void); 78 static void resp_searchform(const struct req *); 79 static void resp_show(const struct req *, const char *); 80 static void set_query_attr(char **, char **); 81 static int validate_filename(const char *); 82 static int validate_manpath(const struct req *, const char *); 83 static int validate_urifrag(const char *); 84 85 static const char *scriptname; /* CGI script name */ 86 87 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; 88 static const char *const sec_numbers[] = { 89 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9" 90 }; 91 static const char *const sec_names[] = { 92 "All Sections", 93 "1 - General Commands", 94 "2 - System Calls", 95 "3 - Library Functions", 96 "3p - Perl Library", 97 "4 - Device Drivers", 98 "5 - File Formats", 99 "6 - Games", 100 "7 - Miscellaneous Information", 101 "8 - System Manager\'s Manual", 102 "9 - Kernel Developer\'s Manual" 103 }; 104 static const int sec_MAX = sizeof(sec_names) / sizeof(char *); 105 106 static const char *const arch_names[] = { 107 "amd64", "alpha", "armish", "armv7", 108 "aviion", "hppa", "hppa64", "i386", 109 "ia64", "landisk", "loongson", "luna88k", 110 "macppc", "mips64", "octeon", "sgi", 111 "socppc", "solbourne", "sparc", "sparc64", 112 "vax", "zaurus", 113 "amiga", "arc", "arm32", "atari", 114 "beagle", "cats", "hp300", "mac68k", 115 "mvme68k", "mvme88k", "mvmeppc", "palm", 116 "pc532", "pegasos", "pmax", "powerpc", 117 "sun3", "wgrisc", "x68k" 118 }; 119 static const int arch_MAX = sizeof(arch_names) / sizeof(char *); 120 121 /* 122 * Print a character, escaping HTML along the way. 123 * This will pass non-ASCII straight to output: be warned! 124 */ 125 static void 126 html_putchar(char c) 127 { 128 129 switch (c) { 130 case ('"'): 131 printf(""e;"); 132 break; 133 case ('&'): 134 printf("&"); 135 break; 136 case ('>'): 137 printf(">"); 138 break; 139 case ('<'): 140 printf("<"); 141 break; 142 default: 143 putchar((unsigned char)c); 144 break; 145 } 146 } 147 148 static void 149 http_printquery(const struct req *req, const char *sep) 150 { 151 152 if (NULL != req->q.query) { 153 printf("query="); 154 http_print(req->q.query); 155 } 156 if (0 == req->q.equal) 157 printf("%sapropos=1", sep); 158 if (NULL != req->q.sec) { 159 printf("%ssec=", sep); 160 http_print(req->q.sec); 161 } 162 if (NULL != req->q.arch) { 163 printf("%sarch=", sep); 164 http_print(req->q.arch); 165 } 166 if (strcmp(req->q.manpath, req->p[0])) { 167 printf("%smanpath=", sep); 168 http_print(req->q.manpath); 169 } 170 } 171 172 static void 173 http_print(const char *p) 174 { 175 176 if (NULL == p) 177 return; 178 while ('\0' != *p) 179 http_putchar(*p++); 180 } 181 182 /* 183 * Call through to html_putchar(). 184 * Accepts NULL strings. 185 */ 186 static void 187 html_print(const char *p) 188 { 189 190 if (NULL == p) 191 return; 192 while ('\0' != *p) 193 html_putchar(*p++); 194 } 195 196 /* 197 * Transfer the responsibility for the allocated string *val 198 * to the query structure. 199 */ 200 static void 201 set_query_attr(char **attr, char **val) 202 { 203 204 free(*attr); 205 if (**val == '\0') { 206 *attr = NULL; 207 free(*val); 208 } else 209 *attr = *val; 210 *val = NULL; 211 } 212 213 /* 214 * Parse the QUERY_STRING for key-value pairs 215 * and store the values into the query structure. 216 */ 217 static void 218 http_parse(struct req *req, const char *qs) 219 { 220 char *key, *val; 221 size_t keysz, valsz; 222 223 req->q.manpath = NULL; 224 req->q.arch = NULL; 225 req->q.sec = NULL; 226 req->q.query = NULL; 227 req->q.equal = 1; 228 229 key = val = NULL; 230 while (*qs != '\0') { 231 232 /* Parse one key. */ 233 234 keysz = strcspn(qs, "=;&"); 235 key = mandoc_strndup(qs, keysz); 236 qs += keysz; 237 if (*qs != '=') 238 goto next; 239 240 /* Parse one value. */ 241 242 valsz = strcspn(++qs, ";&"); 243 val = mandoc_strndup(qs, valsz); 244 qs += valsz; 245 246 /* Decode and catch encoding errors. */ 247 248 if ( ! (http_decode(key) && http_decode(val))) 249 goto next; 250 251 /* Handle key-value pairs. */ 252 253 if ( ! strcmp(key, "query")) 254 set_query_attr(&req->q.query, &val); 255 256 else if ( ! strcmp(key, "apropos")) 257 req->q.equal = !strcmp(val, "0"); 258 259 else if ( ! strcmp(key, "manpath")) { 260 #ifdef COMPAT_OLDURI 261 if ( ! strncmp(val, "OpenBSD ", 8)) { 262 val[7] = '-'; 263 if ('C' == val[8]) 264 val[8] = 'c'; 265 } 266 #endif 267 set_query_attr(&req->q.manpath, &val); 268 } 269 270 else if ( ! (strcmp(key, "sec") 271 #ifdef COMPAT_OLDURI 272 && strcmp(key, "sektion") 273 #endif 274 )) { 275 if ( ! strcmp(val, "0")) 276 *val = '\0'; 277 set_query_attr(&req->q.sec, &val); 278 } 279 280 else if ( ! strcmp(key, "arch")) { 281 if ( ! strcmp(val, "default")) 282 *val = '\0'; 283 set_query_attr(&req->q.arch, &val); 284 } 285 286 /* 287 * The key must be freed in any case. 288 * The val may have been handed over to the query 289 * structure, in which case it is now NULL. 290 */ 291 next: 292 free(key); 293 key = NULL; 294 free(val); 295 val = NULL; 296 297 if (*qs != '\0') 298 qs++; 299 } 300 } 301 302 static void 303 http_putchar(char c) 304 { 305 306 if (isalnum((unsigned char)c)) { 307 putchar((unsigned char)c); 308 return; 309 } else if (' ' == c) { 310 putchar('+'); 311 return; 312 } 313 printf("%%%.2x", c); 314 } 315 316 /* 317 * HTTP-decode a string. The standard explanation is that this turns 318 * "%4e+foo" into "n foo" in the regular way. This is done in-place 319 * over the allocated string. 320 */ 321 static int 322 http_decode(char *p) 323 { 324 char hex[3]; 325 char *q; 326 int c; 327 328 hex[2] = '\0'; 329 330 q = p; 331 for ( ; '\0' != *p; p++, q++) { 332 if ('%' == *p) { 333 if ('\0' == (hex[0] = *(p + 1))) 334 return(0); 335 if ('\0' == (hex[1] = *(p + 2))) 336 return(0); 337 if (1 != sscanf(hex, "%x", &c)) 338 return(0); 339 if ('\0' == c) 340 return(0); 341 342 *q = (char)c; 343 p += 2; 344 } else 345 *q = '+' == *p ? ' ' : *p; 346 } 347 348 *q = '\0'; 349 return(1); 350 } 351 352 static void 353 resp_begin_http(int code, const char *msg) 354 { 355 356 if (200 != code) 357 printf("Status: %d %s\r\n", code, msg); 358 359 printf("Content-Type: text/html; charset=utf-8\r\n" 360 "Cache-Control: no-cache\r\n" 361 "Pragma: no-cache\r\n" 362 "\r\n"); 363 364 fflush(stdout); 365 } 366 367 static void 368 resp_begin_html(int code, const char *msg) 369 { 370 371 resp_begin_http(code, msg); 372 373 printf("<!DOCTYPE html>\n" 374 "<HTML>\n" 375 "<HEAD>\n" 376 "<META CHARSET=\"UTF-8\" />\n" 377 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\"" 378 " TYPE=\"text/css\" media=\"all\">\n" 379 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\"" 380 " TYPE=\"text/css\" media=\"all\">\n" 381 "<TITLE>%s</TITLE>\n" 382 "</HEAD>\n" 383 "<BODY>\n" 384 "<!-- Begin page content. //-->\n", 385 CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE); 386 } 387 388 static void 389 resp_end_html(void) 390 { 391 392 puts("</BODY>\n" 393 "</HTML>"); 394 } 395 396 static void 397 resp_searchform(const struct req *req) 398 { 399 int i; 400 401 puts(CUSTOMIZE_BEGIN); 402 puts("<!-- Begin search form. //-->"); 403 printf("<DIV ID=\"mancgi\">\n" 404 "<FORM ACTION=\"%s\" METHOD=\"get\">\n" 405 "<FIELDSET>\n" 406 "<LEGEND>Manual Page Search Parameters</LEGEND>\n", 407 scriptname); 408 409 /* Write query input box. */ 410 411 printf( "<TABLE><TR><TD>\n" 412 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\""); 413 if (NULL != req->q.query) 414 html_print(req->q.query); 415 puts("\" SIZE=\"40\">"); 416 417 /* Write submission and reset buttons. */ 418 419 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n" 420 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"); 421 422 /* Write show radio button */ 423 424 printf( "</TD><TD>\n" 425 "<INPUT TYPE=\"radio\" "); 426 if (req->q.equal) 427 printf("CHECKED=\"checked\" "); 428 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n" 429 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n"); 430 431 /* Write section selector. */ 432 433 puts( "</TD></TR><TR><TD>\n" 434 "<SELECT NAME=\"sec\">"); 435 for (i = 0; i < sec_MAX; i++) { 436 printf("<OPTION VALUE=\"%s\"", sec_numbers[i]); 437 if (NULL != req->q.sec && 438 0 == strcmp(sec_numbers[i], req->q.sec)) 439 printf(" SELECTED=\"selected\""); 440 printf(">%s</OPTION>\n", sec_names[i]); 441 } 442 puts("</SELECT>"); 443 444 /* Write architecture selector. */ 445 446 printf( "<SELECT NAME=\"arch\">\n" 447 "<OPTION VALUE=\"default\""); 448 if (NULL == req->q.arch) 449 printf(" SELECTED=\"selected\""); 450 puts(">All Architectures</OPTION>"); 451 for (i = 0; i < arch_MAX; i++) { 452 printf("<OPTION VALUE=\"%s\"", arch_names[i]); 453 if (NULL != req->q.arch && 454 0 == strcmp(arch_names[i], req->q.arch)) 455 printf(" SELECTED=\"selected\""); 456 printf(">%s</OPTION>\n", arch_names[i]); 457 } 458 puts("</SELECT>"); 459 460 /* Write manpath selector. */ 461 462 if (req->psz > 1) { 463 puts("<SELECT NAME=\"manpath\">"); 464 for (i = 0; i < (int)req->psz; i++) { 465 printf("<OPTION "); 466 if (strcmp(req->q.manpath, req->p[i]) == 0) 467 printf("SELECTED=\"selected\" "); 468 printf("VALUE=\""); 469 html_print(req->p[i]); 470 printf("\">"); 471 html_print(req->p[i]); 472 puts("</OPTION>"); 473 } 474 puts("</SELECT>"); 475 } 476 477 /* Write search radio button */ 478 479 printf( "</TD><TD>\n" 480 "<INPUT TYPE=\"radio\" "); 481 if (0 == req->q.equal) 482 printf("CHECKED=\"checked\" "); 483 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n" 484 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n"); 485 486 puts("</TD></TR></TABLE>\n" 487 "</FIELDSET>\n" 488 "</FORM>\n" 489 "</DIV>"); 490 puts("<!-- End search form. //-->"); 491 } 492 493 static int 494 validate_urifrag(const char *frag) 495 { 496 497 while ('\0' != *frag) { 498 if ( ! (isalnum((unsigned char)*frag) || 499 '-' == *frag || '.' == *frag || 500 '/' == *frag || '_' == *frag)) 501 return(0); 502 frag++; 503 } 504 return(1); 505 } 506 507 static int 508 validate_manpath(const struct req *req, const char* manpath) 509 { 510 size_t i; 511 512 if ( ! strcmp(manpath, "mandoc")) 513 return(1); 514 515 for (i = 0; i < req->psz; i++) 516 if ( ! strcmp(manpath, req->p[i])) 517 return(1); 518 519 return(0); 520 } 521 522 static int 523 validate_filename(const char *file) 524 { 525 526 if ('.' == file[0] && '/' == file[1]) 527 file += 2; 528 529 return ( ! (strstr(file, "../") || strstr(file, "/..") || 530 (strncmp(file, "man", 3) && strncmp(file, "cat", 3)))); 531 } 532 533 static void 534 pg_index(const struct req *req) 535 { 536 537 resp_begin_html(200, NULL); 538 resp_searchform(req); 539 printf("<P>\n" 540 "This web interface is documented in the\n" 541 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n" 542 "manual, and the\n" 543 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n" 544 "manual explains the query syntax.\n" 545 "</P>\n", 546 scriptname, scriptname); 547 resp_end_html(); 548 } 549 550 static void 551 pg_noresult(const struct req *req, const char *msg) 552 { 553 resp_begin_html(200, NULL); 554 resp_searchform(req); 555 puts("<P>"); 556 puts(msg); 557 puts("</P>"); 558 resp_end_html(); 559 } 560 561 static void 562 pg_error_badrequest(const char *msg) 563 { 564 565 resp_begin_html(400, "Bad Request"); 566 puts("<H1>Bad Request</H1>\n" 567 "<P>\n"); 568 puts(msg); 569 printf("Try again from the\n" 570 "<A HREF=\"%s\">main page</A>.\n" 571 "</P>", scriptname); 572 resp_end_html(); 573 } 574 575 static void 576 pg_error_internal(void) 577 { 578 resp_begin_html(500, "Internal Server Error"); 579 puts("<P>Internal Server Error</P>"); 580 resp_end_html(); 581 } 582 583 static void 584 pg_searchres(const struct req *req, struct manpage *r, size_t sz) 585 { 586 char *arch, *archend; 587 size_t i, iuse, isec; 588 int archprio, archpriouse; 589 int prio, priouse; 590 char sec; 591 592 for (i = 0; i < sz; i++) { 593 if (validate_filename(r[i].file)) 594 continue; 595 fprintf(stderr, "invalid filename %s in %s database\n", 596 r[i].file, req->q.manpath); 597 pg_error_internal(); 598 return; 599 } 600 601 if (1 == sz) { 602 /* 603 * If we have just one result, then jump there now 604 * without any delay. 605 */ 606 printf("Status: 303 See Other\r\n"); 607 printf("Location: http://%s%s/%s/%s?", 608 HTTP_HOST, scriptname, req->q.manpath, r[0].file); 609 http_printquery(req, "&"); 610 printf("\r\n" 611 "Content-Type: text/html; charset=utf-8\r\n" 612 "\r\n"); 613 return; 614 } 615 616 resp_begin_html(200, NULL); 617 resp_searchform(req); 618 puts("<DIV CLASS=\"results\">"); 619 puts("<TABLE>"); 620 621 for (i = 0; i < sz; i++) { 622 printf("<TR>\n" 623 "<TD CLASS=\"title\">\n" 624 "<A HREF=\"%s/%s/%s?", 625 scriptname, req->q.manpath, r[i].file); 626 http_printquery(req, "&"); 627 printf("\">"); 628 html_print(r[i].names); 629 printf("</A>\n" 630 "</TD>\n" 631 "<TD CLASS=\"desc\">"); 632 html_print(r[i].output); 633 puts("</TD>\n" 634 "</TR>"); 635 } 636 637 puts("</TABLE>\n" 638 "</DIV>"); 639 640 /* 641 * In man(1) mode, show one of the pages 642 * even if more than one is found. 643 */ 644 645 if (req->q.equal) { 646 puts("<HR>"); 647 iuse = 0; 648 priouse = 10; 649 archpriouse = 3; 650 for (i = 0; i < sz; i++) { 651 isec = strcspn(r[i].file, "123456789"); 652 sec = r[i].file[isec]; 653 if ('\0' == sec) 654 continue; 655 prio = sec_prios[sec - '1']; 656 if (NULL == req->q.arch) { 657 archprio = 658 (NULL == (arch = strchr( 659 r[i].file + isec, '/'))) ? 3 : 660 (NULL == (archend = strchr( 661 arch + 1, '/'))) ? 0 : 662 strncmp(arch, "amd64/", 663 archend - arch) ? 2 : 1; 664 if (archprio < archpriouse) { 665 archpriouse = archprio; 666 priouse = prio; 667 iuse = i; 668 continue; 669 } 670 if (archprio > archpriouse) 671 continue; 672 } 673 if (prio >= priouse) 674 continue; 675 priouse = prio; 676 iuse = i; 677 } 678 resp_show(req, r[iuse].file); 679 } 680 681 resp_end_html(); 682 } 683 684 static void 685 catman(const struct req *req, const char *file) 686 { 687 FILE *f; 688 size_t len; 689 int i; 690 char *p; 691 int italic, bold; 692 693 if (NULL == (f = fopen(file, "r"))) { 694 puts("<P>You specified an invalid manual file.</P>"); 695 return; 696 } 697 698 puts("<DIV CLASS=\"catman\">\n" 699 "<PRE>"); 700 701 while (NULL != (p = fgetln(f, &len))) { 702 bold = italic = 0; 703 for (i = 0; i < (int)len - 1; i++) { 704 /* 705 * This means that the catpage is out of state. 706 * Ignore it and keep going (although the 707 * catpage is bogus). 708 */ 709 710 if ('\b' == p[i] || '\n' == p[i]) 711 continue; 712 713 /* 714 * Print a regular character. 715 * Close out any bold/italic scopes. 716 * If we're in back-space mode, make sure we'll 717 * have something to enter when we backspace. 718 */ 719 720 if ('\b' != p[i + 1]) { 721 if (italic) 722 printf("</I>"); 723 if (bold) 724 printf("</B>"); 725 italic = bold = 0; 726 html_putchar(p[i]); 727 continue; 728 } else if (i + 2 >= (int)len) 729 continue; 730 731 /* Italic mode. */ 732 733 if ('_' == p[i]) { 734 if (bold) 735 printf("</B>"); 736 if ( ! italic) 737 printf("<I>"); 738 bold = 0; 739 italic = 1; 740 i += 2; 741 html_putchar(p[i]); 742 continue; 743 } 744 745 /* 746 * Handle funny behaviour troff-isms. 747 * These grok'd from the original man2html.c. 748 */ 749 750 if (('+' == p[i] && 'o' == p[i + 2]) || 751 ('o' == p[i] && '+' == p[i + 2]) || 752 ('|' == p[i] && '=' == p[i + 2]) || 753 ('=' == p[i] && '|' == p[i + 2]) || 754 ('*' == p[i] && '=' == p[i + 2]) || 755 ('=' == p[i] && '*' == p[i + 2]) || 756 ('*' == p[i] && '|' == p[i + 2]) || 757 ('|' == p[i] && '*' == p[i + 2])) { 758 if (italic) 759 printf("</I>"); 760 if (bold) 761 printf("</B>"); 762 italic = bold = 0; 763 putchar('*'); 764 i += 2; 765 continue; 766 } else if (('|' == p[i] && '-' == p[i + 2]) || 767 ('-' == p[i] && '|' == p[i + 1]) || 768 ('+' == p[i] && '-' == p[i + 1]) || 769 ('-' == p[i] && '+' == p[i + 1]) || 770 ('+' == p[i] && '|' == p[i + 1]) || 771 ('|' == p[i] && '+' == p[i + 1])) { 772 if (italic) 773 printf("</I>"); 774 if (bold) 775 printf("</B>"); 776 italic = bold = 0; 777 putchar('+'); 778 i += 2; 779 continue; 780 } 781 782 /* Bold mode. */ 783 784 if (italic) 785 printf("</I>"); 786 if ( ! bold) 787 printf("<B>"); 788 bold = 1; 789 italic = 0; 790 i += 2; 791 html_putchar(p[i]); 792 } 793 794 /* 795 * Clean up the last character. 796 * We can get to a newline; don't print that. 797 */ 798 799 if (italic) 800 printf("</I>"); 801 if (bold) 802 printf("</B>"); 803 804 if (i == (int)len - 1 && '\n' != p[i]) 805 html_putchar(p[i]); 806 807 putchar('\n'); 808 } 809 810 puts("</PRE>\n" 811 "</DIV>"); 812 813 fclose(f); 814 } 815 816 static void 817 format(const struct req *req, const char *file) 818 { 819 struct mparse *mp; 820 struct mchars *mchars; 821 struct mdoc *mdoc; 822 struct man *man; 823 void *vp; 824 char *opts; 825 int fd; 826 int usepath; 827 828 if (-1 == (fd = open(file, O_RDONLY, 0))) { 829 puts("<P>You specified an invalid manual file.</P>"); 830 return; 831 } 832 833 mchars = mchars_alloc(); 834 mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, 835 mchars, req->q.manpath); 836 mparse_readfd(mp, fd, file); 837 close(fd); 838 839 usepath = strcmp(req->q.manpath, req->p[0]); 840 mandoc_asprintf(&opts, 841 "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s", 842 scriptname, 843 req->q.arch ? "&arch=" : "", 844 req->q.arch ? req->q.arch : "", 845 usepath ? "&manpath=" : "", 846 usepath ? req->q.manpath : ""); 847 848 mparse_result(mp, &mdoc, &man, NULL); 849 if (NULL == man && NULL == mdoc) { 850 fprintf(stderr, "fatal mandoc error: %s/%s\n", 851 req->q.manpath, file); 852 pg_error_internal(); 853 mparse_free(mp); 854 mchars_free(mchars); 855 return; 856 } 857 858 vp = html_alloc(mchars, opts); 859 860 if (NULL != mdoc) 861 html_mdoc(vp, mdoc); 862 else 863 html_man(vp, man); 864 865 html_free(vp); 866 mparse_free(mp); 867 mchars_free(mchars); 868 free(opts); 869 } 870 871 static void 872 resp_show(const struct req *req, const char *file) 873 { 874 875 if ('.' == file[0] && '/' == file[1]) 876 file += 2; 877 878 if ('c' == *file) 879 catman(req, file); 880 else 881 format(req, file); 882 } 883 884 static void 885 pg_show(struct req *req, const char *fullpath) 886 { 887 char *manpath; 888 const char *file; 889 890 if ((file = strchr(fullpath, '/')) == NULL) { 891 pg_error_badrequest( 892 "You did not specify a page to show."); 893 return; 894 } 895 manpath = mandoc_strndup(fullpath, file - fullpath); 896 file++; 897 898 if ( ! validate_manpath(req, manpath)) { 899 pg_error_badrequest( 900 "You specified an invalid manpath."); 901 free(manpath); 902 return; 903 } 904 905 /* 906 * Begin by chdir()ing into the manpath. 907 * This way we can pick up the database files, which are 908 * relative to the manpath root. 909 */ 910 911 if (chdir(manpath) == -1) { 912 fprintf(stderr, "chdir %s: %s\n", 913 manpath, strerror(errno)); 914 pg_error_internal(); 915 free(manpath); 916 return; 917 } 918 919 if (strcmp(manpath, "mandoc")) { 920 free(req->q.manpath); 921 req->q.manpath = manpath; 922 } else 923 free(manpath); 924 925 if ( ! validate_filename(file)) { 926 pg_error_badrequest( 927 "You specified an invalid manual file."); 928 return; 929 } 930 931 resp_begin_html(200, NULL); 932 resp_searchform(req); 933 resp_show(req, file); 934 resp_end_html(); 935 } 936 937 static void 938 pg_search(const struct req *req) 939 { 940 struct mansearch search; 941 struct manpaths paths; 942 struct manpage *res; 943 char **argv; 944 char *query, *rp, *wp; 945 size_t ressz; 946 int argc; 947 948 /* 949 * Begin by chdir()ing into the root of the manpath. 950 * This way we can pick up the database files, which are 951 * relative to the manpath root. 952 */ 953 954 if (-1 == (chdir(req->q.manpath))) { 955 fprintf(stderr, "chdir %s: %s\n", 956 req->q.manpath, strerror(errno)); 957 pg_error_internal(); 958 return; 959 } 960 961 search.arch = req->q.arch; 962 search.sec = req->q.sec; 963 search.outkey = "Nd"; 964 search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR; 965 search.firstmatch = 1; 966 967 paths.sz = 1; 968 paths.paths = mandoc_malloc(sizeof(char *)); 969 paths.paths[0] = mandoc_strdup("."); 970 971 /* 972 * Break apart at spaces with backslash-escaping. 973 */ 974 975 argc = 0; 976 argv = NULL; 977 rp = query = mandoc_strdup(req->q.query); 978 for (;;) { 979 while (isspace((unsigned char)*rp)) 980 rp++; 981 if (*rp == '\0') 982 break; 983 argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *)); 984 argv[argc++] = wp = rp; 985 for (;;) { 986 if (isspace((unsigned char)*rp)) { 987 *wp = '\0'; 988 rp++; 989 break; 990 } 991 if (rp[0] == '\\' && rp[1] != '\0') 992 rp++; 993 if (wp != rp) 994 *wp = *rp; 995 if (*rp == '\0') 996 break; 997 wp++; 998 rp++; 999 } 1000 } 1001 1002 if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz)) 1003 pg_noresult(req, "You entered an invalid query."); 1004 else if (0 == ressz) 1005 pg_noresult(req, "No results found."); 1006 else 1007 pg_searchres(req, res, ressz); 1008 1009 free(query); 1010 mansearch_free(res, ressz); 1011 free(paths.paths[0]); 1012 free(paths.paths); 1013 } 1014 1015 int 1016 main(void) 1017 { 1018 struct req req; 1019 struct itimerval itimer; 1020 const char *path; 1021 const char *querystring; 1022 int i; 1023 1024 /* Poor man's ReDoS mitigation. */ 1025 1026 itimer.it_value.tv_sec = 2; 1027 itimer.it_value.tv_usec = 0; 1028 itimer.it_interval.tv_sec = 2; 1029 itimer.it_interval.tv_usec = 0; 1030 if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) { 1031 fprintf(stderr, "setitimer: %s\n", strerror(errno)); 1032 pg_error_internal(); 1033 return(EXIT_FAILURE); 1034 } 1035 1036 /* Scan our run-time environment. */ 1037 1038 if (NULL == (scriptname = getenv("SCRIPT_NAME"))) 1039 scriptname = ""; 1040 1041 if ( ! validate_urifrag(scriptname)) { 1042 fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n", 1043 scriptname); 1044 pg_error_internal(); 1045 return(EXIT_FAILURE); 1046 } 1047 1048 /* 1049 * First we change directory into the MAN_DIR so that 1050 * subsequent scanning for manpath directories is rooted 1051 * relative to the same position. 1052 */ 1053 1054 if (-1 == chdir(MAN_DIR)) { 1055 fprintf(stderr, "MAN_DIR: %s: %s\n", 1056 MAN_DIR, strerror(errno)); 1057 pg_error_internal(); 1058 return(EXIT_FAILURE); 1059 } 1060 1061 memset(&req, 0, sizeof(struct req)); 1062 pathgen(&req); 1063 1064 /* Next parse out the query string. */ 1065 1066 if (NULL != (querystring = getenv("QUERY_STRING"))) 1067 http_parse(&req, querystring); 1068 1069 if (req.q.manpath == NULL) 1070 req.q.manpath = mandoc_strdup(req.p[0]); 1071 else if ( ! validate_manpath(&req, req.q.manpath)) { 1072 pg_error_badrequest( 1073 "You specified an invalid manpath."); 1074 return(EXIT_FAILURE); 1075 } 1076 1077 if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) { 1078 pg_error_badrequest( 1079 "You specified an invalid architecture."); 1080 return(EXIT_FAILURE); 1081 } 1082 1083 /* Dispatch to the three different pages. */ 1084 1085 path = getenv("PATH_INFO"); 1086 if (NULL == path) 1087 path = ""; 1088 else if ('/' == *path) 1089 path++; 1090 1091 if ('\0' != *path) 1092 pg_show(&req, path); 1093 else if (NULL != req.q.query) 1094 pg_search(&req); 1095 else 1096 pg_index(&req); 1097 1098 free(req.q.manpath); 1099 free(req.q.arch); 1100 free(req.q.sec); 1101 free(req.q.query); 1102 for (i = 0; i < (int)req.psz; i++) 1103 free(req.p[i]); 1104 free(req.p); 1105 return(EXIT_SUCCESS); 1106 } 1107 1108 /* 1109 * Scan for indexable paths. 1110 */ 1111 static void 1112 pathgen(struct req *req) 1113 { 1114 FILE *fp; 1115 char *dp; 1116 size_t dpsz; 1117 1118 if (NULL == (fp = fopen("manpath.conf", "r"))) { 1119 fprintf(stderr, "%s/manpath.conf: %s\n", 1120 MAN_DIR, strerror(errno)); 1121 pg_error_internal(); 1122 exit(EXIT_FAILURE); 1123 } 1124 1125 while (NULL != (dp = fgetln(fp, &dpsz))) { 1126 if ('\n' == dp[dpsz - 1]) 1127 dpsz--; 1128 req->p = mandoc_realloc(req->p, 1129 (req->psz + 1) * sizeof(char *)); 1130 dp = mandoc_strndup(dp, dpsz); 1131 if ( ! validate_urifrag(dp)) { 1132 fprintf(stderr, "%s/manpath.conf contains " 1133 "unsafe path \"%s\"\n", MAN_DIR, dp); 1134 pg_error_internal(); 1135 exit(EXIT_FAILURE); 1136 } 1137 if (NULL != strchr(dp, '/')) { 1138 fprintf(stderr, "%s/manpath.conf contains " 1139 "path with slash \"%s\"\n", MAN_DIR, dp); 1140 pg_error_internal(); 1141 exit(EXIT_FAILURE); 1142 } 1143 req->p[req->psz++] = dp; 1144 } 1145 1146 if ( req->p == NULL ) { 1147 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR); 1148 pg_error_internal(); 1149 exit(EXIT_FAILURE); 1150 } 1151 } 1152