/* $Id: connection_ssl.cpp,v 1.10 2004/01/02 03:54:01 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library 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 Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library 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.  *
 *                                                                             *
 ******************************************************************************/
#if defined WITH_SSL

#include <fmail.h>

#include "connection_ssl.h"
#include "cfgfile.h"

extern cfgfile Config;

connection_ssl::connection_ssl(int psock, string phost) : connection(psock,phost) {
    m_SslContext = NULL;
    m_SslConnection = NULL;
    m_SslCert = NULL;
}

connection_ssl::~connection_ssl() {
}

void connection_ssl::disconnect() {

    SSL_shutdown(m_SslConnection);
    X509_free(m_SslCert);
    SSL_free(m_SslConnection);
    SSL_CTX_free(m_SslContext);

    return;
}

bool connection_ssl::connect() {
    bool bTlsVer1 = (Config.get("SSL_NO_TLSV1", "Y") == "Y") ? true : false;
    bool bSslVer2 = (Config.get("SSL_NO_SSLV2", "Y") == "Y") ? true : false;
    bool bSslVer3 = (Config.get("SSL_NO_SSLV3", "Y") == "Y") ? true : false;

    m_SslContext = SSL_CTX_new(SSLv23_client_method());

    if(m_SslContext != NULL) {

        if(!bTlsVer1) {
            SSL_CTX_set_options(m_SslContext, SSL_OP_NO_TLSv1);
            cerr << "Turning off TLSv1" << endl;
        }

        if(!bSslVer2) {
            SSL_CTX_set_options(m_SslContext, SSL_OP_NO_SSLv2);
            cerr << "Turning off SSLv2" << endl;
        }

        if(!bSslVer3) {
            SSL_CTX_set_options(m_SslContext, SSL_OP_NO_SSLv3);
            cerr << "Turning off SSLv3" << endl;
        }

        m_SslConnection = SSL_new(m_SslContext);

        if(m_SslConnection != NULL) {
            SSL_set_fd(m_SslConnection,getSock());

            if(negotiate()) {
                int iBits;

                iBits = SSL_CIPHER_get_bits (SSL_get_current_cipher(m_SslConnection), &iBits);
                cerr << "SSL Tunnel with " <<  iBits << " bits" << endl;

                return true;
            } else {
                cerr << "Unable to negotate SSL Connection" << endl;
                disconnect();
            }
        } else {
            cerr << "Unable to allocate new SSL Connection" << endl;
            disconnect();
        }
    } else {
        cerr << "Unable to allocate new SSL Context" << endl;
    }

    return false;
}

bool connection_ssl::negotiate() {
    int iError;
    string sErrorMsg;
    string sSslName;
    string sSslVersion;

    SSL_set_mode(m_SslConnection,SSL_MODE_AUTO_RETRY);

    iError = SSL_connect(m_SslConnection);

    if (iError != 1) {
        int iSslError = SSL_get_error(m_SslConnection, iError);

        switch (iSslError) {
            case SSL_ERROR_SYSCALL:
                sErrorMsg = "I/O error";
            break;

            case SSL_ERROR_SSL:
                sErrorMsg = "unspecified protocol error";
            break;

            case SSL_ERROR_WANT_READ:
                sErrorMsg = "SSL_ERROR_WANT_READ";
            break;

            default:
                sErrorMsg = "unknown error";
        }

        cerr << "SSL_connect failed: " << sErrorMsg << " (" << iSslError << ")" << endl;
        return false;
    }

    m_SslCert = SSL_get_peer_certificate(m_SslConnection);
    if (m_SslCert == NULL) {
        cerr << "Unable to get certificate from peer" << endl;
        return false;
    }

    if (!verify_cert())
        return false;

    sSslName = SSL_get_cipher_name (m_SslConnection);
    sSslVersion = SSL_get_cipher_version (m_SslConnection);
    cerr << "SSL connection using " << sSslName << " (" << sSslVersion << ")" << endl;

    return true;
}

bool connection_ssl::verify_cert() {
    //Put code to check/verify the cert
    return true;
}

bool connection_ssl::starttls() {
    bool bRc;

    m_SslContext = SSL_CTX_new(TLSv1_client_method());
    if(m_SslContext != NULL) {

        m_SslConnection = SSL_new(m_SslContext);
        if(m_SslContext != NULL) {
            int iRc = SSL_set_fd(m_SslConnection,getSock());

            if(iRc == 1) {
                bRc = true;
            } else {
                cerr << "SSL_set_fd failed" << endl;
                bRc = false;
            }
        } else {
            cerr << "Unable to new SSL Connection" << endl;
            bRc = false;
        }

    } else {
        cerr << "Unable to new SSL Context" << endl;
        bRc = false;
    }

    if(bRc) {
        int iBits;
        int iSsf;

        iSsf = SSL_CIPHER_get_bits(SSL_get_current_cipher(m_SslConnection), &iBits);

        cerr << "MaxBits: " << iBits << " SSF: " << iSsf << endl;
        
        return true;
    } else {
        disconnect();
        return false;
    }
}

#endif // WITH_SSL
