Annotation of libwww/Library/src/HTAABrow.c, revision 2.62
2.15 frystyk 1: /* HTAABrow.c
2.32 frystyk 2: ** BROWSER SIDE ACCESS AUTHORIZATION MODULE
2.15 frystyk 3: **
2.19 frystyk 4: ** (c) COPYRIGHT MIT 1995.
2.15 frystyk 5: ** Please first read the full copyright statement in the file COPYRIGH.
2.62 ! kahan 6: ** @(#) $Id: HTAABrow.c,v 2.61 2003/04/10 14:23:06 kahan Exp $
2.1 luotonen 7: **
2.32 frystyk 8: ** Contains code for parsing challenges and creating credentials for
2.36 frystyk 9: ** basic authentication schemes. See also the HTAAUtil module
2.32 frystyk 10: ** for how to handle other authentication schemes. You don't have to use
11: ** this code at all.
2.1 luotonen 12: **
13: ** AUTHORS:
14: ** AL Ari Luotonen luotonen@dxcern.cern.ch
2.32 frystyk 15: ** HFN Henrik Frystyk
2.50 kahan 16: ** JKO Jose Kahan
2.1 luotonen 17: **
18: ** HISTORY:
2.5 luotonen 19: ** Oct 17 AL Made corrections suggested by marca:
20: ** Added if (!realm->username) return NULL;
21: ** Changed some ""s to NULLs.
2.33 frystyk 22: ** Now doing HT_CALLOC() to init uuencode source;
2.5 luotonen 23: ** otherwise HTUU_encode() reads uninitialized memory
24: ** every now and then (not a real bug but not pretty).
25: ** Corrected the formula for uuencode destination size.
2.32 frystyk 26: ** Feb 96 HFN Rewritten to make it scheme independent and based on
27: ** callback functions and an info structure
2.50 kahan 28: ** Nov 98 JKO Added support for message digest authentication
2.58 kahan 29: ** Jun 2000 JKO Changed the buffer size for HTUU_encode in order
30: ** to avoid a potential SIGSEV when calling that
31: ** function (as advised by Heiner Kallweit).
2.60 kahan 32: ** Mar 2001 JKO When doing pipelining digest requests, the stale
33: ** nonce reply appears only for one of such requests,
34: ** all the following ones in the pipe will receive a
35: ** 401. I added some code to take into account these cases
36: ** by trying to infer if a nonce is stale.
2.58 kahan 37: **
2.1 luotonen 38: */
39:
2.50 kahan 40: /* Portions of this code (as indicated) are derived from the Internet Draft
41: ** draft-ietf-http-authentication-03 and are covered by the following
42: ** copyright:
43:
44: ** Copyright (C) The Internet Society (1998). All Rights Reserved.
45:
46: ** This document and translations of it may be copied and furnished to
47: ** others, and derivative works that comment on or otherwise explain it or
48: ** assist in its implmentation may be prepared, copied, published and
49: ** distributed, in whole or in part, without restriction of any kind,
50: ** provided that the above copyright notice and this paragraph are included
51: ** on all such copies and derivative works. However, this document itself
52: ** may not be modified in any way, such as by removing the copyright notice
53: ** or references to the Internet Society or other Internet organizations,
54: ** except as needed for the purpose of developing Internet standards in
55: ** which case the procedures for copyrights defined in the Internet
56: ** Standards process must be followed, or as required to translate it into
57: ** languages other than English.
58:
59: ** The limited permissions granted above are perpetual and will not be
60: ** revoked by the Internet Society or its successors or assigns.
61:
62: ** This document and the information contained herein is provided on an "AS
63: ** IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK
64: ** FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT
65: ** LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT
66: ** INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR
67: ** FITNESS FOR A PARTICULAR PURPOSE.
68: **/
69:
2.17 frystyk 70: /* Library include files */
2.27 frystyk 71: #include "WWWLib.h"
72: #include "HTAAUtil.h"
2.50 kahan 73: #include "HTParse.h"
2.27 frystyk 74: #include "HTAABrow.h" /* Implemented here */
2.50 kahan 75: #include "HTDigest.h"
2.1 luotonen 76:
2.36 frystyk 77: #define BASIC_AUTH "basic"
2.44 frystyk 78: #define DIGEST_AUTH "digest"
2.52 kahan 79: #define DIGEST_AI "authentication-info"
80: #define PROXY_DIGEST_AI "proxy-authentication-info"
2.36 frystyk 81:
2.32 frystyk 82: typedef struct _HTBasic { /* Basic challenge and credentials */
83: char * uid;
84: char * pw;
2.38 frystyk 85: BOOL retry; /* Should we ask the user again? */
2.40 frystyk 86: BOOL proxy; /* Proxy authentication */
2.32 frystyk 87: } HTBasic;
88:
2.44 frystyk 89: typedef struct _HTDigest { /* Digest challenge and credentials */
2.50 kahan 90: /* digest info can be shared by one or more UT entries */
91: int references;
92: /* client authentication data */
2.44 frystyk 93: char * uid;
94: char * pw;
2.50 kahan 95: char * realm;
96: char * cnonce;
97: long nc;
98: /* server authentication data */
99: char * nonce;
2.44 frystyk 100: char * opaque;
2.50 kahan 101: /* session authentication data */
102: int algorithm;
103: char * qop;
2.44 frystyk 104: BOOL stale;
105: BOOL retry; /* Should we ask the user again? */
106: BOOL proxy; /* Proxy authentication */
107: } HTDigest;
108:
2.50 kahan 109: #define HASHLEN 16
110: typedef char HASH[HASHLEN+1];
111: #define HASHHEXLEN 32
112: typedef char HASHHEX[HASHHEXLEN+1];
113:
2.36 frystyk 114: /* ------------------------------------------------------------------------- */
115:
2.32 frystyk 116: /*
117: ** Create a protection template for the files
118: ** in the same directory as the given file
119: ** Returns a template matching docname, and other files in that directory.
120: **
121: ** E.g. /foo/bar/x.html => /foo/bar/ *
122: ** ^
123: ** Space only to prevent it from
124: ** being a comment marker here,
125: ** there really isn't any space.
2.1 luotonen 126: */
2.33 frystyk 127: PRIVATE char * make_template (const char * docname)
2.1 luotonen 128: {
2.39 frystyk 129: char * tmplate = NULL;
2.32 frystyk 130: if (docname) {
2.39 frystyk 131: char * host = HTParse(docname, "", PARSE_ACCESS|PARSE_HOST|PARSE_PUNCTUATION);
132: char * path = HTParse(docname, "", PARSE_PATH|PARSE_PUNCTUATION);
133: char * slash = strrchr(path, '/');
134: if (slash) {
2.47 frystyk 135: #if 0
2.39 frystyk 136: if (*(slash+1)) {
137: strcpy(slash, "*");
138: StrAllocCat(host, path);
139: } else
2.43 frystyk 140: StrAllocCat(host, "/*");
2.47 frystyk 141: #else
2.49 kahan 142: if (*(slash+1)) {
143: strcpy(slash + 1, "*");
2.47 frystyk 144: StrAllocCat(host, path);
2.49 kahan 145: } else {
146: StrAllocCat(host, path);
147: StrAllocCat(host, "*");
148: }
2.47 frystyk 149: #endif
2.39 frystyk 150: }
151: HT_FREE(path);
152: tmplate = host;
153: } else
154: StrAllocCopy(tmplate, "*");
2.56 frystyk 155: HTTRACE(AUTH_TRACE, "Template.... Made template `%s' for file `%s'\n" _
156: tmplate _ docname ? docname : "<null>");
2.32 frystyk 157: return tmplate;
2.1 luotonen 158: }
159:
2.44 frystyk 160: /* ------------------------------------------------------------------------- */
161: /* Basic Authentication */
162: /* ------------------------------------------------------------------------- */
163:
164: /*
165: ** Prompt the user for username and password.
166: ** Returns YES if user name was typed in, else NO
167: */
168: PRIVATE int prompt_user (HTRequest * request, const char * realm,
169: HTBasic * basic)
170: {
171: HTAlertCallback * cbf = HTAlert_find(HT_A_USER_PW);
2.53 frystyk 172:
173: /* If no method for prompting the user then we might as well give up */
174: if (!cbf) return HT_ERROR;
175:
176: /* Otherwise go ahead and ask the user */
177: if (request) {
2.44 frystyk 178: HTAlertPar * reply = HTAlert_newReply();
179: int msg = basic->proxy ? HT_MSG_PROXY_UID : HT_MSG_UID;
180: BOOL res = (*cbf)(request, HT_A_USER_PW, msg,
181: basic->uid, (char *) realm, reply);
182: if (res) {
183: HT_FREE(basic->uid);
184: HT_FREE(basic->pw);
185: basic->uid = HTAlert_replyMessage(reply);
186: basic->pw = HTAlert_replySecret(reply);
187: }
188: HTAlert_deleteReply(reply);
189: return res ? HT_OK : HT_ERROR;
190: }
191: return HT_OK;
192: }
193:
194: PRIVATE HTBasic * HTBasic_new()
195: {
196: HTBasic * me = NULL;
197: if ((me = (HTBasic *) HT_CALLOC(1, sizeof(HTBasic))) == NULL)
198: HT_OUTOFMEM("HTBasic_new");
199: me->retry = YES; /* Ask the first time through */
200: return me;
201: }
202:
203: /* HTBasic_delete
204: ** --------------
205: ** Deletes a "basic" information object
206: */
207: PUBLIC int HTBasic_delete (void * context)
208: {
209: HTBasic * basic = (HTBasic *) context;
210: if (basic) {
211: HT_FREE(basic->uid);
212: HT_FREE(basic->pw);
213: HT_FREE(basic);
214: return YES;
215: }
216: return NO;
217: }
218:
2.32 frystyk 219: /*
220: ** Make basic authentication scheme credentials and register this
221: ** information in the request object as credentials. They will then
222: ** be included in the request header. An example is
223: **
224: ** "Basic AkRDIhEF8sdEgs72F73bfaS=="
225: **
2.40 frystyk 226: ** The function can both create normal and proxy credentials
2.36 frystyk 227: ** Returns HT_OK or HT_ERROR
2.32 frystyk 228: */
2.36 frystyk 229: PRIVATE BOOL basic_credentials (HTRequest * request, HTBasic * basic)
2.32 frystyk 230: {
231: if (request && basic) {
232: char * cleartext = NULL;
233: char * cipher = NULL;
234: int cl_len = strlen(basic->uid ? basic->uid : "") +
2.42 frystyk 235: strlen(basic->pw ? basic->pw : "") + 5;
2.58 kahan 236: int ci_len = 5 + 4 * (cl_len/3);
2.37 frystyk 237: if ((cleartext = (char *) HT_CALLOC(1, cl_len)) == NULL)
2.32 frystyk 238: HT_OUTOFMEM("basic_credentials");
239: *cleartext = '\0';
240: if (basic->uid) strcpy(cleartext, basic->uid);
241: strcat(cleartext, ":");
242: if (basic->pw) strcat(cleartext, basic->pw);
2.37 frystyk 243: if ((cipher = (char *) HT_CALLOC(1, ci_len + 3)) == NULL)
2.32 frystyk 244: HT_OUTOFMEM("basic_credentials");
2.37 frystyk 245: HTUU_encode((unsigned char *) cleartext, strlen(cleartext), cipher);
2.1 luotonen 246:
2.32 frystyk 247: /* Create the credentials and assign them to the request object */
248: {
2.37 frystyk 249: int cr_len = strlen("basic") + ci_len + 3;
2.32 frystyk 250: char * cookie = (char *) HT_MALLOC(cr_len+1);
251: if (!cookie) HT_OUTOFMEM("basic_credentials");
252: strcpy(cookie, "Basic ");
253: strcat(cookie, cipher);
2.56 frystyk 254: HTTRACE(AUTH_TRACE, "Basic Cookie `%s\'\n" _ cookie);
2.40 frystyk 255:
256: /* Check whether it is proxy or normal credentials */
257: if (basic->proxy)
258: HTRequest_addCredentials(request, "Proxy-Authorization", cookie);
259: else
260: HTRequest_addCredentials(request, "Authorization", cookie);
261:
2.32 frystyk 262: HT_FREE(cookie);
2.1 luotonen 263: }
2.32 frystyk 264: HT_FREE(cleartext);
265: HT_FREE(cipher);
2.36 frystyk 266: return HT_OK;
2.32 frystyk 267: }
2.36 frystyk 268: return HT_ERROR;
2.1 luotonen 269: }
270:
2.32 frystyk 271: /* HTBasic_generate
272: ** ----------------
273: ** This function generates "basic" credentials for the challenge found in
274: ** the authentication information base for this request. The result is
275: ** stored as an association list in the request object.
276: ** This is a callback function for the AA handler.
277: */
2.45 frystyk 278: PUBLIC int HTBasic_generate (HTRequest * request, void * context, int mode)
2.32 frystyk 279: {
2.36 frystyk 280: HTBasic * basic = (HTBasic *) context;
2.45 frystyk 281: BOOL proxy = mode==HT_NO_PROXY_ACCESS ? YES : NO;
2.36 frystyk 282: if (request) {
283: const char * realm = HTRequest_realm(request);
284:
2.40 frystyk 285: /*
2.46 frystyk 286: ** If we were asked to explicitly ask the user again
287: */
288: if (mode == HT_REAUTH || mode == HT_PROXY_REAUTH)
289: basic->retry = YES;
290:
291: /*
2.40 frystyk 292: ** If we don't have a basic context then add a new one to the tree.
2.42 frystyk 293: ** We use different trees for normal and proxy authentication
2.40 frystyk 294: */
2.36 frystyk 295: if (!basic) {
2.50 kahan 296: basic = HTBasic_new();
2.42 frystyk 297: if (proxy) {
298: char * url = HTRequest_proxy(request);
299: basic->proxy = YES;
300: HTAA_updateNode(proxy, BASIC_AUTH, realm, url, basic);
301: } else {
302: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
303: HTAA_updateNode(proxy, BASIC_AUTH, realm, url, basic);
304: HT_FREE(url);
305: }
2.36 frystyk 306: }
307:
2.32 frystyk 308: /*
2.36 frystyk 309: ** If we have a set of credentials (or the user provides a new set)
310: ** then store it in the request object as the credentials
2.32 frystyk 311: */
2.39 frystyk 312: if ((basic->retry && prompt_user(request, realm, basic) == HT_OK) ||
313: (!basic->retry && basic->uid)) {
2.38 frystyk 314: basic->retry = NO;
2.36 frystyk 315: return basic_credentials(request, basic);
2.48 frystyk 316: } else {
317: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
318: HTAA_deleteNode(proxy, BASIC_AUTH, realm, url);
319: HT_FREE(url);
2.37 frystyk 320: return HT_ERROR;
2.48 frystyk 321: }
2.1 luotonen 322: }
2.36 frystyk 323: return HT_OK;
2.1 luotonen 324: }
325:
2.32 frystyk 326: /* HTBasic_parse
327: ** -------------
328: ** This function parses the contents of a "basic" challenge
329: ** and stores the challenge in our authentication information datebase.
330: ** We also store the realm in the request object which will help finding
331: ** the right set of credentials to generate.
332: ** The function is a callback function for the AA handler.
333: */
2.45 frystyk 334: PUBLIC int HTBasic_parse (HTRequest * request, HTResponse * response,
335: void * context, int status)
2.32 frystyk 336: {
2.45 frystyk 337: HTAssocList * challenge = HTResponse_challenge(response);
2.38 frystyk 338: HTBasic * basic = NULL;
2.40 frystyk 339: BOOL proxy = status==HT_NO_PROXY_ACCESS ? YES : NO;
2.36 frystyk 340: if (request && challenge) {
341: char * p = HTAssocList_findObject(challenge, BASIC_AUTH);
342: char * realm = HTNextField(&p);
343: char * rm = HTNextField(&p);
2.38 frystyk 344:
2.32 frystyk 345: /*
2.36 frystyk 346: ** If valid challenge then make a template for the resource and
347: ** store this information in our authentication URL Tree
2.32 frystyk 348: */
2.36 frystyk 349: if (realm && !strcasecomp(realm, "realm") && rm) {
2.56 frystyk 350: HTTRACE(AUTH_TRACE, "Basic Parse. Realm `%s\' found\n" _ rm);
2.36 frystyk 351: HTRequest_setRealm(request, rm);
2.40 frystyk 352:
353: /*
354: ** If we are in proxy mode then add the proxy - not the final URL
355: */
356: if (proxy) {
357: char * url = HTRequest_proxy(request);
2.56 frystyk 358: HTTRACE(AUTH_TRACE, "Basic Parse. Proxy authentication\n");
2.40 frystyk 359: basic = (HTBasic *) HTAA_updateNode(proxy, BASIC_AUTH, rm,
360: url, NULL);
2.49 kahan 361: /* if the previous authentication failed, then try again */
362: if (HTRequest_AAretrys (request) > 1
363: && status == HT_NO_ACCESS && basic)
364: basic->retry = YES;
2.40 frystyk 365: } else {
366: char * url = HTAnchor_address((HTAnchor *)
367: HTRequest_anchor(request));
368: char * tmplate = make_template(url);
369: basic = (HTBasic *) HTAA_updateNode(proxy, BASIC_AUTH, rm,
370: tmplate, NULL);
2.49 kahan 371: /* if the previous authentication failed, then try again */
372: if (HTRequest_AAretrys (request) > 1
373: && status == HT_NO_ACCESS && basic)
374: basic->retry = YES;
2.40 frystyk 375: HT_FREE(url);
376: HT_FREE(tmplate);
377: }
2.1 luotonen 378: }
2.38 frystyk 379:
380: /*
381: ** For some reason the authentication failed so we have to ask the user
382: ** if we should try again. It may be because the user typed the wrong
383: ** user name and password
384: */
2.49 kahan 385: if (basic && basic->retry) {
2.38 frystyk 386: HTAlertCallback * prompt = HTAlert_find(HT_A_CONFIRM);
2.40 frystyk 387:
388: /*
2.50 kahan 389: ** Do we have a method registered for prompting the user whether
2.42 frystyk 390: ** we should retry
2.40 frystyk 391: */
2.38 frystyk 392: if (prompt) {
2.40 frystyk 393: int code = proxy ?
394: HT_MSG_RETRY_PROXY_AUTH : HT_MSG_RETRY_AUTHENTICATION;
395: if ((*prompt)(request, HT_A_CONFIRM, code,
2.38 frystyk 396: NULL, NULL, NULL) != YES)
397: return HT_ERROR;
398: }
399: }
2.36 frystyk 400: return HT_OK;
2.1 luotonen 401: }
2.56 frystyk 402: HTTRACE(AUTH_TRACE, "Auth........ No challenges found\n");
2.38 frystyk 403: return HT_ERROR;
2.7 luotonen 404: }
2.44 frystyk 405:
406: /* ------------------------------------------------------------------------- */
407: /* Digest Authentication */
408: /* ------------------------------------------------------------------------- */
409:
410: /*
411: ** Prompt the user for username and password.
412: ** Returns YES if user name was typed in, else NO
413: */
414: PRIVATE int prompt_digest_user (HTRequest * request, const char * realm,
415: HTDigest * digest)
416: {
417: HTAlertCallback * cbf = HTAlert_find(HT_A_USER_PW);
2.53 frystyk 418:
419: /* If no method for prompting the user then we might as well give up */
420: if (!cbf) return HT_ERROR;
421:
422: /* Otherwise go ahead and ask the user */
423: if (request) {
2.44 frystyk 424: HTAlertPar * reply = HTAlert_newReply();
425: int msg = digest->proxy ? HT_MSG_PROXY_UID : HT_MSG_UID;
426: BOOL res = (*cbf)(request, HT_A_USER_PW, msg,
427: digest->uid, (char *) realm, reply);
428: if (res) {
429: HT_FREE(digest->uid);
430: HT_FREE(digest->pw);
431: digest->uid = HTAlert_replyMessage(reply);
432: digest->pw = HTAlert_replySecret(reply);
433: }
434: HTAlert_deleteReply(reply);
435: return res ? HT_OK : HT_ERROR;
436: }
437: return HT_OK;
438: }
439:
440: PRIVATE HTDigest * HTDigest_new()
441: {
442: HTDigest * me = NULL;
443: if ((me = (HTDigest *) HT_CALLOC(1, sizeof(HTDigest))) == NULL)
444: HT_OUTOFMEM("HTDigest_new");
2.50 kahan 445: me->algorithm = HTDaMD5; /* use md5 as a default value */
2.44 frystyk 446: me->retry = YES; /* Ask the first time through */
447: return me;
448: }
449:
450: /* HTDigest_delete
451: ** --------------
452: ** Deletes a "digest" information object
453: ** A single object may be registered multiple places in the URL tree.
454: ** We keep a simple reference count on the object so that we know
455: ** when to delete the object.
456: */
457: PUBLIC int HTDigest_delete (void * context)
458: {
459: HTDigest * digest = (HTDigest *) context;
460: if (digest) {
461: if (digest->references <= 0) {
462: HT_FREE(digest->uid);
463: HT_FREE(digest->pw);
2.50 kahan 464: HT_FREE(digest->realm);
465: HT_FREE(digest->cnonce);
466: HT_FREE(digest->nonce);
2.44 frystyk 467: HT_FREE(digest->opaque);
2.50 kahan 468: HT_FREE(digest->qop);
2.44 frystyk 469: HT_FREE(digest);
2.50 kahan 470: return YES;
471: }
472: else
2.44 frystyk 473: digest->references--;
2.50 kahan 474: }
475: return NO;
476: }
477:
478: /* HTDigest_reset
479: ** --------------
480: ** When digest authentication fails, we simulate a new digest by
481: ** erasing the old one, but keeping the uid and the password. This is
482: ** so that we can take into account the stale nonce protocol, without
483: ** prompting the user for a new password.
484: */
485:
486: PRIVATE int HTDigest_reset (HTDigest *digest)
487: {
488: if (digest) {
489: digest->nc = 0l;
490: digest->stale = 0;
491: digest->retry = YES;
492: HT_FREE(digest->cnonce);
493: HT_FREE(digest->nonce);
494: HT_FREE(digest->opaque);
495: HT_FREE(digest->qop);
2.44 frystyk 496: return YES;
497: }
2.50 kahan 498: else
499: return NO;
500: }
501:
2.52 kahan 502: /* HTDigest_updateInfo
2.50 kahan 503: ** --------------
504: ** This function updates the digest with whatever new
505: ** authentification information the server sent back.
506: */
507:
2.52 kahan 508: PUBLIC int HTDigest_updateInfo (HTRequest *request, HTResponse *response,
509: void * context, int status)
2.50 kahan 510: {
2.52 kahan 511: HTAssocList * challenge = HTResponse_challenge(response);
512: const char * realm = HTRequest_realm (request);
513:
514: if (request && challenge && realm) {
515: BOOL proxy = 0;
516: char * value = NULL;
517: char * token = NULL;
518: char * auth_info = NULL;
519:
2.50 kahan 520: HTDigest *digest;
521: char *url;
522:
2.52 kahan 523: /*
524: ** try to find the magic string in the challenge
525: */
2.56 frystyk 526: HTTRACE(AUTH_TRACE, "Digest Update.. Processing authentication-info\n");
2.52 kahan 527: if ((auth_info = HTAssocList_findObject(challenge, DIGEST_AI)))
528: proxy = 0;
529: else if ((auth_info = HTAssocList_findObject(challenge,
530: PROXY_DIGEST_AI)))
531: proxy = 1;
532: else {
2.56 frystyk 533: HTTRACE(AUTH_TRACE, "Digest Update.. Didn't find any authentication-info\n");
2.52 kahan 534: return HT_OK;
535: }
536:
2.50 kahan 537: /*
538: ** find the digest credentials
539: */
540: if (proxy) {
541: url = HTRequest_proxy(request);
542: digest = (HTDigest *) HTAA_updateNode (proxy, DIGEST_AUTH, realm,
543: url, NULL);
544: } else {
545: url = HTAnchor_address((HTAnchor *)
546: HTRequest_anchor(request));
547: digest = (HTDigest *) HTAA_updateNode (proxy, DIGEST_AUTH, realm,
548: url, NULL);
2.57 kahan 549: HT_FREE(url);
2.50 kahan 550: }
551: if (!digest) {
2.56 frystyk 552: HTTRACE(AUTH_TRACE, "Digest Update.. Error: received authentication-info without having a local digest\n");
2.50 kahan 553: return HT_ERROR;
554: }
555:
556: /*
557: ** Search through the set of parameters in the Authentication-info
558: ** header.
559: */
560: while ((token = HTNextField(&auth_info))) {
561: if (!strcasecomp(token, "nextnonce")) {
562: if ((value = HTNextField(&auth_info))) {
563: HT_FREE (digest->nonce);
564: StrAllocCopy(digest->nonce, value);
565: } else if (!strcasecomp(token, "qop")) {
566: value = HTNextField(&auth_info);
567: /* split, process the qop, report errors */
568: } else if (!strcasecomp(token, "rspauth")) {
569: value = HTNextField(&auth_info);
570: /* process rspauth */
571: } else if (!strcasecomp(token, "cnonce")) {
572: value = HTNextField (&auth_info);
573: if (value && strcmp (digest->cnonce, value)) {
574: /* print an alert?, bad cnonce? */
575: }
576: } else if (!strcasecomp(token, "nc")) {
577: value = HTNextField(&auth_info);
578: /* compare and printo some error? */
579: }
580: }
581: }
582: }
2.52 kahan 583: return HT_OK;
2.50 kahan 584: }
585:
586: /*
587: ** Simple function to add a parameter/value pair to a string
588: **
589: */
590:
591: PRIVATE BOOL add_param (char ** dest, char *param, char * value, BOOL quoted)
592: {
593: char *tmp = *dest;
594:
595: if (!param || *param == '\0' || !value || *value == '\0')
596: return NO;
597:
598: /* if there was a previous parameter, we add the next one in the
599: following line */
600: if (tmp)
601: StrAllocCat(tmp, ",");
602:
603: /* copy the new parameter and value */
604: StrAllocCat(tmp, param);
605: StrAllocCat(tmp, "=");
606: if (quoted) {
607: StrAllocCat(tmp, "\"");
608: StrAllocCat(tmp, value);
609: StrAllocCat(tmp, "\"");
610: } else
611: StrAllocCat(tmp, value);
612: *dest = tmp;
613:
614: return YES;
615: }
616:
617: /*
618: ** Code derived from draft-ietf-http-authentication-03 starts here
619: */
620:
621: PRIVATE void CvtHex (HASH Bin, HASHHEX Hex)
622: {
623: unsigned short i;
624: unsigned char j;
625:
626: for (i = 0; i < HASHLEN; i++) {
627: j = (Bin[i] >> 4) & 0xf;
628: if (j <= 9)
629: Hex[i*2] = (j + '0');
630: else
631: Hex[i*2] = (j + 'a' - 10);
632: j = Bin[i] & 0xf;
633: if (j <= 9)
634: Hex[i*2+1] = (j + '0');
635: else
636: Hex[i*2+1] = (j + 'a' - 10);
637: }
638: Hex[HASHHEXLEN] = '\0';
639: }
640:
641: /* calculate H(A1) as per spec */
642: PRIVATE void DigestCalcHA1 (int algorithm, char * pszAlg, char * pszUserName,
643: char * pszRealm, char * pszPassword,
644: char * pszNonce, char * pszCNonce,
645: HASHHEX SessionKey)
646: {
647: HTDigestContext MdCtx;
648: HASH HA1;
649:
650: HTDigest_init (&MdCtx, algorithm);
651: HTDigest_update (&MdCtx, pszUserName, strlen(pszUserName));
652: HTDigest_update (&MdCtx, ":", 1);
653: HTDigest_update (&MdCtx, pszRealm, strlen(pszRealm));
654: HTDigest_update (&MdCtx, ":", 1);
655: HTDigest_update (&MdCtx, pszPassword, strlen(pszPassword));
656: HTDigest_final (HA1, &MdCtx);
2.51 frystyk 657: if (strcasecomp (pszAlg, "md5-sess") == 0) {
2.50 kahan 658: HTDigest_init (&MdCtx, algorithm);
659: HTDigest_update (&MdCtx, HA1, strlen (HA1));
660: HTDigest_update (&MdCtx, ":", 1);
661: HTDigest_update (&MdCtx, pszNonce, strlen(pszNonce));
662: HTDigest_update (&MdCtx, ":", 1);
663: HTDigest_update (&MdCtx, pszCNonce, strlen(pszCNonce));
664: HTDigest_final (HA1, &MdCtx);
665: }
666: CvtHex (HA1, SessionKey);
2.44 frystyk 667: }
668:
2.50 kahan 669: /* calculate request-digest/response-digest as per HTTP Digest spec */
670: PRIVATE void DigestCalcResponse (
671: int algorithm, /* message digest algorithm */
672: HASHHEX HA1, /* H(A1) */
673: char * pszNonce, /* nonce from server */
674: char * pszNonceCount, /* 8 hex digits */
675: char * pszCNonce, /* client nonce */
676: char * pszQop, /* qop-value: "", "auth", "auth-int" */
677: char * pszMethod, /* method from the request */
678: char * pszDigestUri, /* requested URL */
679: char * HEntity, /* H(entity body) if qop="auth-int" */
680: char * Response /* request-digest or response-digest */
681: )
682: {
683: HTDigestContext MdCtx;
684: HASH HA2;
685: HASH RespHash;
686: HASHHEX HA2Hex;
687:
688: /* calculate H(A2) */
689:
690: HTDigest_init (&MdCtx, algorithm);
691: HTDigest_update (&MdCtx, pszMethod, strlen(pszMethod));
692: HTDigest_update (&MdCtx, ":", 1);
693: HTDigest_update (&MdCtx, pszDigestUri, strlen(pszDigestUri));
2.51 frystyk 694: if (pszQop && strcasecomp (pszQop, "auth-int") == 0) {
2.50 kahan 695: HTDigest_update (&MdCtx, ":", 1);
696: HTDigest_update (&MdCtx, HEntity, HASHHEXLEN);
697: }
698: HTDigest_final (HA2, &MdCtx);
699: CvtHex (HA2, HA2Hex);
700:
701: /* calculate response */
702: HTDigest_init (&MdCtx, algorithm);
703: HTDigest_update (&MdCtx, HA1, HASHHEXLEN);
704: HTDigest_update (&MdCtx, ":", 1);
705: HTDigest_update (&MdCtx, pszNonce, strlen(pszNonce));
706: HTDigest_update (&MdCtx, ":", 1);
707: if (pszQop && *pszQop) {
708: HTDigest_update (&MdCtx, pszNonceCount, strlen(pszNonceCount));
709: HTDigest_update (&MdCtx, ":", 1);
710: HTDigest_update (&MdCtx, pszCNonce, strlen(pszCNonce));
711: HTDigest_update (&MdCtx, ":", 1);
712: HTDigest_update (&MdCtx, pszQop, strlen(pszQop));
713: HTDigest_update (&MdCtx, ":", 1);
714: }
715: HTDigest_update (&MdCtx, HA2Hex, HASHHEXLEN);
716: HTDigest_final (RespHash, &MdCtx);
717: CvtHex (RespHash, Response);
718: }
719:
720: /*
721: ** Code derived from draft-ietf-http-authentication-03 ends here
722: */
723:
2.44 frystyk 724: /*
725: ** Make digest authentication scheme credentials and register this
726: ** information in the request object as credentials. They will then
2.50 kahan 727: ** be included in the request header. An example is
728: **
729: ** "Digest nonce:cnonce:blahblahblhah:digest-response"
730: **
2.44 frystyk 731: ** The function can both create normal and proxy credentials
732: ** Returns HT_OK or HT_ERROR
733: */
2.50 kahan 734:
2.44 frystyk 735: PRIVATE BOOL digest_credentials (HTRequest * request, HTDigest * digest)
736: {
2.50 kahan 737: if (request && digest && digest->realm)
738: {
739: char * realm = (char *) digest->realm;
740: char * uri;
741: char * method = (char *) HTMethod_name (HTRequest_method (request));
742: char * cleartext = NULL;
743: char nc[9];
744: HASHHEX HA1;
745: HASHHEX HA2;
746: HASHHEX response;
2.44 frystyk 747:
2.50 kahan 748: /* @@ maybe optimize all my reallocs by preallocating the memory */
2.44 frystyk 749:
2.50 kahan 750: if (digest->proxy)
751: uri = HTRequest_proxy(request);
2.57 kahan 752: else {
753: char * tmp;
754: /* we get the absolute URL */
755: tmp = HTAnchor_address( (HTAnchor*)HTRequest_anchor(request));
756: /* and then remove what makes it absolute, to be backwards
757: compatible */
758: uri = HTParse (tmp, "", PARSE_PATH | PARSE_PUNCTUATION);
759: HT_FREE(tmp);
760: }
2.50 kahan 761:
762: /* increment the nonce counter */
763: digest->nc++;
764: sprintf (nc, "%08lx", digest->nc);
765: add_param (&cleartext, "username", digest->uid, YES);
766: add_param (&cleartext, "realm", realm, YES);
767: add_param (&cleartext, "nonce", digest->nonce, YES);
768: add_param (&cleartext, "uri", uri, YES);
769: /* @@@ no support for auth-int yet */
770: if (digest->qop) {
771: add_param (&cleartext, "qop", "auth", NO);
772: add_param (&cleartext, "nc", nc, NO);
773: add_param (&cleartext, "cnonce", digest->cnonce, YES);
2.62 ! kahan 774: if (strcasecomp (digest->qop, "auth")) {
2.61 kahan 775: /* we got some options, as we only know how to handle auth, we'll do so.
776: We should split the options and see if it was a valid Qop here, though,
777: to things correctly */
778: HT_FREE (digest->qop);
779: StrAllocCopy (digest->qop, "auth");
780: }
2.50 kahan 781: }
782: /* compute the response digest */
783: /* @@@ md5 hard coded, change it to something from the answer,
784: md5-sess, etc */
785: DigestCalcHA1 (digest->algorithm, "md5", digest->uid, realm, digest->pw, digest->nonce,
786: digest->cnonce, HA1);
787: DigestCalcResponse (digest->algorithm, HA1, digest->nonce, nc, digest->cnonce,
788: digest->qop, method, uri, HA2, response);
789: add_param (&cleartext, "response", response, NO);
790: add_param (&cleartext, "opaque", digest->opaque, NO);
2.44 frystyk 791:
792: /* Create the credentials and assign them to the request object */
793: {
2.50 kahan 794: int cr_len = strlen ("Digest") + strlen (cleartext) + 3;
2.44 frystyk 795: char * cookie = (char *) HT_MALLOC(cr_len+1);
796: if (!cookie) HT_OUTOFMEM("digest_credentials");
797: strcpy(cookie, "Digest ");
2.50 kahan 798: strcat (cookie, cleartext);
2.56 frystyk 799: HTTRACE(AUTH_TRACE, "Digest Cookie `%s\'\n" _ cookie);
2.44 frystyk 800:
801: /* Check whether it is proxy or normal credentials */
802: if (digest->proxy)
2.50 kahan 803: HTRequest_addCredentials(request, "Proxy-Authorization",
804: cookie);
2.44 frystyk 805: else
806: HTRequest_addCredentials(request, "Authorization", cookie);
807:
808: HT_FREE(cookie);
809: }
2.57 kahan 810: if (!digest->proxy)
811: HT_FREE(uri);
2.44 frystyk 812: HT_FREE(cleartext);
813: return HT_OK;
814: }
815: return HT_ERROR;
816: }
817:
818: /* HTDigest_generate
819: ** ----------------
820: ** This function generates "digest" credentials for the challenge found in
821: ** the authentication information base for this request. The result is
822: ** stored as an association list in the request object.
823: ** This is a callback function for the AA handler.
824: */
2.45 frystyk 825: PUBLIC int HTDigest_generate (HTRequest * request, void * context, int mode)
2.44 frystyk 826: {
827: HTDigest * digest = (HTDigest *) context;
2.45 frystyk 828: BOOL proxy = mode==HT_NO_PROXY_ACCESS ? YES : NO;
2.44 frystyk 829: if (request) {
830: const char * realm = HTRequest_realm(request);
2.46 frystyk 831:
832: /*
833: ** If we were asked to explicitly ask the user again
834: */
835: if (mode == HT_REAUTH || mode == HT_PROXY_REAUTH)
836: digest->retry = YES;
2.44 frystyk 837:
838: /*
839: ** If we don't have a digest context then add a new one to the tree.
840: ** We use different trees for normal and proxy authentication
841: */
842: if (!digest) {
2.50 kahan 843: digest = HTDigest_new();
2.44 frystyk 844: if (proxy) {
845: char * url = HTRequest_proxy(request);
846: digest->proxy = YES;
847: HTAA_updateNode(proxy, DIGEST_AUTH, realm, url, digest);
848: } else {
849: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
850: HTAA_updateNode(proxy, DIGEST_AUTH, realm, url, digest);
851: HT_FREE(url);
852: }
853: }
854:
855: /*
856: ** If we have a set of credentials (or the user provides a new set)
857: ** then store it in the request object as the credentials
858: */
2.50 kahan 859: if ((digest->retry &&
2.44 frystyk 860: prompt_digest_user(request, realm, digest) == HT_OK) ||
861: (!digest->retry && digest->uid)) {
2.50 kahan 862: /* @@@ here we should generate a new cnonce value */
2.59 kahan 863: HTSACopy (&(digest->cnonce), "012345678");
2.44 frystyk 864: digest->retry = NO;
865: return digest_credentials(request, digest);
2.50 kahan 866: } else {
867: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
868: if (proxy)
869: HTAA_deleteNode(proxy, DIGEST_AUTH, realm, url);
870: else
871: HTAA_deleteNode(proxy, DIGEST_AUTH, realm, url);
872: HT_FREE(url);
2.44 frystyk 873: return HT_ERROR;
2.50 kahan 874: }
2.44 frystyk 875: }
876: return HT_OK;
877: }
878:
2.60 kahan 879: /*
880: ** Evaluates the existing authentication info (nonce, uid, pwd) and
881: ** returns TRUE if we evaluate that the nonce is stale, FALSE
882: ** otherwise.
883: */
884: PRIVATE BOOL nonce_is_stale (HTRequest *request, HTDigest * digest, char * old_nonce)
885: {
886: if (!digest->uid || !digest->pw)
887: return FALSE;
888: if (!digest->nonce || !old_nonce)
889: return FALSE;
890: if (strcmp (digest->nonce, old_nonce))
891: return TRUE;
892: /* because of a pipelining implementation bug, we don't send any good
893: credentials on requests following the first one in the pipeline */
894: if (!HTRequest_credentials (request) && HTRequest_AAretrys (request) == 1)
895: return TRUE;
896:
897: return FALSE;
898: }
899:
2.44 frystyk 900: /* HTDigest_parse
901: ** -------------
902: ** This function parses the contents of a "digest" challenge
903: ** and stores the challenge in our authentication information datebase.
904: ** We also store the realm in the request object which will help finding
905: ** the right set of credentials to generate.
906: ** The function is a callback function for the AA handler.
907: */
2.45 frystyk 908: PUBLIC int HTDigest_parse (HTRequest * request, HTResponse * response,
909: void * context, int status)
2.44 frystyk 910: {
2.45 frystyk 911: HTAssocList * challenge = HTResponse_challenge(response);
2.44 frystyk 912: HTDigest * digest = NULL;
913: BOOL proxy = status==HT_NO_PROXY_ACCESS ? YES : NO;
914: if (request && challenge) {
915: char * p = HTAssocList_findObject(challenge, DIGEST_AUTH);
916: char * realm = HTNextField(&p);
2.50 kahan 917: char * rm = HTNextField(&p);
918: char * value = NULL;
2.44 frystyk 919: char * token = NULL;
920: char * uris = NULL;
2.60 kahan 921: /* the value of the previous nonce in case the server has changed its
922: challenge */
923: char * old_nonce = NULL;
2.44 frystyk 924:
925: /*
2.50 kahan 926: ** If valid challenge then make a template for the resource and
927: ** store this information in our authentication URL Tree
2.44 frystyk 928: */
2.50 kahan 929: if (realm && !strcasecomp(realm, "realm") && rm) {
2.56 frystyk 930: HTTRACE(AUTH_TRACE, "Digest Parse. Realm `%s\' found\n" _ rm);
2.50 kahan 931: HTRequest_setRealm(request, rm);
932:
933: /*
934: ** If we are in proxy mode then add the proxy - not the final URL
935: */
936: if (proxy) {
937: char * url = HTRequest_proxy(request);
2.56 frystyk 938: HTTRACE(AUTH_TRACE, "Digest Parse. Proxy authentication\n");
2.50 kahan 939: digest = (HTDigest *) HTAA_updateNode(proxy, DIGEST_AUTH, rm,
940: url, NULL);
941: /* if the previous authentication failed, then try again */
942: if (HTRequest_AAretrys (request) > 1
943: && status == HT_NO_ACCESS && digest)
944: digest->retry = YES;
945: } else {
946: char * url = HTAnchor_address((HTAnchor *)
947: HTRequest_anchor(request));
948: char * tmplate = make_template(url);
949: digest = (HTDigest *) HTAA_updateNode(proxy, DIGEST_AUTH, rm,
950: tmplate, NULL);
951: /* if the previous authentication failed, then try again */
952: if (HTRequest_AAretrys (request) > 1
953: && status == HT_NO_ACCESS && digest)
954: digest->retry = YES;
955: HT_FREE(tmplate);
956: HT_FREE(url);
957: }
958: } else {
2.56 frystyk 959: HTTRACE(AUTH_TRACE, "Digest Parse. Missing or incomplete realm\n");
2.50 kahan 960: return HT_ERROR;
2.44 frystyk 961: }
2.50 kahan 962:
963:
964: /* if we get here it's because there's no digest */
965: /* we parse the digest parameters from the challenge */
966:
967: if (digest) {
968: /* it's an old digest, so we clean all in it except for the
969: uid and the password, hoping that the server send back
970: that data */
2.60 kahan 971: old_nonce = digest->nonce;
972: digest->nonce = NULL;
2.50 kahan 973: HTDigest_reset (digest);
974: } else {
975: /* it's a brand new digest */
2.44 frystyk 976: digest = HTDigest_new();
2.50 kahan 977: StrAllocCopy (digest->realm, rm);
978: }
2.44 frystyk 979:
980: /*
981: ** Search through the set of parameters in the digest header.
982: ** If valid challenge then make a template for the resource and
983: ** store this information in our authentication URL Tree
984: */
985: while ((token = HTNextField(&p))) {
986: if (!strcasecomp(token, "domain")) {
987: if ((value = HTNextField(&p)))
988: uris = value;
2.50 kahan 989: } else if (!strcasecomp(token, "nonce")) {
2.44 frystyk 990: if ((value = HTNextField(&p)))
2.50 kahan 991: StrAllocCopy(digest->nonce, value);
2.44 frystyk 992: } else if (!strcasecomp(token, "opaque")) {
993: if ((value = HTNextField(&p)))
994: StrAllocCopy(digest->opaque, value);
2.50 kahan 995: } else if (!strcasecomp(token, "qop")) {
996: /* split the qop */
997: if ((value = HTNextField(&p)))
998: StrAllocCopy(digest->qop, value);
2.44 frystyk 999: } else if (!strcasecomp(token, "stale")) {
2.50 kahan 1000: if ((value = HTNextField(&p)) && !strcasecomp(value, "true")) {
1001: /* only true if we already had a digest with uid and pw info */
1002: if (digest->uid && digest->pw) {
1003: digest->stale = YES;
1004: }
1005: }
2.44 frystyk 1006: } else if (!strcasecomp(token, "algorithm")) {
2.50 kahan 1007: if ((value = HTNextField(&p)) && strcasecomp(value, "md5")) {
2.44 frystyk 1008: /*
1009: ** We only support MD5 for the moment
1010: */
2.56 frystyk 1011: HTTRACE(AUTH_TRACE, "Digest Parse Unknown algorithm `%s\'\n" _ value);
2.44 frystyk 1012: HTDigest_delete(digest);
2.60 kahan 1013: if (old_nonce)
1014: HT_FREE (old_nonce);
2.44 frystyk 1015: return HT_ERROR;
2.50 kahan 1016: } else
1017: digest->algorithm = HTDaMD5;
1018: }
1019: }
2.60 kahan 1020:
1021: /* Pipelining support. If a nonce becomes stale When sending
1022: ** several requests thru the pipeline, we may miss the stale
1023: ** reply in the server's answer. To avoid this, we keep a copy
1024: ** of the nonce in each request. If the nonce wasn't explicitly
1025: ** marked stale and if it's the same that we sent, then we
1026: ** consider that the uid/pwd pairs were false. Otherwise, we
1027: ** assume the stole went stale before
1028: */
1029: if (!digest->stale && nonce_is_stale (request, digest, old_nonce))
1030: digest->stale = YES;
1031:
1032: if (old_nonce)
1033: HT_FREE (old_nonce);
1034:
1035: if (digest->stale) {
1036: digest->stale = NO;
1037: digest->retry = NO;
2.50 kahan 1038: return HT_OK;
2.60 kahan 1039: }
2.50 kahan 1040: else if (digest->uid || digest->pw) {
1041: /*
1042: ** For some reason there was no stale nonce header and the
1043: ** authentication failed so we have to ask the user if we should
1044: ** try again. It may be because the user typed the wrong user name
1045: ** and password
1046: */
1047: HTAlertCallback * prompt = HTAlert_find(HT_A_CONFIRM);
1048:
1049: /*
1050: ** Do we have a method registered for prompting the user whether
1051: ** we should retry
1052: */
1053: if (prompt) {
1054: int code = proxy ?
1055: HT_MSG_RETRY_PROXY_AUTH : HT_MSG_RETRY_AUTHENTICATION;
1056: if ((*prompt)(request, HT_A_CONFIRM, code,
1057: NULL, NULL, NULL) != YES)
1058: return HT_ERROR;
1059: return HT_OK;
2.44 frystyk 1060: }
2.50 kahan 1061: return HT_ERROR;
2.44 frystyk 1062: }
1063:
1064: /*
2.50 kahan 1065: ** It's the first time we go this way, so we check the domain field to
1066: ** create the digest node entries for each URI.
2.44 frystyk 1067: */
1068: if (!uris) {
1069: if (proxy) {
2.50 kahan 1070: /* we ignore the domain */
2.44 frystyk 1071: char * location = HTRequest_proxy(request);
2.56 frystyk 1072: HTTRACE(AUTH_TRACE, "Digest Parse Proxy authentication\n");
2.50 kahan 1073: HTAA_updateNode(proxy, DIGEST_AUTH, rm, location, digest);
2.44 frystyk 1074: } else {
1075: char * url = HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
1076: char * tmplate = make_template(url);
2.50 kahan 1077: HTAA_updateNode(proxy, DIGEST_AUTH, rm, tmplate, digest);
2.44 frystyk 1078: HT_FREE(url);
1079: HT_FREE(tmplate);
1080: }
1081: } else {
2.50 kahan 1082: char * base_url =
1083: HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
1084: char * domain_url;
1085: char * full_url;
1086:
1087: while ((domain_url = HTNextField (&uris))) {
1088: /* complete the URL if it's an absolute one */
1089: full_url = HTParse (domain_url, base_url, PARSE_ALL);
1090: digest->references++;
1091: if (proxy) {
2.56 frystyk 1092: HTTRACE(AUTH_TRACE, "Digest Parse Proxy authentication\n");
2.50 kahan 1093: HTAA_updateNode(proxy, DIGEST_AUTH, rm, full_url, digest);
1094: } else {
1095: char * tmplate = make_template(full_url);
1096: HTAA_updateNode (proxy, DIGEST_AUTH, rm, tmplate, digest);
1097: HT_FREE (tmplate);
1098: }
1099: HT_FREE (full_url);
2.44 frystyk 1100: }
2.50 kahan 1101: HT_FREE (base_url);
2.44 frystyk 1102: }
1103: return HT_OK;
2.50 kahan 1104: }
2.56 frystyk 1105: HTTRACE(AUTH_TRACE, "Auth........ No challenges found\n");
2.44 frystyk 1106: return HT_ERROR;
1107: }
Webmaster