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