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

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