xref: /minix3/external/bsd/mdocml/dist/cgi.c (revision b5e2faaaaf60a8b9a02f8d72f64caa56a87eb312)
1 /*	$Vendor-Id: cgi.c,v 1.42 2012/03/24 01:46:25 kristaps Exp $ */
2 /*
3  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include <sys/param.h>
22 #include <sys/wait.h>
23 
24 #include <assert.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <dirent.h>
28 #include <fcntl.h>
29 #include <limits.h>
30 #include <regex.h>
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <stdint.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "apropos_db.h"
39 #include "mandoc.h"
40 #include "mdoc.h"
41 #include "man.h"
42 #include "main.h"
43 #include "manpath.h"
44 #include "mandocdb.h"
45 
46 #ifdef __linux__
47 # include <db_185.h>
48 #else
49 # include <db.h>
50 #endif
51 
52 enum	page {
53 	PAGE_INDEX,
54 	PAGE_SEARCH,
55 	PAGE_SHOW,
56 	PAGE__MAX
57 };
58 
59 struct	paths {
60 	char		*name;
61 	char		*path;
62 };
63 
64 /*
65  * A query as passed to the search function.
66  */
67 struct	query {
68 	const char	*arch; /* architecture */
69 	const char	*sec; /* manual section */
70 	const char	*expr; /* unparsed expression string */
71 	int		 manroot; /* manroot index (or -1)*/
72 	int		 legacy; /* whether legacy mode */
73 };
74 
75 struct	req {
76 	struct query	 q;
77 	struct paths	*p;
78 	size_t		 psz;
79 	enum page	 page;
80 };
81 
82 static	int		 atou(const char *, unsigned *);
83 static	void		 catman(const struct req *, const char *);
84 static	int	 	 cmp(const void *, const void *);
85 static	void		 format(const struct req *, const char *);
86 static	void		 html_print(const char *);
87 static	void		 html_printquery(const struct req *);
88 static	void		 html_putchar(char);
89 static	int 		 http_decode(char *);
90 static	void		 http_parse(struct req *, char *);
91 static	void		 http_print(const char *);
92 static	void 		 http_putchar(char);
93 static	void		 http_printquery(const struct req *);
94 static	int		 pathstop(DIR *);
95 static	void		 pathgen(DIR *, char *, struct req *);
96 static	void		 pg_index(const struct req *, char *);
97 static	void		 pg_search(const struct req *, char *);
98 static	void		 pg_show(const struct req *, char *);
99 static	void		 resp_bad(void);
100 static	void		 resp_baddb(void);
101 static	void		 resp_error400(void);
102 static	void		 resp_error404(const char *);
103 static	void		 resp_begin_html(int, const char *);
104 static	void		 resp_begin_http(int, const char *);
105 static	void		 resp_end_html(void);
106 static	void		 resp_index(const struct req *);
107 static	void		 resp_search(struct res *, size_t, void *);
108 static	void		 resp_searchform(const struct req *);
109 
110 static	const char	 *progname; /* cgi script name */
111 static	const char	 *cache; /* cache directory */
112 static	const char	 *css; /* css directory */
113 static	const char	 *host; /* hostname */
114 
115 static	const char * const pages[PAGE__MAX] = {
116 	"index", /* PAGE_INDEX */
117 	"search", /* PAGE_SEARCH */
118 	"show", /* PAGE_SHOW */
119 };
120 
121 /*
122  * This is just OpenBSD's strtol(3) suggestion.
123  * I use it instead of strtonum(3) for portability's sake.
124  */
125 static int
126 atou(const char *buf, unsigned *v)
127 {
128 	char		*ep;
129 	long		 lval;
130 
131 	errno = 0;
132 	lval = strtol(buf, &ep, 10);
133 	if (buf[0] == '\0' || *ep != '\0')
134 		return(0);
135 	if ((errno == ERANGE && (lval == LONG_MAX ||
136 					lval == LONG_MIN)) ||
137 			(lval > INT_MAX || lval < 0))
138 		return(0);
139 
140 	*v = (unsigned int)lval;
141 	return(1);
142 }
143 
144 /*
145  * Print a character, escaping HTML along the way.
146  * This will pass non-ASCII straight to output: be warned!
147  */
148 static void
149 html_putchar(char c)
150 {
151 
152 	switch (c) {
153 	case ('"'):
154 		printf("&quote;");
155 		break;
156 	case ('&'):
157 		printf("&amp;");
158 		break;
159 	case ('>'):
160 		printf("&gt;");
161 		break;
162 	case ('<'):
163 		printf("&lt;");
164 		break;
165 	default:
166 		putchar((unsigned char)c);
167 		break;
168 	}
169 }
170 static void
171 http_printquery(const struct req *req)
172 {
173 
174 	printf("&expr=");
175 	http_print(req->q.expr ? req->q.expr : "");
176 	printf("&sec=");
177 	http_print(req->q.sec ? req->q.sec : "");
178 	printf("&arch=");
179 	http_print(req->q.arch ? req->q.arch : "");
180 }
181 
182 
183 static void
184 html_printquery(const struct req *req)
185 {
186 
187 	printf("&amp;expr=");
188 	html_print(req->q.expr ? req->q.expr : "");
189 	printf("&amp;sec=");
190 	html_print(req->q.sec ? req->q.sec : "");
191 	printf("&amp;arch=");
192 	html_print(req->q.arch ? req->q.arch : "");
193 }
194 
195 static void
196 http_print(const char *p)
197 {
198 
199 	if (NULL == p)
200 		return;
201 	while ('\0' != *p)
202 		http_putchar(*p++);
203 }
204 
205 /*
206  * Call through to html_putchar().
207  * Accepts NULL strings.
208  */
209 static void
210 html_print(const char *p)
211 {
212 
213 	if (NULL == p)
214 		return;
215 	while ('\0' != *p)
216 		html_putchar(*p++);
217 }
218 
219 /*
220  * Parse out key-value pairs from an HTTP request variable.
221  * This can be either a cookie or a POST/GET string, although man.cgi
222  * uses only GET for simplicity.
223  */
224 static void
225 http_parse(struct req *req, char *p)
226 {
227 	char            *key, *val, *manroot;
228 	int		 i, legacy;
229 
230 	memset(&req->q, 0, sizeof(struct query));
231 
232 	legacy = -1;
233 	manroot = NULL;
234 
235 	while ('\0' != *p) {
236 		key = p;
237 		val = NULL;
238 
239 		p += (int)strcspn(p, ";&");
240 		if ('\0' != *p)
241 			*p++ = '\0';
242 		if (NULL != (val = strchr(key, '=')))
243 			*val++ = '\0';
244 
245 		if ('\0' == *key || NULL == val || '\0' == *val)
246 			continue;
247 
248 		/* Just abort handling. */
249 
250 		if ( ! http_decode(key))
251 			break;
252 		if (NULL != val && ! http_decode(val))
253 			break;
254 
255 		if (0 == strcmp(key, "expr"))
256 			req->q.expr = val;
257 		else if (0 == strcmp(key, "query"))
258 			req->q.expr = val;
259 		else if (0 == strcmp(key, "sec"))
260 			req->q.sec = val;
261 		else if (0 == strcmp(key, "sektion"))
262 			req->q.sec = val;
263 		else if (0 == strcmp(key, "arch"))
264 			req->q.arch = val;
265 		else if (0 == strcmp(key, "manpath"))
266 			manroot = val;
267 		else if (0 == strcmp(key, "apropos"))
268 			legacy = 0 == strcmp(val, "0");
269 	}
270 
271 	/* Test for old man.cgi compatibility mode. */
272 
273 	req->q.legacy = legacy > 0;
274 
275 	/*
276 	 * Section "0" means no section when in legacy mode.
277 	 * For some man.cgi scripts, "default" arch is none.
278 	 */
279 
280 	if (req->q.legacy && NULL != req->q.sec)
281 		if (0 == strcmp(req->q.sec, "0"))
282 			req->q.sec = NULL;
283 	if (req->q.legacy && NULL != req->q.arch)
284 		if (0 == strcmp(req->q.arch, "default"))
285 			req->q.arch = NULL;
286 
287 	/* Default to first manroot. */
288 
289 	if (NULL != manroot) {
290 		for (i = 0; i < (int)req->psz; i++)
291 			if (0 == strcmp(req->p[i].name, manroot))
292 				break;
293 		req->q.manroot = i < (int)req->psz ? i : -1;
294 	}
295 }
296 
297 static void
298 http_putchar(char c)
299 {
300 
301 	if (isalnum((unsigned char)c)) {
302 		putchar((unsigned char)c);
303 		return;
304 	} else if (' ' == c) {
305 		putchar('+');
306 		return;
307 	}
308 	printf("%%%.2x", c);
309 }
310 
311 /*
312  * HTTP-decode a string.  The standard explanation is that this turns
313  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
314  * over the allocated string.
315  */
316 static int
317 http_decode(char *p)
318 {
319 	char             hex[3];
320 	int              c;
321 
322 	hex[2] = '\0';
323 
324 	for ( ; '\0' != *p; p++) {
325 		if ('%' == *p) {
326 			if ('\0' == (hex[0] = *(p + 1)))
327 				return(0);
328 			if ('\0' == (hex[1] = *(p + 2)))
329 				return(0);
330 			if (1 != sscanf(hex, "%x", &c))
331 				return(0);
332 			if ('\0' == c)
333 				return(0);
334 
335 			*p = (char)c;
336 			memmove(p + 1, p + 3, strlen(p + 3) + 1);
337 		} else
338 			*p = '+' == *p ? ' ' : *p;
339 	}
340 
341 	*p = '\0';
342 	return(1);
343 }
344 
345 static void
346 resp_begin_http(int code, const char *msg)
347 {
348 
349 	if (200 != code)
350 		printf("Status: %d %s\n", code, msg);
351 
352 	puts("Content-Type: text/html; charset=utf-8\n"
353 	     "Cache-Control: no-cache\n"
354 	     "Pragma: no-cache\n"
355 	     "");
356 
357 	fflush(stdout);
358 }
359 
360 static void
361 resp_begin_html(int code, const char *msg)
362 {
363 
364 	resp_begin_http(code, msg);
365 
366 	printf("<!DOCTYPE HTML PUBLIC "
367 	       " \"-//W3C//DTD HTML 4.01//EN\""
368 	       " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
369 	       "<HTML>\n"
370 	       "<HEAD>\n"
371 	       "<META HTTP-EQUIV=\"Content-Type\""
372 	       " CONTENT=\"text/html; charset=utf-8\">\n"
373 	       "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
374 	       " TYPE=\"text/css\" media=\"all\">\n"
375 	       "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
376 	       " TYPE=\"text/css\" media=\"all\">\n"
377 	       "<TITLE>System Manpage Reference</TITLE>\n"
378 	       "</HEAD>\n"
379 	       "<BODY>\n"
380 	       "<!-- Begin page content. //-->\n", css, css);
381 }
382 
383 static void
384 resp_end_html(void)
385 {
386 
387 	puts("</BODY>\n"
388 	     "</HTML>");
389 }
390 
391 static void
392 resp_searchform(const struct req *req)
393 {
394 	int		 i;
395 
396 	puts("<!-- Begin search form. //-->");
397 	printf("<DIV ID=\"mancgi\">\n"
398 	       "<FORM ACTION=\"%s/search.html\" METHOD=\"get\">\n"
399 	       "<FIELDSET>\n"
400 	       "<LEGEND>Search Parameters</LEGEND>\n"
401 	       "<INPUT TYPE=\"submit\" "
402 	       " VALUE=\"Search\"> for manuals satisfying \n"
403 	       "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"",
404 	       progname);
405 	html_print(req->q.expr ? req->q.expr : "");
406 	printf("\">, section "
407 	       "<INPUT TYPE=\"text\""
408 	       " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
409 	html_print(req->q.sec ? req->q.sec : "");
410 	printf("\">, arch "
411 	       "<INPUT TYPE=\"text\""
412 	       " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
413 	html_print(req->q.arch ? req->q.arch : "");
414 	printf("\">");
415 	if (req->psz > 1) {
416 		puts(", <SELECT NAME=\"manpath\">");
417 		for (i = 0; i < (int)req->psz; i++) {
418 			printf("<OPTION %s VALUE=\"",
419 				(i == req->q.manroot) ||
420 				(0 == i && -1 == req->q.manroot) ?
421 				"SELECTED=\"selected\"" : "");
422 			html_print(req->p[i].name);
423 			printf("\">");
424 			html_print(req->p[i].name);
425 			puts("</OPTION>");
426 		}
427 		puts("</SELECT>");
428 	}
429 	puts(".\n"
430 	     "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
431 	     "</FIELDSET>\n"
432 	     "</FORM>\n"
433 	     "</DIV>");
434 	puts("<!-- End search form. //-->");
435 }
436 
437 static void
438 resp_index(const struct req *req)
439 {
440 
441 	resp_begin_html(200, NULL);
442 	resp_searchform(req);
443 	resp_end_html();
444 }
445 
446 static void
447 resp_error400(void)
448 {
449 
450 	resp_begin_html(400, "Query Malformed");
451 	printf("<H1>Malformed Query</H1>\n"
452 	       "<P>\n"
453 	       "The query your entered was malformed.\n"
454 	       "Try again from the\n"
455 	       "<A HREF=\"%s/index.html\">main page</A>.\n"
456 	       "</P>", progname);
457 	resp_end_html();
458 }
459 
460 static void
461 resp_error404(const char *page)
462 {
463 
464 	resp_begin_html(404, "Not Found");
465 	puts("<H1>Page Not Found</H1>\n"
466 	     "<P>\n"
467 	     "The page you're looking for, ");
468 	printf("<B>");
469 	html_print(page);
470 	printf("</B>,\n"
471 	       "could not be found.\n"
472 	       "Try searching from the\n"
473 	       "<A HREF=\"%s/index.html\">main page</A>.\n"
474 	       "</P>", progname);
475 	resp_end_html();
476 }
477 
478 static void
479 resp_bad(void)
480 {
481 	resp_begin_html(500, "Internal Server Error");
482 	puts("<P>Generic badness happened.</P>");
483 	resp_end_html();
484 }
485 
486 static void
487 resp_baddb(void)
488 {
489 
490 	resp_begin_html(500, "Internal Server Error");
491 	puts("<P>Your database is broken.</P>");
492 	resp_end_html();
493 }
494 
495 static void
496 resp_search(struct res *r, size_t sz, void *arg)
497 {
498 	size_t		 i, matched;
499 	const struct req *req;
500 
501 	req = (const struct req *)arg;
502 
503 	if (sz > 0)
504 		assert(req->q.manroot >= 0);
505 
506 	for (matched = i = 0; i < sz; i++)
507 		if (r[i].matched)
508 			matched++;
509 
510 	if (1 == matched) {
511 		for (i = 0; i < sz; i++)
512 			if (r[i].matched)
513 				break;
514 		/*
515 		 * If we have just one result, then jump there now
516 		 * without any delay.
517 		 */
518 		puts("Status: 303 See Other");
519 		printf("Location: http://%s%s/show/%d/%u/%u.html?",
520 				host, progname, req->q.manroot,
521 				r[i].volume, r[i].rec);
522 		http_printquery(req);
523 		puts("\n"
524 		     "Content-Type: text/html; charset=utf-8\n");
525 		return;
526 	}
527 
528 	resp_begin_html(200, NULL);
529 	resp_searchform(req);
530 
531 	puts("<DIV CLASS=\"results\">");
532 
533 	if (0 == matched) {
534 		puts("<P>\n"
535 		     "No results found.\n"
536 		     "</P>\n"
537 		     "</DIV>");
538 		resp_end_html();
539 		return;
540 	}
541 
542 	qsort(r, sz, sizeof(struct res), cmp);
543 
544 	puts("<TABLE>");
545 
546 	for (i = 0; i < sz; i++) {
547 		if ( ! r[i].matched)
548 			continue;
549 		printf("<TR>\n"
550 		       "<TD CLASS=\"title\">\n"
551 		       "<A HREF=\"%s/show/%d/%u/%u.html?",
552 				progname, req->q.manroot,
553 				r[i].volume, r[i].rec);
554 		html_printquery(req);
555 		printf("\">");
556 		html_print(r[i].title);
557 		putchar('(');
558 		html_print(r[i].cat);
559 		if (r[i].arch && '\0' != *r[i].arch) {
560 			putchar('/');
561 			html_print(r[i].arch);
562 		}
563 		printf(")</A>\n"
564 		       "</TD>\n"
565 		       "<TD CLASS=\"desc\">");
566 		html_print(r[i].desc);
567 		puts("</TD>\n"
568 		     "</TR>");
569 	}
570 
571 	puts("</TABLE>\n"
572 	     "</DIV>");
573 	resp_end_html();
574 }
575 
576 /* ARGSUSED */
577 static void
578 pg_index(const struct req *req, char *path)
579 {
580 
581 	resp_index(req);
582 }
583 
584 static void
585 catman(const struct req *req, const char *file)
586 {
587 	FILE		*f;
588 	size_t		 len;
589 	int		 i;
590 	char		*p;
591 	int		 italic, bold;
592 
593 	if (NULL == (f = fopen(file, "r"))) {
594 		resp_baddb();
595 		return;
596 	}
597 
598 	resp_begin_html(200, NULL);
599 	resp_searchform(req);
600 	puts("<DIV CLASS=\"catman\">\n"
601 	     "<PRE>");
602 
603 	while (NULL != (p = fgetln(f, &len))) {
604 		bold = italic = 0;
605 		for (i = 0; i < (int)len - 1; i++) {
606 			/*
607 			 * This means that the catpage is out of state.
608 			 * Ignore it and keep going (although the
609 			 * catpage is bogus).
610 			 */
611 
612 			if ('\b' == p[i] || '\n' == p[i])
613 				continue;
614 
615 			/*
616 			 * Print a regular character.
617 			 * Close out any bold/italic scopes.
618 			 * If we're in back-space mode, make sure we'll
619 			 * have something to enter when we backspace.
620 			 */
621 
622 			if ('\b' != p[i + 1]) {
623 				if (italic)
624 					printf("</I>");
625 				if (bold)
626 					printf("</B>");
627 				italic = bold = 0;
628 				html_putchar(p[i]);
629 				continue;
630 			} else if (i + 2 >= (int)len)
631 				continue;
632 
633 			/* Italic mode. */
634 
635 			if ('_' == p[i]) {
636 				if (bold)
637 					printf("</B>");
638 				if ( ! italic)
639 					printf("<I>");
640 				bold = 0;
641 				italic = 1;
642 				i += 2;
643 				html_putchar(p[i]);
644 				continue;
645 			}
646 
647 			/*
648 			 * Handle funny behaviour troff-isms.
649 			 * These grok'd from the original man2html.c.
650 			 */
651 
652 			if (('+' == p[i] && 'o' == p[i + 2]) ||
653 					('o' == p[i] && '+' == p[i + 2]) ||
654 					('|' == p[i] && '=' == p[i + 2]) ||
655 					('=' == p[i] && '|' == p[i + 2]) ||
656 					('*' == p[i] && '=' == p[i + 2]) ||
657 					('=' == p[i] && '*' == p[i + 2]) ||
658 					('*' == p[i] && '|' == p[i + 2]) ||
659 					('|' == p[i] && '*' == p[i + 2]))  {
660 				if (italic)
661 					printf("</I>");
662 				if (bold)
663 					printf("</B>");
664 				italic = bold = 0;
665 				putchar('*');
666 				i += 2;
667 				continue;
668 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
669 					('-' == p[i] && '|' == p[i + 1]) ||
670 					('+' == p[i] && '-' == p[i + 1]) ||
671 					('-' == p[i] && '+' == p[i + 1]) ||
672 					('+' == p[i] && '|' == p[i + 1]) ||
673 					('|' == p[i] && '+' == p[i + 1]))  {
674 				if (italic)
675 					printf("</I>");
676 				if (bold)
677 					printf("</B>");
678 				italic = bold = 0;
679 				putchar('+');
680 				i += 2;
681 				continue;
682 			}
683 
684 			/* Bold mode. */
685 
686 			if (italic)
687 				printf("</I>");
688 			if ( ! bold)
689 				printf("<B>");
690 			bold = 1;
691 			italic = 0;
692 			i += 2;
693 			html_putchar(p[i]);
694 		}
695 
696 		/*
697 		 * Clean up the last character.
698 		 * We can get to a newline; don't print that.
699 		 */
700 
701 		if (italic)
702 			printf("</I>");
703 		if (bold)
704 			printf("</B>");
705 
706 		if (i == (int)len - 1 && '\n' != p[i])
707 			html_putchar(p[i]);
708 
709 		putchar('\n');
710 	}
711 
712 	puts("</PRE>\n"
713 	     "</DIV>\n"
714 	     "</BODY>\n"
715 	     "</HTML>");
716 
717 	fclose(f);
718 }
719 
720 static void
721 format(const struct req *req, const char *file)
722 {
723 	struct mparse	*mp;
724 	int		 fd;
725 	struct mdoc	*mdoc;
726 	struct man	*man;
727 	void		*vp;
728 	enum mandoclevel rc;
729 	char		 opts[MAXPATHLEN + 128];
730 
731 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
732 		resp_baddb();
733 		return;
734 	}
735 
736 	mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
737 	rc = mparse_readfd(mp, fd, file);
738 	close(fd);
739 
740 	if (rc >= MANDOCLEVEL_FATAL) {
741 		resp_baddb();
742 		return;
743 	}
744 
745 	snprintf(opts, sizeof(opts), "fragment,"
746 			"man=%s/search.html?sec=%%S&expr=%%N,"
747 			/*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
748 			progname);
749 
750 	mparse_result(mp, &mdoc, &man);
751 	if (NULL == man && NULL == mdoc) {
752 		resp_baddb();
753 		mparse_free(mp);
754 		return;
755 	}
756 
757 	resp_begin_html(200, NULL);
758 	resp_searchform(req);
759 
760 	vp = html_alloc(opts);
761 
762 	if (NULL != mdoc)
763 		html_mdoc(vp, mdoc);
764 	else
765 		html_man(vp, man);
766 
767 	puts("</BODY>\n"
768 	     "</HTML>");
769 
770 	html_free(vp);
771 	mparse_free(mp);
772 }
773 
774 static void
775 pg_show(const struct req *req, char *path)
776 {
777 	struct manpaths	 ps;
778 	size_t		 sz;
779 	char		*sub;
780 	char		 file[MAXPATHLEN];
781 	const char	*cp;
782 	int		 rc, catm;
783 	unsigned int	 vol, rec, mr;
784 	DB		*idx;
785 	DBT		 key, val;
786 
787 	idx = NULL;
788 
789 	/* Parse out mroot, volume, and record from the path. */
790 
791 	if (NULL == path || NULL == (sub = strchr(path, '/'))) {
792 		resp_error400();
793 		return;
794 	}
795 	*sub++ = '\0';
796 	if ( ! atou(path, &mr)) {
797 		resp_error400();
798 		return;
799 	}
800 	path = sub;
801 	if (NULL == (sub = strchr(path, '/'))) {
802 		resp_error400();
803 		return;
804 	}
805 	*sub++ = '\0';
806 	if ( ! atou(path, &vol) || ! atou(sub, &rec)) {
807 		resp_error400();
808 		return;
809 	} else if (mr >= (unsigned int)req->psz) {
810 		resp_error400();
811 		return;
812 	}
813 
814 	/*
815 	 * Begin by chdir()ing into the manroot.
816 	 * This way we can pick up the database files, which are
817 	 * relative to the manpath root.
818 	 */
819 
820 	if (-1 == chdir(req->p[(int)mr].path)) {
821 		perror(req->p[(int)mr].path);
822 		resp_baddb();
823 		return;
824 	}
825 
826 	memset(&ps, 0, sizeof(struct manpaths));
827 	manpath_manconf(&ps, "etc/catman.conf");
828 
829 	if (vol >= (unsigned int)ps.sz) {
830 		resp_error400();
831 		goto out;
832 	}
833 
834 	sz = strlcpy(file, ps.paths[vol], MAXPATHLEN);
835 	assert(sz < MAXPATHLEN);
836 	strlcat(file, "/", MAXPATHLEN);
837 	strlcat(file, MANDOC_IDX, MAXPATHLEN);
838 
839 	/* Open the index recno(3) database. */
840 
841 	idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
842 	if (NULL == idx) {
843 		perror(file);
844 		resp_baddb();
845 		goto out;
846 	}
847 
848 	key.data = &rec;
849 	key.size = 4;
850 
851 	if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
852 		rc < 0 ? resp_baddb() : resp_error400();
853 		goto out;
854 	} else if (0 == val.size) {
855 		resp_baddb();
856 		goto out;
857 	}
858 
859 	cp = (char *)val.data;
860 	catm = 'c' == *cp++;
861 
862 	if (NULL == memchr(cp, '\0', val.size - 1))
863 		resp_baddb();
864 	else {
865  		file[(int)sz] = '\0';
866  		strlcat(file, "/", MAXPATHLEN);
867  		strlcat(file, cp, MAXPATHLEN);
868 		if (catm)
869 			catman(req, file);
870 		else
871 			format(req, file);
872 	}
873 out:
874 	if (idx)
875 		(*idx->close)(idx);
876 	manpath_free(&ps);
877 }
878 
879 static void
880 pg_search(const struct req *req, char *path)
881 {
882 	size_t		  tt, ressz;
883 	struct manpaths	  ps;
884 	int		  i, sz, rc;
885 	const char	 *ep, *start;
886 	struct res	*res;
887 	char		**cp;
888 	struct opts	  opt;
889 	struct expr	 *expr;
890 
891 	if (req->q.manroot < 0 || 0 == req->psz) {
892 		resp_search(NULL, 0, (void *)req);
893 		return;
894 	}
895 
896 	memset(&opt, 0, sizeof(struct opts));
897 
898 	ep 	 = req->q.expr;
899 	opt.arch = req->q.arch;
900 	opt.cat  = req->q.sec;
901 	rc 	 = -1;
902 	sz 	 = 0;
903 	cp	 = NULL;
904 	ressz	 = 0;
905 	res	 = NULL;
906 
907 	/*
908 	 * Begin by chdir()ing into the root of the manpath.
909 	 * This way we can pick up the database files, which are
910 	 * relative to the manpath root.
911 	 */
912 
913 	assert(req->q.manroot < (int)req->psz);
914 	if (-1 == (chdir(req->p[req->q.manroot].path))) {
915 		perror(req->p[req->q.manroot].path);
916 		resp_search(NULL, 0, (void *)req);
917 		return;
918 	}
919 
920 	memset(&ps, 0, sizeof(struct manpaths));
921 	manpath_manconf(&ps, "etc/catman.conf");
922 
923 	/*
924 	 * Poor man's tokenisation: just break apart by spaces.
925 	 * Yes, this is half-ass.  But it works for now.
926 	 */
927 
928 	while (ep && isspace((unsigned char)*ep))
929 		ep++;
930 
931 	while (ep && '\0' != *ep) {
932 		cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
933 		start = ep;
934 		while ('\0' != *ep && ! isspace((unsigned char)*ep))
935 			ep++;
936 		cp[sz] = mandoc_malloc((ep - start) + 1);
937 		memcpy(cp[sz], start, ep - start);
938 		cp[sz++][ep - start] = '\0';
939 		while (isspace((unsigned char)*ep))
940 			ep++;
941 	}
942 
943 	/*
944 	 * Pump down into apropos backend.
945 	 * The resp_search() function is called with the results.
946 	 */
947 
948 	expr = req->q.legacy ?
949 		termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
950 
951 	if (NULL != expr)
952 		rc = apropos_search
953 			(ps.sz, ps.paths, &opt, expr, tt,
954 			 (void *)req, &ressz, &res, resp_search);
955 
956 	/* ...unless errors occured. */
957 
958 	if (0 == rc)
959 		resp_baddb();
960 	else if (-1 == rc)
961 		resp_search(NULL, 0, NULL);
962 
963 	for (i = 0; i < sz; i++)
964 		free(cp[i]);
965 
966 	free(cp);
967 	resfree(res, ressz);
968 	exprfree(expr);
969 	manpath_free(&ps);
970 }
971 
972 int
973 main(void)
974 {
975 	int		 i;
976 	char		 buf[MAXPATHLEN];
977 	DIR		*cwd;
978 	struct req	 req;
979 	char		*p, *path, *subpath;
980 
981 	/* Scan our run-time environment. */
982 
983 	if (NULL == (cache = getenv("CACHE_DIR")))
984 		cache = "/cache/man.cgi";
985 
986 	if (NULL == (progname = getenv("SCRIPT_NAME")))
987 		progname = "";
988 
989 	if (NULL == (css = getenv("CSS_DIR")))
990 		css = "";
991 
992 	if (NULL == (host = getenv("HTTP_HOST")))
993 		host = "localhost";
994 
995 	/*
996 	 * First we change directory into the cache directory so that
997 	 * subsequent scanning for manpath directories is rooted
998 	 * relative to the same position.
999 	 */
1000 
1001 	if (-1 == chdir(cache)) {
1002 		perror(cache);
1003 		resp_bad();
1004 		return(EXIT_FAILURE);
1005 	} else if (NULL == (cwd = opendir(cache))) {
1006 		perror(cache);
1007 		resp_bad();
1008 		return(EXIT_FAILURE);
1009 	}
1010 
1011 	memset(&req, 0, sizeof(struct req));
1012 
1013 	strlcpy(buf, ".", MAXPATHLEN);
1014 	pathgen(cwd, buf, &req);
1015 	closedir(cwd);
1016 
1017 	/* Next parse out the query string. */
1018 
1019 	if (NULL != (p = getenv("QUERY_STRING")))
1020 		http_parse(&req, p);
1021 
1022 	/*
1023 	 * Now juggle paths to extract information.
1024 	 * We want to extract our filetype (the file suffix), the
1025 	 * initial path component, then the trailing component(s).
1026 	 * Start with leading subpath component.
1027 	 */
1028 
1029 	subpath = path = NULL;
1030 	req.page = PAGE__MAX;
1031 
1032 	if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
1033 		req.page = PAGE_INDEX;
1034 
1035 	if (NULL != path && '/' == *path && '\0' == *++path)
1036 		req.page = PAGE_INDEX;
1037 
1038 	/* Strip file suffix. */
1039 
1040 	if (NULL != path && NULL != (p = strrchr(path, '.')))
1041 		if (NULL != p && NULL == strchr(p, '/'))
1042 			*p++ = '\0';
1043 
1044 	/* Resolve subpath component. */
1045 
1046 	if (NULL != path && NULL != (subpath = strchr(path, '/')))
1047 		*subpath++ = '\0';
1048 
1049 	/* Map path into one we recognise. */
1050 
1051 	if (NULL != path && '\0' != *path)
1052 		for (i = 0; i < (int)PAGE__MAX; i++)
1053 			if (0 == strcmp(pages[i], path)) {
1054 				req.page = (enum page)i;
1055 				break;
1056 			}
1057 
1058 	/* Route pages. */
1059 
1060 	switch (req.page) {
1061 	case (PAGE_INDEX):
1062 		pg_index(&req, subpath);
1063 		break;
1064 	case (PAGE_SEARCH):
1065 		pg_search(&req, subpath);
1066 		break;
1067 	case (PAGE_SHOW):
1068 		pg_show(&req, subpath);
1069 		break;
1070 	default:
1071 		resp_error404(path);
1072 		break;
1073 	}
1074 
1075 	for (i = 0; i < (int)req.psz; i++) {
1076 		free(req.p[i].path);
1077 		free(req.p[i].name);
1078 	}
1079 
1080 	free(req.p);
1081 	return(EXIT_SUCCESS);
1082 }
1083 
1084 static int
1085 cmp(const void *p1, const void *p2)
1086 {
1087 
1088 	return(strcasecmp(((const struct res *)p1)->title,
1089 				((const struct res *)p2)->title));
1090 }
1091 
1092 /*
1093  * Check to see if an "etc" path consists of a catman.conf file.  If it
1094  * does, that means that the path contains a tree created by catman(8)
1095  * and should be used for indexing.
1096  */
1097 static int
1098 pathstop(DIR *dir)
1099 {
1100 	struct dirent	*d;
1101 
1102 	while (NULL != (d = readdir(dir)))
1103 		if (DT_REG == d->d_type)
1104 			if (0 == strcmp(d->d_name, "catman.conf"))
1105 				return(1);
1106 
1107 	return(0);
1108 }
1109 
1110 /*
1111  * Scan for indexable paths.
1112  * This adds all paths with "etc/catman.conf" to the buffer.
1113  */
1114 static void
1115 pathgen(DIR *dir, char *path, struct req *req)
1116 {
1117 	struct dirent	*d;
1118 	char		*cp;
1119 	DIR		*cd;
1120 	int		 rc;
1121 	size_t		 sz, ssz;
1122 
1123 	sz = strlcat(path, "/", MAXPATHLEN);
1124 	if (sz >= MAXPATHLEN) {
1125 		fprintf(stderr, "%s: Path too long", path);
1126 		return;
1127 	}
1128 
1129 	/*
1130 	 * First, scan for the "etc" directory.
1131 	 * If it's found, then see if it should cause us to stop.  This
1132 	 * happens when a catman.conf is found in the directory.
1133 	 */
1134 
1135 	rc = 0;
1136 	while (0 == rc && NULL != (d = readdir(dir))) {
1137 		if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
1138 			continue;
1139 
1140 		path[(int)sz] = '\0';
1141 		ssz = strlcat(path, d->d_name, MAXPATHLEN);
1142 
1143 		if (ssz >= MAXPATHLEN) {
1144 			fprintf(stderr, "%s: Path too long", path);
1145 			return;
1146 		} else if (NULL == (cd = opendir(path))) {
1147 			perror(path);
1148 			return;
1149 		}
1150 
1151 		rc = pathstop(cd);
1152 		closedir(cd);
1153 	}
1154 
1155 	if (rc > 0) {
1156 		/* This also strips the trailing slash. */
1157 		path[(int)--sz] = '\0';
1158 		req->p = mandoc_realloc
1159 			(req->p,
1160 			 (req->psz + 1) * sizeof(struct paths));
1161 		/*
1162 		 * Strip out the leading "./" unless we're just a ".",
1163 		 * in which case use an empty string as our name.
1164 		 */
1165 		req->p[(int)req->psz].path = mandoc_strdup(path);
1166 		req->p[(int)req->psz].name =
1167 			cp = mandoc_strdup(path + (1 == sz ? 1 : 2));
1168 		req->psz++;
1169 		/*
1170 		 * The name is just the path with all the slashes taken
1171 		 * out of it.  Simple but effective.
1172 		 */
1173 		for ( ; '\0' != *cp; cp++)
1174 			if ('/' == *cp)
1175 				*cp = ' ';
1176 		return;
1177 	}
1178 
1179 	/*
1180 	 * If no etc/catman.conf was found, recursively enter child
1181 	 * directory and continue scanning.
1182 	 */
1183 
1184 	rewinddir(dir);
1185 	while (NULL != (d = readdir(dir))) {
1186 		if (DT_DIR != d->d_type || '.' == d->d_name[0])
1187 			continue;
1188 
1189 		path[(int)sz] = '\0';
1190 		ssz = strlcat(path, d->d_name, MAXPATHLEN);
1191 
1192 		if (ssz >= MAXPATHLEN) {
1193 			fprintf(stderr, "%s: Path too long", path);
1194 			return;
1195 		} else if (NULL == (cd = opendir(path))) {
1196 			perror(path);
1197 			return;
1198 		}
1199 
1200 		pathgen(cd, path, req);
1201 		closedir(cd);
1202 	}
1203 }
1204