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