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