-/* $Id: cgi.c,v 1.71 2014/07/13 12:45:23 schwarze Exp $ */
+/* $Id: cgi.c,v 1.84 2014/07/25 16:43:37 schwarze Exp $ */
/*
* Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
* Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
* 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 */
+ char *manpath; /* desired manual directory */
+ char *arch; /* architecture */
+ char *sec; /* manual section */
+ char *expr; /* unparsed expression string */
int equal; /* match whole names, not substrings */
};
};
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_parse(struct req *, const 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_error_badrequest(const char *);
+static void pg_error_internal(void);
+static void pg_index(const struct req *);
+static void pg_noresult(const struct req *, const char *);
static void pg_search(const struct req *);
-static void pg_show(const struct req *, const char *);
+static void pg_searchres(const struct req *,
+ struct manpage *, size_t);
+static void pg_show(struct req *, const 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_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 void resp_show(const struct req *, const char *);
static const char *scriptname; /* CGI script name */
-static const char *httphost; /* hostname used in the URIs */
static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
static const char *const sec_numbers[] = {
}
/*
- * 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.
+ * Transfer the responsibility for the allocated string *val
+ * to the query structure.
*/
static void
-http_parse(struct req *req, char *p)
+set_query_attr(char **attr, char **val)
{
- char *key, *val;
- memset(&req->q, 0, sizeof(struct query));
- req->q.manpath = req->p[0];
- req->q.equal = 1;
+ free(*attr);
+ if (**val == '\0') {
+ *attr = NULL;
+ free(*val);
+ } else
+ *attr = *val;
+ *val = NULL;
+}
- while ('\0' != *p) {
- key = p;
- val = NULL;
+/*
+ * Parse the QUERY_STRING for key-value pairs
+ * and store the values into the query structure.
+ */
+static void
+http_parse(struct req *req, const char *qs)
+{
+ char *key, *val;
+ size_t keysz, valsz;
- p += (int)strcspn(p, ";&");
- if ('\0' != *p)
- *p++ = '\0';
- if (NULL != (val = strchr(key, '=')))
- *val++ = '\0';
+ req->q.manpath = NULL;
+ req->q.arch = NULL;
+ req->q.sec = NULL;
+ req->q.expr = NULL;
+ req->q.equal = 1;
- if ('\0' == *key || NULL == val || '\0' == *val)
- continue;
+ key = val = NULL;
+ while (*qs != '\0') {
+
+ /* Parse one key. */
+
+ keysz = strcspn(qs, "=;&");
+ key = mandoc_strndup(qs, keysz);
+ qs += keysz;
+ if (*qs != '=')
+ goto next;
+
+ /* Parse one value. */
- /* Just abort handling. */
+ valsz = strcspn(++qs, ";&");
+ val = mandoc_strndup(qs, valsz);
+ qs += valsz;
- if ( ! http_decode(key))
- break;
- if (NULL != val && ! http_decode(val))
- break;
+ /* Decode and catch encoding errors. */
- if (0 == strcmp(key, "query"))
- req->q.expr = val;
- else if (0 == strcmp(key, "manpath"))
- req->q.manpath = val;
- else if (0 == strcmp(key, "apropos"))
+ if ( ! (http_decode(key) && http_decode(val)))
+ goto next;
+
+ /* Handle key-value pairs. */
+
+ if ( ! strcmp(key, "query"))
+ set_query_attr(&req->q.expr, &val);
+
+ else if ( ! strcmp(key, "apropos"))
req->q.equal = !strcmp(val, "0");
- else if (0 == strcmp(key, "sec") ||
- 0 == strcmp(key, "sektion")) {
- if (strcmp(val, "0"))
- req->q.sec = val;
- } else if (0 == strcmp(key, "arch")) {
- if (strcmp(val, "default"))
- req->q.arch = val;
+
+ else if ( ! strcmp(key, "manpath")) {
+#ifdef COMPAT_OLDURI
+ if ( ! strncmp(val, "OpenBSD ", 8)) {
+ val[7] = '-';
+ if ('C' == val[8])
+ val[8] = 'c';
+ }
+#endif
+ set_query_attr(&req->q.manpath, &val);
+ }
+
+ else if ( ! (strcmp(key, "sec")
+#ifdef COMPAT_OLDURI
+ && strcmp(key, "sektion")
+#endif
+ )) {
+ if ( ! strcmp(val, "0"))
+ *val = '\0';
+ set_query_attr(&req->q.sec, &val);
+ }
+
+ else if ( ! strcmp(key, "arch")) {
+ if ( ! strcmp(val, "default"))
+ *val = '\0';
+ set_query_attr(&req->q.arch, &val);
}
+
+ /*
+ * The key must be freed in any case.
+ * The val may have been handed over to the query
+ * structure, in which case it is now NULL.
+ */
+next:
+ free(key);
+ key = NULL;
+ free(val);
+ val = NULL;
+
+ if (*qs != '\0')
+ qs++;
}
+
+ /* Fall back to the default manpath. */
+
+ if (req->q.manpath == NULL)
+ req->q.manpath = mandoc_strdup(req->p[0]);
}
static void
/* Write architecture selector. */
- puts("<SELECT NAME=\"arch\">");
+ printf( "<SELECT NAME=\"arch\">\n"
+ "<OPTION VALUE=\"default\"");
+ if (NULL == req->q.arch)
+ printf(" SELECTED");
+ puts(">All Architectures</OPTION>");
for (i = 0; i < arch_MAX; i++) {
printf("<OPTION VALUE=\"%s\"", arch_names[i]);
if (NULL != req->q.arch &&
puts("<!-- End search form. //-->");
}
+static int
+validate_urifrag(const char *frag)
+{
+
+ while ('\0' != *frag) {
+ if ( ! (isalnum((unsigned char)*frag) ||
+ '-' == *frag || '.' == *frag ||
+ '/' == *frag || '_' == *frag))
+ return(0);
+ frag++;
+ }
+ return(1);
+}
+
+static int
+validate_manpath(const struct req *req, const char* manpath)
+{
+ size_t i;
+
+ if ( ! strcmp(manpath, "mandoc"))
+ return(1);
+
+ for (i = 0; i < req->psz; i++)
+ if ( ! strcmp(manpath, req->p[i]))
+ return(1);
+
+ return(0);
+}
+
+static int
+validate_filename(const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ return ( ! (strstr(file, "../") || strstr(file, "/..") ||
+ (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
+}
+
static void
-resp_index(const struct req *req)
+pg_index(const struct req *req)
{
resp_begin_html(200, NULL);
}
static void
-resp_noresult(const struct req *req, const char *msg)
+pg_noresult(const struct req *req, const char *msg)
{
resp_begin_html(200, NULL);
resp_searchform(req);
}
static void
-resp_error_badrequest(const char *msg)
+pg_error_badrequest(const char *msg)
{
resp_begin_html(400, "Bad Request");
}
static void
-resp_error_internal(void)
+pg_error_internal(void)
{
resp_begin_html(500, "Internal Server Error");
puts("<P>Internal Server Error</P>");
}
static void
-resp_search(const struct req *req, struct manpage *r, size_t sz)
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
{
+ char *arch, *archend;
size_t i, iuse, isec;
+ int archprio, archpriouse;
int prio, priouse;
char sec;
+ for (i = 0; i < sz; i++) {
+ if (validate_filename(r[i].file))
+ continue;
+ fprintf(stderr, "invalid filename %s in %s database\n",
+ r[i].file, req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
if (1 == sz) {
/*
* If we have just one result, then jump there now
*/
printf("Status: 303 See Other\r\n");
printf("Location: http://%s%s/%s/%s?",
- httphost, scriptname, req->q.manpath, r[0].file);
+ HTTP_HOST, scriptname, req->q.manpath, r[0].file);
http_printquery(req);
printf("\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
return;
}
- qsort(r, sz, sizeof(struct manpage), cmp);
-
resp_begin_html(200, NULL);
resp_searchform(req);
puts("<DIV CLASS=\"results\">");
puts("<HR>");
iuse = 0;
priouse = 10;
+ archpriouse = 3;
for (i = 0; i < sz; i++) {
isec = strcspn(r[i].file, "123456789");
sec = r[i].file[isec];
if ('\0' == sec)
continue;
prio = sec_prios[sec - '1'];
+ if (NULL == req->q.arch) {
+ archprio =
+ (NULL == (arch = strchr(
+ r[i].file + isec, '/'))) ? 3 :
+ (NULL == (archend = strchr(
+ arch + 1, '/'))) ? 0 :
+ strncmp(arch, "amd64/",
+ archend - arch) ? 2 : 1;
+ if (archprio < archpriouse) {
+ archpriouse = archprio;
+ priouse = prio;
+ iuse = i;
+ continue;
+ }
+ if (archprio > archpriouse)
+ continue;
+ }
if (prio >= priouse)
continue;
priouse = prio;
if (rc >= MANDOCLEVEL_FATAL) {
fprintf(stderr, "fatal mandoc error: %s/%s\n",
req->q.manpath, file);
- resp_error_internal();
+ pg_error_internal();
return;
}
- snprintf(opts, sizeof(opts),
- "fragment,man=%s?query=%%N&sec=%%S",
- scriptname);
+ snprintf(opts, sizeof(opts), "fragment,man=%s?"
+ "manpath=%s&query=%%N&sec=%%S&arch=%s",
+ scriptname, req->q.manpath,
+ req->q.arch ? req->q.arch : "");
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();
+ pg_error_internal();
mparse_free(mp);
return;
}
static void
resp_show(const struct req *req, const char *file)
{
- if ('.' == file[0] || '/' == file[1])
+
+ if ('.' == file[0] && '/' == file[1])
file += 2;
if ('c' == *file)
}
static void
-pg_show(const struct req *req, const char *path)
+pg_show(struct req *req, const char *fullpath)
{
- char *sub;
+ char *manpath;
+ const char *file;
- if (NULL == path || NULL == (sub = strchr(path, '/'))) {
- resp_error_badrequest(
+ if ((file = strchr(fullpath, '/')) == NULL) {
+ pg_error_badrequest(
"You did not specify a page to show.");
return;
}
- *sub++ = '\0';
+ manpath = mandoc_strndup(fullpath, file - fullpath);
+ file++;
+
+ if ( ! validate_manpath(req, manpath)) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ free(manpath);
+ return;
+ }
/*
* Begin by chdir()ing into the manpath.
* relative to the manpath root.
*/
- if (-1 == chdir(path)) {
- resp_error_badrequest(
- "You specified an invalid manpath.");
+ if (chdir(manpath) == -1) {
+ fprintf(stderr, "chdir %s: %s\n",
+ manpath, strerror(errno));
+ pg_error_internal();
+ free(manpath);
+ return;
+ }
+
+ if (strcmp(manpath, "mandoc")) {
+ free(req->q.manpath);
+ req->q.manpath = manpath;
+ } else
+ free(manpath);
+
+ if ( ! validate_filename(file)) {
+ pg_error_badrequest(
+ "You specified an invalid manual file.");
return;
}
resp_begin_html(200, NULL);
resp_searchform(req);
- resp_show(req, sub);
+ resp_show(req, file);
resp_end_html();
}
*/
if (-1 == (chdir(req->q.manpath))) {
- resp_error_badrequest(
- "You specified an invalid manpath.");
+ fprintf(stderr, "chdir %s: %s\n",
+ req->q.manpath, strerror(errno));
+ pg_error_internal();
return;
}
}
if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
- resp_noresult(req, "You entered an invalid query.");
+ pg_noresult(req, "You entered an invalid query.");
else if (0 == ressz)
- resp_noresult(req, "No results found.");
+ pg_noresult(req, "No results found.");
else
- resp_search(req, res, ressz);
+ pg_searchres(req, res, ressz);
for (i = 0; i < sz; i++)
free(cp[i]);
{
struct req req;
const char *path;
- char *querystring;
+ const char *querystring;
int i;
/* Scan our run-time environment. */
if (NULL == (scriptname = getenv("SCRIPT_NAME")))
scriptname = "";
- if (NULL == (httphost = getenv("HTTP_HOST")))
- httphost = "localhost";
+ if ( ! validate_urifrag(scriptname)) {
+ fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
+ scriptname);
+ pg_error_internal();
+ return(EXIT_FAILURE);
+ }
/*
* First we change directory into the MAN_DIR so that
if (-1 == chdir(MAN_DIR)) {
fprintf(stderr, "MAN_DIR: %s: %s\n",
MAN_DIR, strerror(errno));
- resp_error_internal();
+ pg_error_internal();
return(EXIT_FAILURE);
}
if (NULL != (querystring = getenv("QUERY_STRING")))
http_parse(&req, querystring);
+ if ( ! validate_manpath(&req, req.q.manpath)) {
+ pg_error_badrequest(
+ "You specified an invalid manpath.");
+ return(EXIT_FAILURE);
+ }
+
+ if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
+ pg_error_badrequest(
+ "You specified an invalid architecture.");
+ return(EXIT_FAILURE);
+ }
+
/* Dispatch to the three different pages. */
path = getenv("PATH_INFO");
else if (NULL != req.q.expr)
pg_search(&req);
else
- resp_index(&req);
+ pg_index(&req);
+ free(req.q.manpath);
+ free(req.q.arch);
+ free(req.q.sec);
+ free(req.q.expr);
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.
*/
char *dp;
size_t dpsz;
- if (NULL == (fp = fopen("manpath.conf", "r")))
- return;
+ if (NULL == (fp = fopen("manpath.conf", "r"))) {
+ fprintf(stderr, "%s/manpath.conf: %s\n",
+ MAN_DIR, strerror(errno));
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
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);
+ dp = mandoc_strndup(dp, dpsz);
+ if ( ! validate_urifrag(dp)) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "unsafe path \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ if (NULL != strchr(dp, '/')) {
+ fprintf(stderr, "%s/manpath.conf contains "
+ "path with slash \"%s\"\n", MAN_DIR, dp);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
+ }
+ req->p[req->psz++] = dp;
+ }
+
+ if ( req->p == NULL ) {
+ fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
+ pg_error_internal();
+ exit(EXIT_FAILURE);
}
}