• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.10.1 API Reference
  • KDE Home
  • Contact Us
 

KIO

  • kio
  • kio
tcpslavebase.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net>
3  * Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
4  * Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
5  * Copyright (C) 2007,2008 Andreas Hartmetz <ahartmetz@gmail.com>
6  * Copyright (C) 2008 Roland Harnau <tau@gmx.eu>
7  * Copyright (C) 2010 Richard Moore <rich@kde.org>
8  *
9  * This file is part of the KDE project
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB. If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26 
27 #include "tcpslavebase.h"
28 
29 #include <config.h>
30 
31 #include <kdebug.h>
32 #include <kconfiggroup.h>
33 #include <ksslcertificatemanager.h>
34 #include <ksslsettings.h>
35 #include <kmessagebox.h>
36 #include <klocale.h>
37 #include <ktoolinvocation.h>
38 #include <network/ktcpsocket.h>
39 
40 #include <QtCore/QDataStream>
41 #include <QtCore/QTime>
42 #include <QtNetwork/QTcpSocket>
43 #include <QtNetwork/QHostInfo>
44 #include <QtNetwork/QSslConfiguration>
45 #include <QtDBus/QtDBus>
46 
47 
48 using namespace KIO;
49 //using namespace KNetwork;
50 
51 typedef QMap<QString, QString> StringStringMap;
52 Q_DECLARE_METATYPE(StringStringMap)
53 
54 namespace KIO {
55 Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult)
56 }
57 
58 //TODO Proxy support whichever way works; KPAC reportedly does *not* work.
59 //NOTE kded_proxyscout may or may not be interesting
60 
61 //TODO resurrect SSL session recycling; this means save the session on disconnect and look
62 //for a reusable session on connect. Consider how HTTP persistent connections interact with that.
63 
64 //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it
65 //in most places we ATM check for d->isSSL.
66 
67 //TODO check if d->isBlocking is honored everywhere it makes sense
68 
69 //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere.
70 
71 //TODO recognize partially encrypted websites as "somewhat safe"
72 
73 /* List of dialogs/messageboxes we need to use (current code location in parentheses)
74  - Can the "dontAskAgainName" thing be improved?
75 
76  - "SSLCertDialog" [select client cert] (SlaveInterface)
77  - Enter password for client certificate (inline)
78  - Password for client cert was wrong. Please reenter. (inline)
79  - Setting client cert failed. [doesn't give reason] (inline)
80  - "SSLInfoDialog" [mostly server cert info] (SlaveInterface)
81  - You are about to enter secure mode. Security information/Display SSL information/Connect (inline)
82  - You are about to leave secure mode. Security information/Continue loading/Abort (inline)
83  - Hostname mismatch: Continue/Details/Cancel (inline)
84  - IP address mismatch: Continue/Details/Cancel (inline)
85  - Certificate failed authenticity check: Continue/Details/Cancel (inline)
86  - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline)
87  */
88 
89 
91 class TCPSlaveBase::TcpSlaveBasePrivate
92 {
93 public:
94  TcpSlaveBasePrivate(TCPSlaveBase* qq) : q(qq) {}
95 
96  void setSslMetaData()
97  {
98  sslMetaData.insert("ssl_in_use", "TRUE");
99  KSslCipher cipher = socket.sessionCipher();
100  sslMetaData.insert("ssl_protocol_version", socket.negotiatedSslVersionName());
101  QString sslCipher = cipher.encryptionMethod() + '\n';
102  sslCipher += cipher.authenticationMethod() + '\n';
103  sslCipher += cipher.keyExchangeMethod() + '\n';
104  sslCipher += cipher.digestMethod();
105  sslMetaData.insert("ssl_cipher", sslCipher);
106  sslMetaData.insert("ssl_cipher_name", cipher.name());
107  sslMetaData.insert("ssl_cipher_used_bits", QString::number(cipher.usedBits()));
108  sslMetaData.insert("ssl_cipher_bits", QString::number(cipher.supportedBits()));
109  sslMetaData.insert("ssl_peer_ip", ip);
110 
111  // try to fill in the blanks, i.e. missing certificates, and just assume that
112  // those belong to the peer (==website or similar) certificate.
113  for (int i = 0; i < sslErrors.count(); i++) {
114  if (sslErrors[i].certificate().isNull()) {
115  sslErrors[i] = KSslError(sslErrors[i].error(),
116  socket.peerCertificateChain()[0]);
117  }
118  }
119 
120  QString errorStr;
121  // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators
122  Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
123  Q_FOREACH (const KSslError &error, sslErrors) {
124  if (error.certificate() == cert) {
125  errorStr += QString::number(static_cast<int>(error.error())) + '\t';
126  }
127  }
128  if (errorStr.endsWith('\t')) {
129  errorStr.chop(1);
130  }
131  errorStr += '\n';
132  }
133  errorStr.chop(1);
134  sslMetaData.insert("ssl_cert_errors", errorStr);
135 
136  QString peerCertChain;
137  Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
138  peerCertChain.append(cert.toPem());
139  peerCertChain.append('\x01');
140  }
141  peerCertChain.chop(1);
142  sslMetaData.insert("ssl_peer_chain", peerCertChain);
143  sendSslMetaData();
144  }
145 
146  void clearSslMetaData()
147  {
148  sslMetaData.clear();
149  sslMetaData.insert("ssl_in_use", "FALSE");
150  sendSslMetaData();
151  }
152 
153  void sendSslMetaData()
154  {
155  MetaData::ConstIterator it = sslMetaData.constBegin();
156  for (; it != sslMetaData.constEnd(); ++it) {
157  q->setMetaData(it.key(), it.value());
158  }
159  }
160 
161  SslResult startTLSInternal(KTcpSocket::SslVersion sslVersion,
162  const QSslConfiguration& configuration = QSslConfiguration(),
163  int waitForEncryptedTimeout = -1);
164 
165  TCPSlaveBase* q;
166 
167  bool isBlocking;
168 
169  KTcpSocket socket;
170 
171  QString host;
172  QString ip;
173  quint16 port;
174  QByteArray serviceName;
175 
176  KSSLSettings sslSettings;
177  bool usingSSL;
178  bool autoSSL;
179  bool sslNoUi; // If true, we just drop the connection silently
180  // if SSL certificate check fails in some way.
181  QList<KSslError> sslErrors;
182 
183  MetaData sslMetaData;
184 };
185 
186 
187 //### uh, is this a good idea??
188 QIODevice *TCPSlaveBase::socket() const
189 {
190  return &d->socket;
191 }
192 
193 
194 TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol,
195  const QByteArray &poolSocket,
196  const QByteArray &appSocket,
197  bool autoSSL)
198  : SlaveBase(protocol, poolSocket, appSocket),
199  d(new TcpSlaveBasePrivate(this))
200 {
201  d->isBlocking = true;
202  d->port = 0;
203  d->serviceName = protocol;
204  d->usingSSL = false;
205  d->autoSSL = autoSSL;
206  d->sslNoUi = false;
207  // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit
208  // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize
209  // and the BR# 187876 to understand why setting this limit is necessary.
210  d->socket.setReadBufferSize(14680064);
211 }
212 
213 
214 TCPSlaveBase::~TCPSlaveBase()
215 {
216  delete d;
217 }
218 
219 
220 ssize_t TCPSlaveBase::write(const char *data, ssize_t len)
221 {
222  ssize_t written = d->socket.write(data, len);
223  if (written == -1) {
224  kDebug(7027) << "d->socket.write() returned -1! Socket error is"
225  << d->socket.error() << ", Socket state is" << d->socket.state();
226  }
227 
228  bool success = false;
229  if (d->isBlocking) {
230  // Drain the tx buffer
231  success = d->socket.waitForBytesWritten(-1);
232  } else {
233  // ### I don't know how to make sure that all data does get written at some point
234  // without doing it now. There is no event loop to do it behind the scenes.
235  // Polling in the dispatch() loop? Something timeout based?
236  success = d->socket.waitForBytesWritten(0);
237  }
238 
239  d->socket.flush(); //this is supposed to get the data on the wire faster
240 
241  if (d->socket.state() != KTcpSocket::ConnectedState || !success) {
242  kDebug(7027) << "Write failed, will return -1! Socket error is"
243  << d->socket.error() << ", Socket state is" << d->socket.state()
244  << "Return value of waitForBytesWritten() is" << success;
245  return -1;
246  }
247 
248  return written;
249 }
250 
251 
252 ssize_t TCPSlaveBase::read(char* data, ssize_t len)
253 {
254  if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
255  d->clearSslMetaData();
256  kDebug(7029) << "lost SSL connection.";
257  return -1;
258  }
259 
260  if (!d->socket.bytesAvailable()) {
261  const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000);
262  d->socket.waitForReadyRead(timeout);
263  }
264 #if 0
265  // Do not do this because its only benefit is to cause a nasty side effect
266  // upstream in Qt. See BR# 260769.
267  else if (d->socket.encryptionMode() != KTcpSocket::SslClientMode ||
268  QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) {
269  // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything
270  // it seems to help performance.
271  d->socket.waitForReadyRead(0);
272  }
273 #endif
274  return d->socket.read(data, len);
275 }
276 
277 
278 ssize_t TCPSlaveBase::readLine(char *data, ssize_t len)
279 {
280  if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
281  d->clearSslMetaData();
282  kDebug(7029) << "lost SSL connection.";
283  return -1;
284  }
285 
286  const int timeout = (d->isBlocking ? -1: (readTimeout() * 1000));
287  ssize_t readTotal = 0;
288  do {
289  if (!d->socket.bytesAvailable())
290  d->socket.waitForReadyRead(timeout);
291  ssize_t readStep = d->socket.readLine(&data[readTotal], len-readTotal);
292  if (readStep == -1 || (readStep == 0 && d->socket.state() != KTcpSocket::ConnectedState)) {
293  return -1;
294  }
295  readTotal += readStep;
296  } while (readTotal == 0 || data[readTotal-1] != '\n');
297 
298  return readTotal;
299 }
300 
301 
302 bool TCPSlaveBase::connectToHost(const QString &/*protocol*/,
303  const QString &host,
304  quint16 port)
305 {
306  QString errorString;
307  const int errCode = connectToHost(host, port, &errorString);
308  if (errCode == 0)
309  return true;
310 
311  error(errCode, errorString);
312  return false;
313 }
314 
315 int TCPSlaveBase::connectToHost(const QString& host, quint16 port, QString* errorString)
316 {
317  d->clearSslMetaData(); //We have separate connection and SSL setup phases
318 
319  if (errorString) {
320  errorString->clear(); // clear prior error messages.
321  }
322 
323  d->socket.setVerificationPeerName(host); // Used for ssl certificate verification (SNI)
324 
325  // - leaving SSL - warn before we even connect
326  //### see if it makes sense to move this into the HTTP ioslave which is the only
327  // user.
328  if (metaData("main_frame_request") == "TRUE" //### this looks *really* unreliable
329  && metaData("ssl_activate_warnings") == "TRUE"
330  && metaData("ssl_was_in_use") == "TRUE"
331  && !d->autoSSL) {
332  KSSLSettings kss;
333  if (kss.warnOnLeave()) {
334  int result = messageBox(i18n("You are about to leave secure "
335  "mode. Transmissions will no "
336  "longer be encrypted.\nThis "
337  "means that a third party could "
338  "observe your data in transit."),
339  WarningContinueCancel,
340  i18n("Security Information"),
341  i18n("C&ontinue Loading"), QString(),
342  "WarnOnLeaveSSLMode");
343 
344  if (result == KMessageBox::Cancel) {
345  if (errorString)
346  *errorString = host;
347  return ERR_USER_CANCELED;
348  }
349  }
350  }
351 
352  /*
353  By default the SSL handshake attempt uses these settings in the order shown:
354 
355  1.) Protocol: KTcpSocket::SecureProtocols SSL compression: ON (DEFAULT)
356  2.) Protocol: KTcpSocket::SecureProtocols SSL compression: OFF
357  3.) Protocol: KTcpSocket::TlsV1 SSL compression: ON
358  4.) Protocol: KTcpSocket::TlsV1 SSL compression: OFF
359  5.) Protocol: KTcpSocket::SslV3 SSL compression: ON
360  6.) Protocol: KTcpSocket::SslV3 SSL compression: OFF
361 
362  If any combination other than the one marked DEFAULT is used to complete
363  the SSL handshake, then that combination will be cached using KIO's internal
364  meta-data mechanism in order to speed up future connections to the same host.
365  */
366 
367  QSslConfiguration sslConfig = d->socket.sslConfiguration();
368 #if QT_VERSION >= 0x040800
369  const bool isSslCompressionDisabled = sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
370  const bool shouldSslCompressBeDisabled = config()->readEntry("LastUsedSslDisableCompressionFlag", isSslCompressionDisabled);
371  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, shouldSslCompressBeDisabled);
372 #endif
373 
374  const int lastSslVerson = config()->readEntry("LastUsedSslVersion", static_cast<int>(KTcpSocket::SecureProtocols));
375  KTcpSocket::SslVersion trySslVersion = static_cast<KTcpSocket::SslVersion>(lastSslVerson);
376  KTcpSocket::SslVersions alreadyTriedSslVersions = trySslVersion;
377 
378  const int timeout = (connectTimeout() * 1000);
379  while (true) {
380  disconnectFromHost(); //Reset some state, even if we are already disconnected
381  d->host = host;
382 
383  d->socket.connectToHost(host, port);
384  const bool connectOk = d->socket.waitForConnected(timeout > -1 ? timeout : -1);
385 
386  kDebug(7027) << "Socket: state=" << d->socket.state()
387  << ", error=" << d->socket.error()
388  << ", connected?" << connectOk;
389 
390  if (d->socket.state() != KTcpSocket::ConnectedState) {
391  if (errorString)
392  *errorString = host + QLatin1String(": ") + d->socket.errorString();
393  switch (d->socket.error()) {
394  case KTcpSocket::UnsupportedSocketOperationError:
395  return ERR_UNSUPPORTED_ACTION;
396  case KTcpSocket::RemoteHostClosedError:
397  return ERR_CONNECTION_BROKEN;
398  case KTcpSocket::SocketTimeoutError:
399  return ERR_SERVER_TIMEOUT;
400  case KTcpSocket::HostNotFoundError:
401  return ERR_UNKNOWN_HOST;
402  default:
403  return ERR_COULD_NOT_CONNECT;
404  }
405  }
406 
407  //### check for proxyAuthenticationRequiredError
408 
409  d->ip = d->socket.peerAddress().toString();
410  d->port = d->socket.peerPort();
411 
412  if (d->autoSSL) {
413  SslResult res = d->startTLSInternal(trySslVersion, sslConfig, 30000 /*30 secs timeout*/);
414  if ((res & ResultFailed) && (res & ResultFailedEarly)) {
415 #if QT_VERSION >= 0x040800
416  if (!sslConfig.testSslOption(QSsl::SslOptionDisableCompression)) {
417  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, true);
418  continue;
419  }
420 #endif
421 
422  if (!(alreadyTriedSslVersions & KTcpSocket::SecureProtocols)) {
423  trySslVersion = KTcpSocket::SecureProtocols;
424  alreadyTriedSslVersions |= trySslVersion;
425 #if QT_VERSION >= 0x040800
426  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
427 #endif
428  continue;
429  }
430 
431  if (!(alreadyTriedSslVersions & KTcpSocket::TlsV1)) {
432  trySslVersion = KTcpSocket::TlsV1;
433  alreadyTriedSslVersions |= trySslVersion;
434 #if QT_VERSION >= 0x040800
435  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
436 #endif
437  continue;
438  }
439 
440  if (!(alreadyTriedSslVersions & KTcpSocket::SslV3)) {
441  trySslVersion = KTcpSocket::SslV3;
442  alreadyTriedSslVersions |= trySslVersion;
443 #if QT_VERSION >= 0x040800
444  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
445 #endif
446  continue;
447  }
448  }
449 
450  //### SSL 2.0 is (close to) dead and it's a good thing, too.
451  if (res & ResultFailed) {
452  if (errorString)
453  *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host);
454  return ERR_COULD_NOT_CONNECT;
455  }
456  }
457  // If the SSL handshake was done with anything protocol other than the default,
458  // save that information so that any subsequent requests do not have to do thesame thing.
459  if (trySslVersion != KTcpSocket::SecureProtocols && lastSslVerson == KTcpSocket::SecureProtocols) {
460  setMetaData(QLatin1String("{internal~currenthost}LastUsedSslVersion"),
461  QString::number(trySslVersion));
462  }
463 #if QT_VERSION >= 0x040800
464  if (sslConfig.testSslOption(QSsl::SslOptionDisableCompression) && !shouldSslCompressBeDisabled) {
465  setMetaData(QLatin1String("{internal~currenthost}LastUsedSslDisableCompressionFlag"),
466  QString::number(true));
467  }
468 #endif
469  return 0;
470  }
471  Q_ASSERT(false);
472  // Code flow never gets here but let's make the compiler happy.
473  // More: the stack allocation of QSslSettings seems to be confusing the compiler;
474  // in fact, any non-POD allocation does.
475  // even a 'return 0;' directly after the allocation (so before the while(true))
476  // is ignored. definitely seems to be a compiler bug? - aseigo
477  return 0;
478 }
479 
480 void TCPSlaveBase::disconnectFromHost()
481 {
482  kDebug(7027);
483  d->host.clear();
484  d->ip.clear();
485  d->usingSSL = false;
486 
487  if (d->socket.state() == KTcpSocket::UnconnectedState) {
488  // discard incoming data - the remote host might have disconnected us in the meantime
489  // but the visible effect of disconnectFromHost() should stay the same.
490  d->socket.close();
491  return;
492  }
493 
494  //### maybe save a session for reuse on SSL shutdown if and when QSslSocket
495  // does that. QCA::TLS can do it apparently but that is not enough if
496  // we want to present that as KDE API. Not a big loss in any case.
497  d->socket.disconnectFromHost();
498  if (d->socket.state() != KTcpSocket::UnconnectedState)
499  d->socket.waitForDisconnected(-1); // wait for unsent data to be sent
500  d->socket.close(); //whatever that means on a socket
501 }
502 
503 bool TCPSlaveBase::isAutoSsl() const
504 {
505  return d->autoSSL;
506 }
507 
508 bool TCPSlaveBase::isUsingSsl() const
509 {
510  return d->usingSSL;
511 }
512 
513 quint16 TCPSlaveBase::port() const
514 {
515  return d->port;
516 }
517 
518 bool TCPSlaveBase::atEnd() const
519 {
520  return d->socket.atEnd();
521 }
522 
523 bool TCPSlaveBase::startSsl()
524 {
525  if (d->usingSSL)
526  return false;
527  return d->startTLSInternal(KTcpSocket::TlsV1) & ResultOk;
528 }
529 
530 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version,
531  const QSslConfiguration& sslConfig,
532  int waitForEncryptedTimeout)
533 {
534  q->selectClientCertificate();
535 
536  //setMetaData("ssl_session_id", d->kssl->session()->toString());
537  //### we don't support session reuse for now...
538  usingSSL = true;
539 #if QT_VERSION >= 0x040800
540  kDebug(7027) << "Trying SSL handshake with protocol:" << version
541  << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
542 #endif
543  // Set the SSL version to use...
544  socket.setAdvertisedSslVersion(version);
545 
546  // Set SSL configuration information
547  if (!sslConfig.isNull())
548  socket.setSslConfiguration(sslConfig);
549 
550  /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors()
551  signal but that would mess up the flow of control. We will check for errors
552  anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors()
553  before connecting would be very insecure. */
554  socket.ignoreSslErrors();
555  socket.startClientEncryption();
556  const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout);
557 
558  //Set metadata, among other things for the "SSL Details" dialog
559  KSslCipher cipher = socket.sessionCipher();
560 
561  if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode
562  || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) {
563  usingSSL = false;
564  clearSslMetaData();
565  kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is"
566  << encryptionStarted << ", cipher.isNull() is" << cipher.isNull()
567  << ", cipher.usedBits() is" << cipher.usedBits()
568  << ", length of certificate chain is" << socket.peerCertificateChain().count()
569  << ", the socket says:" << socket.errorString()
570  << "and the list of SSL errors contains"
571  << socket.sslErrors().count() << "items.";
572  Q_FOREACH(const KSslError& sslError, socket.sslErrors()) {
573  kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString();
574  }
575  return ResultFailed | ResultFailedEarly;
576  }
577 
578  kDebug(7029) << "Cipher info - "
579  << " advertised SSL protocol version" << socket.advertisedSslVersion()
580  << " negotiated SSL protocol version" << socket.negotiatedSslVersion()
581  << " authenticationMethod:" << cipher.authenticationMethod()
582  << " encryptionMethod:" << cipher.encryptionMethod()
583  << " keyExchangeMethod:" << cipher.keyExchangeMethod()
584  << " name:" << cipher.name()
585  << " supportedBits:" << cipher.supportedBits()
586  << " usedBits:" << cipher.usedBits();
587 
588  sslErrors = socket.sslErrors();
589 
590  // TODO: review / rewrite / remove the comment
591  // The app side needs the metadata now for the SSL error dialog (if any) but
592  // the same metadata will be needed later, too. When "later" arrives the slave
593  // may actually be connected to a different application that doesn't know
594  // the metadata the slave sent to the previous application.
595  // The quite important SSL indicator icon in Konqi's URL bar relies on metadata
596  // from here, for example. And Konqi will be the second application to connect
597  // to the slave.
598  // Therefore we choose to have our metadata and send it, too :)
599  setSslMetaData();
600  q->sendAndKeepMetaData();
601 
602  SslResult rc = q->verifyServerCertificate();
603  if (rc & ResultFailed) {
604  usingSSL = false;
605  clearSslMetaData();
606  kDebug(7029) << "server certificate verification failed.";
607  socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors())
608  return ResultFailed;
609  } else if (rc & ResultOverridden) {
610  kDebug(7029) << "server certificate verification failed but continuing at user's request.";
611  }
612 
613  //"warn" when starting SSL/TLS
614  if (q->metaData("ssl_activate_warnings") == "TRUE"
615  && q->metaData("ssl_was_in_use") == "FALSE"
616  && sslSettings.warnOnEnter()) {
617 
618  int msgResult = q->messageBox(i18n("You are about to enter secure mode. "
619  "All transmissions will be encrypted "
620  "unless otherwise noted.\nThis means "
621  "that no third party will be able to "
622  "easily observe your data in transit."),
623  WarningYesNo,
624  i18n("Security Information"),
625  i18n("Display SSL &Information"),
626  i18n("C&onnect"),
627  "WarnOnEnterSSLMode");
628  if (msgResult == KMessageBox::Yes) {
629  q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host);
630  }
631  }
632 
633  return rc;
634 }
635 
636 void TCPSlaveBase::selectClientCertificate()
637 {
638 #if 0 //hehe
639  QString certname; // the cert to use this session
640  bool send = false, prompt = false, save = false, forcePrompt = false;
641  KSSLCertificateHome::KSSLAuthAction aa;
642 
643  setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed
644 
645  if (metaData("ssl_no_client_cert") == "TRUE") return;
646  forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE");
647 
648  // Delete the old cert since we're certainly done with it now
649  if (d->pkcs) {
650  delete d->pkcs;
651  d->pkcs = NULL;
652  }
653 
654  if (!d->kssl) return;
655 
656  // Look for a general certificate
657  if (!forcePrompt) {
658  certname = KSSLCertificateHome::getDefaultCertificateName(&aa);
659  switch (aa) {
660  case KSSLCertificateHome::AuthSend:
661  send = true; prompt = false;
662  break;
663  case KSSLCertificateHome::AuthDont:
664  send = false; prompt = false;
665  certname.clear();
666  break;
667  case KSSLCertificateHome::AuthPrompt:
668  send = false; prompt = true;
669  break;
670  default:
671  break;
672  }
673  }
674 
675  // Look for a certificate on a per-host basis as an override
676  QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa);
677  if (aa != KSSLCertificateHome::AuthNone) { // we must override
678  switch (aa) {
679  case KSSLCertificateHome::AuthSend:
680  send = true;
681  prompt = false;
682  certname = tmpcn;
683  break;
684  case KSSLCertificateHome::AuthDont:
685  send = false;
686  prompt = false;
687  certname.clear();
688  break;
689  case KSSLCertificateHome::AuthPrompt:
690  send = false;
691  prompt = true;
692  certname = tmpcn;
693  break;
694  default:
695  break;
696  }
697  }
698 
699  // Finally, we allow the application to override anything.
700  if (hasMetaData("ssl_demand_certificate")) {
701  certname = metaData("ssl_demand_certificate");
702  if (!certname.isEmpty()) {
703  forcePrompt = false;
704  prompt = false;
705  send = true;
706  }
707  }
708 
709  if (certname.isEmpty() && !prompt && !forcePrompt) return;
710 
711  // Ok, we're supposed to prompt the user....
712  if (prompt || forcePrompt) {
713  QStringList certs = KSSLCertificateHome::getCertificateList();
714 
715  QStringList::const_iterator it = certs.begin();
716  while (it != certs.end()) {
717  KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it);
718  if (pkcs && (!pkcs->getCertificate() ||
719  !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) {
720  it = certs.erase(it);
721  } else {
722  ++it;
723  }
724  delete pkcs;
725  }
726 
727  if (certs.isEmpty()) return; // we had nothing else, and prompt failed
728 
729  if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) {
730  KToolInvocation::startServiceByDesktopPath("kuiserver.desktop",
731  QStringList());
732  }
733 
734  QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer");
735 
736  QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong());
737  if (retVal.type() == QDBusMessage::ReplyMessage) {
738  if (retVal.arguments().at(0).toBool()) {
739  send = retVal.arguments().at(1).toBool();
740  save = retVal.arguments().at(2).toBool();
741  certname = retVal.arguments().at(3).toString();
742  }
743  }
744  }
745 
746  // The user may have said to not send the certificate,
747  // but to save the choice
748  if (!send) {
749  if (save) {
750  KSSLCertificateHome::setDefaultCertificate(certname, d->host,
751  false, false);
752  }
753  return;
754  }
755 
756  // We're almost committed. If we can read the cert, we'll send it now.
757  KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname);
758  if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password
759  KIO::AuthInfo ai;
760  bool first = true;
761  do {
762  ai.prompt = i18n("Enter the certificate password:");
763  ai.caption = i18n("SSL Certificate Password");
764  ai.url.setProtocol("kssl");
765  ai.url.setHost(certname);
766  ai.username = certname;
767  ai.keepPassword = true;
768 
769  bool showprompt;
770  if (first)
771  showprompt = !checkCachedAuthentication(ai);
772  else
773  showprompt = true;
774  if (showprompt) {
775  if (!openPasswordDialog(ai, first ? QString() :
776  i18n("Unable to open the certificate. Try a new password?")))
777  break;
778  }
779 
780  first = false;
781  pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password);
782  } while (!pkcs);
783 
784  }
785 
786  // If we could open the certificate, let's send it
787  if (pkcs) {
788  if (!d->kssl->setClientCertificate(pkcs)) {
789  messageBox(Information, i18n("The procedure to set the "
790  "client certificate for the session "
791  "failed."), i18n("SSL"));
792  delete pkcs; // we don't need this anymore
793  pkcs = 0L;
794  } else {
795  kDebug(7029) << "Client SSL certificate is being used.";
796  setMetaData("ssl_using_client_cert", "TRUE");
797  if (save) {
798  KSSLCertificateHome::setDefaultCertificate(certname, d->host,
799  true, false);
800  }
801  }
802  d->pkcs = pkcs;
803  }
804 #endif
805 }
806 
807 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate()
808 {
809  d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE");
810 
811  if (d->sslErrors.isEmpty()) {
812  return ResultOk;
813  } else if (d->sslNoUi) {
814  return ResultFailed;
815  }
816 
817  QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors);
818  if (!fatalErrors.isEmpty()) {
819  //TODO message "sorry, fatal error, you can't override it"
820  return ResultFailed;
821  }
822 
823  KSslCertificateManager *const cm = KSslCertificateManager::self();
824  KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host);
825 
826  // remove previously seen and acknowledged errors
827  QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors);
828  if (remainingErrors.isEmpty()) {
829  kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing.";
830  return ResultOk | ResultOverridden;
831  }
832 
833  //### We don't ask to permanently reject the certificate
834 
835  QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host);
836  Q_FOREACH (const KSslError &err, d->sslErrors) {
837  message.append(err.errorString());
838  message.append('\n');
839  }
840  message = message.trimmed();
841 
842  int msgResult;
843  do {
844  msgResult = messageBox(WarningYesNoCancel, message,
845  i18n("Server Authentication"),
846  i18n("&Details"), i18n("Co&ntinue"));
847  if (msgResult == KMessageBox::Yes) {
848  //Details was chosen- show the certificate and error details
849  messageBox(SSLMessageBox /*the SSL info dialog*/, d->host);
850  } else if (msgResult == KMessageBox::Cancel) {
851  return ResultFailed;
852  }
853  //fall through on KMessageBox::No
854  } while (msgResult == KMessageBox::Yes);
855 
856  //Save the user's choice to ignore the SSL errors.
857 
858  msgResult = messageBox(WarningYesNo,
859  i18n("Would you like to accept this "
860  "certificate forever without "
861  "being prompted?"),
862  i18n("Server Authentication"),
863  i18n("&Forever"),
864  i18n("&Current Session only"));
865  QDateTime ruleExpiry = QDateTime::currentDateTime();
866  if (msgResult == KMessageBox::Yes) {
867  //accept forever ("for a very long time")
868  ruleExpiry = ruleExpiry.addYears(1000);
869  } else {
870  //accept "for a short time", half an hour.
871  ruleExpiry = ruleExpiry.addSecs(30*60);
872  }
873 
874  //TODO special cases for wildcard domain name in the certificate!
875  //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
876 
877  rule.setExpiryDateTime(ruleExpiry);
878  rule.setIgnoredErrors(d->sslErrors);
879  cm->setRule(rule);
880 
881  return ResultOk | ResultOverridden;
882 #if 0 //### need to to do something like the old code about the main and subframe stuff
883  kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request");
884  if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") {
885  // Since we're the parent, we need to teach the child.
886  setMetaData("ssl_parent_ip", d->ip);
887  setMetaData("ssl_parent_cert", pc.toString());
888  // - Read from cache and see if there is a policy for this
889  KSSLCertificateCache::KSSLCertificatePolicy cp =
890  d->certCache->getPolicyByCertificate(pc);
891 
892  // - validation code
893  if (ksv != KSSLCertificate::Ok) {
894  if (d->sslNoUi) {
895  return -1;
896  }
897 
898  if (cp == KSSLCertificateCache::Unknown ||
899  cp == KSSLCertificateCache::Ambiguous) {
900  cp = KSSLCertificateCache::Prompt;
901  } else {
902  // A policy was already set so let's honor that.
903  permacache = d->certCache->isPermanent(pc);
904  }
905 
906  if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) {
907  cp = KSSLCertificateCache::Prompt;
908 // ksv = KSSLCertificate::Ok;
909  }
910 
912 
913  // - cache the results
914  d->certCache->addCertificate(pc, cp, permacache);
915  if (doAddHost) d->certCache->addHost(pc, d->host);
916  } else { // Child frame
917  // - Read from cache and see if there is a policy for this
918  KSSLCertificateCache::KSSLCertificatePolicy cp =
919  d->certCache->getPolicyByCertificate(pc);
920  isChild = true;
921 
922  // Check the cert and IP to make sure they're the same
923  // as the parent frame
924  bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") &&
925  pc.toString() == metaData("ssl_parent_cert"));
926 
927  if (ksv == KSSLCertificate::Ok) {
928  if (certAndIPTheSame) { // success
929  rc = 1;
930  setMetaData("ssl_action", "accept");
931  } else {
932  /*
933  if (d->sslNoUi) {
934  return -1;
935  }
936  result = messageBox(WarningYesNo,
937  i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"),
938  i18n("Server Authentication"));
939  if (result == KMessageBox::Yes) { // success
940  rc = 1;
941  setMetaData("ssl_action", "accept");
942  } else { // fail
943  rc = -1;
944  setMetaData("ssl_action", "reject");
945  }
946  */
947  setMetaData("ssl_action", "accept");
948  rc = 1; // Let's accept this now. It's bad, but at least the user
949  // will see potential attacks in KDE3 with the pseudo-lock
950  // icon on the toolbar, and can investigate with the RMB
951  }
952  } else {
953  if (d->sslNoUi) {
954  return -1;
955  }
956 
957  if (cp == KSSLCertificateCache::Accept) {
958  if (certAndIPTheSame) { // success
959  rc = 1;
960  setMetaData("ssl_action", "accept");
961  } else { // fail
962  result = messageBox(WarningYesNo,
963  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?"),
964  i18n("Server Authentication"));
965  if (result == KMessageBox::Yes) {
966  rc = 1;
967  setMetaData("ssl_action", "accept");
968  d->certCache->addHost(pc, d->host);
969  } else {
970  rc = -1;
971  setMetaData("ssl_action", "reject");
972  }
973  }
974  } else if (cp == KSSLCertificateCache::Reject) { // fail
975  messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."),
976  i18n("Server Authentication"));
977  rc = -1;
978  setMetaData("ssl_action", "reject");
979  } else {
980 
982 
983  return rc;
984 #endif //#if 0
985  return ResultOk | ResultOverridden;
986 }
987 
988 
989 bool TCPSlaveBase::isConnected() const
990 {
991  //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady...
992  return d->socket.state() == KTcpSocket::ConnectedState;
993 }
994 
995 
996 bool TCPSlaveBase::waitForResponse(int t)
997 {
998  if (d->socket.bytesAvailable()) {
999  return true;
1000  }
1001  return d->socket.waitForReadyRead(t * 1000);
1002 }
1003 
1004 void TCPSlaveBase::setBlocking(bool b)
1005 {
1006  if (!b) {
1007  kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work";
1008  return;
1009  }
1010  d->isBlocking = b;
1011 }
1012 
1013 void TCPSlaveBase::virtual_hook(int id, void* data)
1014 {
1015  if (id == SlaveBase::AppConnectionMade) {
1016  d->sendSslMetaData();
1017  } else {
1018  SlaveBase::virtual_hook(id, data);
1019  }
1020 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Wed Mar 20 2013 07:19:37 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

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

kdelibs-4.10.1 API Reference

Skip menu "kdelibs-4.10.1 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal