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

Reply via email to