/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast * * Copyright 2001 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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. * */ #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_NSS #include #include #include #include #include #include #include #include #include #include #include "nss.h" /* Don't use <> here or it will include the system nss.h instead */ #include #include #include #include #include "camel-tcp-stream-ssl.h" #include "camel-session.h" static CamelTcpStreamClass *parent_class = NULL; /* Returns the class for a CamelTcpStreamSSL */ #define CTSS_CLASS(so) CAMEL_TCP_STREAM_SSL_CLASS (CAMEL_OBJECT_GET_CLASS (so)) static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n); static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n); static int stream_flush (CamelStream *stream); static int stream_close (CamelStream *stream); static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port); static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data); static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data); static gpointer stream_get_socket (CamelTcpStream *stream); struct _CamelTcpStreamSSLPrivate { PRFileDesc *sockfd; CamelService *service; char *expected_host; }; static void camel_tcp_stream_ssl_class_init (CamelTcpStreamSSLClass *camel_tcp_stream_ssl_class) { CamelTcpStreamClass *camel_tcp_stream_class = CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_ssl_class); CamelStreamClass *camel_stream_class = CAMEL_STREAM_CLASS (camel_tcp_stream_ssl_class); parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ())); /* virtual method overload */ camel_stream_class->read = stream_read; camel_stream_class->write = stream_write; camel_stream_class->flush = stream_flush; camel_stream_class->close = stream_close; camel_tcp_stream_class->connect = stream_connect; camel_tcp_stream_class->getsockopt = stream_getsockopt; camel_tcp_stream_class->setsockopt = stream_setsockopt; camel_tcp_stream_class->get_socket = stream_get_socket; } static void camel_tcp_stream_ssl_init (gpointer object, gpointer klass) { CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object); stream->priv = g_new0 (struct _CamelTcpStreamSSLPrivate, 1); } static void camel_tcp_stream_ssl_finalize (CamelObject *object) { CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object); if (stream->priv->sockfd != NULL) PR_Close (stream->priv->sockfd); g_free (stream->priv->expected_host); g_free (stream->priv); } CamelType camel_tcp_stream_ssl_get_type (void) { static CamelType type = CAMEL_INVALID_TYPE; if (type == CAMEL_INVALID_TYPE) { type = camel_type_register (camel_tcp_stream_get_type (), "CamelTcpStreamSSL", sizeof (CamelTcpStreamSSL), sizeof (CamelTcpStreamSSLClass), (CamelObjectClassInitFunc) camel_tcp_stream_ssl_class_init, NULL, (CamelObjectInitFunc) camel_tcp_stream_ssl_init, (CamelObjectFinalizeFunc) camel_tcp_stream_ssl_finalize); } return type; } /** * camel_tcp_stream_ssl_new: * @service: camel service * @expected_host: host that the stream is expected to connect with. * * Since the SSL certificate authenticator may need to prompt the * user, a CamelService is needed. @expected_host is needed as a * protection against an MITM attack. * * Return value: a tcp stream **/ CamelStream * camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host) { CamelTcpStreamSSL *stream; stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ())); stream->priv->service = service; stream->priv->expected_host = g_strdup (expected_host); return CAMEL_STREAM (stream); } static void set_errno (int code) { /* FIXME: this should handle more. */ switch (code) { case PR_PENDING_INTERRUPT_ERROR: errno = EINTR; break; case PR_IO_PENDING_ERROR: case PR_IO_TIMEOUT_ERROR: errno = EAGAIN; break; case PR_WOULD_BLOCK_ERROR: errno = EWOULDBLOCK; break; case PR_IO_ERROR: default: errno = EIO; break; } } static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n) { CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream); ssize_t nread; do { if (camel_operation_cancel_check (NULL)) { errno = EINTR; return -1; } nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n); if (nread == -1) set_errno (PR_GetError ()); } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); return nread; } static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n) { CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream); ssize_t w, written = 0; do { if (camel_operation_cancel_check (NULL)) { errno = EINTR; return -1; } do { w = PR_Write (tcp_stream_ssl->priv->sockfd, buffer + written, n - written); if (w == -1) set_errno (PR_GetError ()); } while (w == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); if (w > 0) written += w; } while (w != -1 && written < n); return written; } static int stream_flush (CamelStream *stream) { return PR_Sync (((CamelTcpStreamSSL *)stream)->priv->sockfd); } static int stream_close (CamelStream *stream) { if (PR_Close (((CamelTcpStreamSSL *)stream)->priv->sockfd) == PR_FAILURE) return -1; ((CamelTcpStreamSSL *)stream)->priv->sockfd = NULL; return 0; } #if 0 /* Since this is default implementation, let NSS handle it. */ static SECStatus ssl_get_client_auth (void *data, PRFileDesc *sockfd, struct CERTDistNamesStr *caNames, struct CERTCertificateStr **pRetCert, struct SECKEYPrivateKeyStr **pRetKey) { SECStatus status = SECFailure; SECKEYPrivateKey *privkey; CERTCertificate *cert; void *proto_win; proto_win = SSL_RevealPinArg (sockfd); if ((char *)data) { cert = PK11_FindCertFromNickname ((char *)data, proto_win); if (cert) { privKey = PK11_FindKeyByAnyCert (cert, proto_win); if (privkey) { status = SECSuccess; } else { CERT_DestroyCertificate (cert); } } } else { /* no nickname given, automatically find the right cert */ CERTCertNicknames *names; int i; names = CERT_GetCertNicknames (CERT_GetDefaultCertDB (), SEC_CERT_NICKNAMES_USER, proto_win); if (names != NULL) { for (i = 0; i < names->numnicknames; i++) { cert = PK11_FindCertFromNickname (names->nicknames[i], proto_win); if (!cert) continue; /* Only check unexpired certs */ if (CERT_CheckCertValidTimes (cert, PR_Now (), PR_FALSE) != secCertTimeValid) { CERT_DestroyCertificate (cert); continue; } status = NSS_CmpCertChainWCANames (cert, caNames); if (status == SECSuccess) { privkey = PK11_FindKeyByAnyCert (cert, proto_win); if (privkey) break; status = SECFailure; break; } CERT_FreeNicknames (names); } } } if (status == SECSuccess) { *pRetCert = cert; *pRetKey = privkey; } return status; } #endif #if 0 /* Since this is the default NSS implementation, no need for us to use this. */ static SECStatus ssl_auth_cert (void *data, PRFileDesc *sockfd, PRBool checksig, PRBool is_server) { CERTCertificate *cert; SECStatus status; void *pinarg; char *host; cert = SSL_PeerCertificate (sockfd); pinarg = SSL_RevealPinArg (sockfd); status = CERT_VerifyCertNow ((CERTCertDBHandle *)data, cert, checksig, certUsageSSLClient, pinarg); if (status != SECSuccess) return SECFailure; /* Certificate is OK. Since this is the client side of an SSL * connection, we need to verify that the name field in the cert * matches the desired hostname. This is our defense against * man-in-the-middle attacks. */ /* SSL_RevealURL returns a hostname, not a URL. */ host = SSL_RevealURL (sockfd); if (host && *host) { status = CERT_VerifyCertName (cert, host); } else { PR_SetError (SSL_ERROR_BAD_CERT_DOMAIN, 0); status = SECFailure; } if (host) PR_Free (hostName); return secStatus; } #endif static void save_ssl_cert (const char *certid) { char *path, *filename; struct stat st; int fd; path = g_strdup_printf ("%s/.camel_certs", getenv ("HOME")); if (mkdir (path, 0700) == -1) { if (errno != EEXIST) return; if (stat (path, &st) == -1) return; if (!S_ISDIR (st.st_mode)) return; } filename = g_strdup_printf ("%s/%s", path, certid); g_free (path); fd = open (filename, O_WRONLY | O_CREAT, 0600); if (fd != -1) close (fd); g_free (filename); } static gboolean ssl_cert_is_saved (const char *certid) { char *filename; struct stat st; filename = g_strdup_printf ("%s/.camel_certs/%s", getenv ("HOME"), certid); if (stat (filename, &st) == -1) { g_free (filename); return FALSE; } g_free (filename); return st.st_uid == getuid (); } static SECStatus ssl_bad_cert (void *data, PRFileDesc *sockfd) { CamelTcpStreamSSL *ssl; CERTCertificate *cert; CamelService *service; char *prompt, *cert_str; gboolean accept; g_return_val_if_fail (data != NULL, SECFailure); g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (data), SECFailure); ssl = CAMEL_TCP_STREAM_SSL (data); service = ssl->priv->service; /* this is part of a work-around hack */ if (ssl_cert_is_saved (ssl->priv->expected_host)) return SECSuccess; cert = SSL_PeerCertificate (sockfd); cert_str = g_strdup_printf (_("EMail: %s\n" "Common Name: %s\n" "Organization Unit: %s\n" "Organization: %s\n" "Locality: %s\n" "State: %s\n" "Country: %s"), cert->emailAddr ? cert->emailAddr : "", CERT_GetCommonName (&cert->issuer) ? CERT_GetCommonName (&cert->issuer) : "", CERT_GetOrgUnitName (&cert->issuer) ? CERT_GetOrgUnitName (&cert->issuer) : "", CERT_GetOrgName (&cert->issuer) ? CERT_GetOrgName (&cert->issuer) : "", CERT_GetLocalityName (&cert->issuer) ? CERT_GetLocalityName (&cert->issuer) : "", CERT_GetStateName (&cert->issuer) ? CERT_GetStateName (&cert->issuer) : "", CERT_GetCountryName (&cert->issuer) ? CERT_GetCountryName (&cert->issuer) : ""); /* construct our user prompt */ prompt = g_strdup_printf (_("Bad certificate from %s:\n\n%s\n\nDo you wish to accept anyway?"), service->url->host, cert_str); g_free (cert_str); /* query the user to find out if we want to accept this certificate */ accept = camel_session_alert_user (service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE); g_free (prompt); if (accept) { SECItem *certs[1]; if (!cert->trust) cert->trust = PORT_ZAlloc (sizeof (CERTCertTrust)); cert->trust->sslFlags = CERTDB_VALID_PEER | CERTDB_TRUSTED; certs[0] = &cert->derCert; CERT_ImportCerts (CERT_GetDefaultCertDB (), certUsageSSLServer, 1, certs, NULL, TRUE, FALSE, cert->nickname); /* and since the above code doesn't seem to work... time for a good ol' fashioned hack */ save_ssl_cert (ssl->priv->expected_host); return SECSuccess; } return SECFailure; } static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port) { CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream); PRIntervalTime timeout = PR_INTERVAL_MIN; PRNetAddr netaddr; PRFileDesc *fd, *ssl_fd; g_return_val_if_fail (host != NULL, -1); memset ((void *) &netaddr, 0, sizeof (PRNetAddr)); memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip)); if (PR_InitializeNetAddr (PR_IpAddrNull, port, &netaddr) == PR_FAILURE) return -1; fd = PR_OpenTCPSocket (host->h_addrtype); ssl_fd = SSL_ImportFD (NULL, fd); SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE); SSL_SetURL (ssl_fd, ssl->priv->expected_host); if (ssl_fd == NULL || PR_Connect (ssl_fd, &netaddr, timeout) == PR_FAILURE) { if (ssl_fd != NULL) PR_Close (ssl_fd); return -1; } /*SSL_GetClientAuthDataHook (sslSocket, ssl_get_client_auth, (void *)certNickname);*/ /*SSL_AuthCertificateHook (ssl_fd, ssl_auth_cert, (void *) CERT_GetDefaultCertDB ());*/ SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl); ssl->priv->sockfd = ssl_fd; return 0; } static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data) { PRSocketOptionData sodata; memset ((void *) &sodata, 0, sizeof (sodata)); memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData)); if (PR_GetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE) return -1; memcpy ((void *) data, (void *) &sodata, sizeof (CamelSockOptData)); return 0; } static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data) { PRSocketOptionData sodata; memset ((void *) &sodata, 0, sizeof (sodata)); memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData)); if (PR_SetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE) return -1; return 0; } static gpointer stream_get_socket (CamelTcpStream *stream) { return (gpointer) CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd; } #endif /* HAVE_NSS */