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