Files

381 lines
9.7 KiB
C

/* $Id$ */
/*
* Copyright (c) 2012, 2014--2017 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 <assert.h>
#if HAVE_MD5
# include <sys/types.h>
# include <md5.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "kcgi.h"
#include "extern.h"
/*
* Read a single kpair from the child.
* This returns 0 if there are no more pairs to read (and eofok has been
* set) and -1 if any errors occur (the parent should also exit with
* server failure).
* Otherwise, it returns 1 and the pair is zeroed and filled in.
*/
static int
input(enum input *type, struct kpair *kp, int fd,
enum kcgi_err *ke, int eofok, size_t mimesz, size_t keysz)
{
size_t sz;
int rc;
ptrdiff_t diff;
memset(kp, 0, sizeof(struct kpair));
rc = fullread(fd, type, sizeof(enum input), 1, ke);
if (rc == 0) {
if (eofok)
return 0;
kutil_warnx(NULL, NULL, "unexpected EOF from child");
*ke = KCGI_FORM;
return (-1);
} else if (rc < 0) {
kutil_warnx(NULL, NULL, "failed read kpair type");
return (-1);
}
if (*type == IN__MAX)
return 0;
if (*type > IN__MAX) {
kutil_warnx(NULL, NULL, "invalid kpair type");
*ke = KCGI_FORM;
return (-1);
}
*ke = fullreadword(fd, &kp->key);
if (*ke != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read kpair key");
return (-1);
}
*ke = fullreadwordsz(fd, &kp->val, &kp->valsz);
if (*ke != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read kpair value");
return (-1);
}
sz = sizeof(enum kpairstate);
if (fullread(fd, &kp->state, sz, 0, ke) < 0) {
kutil_warnx(NULL, NULL, "failed read kpair state");
return (-1);
} else if (kp->state > KPAIR_INVALID) {
kutil_warnx(NULL, NULL, "invalid kpair state");
return (-1);
}
sz = sizeof(enum kpairtype);
if (fullread(fd, &kp->type, sz, 0, ke) < 0) {
kutil_warnx(NULL, NULL, "failed read kpair type");
return (-1);
} else if (kp->type > KPAIR__MAX) {
kutil_warnx(NULL, NULL, "invalid kpair type");
return (-1);
}
sz = sizeof(size_t);
if (fullread(fd, &kp->keypos, sz, 0, ke) < 0) {
kutil_warnx(NULL, NULL, "failed read kpair position");
return (-1);
} else if (kp->keypos > keysz) {
kutil_warnx(NULL, NULL, "invalid kpair position");
return (-1);
}
if (kp->state == KPAIR_VALID)
switch (kp->type) {
case KPAIR_DOUBLE:
sz = sizeof(double);
if (fullread(fd, &kp->parsed.d, sz, 0, ke) > 0)
break;
kutil_warnx(NULL, NULL,
"failed read kpair double");
return (-1);
case KPAIR_INTEGER:
sz = sizeof(int64_t);
if (fullread(fd, &kp->parsed.i, sz, 0, ke) > 0)
break;
kutil_warnx(NULL, NULL,
"failed read kpair integer");
return (-1);
case KPAIR_STRING:
sz = sizeof(ptrdiff_t);
if (fullread(fd, &diff, sz, 0, ke) < 0) {
kutil_warnx(NULL, NULL,
"failed read kpair ptrdiff");
return (-1);
}
if (diff > (ssize_t)kp->valsz) {
*ke = KCGI_FORM;
kutil_warnx(NULL, NULL,
"invalid kpair ptrdiff");
return (-1);
}
kp->parsed.s = kp->val + diff;
break;
default:
break;
}
*ke = fullreadword(fd, &kp->file);
if (*ke != KCGI_OK) {
kutil_warnx(NULL, NULL,
"failed read kpair filename");
return (-1);
}
*ke = fullreadword(fd, &kp->ctype);
if (*ke != KCGI_OK) {
kutil_warnx(NULL, NULL,
"failed read kpair content type");
return (-1);
}
sz = sizeof(size_t);
if (fullread(fd, &kp->ctypepos, sz, 0, ke) < 0) {
kutil_warnx(NULL, NULL,
"failed read kpair MIME position");
return (-1);
} else if (kp->ctypepos > mimesz) {
kutil_warnx(NULL, NULL,
"invalid kpair MIME position");
return (-1);
}
*ke = fullreadword(fd, &kp->xcode);
if (*ke != KCGI_OK) {
kutil_warnx(NULL, NULL,
"failed read kpair content transfer encoding");
return (-1);
}
return 1;
}
static struct kpair *
kpair_expand(struct kpair **kv, size_t *kvsz)
{
void *pp;
pp = kxreallocarray(*kv, *kvsz + 1, sizeof(struct kpair));
if (pp == NULL)
return NULL;
*kv = pp;
memset(&(*kv)[*kvsz], 0, sizeof(struct kpair));
(*kvsz)++;
return(&(*kv)[*kvsz - 1]);
}
/*
* This is the parent kcgi process.
* It spins on input from the child until all fields have been received.
* These fields are sent from the child's output() function.
* Each input field consists of the data and its validation state.
* We build up the kpair arrays here with this data, then assign the
* kpairs into named buckets.
*/
enum kcgi_err
kworker_parent(int fd, struct kreq *r, int eofok, size_t mimesz)
{
struct kpair kp;
struct kpair *kpp;
enum krequ requ;
enum input type;
int rc;
enum kcgi_err ke;
size_t i, dgsz;
/* Pointers freed at "out" label. */
memset(&kp, 0, sizeof(struct kpair));
/*
* First read all of our parsed parameters.
* Each parsed parameter is handled a little differently.
* This list will end with META__MAX.
*/
if (fullread(fd, &r->reqsz, sizeof(size_t), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "read request header size");
goto out;
}
if (r->reqsz) {
r->reqs = kxcalloc(r->reqsz, sizeof(struct khead));
if (r->reqs == NULL) {
ke = KCGI_ENOMEM;
goto out;
}
}
for (i = 0; i < r->reqsz; i++) {
if (fullread(fd, &requ, sizeof(enum krequ), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "read request identifier");
goto out;
}
if ((ke = fullreadword(fd, &r->reqs[i].key)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "read request key");
goto out;
}
if ((ke = fullreadword(fd, &r->reqs[i].val)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "read request value");
goto out;
}
if (requ != KREQU__MAX)
r->reqmap[requ] = &r->reqs[i];
}
if (fullread(fd, &r->method, sizeof(enum kmethod), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read request method");
goto out;
} else if (fullread(fd, &r->auth, sizeof(enum kauth), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read authorisation type");
goto out;
} else if ((ke = kworker_auth_parent(fd, &r->rawauth)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read raw authorisation");
goto out;
} else if (fullread(fd, &r->scheme, sizeof(enum kscheme), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read scheme");
goto out;
} else if ((ke = fullreadword(fd, &r->remote)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read remote");
goto out;
} else if ((ke = fullreadword(fd, &r->fullpath)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read fullpath");
goto out;
} else if ((ke = fullreadword(fd, &r->suffix)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read suffix");
goto out;
} else if ((ke = fullreadword(fd, &r->pagename)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read page part");
goto out;
} else if ((ke = fullreadword(fd, &r->path)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read path part");
goto out;
} else if ((ke = fullreadword(fd, &r->pname)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read script name");
goto out;
} else if ((ke = fullreadword(fd, &r->host)) != KCGI_OK) {
kutil_warnx(NULL, NULL, "failed read host name");
goto out;
} else if (fullread(fd, &r->port, sizeof(uint16_t), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read port");
goto out;
} else if (fullread(fd, &dgsz, sizeof(size_t), 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read digest length");
goto out;
} else if (dgsz == MD5_DIGEST_LENGTH) {
/* This is a binary value. */
if ((r->rawauth.digest = kxmalloc(dgsz)) == NULL)
goto out;
if (fullread(fd, r->rawauth.digest, dgsz, 0, &ke) < 0) {
kutil_warnx(NULL, NULL, "failed read digest");
goto out;
}
}
for (;;) {
rc = input(&type, &kp, fd, &ke,
eofok, mimesz, r->keysz);
if (rc < 0)
goto out;
else if (rc == 0)
break;
/*
* We have a parsed field from the child process.
* Begin by expanding the number of parsed fields
* depending on whether we have a cookie or form input.
* Then copy the new data.
*/
assert(type < IN__MAX);
kpp = type == IN_COOKIE ?
kpair_expand(&r->cookies, &r->cookiesz) :
kpair_expand(&r->fields, &r->fieldsz);
if (kpp == NULL) {
ke = KCGI_ENOMEM;
goto out;
}
*kpp = kp;
}
assert(rc == 0);
/*
* Now that the field and cookie arrays are fixed and not going
* to be reallocated any more, we run through both arrays and
* assign the named fields into buckets.
* The assignment enqueues into the field.
*/
for (i = 0; i < r->fieldsz; i++) {
kpp = &r->fields[i];
if (kpp->keypos == r->keysz)
continue;
assert(kpp->keypos < r->keysz);
if (kpp->state != KPAIR_INVALID) {
kpp->next = r->fieldmap[kpp->keypos];
r->fieldmap[kpp->keypos] = kpp;
} else {
kpp->next = r->fieldnmap[kpp->keypos];
r->fieldnmap[kpp->keypos] = kpp;
}
}
for (i = 0; i < r->cookiesz; i++) {
kpp = &r->cookies[i];
if (kpp->keypos == r->keysz)
continue;
assert(kpp->keypos < r->keysz);
if (kpp->state != KPAIR_INVALID) {
kpp->next = r->cookiemap[kpp->keypos];
r->cookiemap[kpp->keypos] = kpp;
} else {
kpp->next = r->cookienmap[kpp->keypos];
r->cookienmap[kpp->keypos] = kpp;
}
}
return KCGI_OK;
out:
assert(ke != KCGI_OK);
free(kp.key);
free(kp.val);
free(kp.file);
free(kp.ctype);
free(kp.xcode);
return ke;
}