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