1 /*
2    HTTP Authentication routines
3    Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18    MA 02111-1307, USA
19 
20 */
21 
22 
23 /* HTTP Authentication, as per RFC2617.
24  *
25  * TODO:
26  *  - Test auth-int support
27  */
28 
29 #include "config.h"
30 
31 #include <sys/types.h>
32 
33 #ifdef HAVE_SYS_TIME_H
34 #include <sys/time.h>
35 #endif
36 #ifdef HAVE_STDLIB_H
37 #include <stdlib.h>
38 #endif
39 #ifdef HAVE_STRING_H
40 #include <string.h>
41 #endif
42 #ifdef HAVE_STRINGS_H
43 #include <strings.h>
44 #endif
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h> /* for getpid() */
47 #endif
48 
49 #ifdef WIN32
50 #include <windows.h> /* for GetCurrentThreadId() etc */
51 #endif
52 
53 #ifdef NEON_SSL
54 #include <openssl/rand.h>
55 #endif
56 
57 #include <time.h>
58 
59 #include "ne_md5.h"
60 #include "ne_dates.h"
61 #include "ne_request.h"
62 #include "ne_auth.h"
63 #include "ne_string.h"
64 #include "ne_utils.h"
65 #include "ne_alloc.h"
66 #include "ne_uri.h"
67 #include "ne_i18n.h"
68 
69 #ifdef HAVE_GSSAPI
70 #ifdef HAVE_GSSAPI_GSSAPI_H
71 #include <gssapi/gssapi.h>
72 #ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
73 #include <gssapi/gssapi_generic.h>
74 #endif
75 #else
76 #include <gssapi.h>
77 #endif
78 #endif
79 
80 /* TODO: should remove this eventually. Need it for
81  * ne_pull_request_body. */
82 #include "ne_private.h"
83 
84 #define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth"
85 #define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth"
86 
87 /* The authentication scheme we are using */
88 typedef enum {
89     auth_scheme_basic,
90     auth_scheme_digest,
91     auth_scheme_gssapi
92 } auth_scheme;
93 
94 typedef enum {
95     auth_alg_md5,
96     auth_alg_md5_sess,
97     auth_alg_unknown
98 } auth_algorithm;
99 
100 /* Selected method of qop which the client is using */
101 typedef enum {
102     auth_qop_none,
103     auth_qop_auth,
104     auth_qop_auth_int
105 } auth_qop;
106 
107 /* A challenge */
108 struct auth_challenge {
109     auth_scheme scheme;
110     const char *realm;
111     const char *nonce;
112     const char *opaque;
113     unsigned int stale:1; /* if stale=true */
114     unsigned int got_qop:1; /* we were given a qop directive */
115     unsigned int qop_auth:1; /* "auth" token in qop attrib */
116     unsigned int qop_auth_int:1; /* "auth-int" token in qop attrib */
117     auth_algorithm alg;
118     struct auth_challenge *next;
119 };
120 
121 static const struct auth_class {
122     const char *id, *req_hdr, *resp_hdr, *resp_info_hdr, *fail_msg;
123     int status_code, fail_code;
124 } ah_server_class = {
125     HOOK_SERVER_ID,
126     "Authorization", "WWW-Authenticate", "Authentication-Info",
127     N_("Server was not authenticated correctly."), 401, NE_AUTH
128 }, ah_proxy_class = {
129     HOOK_PROXY_ID,
130     "Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info",
131     N_("Proxy server was not authenticated correctly."), 407, NE_PROXYAUTH
132 };
133 
134 /* Authentication session state. */
135 typedef struct {
136     ne_session *sess;
137 
138     /* Which context will auth challenges be accepted? */
139     enum {
140         AUTH_ANY, /* ignore nothing. */
141         AUTH_CONNECT, /* only in response to a CONNECT request. */
142         AUTH_NOTCONNECT /* only in non-CONNECT responsees */
143     } context;
144 
145     /* Specifics for server/proxy auth. FIXME: need a better field
146      * name! */
147     const struct auth_class *spec;
148 
149     /* The scheme used for this authentication session */
150     auth_scheme scheme;
151     /* The callback used to request new username+password */
152     ne_auth_creds creds;
153     void *userdata;
154 
155     /*** Session details ***/
156 
157     /* The username and password we are using to authenticate with */
158     char username[NE_ABUFSIZ];
159     /* Whether we CAN supply authentication at the moment */
160     unsigned int can_handle:1;
161     /* This used for Basic auth */
162     char *basic;
163 #ifdef HAVE_GSSAPI
164     /* This used for GSSAPI auth */
165     char *gssapi_token;
166 #endif
167     /* These all used for Digest auth */
168     char *realm;
169     char *nonce;
170     char *cnonce;
171     char *opaque;
172     auth_qop qop;
173     auth_algorithm alg;
174     unsigned int nonce_count;
175     /* The ASCII representation of the session's H(A1) value */
176     char h_a1[33];
177 
178     /* Temporary store for half of the Request-Digest
179      * (an optimisation - used in the response-digest calculation) */
180     struct ne_md5_ctx stored_rdig;
181 
182     /* Details of server... needed to reconstruct absoluteURI's when
183      * necessary */
184     const char *host;
185     const char *uri_scheme;
186     unsigned int port;
187 
188     int attempt;
189 } auth_session;
190 
191 struct auth_request {
192     /*** Per-request details. ***/
193     ne_request *request; /* the request object. */
194 
195     /* The method and URI we are using for the current request */
196     const char *uri;
197     const char *method;
198     /* Whether we WILL supply authentication for this request or not */
199     unsigned int will_handle:1;
200 
201     /* Used for calculation of H(entity-body) of the response */
202     struct ne_md5_ctx response_body;
203 
204     /* Results of response-header callbacks */
205     char *auth_hdr, *auth_info_hdr;
206 };
207 
clean_session(auth_session * sess)208 static void clean_session(auth_session *sess)
209 {
210     sess->can_handle = 0;
211     NE_FREE(sess->basic);
212     NE_FREE(sess->nonce);
213     NE_FREE(sess->cnonce);
214     NE_FREE(sess->opaque);
215     NE_FREE(sess->realm);
216 #ifdef HAVE_GSSAPI
217     NE_FREE(sess->gssapi_token);
218 #endif
219 }
220 
221 /* Returns client nonce string. */
get_cnonce(void)222 static char *get_cnonce(void)
223 {
224     char ret[33];
225     unsigned char data[256], tmp[16];
226     struct ne_md5_ctx hash;
227 
228     ne_md5_init_ctx(&hash);
229 
230 #ifdef NEON_SSL
231     if (RAND_status() == 1 && RAND_pseudo_bytes(data, sizeof data) >= 0)
232 	ne_md5_process_bytes(data, sizeof data, &hash);
233     else {
234 #endif
235     /* Fallback sources of random data: all bad, but no good sources
236      * are available. */
237 
238     /* Uninitialized stack data; yes, happy valgrinders, this is
239      * supposed to be here. */
240     ne_md5_process_bytes(data, sizeof data, &hash);
241 
242 #ifdef HAVE_GETTIMEOFDAY
243     {
244 	struct timeval tv;
245 	if (gettimeofday(&tv, NULL) == 0)
246 	    ne_md5_process_bytes(&tv, sizeof tv, &hash);
247     }
248 #else /* HAVE_GETTIMEOFDAY */
249     {
250 	time_t t = time(NULL);
251 	ne_md5_process_bytes(&t, sizeof t, &hash);
252     }
253 #endif
254     {
255 #ifdef WIN32
256 	DWORD pid = GetCurrentThreadId();
257 #else
258 	pid_t pid = getpid();
259 #endif
260 	ne_md5_process_bytes(&pid, sizeof pid, &hash);
261     }
262 
263 #ifdef NEON_SSL
264     }
265 #endif
266 
267     ne_md5_finish_ctx(&hash, tmp);
268     ne_md5_to_ascii(tmp, ret);
269 
270     return ne_strdup(ret);
271 }
272 
get_credentials(auth_session * sess,char * pwbuf)273 static int get_credentials(auth_session *sess, char *pwbuf)
274 {
275     return sess->creds(sess->userdata, sess->realm, sess->attempt++,
276 		       sess->username, pwbuf);
277 }
278 
279 /* Examine a Basic auth challenge.
280  * Returns 0 if an valid challenge, else non-zero. */
basic_challenge(auth_session * sess,struct auth_challenge * parms)281 static int basic_challenge(auth_session *sess, struct auth_challenge *parms)
282 {
283     char *tmp, password[NE_ABUFSIZ];
284 
285     /* Verify challenge... must have a realm */
286     if (parms->realm == NULL) {
287 	return -1;
288     }
289 
290     NE_DEBUG(NE_DBG_HTTPAUTH, "Got Basic challenge with realm [%s]\n",
291 	     parms->realm);
292 
293     clean_session(sess);
294 
295     sess->realm = ne_strdup(parms->realm);
296 
297     if (get_credentials(sess, password)) {
298 	/* Failed to get credentials */
299 	return -1;
300     }
301 
302     sess->scheme = auth_scheme_basic;
303 
304     tmp = ne_concat(sess->username, ":", password, NULL);
305     sess->basic = ne_base64((unsigned char *)tmp, strlen(tmp));
306     ne_free(tmp);
307 
308     /* Paranoia. */
309     memset(password, 0, sizeof password);
310 
311     return 0;
312 }
313 
314 /* Add Basic authentication credentials to a request */
request_basic(auth_session * sess)315 static char *request_basic(auth_session *sess)
316 {
317     return ne_concat("Basic ", sess->basic, "\r\n", NULL);
318 }
319 
320 #ifdef HAVE_GSSAPI
321 /* Add GSSAPI authentication credentials to a request */
request_gssapi(auth_session * sess)322 static char *request_gssapi(auth_session *sess)
323 {
324     return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL);
325 }
326 
get_gss_name(gss_name_t * server,auth_session * sess)327 static int get_gss_name(gss_name_t *server, auth_session *sess)
328 {
329     unsigned int major_status, minor_status;
330     gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
331 
332     token.value = ne_concat("HTTP@", sess->sess->server.hostname, NULL);
333     token.length = strlen(token.value);
334 
335     major_status = gss_import_name(&minor_status, &token,
336                                    GSS_C_NT_HOSTBASED_SERVICE,
337                                    server);
338     return GSS_ERROR(major_status) ? -1 : 0;
339 }
340 
341 /* Examine a GSSAPI auth challenge; returns 0 if a valid challenge,
342  * else non-zero. */
343 static int
gssapi_challenge(auth_session * sess,struct auth_challenge * parms)344 gssapi_challenge(auth_session *sess, struct auth_challenge *parms)
345 {
346     gss_ctx_id_t context;
347     gss_name_t server_name;
348     unsigned int major_status, minor_status;
349     gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
350     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
351 
352     clean_session(sess);
353 
354     if (get_gss_name(&server_name, sess))
355         return -1;
356 
357     major_status = gss_init_sec_context(&minor_status,
358                                         GSS_C_NO_CREDENTIAL,
359                                         &context,
360                                         server_name,
361                                         GSS_C_NO_OID,
362                                         0,
363                                         GSS_C_INDEFINITE,
364                                         GSS_C_NO_CHANNEL_BINDINGS,
365                                         &input_token,
366                                         NULL,
367                                         &output_token,
368                                         NULL,
369                                         NULL);
370     gss_release_name(&minor_status, &server_name);
371 
372     if (GSS_ERROR(major_status)) {
373         NE_DEBUG(NE_DBG_HTTPAUTH, "gss_init_sec_context failed.\n");
374         return -1;
375     }
376 
377     if (output_token.length == 0)
378         return -1;
379 
380     sess->gssapi_token = ne_base64(output_token.value, output_token.length);
381     gss_release_buffer(&major_status, &output_token);
382 
383     NE_DEBUG(NE_DBG_HTTPAUTH,
384              "Base64 encoded GSSAPI challenge: %s.\n", sess->gssapi_token);
385     sess->scheme = auth_scheme_gssapi;
386     return 0;
387 }
388 #endif
389 
390 /* Examine a digest challenge: return 0 if it is a valid Digest challenge,
391  * else non-zero. */
digest_challenge(auth_session * sess,struct auth_challenge * parms)392 static int digest_challenge(auth_session *sess, struct auth_challenge *parms)
393 {
394     struct ne_md5_ctx tmp;
395     unsigned char tmp_md5[16];
396     char password[NE_ABUFSIZ];
397 
398     /* Verify they've given us the right bits. */
399     if (parms->alg == auth_alg_unknown ||
400 	((parms->alg == auth_alg_md5_sess) &&
401 	 !(parms->qop_auth || parms->qop_auth_int)) ||
402 	parms->realm==NULL || parms->nonce==NULL) {
403 	NE_DEBUG(NE_DBG_HTTPAUTH, "Invalid challenge.");
404 	return -1;
405     }
406 
407     if (parms->stale) {
408 	/* Just a stale response, don't need to get a new username/password */
409 	NE_DEBUG(NE_DBG_HTTPAUTH, "Stale digest challenge.\n");
410     } else {
411 	/* Forget the old session details */
412 	NE_DEBUG(NE_DBG_HTTPAUTH, "In digest challenge.\n");
413 
414 	clean_session(sess);
415 
416 	sess->realm = ne_strdup(parms->realm);
417 
418 	/* Not a stale response: really need user authentication */
419 	if (get_credentials(sess, password)) {
420 	    /* Failed to get credentials */
421 	    return -1;
422 	}
423     }
424     sess->alg = parms->alg;
425     sess->scheme = auth_scheme_digest;
426     sess->nonce = ne_strdup(parms->nonce);
427     sess->cnonce = get_cnonce();
428     /* TODO: add domain handling. */
429     if (parms->opaque != NULL) {
430 	sess->opaque = ne_strdup(parms->opaque); /* don't strip the quotes */
431     }
432 
433     if (parms->got_qop) {
434 	/* What type of qop are we to apply to the message? */
435 	NE_DEBUG(NE_DBG_HTTPAUTH, "Got qop directive.\n");
436 	sess->nonce_count = 0;
437 	if (parms->qop_auth_int) {
438 	    sess->qop = auth_qop_auth_int;
439 	} else {
440 	    sess->qop = auth_qop_auth;
441 	}
442     } else {
443 	/* No qop at all/ */
444 	sess->qop = auth_qop_none;
445     }
446 
447     if (!parms->stale) {
448 	/* Calculate H(A1).
449 	 * tmp = H(unq(username-value) ":" unq(realm-value) ":" passwd)
450 	 */
451 	NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating H(A1).\n");
452 	ne_md5_init_ctx(&tmp);
453 	ne_md5_process_bytes(sess->username, strlen(sess->username), &tmp);
454 	ne_md5_process_bytes(":", 1, &tmp);
455 	ne_md5_process_bytes(sess->realm, strlen(sess->realm), &tmp);
456 	ne_md5_process_bytes(":", 1, &tmp);
457 	ne_md5_process_bytes(password, strlen(password), &tmp);
458 	memset(password, 0, sizeof password); /* done with that. */
459 	ne_md5_finish_ctx(&tmp, tmp_md5);
460 	if (sess->alg == auth_alg_md5_sess) {
461 	    unsigned char a1_md5[16];
462 	    struct ne_md5_ctx a1;
463 	    char tmp_md5_ascii[33];
464 	    /* Now we calculate the SESSION H(A1)
465 	     *    A1 = H(...above...) ":" unq(nonce-value) ":" unq(cnonce-value)
466 	     */
467 	    ne_md5_to_ascii(tmp_md5, tmp_md5_ascii);
468 	    ne_md5_init_ctx(&a1);
469 	    ne_md5_process_bytes(tmp_md5_ascii, 32, &a1);
470 	    ne_md5_process_bytes(":", 1, &a1);
471 	    ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), &a1);
472 	    ne_md5_process_bytes(":", 1, &a1);
473 	    ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), &a1);
474 	    ne_md5_finish_ctx(&a1, a1_md5);
475 	    ne_md5_to_ascii(a1_md5, sess->h_a1);
476 	    NE_DEBUG(NE_DBG_HTTPAUTH, "Session H(A1) is [%s]\n", sess->h_a1);
477 	} else {
478 	    ne_md5_to_ascii(tmp_md5, sess->h_a1);
479 	    NE_DEBUG(NE_DBG_HTTPAUTH, "H(A1) is [%s]\n", sess->h_a1);
480 	}
481 
482     }
483 
484     NE_DEBUG(NE_DBG_HTTPAUTH, "I like this Digest challenge.\n");
485 
486     return 0;
487 }
488 
489 /* callback for ne_pull_request_body. */
digest_body(void * userdata,const char * buf,size_t count)490 static int digest_body(void *userdata, const char *buf, size_t count)
491 {
492     struct ne_md5_ctx *ctx = userdata;
493     ne_md5_process_bytes(buf, count, ctx);
494     return 0;
495 }
496 
497 /* Return Digest authentication credentials header value for the given
498  * session. */
request_digest(auth_session * sess,struct auth_request * req)499 static char *request_digest(auth_session *sess, struct auth_request *req)
500 {
501     struct ne_md5_ctx a2, rdig;
502     unsigned char a2_md5[16], rdig_md5[16];
503     char a2_md5_ascii[33], rdig_md5_ascii[33];
504     char nc_value[9] = {0};
505     const char *qop_value; /* qop-value */
506     ne_buffer *ret;
507 
508     /* Increase the nonce-count */
509     if (sess->qop != auth_qop_none) {
510 	sess->nonce_count++;
511 	ne_snprintf(nc_value, 9, "%08x", sess->nonce_count);
512 	NE_DEBUG(NE_DBG_HTTPAUTH, "Nonce count is %u, nc is [%s]\n",
513 		 sess->nonce_count, nc_value);
514     }
515 
516     qop_value = sess->qop == auth_qop_auth_int ? "auth-int" : "auth";
517 
518     /* Calculate H(A2). */
519     ne_md5_init_ctx(&a2);
520     ne_md5_process_bytes(req->method, strlen(req->method), &a2);
521     ne_md5_process_bytes(":", 1, &a2);
522     ne_md5_process_bytes(req->uri, strlen(req->uri), &a2);
523 
524     if (sess->qop == auth_qop_auth_int) {
525 	struct ne_md5_ctx body;
526 	char tmp_md5_ascii[33];
527 	unsigned char tmp_md5[16];
528 
529 	ne_md5_init_ctx(&body);
530 
531 	/* Calculate H(entity-body): pull in the request body from
532 	 * where-ever it is coming from, and calculate the digest. */
533 
534 	NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting request body...\n");
535 	ne_pull_request_body(req->request, digest_body, &body);
536 	NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting request body done.\n");
537 
538 	ne_md5_finish_ctx(&body, tmp_md5);
539 	ne_md5_to_ascii(tmp_md5, tmp_md5_ascii);
540 
541 	NE_DEBUG(NE_DBG_HTTPAUTH, "H(entity-body) is [%s]\n", tmp_md5_ascii);
542 
543 	/* Append to A2 */
544 	ne_md5_process_bytes(":", 1, &a2);
545 	ne_md5_process_bytes(tmp_md5_ascii, 32, &a2);
546     }
547     ne_md5_finish_ctx(&a2, a2_md5);
548     ne_md5_to_ascii(a2_md5, a2_md5_ascii);
549     NE_DEBUG(NE_DBG_HTTPAUTH, "H(A2): %s\n", a2_md5_ascii);
550 
551     NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating Request-Digest.\n");
552     /* Now, calculation of the Request-Digest.
553      * The first section is the regardless of qop value
554      *     H(A1) ":" unq(nonce-value) ":" */
555     ne_md5_init_ctx(&rdig);
556 
557     /* Use the calculated H(A1) */
558     ne_md5_process_bytes(sess->h_a1, 32, &rdig);
559 
560     ne_md5_process_bytes(":", 1, &rdig);
561     ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), &rdig);
562     ne_md5_process_bytes(":", 1, &rdig);
563     if (sess->qop != auth_qop_none) {
564 	/* Add on:
565 	 *    nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":"
566 	 */
567 	NE_DEBUG(NE_DBG_HTTPAUTH, "Have qop directive, digesting: [%s:%s:%s]\n",
568 		 nc_value, sess->cnonce, qop_value);
569 	ne_md5_process_bytes(nc_value, 8, &rdig);
570 	ne_md5_process_bytes(":", 1, &rdig);
571 	ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), &rdig);
572 	ne_md5_process_bytes(":", 1, &rdig);
573 	/* Store a copy of this structure (see note below) */
574 	sess->stored_rdig = rdig;
575 	ne_md5_process_bytes(qop_value, strlen(qop_value), &rdig);
576 	ne_md5_process_bytes(":", 1, &rdig);
577     } else {
578 	/* Store a copy of this structure... we do this because the
579 	 * calculation of the rspauth= field in the Auth-Info header
580 	 * is the same as this digest, up to this point. */
581 	sess->stored_rdig = rdig;
582     }
583     /* And finally, H(A2) */
584     ne_md5_process_bytes(a2_md5_ascii, 32, &rdig);
585     ne_md5_finish_ctx(&rdig, rdig_md5);
586     ne_md5_to_ascii(rdig_md5, rdig_md5_ascii);
587 
588     ret = ne_buffer_create();
589 
590     ne_buffer_concat(ret,
591 		     "Digest username=\"", sess->username, "\", "
592 		     "realm=\"", sess->realm, "\", "
593 		     "nonce=\"", sess->nonce, "\", "
594 		     "uri=\"", req->uri, "\", "
595 		     "response=\"", rdig_md5_ascii, "\", "
596 		     "algorithm=\"", sess->alg == auth_alg_md5 ? "MD5" : "MD5-sess", "\"",
597 		     NULL);
598 
599     if (sess->opaque != NULL) {
600 	ne_buffer_concat(ret, ", opaque=\"", sess->opaque, "\"", NULL);
601     }
602 
603     if (sess->qop != auth_qop_none) {
604 	/* Add in cnonce and nc-value fields */
605 	ne_buffer_concat(ret, ", cnonce=\"", sess->cnonce, "\", "
606 			 "nc=", nc_value, ", "
607 			 "qop=\"", qop_value, "\"", NULL);
608     }
609 
610     ne_buffer_zappend(ret, "\r\n");
611 
612     NE_DEBUG(NE_DBG_HTTPAUTH, "Digest request header is %s\n", ret->data);
613 
614     return ne_buffer_finish(ret);
615 }
616 
617 /* Parse line of comma-separated key-value pairs.  If 'ischall' == 1,
618  * then also return a leading space-separated token, as *value == NULL.
619  * Otherwise, if return value is 0, *key and *value will be non-NULL.
620  * If return value is non-zero, parsing has ended. */
tokenize(char ** hdr,char ** key,char ** value,int ischall)621 static int tokenize(char **hdr, char **key, char **value, int ischall)
622 {
623     char *pnt = *hdr;
624     enum { BEFORE_EQ, AFTER_EQ, AFTER_EQ_QUOTED } state = BEFORE_EQ;
625 
626     if (**hdr == '\0')
627 	return 1;
628 
629     *key = NULL;
630 
631     do {
632 	switch (state) {
633 	case BEFORE_EQ:
634 	    if (*pnt == '=') {
635 		if (*key == NULL)
636 		    return -1;
637 		*pnt = '\0';
638 		*value = pnt + 1;
639 		state = AFTER_EQ;
640 	    } else if (*pnt == ' ' && ischall && *key != NULL) {
641 		*value = NULL;
642 		*pnt = '\0';
643 		*hdr = pnt + 1;
644 		return 0;
645 	    } else if (*key == NULL && strchr(" \r\n\t", *pnt) == NULL) {
646 		*key = pnt;
647 	    }
648 	    break;
649 	case AFTER_EQ:
650 	    if (*pnt == ',') {
651 		*pnt = '\0';
652 		*hdr = pnt + 1;
653 		return 0;
654 	    } else if (*pnt == '\"') {
655 		state = AFTER_EQ_QUOTED;
656 	    }
657 	    break;
658 	case AFTER_EQ_QUOTED:
659 	    if (*pnt == '\"') {
660 		state = AFTER_EQ;
661 	    }
662 	    break;
663 	}
664     } while (*++pnt != '\0');
665 
666     if (state == BEFORE_EQ && ischall && *key != NULL) {
667 	*value = NULL;
668     }
669 
670     *hdr = pnt;
671 
672     /* End of string: */
673     return 0;
674 }
675 
676 /* Pass this the value of the 'Authentication-Info:' header field, if
677  * one is received.
678  * Returns:
679  *    0 if it gives a valid authentication for the server
680  *    non-zero otherwise (don't believe the response in this case!).
681  */
verify_response(struct auth_request * req,auth_session * sess,const char * value)682 static int verify_response(struct auth_request *req, auth_session *sess,
683 			   const char *value)
684 {
685     char *hdr, *pnt, *key, *val;
686     auth_qop qop = auth_qop_none;
687     char *nextnonce = NULL, /* for the nextnonce= value */
688 	*rspauth = NULL, /* for the rspauth= value */
689 	*cnonce = NULL, /* for the cnonce= value */
690 	*nc = NULL, /* for the nc= value */
691 	*qop_value = NULL;
692     unsigned int nonce_count;
693     int okay;
694 
695     if (!req->will_handle) {
696 	/* Ignore it */
697 	return 0;
698     }
699 
700     if (sess->scheme != auth_scheme_digest) {
701 	NE_DEBUG(NE_DBG_HTTPAUTH, "Found Auth-Info header not in response "
702 		 " to Digest credentials - dodgy.\n");
703 	return -1;
704     }
705 
706     pnt = hdr = ne_strdup(value);
707 
708     NE_DEBUG(NE_DBG_HTTPAUTH, "Auth-Info header: %s\n", value);
709 
710     while (tokenize(&pnt, &key, &val, 0) == 0) {
711 	val = ne_shave(val, "\"");
712 	NE_DEBUG(NE_DBG_HTTPAUTH, "Pair: [%s] = [%s]\n", key, val);
713 	if (strcasecmp(key, "qop") == 0) {
714 	    qop_value = val;
715 	    if (strcasecmp(val, "auth-int") == 0) {
716 		qop = auth_qop_auth_int;
717 	    } else if (strcasecmp(val, "auth") == 0) {
718 		qop = auth_qop_auth;
719 	    } else {
720 		qop = auth_qop_none;
721 	    }
722 	} else if (strcasecmp(key, "nextnonce") == 0) {
723 	    nextnonce = val;
724 	} else if (strcasecmp(key, "rspauth") == 0) {
725 	    rspauth = val;
726 	} else if (strcasecmp(key, "cnonce") == 0) {
727 	    cnonce = val;
728 	} else if (strcasecmp(key, "nc") == 0) {
729 	    nc = val;
730 	    if (sscanf(val, "%x", &nonce_count) != 1) {
731 		NE_DEBUG(NE_DBG_HTTPAUTH, "Couldn't find nonce count.\n");
732 	    } else {
733 		NE_DEBUG(NE_DBG_HTTPAUTH, "Got nonce_count: %u\n", nonce_count);
734 	    }
735 	}
736     }
737 
738     /* Presume the worst */
739     okay = -1;
740 
741     if ((qop != auth_qop_none) && (qop_value != NULL)) {
742 	if ((rspauth == NULL) || (cnonce == NULL) || (nc == NULL)) {
743 	    NE_DEBUG(NE_DBG_HTTPAUTH, "Missing rspauth, cnonce or nc with qop.\n");
744 	} else { /* Have got rspauth, cnonce and nc */
745 	    if (strcmp(cnonce, sess->cnonce) != 0) {
746 		NE_DEBUG(NE_DBG_HTTPAUTH, "Response cnonce doesn't match.\n");
747 	    } else if (nonce_count != sess->nonce_count) {
748 		NE_DEBUG(NE_DBG_HTTPAUTH, "Response nonce count doesn't match.\n");
749 	    } else {
750 		/* Calculate and check the response-digest value.
751 		 * joe: IMO the spec is slightly ambiguous as to whether
752 		 * we use the qop which WE sent, or the qop which THEY
753 		 * sent...  */
754 		struct ne_md5_ctx a2;
755 		unsigned char a2_md5[16], rdig_md5[16];
756 		char a2_md5_ascii[33], rdig_md5_ascii[33];
757 
758 		NE_DEBUG(NE_DBG_HTTPAUTH, "Calculating response-digest.\n");
759 
760 		/* First off, H(A2) again. */
761 		ne_md5_init_ctx(&a2);
762 		ne_md5_process_bytes(":", 1, &a2);
763 		ne_md5_process_bytes(req->uri, strlen(req->uri), &a2);
764 		if (qop == auth_qop_auth_int) {
765 		    unsigned char heb_md5[16];
766 		    char heb_md5_ascii[33];
767 		    /* Add on ":" H(entity-body) */
768 		    ne_md5_finish_ctx(&req->response_body, heb_md5);
769 		    ne_md5_to_ascii(heb_md5, heb_md5_ascii);
770 		    ne_md5_process_bytes(":", 1, &a2);
771 		    ne_md5_process_bytes(heb_md5_ascii, 32, &a2);
772 		    NE_DEBUG(NE_DBG_HTTPAUTH, "Digested [:%s]\n", heb_md5_ascii);
773 		}
774 		ne_md5_finish_ctx(&a2, a2_md5);
775 		ne_md5_to_ascii(a2_md5, a2_md5_ascii);
776 
777 		/* We have the stored digest-so-far of
778 		 *   H(A1) ":" unq(nonce-value)
779 		 *        [ ":" nc-value ":" unq(cnonce-value) ] for qop
780 		 * in sess->stored_rdig, to save digesting them again.
781 		 *
782 		 */
783 		if (qop != auth_qop_none) {
784 		    /* Add in qop-value */
785 		    NE_DEBUG(NE_DBG_HTTPAUTH, "Digesting qop-value [%s:].\n",
786 			     qop_value);
787 		    ne_md5_process_bytes(qop_value, strlen(qop_value),
788 					 &sess->stored_rdig);
789 		    ne_md5_process_bytes(":", 1, &sess->stored_rdig);
790 		}
791 		/* Digest ":" H(A2) */
792 		ne_md5_process_bytes(a2_md5_ascii, 32, &sess->stored_rdig);
793 		/* All done */
794 		ne_md5_finish_ctx(&sess->stored_rdig, rdig_md5);
795 		ne_md5_to_ascii(rdig_md5, rdig_md5_ascii);
796 
797 		NE_DEBUG(NE_DBG_HTTPAUTH, "Calculated response-digest of: "
798 			 "[%s]\n", rdig_md5_ascii);
799 		NE_DEBUG(NE_DBG_HTTPAUTH, "Given response-digest of:      "
800 			 "[%s]\n", rspauth);
801 
802 		/* And... do they match? */
803 		okay = (strcasecmp(rdig_md5_ascii, rspauth) == 0)?0:-1;
804 		NE_DEBUG(NE_DBG_HTTPAUTH, "Matched: %s\n", okay?"nope":"YES!");
805 	    }
806 	}
807     } else {
808 	NE_DEBUG(NE_DBG_HTTPAUTH, "No qop directive, auth okay.\n");
809 	okay = 0;
810     }
811 
812     /* Check for a nextnonce */
813     if (nextnonce != NULL) {
814 	NE_DEBUG(NE_DBG_HTTPAUTH, "Found nextnonce of [%s].\n", nextnonce);
815 	if (sess->nonce != NULL)
816 	    ne_free(sess->nonce);
817 	sess->nonce = ne_strdup(nextnonce);
818     }
819 
820     ne_free(hdr);
821 
822     return okay;
823 }
824 
825 /* Passed the value of a "(Proxy,WWW)-Authenticate: " header field.
826  * Returns 0 if valid challenge was accepted; non-zero if no valid
827  * challenge was found. */
auth_challenge(auth_session * sess,const char * value)828 static int auth_challenge(auth_session *sess, const char *value)
829 {
830     char *pnt, *key, *val, *hdr;
831     struct auth_challenge *chall = NULL, *challenges = NULL;
832     int success;
833 
834     pnt = hdr = ne_strdup(value);
835 
836     NE_DEBUG(NE_DBG_HTTPAUTH, "Got new auth challenge: %s\n", value);
837 
838     /* The header value may be made up of one or more challenges.  We
839      * split it down into attribute-value pairs, then search for
840      * schemes in the pair keys. */
841 
842     while (!tokenize(&pnt, &key, &val, 1)) {
843 
844 	if (val == NULL) {
845 	    /* We have a new challenge */
846 	    NE_DEBUG(NE_DBG_HTTPAUTH, "New challenge for scheme [%s]\n", key);
847 	    chall = ne_calloc(sizeof *chall);
848 
849 	    chall->next = challenges;
850 	    challenges = chall;
851 	    /* Initialize the challenge parameters */
852 	    /* Which auth-scheme is it (case-insensitive matching) */
853 	    if (strcasecmp(key, "basic") == 0) {
854 		NE_DEBUG(NE_DBG_HTTPAUTH, "Basic scheme.\n");
855 		chall->scheme = auth_scheme_basic;
856 	    } else if (strcasecmp(key, "digest") == 0) {
857 		NE_DEBUG(NE_DBG_HTTPAUTH, "Digest scheme.\n");
858 		chall->scheme = auth_scheme_digest;
859 #ifdef HAVE_GSSAPI
860 	    } else if (strcasecmp(key, "negotiate") == 0) {
861 		NE_DEBUG(NE_DBG_HTTPAUTH, "GSSAPI scheme.\n");
862 		chall->scheme = auth_scheme_gssapi;
863 #endif
864 	    } else {
865 		NE_DEBUG(NE_DBG_HTTPAUTH, "Unknown scheme.\n");
866 		ne_free(chall);
867 		challenges = NULL;
868 		break;
869 	    }
870 	    continue;
871 	} else if (chall == NULL) {
872 	    /* If we haven't got an auth-scheme, and we're
873 	     * haven't yet found a challenge, skip this pair.
874 	     */
875 	    continue;
876 	}
877 
878 	/* Strip quotes off value. */
879 	val = ne_shave(val, "\"'");
880 
881 	NE_DEBUG(NE_DBG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, val);
882 
883 	if (strcasecmp(key, "realm") == 0) {
884 	    chall->realm = val;
885 	} else if (strcasecmp(key, "nonce") == 0) {
886 	    chall->nonce = val;
887 	} else if (strcasecmp(key, "opaque") == 0) {
888 	    chall->opaque = val;
889 	} else if (strcasecmp(key, "stale") == 0) {
890 	    /* Truth value */
891 	    chall->stale = (strcasecmp(val, "true") == 0);
892 	} else if (strcasecmp(key, "algorithm") == 0) {
893 	    if (strcasecmp(val, "md5") == 0) {
894 		chall->alg = auth_alg_md5;
895 	    } else if (strcasecmp(val, "md5-sess") == 0) {
896 		chall->alg = auth_alg_md5_sess;
897 	    } else {
898 		chall->alg = auth_alg_unknown;
899 	    }
900 	} else if (strcasecmp(key, "qop") == 0) {
901             /* iterate over each token in the value */
902             do {
903                 const char *tok = ne_shave(ne_token(&val, ','), " \t");
904 
905                 if (strcasecmp(tok, "auth") == 0) {
906                     chall->qop_auth = 1;
907                 } else if (strcasecmp(tok, "auth-int") == 0 ) {
908                     chall->qop_auth_int = 1;
909                 }
910             } while (val);
911 
912             chall->got_qop = chall->qop_auth || chall->qop_auth_int;
913 	}
914     }
915 
916     NE_DEBUG(NE_DBG_HTTPAUTH, "Finished parsing parameters.\n");
917 
918     /* Did we find any challenges */
919     if (challenges == NULL) {
920 	ne_free(hdr);
921 	return -1;
922     }
923 
924     success = 0;
925 
926 #ifdef HAVE_GSSAPI
927     if (strcmp(ne_get_scheme(sess->sess), "https") == 0) {
928         NE_DEBUG(NE_DBG_HTTPAUTH, "Looking for GSSAPI.\n");
929         /* Try a GSSAPI challenge */
930         for (chall = challenges; chall != NULL; chall = chall->next) {
931             if (chall->scheme == auth_scheme_gssapi) {
932 	    if (!gssapi_challenge(sess, chall)) {
933 		success = 1;
934 		break;
935 	    }
936             }
937         }
938     }
939 #endif
940 
941     /* Try a digest challenge */
942     if (!success) {
943         NE_DEBUG(NE_DBG_HTTPAUTH, "Looking for Digest challenges.\n");
944         for (chall = challenges; chall != NULL; chall = chall->next) {
945         	if (chall->scheme == auth_scheme_digest) {
946         	    if (!digest_challenge(sess, chall)) {
947         		success = 1;
948         		break;
949         	    }
950         	}
951         }
952     }
953 
954     if (!success) {
955 	NE_DEBUG(NE_DBG_HTTPAUTH,
956 		 "No good Digest challenges, looking for Basic.\n");
957 	for (chall = challenges; chall != NULL; chall = chall->next) {
958 	    if (chall->scheme == auth_scheme_basic) {
959 		if (!basic_challenge(sess, chall)) {
960 		    success = 1;
961 		    break;
962 		}
963 	    }
964 	}
965 
966 	if (!success) {
967 	    /* No good challenges - record this in the session state */
968 	    NE_DEBUG(NE_DBG_HTTPAUTH, "Did not understand any challenges.\n");
969 	}
970 
971     }
972 
973     /* Remember whether we can now supply the auth details */
974     sess->can_handle = success;
975 
976     while (challenges != NULL) {
977 	chall = challenges->next;
978 	ne_free(challenges);
979 	challenges = chall;
980     }
981 
982     ne_free(hdr);
983 
984     return !success;
985 }
986 
987 /* The body reader callback. */
auth_body_reader(void * cookie,const char * block,size_t length)988 static void auth_body_reader(void *cookie, const char *block, size_t length)
989 {
990     struct ne_md5_ctx *ctx = cookie;
991     NE_DEBUG(NE_DBG_HTTPAUTH,
992 	     "Digesting %" NE_FMT_SIZE_T " bytes of response body.\n", length);
993     ne_md5_process_bytes(block, length, ctx);
994 }
995 
ah_create(ne_request * req,void * session,const char * method,const char * uri)996 static void ah_create(ne_request *req, void *session, const char *method,
997 		      const char *uri)
998 {
999     auth_session *sess = session;
1000     int is_connect = strcmp(method, "CONNECT") == 0;
1001 
1002     if (sess->context == AUTH_ANY ||
1003         (is_connect && sess->context == AUTH_CONNECT) ||
1004         (!is_connect && sess->context == AUTH_NOTCONNECT)) {
1005         struct auth_request *areq = ne_calloc(sizeof *areq);
1006 
1007         NE_DEBUG(NE_DBG_HTTPAUTH, "ah_create, for %s\n", sess->spec->resp_hdr);
1008 
1009         areq->method = method;
1010         areq->uri = uri;
1011         areq->request = req;
1012 
1013         ne_add_response_header_handler(req, sess->spec->resp_hdr,
1014                                        ne_duplicate_header, &areq->auth_hdr);
1015 
1016 
1017         ne_add_response_header_handler(req, sess->spec->resp_info_hdr,
1018                                        ne_duplicate_header,
1019                                        &areq->auth_info_hdr);
1020 
1021         sess->attempt = 0;
1022 
1023         ne_set_request_private(req, sess->spec->id, areq);
1024     }
1025 }
1026 
1027 
ah_pre_send(ne_request * r,void * cookie,ne_buffer * request)1028 static void ah_pre_send(ne_request *r, void *cookie, ne_buffer *request)
1029 {
1030     auth_session *sess = cookie;
1031     struct auth_request *req = ne_get_request_private(r, sess->spec->id);
1032 
1033     if (!sess->can_handle || !req) {
1034 	NE_DEBUG(NE_DBG_HTTPAUTH, "Not handling session.\n");
1035     } else {
1036 	char *value;
1037 
1038 	NE_DEBUG(NE_DBG_HTTPAUTH, "Handling.");
1039 	req->will_handle = 1;
1040 
1041 	if (sess->qop == auth_qop_auth_int) {
1042 	    /* Digest mode / qop=auth-int: take an MD5 digest of the
1043 	     * response body. */
1044 	    ne_md5_init_ctx(&req->response_body);
1045 	    ne_add_response_body_reader(req->request, ne_accept_always,
1046 					auth_body_reader, &req->response_body);
1047 	}
1048 
1049 	switch(sess->scheme) {
1050 	case auth_scheme_basic:
1051 	    value = request_basic(sess);
1052 	    break;
1053 	case auth_scheme_digest:
1054 	    value = request_digest(sess, req);
1055 	    break;
1056 #ifdef HAVE_GSSAPI
1057 	case auth_scheme_gssapi:
1058 	    value = request_gssapi(sess);
1059 	    break;
1060 #endif
1061 	default:
1062 	    value = NULL;
1063 	    break;
1064 	}
1065 
1066 	if (value != NULL) {
1067 	    ne_buffer_concat(request, sess->spec->req_hdr, ": ", value, NULL);
1068 	    ne_free(value);
1069 	}
1070 
1071     }
1072 
1073 }
1074 
1075 #define SAFELY(x) ((x) != NULL?(x):"null")
1076 
ah_post_send(ne_request * req,void * cookie,const ne_status * status)1077 static int ah_post_send(ne_request *req, void *cookie, const ne_status *status)
1078 {
1079     auth_session *sess = cookie;
1080     struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1081     int ret = NE_OK;
1082 
1083     if (!areq) return NE_OK;
1084 
1085     NE_DEBUG(NE_DBG_HTTPAUTH,
1086 	     "ah_post_send (#%d), code is %d (want %d), %s is %s\n",
1087 	     sess->attempt, status->code, sess->spec->status_code,
1088 	     sess->spec->resp_hdr, SAFELY(areq->auth_hdr));
1089     if (areq->auth_info_hdr != NULL &&
1090 	verify_response(areq, sess, areq->auth_info_hdr)) {
1091 	NE_DEBUG(NE_DBG_HTTPAUTH, "Response authentication invalid.\n");
1092 	ne_set_error(sess->sess, "%s", _(sess->spec->fail_msg));
1093 	ret = NE_ERROR;
1094     } else if ((status->code == sess->spec->status_code ||
1095                 (status->code == 401 && sess->context == AUTH_CONNECT)) &&
1096 	       areq->auth_hdr != NULL) {
1097         /* note above: allow a 401 in response to a CONNECT request
1098          * from a proxy since some buggy proxies send that. */
1099 	NE_DEBUG(NE_DBG_HTTPAUTH, "Got challenge with code %d.\n", status->code);
1100 	if (!auth_challenge(sess, areq->auth_hdr)) {
1101 	    ret = NE_RETRY;
1102 	} else {
1103 	    clean_session(sess);
1104 	    ret = sess->spec->fail_code;
1105 	}
1106     }
1107 
1108     NE_FREE(areq->auth_info_hdr);
1109     NE_FREE(areq->auth_hdr);
1110 
1111     return ret;
1112 }
1113 
ah_destroy(ne_request * req,void * session)1114 static void ah_destroy(ne_request *req, void *session)
1115 {
1116     auth_session *sess = session;
1117     struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1118     if (areq) ne_free(areq);
1119 }
1120 
free_auth(void * cookie)1121 static void free_auth(void *cookie)
1122 {
1123     auth_session *sess = cookie;
1124 
1125     clean_session(sess);
1126     ne_free(sess);
1127 }
1128 
auth_register(ne_session * sess,int isproxy,const struct auth_class * ahc,const char * id,ne_auth_creds creds,void * userdata)1129 static void auth_register(ne_session *sess, int isproxy,
1130 			  const struct auth_class *ahc, const char *id,
1131 			  ne_auth_creds creds, void *userdata)
1132 {
1133     auth_session *ahs = ne_calloc(sizeof *ahs);
1134 
1135     ahs->creds = creds;
1136     ahs->userdata = userdata;
1137     ahs->sess = sess;
1138     ahs->spec = ahc;
1139 
1140     if (strcmp(ne_get_scheme(sess), "https") == 0)
1141         ahs->context = isproxy ? AUTH_CONNECT : AUTH_NOTCONNECT;
1142     else
1143         ahs->context = AUTH_ANY;
1144 
1145     /* Register hooks */
1146     ne_hook_create_request(sess, ah_create, ahs);
1147     ne_hook_pre_send(sess, ah_pre_send, ahs);
1148     ne_hook_post_send(sess, ah_post_send, ahs);
1149     ne_hook_destroy_request(sess, ah_destroy, ahs);
1150     ne_hook_destroy_session(sess, free_auth, ahs);
1151 
1152     ne_set_session_private(sess, id, ahs);
1153 }
1154 
ne_set_server_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1155 void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1156 {
1157     auth_register(sess, 0, &ah_server_class, HOOK_SERVER_ID, creds, userdata);
1158 }
1159 
ne_set_proxy_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1160 void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1161 {
1162     auth_register(sess, 1, &ah_proxy_class, HOOK_PROXY_ID, creds, userdata);
1163 }
1164 
ne_forget_auth(ne_session * sess)1165 void ne_forget_auth(ne_session *sess)
1166 {
1167     auth_session *as;
1168     if ((as = ne_get_session_private(sess, HOOK_SERVER_ID)) != NULL)
1169 	clean_session(as);
1170     if ((as = ne_get_session_private(sess, HOOK_PROXY_ID)) != NULL)
1171 	clean_session(as);
1172 }
1173 
1174