mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
452 lines
11 KiB
C
452 lines
11 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (c) 2015, 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>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "kcgi.h"
|
|
#include "extern.h"
|
|
|
|
/*
|
|
* A sequence of bytes parsed from the input stream.
|
|
*/
|
|
struct pdigbuf {
|
|
const char *pos;
|
|
size_t sz;
|
|
};
|
|
|
|
/*
|
|
* Pointers to the components of an HTTP digest scheme in the input
|
|
* stream.
|
|
* These are empty strings (sz == 0) if undescribed.
|
|
*/
|
|
struct pdigest {
|
|
enum khttpalg alg;
|
|
enum khttpqop qop;
|
|
struct pdigbuf user;
|
|
struct pdigbuf uri;
|
|
struct pdigbuf realm;
|
|
struct pdigbuf nonce;
|
|
struct pdigbuf cnonce;
|
|
struct pdigbuf response;
|
|
struct pdigbuf opaque;
|
|
uint32_t count;
|
|
};
|
|
|
|
/*
|
|
* Hash algorithm identifiers.
|
|
*/
|
|
static const char *const httpalgs[KHTTPALG__MAX] = {
|
|
"MD5", /* KHTTPALG_MD5 */
|
|
"MD5-sess" /* KHTTPALG_MD5_SESS */
|
|
};
|
|
|
|
/*
|
|
* Quality-of-protection identifiers.
|
|
*/
|
|
static const char *const httpqops[KHTTPQOP__MAX] = {
|
|
NULL, /* KHTTPQOP_NONE */
|
|
"auth", /* KHTTPQOP_AUTH */
|
|
"auth-int" /* KHTTPQOP_AUTH_INT */
|
|
};
|
|
|
|
/*
|
|
* Parses the next token part---a sequence of non-whitespace (and
|
|
* non-"delim", with '\0' being the noop delimeter) characters---after
|
|
* skipping leading whitespace, then positions "next" to be at the next
|
|
* token (after any whitespace).
|
|
* Returns the start of the token and its size.
|
|
*/
|
|
static const char *
|
|
kauth_nexttok(const char **next, char delim, size_t *sz)
|
|
{
|
|
const char *cp;
|
|
|
|
/* Skip past leading white-space. */
|
|
|
|
while (isspace((unsigned char)**next))
|
|
(*next)++;
|
|
|
|
/* Scan til whitespace or delimiter. */
|
|
|
|
cp = *next;
|
|
while ('\0' != **next && delim != **next &&
|
|
! isspace((unsigned char)**next))
|
|
(*next)++;
|
|
*sz = *next - cp;
|
|
|
|
/* Scan after delimiter, if applicable. */
|
|
|
|
if ('\0' != delim && delim == **next)
|
|
(*next)++;
|
|
|
|
/* Scan til next non-whitespace. */
|
|
|
|
while (isspace((unsigned char)**next))
|
|
(*next)++;
|
|
|
|
return(cp);
|
|
}
|
|
|
|
/*
|
|
* Parse a quoted-pair (or non-quoted pair) from the string "cp".
|
|
* Puts the location of the next token back into "cp" and fills the
|
|
* pointer in "val" (if non-NULL) and its size in "sz".
|
|
*/
|
|
static void
|
|
kauth_nextvalue(struct pdigbuf *val, const char **cp)
|
|
{
|
|
int quot;
|
|
|
|
if ('\0' == **cp)
|
|
return;
|
|
|
|
if ((quot = ('"' == **cp)))
|
|
(*cp)++;
|
|
|
|
if (NULL != val) {
|
|
val->pos = *cp;
|
|
val->sz = 0;
|
|
}
|
|
|
|
for ( ; '\0' != **cp; (*cp)++) {
|
|
if (quot && '"' == **cp && '\\' != (*cp)[-1])
|
|
break;
|
|
else if ( ! quot && isspace((unsigned char)**cp))
|
|
break;
|
|
else if ( ! quot && ',' == **cp)
|
|
break;
|
|
if (NULL != val)
|
|
val->sz++;
|
|
}
|
|
|
|
if (quot && '"' == **cp)
|
|
(*cp)++;
|
|
while (isspace((unsigned char)**cp))
|
|
(*cp)++;
|
|
if (',' == **cp)
|
|
(*cp)++;
|
|
while (isspace((unsigned char)**cp))
|
|
(*cp)++;
|
|
}
|
|
|
|
/*
|
|
* Parse a token.
|
|
* We don't strictly follow RFC 2615's TOKEN specification, which says
|
|
* that tokens can be followed by any separator.
|
|
* We only use commas as separators.
|
|
*/
|
|
static void
|
|
kauth_nexttoken(size_t *val, const char **cp,
|
|
const char *const *vals, size_t valsz)
|
|
{
|
|
struct pdigbuf buf;
|
|
|
|
memset(&buf, 0, sizeof(struct pdigbuf));
|
|
kauth_nextvalue(&buf, cp);
|
|
|
|
for (*val = 0; *val < valsz; (*val)++) {
|
|
if (NULL == vals[*val])
|
|
continue;
|
|
if (buf.sz != strlen(vals[*val]))
|
|
continue;
|
|
if (0 == strncasecmp(buf.pos, vals[*val], buf.sz))
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
kauth_alg(enum khttpalg *val, const char **cp)
|
|
{
|
|
size_t i;
|
|
|
|
kauth_nexttoken(&i, cp, httpalgs, KHTTPALG__MAX);
|
|
*val = i;
|
|
}
|
|
|
|
static void
|
|
kauth_qop(enum khttpqop *val, const char **cp)
|
|
{
|
|
size_t i;
|
|
|
|
kauth_nexttoken(&i, cp, httpqops, KHTTPQOP__MAX);
|
|
*val = i;
|
|
}
|
|
|
|
/*
|
|
* Parse the 8-byte nonce count ("nc") value.
|
|
* See RFC 7616 section 3.4.
|
|
*/
|
|
static void
|
|
kauth_count(uint32_t *count, const char **cp)
|
|
{
|
|
struct pdigbuf buf;
|
|
char numbuf[9];
|
|
char *ep;
|
|
unsigned long long ulval;
|
|
|
|
*count = 0;
|
|
|
|
memset(&buf, 0, sizeof(struct pdigbuf));
|
|
|
|
/* According to the RFC, this is 8 bytes long. */
|
|
|
|
kauth_nextvalue(&buf, cp);
|
|
if (buf.sz != 8)
|
|
return;
|
|
|
|
/* Copy into a NUL-terminated buffer. */
|
|
|
|
memcpy(numbuf, buf.pos, buf.sz);
|
|
numbuf[buf.sz] = '\0';
|
|
|
|
/*
|
|
* Convert from the hex string into a number.
|
|
* There are a maximum of 8 possible digits in this hex value,
|
|
* so we'll have no more than 0xffffffff.
|
|
* Default to zero if there are errors.
|
|
* Note: UINT32_MAX < long long int maximum.
|
|
*/
|
|
|
|
errno = 0;
|
|
ulval = strtoull(numbuf, &ep, 16);
|
|
if (numbuf[0] == '\0' || *ep != '\0')
|
|
*count = 0;
|
|
else if (errno == ERANGE && ulval == ULLONG_MAX)
|
|
*count = 0;
|
|
else if (ulval > UINT32_MAX)
|
|
*count = 0;
|
|
else
|
|
*count = ulval;
|
|
}
|
|
|
|
static int
|
|
kauth_eq(const char *p, const char *start, size_t sz, size_t want)
|
|
{
|
|
|
|
return(want == sz && 0 == strncasecmp(start, p, want));
|
|
}
|
|
|
|
/*
|
|
* The "bearer" or "basic" authentication is just that word followed by
|
|
* opaque text. HTTP "basic" authentication has a well-defined username
|
|
* and password structure, but "bearer" is opaque. These are both
|
|
* handled by the calling context: we don't do any validation here.
|
|
*/
|
|
static void
|
|
khttpbasic_input(int fd, const char *cp, enum kauth auth)
|
|
{
|
|
int authorised;
|
|
|
|
fullwrite(fd, &auth, sizeof(enum kauth));
|
|
while (isspace((unsigned char)*cp))
|
|
cp++;
|
|
|
|
if ('\0' == *cp) {
|
|
authorised = 0;
|
|
fullwrite(fd, &authorised, sizeof(int));
|
|
return;
|
|
}
|
|
|
|
authorised = 1;
|
|
fullwrite(fd, &authorised, sizeof(int));
|
|
fullwriteword(fd, cp);
|
|
}
|
|
|
|
/*
|
|
* Parse HTTP ``Digest'' authentication tokens from the NUL-terminated
|
|
* string, which can be NULL or malformed.
|
|
*/
|
|
static int
|
|
khttpdigest_input(int fd, const char *cp)
|
|
{
|
|
enum kauth auth;
|
|
const char *start;
|
|
int rc, authorised;
|
|
size_t sz;
|
|
struct pdigest d;
|
|
|
|
auth = KAUTH_DIGEST;
|
|
fullwrite(fd, &auth, sizeof(enum kauth));
|
|
memset(&d, 0, sizeof(struct pdigest));
|
|
|
|
for (rc = 1; 1 == rc && '\0' != *cp; ) {
|
|
start = kauth_nexttok(&cp, '=', &sz);
|
|
if (kauth_eq("username", start, sz, 8))
|
|
kauth_nextvalue(&d.user, &cp);
|
|
else if (kauth_eq("realm", start, sz, 5))
|
|
kauth_nextvalue(&d.realm, &cp);
|
|
else if (kauth_eq("nonce", start, sz, 5))
|
|
kauth_nextvalue(&d.nonce, &cp);
|
|
else if (kauth_eq("cnonce", start, sz, 6))
|
|
kauth_nextvalue(&d.cnonce, &cp);
|
|
else if (kauth_eq("response", start, sz, 8))
|
|
kauth_nextvalue(&d.response, &cp);
|
|
else if (kauth_eq("uri", start, sz, 3))
|
|
kauth_nextvalue(&d.uri, &cp);
|
|
else if (kauth_eq("algorithm", start, sz, 9))
|
|
kauth_alg(&d.alg, &cp);
|
|
else if (kauth_eq("qop", start, sz, 3))
|
|
kauth_qop(&d.qop, &cp);
|
|
else if (kauth_eq("nc", start, sz, 2))
|
|
kauth_count(&d.count, &cp);
|
|
else if (kauth_eq("opaque", start, sz, 6))
|
|
kauth_nextvalue(&d.opaque, &cp);
|
|
else
|
|
kauth_nextvalue(NULL, &cp);
|
|
}
|
|
|
|
/* Minimum requirements. */
|
|
authorised =
|
|
0 != d.user.sz &&
|
|
0 != d.realm.sz &&
|
|
0 != d.nonce.sz &&
|
|
0 != d.response.sz &&
|
|
0 != d.uri.sz;
|
|
|
|
/* Additional requirements: MD5-sess. */
|
|
if (authorised && KHTTPALG_MD5_SESS == d.alg)
|
|
authorised = 0 != d.cnonce.sz;
|
|
|
|
/* Additional requirements: qop. */
|
|
if (authorised &&
|
|
(KHTTPQOP_AUTH == d.qop ||
|
|
KHTTPQOP_AUTH_INT == d.qop))
|
|
authorised =
|
|
0 != d.count &&
|
|
0 != d.cnonce.sz;
|
|
|
|
fullwrite(fd, &authorised, sizeof(int));
|
|
|
|
if ( ! authorised)
|
|
return(0);
|
|
|
|
fullwrite(fd, &d.alg, sizeof(enum khttpalg));
|
|
fullwrite(fd, &d.qop, sizeof(enum khttpqop));
|
|
fullwrite(fd, &d.user.sz, sizeof(size_t));
|
|
fullwrite(fd, d.user.pos, d.user.sz);
|
|
fullwrite(fd, &d.uri.sz, sizeof(size_t));
|
|
fullwrite(fd, d.uri.pos, d.uri.sz);
|
|
fullwrite(fd, &d.realm.sz, sizeof(size_t));
|
|
fullwrite(fd, d.realm.pos, d.realm.sz);
|
|
fullwrite(fd, &d.nonce.sz, sizeof(size_t));
|
|
fullwrite(fd, d.nonce.pos, d.nonce.sz);
|
|
fullwrite(fd, &d.cnonce.sz, sizeof(size_t));
|
|
fullwrite(fd, d.cnonce.pos, d.cnonce.sz);
|
|
fullwrite(fd, &d.response.sz, sizeof(size_t));
|
|
fullwrite(fd, d.response.pos, d.response.sz);
|
|
fullwrite(fd, &d.count, sizeof(uint32_t));
|
|
fullwrite(fd, &d.opaque.sz, sizeof(size_t));
|
|
fullwrite(fd, d.opaque.pos, d.opaque.sz);
|
|
|
|
/* Do we need to MD5-hash our contents? */
|
|
return(KHTTPQOP_AUTH_INT == d.qop);
|
|
}
|
|
|
|
enum kcgi_err
|
|
kworker_auth_parent(int fd, struct khttpauth *auth)
|
|
{
|
|
enum kcgi_err ke;
|
|
|
|
if (fullread(fd, &auth->type, sizeof(enum kauth), 0, &ke) < 0)
|
|
return ke;
|
|
|
|
switch (auth->type) {
|
|
case KAUTH_DIGEST:
|
|
if (fullread(fd, &auth->authorised, sizeof(int), 0, &ke) < 0)
|
|
return ke;
|
|
if (!auth->authorised)
|
|
break;
|
|
if (fullread(fd, &auth->d.digest.alg, sizeof(enum khttpalg), 0, &ke) < 0)
|
|
return ke;
|
|
if (fullread(fd, &auth->d.digest.qop, sizeof(enum khttpqop), 0, &ke) < 0)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.user)) != KCGI_OK)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.uri)) != KCGI_OK)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.realm)) != KCGI_OK)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.nonce)) != KCGI_OK)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.cnonce)) != KCGI_OK)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.response)) != KCGI_OK)
|
|
return ke;
|
|
if (fullread(fd, &auth->d.digest.count, sizeof(uint32_t), 0, &ke) < 0)
|
|
return ke;
|
|
if ((ke = fullreadword(fd, &auth->d.digest.opaque)) != KCGI_OK)
|
|
return ke;
|
|
break;
|
|
case KAUTH_BASIC:
|
|
case KAUTH_BEARER:
|
|
if (fullread(fd, &auth->authorised, sizeof(int), 0, &ke) < 0)
|
|
return ke;
|
|
if (!auth->authorised)
|
|
break;
|
|
if ((ke = fullreadword(fd, &auth->d.basic.response)) != KCGI_OK)
|
|
return ke;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return KCGI_OK;
|
|
}
|
|
|
|
/*
|
|
* Parse the "basic", "digest", or "bearer" authorisation from the request.
|
|
* We return non-zero if the body of the request needs to be MD5-hashed,
|
|
* i.e., if we have auth-int digest QOP.
|
|
*/
|
|
int
|
|
kworker_auth_child(int fd, const char *cp)
|
|
{
|
|
const char *start;
|
|
size_t sz;
|
|
enum kauth auth;
|
|
|
|
if (cp == NULL || *cp == '\0') {
|
|
auth = KAUTH_NONE;
|
|
fullwrite(fd, &auth, sizeof(enum kauth));
|
|
return 0;
|
|
}
|
|
|
|
start = kauth_nexttok(&cp, '\0', &sz);
|
|
|
|
if (sz == 6 && strncasecmp(start, "bearer", sz) == 0) {
|
|
khttpbasic_input(fd, cp, KAUTH_BEARER);
|
|
return 0;
|
|
} else if (sz == 5 && strncasecmp(start, "basic", sz) == 0) {
|
|
khttpbasic_input(fd, cp, KAUTH_BASIC);
|
|
return 0;
|
|
} else if (sz == 6 && strncasecmp(start, "digest", sz) == 0)
|
|
return khttpdigest_input(fd, cp);
|
|
|
|
auth = KAUTH_UNKNOWN;
|
|
fullwrite(fd, &auth, sizeof(enum kauth));
|
|
return 0;
|
|
}
|