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

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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