• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

tcpslavebase.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net>
00003  * Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
00004  * Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
00005  * Copyright (C) 2007,2008 Andreas Hartmetz <ahartmetz@gmail.com>
00006  * Copyright (C) 2008 Roland Harnau <tau@gmx.eu>
00007  * Copyright (C) 2010 Richard Moore <rich@kde.org>
00008  *
00009  * This file is part of the KDE project
00010  *
00011  * This library is free software; you can redistribute it and/or
00012  * modify it under the terms of the GNU Library General Public
00013  * License as published by the Free Software Foundation; either
00014  * version 2 of the License, or (at your option) any later version.
00015  *
00016  * This library is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019  * Library General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU Library General Public License
00022  * along with this library; see the file COPYING.LIB.  If not, write to
00023  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00024  * Boston, MA 02110-1301, USA.
00025  */
00026 
00027 #include "tcpslavebase.h"
00028 
00029 #include <config.h>
00030 
00031 #include <kdebug.h>
00032 #include <kconfiggroup.h>
00033 #include <ksslcertificatemanager.h>
00034 #include <ksslsettings.h>
00035 #include <kmessagebox.h>
00036 #include <klocale.h>
00037 #include <ktoolinvocation.h>
00038 #include <network/ktcpsocket.h>
00039 
00040 #include <QtCore/QDataStream>
00041 #include <QtCore/QTime>
00042 #include <QtNetwork/QTcpSocket>
00043 #include <QtNetwork/QHostInfo>
00044 #include <QtNetwork/QSslConfiguration>
00045 #include <QtDBus/QtDBus>
00046 
00047 
00048 using namespace KIO;
00049 //using namespace KNetwork;
00050 
00051 typedef QMap<QString, QString> StringStringMap;
00052 Q_DECLARE_METATYPE(StringStringMap)
00053 
00054 namespace KIO {
00055 Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult)
00056 }
00057 
00058 //TODO Proxy support whichever way works; KPAC reportedly does *not* work.
00059 //NOTE kded_proxyscout may or may not be interesting
00060 
00061 //TODO resurrect SSL session recycling; this means save the session on disconnect and look
00062 //for a reusable session on connect. Consider how HTTP persistent connections interact with that.
00063 
00064 //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it
00065 //in most places we ATM check for d->isSSL.
00066 
00067 //TODO check if d->isBlocking is honored everywhere it makes sense
00068 
00069 //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere.
00070 
00071 //TODO recognize partially encrypted websites as "somewhat safe"
00072 
00073 /* List of dialogs/messageboxes we need to use (current code location in parentheses)
00074  - Can the "dontAskAgainName" thing be improved?
00075 
00076  - "SSLCertDialog" [select client cert] (SlaveInterface)
00077  - Enter password for client certificate (inline)
00078  - Password for client cert was wrong. Please reenter. (inline)
00079  - Setting client cert failed. [doesn't give reason] (inline)
00080  - "SSLInfoDialog" [mostly server cert info] (SlaveInterface)
00081  - You are about to enter secure mode. Security information/Display SSL information/Connect (inline)
00082  - You are about to leave secure mode. Security information/Continue loading/Abort (inline)
00083  - Hostname mismatch: Continue/Details/Cancel (inline)
00084  - IP address mismatch: Continue/Details/Cancel (inline)
00085  - Certificate failed authenticity check: Continue/Details/Cancel (inline)
00086  - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline)
00087  */
00088 
00089 
00091 class TCPSlaveBase::TcpSlaveBasePrivate
00092 {
00093 public:
00094     TcpSlaveBasePrivate(TCPSlaveBase* qq) : q(qq) {}
00095 
00096     void setSslMetaData()
00097     {
00098         sslMetaData.insert("ssl_in_use", "TRUE");
00099         KSslCipher cipher = socket.sessionCipher();
00100         sslMetaData.insert("ssl_protocol_version", socket.negotiatedSslVersionName());
00101         QString sslCipher = cipher.encryptionMethod() + '\n';
00102         sslCipher += cipher.authenticationMethod() + '\n';
00103         sslCipher += cipher.keyExchangeMethod() + '\n';
00104         sslCipher += cipher.digestMethod();
00105         sslMetaData.insert("ssl_cipher", sslCipher);
00106         sslMetaData.insert("ssl_cipher_name", cipher.name());
00107         sslMetaData.insert("ssl_cipher_used_bits", QString::number(cipher.usedBits()));
00108         sslMetaData.insert("ssl_cipher_bits", QString::number(cipher.supportedBits()));
00109         sslMetaData.insert("ssl_peer_ip", ip);
00110 
00111         // try to fill in the blanks, i.e. missing certificates, and just assume that
00112         // those belong to the peer (==website or similar) certificate.
00113         for (int i = 0; i < sslErrors.count(); i++) {
00114             if (sslErrors[i].certificate().isNull()) {
00115                 sslErrors[i] = KSslError(sslErrors[i].error(),
00116                                         socket.peerCertificateChain()[0]);
00117             }
00118         }
00119 
00120         QString errorStr;
00121         // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators
00122         Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
00123             Q_FOREACH (const KSslError &error, sslErrors) {
00124                 if (error.certificate() == cert) {
00125                     errorStr += QString::number(static_cast<int>(error.error())) + '\t';
00126                 }
00127             }
00128             if (errorStr.endsWith('\t')) {
00129                 errorStr.chop(1);
00130             }
00131             errorStr += '\n';
00132         }
00133         errorStr.chop(1);
00134         sslMetaData.insert("ssl_cert_errors", errorStr);
00135 
00136         QString peerCertChain;
00137         Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
00138             peerCertChain.append(cert.toPem());
00139             peerCertChain.append('\x01');
00140         }
00141         peerCertChain.chop(1);
00142         sslMetaData.insert("ssl_peer_chain", peerCertChain);
00143         sendSslMetaData();
00144     }
00145 
00146     void clearSslMetaData()
00147     {
00148         sslMetaData.clear();
00149         sslMetaData.insert("ssl_in_use", "FALSE");
00150         sendSslMetaData();
00151     }
00152 
00153     void sendSslMetaData()
00154     {
00155         MetaData::ConstIterator it = sslMetaData.constBegin();
00156         for (; it != sslMetaData.constEnd(); ++it) {
00157             q->setMetaData(it.key(), it.value());
00158         }
00159     }
00160 
00161     SslResult startTLSInternal(KTcpSocket::SslVersion sslVersion,
00162                                const QSslConfiguration& configuration = QSslConfiguration(),
00163                                int waitForEncryptedTimeout = -1);
00164 
00165     TCPSlaveBase* q;
00166 
00167     bool isBlocking;
00168 
00169     KTcpSocket socket;
00170 
00171     QString host;
00172     QString ip;
00173     quint16 port;
00174     QByteArray serviceName;
00175 
00176     KSSLSettings sslSettings;
00177     bool usingSSL;
00178     bool autoSSL;
00179     bool sslNoUi; // If true, we just drop the connection silently
00180                   // if SSL certificate check fails in some way.
00181     QList<KSslError> sslErrors;
00182 
00183     MetaData sslMetaData;
00184 };
00185 
00186 
00187 //### uh, is this a good idea??
00188 QIODevice *TCPSlaveBase::socket() const
00189 {
00190     return &d->socket;
00191 }
00192 
00193 
00194 TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol,
00195                            const QByteArray &poolSocket,
00196                            const QByteArray &appSocket,
00197                            bool autoSSL)
00198  : SlaveBase(protocol, poolSocket, appSocket),
00199    d(new TcpSlaveBasePrivate(this))
00200 {
00201     d->isBlocking = true;
00202     d->port = 0;
00203     d->serviceName = protocol;
00204     d->usingSSL = false;
00205     d->autoSSL = autoSSL;
00206     d->sslNoUi = false;
00207     // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit
00208     // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize
00209     // and the BR# 187876 to understand why setting this limit is necessary.
00210     d->socket.setReadBufferSize(14680064);
00211 }
00212 
00213 
00214 TCPSlaveBase::~TCPSlaveBase()
00215 {
00216     delete d;
00217 }
00218 
00219 
00220 ssize_t TCPSlaveBase::write(const char *data, ssize_t len)
00221 {
00222     ssize_t written = d->socket.write(data, len);
00223     if (written == -1) {
00224         kDebug(7027) << "d->socket.write() returned -1! Socket error is"
00225                      << d->socket.error() << ", Socket state is" << d->socket.state();
00226     }
00227 
00228     bool success = false;
00229     if (d->isBlocking) {
00230         // Drain the tx buffer
00231         success = d->socket.waitForBytesWritten(-1);
00232     } else {
00233         // ### I don't know how to make sure that all data does get written at some point
00234         // without doing it now. There is no event loop to do it behind the scenes.
00235         // Polling in the dispatch() loop? Something timeout based?
00236         success = d->socket.waitForBytesWritten(0);
00237     }
00238 
00239     d->socket.flush();  //this is supposed to get the data on the wire faster
00240 
00241     if (d->socket.state() != KTcpSocket::ConnectedState || !success) {
00242         kDebug(7027) << "Write failed, will return -1! Socket error is"
00243                      << d->socket.error() << ", Socket state is" << d->socket.state()
00244                      << "Return value of waitForBytesWritten() is" << success;
00245         return -1;
00246     }
00247 
00248     return written;
00249 }
00250 
00251 
00252 ssize_t TCPSlaveBase::read(char* data, ssize_t len)
00253 {
00254     if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
00255         d->clearSslMetaData();
00256         kDebug(7029) << "lost SSL connection.";
00257         return -1;
00258     }
00259 
00260     if (!d->socket.bytesAvailable()) {
00261         const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000);
00262         d->socket.waitForReadyRead(timeout);
00263     }
00264 #if 0
00265     // Do not do this because its only benefit is to cause a nasty side effect
00266     // upstream in Qt. See BR# 260769.
00267     else if (d->socket.encryptionMode() != KTcpSocket::SslClientMode ||
00268                QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) {
00269         // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything
00270         // it seems to help performance.
00271         d->socket.waitForReadyRead(0);
00272     }
00273 #endif
00274     return d->socket.read(data, len);
00275 }
00276 
00277 
00278 ssize_t TCPSlaveBase::readLine(char *data, ssize_t len)
00279 {
00280     if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
00281         d->clearSslMetaData();
00282         kDebug(7029) << "lost SSL connection.";
00283         return -1;
00284     }
00285 
00286     const int timeout = (d->isBlocking ? -1: (readTimeout() * 1000));
00287     ssize_t readTotal = 0;
00288     do {
00289         if (!d->socket.bytesAvailable())
00290             d->socket.waitForReadyRead(timeout);
00291         ssize_t readStep = d->socket.readLine(&data[readTotal], len-readTotal);
00292         if (readStep == -1 || (readStep == 0 && d->socket.state() != KTcpSocket::ConnectedState)) {
00293             return -1;
00294         }
00295         readTotal += readStep;
00296     } while (readTotal == 0 || data[readTotal-1] != '\n');
00297 
00298     return readTotal;
00299 }
00300 
00301 
00302 bool TCPSlaveBase::connectToHost(const QString &/*protocol*/,
00303                                  const QString &host,
00304                                  quint16 port)
00305 {
00306     QString errorString;
00307     const int errCode = connectToHost(host, port, &errorString);
00308     if (errCode == 0)
00309         return true;
00310 
00311     error(errCode, errorString);
00312     return false;
00313 }
00314 
00315 int TCPSlaveBase::connectToHost(const QString& host, quint16 port, QString* errorString)
00316 {
00317     d->clearSslMetaData(); //We have separate connection and SSL setup phases
00318 
00319     if (errorString) {
00320         errorString->clear();  // clear prior error messages.
00321     }
00322 
00323     d->socket.setVerificationPeerName(host); // Used for ssl certificate verification (SNI)
00324 
00325     //  - leaving SSL - warn before we even connect
00326     //### see if it makes sense to move this into the HTTP ioslave which is the only
00327     //    user.
00328     if (metaData("main_frame_request") == "TRUE"  //### this looks *really* unreliable
00329           && metaData("ssl_activate_warnings") == "TRUE"
00330           && metaData("ssl_was_in_use") == "TRUE"
00331           && !d->autoSSL) {
00332         KSSLSettings kss;
00333         if (kss.warnOnLeave()) {
00334             int result = messageBox(i18n("You are about to leave secure "
00335                                          "mode. Transmissions will no "
00336                                          "longer be encrypted.\nThis "
00337                                          "means that a third party could "
00338                                          "observe your data in transit."),
00339                                     WarningContinueCancel,
00340                                     i18n("Security Information"),
00341                                     i18n("C&ontinue Loading"), QString(),
00342                                     "WarnOnLeaveSSLMode");
00343 
00344             if (result == KMessageBox::Cancel) {
00345                 if (errorString)
00346                     *errorString = host;
00347                 return ERR_USER_CANCELED;
00348             }
00349         }
00350     }
00351 
00352     /*
00353       By default the SSL handshake attempt uses these settings in the order shown:
00354 
00355       1.) Protocol: KTcpSocket::SecureProtocols   SSL compression: ON  (DEFAULT)
00356       2.) Protocol: KTcpSocket::SecureProtocols   SSL compression: OFF
00357       3.) Protocol: KTcpSocket::TlsV1             SSL compression: ON
00358       4.) Protocol: KTcpSocket::TlsV1             SSL compression: OFF
00359       5.) Protocol: KTcpSocket::SslV3             SSL compression: ON
00360       6.) Protocol: KTcpSocket::SslV3             SSL compression: OFF
00361 
00362       If any combination other than the one marked DEFAULT is used to complete
00363       the SSL handshake, then that combination will be cached using KIO's internal
00364       meta-data mechanism in order to speed up future connections to the same host.
00365     */
00366 
00367     QSslConfiguration sslConfig = d->socket.sslConfiguration();
00368 #if QT_VERSION >= 0x040800
00369     const bool isSslCompressionDisabled = sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
00370     const bool shouldSslCompressBeDisabled = config()->readEntry("LastUsedSslDisableCompressionFlag", isSslCompressionDisabled);
00371     sslConfig.setSslOption(QSsl::SslOptionDisableCompression, shouldSslCompressBeDisabled);
00372 #endif
00373     
00374     const int lastSslVerson = config()->readEntry("LastUsedSslVersion", static_cast<int>(KTcpSocket::SecureProtocols));
00375     KTcpSocket::SslVersion trySslVersion = static_cast<KTcpSocket::SslVersion>(lastSslVerson);
00376     KTcpSocket::SslVersions alreadyTriedSslVersions = trySslVersion;
00377 
00378     const int timeout = (connectTimeout() * 1000);
00379     while (true) {
00380         disconnectFromHost();  //Reset some state, even if we are already disconnected
00381         d->host = host;
00382 
00383         d->socket.connectToHost(host, port);
00384         const bool connectOk = d->socket.waitForConnected(timeout > -1 ? timeout : -1);
00385 
00386         kDebug(7027) << "Socket: state=" << d->socket.state()
00387                      << ", error=" << d->socket.error()
00388                      << ", connected?" << connectOk;
00389 
00390         if (d->socket.state() != KTcpSocket::ConnectedState) {
00391             if (errorString)
00392                 *errorString = host + QLatin1String(": ") + d->socket.errorString();
00393             switch (d->socket.error()) {
00394             case KTcpSocket::UnsupportedSocketOperationError:
00395                 return ERR_UNSUPPORTED_ACTION;
00396             case KTcpSocket::RemoteHostClosedError:
00397                 return ERR_CONNECTION_BROKEN;
00398             case KTcpSocket::SocketTimeoutError:
00399                 return ERR_SERVER_TIMEOUT;
00400             case KTcpSocket::HostNotFoundError:
00401                 return ERR_UNKNOWN_HOST;
00402             default:
00403                 return ERR_COULD_NOT_CONNECT;
00404             }
00405         }
00406 
00407         //### check for proxyAuthenticationRequiredError
00408 
00409         d->ip = d->socket.peerAddress().toString();
00410         d->port = d->socket.peerPort();
00411 
00412         if (d->autoSSL) {
00413             SslResult res = d->startTLSInternal(trySslVersion, sslConfig, 30000 /*30 secs timeout*/);
00414             if ((res & ResultFailed) && (res & ResultFailedEarly)) {
00415 #if QT_VERSION >= 0x040800
00416                 if (!sslConfig.testSslOption(QSsl::SslOptionDisableCompression)) {
00417                     sslConfig.setSslOption(QSsl::SslOptionDisableCompression, true);
00418                     continue;
00419                 }
00420 #endif
00421 
00422                 if (!(alreadyTriedSslVersions & KTcpSocket::SecureProtocols)) {
00423                     trySslVersion = KTcpSocket::SecureProtocols;
00424                     alreadyTriedSslVersions |= trySslVersion;
00425 #if QT_VERSION >= 0x040800
00426                     sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
00427 #endif
00428                     continue;
00429                 }
00430 
00431                 if (!(alreadyTriedSslVersions & KTcpSocket::TlsV1)) {
00432                     trySslVersion = KTcpSocket::TlsV1;
00433                     alreadyTriedSslVersions |= trySslVersion;
00434 #if QT_VERSION >= 0x040800
00435                     sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
00436 #endif
00437                     continue;
00438                 }
00439 
00440                 if (!(alreadyTriedSslVersions & KTcpSocket::SslV3)) {
00441                     trySslVersion = KTcpSocket::SslV3;
00442                     alreadyTriedSslVersions |= trySslVersion;
00443 #if QT_VERSION >= 0x040800
00444                     sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
00445 #endif
00446                     continue;
00447                 }
00448             }
00449 
00450             //### SSL 2.0 is (close to) dead and it's a good thing, too.
00451             if (res & ResultFailed) {
00452                 if (errorString)
00453                     *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host);
00454                 return ERR_COULD_NOT_CONNECT;
00455             }
00456         }
00457         // If the SSL handshake was done with anything protocol other than the default,
00458         // save that information so that any subsequent requests do not have to do thesame thing.
00459         if (trySslVersion != KTcpSocket::SecureProtocols && lastSslVerson == KTcpSocket::SecureProtocols) {
00460             setMetaData(QLatin1String("{internal~currenthost}LastUsedSslVersion"),
00461                         QString::number(trySslVersion));
00462         }
00463 #if QT_VERSION >= 0x040800
00464         if (sslConfig.testSslOption(QSsl::SslOptionDisableCompression) && !shouldSslCompressBeDisabled) {
00465             setMetaData(QLatin1String("{internal~currenthost}LastUsedSslDisableCompressionFlag"),
00466                         QString::number(true));
00467         }
00468 #endif
00469         return 0;
00470     }
00471     Q_ASSERT(false);
00472     // Code flow never gets here but let's make the compiler happy.
00473     // More: the stack allocation of QSslSettings seems to be confusing the compiler;
00474     //       in fact, any non-POD allocation does. 
00475     //       even a 'return 0;' directly after the allocation (so before the while(true))
00476     //       is ignored. definitely seems to be a compiler bug? - aseigo
00477     return 0;
00478 }
00479 
00480 void TCPSlaveBase::disconnectFromHost()
00481 {
00482     kDebug(7027);
00483     d->host.clear();
00484     d->ip.clear();
00485     d->usingSSL = false;
00486 
00487     if (d->socket.state() == KTcpSocket::UnconnectedState) {
00488         // discard incoming data - the remote host might have disconnected us in the meantime
00489         // but the visible effect of disconnectFromHost() should stay the same.
00490         d->socket.close();
00491         return;
00492     }
00493 
00494     //### maybe save a session for reuse on SSL shutdown if and when QSslSocket
00495     //    does that. QCA::TLS can do it apparently but that is not enough if
00496     //    we want to present that as KDE API. Not a big loss in any case.
00497     d->socket.disconnectFromHost();
00498     if (d->socket.state() != KTcpSocket::UnconnectedState)
00499         d->socket.waitForDisconnected(-1); // wait for unsent data to be sent
00500     d->socket.close(); //whatever that means on a socket
00501 }
00502 
00503 bool TCPSlaveBase::isAutoSsl() const
00504 {
00505     return d->autoSSL;
00506 }
00507 
00508 bool TCPSlaveBase::isUsingSsl() const
00509 {
00510     return d->usingSSL;
00511 }
00512 
00513 quint16 TCPSlaveBase::port() const
00514 {
00515     return d->port;
00516 }
00517 
00518 bool TCPSlaveBase::atEnd() const
00519 {
00520     return d->socket.atEnd();
00521 }
00522 
00523 bool TCPSlaveBase::startSsl()
00524 {
00525     if (d->usingSSL)
00526         return false;
00527     return d->startTLSInternal(KTcpSocket::TlsV1) & ResultOk;
00528 }
00529 
00530 // Find out if a hostname matches an SSL certificate's Common Name (including wildcards)
00531 static bool isMatchingHostname(const QString &cnIn, const QString &hostnameIn)
00532 {
00533     const QString cn = cnIn.toLower();
00534     const QString hostname = hostnameIn.toLower();
00535 
00536     const int wildcard = cn.indexOf(QLatin1Char('*'));
00537 
00538     // Check this is a wildcard cert, if not then just compare the strings
00539     if (wildcard < 0)
00540         return cn == hostname;
00541 
00542     const int firstCnDot = cn.indexOf(QLatin1Char('.'));
00543     const int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1);
00544 
00545     // Check at least 3 components
00546     if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length()))
00547         return false;
00548 
00549     // Check * is last character of 1st component (ie. there's a following .)
00550     if (wildcard+1 != firstCnDot)
00551         return false;
00552 
00553     // Check only one star
00554     if (cn.lastIndexOf(QLatin1Char('*')) != wildcard)
00555         return false;
00556 
00557     // Check characters preceding * (if any) match
00558     if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard)))
00559         return false;
00560 
00561     // Check characters following first . match
00562     if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot))
00563         return false;
00564 
00565     // Check if the hostname is an IP address, if so then wildcards are not allowed
00566     QHostAddress addr(hostname);
00567     if (!addr.isNull())
00568         return false;
00569 
00570     // Ok, I guess this was a wildcard CN and the hostname matches.
00571     return true;
00572 }
00573 
00574 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version,
00575                                                                              const QSslConfiguration& sslConfig,
00576                                                                              int waitForEncryptedTimeout)
00577 {
00578     q->selectClientCertificate();
00579 
00580     //setMetaData("ssl_session_id", d->kssl->session()->toString());
00581     //### we don't support session reuse for now...
00582     usingSSL = true;
00583 #if QT_VERSION >= 0x040800
00584     kDebug(7027) << "Trying SSL handshake with protocol:" << version
00585                  << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
00586 #endif
00587     // Set the SSL version to use...
00588     socket.setAdvertisedSslVersion(version);
00589 
00590     // Set SSL configuration information
00591     if (!sslConfig.isNull())
00592         socket.setSslConfiguration(sslConfig);
00593 
00594     /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors()
00595        signal but that would mess up the flow of control. We will check for errors
00596        anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors()
00597        before connecting would be very insecure. */
00598     socket.ignoreSslErrors();
00599     socket.startClientEncryption();
00600     const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout);
00601 
00602     //Set metadata, among other things for the "SSL Details" dialog
00603     KSslCipher cipher = socket.sessionCipher();
00604 
00605     if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode
00606         || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) {
00607         usingSSL = false;
00608         clearSslMetaData();
00609         kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is"
00610                      << encryptionStarted << ", cipher.isNull() is" << cipher.isNull()
00611                      << ", cipher.usedBits() is" << cipher.usedBits()
00612                      << ", length of certificate chain is" << socket.peerCertificateChain().count()
00613                      << ", the socket says:" << socket.errorString()
00614                      << "and the list of SSL errors contains"
00615                      << socket.sslErrors().count() << "items.";
00616         Q_FOREACH(const KSslError& sslError, socket.sslErrors()) {
00617             kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString();
00618         }
00619         return ResultFailed | ResultFailedEarly;
00620     }
00621 
00622     kDebug(7029) << "Cipher info - "
00623                  << " advertised SSL protocol version" << socket.advertisedSslVersion()
00624                  << " negotiated SSL protocol version" << socket.negotiatedSslVersion()
00625                  << " authenticationMethod:" << cipher.authenticationMethod()
00626                  << " encryptionMethod:" << cipher.encryptionMethod()
00627                  << " keyExchangeMethod:" << cipher.keyExchangeMethod()
00628                  << " name:" << cipher.name()
00629                  << " supportedBits:" << cipher.supportedBits()
00630                  << " usedBits:" << cipher.usedBits();
00631 
00632     // Since we connect by IP (cf. KIO::HostInfo) the SSL code will not recognize
00633     // that the site certificate belongs to the domain. We therefore do the
00634     // domain<->certificate matching here.
00635     sslErrors = socket.sslErrors();
00636     QSslCertificate peerCert = socket.peerCertificateChain().first();
00637     QMutableListIterator<KSslError> it(sslErrors);
00638     while (it.hasNext()) {
00639         // As of 4.4.0 Qt does not assign a certificate to the QSslError it emits
00640         // *in the case of HostNameMismatch*. A HostNameMismatch, however, will always
00641         // be an error of the peer certificate so we just don't check the error's
00642         // certificate().
00643 
00644         // Remove all HostNameMismatch, we have to redo name checking later.
00645         if (it.next().error() == KSslError::HostNameMismatch) {
00646             it.remove();
00647         }
00648     }
00649     // Redo name checking here and (re-)insert HostNameMismatch to sslErrors if
00650     // host name does not match any of the names in server certificate.
00651     // QSslSocket may not report HostNameMismatch error, when server
00652     // certificate was issued for the IP we are connecting to.
00653     QStringList domainPatterns(peerCert.subjectInfo(QSslCertificate::CommonName));
00654     domainPatterns += peerCert.alternateSubjectNames().values(QSsl::DnsEntry);
00655     bool names_match = false;
00656     foreach (const QString &dp, domainPatterns) {
00657         if (isMatchingHostname(dp, host)) {
00658             names_match = true;
00659             break;
00660         }
00661     }
00662     if (!names_match) {
00663         sslErrors.insert(0, KSslError(KSslError::HostNameMismatch, peerCert));
00664     }
00665 
00666     // TODO: review / rewrite / remove the comment
00667     // The app side needs the metadata now for the SSL error dialog (if any) but
00668     // the same metadata will be needed later, too. When "later" arrives the slave
00669     // may actually be connected to a different application that doesn't know
00670     // the metadata the slave sent to the previous application.
00671     // The quite important SSL indicator icon in Konqi's URL bar relies on metadata
00672     // from here, for example. And Konqi will be the second application to connect
00673     // to the slave.
00674     // Therefore we choose to have our metadata and send it, too :)
00675     setSslMetaData();
00676     q->sendAndKeepMetaData();
00677 
00678     SslResult rc = q->verifyServerCertificate();
00679     if (rc & ResultFailed) {
00680         usingSSL = false;
00681         clearSslMetaData();
00682         kDebug(7029) << "server certificate verification failed.";
00683         socket.disconnectFromHost();     //Make the connection fail (cf. ignoreSslErrors())
00684         return ResultFailed;
00685     } else if (rc & ResultOverridden) {
00686         kDebug(7029) << "server certificate verification failed but continuing at user's request.";
00687     }
00688 
00689     //"warn" when starting SSL/TLS
00690     if (q->metaData("ssl_activate_warnings") == "TRUE"
00691         && q->metaData("ssl_was_in_use") == "FALSE"
00692         && sslSettings.warnOnEnter()) {
00693 
00694         int msgResult = q->messageBox(i18n("You are about to enter secure mode. "
00695                                         "All transmissions will be encrypted "
00696                                         "unless otherwise noted.\nThis means "
00697                                         "that no third party will be able to "
00698                                         "easily observe your data in transit."),
00699                                    WarningYesNo,
00700                                    i18n("Security Information"),
00701                                    i18n("Display SSL &Information"),
00702                                    i18n("C&onnect"),
00703                                    "WarnOnEnterSSLMode");
00704         if (msgResult == KMessageBox::Yes) {
00705             q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host);
00706         }
00707     }
00708 
00709     return rc;
00710 }
00711 
00712 void TCPSlaveBase::selectClientCertificate()
00713 {
00714 #if 0 //hehe
00715     QString certname;   // the cert to use this session
00716     bool send = false, prompt = false, save = false, forcePrompt = false;
00717     KSSLCertificateHome::KSSLAuthAction aa;
00718 
00719     setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed
00720 
00721     if (metaData("ssl_no_client_cert") == "TRUE") return;
00722     forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE");
00723 
00724     // Delete the old cert since we're certainly done with it now
00725     if (d->pkcs) {
00726         delete d->pkcs;
00727         d->pkcs = NULL;
00728     }
00729 
00730     if (!d->kssl) return;
00731 
00732     // Look for a general certificate
00733     if (!forcePrompt) {
00734         certname = KSSLCertificateHome::getDefaultCertificateName(&aa);
00735         switch (aa) {
00736         case KSSLCertificateHome::AuthSend:
00737             send = true; prompt = false;
00738             break;
00739         case KSSLCertificateHome::AuthDont:
00740             send = false; prompt = false;
00741             certname.clear();
00742             break;
00743         case KSSLCertificateHome::AuthPrompt:
00744             send = false; prompt = true;
00745             break;
00746         default:
00747             break;
00748         }
00749     }
00750 
00751     // Look for a certificate on a per-host basis as an override
00752     QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa);
00753     if (aa != KSSLCertificateHome::AuthNone) {   // we must override
00754         switch (aa) {
00755         case KSSLCertificateHome::AuthSend:
00756             send = true;
00757             prompt = false;
00758             certname = tmpcn;
00759             break;
00760         case KSSLCertificateHome::AuthDont:
00761             send = false;
00762             prompt = false;
00763             certname.clear();
00764             break;
00765         case KSSLCertificateHome::AuthPrompt:
00766             send = false;
00767             prompt = true;
00768             certname = tmpcn;
00769             break;
00770         default:
00771             break;
00772         }
00773     }
00774 
00775     // Finally, we allow the application to override anything.
00776     if (hasMetaData("ssl_demand_certificate")) {
00777         certname = metaData("ssl_demand_certificate");
00778         if (!certname.isEmpty()) {
00779             forcePrompt = false;
00780             prompt = false;
00781             send = true;
00782         }
00783     }
00784 
00785     if (certname.isEmpty() && !prompt && !forcePrompt) return;
00786 
00787     // Ok, we're supposed to prompt the user....
00788     if (prompt || forcePrompt) {
00789         QStringList certs = KSSLCertificateHome::getCertificateList();
00790 
00791         QStringList::const_iterator it = certs.begin();
00792         while (it != certs.end()) {
00793             KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it);
00794             if (pkcs && (!pkcs->getCertificate() ||
00795                          !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) {
00796                 it = certs.erase(it);
00797             } else {
00798                 ++it;
00799             }
00800             delete pkcs;
00801         }
00802 
00803         if (certs.isEmpty()) return;  // we had nothing else, and prompt failed
00804 
00805         if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) {
00806             KToolInvocation::startServiceByDesktopPath("kuiserver.desktop",
00807                     QStringList());
00808         }
00809 
00810         QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer");
00811 
00812         QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong());
00813         if (retVal.type() == QDBusMessage::ReplyMessage) {
00814             if (retVal.arguments().at(0).toBool()) {
00815                 send = retVal.arguments().at(1).toBool();
00816                 save = retVal.arguments().at(2).toBool();
00817                 certname = retVal.arguments().at(3).toString();
00818             }
00819         }
00820     }
00821 
00822     // The user may have said to not send the certificate,
00823     // but to save the choice
00824     if (!send) {
00825         if (save) {
00826             KSSLCertificateHome::setDefaultCertificate(certname, d->host,
00827                     false, false);
00828         }
00829         return;
00830     }
00831 
00832     // We're almost committed.  If we can read the cert, we'll send it now.
00833     KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname);
00834     if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) {           // We need the password
00835         KIO::AuthInfo ai;
00836         bool first = true;
00837         do {
00838             ai.prompt = i18n("Enter the certificate password:");
00839             ai.caption = i18n("SSL Certificate Password");
00840             ai.url.setProtocol("kssl");
00841             ai.url.setHost(certname);
00842             ai.username = certname;
00843             ai.keepPassword = true;
00844 
00845             bool showprompt;
00846             if (first)
00847                 showprompt = !checkCachedAuthentication(ai);
00848             else
00849                 showprompt = true;
00850             if (showprompt) {
00851                 if (!openPasswordDialog(ai, first ? QString() :
00852                                         i18n("Unable to open the certificate. Try a new password?")))
00853                     break;
00854             }
00855 
00856             first = false;
00857             pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password);
00858         } while (!pkcs);
00859 
00860     }
00861 
00862     // If we could open the certificate, let's send it
00863     if (pkcs) {
00864         if (!d->kssl->setClientCertificate(pkcs)) {
00865             messageBox(Information, i18n("The procedure to set the "
00866                                          "client certificate for the session "
00867                                          "failed."), i18n("SSL"));
00868             delete pkcs;  // we don't need this anymore
00869             pkcs = 0L;
00870         } else {
00871             kDebug(7029) << "Client SSL certificate is being used.";
00872             setMetaData("ssl_using_client_cert", "TRUE");
00873             if (save) {
00874                 KSSLCertificateHome::setDefaultCertificate(certname, d->host,
00875                         true, false);
00876             }
00877         }
00878         d->pkcs = pkcs;
00879     }
00880 #endif
00881 }
00882 
00883 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate()
00884 {
00885     d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE");
00886 
00887     if (d->sslErrors.isEmpty()) {
00888         return ResultOk;
00889     } else if (d->sslNoUi) {
00890         return ResultFailed;
00891     }
00892 
00893     QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors);
00894     if (!fatalErrors.isEmpty()) {
00895         //TODO message "sorry, fatal error, you can't override it"
00896         return ResultFailed;
00897     }
00898 
00899     KSslCertificateManager *const cm = KSslCertificateManager::self();
00900     KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host);
00901 
00902     // remove previously seen and acknowledged errors
00903     QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors);
00904     if (remainingErrors.isEmpty()) {
00905         kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing.";
00906         return ResultOk | ResultOverridden;
00907     }
00908 
00909     //### We don't ask to permanently reject the certificate
00910 
00911     QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host);
00912     Q_FOREACH (const KSslError &err, d->sslErrors) {
00913         message.append(err.errorString());
00914         message.append('\n');
00915     }
00916     message = message.trimmed();
00917 
00918     int msgResult;
00919     do {
00920         msgResult = messageBox(WarningYesNoCancel, message,
00921                                i18n("Server Authentication"),
00922                                i18n("&Details"), i18n("Co&ntinue"));
00923         if (msgResult == KMessageBox::Yes) {
00924             //Details was chosen- show the certificate and error details
00925             messageBox(SSLMessageBox /*the SSL info dialog*/, d->host);
00926         } else if (msgResult == KMessageBox::Cancel) {
00927             return ResultFailed;
00928         }
00929         //fall through on KMessageBox::No
00930     } while (msgResult == KMessageBox::Yes);
00931 
00932     //Save the user's choice to ignore the SSL errors.
00933 
00934     msgResult = messageBox(WarningYesNo,
00935                             i18n("Would you like to accept this "
00936                                  "certificate forever without "
00937                                  "being prompted?"),
00938                             i18n("Server Authentication"),
00939                             i18n("&Forever"),
00940                             i18n("&Current Session only"));
00941     QDateTime ruleExpiry = QDateTime::currentDateTime();
00942     if (msgResult == KMessageBox::Yes) {
00943         //accept forever ("for a very long time")
00944         ruleExpiry = ruleExpiry.addYears(1000);
00945     } else {
00946         //accept "for a short time", half an hour.
00947         ruleExpiry = ruleExpiry.addSecs(30*60);
00948     }
00949 
00950     //TODO special cases for wildcard domain name in the certificate!
00951     //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
00952 
00953     rule.setExpiryDateTime(ruleExpiry);
00954     rule.setIgnoredErrors(d->sslErrors);
00955     cm->setRule(rule);
00956 
00957     return ResultOk | ResultOverridden;
00958 #if 0 //### need to to do something like the old code about the main and subframe stuff
00959     kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request");
00960     if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") {
00961         // Since we're the parent, we need to teach the child.
00962         setMetaData("ssl_parent_ip", d->ip);
00963         setMetaData("ssl_parent_cert", pc.toString());
00964         //  - Read from cache and see if there is a policy for this
00965         KSSLCertificateCache::KSSLCertificatePolicy cp =
00966             d->certCache->getPolicyByCertificate(pc);
00967 
00968         //  - validation code
00969         if (ksv != KSSLCertificate::Ok) {
00970             if (d->sslNoUi) {
00971                 return -1;
00972             }
00973 
00974             if (cp == KSSLCertificateCache::Unknown ||
00975                     cp == KSSLCertificateCache::Ambiguous) {
00976                 cp = KSSLCertificateCache::Prompt;
00977             } else {
00978                 // A policy was already set so let's honor that.
00979                 permacache = d->certCache->isPermanent(pc);
00980             }
00981 
00982             if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) {
00983                 cp = KSSLCertificateCache::Prompt;
00984 //            ksv = KSSLCertificate::Ok;
00985             }
00986 
00988 
00989         //  - cache the results
00990         d->certCache->addCertificate(pc, cp, permacache);
00991         if (doAddHost) d->certCache->addHost(pc, d->host);
00992     } else {    // Child frame
00993         //  - Read from cache and see if there is a policy for this
00994         KSSLCertificateCache::KSSLCertificatePolicy cp =
00995             d->certCache->getPolicyByCertificate(pc);
00996         isChild = true;
00997 
00998         // Check the cert and IP to make sure they're the same
00999         // as the parent frame
01000         bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") &&
01001                                  pc.toString() == metaData("ssl_parent_cert"));
01002 
01003         if (ksv == KSSLCertificate::Ok) {
01004             if (certAndIPTheSame) {       // success
01005                 rc = 1;
01006                 setMetaData("ssl_action", "accept");
01007             } else {
01008                 /*
01009                 if (d->sslNoUi) {
01010                   return -1;
01011                 }
01012                 result = messageBox(WarningYesNo,
01013                                     i18n("The certificate is valid but does not appear to have been assigned to this server.  Do you wish to continue loading?"),
01014                                     i18n("Server Authentication"));
01015                 if (result == KMessageBox::Yes) {     // success
01016                   rc = 1;
01017                   setMetaData("ssl_action", "accept");
01018                 } else {    // fail
01019                   rc = -1;
01020                   setMetaData("ssl_action", "reject");
01021                 }
01022                 */
01023                 setMetaData("ssl_action", "accept");
01024                 rc = 1;   // Let's accept this now.  It's bad, but at least the user
01025                 // will see potential attacks in KDE3 with the pseudo-lock
01026                 // icon on the toolbar, and can investigate with the RMB
01027             }
01028         } else {
01029             if (d->sslNoUi) {
01030                 return -1;
01031             }
01032 
01033             if (cp == KSSLCertificateCache::Accept) {
01034                 if (certAndIPTheSame) {    // success
01035                     rc = 1;
01036                     setMetaData("ssl_action", "accept");
01037                 } else {   // fail
01038                     result = messageBox(WarningYesNo,
01039                                         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?"),
01040                                         i18n("Server Authentication"));
01041                     if (result == KMessageBox::Yes) {
01042                         rc = 1;
01043                         setMetaData("ssl_action", "accept");
01044                         d->certCache->addHost(pc, d->host);
01045                     } else {
01046                         rc = -1;
01047                         setMetaData("ssl_action", "reject");
01048                     }
01049                 }
01050             } else if (cp == KSSLCertificateCache::Reject) {      // fail
01051                 messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."),
01052                            i18n("Server Authentication"));
01053                 rc = -1;
01054                 setMetaData("ssl_action", "reject");
01055             } else {
01056 
01058 
01059     return rc;
01060 #endif //#if 0
01061     return ResultOk | ResultOverridden;
01062 }
01063 
01064 
01065 bool TCPSlaveBase::isConnected() const
01066 {
01067     //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady...
01068     return d->socket.state() == KTcpSocket::ConnectedState;
01069 }
01070 
01071 
01072 bool TCPSlaveBase::waitForResponse(int t)
01073 {
01074     if (d->socket.bytesAvailable()) {
01075         return true;
01076     }
01077     return d->socket.waitForReadyRead(t * 1000);
01078 }
01079 
01080 void TCPSlaveBase::setBlocking(bool b)
01081 {
01082     if (!b) {
01083         kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work";
01084         return;
01085     }
01086     d->isBlocking = b;
01087 }
01088 
01089 void TCPSlaveBase::virtual_hook(int id, void* data)
01090 {
01091     if (id == SlaveBase::AppConnectionMade) {
01092         d->sendSslMetaData();
01093     } else {
01094         SlaveBase::virtual_hook(id, data);
01095     }
01096 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed Nov 28 2012 09:50:38 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal