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