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