You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/irc/libkirc/ksslsocket.cpp

459 lines
12 KiB

/*
ksslsocket.cpp - KDE SSL Socket
Copyright (c) 2005 by Tommi Rantala <tommi.rantala@cs.helsinki.fi>
Copyright (c) 2004 by Jason Keirstead <jason@keirstead.org>
Kopete (c) 2002-2005 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 <tqsocketnotifier.h>
#include <dcopclient.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <kssl.h>
#include <ksslinfodlg.h>
#include <ksslpeerinfo.h>
#include <ksslcertchain.h>
#include <ksslcertificatecache.h>
#include <tdeapplication.h>
#include <tdemessagebox.h>
#include "ksslsocket.h"
struct KSSLSocketPrivate
{
mutable KSSL *kssl;
KSSLCertificateCache *cc;
DCOPClient *dcc;
TQMap<TQString,TQString> metaData;
TQSocketNotifier *socketNotifier;
};
KSSLSocket::KSSLSocket() : KExtendedSocket()
{
d = new KSSLSocketPrivate;
d->kssl = 0;
d->dcc = TDEApplication::kApplication()->dcopClient();
d->cc = new KSSLCertificateCache;
d->cc->reload();
//No blocking
setBlockingMode(false);
//Connect internal slots
TQObject::connect( this, TQ_SIGNAL(connectionSuccess()), this, TQ_SLOT(slotConnected()) );
TQObject::connect( this, TQ_SIGNAL(closed(int)), this, TQ_SLOT(slotDisconnected()) );
TQObject::connect( this, TQ_SIGNAL(connectionFailed(int)), this, TQ_SLOT(slotDisconnected()));
}
KSSLSocket::~KSSLSocket()
{
//Close connection
closeNow();
if( d->kssl )
{
d->kssl->close();
delete d->kssl;
}
delete d->cc;
delete d;
}
TQ_LONG KSSLSocket::readBlock( char* data, TQ_ULONG maxLen )
{
//Re-implemented because KExtSocket doesn't use this when not in buffered mode
TQ_LONG retval = consumeReadBuffer(maxLen, data);
if( retval == 0 )
{
if (sockfd == -1)
return 0;
retval = -1;
}
return retval;
}
int KSSLSocket::peekBlock( char* data, uint maxLen )
{
//Re-implemented because KExtSocket doesn't use this when not in buffered mode
if( socketStatus() < connected )
return -2;
if( sockfd == -1 )
return -2;
return consumeReadBuffer(maxLen, data, false);
}
TQ_LONG KSSLSocket::writeBlock( const char* data, TQ_ULONG len )
{
return d->kssl ? (d->kssl->write(data, len)) : -1;
}
int KSSLSocket::bytesAvailable() const
{
if( socketStatus() < connected )
return -2;
//Re-implemented because KExtSocket doesn't use this when not in buffered mode
return TDEBufferedIO::bytesAvailable();
}
void KSSLSocket::slotReadData()
{
kdDebug(14120) << k_funcinfo << d->kssl->pending() << endl;
TQByteArray buff(512);
int bytesRead = d->kssl->read( buff.data(), 512 );
//Fill the read buffer
feedReadBuffer( bytesRead, buff.data() );
emit readyRead();
}
void KSSLSocket::slotConnected()
{
if (!KSSL::doesSSLWork()) {
kdError(14120) << k_funcinfo << "SSL not functional!" << endl;
closeNow();
emit sslFailure();
return;
}
delete d->kssl;
d->kssl = new KSSL();
if (d->kssl->connect( sockfd ) != 1) {
kdError(14120) << k_funcinfo << "SSL connect() failed." << endl;
closeNow();
emit sslFailure();
return;
}
//Disconnect the KExtSocket notifier slot, we use our own
TQObject::disconnect( readNotifier(), TQ_SIGNAL(activated( int )),
this, TQ_SLOT(socketActivityRead()) );
TQObject::connect( readNotifier(), TQ_SIGNAL(activated( int )),
this, TQ_SLOT(slotReadData()) );
readNotifier()->setEnabled(true);
if (verifyCertificate() != 1) {
closeNow();
emit certificateRejected();
return;
}
emit certificateAccepted();
}
void KSSLSocket::slotDisconnected()
{
kdDebug(14120) << k_funcinfo << "Disconnected" << endl;
if( readNotifier() )
readNotifier()->setEnabled(false);
delete d->kssl;
d->kssl = 0L;
}
void KSSLSocket::showInfoDialog()
{
if( socketStatus() == connected )
{
if (!d->dcc->isApplicationRegistered("tdeio_uiserver"))
{
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",TQStringList());
}
TQByteArray data, ignore;
TQCString ignoretype;
TQDataStream arg(data, IO_WriteOnly);
arg << "irc://" + peerAddress()->pretty() + ":" + port() << d->metaData;
d->dcc->call("tdeio_uiserver", "UIServer",
"showSSLInfoDialog(TQString,TDEIO::MetaData)", data, ignoretype, ignore);
}
}
void KSSLSocket::setMetaData( const TQString &key, const TQVariant &data )
{
TQVariant v = data;
d->metaData[key] = v.asString();
}
bool KSSLSocket::hasMetaData( const TQString &key )
{
return d->metaData.contains(key);
}
TQString KSSLSocket::metaData( const TQString &key )
{
if( d->metaData.contains(key) )
return d->metaData[key];
return TQString();
}
/*
I basically copied the below from tcpTDEIO::SlaveBase.hpp, with some modificaions and formatting.
* Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net
* Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
* Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
*/
int KSSLSocket::messageBox( TDEIO::SlaveBase::MessageBoxType type, const TQString &text, const TQString &caption,
const TQString &buttonYes, const TQString &buttonNo )
{
kdDebug(14120) << "messageBox " << type << " " << text << " - " << caption << buttonYes << buttonNo << endl;
TQByteArray data, result;
TQCString returnType;
TQDataStream arg(data, IO_WriteOnly);
arg << (int)1 << (int)type << text << caption << buttonYes << buttonNo;
if (!d->dcc->isApplicationRegistered("tdeio_uiserver"))
{
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",TQStringList());
}
d->dcc->call("tdeio_uiserver", "UIServer",
"messageBox(int,int,TQString,TQString,TQString,TQString)", data, returnType, result);
if( returnType == "int" )
{
int res;
TQDataStream r(result, IO_ReadOnly);
r >> res;
return res;
}
else
return 0; // communication failure
}
// Returns 0 for failed verification, -1 for rejected cert and 1 for ok
int KSSLSocket::verifyCertificate()
{
int rc = 0;
bool permacache = false;
bool _IPmatchesCN = false;
int result;
bool doAddHost = false;
TQString ourHost = host();
TQString ourIp = peerAddress()->pretty();
TQString theurl = "irc://" + ourHost + ":" + port();
if (!d->cc)
d->cc = new KSSLCertificateCache;
KSSLCertificate& pc = d->kssl->peerInfo().getPeerCertificate();
KSSLCertificate::KSSLValidationList ksvl = pc.validateVerbose(KSSLCertificate::SSLServer);
_IPmatchesCN = d->kssl->peerInfo().certMatchesAddress();
if (!_IPmatchesCN)
{
ksvl << KSSLCertificate::InvalidHost;
}
KSSLCertificate::KSSLValidation ksv = KSSLCertificate::Ok;
if (!ksvl.isEmpty())
ksv = ksvl.first();
/* Setting the various bits of meta-info that will be needed. */
setMetaData("ssl_cipher", d->kssl->connectionInfo().getCipher());
setMetaData("ssl_cipher_desc", d->kssl->connectionInfo().getCipherDescription());
setMetaData("ssl_cipher_version", d->kssl->connectionInfo().getCipherVersion());
setMetaData("ssl_cipher_used_bits", TQString::number(d->kssl->connectionInfo().getCipherUsedBits()));
setMetaData("ssl_cipher_bits", TQString::number(d->kssl->connectionInfo().getCipherBits()));
setMetaData("ssl_peer_ip", ourIp );
TQString errorStr;
for(KSSLCertificate::KSSLValidationList::ConstIterator it = ksvl.begin();
it != ksvl.end(); ++it)
{
errorStr += TQString::number(*it)+":";
}
setMetaData("ssl_cert_errors", errorStr);
setMetaData("ssl_peer_certificate", pc.toString());
if (pc.chain().isValid() && pc.chain().depth() > 1)
{
TQString theChain;
TQPtrList<KSSLCertificate> chain = pc.chain().getChain();
for (KSSLCertificate *c = chain.first(); c; c = chain.next())
{
theChain += c->toString();
theChain += "\n";
}
setMetaData("ssl_peer_chain", theChain);
}
else
{
setMetaData("ssl_peer_chain", "");
}
setMetaData("ssl_cert_state", TQString::number(ksv));
if (ksv == KSSLCertificate::Ok)
{
rc = 1;
setMetaData("ssl_action", "accept");
}
// Since we're the parent, we need to teach the child.
setMetaData("ssl_parent_ip", ourIp );
setMetaData("ssl_parent_cert", pc.toString());
// - Read from cache and see if there is a policy for this
KSSLCertificateCache::KSSLCertificatePolicy cp = d->cc->getPolicyByCertificate(pc);
// - validation code
if (ksv != 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(pc);
}
if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept)
{
cp = KSSLCertificateCache::Prompt;
}
// 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 (ksv == KSSLCertificate::InvalidHost)
{
TQString msg = i18n("The IP address of the host %1 "
"does not match the one the "
"certificate was issued to.");
result = messageBox( TDEIO::SlaveBase::WarningYesNoCancel,
msg.arg(ourHost),
i18n("Server Authentication"),
i18n("&Details"),
i18n("Co&ntinue") );
}
else
{
TQString msg = i18n("The server certificate failed the "
"authenticity test (%1).");
result = messageBox( TDEIO::SlaveBase::WarningYesNoCancel,
msg.arg(ourHost),
i18n("Server Authentication"),
i18n("&Details"),
i18n("Co&ntinue") );
}
if (result == KMessageBox::Yes)
{
showInfoDialog();
}
}
while (result == KMessageBox::Yes);
if (result == KMessageBox::No)
{
rc = 1;
cp = KSSLCertificateCache::Accept;
doAddHost = true;
result = messageBox( TDEIO::SlaveBase::WarningYesNo,
i18n("Would you like to accept this "
"certificate forever without "
"being prompted?"),
i18n("Server Authentication"),
i18n("&Forever"),
i18n("&Current Sessions Only"));
if (result == KMessageBox::Yes)
permacache = true;
else
permacache = false;
}
else
{
rc = -1;
cp = KSSLCertificateCache::Prompt;
}
break;
}
default:
kdDebug(14120) << "SSL error in cert code."
<< "Please report this to kopete-devel@kde.org."
<< endl;
break;
}
}
// - cache the results
d->cc->addCertificate(pc, cp, permacache);
if (doAddHost)
d->cc->addHost(pc, ourHost);
if (rc == -1)
return rc;
kdDebug(14120) << "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)ksv << endl
<< "| Certificate matches IP: " << _IPmatchesCN << endl
<< "+-----------------------------------------------"
<< endl;
// sendMetaData(); Do not call this function!!
return rc;
}
#include "ksslsocket.moc"