From 3e1caa61859a6057a65eb7c1585d47e05026c4f2 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 24 Nov 2004 16:11:35 +0000 Subject: HTTP "auth done right". See lib/README.httpauth --- lib/http.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 49 deletions(-) (limited to 'lib/http.c') diff --git a/lib/http.c b/lib/http.c index 50638f5bf..1277fa21d 100644 --- a/lib/http.c +++ b/lib/http.c @@ -192,6 +192,80 @@ static bool pickoneauth(struct auth *pick) return picked; } +/* + * perhapsrewind() + * + * If we are doing POST or PUT { + * If we have more data to send { + * If we are doing NTLM { + * Keep sending since we must not disconnect + * } + * else { + * If there is more than just a little data left to send, close + * the current connection by force. + * } + * } + * If we have sent any data { + * If we don't have track of all the data { + * call app to tell it to rewind + * } + * else { + * rewind internally so that the operation can restart fine + * } + * } + * } + */ +static CURLcode perhapsrewind(struct connectdata *conn) +{ + struct HTTP *http = conn->proto.http; + struct SessionHandle *data = conn->data; + curl_off_t bytessent = http->writebytecount; + curl_off_t expectsend = -1; /* default is unknown */ + + /* figure out how much data we are expected to send */ + switch(data->set.httpreq) { + case HTTPREQ_POST: + if(data->set.postfieldsize != -1) + expectsend = data->set.postfieldsize; + break; + case HTTPREQ_PUT: + if(data->set.infilesize != -1) + expectsend = data->set.infilesize; + break; + case HTTPREQ_POST_FORM: + expectsend = http->postsize; + break; + default: + break; + } + + conn->bits.rewindaftersend = FALSE; /* default */ + + if((expectsend == -1) || (expectsend > bytessent)) { + /* There is still data left to send */ + if((data->state.authproxy.picked == CURLAUTH_NTLM) ||/* using NTLM */ + (data->state.authhost.picked == CURLAUTH_NTLM) ) { + conn->bits.close = FALSE; /* don't close, keep on sending */ + + /* rewind data when completely done sending! */ + conn->bits.rewindaftersend = TRUE; + return CURLE_OK; + } + else { + /* If there is more than just a little data left to send, close the + * current connection by force. + */ + conn->bits.close = TRUE; + conn->size = 0; /* don't download any more than 0 bytes */ + } + } + + if(bytessent) + return Curl_readrewind(conn); + + return CURLE_OK; +} + /* * Curl_http_auth_act() gets called when a all HTTP headers have been received * and it checks what authentication methods that are available and decides @@ -211,25 +285,33 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) if(conn->bits.user_passwd && ((conn->keep.httpcode == 401) || - (conn->bits.authprobe && conn->keep.httpcode < 300))) { + (conn->bits.authneg && conn->keep.httpcode < 300))) { pickhost = pickoneauth(&data->state.authhost); if(!pickhost) data->state.authproblem = TRUE; } if(conn->bits.proxy_user_passwd && ((conn->keep.httpcode == 407) || - (conn->bits.authprobe && conn->keep.httpcode < 300))) { + (conn->bits.authneg && conn->keep.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy); if(!pickproxy) data->state.authproblem = TRUE; } - if(pickhost || pickproxy) + if(pickhost || pickproxy) { conn->newurl = strdup(data->change.url); /* clone URL */ + if((data->set.httpreq != HTTPREQ_GET) && + (data->set.httpreq != HTTPREQ_HEAD)) { + code = perhapsrewind(conn); + if(code) + return code; + } + } + else if((conn->keep.httpcode < 300) && (!data->state.authhost.done) && - conn->bits.authprobe) { + conn->bits.authneg) { /* no (known) authentication available, authentication is not "done" yet and no authentication seems to be required and @@ -273,29 +355,34 @@ Curl_http_output_auth(struct connectdata *conn, CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; char *auth=NULL; + struct auth *authhost; + struct auth *authproxy; curlassert(data); + authhost = &data->state.authhost; + authproxy = &data->state.authproxy; + if((conn->bits.httpproxy && conn->bits.proxy_user_passwd) || conn->bits.user_passwd) /* continue please */ ; else { - data->state.authhost.done = TRUE; - data->state.authproxy.done = TRUE; + authhost->done = TRUE; + authproxy->done = TRUE; return CURLE_OK; /* no authentication with no user or password */ } - if(data->state.authhost.want && !data->state.authhost.picked) + if(authhost->want && !authhost->picked) /* The app has selected one or more methods, but none has been picked so far by a server round-trip. Then we set the picked one to the want one, and if this is one single bit it'll be used instantly. */ - data->state.authhost.picked = data->state.authhost.want; + authhost->picked = authhost->want; - if(data->state.authproxy.want && !data->state.authproxy.picked) + if(authproxy->want && !authproxy->picked) /* The app has selected one or more methods, but none has been picked so far by a proxy round-trip. Then we set the picked one to the want one, and if this is one single bit it'll be used instantly. */ - data->state.authproxy.picked = data->state.authproxy.want; + authproxy->picked = authproxy->want; /* To prevent the user+password to get sent to other than the original host due to a location-follow, we do some weirdo checks here */ @@ -308,7 +395,7 @@ Curl_http_output_auth(struct connectdata *conn, if (conn->bits.httpproxy && (conn->bits.tunnel_proxy == proxytunnel)) { #ifdef USE_SSLEAY - if(data->state.authproxy.want == CURLAUTH_NTLM) { + if(authproxy->want == CURLAUTH_NTLM) { auth=(char *)"NTLM"; result = Curl_output_ntlm(conn, TRUE); if(result) @@ -316,7 +403,7 @@ Curl_http_output_auth(struct connectdata *conn, } else #endif - if(data->state.authproxy.want == CURLAUTH_BASIC) { + if(authproxy->want == CURLAUTH_BASIC) { /* Basic */ if(conn->bits.proxy_user_passwd && !checkheaders(data, "Proxy-authorization:")) { @@ -325,10 +412,12 @@ Curl_http_output_auth(struct connectdata *conn, if(result) return result; } - data->state.authproxy.done = TRUE; + /* NOTE: Curl_output_basic() should set 'done' TRUE, as the other auth + functions work that way */ + authproxy->done = TRUE; } #ifndef CURL_DISABLE_CRYPTO_AUTH - else if(data->state.authproxy.want == CURLAUTH_DIGEST) { + else if(authproxy->want == CURLAUTH_DIGEST) { auth=(char *)"Digest"; result = Curl_output_digest(conn, TRUE, /* proxy */ @@ -338,31 +427,36 @@ Curl_http_output_auth(struct connectdata *conn, return result; } #endif - infof(data, "Proxy auth using %s with user '%s'\n", - auth, conn->proxyuser?conn->proxyuser:""); + if(auth) { + infof(data, "Proxy auth using %s with user '%s'\n", + auth, conn->proxyuser?conn->proxyuser:""); + authproxy->multi = !authproxy->done; + } + else + authproxy->multi = FALSE; } else /* we have no proxy so let's pretend we're done authenticating with it */ - data->state.authproxy.done = TRUE; + authproxy->done = TRUE; /* Send web authentication header if needed */ { auth = NULL; #ifdef HAVE_GSSAPI - if((data->state.authhost.want == CURLAUTH_GSSNEGOTIATE) && + if((authhost->want == CURLAUTH_GSSNEGOTIATE) && data->state.negotiate.context && !GSS_ERROR(data->state.negotiate.status)) { auth=(char *)"GSS-Negotiate"; result = Curl_output_negotiate(conn); if (result) return result; - data->state.authhost.done = TRUE; + authhost->done = TRUE; } else #endif #ifdef USE_SSLEAY - if(data->state.authhost.picked == CURLAUTH_NTLM) { + if(authhost->picked == CURLAUTH_NTLM) { auth=(char *)"NTLM"; result = Curl_output_ntlm(conn, FALSE); if(result) @@ -372,7 +466,7 @@ Curl_http_output_auth(struct connectdata *conn, #endif { #ifndef CURL_DISABLE_CRYPTO_AUTH - if(data->state.authhost.picked == CURLAUTH_DIGEST) { + if(authhost->picked == CURLAUTH_DIGEST) { auth=(char *)"Digest"; result = Curl_output_digest(conn, FALSE, /* not a proxy */ @@ -382,7 +476,7 @@ Curl_http_output_auth(struct connectdata *conn, return result; } else #endif - if(data->state.authhost.picked == CURLAUTH_BASIC) { + if(authhost->picked == CURLAUTH_BASIC) { if(conn->bits.user_passwd && !checkheaders(data, "Authorization:")) { auth=(char *)"Basic"; @@ -391,16 +485,21 @@ Curl_http_output_auth(struct connectdata *conn, return result; } /* basic is always ready */ - data->state.authhost.done = TRUE; + authhost->done = TRUE; } } - if(auth) + if(auth) { infof(data, "Server auth using %s with user '%s'\n", auth, conn->user); + + authhost->multi = !authhost->done; + } + else + authhost->multi = FALSE; } } else - data->state.authhost.done = TRUE; + authhost->done = TRUE; return result; } @@ -1304,20 +1403,15 @@ CURLcode Curl_http(struct connectdata *conn) if(result) return result; - if((!data->state.authhost.done || !data->state.authproxy.done ) && - (httpreq != HTTPREQ_GET)) { - /* Until we are authenticated, we switch over to HEAD. Unless its a GET - we want to do. The explanation for this is rather long and boring, but - the point is that it can't be done otherwise without risking having to - send the POST or PUT data multiple times. */ - httpreq = HTTPREQ_HEAD; - request = (char *)"HEAD"; - conn->bits.no_body = TRUE; - conn->bits.authprobe = TRUE; /* this is a request done to probe for - authentication methods */ + if((data->state.authhost.multi || data->state.authproxy.multi) && + (httpreq != HTTPREQ_GET) && + (httpreq != HTTPREQ_HEAD)) { + /* Auth is required and we are not authenticated yet. Make a PUT or POST + with content-length zero as a "probe". */ + conn->bits.authneg = TRUE; } else - conn->bits.authprobe = FALSE; + conn->bits.authneg = FALSE; Curl_safefree(conn->allocptr.ref); if(data->change.referer && !checkheaders(data, "Referer:")) @@ -1759,7 +1853,7 @@ CURLcode Curl_http(struct connectdata *conn) switch(httpreq) { case HTTPREQ_POST_FORM: - if(!http->sendit) { + if(!http->sendit || conn->bits.authneg) { /* nothing to post! */ result = add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); if(result) @@ -1857,11 +1951,16 @@ CURLcode Curl_http(struct connectdata *conn) case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - if((data->set.infilesize>0) && !conn->bits.upload_chunky) { + if(conn->bits.authneg) + postsize = 0; + else + postsize = data->set.infilesize; + + if((postsize != -1) && !conn->bits.upload_chunky) { /* only add Content-Length if not uploading chunked */ result = add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T "\r\n", /* size */ - data->set.infilesize ); + "Content-Length: %" FORMAT_OFF_T "\r\n", + postsize ); if(result) return result; } @@ -1884,19 +1983,19 @@ CURLcode Curl_http(struct connectdata *conn) return result; /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); + Curl_pgrsSetUploadSize(data, postsize); /* this sends the buffer and frees all the buffer resources */ result = add_buffer_send(req_buffer, conn, &data->info.request_size); if(result) - failf(data, "Failed sending POST request"); + failf(data, "Failed sending PUT request"); else /* prepare for transfer */ result = Curl_Transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - FIRSTSOCKET, - &http->writebytecount); + postsize?FIRSTSOCKET:-1, + postsize?&http->writebytecount:NULL); if(result) return result; break; @@ -1904,10 +2003,13 @@ CURLcode Curl_http(struct connectdata *conn) case HTTPREQ_POST: /* this is the simple POST, using x-www-form-urlencoded style */ - /* store the size of the postfields */ - postsize = (data->set.postfieldsize != -1)? - data->set.postfieldsize: - (data->set.postfields?(curl_off_t)strlen(data->set.postfields):0); + if(conn->bits.authneg) + postsize = 0; + else + /* figure out the size of the postfields */ + postsize = (data->set.postfieldsize != -1)? + data->set.postfieldsize: + (data->set.postfields?(curl_off_t)strlen(data->set.postfields):0); if(!conn->bits.upload_chunky) { /* We only set Content-Length and allow a custom Content-Length if -- cgit v1.2.3