Back to index

avfs  1.0.1
http_auth.c
Go to the documentation of this file.
00001 /* 
00002    HTTP Authentication routines
00003    Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009    
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public
00016    License along with this library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
00018    MA 02111-1307, USA
00019 
00020 */
00021 
00022 
00023 /* HTTP Authentication, as per RFC2617.
00024  * 
00025  * TODO:
00026  *  - Improve cnonce? Make it really random using /dev/random or whatever?
00027  *  - Test auth-int support
00028  */
00029 
00030 #include "config.h"
00031 
00032 #ifdef HAVE_STDLIB_H
00033 #include <stdlib.h>
00034 #endif
00035 #include <stdio.h>
00036 
00037 #ifdef HAVE_STRING_H
00038 #include <string.h>
00039 #endif
00040 
00041 #include <time.h>
00042 
00043 #ifdef HAVE_SNPRINTF_H
00044 #include "snprintf.h"
00045 #endif
00046 
00047 #include "base64.h"
00048 #include "dates.h"
00049 
00050 #include "http_auth.h"
00051 #include "string_utils.h"
00052 #include "uri.h"
00053 #include "http_utils.h"
00054 #include "ne_alloc.h"
00055 
00056 /* The MD5 digest of a zero-length entity-body */
00057 #define DIGEST_MD5_EMPTY "d41d8cd98f00b204e9800998ecf8427e"
00058 
00059 /* A challenge */
00060 struct http_auth_chall {
00061     http_auth_scheme scheme;
00062     char *realm;
00063     char *domain;
00064     char *nonce;
00065     char *opaque;
00066     unsigned int stale:1; /* if stale=true */
00067     unsigned int got_qop:1; /* we were given a qop directive */
00068     unsigned int qop_auth:1; /* "auth" token in qop attrib */
00069     unsigned int qop_auth_int:1; /* "auth-int" token in qop attrib */
00070     http_auth_algorithm alg;
00071     struct http_auth_chall *next;
00072 };
00073 
00074 static const char *qop_values[] = {
00075     NULL,
00076     "auth",
00077     "auth-int"
00078 };
00079 static const char *algorithm_names[] = {
00080     "MD5",
00081     "MD5-sess",
00082     NULL
00083 };
00084 
00085 /* Private prototypes */
00086 static char *get_cnonce(void);
00087 static void clean_session(http_auth_session *sess);
00088 static int digest_challenge(http_auth_session *, struct http_auth_chall *);
00089 static int basic_challenge(http_auth_session *, struct http_auth_chall *);
00090 static char *request_digest(http_auth_session *);
00091 static char *request_basic(http_auth_session *);
00092 
00093 /* Domain handling */
00094 static int is_in_domain(http_auth_session *sess, const char *uri);
00095 static int parse_domain(http_auth_session *sess, const char *domain);
00096 
00097 /* Get the credentials, passing a temporary store for the password value */
00098 static int get_credentials(http_auth_session *sess, char **password);
00099 
00100 /* Initialize an auth session */
00101 void http_auth_init(http_auth_session *sess) 
00102 {
00103     memset(sess, 0, sizeof(http_auth_session));
00104 }
00105 
00106 http_auth_session *http_auth_create(void) 
00107 {
00108     http_auth_session *sess = ne_calloc(sizeof(http_auth_session));
00109     http_auth_init(sess);
00110     return sess;
00111 }
00112 
00113 void http_auth_destroy(http_auth_session *sess) 
00114 {
00115     http_auth_finish(sess);
00116     free(sess);
00117 }
00118 
00119 void http_auth_set_creds_cb(http_auth_session *sess,
00120                           http_auth_request_creds callback, void *userdata)
00121 {
00122     sess->reqcreds = callback;
00123     sess->reqcreds_udata = userdata;
00124 }
00125 
00126 #if 0
00127 void http_auth_set_server(http_auth_session *sess, 
00128                         const char *host, unsigned int port, const char *scheme)
00129 {
00130     sess->host = host;
00131     sess->port = port;
00132     sess->req_scheme = scheme;
00133 }
00134 #endif
00135 
00136 /* Start a new request. */
00137 void http_auth_new_request(http_auth_session *sess,
00138                         const char *method, const char *uri, 
00139                         const char *body_buffer, FILE *body_stream) 
00140 {
00141     if (!sess->can_handle) {
00142        DEBUG(DEBUG_HTTPAUTH, "Not handling session.\n");
00143     } else if (!is_in_domain(sess, uri)) {
00144            /* We have moved out of the authentication domain */
00145            DEBUG(DEBUG_HTTPAUTH, "URI %s outside session domain, not handling.\n", uri);
00146            sess->will_handle = 0;
00147        } else 
00148 
00149     {
00150        DEBUG(DEBUG_HTTPAUTH, "URI %s inside session domain, will handle.\n", uri);
00151 
00152        sess->will_handle = 1;
00153        sess->uri = uri;
00154        sess->method = method;
00155        sess->got_body = (body_buffer!=NULL) || (body_stream!=NULL);
00156        sess->body_buffer = body_buffer;
00157        sess->body_stream = body_stream;
00158        md5_init_ctx(&sess->response_body);
00159     }
00160 }
00161 
00162 static void clean_session(http_auth_session *sess) 
00163 {
00164     sess->can_handle = 0;
00165     HTTP_FREE(sess->basic);
00166     HTTP_FREE(sess->unq_realm);
00167     HTTP_FREE(sess->unq_nonce);
00168     HTTP_FREE(sess->unq_cnonce);
00169     HTTP_FREE(sess->opaque);
00170     HTTP_FREE(sess->username);
00171     if (sess->domain_count > 0) {
00172        split_string_free(sess->domain);
00173        sess->domain_count = 0;
00174     }
00175 }
00176 
00177 void http_auth_finish(http_auth_session *sess) {
00178     clean_session(sess);
00179 }
00180 
00181 /* Returns cnonce-value. We just use base64(time).
00182  * TODO: Could improve this? */
00183 static char *get_cnonce(void) 
00184 {
00185     char *ret, *tmp;
00186     tmp = rfc1123_date(time(NULL));
00187     ret = base64(tmp);
00188     free(tmp);
00189     return ret;
00190 }
00191 
00192 static int 
00193 get_credentials(http_auth_session *sess, char **password) 
00194 {
00195     return (*sess->reqcreds)(sess->reqcreds_udata, sess->unq_realm,
00196                            &sess->username, password);
00197 }
00198 
00199 static int parse_domain(http_auth_session *sess, const char *domain) {
00200     char *unq, **doms;
00201     int count, ret;
00202 
00203     unq = shave_string(domain, '"');
00204     doms = split_string_c(unq, ' ', NULL, HTTP_WHITESPACE, &count);
00205     if (doms != NULL) {
00206        if (count > 0) {
00207            sess->domain = doms;
00208            sess->domain_count = count;
00209            ret = 0;
00210        } else {
00211            free(doms);
00212            ret = -1;
00213        }
00214     } else {
00215        ret = -1;
00216     }
00217     free(unq);
00218     return ret;
00219 }
00220 
00221 /* Returns:
00222  *    0: if uri is in NOT in domain of session
00223  * else: uri IS in domain of session (or no domain known)
00224  */
00225 static int is_in_domain(http_auth_session *sess, const char *uri)
00226 {
00227 #if 1
00228     return 1;
00229 #else
00230     /* TODO: Need proper URI comparison for this to work. */
00231     int ret, dom;
00232     const char *abs_path;
00233     if (sess->domain_count == 0) {
00234        DEBUG(DEBUG_HTTPAUTH, "No domain, presuming okay.\n");
00235        return 1;
00236     }
00237     ret = 0;
00238     abs_path = uri_abspath(uri);
00239     for (dom = 0; dom < sess->domain_count; dom++) {
00240        DEBUG(DEBUG_HTTPAUTH, "Checking domain: %s\n", sess->domain[dom]);
00241        if (uri_childof(sess->domain[dom], abs_path)) {
00242            ret = 1;
00243            break;
00244        }
00245     }
00246     return ret;
00247 #endif
00248 }
00249 
00250 /* Add authentication creditials to a request */
00251 char *http_auth_request_header(http_auth_session *sess) 
00252 {
00253     if (sess->will_handle) {
00254        switch(sess->scheme) {
00255        case http_auth_scheme_basic:
00256            return request_basic(sess);
00257            break;
00258        case http_auth_scheme_digest:
00259            return request_digest(sess);
00260            break;
00261        default:
00262            break;
00263        }
00264     }
00265     return NULL;
00266 }
00267 
00268 /* Examine a Basic auth challenge.
00269  * Returns 0 if an valid challenge, else non-zero. */
00270 static int 
00271 basic_challenge(http_auth_session *sess, struct http_auth_chall *parms) 
00272 {
00273     char *tmp, *password;
00274 
00275     /* Verify challenge... must have a realm */
00276     if (parms->realm == NULL) {
00277        return -1;
00278     }
00279 
00280     DEBUG(DEBUG_HTTPAUTH, "Got Basic challenge with realm [%s]\n", 
00281           parms->realm);
00282 
00283     clean_session(sess);
00284 
00285     sess->unq_realm = shave_string(parms->realm, '"');
00286 
00287     if (get_credentials(sess, &password)) {
00288        /* Failed to get credentials */
00289        HTTP_FREE(sess->unq_realm);
00290        return -1;
00291     }
00292 
00293     sess->scheme = http_auth_scheme_basic;
00294 
00295     CONCAT3(tmp, sess->username, ":", password?password:"");
00296     sess->basic = base64(tmp);
00297     free(tmp);
00298 
00299     HTTP_FREE(password);
00300 
00301     return 0;
00302 }
00303 
00304 /* Add Basic authentication credentials to a request */
00305 static char *request_basic(http_auth_session *sess) 
00306 {
00307     char *buf;
00308     CONCAT3(buf, "Basic ", sess->basic, "\r\n");
00309     return buf;
00310 }
00311 
00312 /* Examine a digest challenge: return 0 if it is a valid Digest challenge,
00313  * else non-zero. */
00314 static int digest_challenge(http_auth_session *sess,
00315                          struct http_auth_chall *parms) 
00316 {
00317     struct md5_ctx tmp;
00318     unsigned char tmp_md5[16];
00319     char *password;
00320 
00321     /* Do we understand this challenge? */
00322     if (parms->alg == http_auth_alg_unknown) {
00323        DEBUG(DEBUG_HTTPAUTH, "Unknown algorithm.\n");
00324        return -1;
00325     }
00326     if ((parms->alg == http_auth_alg_md5_sess) &&
00327        !(parms->qop_auth || parms->qop_auth_int)) {
00328        DEBUG(DEBUG_HTTPAUTH, "Server did not give qop with MD5-session alg.\n");
00329        return -1;
00330     }
00331     if ((parms->realm==NULL) || (parms->nonce==NULL)) {
00332        DEBUG(DEBUG_HTTPAUTH, "Challenge missing nonce or realm.\n");
00333        return -1;
00334     }
00335 
00336     if (parms->stale) {
00337        /* Just a stale response, don't need to get a new username/password */
00338        DEBUG(DEBUG_HTTPAUTH, "Stale digest challenge.\n");
00339     } else {
00340        /* Forget the old session details */
00341        DEBUG(DEBUG_HTTPAUTH, "In digest challenge.\n");
00342 
00343        clean_session(sess);
00344        sess->unq_realm = shave_string(parms->realm, '"');
00345 
00346        /* Not a stale response: really need user authentication */
00347        if (get_credentials(sess, &password)) {
00348            /* Failed to get credentials */
00349            HTTP_FREE(sess->unq_realm);
00350            return -1;
00351        }
00352     }
00353     sess->alg = parms->alg;
00354     sess->scheme = http_auth_scheme_digest;
00355     sess->unq_nonce = shave_string(parms->nonce, '"');
00356     sess->unq_cnonce = get_cnonce();
00357     if (parms->domain) {
00358        if (parse_domain(sess, parms->domain)) {
00359            /* TODO: Handle the error? */
00360        }
00361     } else {
00362        sess->domain = NULL;
00363        sess->domain_count = 0;
00364     }
00365     if (parms->opaque != NULL) {
00366        sess->opaque = ne_strdup(parms->opaque); /* don't strip the quotes */
00367     }
00368     
00369     if (parms->got_qop) {
00370        /* What type of qop are we to apply to the message? */
00371        DEBUG(DEBUG_HTTPAUTH, "Got qop directive.\n");
00372        sess->nonce_count = 0;
00373        if (parms->qop_auth_int) {
00374            sess->qop = http_auth_qop_auth_int;
00375        } else {
00376            sess->qop = http_auth_qop_auth;
00377        }
00378     } else {
00379        /* No qop at all/ */
00380        sess->qop = http_auth_qop_none;
00381     }
00382     
00383     if (!parms->stale) {
00384        /* Calculate H(A1).
00385         * tmp = H(unq(username-value) ":" unq(realm-value) ":" passwd)
00386         */
00387        DEBUG(DEBUG_HTTPAUTH, "Calculating H(A1).\n");
00388        md5_init_ctx(&tmp);
00389        md5_process_bytes(sess->username, strlen(sess->username), &tmp);
00390        md5_process_bytes(":", 1, &tmp);
00391        md5_process_bytes(sess->unq_realm, strlen(sess->unq_realm), &tmp);
00392        md5_process_bytes(":", 1, &tmp);
00393        if (password != NULL)
00394            md5_process_bytes(password, strlen(password), &tmp);
00395        md5_finish_ctx(&tmp, tmp_md5);
00396        if (sess->alg == http_auth_alg_md5_sess) {
00397            unsigned char a1_md5[16];
00398            struct md5_ctx a1;
00399            char tmp_md5_ascii[33];
00400            /* Now we calculate the SESSION H(A1)
00401             *    A1 = H(...above...) ":" unq(nonce-value) ":" unq(cnonce-value) 
00402             */
00403            md5_to_ascii(tmp_md5, tmp_md5_ascii);
00404            md5_init_ctx(&a1);
00405            md5_process_bytes(tmp_md5_ascii, 32, &a1);
00406            md5_process_bytes(":", 1, &a1);
00407            md5_process_bytes(sess->unq_nonce, strlen(sess->unq_nonce), &a1);
00408            md5_process_bytes(":", 1, &a1);
00409            md5_process_bytes(sess->unq_cnonce, strlen(sess->unq_cnonce), &a1);
00410            md5_finish_ctx(&a1, a1_md5);
00411            md5_to_ascii(a1_md5, sess->h_a1);
00412            DEBUG(DEBUG_HTTPAUTH, "Session H(A1) is [%s]\n", sess->h_a1);
00413        } else {
00414            md5_to_ascii(tmp_md5, sess->h_a1);
00415            DEBUG(DEBUG_HTTPAUTH, "H(A1) is [%s]\n", sess->h_a1);
00416        }
00417        
00418        HTTP_FREE(password);
00419     }
00420     
00421     DEBUG(DEBUG_HTTPAUTH, "I like this Digest challenge.\n");
00422 
00423     return 0;
00424 }
00425 
00426 /* Return Digest authentication credentials header value for the given
00427  * session. */
00428 static char *request_digest(http_auth_session *sess) 
00429 {
00430     struct md5_ctx a2, rdig;
00431     unsigned char a2_md5[16], rdig_md5[16];
00432     char a2_md5_ascii[33], rdig_md5_ascii[33];
00433     char nc_value[9] = {0}, *ret;
00434     const char *qop_value; /* qop-value */
00435     size_t retlen;
00436 
00437     /* Increase the nonce-count */
00438     if (sess->qop != http_auth_qop_none) {
00439        sess->nonce_count++;
00440        snprintf(nc_value, 9, "%08x", sess->nonce_count);
00441        DEBUG(DEBUG_HTTPAUTH, "Nonce count is %d, nc is [%s]\n", 
00442               sess->nonce_count, nc_value);
00443     }
00444     qop_value = qop_values[sess->qop];
00445 
00446     /* Calculate H(A2). */
00447     md5_init_ctx(&a2);
00448     md5_process_bytes(sess->method, strlen(sess->method), &a2);
00449     md5_process_bytes(":", 1, &a2);
00450     md5_process_bytes(sess->uri, strlen(sess->uri), &a2);
00451     if (sess->qop == http_auth_qop_auth_int) {
00452        /* Calculate H(entity-body) */
00453        if (sess->got_body) {
00454            char tmp_md5_ascii[33];
00455            unsigned char tmp_md5[16];
00456            if (sess->body_stream != NULL) {
00457               DEBUG(DEBUG_HTTPAUTH, "Digesting body stream.\n");
00458               md5_stream(sess->body_stream, tmp_md5);
00459               rewind(sess->body_stream); /* leave it at the beginning */
00460            } else if (sess->body_buffer) {
00461               DEBUG(DEBUG_HTTPAUTH, "Digesting body buffer.\n");
00462               md5_buffer(sess->body_buffer, strlen(sess->body_buffer), 
00463                          tmp_md5);
00464            }
00465            md5_to_ascii(tmp_md5, tmp_md5_ascii);
00466            DEBUG(DEBUG_HTTPAUTH, "H(entity-body) is [%s]\n", tmp_md5_ascii);
00467            /* Append to A2 */
00468            md5_process_bytes(":", 1, &a2);
00469            md5_process_bytes(tmp_md5_ascii, 32, &a2);
00470        } else {
00471            /* No entity-body. */
00472            DEBUG(DEBUG_HTTPAUTH, "Digesting empty entity-body.\n");
00473            md5_process_bytes(":" DIGEST_MD5_EMPTY, 33, &a2);
00474        }
00475     }
00476     md5_finish_ctx(&a2, a2_md5);
00477     md5_to_ascii(a2_md5, a2_md5_ascii);
00478     DEBUG(DEBUG_HTTPAUTH, "H(A2): %s\n", a2_md5_ascii);
00479 
00480     DEBUG(DEBUG_HTTPAUTH, "Calculating Request-Digest.\n");
00481     /* Now, calculation of the Request-Digest.
00482      * The first section is the regardless of qop value
00483      *     H(A1) ":" unq(nonce-value) ":" */
00484     md5_init_ctx(&rdig);
00485 
00486     /* Use the calculated H(A1) */
00487     md5_process_bytes(sess->h_a1, 32, &rdig);
00488 
00489     md5_process_bytes(":", 1, &rdig);
00490     md5_process_bytes(sess->unq_nonce, strlen(sess->unq_nonce), &rdig);
00491     md5_process_bytes(":", 1, &rdig);
00492     if (sess->qop != http_auth_qop_none) {
00493        /* Add on:
00494         *    nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":"
00495         */
00496        DEBUG(DEBUG_HTTPAUTH, "Have qop directive, digesting: [%s:%s:%s]\n",
00497               nc_value, sess->unq_cnonce, qop_value);
00498        md5_process_bytes(nc_value, 8, &rdig);
00499        md5_process_bytes(":", 1, &rdig);
00500        md5_process_bytes(sess->unq_cnonce, strlen(sess->unq_cnonce), &rdig);
00501        md5_process_bytes(":", 1, &rdig);
00502        /* Store a copy of this structure (see note below) */
00503        sess->stored_rdig = rdig;
00504        md5_process_bytes(qop_value, strlen(qop_value), &rdig);
00505        md5_process_bytes(":", 1, &rdig);
00506     } else {
00507        /* Store a copy of this structure... we do this because the
00508         * calculation of the rspauth= field in the Auth-Info header 
00509         * is the same as this digest, up to this point. */
00510        sess->stored_rdig = rdig;
00511     }
00512     /* And finally, H(A2) */
00513     md5_process_bytes(a2_md5_ascii, 32, &rdig);
00514     md5_finish_ctx(&rdig, rdig_md5);
00515     md5_to_ascii(rdig_md5, rdig_md5_ascii);
00516     
00517     /* Buffer size calculation. */
00518     
00519     retlen = 
00520        6                                      /* Digest */
00521        + 1 + 8 + 1 + 2 + strlen(sess->username)  /*  username="..." */
00522        + 2 + 5 + 1 + 2 + strlen(sess->unq_realm) /* , realm="..." */
00523        + 2 + 5 + 1 + 2 + strlen(sess->unq_nonce) /* , nonce="..." */
00524        + 2 + 3 + 1 + 2 + strlen(sess->uri)       /* , uri="..." */
00525        + 2 + 8 + 1 + 2 + 32                      /* , response="..." */
00526        + 2 + 9 + 1 + 2 + strlen(algorithm_names[sess->alg]) /* 
00527                                              , algorithm="..." */
00528        ;
00529 
00530     if (sess->opaque != NULL)
00531        retlen += 2 + 6 + 1 + strlen(sess->opaque);   /* , opaque=... */
00532 
00533     if (sess->qop != http_auth_qop_none)
00534        retlen += 
00535            2 + 6 + 2 + 1 + strlen(sess->unq_cnonce) +   /* , cnonce="..." */
00536            2 + 2 + 1 + 8 +                       /* , nc=... */
00537            2 + 3 + 1 + 2 + strlen(qop_values[sess->qop]) /* , qop="..." */
00538            ;
00539 
00540     retlen += 2;   /* \r\n */
00541 
00542     DEBUG(DEBUG_HTTPAUTH, "Calculated length of buffer: %d\n", retlen);
00543 
00544     ret = ne_calloc(retlen + 1);
00545 
00546     sprintf(ret,
00547              "Digest username=\"%s\", realm=\"%s\""
00548              ", nonce=\"%s\", uri=\"%s\", response=\"%s\""
00549              ", algorithm=\"%s\"",
00550              sess->username, sess->unq_realm, 
00551              sess->unq_nonce, sess->uri, rdig_md5_ascii,
00552              algorithm_names[sess->alg]);
00553     
00554     if (sess->opaque != NULL) {
00555        /* We never unquote it, so it's still quoted here */
00556        strcat(ret, ", opaque=");
00557        strcat(ret, sess->opaque);
00558     }
00559 
00560     if (sess->qop != http_auth_qop_none) {
00561        /* Add in cnonce and nc-value fields */
00562        strcat(ret, ", cnonce=\"");
00563        strcat(ret, sess->unq_cnonce);
00564        strcat(ret, "\", nc=");
00565        strcat(ret, nc_value);
00566        strcat(ret, ", qop=\"");
00567        strcat(ret, qop_values[sess->qop]);
00568        strcat(ret, "\"");
00569     }
00570 
00571     DEBUG(DEBUG_HTTPAUTH, "Digest header field value:\n%s\n", ret);
00572 
00573     strcat(ret, "\r\n");
00574 
00575     DEBUG(DEBUG_HTTPAUTH, "Calculated length: %d, actual length: %d\n", 
00576           retlen, strlen(ret));
00577     
00578     return ret;
00579 }
00580 
00581 void http_auth_response_body(http_auth_session *sess, 
00582                           const char *buffer, size_t buffer_len) 
00583 {
00584     if (!sess->will_handle ||
00585        sess->scheme != http_auth_scheme_digest) return;
00586     DEBUG(DEBUG_HTTPAUTH, "Digesting %d bytes of response body.\n",
00587           buffer_len);
00588     md5_process_bytes(buffer, buffer_len, &sess->response_body);
00589 }
00590 
00591 /* Pass this the value of the 'Authentication-Info:' header field, if
00592  * one is received.
00593  * Returns:
00594  *    0 if it gives a valid authentication for the server 
00595  *    non-zero otherwise (don't believe the response in this case!).
00596  */
00597 int http_auth_verify_response(http_auth_session *sess, const char *value) 
00598 {
00599     char **pairs;
00600     http_auth_qop qop = http_auth_qop_none;
00601     char *nextnonce = NULL, /* for the nextnonce= value */
00602        *rspauth = NULL, /* for the rspauth= value */
00603        *cnonce = NULL, /* for the cnonce= value */
00604        *nc = NULL, /* for the nc= value */
00605        *unquoted, *qop_value = NULL;
00606     int n, nonce_count, okay;
00607     
00608     if (!sess->will_handle) {
00609        /* Ignore it */
00610        return 0;
00611     }
00612     
00613     if (sess->scheme != http_auth_scheme_digest) {
00614        DEBUG(DEBUG_HTTPAUTH, "Found Auth-Info header not in response to Digest credentials - dodgy.\n");
00615        return -1;
00616     }
00617     
00618     DEBUG (DEBUG_HTTPAUTH, "Auth-Info header: %s\n", value);
00619 
00620     pairs = pair_string(value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE);
00621     
00622     for (n = 0; pairs[n]!=NULL; n+=2) {
00623        unquoted = shave_string(pairs[n+1], '"');
00624        if (strcasecmp(pairs[n], "qop") == 0) {
00625            qop_value = ne_strdup(pairs[n+1]);
00626            if (strcasecmp(pairs[n+1], "auth-int") == 0) {
00627               qop = http_auth_qop_auth_int;
00628            } else if (strcasecmp(pairs[n+1], "auth") == 0) {
00629               qop = http_auth_qop_auth;
00630            } else {
00631               qop = http_auth_qop_none;
00632            }
00633        } else if (strcasecmp(pairs[n], "nextnonce") == 0) {
00634            nextnonce = ne_strdup(unquoted);
00635        } else if (strcasecmp(pairs[n], "rspauth") == 0) {
00636            rspauth = ne_strdup(unquoted);
00637        } else if (strcasecmp(pairs[n], "cnonce") == 0) {
00638            cnonce = ne_strdup(unquoted);
00639        } else if (strcasecmp(pairs[n], "nc") == 0) { 
00640            nc = ne_strdup(pairs[n]);
00641            if (sscanf(pairs[n+1], "%x", &nonce_count) != 1) {
00642               DEBUG(DEBUG_HTTPAUTH, "Couldn't scan [%s] for nonce count.\n",
00643                      pairs[n+1]);
00644            } else {
00645               DEBUG(DEBUG_HTTPAUTH, "Got nonce_count: %d\n", nonce_count);
00646            }
00647        }
00648        free(unquoted);
00649     }
00650     pair_string_free(pairs);
00651 
00652     /* Presume the worst */
00653     okay = -1;
00654 
00655     if ((qop != http_auth_qop_none) && (qop_value != NULL)) {
00656        if ((rspauth == NULL) || (cnonce == NULL) || (nc == NULL)) {
00657            DEBUG(DEBUG_HTTPAUTH, "Missing rspauth, cnonce or nc with qop.\n");
00658        } else { /* Have got rspauth, cnonce and nc */
00659            if (strcmp(cnonce, sess->unq_cnonce) != 0) {
00660               DEBUG(DEBUG_HTTPAUTH, "Response cnonce doesn't match.\n");
00661            } else if (nonce_count != sess->nonce_count) { 
00662               DEBUG(DEBUG_HTTPAUTH, "Response nonce count doesn't match.\n");
00663            } else {
00664               /* Calculate and check the response-digest value.
00665                * joe: IMO the spec is slightly ambiguous as to whether
00666                * we use the qop which WE sent, or the qop which THEY
00667                * sent...  */
00668               struct md5_ctx a2;
00669               unsigned char a2_md5[16], rdig_md5[16];
00670               char a2_md5_ascii[33], rdig_md5_ascii[33];
00671 
00672               DEBUG(DEBUG_HTTPAUTH, "Calculating response-digest.\n");
00673 
00674               /* First off, H(A2) again. */
00675               md5_init_ctx(&a2);
00676               md5_process_bytes(":", 1, &a2);
00677               md5_process_bytes(sess->uri, strlen(sess->uri), &a2);
00678               if (qop == http_auth_qop_auth_int) {
00679                   unsigned char heb_md5[16];
00680                   char heb_md5_ascii[33];
00681                   /* Add on ":" H(entity-body) */
00682                   md5_finish_ctx(&sess->response_body, heb_md5);
00683                   md5_to_ascii(heb_md5, heb_md5_ascii);
00684                   md5_process_bytes(":", 1, &a2);
00685                   md5_process_bytes(heb_md5_ascii, 32, &a2);
00686                   DEBUG(DEBUG_HTTPAUTH, "Digested [:%s]\n", heb_md5_ascii);
00687               }
00688               md5_finish_ctx(&a2, a2_md5);
00689               md5_to_ascii(a2_md5, a2_md5_ascii);
00690               
00691               /* We have the stored digest-so-far of 
00692                *   H(A1) ":" unq(nonce-value) 
00693                *        [ ":" nc-value ":" unq(cnonce-value) ] for qop
00694                * in sess->stored_rdig, to save digesting them again.
00695                *
00696                */
00697               if (qop != http_auth_qop_none) {
00698                   /* Add in qop-value */
00699                   DEBUG(DEBUG_HTTPAUTH, "Digesting qop-value [%s:].\n", 
00700                         qop_value);
00701                   md5_process_bytes(qop_value, strlen(qop_value), 
00702                                    &sess->stored_rdig);
00703                   md5_process_bytes(":", 1, &sess->stored_rdig);
00704               }
00705               /* Digest ":" H(A2) */
00706               md5_process_bytes(a2_md5_ascii, 32, &sess->stored_rdig);
00707               /* All done */
00708               md5_finish_ctx(&sess->stored_rdig, rdig_md5);
00709               md5_to_ascii(rdig_md5, rdig_md5_ascii);
00710 
00711               DEBUG(DEBUG_HTTPAUTH, "Calculated response-digest of: [%s]\n",
00712                      rdig_md5_ascii);
00713               DEBUG(DEBUG_HTTPAUTH, "Given response-digest of:      [%s]\n",
00714                      rspauth);
00715 
00716               /* And... do they match? */
00717               okay = (strcasecmp(rdig_md5_ascii, rspauth) == 0)?0:-1;
00718               DEBUG(DEBUG_HTTPAUTH, "Matched: %s\n", okay?"nope":"YES!");
00719            }
00720            free(rspauth);
00721            free(cnonce);
00722            free(nc);
00723        }
00724        free(qop_value);
00725     } else {
00726        DEBUG(DEBUG_HTTPAUTH, "No qop directive, auth okay.\n");
00727        okay = 0;
00728     }
00729 
00730     /* Check for a nextnonce */
00731     if (nextnonce != NULL) {
00732        DEBUG(DEBUG_HTTPAUTH, "Found nextnonce of [%s].\n", nextnonce);
00733        if (sess->unq_nonce != NULL)
00734            free(sess->unq_nonce);
00735        sess->unq_nonce = nextnonce;
00736     }
00737 
00738     return okay;
00739 }
00740 
00741 /* A new challenge presented by the server */
00742 int http_auth_challenge(http_auth_session *sess, const char *value) 
00743 {
00744     char **pairs, *pnt, *unquoted, *key;
00745     struct http_auth_chall *chall = NULL, *challenges = NULL;
00746     int n, success;
00747 
00748     DEBUG(DEBUG_HTTPAUTH, "Got new auth challenge: %s\n", value);
00749 
00750     /* The header value may be made up of one or more challenges.
00751      * We split it down into attribute-value pairs, then search for
00752      * schemes in the pair keys.
00753      */
00754     pairs = pair_string(value, ',', '=', HTTP_QUOTES, HTTP_WHITESPACE);
00755 
00756     for (n = 0; pairs[n]!=NULL; n+=2) {
00757        /* Look for an auth-scheme in the key */
00758        pnt = strchr(pairs[n], ' ');
00759        if (pnt != NULL) {
00760            /* We have a new challenge */
00761            DEBUG(DEBUG_HTTPAUTH, "New challenge.\n");
00762            chall = ne_calloc(sizeof *chall);
00763 
00764            chall->next = challenges;
00765            challenges = chall;
00766            /* Initialize the challenge parameters */
00767            /* Which auth-scheme is it (case-insensitive matching) */
00768            if (strncasecmp(pairs[n], "basic ", 6) == 0) {
00769               DEBUG(DEBUG_HTTPAUTH, "Basic scheme.\n");
00770               chall->scheme = http_auth_scheme_basic;
00771            } else if (strncasecmp(pairs[n], "digest ", 7) == 0) {
00772               DEBUG(DEBUG_HTTPAUTH, "Digest scheme.\n");
00773               chall->scheme = http_auth_scheme_digest;
00774            } else {
00775               DEBUG(DEBUG_HTTPAUTH, "Unknown scheme.\n");
00776               free(chall);
00777               challenges = NULL;
00778               break;
00779            }
00780            /* Now, the real key for this pair starts after the 
00781             * auth-scheme... skipping whitespace */
00782            while (strchr(HTTP_WHITESPACE, *(++pnt)) != NULL)
00783               /* nullop */;
00784            key = pnt;
00785        } else if (chall == NULL) {
00786            /* If we haven't got an auth-scheme, and we're
00787             * haven't yet found a challenge, skip this pair.
00788             */
00789            continue;
00790        } else {
00791            key = pairs[n];
00792        }
00793        DEBUG(DEBUG_HTTPAUTH, "Got pair: [%s] = [%s]\n", key, pairs[n+1]);
00794        /* Most values are quoted, so unquote them here */
00795        unquoted = shave_string(pairs[n+1], '"');
00796        /* Now parse the attribute */
00797        DEBUG(DEBUG_HTTPAUTH, "Unquoted pair is: [%s]\n", unquoted);
00798        if (strcasecmp(key, "realm") == 0) {
00799            chall->realm = pairs[n+1];
00800        } else if (strcasecmp(key, "nonce") == 0) {
00801            chall->nonce = pairs[n+1];
00802        } else if (strcasecmp(key, "opaque") == 0) {
00803            chall->opaque = pairs[n+1];
00804        } else if (strcasecmp(key, "domain") == 0) {
00805            chall->domain = pairs[n+1];
00806        } else if (strcasecmp(key, "stale") == 0) {
00807            /* Truth value */
00808            chall->stale = 
00809               (strcasecmp(unquoted, "true") == 0);
00810        } else if (strcasecmp(key, "algorithm") == 0) {
00811            if (strcasecmp(unquoted, "md5") == 0) {
00812               chall->alg = http_auth_alg_md5;
00813            } else if (strcasecmp(unquoted, "md5-sess") == 0) {
00814               chall->alg = http_auth_alg_md5_sess;
00815            } else {
00816               chall->alg = http_auth_alg_unknown;
00817            }
00818        } else if (strcasecmp(key, "qop") == 0) {
00819            char **qops;
00820            int qop;
00821            qops = split_string(unquoted, ',', NULL, HTTP_WHITESPACE);
00822            chall->got_qop = 1;
00823            for (qop = 0; qops[qop] != NULL; qop++) {
00824               if (strcasecmp(qops[qop], "auth") == 0) {
00825                   chall->qop_auth = 1;
00826               } else if (strcasecmp(qops[qop], "auth-int") == 0) {
00827                   chall->qop_auth_int = 1;
00828               }
00829            }
00830            split_string_free(qops);
00831        }
00832        free(unquoted);
00833     }
00834 
00835     DEBUG(DEBUG_HTTPAUTH, "Finished parsing parameters.\n");
00836 
00837     /* Did we find any challenges */
00838     if (challenges == NULL) {
00839        pair_string_free(pairs);
00840        return -1;
00841     }
00842     
00843     success = 0;
00844 
00845     DEBUG(DEBUG_HTTPAUTH, "Looking for Digest challenges.\n");
00846 
00847     /* Try a digest challenge */
00848     for (chall = challenges; chall != NULL; chall = chall->next) {
00849        if (chall->scheme == http_auth_scheme_digest) {
00850            if (!digest_challenge(sess, chall)) {
00851               success = 1;
00852               break;
00853            }
00854        }
00855     }
00856 
00857     if (!success) {
00858        DEBUG(DEBUG_HTTPAUTH, "No good Digest challenges, looking for Basic.\n");
00859        for (chall = challenges; chall != NULL; chall = chall->next) {
00860            if (chall->scheme == http_auth_scheme_basic) {
00861               if (!basic_challenge(sess, chall)) {
00862                   success = 1;
00863                   break;
00864               }
00865            }
00866        }
00867 
00868        if (!success) {
00869            /* No good challenges - record this in the session state */
00870            DEBUG(DEBUG_HTTPAUTH, "Did not understand any challenges.\n");
00871        }
00872 
00873     }
00874     
00875     /* Remember whether we can now supply the auth details */
00876     sess->can_handle = success;
00877 
00878     while (challenges != NULL) {
00879        chall = challenges->next;
00880        free(challenges);
00881        challenges = chall;
00882     }
00883 
00884     /* Free up the parsed header values */
00885     pair_string_free(pairs);
00886 
00887     return !success;
00888 }