cURL / Mailing Lists / curl-library / Single Mail

curl-library

How to provoke 407/401 in regerssion tests? (was: Re: Obtaining realm)

From: Josef Wolf <jw_at_raven.inka.de>
Date: Mon, 17 Nov 2008 22:36:39 +0100

On Wed, Nov 05, 2008 at 07:58:37PM +0100, Daniel Stenberg wrote:
> On Wed, 5 Nov 2008, Maxi Combina wrote:
>
> >I am using libcurl 7.19.0. I am behind a proxy with Basic authentication,
> >and need to obtain the realm that the proxy sends me with a 407 message.
> >If I set CURLOPT_HEADER to 1 and set CURLOPT_WRITEFUNCTION I can see that
> >the proxy sends basically this:
> >
> >HTTP/1.1 407 Proxy Authentication Required
> >Proxy-Authenticate: Basic realm="my.domain"
> >
> >I could parse that and I'm done.
>
> Funny how timely you ask this, as Josef Wolf is currently doing HTTP auth
> work that also needs the realm parsed...

Sorry for the late response. While the main patch seems to (mostly)
work, I got somewhat stuck with the regression test and I have not
found the time yet to dive deeply enough into the test architecture
to see how to integrate the new test. I just can't figure out how
to provoke 407/401 responses with retries from the test infrastructure.

> but you're right. There's
> currently no "easy" way to get the realm other than parsing the headers
> yourself.

With my patch the realm would be accessible in the credential-callback
(though this part is not implemented yet).

> >Has this feature already been implemented? If not, I _might_ have some
> >time to implement it, but would greatly appreciate help for diving into
> >libcurl code.
>
> It isn't available yet, but I'm sure you can join in and help Josef Wolf
> get his work done by doing a little realm parsing code.

I'd rather get regression test working first. Anyway, I attach my
current patch at the end of this mail.

My current approach for the regression test is based on test503.
I have created curl/tests/data/test560 and curl/tests/libtest/lib560.c
(attached below). But I just can't figure out how to provoke a 407
from the test-server. I always get "200 OK", no matter whether the
credentials are included in the request or not. Any hints?

# this is curl/tests/data/test560
<testcase>
<info>
<keywords>
HTTP
HTTP GET
HTTP CONNECT
HTTP proxy
HTTP proxy Basic auth
proxytunnel
multi
</keywords>
</info>

# Server-side
<reply>
<data>
HTTP/1.1 407 Authorization Required to proxy me my dear swsclose
Proxy-Authenticate: Basic realm="weirdorealm"

And you should ignore this data
</data>
<datacheck>
HTTP/1.1 407 Authorization Required to proxy me my dear swsclose
Proxy-Authenticate: Basic realm="weirdorealm"

HTTP/1.1 200 OK swsclose
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"

</datacheck>
</reply>

# Client-side
<client>
<server>
http
</server>
# tool is what to use instead of 'curl'
<tool>
lib560
</tool>

 <name>
simple multi http:// through proxytunnel with authentication info
 </name>
 <command>
http://%HOSTIP:%HTTPSPORT/560 %HOSTIP:%HTTPPORT
</command>
<file name="log/test560.txt">
foo
   bar
bar
   foo
moo
</file>
</client>

# Verify data after the test has been "shot"
<verify>
<protocol>
CONNECT %HOSTIP:%HTTPSPORT HTTP/1.0
Host: %HOSTIP:%HTTPSPORT
Proxy-Authorization: Basic dGVzdDppbmc=
Proxy-Connection: Keep-Alive

GET /560 HTTP/1.1
Authorization: Basic dGVzdDppbmc=
Host: %HOSTIP:%HTTPSPORT
Accept: */*

</protocol>
</verify>
</testcase>

/*****************************************************************************
 * _ _ ____ _
 * Project ___| | | | _ \| |
 * / __| | | | |_) | |
 * | (__| |_| | _ <| |___
 * \___|\___/|_| \_\_____|
 *
 * $Id: lib560.c,v 1.19 2008-09-20 04:26:56 yangtse Exp $
 */

#include "test.h"

#include <sys/types.h>

#include "testutil.h"
#include "memdebug.h"

#define MAIN_LOOP_HANG_TIMEOUT 90 * 1000
#define MULTI_PERFORM_HANG_TIMEOUT 60 * 1000

static char *orig_userdata;

static bool get_credentials (CURL *data, void *passed_userdata,
                             bool isproxy,
                             char *userbuf, size_t ubuflen,
                             char *passbuf, size_t pbuflen)
{
  if (passed_userdata!=orig_userdata) {
    fprintf (stderr, "userdata lost\n");
  }

  if (isproxy) {
    strcpy (userbuf, "foo");
    strcpy (passbuf, "bar");
  } else {
    strcpy (userbuf, "basic");
    strcpy (passbuf, "alot");
  }

  return TRUE;
}

/*
 * Source code in here hugely as reported in bug report 651460 by
 * Christopher R. Palmer.
 *
 * Use multi interface to get HTTPS document over proxy, and provide
 * auth info.
 */

int test(char *URL)
{
  CURL *c;
  CURLM *m;
  int res = 0;
  int running;
  char done = FALSE;
  struct timeval ml_start;
  struct timeval mp_start;
  char ml_timedout = FALSE;
  char mp_timedout = FALSE;

  orig_userdata=strdup("some wired pointer");

  if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
    fprintf(stderr, "curl_global_init() failed\n");
    return TEST_ERR_MAJOR_BAD;
  }

  if ((c = curl_easy_init()) == NULL) {
    fprintf(stderr, "curl_easy_init() failed\n");
    curl_global_cleanup();
    return TEST_ERR_MAJOR_BAD;
  }

  curl_easy_setopt(c, CURLOPT_PROXY, libtest_arg2); /* set in first.c */
  curl_easy_setopt(c, CURLOPT_URL, URL);
  curl_easy_setopt(c, CURLOPT_HTTPPROXYTUNNEL, 1L);
  curl_easy_setopt(c, CURLOPT_HEADER, 1L);
  curl_easy_setopt(c, CURLOPT_CREDENTIALFUNCTION, get_credentials);
  curl_easy_setopt(c, CURLOPT_CREDENTIALDATA, orig_userdata);

  if ((m = curl_multi_init()) == NULL) {
    fprintf(stderr, "curl_multi_init() failed\n");
    curl_easy_cleanup(c);
    curl_global_cleanup();
    return TEST_ERR_MAJOR_BAD;
  }

  if ((res = (int)curl_multi_add_handle(m, c)) != CURLM_OK) {
    fprintf(stderr, "curl_multi_add_handle() failed, "
            "with code %d\n", res);
    curl_multi_cleanup(m);
    curl_easy_cleanup(c);
    curl_global_cleanup();
    return TEST_ERR_MAJOR_BAD;
  }

  ml_timedout = FALSE;
  ml_start = tutil_tvnow();

  while(!done) {
    fd_set rd, wr, exc;
    int max_fd;
    struct timeval interval;

    interval.tv_sec = 1;
    interval.tv_usec = 0;

    if (tutil_tvdiff(tutil_tvnow(), ml_start) >
        MAIN_LOOP_HANG_TIMEOUT) {
      ml_timedout = TRUE;
      break;
    }
    mp_timedout = FALSE;
    mp_start = tutil_tvnow();

    while (res == CURLM_CALL_MULTI_PERFORM) {
      res = (int)curl_multi_perform(m, &running);
      if (tutil_tvdiff(tutil_tvnow(), mp_start) >
          MULTI_PERFORM_HANG_TIMEOUT) {
        mp_timedout = TRUE;
        break;
      }
      if (running <= 0) {
        done = TRUE;
        break;
      }
    }
    if (mp_timedout || done)
      break;

    if (res != CURLM_OK) {
      fprintf(stderr, "not okay???\n");
      break;
    }

    FD_ZERO(&rd);
    FD_ZERO(&wr);
    FD_ZERO(&exc);
    max_fd = 0;

    if (curl_multi_fdset(m, &rd, &wr, &exc, &max_fd) != CURLM_OK) {
      fprintf(stderr, "unexpected failured of fdset.\n");
      res = 89;
      break;
    }

    if (select_test(max_fd+1, &rd, &wr, &exc, &interval) == -1) {
      fprintf(stderr, "bad select??\n");
      res = 95;
      break;
    }

    res = CURLM_CALL_MULTI_PERFORM;
  }

  if (ml_timedout || mp_timedout) {
    if (ml_timedout) fprintf(stderr, "ml_timedout\n");
    if (mp_timedout) fprintf(stderr, "mp_timedout\n");
    fprintf(stderr, "ABORTING TEST, since it seems "
            "that it would have run forever.\n");
    res = TEST_ERR_RUNS_FOREVER;
  }

  curl_multi_remove_handle(m, c);
  curl_easy_cleanup(c);
  curl_multi_cleanup(m);
  curl_global_cleanup();

  return res;
}

Here's the current patch (without the test yet):

Index: include/curl/curl.h
===================================================================
RCS file: /cvsroot/curl/curl/include/curl/curl.h,v
retrieving revision 1.370
diff -u -r1.370 curl.h
--- include/curl/curl.h 17 Oct 2008 03:59:02 -0000 1.370
+++ include/curl/curl.h 17 Nov 2008 21:01:07 -0000
@@ -201,6 +201,11 @@
                                       size_t nitems,
                                       void *instream);
 
+typedef bool (*curl_credential_callback)(CURL *handle, void *userdata,
+ bool isproxy,
+ char *userbuf, size_t ubuflen,
+ char *passbuf, size_t pbuflen);
+
 typedef enum {
   CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */
   CURLSOCKTYPE_LAST /* never use */
@@ -1149,6 +1154,13 @@
   CINIT(PROXYUSERNAME, OBJECTPOINT, 175),
   CINIT(PROXYPASSWORD, OBJECTPOINT, 176),
 
+ /* Function that will be called to retrieve credentials from the
+ application. */
+ CINIT(CREDENTIALFUNCTION, FUNCTIONPOINT, 177),
+
+ /* Userdata to pass to the credential callback */
+ CINIT(CREDENTIALDATA, OBJECTPOINT, 178),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
Index: include/curl/typecheck-gcc.h
===================================================================
RCS file: /cvsroot/curl/curl/include/curl/typecheck-gcc.h,v
retrieving revision 1.8
diff -u -r1.8 typecheck-gcc.h
--- include/curl/typecheck-gcc.h 17 Oct 2008 03:59:02 -0000 1.8
+++ include/curl/typecheck-gcc.h 17 Nov 2008 21:01:07 -0000
@@ -48,6 +48,9 @@
       _curl_easy_setopt_err_write_callback(); \
     if ((_curl_opt) == CURLOPT_READFUNCTION && !_curl_is_read_cb(value)) \
       _curl_easy_setopt_err_read_cb(); \
+ if ((_curl_opt) == CURLOPT_CREDENTIALFUNCTION && \
+ !_curl_is_credential_cb(value)) \
+ _curl_easy_setopt_err_credential_cb(); \
     if ((_curl_opt) == CURLOPT_IOCTLFUNCTION && !_curl_is_ioctl_cb(value)) \
       _curl_easy_setopt_err_ioctl_cb(); \
     if ((_curl_opt) == CURLOPT_SOCKOPTFUNCTION && !_curl_is_sockopt_cb(value))\
@@ -133,6 +136,8 @@
   "curl_easy_setopt expects a curl_write_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_read_cb,
   "curl_easy_setopt expects a curl_read_callback argument for this option")
+_CURL_WARNING(_curl_easy_setopt_err_credential_cb,
+ "curl_easy_setopt expects a curl_credential_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_ioctl_cb,
   "curl_easy_setopt expects a curl_ioctl_callback argument for this option")
 _CURL_WARNING(_curl_easy_setopt_err_sockopt_cb,
@@ -237,6 +242,11 @@
   ((option) == CURLOPT_HEADERFUNCTION || \
    (option) == CURLOPT_WRITEFUNCTION)
 
+/* evaluates to true if option takes a curl_write_callback argument */
+#define _curl_is_credential_cb_option(option) \
+ ((option) == CURLOPT_CREDENTIALFUNCTION || \
+ 0)
+
 /* evaluates to true if option takes a curl_conv_callback argument */
 #define _curl_is_conv_cb_option(option) \
   ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION || \
@@ -247,6 +257,7 @@
 #define _curl_is_cb_data_option(option) \
   ((option) == CURLOPT_WRITEDATA || \
    (option) == CURLOPT_READDATA || \
+ (option) == CURLOPT_CREDENTIALDATA || \
    (option) == CURLOPT_IOCTLDATA || \
    (option) == CURLOPT_SOCKOPTDATA || \
    (option) == CURLOPT_OPENSOCKETDATA || \
@@ -424,6 +435,10 @@
                                        const void*);
 typedef size_t (_curl_write_callback6)(const void *, size_t, size_t, FILE*);
 
+/* evaluates to true if expr is of type curl_credential_callback */
+#define _curl_is_credential_cb(expr) \
+ __builtin_types_compatible_p(__typeof__(expr),curl_credential_callback)
+
 /* evaluates to true if expr is of type curl_ioctl_callback or "similar" */
 #define _curl_is_ioctl_cb(expr) \
   (_curl_is_NULL(expr) || \
Index: lib/http.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/http.c,v
retrieving revision 1.402
diff -u -r1.402 http.c
--- lib/http.c 28 Oct 2008 23:34:19 -0000 1.402
+++ lib/http.c 17 Nov 2008 21:01:09 -0000
@@ -419,6 +419,62 @@
   return CURLE_OK;
 }
 
+static bool get_credentials (struct SessionHandle *data, bool isproxy,
+ char *userbuf, size_t ubuflen,
+ char *passbuf, size_t pbuflen)
+{
+ /* credential caching would go here
+ */
+
+ return data->set.credential_func(data, data->set.credential_data,
+ isproxy,
+ userbuf, ubuflen,
+ passbuf, pbuflen);
+}
+
+static bool retry_auth (struct connectdata *conn,
+ bool *passwd_used,
+ bool isproxy)
+{
+ struct SessionHandle *data = conn->data;
+ struct UrlState *state = &data->state;
+ struct auth *authinfo = isproxy ? &state->authproxy : &state->authhost;
+ int httpcode = isproxy ? 407 : 401;
+ int pick = FALSE;
+ char userbuf[1024];
+ char passbuf[1024]; /* maybe this should go to non-swappable memory */
+
+ if ((data->req.httpcode == httpcode) ||
+ (conn->bits.authneg && data->req.httpcode < 300)) {
+
+ if (data->set.credential_func) { /* callback is installed */
+ bool got_cred = get_credentials (data, isproxy,
+ userbuf, sizeof(userbuf)-1,
+ passbuf, sizeof(passbuf)-1);
+ userbuf[sizeof(userbuf)-1] = 0;
+ passbuf[sizeof(passbuf)-1] = 0;
+
+ if (curl_easy_setopt (data, CURLOPT_USERNAME, userbuf) != CURLE_OK)
+ return FALSE;
+ if (curl_easy_setopt (data, CURLOPT_PASSWORD, passbuf) != CURLE_OK)
+ return FALSE;
+
+ memset (passbuf, 0, sizeof(passbuf));
+ *passwd_used = got_cred;
+ }
+
+ if (!*passwd_used)
+ return FALSE;
+
+ pick = pickoneauth(authinfo);
+
+ if (!pick)
+ state->authproblem = TRUE;
+ }
+
+ return pick;
+}
+
 /*
  * Curl_http_auth_act() gets called when all HTTP headers have been received
  * and it checks what authentication methods that are available and decides
@@ -429,8 +485,8 @@
 CURLcode Curl_http_auth_act(struct connectdata *conn)
 {
   struct SessionHandle *data = conn->data;
- bool pickhost = FALSE;
- bool pickproxy = FALSE;
+ bool retryhost = FALSE;
+ bool retryproxy = FALSE;
   CURLcode code = CURLE_OK;
 
   if(100 <= data->req.httpcode && 199 >= data->req.httpcode)
@@ -440,22 +496,10 @@
   if(data->state.authproblem)
     return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK;
 
- if(conn->bits.user_passwd &&
- ((data->req.httpcode == 401) ||
- (conn->bits.authneg && data->req.httpcode < 300))) {
- pickhost = pickoneauth(&data->state.authhost);
- if(!pickhost)
- data->state.authproblem = TRUE;
- }
- if(conn->bits.proxy_user_passwd &&
- ((data->req.httpcode == 407) ||
- (conn->bits.authneg && data->req.httpcode < 300))) {
- pickproxy = pickoneauth(&data->state.authproxy);
- if(!pickproxy)
- data->state.authproblem = TRUE;
- }
+ retryhost = retry_auth (conn, &conn->bits.user_passwd, FALSE);
+ retryproxy = retry_auth (conn, &conn->bits.proxy_user_passwd, TRUE);
 
- if(pickhost || pickproxy) {
+ if(retryhost || retryproxy) {
     data->req.newurl = strdup(data->change.url); /* clone URL */
     if(!data->req.newurl)
       return CURLE_OUT_OF_MEMORY;
Index: lib/url.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/url.c,v
retrieving revision 1.769
diff -u -r1.769 url.c
--- lib/url.c 23 Oct 2008 11:49:19 -0000 1.769
+++ lib/url.c 17 Nov 2008 21:01:12 -0000
@@ -680,6 +680,10 @@
     /* use fread as default function to read input */
     data->set.fread_func = (curl_read_callback)fread;
 
+ /* do not install default credential callback */
+ data->set.credential_func = NULL;
+ data->set.credential_data = NULL;
+
     /* don't use a seek function by default */
     data->set.seek_func = ZERO_NULL;
     data->set.seek_client = ZERO_NULL;
@@ -1701,6 +1705,18 @@
       /* When set to NULL, reset to our internal default function */
       data->set.fread_func = (curl_read_callback)fread;
     break;
+ case CURLOPT_CREDENTIALFUNCTION:
+ /*
+ * Callback to retrieve credentials
+ */
+ data->set.credential_func = va_arg(param, curl_credential_callback);
+ break;
+ case CURLOPT_CREDENTIALDATA:
+ /*
+ * User data to pass to credential retrieval callback
+ */
+ data->set.credential_data = va_arg(param, void *);
+ break;
   case CURLOPT_SEEKFUNCTION:
     /*
      * Seek callback. Might be NULL.
Index: lib/urldata.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/urldata.h,v
retrieving revision 1.393
diff -u -r1.393 urldata.h
--- lib/urldata.h 25 Oct 2008 05:41:02 -0000 1.393
+++ lib/urldata.h 17 Nov 2008 21:01:12 -0000
@@ -1359,6 +1359,7 @@
   void *out; /* the fetched file goes here */
   void *in; /* the uploaded file is read from here */
   void *writeheader; /* write the header to this if non-NULL */
+ void *credential_data; /* User data to pass to credential callback */
   long use_port; /* which port to use (when not using default) */
   long httpauth; /* what kind of HTTP authentication to use (bitmask) */
   long proxyauth; /* what kind of proxy authentication to use (bitmask) */
@@ -1381,6 +1382,8 @@
   curl_write_callback fwrite_func; /* function that stores the output */
   curl_write_callback fwrite_header; /* function that stores headers */
   curl_read_callback fread_func; /* function that reads the input */
+ curl_credential_callback credential_func; /* function to retrieve
+ credentials */
   curl_progress_callback fprogress; /* function for progress information */
   curl_debug_callback fdebug; /* function that write informational data */
   curl_ioctl_callback ioctl_func; /* function for I/O control */
Received on 2008-11-17