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