diff options
| author | Daniel Stenberg <daniel@haxx.se> | 2004-11-24 16:11:35 +0000 | 
|---|---|---|
| committer | Daniel Stenberg <daniel@haxx.se> | 2004-11-24 16:11:35 +0000 | 
| commit | 3e1caa61859a6057a65eb7c1585d47e05026c4f2 (patch) | |
| tree | 3c1aed832eaf3cd917a90d8c8558106f8ea40ec5 /lib | |
| parent | 50eafb7668de871451ae556e5405f5ab01c937ff (diff) | |
HTTP "auth done right". See lib/README.httpauth
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/README.httpauth | 5 | ||||
| -rw-r--r-- | lib/http.c | 200 | ||||
| -rw-r--r-- | lib/http.h | 19 | ||||
| -rw-r--r-- | lib/transfer.c | 55 | ||||
| -rw-r--r-- | lib/transfer.h | 2 | ||||
| -rw-r--r-- | lib/url.c | 12 | ||||
| -rw-r--r-- | lib/urldata.h | 16 | 
7 files changed, 250 insertions, 59 deletions
diff --git a/lib/README.httpauth b/lib/README.httpauth index baa279a7f..960504510 100644 --- a/lib/README.httpauth +++ b/lib/README.httpauth @@ -19,6 +19,11 @@     determined. Possibly the current transfer speed should be taken into     account as well. +   NOTE: if the size of the POST data is less than MAX_INITIAL_POST_SIZE (when +   CURLOPT_POSTFIELDS is used), libcurl will send everything in one single +   write() (all request-headers and request-body) and thus it will +   unconditionally send the full post data here. +  2. PUT/POST with multi-pass auth but not yet completely negotiated:     Send a PUT/POST request, we know that it will be rejected and thus we claim diff --git a/lib/http.c b/lib/http.c index 50638f5bf..1277fa21d 100644 --- a/lib/http.c +++ b/lib/http.c @@ -193,6 +193,80 @@ static bool pickoneauth(struct auth *pick)  }  /* + * 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   * which one (if any) to use. It will set 'newurl' if an auth metod was @@ -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 diff --git a/lib/http.h b/lib/http.h index d321333fe..57f1d115e 100644 --- a/lib/http.h +++ b/lib/http.h @@ -57,13 +57,20 @@ int Curl_http_should_fail(struct connectdata *conn);     public curl/curl.h header. */  #define CURLAUTH_PICKNONE (1<<30) /* don't use auth */ -/* MAX_INITIAL_POST_SIZE indicates the number of kilobytes that will be sent -   in the initial part of a multi-part POST message. This is primarily for -   OpenVMS where the maximum number of bytes allowed per I/O is 64K.  For -   other systems that do not define this, the default is (as it was -   previously) 100K. */ +/* MAX_INITIAL_POST_SIZE indicates the number of bytes that will make the POST +   data get included in the initial data chunk sent to the server. If the +   data is larger than this, it will automatically get split up in multiple +   system calls. + +   This value used to be fairly big (100K), but we must take into account that +   if the server rejects the POST due for authentication reasons, this data +   will always be uncondtionally sent and thus it may not be larger than can +   always be afforded to send twice. + +   It must not be greater than 64K to work on VMS. +*/  #ifndef MAX_INITIAL_POST_SIZE -#define MAX_INITIAL_POST_SIZE (100*1024) +#define MAX_INITIAL_POST_SIZE 1024  #endif  #endif diff --git a/lib/transfer.c b/lib/transfer.c index b52e40ef1..5e8333538 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -193,6 +193,55 @@ checkhttpprefix(struct SessionHandle *data,    return FALSE;  } +/* + * Curl_readrewind() rewinds the read stream. This typically (so far) only + * used for HTTP POST/PUT with multi-pass authentication when a sending was + * denied and a resend is necessary. + */ +CURLcode Curl_readrewind(struct connectdata *conn) +{ +  struct SessionHandle *data = conn->data; + +  conn->bits.rewindaftersend = FALSE; /* we rewind now */ + +  /* We have sent away data. If not using CURLOPT_POSTFIELDS or +     CURLOPT_HTTPPOST, call app to rewind +  */ +  if(data->set.postfields || +     (data->set.httpreq == HTTPREQ_POST_FORM)) +    ; /* do nothing */ +  else { +    if(data->set.ioctl) { +      curlioerr err; + +      err = data->set.ioctl(data, CURLIOCMD_RESTARTREAD, +                            data->set.ioctl_client); +      infof(data, "the ioctl callback returned %d\n", (int)err); + +      if(err) { +        /* FIXME: convert to a human readable error message */ +        failf(data, "ioctl callback returned error %d\n", (int)err); +        return CURLE_SEND_FAIL_REWIND; +      } +    } +    else { +      /* If no CURLOPT_READFUNCTION is used, we know that we operate on a +         given FILE * stream and we can actually attempt to rewind that +         ourself with fseek() */ +      if(data->set.fread == (curl_read_callback)fread) { +        if(-1 != fseek(data->set.in, 0, SEEK_SET)) +          /* successful rewind */ +          return CURLE_OK; +      } + +      /* no callback set or failure aboe, makes us fail at once */ +      failf(data, "necessary data rewind wasn't possible\n"); +      return CURLE_SEND_FAIL_REWIND; +    } +  } +  return CURLE_OK; +} +  /*   * Curl_readwrite() is the low-level function to be called when data is to @@ -1163,6 +1212,12 @@ CURLcode Curl_readwrite(struct connectdata *conn,              /* done */              k->keepon &= ~KEEP_WRITE; /* we're done writing */              writedone = TRUE; + +            if(conn->bits.rewindaftersend) { +              result = Curl_readrewind(conn); +              if(result) +                return result; +            }              break;            } diff --git a/lib/transfer.h b/lib/transfer.h index 1b7eb828f..78a3e046b 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -34,7 +34,7 @@ void Curl_single_fdset(struct connectdata *conn,                         fd_set *exc_fd_set,                         int *max_fd);  CURLcode Curl_readwrite_init(struct connectdata *conn); - +CURLcode Curl_readrewind(struct connectdata *conn);  CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp);  /* This sets up a forthcoming transfer */ @@ -1099,6 +1099,18 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, ...)        /* When set to NULL, reset to our internal default function */        data->set.fread = (curl_read_callback)fread;      break; +  case CURLOPT_IOCTLFUNCTION: +    /* +     * I/O control callback. Might be NULL. +     */ +    data->set.ioctl = va_arg(param, curl_ioctl_callback); +    break; +  case CURLOPT_IOCTLDATA: +    /* +     * I/O control data pointer. Might be NULL. +     */ +    data->set.ioctl_client = va_arg(param, void *); +    break;    case CURLOPT_SSLCERT:      /*       * String that holds file name of the SSL certificate to use diff --git a/lib/urldata.h b/lib/urldata.h index 764ca3c51..e161e8083 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -319,9 +319,14 @@ struct ConnectBits {                           This is implicit when SSL-protocols are used through                           proxies, but can also be enabled explicitly by                           apps */ -  bool authprobe;     /* set TRUE when this transfer is done to probe for auth -                         types, as when asking for "any" type when speaking -                         HTTP */ +  bool authneg;       /* TRUE when the auth phase has started, which means +                         that we are creating a request with an auth header, +                         but it is not the final request in the auth +                         negotiation. */ +  bool rewindaftersend;/* TRUE when the sending couldn't be stopped even +                          though it will be discarded. When the whole send +                          operation is done, we must call the data rewind +                          callback. */  };  struct hostname { @@ -696,6 +701,9 @@ struct auth {                   resource */    bool done;  /* TRUE when the auth phase is done and ready to do the *actual*                   request */ +  bool multi; /* TRUE if this is not yet authenticated but within the auth +                 multipass negotiation */ +  };  struct UrlState { @@ -827,7 +835,9 @@ struct UserDefined {    curl_read_callback fread;          /* function that reads the input */    curl_progress_callback fprogress;  /* function for progress information */    curl_debug_callback fdebug;      /* function that write informational data */ +  curl_ioctl_callback ioctl;       /* function for I/O control */    void *progress_client; /* pointer to pass to the progress callback */ +  void *ioctl_client;   /* pointer to pass to the ioctl callback */    long timeout;         /* in seconds, 0 means no timeout */    long connecttimeout;  /* in seconds, 0 means no timeout */    long ftp_response_timeout; /* in seconds, 0 means no timeout */  | 
