/*
    Copyright (c) 2004,2005 by İsmail Dönmez <ismail.donmez@boun.edu.tr>

    based on the code by :
    Copyright (c) 2004      by Jason Keirstead <jason@keirstead.org>
    Kopete    (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * This program is free software; you can redistribute it and/or modify  *
    * it under the terms of the GNU General Public License as published by  *
* the Free Software Foundation; either version 2 of the License, or     *
* (at your option) any later version.                                   *
*                                                                       *
*************************************************************************
*/

#include "sslsocket.h"

#include <tdelocale.h>
#include <kdebug.h>
#include <kssl.h>
#include <ksslinfodlg.h>
#include <ksslpeerinfo.h>
#include <ksslcertchain.h>
#include <ksslcertificatecache.h>
#include <tdemessagebox.h>
#include <tdesocketdevice.h>


struct SSLSocketPrivate
{
    int m_sslCertState;
    TQString remoteHost;
    TQString url;
    TQString m_sslCertErrors;

    KSSL* kssl;
    KSSLCertificateCache* cc;
};

SSLSocket::SSLSocket(TQWidget* serverParent, TQObject* parent, const char* name)
: KStreamSocket(0L,0L,parent,name), m_serverParent(serverParent)
{
    d = new SSLSocketPrivate;
    d->kssl = 0L;
    d->cc = new KSSLCertificateCache;
    d->cc->reload();

}

SSLSocket::~SSLSocket()
{
    // Close stream socket
    close();

    // close ssl socket
    if( d->kssl ) d->kssl->close();

    delete d->kssl;
    delete d->cc;
    delete d;
}

TQ_LONG SSLSocket::writeBlock(const char *data, TQ_ULONG len)
{
    if (d->kssl && KSSL::doesSSLWork() && state() == KNetwork::KClientSocketBase::Connected)
        return d->kssl->write( data,len );
    else
        return 0;
}

TQ_LONG SSLSocket::readBlock(char *data, TQ_ULONG maxlen)
{
    int err = d->kssl->read( data, maxlen );
    return err;
}

void SSLSocket::stateChanging(KClientSocketBase::SocketState newState)
{
    if(newState == KClientSocketBase::Connected)
    {
        KClientSocketBase::stateChanging(KClientSocketBase::Connected);
        connected();
    }
    else
        KClientSocketBase::stateChanging(newState);
}

const TQString SSLSocket::details()
{
    int strength = d->kssl->connectionInfo().getCipherUsedBits();

    TQString details = i18n("Connection is secured with %1 bit SSL.").arg(strength);

    return details;
}

void SSLSocket::connected()
{

    if( KSSL::doesSSLWork() )
    {
        if( !d->kssl )
        {
            d->kssl = new KSSL();
            if( d->kssl->connect( socketDevice()->socket() ) )
            {
                if( verifyCertificate() != 1 )
                {
                    close();
                }
                else
                    emit sslInitDone();
            }
        }
        else
        {
            d->kssl->reInitialize();
        }
    }
    else
    {
        kdError() << "SSL not functional!" << endl;
        emit sslFailure(i18n("The functionality to connect to servers using encrypted SSL communications is not available to Konversation because OpenSSL support was not enabled at compile time. You will need to get new version of TDE that has SSL support."));
        close();
    }
}

void SSLSocket::showInfoDialog()
{
    if( state() == KNetwork::KClientSocketBase::Connected )
    {
        showSSLInfoDialog();
    }
}

void SSLSocket::showSSLInfoDialog()
{

    KSSLInfoDlg* sslInfoDlg = new KSSLInfoDlg(true, m_serverParent, "sslInfoDlg", true);
    sslInfoDlg->setCertState( d->m_sslCertErrors );
    sslInfoDlg->setup( *(d->kssl),
        (const TQString&) d->remoteHost,
        (const TQString&) d->url
        );
    sslInfoDlg->exec();
}

int SSLSocket::verifyCertificate()
{
    int rc = 0;
    int result;
    bool permacache = false;
    bool ipMatchesCN = false;
    bool doAddHost = false;
    TQString hostname;
    KSSLCertificate::KSSLValidation validation;

    d->remoteHost = peerAddress().nodeName();
    d->url = "irc://"+d->remoteHost+':'+peerAddress().serviceName();

    KSSLCertificate& peerCertificate = d->kssl->peerInfo().getPeerCertificate();

    validation = peerCertificate.validate();
    if(validation == KSSLCertificate::Unknown )
    {
        emit sslFailure(i18n("The SSL certificate returned from the server was not recognized.  Maybe this server does not support SSL on the given port?  If this server supports normal, non-SSL communications as well, then SSL will be on a different port."));
        return 0;
    }

    KSSLX509Map certinfo(peerCertificate.getSubject());
    hostname = certinfo.getValue("CN");

    KSSLCertificate::KSSLValidationList validationList
        = peerCertificate.validateVerbose(KSSLCertificate::SSLServer);

    ipMatchesCN = d->kssl->peerInfo().certMatchesAddress();

    validation = KSSLCertificate::Ok;

    if (!validationList.isEmpty())
        validation = validationList.first();

    for(KSSLCertificate::KSSLValidationList::ConstIterator it = validationList.begin();
        it != validationList.end(); ++it)
    {
        d->m_sslCertErrors += TQString::number(*it)+':';
    }

    if (peerCertificate.chain().isValid() && peerCertificate.chain().depth() > 1)
    {
        TQString theChain;
        TQPtrList<KSSLCertificate> chain = peerCertificate.chain().getChain();
        for (KSSLCertificate *c = chain.first(); c; c = chain.next())
        {
            theChain += c->toString();
            theChain += '\n';
        }
    }

    d->m_sslCertState = validation;

    if (validation == KSSLCertificate::Ok)
        rc = 1;

    //  - Read from cache and see if there is a policy for this
    KSSLCertificateCache::KSSLCertificatePolicy cp = d->cc->getPolicyByCertificate(peerCertificate);

    //  - validation code
    if (validation != KSSLCertificate::Ok)
    {
        if( cp == KSSLCertificateCache::Unknown || cp == KSSLCertificateCache::Ambiguous)
        {
            cp = KSSLCertificateCache::Prompt;
        }
        else
        {
            // A policy was already set so let's honor that.
            permacache = d->cc->isPermanent(peerCertificate);
        }

        if (!ipMatchesCN && cp == KSSLCertificateCache::Accept )
        {
            do
            {
                TQString msg = i18n("The IP address of the host %1 "
                    "does not match the one the "
                    "certificate was issued to.");
                result = KMessageBox::warningYesNoCancel( m_serverParent,
                    msg.arg(hostname),
                    i18n("Server Authentication"),
                    KGuiItem(i18n("Details")),
                    KGuiItem(i18n("Continue")),
                    "SslIpCNMismatch");
                if(result == KMessageBox::Yes)
                    showInfoDialog();
                else if(result == KMessageBox::Cancel)
                {
                    return 0;
                }
            }
            while ( result == KMessageBox::Yes );

        }

        // Precondition: cp is one of Reject, Accept or Prompt
        switch (cp)
        {
            case KSSLCertificateCache::Accept:
                rc = 1;
                break;

            case KSSLCertificateCache::Reject:
                rc = -1;
                break;

            case KSSLCertificateCache::Prompt:
            {
                do
                {
                    if (validation == KSSLCertificate::InvalidHost)
                    {
                        TQString msg = i18n("The IP address of the host %1 "
                            "does not match the one the "
                            "certificate was issued to.");
                        result = KMessageBox::warningYesNoCancel( m_serverParent,
                            msg.arg(hostname),
                            i18n("Server Authentication"),
                            KGuiItem(i18n("Details")),
                            KGuiItem(i18n("Continue")),
			    TQString(),
			    KMessageBox::Dangerous);
                    }
                    else
                    {
                        TQString msg = i18n("The server (%1) certificate failed the "
                            "authenticity test.");
                        result = KMessageBox::warningYesNoCancel( m_serverParent,
                            msg.arg(hostname),
                            i18n("Server Authentication"),
                            KGuiItem(i18n("Details")),
                            KGuiItem(i18n("Continue")),
			    TQString(),
			    KMessageBox::Dangerous);
                    }

                    if (result == KMessageBox::Yes)
                    {
                        showInfoDialog();
                    }
                    else if(result == KMessageBox::Cancel)
                    {
                        return 0;
                    }
                }
                while (result == KMessageBox::Yes);

                if (result == KMessageBox::No)
                {
                    rc = 1;
                    cp = KSSLCertificateCache::Accept;
                    doAddHost = true;
                    result = KMessageBox::warningYesNo( m_serverParent,
                        i18n("Would you like to accept this "
                        "certificate forever without "
                        "being prompted?"),
                        i18n("Server Authentication"),
                        KGuiItem(i18n("&Forever")),
                        KGuiItem(i18n("&Current Sessions Only"))
                        );

                    if (result == KMessageBox::Yes)
                        permacache = true;
                    else
                        permacache = false;
                }
                else
                {
                    rc = -1;
                    cp = KSSLCertificateCache::Prompt;
                }

                break;
            }
            default:
                kdDebug() << "SSL error in cert code." << endl;
                break;
        }
    }

    //  - cache the results
    d->cc->addCertificate(peerCertificate, cp, permacache);
    if (doAddHost)
        d->cc->addHost(peerCertificate, d->remoteHost);

    if (rc == -1)
        return rc;
    /*
    kdDebug() << "SSL connection information follows:" << endl
        << "+-----------------------------------------------" << endl
        << "| Cipher: " << d->kssl->connectionInfo().getCipher() << endl
        << "| Description: " << d->kssl->connectionInfo().getCipherDescription() << endl
        << "| Version: " << d->kssl->connectionInfo().getCipherVersion() << endl
        << "| Strength: " << d->kssl->connectionInfo().getCipherUsedBits()
        << " of " << d->kssl->connectionInfo().getCipherBits()
        << " bits used." << endl
        << "| PEER:" << endl
        << "| Subject: " << d->kssl->peerInfo().getPeerCertificate().getSubject() << endl
        << "| Issuer: " << d->kssl->peerInfo().getPeerCertificate().getIssuer() << endl
        << "| Validation: " << (int) validation << endl
        << "+-----------------------------------------------"
        << endl;
    */
    return rc;
}

#include "sslsocket.moc"
