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