• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.9.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 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version,
00531                                                                              const QSslConfiguration& sslConfig,
00532                                                                              int waitForEncryptedTimeout)
00533 {
00534     q->selectClientCertificate();
00535 
00536     //setMetaData("ssl_session_id", d->kssl->session()->toString());
00537     //### we don't support session reuse for now...
00538     usingSSL = true;
00539 #if QT_VERSION >= 0x040800
00540     kDebug(7027) << "Trying SSL handshake with protocol:" << version
00541                  << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
00542 #endif
00543     // Set the SSL version to use...
00544     socket.setAdvertisedSslVersion(version);
00545 
00546     // Set SSL configuration information
00547     if (!sslConfig.isNull())
00548         socket.setSslConfiguration(sslConfig);
00549 
00550     /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors()
00551        signal but that would mess up the flow of control. We will check for errors
00552        anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors()
00553        before connecting would be very insecure. */
00554     socket.ignoreSslErrors();
00555     socket.startClientEncryption();
00556     const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout);
00557 
00558     //Set metadata, among other things for the "SSL Details" dialog
00559     KSslCipher cipher = socket.sessionCipher();
00560 
00561     if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode
00562         || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) {
00563         usingSSL = false;
00564         clearSslMetaData();
00565         kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is"
00566                      << encryptionStarted << ", cipher.isNull() is" << cipher.isNull()
00567                      << ", cipher.usedBits() is" << cipher.usedBits()
00568                      << ", length of certificate chain is" << socket.peerCertificateChain().count()
00569                      << ", the socket says:" << socket.errorString()
00570                      << "and the list of SSL errors contains"
00571                      << socket.sslErrors().count() << "items.";
00572         Q_FOREACH(const KSslError& sslError, socket.sslErrors()) {
00573             kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString();
00574         }
00575         return ResultFailed | ResultFailedEarly;
00576     }
00577 
00578     kDebug(7029) << "Cipher info - "
00579                  << " advertised SSL protocol version" << socket.advertisedSslVersion()
00580                  << " negotiated SSL protocol version" << socket.negotiatedSslVersion()
00581                  << " authenticationMethod:" << cipher.authenticationMethod()
00582                  << " encryptionMethod:" << cipher.encryptionMethod()
00583                  << " keyExchangeMethod:" << cipher.keyExchangeMethod()
00584                  << " name:" << cipher.name()
00585                  << " supportedBits:" << cipher.supportedBits()
00586                  << " usedBits:" << cipher.usedBits();
00587 
00588     sslErrors = socket.sslErrors();
00589 
00590     // TODO: review / rewrite / remove the comment
00591     // The app side needs the metadata now for the SSL error dialog (if any) but
00592     // the same metadata will be needed later, too. When "later" arrives the slave
00593     // may actually be connected to a different application that doesn't know
00594     // the metadata the slave sent to the previous application.
00595     // The quite important SSL indicator icon in Konqi's URL bar relies on metadata
00596     // from here, for example. And Konqi will be the second application to connect
00597     // to the slave.
00598     // Therefore we choose to have our metadata and send it, too :)
00599     setSslMetaData();
00600     q->sendAndKeepMetaData();
00601 
00602     SslResult rc = q->verifyServerCertificate();
00603     if (rc & ResultFailed) {
00604         usingSSL = false;
00605         clearSslMetaData();
00606         kDebug(7029) << "server certificate verification failed.";
00607         socket.disconnectFromHost();     //Make the connection fail (cf. ignoreSslErrors())
00608         return ResultFailed;
00609     } else if (rc & ResultOverridden) {
00610         kDebug(7029) << "server certificate verification failed but continuing at user's request.";
00611     }
00612 
00613     //"warn" when starting SSL/TLS
00614     if (q->metaData("ssl_activate_warnings") == "TRUE"
00615         && q->metaData("ssl_was_in_use") == "FALSE"
00616         && sslSettings.warnOnEnter()) {
00617 
00618         int msgResult = q->messageBox(i18n("You are about to enter secure mode. "
00619                                         "All transmissions will be encrypted "
00620                                         "unless otherwise noted.\nThis means "
00621                                         "that no third party will be able to "
00622                                         "easily observe your data in transit."),
00623                                    WarningYesNo,
00624                                    i18n("Security Information"),
00625                                    i18n("Display SSL &Information"),
00626                                    i18n("C&onnect"),
00627                                    "WarnOnEnterSSLMode");
00628         if (msgResult == KMessageBox::Yes) {
00629             q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host);
00630         }
00631     }
00632 
00633     return rc;
00634 }
00635 
00636 void TCPSlaveBase::selectClientCertificate()
00637 {
00638 #if 0 //hehe
00639     QString certname;   // the cert to use this session
00640     bool send = false, prompt = false, save = false, forcePrompt = false;
00641     KSSLCertificateHome::KSSLAuthAction aa;
00642 
00643     setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed
00644 
00645     if (metaData("ssl_no_client_cert") == "TRUE") return;
00646     forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE");
00647 
00648     // Delete the old cert since we're certainly done with it now
00649     if (d->pkcs) {
00650         delete d->pkcs;
00651         d->pkcs = NULL;
00652     }
00653 
00654     if (!d->kssl) return;
00655 
00656     // Look for a general certificate
00657     if (!forcePrompt) {
00658         certname = KSSLCertificateHome::getDefaultCertificateName(&aa);
00659         switch (aa) {
00660         case KSSLCertificateHome::AuthSend:
00661             send = true; prompt = false;
00662             break;
00663         case KSSLCertificateHome::AuthDont:
00664             send = false; prompt = false;
00665             certname.clear();
00666             break;
00667         case KSSLCertificateHome::AuthPrompt:
00668             send = false; prompt = true;
00669             break;
00670         default:
00671             break;
00672         }
00673     }
00674 
00675     // Look for a certificate on a per-host basis as an override
00676     QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa);
00677     if (aa != KSSLCertificateHome::AuthNone) {   // we must override
00678         switch (aa) {
00679         case KSSLCertificateHome::AuthSend:
00680             send = true;
00681             prompt = false;
00682             certname = tmpcn;
00683             break;
00684         case KSSLCertificateHome::AuthDont:
00685             send = false;
00686             prompt = false;
00687             certname.clear();
00688             break;
00689         case KSSLCertificateHome::AuthPrompt:
00690             send = false;
00691             prompt = true;
00692             certname = tmpcn;
00693             break;
00694         default:
00695             break;
00696         }
00697     }
00698 
00699     // Finally, we allow the application to override anything.
00700     if (hasMetaData("ssl_demand_certificate")) {
00701         certname = metaData("ssl_demand_certificate");
00702         if (!certname.isEmpty()) {
00703             forcePrompt = false;
00704             prompt = false;
00705             send = true;
00706         }
00707     }
00708 
00709     if (certname.isEmpty() && !prompt && !forcePrompt) return;
00710 
00711     // Ok, we're supposed to prompt the user....
00712     if (prompt || forcePrompt) {
00713         QStringList certs = KSSLCertificateHome::getCertificateList();
00714 
00715         QStringList::const_iterator it = certs.begin();
00716         while (it != certs.end()) {
00717             KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it);
00718             if (pkcs && (!pkcs->getCertificate() ||
00719                          !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) {
00720                 it = certs.erase(it);
00721             } else {
00722                 ++it;
00723             }
00724             delete pkcs;
00725         }
00726 
00727         if (certs.isEmpty()) return;  // we had nothing else, and prompt failed
00728 
00729         if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) {
00730             KToolInvocation::startServiceByDesktopPath("kuiserver.desktop",
00731                     QStringList());
00732         }
00733 
00734         QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer");
00735 
00736         QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong());
00737         if (retVal.type() == QDBusMessage::ReplyMessage) {
00738             if (retVal.arguments().at(0).toBool()) {
00739                 send = retVal.arguments().at(1).toBool();
00740                 save = retVal.arguments().at(2).toBool();
00741                 certname = retVal.arguments().at(3).toString();
00742             }
00743         }
00744     }
00745 
00746     // The user may have said to not send the certificate,
00747     // but to save the choice
00748     if (!send) {
00749         if (save) {
00750             KSSLCertificateHome::setDefaultCertificate(certname, d->host,
00751                     false, false);
00752         }
00753         return;
00754     }
00755 
00756     // We're almost committed.  If we can read the cert, we'll send it now.
00757     KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname);
00758     if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) {           // We need the password
00759         KIO::AuthInfo ai;
00760         bool first = true;
00761         do {
00762             ai.prompt = i18n("Enter the certificate password:");
00763             ai.caption = i18n("SSL Certificate Password");
00764             ai.url.setProtocol("kssl");
00765             ai.url.setHost(certname);
00766             ai.username = certname;
00767             ai.keepPassword = true;
00768 
00769             bool showprompt;
00770             if (first)
00771                 showprompt = !checkCachedAuthentication(ai);
00772             else
00773                 showprompt = true;
00774             if (showprompt) {
00775                 if (!openPasswordDialog(ai, first ? QString() :
00776                                         i18n("Unable to open the certificate. Try a new password?")))
00777                     break;
00778             }
00779 
00780             first = false;
00781             pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password);
00782         } while (!pkcs);
00783 
00784     }
00785 
00786     // If we could open the certificate, let's send it
00787     if (pkcs) {
00788         if (!d->kssl->setClientCertificate(pkcs)) {
00789             messageBox(Information, i18n("The procedure to set the "
00790                                          "client certificate for the session "
00791                                          "failed."), i18n("SSL"));
00792             delete pkcs;  // we don't need this anymore
00793             pkcs = 0L;
00794         } else {
00795             kDebug(7029) << "Client SSL certificate is being used.";
00796             setMetaData("ssl_using_client_cert", "TRUE");
00797             if (save) {
00798                 KSSLCertificateHome::setDefaultCertificate(certname, d->host,
00799                         true, false);
00800             }
00801         }
00802         d->pkcs = pkcs;
00803     }
00804 #endif
00805 }
00806 
00807 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate()
00808 {
00809     d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE");
00810 
00811     if (d->sslErrors.isEmpty()) {
00812         return ResultOk;
00813     } else if (d->sslNoUi) {
00814         return ResultFailed;
00815     }
00816 
00817     QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors);
00818     if (!fatalErrors.isEmpty()) {
00819         //TODO message "sorry, fatal error, you can't override it"
00820         return ResultFailed;
00821     }
00822 
00823     KSslCertificateManager *const cm = KSslCertificateManager::self();
00824     KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host);
00825 
00826     // remove previously seen and acknowledged errors
00827     QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors);
00828     if (remainingErrors.isEmpty()) {
00829         kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing.";
00830         return ResultOk | ResultOverridden;
00831     }
00832 
00833     //### We don't ask to permanently reject the certificate
00834 
00835     QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host);
00836     Q_FOREACH (const KSslError &err, d->sslErrors) {
00837         message.append(err.errorString());
00838         message.append('\n');
00839     }
00840     message = message.trimmed();
00841 
00842     int msgResult;
00843     do {
00844         msgResult = messageBox(WarningYesNoCancel, message,
00845                                i18n("Server Authentication"),
00846                                i18n("&Details"), i18n("Co&ntinue"));
00847         if (msgResult == KMessageBox::Yes) {
00848             //Details was chosen- show the certificate and error details
00849             messageBox(SSLMessageBox /*the SSL info dialog*/, d->host);
00850         } else if (msgResult == KMessageBox::Cancel) {
00851             return ResultFailed;
00852         }
00853         //fall through on KMessageBox::No
00854     } while (msgResult == KMessageBox::Yes);
00855 
00856     //Save the user's choice to ignore the SSL errors.
00857 
00858     msgResult = messageBox(WarningYesNo,
00859                             i18n("Would you like to accept this "
00860                                  "certificate forever without "
00861                                  "being prompted?"),
00862                             i18n("Server Authentication"),
00863                             i18n("&Forever"),
00864                             i18n("&Current Session only"));
00865     QDateTime ruleExpiry = QDateTime::currentDateTime();
00866     if (msgResult == KMessageBox::Yes) {
00867         //accept forever ("for a very long time")
00868         ruleExpiry = ruleExpiry.addYears(1000);
00869     } else {
00870         //accept "for a short time", half an hour.
00871         ruleExpiry = ruleExpiry.addSecs(30*60);
00872     }
00873 
00874     //TODO special cases for wildcard domain name in the certificate!
00875     //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
00876 
00877     rule.setExpiryDateTime(ruleExpiry);
00878     rule.setIgnoredErrors(d->sslErrors);
00879     cm->setRule(rule);
00880 
00881     return ResultOk | ResultOverridden;
00882 #if 0 //### need to to do something like the old code about the main and subframe stuff
00883     kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request");
00884     if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") {
00885         // Since we're the parent, we need to teach the child.
00886         setMetaData("ssl_parent_ip", d->ip);
00887         setMetaData("ssl_parent_cert", pc.toString());
00888         //  - Read from cache and see if there is a policy for this
00889         KSSLCertificateCache::KSSLCertificatePolicy cp =
00890             d->certCache->getPolicyByCertificate(pc);
00891 
00892         //  - validation code
00893         if (ksv != KSSLCertificate::Ok) {
00894             if (d->sslNoUi) {
00895                 return -1;
00896             }
00897 
00898             if (cp == KSSLCertificateCache::Unknown ||
00899                     cp == KSSLCertificateCache::Ambiguous) {
00900                 cp = KSSLCertificateCache::Prompt;
00901             } else {
00902                 // A policy was already set so let's honor that.
00903                 permacache = d->certCache->isPermanent(pc);
00904             }
00905 
00906             if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) {
00907                 cp = KSSLCertificateCache::Prompt;
00908 //            ksv = KSSLCertificate::Ok;
00909             }
00910 
00912 
00913         //  - cache the results
00914         d->certCache->addCertificate(pc, cp, permacache);
00915         if (doAddHost) d->certCache->addHost(pc, d->host);
00916     } else {    // Child frame
00917         //  - Read from cache and see if there is a policy for this
00918         KSSLCertificateCache::KSSLCertificatePolicy cp =
00919             d->certCache->getPolicyByCertificate(pc);
00920         isChild = true;
00921 
00922         // Check the cert and IP to make sure they're the same
00923         // as the parent frame
00924         bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") &&
00925                                  pc.toString() == metaData("ssl_parent_cert"));
00926 
00927         if (ksv == KSSLCertificate::Ok) {
00928             if (certAndIPTheSame) {       // success
00929                 rc = 1;
00930                 setMetaData("ssl_action", "accept");
00931             } else {
00932                 /*
00933                 if (d->sslNoUi) {
00934                   return -1;
00935                 }
00936                 result = messageBox(WarningYesNo,
00937                                     i18n("The certificate is valid but does not appear to have been assigned to this server.  Do you wish to continue loading?"),
00938                                     i18n("Server Authentication"));
00939                 if (result == KMessageBox::Yes) {     // success
00940                   rc = 1;
00941                   setMetaData("ssl_action", "accept");
00942                 } else {    // fail
00943                   rc = -1;
00944                   setMetaData("ssl_action", "reject");
00945                 }
00946                 */
00947                 setMetaData("ssl_action", "accept");
00948                 rc = 1;   // Let's accept this now.  It's bad, but at least the user
00949                 // will see potential attacks in KDE3 with the pseudo-lock
00950                 // icon on the toolbar, and can investigate with the RMB
00951             }
00952         } else {
00953             if (d->sslNoUi) {
00954                 return -1;
00955             }
00956 
00957             if (cp == KSSLCertificateCache::Accept) {
00958                 if (certAndIPTheSame) {    // success
00959                     rc = 1;
00960                     setMetaData("ssl_action", "accept");
00961                 } else {   // fail
00962                     result = messageBox(WarningYesNo,
00963                                         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?"),
00964                                         i18n("Server Authentication"));
00965                     if (result == KMessageBox::Yes) {
00966                         rc = 1;
00967                         setMetaData("ssl_action", "accept");
00968                         d->certCache->addHost(pc, d->host);
00969                     } else {
00970                         rc = -1;
00971                         setMetaData("ssl_action", "reject");
00972                     }
00973                 }
00974             } else if (cp == KSSLCertificateCache::Reject) {      // fail
00975                 messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."),
00976                            i18n("Server Authentication"));
00977                 rc = -1;
00978                 setMetaData("ssl_action", "reject");
00979             } else {
00980 
00982 
00983     return rc;
00984 #endif //#if 0
00985     return ResultOk | ResultOverridden;
00986 }
00987 
00988 
00989 bool TCPSlaveBase::isConnected() const
00990 {
00991     //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady...
00992     return d->socket.state() == KTcpSocket::ConnectedState;
00993 }
00994 
00995 
00996 bool TCPSlaveBase::waitForResponse(int t)
00997 {
00998     if (d->socket.bytesAvailable()) {
00999         return true;
01000     }
01001     return d->socket.waitForReadyRead(t * 1000);
01002 }
01003 
01004 void TCPSlaveBase::setBlocking(bool b)
01005 {
01006     if (!b) {
01007         kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work";
01008         return;
01009     }
01010     d->isBlocking = b;
01011 }
01012 
01013 void TCPSlaveBase::virtual_hook(int id, void* data)
01014 {
01015     if (id == SlaveBase::AppConnectionMade) {
01016         d->sendSslMetaData();
01017     } else {
01018         SlaveBase::virtual_hook(id, data);
01019     }
01020 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:03 by doxygen 1.7.5.1 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.9.5 API Reference

Skip menu "kdelibs-4.9.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