summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorschwarze <schwarze@openbsd.org>2014-07-11 15:37:22 +0000
committerschwarze <schwarze@openbsd.org>2014-07-11 15:37:22 +0000
commitc6c22f1270d37eb7983423f3abe455e6edbc0942 (patch)
treeb0d26d21b67148611a8c1668954102c1356b2175
parentMissing bounds check in do_PVK_body(); OpenSSL RT #2277, from OpenSSL trunk, (diff)
downloadwireguard-openbsd-c6c22f1270d37eb7983423f3abe455e6edbc0942.tar.xz
wireguard-openbsd-c6c22f1270d37eb7983423f3abe455e6edbc0942.zip
Bring in man.cgi(8) to maintain it in our tree together with mandoc.
It will not be enabled in the build nor installed by default. A comment in the Makefile lists the three simple steps needed to build, install, and run it on the two machines worldwide that are going to run it. deraadt@ agrees with having the code in the tree.
-rw-r--r--usr.bin/mandoc/Makefile36
-rw-r--r--usr.bin/mandoc/cgi.c954
-rw-r--r--usr.bin/mandoc/man.cgi.8332
3 files changed, 1321 insertions, 1 deletions
diff --git a/usr.bin/mandoc/Makefile b/usr.bin/mandoc/Makefile
index e2a6c0dc232..9f681b167d4 100644
--- a/usr.bin/mandoc/Makefile
+++ b/usr.bin/mandoc/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.77 2014/05/12 19:11:20 espie Exp $
+# $OpenBSD: Makefile,v 1.78 2014/07/11 15:37:22 schwarze Exp $
.include <bsd.own.mk>
@@ -28,4 +28,38 @@ LINKS = ${BINDIR}/mandoc ${BINDIR}/apropos \
MAN = apropos.1 mandoc.1 whatis.1 makewhatis.8
+CLEANFILES += man.cgi cgi.o man.cgi.8.manlint
+
+
+# ----------------------------------------------------------------------
+# Variables and targets to build and install man.cgi(8),
+# not used during make build and make release.
+
+# To build, run: make man.cgi
+# To install, run: sudo make installcgi
+# After that, read: man man.cgi.8
+
+LIBMDOC_OBJS = mdoc_argv.o mdoc_hash.o mdoc_macro.o mdoc_validate.o \
+ mdoc.o arch.o att.o lib.o st.o vol.o
+LIBMAN_OBJS = man.o man_hash.o man_macro.o man_validate.o
+LIBROFF_OBJS = roff.o eqn.o tbl.o tbl_data.o tbl_layout.o tbl_opts.o
+LIBMANDOC_OBJS = ${LIBMDOC_OBJS} ${LIBMAN_OBJS} ${LIBROFF_OBJS} \
+ mandoc.o mandoc_aux.o chars.o msec.o read.o
+HTML_OBJS = html.o mdoc_html.o man_html.o tbl_html.o eqn_html.o out.o
+CGI_OBJS = ${LIBMANDOC_OBJS} ${HTML_OBJS} \
+ mansearch.o mansearch_const.o cgi.o
+
+cgi.o: main.h mandoc.h mandoc_aux.h manpath.h mansearch.h
+
+man.cgi: ${CGI_OBJS}
+ ${CC} ${LDFLAGS} -static -o ${.TARGET} ${CGI_OBJS} ${LDADD}
+
+installcgi: man.cgi
+ ${INSTALL} -d -o root -g wheel -m 755 ${DESTDIR}/var/www/cgi-bin
+ ${INSTALL} ${INSTALL_COPY} -S ${INSTALL_STRIP} \
+ -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+ man.cgi ${DESTDIR}/var/www/cgi-bin/man.cgi
+ ${INSTALL} ${INSTALL_COPY} -o ${MANOWN} -g ${MANGRP} -m ${MANMODE} \
+ ${.CURDIR}/man.cgi.8 ${DESTDIR}${MANDIR}8/man.cgi.8
+
.include <bsd.prog.mk>
diff --git a/usr.bin/mandoc/cgi.c b/usr.bin/mandoc/cgi.c
new file mode 100644
index 00000000000..319832b6715
--- /dev/null
+++ b/usr.bin/mandoc/cgi.c
@@ -0,0 +1,954 @@
+/* $Id: cgi.c,v 1.1 2014/07/11 15:37:22 schwarze Exp $ */
+/*
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "mandoc_aux.h"
+#include "main.h"
+#include "manpath.h"
+#include "mansearch.h"
+
+enum page {
+ PAGE_INDEX,
+ PAGE_SEARCH,
+ PAGE_SHOW,
+ PAGE__MAX
+};
+
+/*
+ * A query as passed to the search function.
+ */
+struct query {
+ const char *manpath; /* desired manual directory */
+ const char *arch; /* architecture */
+ const char *sec; /* manual section */
+ const char *expr; /* unparsed expression string */
+ int legacy; /* whether legacy mode */
+};
+
+struct req {
+ struct query q;
+ char **p; /* array of available manpaths */
+ size_t psz; /* number of available manpaths */
+ enum page page;
+};
+
+static void catman(const struct req *, const char *);
+static int cmp(const void *, const void *);
+static void format(const struct req *, const char *);
+static void html_print(const char *);
+static void html_printquery(const struct req *);
+static void html_putchar(char);
+static int http_decode(char *);
+static void http_parse(struct req *, char *);
+static void http_print(const char *);
+static void http_putchar(char);
+static void http_printquery(const struct req *);
+static void pathgen(struct req *);
+static void pg_index(const struct req *, char *);
+static void pg_search(const struct req *, char *);
+static void pg_show(const struct req *, char *);
+static void resp_begin_html(int, const char *);
+static void resp_begin_http(int, const char *);
+static void resp_end_html(void);
+static void resp_error_badrequest(const char *);
+static void resp_error_internal(void);
+static void resp_error_notfound(const char *);
+static void resp_index(const struct req *);
+static void resp_noresult(const struct req *,
+ const char *);
+static void resp_search(const struct req *,
+ struct manpage *, size_t);
+static void resp_searchform(const struct req *);
+
+static const char *scriptname; /* CGI script name */
+static const char *mandir; /* contains all manpath directories */
+static const char *cssdir; /* css directory */
+static const char *httphost; /* hostname used in the URIs */
+
+static const char * const pages[PAGE__MAX] = {
+ "index", /* PAGE_INDEX */
+ "search", /* PAGE_SEARCH */
+ "show", /* PAGE_SHOW */
+};
+
+/*
+ * Print a character, escaping HTML along the way.
+ * This will pass non-ASCII straight to output: be warned!
+ */
+static void
+html_putchar(char c)
+{
+
+ switch (c) {
+ case ('"'):
+ printf("&quote;");
+ break;
+ case ('&'):
+ printf("&amp;");
+ break;
+ case ('>'):
+ printf("&gt;");
+ break;
+ case ('<'):
+ printf("&lt;");
+ break;
+ default:
+ putchar((unsigned char)c);
+ break;
+ }
+}
+
+static void
+http_printquery(const struct req *req)
+{
+
+ if (NULL != req->q.manpath) {
+ printf("&manpath=");
+ http_print(req->q.manpath);
+ }
+ if (NULL != req->q.sec) {
+ printf("&sec=");
+ http_print(req->q.sec);
+ }
+ if (NULL != req->q.arch) {
+ printf("&arch=");
+ http_print(req->q.arch);
+ }
+ if (NULL != req->q.expr) {
+ printf("&expr=");
+ http_print(req->q.expr ? req->q.expr : "");
+ }
+}
+
+static void
+html_printquery(const struct req *req)
+{
+
+ if (NULL != req->q.manpath) {
+ printf("&amp;manpath=");
+ html_print(req->q.manpath);
+ }
+ if (NULL != req->q.sec) {
+ printf("&amp;sec=");
+ html_print(req->q.sec);
+ }
+ if (NULL != req->q.arch) {
+ printf("&amp;arch=");
+ html_print(req->q.arch);
+ }
+ if (NULL != req->q.expr) {
+ printf("&amp;expr=");
+ html_print(req->q.expr ? req->q.expr : "");
+ }
+}
+
+static void
+http_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ http_putchar(*p++);
+}
+
+/*
+ * Call through to html_putchar().
+ * Accepts NULL strings.
+ */
+static void
+html_print(const char *p)
+{
+
+ if (NULL == p)
+ return;
+ while ('\0' != *p)
+ html_putchar(*p++);
+}
+
+/*
+ * Parse out key-value pairs from an HTTP request variable.
+ * This can be either a cookie or a POST/GET string, although man.cgi
+ * uses only GET for simplicity.
+ */
+static void
+http_parse(struct req *req, char *p)
+{
+ char *key, *val;
+ int legacy;
+
+ memset(&req->q, 0, sizeof(struct query));
+ req->q.manpath = req->p[0];
+
+ legacy = -1;
+ while ('\0' != *p) {
+ key = p;
+ val = NULL;
+
+ p += (int)strcspn(p, ";&");
+ if ('\0' != *p)
+ *p++ = '\0';
+ if (NULL != (val = strchr(key, '=')))
+ *val++ = '\0';
+
+ if ('\0' == *key || NULL == val || '\0' == *val)
+ continue;
+
+ /* Just abort handling. */
+
+ if ( ! http_decode(key))
+ break;
+ if (NULL != val && ! http_decode(val))
+ break;
+
+ if (0 == strcmp(key, "expr"))
+ req->q.expr = val;
+ else if (0 == strcmp(key, "query"))
+ req->q.expr = val;
+ else if (0 == strcmp(key, "sec"))
+ req->q.sec = val;
+ else if (0 == strcmp(key, "sektion"))
+ req->q.sec = val;
+ else if (0 == strcmp(key, "arch"))
+ req->q.arch = val;
+ else if (0 == strcmp(key, "manpath"))
+ req->q.manpath = val;
+ else if (0 == strcmp(key, "apropos"))
+ legacy = 0 == strcmp(val, "0");
+ }
+
+ /* Test for old man.cgi compatibility mode. */
+
+ req->q.legacy = legacy > 0;
+
+ /*
+ * Section "0" means no section when in legacy mode.
+ * For some man.cgi scripts, "default" arch is none.
+ */
+
+ if (req->q.legacy && NULL != req->q.sec)
+ if (0 == strcmp(req->q.sec, "0"))
+ req->q.sec = NULL;
+ if (req->q.legacy && NULL != req->q.arch)
+ if (0 == strcmp(req->q.arch, "default"))
+ req->q.arch = NULL;
+}
+
+static void
+http_putchar(char c)
+{
+
+ if (isalnum((unsigned char)c)) {
+ putchar((unsigned char)c);
+ return;
+ } else if (' ' == c) {
+ putchar('+');
+ return;
+ }
+ printf("%%%.2x", c);
+}
+
+/*
+ * HTTP-decode a string. The standard explanation is that this turns
+ * "%4e+foo" into "n foo" in the regular way. This is done in-place
+ * over the allocated string.
+ */
+static int
+http_decode(char *p)
+{
+ char hex[3];
+ int c;
+
+ hex[2] = '\0';
+
+ for ( ; '\0' != *p; p++) {
+ if ('%' == *p) {
+ if ('\0' == (hex[0] = *(p + 1)))
+ return(0);
+ if ('\0' == (hex[1] = *(p + 2)))
+ return(0);
+ if (1 != sscanf(hex, "%x", &c))
+ return(0);
+ if ('\0' == c)
+ return(0);
+
+ *p = (char)c;
+ memmove(p + 1, p + 3, strlen(p + 3) + 1);
+ } else
+ *p = '+' == *p ? ' ' : *p;
+ }
+
+ *p = '\0';
+ return(1);
+}
+
+static void
+resp_begin_http(int code, const char *msg)
+{
+
+ if (200 != code)
+ printf("Status: %d %s\n", code, msg);
+
+ puts("Content-Type: text/html; charset=utf-8\n"
+ "Cache-Control: no-cache\n"
+ "Pragma: no-cache\n"
+ "");
+
+ fflush(stdout);
+}
+
+static void
+resp_begin_html(int code, const char *msg)
+{
+
+ resp_begin_http(code, msg);
+
+ printf("<!DOCTYPE HTML PUBLIC "
+ " \"-//W3C//DTD HTML 4.01//EN\""
+ " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
+ "<HTML>\n"
+ "<HEAD>\n"
+ "<META HTTP-EQUIV=\"Content-Type\""
+ " CONTENT=\"text/html; charset=utf-8\">\n"
+ "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
+ " TYPE=\"text/css\" media=\"all\">\n"
+ "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
+ " TYPE=\"text/css\" media=\"all\">\n"
+ "<TITLE>System Manpage Reference</TITLE>\n"
+ "</HEAD>\n"
+ "<BODY>\n"
+ "<!-- Begin page content. //-->\n",
+ cssdir, cssdir);
+}
+
+static void
+resp_end_html(void)
+{
+
+ puts("</BODY>\n"
+ "</HTML>");
+}
+
+static void
+resp_searchform(const struct req *req)
+{
+ int i;
+
+ puts("<!-- Begin search form. //-->");
+ printf("<DIV ID=\"mancgi\">\n"
+ "<FORM ACTION=\"%s/search\" METHOD=\"get\">\n"
+ "<FIELDSET>\n"
+ "<LEGEND>Search Parameters</LEGEND>\n"
+ "<INPUT TYPE=\"submit\" "
+ " VALUE=\"Search\"> for manuals matching \n"
+ "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"",
+ scriptname);
+ html_print(req->q.expr ? req->q.expr : "");
+ printf("\">, section "
+ "<INPUT TYPE=\"text\""
+ " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
+ html_print(req->q.sec ? req->q.sec : "");
+ printf("\">, arch "
+ "<INPUT TYPE=\"text\""
+ " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
+ html_print(req->q.arch ? req->q.arch : "");
+ printf("\">");
+ if (req->psz > 1) {
+ puts(", in <SELECT NAME=\"manpath\">");
+ for (i = 0; i < (int)req->psz; i++) {
+ printf("<OPTION ");
+ if (NULL == req->q.manpath ? 0 == i :
+ 0 == strcmp(req->q.manpath, req->p[i]))
+ printf("SELECTED=\"selected\" ");
+ printf("VALUE=\"");
+ html_print(req->p[i]);
+ printf("\">");
+ html_print(req->p[i]);
+ puts("</OPTION>");
+ }
+ puts("</SELECT>");
+ }
+ puts("&mdash;\n"
+ "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
+ "</FIELDSET>\n"
+ "</FORM>\n"
+ "</DIV>");
+ puts("<!-- End search form. //-->");
+}
+
+static void
+resp_index(const struct req *req)
+{
+
+ resp_begin_html(200, NULL);
+ puts("<H1>\n"
+ "Online manuals with "
+ "<A HREF=\"http://mdocml.bsd.lv/\">mandoc</A>\n"
+ "</H1>");
+ resp_searchform(req);
+ puts("<P>\n"
+ "This web interface is documented in the "
+ "<A HREF=\"search?expr=Nm~^man\\.cgi$&amp;sec=8\">"
+ "man.cgi</A> manual, and the "
+ "<A HREF=\"search?expr=Nm~^apropos$&amp;sec=1\">"
+ "apropos</A> manual explains the query syntax.\n"
+ "</P>");
+ resp_end_html();
+}
+
+static void
+resp_noresult(const struct req *req, const char *msg)
+{
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("<P>");
+ puts(msg);
+ puts("</P>");
+ resp_end_html();
+}
+
+static void
+resp_error_badrequest(const char *msg)
+{
+
+ resp_begin_html(400, "Bad Request");
+ puts("<H1>Bad Request</H1>\n"
+ "<P>\n");
+ puts(msg);
+ printf("Try again from the\n"
+ "<A HREF=\"%s\">main page</A>.\n"
+ "</P>", scriptname);
+ resp_end_html();
+}
+
+static void
+resp_error_notfound(const char *page)
+{
+
+ resp_begin_html(404, "Not Found");
+ puts("<H1>Page Not Found</H1>\n"
+ "<P>\n"
+ "The page you're looking for, ");
+ printf("<B>");
+ html_print(page);
+ printf("</B>,\n"
+ "could not be found.\n"
+ "Try searching from the\n"
+ "<A HREF=\"%s\">main page</A>.\n"
+ "</P>", scriptname);
+ resp_end_html();
+}
+
+static void
+resp_error_internal(void)
+{
+ resp_begin_html(500, "Internal Server Error");
+ puts("<P>Internal Server Error</P>");
+ resp_end_html();
+}
+
+static void
+resp_search(const struct req *req, struct manpage *r, size_t sz)
+{
+ size_t i;
+
+ if (1 == sz) {
+ /*
+ * If we have just one result, then jump there now
+ * without any delay.
+ */
+ puts("Status: 303 See Other");
+ printf("Location: http://%s%s/show/%s/%s?",
+ httphost, scriptname, req->q.manpath, r[0].file);
+ http_printquery(req);
+ puts("\n"
+ "Content-Type: text/html; charset=utf-8\n");
+ return;
+ }
+
+ qsort(r, sz, sizeof(struct manpage), cmp);
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("<DIV CLASS=\"results\">");
+ puts("<TABLE>");
+
+ for (i = 0; i < sz; i++) {
+ printf("<TR>\n"
+ "<TD CLASS=\"title\">\n"
+ "<A HREF=\"%s/show/%s/%s?",
+ scriptname, req->q.manpath, r[i].file);
+ html_printquery(req);
+ printf("\">");
+ html_print(r[i].names);
+ printf("</A>\n"
+ "</TD>\n"
+ "<TD CLASS=\"desc\">");
+ html_print(r[i].output);
+ puts("</TD>\n"
+ "</TR>");
+ }
+
+ puts("</TABLE>\n"
+ "</DIV>");
+ resp_end_html();
+}
+
+/* ARGSUSED */
+static void
+pg_index(const struct req *req, char *path)
+{
+
+ resp_index(req);
+}
+
+static void
+catman(const struct req *req, const char *file)
+{
+ FILE *f;
+ size_t len;
+ int i;
+ char *p;
+ int italic, bold;
+
+ if (NULL == (f = fopen(file, "r"))) {
+ resp_error_badrequest(
+ "You specified an invalid manual file.");
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+ puts("<DIV CLASS=\"catman\">\n"
+ "<PRE>");
+
+ while (NULL != (p = fgetln(f, &len))) {
+ bold = italic = 0;
+ for (i = 0; i < (int)len - 1; i++) {
+ /*
+ * This means that the catpage is out of state.
+ * Ignore it and keep going (although the
+ * catpage is bogus).
+ */
+
+ if ('\b' == p[i] || '\n' == p[i])
+ continue;
+
+ /*
+ * Print a regular character.
+ * Close out any bold/italic scopes.
+ * If we're in back-space mode, make sure we'll
+ * have something to enter when we backspace.
+ */
+
+ if ('\b' != p[i + 1]) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ html_putchar(p[i]);
+ continue;
+ } else if (i + 2 >= (int)len)
+ continue;
+
+ /* Italic mode. */
+
+ if ('_' == p[i]) {
+ if (bold)
+ printf("</B>");
+ if ( ! italic)
+ printf("<I>");
+ bold = 0;
+ italic = 1;
+ i += 2;
+ html_putchar(p[i]);
+ continue;
+ }
+
+ /*
+ * Handle funny behaviour troff-isms.
+ * These grok'd from the original man2html.c.
+ */
+
+ if (('+' == p[i] && 'o' == p[i + 2]) ||
+ ('o' == p[i] && '+' == p[i + 2]) ||
+ ('|' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '|' == p[i + 2]) ||
+ ('*' == p[i] && '=' == p[i + 2]) ||
+ ('=' == p[i] && '*' == p[i + 2]) ||
+ ('*' == p[i] && '|' == p[i + 2]) ||
+ ('|' == p[i] && '*' == p[i + 2])) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ putchar('*');
+ i += 2;
+ continue;
+ } else if (('|' == p[i] && '-' == p[i + 2]) ||
+ ('-' == p[i] && '|' == p[i + 1]) ||
+ ('+' == p[i] && '-' == p[i + 1]) ||
+ ('-' == p[i] && '+' == p[i + 1]) ||
+ ('+' == p[i] && '|' == p[i + 1]) ||
+ ('|' == p[i] && '+' == p[i + 1])) {
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+ italic = bold = 0;
+ putchar('+');
+ i += 2;
+ continue;
+ }
+
+ /* Bold mode. */
+
+ if (italic)
+ printf("</I>");
+ if ( ! bold)
+ printf("<B>");
+ bold = 1;
+ italic = 0;
+ i += 2;
+ html_putchar(p[i]);
+ }
+
+ /*
+ * Clean up the last character.
+ * We can get to a newline; don't print that.
+ */
+
+ if (italic)
+ printf("</I>");
+ if (bold)
+ printf("</B>");
+
+ if (i == (int)len - 1 && '\n' != p[i])
+ html_putchar(p[i]);
+
+ putchar('\n');
+ }
+
+ puts("</PRE>\n"
+ "</DIV>\n"
+ "</BODY>\n"
+ "</HTML>");
+
+ fclose(f);
+}
+
+static void
+format(const struct req *req, const char *file)
+{
+ struct mparse *mp;
+ int fd;
+ struct mdoc *mdoc;
+ struct man *man;
+ void *vp;
+ enum mandoclevel rc;
+ char opts[PATH_MAX + 128];
+
+ if (-1 == (fd = open(file, O_RDONLY, 0))) {
+ resp_error_badrequest(
+ "You specified an invalid manual file.");
+ return;
+ }
+
+ mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
+ req->q.manpath);
+ rc = mparse_readfd(mp, fd, file);
+ close(fd);
+
+ if (rc >= MANDOCLEVEL_FATAL) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ resp_error_internal();
+ return;
+ }
+
+ snprintf(opts, sizeof(opts),
+ "fragment,man=%s/search?sec=%%S&expr=Nm~^%%N$",
+ scriptname);
+
+ mparse_result(mp, &mdoc, &man, NULL);
+ if (NULL == man && NULL == mdoc) {
+ fprintf(stderr, "fatal mandoc error: %s/%s\n",
+ req->q.manpath, file);
+ resp_error_internal();
+ mparse_free(mp);
+ return;
+ }
+
+ resp_begin_html(200, NULL);
+ resp_searchform(req);
+
+ vp = html_alloc(opts);
+
+ if (NULL != mdoc)
+ html_mdoc(vp, mdoc);
+ else
+ html_man(vp, man);
+
+ puts("</BODY>\n"
+ "</HTML>");
+
+ html_free(vp);
+ mparse_free(mp);
+}
+
+static void
+pg_show(const struct req *req, char *path)
+{
+ char *sub;
+
+ if (NULL == path || NULL == (sub = strchr(path, '/'))) {
+ resp_error_badrequest(
+ "You did not specify a page to show.");
+ return;
+ }
+ *sub++ = '\0';
+
+ /*
+ * Begin by chdir()ing into the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (-1 == chdir(path)) {
+ resp_error_badrequest(
+ "You specified an invalid manpath.");
+ return;
+ }
+
+ if ('c' == *sub)
+ catman(req, sub);
+ else
+ format(req, sub);
+}
+
+static void
+pg_search(const struct req *req, char *path)
+{
+ struct mansearch search;
+ struct manpaths paths;
+ struct manpage *res;
+ char **cp;
+ const char *ep, *start;
+ size_t ressz;
+ int i, sz;
+
+ /*
+ * Begin by chdir()ing into the root of the manpath.
+ * This way we can pick up the database files, which are
+ * relative to the manpath root.
+ */
+
+ if (-1 == (chdir(req->q.manpath))) {
+ resp_error_badrequest(
+ "You specified an invalid manpath.");
+ return;
+ }
+
+ search.arch = req->q.arch;
+ search.sec = req->q.sec;
+ search.deftype = TYPE_Nm | TYPE_Nd;
+ search.flags = 0;
+
+ paths.sz = 1;
+ paths.paths = mandoc_malloc(sizeof(char *));
+ paths.paths[0] = mandoc_strdup(".");
+
+ /*
+ * Poor man's tokenisation: just break apart by spaces.
+ * Yes, this is half-ass. But it works for now.
+ */
+
+ ep = req->q.expr;
+ while (ep && isspace((unsigned char)*ep))
+ ep++;
+
+ sz = 0;
+ cp = NULL;
+ while (ep && '\0' != *ep) {
+ cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
+ start = ep;
+ while ('\0' != *ep && ! isspace((unsigned char)*ep))
+ ep++;
+ cp[sz] = mandoc_malloc((ep - start) + 1);
+ memcpy(cp[sz], start, ep - start);
+ cp[sz++][ep - start] = '\0';
+ while (isspace((unsigned char)*ep))
+ ep++;
+ }
+
+ if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
+ resp_noresult(req, "You entered an invalid query.");
+ else if (0 == ressz)
+ resp_noresult(req, "No results found.");
+ else
+ resp_search(req, res, ressz);
+
+ for (i = 0; i < sz; i++)
+ free(cp[i]);
+ free(cp);
+
+ for (i = 0; i < (int)ressz; i++) {
+ free(res[i].file);
+ free(res[i].names);
+ free(res[i].output);
+ }
+ free(res);
+
+ free(paths.paths[0]);
+ free(paths.paths);
+}
+
+int
+main(void)
+{
+ int i;
+ struct req req;
+ char *querystring, *path, *subpath;
+
+ /* Scan our run-time environment. */
+
+ if (NULL == (mandir = getenv("MAN_DIR")))
+ mandir = "/man";
+
+ if (NULL == (scriptname = getenv("SCRIPT_NAME")))
+ scriptname = "";
+
+ if (NULL == (cssdir = getenv("CSS_DIR")))
+ cssdir = "";
+
+ if (NULL == (httphost = getenv("HTTP_HOST")))
+ httphost = "localhost";
+
+ /*
+ * First we change directory into the mandir so that
+ * subsequent scanning for manpath directories is rooted
+ * relative to the same position.
+ */
+
+ if (-1 == chdir(mandir)) {
+ fprintf(stderr, "MAN_DIR: %s: %s\n",
+ mandir, strerror(errno));
+ resp_error_internal();
+ return(EXIT_FAILURE);
+ }
+
+ memset(&req, 0, sizeof(struct req));
+ pathgen(&req);
+
+ /* Next parse out the query string. */
+
+ if (NULL != (querystring = getenv("QUERY_STRING")))
+ http_parse(&req, querystring);
+
+ /*
+ * Now juggle paths to extract information.
+ * We want to extract our filetype (the file suffix), the
+ * initial path component, then the trailing component(s).
+ * Start with leading subpath component.
+ */
+
+ subpath = path = NULL;
+ req.page = PAGE__MAX;
+
+ if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
+ req.page = PAGE_INDEX;
+
+ if (NULL != path && '/' == *path && '\0' == *++path)
+ req.page = PAGE_INDEX;
+
+ /* Resolve subpath component. */
+
+ if (NULL != path && NULL != (subpath = strchr(path, '/')))
+ *subpath++ = '\0';
+
+ /* Map path into one we recognise. */
+
+ if (NULL != path && '\0' != *path)
+ for (i = 0; i < (int)PAGE__MAX; i++)
+ if (0 == strcmp(pages[i], path)) {
+ req.page = (enum page)i;
+ break;
+ }
+
+ /* Route pages. */
+
+ switch (req.page) {
+ case (PAGE_INDEX):
+ pg_index(&req, subpath);
+ break;
+ case (PAGE_SEARCH):
+ pg_search(&req, subpath);
+ break;
+ case (PAGE_SHOW):
+ pg_show(&req, subpath);
+ break;
+ default:
+ resp_error_notfound(path);
+ break;
+ }
+
+ for (i = 0; i < (int)req.psz; i++)
+ free(req.p[i]);
+ free(req.p);
+ return(EXIT_SUCCESS);
+}
+
+static int
+cmp(const void *p1, const void *p2)
+{
+
+ return(strcasecmp(((const struct manpage *)p1)->names,
+ ((const struct manpage *)p2)->names));
+}
+
+/*
+ * Scan for indexable paths.
+ */
+static void
+pathgen(struct req *req)
+{
+ FILE *fp;
+ char *dp;
+ size_t dpsz;
+
+ if (NULL == (fp = fopen("manpath.conf", "r")))
+ return;
+
+ while (NULL != (dp = fgetln(fp, &dpsz))) {
+ if ('\n' == dp[dpsz - 1])
+ dpsz--;
+ req->p = mandoc_realloc(req->p,
+ (req->psz + 1) * sizeof(char *));
+ req->p[req->psz++] = mandoc_strndup(dp, dpsz);
+ }
+}
diff --git a/usr.bin/mandoc/man.cgi.8 b/usr.bin/mandoc/man.cgi.8
new file mode 100644
index 00000000000..c7aff6b850e
--- /dev/null
+++ b/usr.bin/mandoc/man.cgi.8
@@ -0,0 +1,332 @@
+.\" $Id: man.cgi.8,v 1.1 2014/07/11 15:37:22 schwarze Exp $
+.\"
+.\" Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 11 2014 $
+.Dt MAN.CGI 8
+.Os
+.Sh NAME
+.Nm man.cgi
+.Nd CGI program to search and display manual pages
+.Sh DESCRIPTION
+The
+.Nm
+CGI program searches for manual pages on a WWW server
+and displays them to HTTP clients,
+providing functionality equivalent to the
+.Xr apropos 1
+and
+.Xr man 1
+utilities.
+It can use multiple manual trees in parallel.
+.Ss HTML search interface
+At the top of each generated HTML page,
+.Nm
+displays a search form containing these elements:
+.Bl -enum
+.It
+A
+.Dq Search
+button to send a search request from the client to the server,
+to be clicked after filling in the following input box(es).
+.It
+An input box for search queries, expecting an
+.Ar expression
+using the syntax described in the
+.Xr apropos 1
+manual; filling this in is required for each search.
+.It
+An input box for an optional section number.
+If one is provided, it has the same effect as the
+.Xr apropos 1
+.Fl s
+option.
+Otherwise, pages from all sections are shown.
+.It
+An input box for an optional architecture name.
+If one is provided, it has the same effect as the
+.Xr apropos 1
+.Fl S
+option.
+By default, pages for all architectures are shown.
+.It
+A dropdown menu to select a manual tree.
+If the configuration file
+.Pa /var/www/man/manpath.conf
+contains only one manpath, the dropdown menu is not shown.
+By default, the first manpath given in the file is used.
+.It
+A
+.Dq Reset
+button to undo any changes to the input boxes and the dropdown menu
+and reset them to the values contained in the
+.Ev QUERY_STRING .
+.El
+.Ss Program output
+The
+.Nm
+program generates five kinds of output pages:
+.Bl -tag -width Ds
+.It The index page.
+This is returned when calling
+.Nm
+without
+.Ev PATH_INFO
+and without a
+.Ev QUERY_STRING .
+It serves as a starting point for using the program
+and shows the search form only.
+.It A list page.
+Lists are returned when searches match more than one manual page.
+The first column shows the names and section numbers of manuals
+as clickable links.
+The second column shows the one-line descriptions of the manuals.
+.It A manual page.
+This output format is used when a search matches exactly one
+manual page, or when a link on a list page or an
+.Ic \&Xr
+link on another manual page is followed.
+.It A no-result page.
+This is shown when a search request returns no results -
+eiher because it violates the query syntax, or because
+the search does not match any manual pages.
+.It \&An error page.
+This cannot happen by merely clicking the
+.Dq Search
+button, but only by manually entering an invalid URI.
+It does not show the search form, but only an error message
+and a link back to the index page.
+.El
+.Ss Setup
+For each manual tree, create one first-level subdirectory below
+.Pa /var/www/man .
+The name of one of these directories is called a
+.Dq manpath
+in the context of
+.Nm .
+Create a single ASCII text file
+.Pa /var/www/man/manpath.conf
+containing the names of these directories, one per line.
+The directory given first is used as the default manpath.
+.Pp
+Inside each of these directories, use the same directory and file
+structure as found below
+.Pa /usr/share/man ,
+that is, second-level subdirectories
+.Pa /var/www/man/*/man1 , /var/www/man/*/man2
+etc. containing source
+.Xr mdoc 7
+and
+.Xr man 7
+manuals with file name extensions matching the section numbers,
+second-level subdirectories
+.Pa /var/www/man/*/cat1 , /var/www/man/*/cat2
+etc. containing preformatted manuals with the file name extension
+.Sq 0 ,
+and optional third-level subdirectories for architectures.
+Use
+.Xr makewhatis 8
+to create a
+.Xr mandoc.db 5
+database inside each manpath.
+.Pp
+Configure your web server to execute CGI programs located in
+.Pa /cgi-bin .
+When using
+.Xr nginx 8 ,
+the
+.Xr slowcgi 8
+proxy daemon is needed to translate FastCGI requests to plain old CGI.
+.Ss URI interface
+.Nm
+uniform resource identifiers are not needed for interactive use,
+but can be useful for deep linking.
+They consist of:
+.Bl -enum
+.It
+The
+.Cm http://
+protocol specifier.
+.It
+The host name and a following slash.
+.It
+The path to the program, normally
+.Pa cgi-bin/man.cgi/ .
+.It
+A page name, which can be
+.Cm index ,
+which is the default and can be omitted,
+.Cm search ,
+or
+.Cm show .
+.It
+For
+.Cm show
+only, a slash, the manpath, another slash,
+and the name of the requested file, for example
+.Pa /OpenBSD-current/man1/mandoc.1 .
+.It
+For
+.Cm search
+only, a query string starting with a question mark
+and consisting of
+.Ar key Ns = Ns Ar value
+pairs, separated by ampersands, for example
+.Pa ?manpath=OpenBSD-current&expr=mandoc .
+Supported keys are
+.Cm manpath ,
+.Cm expr ,
+.Cm sec ,
+and
+.Cm arch ,
+corresponding to
+.Xr apropos 1
+.Fl M ,
+.Ar expression ,
+.Fl s ,
+and
+.Fl S ,
+respectively.
+For backward compatibility with the traditional
+.Nm ,
+.Cm query
+is supported as an alias for
+.Cm expr
+and
+.Cm sektion
+as an alias for
+.Cm sec .
+.El
+.Sh ENVIRONMENT
+The web server may pass the following CGI variables to
+.Nm :
+.Bl -tag -width Ds
+.It Ev CSS_DIR
+An optional path to the directory containing the CSS files,
+to be specified relative to the server's document root,
+and to be specified without a trailing slash.
+When not specified, the CSS files
+are assumed to be in the document root.
+This is used in generated HTML code.
+.It Ev HTTP_HOST
+The FQDN of the (possibly virtual) host the HTTP server is running on.
+This is used for
+.Ic Location:
+headers in HTTP 303 responses.
+.It Ev MAN_DIR
+A path to the
+.Nm
+data directory to be used instead of
+.Pa /man ,
+relative to the web server
+.Xr chroot 2
+directory, to be specified without a trailing slash.
+This is prepended to the manpath when opening
+.Xr mandoc.db 5
+and manual page files.
+.It Ev PATH_INFO
+The final part of the URI path passed from the client to the server,
+starting after the
+.Ev SCRIPT_NAME
+and ending before the
+.Ev QUERY_STRING .
+It is used by the
+.Cm show
+page to aquire the manpath and filename it needs.
+.It Ev QUERY_STRING
+The HTTP query string passed from the client to the server.
+It is the final part of the URI, after the question mark.
+It is used by the
+.Cm search
+page to acquire the named parameters it needs.
+.It Ev SCRIPT_NAME
+The path to the
+.Nm
+binary relative to the server root, usually
+.Pa /cgi-bin/man.cgi .
+This is used for generating URIs to be embedded
+in generated HTML code and HTTP headers.
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa /var/www
+Default web server
+.Xr chroot 2
+directory.
+All the following paths are specified relative to this directory.
+.It Pa /cgi-bin/man.cgi
+The path to the
+.Nm
+program relative to the server root.
+Can be overridden by
+.Ev SCRIPT_NAME .
+.It Pa /htdocs
+The path to the server document root relative to the server root.
+This is part of the web server configuration and not specific to
+.Nm .
+.It Pa /htdocs/man-cgi.css
+A style sheet for general
+.Nm
+styling, referenced from each generated HTML page.
+.It Pa /htdocs/man.css
+A style sheet for
+.Xr mandoc 1
+HTML styling, referenced from each generated HTML page after
+.Pa man-cgi.css .
+.It Pa /man
+Default
+.Nm
+data directory containing all the manual trees.
+Can be overridden by
+.Ev MAN_DIR .
+.It Pa /man/manpath.conf
+The list of available manpaths, one per line.
+.It Pa /man/OpenBSD-current/man1/mandoc.1
+An example
+.Xr mdoc 7
+source file located below the
+.Dq OpenBSD-current
+manpath.
+.El
+.Sh COMPATIBILITY
+The
+.Nm
+CGI program is call-compatible with queries from the traditional
+.Pa man.cgi
+script by Wolfram Schneider.
+However, the results may not be quite the same.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr mandoc.db 5 ,
+.Xr makewhatis 8 ,
+.Xr slowcgi 8
+.Sh HISTORY
+A version of
+.Nm
+based on
+.Xr mandoc 1
+first appeared in mdocml-1.12.1 (March 2012).
+The current SQLite3-based version first appeared in
+.Ox 5.6 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
+and ported to the SQLite3-based
+.Xr mandoc.db 5
+backend by
+.An Ingo Schwarze Aq Mt schwarze@openbsd.org .