GnuTLS is an LGPL alternative to OpenSSL, required for IMAP over SSL
support on Debian.

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=434599

This patch uses the OpenSSL compatibility library of GnuTLS for the same
behavior with minimal differences.

However, GnuTLS does not provide equivalents to the base64 and md5
routines needed for CRAM-MD5 authentication.  This patch also introduces
some additional files to provide the OpenSSL calls using libgcrypt and
base64 functions adapted from GnuTLS.

When compiling, GnuTLS is only used when NO_OPENSSL is defined.
---
 Makefile        |   16 +++++
 config.mak.in   |    1 +
 configure.ac    |   24 +++++++
 gcrypt-hmac.c   |   41 ++++++++++++
 gcrypt-hmac.h   |   14 ++++
 gnutls-base64.c |  197 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gnutls-base64.h |   12 ++++
 imap-send.c     |   31 +++++++--
 8 files changed, 331 insertions(+), 5 deletions(-)
 create mode 100644 gcrypt-hmac.c
 create mode 100644 gcrypt-hmac.h
 create mode 100644 gnutls-base64.c
 create mode 100644 gnutls-base64.h
diff --git a/Makefile b/Makefile
index 1f11618..444819d 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,9 @@ all::
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies BLK_SHA1.
 #
+# Define NO_GNUTLS if you do not have gnutls installed.  gnutls provides
+# SSL when OpenSSL is not used.
+#
 # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
 # transports.
@@ -1244,8 +1247,21 @@ ifndef NO_OPENSSL
 else
 	BASIC_CFLAGS += -DNO_OPENSSL
 	BLK_SHA1 = 1
+ifndef NO_GNUTLS
+	OPENSSL_LIBSSL = -lgnutls-openssl -lgnutls -lgcrypt
+	ifdef GNUTLSDIR
+		BASIC_CFLAGS += -I$(GNUTLSDIR)/include
+		OPENSSL_LINK = -L$(GNUTLSDIR)/$(lib) $(CC_LD_DYNPATH)$(GNUTLSDIR)/$(lib)
+	else
+		OPENSSL_LINK =
+	endif
+	LIB_OBJS += gnutls-base64.o gcrypt-hmac.o
+	LIB_H += gnutls-base64.h gcrypt-hmac.h
+else
+	BASIC_CFLAGS += -DNO_GNUTLS
 	OPENSSL_LIBSSL =
 endif
+endif
 ifdef NEEDS_SSL_WITH_CRYPTO
 	LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
 else
diff --git a/config.mak.in b/config.mak.in
index b4e65c3..70918a0 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -30,6 +30,7 @@ export srcdir VPATH
 asciido...@asciidoc8@
 needs_ssl_with_cryp...@needs_ssl_with_crypto@
 no_opens...@no_openssl@
+no_gnut...@no_gnutls@
 no_cu...@no_curl@
 no_exp...@no_expat@
 no_libgen...@no_libgen_h@
diff --git a/configure.ac b/configure.ac
index 5601e8b..a2643c8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -220,6 +220,16 @@ AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
 AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),\
 GIT_PARSE_WITH(openssl))
 #
+# Define NO_GNUTLS if you do not have gnutls installed.  gnutls provides
+# SSL when OpenSSL is not used.
+#
+# Define GNUTLSDIR=/foo/bar if your gnutls header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+AC_ARG_WITH(gnutls,
+AS_HELP_STRING([--with-gnutls],[use GnuTLS library (default is YES)])
+AS_HELP_STRING([],             [ARG can be prefix for gnutls library and headers]),\
+GIT_PARSE_WITH(gnutls))
+#
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
 # transports.
@@ -426,6 +436,20 @@ GIT_UNSTASH_FLAGS($OPENSSLDIR)
 AC_SUBST(NEEDS_SSL_WITH_CRYPTO)
 AC_SUBST(NO_OPENSSL)
 
+# Define NO_GNUTLS if you do not have gnutls installed.  gnutls provides
+# SSL when OpenSSL is not used.
+
+GIT_STASH_FLAGS($GNUTLSDIR)
+
+AC_CHECK_LIB([gnutls-openssl], [SSL_CTX_new],
+[NO_GNUTLS=],
+[NO_GNUTLS=YesPlease],
+[-lgnutls -lgcrypt])
+
+GIT_UNSTASH_FLAGS($GNUTLSDIR)
+
+AC_SUBST(NO_GNUTLS)
+
 #
 # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
diff --git a/gcrypt-hmac.c b/gcrypt-hmac.c
new file mode 100644
index 0000000..368aae3
--- /dev/null
+++ b/gcrypt-hmac.c
@@ -0,0 +1,41 @@
+/*
+ * gcrypt-hmac.c - interface wrapper to provide OpenSSL HMAC API using gcrypt.
+ *
+ * Currently implements only the functions needed by imap-send.
+ */
+
+#include "gcrypt-hmac.h"
+
+int EVP_md5()
+{
+	return GCRY_MD_MD5;
+}
+
+void HMAC_Init(HMAC_CTX *ctx, const void *key, int len, int md)
+{
+	gcry_md_open(ctx, md, GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
+	gcry_md_setkey(*ctx, key, len);
+}
+
+void HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len)
+{
+	gcry_md_write(*ctx, data, len);
+}
+
+void HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len)
+{
+	unsigned char *d;
+	unsigned int dlen;
+	int algo;
+
+	algo = gcry_md_get_algo(*ctx);
+	d = gcry_md_read(*ctx, algo);
+	dlen = gcry_md_get_algo_dlen(algo);
+	memcpy(md, d, dlen);
+	if (len) *len = dlen;
+}
+
+void HMAC_CTX_cleanup(HMAC_CTX *ctx)
+{
+	gcry_md_close(*ctx);
+}
diff --git a/gcrypt-hmac.h b/gcrypt-hmac.h
new file mode 100644
index 0000000..16b0658
--- /dev/null
+++ b/gcrypt-hmac.h
@@ -0,0 +1,14 @@
+#ifndef GCRYPT_HMAC_H
+#define GCRYPT_HMAC_H
+
+#include <gcrypt.h>
+
+typedef gcry_md_hd_t HMAC_CTX;
+
+int EVP_md5();
+void HMAC_Init(HMAC_CTX *ctx, const void *key, int len, int md);
+void HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len);
+void HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len);
+void HMAC_CTX_cleanup(HMAC_CTX *ctx);
+
+#endif /* GCRYPT_HMAC_H */
diff --git a/gnutls-base64.c b/gnutls-base64.c
new file mode 100644
index 0000000..81cdf29
--- /dev/null
+++ b/gnutls-base64.c
@@ -0,0 +1,197 @@
+/*
+ * gnutls-base64.c - base64 encode and decode
+ *                   adapted from GnuTLS, original copyright follows
+ *
+ * Copyright (C) 2000, 2001, 2003, 2004, 2005, 2008, 2010 Free Software
+ * Foundation, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA
+ *
+ */
+
+/* Functions that relate to base64 encoding and decoding.
+ */
+
+#include <string.h>
+#include "gnutls-base64.h"
+
+#define B64SIZE( data_size) ((data_size%3==0)?((data_size*4)/3):(4+((data_size/3)*4)))
+
+static const uint8_t b64table[] =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const uint8_t asciitable[128] = {
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+  0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+  0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff,
+  0xff, 0xf1, 0xff, 0xff, 0xff, 0x00,	/* 0xf1 for '=' */
+  0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+  0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+  0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+  0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+  0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
+  0x1f, 0x20, 0x21, 0x22, 0x23, 0x24,
+  0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
+  0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+  0x31, 0x32, 0x33, 0xff, 0xff, 0xff,
+  0xff, 0xff
+};
+
+inline static int
+encode (char *result, const uint8_t * data, int left)
+{
+
+  int data_len;
+
+  if (left > 3)
+    data_len = 3;
+  else
+    data_len = left;
+
+  switch (data_len)
+    {
+    case 3:
+      result[0] = b64table[(data[0] >> 2)];
+      result[1] =
+	b64table[(((((data[0] & 0x03) & 0xff) << 4) & 0xff) |
+		  (data[1] >> 4))];
+      result[2] =
+	b64table[((((data[1] & 0x0f) << 2) & 0xff) | (data[2] >> 6))];
+      result[3] = b64table[(((data[2] << 2) & 0xff) >> 2)];
+      break;
+    case 2:
+      result[0] = b64table[(data[0] >> 2)];
+      result[1] =
+	b64table[(((((data[0] & 0x03) & 0xff) << 4) & 0xff) |
+		  (data[1] >> 4))];
+      result[2] = b64table[(((data[1] << 4) & 0xff) >> 2)];
+      result[3] = '=';
+      break;
+    case 1:
+      result[0] = b64table[(data[0] >> 2)];
+      result[1] = b64table[(((((data[0] & 0x03) & 0xff) << 4) & 0xff))];
+      result[2] = '=';
+      result[3] = '=';
+      break;
+    default:
+      return -1;
+    }
+
+  return 4;
+
+}
+
+/* data must be 4 bytes
+ * result should be 3 bytes
+ */
+#define TOASCII(c) (c < 127 ? asciitable[c] : 0xff)
+inline static int
+decode (uint8_t * result, const uint8_t * data)
+{
+  uint8_t a1, a2;
+  int ret = 3;
+
+  a1 = TOASCII (data[0]);
+  a2 = TOASCII (data[1]);
+  if (a1 == 0xff || a2 == 0xff)
+    return -1;
+  result[0] = ((a1 << 2) & 0xff) | ((a2 >> 4) & 0xff);
+
+  a1 = a2;
+  a2 = TOASCII (data[2]);
+  if (a2 == 0xff)
+    return -1;
+  result[1] = ((a1 << 4) & 0xff) | ((a2 >> 2) & 0xff);
+
+  a1 = a2;
+  a2 = TOASCII (data[3]);
+  if (a2 == 0xff)
+    return -1;
+  result[2] = ((a1 << 6) & 0xff) | (a2 & 0xff);
+
+  if (data[2] == '=')
+    ret--;
+
+  if (data[3] == '=')
+    ret--;
+  return ret;
+}
+
+/* encodes data and puts the result into result (allocated by caller)
+ * The result_size is the return value
+ */
+int
+base64_encode (const uint8_t * data, size_t data_size,
+	       uint8_t * result)
+{
+  unsigned int i, j;
+  int ret, tmp;
+  char tmpres[4];
+
+  ret = B64SIZE (data_size);
+
+  for (i = j = 0; i < data_size; i += 3, j += 4)
+    {
+      tmp = encode (tmpres, &data[i], data_size - i);
+      if (tmp == -1)
+	{
+	  return tmp;
+	}
+      memcpy (&result[j], tmpres, tmp);
+    }
+  result[ret] = 0;		/* null terminated */
+
+  return ret;
+}
+
+/* decodes data and puts the result into result (allocated by caller)
+ * The result_size is the return value
+ */
+int
+base64_decode (const uint8_t * data, size_t data_size,
+	       uint8_t * result)
+{
+  unsigned int i, j;
+  int ret, tmp, est;
+  uint8_t tmpres[3];
+
+  est = ((data_size * 3) / 4) + 1;
+
+  ret = 0;
+  for (i = j = 0; i < data_size; i += 4, j += 3)
+    {
+      tmp = decode (tmpres, &data[i]);
+      if (tmp < 0)
+	{
+	  return tmp;
+	}
+      memcpy (&result[j], tmpres, tmp);
+      ret += tmp;
+    }
+  return ret;
+}
diff --git a/gnutls-base64.h b/gnutls-base64.h
new file mode 100644
index 0000000..b8e44cb
--- /dev/null
+++ b/gnutls-base64.h
@@ -0,0 +1,12 @@
+#ifndef GNUTLS_BASE64_H
+#define GNUTLS_BASE64_H
+
+#include <inttypes.h>
+
+int base64_encode(const uint8_t * data, size_t data_size, uint8_t * result);
+int base64_decode(const uint8_t * data, size_t data_size, uint8_t * result);
+
+#define EVP_EncodeBlock(t, f, n) base64_encode(f, n, t)
+#define EVP_DecodeBlock(t, f, n) base64_decode(f, n, t)
+
+#endif /* GNUTLS_BASE64_H */
diff --git a/imap-send.c b/imap-send.c
index 71506a8..0e49d38 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -26,10 +26,19 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #ifdef NO_OPENSSL
+#ifdef NO_GNUTLS
 typedef void *SSL;
 #else
+#include <gnutls/openssl.h>
+#include "gnutls-base64.h"
+#include "gcrypt-hmac.h"
+#undef NO_OPENSSL /* gnutls is providing an openssl API */
+#define SSL_VERIFY_PEER 1 /* doesn't matter */
+#endif
+#else
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
+#define USE_OPENSSL_REAL 1
 #endif
 
 struct store_conf {
@@ -307,9 +316,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	SSL_load_error_strings();
 
 	if (use_tls_only)
-		meth = TLSv1_method();
+		meth = TLSv1_client_method();
 	else
-		meth = SSLv23_method();
+		meth = SSLv23_client_method();
 
 	if (!meth) {
 		ssl_socket_perror("SSLv23_method");
@@ -321,10 +330,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 	if (verify)
 		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
+#ifdef USE_OPENSSL_REAL
 	if (!SSL_CTX_set_default_verify_paths(ctx)) {
 		ssl_socket_perror("SSL_CTX_set_default_verify_paths");
 		return -1;
 	}
+#endif
 	sock->ssl = SSL_new(ctx);
 	if (!sock->ssl) {
 		ssl_socket_perror("SSL_new");
@@ -371,9 +382,19 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len)
 {
 	int n;
 #ifndef NO_OPENSSL
-	if (sock->ssl)
-		n = SSL_write(sock->ssl, buf, len);
-	else
+	if (sock->ssl) {
+		/* loop based on write_in_full, the gnutls implementation of
+		 * SSL_write may write a partial buffer. */
+		int count = len;
+		n = 0;
+		while (count > 0) {
+			int written = SSL_write(sock->ssl, buf, count);
+			if (written <= 0) break;
+			count -= written;
+			buf += written;
+			n += written;
+		}
+	} else
 #endif
 		n = write_in_full(sock->fd[1], buf, len);
 	if (n != len) {

Reply via email to