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