ksgi
/
httpauth.c
451 строка · 10.9 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2015, 2017 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include "config.h"18
19#include <assert.h>20#include <ctype.h>21#include <errno.h>22#include <limits.h>23#include <stdarg.h>24#include <stdio.h>25#include <stdint.h>26#include <stdlib.h>27#include <string.h>28
29#include "kcgi.h"30#include "extern.h"31
32/*
33* A sequence of bytes parsed from the input stream.
34*/
35struct pdigbuf {36const char *pos;37size_t sz;38};39
40/*
41* Pointers to the components of an HTTP digest scheme in the input
42* stream.
43* These are empty strings (sz == 0) if undescribed.
44*/
45struct pdigest {46enum khttpalg alg;47enum khttpqop qop;48struct pdigbuf user;49struct pdigbuf uri;50struct pdigbuf realm;51struct pdigbuf nonce;52struct pdigbuf cnonce;53struct pdigbuf response;54struct pdigbuf opaque;55uint32_t count;56};57
58/*
59* Hash algorithm identifiers.
60*/
61static const char *const httpalgs[KHTTPALG__MAX] = {62"MD5", /* KHTTPALG_MD5 */63"MD5-sess" /* KHTTPALG_MD5_SESS */64};65
66/*
67* Quality-of-protection identifiers.
68*/
69static const char *const httpqops[KHTTPQOP__MAX] = {70NULL, /* KHTTPQOP_NONE */71"auth", /* KHTTPQOP_AUTH */72"auth-int" /* KHTTPQOP_AUTH_INT */73};74
75/*
76* Parses the next token part---a sequence of non-whitespace (and
77* non-"delim", with '\0' being the noop delimeter) characters---after
78* skipping leading whitespace, then positions "next" to be at the next
79* token (after any whitespace).
80* Returns the start of the token and its size.
81*/
82static const char *83kauth_nexttok(const char **next, char delim, size_t *sz)84{
85const char *cp;86
87/* Skip past leading white-space. */88
89while (isspace((unsigned char)**next))90(*next)++;91
92/* Scan til whitespace or delimiter. */93
94cp = *next;95while ('\0' != **next && delim != **next &&96! isspace((unsigned char)**next))97(*next)++;98*sz = *next - cp;99
100/* Scan after delimiter, if applicable. */101
102if ('\0' != delim && delim == **next)103(*next)++;104
105/* Scan til next non-whitespace. */106
107while (isspace((unsigned char)**next))108(*next)++;109
110return(cp);111}
112
113/*
114* Parse a quoted-pair (or non-quoted pair) from the string "cp".
115* Puts the location of the next token back into "cp" and fills the
116* pointer in "val" (if non-NULL) and its size in "sz".
117*/
118static void119kauth_nextvalue(struct pdigbuf *val, const char **cp)120{
121int quot;122
123if ('\0' == **cp)124return;125
126if ((quot = ('"' == **cp)))127(*cp)++;128
129if (NULL != val) {130val->pos = *cp;131val->sz = 0;132}133
134for ( ; '\0' != **cp; (*cp)++) {135if (quot && '"' == **cp && '\\' != (*cp)[-1])136break;137else if ( ! quot && isspace((unsigned char)**cp))138break;139else if ( ! quot && ',' == **cp)140break;141if (NULL != val)142val->sz++;143}144
145if (quot && '"' == **cp)146(*cp)++;147while (isspace((unsigned char)**cp))148(*cp)++;149if (',' == **cp)150(*cp)++;151while (isspace((unsigned char)**cp))152(*cp)++;153}
154
155/*
156* Parse a token.
157* We don't strictly follow RFC 2615's TOKEN specification, which says
158* that tokens can be followed by any separator.
159* We only use commas as separators.
160*/
161static void162kauth_nexttoken(size_t *val, const char **cp,163const char *const *vals, size_t valsz)164{
165struct pdigbuf buf;166
167memset(&buf, 0, sizeof(struct pdigbuf));168kauth_nextvalue(&buf, cp);169
170for (*val = 0; *val < valsz; (*val)++) {171if (NULL == vals[*val])172continue;173if (buf.sz != strlen(vals[*val]))174continue;175if (0 == strncasecmp(buf.pos, vals[*val], buf.sz))176return;177}178}
179
180static void181kauth_alg(enum khttpalg *val, const char **cp)182{
183size_t i;184
185kauth_nexttoken(&i, cp, httpalgs, KHTTPALG__MAX);186*val = i;187}
188
189static void190kauth_qop(enum khttpqop *val, const char **cp)191{
192size_t i;193
194kauth_nexttoken(&i, cp, httpqops, KHTTPQOP__MAX);195*val = i;196}
197
198/*
199* Parse the 8-byte nonce count ("nc") value.
200* See RFC 7616 section 3.4.
201*/
202static void203kauth_count(uint32_t *count, const char **cp)204{
205struct pdigbuf buf;206char numbuf[9];207char *ep;208unsigned long long ulval;209
210*count = 0;211
212memset(&buf, 0, sizeof(struct pdigbuf));213
214/* According to the RFC, this is 8 bytes long. */215
216kauth_nextvalue(&buf, cp);217if (buf.sz != 8)218return;219
220/* Copy into a NUL-terminated buffer. */221
222memcpy(numbuf, buf.pos, buf.sz);223numbuf[buf.sz] = '\0';224
225/*226* Convert from the hex string into a number.
227* There are a maximum of 8 possible digits in this hex value,
228* so we'll have no more than 0xffffffff.
229* Default to zero if there are errors.
230* Note: UINT32_MAX < long long int maximum.
231*/
232
233errno = 0;234ulval = strtoull(numbuf, &ep, 16);235if (numbuf[0] == '\0' || *ep != '\0')236*count = 0;237else if (errno == ERANGE && ulval == ULLONG_MAX)238*count = 0;239else if (ulval > UINT32_MAX)240*count = 0;241else242*count = ulval;243}
244
245static int246kauth_eq(const char *p, const char *start, size_t sz, size_t want)247{
248
249return(want == sz && 0 == strncasecmp(start, p, want));250}
251
252/*
253* The "bearer" or "basic" authentication is just that word followed by
254* opaque text. HTTP "basic" authentication has a well-defined username
255* and password structure, but "bearer" is opaque. These are both
256* handled by the calling context: we don't do any validation here.
257*/
258static void259khttpbasic_input(int fd, const char *cp, enum kauth auth)260{
261int authorised;262
263fullwrite(fd, &auth, sizeof(enum kauth));264while (isspace((unsigned char)*cp))265cp++;266
267if ('\0' == *cp) {268authorised = 0;269fullwrite(fd, &authorised, sizeof(int));270return;271}272
273authorised = 1;274fullwrite(fd, &authorised, sizeof(int));275fullwriteword(fd, cp);276}
277
278/*
279* Parse HTTP ``Digest'' authentication tokens from the NUL-terminated
280* string, which can be NULL or malformed.
281*/
282static int283khttpdigest_input(int fd, const char *cp)284{
285enum kauth auth;286const char *start;287int rc, authorised;288size_t sz;289struct pdigest d;290
291auth = KAUTH_DIGEST;292fullwrite(fd, &auth, sizeof(enum kauth));293memset(&d, 0, sizeof(struct pdigest));294
295for (rc = 1; 1 == rc && '\0' != *cp; ) {296start = kauth_nexttok(&cp, '=', &sz);297if (kauth_eq("username", start, sz, 8))298kauth_nextvalue(&d.user, &cp);299else if (kauth_eq("realm", start, sz, 5))300kauth_nextvalue(&d.realm, &cp);301else if (kauth_eq("nonce", start, sz, 5))302kauth_nextvalue(&d.nonce, &cp);303else if (kauth_eq("cnonce", start, sz, 6))304kauth_nextvalue(&d.cnonce, &cp);305else if (kauth_eq("response", start, sz, 8))306kauth_nextvalue(&d.response, &cp);307else if (kauth_eq("uri", start, sz, 3))308kauth_nextvalue(&d.uri, &cp);309else if (kauth_eq("algorithm", start, sz, 9))310kauth_alg(&d.alg, &cp);311else if (kauth_eq("qop", start, sz, 3))312kauth_qop(&d.qop, &cp);313else if (kauth_eq("nc", start, sz, 2))314kauth_count(&d.count, &cp);315else if (kauth_eq("opaque", start, sz, 6))316kauth_nextvalue(&d.opaque, &cp);317else318kauth_nextvalue(NULL, &cp);319}320
321/* Minimum requirements. */322authorised =3230 != d.user.sz &&3240 != d.realm.sz &&3250 != d.nonce.sz &&3260 != d.response.sz &&3270 != d.uri.sz;328
329/* Additional requirements: MD5-sess. */330if (authorised && KHTTPALG_MD5_SESS == d.alg)331authorised = 0 != d.cnonce.sz;332
333/* Additional requirements: qop. */334if (authorised &&335(KHTTPQOP_AUTH == d.qop ||336KHTTPQOP_AUTH_INT == d.qop))337authorised =3380 != d.count &&3390 != d.cnonce.sz;340
341fullwrite(fd, &authorised, sizeof(int));342
343if ( ! authorised)344return(0);345
346fullwrite(fd, &d.alg, sizeof(enum khttpalg));347fullwrite(fd, &d.qop, sizeof(enum khttpqop));348fullwrite(fd, &d.user.sz, sizeof(size_t));349fullwrite(fd, d.user.pos, d.user.sz);350fullwrite(fd, &d.uri.sz, sizeof(size_t));351fullwrite(fd, d.uri.pos, d.uri.sz);352fullwrite(fd, &d.realm.sz, sizeof(size_t));353fullwrite(fd, d.realm.pos, d.realm.sz);354fullwrite(fd, &d.nonce.sz, sizeof(size_t));355fullwrite(fd, d.nonce.pos, d.nonce.sz);356fullwrite(fd, &d.cnonce.sz, sizeof(size_t));357fullwrite(fd, d.cnonce.pos, d.cnonce.sz);358fullwrite(fd, &d.response.sz, sizeof(size_t));359fullwrite(fd, d.response.pos, d.response.sz);360fullwrite(fd, &d.count, sizeof(uint32_t));361fullwrite(fd, &d.opaque.sz, sizeof(size_t));362fullwrite(fd, d.opaque.pos, d.opaque.sz);363
364/* Do we need to MD5-hash our contents? */365return(KHTTPQOP_AUTH_INT == d.qop);366}
367
368enum kcgi_err369kworker_auth_parent(int fd, struct khttpauth *auth)370{
371enum kcgi_err ke;372
373if (fullread(fd, &auth->type, sizeof(enum kauth), 0, &ke) < 0)374return ke;375
376switch (auth->type) {377case KAUTH_DIGEST:378if (fullread(fd, &auth->authorised, sizeof(int), 0, &ke) < 0)379return ke;380if (!auth->authorised)381break;382if (fullread(fd, &auth->d.digest.alg, sizeof(enum khttpalg), 0, &ke) < 0)383return ke;384if (fullread(fd, &auth->d.digest.qop, sizeof(enum khttpqop), 0, &ke) < 0)385return ke;386if ((ke = fullreadword(fd, &auth->d.digest.user)) != KCGI_OK)387return ke;388if ((ke = fullreadword(fd, &auth->d.digest.uri)) != KCGI_OK)389return ke;390if ((ke = fullreadword(fd, &auth->d.digest.realm)) != KCGI_OK)391return ke;392if ((ke = fullreadword(fd, &auth->d.digest.nonce)) != KCGI_OK)393return ke;394if ((ke = fullreadword(fd, &auth->d.digest.cnonce)) != KCGI_OK)395return ke;396if ((ke = fullreadword(fd, &auth->d.digest.response)) != KCGI_OK)397return ke;398if (fullread(fd, &auth->d.digest.count, sizeof(uint32_t), 0, &ke) < 0)399return ke;400if ((ke = fullreadword(fd, &auth->d.digest.opaque)) != KCGI_OK)401return ke;402break;403case KAUTH_BASIC:404case KAUTH_BEARER:405if (fullread(fd, &auth->authorised, sizeof(int), 0, &ke) < 0)406return ke;407if (!auth->authorised)408break;409if ((ke = fullreadword(fd, &auth->d.basic.response)) != KCGI_OK)410return ke;411break;412default:413break;414}415
416return KCGI_OK;417}
418
419/*
420* Parse the "basic", "digest", or "bearer" authorisation from the request.
421* We return non-zero if the body of the request needs to be MD5-hashed,
422* i.e., if we have auth-int digest QOP.
423*/
424int
425kworker_auth_child(int fd, const char *cp)426{
427const char *start;428size_t sz;429enum kauth auth;430
431if (cp == NULL || *cp == '\0') {432auth = KAUTH_NONE;433fullwrite(fd, &auth, sizeof(enum kauth));434return 0;435}436
437start = kauth_nexttok(&cp, '\0', &sz);438
439if (sz == 6 && strncasecmp(start, "bearer", sz) == 0) {440khttpbasic_input(fd, cp, KAUTH_BEARER);441return 0;442} else if (sz == 5 && strncasecmp(start, "basic", sz) == 0) {443khttpbasic_input(fd, cp, KAUTH_BASIC);444return 0;445} else if (sz == 6 && strncasecmp(start, "digest", sz) == 0)446return khttpdigest_input(fd, cp);447
448auth = KAUTH_UNKNOWN;449fullwrite(fd, &auth, sizeof(enum kauth));450return 0;451}
452