/*	$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;
}
