Mercurial > pidgin
view libpurple/plugins/ssl/ssl-nss.c @ 15476:5e5f9ffa5b72
merge of '921d892371d1bd4b52a6f589936628d8a8c4ed72'
and 'ea057dea5d8e52e6c6bf8c6e1c13033619eb41f5'
author | Sadrul Habib Chowdhury <imadil@gmail.com> |
---|---|
date | Wed, 31 Jan 2007 06:59:45 +0000 |
parents | 5fe8042783c1 |
children | 32c366eeeb99 |
line wrap: on
line source
/** * @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)