diff options
Diffstat (limited to 'tdeio/tdeio/tcpslavebase.cpp')
-rw-r--r-- | tdeio/tdeio/tcpslavebase.cpp | 1343 |
1 files changed, 1343 insertions, 0 deletions
diff --git a/tdeio/tdeio/tcpslavebase.cpp b/tdeio/tdeio/tcpslavebase.cpp new file mode 100644 index 000000000..2b7df9d7b --- /dev/null +++ b/tdeio/tdeio/tcpslavebase.cpp @@ -0,0 +1,1343 @@ +/* + * $Id$ + * + * 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> + * + * This file is part of the KDE project + * + * This library 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 library 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 library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <time.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> + +#include <ksocks.h> +#include <kdebug.h> +#include <ksslall.h> +#include <ksslcertdlg.h> +#include <kmessagebox.h> +#ifndef Q_WS_WIN //temporary +#include <kresolver.h> +#endif + +#include <klocale.h> +#include <dcopclient.h> +#include <tqcstring.h> +#include <tqdatastream.h> + +#include <kapplication.h> + +#include <kprotocolmanager.h> +#include <kde_file.h> + +#include "tdeio/tcpslavebase.h" + +using namespace TDEIO; + +class TCPSlaveBase::TcpSlaveBasePrivate +{ +public: + + TcpSlaveBasePrivate() : rblockSz(256), militantSSL(false), userAborted(false) {} + ~TcpSlaveBasePrivate() {} + + KSSL *kssl; + bool usingTLS; + KSSLCertificateCache *cc; + TQString host; + TQString realHost; + TQString ip; + DCOPClient *dcc; + KSSLPKCS12 *pkcs; + + int status; + int timeout; + int rblockSz; // Size for reading blocks in readLine() + bool block; + bool useSSLTunneling; + bool needSSLHandShake; + bool militantSSL; // If true, we just drop a connection silently + // if SSL certificate check fails in any way. + bool userAborted; + MetaData savedMetaData; +}; + + +TCPSlaveBase::TCPSlaveBase(unsigned short int defaultPort, + const TQCString &protocol, + const TQCString &poolSocket, + const TQCString &appSocket) + :SlaveBase (protocol, poolSocket, appSocket), + m_iSock(-1), + m_iDefaultPort(defaultPort), + m_sServiceName(protocol), + fp(0) +{ + // We have to have two constructors, so don't add anything + // else in here. Put it in doConstructorStuff() instead. + doConstructorStuff(); + m_bIsSSL = false; +} + +TCPSlaveBase::TCPSlaveBase(unsigned short int defaultPort, + const TQCString &protocol, + const TQCString &poolSocket, + const TQCString &appSocket, + bool useSSL) + :SlaveBase (protocol, poolSocket, appSocket), + m_iSock(-1), + m_bIsSSL(useSSL), + m_iDefaultPort(defaultPort), + m_sServiceName(protocol), + fp(0) +{ + doConstructorStuff(); + if (useSSL) + m_bIsSSL = initializeSSL(); +} + +// The constructor procedures go here now. +void TCPSlaveBase::doConstructorStuff() +{ + d = new TcpSlaveBasePrivate; + d->kssl = 0L; + d->ip = ""; + d->cc = 0L; + d->usingTLS = false; + d->dcc = 0L; + d->pkcs = 0L; + d->status = -1; + d->timeout = KProtocolManager::connectTimeout(); + d->block = false; + d->useSSLTunneling = false; +} + +TCPSlaveBase::~TCPSlaveBase() +{ + cleanSSL(); + if (d->usingTLS) delete d->kssl; + if (d->dcc) delete d->dcc; + if (d->pkcs) delete d->pkcs; + delete d; +} + +ssize_t TCPSlaveBase::write(const void *data, ssize_t len) +{ +#ifdef Q_OS_UNIX + if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling ) + { + if ( d->needSSLHandShake ) + (void) doSSLHandShake( true ); + return d->kssl->write(data, len); + } + return KSocks::self()->write(m_iSock, data, len); +#else + return 0; +#endif +} + +ssize_t TCPSlaveBase::read(void *data, ssize_t len) +{ +#ifdef Q_OS_UNIX + if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling ) + { + if ( d->needSSLHandShake ) + (void) doSSLHandShake( true ); + return d->kssl->read(data, len); + } + return KSocks::self()->read(m_iSock, data, len); +#else + return 0; +#endif +} + + +void TCPSlaveBase::setBlockSize(int sz) +{ + if (sz <= 0) + sz = 1; + + d->rblockSz = sz; +} + + +ssize_t TCPSlaveBase::readLine(char *data, ssize_t len) +{ +// Optimization: +// It's small, but it probably results in a gain on very high +// speed connections. I moved 3 if statements out of the while loop +// so that the while loop is as small as possible. (GS) + + // let's not segfault! + if (!data) + return -1; + + char tmpbuf[1024]; // 1kb temporary buffer for peeking + *data = 0; + ssize_t clen = 0; + char *buf = data; + int rc = 0; + +if ((m_bIsSSL || d->usingTLS) && !d->useSSLTunneling) { // SSL CASE + if ( d->needSSLHandShake ) + (void) doSSLHandShake( true ); + + while (clen < len-1) { + rc = d->kssl->pending(); + if (rc > 0) { // Read a chunk + int bytes = rc; + if (bytes > d->rblockSz) + bytes = d->rblockSz; + + rc = d->kssl->peek(tmpbuf, bytes); + if (rc <= 0) { + // FIXME: this doesn't cover rc == 0 case + return -1; + } + + bytes = rc; // in case it contains no \n + for (int i = 0; i < rc; i++) { + if (tmpbuf[i] == '\n') { + bytes = i+1; + break; + } + } + + if (bytes+clen >= len) // don't read too much! + bytes = len - clen - 1; + + rc = d->kssl->read(buf, bytes); + if (rc > 0) { + clen += rc; + buf += (rc-1); + if (*buf++ == '\n') + break; + } else { + // FIXME: different case if rc == 0; + return -1; + } + } else { // Read a byte + rc = d->kssl->read(buf, 1); + if (rc <= 0) { + return -1; + // hm rc = 0 then + // SSL_read says to call SSL_get_error to see if + // this was an error. FIXME + } else { + clen++; + if (*buf++ == '\n') + break; + } + } + } +} else { // NON SSL CASE + while (clen < len-1) { +#ifdef Q_OS_UNIX + rc = KSocks::self()->read(m_iSock, buf, 1); +#else + rc = 0; +#endif + if (rc <= 0) { + // FIXME: this doesn't cover rc == 0 case + return -1; + } else { + clen++; + if (*buf++ == '\n') + break; + } + } +} + + // Both cases fall through to here + *buf = 0; +return clen; +} + +unsigned short int TCPSlaveBase::port(unsigned short int _p) +{ + unsigned short int p = _p; + + if (_p <= 0) + { + p = m_iDefaultPort; + } + + return p; +} + +// This function is simply a wrapper to establish the connection +// to the server. It's a bit more complicated than ::connect +// because we first have to check to see if the user specified +// a port, and if so use it, otherwise we check to see if there +// is a port specified in /etc/services, and if so use that +// otherwise as a last resort use the supplied default port. +bool TCPSlaveBase::connectToHost( const TQString &host, + unsigned int _port, + bool sendError ) +{ +#ifdef Q_OS_UNIX + unsigned short int p; + KExtendedSocket ks; + + d->userAborted = false; + + // - leaving SSL - warn before we even connect + if (metaData("main_frame_request") == "TRUE" && + metaData("ssl_activate_warnings") == "TRUE" && + metaData("ssl_was_in_use") == "TRUE" && + !m_bIsSSL) { + KSSLSettings kss; + if (kss.warnOnLeave()) { + int result = messageBox( i18n("You are about to leave secure " + "mode. Transmissions will no " + "longer be encrypted.\nThis " + "means that a third party could " + "observe your data in transit."), + WarningContinueCancel, + i18n("Security Information"), + i18n("C&ontinue Loading"), TQString::null, + "WarnOnLeaveSSLMode" ); + + // Move this setting into KSSL instead + TDEConfig *config = new TDEConfig("tdeioslaverc"); + config->setGroup("Notification Messages"); + + if (!config->readBoolEntry("WarnOnLeaveSSLMode", true)) { + config->deleteEntry("WarnOnLeaveSSLMode"); + config->sync(); + kss.setWarnOnLeave(false); + kss.save(); + } + delete config; + + if ( result == KMessageBox::Cancel ) { + d->userAborted = true; + return false; + } + } + } + + d->status = -1; + d->host = host; + d->needSSLHandShake = m_bIsSSL; + p = port(_port); + ks.setAddress(host, p); + if ( d->timeout > -1 ) + ks.setTimeout( d->timeout ); + + if (ks.connect() < 0) + { + d->status = ks.status(); + if ( sendError ) + { + if (d->status == IO_LookupError) + error( ERR_UNKNOWN_HOST, host); + else if ( d->status != -1 ) + error( ERR_COULD_NOT_CONNECT, host); + } + return false; + } + + m_iSock = ks.fd(); + + // store the IP for later + const TDESocketAddress *sa = ks.peerAddress(); + if (sa) + d->ip = sa->nodeName(); + else + d->ip = ""; + + ks.release(); // KExtendedSocket no longer applicable + + if ( d->block != ks.blockingMode() ) + ks.setBlockingMode( d->block ); + + m_iPort=p; + + if (m_bIsSSL && !d->useSSLTunneling) { + if ( !doSSLHandShake( sendError ) ) + return false; + } + else + setMetaData("ssl_in_use", "FALSE"); + + // Since we want to use stdio on the socket, + // we must fdopen it to get a file pointer, + // if it fails, close everything up + if ((fp = KDE_fdopen(m_iSock, "w+")) == 0) { + closeDescriptor(); + return false; + } + + return true; +#else //!Q_OS_UNIX + return false; +#endif //Q_OS_UNIX +} + +void TCPSlaveBase::closeDescriptor() +{ + stopTLS(); + if (fp) { + fclose(fp); + fp=0; + m_iSock=-1; + if (m_bIsSSL) + d->kssl->close(); + } + if (m_iSock != -1) { + close(m_iSock); + m_iSock=-1; + } + d->ip = ""; + d->host = ""; +} + +bool TCPSlaveBase::initializeSSL() +{ + if (m_bIsSSL) { + if (KSSL::doesSSLWork()) { + d->kssl = new KSSL; + return true; + } + } +return false; +} + +void TCPSlaveBase::cleanSSL() +{ + delete d->cc; + + if (m_bIsSSL) { + delete d->kssl; + d->kssl = 0; + } + d->militantSSL = false; +} + +bool TCPSlaveBase::atEnd() +{ + return feof(fp); +} + +int TCPSlaveBase::startTLS() +{ + if (d->usingTLS || d->useSSLTunneling || m_bIsSSL || !KSSL::doesSSLWork()) + return false; + + d->kssl = new KSSL(false); + if (!d->kssl->TLSInit()) { + delete d->kssl; + return -1; + } + + if ( !d->realHost.isEmpty() ) + { + kdDebug(7029) << "Setting real hostname: " << d->realHost << endl; + d->kssl->setPeerHost(d->realHost); + } else { + kdDebug(7029) << "Setting real hostname: " << d->host << endl; + d->kssl->setPeerHost(d->host); + } + + if (hasMetaData("ssl_session_id")) { + KSSLSession *s = KSSLSession::fromString(metaData("ssl_session_id")); + if (s) { + d->kssl->setSession(s); + delete s; + } + } + certificatePrompt(); + + int rc = d->kssl->connect(m_iSock); + if (rc < 0) { + delete d->kssl; + return -2; + } + + setMetaData("ssl_session_id", d->kssl->session()->toString()); + + d->usingTLS = true; + setMetaData("ssl_in_use", "TRUE"); + + if (!d->kssl->reusingSession()) { + rc = verifyCertificate(); + if (rc != 1) { + setMetaData("ssl_in_use", "FALSE"); + d->usingTLS = false; + delete d->kssl; + return -3; + } + } + + d->savedMetaData = mOutgoingMetaData; + return (d->usingTLS ? 1 : 0); +} + + +void TCPSlaveBase::stopTLS() +{ + if (d->usingTLS) { + delete d->kssl; + d->usingTLS = false; + setMetaData("ssl_in_use", "FALSE"); + } +} + + +void TCPSlaveBase::setSSLMetaData() { + if (!(d->usingTLS || d->useSSLTunneling || m_bIsSSL)) + return; + + mOutgoingMetaData = d->savedMetaData; +} + + +bool TCPSlaveBase::canUseTLS() +{ + if (m_bIsSSL || d->needSSLHandShake || !KSSL::doesSSLWork()) + return false; + + KSSLSettings kss; + return kss.tlsv1(); +} + + +void TCPSlaveBase::certificatePrompt() +{ +TQString certname; // the cert to use this session +bool send = false, prompt = false, save = false, forcePrompt = false; +KSSLCertificateHome::KSSLAuthAction aa; + + setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed + + if (metaData("ssl_no_client_cert") == "TRUE") return; + forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE"); + + // Delete the old cert since we're certainly done with it now + if (d->pkcs) { + delete d->pkcs; + d->pkcs = NULL; + } + + if (!d->kssl) return; + + // Look for a general certificate + if (!forcePrompt) { + certname = KSSLCertificateHome::getDefaultCertificateName(&aa); + switch(aa) { + case KSSLCertificateHome::AuthSend: + send = true; prompt = false; + break; + case KSSLCertificateHome::AuthDont: + send = false; prompt = false; + certname = TQString::null; + break; + case KSSLCertificateHome::AuthPrompt: + send = false; prompt = true; + break; + default: + break; + } + } + + TQString ourHost; + if (!d->realHost.isEmpty()) { + ourHost = d->realHost; + } else { + ourHost = d->host; + } + + // Look for a certificate on a per-host basis as an override + TQString tmpcn = KSSLCertificateHome::getDefaultCertificateName(ourHost, &aa); + if (aa != KSSLCertificateHome::AuthNone) { // we must override + switch (aa) { + case KSSLCertificateHome::AuthSend: + send = true; + prompt = false; + certname = tmpcn; + break; + case KSSLCertificateHome::AuthDont: + send = false; + prompt = false; + certname = TQString::null; + break; + case KSSLCertificateHome::AuthPrompt: + send = false; + prompt = true; + certname = tmpcn; + break; + default: + break; + } + } + + // Finally, we allow the application to override anything. + if (hasMetaData("ssl_demand_certificate")) { + certname = metaData("ssl_demand_certificate"); + if (!certname.isEmpty()) { + forcePrompt = false; + prompt = false; + send = true; + } + } + + if (certname.isEmpty() && !prompt && !forcePrompt) return; + + // Ok, we're supposed to prompt the user.... + if (prompt || forcePrompt) { + TQStringList certs = KSSLCertificateHome::getCertificateList(); + + for (TQStringList::Iterator it = certs.begin(); it != certs.end(); ++it) { + KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it); + if (pkcs && (!pkcs->getCertificate() || + !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) { + certs.remove(*it); + } + delete pkcs; + } + + if (certs.isEmpty()) return; // we had nothing else, and prompt failed + + if (!d->dcc) { + d->dcc = new DCOPClient; + d->dcc->attach(); + if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) { + TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop", + TQStringList() ); + } + } + + TQByteArray data, retval; + TQCString rettype; + TQDataStream arg(data, IO_WriteOnly); + arg << ourHost; + arg << certs; + arg << metaData("window-id").toInt(); + bool rc = d->dcc->call("tdeio_uiserver", "UIServer", + "showSSLCertDialog(TQString, TQStringList,int)", + data, rettype, retval); + + if (rc && rettype == "KSSLCertDlgRet") { + TQDataStream retStream(retval, IO_ReadOnly); + KSSLCertDlgRet drc; + retStream >> drc; + if (drc.ok) { + send = drc.send; + save = drc.save; + certname = drc.choice; + } + } + } + + // The user may have said to not send the certificate, + // but to save the choice + if (!send) { + if (save) { + KSSLCertificateHome::setDefaultCertificate(certname, ourHost, + false, false); + } + return; + } + + // We're almost committed. If we can read the cert, we'll send it now. + KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname); + if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password + TDEIO::AuthInfo ai; + bool first = true; + do { + ai.prompt = i18n("Enter the certificate password:"); + ai.caption = i18n("SSL Certificate Password"); + ai.url.setProtocol("kssl"); + ai.url.setHost(certname); + ai.username = certname; + ai.keepPassword = true; + + bool showprompt; + if (first) + showprompt = !checkCachedAuthentication(ai); + else + showprompt = true; + if (showprompt) { + if (!openPassDlg(ai, first ? TQString::null : + i18n("Unable to open the certificate. Try a new password?"))) + break; + } + + first = false; + pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password); + } while (!pkcs); + + } + + // If we could open the certificate, let's send it + if (pkcs) { + if (!d->kssl->setClientCertificate(pkcs)) { + messageBox(Information, i18n("The procedure to set the " + "client certificate for the session " + "failed."), i18n("SSL")); + delete pkcs; // we don't need this anymore + pkcs = 0L; + } else { + kdDebug(7029) << "Client SSL certificate is being used." << endl; + setMetaData("ssl_using_client_cert", "TRUE"); + if (save) { + KSSLCertificateHome::setDefaultCertificate(certname, ourHost, + true, false); + } + } + d->pkcs = pkcs; + } +} + + + +bool TCPSlaveBase::usingTLS() const +{ + return d->usingTLS; +} + +// ### remove this for KDE4 (misses const): +bool TCPSlaveBase::usingTLS() +{ + return d->usingTLS; +} + + +// Returns 0 for failed verification, -1 for rejected cert and 1 for ok +int TCPSlaveBase::verifyCertificate() +{ + int rc = 0; + bool permacache = false; + bool isChild = false; + bool _IPmatchesCN = false; + int result; + bool doAddHost = false; + TQString ourHost; + + if (!d->realHost.isEmpty()) + ourHost = d->realHost; + else ourHost = d->host; + + TQString theurl = TQString(m_sServiceName)+"://"+ourHost+":"+TQString::number(m_iPort); + + if (!hasMetaData("ssl_militant") || metaData("ssl_militant") == "FALSE") + d->militantSSL = false; + else if (metaData("ssl_militant") == "TRUE") + d->militantSSL = true; + + 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) { +#ifndef Q_WS_WIN //temporary + KNetwork::KResolverResults res = KNetwork::KResolver::resolve(d->kssl->peerInfo().peerHost(), "80", KNetwork::KResolver::CanonName); + if (!res.isEmpty()) { + TQString old = d->kssl->peerInfo().peerHost(); + d->kssl->peerInfo().setPeerHost(res[0].canonicalName()); + _IPmatchesCN = d->kssl->peerInfo().certMatchesAddress(); + if (!_IPmatchesCN) { + d->kssl->peerInfo().setPeerHost(old); + } + } +#endif + if (!_IPmatchesCN && !d->militantSSL) { // force this if the user wants it + if (d->cc->getHostList(pc).contains(ourHost)) { + _IPmatchesCN = true; + } + } + } + + 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", d->ip); + if (!d->realHost.isEmpty()) { + setMetaData("ssl_proxied", "true"); + } + + 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(); + chain.setAutoDelete(true); + 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"); + } + + kdDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request") << endl; + if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") { + // Since we're the parent, we need to teach the child. + setMetaData("ssl_parent_ip", d->ip); + 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 (d->militantSSL) { + return -1; + } + + 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; +// ksv = KSSLCertificate::Ok; + } + + // Precondition: cp is one of Reject, Accept or Prompt + switch (cp) { + case KSSLCertificateCache::Accept: + rc = 1; + setMetaData("ssl_action", "accept"); + break; + case KSSLCertificateCache::Reject: + rc = -1; + setMetaData("ssl_action", "reject"); + 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( 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( WarningYesNoCancel, + msg.arg(ourHost), + i18n("Server Authentication"), + i18n("&Details"), + i18n("Co&ntinue") ); + } + + if (result == KMessageBox::Yes) { + if (!d->dcc) { + d->dcc = new DCOPClient; + d->dcc->attach(); + if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) { + TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop", + TQStringList() ); + } + + } + TQByteArray data, ignore; + TQCString ignoretype; + TQDataStream arg(data, IO_WriteOnly); + arg << theurl << mOutgoingMetaData; + arg << metaData("window-id").toInt(); + d->dcc->call("tdeio_uiserver", "UIServer", + "showSSLInfoDialog(TQString,TDEIO::MetaData,int)", + data, ignoretype, ignore); + } + } while (result == KMessageBox::Yes); + + if (result == KMessageBox::No) { + setMetaData("ssl_action", "accept"); + rc = 1; + cp = KSSLCertificateCache::Accept; + doAddHost = true; + result = messageBox( 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 { + setMetaData("ssl_action", "reject"); + rc = -1; + cp = KSSLCertificateCache::Prompt; + } + break; + } + default: + kdDebug(7029) << "TCPSlaveBase/SSL error in cert code." + << "Please report this to kfm-devel@kde.org." + << endl; + break; + } + } + + + // - cache the results + d->cc->addCertificate(pc, cp, permacache); + if (doAddHost) d->cc->addHost(pc, ourHost); + } else { // Child frame + // - Read from cache and see if there is a policy for this + KSSLCertificateCache::KSSLCertificatePolicy cp = + d->cc->getPolicyByCertificate(pc); + isChild = true; + + // Check the cert and IP to make sure they're the same + // as the parent frame + bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") && + pc.toString() == metaData("ssl_parent_cert")); + + if (ksv == KSSLCertificate::Ok) { + if (certAndIPTheSame) { // success + rc = 1; + setMetaData("ssl_action", "accept"); + } else { + /* + if (d->militantSSL) { + return -1; + } + result = messageBox(WarningYesNo, + i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"), + i18n("Server Authentication")); + if (result == KMessageBox::Yes) { // success + rc = 1; + setMetaData("ssl_action", "accept"); + } else { // fail + rc = -1; + setMetaData("ssl_action", "reject"); + } + */ + setMetaData("ssl_action", "accept"); + rc = 1; // Let's accept this now. It's bad, but at least the user + // will see potential attacks in KDE3 with the pseudo-lock + // icon on the toolbar, and can investigate with the RMB + } + } else { + if (d->militantSSL) { + return -1; + } + + if (cp == KSSLCertificateCache::Accept) { + if (certAndIPTheSame) { // success + rc = 1; + setMetaData("ssl_action", "accept"); + } else { // fail + result = messageBox(WarningYesNo, + i18n("You have indicated that you wish to accept this certificate, but it is not issued to the server who is presenting it. Do you wish to continue loading?"), + i18n("Server Authentication")); + if (result == KMessageBox::Yes) { + rc = 1; + setMetaData("ssl_action", "accept"); + d->cc->addHost(pc, ourHost); + } else { + rc = -1; + setMetaData("ssl_action", "reject"); + } + } + } else if (cp == KSSLCertificateCache::Reject) { // fail + messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the TDE Control Center."), + i18n("Server Authentication")); + rc = -1; + setMetaData("ssl_action", "reject"); + } else { + do { + TQString msg = i18n("The server certificate failed the " + "authenticity test (%1)."); + result = messageBox(WarningYesNoCancel, + msg.arg(ourHost), + i18n("Server Authentication"), + i18n("&Details"), + i18n("Co&nnect")); + if (result == KMessageBox::Yes) { + if (!d->dcc) { + d->dcc = new DCOPClient; + d->dcc->attach(); + if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) { + TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop", + TQStringList() ); + } + } + TQByteArray data, ignore; + TQCString ignoretype; + TQDataStream arg(data, IO_WriteOnly); + arg << theurl << mOutgoingMetaData; + arg << metaData("window-id").toInt(); + d->dcc->call("tdeio_uiserver", "UIServer", + "showSSLInfoDialog(TQString,TDEIO::MetaData,int)", + data, ignoretype, ignore); + } + } while (result == KMessageBox::Yes); + + if (result == KMessageBox::No) { + setMetaData("ssl_action", "accept"); + rc = 1; + cp = KSSLCertificateCache::Accept; + result = messageBox(WarningYesNo, + i18n("Would you like to accept this " + "certificate forever without " + "being prompted?"), + i18n("Server Authentication"), + i18n("&Forever"), + i18n("&Current Sessions Only")); + permacache = (result == KMessageBox::Yes); + d->cc->addCertificate(pc, cp, permacache); + d->cc->addHost(pc, ourHost); + } else { + setMetaData("ssl_action", "reject"); + rc = -1; + cp = KSSLCertificateCache::Prompt; + d->cc->addCertificate(pc, cp, permacache); + } + } + } + } + + + if (rc == -1) { + return rc; + } + + if (metaData("ssl_activate_warnings") == "TRUE") { + // - entering SSL + if (!isChild && metaData("ssl_was_in_use") == "FALSE" && + d->kssl->settings()->warnOnEnter()) { + int result; + do { + result = messageBox( i18n("You are about to " + "enter secure mode. " + "All transmissions " + "will be encrypted " + "unless otherwise " + "noted.\nThis means " + "that no third party " + "will be able to " + "easily observe your " + "data in transit."), + WarningYesNo, + i18n("Security Information"), + i18n("Display SSL " + "&Information"), + i18n("C&onnect"), + "WarnOnEnterSSLMode" ); + // Move this setting into KSSL instead + TDEConfig *config = new TDEConfig("tdeioslaverc"); + config->setGroup("Notification Messages"); + + if (!config->readBoolEntry("WarnOnEnterSSLMode", true)) { + config->deleteEntry("WarnOnEnterSSLMode"); + config->sync(); + d->kssl->settings()->setWarnOnEnter(false); + d->kssl->settings()->save(); + } + delete config; + + if ( result == KMessageBox::Yes ) + { + if (!d->dcc) { + d->dcc = new DCOPClient; + d->dcc->attach(); + if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) { + TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop", + TQStringList() ); + } + } + TQByteArray data, ignore; + TQCString ignoretype; + TQDataStream arg(data, IO_WriteOnly); + arg << theurl << mOutgoingMetaData; + arg << metaData("window-id").toInt(); + d->dcc->call("tdeio_uiserver", "UIServer", + "showSSLInfoDialog(TQString,TDEIO::MetaData,int)", + data, ignoretype, ignore); + } + } while (result != KMessageBox::No); + } + + } // if ssl_activate_warnings + + + kdDebug(7029) << "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; +} + + +bool TCPSlaveBase::isConnectionValid() +{ + if ( m_iSock == -1 ) + return false; + + fd_set rdfs; + FD_ZERO(&rdfs); + FD_SET(m_iSock , &rdfs); + + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 0; + int retval; +#ifdef Q_OS_UNIX + do { + retval = KSocks::self()->select(m_iSock+1, &rdfs, NULL, NULL, &tv); + if (wasKilled()) + return false; // Beam us out of here + } while ((retval == -1) && (errno == EAGAIN)); +#else + retval = -1; +#endif + // retval == -1 ==> Error + // retval == 0 ==> Connection Idle + // retval >= 1 ==> Connection Active + //kdDebug(7029) << "TCPSlaveBase::isConnectionValid: select returned: " + // << retval << endl; + + if (retval == -1) + return false; + + if (retval == 0) + return true; + + // Connection is active, check if it has closed. + char buffer[100]; +#ifdef Q_OS_UNIX + do { + retval = KSocks::self()->recv(m_iSock, buffer, 80, MSG_PEEK); + + } while ((retval == -1) && (errno == EAGAIN)); +#else + retval = -1; +#endif + //kdDebug(7029) << "TCPSlaveBase::isConnectionValid: recv returned: " + // << retval << endl; + if (retval <= 0) + return false; // Error or connection closed. + + return true; // Connection still valid. +} + + +bool TCPSlaveBase::waitForResponse( int t ) +{ + fd_set rd; + struct timeval timeout; + + if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling && d->kssl ) + if (d->kssl->pending() > 0) + return true; + + FD_ZERO(&rd); + FD_SET(m_iSock, &rd); + + timeout.tv_usec = 0; + timeout.tv_sec = t; + time_t startTime; + + int rc; + int n = t; + +reSelect: + startTime = time(NULL); +#ifdef Q_OS_UNIX + rc = KSocks::self()->select(m_iSock+1, &rd, NULL, NULL, &timeout); +#else + rc = -1; +#endif + if (wasKilled()) + return false; // We're dead. + + if (rc == -1) + return false; + + if (FD_ISSET(m_iSock, &rd)) + return true; + + // Well it returned but it wasn't set. Let's see if it + // returned too early (perhaps from an errant signal) and + // start over with the remaining time + int timeDone = time(NULL) - startTime; + if (timeDone < n) + { + n -= timeDone; + timeout.tv_sec = n; + goto reSelect; + } + + return false; // Timed out! +} + +int TCPSlaveBase::connectResult() +{ + return d->status; +} + +void TCPSlaveBase::setBlockConnection( bool b ) +{ + d->block = b; +} + +void TCPSlaveBase::setConnectTimeout( int t ) +{ + d->timeout = t; +} + +bool TCPSlaveBase::isSSLTunnelEnabled() +{ + return d->useSSLTunneling; +} + +void TCPSlaveBase::setEnableSSLTunnel( bool enable ) +{ + d->useSSLTunneling = enable; +} + +void TCPSlaveBase::setRealHost( const TQString& realHost ) +{ + d->realHost = realHost; +} + +bool TCPSlaveBase::doSSLHandShake( bool sendError ) +{ + kdDebug(7029) << "TCPSlaveBase::doSSLHandShake: " << endl; + TQString msgHost = d->host; + + d->kssl->reInitialize(); + + if (hasMetaData("ssl_session_id")) { + KSSLSession *s = KSSLSession::fromString(metaData("ssl_session_id")); + if (s) { + d->kssl->setSession(s); + delete s; + } + } + certificatePrompt(); + + if ( !d->realHost.isEmpty() ) + { + msgHost = d->realHost; + } + + kdDebug(7029) << "Setting real hostname: " << msgHost << endl; + d->kssl->setPeerHost(msgHost); + + d->status = d->kssl->connect(m_iSock); + if (d->status < 0) + { + closeDescriptor(); + if ( sendError ) + error( ERR_COULD_NOT_CONNECT, msgHost); + return false; + } + + setMetaData("ssl_session_id", d->kssl->session()->toString()); + setMetaData("ssl_in_use", "TRUE"); + + if (!d->kssl->reusingSession()) { + int rc = verifyCertificate(); + if ( rc != 1 ) { + d->status = -1; + closeDescriptor(); + if ( sendError ) + error( ERR_COULD_NOT_CONNECT, msgHost); + return false; + } + } + + d->needSSLHandShake = false; + + d->savedMetaData = mOutgoingMetaData; + return true; +} + + +bool TCPSlaveBase::userAborted() const +{ + return d->userAborted; +} + +void TCPSlaveBase::virtual_hook( int id, void* data ) +{ SlaveBase::virtual_hook( id, data ); } + |