"Fossies" - the Fresh Open Source Software Archive

Member "neon-0.31.2/src/ne_auth.c" (20 Jun 2020, 53200 Bytes) of package /linux/www/neon-0.31.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ne_auth.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.31.1_vs_0.31.2.

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