/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: http_digest.c,v 1.23 2004/10/06 07:50:18 bagder Exp $ ***************************************************************************/ #include "setup.h" #ifndef CURL_DISABLE_HTTP /* -- WIN32 approved -- */ #include #include #include #include #include #include "urldata.h" #include "sendf.h" #include "strequal.h" #include "base64.h" #include "md5.h" #include "http_digest.h" #include "strtok.h" #include "url.h" /* for Curl_safefree() */ #include "memory.h" #define _MPRINTF_REPLACE /* use our functions only */ #include /* The last #include file should be: */ #include "memdebug.h" /* Test example headers: WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" */ CURLdigest Curl_input_digest( struct connectdata *conn, bool proxy, char *header ) {/* rest of the *-authenticate: header */ bool more = TRUE; char *token = NULL; char *tmp = NULL; bool foundAuth = FALSE; bool foundAuthInt = FALSE; struct SessionHandle *data = conn->data; bool before = FALSE; /* got a nonce before */ struct digestdata *d; if ( proxy ) { d = &data->state.proxydigest; } else { d = &data->state.digest; } /* skip initial whitespaces */ while ( *header && isspace( (int)*header ) ) header++; if ( checkprefix( "Digest", header ) ) { header += strlen( "Digest" ); /* If we already have received a nonce, keep that in mind */ if ( d->nonce ) { before = TRUE; } /* clear off any former leftovers and init to defaults */ Curl_digest_cleanup_one( d ); while ( more ) { char value[32]; char content[128]; size_t totlen = 0; while ( *header && isspace( (int)*header ) ) header++; /* how big can these strings be? */ if ( ( 2 == sscanf( header, "%31[^=]=\"%127[^\"]\"", value, content ) ) || /* try the same scan but without quotes around the content but don't include the possibly trailing comma */ ( 2 == sscanf( header, "%31[^=]=%127[^,]", value, content ) ) ) { if ( strequal( value, "nonce" ) ) { d->nonce = strdup( content ); if ( !d->nonce ) { return CURLDIGEST_NOMEM; } } else if ( strequal( value, "stale" ) ) { if ( strequal( content, "true" ) ) { d->stale = TRUE; d->nc = 1; /* we make a new nonce now */ } } else if ( strequal( value, "realm" ) ) { d->realm = strdup( content ); if ( !d->realm ) { return CURLDIGEST_NOMEM; } } else if ( strequal( value, "opaque" ) ) { d->opaque = strdup( content ); if ( !d->opaque ) { return CURLDIGEST_NOMEM; } } else if ( strequal( value, "qop" ) ) { char *tok_buf; /* tokenize the list and choose auth if possible, use a temporary clone of the buffer since strtok_r() ruins it */ tmp = strdup( content ); if ( !tmp ) { return CURLDIGEST_NOMEM; } token = strtok_r( tmp, ",", &tok_buf ); while ( token != NULL ) { if ( strequal( token, "auth" ) ) { foundAuth = TRUE; } else if ( strequal( token, "auth-int" ) ) { foundAuthInt = TRUE; } token = strtok_r( NULL, ",", &tok_buf ); } free( tmp ); /*select only auth o auth-int. Otherwise, ignore*/ if ( foundAuth ) { d->qop = strdup( "auth" ); if ( !d->qop ) { return CURLDIGEST_NOMEM; } } else if ( foundAuthInt ) { d->qop = strdup( "auth-int" ); if ( !d->qop ) { return CURLDIGEST_NOMEM; } } } else if ( strequal( value, "algorithm" ) ) { d->algorithm = strdup( content ); if ( !d->algorithm ) { return CURLDIGEST_NOMEM; } if ( strequal( content, "MD5-sess" ) ) { d->algo = CURLDIGESTALGO_MD5SESS; } else if ( strequal( content, "MD5" ) ) { d->algo = CURLDIGESTALGO_MD5; } else { return CURLDIGEST_BADALGO; } } else { /* unknown specifier, ignore it! */ } totlen = strlen( value ) + strlen( content ) + 1; if ( header[strlen( value ) + 1] == '\"' ) { /* the contents were within quotes, then add 2 for them to the length */ totlen += 2; } } else { break; /* we're done here */ } header += totlen; if ( ',' == *header ) { /* allow the list to be comma-separated */ header++; } } /* We had a nonce since before, and we got another one now without 'stale=true'. This means we provided bad credentials in the previous request */ if ( before && !d->stale ) { return CURLDIGEST_BAD; } /* We got this header without a nonce, that's a bad Digest line! */ if ( !d->nonce ) { return CURLDIGEST_BAD; } } else { /* else not a digest, get out */ return CURLDIGEST_NONE; } return CURLDIGEST_FINE; } /* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ static void md5_to_ascii( unsigned char *source, /* 16 bytes */ unsigned char *dest ) { /* 33 bytes */ int i; for ( i = 0; i < 16; i++ ) snprintf( (char *)&dest[i * 2], 3, "%02x", source[i] ); } CURLcode Curl_output_digest( struct connectdata *conn, bool proxy, unsigned char *request, unsigned char *uripath ) { /* We have a Digest setup for this, use it! Now, to get all the details for this sorted out, I must urge you dear friend to read up on the RFC2617 section 3.2.2, */ unsigned char md5buf[16]; /* 16 bytes/128 bits */ unsigned char request_digest[33]; unsigned char *md5this; unsigned char *ha1; unsigned char ha2[33]; /* 32 digits and 1 zero byte */ char cnoncebuf[7]; char *cnonce; char *tmp = NULL; struct timeval now; char **allocuserpwd; char *userp; char *passwdp; struct auth *authp; struct SessionHandle *data = conn->data; struct digestdata *d; if ( proxy ) { d = &data->state.proxydigest; allocuserpwd = &conn->allocptr.proxyuserpwd; userp = conn->proxyuser; passwdp = conn->proxypasswd; authp = &data->state.authproxy; } else { d = &data->state.digest; allocuserpwd = &conn->allocptr.userpwd; userp = conn->user; passwdp = conn->passwd; authp = &data->state.authhost; } /* not set means empty */ if ( !userp ) { userp = (char *)""; } if ( !passwdp ) { passwdp = (char *)""; } if ( !d->nonce ) { authp->done = FALSE; return CURLE_OK; } authp->done = TRUE; if ( !d->nc ) { d->nc = 1; } if ( !d->cnonce ) { /* Generate a cnonce */ now = Curl_tvnow(); snprintf( cnoncebuf, sizeof( cnoncebuf ), "%06ld", now.tv_sec ); if ( Curl_base64_encode( cnoncebuf, strlen( cnoncebuf ), &cnonce ) ) { d->cnonce = cnonce; } else { return CURLE_OUT_OF_MEMORY; } } /* if the algorithm is "MD5" or unspecified (which then defaults to MD5): A1 = unq(username-value) ":" unq(realm-value) ":" passwd if the algorithm is "MD5-sess" then: A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) ":" unq(nonce-value) ":" unq(cnonce-value) */ md5this = (unsigned char *) aprintf( "%s:%s:%s", userp, d->realm, passwdp ); if ( !md5this ) { return CURLE_OUT_OF_MEMORY; } Curl_md5it( md5buf, md5this ); free( md5this ); /* free this again */ ha1 = (unsigned char *)malloc( 33 ); /* 32 digits and 1 zero byte */ if ( !ha1 ) { return CURLE_OUT_OF_MEMORY; } md5_to_ascii( md5buf, ha1 ); if ( d->algo == CURLDIGESTALGO_MD5SESS ) { /* nonce and cnonce are OUTSIDE the hash */ tmp = aprintf( "%s:%s:%s", ha1, d->nonce, d->cnonce ); free( ha1 ); if ( !tmp ) { return CURLE_OUT_OF_MEMORY; } ha1 = (unsigned char *)tmp; } /* If the "qop" directive's value is "auth" or is unspecified, then A2 is: A2 = Method ":" digest-uri-value If the "qop" value is "auth-int", then A2 is: A2 = Method ":" digest-uri-value ":" H(entity-body) (The "Method" value is the HTTP request method as specified in section 5.1.1 of RFC 2616) */ md5this = (unsigned char *)aprintf( "%s:%s", request, uripath ); if ( !md5this ) { free( ha1 ); return CURLE_OUT_OF_MEMORY; } if ( d->qop && strequal( d->qop, "auth-int" ) ) { /* We don't support auth-int at the moment. I can't see a easy way to get entity-body here */ /* TODO: Append H(entity-body)*/ } Curl_md5it( md5buf, md5this ); free( md5this ); /* free this again */ md5_to_ascii( md5buf, ha2 ); if ( d->qop ) { md5this = (unsigned char *)aprintf( "%s:%s:%08x:%s:%s:%s", ha1, d->nonce, d->nc, d->cnonce, d->qop, ha2 ); } else { md5this = (unsigned char *)aprintf( "%s:%s:%s", ha1, d->nonce, ha2 ); } free( ha1 ); if ( !md5this ) { return CURLE_OUT_OF_MEMORY; } Curl_md5it( md5buf, md5this ); free( md5this ); /* free this again */ md5_to_ascii( md5buf, request_digest ); /* for test case 64 (snooped from a Mozilla 1.3a request) Authorization: Digest username="testuser", realm="testrealm", \ nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" */ Curl_safefree( *allocuserpwd ); if ( d->qop ) { *allocuserpwd = aprintf( "%sAuthorization: Digest " "username=\"%s\", " "realm=\"%s\", " "nonce=\"%s\", " "uri=\"%s\", " "cnonce=\"%s\", " "nc=%08x, " "qop=\"%s\", " "response=\"%s\"", proxy ? "Proxy-" : "", userp, d->realm, d->nonce, uripath, /* this is the PATH part of the URL */ d->cnonce, d->nc, d->qop, request_digest ); if ( strequal( d->qop, "auth" ) ) { d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded which tells to the server how many times you are using the same nonce in the qop=auth mode. */ } } else { *allocuserpwd = aprintf( "%sAuthorization: Digest " "username=\"%s\", " "realm=\"%s\", " "nonce=\"%s\", " "uri=\"%s\", " "response=\"%s\"", proxy ? "Proxy-" : "", userp, d->realm, d->nonce, uripath, /* this is the PATH part of the URL */ request_digest ); } if ( !*allocuserpwd ) { return CURLE_OUT_OF_MEMORY; } /* Add optional fields */ if ( d->opaque ) { /* append opaque */ tmp = aprintf( "%s, opaque=\"%s\"", *allocuserpwd, d->opaque ); if ( !tmp ) { return CURLE_OUT_OF_MEMORY; } free( *allocuserpwd ); *allocuserpwd = tmp; } if ( d->algorithm ) { /* append algorithm */ tmp = aprintf( "%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm ); if ( !tmp ) { return CURLE_OUT_OF_MEMORY; } free( *allocuserpwd ); *allocuserpwd = tmp; } /* append CRLF to the userpwd header */ tmp = (char*) realloc( *allocuserpwd, strlen( *allocuserpwd ) + 3 + 1 ); if ( !tmp ) { return CURLE_OUT_OF_MEMORY; } strcat( tmp, "\r\n" ); *allocuserpwd = tmp; return CURLE_OK; } void Curl_digest_cleanup_one( struct digestdata *d ) { if ( d->nonce ) { free( d->nonce ); } d->nonce = NULL; if ( d->cnonce ) { free( d->cnonce ); } d->cnonce = NULL; if ( d->realm ) { free( d->realm ); } d->realm = NULL; if ( d->opaque ) { free( d->opaque ); } d->opaque = NULL; if ( d->qop ) { free( d->qop ); } d->qop = NULL; if ( d->algorithm ) { free( d->algorithm ); } d->algorithm = NULL; d->nc = 0; d->algo = CURLDIGESTALGO_MD5; /* default algorithm */ d->stale = FALSE; /* default means normal, not stale */ } void Curl_digest_cleanup( struct SessionHandle *data ) { Curl_digest_cleanup_one( &data->state.digest ); Curl_digest_cleanup_one( &data->state.proxydigest ); } #endif