diff libpurple/plugins/ssl/ssl-nss.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,433 @@
+/**
+ * @file ssl-nss.c Mozilla NSS SSL plugin.
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "internal.h"
+#include "debug.h"
+#include "plugin.h"
+#include "sslconn.h"
+#include "version.h"
+
+#define SSL_NSS_PLUGIN_ID "ssl-nss"
+
+#ifdef HAVE_NSS
+
+#undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
+
+#include <nspr.h>
+#include <private/pprio.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+typedef struct
+{
+	PRFileDesc *fd;
+	PRFileDesc *in;
+	guint handshake_handler;
+
+} GaimSslNssData;
+
+#define GAIM_SSL_NSS_DATA(gsc) ((GaimSslNssData *)gsc->private_data)
+
+static const PRIOMethods *_nss_methods = NULL;
+static PRDescIdentity _identity;
+
+/* Thank you, Evolution */
+static void
+set_errno(int code)
+{
+	/* FIXME: this should handle more. */
+	switch (code) {
+	case PR_INVALID_ARGUMENT_ERROR:
+		errno = EINVAL;
+		break;
+	case PR_PENDING_INTERRUPT_ERROR:
+		errno = EINTR;
+		break;
+	case PR_IO_PENDING_ERROR:
+		errno = EAGAIN;
+		break;
+	case PR_WOULD_BLOCK_ERROR:
+		errno = EAGAIN;
+		/*errno = EWOULDBLOCK; */
+		break;
+	case PR_IN_PROGRESS_ERROR:
+		errno = EINPROGRESS;
+		break;
+	case PR_ALREADY_INITIATED_ERROR:
+		errno = EALREADY;
+		break;
+	case PR_NETWORK_UNREACHABLE_ERROR:
+		errno = EHOSTUNREACH;
+		break;
+	case PR_CONNECT_REFUSED_ERROR:
+		errno = ECONNREFUSED;
+		break;
+	case PR_CONNECT_TIMEOUT_ERROR:
+	case PR_IO_TIMEOUT_ERROR:
+		errno = ETIMEDOUT;
+		break;
+	case PR_NOT_CONNECTED_ERROR:
+		errno = ENOTCONN;
+		break;
+	case PR_CONNECT_RESET_ERROR:
+		errno = ECONNRESET;
+		break;
+	case PR_IO_ERROR:
+	default:
+		errno = EIO;
+		break;
+	}
+}
+
+static void
+ssl_nss_init_nss(void)
+{
+	char *lib;
+	PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+	NSS_NoDB_Init(NULL);
+
+	/* TODO: Fix this so autoconf does the work trying to find this lib. */
+#ifndef _WIN32
+	lib = g_strdup(BR_LIBDIR("/libnssckbi.so"));
+#else
+	lib = g_strdup("nssckbi.dll");
+#endif
+	SECMOD_AddNewModule("Builtins", lib, 0, 0);
+	g_free(lib);
+	NSS_SetDomesticPolicy();
+
+	_identity = PR_GetUniqueIdentity("Gaim");
+	_nss_methods = PR_GetDefaultIOMethods();
+}
+
+static SECStatus
+ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig,
+			  PRBool is_server)
+{
+	return SECSuccess;
+
+#if 0
+	CERTCertificate *cert;
+	void *pinArg;
+	SECStatus status;
+
+	cert = SSL_PeerCertificate(socket);
+	pinArg = SSL_RevealPinArg(socket);
+
+	status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig,
+								certUsageSSLClient, pinArg);
+
+	if (status != SECSuccess) {
+		gaim_debug_error("nss", "CERT_VerifyCertNow failed\n");
+		CERT_DestroyCertificate(cert);
+		return status;
+	}
+
+	CERT_DestroyCertificate(cert);
+	return SECSuccess;
+#endif
+}
+
+static SECStatus
+ssl_bad_cert(void *arg, PRFileDesc *socket)
+{
+	SECStatus status = SECFailure;
+	PRErrorCode err;
+
+	if (arg == NULL)
+		return status;
+
+	*(PRErrorCode *)arg = err = PORT_GetError();
+
+	switch (err)
+	{
+		case SEC_ERROR_INVALID_AVA:
+		case SEC_ERROR_INVALID_TIME:
+		case SEC_ERROR_BAD_SIGNATURE:
+		case SEC_ERROR_EXPIRED_CERTIFICATE:
+		case SEC_ERROR_UNKNOWN_ISSUER:
+		case SEC_ERROR_UNTRUSTED_CERT:
+		case SEC_ERROR_CERT_VALID:
+		case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+		case SEC_ERROR_CRL_EXPIRED:
+		case SEC_ERROR_CRL_BAD_SIGNATURE:
+		case SEC_ERROR_EXTENSION_VALUE_INVALID:
+		case SEC_ERROR_CA_CERT_INVALID:
+		case SEC_ERROR_CERT_USAGES_INVALID:
+		case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+			status = SECSuccess;
+			break;
+
+		default:
+			status = SECFailure;
+			break;
+	}
+
+	gaim_debug_error("nss", "Bad certificate: %d\n", err);
+
+	return status;
+}
+
+static gboolean
+ssl_nss_init(void)
+{
+   return TRUE;
+}
+
+static void
+ssl_nss_uninit(void)
+{
+	PR_Cleanup();
+
+	_nss_methods = NULL;
+}
+
+static void
+ssl_nss_handshake_cb(gpointer data, int fd, GaimInputCondition cond)
+{
+	GaimSslConnection *gsc = (GaimSslConnection *)data;
+	GaimSslNssData *nss_data = gsc->private_data;
+
+	/* I don't think this the best way to do this...
+	 * It seems to work because it'll eventually use the cached value
+	 */
+	if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
+		set_errno(PR_GetError());
+		if (errno == EAGAIN || errno == EWOULDBLOCK)
+			return;
+
+		gaim_debug_error("nss", "Handshake failed %d\n", PR_GetError());
+
+		if (gsc->error_cb != NULL)
+			gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
+
+		gaim_ssl_close(gsc);
+
+		return;
+	}
+
+	gaim_input_remove(nss_data->handshake_handler);
+	nss_data->handshake_handler = 0;
+
+	gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+}
+
+static void
+ssl_nss_connect(GaimSslConnection *gsc)
+{
+	GaimSslNssData *nss_data = g_new0(GaimSslNssData, 1);
+	PRSocketOptionData socket_opt;
+
+	gsc->private_data = nss_data;
+
+	nss_data->fd = PR_ImportTCPSocket(gsc->fd);
+
+	if (nss_data->fd == NULL)
+	{
+		gaim_debug_error("nss", "nss_data->fd == NULL!\n");
+
+		if (gsc->error_cb != NULL)
+			gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+
+		gaim_ssl_close((GaimSslConnection *)gsc);
+
+		return;
+	}
+
+	socket_opt.option = PR_SockOpt_Nonblocking;
+	socket_opt.value.non_blocking = PR_TRUE;
+
+	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS)
+		gaim_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError());
+
+	nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
+
+	if (nss_data->in == NULL)
+	{
+		gaim_debug_error("nss", "nss_data->in == NUL!\n");
+
+		if (gsc->error_cb != NULL)
+			gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+
+		gaim_ssl_close((GaimSslConnection *)gsc);
+
+		return;
+	}
+
+	SSL_OptionSet(nss_data->in, SSL_SECURITY,            PR_TRUE);
+	SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+	SSL_AuthCertificateHook(nss_data->in,
+							(SSLAuthCertificate)ssl_auth_cert,
+							(void *)CERT_GetDefaultCertDB());
+	SSL_BadCertHook(nss_data->in, (SSLBadCertHandler)ssl_bad_cert, NULL);
+
+	if(gsc->host)
+		SSL_SetURL(nss_data->in, gsc->host);
+
+#if 0
+	/* This seems like it'd the be the correct way to implement the
+	nonblocking stuff, but it doesn't seem to work */
+	SSL_HandshakeCallback(nss_data->in,
+		(SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
+#endif
+	SSL_ResetHandshake(nss_data->in, PR_FALSE);
+
+	nss_data->handshake_handler = gaim_input_add(gsc->fd,
+		GAIM_INPUT_READ, ssl_nss_handshake_cb, gsc);
+
+	ssl_nss_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
+}
+
+static void
+ssl_nss_close(GaimSslConnection *gsc)
+{
+	GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+	if(!nss_data)
+		return;
+
+	if (nss_data->in) PR_Close(nss_data->in);
+	/* if (nss_data->fd) PR_Close(nss_data->fd); */
+
+	if (nss_data->handshake_handler)
+		gaim_input_remove(nss_data->handshake_handler);
+
+	g_free(nss_data);
+	gsc->private_data = NULL;
+}
+
+static size_t
+ssl_nss_read(GaimSslConnection *gsc, void *data, size_t len)
+{
+	ssize_t ret;
+	GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+	ret = PR_Read(nss_data->in, data, len);
+
+	if (ret == -1)
+		set_errno(PR_GetError());
+
+	return ret;
+}
+
+static size_t
+ssl_nss_write(GaimSslConnection *gsc, const void *data, size_t len)
+{
+	ssize_t ret;
+	GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+	if(!nss_data)
+		return 0;
+
+	ret = PR_Write(nss_data->in, data, len);
+
+	if (ret == -1)
+		set_errno(PR_GetError());
+
+	return ret;
+}
+
+static GaimSslOps ssl_ops =
+{
+	ssl_nss_init,
+	ssl_nss_uninit,
+	ssl_nss_connect,
+	ssl_nss_close,
+	ssl_nss_read,
+	ssl_nss_write
+};
+
+#endif /* HAVE_NSS */
+
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+#ifdef HAVE_NSS
+	if (!gaim_ssl_get_ops()) {
+		gaim_ssl_set_ops(&ssl_ops);
+	}
+
+	/* Init NSS now, so others can use it even if sslconn never does */
+	ssl_nss_init_nss();
+
+	return TRUE;
+#else
+	return FALSE;
+#endif
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+#ifdef HAVE_NSS
+	if (gaim_ssl_get_ops() == &ssl_ops) {
+		gaim_ssl_set_ops(NULL);
+	}
+#endif
+
+	return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+	GAIM_PLUGIN_MAGIC,
+	GAIM_MAJOR_VERSION,
+	GAIM_MINOR_VERSION,
+	GAIM_PLUGIN_STANDARD,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	GAIM_PLUGIN_FLAG_INVISIBLE,                       /**< flags          */
+	NULL,                                             /**< dependencies   */
+	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
+
+	SSL_NSS_PLUGIN_ID,                             /**< id             */
+	N_("NSS"),                                        /**< name           */
+	VERSION,                                          /**< version        */
+	                                                  /**  summary        */
+	N_("Provides SSL support through Mozilla NSS."),
+	                                                  /**  description    */
+	N_("Provides SSL support through Mozilla NSS."),
+	"Christian Hammond <chipx86@gnupdate.org>",
+	GAIM_WEBSITE,                                     /**< homepage       */
+
+	plugin_load,                                      /**< load           */
+	plugin_unload,                                    /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	NULL,                                             /**< extra_info     */
+	NULL,                                             /**< prefs_info     */
+	NULL                                              /**< actions        */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(ssl_nss, init_plugin, info)