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