Browse code

Proxy improvements:

Improved the ability of http-auth "auto" flag to dynamically detect
the auth method required by the proxy.

Added http-auth "auto-nct" flag to reject weak proxy auth methods.

Added HTTP proxy digest authentication method.

Removed extraneous openvpn_sleep calls from proxy.c.


git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@5628 e7ae566f-a301-0410-adde-c780ea21d3b5

James Yonan authored on 2010/05/12 04:32:41
Showing 10 changed files
... ...
@@ -88,6 +88,7 @@ openvpn_SOURCES = \
88 88
 	fragment.c fragment.h \
89 89
 	gremlin.c gremlin.h \
90 90
 	helper.c helper.h \
91
+	httpdigest.c httpdigest.h \
91 92
 	lladdr.c lladdr.h \
92 93
 	init.c init.h \
93 94
 	integer.h \
... ...
@@ -291,6 +291,7 @@ typedef unsigned long in_addr_t;
291 291
 #define lseek _lseek
292 292
 #define chdir _chdir
293 293
 #define strdup _strdup
294
+#define stricmp _stricmp
294 295
 #define chsize _chsize
295 296
 #define S_IRUSR 0
296 297
 #define S_IWUSR 0
297 298
new file mode 100644
... ...
@@ -0,0 +1,143 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
8
+ *
9
+ *  This program is free software; you can redistribute it and/or modify
10
+ *  it under the terms of the GNU General Public License version 2
11
+ *  as published by the Free Software Foundation.
12
+ *
13
+ *  This program is distributed in the hope that it will be useful,
14
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ *  GNU General Public License for more details.
17
+ *
18
+ *  You should have received a copy of the GNU General Public License
19
+ *  along with this program (see the file COPYING included with this
20
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
21
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
+ */
23
+
24
+#include "syshead.h"
25
+
26
+#if PROXY_DIGEST_AUTH
27
+
28
+#include "crypto.h"
29
+#include "httpdigest.h"
30
+
31
+static void
32
+CvtHex(
33
+       IN HASH Bin,
34
+       OUT HASHHEX Hex
35
+       )
36
+{
37
+  unsigned short i;
38
+  unsigned char j;
39
+
40
+  for (i = 0; i < HASHLEN; i++) {
41
+    j = (Bin[i] >> 4) & 0xf;
42
+    if (j <= 9)
43
+      Hex[i*2] = (j + '0');
44
+    else
45
+      Hex[i*2] = (j + 'a' - 10);
46
+    j = Bin[i] & 0xf;
47
+    if (j <= 9)
48
+      Hex[i*2+1] = (j + '0');
49
+    else
50
+      Hex[i*2+1] = (j + 'a' - 10);
51
+  };
52
+  Hex[HASHHEXLEN] = '\0';
53
+};
54
+
55
+/* calculate H(A1) as per spec */
56
+void
57
+DigestCalcHA1(
58
+	      IN char * pszAlg,
59
+	      IN char * pszUserName,
60
+	      IN char * pszRealm,
61
+	      IN char * pszPassword,
62
+	      IN char * pszNonce,
63
+	      IN char * pszCNonce,
64
+	      OUT HASHHEX SessionKey
65
+	      )
66
+{
67
+  MD5_CTX Md5Ctx;
68
+  HASH HA1;
69
+
70
+  MD5_Init(&Md5Ctx);
71
+  MD5_Update(&Md5Ctx, pszUserName, strlen(pszUserName));
72
+  MD5_Update(&Md5Ctx, ":", 1);
73
+  MD5_Update(&Md5Ctx, pszRealm, strlen(pszRealm));
74
+  MD5_Update(&Md5Ctx, ":", 1);
75
+  MD5_Update(&Md5Ctx, pszPassword, strlen(pszPassword));
76
+  MD5_Final(HA1, &Md5Ctx);
77
+  if (pszAlg && stricmp(pszAlg, "md5-sess") == 0)
78
+    {
79
+      MD5_Init(&Md5Ctx);
80
+      MD5_Update(&Md5Ctx, HA1, HASHLEN);
81
+      MD5_Update(&Md5Ctx, ":", 1);
82
+      MD5_Update(&Md5Ctx, pszNonce, strlen(pszNonce));
83
+      MD5_Update(&Md5Ctx, ":", 1);
84
+      MD5_Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
85
+      MD5_Final(HA1, &Md5Ctx);
86
+    };
87
+  CvtHex(HA1, SessionKey);
88
+}
89
+
90
+/* calculate request-digest/response-digest as per HTTP Digest spec */
91
+void
92
+DigestCalcResponse(
93
+		   IN HASHHEX HA1,           /* H(A1) */
94
+		   IN char * pszNonce,       /* nonce from server */
95
+		   IN char * pszNonceCount,  /* 8 hex digits */
96
+		   IN char * pszCNonce,      /* client nonce */
97
+		   IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
98
+		   IN char * pszMethod,      /* method from the request */
99
+		   IN char * pszDigestUri,   /* requested URL */
100
+		   IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
101
+		   OUT HASHHEX Response      /* request-digest or response-digest */
102
+		   )
103
+{
104
+  MD5_CTX Md5Ctx;
105
+  HASH HA2;
106
+  HASH RespHash;
107
+  HASHHEX HA2Hex;
108
+
109
+  // calculate H(A2)
110
+  MD5_Init(&Md5Ctx);
111
+  MD5_Update(&Md5Ctx, pszMethod, strlen(pszMethod));
112
+  MD5_Update(&Md5Ctx, ":", 1);
113
+  MD5_Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
114
+  if (stricmp(pszQop, "auth-int") == 0)
115
+    {
116
+      MD5_Update(&Md5Ctx, ":", 1);
117
+      MD5_Update(&Md5Ctx, HEntity, HASHHEXLEN);
118
+    };
119
+  MD5_Final(HA2, &Md5Ctx);
120
+  CvtHex(HA2, HA2Hex);
121
+
122
+  // calculate response
123
+  MD5_Init(&Md5Ctx);
124
+  MD5_Update(&Md5Ctx, HA1, HASHHEXLEN);
125
+  MD5_Update(&Md5Ctx, ":", 1);
126
+  MD5_Update(&Md5Ctx, pszNonce, strlen(pszNonce));
127
+  MD5_Update(&Md5Ctx, ":", 1);
128
+  if (*pszQop)
129
+    {
130
+      MD5_Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
131
+      MD5_Update(&Md5Ctx, ":", 1);
132
+      MD5_Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
133
+      MD5_Update(&Md5Ctx, ":", 1);
134
+      MD5_Update(&Md5Ctx, pszQop, strlen(pszQop));
135
+      MD5_Update(&Md5Ctx, ":", 1);
136
+    };
137
+  MD5_Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
138
+  MD5_Final(RespHash, &Md5Ctx);
139
+  CvtHex(RespHash, Response);
140
+}
141
+
142
+#endif
0 143
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
8
+ *
9
+ *  This program is free software; you can redistribute it and/or modify
10
+ *  it under the terms of the GNU General Public License version 2
11
+ *  as published by the Free Software Foundation.
12
+ *
13
+ *  This program is distributed in the hope that it will be useful,
14
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ *  GNU General Public License for more details.
17
+ *
18
+ *  You should have received a copy of the GNU General Public License
19
+ *  along with this program (see the file COPYING included with this
20
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
21
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
+ */
23
+
24
+#if PROXY_DIGEST_AUTH
25
+
26
+#define HASHLEN 16
27
+typedef char HASH[HASHLEN];
28
+#define HASHHEXLEN 32
29
+typedef char HASHHEX[HASHHEXLEN+1];
30
+#undef IN
31
+#undef OUT
32
+#define IN const
33
+#define OUT
34
+
35
+/* calculate H(A1) as per HTTP Digest spec */
36
+void DigestCalcHA1(
37
+    IN char * pszAlg,
38
+    IN char * pszUserName,
39
+    IN char * pszRealm,
40
+    IN char * pszPassword,
41
+    IN char * pszNonce,
42
+    IN char * pszCNonce,
43
+    OUT HASHHEX SessionKey
44
+    );
45
+
46
+/* calculate request-digest/response-digest as per HTTP Digest spec */
47
+void DigestCalcResponse(
48
+    IN HASHHEX HA1,           /* H(A1) */
49
+    IN char * pszNonce,       /* nonce from server */
50
+    IN char * pszNonceCount,  /* 8 hex digits */
51
+    IN char * pszCNonce,      /* client nonce */
52
+    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
53
+    IN char * pszMethod,      /* method from the request */
54
+    IN char * pszDigestUri,   /* requested URL */
55
+    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
56
+    OUT HASHHEX Response      /* request-digest or response-digest */
57
+    );
58
+
59
+#endif
... ...
@@ -474,7 +474,7 @@ InternetQueryOption API.
474 474
 This option exists in OpenVPN 2.1 or higher.
475 475
 .\"*********************************************************
476 476
 .TP
477
-.B --http-proxy server port [authfile|'auto'] [auth-method]
477
+.B --http-proxy server port [authfile|'auto'|'auto-nct'] [auth-method]
478 478
 Connect to remote host through an HTTP proxy at address
479 479
 .B server
480 480
 and port
... ...
@@ -487,6 +487,13 @@ is a file containing a username and password on 2 lines, or
487 487
 .B auth-method
488 488
 should be one of "none", "basic", or "ntlm".
489 489
 
490
+HTTP Digest authentication is supported as well, but only via
491
+the
492
+.B auto
493
+or
494
+.B auto-nct
495
+flags (below).
496
+
490 497
 The
491 498
 .B auto
492 499
 flag causes OpenVPN to automatically determine the
... ...
@@ -494,6 +501,12 @@ flag causes OpenVPN to automatically determine the
494 494
 and query stdin or the management interface for
495 495
 username/password credentials, if required.  This flag
496 496
 exists on OpenVPN 2.1 or higher.
497
+
498
+The
499
+.B auto-nct
500
+flag (no clear-text auth) instructs OpenVPN to automatically
501
+determine the authentication method, but to reject weak
502
+authentication protocols such as HTTP Basic Authentication.
497 503
 .\"*********************************************************
498 504
 .TP
499 505
 .B --http-proxy-retry
... ...
@@ -108,8 +108,9 @@ static const char usage_message[] =
108 108
   "                  up is a file containing username/password on 2 lines, or\n"
109 109
   "                  'stdin' to prompt from console.  Add auth='ntlm' if\n"
110 110
   "                  the proxy requires NTLM authentication.\n"
111
-  "--http-proxy s p 'auto': Like the above directive, but automatically determine\n"
112
-  "                         auth method and query for username/password if needed.\n"
111
+  "--http-proxy s p 'auto[-nct]' : Like the above directive, but automatically\n"
112
+  "                  determine auth method and query for username/password\n"
113
+  "                  if needed.  auto-nct disables weak proxy auth methods.\n"
113 114
   "--http-proxy-retry     : Retry indefinitely on HTTP proxy errors.\n"
114 115
   "--http-proxy-timeout n : Proxy timeout in seconds, default=5.\n"
115 116
   "--http-proxy-option type [parm] : Set extended HTTP proxy options.\n"
... ...
@@ -4197,8 +4198,13 @@ add_option (struct options *options,
4197 4197
 
4198 4198
       if (p[3])
4199 4199
 	{
4200
+	  /* auto -- try to figure out proxy addr, port, and type automatically */
4201
+	  /* semiauto -- given proxy addr:port, try to figure out type automatically */
4202
+	  /* (auto|semiauto)-nct -- disable proxy auth cleartext protocols (i.e. basic auth) */
4200 4203
 	  if (streq (p[3], "auto"))
4201
-	    ho->auth_retry = true;
4204
+	    ho->auth_retry = PAR_ALL;
4205
+	  else if (streq (p[3], "auto-nct"))
4206
+	    ho->auth_retry = PAR_NCT;
4202 4207
 	  else
4203 4208
 	    {
4204 4209
 	      ho->auth_method_string = "basic";
... ...
@@ -26,11 +26,13 @@
26 26
 
27 27
 #include "common.h"
28 28
 #include "misc.h"
29
+#include "crypto.h"
29 30
 #include "win32.h"
30 31
 #include "socket.h"
31 32
 #include "fdmisc.h"
32 33
 #include "proxy.h"
33 34
 #include "base64.h"
35
+#include "httpdigest.h"
34 36
 #include "ntlm.h"
35 37
 
36 38
 #ifdef WIN32
... ...
@@ -229,6 +231,189 @@ get_user_pass_http (struct http_proxy_info *p, const bool force)
229 229
       p->up = static_proxy_user_pass;
230 230
     }
231 231
 }
232
+static void
233
+clear_user_pass_http (void)
234
+{
235
+  purge_user_pass (&static_proxy_user_pass, true);
236
+}
237
+
238
+static void
239
+dump_residual (socket_descriptor_t sd,
240
+	       int timeout,
241
+	       volatile int *signal_received)
242
+{
243
+  char buf[256];
244
+  while (true)
245
+    {
246
+      if (!recv_line (sd, buf, sizeof (buf), timeout, true, NULL, signal_received))
247
+	return;
248
+      chomp (buf);
249
+      msg (D_PROXY, "PROXY HEADER: '%s'", buf);
250
+    }
251
+}
252
+
253
+/*
254
+ * Extract the Proxy-Authenticate header from the stream.
255
+ * Consumes all headers.
256
+ */
257
+static int
258
+get_proxy_authenticate (socket_descriptor_t sd,
259
+		        int timeout,
260
+			char **data,
261
+			struct gc_arena *gc,
262
+		        volatile int *signal_received)
263
+{
264
+  char buf[256];
265
+  int ret = HTTP_AUTH_NONE;
266
+  while (true)
267
+    {
268
+      if (!recv_line (sd, buf, sizeof (buf), timeout, true, NULL, signal_received))
269
+	{
270
+	  *data = NULL;
271
+	  return HTTP_AUTH_NONE;
272
+	}
273
+      chomp (buf);
274
+      if (!strlen(buf))
275
+	return ret;
276
+      if (ret == HTTP_AUTH_NONE && !strncmp(buf, "Proxy-Authenticate: ", 20))
277
+	{
278
+	  if (!strncmp(buf+20, "Basic ", 6))
279
+	    {
280
+	      msg (D_PROXY, "PROXY AUTH BASIC: '%s'", buf);
281
+	      *data = string_alloc(buf+26, gc);
282
+	      ret = HTTP_AUTH_BASIC;
283
+	    }
284
+#if PROXY_DIGEST_AUTH
285
+	  else if (!strncmp(buf+20, "Digest ", 7))
286
+	    {
287
+	      msg (D_PROXY, "PROXY AUTH DIGEST: '%s'", buf);
288
+	      *data = string_alloc(buf+27, gc);
289
+	      ret = HTTP_AUTH_DIGEST;
290
+	    }
291
+#endif
292
+#if NTLM
293
+	  else if (!strncmp(buf+20, "NTLM", 4))
294
+	    {
295
+	      msg (D_PROXY, "PROXY AUTH HTLM: '%s'", buf);
296
+	      *data = NULL;
297
+	      ret = HTTP_AUTH_NTLM;
298
+	    }
299
+#endif
300
+	}
301
+    }
302
+}
303
+
304
+static void
305
+store_proxy_authenticate (struct http_proxy_info *p, char *data)
306
+{
307
+  if (p->proxy_authenticate)
308
+    free (p->proxy_authenticate);
309
+  p->proxy_authenticate = data;
310
+}
311
+
312
+/*
313
+ * Parse out key/value pairs from Proxy-Authenticate string.
314
+ * Return true on success, or false on parse failure.
315
+ */
316
+static bool
317
+get_key_value(const char *str,       /* source string */
318
+	      char *key,             /* key stored here */
319
+	      char *value,           /* value stored here */
320
+	      int max_key_len,
321
+	      int max_value_len,
322
+	      const char **endptr)   /* next search position */
323
+{
324
+  int c;
325
+  bool starts_with_quote = false;
326
+  bool escape = false;
327
+
328
+  for (c = max_key_len-1; (*str && (*str != '=') && c--); )
329
+    *key++ = *str++;
330
+  *key = '\0';
331
+
332
+  if('=' != *str++)
333
+    /* no key/value found */
334
+    return false;
335
+
336
+  if('\"' == *str)
337
+    {
338
+      /* quoted string */
339
+      str++;
340
+      starts_with_quote = true;
341
+    }
342
+
343
+  for (c = max_value_len-1; *str && c--; str++)
344
+    {
345
+      switch (*str)
346
+	{
347
+	case '\\':
348
+	  if (!escape)
349
+	    {
350
+	      /* possibly the start of an escaped quote */
351
+	      escape = true;
352
+	      *value++ = '\\'; /* even though this is an escape character, we still
353
+				  store it as-is in the target buffer */
354
+	      continue;
355
+	    }
356
+	  break;
357
+	case ',':
358
+	  if (!starts_with_quote)
359
+	    {
360
+	      /* this signals the end of the value if we didn't get a starting quote
361
+		 and then we do "sloppy" parsing */
362
+	      c=0; /* the end */
363
+	      continue;
364
+	    }
365
+	  break;
366
+	case '\r':
367
+	case '\n':
368
+	  /* end of string */
369
+	  c=0;
370
+	continue;
371
+	case '\"':
372
+	  if (!escape && starts_with_quote)
373
+	    {
374
+	      /* end of string */
375
+	      c=0;
376
+	      continue;
377
+	    }
378
+	  break;
379
+	}
380
+      escape = false;
381
+      *value++ = *str;
382
+    }
383
+  *value = '\0';
384
+
385
+  *endptr = str;
386
+
387
+  return true; /* success */
388
+}
389
+
390
+static char *
391
+get_pa_var (const char *key, const char *pa, struct gc_arena *gc)
392
+{
393
+  char k[64];
394
+  char v[256];
395
+  const char *content = pa;
396
+
397
+  while (true)
398
+    {
399
+      const int status = get_key_value(content, k, v, sizeof(k), sizeof(v), &content);
400
+      if (status)
401
+	{
402
+	  if (!strcmp(key, k))
403
+	    return string_alloc(v, gc);
404
+	}
405
+      else
406
+	return NULL;
407
+
408
+      /* advance to start of next key */
409
+      if (*content == ',')
410
+	++content;
411
+      while (*content && isspace(*content))
412
+	++content;
413
+    }
414
+}
232 415
 
233 416
 struct http_proxy_info *
234 417
 http_proxy_new (const struct http_proxy_options *o,
... ...
@@ -265,7 +450,8 @@ http_proxy_new (const struct http_proxy_options *o,
265 265
 
266 266
 	  opt.server = auto_proxy_info->http.server;
267 267
 	  opt.port = auto_proxy_info->http.port;
268
-	  opt.auth_retry = true;
268
+	  if (!opt.auth_retry)
269
+	    opt.auth_retry = PAR_ALL;
269 270
 
270 271
 	  o = &opt;
271 272
 	}
... ...
@@ -287,12 +473,14 @@ http_proxy_new (const struct http_proxy_options *o,
287 287
 	p->auth_method = HTTP_AUTH_NONE;
288 288
       else if (!strcmp (o->auth_method_string, "basic"))
289 289
 	p->auth_method = HTTP_AUTH_BASIC;
290
+#if NTLM
290 291
       else if (!strcmp (o->auth_method_string, "ntlm"))
291 292
 	p->auth_method = HTTP_AUTH_NTLM;
292 293
       else if (!strcmp (o->auth_method_string, "ntlm2"))
293 294
 	p->auth_method = HTTP_AUTH_NTLM2;
295
+#endif
294 296
       else
295
-	msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s' -- only the 'none', 'basic', 'ntlm', or 'ntlm2' methods are currently supported",
297
+	msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s'",
296 298
 	     o->auth_method_string);
297 299
     }
298 300
 
... ...
@@ -326,101 +514,110 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
326 326
 			       volatile int *signal_received)
327 327
 {
328 328
   struct gc_arena gc = gc_new ();
329
-  char buf[256];
329
+  char buf[512];
330 330
   char buf2[128];
331 331
   char get[80];
332 332
   int status;
333 333
   int nparms;
334 334
   bool ret = false;
335
+  bool processed = false;
335 336
 
336 337
   /* get user/pass if not previously given or if --auto-proxy is being used */
337 338
   if (p->auth_method == HTTP_AUTH_BASIC
339
+      || p->auth_method == HTTP_AUTH_DIGEST
338 340
       || p->auth_method == HTTP_AUTH_NTLM)
339 341
     get_user_pass_http (p, false);
340 342
 
341
-  /* format HTTP CONNECT message */
342
-  openvpn_snprintf (buf, sizeof(buf), "CONNECT %s:%d HTTP/%s",
343
-		    host,
344
-		    port,
345
-		    p->options.http_version);
346
-
347
-  msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
348
-
349
-  /* send HTTP CONNECT message to proxy */
350
-  if (!send_line_crlf (sd, buf))
351
-    goto error;
352
-
353
-  /* send User-Agent string if provided */
354
-  if (p->options.user_agent)
343
+  /* are we being called again after getting the digest server nonce in the previous transaction? */
344
+  if (p->auth_method == HTTP_AUTH_DIGEST && p->proxy_authenticate)
355 345
     {
356
-      openvpn_snprintf (buf, sizeof(buf), "User-Agent: %s",
357
-			p->options.user_agent);
358
-      if (!send_line_crlf (sd, buf))
359
-	goto error;
346
+      nparms = 1;
347
+      status = 407;
360 348
     }
361
-
362
-  /* auth specified? */
363
-  switch (p->auth_method)
349
+  else
364 350
     {
365
-    case HTTP_AUTH_NONE:
366
-      break;
351
+      /* format HTTP CONNECT message */
352
+      openvpn_snprintf (buf, sizeof(buf), "CONNECT %s:%d HTTP/%s",
353
+			host,
354
+			port,
355
+			p->options.http_version);
367 356
 
368
-    case HTTP_AUTH_BASIC:
369
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Basic %s",
370
-			username_password_as_base64 (p, &gc));
371
-      msg (D_PROXY, "Attempting Basic Proxy-Authorization");
372
-      dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
373
-      openvpn_sleep (1);
357
+      msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
358
+
359
+      /* send HTTP CONNECT message to proxy */
374 360
       if (!send_line_crlf (sd, buf))
375 361
 	goto error;
376
-      break;
362
+
363
+      /* send User-Agent string if provided */
364
+      if (p->options.user_agent)
365
+	{
366
+	  openvpn_snprintf (buf, sizeof(buf), "User-Agent: %s",
367
+			    p->options.user_agent);
368
+	  if (!send_line_crlf (sd, buf))
369
+	    goto error;
370
+	}
371
+
372
+      /* auth specified? */
373
+      switch (p->auth_method)
374
+	{
375
+	case HTTP_AUTH_NONE:
376
+	  break;
377
+
378
+	case HTTP_AUTH_BASIC:
379
+	  openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Basic %s",
380
+			    username_password_as_base64 (p, &gc));
381
+	  msg (D_PROXY, "Attempting Basic Proxy-Authorization");
382
+	  dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
383
+	  if (!send_line_crlf (sd, buf))
384
+	    goto error;
385
+	  break;
377 386
 
378 387
 #if NTLM
379
-    case HTTP_AUTH_NTLM:
380
-    case HTTP_AUTH_NTLM2:
381
-      /* keep-alive connection */
382
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
383
-      if (!send_line_crlf (sd, buf))
384
-	goto error;
388
+	case HTTP_AUTH_NTLM:
389
+	case HTTP_AUTH_NTLM2:
390
+	  /* keep-alive connection */
391
+	  openvpn_snprintf (buf, sizeof(buf), "Proxy-Connection: Keep-Alive");
392
+	  if (!send_line_crlf (sd, buf))
393
+	    goto error;
385 394
 
386
-      openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
387
-			ntlm_phase_1 (p, &gc));
388
-      msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 1");
389
-      dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
390
-      openvpn_sleep (1);
391
-      if (!send_line_crlf (sd, buf))
392
-	goto error;
393
-      break;
395
+	  openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
396
+			    ntlm_phase_1 (p, &gc));
397
+	  msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 1");
398
+	  dmsg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
399
+	  if (!send_line_crlf (sd, buf))
400
+	    goto error;
401
+	  break;
394 402
 #endif
395 403
 
396
-    default:
397
-      ASSERT (0);
398
-    }
404
+	default:
405
+	  ASSERT (0);
406
+	}
399 407
 
400
-  /* send empty CR, LF */
401
-  openvpn_sleep (1);
402
-  if (!send_crlf (sd))
403
-    goto error;
408
+      /* send empty CR, LF */
409
+      if (!send_crlf (sd))
410
+	goto error;
404 411
 
405
-  /* receive reply from proxy */
406
-  if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
407
-    goto error;
412
+      /* receive reply from proxy */
413
+      if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
414
+	goto error;
415
+
416
+      /* remove trailing CR, LF */
417
+      chomp (buf);
408 418
 
409
-  /* remove trailing CR, LF */
410
-  chomp (buf);
419
+      msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
411 420
 
412
-  msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
421
+      /* parse return string */
422
+      nparms = sscanf (buf, "%*s %d", &status);
413 423
 
414
-  /* parse return string */
415
-  nparms = sscanf (buf, "%*s %d", &status);
424
+    }
416 425
 
417 426
   /* check for a "407 Proxy Authentication Required" response */
418
-  if (nparms >= 1 && status == 407)
427
+  while (nparms >= 1 && status == 407)
419 428
     {
420 429
       msg (D_PROXY, "Proxy requires authentication");
421 430
 
422 431
       /* check for NTLM */
423
-      if (p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2)
432
+      if ((p->auth_method == HTTP_AUTH_NTLM || p->auth_method == HTTP_AUTH_NTLM2) && !processed)
424 433
         {
425 434
 #if NTLM
426 435
           /* look for the phase 2 response */
... ...
@@ -448,7 +645,7 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
448 448
           msg (D_PROXY, "Received NTLM Proxy-Authorization phase 2 response");
449 449
 
450 450
           /* receive and discard everything else */
451
-          while (recv_line (sd, NULL, 0, p->options.timeout, true, NULL, signal_received))
451
+          while (recv_line (sd, NULL, 0, 2, true, NULL, signal_received))
452 452
             ;
453 453
 
454 454
           /* now send the phase 3 reply */
... ...
@@ -472,7 +669,6 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
472 472
 
473 473
           
474 474
           /* send HOST etc, */
475
-          openvpn_sleep (1);
476 475
           openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
477 476
           msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
478 477
           if (!send_line_crlf (sd, buf))
... ...
@@ -490,12 +686,10 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
490 490
 	  }
491 491
 
492 492
           msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
493
-          openvpn_sleep (1);
494 493
           if (!send_line_crlf (sd, buf))
495 494
 	    goto error;
496 495
           /* ok so far... */
497 496
           /* send empty CR, LF */
498
-          openvpn_sleep (1);
499 497
           if (!send_crlf (sd))
500 498
             goto error;
501 499
 
... ...
@@ -510,27 +704,167 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
510 510
 
511 511
           /* parse return string */
512 512
           nparms = sscanf (buf, "%*s %d", &status);
513
-#else
514
-	  ASSERT (0); /* No NTLM support */
513
+	  processed = true;
515 514
 #endif
516 515
 	}
517
-      else if (p->auth_method == HTTP_AUTH_NONE && p->options.auth_retry)
516
+#if PROXY_DIGEST_AUTH
517
+      else if (p->auth_method == HTTP_AUTH_DIGEST && !processed)
518
+	{
519
+	  char *pa = p->proxy_authenticate;
520
+	  const int method = p->auth_method;
521
+	  ASSERT(pa);
522
+
523
+	  if (method == HTTP_AUTH_DIGEST)
524
+	    {
525
+	      const char *http_method = "CONNECT";
526
+	      const char *nonce_count = "00000001";
527
+	      const char *qop = "auth";
528
+	      const char *username = p->up.username;
529
+	      const char *password = p->up.password;
530
+	      char *opaque_kv = "";
531
+	      char uri[128];
532
+	      uint8_t cnonce_raw[8];
533
+	      uint8_t *cnonce;
534
+	      HASHHEX session_key;
535
+	      HASHHEX response;
536
+
537
+	      const char *realm = get_pa_var("realm", pa, &gc);
538
+	      const char *nonce = get_pa_var("nonce", pa, &gc);
539
+	      const char *algor = get_pa_var("algorithm", pa, &gc);
540
+	      const char *opaque = get_pa_var("opaque", pa, &gc);
541
+
542
+	      /* generate a client nonce */
543
+	      ASSERT(RAND_bytes(cnonce_raw, sizeof(cnonce_raw)));
544
+	      cnonce = make_base64_string2(cnonce_raw, sizeof(cnonce_raw), &gc);
545
+
546
+
547
+	      /* build the digest response */
548
+	      openvpn_snprintf (uri, sizeof(uri), "%s:%d",
549
+				host,
550
+				port);
551
+
552
+	      if (opaque)
553
+		{
554
+		  const int len = strlen(opaque)+16;
555
+		  opaque_kv = gc_malloc(len, false, &gc);
556
+		  openvpn_snprintf (opaque_kv, len, ", opaque=\"%s\"", opaque);
557
+		}
558
+
559
+	      DigestCalcHA1(algor,
560
+			    username,
561
+			    realm,
562
+			    password,
563
+			    nonce,
564
+			    cnonce,
565
+			    session_key);
566
+	      DigestCalcResponse(session_key,
567
+				 nonce,
568
+				 nonce_count,
569
+				 cnonce,
570
+				 qop,
571
+				 http_method,
572
+				 uri,
573
+				 NULL,
574
+				 response);
575
+
576
+	      /* format HTTP CONNECT message */
577
+	      openvpn_snprintf (buf, sizeof(buf), "%s %s HTTP/%s",
578
+				http_method,
579
+				uri,
580
+				p->options.http_version);
581
+
582
+	      msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
583
+
584
+	      /* send HTTP CONNECT message to proxy */
585
+	      if (!send_line_crlf (sd, buf))
586
+		goto error;
587
+
588
+	      /* send HOST etc, */
589
+	      openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
590
+	      msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
591
+	      if (!send_line_crlf (sd, buf))
592
+		goto error;
593
+
594
+	      /* send digest response */
595
+	      openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=%s, nc=%s, cnonce=\"%s\", response=\"%s\"%s",
596
+				username,
597
+				realm,
598
+				nonce,
599
+				uri,
600
+				qop,
601
+				nonce_count,
602
+				cnonce,
603
+				response,
604
+				opaque_kv
605
+				);
606
+	      msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
607
+	      if (!send_line_crlf (sd, buf))
608
+		goto error;
609
+	      if (!send_crlf (sd))
610
+		goto error;
611
+
612
+	      /* receive reply from proxy */
613
+	      if (!recv_line (sd, buf, sizeof(buf), p->options.timeout, true, NULL, signal_received))
614
+		goto error;
615
+
616
+	      /* remove trailing CR, LF */
617
+	      chomp (buf);
618
+
619
+	      msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
620
+
621
+	      /* parse return string */
622
+	      nparms = sscanf (buf, "%*s %d", &status);
623
+	      processed = true;
624
+	    }
625
+	  else
626
+	    {
627
+	      msg (D_PROXY, "HTTP proxy: digest method not supported");
628
+	      goto error;
629
+	    }
630
+	}
631
+#endif
632
+      else if (p->options.auth_retry)
518 633
 	{
519
-	  /*
520
-	   * Proxy needs authentication, but we don't have a user/pass.
521
-	   * Now we will change p->auth_method and return true so that
522
-	   * our caller knows to call us again on a newly opened socket.
523
-	   * JYFIXME: This code needs to check proxy error output and set
524
-	   * JYFIXME: p->auth_method = HTTP_AUTH_NTLM if necessary.
525
-	   */
526
-	  p->auth_method = HTTP_AUTH_BASIC;
527
-	  ret = true;
528
-	  goto done;
634
+	  /* figure out what kind of authentication the proxy needs */
635
+	  char *pa = NULL;
636
+	  const int method = get_proxy_authenticate(sd,
637
+						    p->options.timeout,
638
+						    &pa,
639
+						    NULL,
640
+						    signal_received);
641
+	  if (method != HTTP_AUTH_NONE)
642
+	    {
643
+	      if (pa)
644
+		msg (D_PROXY, "HTTP proxy authenticate '%s'", pa);
645
+	      if (p->options.auth_retry == PAR_NCT && method == HTTP_AUTH_BASIC)
646
+		{
647
+		  msg (D_PROXY, "HTTP proxy: support for basic auth and other cleartext proxy auth methods is disabled");
648
+		  goto error;
649
+		}
650
+	      p->auth_method = method;
651
+	      store_proxy_authenticate(p, pa);
652
+	      ret = true;
653
+	      goto done;
654
+	    }
655
+	  else
656
+	    {
657
+	      msg (D_PROXY, "HTTP proxy: do not recognize the authentication method required by proxy");
658
+	      free (pa);
659
+	      goto error;
660
+	    }
529 661
 	}
530 662
       else
531
-	goto error;
532
-    }
663
+	{
664
+	  if (!processed)
665
+	    msg (D_PROXY, "HTTP proxy: no support for proxy authentication method");
666
+	  goto error;
667
+	}
533 668
 
669
+      /* clear state */
670
+      if (p->options.auth_retry)
671
+	clear_user_pass_http();
672
+      store_proxy_authenticate(p, NULL);
673
+    }
534 674
 
535 675
   /* check return code, success = 200 */
536 676
   if (nparms < 1 || status != 200)
... ...
@@ -538,13 +872,7 @@ establish_http_proxy_passthru (struct http_proxy_info *p,
538 538
       msg (D_LINK_ERRORS, "HTTP proxy returned bad status");
539 539
 #if 0
540 540
       /* DEBUGGING -- show a multi-line HTTP error response */
541
-      while (true)
542
-	{
543
-	  if (!recv_line (sd, buf, sizeof (buf), p->options.timeout, true, NULL, signal_received))
544
-	    goto error;
545
-	  chomp (buf);
546
-	  msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
547
-	}
541
+      dump_residual(sd, p->options.timeout, signal_received);
548 542
 #endif
549 543
       goto error;
550 544
     }
... ...
@@ -55,18 +55,24 @@ void show_win_proxy_settings (const int msglevel);
55 55
 #ifdef ENABLE_HTTP_PROXY
56 56
 
57 57
 /* HTTP CONNECT authentication methods */
58
-#define HTTP_AUTH_NONE  0
59
-#define HTTP_AUTH_BASIC 1
60
-#define HTTP_AUTH_NTLM  2
61
-#define HTTP_AUTH_N     3
62
-#define HTTP_AUTH_NTLM2 4
58
+#define HTTP_AUTH_NONE   0
59
+#define HTTP_AUTH_BASIC  1
60
+#define HTTP_AUTH_DIGEST 2
61
+#define HTTP_AUTH_NTLM   3
62
+#define HTTP_AUTH_NTLM2  4
63
+#define HTTP_AUTH_N      5 /* number of HTTP_AUTH methods */
63 64
 
64 65
 struct http_proxy_options {
65 66
   const char *server;
66 67
   int port;
67 68
   bool retry;
68 69
   int timeout;
70
+
71
+# define PAR_NO  0  /* don't support any auth retries */
72
+# define PAR_ALL 1  /* allow all proxy auth protocols */
73
+# define PAR_NCT 2  /* disable cleartext proxy auth protocols */
69 74
   bool auth_retry;
75
+
70 76
   const char *auth_method_string;
71 77
   const char *auth_file;
72 78
   const char *http_version;
... ...
@@ -78,6 +84,7 @@ struct http_proxy_info {
78 78
   int auth_method;
79 79
   struct http_proxy_options options;
80 80
   struct user_pass up;
81
+  char *proxy_authenticate;
81 82
 };
82 83
 
83 84
 struct http_proxy_info *http_proxy_new (const struct http_proxy_options *o,
... ...
@@ -572,6 +572,15 @@ socket_defined (const socket_descriptor_t sd)
572 572
 #endif
573 573
 
574 574
 /*
575
+ * Should we include proxy digest auth functionality
576
+ */
577
+#if defined(USE_CRYPTO) && defined(ENABLE_HTTP_PROXY)
578
+#define PROXY_DIGEST_AUTH 1
579
+#else
580
+#define PROXY_DIGEST_AUTH 0
581
+#endif
582
+
583
+/*
575 584
  * Should we include code common to all proxy methods?
576 585
  */
577 586
 #if defined(ENABLE_HTTP_PROXY) || defined(ENABLE_SOCKS)
... ...
@@ -1,4 +1,4 @@
1
-import os
1
+import os, sys
2 2
 from wb import system, config, home_fn, cd_home
3 3
 
4 4
 os.environ['PATH'] += ";%s\\VC" % (os.path.normpath(config['MSVC']),)
... ...
@@ -10,6 +10,13 @@ def main():
10 10
     cd_home()
11 11
     build_vc("nmake /f %s" % (home_fn('msvc.mak'),))
12 12
 
13
+def clean():
14
+    cd_home()
15
+    build_vc("nmake /f %s clean" % (home_fn('msvc.mak'),))
16
+
13 17
 # if we are run directly, and not loaded as a module
14 18
 if __name__ == "__main__":
15
-    main()
19
+    if len(sys.argv) == 2 and sys.argv[1] == 'clean':
20
+        clean()
21
+    else:
22
+        main()