]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: use credentials from transfer, not connection
authorDaniel Stenberg <daniel@haxx.se>
Fri, 12 Feb 2021 09:27:42 +0000 (10:27 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 13 Feb 2021 21:36:15 +0000 (22:36 +0100)
HTTP auth "accidentally" worked before this cleanup since the code would
always overwrite the connection credentials with the credentials from
the most recent transfer and since HTTP auth is typically done first
thing, this has not been an issue. It was still wrong and subject to
possible race conditions or future breakage if the sequence of functions
would change.

The data.set.str[] strings MUST remain unmodified exactly as set by the
user, and the credentials to use internally are instead set/updated in
state.aptr.*

Added test 675 to verify different credentials used in two requests done
over a reused HTTP connection, which previously behaved wrongly.

Fixes #6542
Closes #6545

12 files changed:
lib/http.c
lib/http_aws_sigv4.c
lib/http_digest.c
lib/http_digest.h
lib/http_ntlm.c
lib/setopt.c
lib/transfer.c
lib/url.c
lib/urldata.h
tests/data/Makefile.inc
tests/data/test134
tests/data/test675 [new file with mode: 0644]

index d6eceb7d58766feab71ef3171b80d6be08450e36..33e4c62e5f6f2b6ba690350fcd3ad7b094a697a9 100644 (file)
@@ -298,26 +298,27 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy)
 {
   size_t size = 0;
   char *authorization = NULL;
-  struct connectdata *conn = data->conn;
   char **userp;
   const char *user;
   const char *pwd;
   CURLcode result;
   char *out;
 
+  /* credentials are unique per transfer for HTTP, do not use the ones for the
+     connection */
   if(proxy) {
 #ifndef CURL_DISABLE_PROXY
     userp = &data->state.aptr.proxyuserpwd;
-    user = conn->http_proxy.user;
-    pwd = conn->http_proxy.passwd;
+    user = data->state.aptr.proxyuser;
+    pwd = data->state.aptr.proxypasswd;
 #else
     return CURLE_NOT_BUILT_IN;
 #endif
   }
   else {
     userp = &data->state.aptr.userpwd;
-    user = conn->user;
-    pwd = conn->passwd;
+    user = data->state.aptr.user;
+    pwd = data->state.aptr.passwd;
   }
 
   out = aprintf("%s:%s", user, pwd ? pwd : "");
@@ -709,7 +710,6 @@ output_auth_headers(struct Curl_easy *data,
   if(authstatus->picked == CURLAUTH_DIGEST) {
     auth = "Digest";
     result = Curl_output_digest(data,
-                                conn,
                                 proxy,
                                 (const unsigned char *)request,
                                 (const unsigned char *)path);
@@ -756,11 +756,14 @@ output_auth_headers(struct Curl_easy *data,
 #ifndef CURL_DISABLE_PROXY
     infof(data, "%s auth using %s with user '%s'\n",
           proxy ? "Proxy" : "Server", auth,
-          proxy ? (conn->http_proxy.user ? conn->http_proxy.user : "") :
-          (conn->user ? conn->user : ""));
+          proxy ? (data->state.aptr.proxyuser ?
+                   data->state.aptr.proxyuser : "") :
+          (data->state.aptr.user ?
+           data->state.aptr.user : ""));
 #else
     infof(data, "Server auth using %s with user '%s'\n",
-          auth, conn->user ? conn->user : "");
+          auth, data->state.aptr.user ?
+          data->state.aptr.user : "");
 #endif
     authstatus->multipass = (!authstatus->done) ? TRUE : FALSE;
   }
index 8fd1c77e606824fa7e7e52135004a02219eca93a..a04b46a3514aebe0ff89ed71a4c6a62f3c55a845 100644 (file)
@@ -99,8 +99,8 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
   char *request_type = NULL;
   char *credential_scope = NULL;
   char *str_to_sign = NULL;
-  const char *user = conn->user ? conn->user : "";
-  const char *passwd = conn->passwd ? conn->passwd : "";
+  const char *user = data->state.aptr.user ? data->state.aptr.user : "";
+  const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : "";
   char *secret = NULL;
   unsigned char tmp_sign0[32] = {0};
   unsigned char tmp_sign1[32] = {0};
index 596b215db64e3461c6b1ff1cb552cdf5fb8ec608..132f3930c8acf114bf0d98d6b44487ca395b6505 100644 (file)
@@ -67,7 +67,6 @@ CURLcode Curl_input_digest(struct Curl_easy *data,
 }
 
 CURLcode Curl_output_digest(struct Curl_easy *data,
-                            struct connectdata *conn,
                             bool proxy,
                             const unsigned char *request,
                             const unsigned char *uripath)
@@ -97,16 +96,16 @@ CURLcode Curl_output_digest(struct Curl_easy *data,
 #else
     digest = &data->state.proxydigest;
     allocuserpwd = &data->state.aptr.proxyuserpwd;
-    userp = conn->http_proxy.user;
-    passwdp = conn->http_proxy.passwd;
+    userp = data->state.aptr.proxyuser;
+    passwdp = data->state.aptr.proxypasswd;
     authp = &data->state.authproxy;
 #endif
   }
   else {
     digest = &data->state.digest;
     allocuserpwd = &data->state.aptr.userpwd;
-    userp = conn->user;
-    passwdp = conn->passwd;
+    userp = data->state.aptr.user;
+    passwdp = data->state.aptr.passwd;
     authp = &data->state.authhost;
   }
 
index 106caa7c4f4de8eb5a01b80eefaf906286f3f68c..89438d1a1ffd194403e4854453bf42a3ddd78607 100644 (file)
@@ -31,7 +31,6 @@ CURLcode Curl_input_digest(struct Curl_easy *data,
 
 /* this is for creating digest header output */
 CURLcode Curl_output_digest(struct Curl_easy *data,
-                            struct connectdata *conn,
                             bool proxy,
                             const unsigned char *request,
                             const unsigned char *uripath);
index 6cb829edfa19b6848cde23c5d5aa2e5faf525864..4fa38f0b096cbdd9a71a3060e07125bd71bad17a 100644 (file)
@@ -140,10 +140,10 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy)
   if(proxy) {
 #ifndef CURL_DISABLE_PROXY
     allocuserpwd = &data->state.aptr.proxyuserpwd;
-    userp = conn->http_proxy.user;
-    passwdp = conn->http_proxy.passwd;
+    userp = data->state.aptr.proxyuser;
+    passwdp = data->state.aptr.proxypasswd;
     service = data->set.str[STRING_PROXY_SERVICE_NAME] ?
-              data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP";
+      data->set.str[STRING_PROXY_SERVICE_NAME] : "HTTP";
     hostname = conn->http_proxy.host.name;
     ntlm = &conn->proxyntlm;
     state = &conn->proxy_ntlm_state;
@@ -154,10 +154,10 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy)
   }
   else {
     allocuserpwd = &data->state.aptr.userpwd;
-    userp = conn->user;
-    passwdp = conn->passwd;
+    userp = data->state.aptr.user;
+    passwdp = data->state.aptr.passwd;
     service = data->set.str[STRING_SERVICE_NAME] ?
-              data->set.str[STRING_SERVICE_NAME] : "HTTP";
+      data->set.str[STRING_SERVICE_NAME] : "HTTP";
     hostname = conn->host.name;
     ntlm = &conn->ntlm;
     state = &conn->http_ntlm_state;
index a12873760170229be7a2197943b0ec9f70455eb8..7edc75f5f083ea64e916eb0129835411066baf83 100644 (file)
@@ -1413,7 +1413,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     result = Curl_setstropt(&data->set.str[STRING_USERNAME],
                             va_arg(param, char *));
     break;
-
   case CURLOPT_PASSWORD:
     /*
      * authentication password to use in the operation
index 0b27b3455eef15124471a557619a7aea5090048a..ae414cfc5e92c32255189bd9045f07e86519905f 100644 (file)
@@ -79,6 +79,7 @@
 #include "strcase.h"
 #include "urlapi-int.h"
 #include "hsts.h"
+#include "setopt.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -1508,6 +1509,19 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
       return CURLE_OUT_OF_MEMORY;
   }
 
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.user,
+                            data->set.str[STRING_USERNAME]);
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.passwd,
+                            data->set.str[STRING_PASSWORD]);
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.proxyuser,
+                            data->set.str[STRING_PROXYUSERNAME]);
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.proxypasswd,
+                            data->set.str[STRING_PROXYPASSWORD]);
+
   data->req.headerbytecount = 0;
   return result;
 }
index d21e4ad4ff1a7dbcfdde3e4426d568532b1ada92..3e3355d6b4cbdf8bb84074f675182e2bd4b5904b 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -449,6 +449,10 @@ CURLcode Curl_close(struct Curl_easy **datap)
   Curl_safefree(data->state.aptr.host);
   Curl_safefree(data->state.aptr.cookiehost);
   Curl_safefree(data->state.aptr.rtsp_transport);
+  Curl_safefree(data->state.aptr.user);
+  Curl_safefree(data->state.aptr.passwd);
+  Curl_safefree(data->state.aptr.proxyuser);
+  Curl_safefree(data->state.aptr.proxypasswd);
 
 #ifndef CURL_DISABLE_DOH
   if(data->req.doh) {
@@ -1699,11 +1703,11 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
   }
 
   conn->bits.proxy_user_passwd =
-    (data->set.str[STRING_PROXYUSERNAME]) ? TRUE : FALSE;
+    (data->state.aptr.proxyuser) ? TRUE : FALSE;
   conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy;
 #endif /* CURL_DISABLE_PROXY */
 
-  conn->bits.user_passwd = (data->set.str[STRING_USERNAME]) ? TRUE : FALSE;
+  conn->bits.user_passwd = (data->state.aptr.user) ? TRUE : FALSE;
 #ifndef CURL_DISABLE_FTP
   conn->bits.ftp_use_epsv = data->set.ftp_use_epsv;
   conn->bits.ftp_use_eprt = data->set.ftp_use_eprt;
@@ -1970,36 +1974,50 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
   if(result)
     return result;
 
-  /* we don't use the URL API's URL decoder option here since it rejects
-     control codes and we want to allow them for some schemes in the user and
-     password fields */
-  uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0);
-  if(!uc) {
-    char *decoded;
-    result = Curl_urldecode(NULL, data->state.up.user, 0, &decoded, NULL,
-                            conn->handler->flags&PROTOPT_USERPWDCTRL ?
-                            REJECT_ZERO : REJECT_CTRL);
-    if(result)
-      return result;
-    conn->user = decoded;
-    conn->bits.user_passwd = TRUE;
+  /*
+   * User name and password set with their own options override the
+   * credentials possibly set in the URL.
+   */
+  if(!data->state.aptr.user) {
+    /* we don't use the URL API's URL decoder option here since it rejects
+       control codes and we want to allow them for some schemes in the user
+       and password fields */
+    uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0);
+    if(!uc) {
+      char *decoded;
+      result = Curl_urldecode(NULL, data->state.up.user, 0, &decoded, NULL,
+                              conn->handler->flags&PROTOPT_USERPWDCTRL ?
+                              REJECT_ZERO : REJECT_CTRL);
+      if(result)
+        return result;
+      conn->user = decoded;
+      conn->bits.user_passwd = TRUE;
+      result = Curl_setstropt(&data->state.aptr.user, decoded);
+      if(result)
+        return result;
+    }
+    else if(uc != CURLUE_NO_USER)
+      return Curl_uc_to_curlcode(uc);
   }
-  else if(uc != CURLUE_NO_USER)
-    return Curl_uc_to_curlcode(uc);
 
-  uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0);
-  if(!uc) {
-    char *decoded;
-    result = Curl_urldecode(NULL, data->state.up.password, 0, &decoded, NULL,
-                            conn->handler->flags&PROTOPT_USERPWDCTRL ?
-                            REJECT_ZERO : REJECT_CTRL);
-    if(result)
-      return result;
-    conn->passwd = decoded;
-    conn->bits.user_passwd = TRUE;
+  if(!data->state.aptr.passwd) {
+    uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0);
+    if(!uc) {
+      char *decoded;
+      result = Curl_urldecode(NULL, data->state.up.password, 0, &decoded, NULL,
+                              conn->handler->flags&PROTOPT_USERPWDCTRL ?
+                              REJECT_ZERO : REJECT_CTRL);
+      if(result)
+        return result;
+      conn->passwd = decoded;
+      conn->bits.user_passwd = TRUE;
+      result = Curl_setstropt(&data->state.aptr.passwd, decoded);
+      if(result)
+        return result;
+    }
+    else if(uc != CURLUE_NO_PASSWORD)
+      return Curl_uc_to_curlcode(uc);
   }
-  else if(uc != CURLUE_NO_PASSWORD)
-    return Curl_uc_to_curlcode(uc);
 
   uc = curl_url_get(uh, CURLUPART_OPTIONS, &data->state.up.options,
                     CURLU_URLDECODE);
@@ -2390,6 +2408,10 @@ static CURLcode parse_proxy(struct Curl_easy *data,
   if(proxyuser || proxypasswd) {
     Curl_safefree(proxyinfo->user);
     proxyinfo->user = proxyuser;
+    result = Curl_setstropt(&data->state.aptr.proxyuser,
+                            proxyuser);
+    if(result)
+      goto error;
     Curl_safefree(proxyinfo->passwd);
     if(!proxypasswd) {
       proxypasswd = strdup("");
@@ -2399,6 +2421,10 @@ static CURLcode parse_proxy(struct Curl_easy *data,
       }
     }
     proxyinfo->passwd = proxypasswd;
+    result = Curl_setstropt(&data->state.aptr.proxypasswd,
+                            proxypasswd);
+    if(result)
+      goto error;
     conn->bits.proxy_user_passwd = TRUE; /* enable it */
   }
 
@@ -2455,18 +2481,26 @@ static CURLcode parse_proxy(struct Curl_easy *data,
 static CURLcode parse_proxy_auth(struct Curl_easy *data,
                                  struct connectdata *conn)
 {
-  const char *proxyuser = data->set.str[STRING_PROXYUSERNAME] ?
-    data->set.str[STRING_PROXYUSERNAME] : "";
-  const char *proxypasswd = data->set.str[STRING_PROXYPASSWORD] ?
-    data->set.str[STRING_PROXYPASSWORD] : "";
+  const char *proxyuser = data->state.aptr.proxyuser ?
+    data->state.aptr.proxyuser : "";
+  const char *proxypasswd = data->state.aptr.proxypasswd ?
+    data->state.aptr.proxypasswd : "";
   CURLcode result = CURLE_OK;
 
-  if(proxyuser)
+  if(proxyuser) {
     result = Curl_urldecode(data, proxyuser, 0, &conn->http_proxy.user, NULL,
                             REJECT_ZERO);
-  if(!result && proxypasswd)
+    if(!result)
+      result = Curl_setstropt(&data->state.aptr.proxyuser,
+                              conn->http_proxy.user);
+  }
+  if(!result && proxypasswd) {
     result = Curl_urldecode(data, proxypasswd, 0, &conn->http_proxy.passwd,
                             NULL, REJECT_ZERO);
+    if(!result)
+      result = Curl_setstropt(&data->state.aptr.proxypasswd,
+                              conn->http_proxy.passwd);
+  }
   return result;
 }
 
@@ -2808,44 +2842,19 @@ static CURLcode parse_remote_port(struct Curl_easy *data,
  * option or a .netrc file, if applicable.
  */
 static CURLcode override_login(struct Curl_easy *data,
-                               struct connectdata *conn,
-                               char **userp, char **passwdp, char **optionsp)
+                               struct connectdata *conn)
 {
-  bool user_changed = FALSE;
-  bool passwd_changed = FALSE;
   CURLUcode uc;
+  char **userp = &conn->user;
+  char **passwdp = &conn->passwd;
+  char **optionsp = &conn->options;
 
   if(data->set.use_netrc == CURL_NETRC_REQUIRED && conn->bits.user_passwd) {
-    /* ignore user+password in the URL */
-    if(*userp) {
-      Curl_safefree(*userp);
-      user_changed = TRUE;
-    }
-    if(*passwdp) {
-      Curl_safefree(*passwdp);
-      passwd_changed = TRUE;
-    }
+    Curl_safefree(*userp);
+    Curl_safefree(*passwdp);
     conn->bits.user_passwd = FALSE; /* disable user+password */
   }
 
-  if(data->set.str[STRING_USERNAME]) {
-    free(*userp);
-    *userp = strdup(data->set.str[STRING_USERNAME]);
-    if(!*userp)
-      return CURLE_OUT_OF_MEMORY;
-    conn->bits.user_passwd = TRUE; /* enable user+password */
-    user_changed = TRUE;
-  }
-
-  if(data->set.str[STRING_PASSWORD]) {
-    free(*passwdp);
-    *passwdp = strdup(data->set.str[STRING_PASSWORD]);
-    if(!*passwdp)
-      return CURLE_OUT_OF_MEMORY;
-    conn->bits.user_passwd = TRUE; /* enable user+password */
-    passwd_changed = TRUE;
-  }
-
   if(data->set.str[STRING_OPTIONS]) {
     free(*optionsp);
     *optionsp = strdup(data->set.str[STRING_OPTIONS]);
@@ -2854,8 +2863,7 @@ static CURLcode override_login(struct Curl_easy *data,
   }
 
   conn->bits.netrc = FALSE;
-  if(data->set.use_netrc != CURL_NETRC_IGNORED &&
-      (!*userp || !**userp || !*passwdp || !**passwdp)) {
+  if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
     bool netrc_user_changed = FALSE;
     bool netrc_passwd_changed = FALSE;
     int ret;
@@ -2865,8 +2873,8 @@ static CURLcode override_login(struct Curl_easy *data,
                           &netrc_user_changed, &netrc_passwd_changed,
                           data->set.str[STRING_NETRC_FILE]);
     if(ret > 0) {
-      infof(data, "Couldn't find host %s in the .netrc file; using defaults\n",
-            conn->host.name);
+      infof(data, "Couldn't find host %s in the %s file; using defaults\n",
+            conn->host.name, data->set.str[STRING_NETRC_FILE]);
     }
     else if(ret < 0) {
       return CURLE_OUT_OF_MEMORY;
@@ -2877,29 +2885,44 @@ static CURLcode override_login(struct Curl_easy *data,
          different host or similar. */
       conn->bits.netrc = TRUE;
       conn->bits.user_passwd = TRUE; /* enable user+password */
-
-      if(netrc_user_changed) {
-        user_changed = TRUE;
-      }
-      if(netrc_passwd_changed) {
-        passwd_changed = TRUE;
-      }
     }
   }
 
   /* for updated strings, we update them in the URL */
-  if(user_changed) {
-    uc = curl_url_set(data->state.uh, CURLUPART_USER, *userp,
+  if(*userp) {
+    CURLcode result = Curl_setstropt(&data->state.aptr.user, *userp);
+    if(result)
+      return result;
+  }
+  if(data->state.aptr.user) {
+    uc = curl_url_set(data->state.uh, CURLUPART_USER, data->state.aptr.user,
                       CURLU_URLENCODE);
     if(uc)
       return Curl_uc_to_curlcode(uc);
+    if(!*userp) {
+      *userp = strdup(data->state.aptr.user);
+      if(!*userp)
+        return CURLE_OUT_OF_MEMORY;
+    }
   }
-  if(passwd_changed) {
-    uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, *passwdp,
-                      CURLU_URLENCODE);
+
+  if(*passwdp) {
+    CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp);
+    if(result)
+      return result;
+  }
+  if(data->state.aptr.passwd) {
+    uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
+                      data->state.aptr.passwd, CURLU_URLENCODE);
     if(uc)
       return Curl_uc_to_curlcode(uc);
+    if(!*passwdp) {
+      *passwdp = strdup(data->state.aptr.passwd);
+      if(!*passwdp)
+        return CURLE_OUT_OF_MEMORY;
+    }
   }
+
   return CURLE_OK;
 }
 
@@ -3560,8 +3583,7 @@ static CURLcode create_conn(struct Curl_easy *data,
 
   /* Check for overridden login details and set them accordingly so they
      they are known when protocol->setup_connection is called! */
-  result = override_login(data, conn, &conn->user, &conn->passwd,
-                          &conn->options);
+  result = override_login(data, conn);
   if(result)
     goto out;
 
index b78bf905ca1d476572aa51e0f9f118e035893e04..b37344f8cdfe3df02d174891ce639928d193450a 100644 (file)
@@ -1436,6 +1436,12 @@ struct UrlState {
     char *cookiehost;
     char *rtsp_transport;
     char *te; /* TE: request header */
+
+    /* transfer credentials */
+    char *user;
+    char *passwd;
+    char *proxyuser;
+    char *proxypasswd;
   } aptr;
 
 #ifdef CURLDEBUG
index 0b0e90d3e455866da3b901cf869787f7a6477776..f6e48e3de767560e78fa57350e5867df6e081ed8 100644 (file)
@@ -90,7 +90,7 @@ test635 test636 test637 test638 test639 test640 test641 test642 \
 test643 test644 test645 test646 test647 test648 test649 test650 test651 \
 test652 test653 test654 test655 test656 test658 test659 test660 test661 \
 test662 test663 test664 test665 test666 test667 test668 test669 \
-test670 test671 test672 test673 test674 \
+test670 test671 test672 test673 test674 test675 \
 \
 test700 test701 test702 test703 test704 test705 test706 test707 test708 \
 test709 test710 test711 test712 test713 test714 test715 test716 test717 \
index e314b662e95e94d2135f9225876a93dd6816729b..4ca63accdbb789876efafc7f67d976cfe7e46f54 100644 (file)
@@ -33,10 +33,12 @@ dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr
 <server>
 ftp
 </server>
- <name>
+<name>
 FTP (optional .netrc; programmatic user/passwd) dir list PASV
- </name>
- <command>
+</name>
+
+# -u overrides netrc which overrides the URL
+<command>
 --netrc-optional --netrc-file log/netrc134 -u romulus:rhemus ftp://mary:mark@%HOSTIP:%FTPPORT/
 </command>
 <file name="log/netrc134" >
diff --git a/tests/data/test675 b/tests/data/test675
new file mode 100644 (file)
index 0000000..19549dd
--- /dev/null
@@ -0,0 +1,55 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+HTTP Basic auth
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK swsclose\r
+Date: Thu, 09 Nov 2010 14:49:00 GMT\r
+Content-Type: text/html\r
+Content-Length: 26
+\r
+the content would go here
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+HTTP connection re-use and different credentials
+ </name>
+
+<command>
+http://user1:foo1@%HOSTIP:%HTTPPORT/user1/675 http://user2:foo2@%HOSTIP:%HTTPPORT/user2/675
+</command>
+<features>
+proxy
+</features>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /user1/675 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Authorization: Basic dXNlcjE6Zm9vMQ==\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+GET /user2/675 HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Authorization: Basic dXNlcjI6Zm9vMg==\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>