cURL / Mailing Lists / curl-library / Single Mail

curl-library

Re: [Patch]: Prepare for credential retrieving callback.

From: Josef Wolf <jw_at_raven.inka.de>
Date: Mon, 3 Nov 2008 22:01:59 +0100

On Fri, Oct 31, 2008 at 12:03:50AM +0100, Daniel Stenberg wrote:

Thanks for the comments, Daniel!

> This won't work. Let me do a quick description:

OK, I'll let the structures alone.

So here's a second try. Still not "production" quality, but this time
doing the real change and without messing with the data structures.

This version introduces CURLOPT_CREDENTIALFUNCTION to install the
callback. There are no functional changes as long as the callback
is not installed. Therefore all tests pass as long as the hunk
to src/main.c is not applied. When the hunk is applied, then the
retrieval (run manually) works as expected, but a number of test-cases
fail:

> TESTDONE: 426 tests out of 468 reported OK: 91%
> TESTFAIL: These test cases failed:
> 64 65 67 68 69 70 72 81 88 89 90 91 150 153 154 155 159
> 167 168 169 206 209 213 239 243 245 246 258 259 265 267
> 273 1001 1002 1008 1021 1030 1060 1061 1071 1075 1079

The failure of those test cases was to be expected, since the current
stub provides only a fixed (and wrong) user/password combination.
The question here is, how to deal with those test cases. I think
they should be duplicated. The original versions of the test cases
should be run without installing the callback, and the duplicates
should be fixed to run properly with the installed callback. But to
make this work, the curl utility should be able to detect whether it
is running a test case, whether to install the callback or not, and
which credentials to provide. I guess there's no mechanism to handle
this in test cases yet?

Then we come to one more question (which Daniel already mentioned
at the very beginning): how should this be handled in the curl utility?
Probably it would make sense to introduce a command line switch to
activate the interactive callback. After all, many people use curl
in non-interactive scripts and we do not want to break those scripts
by letting them sit and wait for interactive user input. Using a
command line switch might also be the solution to the test case
questions mentioned in the previous paragraph.

BTW: I can't seem to find a way for properly determine the realm.
     AFAICS, the realm is extracted only for Digest?

Comments? Suggestions?

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 3 Nov 2008 17:06:03 -0000
@@ -201,6 +201,10 @@
                                       size_t nitems,
                                       void *instream);
 
+typedef bool (*curl_credential_callback)(CURL *handle, 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 +1153,10 @@
   CINIT(PROXYUSERNAME, OBJECTPOINT, 175),
   CINIT(PROXYPASSWORD, OBJECTPOINT, 176),
 
+ /* Function that will be called to retrieve credentials from the
+ application. */
+ CINIT(CREDENTIALFUNCTION, FUNCTIONPOINT, 177),
+
   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 3 Nov 2008 17:06:04 -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 || \
@@ -424,6 +434,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 3 Nov 2008 17:06:06 -0000
@@ -419,6 +419,61 @@
   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, 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 +484,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 +495,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 3 Nov 2008 17:06:08 -0000
@@ -680,6 +680,9 @@
     /* 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;
+
     /* don't use a seek function by default */
     data->set.seek_func = ZERO_NULL;
     data->set.seek_client = ZERO_NULL;
@@ -1701,6 +1704,12 @@
       /* 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_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 3 Nov 2008 17:06:09 -0000
@@ -1381,6 +1381,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 */
Index: src/main.c
===================================================================
RCS file: /cvsroot/curl/curl/src/main.c,v
retrieving revision 1.499
diff -u -r1.499 main.c
--- src/main.c 28 Oct 2008 19:51:04 -0000 1.499
+++ src/main.c 3 Nov 2008 17:06:10 -0000
@@ -1488,6 +1488,22 @@
   }
 }
 
+static bool credential_callback(CURL *handle, bool isproxy,
+ char *userbuf, size_t ubuflen,
+ char *passbuf, size_t pbuflen)
+{
+ /* For now, this is just a stub to have something to play as long as the
+ functionality in the library is not done */
+
+ if (isproxy) {
+ return FALSE;
+ } else {
+ strcpy(userbuf, "test");
+ strcpy(passbuf, "foobar");
+ return TRUE;
+ }
+}
+
 static ParameterError add2list(struct curl_slist **list,
                                const char *ptr)
 {
@@ -3964,6 +3980,8 @@
   }
   config->easy = curl;
 
+ curl_easy_setopt (curl, CURLOPT_CREDENTIALFUNCTION, credential_callback);
+
   memset(&outs,0,sizeof(outs));
 
   config->outs = &outs;
Received on 2008-11-03