/* $Id$ */ /* * Copyright (c) 2015, 2017 Kristaps Dzonsons * * 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 #include #include #include #include #include #include #include #include #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; }