Annotation of libwww/Library/src/HTAABrow.c, revision 2.60
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.60 ! kahan 6: ** @(#) $Id: HTAABrow.c,v 2.59 2000/06/19 10:00:32 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);
774: }
775: /* compute the response digest */
776: /* @@@ md5 hard coded, change it to something from the answer,
777: md5-sess, etc */
778: DigestCalcHA1 (digest->algorithm, "md5", digest->uid, realm, digest->pw, digest->nonce,
779: digest->cnonce, HA1);
780: DigestCalcResponse (digest->algorithm, HA1, digest->nonce, nc, digest->cnonce,
781: digest->qop, method, uri, HA2, response);
782: add_param (&cleartext, "response", response, NO);
783: add_param (&cleartext, "opaque", digest->opaque, NO);
2.44 frystyk 784:
785: /* Create the credentials and assign them to the request object */
786: {
2.50 kahan 787: int cr_len = strlen ("Digest") + strlen (cleartext) + 3;
2.44 frystyk 788: char * cookie = (char *) HT_MALLOC(cr_len+1);
789: if (!cookie) HT_OUTOFMEM("digest_credentials");
790: strcpy(cookie, "Digest ");
2.50 kahan 791: strcat (cookie, cleartext);
2.56 frystyk 792: HTTRACE(AUTH_TRACE, "Digest Cookie `%s\'\n" _ cookie);
2.44 frystyk 793:
794: /* Check whether it is proxy or normal credentials */
795: if (digest->proxy)
2.50 kahan 796: HTRequest_addCredentials(request, "Proxy-Authorization",
797: cookie);
2.44 frystyk 798: else
799: HTRequest_addCredentials(request, "Authorization", cookie);
800:
801: HT_FREE(cookie);
802: }
2.57 kahan 803: if (!digest->proxy)
804: HT_FREE(uri);
2.44 frystyk 805: HT_FREE(cleartext);
806: return HT_OK;
807: }
808: return HT_ERROR;
809: }
810:
811: /* HTDigest_generate
812: ** ----------------
813: ** This function generates "digest" credentials for the challenge found in
814: ** the authentication information base for this request. The result is
815: ** stored as an association list in the request object.
816: ** This is a callback function for the AA handler.
817: */
2.45 frystyk 818: PUBLIC int HTDigest_generate (HTRequest * request, void * context, int mode)
2.44 frystyk 819: {
820: HTDigest * digest = (HTDigest *) context;
2.45 frystyk 821: BOOL proxy = mode==HT_NO_PROXY_ACCESS ? YES : NO;
2.44 frystyk 822: if (request) {
823: const char * realm = HTRequest_realm(request);
2.46 frystyk 824:
825: /*
826: ** If we were asked to explicitly ask the user again
827: */
828: if (mode == HT_REAUTH || mode == HT_PROXY_REAUTH)
829: digest->retry = YES;
2.44 frystyk 830:
831: /*
832: ** If we don't have a digest context then add a new one to the tree.
833: ** We use different trees for normal and proxy authentication
834: */
835: if (!digest) {
2.50 kahan 836: digest = HTDigest_new();
2.44 frystyk 837: if (proxy) {
838: char * url = HTRequest_proxy(request);
839: digest->proxy = YES;
840: HTAA_updateNode(proxy, DIGEST_AUTH, realm, url, digest);
841: } else {
842: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
843: HTAA_updateNode(proxy, DIGEST_AUTH, realm, url, digest);
844: HT_FREE(url);
845: }
846: }
847:
848: /*
849: ** If we have a set of credentials (or the user provides a new set)
850: ** then store it in the request object as the credentials
851: */
2.50 kahan 852: if ((digest->retry &&
2.44 frystyk 853: prompt_digest_user(request, realm, digest) == HT_OK) ||
854: (!digest->retry && digest->uid)) {
2.50 kahan 855: /* @@@ here we should generate a new cnonce value */
2.59 kahan 856: HTSACopy (&(digest->cnonce), "012345678");
2.44 frystyk 857: digest->retry = NO;
858: return digest_credentials(request, digest);
2.50 kahan 859: } else {
860: char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
861: if (proxy)
862: HTAA_deleteNode(proxy, DIGEST_AUTH, realm, url);
863: else
864: HTAA_deleteNode(proxy, DIGEST_AUTH, realm, url);
865: HT_FREE(url);
2.44 frystyk 866: return HT_ERROR;
2.50 kahan 867: }
2.44 frystyk 868: }
869: return HT_OK;
870: }
871:
2.60 ! kahan 872: /*
! 873: ** Evaluates the existing authentication info (nonce, uid, pwd) and
! 874: ** returns TRUE if we evaluate that the nonce is stale, FALSE
! 875: ** otherwise.
! 876: */
! 877: PRIVATE BOOL nonce_is_stale (HTRequest *request, HTDigest * digest, char * old_nonce)
! 878: {
! 879: if (!digest->uid || !digest->pw)
! 880: return FALSE;
! 881: if (!digest->nonce || !old_nonce)
! 882: return FALSE;
! 883: if (strcmp (digest->nonce, old_nonce))
! 884: return TRUE;
! 885: /* because of a pipelining implementation bug, we don't send any good
! 886: credentials on requests following the first one in the pipeline */
! 887: if (!HTRequest_credentials (request) && HTRequest_AAretrys (request) == 1)
! 888: return TRUE;
! 889:
! 890: return FALSE;
! 891: }
! 892:
2.44 frystyk 893: /* HTDigest_parse
894: ** -------------
895: ** This function parses the contents of a "digest" challenge
896: ** and stores the challenge in our authentication information datebase.
897: ** We also store the realm in the request object which will help finding
898: ** the right set of credentials to generate.
899: ** The function is a callback function for the AA handler.
900: */
2.45 frystyk 901: PUBLIC int HTDigest_parse (HTRequest * request, HTResponse * response,
902: void * context, int status)
2.44 frystyk 903: {
2.45 frystyk 904: HTAssocList * challenge = HTResponse_challenge(response);
2.44 frystyk 905: HTDigest * digest = NULL;
906: BOOL proxy = status==HT_NO_PROXY_ACCESS ? YES : NO;
907: if (request && challenge) {
908: char * p = HTAssocList_findObject(challenge, DIGEST_AUTH);
909: char * realm = HTNextField(&p);
2.50 kahan 910: char * rm = HTNextField(&p);
911: char * value = NULL;
2.44 frystyk 912: char * token = NULL;
913: char * uris = NULL;
2.60 ! kahan 914: /* the value of the previous nonce in case the server has changed its
! 915: challenge */
! 916: char * old_nonce = NULL;
2.44 frystyk 917:
918: /*
2.50 kahan 919: ** If valid challenge then make a template for the resource and
920: ** store this information in our authentication URL Tree
2.44 frystyk 921: */
2.50 kahan 922: if (realm && !strcasecomp(realm, "realm") && rm) {
2.56 frystyk 923: HTTRACE(AUTH_TRACE, "Digest Parse. Realm `%s\' found\n" _ rm);
2.50 kahan 924: HTRequest_setRealm(request, rm);
925:
926: /*
927: ** If we are in proxy mode then add the proxy - not the final URL
928: */
929: if (proxy) {
930: char * url = HTRequest_proxy(request);
2.56 frystyk 931: HTTRACE(AUTH_TRACE, "Digest Parse. Proxy authentication\n");
2.50 kahan 932: digest = (HTDigest *) HTAA_updateNode(proxy, DIGEST_AUTH, rm,
933: url, NULL);
934: /* if the previous authentication failed, then try again */
935: if (HTRequest_AAretrys (request) > 1
936: && status == HT_NO_ACCESS && digest)
937: digest->retry = YES;
938: } else {
939: char * url = HTAnchor_address((HTAnchor *)
940: HTRequest_anchor(request));
941: char * tmplate = make_template(url);
942: digest = (HTDigest *) HTAA_updateNode(proxy, DIGEST_AUTH, rm,
943: tmplate, NULL);
944: /* if the previous authentication failed, then try again */
945: if (HTRequest_AAretrys (request) > 1
946: && status == HT_NO_ACCESS && digest)
947: digest->retry = YES;
948: HT_FREE(tmplate);
949: HT_FREE(url);
950: }
951: } else {
2.56 frystyk 952: HTTRACE(AUTH_TRACE, "Digest Parse. Missing or incomplete realm\n");
2.50 kahan 953: return HT_ERROR;
2.44 frystyk 954: }
2.50 kahan 955:
956:
957: /* if we get here it's because there's no digest */
958: /* we parse the digest parameters from the challenge */
959:
960: if (digest) {
961: /* it's an old digest, so we clean all in it except for the
962: uid and the password, hoping that the server send back
963: that data */
2.60 ! kahan 964: old_nonce = digest->nonce;
! 965: digest->nonce = NULL;
2.50 kahan 966: HTDigest_reset (digest);
967: } else {
968: /* it's a brand new digest */
2.44 frystyk 969: digest = HTDigest_new();
2.50 kahan 970: StrAllocCopy (digest->realm, rm);
971: }
2.44 frystyk 972:
973: /*
974: ** Search through the set of parameters in the digest header.
975: ** If valid challenge then make a template for the resource and
976: ** store this information in our authentication URL Tree
977: */
978: while ((token = HTNextField(&p))) {
979: if (!strcasecomp(token, "domain")) {
980: if ((value = HTNextField(&p)))
981: uris = value;
2.50 kahan 982: } else if (!strcasecomp(token, "nonce")) {
2.44 frystyk 983: if ((value = HTNextField(&p)))
2.50 kahan 984: StrAllocCopy(digest->nonce, value);
2.44 frystyk 985: } else if (!strcasecomp(token, "opaque")) {
986: if ((value = HTNextField(&p)))
987: StrAllocCopy(digest->opaque, value);
2.50 kahan 988: } else if (!strcasecomp(token, "qop")) {
989: /* split the qop */
990: if ((value = HTNextField(&p)))
991: StrAllocCopy(digest->qop, value);
2.44 frystyk 992: } else if (!strcasecomp(token, "stale")) {
2.50 kahan 993: if ((value = HTNextField(&p)) && !strcasecomp(value, "true")) {
994: /* only true if we already had a digest with uid and pw info */
995: if (digest->uid && digest->pw) {
996: digest->stale = YES;
997: }
998: }
2.44 frystyk 999: } else if (!strcasecomp(token, "algorithm")) {
2.50 kahan 1000: if ((value = HTNextField(&p)) && strcasecomp(value, "md5")) {
2.44 frystyk 1001: /*
1002: ** We only support MD5 for the moment
1003: */
2.56 frystyk 1004: HTTRACE(AUTH_TRACE, "Digest Parse Unknown algorithm `%s\'\n" _ value);
2.44 frystyk 1005: HTDigest_delete(digest);
2.60 ! kahan 1006: if (old_nonce)
! 1007: HT_FREE (old_nonce);
2.44 frystyk 1008: return HT_ERROR;
2.50 kahan 1009: } else
1010: digest->algorithm = HTDaMD5;
1011: }
1012: }
2.60 ! kahan 1013:
! 1014: /* Pipelining support. If a nonce becomes stale When sending
! 1015: ** several requests thru the pipeline, we may miss the stale
! 1016: ** reply in the server's answer. To avoid this, we keep a copy
! 1017: ** of the nonce in each request. If the nonce wasn't explicitly
! 1018: ** marked stale and if it's the same that we sent, then we
! 1019: ** consider that the uid/pwd pairs were false. Otherwise, we
! 1020: ** assume the stole went stale before
! 1021: */
! 1022: if (!digest->stale && nonce_is_stale (request, digest, old_nonce))
! 1023: digest->stale = YES;
! 1024:
! 1025: if (old_nonce)
! 1026: HT_FREE (old_nonce);
! 1027:
! 1028: if (digest->stale) {
! 1029: digest->stale = NO;
! 1030: digest->retry = NO;
2.50 kahan 1031: return HT_OK;
2.60 ! kahan 1032: }
2.50 kahan 1033: else if (digest->uid || digest->pw) {
1034: /*
1035: ** For some reason there was no stale nonce header and the
1036: ** authentication failed so we have to ask the user if we should
1037: ** try again. It may be because the user typed the wrong user name
1038: ** and password
1039: */
1040: HTAlertCallback * prompt = HTAlert_find(HT_A_CONFIRM);
1041:
1042: /*
1043: ** Do we have a method registered for prompting the user whether
1044: ** we should retry
1045: */
1046: if (prompt) {
1047: int code = proxy ?
1048: HT_MSG_RETRY_PROXY_AUTH : HT_MSG_RETRY_AUTHENTICATION;
1049: if ((*prompt)(request, HT_A_CONFIRM, code,
1050: NULL, NULL, NULL) != YES)
1051: return HT_ERROR;
1052: return HT_OK;
2.44 frystyk 1053: }
2.50 kahan 1054: return HT_ERROR;
2.44 frystyk 1055: }
1056:
1057: /*
2.50 kahan 1058: ** It's the first time we go this way, so we check the domain field to
1059: ** create the digest node entries for each URI.
2.44 frystyk 1060: */
1061: if (!uris) {
1062: if (proxy) {
2.50 kahan 1063: /* we ignore the domain */
2.44 frystyk 1064: char * location = HTRequest_proxy(request);
2.56 frystyk 1065: HTTRACE(AUTH_TRACE, "Digest Parse Proxy authentication\n");
2.50 kahan 1066: HTAA_updateNode(proxy, DIGEST_AUTH, rm, location, digest);
2.44 frystyk 1067: } else {
1068: char * url = HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
1069: char * tmplate = make_template(url);
2.50 kahan 1070: HTAA_updateNode(proxy, DIGEST_AUTH, rm, tmplate, digest);
2.44 frystyk 1071: HT_FREE(url);
1072: HT_FREE(tmplate);
1073: }
1074: } else {
2.50 kahan 1075: char * base_url =
1076: HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
1077: char * domain_url;
1078: char * full_url;
1079:
1080: while ((domain_url = HTNextField (&uris))) {
1081: /* complete the URL if it's an absolute one */
1082: full_url = HTParse (domain_url, base_url, PARSE_ALL);
1083: digest->references++;
1084: if (proxy) {
2.56 frystyk 1085: HTTRACE(AUTH_TRACE, "Digest Parse Proxy authentication\n");
2.50 kahan 1086: HTAA_updateNode(proxy, DIGEST_AUTH, rm, full_url, digest);
1087: } else {
1088: char * tmplate = make_template(full_url);
1089: HTAA_updateNode (proxy, DIGEST_AUTH, rm, tmplate, digest);
1090: HT_FREE (tmplate);
1091: }
1092: HT_FREE (full_url);
2.44 frystyk 1093: }
2.50 kahan 1094: HT_FREE (base_url);
2.44 frystyk 1095: }
1096: return HT_OK;
2.50 kahan 1097: }
2.56 frystyk 1098: HTTRACE(AUTH_TRACE, "Auth........ No challenges found\n");
2.44 frystyk 1099: return HT_ERROR;
1100: }
Webmaster