/* ksslsocket.cpp - KDE SSL Socket Copyright (c) 2005 by Tommi Rantala Copyright (c) 2004 by Jason Keirstead Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * 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 #include #include #include #include #include #include #include #include #include #include #include "ksslsocket.h" struct KSSLSocketPrivate { mutable KSSL *kssl; KSSLCertificateCache *cc; DCOPClient *dcc; TQMap 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, TQT_SIGNAL(connectionSuccess()), this, TQT_SLOT(slotConnected()) ); TQObject::connect( this, TQT_SIGNAL(closed(int)), this, TQT_SLOT(slotDisconnected()) ); TQObject::connect( this, TQT_SIGNAL(connectionFailed(int)), this, TQT_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->write( data, len ); } #ifdef USE_QT4 qint64 KSSLSocket::bytesAvailable() const #else // USE_QT4 int KSSLSocket::bytesAvailable() const #endif // USE_QT4 { 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(), TQT_SIGNAL(activated( int )), this, TQT_SLOT(socketActivityRead()) ); TQObject::connect( readNotifier(), TQT_SIGNAL(activated( int )), this, TQT_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 * Copyright (C) 2001 Dawit Alemayehu */ 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 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"