Logo Search packages:      
Sourcecode: mailody version File versions  Download package

socketsafe.cpp

/* This file is part of the KDE project
   Copyright (C) 2006-2007 KovoKs <info@kovoks.nl>

   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.

   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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

// Uncomment the next line for full comm debug
//#define comm_debug

#include <qregexp.h>

#include <kbufferedsocket.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <klocale.h>

#include <qca.h>

#include "db.h"
#include "socketsafe.h"

namespace Mailody {

00037 SocketSafe::SocketSafe(QObject* parent, const char* name,
                       const QString& server, int port, Secure safe)
    :QObject(parent, name), m_ssl(0), m_socket(0), m_aboutToClose(false),
    m_crypted(false)
{
    kdDebug() << name << k_funcinfo << endl;
    m_server = server;
    m_port = port;
    m_safe = safe;
}

00048 SocketSafe::~SocketSafe()
{
    kdDebug() << name() << " " << k_funcinfo << endl;
    m_aboutToClose=true;
}

00054 void SocketSafe::reconnect()
{
    // kdDebug() << k_funcinfo << endl;
    if (m_aboutToClose)
        return;

    kdDebug() << name() << " " << "Connecting to: " << m_server << ":"
            <<  m_port << endl;
#ifdef comm_debug
    kdDebug() << name() << " " << "SSL: " <<  (m_safe == SSL) << " - "
               << "TLS: " <<  (m_safe == TLS) << endl;
#endif

    m_crypted=false;

    if (!m_socket)
    {
        m_socket = new KNetwork::KBufferedSocket(m_server,
                QString::number(m_port), this);
        connect(m_socket, SIGNAL(stateChanged( int )),
                SLOT(slotStateChanged( int )));
        connect(m_socket, SIGNAL(closed()), SLOT(slotDisconnected()));
        connect(m_socket, SIGNAL(gotError(int)), SLOT(slotError(int)));
        connect(m_socket, SIGNAL(readyRead()), SLOT(cryptedSocketRead()) );

        if (m_safe != NONE)
        {
            m_ssl = new QCA::TLS;
            connect(m_ssl, SIGNAL(handshaken()), SLOT(ssl_handshaken()));
            connect(m_ssl, SIGNAL(readyRead()), SLOT(unCryptedReadyRead()));
            connect(m_ssl, SIGNAL(readyReadOutgoing(int)),
                    SLOT(ssl_readyReadOutgoing(int)));
            connect(m_ssl, SIGNAL(closed()), SLOT(ssl_closed()));
            connect(m_ssl, SIGNAL(error(int)), SLOT(ssl_error(int)));
        }

        // my system timesout after a minute or so, not sure what the KDE
        // default is, but 10 seconds is max imho, non?
        m_socket->setTimeout(10000);
        m_socket->connect();
    }
}

void SocketSafe::slotStateChanged( int i)
{
    // kdDebug() << k_funcinfo << endl;

#ifdef comm_debug
    kdDebug() << name() << " " << "State is now:" << i << endl;
#endif

    if (i == 5)
    {
        emit connected();

        if (m_safe == SSL)
        {
            // When connected, we need a handshake directly for ssl
            kdDebug() << name() << " "
                    << "Connected, starting SSL handshake..." << endl;
            m_ssl->startClient(m_server);
            m_crypted=true;
        }
    }
    else if (i == 2)
        emit hostFound();
}

void SocketSafe::slotError(int i)
{
    kdDebug() << name() << " " << k_funcinfo << i
            << " - " << m_socket->errorString() << endl;

    if (i == 1)
        emit error(i18n("The server %1 could not be found").arg(m_server));
    else if (i == 14)
        emit error(i18n("Could not connect on port %1").arg(m_port));
    else if (i == 15)
        emit error(i18n("Could not connect to the server within 10 seconds"));
    else if (i == 17)
    {
        // eat this, 17 is disconnected, this will also trigger the closed
        // event which is connected to slotDisconnected() already...
    }
    else if (!m_aboutToClose)
    {
        m_aboutToClose = true;
        emit error(i18n("Creating the connection failed, "
            "the error:\n\n%1").arg(m_socket->errorString()));
    }
}

void SocketSafe::unCryptedReadyRead()
{
    // kdDebug() << k_funcinfo << endl;
    // This slot is called when the ssl process has some data to
    // process...
    if (!m_ssl)
        return;

    QByteArray a = m_ssl->read();
    QCString cs;
    cs.resize(a.size()+1);
    memcpy(cs.data(), a.data(), a.size());
    QString t = cs.data();

#ifdef comm_debug
    kdDebug() << name() << " " << "S(1): " << t.stripWhiteSpace() << endl;
#endif

    emit data(t);
}

00167 void SocketSafe::write(const QString& text)
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    // Eat things in the queue when there is no connection. We need
    // to get a connection first don't we...
    if (!m_socket || !available())
        return;

    // \r\n needed for at least:
    //Microsoft Exchange Server 2003 IMAP4rev1 server version 6.5.7638.1
    QCString cs = (text+"\r\n").latin1();
    QByteArray buf(cs.length());
    memcpy(buf.data(), cs.data(), buf.size());

#ifdef comm_debug
    kdDebug() << name() << " " << "C   : "
          << QString(buf).stripWhiteSpace() << endl;
#endif

    if (m_safe == NONE || !m_crypted)
        m_socket->writeBlock(buf.data(), buf.size());
    else
        m_ssl->write(buf);
}

00192 bool SocketSafe::available()
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    bool ok = m_socket && m_socket->state() ==
            KNetwork::KClientSocketBase::Connected;
    return ok;
}

void SocketSafe::slotDisconnected()
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    if (m_aboutToClose)
        return;

    emit disconnected();
}

// ---------------------     SSL / TLS Stuff ---------------------//
// Copied from the examples of qca

void SocketSafe::showCertInfo(const QCA::Cert &cert)
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    printf("-- Cert --\n");
    printf(" CN: %s\n", cert.subject()["CN"].latin1());
    printf(" Valid from: %s, until %s\n",
           cert.notBefore().toString().latin1(),
           cert.notAfter().toString().latin1());
    printf(" PEM:\n%s\n", cert.toPEM().latin1());
}

QString SocketSafe::resultToString(int result)
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    QString s;
    switch(result) {
        case QCA::TLS::NoCert:
            s = i18n("No certificate presented.");
            break;
        case QCA::TLS::Valid:
            break;
        case QCA::TLS::HostMismatch:
            s = i18n("Hostname mismatch.");
            break;
        case QCA::TLS::Rejected:
            s = i18n("Root CA rejects the specified purpose.");
            break;
        case QCA::TLS::Untrusted:
            s = i18n("Not trusted for the specified purpose.");
            break;
        case QCA::TLS::SignatureFailed:
            s = i18n("Invalid signature.");
            break;
        case QCA::TLS::InvalidCA:
            s = i18n("Invalid CA certificate.");
            break;
        case QCA::TLS::InvalidPurpose:
            s = i18n("Invalid certificate purpose.");
            break;
        case QCA::TLS::SelfSigned:
            s = i18n("Certificate is self-signed.");
            break;
        case QCA::TLS::Revoked:
            s = i18n("Certificate has been revoked.");
            break;
        case QCA::TLS::PathLengthExceeded:
            s = i18n("Maximum cert chain length exceeded.");
            break;
        case QCA::TLS::Expired:
            s = i18n("Certificate has expired.");
            break;
        case QCA::TLS::Unknown:
        default:
            s = i18n("General validation error.");
            break;
    }
    return s;
}

void SocketSafe::ssl_handshaken()
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    cert = m_ssl->peerCertificate();
    int vr = m_ssl->certificateValidityResult();

    printf("Successful SSL handshake.\n");
    if(!cert.isNull())
        showCertInfo(cert);
    if(vr == QCA::TLS::Valid)
    {
        kdDebug() << name() << " " << "Valid certificate." << endl;
    }
    else
    {
        kdDebug() << name() << " " << "Invalid certificate: "
                <<  resultToString(vr) << endl;

        DB* db = DB::dbinstance();
        if (!db->hasCert(cert.toPEM(), vr))
        {
            int i = KMessageBox::warningContinueCancel(0,
                    i18n("%1\n\nDo you want to "
                            "permanently accept it anyway?")
                            .arg(resultToString(vr)));
            if (i == KMessageBox::Continue)
            {
                db->addCert(cert.toPEM(), vr);
            }
            else
            {
                delete m_ssl;
                m_ssl = 0;

                delete m_socket;
                m_socket = 0;

                m_crypted=false;
                return;
            }
        }
        else
        {
            kdDebug() << name() << " " << "Certificate invalid (error " << vr
                    << "), but accepted previously... " << endl;
        }
    }

    // flush stored stuff
    unCryptedReadyRead();
}

void SocketSafe::ssl_readyReadOutgoing(int)
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    if (!m_socket || !m_ssl || !available())
        return;

    //kdDebug() << name() << " " << "Writing data to the socket" << endl;
    QByteArray a = m_ssl->readOutgoing();
    m_socket->writeBlock(a.data(), a.size());
}

void SocketSafe::ssl_closed()
{
    // kdDebug() << name() << " " << k_funcinfo << endl;
    printf("SSL session closed\n");
}

void SocketSafe::ssl_error(int x)
{
    kdDebug() << name() << " " << k_funcinfo << x << endl;
    m_crypted=false;
    emit error(i18n("Could not connect due to a SSL error"));
}

void SocketSafe::cryptedSocketRead()
{
    // kdDebug() << name() << " " << k_funcinfo << endl;

    if (!m_socket)
        return;

    if (!m_crypted)
    {
        // The bits received are not crypted. This can happen when
        // we don't want tls or ssl or when the connection is ready for tls.

        uint available = m_socket->bytesAvailable();
        QByteArray buffer(available);
        m_socket->readBlock(buffer.data(), buffer.size());
        QCString cs;
        cs.resize(buffer.size()+1);
        memcpy(cs.data(), buffer.data(), buffer.size());
        QString msg = cs.data();

#ifdef comm_debug
        kdDebug() << name() << " " << "S(0): " << msg.stripWhiteSpace() << endl;
#endif
        if (m_safe == TLS)
        {

            // handle the tls case.
            // TODO move intelligence to imap/smtp
            if (msg.find("a02 OK") != -1 ||
                msg.find("220 ") < msg.find("TLS") && msg.find("220 ") != -1)
            {
                // Request accepted.
                kdDebug() << name() << " " << "Accepted, starting TLS handshake..."
                        << endl;
                m_ssl->startClient(m_server);
                m_crypted=true;
            }
            else if (msg.find("a02 ") != -1)
            {
                // request returned, but not ok.
                KMessageBox::information(0,i18n("TLS was refused:\n\n")+msg);
            }
            else
            {
                // request tls.
                kdDebug() << name() << " "
                        << "Connected, emitting req. to deal with TLS"
                        << endl;
                emit data(msg);
            }
        }
        else
        {
            emit data(msg);
        }
    }
    else
    {
        // We received bit that are meant for the crypted part of the
        // ssl or tls connection

        if (!m_ssl)
            return;

        uint available = m_socket->bytesAvailable();
        QByteArray buffer(available);
        m_socket->readBlock(buffer.data(), buffer.size());
        m_ssl->writeIncoming(buffer);
    }
}

}

#include "socketsafe.moc"

Generated by  Doxygen 1.6.0   Back to index