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