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

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

Skip menu "kdelibs"
  • 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
Generated for kdelibs by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal