source: squid-ssl/trunk/fuentes/src/auth/digest/Config.cc @ 5496

Last change on this file since 5496 was 5495, checked in by Juanma, 2 years ago

Initial release

File size: 34.2 KB
Line 
1/*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 29    Authenticator */
10
11/* The functions in this file handle authentication.
12 * They DO NOT perform access control or auditing.
13 * See acl.c for access control and client_side.c for auditing */
14
15#include "squid.h"
16#include "auth/digest/Config.h"
17#include "auth/digest/Scheme.h"
18#include "auth/digest/User.h"
19#include "auth/digest/UserRequest.h"
20#include "auth/Gadgets.h"
21#include "auth/State.h"
22#include "base64.h"
23#include "cache_cf.h"
24#include "event.h"
25#include "helper.h"
26#include "HttpHeaderTools.h"
27#include "HttpReply.h"
28#include "HttpRequest.h"
29#include "mgr/Registration.h"
30#include "rfc2617.h"
31#include "SBuf.h"
32#include "SquidTime.h"
33#include "Store.h"
34#include "StrList.h"
35#include "wordlist.h"
36
37/* Digest Scheme */
38
39static AUTHSSTATS authenticateDigestStats;
40
41helper *digestauthenticators = NULL;
42
43static hash_table *digest_nonce_cache;
44
45static int authdigest_initialised = 0;
46static MemAllocator *digest_nonce_pool = NULL;
47
48enum http_digest_attr_type {
49    DIGEST_USERNAME,
50    DIGEST_REALM,
51    DIGEST_QOP,
52    DIGEST_ALGORITHM,
53    DIGEST_URI,
54    DIGEST_NONCE,
55    DIGEST_NC,
56    DIGEST_CNONCE,
57    DIGEST_RESPONSE,
58    DIGEST_ENUM_END
59};
60
61static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = {
62    {"username",  (http_hdr_type)DIGEST_USERNAME},
63    {"realm", (http_hdr_type)DIGEST_REALM},
64    {"qop", (http_hdr_type)DIGEST_QOP},
65    {"algorithm", (http_hdr_type)DIGEST_ALGORITHM},
66    {"uri", (http_hdr_type)DIGEST_URI},
67    {"nonce", (http_hdr_type)DIGEST_NONCE},
68    {"nc", (http_hdr_type)DIGEST_NC},
69    {"cnonce", (http_hdr_type)DIGEST_CNONCE},
70    {"response", (http_hdr_type)DIGEST_RESPONSE},
71};
72
73class HttpHeaderFieldInfo;
74static HttpHeaderFieldInfo *DigestFieldsInfo = NULL;
75
76/*
77 *
78 * Nonce Functions
79 *
80 */
81
82static void authenticateDigestNonceCacheCleanup(void *data);
83static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64);
84static void authenticateDigestNonceDelete(digest_nonce_h * nonce);
85static void authenticateDigestNonceSetup(void);
86static void authDigestNonceEncode(digest_nonce_h * nonce);
87static void authDigestNonceLink(digest_nonce_h * nonce);
88#if NOT_USED
89static int authDigestNonceLinks(digest_nonce_h * nonce);
90#endif
91static void authDigestNonceUserUnlink(digest_nonce_h * nonce);
92
93static void
94authDigestNonceEncode(digest_nonce_h * nonce)
95{
96    if (!nonce)
97        return;
98
99    if (nonce->key)
100        xfree(nonce->key);
101
102    nonce->key = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data)));
103}
104
105digest_nonce_h *
106authenticateDigestNonceNew(void)
107{
108    digest_nonce_h *newnonce = static_cast < digest_nonce_h * >(digest_nonce_pool->alloc());
109
110    /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
111     * === EXCERPT FROM RFC 2617 ===
112     * The contents of the nonce are implementation dependent. The quality
113     * of the implementation depends on a good choice. A nonce might, for
114     * example, be constructed as the base 64 encoding of
115     *
116     * time-stamp H(time-stamp ":" ETag ":" private-key)
117     *
118     * where time-stamp is a server-generated time or other non-repeating
119     * value, ETag is the value of the HTTP ETag header associated with
120     * the requested entity, and private-key is data known only to the
121     * server.  With a nonce of this form a server would recalculate the
122     * hash portion after receiving the client authentication header and
123     * reject the request if it did not match the nonce from that header
124     * or if the time-stamp value is not recent enough. In this way the
125     * server can limit the time of the nonce's validity. The inclusion of
126     * the ETag prevents a replay request for an updated version of the
127     * resource.  (Note: including the IP address of the client in the
128     * nonce would appear to offer the server the ability to limit the
129     * reuse of the nonce to the same client that originally got it.
130     * However, that would break proxy farms, where requests from a single
131     * user often go through different proxies in the farm. Also, IP
132     * address spoofing is not that hard.)
133     * ====
134     *
135     * Now for my reasoning:
136     * We will not accept a unrecognised nonce->we have all recognisable
137     * nonces stored. If we send out unique base64 encodings we guarantee
138     * that a given nonce applies to only one user (barring attacks or
139     * really bad timing with expiry and creation).  Using a random
140     * component in the nonce allows us to loop to find a unique nonce.
141     * We use H(nonce_data) so the nonce is meaningless to the reciever.
142     * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
143     * And even if our randomness is not very random (probably due to
144     * bad coding on my part) we don't really care - the timestamp and
145     * memory pointer also guarantee local uniqueness in the input to the hash
146     * function.
147     */
148
149    /* create a new nonce */
150    newnonce->nc = 0;
151    newnonce->flags.valid = true;
152    newnonce->noncedata.self = newnonce;
153    newnonce->noncedata.creationtime = current_time.tv_sec;
154    newnonce->noncedata.randomdata = squid_random();
155
156    authDigestNonceEncode(newnonce);
157    /*
158     * loop until we get a unique nonce. The nonce creation must
159     * have a random factor
160     */
161
162    while (authenticateDigestNonceFindNonce((char const *) (newnonce->key))) {
163        /* create a new nonce */
164        newnonce->noncedata.randomdata = squid_random();
165        /* Bug 3526 high performance fix: add 1 second to creationtime to avoid duplication */
166        ++newnonce->noncedata.creationtime;
167        authDigestNonceEncode(newnonce);
168    }
169
170    hash_join(digest_nonce_cache, newnonce);
171    /* the cache's link */
172    authDigestNonceLink(newnonce);
173    newnonce->flags.incache = true;
174    debugs(29, 5, "created nonce " << newnonce << " at " << newnonce->noncedata.creationtime);
175    return newnonce;
176}
177
178static void
179authenticateDigestNonceDelete(digest_nonce_h * nonce)
180{
181    if (nonce) {
182        assert(nonce->references == 0);
183#if UNREACHABLECODE
184
185        if (nonce->flags.incache)
186            hash_remove_link(digest_nonce_cache, nonce);
187
188#endif
189
190        assert(!nonce->flags.incache);
191
192        safe_free(nonce->key);
193
194        digest_nonce_pool->freeOne(nonce);
195    }
196}
197
198static void
199authenticateDigestNonceSetup(void)
200{
201    if (!digest_nonce_pool)
202        digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h));
203
204    if (!digest_nonce_cache) {
205        digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
206        assert(digest_nonce_cache);
207        eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
208    }
209}
210
211void
212authenticateDigestNonceShutdown(void)
213{
214    /*
215     * We empty the cache of any nonces left in there.
216     */
217    digest_nonce_h *nonce;
218
219    if (digest_nonce_cache) {
220        debugs(29, 2, "Shutting down nonce cache");
221        hash_first(digest_nonce_cache);
222
223        while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
224            assert(nonce->flags.incache);
225            authDigestNoncePurge(nonce);
226        }
227    }
228
229#if DEBUGSHUTDOWN
230    if (digest_nonce_pool) {
231        delete digest_nonce_pool;
232        digest_nonce_pool = NULL;
233    }
234
235#endif
236    debugs(29, 2, "Nonce cache shutdown");
237}
238
239static void
240authenticateDigestNonceCacheCleanup(void *data)
241{
242    /*
243     * We walk the hash by nonceb64 as that is the unique key we
244     * use.  For big hash tables we could consider stepping through
245     * the cache, 100/200 entries at a time. Lets see how it flies
246     * first.
247     */
248    digest_nonce_h *nonce;
249    debugs(29, 3, "Cleaning the nonce cache now");
250    debugs(29, 3, "Current time: " << current_time.tv_sec);
251    hash_first(digest_nonce_cache);
252
253    while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
254        debugs(29, 3, "nonce entry  : " << nonce << " '" << (char *) nonce->key << "'");
255        debugs(29, 4, "Creation time: " << nonce->noncedata.creationtime);
256
257        if (authDigestNonceIsStale(nonce)) {
258            debugs(29, 4, "Removing nonce " << (char *) nonce->key << " from cache due to timeout.");
259            assert(nonce->flags.incache);
260            /* invalidate nonce so future requests fail */
261            nonce->flags.valid = false;
262            /* if it is tied to a auth_user, remove the tie */
263            authDigestNonceUserUnlink(nonce);
264            authDigestNoncePurge(nonce);
265        }
266    }
267
268    debugs(29, 3, "Finished cleaning the nonce cache.");
269
270    if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->active())
271        eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
272}
273
274static void
275authDigestNonceLink(digest_nonce_h * nonce)
276{
277    assert(nonce != NULL);
278    ++nonce->references;
279    debugs(29, 9, "nonce '" << nonce << "' now at '" << nonce->references << "'.");
280}
281
282#if NOT_USED
283static int
284authDigestNonceLinks(digest_nonce_h * nonce)
285{
286    if (!nonce)
287        return -1;
288
289    return nonce->references;
290}
291
292#endif
293
294void
295authDigestNonceUnlink(digest_nonce_h * nonce)
296{
297    assert(nonce != NULL);
298
299    if (nonce->references > 0) {
300        -- nonce->references;
301    } else {
302        debugs(29, DBG_IMPORTANT, "Attempt to lower nonce " << nonce << " refcount below 0!");
303    }
304
305    debugs(29, 9, "nonce '" << nonce << "' now at '" << nonce->references << "'.");
306
307    if (nonce->references == 0)
308        authenticateDigestNonceDelete(nonce);
309}
310
311const char *
312authenticateDigestNonceNonceb64(const digest_nonce_h * nonce)
313{
314    if (!nonce)
315        return NULL;
316
317    return (char const *) nonce->key;
318}
319
320static digest_nonce_h *
321authenticateDigestNonceFindNonce(const char *nonceb64)
322{
323    digest_nonce_h *nonce = NULL;
324
325    if (nonceb64 == NULL)
326        return NULL;
327
328    debugs(29, 9, "looking for nonceb64 '" << nonceb64 << "' in the nonce cache.");
329
330    nonce = static_cast < digest_nonce_h * >(hash_lookup(digest_nonce_cache, nonceb64));
331
332    if ((nonce == NULL) || (strcmp(authenticateDigestNonceNonceb64(nonce), nonceb64)))
333        return NULL;
334
335    debugs(29, 9, "Found nonce '" << nonce << "'");
336
337    return nonce;
338}
339
340int
341authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9])
342{
343    unsigned long intnc;
344    /* do we have a nonce ? */
345
346    if (!nonce)
347        return 0;
348
349    intnc = strtol(nc, NULL, 16);
350
351    /* has it already been invalidated ? */
352    if (!nonce->flags.valid) {
353        debugs(29, 4, "Nonce already invalidated");
354        return 0;
355    }
356
357    /* is the nonce-count ok ? */
358    if (!static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->CheckNonceCount) {
359        /* Ignore client supplied NC */
360        intnc = nonce->nc + 1;
361    }
362
363    if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->NonceStrictness && intnc != nonce->nc + 1) ||
364            intnc < nonce->nc + 1) {
365        debugs(29, 4, "Nonce count doesn't match");
366        nonce->flags.valid = false;
367        return 0;
368    }
369
370    /* increment the nonce count - we've already checked that intnc is a
371     *  valid representation for us, so we don't need the test here.
372     */
373    nonce->nc = intnc;
374
375    return !authDigestNonceIsStale(nonce);
376}
377
378int
379authDigestNonceIsStale(digest_nonce_h * nonce)
380{
381    /* do we have a nonce ? */
382
383    if (!nonce)
384        return -1;
385
386    /* Is it already invalidated? */
387    if (!nonce->flags.valid)
388        return -1;
389
390    /* has it's max duration expired? */
391    if (nonce->noncedata.creationtime + static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration < current_time.tv_sec) {
392        debugs(29, 4, "Nonce is too old. " <<
393               nonce->noncedata.creationtime << " " <<
394               static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration << " " <<
395               current_time.tv_sec);
396
397        nonce->flags.valid = false;
398        return -1;
399    }
400
401    if (nonce->nc > 99999998) {
402        debugs(29, 4, "Nonce count overflow");
403        nonce->flags.valid = false;
404        return -1;
405    }
406
407    if (nonce->nc > static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses) {
408        debugs(29, 4, "Nonce count over user limit");
409        nonce->flags.valid = false;
410        return -1;
411    }
412
413    /* seems ok */
414    return 0;
415}
416
417/**
418 * \retval  0    the digest is not stale yet
419 * \retval -1    the digest will be stale on the next request
420 */
421int
422authDigestNonceLastRequest(digest_nonce_h * nonce)
423{
424    if (!nonce)
425        return -1;
426
427    if (nonce->nc == 99999997) {
428        debugs(29, 4, "Nonce count about to overflow");
429        return -1;
430    }
431
432    if (nonce->nc >= static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses - 1) {
433        debugs(29, 4, "Nonce count about to hit user limit");
434        return -1;
435    }
436
437    /* and other tests are possible. */
438    return 0;
439}
440
441void
442authDigestNoncePurge(digest_nonce_h * nonce)
443{
444    if (!nonce)
445        return;
446
447    if (!nonce->flags.incache)
448        return;
449
450    hash_remove_link(digest_nonce_cache, nonce);
451
452    nonce->flags.incache = false;
453
454    /* the cache's link */
455    authDigestNonceUnlink(nonce);
456}
457
458void
459Auth::Digest::Config::rotateHelpers()
460{
461    /* schedule closure of existing helpers */
462    if (digestauthenticators) {
463        helperShutdown(digestauthenticators);
464    }
465
466    /* NP: dynamic helper restart will ensure they start up again as needed. */
467}
468
469bool
470Auth::Digest::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme) const
471{
472    if (!Auth::Config::dump(entry, name, scheme))
473        return false;
474
475    storeAppendPrintf(entry, "%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n",
476                      name, "digest", noncemaxuses,
477                      name, "digest", (int) noncemaxduration,
478                      name, "digest", (int) nonceGCInterval);
479    storeAppendPrintf(entry, "%s digest utf8 %s\n", name, utf8 ? "on" : "off");
480    return true;
481}
482
483bool
484Auth::Digest::Config::active() const
485{
486    return authdigest_initialised == 1;
487}
488
489bool
490Auth::Digest::Config::configured() const
491{
492    if ((authenticateProgram != NULL) &&
493            (authenticateChildren.n_max != 0) &&
494            !realm.isEmpty() && (noncemaxduration > -1))
495        return true;
496
497    return false;
498}
499
500/* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
501void
502Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
503{
504    if (!authenticateProgram)
505        return;
506
507    bool stale = false;
508    digest_nonce_h *nonce = NULL;
509
510    /* on a 407 or 401 we always use a new nonce */
511    if (auth_user_request != NULL) {
512        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
513
514        if (digest_user) {
515            stale = digest_user->credentials() == Auth::Handshake;
516            if (stale) {
517                nonce = digest_user->currentNonce();
518            }
519        }
520    }
521    if (!nonce) {
522        nonce = authenticateDigestNonceNew();
523    }
524
525    debugs(29, 9, "Sending type:" << hdrType <<
526           " header: 'Digest realm=\"" << realm << "\", nonce=\"" <<
527           authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH <<
528           "\", stale=" << (stale ? "true" : "false"));
529
530    /* in the future, for WWW auth we may want to support the domain entry */
531    httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"" SQUIDSBUFPH "\", nonce=\"%s\", qop=\"%s\", stale=%s",
532                      SQUIDSBUFPRINT(realm), authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false");
533}
534
535/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
536 * config file */
537void
538Auth::Digest::Config::init(Auth::Config * scheme)
539{
540    if (authenticateProgram) {
541        DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END);
542        authenticateDigestNonceSetup();
543        authdigest_initialised = 1;
544
545        if (digestauthenticators == NULL)
546            digestauthenticators = new helper("digestauthenticator");
547
548        digestauthenticators->cmdline = authenticateProgram;
549
550        digestauthenticators->childs.updateLimits(authenticateChildren);
551
552        digestauthenticators->ipc_type = IPC_STREAM;
553
554        helperOpenServers(digestauthenticators);
555    }
556}
557
558void
559Auth::Digest::Config::registerWithCacheManager(void)
560{
561    Mgr::RegisterAction("digestauthenticator",
562                        "Digest User Authenticator Stats",
563                        authenticateDigestStats, 0, 1);
564}
565
566/* free any allocated configuration details */
567void
568Auth::Digest::Config::done()
569{
570    Auth::Config::done();
571
572    authdigest_initialised = 0;
573
574    if (digestauthenticators)
575        helperShutdown(digestauthenticators);
576
577    if (DigestFieldsInfo) {
578        httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END);
579        DigestFieldsInfo = NULL;
580    }
581
582    if (!shutting_down)
583        return;
584
585    delete digestauthenticators;
586    digestauthenticators = NULL;
587
588    if (authenticateProgram)
589        wordlistDestroy(&authenticateProgram);
590}
591
592Auth::Digest::Config::Config() :
593    nonceGCInterval(5*60),
594    noncemaxduration(30*60),
595    noncemaxuses(50),
596    NonceStrictness(0),
597    CheckNonceCount(1),
598    PostWorkaround(0),
599    utf8(0)
600{}
601
602void
603Auth::Digest::Config::parse(Auth::Config * scheme, int n_configured, char *param_str)
604{
605    if (strcmp(param_str, "program") == 0) {
606        if (authenticateProgram)
607            wordlistDestroy(&authenticateProgram);
608
609        parse_wordlist(&authenticateProgram);
610
611        requirePathnameExists("auth_param digest program", authenticateProgram->key);
612    } else if (strcmp(param_str, "nonce_garbage_interval") == 0) {
613        parse_time_t(&nonceGCInterval);
614    } else if (strcmp(param_str, "nonce_max_duration") == 0) {
615        parse_time_t(&noncemaxduration);
616    } else if (strcmp(param_str, "nonce_max_count") == 0) {
617        parse_int((int *) &noncemaxuses);
618    } else if (strcmp(param_str, "nonce_strictness") == 0) {
619        parse_onoff(&NonceStrictness);
620    } else if (strcmp(param_str, "check_nonce_count") == 0) {
621        parse_onoff(&CheckNonceCount);
622    } else if (strcmp(param_str, "post_workaround") == 0) {
623        parse_onoff(&PostWorkaround);
624    } else if (strcmp(param_str, "utf8") == 0) {
625        parse_onoff(&utf8);
626    } else
627        Auth::Config::parse(scheme, n_configured, param_str);
628}
629
630const char *
631Auth::Digest::Config::type() const
632{
633    return Auth::Digest::Scheme::GetInstance()->type();
634}
635
636static void
637authenticateDigestStats(StoreEntry * sentry)
638{
639    helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics");
640}
641
642/* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
643
644static void
645authDigestNonceUserUnlink(digest_nonce_h * nonce)
646{
647    Auth::Digest::User *digest_user;
648    dlink_node *link, *tmplink;
649
650    if (!nonce)
651        return;
652
653    if (!nonce->user)
654        return;
655
656    digest_user = nonce->user;
657
658    /* unlink from the user list. Yes we're crossing structures but this is the only
659     * time this code is needed
660     */
661    link = digest_user->nonces.head;
662
663    while (link) {
664        tmplink = link;
665        link = link->next;
666
667        if (tmplink->data == nonce) {
668            dlinkDelete(tmplink, &digest_user->nonces);
669            authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
670            dlinkNodeDelete(tmplink);
671            link = NULL;
672        }
673    }
674
675    /* this reference to user was not locked because freeeing the user frees
676     * the nonce too.
677     */
678    nonce->user = NULL;
679}
680
681/* authDigesteserLinkNonce: add a nonce to a given user's struct */
682void
683authDigestUserLinkNonce(Auth::Digest::User * user, digest_nonce_h * nonce)
684{
685    dlink_node *node;
686
687    if (!user || !nonce || !nonce->user)
688        return;
689
690    Auth::Digest::User *digest_user = user;
691
692    node = digest_user->nonces.head;
693
694    while (node && (node->data != nonce))
695        node = node->next;
696
697    if (node)
698        return;
699
700    node = dlinkNodeNew();
701
702    dlinkAddTail(nonce, node, &digest_user->nonces);
703
704    authDigestNonceLink(nonce);
705
706    /* ping this nonce to this auth user */
707    assert((nonce->user == NULL) || (nonce->user == user));
708
709    /* we don't lock this reference because removing the user removes the
710     * hash too. Of course if that changes we're stuffed so read the code huh?
711     */
712    nonce->user = user;
713}
714
715/* setup the necessary info to log the username */
716static Auth::UserRequest::Pointer
717authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request, const char *requestRealm)
718{
719    assert(auth_user_request != NULL);
720
721    /* log the username */
722    debugs(29, 9, "Creating new user for logging '" << (username?username:"[no username]") << "'");
723    Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest")), requestRealm);
724    /* save the credentials */
725    digest_user->username(username);
726    /* set the auth_user type */
727    digest_user->auth_type = Auth::AUTH_BROKEN;
728    /* link the request to the user */
729    auth_user_request->user(digest_user);
730    return auth_user_request;
731}
732
733/*
734 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
735 * Auth_user structure.
736 */
737Auth::UserRequest::Pointer
738Auth::Digest::Config::decode(char const *proxy_auth, const char *aRequestRealm)
739{
740    const char *item;
741    const char *p;
742    const char *pos = NULL;
743    char *username = NULL;
744    digest_nonce_h *nonce;
745    int ilen;
746
747    debugs(29, 9, "beginning");
748
749    Auth::Digest::UserRequest *digest_request = new Auth::Digest::UserRequest();
750
751    /* trim DIGEST from string */
752
753    while (xisgraph(*proxy_auth))
754        ++proxy_auth;
755
756    /* Trim leading whitespace before decoding */
757    while (xisspace(*proxy_auth))
758        ++proxy_auth;
759
760    String temp(proxy_auth);
761
762    while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
763        /* isolate directive name & value */
764        size_t nlen;
765        size_t vlen;
766        if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
767            nlen = p - item;
768            ++p;
769            vlen = ilen - (p - item);
770        } else {
771            nlen = ilen;
772            vlen = 0;
773        }
774
775        SBuf keyName(item, nlen);
776        String value;
777
778        if (vlen > 0) {
779            // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
780
781            if (keyName == SBuf("domain",6) || keyName == SBuf("uri",3)) {
782                // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
783                // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
784                if (*p == '"' && *(p + vlen -1) == '"') {
785                    value.limitInit(p+1, vlen-2);
786                }
787            } else if (keyName == SBuf("qop",3)) {
788                // qop is more special.
789                // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
790                // On response this is a single un-quoted token.
791                if (*p == '"' && *(p + vlen -1) == '"') {
792                    value.limitInit(p+1, vlen-2);
793                } else {
794                    value.limitInit(p, vlen);
795                }
796            } else if (*p == '"') {
797                if (!httpHeaderParseQuotedString(p, vlen, &value)) {
798                    debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
799                    continue;
800                }
801            } else {
802                value.limitInit(p, vlen);
803            }
804        } else {
805            debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
806            continue;
807        }
808
809        /* find type */
810        http_digest_attr_type t = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END);
811
812        switch (t) {
813        case DIGEST_USERNAME:
814            safe_free(username);
815            if (value.size() != 0)
816                username = xstrndup(value.rawBuf(), value.size() + 1);
817            debugs(29, 9, "Found Username '" << username << "'");
818            break;
819
820        case DIGEST_REALM:
821            safe_free(digest_request->realm);
822            if (value.size() != 0)
823                digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
824            debugs(29, 9, "Found realm '" << digest_request->realm << "'");
825            break;
826
827        case DIGEST_QOP:
828            safe_free(digest_request->qop);
829            if (value.size() != 0)
830                digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
831            debugs(29, 9, "Found qop '" << digest_request->qop << "'");
832            break;
833
834        case DIGEST_ALGORITHM:
835            safe_free(digest_request->algorithm);
836            if (value.size() != 0)
837                digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
838            debugs(29, 9, "Found algorithm '" << digest_request->algorithm << "'");
839            break;
840
841        case DIGEST_URI:
842            safe_free(digest_request->uri);
843            if (value.size() != 0)
844                digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
845            debugs(29, 9, "Found uri '" << digest_request->uri << "'");
846            break;
847
848        case DIGEST_NONCE:
849            safe_free(digest_request->nonceb64);
850            if (value.size() != 0)
851                digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1);
852            debugs(29, 9, "Found nonce '" << digest_request->nonceb64 << "'");
853            break;
854
855        case DIGEST_NC:
856            if (value.size() != 8) {
857                debugs(29, 9, "Invalid nc '" << value << "' in '" << temp << "'");
858            }
859            xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
860            debugs(29, 9, "Found noncecount '" << digest_request->nc << "'");
861            break;
862
863        case DIGEST_CNONCE:
864            safe_free(digest_request->cnonce);
865            if (value.size() != 0)
866                digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
867            debugs(29, 9, "Found cnonce '" << digest_request->cnonce << "'");
868            break;
869
870        case DIGEST_RESPONSE:
871            safe_free(digest_request->response);
872            if (value.size() != 0)
873                digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
874            debugs(29, 9, "Found response '" << digest_request->response << "'");
875            break;
876
877        default:
878            debugs(29, 3, "Unknown attribute '" << item << "' in '" << temp << "'");
879            break;
880        }
881    }
882
883    temp.clean();
884
885    /* now we validate the data given to us */
886
887    /*
888     * TODO: on invalid parameters we should return 400, not 407.
889     * Find some clean way of doing this. perhaps return a valid
890     * struct, and set the direction to clientwards combined with
891     * a change to the clientwards handling code (ie let the
892     * clientwards call set the error type (but limited to known
893     * correct values - 400/401/407
894     */
895
896    /* 2069 requirements */
897
898    // return value.
899    Auth::UserRequest::Pointer rv;
900    /* do we have a username ? */
901    if (!username || username[0] == '\0') {
902        debugs(29, 2, "Empty or not present username");
903        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
904        safe_free(username);
905        return rv;
906    }
907
908    /* Sanity check of the username.
909     * " can not be allowed in usernames until * the digest helper protocol
910     * have been redone
911     */
912    if (strchr(username, '"')) {
913        debugs(29, 2, "Unacceptable username '" << username << "'");
914        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
915        safe_free(username);
916        return rv;
917    }
918
919    /* do we have a realm ? */
920    if (!digest_request->realm || digest_request->realm[0] == '\0') {
921        debugs(29, 2, "Empty or not present realm");
922        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
923        safe_free(username);
924        return rv;
925    }
926
927    /* and a nonce? */
928    if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') {
929        debugs(29, 2, "Empty or not present nonce");
930        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
931        safe_free(username);
932        return rv;
933    }
934
935    /* we can't check the URI just yet. We'll check it in the
936     * authenticate phase, but needs to be given */
937    if (!digest_request->uri || digest_request->uri[0] == '\0') {
938        debugs(29, 2, "Missing URI field");
939        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
940        safe_free(username);
941        return rv;
942    }
943
944    /* is the response the correct length? */
945    if (!digest_request->response || strlen(digest_request->response) != 32) {
946        debugs(29, 2, "Response length invalid");
947        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
948        safe_free(username);
949        return rv;
950    }
951
952    /* check the algorithm is present and supported */
953    if (!digest_request->algorithm)
954        digest_request->algorithm = xstrndup("MD5", 4);
955    else if (strcmp(digest_request->algorithm, "MD5")
956             && strcmp(digest_request->algorithm, "MD5-sess")) {
957        debugs(29, 2, "invalid algorithm specified!");
958        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
959        safe_free(username);
960        return rv;
961    }
962
963    /* 2617 requirements, indicated by qop */
964    if (digest_request->qop) {
965
966        /* check the qop is what we expected. */
967        if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
968            /* we received a qop option we didn't send */
969            debugs(29, 2, "Invalid qop option received");
970            rv = authDigestLogUsername(username, digest_request, aRequestRealm);
971            safe_free(username);
972            return rv;
973        }
974
975        /* check cnonce */
976        if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
977            debugs(29, 2, "Missing cnonce field");
978            rv = authDigestLogUsername(username, digest_request, aRequestRealm);
979            safe_free(username);
980            return rv;
981        }
982
983        /* check nc */
984        if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
985            debugs(29, 2, "invalid nonce count");
986            rv = authDigestLogUsername(username, digest_request, aRequestRealm);
987            safe_free(username);
988            return rv;
989        }
990    } else {
991        /* cnonce and nc both require qop */
992        if (digest_request->cnonce || digest_request->nc[0] != '\0') {
993            debugs(29, 2, "missing qop!");
994            rv = authDigestLogUsername(username, digest_request, aRequestRealm);
995            safe_free(username);
996            return rv;
997        }
998    }
999
1000    /** below nonce state dependent **/
1001
1002    /* now the nonce */
1003    nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64);
1004    /* check that we're not being hacked / the username hasn't changed */
1005    if (nonce && nonce->user && strcmp(username, nonce->user->username())) {
1006        debugs(29, 2, "Username for the nonce does not equal the username for the request");
1007        nonce = NULL;
1008    }
1009
1010    if (!nonce) {
1011        /* we couldn't find a matching nonce! */
1012        debugs(29, 2, "Unexpected or invalid nonce received from " << username);
1013        Auth::UserRequest::Pointer auth_request = authDigestLogUsername(username, digest_request, aRequestRealm);
1014        auth_request->user()->credentials(Auth::Handshake);
1015        safe_free(username);
1016        return auth_request;
1017    }
1018
1019    digest_request->nonce = nonce;
1020    authDigestNonceLink(nonce);
1021
1022    /* check that we're not being hacked / the username hasn't changed */
1023    if (nonce->user && strcmp(username, nonce->user->username())) {
1024        debugs(29, 2, "Username for the nonce does not equal the username for the request");
1025        rv = authDigestLogUsername(username, digest_request, aRequestRealm);
1026        safe_free(username);
1027        return rv;
1028    }
1029
1030    /* the method we'll check at the authenticate step as well */
1031
1032    /* we don't send or parse opaques. Ok so we're flexable ... */
1033
1034    /* find the user */
1035    Auth::Digest::User *digest_user;
1036
1037    Auth::User::Pointer auth_user;
1038
1039    SBuf key = Auth::User::BuildUserKey(username, aRequestRealm);
1040    if (key.isEmpty() || (auth_user = findUserInCache(key.c_str(), Auth::AUTH_DIGEST)) == NULL) {
1041        /* the user doesn't exist in the username cache yet */
1042        debugs(29, 9, "Creating new digest user '" << username << "'");
1043        digest_user = new Auth::Digest::User(this, aRequestRealm);
1044        /* auth_user is a parent */
1045        auth_user = digest_user;
1046        /* save the username */
1047        digest_user->username(username);
1048        /* set the user type */
1049        digest_user->auth_type = Auth::AUTH_DIGEST;
1050        /* this auth_user struct is the one to get added to the
1051         * username cache */
1052        /* store user in hash's */
1053        digest_user->addToNameCache();
1054
1055        /*
1056         * Add the digest to the user so we can tell if a hacking
1057         * or spoofing attack is taking place. We do this by assuming
1058         * the user agent won't change user name without warning.
1059         */
1060        authDigestUserLinkNonce(digest_user, nonce);
1061    } else {
1062        debugs(29, 9, "Found user '" << username << "' in the user cache as '" << auth_user << "'");
1063        digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw());
1064        digest_user->credentials(Auth::Unchecked);
1065        xfree(username);
1066    }
1067
1068    /*link the request and the user */
1069    assert(digest_request != NULL);
1070
1071    digest_request->user(digest_user);
1072    debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
1073           digest_request->realm << "'\nqop = '" << digest_request->qop <<
1074           "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1075           digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 <<
1076           "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1077           digest_request->cnonce << "'\nresponse = '" <<
1078           digest_request->response << "'\ndigestnonce = '" << nonce << "'");
1079
1080    return digest_request;
1081}
1082
Note: See TracBrowser for help on using the repository browser.