1 /*
2 HTTP Authentication routines
3 Copyright (C) 1999-2009, 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 #include "config.h"
23
24 #include <sys/types.h>
25
26
27 #ifdef HAVE_STDLIB_H
28 #include <stdlib.h>
29 #endif
30 #ifdef HAVE_STRING_H
31 #include <string.h>
32 #endif
33 #ifdef HAVE_STRINGS_H
34 #include <strings.h>
35 #endif
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h> /* for getpid() */
38 #endif
39
40 #ifdef WIN32
41 #include <windows.h> /* for GetCurrentThreadId() etc */
42 #endif
43
44 #ifdef HAVE_OPENSSL
45 #include <openssl/rand.h>
46 #elif defined(HAVE_GNUTLS)
47 #include <gnutls/gnutls.h>
48 #if LIBGNUTLS_VERSION_NUMBER < 0x020b00
49 #include <gcrypt.h>
50 #else
51 #include <gnutls/crypto.h>
52 #endif
53 #endif
54
55 #include <errno.h>
56 #include <time.h>
57
58 #include "ne_md5.h"
59 #include "ne_dates.h"
60 #include "ne_request.h"
61 #include "ne_auth.h"
62 #include "ne_string.h"
63 #include "ne_utils.h"
64 #include "ne_alloc.h"
65 #include "ne_uri.h"
66 #include "ne_internal.h"
67
68 #ifdef HAVE_GSSAPI
69 #ifdef HAVE_GSSAPI_GSSAPI_H
70 #include <gssapi/gssapi.h>
71 #ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
72 #include <gssapi/gssapi_generic.h>
73 #endif
74 #else
75 #include <gssapi.h>
76 #endif
77 #endif
78
79 #ifdef HAVE_SSPI
80 #include "ne_sspi.h"
81 #endif
82
83 #ifdef HAVE_NTLM
84 #include "ne_ntlm.h"
85 #endif
86
87 #define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth"
88 #define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth"
89
90 typedef enum {
91 auth_alg_md5,
92 auth_alg_md5_sess,
93 auth_alg_unknown
94 } auth_algorithm;
95
96 /* Selected method of qop which the client is using */
97 typedef enum {
98 auth_qop_none,
99 auth_qop_auth
100 } auth_qop;
101
102 /* A callback/userdata pair registered by the application for
103 * a particular set of protocols. */
104 struct auth_handler {
105 unsigned protomask;
106
107 ne_auth_creds creds;
108 void *userdata;
109 int attempt; /* number of invocations of this callback for
110 * current request. */
111
112 struct auth_handler *next;
113 };
114
115 /* A challenge */
116 struct auth_challenge {
117 const struct auth_protocol *protocol;
118 struct auth_handler *handler;
119 const char *realm, *nonce, *opaque, *domain;
120 unsigned int stale; /* if stale=true */
121 unsigned int got_qop; /* we were given a qop directive */
122 unsigned int qop_auth; /* "auth" token in qop attrib */
123 auth_algorithm alg;
124 struct auth_challenge *next;
125 };
126
127 static const struct auth_class {
128 const char *id, *req_hdr, *resp_hdr, *resp_info_hdr;
129 int status_code; /* Response status-code to trap. */
130 int fail_code; /* NE_* request to fail with. */
131 const char *error_noauth; /* Error message template use when
132 * giving up authentication attempts. */
133 } ah_server_class = {
134 HOOK_SERVER_ID,
135 "Authorization", "WWW-Authenticate", "Authentication-Info",
136 401, NE_AUTH,
137 N_("Could not authenticate to server: %s")
138 }, ah_proxy_class = {
139 HOOK_PROXY_ID,
140 "Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info",
141 407, NE_PROXYAUTH,
142 N_("Could not authenticate to proxy server: %s")
143 };
144
145 /* Authentication session state. */
146 typedef struct {
147 ne_session *sess;
148
149 /* Which context will auth challenges be accepted? */
150 enum {
151 AUTH_ANY, /* ignore nothing. */
152 AUTH_CONNECT, /* only in response to a CONNECT request. */
153 AUTH_NOTCONNECT /* only in non-CONNECT responsees */
154 } context;
155
156 /* Protocol type for server/proxy auth. */
157 const struct auth_class *spec;
158
159 /* The protocol used for this authentication session */
160 const struct auth_protocol *protocol;
161
162 struct auth_handler *handlers;
163
164 /*** Session details ***/
165
166 /* The username and password we are using to authenticate with */
167 char username[NE_ABUFSIZ];
168
169 /* This used for Basic auth */
170 char *basic;
171 #ifdef HAVE_GSSAPI
172 /* for the GSSAPI/Negotiate scheme: */
173 char *gssapi_token;
174 gss_ctx_id_t gssctx;
175 gss_name_t gssname;
176 gss_OID gssmech;
177 #endif
178 #ifdef HAVE_SSPI
179 /* This is used for SSPI (Negotiate/NTLM) auth */
180 char *sspi_token;
181 void *sspi_context;
182 #endif
183 #ifdef HAVE_NTLM
184 /* This is used for NTLM auth */
185 ne_ntlm_context *ntlm_context;
186 #endif
187 /* These all used for Digest auth */
188 char *realm;
189 char *nonce;
190 char *cnonce;
191 char *opaque;
192 char **domains; /* list of paths given as domain. */
193 size_t ndomains; /* size of domains array */
194 auth_qop qop;
195 auth_algorithm alg;
196 unsigned int nonce_count;
197 /* The ASCII representation of the session's H(A1) value */
198 char h_a1[33];
199
200 /* Temporary store for half of the Request-Digest
201 * (an optimisation - used in the response-digest calculation) */
202 struct ne_md5_ctx *stored_rdig;
203 } auth_session;
204
205 struct auth_request {
206 /*** Per-request details. ***/
207 ne_request *request; /* the request object. */
208
209 /* The method and URI we are using for the current request */
210 const char *uri;
211 const char *method;
212
213 int attempt; /* number of times this request has been retries due
214 * to auth challenges. */
215 };
216
217 /* Used if this protocol takes an unquoted non-name/value-pair
218 * parameter in the challenge. */
219 #define AUTH_FLAG_OPAQUE_PARAM (0x0001)
220 /* Used if this Authentication-Info may be sent for non-40[17]
221 * response for this protocol. */
222 #define AUTH_FLAG_VERIFY_NON40x (0x0002)
223 /* Used for broken the connection-based auth schemes. */
224 #define AUTH_FLAG_CONN_AUTH (0x0004)
225
226 struct auth_protocol {
227 unsigned id; /* public NE_AUTH_* id. */
228
229 int strength; /* protocol strength for sort order. */
230
231 const char *name; /* protocol name. */
232
233 /* Parse the authentication challenge; returns zero on success, or
234 * non-zero if this challenge be handled. 'attempt' is the number
235 * of times the request has been resent due to auth challenges.
236 * On failure, challenge_error() should be used to append an error
237 * message to the error buffer 'errmsg'. */
238 int (*challenge)(auth_session *sess, int attempt,
239 struct auth_challenge *chall, ne_buffer **errmsg);
240
241 /* Return the string to send in the -Authenticate request header:
242 * (ne_malloc-allocated, NUL-terminated string) */
243 char *(*response)(auth_session *sess, struct auth_request *req);
244
245 /* Parse a Authentication-Info response; returns NE_* error code
246 * on failure; on failure, the session error string must be
247 * set. */
248 int (*verify)(struct auth_request *req, auth_session *sess,
249 const char *value);
250
251 int flags; /* AUTH_FLAG_* flags */
252 };
253
254 /* Helper function to append an error to the buffer during challenge
255 * handling. Pass printf-style string. *errmsg may be NULL and is
256 * allocated if necessary. errmsg must be non-NULL. */
257 static void challenge_error(ne_buffer **errmsg, const char *fmt, ...)
258 ne_attribute((format(printf, 2, 3)));
259
260 /* Free the domains array, precondition sess->ndomains > 0. */
free_domains(auth_session * sess)261 static void free_domains(auth_session *sess)
262 {
263 do {
264 ne_free(sess->domains[sess->ndomains - 1]);
265 } while (--sess->ndomains);
266 ne_free(sess->domains);
267 sess->domains = NULL;
268 }
269
clean_session(auth_session * sess)270 static void clean_session(auth_session *sess)
271 {
272 if (sess->basic) ne_free(sess->basic);
273 if (sess->nonce) ne_free(sess->nonce);
274 if (sess->cnonce) ne_free(sess->cnonce);
275 if (sess->opaque) ne_free(sess->opaque);
276 if (sess->realm) ne_free(sess->realm);
277 sess->realm = sess->basic = sess->cnonce = sess->nonce =
278 sess->opaque = NULL;
279 if (sess->stored_rdig) {
280 ne_md5_destroy_ctx(sess->stored_rdig);
281 sess->stored_rdig = NULL;
282 }
283 if (sess->ndomains) free_domains(sess);
284 #ifdef HAVE_GSSAPI
285 {
286 unsigned int major;
287
288 if (sess->gssctx != GSS_C_NO_CONTEXT)
289 gss_delete_sec_context(&major, &sess->gssctx, GSS_C_NO_BUFFER);
290
291 }
292 if (sess->gssapi_token) ne_free(sess->gssapi_token);
293 sess->gssapi_token = NULL;
294 #endif
295 #ifdef HAVE_SSPI
296 if (sess->sspi_token) ne_free(sess->sspi_token);
297 sess->sspi_token = NULL;
298 ne_sspi_destroy_context(sess->sspi_context);
299 sess->sspi_context = NULL;
300 #endif
301 #ifdef HAVE_NTLM
302 if (sess->ntlm_context) {
303 ne__ntlm_destroy_context(sess->ntlm_context);
304 sess->ntlm_context = NULL;
305 }
306 #endif
307
308 sess->protocol = NULL;
309 }
310
311 /* Returns client nonce string. */
get_cnonce(void)312 static char *get_cnonce(void)
313 {
314 char ret[33];
315 unsigned char data[256];
316 struct ne_md5_ctx *hash;
317
318 hash = ne_md5_create_ctx();
319
320 #ifdef HAVE_GNUTLS
321 if (1) {
322 #if LIBGNUTLS_VERSION_NUMBER < 0x020b00
323 gcry_create_nonce(data, sizeof data);
324 #else
325 gnutls_rnd(GNUTLS_RND_NONCE, data, sizeof data);
326 #endif
327 ne_md5_process_bytes(data, sizeof data, hash);
328 }
329 else
330 #elif defined(HAVE_OPENSSL)
331 if (RAND_status() == 1 && RAND_pseudo_bytes(data, sizeof data) >= 0) {
332 ne_md5_process_bytes(data, sizeof data, hash);
333 }
334 else
335 #endif /* HAVE_OPENSSL */
336 {
337 /* Fallback sources of random data: all bad, but no good sources
338 * are available. */
339
340 /* Uninitialized stack data; yes, happy valgrinders, this is
341 * supposed to be here. */
342 ne_md5_process_bytes(data, sizeof data, hash);
343
344 {
345 #ifdef HAVE_GETTIMEOFDAY
346 struct timeval tv;
347 if (gettimeofday(&tv, NULL) == 0)
348 ne_md5_process_bytes(&tv, sizeof tv, hash);
349 #else /* HAVE_GETTIMEOFDAY */
350 time_t t = time(NULL);
351 ne_md5_process_bytes(&t, sizeof t, hash);
352 #endif
353 }
354 {
355 #ifdef WIN32
356 DWORD pid = GetCurrentThreadId();
357 #else
358 pid_t pid = getpid();
359 #endif
360 ne_md5_process_bytes(&pid, sizeof pid, hash);
361 }
362 }
363
364 ne_md5_finish_ascii(hash, ret);
365 ne_md5_destroy_ctx(hash);
366
367 return ne_strdup(ret);
368 }
369
370 /* Callback to retrieve user credentials for given session on given
371 * attempt (pre request) for given challenge. Password is written to
372 * pwbuf (of size NE_ABUFSIZ. On error, challenge_error() is used
373 * with errmsg. */
get_credentials(auth_session * sess,ne_buffer ** errmsg,int attempt,struct auth_challenge * chall,char * pwbuf)374 static int get_credentials(auth_session *sess, ne_buffer **errmsg, int attempt,
375 struct auth_challenge *chall, char *pwbuf)
376 {
377 if (chall->handler->creds(chall->handler->userdata, sess->realm,
378 chall->handler->attempt++, sess->username, pwbuf) == 0) {
379 return 0;
380 } else {
381 challenge_error(errmsg, _("rejected %s challenge"),
382 chall->protocol->name);
383 return -1;
384 }
385 }
386
387 /* Examine a Basic auth challenge.
388 * Returns 0 if an valid challenge, else non-zero. */
basic_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,ne_buffer ** errmsg)389 static int basic_challenge(auth_session *sess, int attempt,
390 struct auth_challenge *parms,
391 ne_buffer **errmsg)
392 {
393 char *tmp, password[NE_ABUFSIZ];
394
395 /* Verify challenge... must have a realm */
396 if (parms->realm == NULL) {
397 challenge_error(errmsg, _("missing realm in Basic challenge"));
398 return -1;
399 }
400
401 clean_session(sess);
402
403 sess->realm = ne_strdup(parms->realm);
404
405 if (get_credentials(sess, errmsg, attempt, parms, password)) {
406 /* Failed to get credentials */
407 return -1;
408 }
409
410 tmp = ne_concat(sess->username, ":", password, NULL);
411 sess->basic = ne_base64((unsigned char *)tmp, strlen(tmp));
412 ne_free(tmp);
413
414 /* Paranoia. */
415 memset(password, 0, sizeof password);
416
417 return 0;
418 }
419
420 /* Add Basic authentication credentials to a request */
request_basic(auth_session * sess,struct auth_request * req)421 static char *request_basic(auth_session *sess, struct auth_request *req)
422 {
423 return ne_concat("Basic ", sess->basic, "\r\n", NULL);
424 }
425
426 #ifdef HAVE_GSSAPI
427 /* Add GSSAPI authentication credentials to a request */
request_negotiate(auth_session * sess,struct auth_request * req)428 static char *request_negotiate(auth_session *sess, struct auth_request *req)
429 {
430 if (sess->gssapi_token)
431 return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL);
432 else
433 return NULL;
434 }
435
436 /* Create an GSSAPI name for server HOSTNAME; returns non-zero on
437 * error. */
get_gss_name(gss_name_t * server,const char * hostname)438 static void get_gss_name(gss_name_t *server, const char *hostname)
439 {
440 unsigned int major, minor;
441 gss_buffer_desc token;
442
443 token.value = ne_concat("HTTP@", hostname, NULL);
444 token.length = strlen(token.value);
445
446 major = gss_import_name(&minor, &token, GSS_C_NT_HOSTBASED_SERVICE,
447 server);
448 ne_free(token.value);
449
450 if (GSS_ERROR(major)) {
451 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: gss_import_name failed.");
452 *server = GSS_C_NO_NAME;
453 }
454 }
455
456 /* Append GSSAPI error(s) for STATUS of type TYPE to BUF; prepending
457 * ": " to each error if *FLAG is non-zero, setting *FLAG after an
458 * error has been appended. */
make_gss_error(ne_buffer * buf,int * flag,unsigned int status,int type)459 static void make_gss_error(ne_buffer *buf, int *flag,
460 unsigned int status, int type)
461 {
462 unsigned int major, minor;
463 unsigned int context = 0;
464
465 do {
466 gss_buffer_desc msg;
467 major = gss_display_status(&minor, status, type,
468 GSS_C_NO_OID, &context, &msg);
469 if (major == GSS_S_COMPLETE && msg.length) {
470 if ((*flag)++) ne_buffer_append(buf, ": ", 2);
471 ne_buffer_append(buf, msg.value, msg.length);
472 }
473 if (msg.length) gss_release_buffer(&minor, &msg);
474 } while (context);
475 }
476
477 /* Continue a GSS-API Negotiate exchange, using input TOKEN if
478 * non-NULL. Returns non-zero on error, in which case *errmsg is
479 * guaranteed to be non-NULL (i.e. an error message is set). */
continue_negotiate(auth_session * sess,const char * token,ne_buffer ** errmsg)480 static int continue_negotiate(auth_session *sess, const char *token,
481 ne_buffer **errmsg)
482 {
483 unsigned int major, minor;
484 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
485 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
486 unsigned char *bintoken = NULL;
487 int ret;
488
489 if (token) {
490 input.length = ne_unbase64(token, &bintoken);
491 if (input.length == 0) {
492 challenge_error(errmsg, _("invalid Negotiate token"));
493 return -1;
494 }
495 input.value = bintoken;
496 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Continuation token [%s]", token);
497 }
498 else if (sess->gssctx != GSS_C_NO_CONTEXT) {
499 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Reset incomplete context.");
500 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
501 }
502
503 major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &sess->gssctx,
504 sess->gssname, sess->gssmech,
505 GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE,
506 GSS_C_NO_CHANNEL_BINDINGS,
507 &input, &sess->gssmech, &output, NULL, NULL);
508
509 /* done with the input token. */
510 if (bintoken) ne_free(bintoken);
511
512 if (GSS_ERROR(major)) {
513 int flag = 0;
514
515 challenge_error(errmsg, _("GSSAPI authentication error: "));
516 make_gss_error(*errmsg, &flag, major, GSS_C_GSS_CODE);
517 make_gss_error(*errmsg, &flag, minor, GSS_C_MECH_CODE);
518
519 return -1;
520 }
521
522 if (major == GSS_S_CONTINUE_NEEDED || major == GSS_S_COMPLETE) {
523 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: init_sec_context OK. (major=%d)",
524 major);
525 ret = 0;
526 }
527 else {
528 challenge_error(errmsg, _("GSSAPI failure (code %u)"), major);
529 ret = -1;
530 }
531
532 if (major != GSS_S_CONTINUE_NEEDED) {
533 /* context no longer needed: destroy it */
534 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
535 }
536
537 if (output.length) {
538 sess->gssapi_token = ne_base64(output.value, output.length);
539 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Output token: [%s]",
540 sess->gssapi_token);
541 gss_release_buffer(&minor, &output);
542 } else {
543 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No output token.");
544 }
545
546 return ret;
547 }
548
549 /* Process a Negotiate challange CHALL in session SESS; returns zero
550 * if challenge is accepted. */
negotiate_challenge(auth_session * sess,int attempt,struct auth_challenge * chall,ne_buffer ** errmsg)551 static int negotiate_challenge(auth_session *sess, int attempt,
552 struct auth_challenge *chall,
553 ne_buffer **errmsg)
554 {
555 const char *token = chall->opaque;
556
557 /* Respect an initial challenge - which must have no input token,
558 * or a continuation - which must have an input token. */
559 if (attempt == 0 || token) {
560 return continue_negotiate(sess, token, errmsg);
561 }
562 else {
563 challenge_error(errmsg, _("ignoring empty Negotiate continuation"));
564 return -1;
565 }
566 }
567
568 /* Verify the header HDR in a Negotiate response. */
verify_negotiate_response(struct auth_request * req,auth_session * sess,const char * hdr)569 static int verify_negotiate_response(struct auth_request *req, auth_session *sess,
570 const char *hdr)
571 {
572 char *duphdr = ne_strdup(hdr);
573 char *sep, *ptr = strchr(duphdr, ' ');
574 int ret;
575 ne_buffer *errmsg = NULL;
576
577 if (!ptr || strncmp(hdr, "Negotiate", ptr - duphdr) != 0) {
578 ne_set_error(sess->sess, _("Negotiate response verification failed: "
579 "invalid response header token"));
580 ne_free(duphdr);
581 return NE_ERROR;
582 }
583
584 ptr++;
585
586 if (strlen(ptr) == 0) {
587 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No token in Negotiate response!");
588 ne_free(duphdr);
589 return NE_OK;
590 }
591
592 if ((sep = strchr(ptr, ',')) != NULL)
593 *sep = '\0';
594 if ((sep = strchr(ptr, ' ')) != NULL)
595 *sep = '\0';
596
597 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Negotiate response token [%s]", ptr);
598 ret = continue_negotiate(sess, ptr, &errmsg);
599 if (ret) {
600 ne_set_error(sess->sess, _("Negotiate response verification failure: %s"),
601 errmsg->data);
602 }
603
604 if (errmsg) ne_buffer_destroy(errmsg);
605 ne_free(duphdr);
606
607 return ret ? NE_ERROR : NE_OK;
608 }
609 #endif
610
611 #ifdef HAVE_SSPI
request_sspi(auth_session * sess,struct auth_request * request)612 static char *request_sspi(auth_session *sess, struct auth_request *request)
613 {
614 if (sess->sspi_token)
615 return ne_concat(sess->protocol->name, " ", sess->sspi_token, "\r\n", NULL);
616 else
617 return NULL;
618 }
619
continue_sspi(auth_session * sess,int ntlm,const char * hdr)620 static int continue_sspi(auth_session *sess, int ntlm, const char *hdr)
621 {
622 int status;
623 char *response = NULL;
624
625 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge.");
626
627 if (!sess->sspi_context) {
628 ne_uri uri = {0};
629
630 ne_fill_server_uri(sess->sess, &uri);
631
632 status = ne_sspi_create_context(&sess->sspi_context, uri.host, ntlm);
633
634 ne_uri_free(&uri);
635
636 if (status) {
637 return status;
638 }
639 }
640
641 status = ne_sspi_authenticate(sess->sspi_context, hdr, &response);
642 if (status) {
643 return status;
644 }
645
646 if (response && *response) {
647 sess->sspi_token = response;
648
649 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge [%s]", sess->sspi_token);
650 }
651
652 return 0;
653 }
654
sspi_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,ne_buffer ** errmsg)655 static int sspi_challenge(auth_session *sess, int attempt,
656 struct auth_challenge *parms,
657 ne_buffer **errmsg)
658 {
659 int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0;
660
661 return continue_sspi(sess, ntlm, parms->opaque);
662 }
663
verify_sspi(struct auth_request * req,auth_session * sess,const char * hdr)664 static int verify_sspi(struct auth_request *req, auth_session *sess,
665 const char *hdr)
666 {
667 int ntlm = ne_strncasecmp(hdr, "NTLM ", 5) == 0;
668 char *ptr = strchr(hdr, ' ');
669
670 if (!ptr) {
671 ne_set_error(sess->sess, _("SSPI response verification failed: "
672 "invalid response header token"));
673 return NE_ERROR;
674 }
675
676 while(*ptr == ' ')
677 ptr++;
678
679 if (*ptr == '\0') {
680 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No token in SSPI response!");
681 return NE_OK;
682 }
683
684 return continue_sspi(sess, ntlm, ptr);
685 }
686
687 #endif
688
689 /* Parse the "domain" challenge parameter and set the domains array up
690 * in the session appropriately. */
parse_domain(auth_session * sess,const char * domain)691 static int parse_domain(auth_session *sess, const char *domain)
692 {
693 char *cp = ne_strdup(domain), *p = cp;
694 ne_uri base;
695 int invalid = 0;
696
697 memset(&base, 0, sizeof base);
698 ne_fill_server_uri(sess->sess, &base);
699
700 do {
701 char *token = ne_token(&p, ' ');
702 ne_uri rel, absolute;
703
704 if (ne_uri_parse(token, &rel) == 0) {
705 /* Resolve relative to the Request-URI. */
706 base.path = "/";
707 ne_uri_resolve(&base, &rel, &absolute);
708
709 /* Compare against the resolved path to check this URI has
710 * the same (scheme, host, port) components; ignore it
711 * otherwise: */
712 base.path = absolute.path;
713 if (absolute.path && ne_uri_cmp(&absolute, &base) == 0) {
714 sess->domains = ne_realloc(sess->domains,
715 ++sess->ndomains *
716 sizeof(*sess->domains));
717 sess->domains[sess->ndomains - 1] = absolute.path;
718 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using domain %s from %s",
719 absolute.path, token);
720 absolute.path = NULL;
721 }
722 else {
723 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignoring domain %s",
724 token);
725 }
726
727 ne_uri_free(&absolute);
728 }
729 else {
730 invalid = 1;
731 }
732
733 ne_uri_free(&rel);
734
735 } while (p && !invalid);
736
737 if (invalid && sess->ndomains) {
738 free_domains(sess);
739 }
740
741 ne_free(cp);
742 base.path = NULL;
743 ne_uri_free(&base);
744
745 return invalid;
746 }
747
748 #ifdef HAVE_NTLM
749
request_ntlm(auth_session * sess,struct auth_request * request)750 static char *request_ntlm(auth_session *sess, struct auth_request *request)
751 {
752 char *token = ne__ntlm_getRequestToken(sess->ntlm_context);
753 if (token) {
754 char *req = ne_concat(sess->protocol->name, " ", token, "\r\n", NULL);
755 ne_free(token);
756 return req;
757 } else {
758 return NULL;
759 }
760 }
761
ntlm_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,ne_buffer ** errmsg)762 static int ntlm_challenge(auth_session *sess, int attempt,
763 struct auth_challenge *parms,
764 ne_buffer **errmsg)
765 {
766 int status;
767
768 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: NTLM challenge.");
769
770 if (!parms->opaque && (!sess->ntlm_context || (attempt > 1))) {
771 char password[NE_ABUFSIZ];
772
773 if (get_credentials(sess, errmsg, attempt, parms, password)) {
774 /* Failed to get credentials */
775 return -1;
776 }
777
778 if (sess->ntlm_context) {
779 ne__ntlm_destroy_context(sess->ntlm_context);
780 sess->ntlm_context = NULL;
781 }
782
783 sess->ntlm_context = ne__ntlm_create_context(sess->username, password);
784 }
785
786 status = ne__ntlm_authenticate(sess->ntlm_context, parms->opaque);
787 if (status) {
788 return status;
789 }
790
791 return 0;
792 }
793 #endif /* HAVE_NTLM */
794
795 /* Examine a digest challenge: return 0 if it is a valid Digest challenge,
796 * else non-zero. */
digest_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,ne_buffer ** errmsg)797 static int digest_challenge(auth_session *sess, int attempt,
798 struct auth_challenge *parms,
799 ne_buffer **errmsg)
800 {
801 char password[NE_ABUFSIZ];
802
803 if (parms->alg == auth_alg_unknown) {
804 challenge_error(errmsg, _("unknown algorithm in Digest challenge"));
805 return -1;
806 }
807 else if (parms->alg == auth_alg_md5_sess && !parms->qop_auth) {
808 challenge_error(errmsg, _("incompatible algorithm in Digest challenge"));
809 return -1;
810 }
811 else if (parms->realm == NULL || parms->nonce == NULL) {
812 challenge_error(errmsg, _("missing parameter in Digest challenge"));
813 return -1;
814 }
815 else if (parms->stale && sess->realm == NULL) {
816 challenge_error(errmsg, _("initial Digest challenge was stale"));
817 return -1;
818 }
819 else if (parms->stale && (sess->alg != parms->alg
820 || strcmp(sess->realm, parms->realm))) {
821 /* With stale=true the realm and algorithm cannot change since these
822 * require re-hashing H(A1) which defeats the point. */
823 challenge_error(errmsg, _("stale Digest challenge with new algorithm or realm"));
824 return -1;
825 }
826
827 if (!parms->stale) {
828 /* Non-stale challenge: clear session and request credentials. */
829 clean_session(sess);
830
831 /* The domain paramater must be parsed after the session is
832 * cleaned; ignore domain for proxy auth. */
833 if (parms->domain && sess->spec == &ah_server_class
834 && parse_domain(sess, parms->domain)) {
835 challenge_error(errmsg, _("could not parse domain in Digest challenge"));
836 return -1;
837 }
838
839 sess->realm = ne_strdup(parms->realm);
840 sess->alg = parms->alg;
841 sess->cnonce = get_cnonce();
842
843 if (get_credentials(sess, errmsg, attempt, parms, password)) {
844 /* Failed to get credentials */
845 return -1;
846 }
847 }
848 else {
849 /* Stale challenge: accept a new nonce or opaque. */
850 if (sess->nonce) ne_free(sess->nonce);
851 if (sess->opaque && parms->opaque) ne_free(sess->opaque);
852 }
853
854 sess->nonce = ne_strdup(parms->nonce);
855 if (parms->opaque) {
856 sess->opaque = ne_strdup(parms->opaque);
857 }
858
859 if (parms->got_qop) {
860 /* What type of qop are we to apply to the message? */
861 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got qop, using 2617-style.");
862 sess->nonce_count = 0;
863 sess->qop = auth_qop_auth;
864 } else {
865 /* No qop at all/ */
866 sess->qop = auth_qop_none;
867 }
868
869 if (!parms->stale) {
870 struct ne_md5_ctx *tmp;
871
872 /* Calculate H(A1).
873 * tmp = H(unq(username-value) ":" unq(realm-value) ":" passwd)
874 */
875 tmp = ne_md5_create_ctx();
876 ne_md5_process_bytes(sess->username, strlen(sess->username), tmp);
877 ne_md5_process_bytes(":", 1, tmp);
878 ne_md5_process_bytes(sess->realm, strlen(sess->realm), tmp);
879 ne_md5_process_bytes(":", 1, tmp);
880 ne_md5_process_bytes(password, strlen(password), tmp);
881 memset(password, 0, sizeof password); /* done with that. */
882 if (sess->alg == auth_alg_md5_sess) {
883 struct ne_md5_ctx *a1;
884 char tmp_md5_ascii[33];
885
886 /* Now we calculate the SESSION H(A1)
887 * A1 = H(...above...) ":" unq(nonce-value) ":" unq(cnonce-value)
888 */
889 ne_md5_finish_ascii(tmp, tmp_md5_ascii);
890 a1 = ne_md5_create_ctx();
891 ne_md5_process_bytes(tmp_md5_ascii, 32, a1);
892 ne_md5_process_bytes(":", 1, a1);
893 ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), a1);
894 ne_md5_process_bytes(":", 1, a1);
895 ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), a1);
896 ne_md5_finish_ascii(a1, sess->h_a1);
897 ne_md5_destroy_ctx(a1);
898 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Session H(A1) is [%s]", sess->h_a1);
899 } else {
900 ne_md5_finish_ascii(tmp, sess->h_a1);
901 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A1) is [%s]", sess->h_a1);
902 }
903 ne_md5_destroy_ctx(tmp);
904
905 }
906
907 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepting digest challenge.");
908
909 return 0;
910 }
911
912 /* Returns non-zero if given Request-URI is inside the authentication
913 * domain defined for the session. */
inside_domain(auth_session * sess,const char * req_uri)914 static int inside_domain(auth_session *sess, const char *req_uri)
915 {
916 int inside = 0;
917 size_t n;
918 ne_uri uri;
919
920 /* Parse the Request-URI; it will be an absoluteURI if using a
921 * proxy, and possibly '*'. */
922 if (strcmp(req_uri, "*") == 0 || ne_uri_parse(req_uri, &uri) != 0) {
923 /* Presume outside the authentication domain. */
924 return 0;
925 }
926
927 for (n = 0; n < sess->ndomains && !inside; n++) {
928 const char *d = sess->domains[n];
929
930 inside = strncmp(uri.path, d, strlen(d)) == 0;
931 }
932
933 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: '%s' is inside auth domain: %d.",
934 uri.path, inside);
935 ne_uri_free(&uri);
936
937 return inside;
938 }
939
940 /* Return Digest authentication credentials header value for the given
941 * session. */
request_digest(auth_session * sess,struct auth_request * req)942 static char *request_digest(auth_session *sess, struct auth_request *req)
943 {
944 struct ne_md5_ctx *a2, *rdig;
945 char a2_md5_ascii[33], rdig_md5_ascii[33];
946 char nc_value[9] = {0};
947 const char *qop_value = "auth"; /* qop-value */
948 ne_buffer *ret;
949
950 /* Do not submit credentials if an auth domain is defined and this
951 * request-uri fails outside it. */
952 if (sess->ndomains && !inside_domain(sess, req->uri)) {
953 return NULL;
954 }
955
956 /* Increase the nonce-count */
957 if (sess->qop != auth_qop_none) {
958 sess->nonce_count++;
959 ne_snprintf(nc_value, 9, "%08x", sess->nonce_count);
960 }
961
962 /* Calculate H(A2). */
963 a2 = ne_md5_create_ctx();
964 ne_md5_process_bytes(req->method, strlen(req->method), a2);
965 ne_md5_process_bytes(":", 1, a2);
966 ne_md5_process_bytes(req->uri, strlen(req->uri), a2);
967 ne_md5_finish_ascii(a2, a2_md5_ascii);
968 ne_md5_destroy_ctx(a2);
969 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A2): %s", a2_md5_ascii);
970
971 /* Now, calculation of the Request-Digest.
972 * The first section is the regardless of qop value
973 * H(A1) ":" unq(nonce-value) ":" */
974 rdig = ne_md5_create_ctx();
975
976 /* Use the calculated H(A1) */
977 ne_md5_process_bytes(sess->h_a1, 32, rdig);
978
979 ne_md5_process_bytes(":", 1, rdig);
980 ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), rdig);
981 ne_md5_process_bytes(":", 1, rdig);
982 if (sess->qop != auth_qop_none) {
983 /* Add on:
984 * nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":"
985 */
986 ne_md5_process_bytes(nc_value, 8, rdig);
987 ne_md5_process_bytes(":", 1, rdig);
988 ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), rdig);
989 ne_md5_process_bytes(":", 1, rdig);
990 /* Store a copy of this structure (see note below) */
991 if (sess->stored_rdig) ne_md5_destroy_ctx(sess->stored_rdig);
992 sess->stored_rdig = ne_md5_dup_ctx(rdig);
993 ne_md5_process_bytes(qop_value, strlen(qop_value), rdig);
994 ne_md5_process_bytes(":", 1, rdig);
995 }
996
997 /* And finally, H(A2) */
998 ne_md5_process_bytes(a2_md5_ascii, 32, rdig);
999 ne_md5_finish_ascii(rdig, rdig_md5_ascii);
1000 ne_md5_destroy_ctx(rdig);
1001
1002 ret = ne_buffer_create();
1003
1004 ne_buffer_concat(ret,
1005 "Digest username=\"", sess->username, "\", "
1006 "realm=\"", sess->realm, "\", "
1007 "nonce=\"", sess->nonce, "\", "
1008 "uri=\"", req->uri, "\", "
1009 "response=\"", rdig_md5_ascii, "\", "
1010 "algorithm=\"", sess->alg == auth_alg_md5 ? "MD5" : "MD5-sess", "\"",
1011 NULL);
1012
1013 if (sess->opaque != NULL) {
1014 ne_buffer_concat(ret, ", opaque=\"", sess->opaque, "\"", NULL);
1015 }
1016
1017 if (sess->qop != auth_qop_none) {
1018 /* Add in cnonce and nc-value fields */
1019 ne_buffer_concat(ret, ", cnonce=\"", sess->cnonce, "\", "
1020 "nc=", nc_value, ", "
1021 "qop=\"", qop_value, "\"", NULL);
1022 }
1023
1024 ne_buffer_zappend(ret, "\r\n");
1025
1026 return ne_buffer_finish(ret);
1027 }
1028
1029 /* Parse line of comma-separated key-value pairs. If 'ischall' == 1,
1030 * then also return a leading space-separated token, as *value ==
1031 * NULL. Otherwise, if return value is 0, *key and *value will be
1032 * non-NULL. If return value is non-zero, parsing has ended. If
1033 * 'sep' is non-NULL and ischall is 1, the separator character is
1034 * written to *sep when a challenge is parsed. */
tokenize(char ** hdr,char ** key,char ** value,char * sep,int ischall)1035 static int tokenize(char **hdr, char **key, char **value, char *sep,
1036 int ischall)
1037 {
1038 char *pnt = *hdr;
1039 enum { BEFORE_EQ, AFTER_EQ, AFTER_EQ_QUOTED } state = BEFORE_EQ;
1040
1041 if (**hdr == '\0')
1042 return 1;
1043
1044 *key = NULL;
1045
1046 do {
1047 switch (state) {
1048 case BEFORE_EQ:
1049 if (*pnt == '=') {
1050 if (*key == NULL)
1051 return -1;
1052 *pnt = '\0';
1053 *value = pnt + 1;
1054 state = AFTER_EQ;
1055 } else if ((*pnt == ' ' || *pnt == ',')
1056 && ischall && *key != NULL) {
1057 *value = NULL;
1058 if (sep) *sep = *pnt;
1059 *pnt = '\0';
1060 *hdr = pnt + 1;
1061 return 0;
1062 } else if (*key == NULL && strchr(" \r\n\t", *pnt) == NULL) {
1063 *key = pnt;
1064 }
1065 break;
1066 case AFTER_EQ:
1067 if (*pnt == ',') {
1068 *pnt = '\0';
1069 *hdr = pnt + 1;
1070 return 0;
1071 } else if (*pnt == '\"') {
1072 state = AFTER_EQ_QUOTED;
1073 }
1074 break;
1075 case AFTER_EQ_QUOTED:
1076 if (*pnt == '\"') {
1077 state = AFTER_EQ;
1078 *pnt = '\0';
1079 }
1080 break;
1081 }
1082 } while (*++pnt != '\0');
1083
1084 if (state == BEFORE_EQ && ischall && *key != NULL) {
1085 *value = NULL;
1086 if (sep) *sep = '\0';
1087 }
1088
1089 *hdr = pnt;
1090
1091 /* End of string: */
1092 return 0;
1093 }
1094
1095 /* Pass this the value of the 'Authentication-Info:' header field, if
1096 * one is received.
1097 * Returns:
1098 * 0 if it gives a valid authentication for the server
1099 * non-zero otherwise (don't believe the response in this case!).
1100 */
verify_digest_response(struct auth_request * req,auth_session * sess,const char * value)1101 static int verify_digest_response(struct auth_request *req, auth_session *sess,
1102 const char *value)
1103 {
1104 char *hdr, *pnt, *key, *val;
1105 auth_qop qop = auth_qop_none;
1106 char *nextnonce, *rspauth, *cnonce, *nc, *qop_value;
1107 unsigned int nonce_count;
1108 int ret = NE_OK;
1109
1110 nextnonce = rspauth = cnonce = nc = qop_value = NULL;
1111
1112 pnt = hdr = ne_strdup(value);
1113
1114 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got Auth-Info header: %s", value);
1115
1116 while (tokenize(&pnt, &key, &val, NULL, 0) == 0) {
1117 val = ne_shave(val, "\"");
1118
1119 if (ne_strcasecmp(key, "qop") == 0) {
1120 qop_value = val;
1121 if (ne_strcasecmp(val, "auth") == 0) {
1122 qop = auth_qop_auth;
1123 } else {
1124 qop = auth_qop_none;
1125 }
1126 } else if (ne_strcasecmp(key, "nextnonce") == 0) {
1127 nextnonce = val;
1128 } else if (ne_strcasecmp(key, "rspauth") == 0) {
1129 rspauth = val;
1130 } else if (ne_strcasecmp(key, "cnonce") == 0) {
1131 cnonce = val;
1132 } else if (ne_strcasecmp(key, "nc") == 0) {
1133 nc = val;
1134 }
1135 }
1136
1137 if (qop == auth_qop_none) {
1138 /* The 2069-style A-I header only has the entity and nextnonce
1139 * parameters. */
1140 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: 2069-style A-I header.");
1141 }
1142 else if (!rspauth || !cnonce || !nc) {
1143 ret = NE_ERROR;
1144 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1145 "missing parameters"));
1146 }
1147 else if (strcmp(cnonce, sess->cnonce) != 0) {
1148 ret = NE_ERROR;
1149 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1150 "client nonce mismatch"));
1151 }
1152 else if (nc) {
1153 char *ptr;
1154
1155 errno = 0;
1156 nonce_count = strtoul(nc, &ptr, 16);
1157 if (*ptr != '\0' || errno) {
1158 ret = NE_ERROR;
1159 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1160 "could not parse nonce count"));
1161 }
1162 else if (nonce_count != sess->nonce_count) {
1163 ret = NE_ERROR;
1164 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1165 "nonce count mismatch (%u not %u)"),
1166 nonce_count, sess->nonce_count);
1167 }
1168 }
1169
1170 /* Finally, for qop=auth cases, if everything else is OK, verify
1171 * the response-digest field. */
1172 if (qop == auth_qop_auth && ret == NE_OK) {
1173 struct ne_md5_ctx *a2;
1174 char a2_md5_ascii[33], rdig_md5_ascii[33];
1175
1176 /* Modified H(A2): */
1177 a2 = ne_md5_create_ctx();
1178 ne_md5_process_bytes(":", 1, a2);
1179 ne_md5_process_bytes(req->uri, strlen(req->uri), a2);
1180 ne_md5_finish_ascii(a2, a2_md5_ascii);
1181 ne_md5_destroy_ctx(a2);
1182
1183 /* sess->stored_rdig contains digest-so-far of:
1184 * H(A1) ":" unq(nonce-value)
1185 */
1186
1187 /* Add in qop-value */
1188 ne_md5_process_bytes(qop_value, strlen(qop_value),
1189 sess->stored_rdig);
1190 ne_md5_process_bytes(":", 1, sess->stored_rdig);
1191
1192 /* Digest ":" H(A2) */
1193 ne_md5_process_bytes(a2_md5_ascii, 32, sess->stored_rdig);
1194 /* All done */
1195 ne_md5_finish_ascii(sess->stored_rdig, rdig_md5_ascii);
1196 ne_md5_destroy_ctx(sess->stored_rdig);
1197 sess->stored_rdig = NULL;
1198
1199 /* And... do they match? */
1200 ret = ne_strcasecmp(rdig_md5_ascii, rspauth) == 0 ? NE_OK : NE_ERROR;
1201
1202 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: response-digest match: %s "
1203 "(expected [%s] vs actual [%s])\n",
1204 ret == NE_OK ? "yes" : "no", rdig_md5_ascii, rspauth);
1205
1206 if (ret) {
1207 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1208 "request-digest mismatch"));
1209 }
1210 }
1211
1212 /* Check for a nextnonce */
1213 if (nextnonce != NULL) {
1214 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Found nextnonce of [%s].", nextnonce);
1215 ne_free(sess->nonce);
1216 sess->nonce = ne_strdup(nextnonce);
1217 sess->nonce_count = 0;
1218 }
1219
1220 ne_free(hdr);
1221
1222 return ret;
1223 }
1224
1225 static const struct auth_protocol protocols[] = {
1226 { NE_AUTH_BASIC, 10, "Basic",
1227 basic_challenge, request_basic, NULL,
1228 0 },
1229 { NE_AUTH_DIGEST, 20, "Digest",
1230 digest_challenge, request_digest, verify_digest_response,
1231 0 },
1232 #ifdef HAVE_GSSAPI
1233 { NE_AUTH_GSSAPI, 30, "Negotiate",
1234 negotiate_challenge, request_negotiate, verify_negotiate_response,
1235 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1236 #endif
1237 #ifdef HAVE_SSPI
1238 { NE_AUTH_NTLM, 30, "NTLM",
1239 sspi_challenge, request_sspi, NULL,
1240 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1241 { NE_AUTH_GSSAPI, 30, "Negotiate",
1242 sspi_challenge, request_sspi, verify_sspi,
1243 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1244 #endif
1245 #ifdef HAVE_NTLM
1246 { NE_AUTH_NTLM, 30, "NTLM",
1247 ntlm_challenge, request_ntlm, NULL,
1248 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1249 #endif
1250 { 0 }
1251 };
1252
1253 /* Insert a new auth challenge for protocol 'proto' in list of
1254 * challenges 'list'. The challenge list is kept in sorted order of
1255 * strength, with highest strength first. */
insert_challenge(struct auth_challenge ** list,const struct auth_protocol * proto)1256 static struct auth_challenge *insert_challenge(struct auth_challenge **list,
1257 const struct auth_protocol *proto)
1258 {
1259 struct auth_challenge *ret = ne_calloc(sizeof *ret);
1260 struct auth_challenge *chall, *prev;
1261
1262 for (chall = *list, prev = NULL; chall != NULL;
1263 prev = chall, chall = chall->next) {
1264 if (proto->strength > chall->protocol->strength) {
1265 break;
1266 }
1267 }
1268
1269 if (prev) {
1270 ret->next = prev->next;
1271 prev->next = ret;
1272 } else {
1273 ret->next = *list;
1274 *list = ret;
1275 }
1276
1277 ret->protocol = proto;
1278
1279 return ret;
1280 }
1281
challenge_error(ne_buffer ** errbuf,const char * fmt,...)1282 static void challenge_error(ne_buffer **errbuf, const char *fmt, ...)
1283 {
1284 char err[128];
1285 va_list ap;
1286 size_t len;
1287
1288 va_start(ap, fmt);
1289 len = ne_vsnprintf(err, sizeof err, fmt, ap);
1290 va_end(ap);
1291
1292 if (*errbuf == NULL) {
1293 *errbuf = ne_buffer_create();
1294 ne_buffer_append(*errbuf, err, len);
1295 }
1296 else {
1297 ne_buffer_concat(*errbuf, ", ", err, NULL);
1298 }
1299 }
1300
1301 /* Passed the value of a "(Proxy,WWW)-Authenticate: " header field.
1302 * Returns 0 if valid challenge was accepted; non-zero if no valid
1303 * challenge was found. */
auth_challenge(auth_session * sess,int attempt,const char * value)1304 static int auth_challenge(auth_session *sess, int attempt,
1305 const char *value)
1306 {
1307 char *pnt, *key, *val, *hdr, sep;
1308 struct auth_challenge *chall = NULL, *challenges = NULL;
1309 ne_buffer *errmsg = NULL;
1310
1311 pnt = hdr = ne_strdup(value);
1312
1313 /* The header value may be made up of one or more challenges. We
1314 * split it down into attribute-value pairs, then search for
1315 * schemes in the pair keys. */
1316
1317 while (!tokenize(&pnt, &key, &val, &sep, 1)) {
1318
1319 if (val == NULL) {
1320 const struct auth_protocol *proto = NULL;
1321 struct auth_handler *hdl;
1322 size_t n;
1323
1324 for (hdl = sess->handlers; hdl; hdl = hdl->next) {
1325 for (n = 0; protocols[n].id; n++) {
1326 if (protocols[n].id & hdl->protomask
1327 && ne_strcasecmp(key, protocols[n].name) == 0) {
1328 proto = &protocols[n];
1329 break;
1330 }
1331 }
1332 if (proto) break;
1333 }
1334
1335 if (proto == NULL) {
1336 /* Ignore this challenge. */
1337 chall = NULL;
1338 challenge_error(&errmsg, _("ignored %s challenge"), key);
1339 continue;
1340 }
1341
1342 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got '%s' challenge.", proto->name);
1343 chall = insert_challenge(&challenges, proto);
1344 chall->handler = hdl;
1345
1346 if ((proto->flags & AUTH_FLAG_OPAQUE_PARAM) && sep == ' ') {
1347 /* Cope with the fact that the unquoted base64
1348 * paramater token doesn't match the 2617 auth-param
1349 * grammar: */
1350 chall->opaque = ne_shave(ne_token(&pnt, ','), " \t");
1351 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: %s opaque parameter '%s'",
1352 proto->name, chall->opaque);
1353 if (!pnt) break; /* stop parsing at end-of-string. */
1354 }
1355 continue;
1356 } else if (chall == NULL) {
1357 /* Ignore pairs for an unknown challenge. */
1358 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignored parameter: %s = %s", key, val);
1359 continue;
1360 }
1361
1362 /* Strip quotes off value. */
1363 val = ne_shave(val, "\"'");
1364
1365 if (ne_strcasecmp(key, "realm") == 0) {
1366 chall->realm = val;
1367 } else if (ne_strcasecmp(key, "nonce") == 0) {
1368 chall->nonce = val;
1369 } else if (ne_strcasecmp(key, "opaque") == 0) {
1370 chall->opaque = val;
1371 } else if (ne_strcasecmp(key, "stale") == 0) {
1372 /* Truth value */
1373 chall->stale = (ne_strcasecmp(val, "true") == 0);
1374 } else if (ne_strcasecmp(key, "algorithm") == 0) {
1375 if (ne_strcasecmp(val, "md5") == 0) {
1376 chall->alg = auth_alg_md5;
1377 } else if (ne_strcasecmp(val, "md5-sess") == 0) {
1378 chall->alg = auth_alg_md5_sess;
1379 } else {
1380 chall->alg = auth_alg_unknown;
1381 }
1382 } else if (ne_strcasecmp(key, "qop") == 0) {
1383 /* iterate over each token in the value */
1384 do {
1385 const char *tok = ne_shave(ne_token(&val, ','), " \t");
1386
1387 if (ne_strcasecmp(tok, "auth") == 0) {
1388 chall->qop_auth = 1;
1389 }
1390 } while (val);
1391
1392 chall->got_qop = chall->qop_auth;
1393 }
1394 else if (ne_strcasecmp(key, "domain") == 0) {
1395 chall->domain = val;
1396 }
1397 }
1398
1399 sess->protocol = NULL;
1400
1401 /* Iterate through the challenge list (which is sorted from
1402 * strongest to weakest) attempting to accept each one. */
1403 for (chall = challenges; chall != NULL; chall = chall->next) {
1404 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Trying %s challenge...",
1405 chall->protocol->name);
1406 if (chall->protocol->challenge(sess, attempt, chall, &errmsg) == 0) {
1407 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepted %s challenge.",
1408 chall->protocol->name);
1409 sess->protocol = chall->protocol;
1410 break;
1411 }
1412 }
1413
1414 if (!sess->protocol) {
1415 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No challenges accepted.");
1416 ne_set_error(sess->sess, _(sess->spec->error_noauth),
1417 errmsg ? errmsg->data : _("could not parse challenge"));
1418 }
1419
1420 while (challenges != NULL) {
1421 chall = challenges->next;
1422 ne_free(challenges);
1423 challenges = chall;
1424 }
1425
1426 ne_free(hdr);
1427 if (errmsg) ne_buffer_destroy(errmsg);
1428
1429 return !(sess->protocol != NULL);
1430 }
1431
ah_create(ne_request * req,void * session,const char * method,const char * uri)1432 static void ah_create(ne_request *req, void *session, const char *method,
1433 const char *uri)
1434 {
1435 auth_session *sess = session;
1436 int is_connect = strcmp(method, "CONNECT") == 0;
1437
1438 if (sess->context == AUTH_ANY ||
1439 (is_connect && sess->context == AUTH_CONNECT) ||
1440 (!is_connect && sess->context == AUTH_NOTCONNECT)) {
1441 struct auth_request *areq = ne_calloc(sizeof *areq);
1442 struct auth_handler *hdl;
1443
1444 NE_DEBUG(NE_DBG_HTTPAUTH, "ah_create, for %s", sess->spec->resp_hdr);
1445
1446 areq->method = method;
1447 areq->uri = uri;
1448 areq->request = req;
1449
1450 ne_set_request_private(req, sess->spec->id, areq);
1451
1452 /* For each new request, reset the attempt counter in every
1453 * registered handler. */
1454 for (hdl = sess->handlers; hdl; hdl = hdl->next) {
1455 hdl->attempt = 0;
1456 }
1457 }
1458 }
1459
1460
ah_pre_send(ne_request * r,void * cookie,ne_buffer * request)1461 static void ah_pre_send(ne_request *r, void *cookie, ne_buffer *request)
1462 {
1463 auth_session *sess = cookie;
1464 struct auth_request *req = ne_get_request_private(r, sess->spec->id);
1465
1466 if (sess->protocol && req) {
1467 char *value;
1468
1469 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Sending '%s' response.",
1470 sess->protocol->name);
1471
1472 value = sess->protocol->response(sess, req);
1473
1474 if (value != NULL) {
1475 ne_buffer_concat(request, sess->spec->req_hdr, ": ", value, NULL);
1476 ne_free(value);
1477 }
1478 }
1479
1480 }
1481
ah_post_send(ne_request * req,void * cookie,const ne_status * status)1482 static int ah_post_send(ne_request *req, void *cookie, const ne_status *status)
1483 {
1484 auth_session *sess = cookie;
1485 struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1486 const char *auth_hdr, *auth_info_hdr;
1487 int ret = NE_OK;
1488
1489 if (!areq) return NE_OK;
1490
1491 auth_hdr = ne_get_response_header(req, sess->spec->resp_hdr);
1492 auth_info_hdr = ne_get_response_header(req, sess->spec->resp_info_hdr);
1493
1494 if (sess->context == AUTH_CONNECT && status->code == 401 && !auth_hdr) {
1495 /* Some broken proxies issue a 401 as a proxy auth challenge
1496 * to a CONNECT request; handle this here. */
1497 auth_hdr = ne_get_response_header(req, "WWW-Authenticate");
1498 auth_info_hdr = NULL;
1499 }
1500
1501 #ifdef HAVE_GSSAPI
1502 /* whatever happens: forget the GSSAPI token cached thus far */
1503 if (sess->gssapi_token) {
1504 ne_free(sess->gssapi_token);
1505 sess->gssapi_token = NULL;
1506 }
1507 #endif
1508
1509 #ifdef HAVE_SSPI
1510 /* whatever happens: forget the SSPI token cached thus far */
1511 if (sess->sspi_token) {
1512 ne_free(sess->sspi_token);
1513 sess->sspi_token = NULL;
1514 }
1515 #endif
1516
1517 NE_DEBUG(NE_DBG_HTTPAUTH,
1518 "ah_post_send (#%d), code is %d (want %d), %s is %s\n",
1519 areq->attempt, status->code, sess->spec->status_code,
1520 sess->spec->resp_hdr, auth_hdr ? auth_hdr : "(none)");
1521 if (auth_info_hdr && sess->protocol && sess->protocol->verify
1522 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x) == 0) {
1523 ret = sess->protocol->verify(areq, sess, auth_info_hdr);
1524 }
1525 else if (sess->protocol && sess->protocol->verify
1526 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x)
1527 && (status->klass == 2 || status->klass == 3)
1528 && auth_hdr) {
1529 ret = sess->protocol->verify(areq, sess, auth_hdr);
1530 }
1531 else if ((status->code == sess->spec->status_code ||
1532 (status->code == 401 && sess->context == AUTH_CONNECT)) &&
1533 auth_hdr) {
1534 /* note above: allow a 401 in response to a CONNECT request
1535 * from a proxy since some buggy proxies send that. */
1536 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got challenge (code %d).", status->code);
1537 if (!auth_challenge(sess, areq->attempt++, auth_hdr)) {
1538 ret = NE_RETRY;
1539 } else {
1540 clean_session(sess);
1541 ret = sess->spec->fail_code;
1542 }
1543
1544 /* Set or clear the conn-auth flag according to whether this
1545 * was an accepted challenge for a borked protocol. */
1546 ne_set_session_flag(sess->sess, NE_SESSFLAG_CONNAUTH,
1547 sess->protocol
1548 && (sess->protocol->flags & AUTH_FLAG_CONN_AUTH));
1549 }
1550
1551 #ifdef HAVE_SSPI
1552 /* Clear the SSPI context after successfull authentication. */
1553 if ((status->klass == 2 || status->klass == 3) && sess->sspi_context) {
1554 ne_sspi_clear_context(sess->sspi_context);
1555 }
1556 #endif
1557
1558 return ret;
1559 }
1560
ah_destroy(ne_request * req,void * session)1561 static void ah_destroy(ne_request *req, void *session)
1562 {
1563 auth_session *sess = session;
1564 struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1565
1566 if (areq) {
1567 ne_free(areq);
1568 }
1569 }
1570
free_auth(void * cookie)1571 static void free_auth(void *cookie)
1572 {
1573 auth_session *sess = cookie;
1574 struct auth_handler *hdl, *next;
1575
1576 #ifdef HAVE_GSSAPI
1577 if (sess->gssname != GSS_C_NO_NAME) {
1578 unsigned int major;
1579 gss_release_name(&major, &sess->gssname);
1580 }
1581 #endif
1582
1583 for (hdl = sess->handlers; hdl; hdl = next) {
1584 next = hdl->next;
1585 ne_free(hdl);
1586 }
1587
1588 clean_session(sess);
1589 ne_free(sess);
1590 }
1591
auth_register(ne_session * sess,int isproxy,unsigned protomask,const struct auth_class * ahc,const char * id,ne_auth_creds creds,void * userdata)1592 static void auth_register(ne_session *sess, int isproxy, unsigned protomask,
1593 const struct auth_class *ahc, const char *id,
1594 ne_auth_creds creds, void *userdata)
1595 {
1596 auth_session *ahs;
1597 struct auth_handler **hdl;
1598
1599 /* Handle the _ALL and _DEFAULT protocol masks: */
1600 if (protomask == NE_AUTH_ALL) {
1601 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST | NE_AUTH_NEGOTIATE;
1602 }
1603 else if (protomask == NE_AUTH_DEFAULT) {
1604 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST;
1605
1606 if (strcmp(ne_get_scheme(sess), "https") == 0 || isproxy) {
1607 protomask |= NE_AUTH_NEGOTIATE;
1608 }
1609 }
1610
1611 if ((protomask & NE_AUTH_NEGOTIATE) == NE_AUTH_NEGOTIATE) {
1612 /* Map NEGOTIATE to NTLM | GSSAPI. */
1613 protomask |= NE_AUTH_GSSAPI | NE_AUTH_NTLM;
1614 }
1615
1616 ahs = ne_get_session_private(sess, id);
1617 if (ahs == NULL) {
1618 ahs = ne_calloc(sizeof *ahs);
1619
1620 ahs->sess = sess;
1621 ahs->spec = ahc;
1622
1623 if (strcmp(ne_get_scheme(sess), "https") == 0) {
1624 ahs->context = isproxy ? AUTH_CONNECT : AUTH_NOTCONNECT;
1625 } else {
1626 ahs->context = AUTH_ANY;
1627 }
1628
1629 /* Register hooks */
1630 ne_hook_create_request(sess, ah_create, ahs);
1631 ne_hook_pre_send(sess, ah_pre_send, ahs);
1632 ne_hook_post_send(sess, ah_post_send, ahs);
1633 ne_hook_destroy_request(sess, ah_destroy, ahs);
1634 ne_hook_destroy_session(sess, free_auth, ahs);
1635
1636 ne_set_session_private(sess, id, ahs);
1637 }
1638
1639 #ifdef HAVE_GSSAPI
1640 if ((protomask & NE_AUTH_GSSAPI) && ahs->gssname == GSS_C_NO_NAME) {
1641 ne_uri uri = {0};
1642
1643 if (isproxy)
1644 ne_fill_proxy_uri(sess, &uri);
1645 else
1646 ne_fill_server_uri(sess, &uri);
1647
1648 get_gss_name(&ahs->gssname, uri.host);
1649
1650 ne_uri_free(&uri);
1651 }
1652 #endif
1653
1654 /* Find the end of the handler list, and add a new one. */
1655 hdl = &ahs->handlers;
1656 while (*hdl)
1657 hdl = &(*hdl)->next;
1658
1659 *hdl = ne_malloc(sizeof **hdl);
1660 (*hdl)->protomask = protomask;
1661 (*hdl)->creds = creds;
1662 (*hdl)->userdata = userdata;
1663 (*hdl)->next = NULL;
1664 (*hdl)->attempt = 0;
1665 }
1666
ne_set_server_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1667 void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1668 {
1669 auth_register(sess, 0, NE_AUTH_DEFAULT, &ah_server_class, HOOK_SERVER_ID,
1670 creds, userdata);
1671 }
1672
ne_set_proxy_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1673 void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1674 {
1675 auth_register(sess, 1, NE_AUTH_DEFAULT, &ah_proxy_class, HOOK_PROXY_ID,
1676 creds, userdata);
1677 }
1678
ne_add_server_auth(ne_session * sess,unsigned protocol,ne_auth_creds creds,void * userdata)1679 void ne_add_server_auth(ne_session *sess, unsigned protocol,
1680 ne_auth_creds creds, void *userdata)
1681 {
1682 auth_register(sess, 0, protocol, &ah_server_class, HOOK_SERVER_ID,
1683 creds, userdata);
1684 }
1685
ne_add_proxy_auth(ne_session * sess,unsigned protocol,ne_auth_creds creds,void * userdata)1686 void ne_add_proxy_auth(ne_session *sess, unsigned protocol,
1687 ne_auth_creds creds, void *userdata)
1688 {
1689 auth_register(sess, 1, protocol, &ah_proxy_class, HOOK_PROXY_ID,
1690 creds, userdata);
1691 }
1692
ne_forget_auth(ne_session * sess)1693 void ne_forget_auth(ne_session *sess)
1694 {
1695 auth_session *as;
1696 if ((as = ne_get_session_private(sess, HOOK_SERVER_ID)) != NULL)
1697 clean_session(as);
1698 if ((as = ne_get_session_private(sess, HOOK_PROXY_ID)) != NULL)
1699 clean_session(as);
1700 }
1701
1702