Files
2021-05-08 13:04:11 +00:00

2265 lines
53 KiB
C

/* $Id$ */
/*
* Copyright (c) 2012, 2014--2020 Kristaps Dzonsons <kristaps@bsd.lv>
*
* 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 "config.h"
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#if HAVE_MD5
# include <sys/types.h>
# include <md5.h>
#endif
#include <poll.h>
#include <stdarg.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "kcgi.h"
#include "extern.h"
#define MD5Updatec(_ctx, _b, _sz) \
MD5Update((_ctx), (const uint8_t *)(_b), (_sz))
enum mimetype {
MIMETYPE_UNKNOWN,
MIMETYPE_TRANSFER_ENCODING,
MIMETYPE_DISPOSITION,
MIMETYPE_TYPE
};
/*
* For handling HTTP multipart forms.
* This consists of data for a single multipart form entry.
*/
struct mime {
char *disp; /* content disposition */
char *name; /* name of form entry */
size_t namesz; /* size of "name" string */
char *file; /* whether a file was specified */
char *ctype; /* content type */
size_t ctypepos; /* position of ctype in mimes */
char *xcode; /* encoding type */
char *bound; /* form entry boundary */
};
/*
* Both CGI and FastCGI use an environment for their HTTP parameters.
* CGI gets it from the actual environment; FastCGI from a transmitted
* environment.
* We use an abstract representation of those key-value pairs here so
* that we can use the same functions for both.
*/
struct env {
char *key; /* key (e.g., HTTP_HOST) */
size_t keysz;
char *val; /* value (e.g., `foo.com') */
size_t valsz;
};
/*
* Types of FastCGI requests.
* Defined in the FastCGI v1.0 spec, section 8.
*/
enum fcgi_type {
FCGI_BEGIN_REQUEST = 1,
FCGI_ABORT_REQUEST = 2,
FCGI_END_REQUEST = 3,
FCGI_PARAMS = 4,
FCGI_STDIN = 5,
FCGI_STDOUT = 6,
FCGI_STDERR = 7,
FCGI_DATA = 8,
FCGI_GET_VALUES = 9,
FCGI_GET_VALUES_RESULT = 10,
FCGI_UNKNOWN_TYPE = 11,
FCGI__MAX
};
/*
* The FastCGI `FCGI_Header' header layout.
* Defined in the FastCGI v1.0 spec, section 8.
*/
struct fcgi_hdr {
uint8_t version;
uint8_t type;
uint16_t requestId;
uint16_t contentLength;
uint8_t paddingLength;
uint8_t reserved;
};
/*
* The FastCGI `FCGI_BeginRequestBody' header layout.
* Defined in the FastCGI v1.0 spec, section 8.
*/
struct fcgi_bgn {
uint16_t role;
uint8_t flags;
uint8_t res[5];
};
/*
* A buffer of reads from kfcgi_control().
*/
struct fcgi_buf {
size_t sz; /* bytes in buffer */
size_t pos; /* current position (from last read) */
int fd; /* file descriptor */
char *buf; /* buffer itself */
};
/*
* Parameters required to validate fields.
*/
struct parms {
int fd;
const char *const *mimes;
size_t mimesz;
const struct kvalid *keys;
size_t keysz;
enum input type;
};
const char *const kmethods[KMETHOD__MAX] = {
"ACL", /* KMETHOD_ACL */
"CONNECT", /* KMETHOD_CONNECT */
"COPY", /* KMETHOD_COPY */
"DELETE", /* KMETHOD_DELETE */
"GET", /* KMETHOD_GET */
"HEAD", /* KMETHOD_HEAD */
"LOCK", /* KMETHOD_LOCK */
"MKCALENDAR", /* KMETHOD_MKCALENDAR */
"MKCOL", /* KMETHOD_MKCOL */
"MOVE", /* KMETHOD_MOVE */
"OPTIONS", /* KMETHOD_OPTIONS */
"POST", /* KMETHOD_POST */
"PROPFIND", /* KMETHOD_PROPFIND */
"PROPPATCH", /* KMETHOD_PROPPATCH */
"PUT", /* KMETHOD_PUT */
"REPORT", /* KMETHOD_REPORT */
"TRACE", /* KMETHOD_TRACE */
"UNLOCK", /* KMETHOD_UNLOCK */
};
static const char *const krequs[KREQU__MAX] = {
"HTTP_ACCEPT", /* KREQU_ACCEPT */
"HTTP_ACCEPT_CHARSET", /* KREQU_ACCEPT_CHARSET */
"HTTP_ACCEPT_ENCODING", /* KREQU_ACCEPT_ENCODING */
"HTTP_ACCEPT_LANGUAGE", /* KREQU_ACCEPT_LANGUAGE */
"HTTP_AUTHORIZATION", /* KREQU_AUTHORIZATION */
"HTTP_DEPTH", /* KREQU_DEPTH */
"HTTP_FROM", /* KREQU_FROM */
"HTTP_HOST", /* KREQU_HOST */
"HTTP_IF", /* KREQU_IF */
"HTTP_IF_MODIFIED_SINCE", /* KREQU_IF_MODIFIED_SINCE */
"HTTP_IF_MATCH", /* KREQU_IF_MATCH */
"HTTP_IF_NONE_MATCH", /* KREQU_IF_NONE_MATCH */
"HTTP_IF_RANGE", /* KREQU_IF_RANGE */
"HTTP_IF_UNMODIFIED_SINCE", /* KREQU_IF_UNMODIFIED_SINCE */
"HTTP_MAX_FORWARDS", /* KREQU_MAX_FORWARDS */
"HTTP_PROXY_AUTHORIZATION", /* KREQU_PROXY_AUTHORIZATION */
"HTTP_RANGE", /* KREQU_RANGE */
"HTTP_REFERER", /* KREQU_REFERER */
"HTTP_USER_AGENT", /* KREQU_USER_AGENT */
};
static const char *const kauths[KAUTH_UNKNOWN] = {
NULL, /* KAUTH_NONE */
"basic", /* KAUTH_BASIC */
"digest", /* KAUTH_DIGEST */
"bearer", /* KAUTH_BEARER */
};
/*
* Parse the type/subtype field out of a content-type.
* The content-type is defined (among other places) in RFC 822, and is
* either the whole string or up until the ';', which marks the
* beginning of the parameters.
*/
static size_t
str2ctype(const struct parms *pp, const char *ctype)
{
size_t i, sz;
if (NULL == ctype)
return(pp->mimesz);
/* Stop at the content-type parameters. */
sz = strcspn(ctype, ";");
for (i = 0; i < pp->mimesz; i++)
if (sz == strlen(pp->mimes[i]) &&
0 == strncasecmp(pp->mimes[i], ctype, sz))
break;
return(i);
}
/*
* Given a parsed field "key" with value "val" of size "valsz" and MIME
* information "mime", first try to look it up in the array of
* recognised keys ("pp->keys") and optionally validate.
* Then output the type, parse status (key, type, etc.), and values read
* by the parent input() function.
*/
static void
output(const struct parms *pp, char *key,
char *val, size_t valsz, struct mime *mime)
{
size_t i;
ptrdiff_t diff;
char *save;
struct kpair pair;
memset(&pair, 0, sizeof(struct kpair));
pair.key = key;
pair.val = save = val;
pair.valsz = valsz;
pair.file = NULL == mime ? NULL : mime->file;
pair.ctype = NULL == mime ? NULL : mime->ctype;
pair.xcode = NULL == mime ? NULL : mime->xcode;
pair.ctypepos = NULL == mime ? pp->mimesz : mime->ctypepos;
pair.type = KPAIR__MAX;
/*
* Look up the key name in our key table.
* If we find it and it has a validator, then run the validator
* and record the output.
* If we fail, reset the type and clear the results.
* Either way, the keypos parameter is going to be the key
* identifier or keysz if none is found.
*/
for (i = 0; i < pp->keysz; i++) {
if (strcmp(pp->keys[i].name, pair.key))
continue;
if (NULL == pp->keys[i].valid)
break;
if ( ! pp->keys[i].valid(&pair)) {
pair.state = KPAIR_INVALID;
pair.type = KPAIR__MAX;
memset(&pair.parsed, 0, sizeof(union parsed));
} else
pair.state = KPAIR_VALID;
break;
}
pair.keypos = i;
fullwrite(pp->fd, &pp->type, sizeof(enum input));
fullwriteword(pp->fd, pair.key);
fullwrite(pp->fd, &pair.valsz, sizeof(size_t));
fullwrite(pp->fd, pair.val, pair.valsz);
fullwrite(pp->fd, &pair.state, sizeof(enum kpairstate));
fullwrite(pp->fd, &pair.type, sizeof(enum kpairtype));
fullwrite(pp->fd, &pair.keypos, sizeof(size_t));
if (KPAIR_VALID == pair.state)
switch (pair.type) {
case (KPAIR_DOUBLE):
fullwrite(pp->fd,
&pair.parsed.d, sizeof(double));
break;
case (KPAIR_INTEGER):
fullwrite(pp->fd,
&pair.parsed.i, sizeof(int64_t));
break;
case (KPAIR_STRING):
assert(pair.parsed.s >= pair.val);
assert(pair.parsed.s <= pair.val + pair.valsz);
diff = pair.val - pair.parsed.s;
fullwrite(pp->fd, &diff, sizeof(ptrdiff_t));
break;
default:
break;
}
fullwriteword(pp->fd, pair.file);
fullwriteword(pp->fd, pair.ctype);
fullwrite(pp->fd, &pair.ctypepos, sizeof(size_t));
fullwriteword(pp->fd, pair.xcode);
/*
* We can write a new "val" in the validator allocated on the
* heap: if we do, free it here.
*/
if (save != pair.val)
free(pair.val);
}
/*
* Read full stdin request into memory.
* This reads at most "len" bytes and NUL-terminates the results, the
* length of which may be less than "len" and is stored in *szp if not
* NULL.
* Returns the pointer to the data.
* NOTE: we can't use fullread() here because we may not get the total
* number of bytes requested.
* NOTE: "szp" can legit be set to zero.
*/
static char *
scanbuf(size_t len, size_t *szp)
{
ssize_t ssz;
size_t sz;
char *p;
int rc;
struct pollfd pfd;
pfd.fd = STDIN_FILENO;
pfd.events = POLLIN;
/* Allocate the entire buffer here. */
if ((p = kxmalloc(len + 1)) == NULL)
_exit(EXIT_FAILURE);
/*
* Keep reading til we get all the data or the sender stops
* giving us data---whichever comes first.
* Use kutil_warn[x] and _exit to avoid flushing buffers.
*/
for (sz = 0; sz < len; sz += (size_t)ssz) {
if ((rc = poll(&pfd, 1, INFTIM)) < 0) {
kutil_warn(NULL, NULL, "poll");
_exit(EXIT_FAILURE);
} else if (0 == rc) {
kutil_warnx(NULL, NULL, "poll: timeout!?");
ssz = 0;
continue;
}
if (!(pfd.revents & POLLIN))
break;
if ((ssz = read(STDIN_FILENO, p + sz, len - sz)) < 0) {
kutil_warn(NULL, NULL, "read");
_exit(EXIT_FAILURE);
} else if (ssz == 0)
break;
}
if (sz < len)
kutil_warnx(NULL, NULL, "content size mismatch: "
"have %zu while %zu specified", sz, len);
/* ALWAYS NUL-terminate. */
p[sz] = '\0';
if (szp != NULL)
*szp = sz;
return p;
}
/*
* Reset a particular mime component.
* We can get duplicates, so reallocate.
*/
static void
mime_reset(char **dst, const char *src)
{
free(*dst);
if ((*dst = kxstrdup(src)) == NULL)
_exit(EXIT_FAILURE);
}
/*
* Free up all MIME headers.
* We might call this more than once, so make sure that it can be
* invoked again by setting the memory to zero.
*/
static void
mime_free(struct mime *mime)
{
free(mime->disp);
free(mime->name);
free(mime->file);
free(mime->ctype);
free(mime->xcode);
free(mime->bound);
memset(mime, 0, sizeof(struct mime));
}
/*
* Parse out all MIME headers.
* This is defined by RFC 2045.
* This returns TRUE if we've parsed up to (and including) the last
* empty CRLF line, or FALSE if something has gone wrong (e.g., parse
* error, out of memory).
* If FALSE, parsing should stop immediately.
*/
static int
mime_parse(const struct parms *pp, struct mime *mime,
char *buf, size_t len, size_t *pos)
{
char *key, *val, *keyend, *end, *start, *line;
enum mimetype type;
int rc = 0;
mime_free(mime);
while (*pos < len) {
/* Each MIME line ends with a CRLF. */
start = &buf[*pos];
end = memmem(start, len - *pos, "\r\n", 2);
if (end == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"MIME header line without CRLF");
return 0;
}
/*
* NUL-terminate to make a nice line.
* Then re-set our starting position.
*/
*end = '\0';
*pos += (end - start) + 2;
/* Empty CRLF line: we're done here! */
if (*start == '\0') {
rc = 1;
break;
}
/*
* Find end of MIME statement name.
* The RFCs disagree on white-space before the colon,
* but as it's allowed in the original RFC 822 and
* obsolete syntax should be supported, we do so here.
*/
key = start;
if ((val = strchr(key, ':')) == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"MIME header without colon separator");
return 0;
} else if (key != val) {
keyend = val - 1;
while (keyend >= key && *keyend == ' ')
*keyend-- = '\0';
}
*val++ = '\0';
while (*val == ' ')
val++;
if (*key == '\0')
kutil_warnx(NULL, NULL, "RFC "
"warning: empty MIME header name");
/*
* Set "line" to be at the MIME value subpart, for
* example, "Content-type: text/plain; charset=us-ascii"
* would put us at the parts before "charset".
*/
line = NULL;
if ((line = strchr(val, ';')) != NULL)
*line++ = '\0';
/*
* Allow these specific MIME header statements.
* We'll follow up by parsing specific information from
* the header values, so remember what we parsed.
*/
if (strcasecmp(key, "content-transfer-encoding") == 0) {
mime_reset(&mime->xcode, val);
type = MIMETYPE_TRANSFER_ENCODING;
} else if (strcasecmp(key, "content-disposition") == 0) {
mime_reset(&mime->disp, val);
type = MIMETYPE_DISPOSITION;
} else if (strcasecmp(key, "content-type") == 0) {
mime_reset(&mime->ctype, val);
type = MIMETYPE_TYPE;
} else
type = MIMETYPE_UNKNOWN;
/*
* Process subpart only for content-type and
* content-disposition.
* The rest have no information we want: silently ignore them.
*/
if (type != MIMETYPE_TYPE &&
type != MIMETYPE_DISPOSITION)
continue;
while ((key = line) != NULL) {
while (*key == ' ')
key++;
if (*key == '\0')
break;
/*
* It's not clear whether we're allowed to have
* OWS before the separator, but allow for it
* anyway.
*/
if ((val = strchr(key, '=')) == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"MIME header without sub-part "
"separator");
return 0;
} else if (key != val) {
keyend = val - 1;
while (keyend >= key && *keyend == ' ')
*keyend-- = '\0';
}
*val++ = '\0';
if (*key == '\0')
kutil_warnx(NULL, NULL, "RFC warning: "
"empty MIME sub-part name");
/* Quoted string. */
if (*val == '"') {
val++;
line = strchr(val, '"');
if (line == NULL) {
kutil_warnx(NULL, NULL, "RFC "
"error: quoted MIME "
"header sub-part not "
"terminated");
return 0;
}
*line++ = '\0';
/*
* It's unclear as to whether this is
* allowed (white-space before the
* semicolon separator), but let's
* accommodate for it anyway.
*/
while (*line == ' ')
line++;
if (*line == ';')
line++;
} else if ((line = strchr(val, ';')) != NULL)
*line++ = '\0';
/* White-listed sub-commands. */
if (type == MIMETYPE_DISPOSITION) {
if (strcasecmp(key, "filename") == 0)
mime_reset(&mime->file, val);
else if (strcasecmp(key, "name") == 0)
mime_reset(&mime->name, val);
} else if (type == MIMETYPE_TYPE) {
if (strcasecmp(key, "boundary") == 0)
mime_reset(&mime->bound, val);
}
}
}
mime->ctypepos = str2ctype(pp, mime->ctype);
if (!rc)
kutil_warnx(NULL, NULL, "RFC error: unexpected "
"end of file while parsing MIME headers");
return rc;
}
/*
* Parse keys and values separated by newlines.
* I'm not aware of any standard that defines this, but the W3
* guidelines for HTML give a rough idea.
* FIXME: deprecate this.
*/
static void
parse_pairs_text(const struct parms *pp, char *p)
{
char *key, *val;
kutil_warnx(NULL, NULL, "RFC warning: "
"text/plain encoding is deprecated");
while (p != NULL && *p != '\0') {
while (*p == ' ')
p++;
/*
* Key/value pair.
* No value is a warning (not processed).
*/
key = p;
val = NULL;
if (NULL != (p = strchr(p, '='))) {
*p++ = '\0';
val = p;
if ((p = strstr(val, "\r\n")) != NULL) {
*p = '\0';
p += 2;
}
} else {
if ((p = strstr(key, "\r\n")) != NULL) {
*p = '\0';
p += 2;
}
kutil_warnx(NULL, NULL, "RFC warning: "
"key with no value");
continue;
}
if (*key == '\0')
kutil_warnx(NULL, NULL, "RFC warning: "
"zero-length key");
else
output(pp, key, val, strlen(val), NULL);
}
}
/*
* Parse an HTTP message that has a given content-type.
* This happens with, e.g., PUT requests.
* We fake up a "name" for this (it's not really a key-value pair) of an
* empty string, then pass that to the validator and forwarder.
*/
static void
parse_body(const char *ct, const struct parms *pp, char *b, size_t bsz)
{
char name;
struct mime mime;
memset(&mime, 0, sizeof(struct mime));
if ((mime.ctype = kxstrdup(ct)) == NULL)
_exit(EXIT_FAILURE);
mime.ctypepos = str2ctype(pp, mime.ctype);
name = '\0';
output(pp, &name, b, bsz, &mime);
free(mime.ctype);
}
/*
* Parse out key-value pairs from an HTTP cookie.
* These are not URL encoded (at this phase): they're just simple
* key-values "crumbs" with opaque values.
* This is defined by RFC 6265, however, we don't [yet] do the
* quoted-string implementation, nor do we check for accepted
* characters so long as the delimiters aren't used.
*/
static void
parse_pairs(const struct parms *pp, char *p)
{
char *key, *val;
while (p != NULL && *p != '\0') {
while (*p == ' ')
p++;
/*
* Don't allow key-pair without a value.
* Keys shouldn't be zero-length.
*/
key = p;
val = NULL;
if ((p = strchr(p, '=')) != NULL) {
*p++ = '\0';
val = p;
if ((p = strchr(p, ';')) != NULL)
*p++ = '\0';
} else {
if ((p = strchr(key, ';')) != NULL)
p++;
kutil_warnx(NULL, NULL, "RFC error: "
"cookie key pair without value");
continue;
}
/* This is sort-of allowed. */
if (*key == '\0')
kutil_warnx(NULL, NULL, "RFC warning: "
"cookie zero-length key");
else
output(pp, key, val, strlen(val), NULL);
}
}
/*
* Parse out key-value pairs from an HTTP request variable.
* This is either a POST or GET string.
* This MUST be a non-binary (i.e., NUL-terminated) string!
*/
static void
parse_pairs_urlenc(const struct parms *pp, char *p)
{
char *key, *val;
assert(p != NULL);
while (*p != '\0') {
while (*p == ' ')
p++;
key = p;
/*
* Look ahead to either '=' or one of the key-value
* terminators (or the end of the string).
* If we have the equal sign, then we're a key-value
* pair; otherwise, we're a standalone key value.
*/
p += strcspn(p, "=;&");
if (*p == '=') {
*p++ = '\0';
val = p;
p += strcspn(p, ";&");
} else
val = p;
if (*p != '\0')
*p++ = '\0';
/*
* Both the key and the value can be URL encoded, so
* decode those into the character string now.
* If decoding fails, don't decode the given pair, but
* instead move on to the next one after logging the
* failure.
*/
if (*key == '\0')
kutil_warnx(NULL, NULL, "RFC warning: "
"zero-length URL-encoded key");
else if (khttp_urldecode_inplace(key) == KCGI_FORM)
kutil_warnx(NULL, NULL, "RFC warning: "
"malformed key URL-encoding");
else if (khttp_urldecode_inplace(val) == KCGI_FORM)
kutil_warnx(NULL, NULL, "RFC warning: "
"malformed value URL-encoding");
else
output(pp, key, val, strlen(val), NULL);
}
}
/*
* This is described by the "multipart-body" BNF part of RFC 2046,
* section 5.1.1.
* We return TRUE if the parse was ok, FALSE if errors occurred (all
* calling parsers should bail too).
*/
static int
parse_multiform(const struct parms *pp, char *name,
const char *bound, char *buf, size_t len, size_t *pos)
{
struct mime mime;
size_t endpos, bbsz, partsz;
char *ln, *bb;
int rc, first;
/* Define our buffer boundary. */
if ((rc = kxasprintf(&bb, "\r\n--%s", bound)) == -1)
_exit(EXIT_FAILURE);
assert(rc > 0);
bbsz = rc;
rc = 0;
memset(&mime, 0, sizeof(struct mime));
/* Read to the next instance of a buffer boundary. */
for (first = 1; *pos < len; first = 0, *pos = endpos) {
/*
* The (first ? 2 : 0) is because the first prologue
* boundary will not incur an initial CRLF, so our bb is
* past the CRLF and two bytes smaller.
*/
ln = memmem(&buf[*pos], len - *pos,
bb + (first ? 2 : 0),
bbsz - (first ? 2 : 0));
if (ln == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"EOF when scanning for boundary");
goto out;
}
/*
* Set "endpos" to point to the beginning of the next
* multipart component, i.e, the end of the boundary
* "bb" string.
* Again, be respectful of whether we should scan after
* the lack of initial CRLF.
*/
endpos = *pos + (ln - &buf[*pos]) +
bbsz - (first ? 2 : 0);
/* Check buffer space. */
if (endpos > len - 2) {
kutil_warnx(NULL, NULL, "RFC error: multipart "
"section writes into trailing CRLF");
goto out;
}
/*
* Terminating boundary has an initial trailing "--".
* If not terminating, must be followed by a CRLF.
* If terminating, RFC 1341 says we can ignore whatever
* comes after the last boundary.
*/
if (memcmp(&buf[endpos], "--", 2)) {
while (endpos < len && buf[endpos] == ' ')
endpos++;
if (endpos > len - 2 ||
memcmp(&buf[endpos], "\r\n", 2)) {
kutil_warnx(NULL, NULL, "RFC error: "
"multipart boundary without "
"CRLF");
goto out;
}
endpos += 2;
} else
endpos = len;
/* First section: jump directly to reprocess. */
if (first)
continue;
/*
* Zero-length part.
* This shouldn't occur, but if it does, it'll screw up
* the MIME parsing (which requires a blank CRLF before
* considering itself finished).
*/
if ((partsz = ln - &buf[*pos]) == 0) {
kutil_warnx(NULL, NULL, "RFC error: "
"zero-length multipart section");
continue;
}
/* We now read our MIME headers, bailing on error. */
if (!mime_parse(pp, &mime, buf, *pos + partsz, pos)) {
kutil_warnx(NULL, NULL, "RFC error: "
"nested error parsing MIME headers");
goto out;
}
/*
* As per RFC 2388, we need a name and disposition.
* Note that multipart/mixed bodies will inherit the
* name of their parent, so the mime.name is ignored.
*/
if (mime.name == NULL && name == NULL) {
kutil_warnx(NULL, NULL,
"RFC error: no MIME name");
continue;
} else if (mime.disp == NULL) {
kutil_warnx(NULL, NULL,
"RFC error: no MIME disposition");
continue;
}
/*
* As per RFC 2045, we default to text/plain.
* We then re-lookup the ctypepos after doing so.
*/
if (mime.ctype == NULL) {
mime.ctype = kxstrdup("text/plain");
if (mime.ctype == NULL)
_exit(EXIT_FAILURE);
mime.ctypepos = str2ctype(pp, mime.ctype);
}
partsz = ln - &buf[*pos];
/*
* Multipart sub-handler.
* We only recognise the multipart/mixed handler.
* This will route into our own function, inheriting the
* current name for content.
*/
if (strcasecmp(mime.ctype, "multipart/mixed") == 0) {
if (mime.bound == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"no mixed multipart boundary");
goto out;
}
if (!parse_multiform(pp,
name != NULL ? name : mime.name,
mime.bound, buf, *pos + partsz, pos)) {
kutil_warnx(NULL, NULL, "RFC error: "
"nested error parsing mixed "
"multipart section");
goto out;
}
continue;
}
assert(buf[*pos + partsz] == '\r' ||
buf[*pos + partsz] == '\0');
if (buf[*pos + partsz] != '\0')
buf[*pos + partsz] = '\0';
/* Assign all of our key-value pair data. */
output(pp, name != NULL ? name : mime.name,
&buf[*pos], partsz, &mime);
}
/*
* According to the specification, we can have transport
* padding, a CRLF, then the epilogue.
* But since we don't care about that crap, just pretend that
* everything's fine and exit.
*/
rc = 1;
out:
free(bb);
mime_free(&mime);
return rc;
}
/*
* Parse the boundary from a multipart CONTENT_TYPE and pass it to the
* actual parsing engine.
* This doesn't actually handle any part of the MIME specification.
*/
static void
parse_multi(const struct parms *pp, char *line, char *b, size_t bsz)
{
char *cp;
size_t len = 0;
while (*line == ' ')
line++;
if (*line++ != ';') {
kutil_warnx(NULL, NULL, "RFC error: expected "
"semicolon following multipart declaration");
return;
}
while (*line == ' ')
line++;
/* We absolutely need the boundary marker. */
if (strncmp(line, "boundary", 8)) {
kutil_warnx(NULL, NULL, "RFC error: expected "
"boundary following multipart declaration");
return;
}
line += 8;
while (*line == ' ')
line++;
if (*line++ != '=') {
kutil_warnx(NULL, NULL, "RFC error: expected "
"key-value for multipart boundary");
return;
}
while (*line == ' ')
line++;
/*
* Make sure the line is terminated in the right place.
* XXX: if it's not, what we do may not properly follow RFC
* 2046, 5.1.1, which specifically lays out the boundary
* characters.
* We simply jump to the first whitespace.
*/
if (*line == '"') {
if ((cp = strchr(++line, '"')) == NULL) {
kutil_warnx(NULL, NULL, "RFC error: "
"unterminated boundary quoted string");
return;
}
*cp = '\0';
} else
line[strcspn(line, " ")] = '\0';
/*
* If we have data following the boundary declaration, we simply
* ignore it.
* The RFC mandates the existence of the boundary, but is silent
* as to whether anything can come after it.
*/
parse_multiform(pp, NULL, line, b, bsz, &len);
}
/*
* Output all of the HTTP_xxx headers.
* This transforms the HTTP_xxx header (CGI form) into HTTP form, which
* is the second part title-cased, e.g., HTTP_FOO = Foo.
* Disallow zero-length values as per RFC 3875, 4.1.18.
*/
static void
kworker_child_env(const struct env *env, int fd, size_t envsz)
{
size_t i, j, sz, reqs;
int first;
enum krequ requ;
char c;
const char *cp;
for (reqs = i = 0; i < envsz; i++)
if (strncmp(env[i].key, "HTTP_", 5) == 0 &&
env[i].key[5] != '\0')
reqs++;
fullwrite(fd, &reqs, sizeof(size_t));
/*
* Process known headers (starting with HTTP_).
* We must have non-zero-length keys.
*/
for (i = 0; i < envsz; i++) {
if (strncmp(env[i].key, "HTTP_", 5) ||
env[i].key[5] == '\0')
continue;
for (requ = 0; requ < KREQU__MAX; requ++)
if (strcmp(krequs[requ], env[i].key) == 0)
break;
fullwrite(fd, &requ, sizeof(enum krequ));
/*
* According to RFC 3875, 4.1.18, HTTP headers are
* re-written into CGI environment variables by
* uppercasing and converting dashes to underscores.
* In this part, we [try to] reverse that so that the
* headers are properly identified.
* (We also skip the HTTP_ leading part.)
*/
sz = env[i].keysz - 5;
cp = env[i].key + 5;
fullwrite(fd, &sz, sizeof(size_t));
for (j = 0, first = 1; j < sz; j++) {
if (cp[j] == '_') {
c = '-';
first = 1;
} else if (first) {
c = cp[j];
first = 0;
} else
c = tolower((unsigned char)cp[j]);
fullwrite(fd, &c, 1);
}
fullwrite(fd, &env[i].valsz, sizeof(size_t));
fullwrite(fd, env[i].val, env[i].valsz);
}
}
/*
* Like getenv() but for our env structure.
*/
static char *
kworker_env(struct env *env, size_t envsz, const char *key)
{
size_t i;
for (i = 0; i < envsz; i++)
if (strcmp(env[i].key, key) == 0)
return env[i].val;
return NULL;
}
/*
* Output the method found in our environment.
* Returns the method.
* Defaults to KMETHOD_GET, uses KETHOD__MAX if the method was bad.
*/
static enum kmethod
kworker_child_method(struct env *env, int fd, size_t envsz)
{
enum kmethod meth;
const char *cp;
/* RFC 3875, 4.1.12. */
/* We assume GET if not supplied. */
meth = KMETHOD_GET;
if ((cp = kworker_env(env, envsz, "REQUEST_METHOD")) != NULL)
for (meth = 0; meth < KMETHOD__MAX; meth++)
if (strcmp(kmethods[meth], cp) == 0)
break;
fullwrite(fd, &meth, sizeof(enum kmethod));
return meth;
}
/*
* Output the web server's authentication.
* Defaults to KAUTH_NONE.
*/
static void
kworker_child_auth(struct env *env, int fd, size_t envsz)
{
enum kauth auth = KAUTH_NONE;
const char *cp;
/* Determine authentication: RFC 3875, 4.1.1. */
if ((cp = kworker_env(env, envsz, "AUTH_TYPE")) != NULL)
for (auth = 0; auth < KAUTH_UNKNOWN; auth++) {
if (kauths[auth] == NULL)
continue;
if (strcmp(kauths[auth], cp) == 0)
break;
}
fullwrite(fd, &auth, sizeof(enum kauth));
}
/*
* Send the raw (i.e., un-webserver-filtered) authorisation to the
* parent.
* Most web servers will `handle this for us'. Ugh.
*/
static int
kworker_child_rawauth(struct env *env, int fd, size_t envsz)
{
return kworker_auth_child(fd,
kworker_env(env, envsz, "HTTP_AUTHORIZATION"));
}
/*
* Send our HTTP scheme (secure or not) to the parent.
*/
static void
kworker_child_scheme(struct env *env, int fd, size_t envsz)
{
const char *cp;
enum kscheme scheme;
/*
* This isn't defined in any RFC.
* It seems to be the best way of getting whether we're HTTPS,
* as the SERVER_PROTOCOL (RFC 3875, 4.1.16) doesn't reliably
* return the scheme.
*/
if ((cp = kworker_env(env, envsz, "HTTPS")) == NULL)
cp = "off";
scheme = strcasecmp(cp, "on") == 0 ?
KSCHEME_HTTPS : KSCHEME_HTTP;
fullwrite(fd, &scheme, sizeof(enum kscheme));
}
/*
* Send remote address to the parent.
* This is required by RFC 3875, 4.1.8.
* Use 127.0.0.1 on protocol violation.
*/
static void
kworker_child_remote(struct env *env, int fd, size_t envsz)
{
const char *cp;
if ((cp = kworker_env(env, envsz, "REMOTE_ADDR")) == NULL) {
kutil_warnx(NULL, NULL, "RFC warning: "
"remote address not set");
cp = "127.0.0.1";
}
fullwriteword(fd, cp);
}
/*
* Parse and send the port to the parent.
* This is required by RFC 3875, 4.1.15.
* Use port 80 if not provided or on parse error.
*/
static void
kworker_child_port(struct env *env, int fd, size_t envsz)
{
uint16_t port = 80;
const char *cp, *er;
if ((cp = kworker_env(env, envsz, "SERVER_PORT")) != NULL) {
port = strtonum(cp, 0, UINT16_MAX, &er);
if (er != NULL) {
kutil_warnx(NULL, NULL, "RFC warning: "
"invalid server port value");
port = 80;
}
} else
kutil_warnx(NULL, NULL, "RFC warning: "
"server port not set");
fullwrite(fd, &port, sizeof(uint16_t));
}
/*
* Send requested host to the parent.
* This is required by RFC 7230, 5.4.
* Use "localhost" if not provided.
*/
static void
kworker_child_httphost(struct env *env, int fd, size_t envsz)
{
const char *cp;
if ((cp = kworker_env(env, envsz, "HTTP_HOST")) == NULL) {
kutil_warnx(NULL, NULL, "RFC warning: host not set");
cp = "localhost";
}
fullwriteword(fd, cp);
}
/*
* Send script name to the parent.
* This is required by RFC 3875, 4.1.13.
* Use the empty string on error.
*/
static void
kworker_child_scriptname(struct env *env, int fd, size_t envsz)
{
const char *cp;
if ((cp = kworker_env(env, envsz, "SCRIPT_NAME")) == NULL) {
kutil_warnx(NULL, NULL, "RFC warning: "
"script name not set");
cp = "";
}
fullwriteword(fd, cp);
}
/*
* Parse all path information (subpath, path, etc.) and send to parent.
*/
static void
kworker_child_path(struct env *env, int fd, size_t envsz)
{
char *cp, *ep, *sub;
size_t len;
/*
* Parse the first path element (the page we want to access),
* subsequent path information, and the file suffix. We convert
* suffix and path element into the respective enum's inline.
*/
cp = kworker_env(env, envsz, "PATH_INFO");
fullwriteword(fd, cp);
/* This isn't possible in the real world. */
if (cp != NULL && *cp == '/')
cp++;
if (cp != NULL && *cp != '\0') {
ep = cp + strlen(cp) - 1;
while (ep > cp && *ep != '/' && *ep != '.')
ep--;
/* Start with writing our suffix. */
if (*ep == '.') {
*ep++ = '\0';
fullwriteword(fd, ep);
} else
fullwriteword(fd, NULL);
/* Now find the top-most path part. */
if ((sub = strchr(cp, '/')) != NULL)
*sub++ = '\0';
/* Send the base path. */
fullwriteword(fd, cp);
/* Send the path part. */
fullwriteword(fd, sub);
} else {
len = 0;
/* Suffix, base path, and path part. */
fullwrite(fd, &len, sizeof(size_t));
fullwrite(fd, &len, sizeof(size_t));
fullwrite(fd, &len, sizeof(size_t));
}
}
/*
* Construct the body hash component of an HTTP digest hash.
* See khttpdigest_validatehash(3) for where this is used.
* See RFC 2617.
* We only do this if our authorisation requires it!
*/
static void
kworker_child_bodymd5(int fd, const char *b, size_t bsz, int md5)
{
MD5_CTX ctx;
unsigned char hab[MD5_DIGEST_LENGTH];
size_t sz;
if (!md5) {
sz = 0;
fullwrite(fd, &sz, sizeof(size_t));
return;
}
MD5Init(&ctx);
MD5Updatec(&ctx, b, bsz);
MD5Final(hab, &ctx);
/* This is a binary write! */
sz = MD5_DIGEST_LENGTH;
fullwrite(fd, &sz, sizeof(size_t));
fullwrite(fd, hab, sz);
}
/*
* Parse and send the body of the request to the parent.
* This is arguably the most complex part of the system.
*/
static void
kworker_child_body(struct env *env, int fd, size_t envsz,
struct parms *pp, enum kmethod meth, char *b,
size_t bsz, unsigned int debugging, int md5)
{
size_t i, len = 0, sz;
char *cp, *bp = b;
const char *end;
int wrap;
/*
* The CONTENT_LENGTH must be a valid integer.
* Since we're storing into "len", make sure it's in size_t.
* If there's an error, it will default to zero.
* Note that LLONG_MAX < SIZE_MAX.
* RFC 3875, 4.1.2.
*/
if ((cp = kworker_env(env, envsz, "CONTENT_LENGTH")) != NULL)
len = strtonum(cp, 0, LLONG_MAX, NULL);
/* If zero, remember to print our MD5 value. */
if (len == 0) {
kworker_child_bodymd5(fd, "", 0, md5);
return;
}
/* Check FastCGI input lengths. */
if (bp != NULL && bsz != len)
kutil_warnx(NULL, NULL, "RFC warning: real (%zu) "
"and reported (%zu) content lengths differ",
bsz, len);
/*
* If a CONTENT_TYPE has been specified (i.e., POST or GET has
* been set -- we don't care which), then switch on that type
* for parsing out key value pairs.
* RFC 3875, 4.1.3.
* HTML5, 4.10.
* We only support the main three content types.
*/
pp->type = IN_FORM;
cp = kworker_env(env, envsz, "CONTENT_TYPE");
/*
* If we're CGI, read the request now.
* Note that the "bsz" can come out as zero.
*/
if (b == NULL)
b = scanbuf(len, &bsz);
assert(b != NULL);
/* If requested, print our MD5 value. */
kworker_child_bodymd5(fd, b, bsz, md5);
/*
* If we're debugging read bodies, emit the body line by line
* (or split at the 80-character mark).
*/
if (bsz && (debugging & KREQ_DEBUG_READ_BODY)) {
i = 0;
do {
if ((end = memchr(&b[i], '\n', bsz - i)) == NULL)
sz = bsz - i;
else
sz = (size_t)(end - &b[i]);
if ((wrap = sz > 80))
sz = 80;
kutil_info(NULL, NULL, "%lu-rx: %.*s%s",
(unsigned long)getpid(), (int)sz,
&b[i], wrap ? "..." : "");
i += wrap ? sz : sz + 1;
} while (i < bsz);
kutil_info(NULL, NULL, "%lu-rx: %zu B",
(unsigned long)getpid(), bsz);
}
if (cp != NULL) {
if (strcasecmp(cp, "application/x-www-form-urlencoded") == 0)
parse_pairs_urlenc(pp, b);
else if (strncasecmp(cp, "multipart/form-data", 19) == 0)
parse_multi(pp, cp + 19, b, bsz);
else if (meth == KMETHOD_POST && strcasecmp(cp, "text/plain") == 0)
parse_pairs_text(pp, b);
else
parse_body(cp, pp, b, bsz);
} else
parse_body(kmimetypes[KMIME_APP_OCTET_STREAM], pp, b, bsz);
/* Free CGI parsed buffer (FastCGI is done elsewhere). */
if (bp == NULL)
free(b);
}
/*
* Send query string data to parent.
* Even POST requests are allowed to have QUERY_STRING elements.
* Note: both QUERY_STRING and CONTENT_TYPE fields share the same field
* space.
*/
static void
kworker_child_query(struct env *env,
int fd, size_t envsz, struct parms *pp)
{
char *cp;
pp->type = IN_QUERY;
if (NULL != (cp = kworker_env(env, envsz, "QUERY_STRING")))
parse_pairs_urlenc(pp, cp);
}
/*
* Send cookies to our parent.
* These use the same syntax as QUERY_STRING elements, but don't share
* the same namespace (just as a means to differentiate the same names).
*/
static void
kworker_child_cookies(struct env *env,
int fd, size_t envsz, struct parms *pp)
{
char *cp;
pp->type = IN_COOKIE;
if ((cp = kworker_env(env, envsz, "HTTP_COOKIE")) != NULL)
parse_pairs(pp, cp);
}
/*
* Terminate the input fields for the parent.
*/
static void
kworker_child_last(int fd)
{
enum input last = IN__MAX;
fullwrite(fd, &last, sizeof(enum input));
}
/*
* This is the child kcgi process that's going to do the unsafe reading
* of network data to parse input.
* When it parses a field, it outputs the key, key size, value, and
* value size along with the field type.
* We use the CGI specification in RFC 3875.
*/
enum kcgi_err
kworker_child(int wfd,
const struct kvalid *keys, size_t keysz,
const char *const *mimes, size_t mimesz,
unsigned int debugging)
{
struct parms pp;
char *cp;
const char *start;
char **evp;
int md5;
enum kmethod meth;
size_t i;
extern char **environ;
struct env *envs = NULL;
size_t envsz;
pp.fd = wfd;
pp.keys = keys;
pp.keysz = keysz;
pp.mimes = mimes;
pp.mimesz = mimesz;
/*
* Pull the entire environment into an array.
*/
for (envsz = 0, evp = environ; NULL != *evp; evp++)
envsz++;
if (envsz) {
envs = kxcalloc(envsz, sizeof(struct env));
if (envs == NULL)
return KCGI_ENOMEM;
}
/*
* Pull all reasonable values from the environment into "envs".
* Filter out variables that don't meet RFC 3875, section 4.1.
* However, we're a bit more relaxed: we don't let through
* zero-length, non-ASCII, control characters, and whitespace.
*/
for (i = 0, evp = environ; *evp != NULL; evp++) {
if ((cp = strchr(*evp, '=')) == NULL || cp == *evp)
continue;
for (start = *evp; *start != '='; start++)
if (!isascii((unsigned char)*start) ||
!isgraph((unsigned char)*start))
break;
/*
* This means something is seriously wrong, so make sure
* that the operator knows.
*/
if (*start != '=') {
kutil_warnx(NULL, NULL, "RFC warning: "
"bad character in environment pair");
continue;
}
assert(i < envsz);
if ((envs[i].key = kxstrdup(*evp)) == NULL)
_exit(EXIT_FAILURE);
envs[i].val = strchr(envs[i].key, '=');
*envs[i].val++ = '\0';
envs[i].keysz = strlen(envs[i].key);
envs[i].valsz = strlen(envs[i].val);
i++;
}
/* Reset this, accounting for crappy entries. */
envsz = i;
/*
* Now run a series of transmissions based upon what's in our
* environment.
*/
kworker_child_env(envs, wfd, envsz);
meth = kworker_child_method(envs, wfd, envsz);
kworker_child_auth(envs, wfd, envsz);
md5 = kworker_child_rawauth(envs, wfd, envsz);
kworker_child_scheme(envs, wfd, envsz);
kworker_child_remote(envs, wfd, envsz);
kworker_child_path(envs, wfd, envsz);
kworker_child_scriptname(envs, wfd, envsz);
kworker_child_httphost(envs, wfd, envsz);
kworker_child_port(envs, wfd, envsz);
/* And now the message body itself. */
kworker_child_body(envs, wfd, envsz,
&pp, meth, NULL, 0, debugging, md5);
kworker_child_query(envs, wfd, envsz, &pp);
kworker_child_cookies(envs, wfd, envsz, &pp);
kworker_child_last(wfd);
/* Note: the "val" is from within the key. */
for (i = 0; i < envsz; i++)
free(envs[i].key);
free(envs);
return KCGI_OK;
}
/*
* Reads from the FastCGI control process, kfcgi_control(), are buffered
* according to what the control process can read from the web server.
* Here we read ahead til we have enough data for what currently needs
* to be read.
* Returns a pointer to the data of size "sz" or NULL if errors occured.
* If error is KCGI_OK, this *always* returns a buffer.
* The error is reported in "er".
*/
static char *
kworker_fcgi_read(struct fcgi_buf *b, size_t nsz, enum kcgi_err *er)
{
void *pp;
int rc;
size_t sz;
again:
*er = KCGI_OK;
if (b->pos + nsz <= b->sz) {
b->pos += nsz;
return &b->buf[b->pos - nsz];
}
/* Fill up the next frame. */
rc = fullread(b->fd, &sz, sizeof(size_t), 0, er);
if (rc <= 0) {
kutil_warnx(NULL, NULL, "FastCGI: "
"error reading frame size from control");
return NULL;
} else if (sz == 0) {
kutil_warnx(NULL, NULL, "FastCGI: connection "
"closed while reading frame size");
*er = KCGI_HUP;
return NULL;
}
if ((pp = kxrealloc(b->buf, b->sz + sz)) == NULL) {
*er = KCGI_ENOMEM;
return NULL;
}
b->buf = pp;
rc = fullread(b->fd, b->buf + b->sz, sz, 0, er);
if (rc <= 0) {
kutil_warnx(NULL, NULL, "FastCGI: error "
"reading frame data from control");
return NULL;
}
b->sz += sz;
goto again;
}
/*
* Read the FastCGI header (see section 8, Types and Contents,
* FCGI_Header, in the FastCGI Specification v1.0).
* Return KCGI_OK on success, KCGI_HUP on connection close, KCGI_FORM
* with FastCGI protocol errors, and a fatal error otherwise.
*/
static enum kcgi_err
kworker_fcgi_header(struct fcgi_buf *b, struct fcgi_hdr *hdr)
{
enum kcgi_err er;
const char *cp;
struct fcgi_hdr buf;
if ((cp = kworker_fcgi_read(b, 8, &er)) == NULL)
return er;
memcpy(&buf, cp, 8);
/* Translate from network-byte order. */
hdr->version = buf.version;
hdr->type = buf.type;
hdr->requestId = ntohs(buf.requestId);
hdr->contentLength = ntohs(buf.contentLength);
hdr->paddingLength = buf.paddingLength;
if (hdr->version == 1)
return KCGI_OK;
kutil_warnx(NULL, NULL, "FastCGI: bad header "
"version: %" PRIu8 " (want 1)", hdr->version);
return KCGI_FORM;
}
/*
* Read in the entire header and data for the begin sequence request.
* This is defined in section 5.1 of the v1.0 specification.
* Return KCGI_OK on success, KCGI_HUP on connection close, KCGI_FORM
* with FastCGI protocol errors, and a fatal error otherwise.
*/
static enum kcgi_err
kworker_fcgi_begin(struct fcgi_buf *b, uint16_t *rid)
{
struct fcgi_hdr hdr;
const struct fcgi_bgn *ptr;
const char *buf;
enum kcgi_err er;
/* Read the header entry. */
if ((er = kworker_fcgi_header(b, &hdr)) != KCGI_OK)
return er;
*rid = hdr.requestId;
if (hdr.type != FCGI_BEGIN_REQUEST) {
kutil_warnx(NULL, NULL, "FastCGI: bad type: %" PRIu8
" (want %d)", hdr.type, FCGI_BEGIN_REQUEST);
return KCGI_FORM;
}
/* Read the "begin" content and discard padding. */
buf = kworker_fcgi_read(b,
hdr.contentLength +
hdr.paddingLength, &er);
ptr = (const struct fcgi_bgn *)buf;
if (ptr->flags) {
kutil_warnx(NULL, NULL, "FastCGI: bad flags: %" PRId8
" (want 0)", ptr->flags);
return KCGI_FORM;
}
return KCGI_OK;
}
/*
* Read in a data stream as defined within section 5.3 of the v1.0
* specification.
* We might have multiple stdin buffers for the same data, so always
* append to the existing NUL-terminated buffer.
* Return KCGI_OK on success, KCGI_HUP on connection close, KCGI_FORM
* with FastCGI protocol errors, and a fatal error otherwise.
*/
static enum kcgi_err
kworker_fcgi_stdin(struct fcgi_buf *b, const struct fcgi_hdr *hdr,
unsigned char **sbp, size_t *ssz)
{
enum kcgi_err er;
void *ptr;
char *bp;
/* Read the "begin" content and discard padding. */
bp = kworker_fcgi_read(b,
hdr->contentLength +
hdr->paddingLength, &er);
/*
* Short-circuit: no data to read.
* The caller should detect this and stop reading from the
* FastCGI connection.
*/
if (hdr->contentLength == 0)
return KCGI_OK;
/*
* Use another buffer for the stdin.
* This is because our buffer (b->buf) consists of FastCGI
* frames (data interspersed with control information).
* Obviously, we want to extract our data from that.
* Make sure it's NUL-terminated!
* FIXME: check for addition overflow.
*/
ptr = kxrealloc(*sbp, *ssz + hdr->contentLength + 1);
if (ptr == NULL)
return KCGI_ENOMEM;
*sbp = ptr;
memcpy(*sbp + *ssz, bp, hdr->contentLength);
(*sbp)[*ssz + hdr->contentLength] = '\0';
*ssz += hdr->contentLength;
return KCGI_OK;
}
/*
* Read out a series of parameters contained within a FastCGI parameter
* request defined in section 5.2 of the v1.0 specification.
* Return KCGI_OK on success, KCGI_HUP on connection close, KCGI_FORM
* with FastCGI protocol errors, and a fatal error otherwise.
*/
static enum kcgi_err
kworker_fcgi_params(struct fcgi_buf *buf, const struct fcgi_hdr *hdr,
struct env **envs, size_t *envsz)
{
size_t i, remain, pos, keysz, valsz;
const unsigned char *b;
enum kcgi_err er;
void *ptr;
b = (unsigned char *)kworker_fcgi_read
(buf, hdr->contentLength +
hdr->paddingLength, &er);
if (b == NULL)
return er;
/*
* Loop through the string data that's laid out as a key length
* then value length, then key, then value.
* There can be arbitrarily many key-values per string.
*/
remain = hdr->contentLength;
pos = 0;
while (remain > 0) {
/* First read the lengths. */
assert(pos < hdr->contentLength);
if (0 != b[pos] >> 7) {
if (remain <= 3) {
kutil_warnx(NULL, NULL,
"FastCGI: bad parameter data");
return 0;
}
keysz = ((b[pos] & 0x7f) << 24) +
(b[pos + 1] << 16) +
(b[pos + 2] << 8) + b[pos + 3];
pos += 4;
remain -= 4;
} else {
keysz = b[pos];
pos++;
remain--;
}
if (remain < 1) {
kutil_warnx(NULL, NULL,
"FastCGI: bad parameter data");
return KCGI_FORM;
}
assert(pos < hdr->contentLength);
if (0 != b[pos] >> 7) {
if (remain <= 3) {
kutil_warnx(NULL, NULL,
"FastCGI: bad parameter data");
return KCGI_FORM;
}
valsz = ((b[pos] & 0x7f) << 24) +
(b[pos + 1] << 16) +
(b[pos + 2] << 8) + b[pos + 3];
pos += 4;
remain -= 4;
} else {
valsz = b[pos];
pos++;
remain--;
}
/* Make sure we have room for data. */
if (pos + keysz + valsz > hdr->contentLength) {
kutil_warnx(NULL, NULL,
"FastCGI: bad parameter data");
return KCGI_FORM;
}
remain -= keysz;
remain -= valsz;
/*
* First, make sure that the key is valid.
* There's no documented precedent for this, so we
* follow CGI's constraints in RFC 3875, sec. 4.1.
* If it's not valid, just skip it.
*/
for (i = 0; i < keysz; i++)
if (!isascii((unsigned char)b[pos + i]) ||
!isgraph((unsigned char)b[pos + i]))
break;
if (keysz == 0) {
kutil_warnx(NULL, NULL, "FastCGI warning: "
"empty environment parameter");
pos += valsz;
continue;
} else if (i < keysz) {
kutil_warnx(NULL, NULL, "RFC warning: bad "
"character in environment parameters");
pos += keysz + valsz;
continue;
}
/* Look up the key in our existing keys. */
for (i = 0; i < *envsz; i++) {
if ((*envs)[i].keysz != keysz)
continue;
if (memcmp((*envs)[i].key, &b[pos], keysz) == 0)
break;
}
/*
* If we don't have the key: expand our table.
* If we do, clear the current value.
*/
if (i == *envsz) {
ptr = kxreallocarray
(*envs, *envsz + 1,
sizeof(struct env));
if (ptr == NULL)
return KCGI_ENOMEM;
*envs = ptr;
(*envs)[i].key = kxmalloc(keysz + 1);
if ((*envs)[i].key == NULL)
return KCGI_ENOMEM;
memcpy((*envs)[i].key, &b[pos], keysz);
(*envs)[i].key[keysz] = '\0';
(*envs)[i].keysz = keysz;
(*envsz)++;
} else
free((*envs)[i].val);
pos += keysz;
/* Copy the value. */
(*envs)[i].val = kxmalloc(valsz + 1);
if ((*envs)[i].val == NULL)
return KCGI_ENOMEM;
memcpy((*envs)[i].val, &b[pos], valsz);
(*envs)[i].val[valsz] = '\0';
(*envs)[i].valsz = valsz;
pos += valsz;
}
return KCGI_OK;
}
/*
* This is executed by the untrusted child for FastCGI setups.
* Throughout, we follow the FastCGI specification, version 1.0, 29
* April 1996.
*/
void
kworker_fcgi_child(int wfd, int work_ctl,
const struct kvalid *keys, size_t keysz,
const char *const *mimes, size_t mimesz,
unsigned int debugging)
{
struct parms pp;
struct fcgi_hdr hdr;
enum kcgi_err er;
unsigned char *sbuf = NULL;
struct env *envs = NULL;
uint16_t rid;
uint32_t cookie = 0;
size_t i, ssz = 0, sz, envsz = 0;
int rc, md5;
enum kmethod meth;
struct fcgi_buf fbuf;
memset(&fbuf, 0, sizeof(struct fcgi_buf));
pp.fd = wfd;
pp.keys = keys;
pp.keysz = keysz;
pp.mimes = mimes;
pp.mimesz = mimesz;
/*
* Loop over all incoming sequences to this particular slave.
* Sequences must consist of a single FastCGI session as defined
* in the FastCGI version 1.0 reference document.
*
* If the connection closes out at any point, we receive a
* zero-length read from the control socket.
* The response to this should be to write an zero error code
* back to the control socket, then keep on listening.
* Otherwise, if we've read the full message, write a non-zero
* error code, then our identifier and cookie, then the rest
* goes directly to the parse routines in kworker_parent().
*/
for (;;) {
free(sbuf);
for (i = 0; i < envsz; i++) {
free(envs[i].key);
free(envs[i].val);
}
free(envs);
free(fbuf.buf);
sbuf = NULL;
ssz = 0;
envs = NULL;
envsz = 0;
cookie = 0;
memset(&fbuf, 0, sizeof(struct fcgi_buf));
fbuf.fd = work_ctl;
/*
* Begin by reading our magic cookie.
* This is emitted by kfcgi_control() at the start of
* our sequence.
* When we've finished reading data with success, we'll
* respond with this value.
*/
rc = fullread(fbuf.fd,
&cookie, sizeof(uint32_t), 1, &er);
if (rc < 0) {
kutil_warnx(NULL, NULL, "FastCGI: "
"error reading worker cookie");
break;
} else if (rc == 0) {
kutil_warnx(NULL, NULL, "FastCGI: "
"worker process termination");
break;
}
/* Now start the FastCGI sequence. */
er = kworker_fcgi_begin(&fbuf, &rid);
if (er == KCGI_HUP) {
kutil_warnx(NULL, NULL, "FastCGI: "
"connection severed at start");
/* Note: writing error code... */
rc = 0;
fullwrite(work_ctl, &rc, sizeof(int));
continue;
} else if (er != KCGI_OK) {
kutil_warnx(NULL, NULL, "FastCGI: "
"unrecoverable error at start");
break;
}
/*
* Now read one or more parameters.
* We read them all at once, then do the parsing later
* after we've read all of our data.
* We read parameters til we no longer have the
* FCGI_PARAMS type on the current header.
*/
er = KCGI_OK;
envsz = 0;
memset(&hdr, 0, sizeof(struct fcgi_hdr));
while (er == KCGI_OK) {
er = kworker_fcgi_header(&fbuf, &hdr);
if (er != KCGI_OK)
break;
if (rid != hdr.requestId) {
kutil_warnx(NULL, NULL,
"FastCGI: wrong request ID");
er = KCGI_FORM;
break;
}
if (hdr.type != FCGI_PARAMS)
break;
er = kworker_fcgi_params
(&fbuf, &hdr, &envs, &envsz);
}
if (er == KCGI_HUP) {
kutil_warnx(NULL, NULL, "FastCGI: "
"connection severed at parameters");
/* Note: writing error code... */
rc = 0;
fullwrite(work_ctl, &rc, sizeof(int));
continue;
} else if (er != KCGI_OK) {
kutil_warnx(NULL, NULL, "FastCGI: "
"unrecoverable error at parameters");
break;
} else if (hdr.type != FCGI_STDIN) {
kutil_warnx(NULL, NULL, "FastCGI: "
"bad header type");
er = KCGI_FORM;
break;
} else if (rid != hdr.requestId) {
kutil_warnx(NULL, NULL, "FastCGI: "
"wrong request ID");
er = KCGI_FORM;
break;
}
/*
* Lastly, we want to process the stdin content.
* These will end with a single zero-length record.
* Keep looping til we've flushed all input.
*/
for (;;) {
/*
* Call this even if we have a zero-length data
* payload as specified by contentLength.
* This is because there might be padding, and
* we want to make sure we've drawn everything
* from the socket before exiting.
*/
er = kworker_fcgi_stdin
(&fbuf, &hdr, &sbuf, &ssz);
if (er != KCGI_OK || hdr.contentLength == 0)
break;
/* Now read the next header. */
er = kworker_fcgi_header(&fbuf, &hdr);
if (er != KCGI_OK)
break;
if (rid != hdr.requestId) {
kutil_warnx(NULL, NULL, "FastCGI: "
"wrong FastCGI request ID");
er = KCGI_FORM;
break;
}
if (hdr.type == FCGI_STDIN)
continue;
kutil_warnx(NULL, NULL,
"FastCGI: bad header type");
er = KCGI_FORM;
break;
}
if (er == KCGI_HUP) {
kutil_warnx(NULL, NULL, "FastCGI: "
"connection severed at stdin");
/* Note: writing error code. */
rc = 0;
fullwrite(work_ctl, &rc, sizeof(int));
continue;
} else if (er != KCGI_OK) {
kutil_warnx(NULL, NULL, "FastCGI: "
"unrecoverable error at stdin");
break;
}
/*
* Notify the control process that we've received all of
* our data by giving back the cookie and requestId.
* FIXME: merge cookie and rc.
*/
rc = 1;
fullwrite(work_ctl, &rc, sizeof(int));
fullwrite(work_ctl, &cookie, sizeof(uint32_t));
fullwrite(work_ctl, &rid, sizeof(uint16_t));
/*
* Read our last zero-length frame.
* This is because kfcgi_control() always ends with an
* empty frame, regardless of whether we're in an error
* or not.
* So if we're this far, we've read the full request,
* and we should have an empty frame.
*/
rc = fullread(fbuf.fd, &sz, sizeof(size_t), 0, &er);
if (rc <= 0) {
kutil_warnx(NULL, NULL,
"FastCGI: error reading trailer");
break;
} else if (sz != 0) {
kutil_warnx(NULL, NULL,
"FastCGI: trailer not zero-length");
er = KCGI_FORM;
break;
}
/*
* Now we can reply to our request.
* See kworker_parent().
* These are in a very specific order.
*/
kworker_child_env(envs, wfd, envsz);
meth = kworker_child_method(envs, wfd, envsz);
kworker_child_auth(envs, wfd, envsz);
md5 = kworker_child_rawauth(envs, wfd, envsz);
kworker_child_scheme(envs, wfd, envsz);
kworker_child_remote(envs, wfd, envsz);
kworker_child_path(envs, wfd, envsz);
kworker_child_scriptname(envs, wfd, envsz);
kworker_child_httphost(envs, wfd, envsz);
kworker_child_port(envs, wfd, envsz);
/*
* And now the message body itself.
* We must either have a NULL message or non-zero
* length.
*/
assert(ssz == 0 || sbuf != NULL);
kworker_child_body(envs, wfd, envsz, &pp,
meth, (char *)sbuf, ssz, debugging, md5);
kworker_child_query(envs, wfd, envsz, &pp);
kworker_child_cookies(envs, wfd, envsz, &pp);
kworker_child_last(wfd);
}
/* The same as what we do at the loop start. */
free(sbuf);
for (i = 0; i < envsz; i++) {
free(envs[i].key);
free(envs[i].val);
}
free(envs);
free(fbuf.buf);
}