aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-tcp-stream-ssl.c
blob: 614b3c552ea040abba4238ac3466468440316bd9 (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.
 *
 */

/* NOTE: This is the default implementation of CamelTcpStreamSSL,
 * used when the Mozilla NSS libraries are used. If you configured
 * OpenSSL support instead, then this file won't be compiled and
 * the CamelTcpStreamSSL implementation in camel-tcp-stream-openssl.c
 * will be used instead.
 */


#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 <secerr.h>
#include <sslerr.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>

/* this is commented because otherwise we get an error about the
   redefinition of MD5Context...yay */
/*#include <e-util/md5-utils.h>*/

#include "camel-tcp-stream-ssl.h"
#include "camel-stream-fs.h"
#include "camel-session.h"
#include "camel-certdb.h"

/* from md5-utils.h */
void md5_get_digest (const char *buffer, int buffer_size, unsigned char digest[16]);


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 PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd);

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 CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream);
static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream);

struct _CamelTcpStreamSSLPrivate {
    PRFileDesc *sockfd;
    
    CamelService *service;
    char *expected_host;
    gboolean ssl_mode;
    guint32 flags;
};

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_local_address  = stream_get_local_address;
    camel_tcp_stream_class->get_remote_address = stream_get_remote_address;
}

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.
 * @flags: ENABLE_SSL2, ENABLE_SSL3 and/or ENABLE_TLS
 *
 * 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 ssl stream (in ssl mode)
 **/
CamelStream *
camel_tcp_stream_ssl_new (CamelService *service, const char *expected_host, guint32 flags)
{
    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);
    stream->priv->ssl_mode = TRUE;
    stream->priv->flags = flags;
    
    return CAMEL_STREAM (stream);
}


/**
 * camel_tcp_stream_ssl_new_raw:
 * @service: camel service
 * @expected_host: host that the stream is expected to connect with.
 * @flags: ENABLE_SSL2, ENABLE_SSL3 and/or ENABLE_TLS
 *
 * 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 ssl-capable stream (in non ssl mode)
 **/
CamelStream *
camel_tcp_stream_ssl_new_raw (CamelService *service, const char *expected_host, guint32 flags)
{
    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);
    stream->priv->ssl_mode = FALSE;
    stream->priv->flags = flags;
    
    return CAMEL_STREAM (stream);
}


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 = 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;
    }
}


/**
 * camel_tcp_stream_ssl_enable_ssl:
 * @ssl: ssl stream
 *
 * Toggles an ssl-capable stream into ssl mode (if it isn't already).
 *
 * Returns 0 on success or -1 on fail.
 **/
int
camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl)
{
    PRFileDesc *fd;
    
    g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (ssl), -1);
    
    if (ssl->priv->sockfd && !ssl->priv->ssl_mode) {
        if (!(fd = enable_ssl (ssl, NULL))) {
            set_errno (PR_GetError ());
            return -1;
        }
        
        ssl->priv->sockfd = fd;
        
        if (SSL_ResetHandshake (fd, FALSE) == SECFailure) {
            set_errno (PR_GetError ());
            return -1;
        }
        
        if (SSL_ForceHandshake (fd) == -1) {
            set_errno (PR_GetError ());
            return -1;
        }
    }
    
    ssl->priv->ssl_mode = TRUE;
    
    return 0;
}


static ssize_t
stream_read (CamelStream *stream, char *buffer, size_t n)
{
    CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
    PRFileDesc *cancel_fd;
    ssize_t nread;
    
    if (camel_operation_cancel_check (NULL)) {
        errno = EINTR;
        return -1;
    }
    
    cancel_fd = camel_operation_cancel_prfd (NULL);
    if (cancel_fd == NULL) {
        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));
    } else {
        PRSocketOptionData sockopts;
        PRPollDesc pollfds[2];
        gboolean nonblock;
        int error;
        
        /* get O_NONBLOCK options */
        sockopts.option = PR_SockOpt_Nonblocking;
        PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        sockopts.option = PR_SockOpt_Nonblocking;
        nonblock = sockopts.value.non_blocking;
        sockopts.value.non_blocking = TRUE;
        PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        
        pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
        pollfds[0].in_flags = PR_POLL_READ;
        pollfds[1].fd = cancel_fd;
        pollfds[1].in_flags = PR_POLL_READ;
        
        do {
            pollfds[0].out_flags = 0;
            pollfds[1].out_flags = 0;
            
            nread = -1;
            if (PR_Poll (pollfds, 2, -1) != -1) {
                if (pollfds[1].out_flags == PR_POLL_READ) {
                    sockopts.option = PR_SockOpt_Nonblocking;
                    sockopts.value.non_blocking = nonblock;
                    PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
                    errno = EINTR;
                    return -1;
                }
                
                do {
                    nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
                    if (nread == -1)
                        set_errno (PR_GetError ());
                } while (nread == -1 && errno == EINTR);
            } else {
                errno = EAGAIN;
            }
        } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
        
        /* restore O_NONBLOCK options */
        error = errno;
        sockopts.option = PR_SockOpt_Nonblocking;
        sockopts.value.non_blocking = nonblock;
        PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        errno = error;
    }
    
    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;
    PRFileDesc *cancel_fd;
    
    if (camel_operation_cancel_check (NULL)) {
        errno = EINTR;
        return -1;
    }
    
    cancel_fd = camel_operation_cancel_prfd (NULL);
    if (cancel_fd == NULL) {
        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);
    } else {
        PRSocketOptionData sockopts;
        PRPollDesc pollfds[2];
        gboolean nonblock;
        int error;
        
        /* get O_NONBLOCK options */
        sockopts.option = PR_SockOpt_Nonblocking;
        PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        sockopts.option = PR_SockOpt_Nonblocking;
        nonblock = sockopts.value.non_blocking;
        sockopts.value.non_blocking = TRUE;
        PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        
        pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
        pollfds[0].in_flags = PR_POLL_WRITE;
        pollfds[1].fd = cancel_fd;
        pollfds[1].in_flags = PR_POLL_READ;
        
        do {
            pollfds[0].out_flags = 0;
            pollfds[1].out_flags = 0;
            
            w = -1;
            if (PR_Poll (pollfds, 2, -1) != -1) {
                if (pollfds[1].out_flags == PR_POLL_READ) {
                    sockopts.option = PR_SockOpt_Nonblocking;
                    sockopts.value.non_blocking = nonblock;
                    PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
                    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);
                
                if (w == -1) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        w = 0;
                    } else {
                        error = errno;
                        sockopts.option = PR_SockOpt_Nonblocking;
                        sockopts.value.non_blocking = nonblock;
                        PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
                        errno = error;
                        return -1;
                    }
                } else
                    written += w;
            } else {
                set_errno (PR_GetError ());
                if (errno == EINTR)
                    w = 0;
            }
        } while (w != -1 && written < n);
        
        /* restore O_NONBLOCK options */
        error = errno;
        sockopts.option = PR_SockOpt_Nonblocking;
        sockopts.value.non_blocking = nonblock;
        PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
        errno = error;
    }
    
    if (w == -1)
        return -1;
    
    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 (host);
    
    return secStatus;
}
#endif

CamelCert *camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert);
CamelCert *camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert);
void camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert);

static char *
cert_fingerprint(CERTCertificate *cert)
{
    unsigned char md5sum[16], fingerprint[50], *f;
    int i;
    const char tohex[16] = "0123456789abcdef";

    md5_get_digest (cert->derCert.data, cert->derCert.len, md5sum);
    for (i=0,f = fingerprint; i<16; i++) {
        unsigned int c = md5sum[i];

        *f++ = tohex[(c >> 4) & 0xf];
        *f++ = tohex[c & 0xf];
        *f++ = ':';
    }

    fingerprint[47] = 0;

    return g_strdup(fingerprint);
}

/* lookup a cert uses fingerprint to index an on-disk file */
CamelCert *
camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert)
{
    char *fingerprint, *path;
    CamelCert *ccert;
    int fd;
    ssize_t len;
    struct stat st;
    
    fingerprint = cert_fingerprint (cert);
    ccert = camel_certdb_get_cert (certdb, fingerprint);
    if (ccert == NULL) {
        g_free (fingerprint);
        return ccert;
    }
    
    if (ccert->rawcert == NULL) {
        path = g_strdup_printf ("%s/.camel_certs/%s", getenv ("HOME"), fingerprint);
        if (stat (path, &st) == -1
            || (fd = open (path, O_RDONLY)) == -1) {
            g_warning ("could not load cert %s: %s", path, strerror (errno));
            g_free (fingerprint);
            g_free (path);
            camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
            camel_certdb_touch (certdb);
            
            return ccert;
        }
        g_free(path);
        
        ccert->rawcert = g_byte_array_new ();
        g_byte_array_set_size(ccert->rawcert, st.st_size);
        len = read(fd, ccert->rawcert->data, st.st_size);
        close(fd);
        if (len != st.st_size) {
            g_warning ("cert size read truncated %s: %d != %ld", path, len, st.st_size);
            g_byte_array_free(ccert->rawcert, TRUE);
            ccert->rawcert = NULL;
            g_free(fingerprint);
            camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
            camel_certdb_touch(certdb);

            return ccert;
        }
    }

    g_free(fingerprint);
    if (ccert->rawcert->len != cert->derCert.len
        || memcmp(ccert->rawcert->data, cert->derCert.data, cert->derCert.len) != 0) {
        g_warning("rawcert != derCer");
        camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
        camel_certdb_touch(certdb);
    }

    return ccert;
}

/* add a cert to the certdb */
CamelCert *
camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert)
{
    CamelCert *ccert;
    char *fingerprint;

    fingerprint = cert_fingerprint(cert);

    ccert = camel_certdb_cert_new(certdb);
    camel_cert_set_issuer(certdb, ccert, CERT_NameToAscii(&cert->issuer));
    camel_cert_set_subject(certdb, ccert, CERT_NameToAscii(&cert->subject));
    /* hostname is set in caller */
    /*camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);*/
    camel_cert_set_fingerprint(certdb, ccert, fingerprint);
    camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
    g_free(fingerprint);

    camel_certdb_nss_cert_set(certdb, ccert, cert);

    camel_certdb_add(certdb, ccert);

    return ccert;
}

/* set the 'raw' cert (& save it) */
void
camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert)
{
    char *dir, *path, *fingerprint;
    CamelStream *stream;
    struct stat st;
    
    fingerprint = ccert->fingerprint;
    
    if (ccert->rawcert == NULL)
        ccert->rawcert = g_byte_array_new ();
    
    g_byte_array_set_size (ccert->rawcert, cert->derCert.len);
    memcpy (ccert->rawcert->data, cert->derCert.data, cert->derCert.len);
    
    dir = g_strdup_printf ("%s/.camel_certs", getenv ("HOME"));
    if (stat (dir, &st) == -1 && mkdir (dir, 0700) == -1) {
        g_warning ("Could not create cert directory '%s': %s", dir, strerror (errno));
        g_free (dir);
        return;
    }
    
    path = g_strdup_printf ("%s/%s", dir, fingerprint);
    g_free (dir);
    
    stream = camel_stream_fs_new_with_name (path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (stream != NULL) {
        if (camel_stream_write (stream, ccert->rawcert->data, ccert->rawcert->len) == -1) {
            g_warning ("Could not save cert: %s: %s", path, strerror (errno));
            unlink (path);
        }
        camel_stream_close (stream);
        camel_object_unref (stream);
    } else {
        g_warning ("Could not save cert: %s: %s", path, strerror (errno));
    }
    
    g_free (path);
}


#if 0
/* used by the mozilla-like code below */
static char *
get_nickname(CERTCertificate *cert)
{
    char *server, *nick = NULL;
    int i;
    PRBool status = PR_TRUE;

    server = CERT_GetCommonName(&cert->subject);
    if (server == NULL)
        return NULL;

    for (i=1;status == PR_TRUE;i++) {
        if (nick) {
            g_free(nick);
            nick = g_strdup_printf("%s #%d", server, i);
        } else {
            nick = g_strdup(server);
        }
        status = SEC_CertNicknameConflict(server, &cert->derSubject, cert->dbhandle);
    }

    return nick;
}
#endif

static SECStatus
ssl_bad_cert (void *data, PRFileDesc *sockfd)
{
    gboolean accept;
    CamelCertDB *certdb = NULL;
    CamelCert *ccert = NULL;
    char *prompt, *cert_str, *fingerprint;
    CamelTcpStreamSSL *ssl;
    CERTCertificate *cert;
    CamelService *service;
    SECStatus status = SECFailure;

    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;
    
    cert = SSL_PeerCertificate (sockfd);
    if (cert == NULL)
        return SECFailure;

    certdb = camel_certdb_get_default();
    ccert = camel_certdb_nss_cert_get(certdb, cert);
    if (ccert == NULL) {
        ccert = camel_certdb_nss_cert_add(certdb, cert);
        camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);
    }

    if (ccert->trust == CAMEL_CERT_TRUST_UNKNOWN) {
        status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
        fingerprint = cert_fingerprint(cert);
        cert_str = g_strdup_printf (_("Issuer:            %s\n"
                          "Subject:           %s\n"
                          "Fingerprint:       %s\n"
                          "Signature:         %s"),
                        CERT_NameToAscii (&cert->issuer),
                        CERT_NameToAscii (&cert->subject),
                        fingerprint, status == SECSuccess?_("GOOD"):_("BAD"));
        g_free(fingerprint);

        /* construct our user prompt */
        prompt = g_strdup_printf (_("SSL Certificate check for %s:\n\n%s\n\nDo you wish to accept?"),
                      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) {
            camel_certdb_nss_cert_set(certdb, ccert, cert);
            camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_FULLY);
            camel_certdb_touch(certdb);
        }
    } else {
        accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
    }

    camel_certdb_cert_unref(certdb, ccert);
    camel_object_unref(certdb);

    return accept ? SECSuccess : SECFailure;

#if 0
    int i, error;
    CERTCertTrust trust;
    SECItem *certs[1];
    int go = 1;
    char *host, *nick;

    error = PR_GetError();

    /* This code is basically what mozilla does - however it doesn't seem to work here
       very reliably :-/ */
    while (go && status != SECSuccess) {
        char *prompt = NULL;

        printf("looping, error '%d'\n", error);

        switch(error) {
        case SEC_ERROR_UNKNOWN_ISSUER:
        case SEC_ERROR_CA_CERT_INVALID:
        case SEC_ERROR_UNTRUSTED_ISSUER:
        case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
            /* add certificate */
            printf("unknown issuer, adding ... \n");
            prompt = g_strdup_printf(_("Certificate problem: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);

            if (camel_session_alert_user(service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {

                nick = get_nickname(cert);
                if (NULL == nick) {
                    g_free(prompt);
                    status = SECFailure;
                    break;
                }

                printf("adding cert '%s'\n", nick);

                if (!cert->trust) {
                    cert->trust = (CERTCertTrust*)PORT_ArenaZAlloc(cert->arena, sizeof(CERTCertTrust));
                    CERT_DecodeTrustString(cert->trust, "P");
                }
        
                certs[0] = &cert->derCert;
                /*CERT_ImportCerts (cert->dbhandle, certUsageSSLServer, 1, certs, NULL, TRUE, FALSE, nick);*/
                CERT_ImportCerts(cert->dbhandle, certUsageUserCertImport, 1, certs, NULL, TRUE, FALSE, nick);
                g_free(nick);

                printf(" cert type %08x\n", cert->nsCertType);

                memset((void*)&trust, 0, sizeof(trust));
                if (CERT_GetCertTrust(cert, &trust) != SECSuccess) {
                    CERT_DecodeTrustString(&trust, "P");
                }
                trust.sslFlags |= CERTDB_VALID_PEER | CERTDB_TRUSTED;
                if (CERT_ChangeCertTrust(cert->dbhandle, cert, &trust) != SECSuccess) {
                    printf("couldn't change cert trust?\n");
                }

                /*status = SECSuccess;*/
#if 1
                /* re-verify? */
                status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLServer, NULL);
                error = PR_GetError();
                printf("re-verify status %d, error %d\n", status, error);
#endif

                printf(" cert type %08x\n", cert->nsCertType);
            } else {
                printf("failed/cancelled\n");
                go = 0;
            }

            break;
        case SSL_ERROR_BAD_CERT_DOMAIN:
            printf("bad domain\n");

            prompt = g_strdup_printf(_("Bad certificate domain: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);

            if (camel_session_alert_user (service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
                host = SSL_RevealURL(sockfd);
                status = CERT_AddOKDomainName(cert, host);
                printf("add ok domain name : %s\n", status == SECFailure?"fail":"ok");
                error = PR_GetError();
                if (status == SECFailure)
                    go = 0;
            } else {
                go = 0;
            }

            break;
            
        case SEC_ERROR_EXPIRED_CERTIFICATE:
            printf("expired\n");

            prompt = g_strdup_printf(_("Certificate expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);

            if (camel_session_alert_user(service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
                cert->timeOK = PR_TRUE;
                status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
                error = PR_GetError();
                if (status == SECFailure)
                    go = 0;
            } else {
                go = 0;
            }

            break;

        case SEC_ERROR_CRL_EXPIRED:
            printf("crl expired\n");

            prompt = g_strdup_printf(_("Certificate revocation list expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);

            if (camel_session_alert_user(service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
                host = SSL_RevealURL(sockfd);
                status = CERT_AddOKDomainName(cert, host);
            }

            go = 0;
            break;

        default:
            printf("generic error\n");
            go = 0;
            break;
        }

        g_free(prompt);
    }

    CERT_DestroyCertificate(cert);

    return status;
#endif
}

static PRFileDesc *
enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd)
{
    PRFileDesc *ssl_fd;
    
    ssl_fd = SSL_ImportFD (NULL, fd ? fd : ssl->priv->sockfd);
    if (!ssl_fd)
        return NULL;
    
    SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE);
    if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL2)
        SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_TRUE);
    else
        SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
    if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
        SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_TRUE);
    else
        SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_FALSE);
    if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
        SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_TRUE);
    else
        SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_FALSE);
    
    SSL_SetURL (ssl_fd, ssl->priv->expected_host);
    
    /*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->ssl_mode = TRUE;
    
    return ssl_fd;
}

#define CONNECT_TIMEOUT PR_TicksPerSecond () * 120

static int
stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
{
    CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream);
    PRIntervalTime timeout = CONNECT_TIMEOUT;
    PRNetAddr netaddr;
    PRFileDesc *fd;
    
    g_return_val_if_fail (host != NULL, -1);
    
    memset ((void *) &netaddr, 0, sizeof (PRNetAddr));
#ifdef ENABLE_IPv6
    if (host->h_addrtype == AF_INET6)
        memcpy (&netaddr.ipv6.ip, host->h_addr, sizeof (netaddr.ipv6.ip));
    else
        memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip));
#else
    memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip));
#endif
    
    if (PR_InitializeNetAddr (PR_IpAddrNull, port, &netaddr) == PR_FAILURE) {
        set_errno (PR_GetError ());
        return -1;
    }
    
    fd = PR_OpenTCPSocket (host->h_addrtype);
    if (fd == NULL) {
        set_errno (PR_GetError ());
        return -1;
    }
    
    if (ssl->priv->ssl_mode) {
        PRFileDesc *ssl_fd;
        
        ssl_fd = enable_ssl (ssl, fd);
        if (ssl_fd == NULL) {
            int errnosave;
            
            set_errno (PR_GetError ());
            errnosave = errno;
            PR_Close (fd);
            errno = errnosave;
            
            return -1;
        }
        
        fd = ssl_fd;
    }
    
    if (PR_Connect (fd, &netaddr, timeout) == PR_FAILURE) {
        int errnosave;
        
        set_errno (PR_GetError ());
        if (errno == EINPROGRESS) {
            gboolean connected = FALSE;
            PRPollDesc poll;
            
            do {
                poll.fd = fd;
                poll.in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
                poll.out_flags = 0;
                
                timeout = CONNECT_TIMEOUT;
                
                if (PR_Poll (&poll, 1, timeout) == PR_FAILURE) {
                    set_errno (PR_GetError ());
                    goto exception;
                }
                
                if (PR_GetConnectStatus (&poll) == PR_FAILURE) {
                    set_errno (PR_GetError ());
                    if (errno != EINPROGRESS)
                        goto exception;
                } else {
                    connected = TRUE;
                }
            } while (!connected);
        } else {
        exception:
            errnosave = errno;
            PR_Close (fd);
            ssl->priv->sockfd = NULL;
            errno = errnosave;
            
            return -1;
        }
        
        errno = 0;
    }
    
    ssl->priv->sockfd = 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 CamelTcpAddress *
stream_get_local_address (CamelTcpStream *stream)
{
    PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
    int family, length;
    gpointer address;
    PRNetAddr addr;
    
    PR_GetSockName (sockfd, &addr);
    
    if (addr.inet.family == PR_AF_INET) {
        family = CAMEL_TCP_ADDRESS_IPv4;
        address = &addr.inet.ip;
        length = 4;
#ifdef ENABLE_IPv6
    } else if (addr.inet.family == PR_AF_INET6) {
        family = CAMEL_TCP_ADDRESS_IPv6;
        address = &addr.ipv6.ip;
        length = 16;
#endif
    } else
        return NULL;
    
    return camel_tcp_address_new (family, addr.inet.port, length, address);
}

static CamelTcpAddress *
stream_get_remote_address (CamelTcpStream *stream)
{
    PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
    int family, length;
    gpointer address;
    PRNetAddr addr;
    
    PR_GetPeerName (sockfd, &addr);
    
    if (addr.inet.family == PR_AF_INET) {
        family = CAMEL_TCP_ADDRESS_IPv4;
        address = &addr.inet.ip;
        length = sizeof (addr.inet.ip);
#ifdef ENABLE_IPv6
    } else if (addr.inet.family == PR_AF_INET6) {
        family = CAMEL_TCP_ADDRESS_IPv6;
        address = &addr.ipv6.ip;
        length = sizeof (addr.ipv6.ip);
#endif
    } else
        return NULL;
    
    return camel_tcp_address_new (family, addr.inet.port, length, address);
}

#endif /* HAVE_NSS */