aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-tcp-stream-ssl.c
blob: 167d94b8b7e1736271df749ba6f2bb1b3d3589b9 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                                                           


                                                                   
  



                                                                    
  



                                                               


   
                    
                   
      

               





                      
 



                    
                                                                                       
                

                   
                     
 


                                 









                                                                                
                                                                                      

                                                                                    
                                                           
 





                                  
 







                                                                                    
                                                                                                                







                                                         

                                                               
                                                               






                                                                  
                                                                    






                                                                  

                                                
        


                                             








                                                   
                                                                         











                                                                                                       

                            
                          


                                                                    
                                                                
                                     



                             
                                                                           




                                                                                            

                                                               
        


                                     




                                             



                                        


                                 


                                    
                         
                

                            


         






                                                                          
                                                                          


                                                                                             
        






                                                                          
                               

            








                                                                                                   

                       




                                  
                                                                     




                                  
                                                                                 

                          
                                                           

                 

 






































































                                                                                                               
 

                                                                               
                
                                                                                 
 
































                                                                           
 
      
 





















                                                                    
                                                       









                                      
                       


                                                                                   




                                         
                          
        
                                      

 
                
                                             
 
                               
                              
                              
                                
                        

                                                        
                                                                          
        

                                          
        



                                                         















                                                                                                                     
        
                                       


                                                                                                       
        
                                                                              
                                                                                                        
                        
        
                     
                                  
                

                                                                           
                
                                                                           
                
                                          
                

                                                                                         
                



                                                                
                                  
         



                          



                                                                       
                                                 
                          
                                





                                                                          
                                                                               


                                                 
                                         
 
                                                      
                                                      
        


                                                                                     



                          

                                                                                               
                                                    
        
                                   



                 








                                                                            
                                                                                                    














                                                                            
                                                                                                    



                          
 





                                                                      
                     
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  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 <config.h>
#endif

#ifdef HAVE_NSS
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include <nspr.h>
#include <prio.h>
#include <prerror.h>
#include <prerr.h>
#include "nss.h"    /* Don't use <> here or it will include the system nss.h instead */
#include <ssl.h>
#include <cert.h>
#include <certdb.h>
#include <pk11func.h>

#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 {
        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 {
        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 */