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