File irssi-0.8.12_update-ssl-code-to-HEAD.patch of Package irssi

Index: src/core/network-openssl.c
===================================================================
--- src/core/network-openssl.c	(.../tags/r_0_8_12/src/core/network-openssl.c)	(revision 5169)
+++ src/core/network-openssl.c	(.../trunk/src/core/network-openssl.c)	(revision 5169)
@@ -26,6 +26,7 @@
 
 #include <openssl/crypto.h>
 #include <openssl/x509.h>
+#include <openssl/x509v3.h>
 #include <openssl/pem.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
@@ -39,28 +40,174 @@
 	SSL *ssl;
 	SSL_CTX *ctx;
 	unsigned int verify:1;
+	const char *hostname;
 } GIOSSLChannel;
-	
-static SSL_CTX *ssl_ctx = NULL;
 
+static int ssl_inited = FALSE;
+
 static void irssi_ssl_free(GIOChannel *handle)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
 	g_io_channel_unref(chan->giochan);
 	SSL_free(chan->ssl);
-	if (chan->ctx != ssl_ctx)
-		SSL_CTX_free(chan->ctx);
+	SSL_CTX_free(chan->ctx);
 	g_free(chan);
 }
 
-static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, X509 *cert)
+/* Checks if the given string has internal NUL characters. */
+static gboolean has_internal_nul(const char* str, int len) {
+	/* Remove trailing nul characters. They would give false alarms */
+	while (len > 0 && str[len-1] == 0)
+		len--;
+	return strlen(str) != len;
+}
+
+/* tls_dns_name - Extract valid DNS name from subjectAltName value */
+static const char *tls_dns_name(const GENERAL_NAME * gn)
 {
-	if (SSL_get_verify_result(ssl) != X509_V_OK) {
+	const char *dnsname;
+
+	/* We expect the OpenSSL library to construct GEN_DNS extension objects as
+	   ASN1_IA5STRING values. Check we got the right union member. */
+	if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING) {
+		g_warning("Invalid ASN1 value type in subjectAltName");
+		return NULL;
+	}
+
+	/* Safe to treat as an ASCII string possibly holding a DNS name */
+	dnsname = (char *) ASN1_STRING_data(gn->d.ia5);
+
+	if (has_internal_nul(dnsname, ASN1_STRING_length(gn->d.ia5))) {
+		g_warning("Internal NUL in subjectAltName");
+		return NULL;
+	}
+
+	return dnsname;
+}
+
+/* tls_text_name - extract certificate property value by name */
+static char *tls_text_name(X509_NAME *name, int nid)
+{
+	int     pos;
+	X509_NAME_ENTRY *entry;
+	ASN1_STRING *entry_str;
+	int     utf8_length;
+	unsigned char *utf8_value;
+	char *result;
+
+	if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
+		return NULL;
+    }
+
+    entry = X509_NAME_get_entry(name, pos);
+    g_return_val_if_fail(entry != NULL, NULL);
+    entry_str = X509_NAME_ENTRY_get_data(entry);
+    g_return_val_if_fail(entry_str != NULL, NULL);
+
+    /* Convert everything into UTF-8. It's up to OpenSSL to do something
+	   reasonable when converting ASCII formats that contain non-ASCII
+	   content. */
+    if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
+    	g_warning("Error decoding ASN.1 type=%d", ASN1_STRING_type(entry_str));
+    	return NULL;
+    }
+
+    if (has_internal_nul((char *)utf8_value, utf8_length)) {
+    	g_warning("NUL character in hostname in certificate");
+    	OPENSSL_free(utf8_value);
+    	return NULL;
+    }
+
+    result = g_strdup((char *) utf8_value);
+	OPENSSL_free(utf8_value);
+	return result;
+}
+
+
+/** check if a hostname in the certificate matches the hostname we used for the connection */
+static gboolean match_hostname(const char *cert_hostname, const char *hostname)
+{
+	const char *hostname_left;
+
+	if (!strcasecmp(cert_hostname, hostname)) { /* exact match */
+		return TRUE;
+	} else if (cert_hostname[0] == '*' && cert_hostname[1] == '.' && cert_hostname[2] != 0) { /* wildcard match */
+		/* The initial '*' matches exactly one hostname component */
+		hostname_left = strchr(hostname, '.');
+		if (hostname_left != NULL && ! strcasecmp(hostname_left + 1, cert_hostname + 2)) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/* based on verify_extract_name from tls_client.c in postfix */
+static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname)
+{
+	int gen_index, gen_count;
+	gboolean matched = FALSE, has_dns_name = FALSE;
+	const char *cert_dns_name;
+	char *cert_subject_cn;
+	const GENERAL_NAME *gn;
+	STACK_OF(GENERAL_NAME) * gens;
+
+	/* Verify the dNSName(s) in the peer certificate against the hostname. */
+	gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+	if (gens) {
+		gen_count = sk_GENERAL_NAME_num(gens);
+		for (gen_index = 0; gen_index < gen_count && !matched; ++gen_index) {
+			gn = sk_GENERAL_NAME_value(gens, gen_index);
+			if (gn->type != GEN_DNS)
+				continue;
+
+			/* Even if we have an invalid DNS name, we still ultimately
+			   ignore the CommonName, because subjectAltName:DNS is
+			   present (though malformed). */
+			has_dns_name = TRUE;
+			cert_dns_name = tls_dns_name(gn);
+			if (cert_dns_name && *cert_dns_name) {
+				matched = match_hostname(cert_dns_name, hostname);
+			}
+    	}
+
+	    /* Free stack *and* member GENERAL_NAME objects */
+	    sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+	}
+
+	if (has_dns_name) {
+		if (! matched) {
+			/* The CommonName in the issuer DN is obsolete when SubjectAltName is available. */
+			g_warning("None of the Subject Alt Names in the certificate match hostname '%s'", hostname);
+		}
+		return matched;
+	} else { /* No subjectAltNames, look at CommonName */
+		cert_subject_cn = tls_text_name(X509_get_subject_name(cert), NID_commonName);
+	    if (cert_subject_cn && *cert_subject_cn) {
+	    	matched = match_hostname(cert_subject_cn, hostname);
+	    	if (! matched) {
+				g_warning("SSL certificate common name '%s' doesn't match host name '%s'", cert_subject_cn, hostname);
+	    	}
+	    } else {
+	    	g_warning("No subjectAltNames and no valid common name in certificate");
+	    }
+	    free(cert_subject_cn);
+	}
+
+	return matched;
+}
+
+static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, X509 *cert)
+{
+	long result;
+
+	result = SSL_get_verify_result(ssl);
+	if (result != X509_V_OK) {
 		unsigned char md[EVP_MAX_MD_SIZE];
 		unsigned int n;
 		char *str;
 
-		g_warning("Could not verify SSL servers certificate:");
+		g_warning("Could not verify SSL servers certificate: %s",
+				X509_verify_cert_error_string(result));
 		if ((str = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) == NULL)
 			g_warning("  Could not get subject-name from peer certificate");
 		else {
@@ -89,42 +236,49 @@
 			}
 		}
 		return FALSE;
+	} else if (! irssi_ssl_verify_hostname(cert, hostname)){
+		return FALSE;
 	}
 	return TRUE;
 }
 
-static GIOStatus ssl_errno(gint e)
-{
-	switch(e)
-	{
-		case EINVAL:
-			return G_IO_STATUS_ERROR;
-		case EINTR:
-		case EAGAIN:
-			return G_IO_STATUS_AGAIN;
-		default:
-			return G_IO_STATUS_ERROR;
-	}
-	/*UNREACH*/
-	return G_IO_STATUS_ERROR;
-}
-
 static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
-	gint err;
-	
-	err = SSL_read(chan->ssl, buf, len);
-	if(err < 0)
+	gint ret1, err;
+	const char *errstr;
+
+	ret1 = SSL_read(chan->ssl, buf, len);
+	if(ret1 <= 0)
 	{
 		*ret = 0;
-		if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
+		err = SSL_get_error(chan->ssl, ret1);
+		if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
 			return G_IO_STATUS_AGAIN;
-		return ssl_errno(errno);
+		else if(err == SSL_ERROR_ZERO_RETURN)
+			return G_IO_STATUS_EOF;
+		else if (err == SSL_ERROR_SYSCALL)
+		{
+			errstr = ERR_reason_error_string(ERR_get_error());
+			if (errstr == NULL && ret1 == -1)
+				errstr = strerror(errno);
+			if (errstr == NULL)
+				errstr = "server closed connection unexpectedly";
+		}
+		else
+		{
+			errstr = ERR_reason_error_string(ERR_get_error());
+			if (errstr == NULL)
+				errstr = "unknown SSL error";
+		}
+		g_warning("SSL read error: %s", errstr);
+		*gerr = g_error_new_literal(G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED,
+					    errstr);
+		return G_IO_STATUS_ERROR;
 	}
 	else
 	{
-		*ret = err;
+		*ret = ret1;
 		return G_IO_STATUS_NORMAL;
 	}
 	/*UNREACH*/
@@ -134,19 +288,40 @@
 static GIOStatus irssi_ssl_write(GIOChannel *handle, const gchar *buf, gsize len, gsize *ret, GError **gerr)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
-	gint err;
+	gint ret1, err;
+	const char *errstr;
 
-	err = SSL_write(chan->ssl, (const char *)buf, len);
-	if(err < 0)
+	ret1 = SSL_write(chan->ssl, (const char *)buf, len);
+	if(ret1 <= 0)
 	{
 		*ret = 0;
-		if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
+		err = SSL_get_error(chan->ssl, ret1);
+		if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
 			return G_IO_STATUS_AGAIN;
-		return ssl_errno(errno);
+		else if(err == SSL_ERROR_ZERO_RETURN)
+			errstr = "server closed connection";
+		else if (err == SSL_ERROR_SYSCALL)
+		{
+			errstr = ERR_reason_error_string(ERR_get_error());
+			if (errstr == NULL && ret1 == -1)
+				errstr = strerror(errno);
+			if (errstr == NULL)
+				errstr = "server closed connection unexpectedly";
+		}
+		else
+		{
+			errstr = ERR_reason_error_string(ERR_get_error());
+			if (errstr == NULL)
+				errstr = "unknown SSL error";
+		}
+		g_warning("SSL write error: %s", errstr);
+		*gerr = g_error_new_literal(G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED,
+					    errstr);
+		return G_IO_STATUS_ERROR;
 	}
 	else
 	{
-		*ret = err;
+		*ret = ret1;
 		return G_IO_STATUS_NORMAL;
 	}
 	/*UNREACH*/
@@ -156,17 +331,15 @@
 static GIOStatus irssi_ssl_seek(GIOChannel *handle, gint64 offset, GSeekType type, GError **gerr)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
-	GIOError e;
-	e = g_io_channel_seek(chan->giochan, offset, type);
-	return (e == G_IO_ERROR_NONE) ? G_IO_STATUS_NORMAL : G_IO_STATUS_ERROR;
+
+	return chan->giochan->funcs->io_seek(handle, offset, type, gerr);
 }
 
 static GIOStatus irssi_ssl_close(GIOChannel *handle, GError **gerr)
 {
 	GIOSSLChannel *chan = (GIOSSLChannel *)handle;
-	g_io_channel_close(chan->giochan);
 
-	return G_IO_STATUS_NORMAL;
+	return chan->giochan->funcs->io_close(handle, gerr);
 }
 
 static GSource *irssi_ssl_create_watch(GIOChannel *handle, GIOCondition cond)
@@ -205,40 +378,38 @@
 {
 	SSL_library_init();
 	SSL_load_error_strings();
-	
-	ssl_ctx = SSL_CTX_new(SSLv23_client_method());
-	if(!ssl_ctx)
-	{
-		g_error("Initialization of the SSL library failed");
-		return FALSE;
-	}
+	OpenSSL_add_all_algorithms();
+	ssl_inited = TRUE;
 
 	return TRUE;
 
 }
 
-static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
+static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *hostname, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
 {
 	GIOSSLChannel *chan;
 	GIOChannel *gchan;
-	int err, fd;
+	int fd;
 	SSL *ssl;
 	SSL_CTX *ctx = NULL;
 
 	g_return_val_if_fail(handle != NULL, NULL);
-	
-	if(!ssl_ctx && !irssi_ssl_init())
+
+	if(!ssl_inited && !irssi_ssl_init())
 		return NULL;
 
 	if(!(fd = g_io_channel_unix_get_fd(handle)))
 		return NULL;
 
-	if (mycert && *mycert) {	
+	ctx = SSL_CTX_new(SSLv23_client_method());
+	if (ctx == NULL) {
+		g_error("Could not allocate memory for SSL context");
+		return NULL;
+	}
+	SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+
+	if (mycert && *mycert) {
 		char *scert = NULL, *spkey = NULL;
-		if ((ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
-			g_error("Could not allocate memory for SSL context");
-			return NULL;
-		}
 		scert = convert_home(mycert);
 		if (mypkey && *mypkey)
 			spkey = convert_home(mypkey);
@@ -255,10 +426,6 @@
 	if ((cafile && *cafile) || (capath && *capath)) {
 		char *scafile = NULL;
 		char *scapath = NULL;
-		if (! ctx && (ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
-			g_error("Could not allocate memory for SSL context");
-			return NULL;
-		}
 		if (cafile && *cafile)
 			scafile = convert_home(cafile);
 		if (capath && *capath)
@@ -273,48 +440,54 @@
 		g_free(scafile);
 		g_free(scapath);
 		verify = TRUE;
+	} else {
+		if (!SSL_CTX_set_default_verify_paths(ctx))
+			g_warning("Could not load default certificates");
 	}
 
-	if (ctx == NULL)
-		ctx = ssl_ctx;
-	
 	if(!(ssl = SSL_new(ctx)))
 	{
 		g_warning("Failed to allocate SSL structure");
+		SSL_CTX_free(ctx);
 		return NULL;
 	}
 
-	if(!(err = SSL_set_fd(ssl, fd)))
+	if(!SSL_set_fd(ssl, fd))
 	{
 		g_warning("Failed to associate socket to SSL stream");
 		SSL_free(ssl);
-		if (ctx != ssl_ctx)
-			SSL_CTX_free(ctx);
+		SSL_CTX_free(ctx);
 		return NULL;
 	}
 
+	SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE |
+			SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
 	chan = g_new0(GIOSSLChannel, 1);
 	chan->fd = fd;
 	chan->giochan = handle;
 	chan->ssl = ssl;
 	chan->ctx = ctx;
 	chan->verify = verify;
+	chan->hostname = hostname;
 
 	gchan = (GIOChannel *)chan;
 	gchan->funcs = &irssi_ssl_channel_funcs;
 	g_io_channel_init(gchan);
-	
+	gchan->is_readable = gchan->is_writeable = TRUE;
+	gchan->use_buffer = FALSE;
+
 	return gchan;
 }
 
-GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
+GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, const char* hostname, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
 {
 	GIOChannel *handle, *ssl_handle;
 
 	handle = net_connect_ip(ip, port, my_ip);
 	if (handle == NULL)
 		return NULL;
-	ssl_handle  = irssi_ssl_get_iochannel(handle, cert, pkey, cafile, capath, verify);
+	ssl_handle  = irssi_ssl_get_iochannel(handle, hostname, cert, pkey, cafile, capath, verify);
 	if (ssl_handle == NULL)
 		g_io_channel_unref(handle);
 	return ssl_handle;
@@ -330,12 +503,25 @@
 	ret = SSL_connect(chan->ssl);
 	if (ret <= 0) {
 		err = SSL_get_error(chan->ssl, ret);
-		if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
-			errstr = ERR_reason_error_string(ERR_get_error());
-			g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "server closed connection");
-			return -1;
+		switch (err) {
+			case SSL_ERROR_WANT_READ:
+				return 1;
+			case SSL_ERROR_WANT_WRITE:
+				return 3;
+			case SSL_ERROR_ZERO_RETURN:
+				g_warning("SSL handshake failed: %s", "server closed connection");
+				return -1;
+			case SSL_ERROR_SYSCALL:
+				errstr = ERR_reason_error_string(ERR_get_error());
+				if (errstr == NULL && ret == -1)
+					errstr = strerror(errno);
+				g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "server closed connection unexpectedly");
+				return -1;
+			default:
+				errstr = ERR_reason_error_string(ERR_get_error());
+				g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "unknown SSL error");
+				return -1;
 		}
-		return err == SSL_ERROR_WANT_READ ? 1 : 3;
 	}
 
 	cert = SSL_get_peer_certificate(chan->ssl);
@@ -343,14 +529,14 @@
 		g_warning("SSL server supplied no certificate");
 		return -1;
 	}
-	ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, cert);
+	ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, chan->hostname, cert);
 	X509_free(cert);
 	return ret ? 0 : -1;
 }
 
 #else /* HAVE_OPENSSL */
 
-GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
+GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, const char* hostname, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify)
 {
 	g_warning("Connection failed: SSL support not enabled in this build.");
 	errno = ENOSYS;
openSUSE Build Service is sponsored by