-/* $Id: cgi.c,v 1.77 2014/07/19 13:15:11 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 pg_search(const struct req *);
static void pg_searchres(const struct req *,
struct manpage *, size_t);
-static void pg_show(const struct req *, const char *);
+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);
}
/*
- * 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. */
+
+ valsz = strcspn(++qs, ";&");
+ val = mandoc_strndup(qs, valsz);
+ qs += valsz;
+
+ /* Decode and catch encoding errors. */
+
+ if ( ! (http_decode(key) && http_decode(val)))
+ goto next;
- /* Just abort handling. */
+ /* Handle key-value pairs. */
- if ( ! http_decode(key))
- break;
- if (NULL != val && ! http_decode(val))
- break;
+ if ( ! strcmp(key, "query"))
+ set_query_attr(&req->q.expr, &val);
- if (0 == strcmp(key, "query"))
- req->q.expr = val;
- else if (0 == strcmp(key, "manpath")) {
+ else if ( ! strcmp(key, "apropos"))
+ req->q.equal = !strcmp(val, "0");
+
+ else if ( ! strcmp(key, "manpath")) {
#ifdef COMPAT_OLDURI
- if (0 == strncmp(val, "OpenBSD ", 8)) {
+ if ( ! strncmp(val, "OpenBSD ", 8)) {
val[7] = '-';
if ('C' == val[8])
val[8] = 'c';
}
#endif
- req->q.manpath = val;
- } else if (0 == strcmp(key, "apropos"))
- req->q.equal = !strcmp(val, "0");
- else if (0 == strcmp(key, "sec")) {
- if (strcmp(val, "0"))
- req->q.sec = val;
+ set_query_attr(&req->q.manpath, &val);
+ }
+
+ else if ( ! (strcmp(key, "sec")
#ifdef COMPAT_OLDURI
- } else if (0 == strcmp(key, "sektion")) {
- if (strcmp(val, "0"))
- req->q.sec = val;
+ && strcmp(key, "sektion")
#endif
- } else if (0 == strcmp(key, "arch")) {
- if (strcmp(val, "default"))
- req->q.arch = val;
+ )) {
+ 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)
{
static void
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;
* without any delay.
*/
printf("Status: 303 See Other\r\n");
- printf("Location: %s/%s/%s?",
- scriptname, req->q.manpath, r[0].file);
+ printf("Location: http://%s%s/%s/%s?",
+ 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;
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) {
}
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, '/'))) {
+ 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, path)) {
+ if ( ! validate_manpath(req, manpath)) {
pg_error_badrequest(
"You specified an invalid manpath.");
+ free(manpath);
return;
}
* relative to the manpath root.
*/
- if (-1 == chdir(path)) {
+ if (chdir(manpath) == -1) {
fprintf(stderr, "chdir %s: %s\n",
- path, strerror(errno));
+ manpath, strerror(errno));
pg_error_internal();
+ free(manpath);
return;
}
- if ( ! validate_filename(sub)) {
+ 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();
}
{
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 ( ! 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
* subsequent scanning for manpath directories is rooted
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
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.
*/
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 ) {