"Fossies" - the Fresh Open Source Software Archive

Member "sitecopy-0.16.6/lib/neon/ne_auth.c" (29 Feb 2008, 48894 Bytes) of archive /linux/www/sitecopy-0.16.6.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.

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