hello list,
here's a patch to add basic ntlm support to openvpn 1.6.0
i've tested it with i386 linux & win32 against MS ISA proxy.
it includes base64 code from heimdal...
- hope it's useful to somebody.
William
--- openvpn/base64.c Thu Sep 2 17:00:25 2004
+++ openvpn-1.6.0/base64.c Thu Sep 2 17:06:36 2004
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 1995-2001 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include "base64.h"
+
+static char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int
+pos(char c)
+{
+ char *p;
+ for (p = base64_chars; *p; p++)
+ if (*p == c)
+ return p - base64_chars;
+ return -1;
+}
+
+int
+base64_encode(const void *data, int size, char **str)
+{
+ char *s, *p;
+ int i;
+ int c;
+ const unsigned char *q;
+
+ p = s = (char *) malloc(size * 4 / 3 + 4);
+ if (p == NULL)
+ return -1;
+ q = (const unsigned char *) data;
+ i = 0;
+ for (i = 0; i < size;) {
+ c = q[i++];
+ c *= 256;
+ if (i < size)
+ c += q[i];
+ i++;
+ c *= 256;
+ if (i < size)
+ c += q[i];
+ i++;
+ p[0] = base64_chars[(c & 0x00fc0000) >> 18];
+ p[1] = base64_chars[(c & 0x0003f000) >> 12];
+ p[2] = base64_chars[(c & 0x00000fc0) >> 6];
+ p[3] = base64_chars[(c & 0x0000003f) >> 0];
+ if (i > size)
+ p[3] = '=';
+ if (i > size + 1)
+ p[2] = '=';
+ p += 4;
+ }
+ *p = 0;
+ *str = s;
+ return strlen(s);
+}
+
+#define DECODE_ERROR 0xffffffff
+
+static unsigned int
+token_decode(const char *token)
+{
+ int i;
+ unsigned int val = 0;
+ int marker = 0;
+ if (strlen(token) < 4)
+ return DECODE_ERROR;
+ for (i = 0; i < 4; i++) {
+ val *= 64;
+ if (token[i] == '=')
+ marker++;
+ else if (marker > 0)
+ return DECODE_ERROR;
+ else
+ val += pos(token[i]);
+ }
+ if (marker > 2)
+ return DECODE_ERROR;
+ return (marker << 24) | val;
+}
+
+int
+base64_decode(const char *str, void *data)
+{
+ const char *p;
+ unsigned char *q;
+
+ q = data;
+ for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
+ unsigned int val = token_decode(p);
+ unsigned int marker = (val >> 24) & 0xff;
+ if (val == DECODE_ERROR)
+ return -1;
+ *q++ = (val >> 16) & 0xff;
+ if (marker < 2)
+ *q++ = (val >> 8) & 0xff;
+ if (marker < 1)
+ *q++ = val & 0xff;
+ }
+ return q - (unsigned char *) data;
+}
--- openvpn/base64.h Thu Sep 2 17:00:26 2004
+++ openvpn-1.6.0/base64.h Thu Sep 2 17:06:38 2004
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $KTH: base64.h,v 1.2 1999/12/02 16:58:45 joda Exp $ */
+
+#ifndef _BASE64_H_
+#define _BASE64_H_
+
+int base64_encode(const void *data, int size, char **str);
+int base64_decode(const char *str, void *data);
+
+#endif
--- openvpn/ntlm.c Thu Sep 2 17:00:33 2004
+++ openvpn-1.6.0/ntlm.c Thu Sep 2 17:06:43 2004
@@ -0,0 +1,188 @@
+/*
+ * ntlm proxy support for OpenVPN
+ *
+ * Copyright (C) 2004 William Preston
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef WIN32
+#include "config-win32.h"
+#else
+#include "config.h"
+#endif
+
+#include <openssl/des.h>
+#include <openssl/md4.h>
+#include "syshead.h"
+
+#include "common.h"
+#include "buffer.h"
+#include "misc.h"
+#include "io.h"
+#include "socket.h"
+#include "fdmisc.h"
+#include "proxy.h"
+#include "ntlm.h"
+#include "base64.h"
+#include "memdbg.h"
+
+
+static void create_des_keys(const unsigned char *hash, unsigned char *key)
+{
+ int i;
+
+ key[0] = hash[0];
+ key[1] = ((hash[0]&1)<<7)|(hash[1]>>1);
+ key[2] = ((hash[1]&3)<<6)|(hash[2]>>2);
+ key[3] = ((hash[2]&7)<<5)|(hash[3]>>3);
+ key[4] = ((hash[3]&15)<<4)|(hash[4]>>4);
+ key[5] = ((hash[4]&31)<<3)|(hash[5]>>5);
+ key[6] = ((hash[5]&63)<<2)|(hash[6]>>6);
+ key[7] = ((hash[6]&127)<<1);
+ des_set_odd_parity((des_cblock *)key);
+}
+
+
+static void gen_md4_hash (const char* data, int data_len, char *result)
+{
+ // result is 16 byte md4 hash
+
+ MD4_CTX c;
+ char md[16];
+
+ MD4_Init (&c);
+ MD4_Update (&c, data, data_len);
+ MD4_Final (md, &c);
+
+ memcpy (result, md, 16);
+}
+
+static int unicodize (char *dst, const char *src)
+{
+ // not really unicode...
+ int i = 0;
+ do
+ {
+ dst[i++] = *src;
+ dst[i++] = 0;
+ }
+ while (*src++);
+
+ return i;
+}
+
+const char *
+ntlm_phase_1 (const struct http_proxy_info *p)
+{
+ struct buffer out = alloc_buf_gc (96);
+ /* try a minimal NTLM handshake
+ *
+ * http://davenport.sourceforge.net/ntlm.html
+ *
+ * This message contains only the NTLMSSP signature,
+ * the NTLM message type,
+ * and the minimal set of flags (Negotiate NTLM and Negotiate OEM).
+ *
+ */
+ buf_printf (&out, "%s", "TlRMTVNTUAABAAAAAgIAAA==");
+ return (BSTR (&out));
+}
+
+const char *
+ntlm_phase_3 (const struct http_proxy_info *p, const char *phase_2)
+{
+ char pwbuf[sizeof (p->password) * 2]; // for unicode password
+ char buf2[128]; // decoded reply from proxy
+ char phase3[146];
+
+ char md4_hash[21];
+ char challenge[8], response[24];
+ int i, ret_val, buflen;
+ uint8_t *b64;
+ des_cblock key1, key2, key3;
+ des_key_schedule sched1, sched2, sched3;
+
+ /* try a minimal NTLM handshake
+ *
+ * http://davenport.sourceforge.net/ntlm.html
+ *
+ */
+ ASSERT (strlen (p->username) > 0);
+ ASSERT (strlen (p->password) > 0);
+
+ // fill 1st 16 bytes with md4 hash, disregard terminating null
+ gen_md4_hash (pwbuf, unicodize (pwbuf, p->password) - 2, md4_hash);
+
+ //pad to 21 bytes
+ memset (md4_hash + 16, 0, 5);
+
+ ret_val = base64_decode( phase_2, (void *)buf2);
+ /* we can be sure that phase_2 is less than 128
+ * therefore buf2 needs to be (3/4 * 128) */
+
+ // extract the challenge from bytes 24-31
+ for (i=0; i<8; i++)
+ {
+ challenge[i] = buf2[i+24];
+ }
+
+
+ create_des_keys (md4_hash, key1);
+ des_set_key_unchecked ((des_cblock *)key1, sched1);
+ des_ecb_encrypt ((des_cblock *)challenge, (des_cblock *)response, sched1, DES_ENCRYPT);
+
+ create_des_keys (&(md4_hash[7]), key2);
+ des_set_key_unchecked ((des_cblock *)key2, sched2);
+ des_ecb_encrypt ((des_cblock *)challenge, (des_cblock *)&(response[8]), sched2, DES_ENCRYPT);
+
+ create_des_keys (&(md4_hash[14]), key3);
+ des_set_key_unchecked ((des_cblock *)key3, sched3);
+ des_ecb_encrypt ((des_cblock *)challenge, (des_cblock *)&(response[16]), sched3, DES_ENCRYPT);
+
+ //clear reply
+ memset (phase3, 0, sizeof (phase3));
+
+ strcpy (phase3, "NTLMSSP\0");
+ phase3[8] = 3; // type 3
+
+ buflen = 0x58 + strlen (p->username);
+ if (buflen > sizeof (phase3))
+ buflen = sizeof (phase3);
+
+ phase3[0x10] = buflen; // lm not used
+ phase3[0x20] = buflen; // default domain (i.e. proxy's domain)
+ phase3[0x30] = buflen; // no workstation name supplied
+ phase3[0x38] = buflen; // no session key
+
+ phase3[0x14] = 24; // ntlm response is 24 bytes long
+ phase3[0x16] = phase3[0x14];
+ phase3[0x18] = 0x40; // ntlm offset
+ memcpy (&(phase3[0x40]), response, 24);
+
+
+ phase3[0x24] = strlen (p->username); // username in ascii
+ phase3[0x26] = phase3[0x24];
+ phase3[0x28] = 0x58;
+ strncpy (&(phase3[0x58]), p->username, sizeof (phase3) - 0x58);
+
+ phase3[0x3c] = 0x02; // negotiate oem
+ phase3[0x3d] = 0x02; // negotiate ntlm
+
+
+ return (make_base64_string2 (phase3, buflen));
+
+}
--- openvpn/ntlm.h Thu Sep 2 17:00:35 2004
+++ openvpn-1.6.0/ntlm.h Thu Sep 2 17:06:45 2004
@@ -0,0 +1,7 @@
+#ifndef NTLM_H
+#define NTLM_H
+
+const char * ntlm_phase_1 (const struct http_proxy_info *p);
+const char * ntlm_phase_3 (const struct http_proxy_info *p, const char *phase_2);
+
+#endif
--- openvpn/options.c Thu Mar 4 09:50:32 2004
+++ openvpn-1.6.0/options.c Tue Aug 3 17:21:28 2004
@@ -86,9 +86,10 @@
" p = udp (default), tcp-server, or tcp-client\n"
"--connect-retry n : For --proto tcp-client, number of seconds to wait\n"
" between connection retries (default=%d).\n"
- "--http-proxy s p [up]: Connect to remote host through an HTTP proxy at address\n"
+ "--http-proxy s p [up] [auth]: Connect to remote host through an HTTP proxy at address\n"
" s and port p. If proxy authentication is required, up is a\n"
- " file containing username/password on 2 lines.\n"
+ " file containing username/password on 2 lines. Add ntlm if\n"
+ " the proxy requires NTLM authentication\n"
"--http-proxy-retry : Retry indefinitely on HTTP proxy errors.\n"
"--socks-proxy s [p]: Connect to remote host through a Socks5 proxy at address\n"
" s and port p (default port = 1080).\n"
@@ -1481,6 +1482,12 @@
++i;
options->http_proxy_auth_method = "basic";
options->http_proxy_auth_file = p[3];
+
+ if (p[4])
+ {
+ options->http_proxy_auth_method = p[4];
+ }
+
}
else
{
--- openvpn/proxy.c Mon Feb 23 04:58:45 2004
+++ openvpn-1.6.0/proxy.c Thu Sep 2 17:32:30 2004
@@ -38,6 +38,7 @@
#include "socket.h"
#include "fdmisc.h"
#include "proxy.h"
+#include "ntlm.h"
#include "memdbg.h"
@@ -185,8 +186,8 @@
return send_line_crlf (sd, "");
}
-static uint8_t *
-make_base64_string (const uint8_t *str)
+uint8_t *
+make_base64_string2 (const uint8_t *str, int src_len)
{
static const char base64_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -194,10 +195,9 @@
uint8_t *buf;
const uint8_t *src;
uint8_t *dst;
- int bits, data, src_len, dst_len;
+ int bits, data, dst_len;
/* make base64 string */
- src_len = strlen (str);
dst_len = (src_len + 2) / 3 * 4;
buf = gc_malloc (dst_len + 1);
bits = data = 0;
@@ -209,8 +209,7 @@
{
data = (data << 8) | *src;
bits += 8;
- if (*src != 0)
- src++;
+ src++;
}
*dst++ = base64_table[0x3F & (data >> (bits - 6))];
bits -= 6;
@@ -228,6 +227,12 @@
return buf;
}
+uint8_t *
+make_base64_string (const uint8_t *str)
+{
+ return make_base64_string2 (str, strlen (str));
+}
+
static const char *
username_password_as_base64 (const struct http_proxy_info *p)
{
@@ -261,20 +266,21 @@
p->auth_method = HTTP_AUTH_NONE;
else if (!strcmp (auth_method, "basic"))
p->auth_method = HTTP_AUTH_BASIC;
+ else if (!strcmp (auth_method, "ntlm"))
+ p->auth_method = HTTP_AUTH_NTLM;
else
- msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s' -- only the 'none' or 'basic' methods are currently supported",
+ msg (M_FATAL, "ERROR: unknown HTTP authentication method: '%s' -- only the 'none', 'basic', or 'ntlm' methods are currently supported",
auth_method);
}
/* only basic authentication supported so far */
- if (p->auth_method == HTTP_AUTH_BASIC)
+ if (p->auth_method == HTTP_AUTH_BASIC || p->auth_method == HTTP_AUTH_NTLM)
{
FILE *fp;
if (!auth_file)
msg (M_FATAL, "ERROR: http proxy authentication requires a username/password file");
- p->auth_method = HTTP_AUTH_BASIC;
warn_if_group_others_accessible (auth_file);
fp = fopen (auth_file, "r");
if (!fp)
@@ -304,7 +310,9 @@
struct buffer *lookahead,
volatile int *signal_received)
{
- char buf[128];
+ char buf[256];
+ char buf2[128];
+ char get[80];
int status;
int nparms;
@@ -316,6 +324,13 @@
if (!send_line_crlf (sd, buf))
goto error;
+ /* send HOST etc, */
+ sleep (1);
+ openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
+ msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+ if (!send_line_crlf (sd, buf))
+ goto error;
+
/* auth specified? */
switch (p->auth_method)
{
@@ -332,6 +347,16 @@
goto error;
break;
+ case HTTP_AUTH_NTLM:
+ openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
+ ntlm_phase_1 (p));
+ msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 1");
+ msg (D_SHOW_KEYS, "Send to HTTP proxy: '%s'", buf);
+ sleep (1);
+ if (!send_line_crlf (sd, buf))
+ goto error;
+ break;
+
default:
ASSERT (0);
}
@@ -353,11 +378,94 @@
/* parse return string */
nparms = sscanf (buf, "%*s %d", &status);
+ /* check for a "407 Proxy Authentication Required" response */
+ if (nparms >= 1 && status == 407)
+ {
+ msg (D_PROXY, "Proxy requires authentication");
+
+ // check for NTLM
+ if (p->auth_method == HTTP_AUTH_NTLM)
+ {
+ // look for the phase 2 response
+
+ while (true)
+ {
+ if (!recv_line (sd, buf, sizeof(buf), 5, true, NULL, signal_received))
+ goto error;
+ chomp (buf);
+ msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
+
+ snprintf (get, sizeof get, "%%*s NTLM %%%ds", sizeof (buf2) - 1);
+ nparms = sscanf (buf, get, buf2);
+ buf2[127] = 0; // we only need the beginning - ensure it's null terminated.
+
+ // check for "Proxy-Authenticate: NTLM TlRM..."
+ if (nparms == 1)
+ {
+ // parse buf2
+ msg (D_PROXY, "auth string: '%s'", buf2);
+ break;
+ }
+ }
+ // if we are here then auth string was got
+ msg (D_PROXY, "Received NTLM Proxy-Authorization phase 2 response");
+
+ /* receive and discard everything else */
+ while (recv_line (sd, NULL, 0, 5, true, NULL, signal_received))
+ ;
+
+ /* now send the phase 3 reply */
+
+ /* format HTTP CONNECT message */
+ openvpn_snprintf (buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0", host, port);
+ msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+
+ /* send HTTP CONNECT message to proxy */
+ if (!send_line_crlf (sd, buf))
+ goto error;
+
+ /* send HOST etc, */
+ sleep (1);
+ openvpn_snprintf (buf, sizeof(buf), "Host: %s", host);
+ msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+ if (!send_line_crlf (sd, buf))
+ goto error;
+
+ openvpn_snprintf (buf, sizeof(buf), "Proxy-Authorization: NTLM %s",
+ ntlm_phase_3 (p, buf2));
+ msg (D_PROXY, "Attempting NTLM Proxy-Authorization phase 3");
+ msg (D_PROXY, "Send to HTTP proxy: '%s'", buf);
+ sleep (1);
+ if (!send_line_crlf (sd, buf))
+ goto error;
+ // ok so far...
+ /* send empty CR, LF */
+ sleep (1);
+ if (!send_crlf (sd))
+ goto error;
+
+ /* receive reply from proxy */
+ if (!recv_line (sd, buf, sizeof(buf), 5, true, NULL, signal_received))
+ goto error;
+
+ /* remove trailing CR, LF */
+ chomp (buf);
+
+ msg (D_PROXY, "HTTP proxy returned: '%s'", buf);
+
+ /* parse return string */
+ nparms = sscanf (buf, "%*s %d", &status);
+
+ }
+ else goto error;
+ }
+
+
/* check return code, success = 200 */
- if (nparms != 1 || status != 200)
+ if (nparms < 1 || status != 200)
{
msg (D_LINK_ERRORS, "HTTP proxy returned bad status");
-#if 0
+#if 0
/* DEBUGGING -- show a multi-line HTTP error response */
while (true)
{
--- openvpn/proxy.h Thu Jan 15 08:23:26 2004
+++ openvpn-1.6.0/proxy.h Thu Sep 2 17:32:31 2004
@@ -30,6 +30,7 @@
#define HTTP_AUTH_NONE 0
#define HTTP_AUTH_BASIC 1
#define HTTP_AUTH_N 2
+#define HTTP_AUTH_NTLM 3
struct http_proxy_info {
bool defined;
@@ -57,4 +58,7 @@
struct buffer *lookahead,
volatile int *signal_received);
+uint8_t * make_base64_string2 (const uint8_t *str, int str_len);
+uint8_t * make_base64_string (const uint8_t *str);
+
#endif