1 /*
2     This file is part of the KDE project
3     SPDX-FileCopyrightText: 2000 Alex Zepeda <zipzippy@sonic.net>
4     SPDX-FileCopyrightText: 2001-2003 George Staikos <staikos@kde.org>
5     SPDX-FileCopyrightText: 2001 Dawit Alemayehu <adawit@kde.org>
6     SPDX-FileCopyrightText: 2007, 2008 Andreas Hartmetz <ahartmetz@gmail.com>
7     SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu>
8     SPDX-FileCopyrightText: 2010 Richard Moore <rich@kde.org>
9 
10     SPDX-License-Identifier: LGPL-2.0-or-later
11 */
12 
13 #include "tcpslavebase.h"
14 #include "kiocoredebug.h"
15 
16 #include <KConfigGroup>
17 #include <KLocalizedString>
18 #include <ksslcertificatemanager.h>
19 #include <ksslsettings.h>
20 
21 #include <QSslCipher>
22 #include <QSslSocket>
23 
24 #include <QDBusConnection>
25 
26 using namespace KIO;
27 // using namespace KNetwork;
28 
29 namespace KIO
30 {
31 Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult)
32 }
33 
34 // TODO Proxy support whichever way works; KPAC reportedly does *not* work.
35 // NOTE kded_proxyscout may or may not be interesting
36 
37 // TODO resurrect SSL session recycling; this means save the session on disconnect and look
38 // for a reusable session on connect. Consider how HTTP persistent connections interact with that.
39 
40 // TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it
41 // in most places we ATM check for d->isSSL.
42 
43 // TODO check if d->isBlocking is honored everywhere it makes sense
44 
45 // TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere.
46 
47 // TODO recognize partially encrypted websites as "somewhat safe"
48 
49 /* List of dialogs/messageboxes we need to use (current code location in parentheses)
50    - Can the "dontAskAgainName" thing be improved?
51 
52    - "SSLCertDialog" [select client cert] (SlaveInterface)
53    - Enter password for client certificate (inline)
54    - Password for client cert was wrong. Please reenter. (inline)
55    - Setting client cert failed. [doesn't give reason] (inline)
56    - "SSLInfoDialog" [mostly server cert info] (SlaveInterface)
57    - You are about to enter secure mode. Security information/Display SSL information/Connect (inline)
58    - You are about to leave secure mode. Security information/Continue loading/Abort (inline)
59    - Hostname mismatch: Continue/Details/Cancel (inline)
60    - IP address mismatch: Continue/Details/Cancel (inline)
61    - Certificate failed authenticity check: Continue/Details/Cancel (inline)
62    - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline)
63  */
64 
65 /** @internal */
66 class Q_DECL_HIDDEN TCPSlaveBase::TcpSlaveBasePrivate
67 {
68 public:
TcpSlaveBasePrivate(TCPSlaveBase * qq)69     explicit TcpSlaveBasePrivate(TCPSlaveBase *qq)
70         : q(qq)
71     {
72     }
73 
setSslMetaData()74     void setSslMetaData()
75     {
76         sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("TRUE"));
77         QSslCipher cipher = socket.sessionCipher();
78         sslMetaData.insert(QStringLiteral("ssl_protocol_version"), cipher.protocolString());
79         sslMetaData.insert(QStringLiteral("ssl_cipher"), cipher.name());
80         sslMetaData.insert(QStringLiteral("ssl_cipher_used_bits"), QString::number(cipher.usedBits()));
81         sslMetaData.insert(QStringLiteral("ssl_cipher_bits"), QString::number(cipher.supportedBits()));
82         sslMetaData.insert(QStringLiteral("ssl_peer_ip"), ip);
83 
84         const QList<QSslCertificate> peerCertificateChain = socket.peerCertificateChain();
85         // try to fill in the blanks, i.e. missing certificates, and just assume that
86         // those belong to the peer (==website or similar) certificate.
87         for (int i = 0; i < sslErrors.count(); i++) {
88             if (sslErrors[i].certificate().isNull()) {
89                 sslErrors[i] = QSslError(sslErrors[i].error(), peerCertificateChain[0]);
90             }
91         }
92 
93         QString errorStr;
94         // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators
95         for (const QSslCertificate &cert : peerCertificateChain) {
96             for (const QSslError &error : std::as_const(sslErrors)) {
97                 if (error.certificate() == cert) {
98                     errorStr += QString::number(static_cast<int>(error.error())) + QLatin1Char('\t');
99                 }
100             }
101             if (errorStr.endsWith(QLatin1Char('\t'))) {
102                 errorStr.chop(1);
103             }
104             errorStr += QLatin1Char('\n');
105         }
106         errorStr.chop(1);
107         sslMetaData.insert(QStringLiteral("ssl_cert_errors"), errorStr);
108 
109         QString peerCertChain;
110         for (const QSslCertificate &cert : peerCertificateChain) {
111             peerCertChain += QString::fromUtf8(cert.toPem()) + QLatin1Char('\x01');
112         }
113         peerCertChain.chop(1);
114         sslMetaData.insert(QStringLiteral("ssl_peer_chain"), peerCertChain);
115         sendSslMetaData();
116     }
117 
clearSslMetaData()118     void clearSslMetaData()
119     {
120         sslMetaData.clear();
121         sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("FALSE"));
122         sendSslMetaData();
123     }
124 
sendSslMetaData()125     void sendSslMetaData()
126     {
127         MetaData::ConstIterator it = sslMetaData.constBegin();
128         for (; it != sslMetaData.constEnd(); ++it) {
129             q->setMetaData(it.key(), it.value());
130         }
131     }
132 
133     SslResult startTLSInternal(QSsl::SslProtocol sslVersion, int waitForEncryptedTimeout = -1);
134 
135     TCPSlaveBase *const q;
136 
137     bool isBlocking;
138 
139     QSslSocket socket;
140 
141     QString host;
142     QString ip;
143     quint16 port;
144     QByteArray serviceName;
145 
146     KSSLSettings sslSettings;
147     bool usingSSL;
148     bool autoSSL;
149     bool sslNoUi; // If true, we just drop the connection silently
150     // if SSL certificate check fails in some way.
151     QList<QSslError> sslErrors;
152 
153     MetaData sslMetaData;
154 };
155 
156 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 83)
socket() const157 QIODevice *TCPSlaveBase::socket() const
158 {
159     return &d->socket;
160 }
161 #endif
162 
tcpSocket() const163 QAbstractSocket *TCPSlaveBase::tcpSocket() const
164 {
165     return &d->socket;
166 }
167 
TCPSlaveBase(const QByteArray & protocol,const QByteArray & poolSocket,const QByteArray & appSocket,bool autoSSL)168 TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket, bool autoSSL)
169     : SlaveBase(protocol, poolSocket, appSocket)
170     , d(new TcpSlaveBasePrivate(this))
171 {
172     d->isBlocking = true;
173     d->port = 0;
174     d->serviceName = protocol;
175     d->usingSSL = false;
176     d->autoSSL = autoSSL;
177     d->sslNoUi = false;
178     // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit
179     // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize
180     // and the BR# 187876 to understand why setting this limit is necessary.
181     d->socket.setReadBufferSize(14680064);
182 }
183 
~TCPSlaveBase()184 TCPSlaveBase::~TCPSlaveBase()
185 {
186     delete d;
187 }
188 
write(const char * data,ssize_t len)189 ssize_t TCPSlaveBase::write(const char *data, ssize_t len)
190 {
191     ssize_t written = d->socket.write(data, len);
192     if (written == -1) {
193         /*qDebug() << "d->socket.write() returned -1! Socket error is"
194           << d->socket.error() << ", Socket state is" << d->socket.state();*/
195     }
196 
197     bool success = false;
198     if (d->isBlocking) {
199         // Drain the tx buffer
200         success = d->socket.waitForBytesWritten(-1);
201     } else {
202         // ### I don't know how to make sure that all data does get written at some point
203         // without doing it now. There is no event loop to do it behind the scenes.
204         // Polling in the dispatch() loop? Something timeout based?
205         success = d->socket.waitForBytesWritten(0);
206     }
207 
208     d->socket.flush(); // this is supposed to get the data on the wire faster
209 
210     if (d->socket.state() != QAbstractSocket::ConnectedState || !success) {
211         /*qDebug() << "Write failed, will return -1! Socket error is"
212           << d->socket.error() << ", Socket state is" << d->socket.state()
213           << "Return value of waitForBytesWritten() is" << success;*/
214         return -1;
215     }
216 
217     return written;
218 }
219 
read(char * data,ssize_t len)220 ssize_t TCPSlaveBase::read(char *data, ssize_t len)
221 {
222     if (d->usingSSL && (d->socket.mode() != QSslSocket::SslClientMode)) {
223         d->clearSslMetaData();
224         // qDebug() << "lost SSL connection.";
225         return -1;
226     }
227 
228     if (!d->socket.bytesAvailable()) {
229         const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000);
230         d->socket.waitForReadyRead(timeout);
231     }
232     return d->socket.read(data, len);
233 }
234 
readLine(char * data,ssize_t len)235 ssize_t TCPSlaveBase::readLine(char *data, ssize_t len)
236 {
237     if (d->usingSSL && (d->socket.mode() != QSslSocket::SslClientMode)) {
238         d->clearSslMetaData();
239         // qDebug() << "lost SSL connection.";
240         return -1;
241     }
242 
243     const int timeout = (d->isBlocking ? -1 : (readTimeout() * 1000));
244     ssize_t readTotal = 0;
245     do {
246         if (!d->socket.bytesAvailable()) {
247             d->socket.waitForReadyRead(timeout);
248         }
249         ssize_t readStep = d->socket.readLine(&data[readTotal], len - readTotal);
250         if (readStep == -1 || (readStep == 0 && d->socket.state() != QAbstractSocket::ConnectedState)) {
251             return -1;
252         }
253         readTotal += readStep;
254     } while (readTotal == 0 || data[readTotal - 1] != '\n');
255 
256     return readTotal;
257 }
258 
connectToHost(const QString &,const QString & host,quint16 port)259 bool TCPSlaveBase::connectToHost(const QString & /*protocol*/, const QString &host, quint16 port)
260 {
261     QString errorString;
262     const int errCode = connectToHost(host, port, &errorString);
263     if (errCode == 0) {
264         return true;
265     }
266 
267     error(errCode, errorString);
268     return false;
269 }
270 
connectToHost(const QString & host,quint16 port,QString * errorString)271 int TCPSlaveBase::connectToHost(const QString &host, quint16 port, QString *errorString)
272 {
273     d->clearSslMetaData(); // We have separate connection and SSL setup phases
274 
275     if (errorString) {
276         errorString->clear(); // clear prior error messages.
277     }
278 
279     d->socket.setPeerVerifyName(host); // Used for ssl certificate verification (SNI)
280 
281     //  - leaving SSL - warn before we even connect
282     //### see if it makes sense to move this into the HTTP ioslave which is the only
283     //    user.
284     if (metaData(QStringLiteral("main_frame_request")) == QLatin1String("TRUE") //### this looks *really* unreliable
285         && metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("TRUE")
286         && !d->autoSSL) {
287         if (d->sslSettings.warnOnLeave()) {
288             int result = messageBox(i18n("You are about to leave secure "
289                                          "mode. Transmissions will no "
290                                          "longer be encrypted.\nThis "
291                                          "means that a third party could "
292                                          "observe your data in transit."),
293                                     WarningContinueCancel,
294                                     i18n("Security Information"),
295                                     i18n("C&ontinue Loading"),
296                                     QString(),
297                                     QStringLiteral("WarnOnLeaveSSLMode"));
298 
299             if (result == SlaveBase::Cancel) {
300                 if (errorString) {
301                     *errorString = host;
302                 }
303                 return ERR_USER_CANCELED;
304             }
305         }
306     }
307 
308     const int timeout = (connectTimeout() * 1000); // 20 sec timeout value
309 
310     disconnectFromHost(); // Reset some state, even if we are already disconnected
311     d->host = host;
312 
313     d->socket.connectToHost(host, port);
314     /*const bool connectOk = */ d->socket.waitForConnected(timeout > -1 ? timeout : -1);
315 
316     /*qDebug() << "Socket: state=" << d->socket.state()
317         << ", error=" << d->socket.error()
318         << ", connected?" << connectOk;*/
319 
320     if (d->socket.state() != QAbstractSocket::ConnectedState) {
321         if (errorString) {
322             *errorString = host + QLatin1String(": ") + d->socket.errorString();
323         }
324         switch (d->socket.error()) {
325         case QAbstractSocket::UnsupportedSocketOperationError:
326             return ERR_UNSUPPORTED_ACTION;
327         case QAbstractSocket::RemoteHostClosedError:
328             return ERR_CONNECTION_BROKEN;
329         case QAbstractSocket::SocketTimeoutError:
330             return ERR_SERVER_TIMEOUT;
331         case QAbstractSocket::HostNotFoundError:
332             return ERR_UNKNOWN_HOST;
333         default:
334             return ERR_CANNOT_CONNECT;
335         }
336     }
337 
338     //### check for proxyAuthenticationRequiredError
339 
340     d->ip = d->socket.peerAddress().toString();
341     d->port = d->socket.peerPort();
342 
343     if (d->autoSSL) {
344         const SslResult res = d->startTLSInternal(QSsl::SecureProtocols, timeout);
345 
346         if (res & ResultFailed) {
347             if (errorString) {
348                 *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host);
349             }
350             return ERR_CANNOT_CONNECT;
351         }
352     }
353     return 0;
354 }
355 
disconnectFromHost()356 void TCPSlaveBase::disconnectFromHost()
357 {
358     // qDebug();
359     d->host.clear();
360     d->ip.clear();
361     d->usingSSL = false;
362 
363     if (d->socket.state() == QAbstractSocket::UnconnectedState) {
364         // discard incoming data - the remote host might have disconnected us in the meantime
365         // but the visible effect of disconnectFromHost() should stay the same.
366         d->socket.close();
367         return;
368     }
369 
370     //### maybe save a session for reuse on SSL shutdown if and when QSslSocket
371     //    does that. QCA::TLS can do it apparently but that is not enough if
372     //    we want to present that as KDE API. Not a big loss in any case.
373     d->socket.disconnectFromHost();
374     if (d->socket.state() != QAbstractSocket::UnconnectedState) {
375         d->socket.waitForDisconnected(-1); // wait for unsent data to be sent
376     }
377     d->socket.close(); // whatever that means on a socket
378 }
379 
isAutoSsl() const380 bool TCPSlaveBase::isAutoSsl() const
381 {
382     return d->autoSSL;
383 }
384 
isUsingSsl() const385 bool TCPSlaveBase::isUsingSsl() const
386 {
387     return d->usingSSL;
388 }
389 
port() const390 quint16 TCPSlaveBase::port() const
391 {
392     return d->port;
393 }
394 
atEnd() const395 bool TCPSlaveBase::atEnd() const
396 {
397     return d->socket.atEnd();
398 }
399 
startSsl()400 bool TCPSlaveBase::startSsl()
401 {
402     if (d->usingSSL) {
403         return false;
404     }
405     return d->startTLSInternal(QSsl::SecureProtocols) & ResultOk;
406 }
407 
startTLSInternal(QSsl::SslProtocol sslVersion,int waitForEncryptedTimeout)408 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal(QSsl::SslProtocol sslVersion, int waitForEncryptedTimeout)
409 {
410     // setMetaData("ssl_session_id", d->kssl->session()->toString());
411     //### we don't support session reuse for now...
412     usingSSL = true;
413 
414     // Set the SSL protocol version to use...
415     socket.setProtocol(sslVersion);
416 
417     /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors()
418        signal but that would mess up the flow of control. We will check for errors
419        anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors()
420        before connecting would be very insecure. */
421     socket.ignoreSslErrors();
422     socket.startClientEncryption();
423     const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout);
424 
425     // Set metadata, among other things for the "SSL Details" dialog
426     QSslCipher cipher = socket.sessionCipher();
427 
428     if (!encryptionStarted || socket.mode() != QSslSocket::SslClientMode || cipher.isNull() || cipher.usedBits() == 0
429         || socket.peerCertificateChain().isEmpty()) {
430         usingSSL = false;
431         clearSslMetaData();
432         /*qDebug() << "Initial SSL handshake failed. encryptionStarted is"
433           << encryptionStarted << ", cipher.isNull() is" << cipher.isNull()
434           << ", cipher.usedBits() is" << cipher.usedBits()
435           << ", length of certificate chain is" << socket.peerCertificateChain().count()
436           << ", the socket says:" << socket.errorString()
437           << "and the list of SSL errors contains"
438           << socket.sslErrors().count() << "items.";*/
439         /*for (const QSslError &sslError : socket.sslErrors()) {
440           qDebug() << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString();
441           }*/
442         return ResultFailed | ResultFailedEarly;
443     }
444 
445     /*qDebug() << "Cipher info - "
446       << " advertised SSL protocol version" << socket.protocol()
447       << " negotiated SSL protocol version" << socket.sessionProtocol()
448       << " authenticationMethod:" << cipher.authenticationMethod()
449       << " encryptionMethod:" << cipher.encryptionMethod()
450       << " keyExchangeMethod:" << cipher.keyExchangeMethod()
451       << " name:" << cipher.name()
452       << " supportedBits:" << cipher.supportedBits()
453       << " usedBits:" << cipher.usedBits();*/
454 
455     sslErrors = socket.sslHandshakeErrors();
456 
457     // TODO: review / rewrite / remove the comment
458     // The app side needs the metadata now for the SSL error dialog (if any) but
459     // the same metadata will be needed later, too. When "later" arrives the slave
460     // may actually be connected to a different application that doesn't know
461     // the metadata the slave sent to the previous application.
462     // The quite important SSL indicator icon in Konqi's URL bar relies on metadata
463     // from here, for example. And Konqi will be the second application to connect
464     // to the slave.
465     // Therefore we choose to have our metadata and send it, too :)
466     setSslMetaData();
467     q->sendAndKeepMetaData();
468 
469     SslResult rc = q->verifyServerCertificate();
470     if (rc & ResultFailed) {
471         usingSSL = false;
472         clearSslMetaData();
473         // qDebug() << "server certificate verification failed.";
474         socket.disconnectFromHost(); // Make the connection fail (cf. ignoreSslErrors())
475         return ResultFailed;
476     } else if (rc & ResultOverridden) {
477         // qDebug() << "server certificate verification failed but continuing at user's request.";
478     }
479 
480     //"warn" when starting SSL/TLS
481     if (q->metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && q->metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("FALSE")
482         && sslSettings.warnOnEnter()) {
483         int msgResult = q->messageBox(i18n("You are about to enter secure mode. "
484                                            "All transmissions will be encrypted "
485                                            "unless otherwise noted.\nThis means "
486                                            "that no third party will be able to "
487                                            "easily observe your data in transit."),
488                                       WarningYesNo,
489                                       i18n("Security Information"),
490                                       i18n("Display SSL &Information"),
491                                       i18n("C&onnect"),
492                                       QStringLiteral("WarnOnEnterSSLMode"));
493         if (msgResult == SlaveBase::Yes) {
494             q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host);
495         }
496     }
497 
498     return rc;
499 }
500 
verifyServerCertificate()501 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate()
502 {
503     d->sslNoUi = hasMetaData(QStringLiteral("ssl_no_ui")) && (metaData(QStringLiteral("ssl_no_ui")) != QLatin1String("FALSE"));
504 
505     if (d->sslErrors.isEmpty()) {
506         return ResultOk;
507     } else if (d->sslNoUi) {
508         return ResultFailed;
509     }
510 
511     const QList<QSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors);
512     if (!fatalErrors.isEmpty()) {
513         // TODO message "sorry, fatal error, you can't override it"
514         return ResultFailed;
515     }
516     QList<QSslCertificate> peerCertificationChain = d->socket.peerCertificateChain();
517     KSslCertificateManager *const cm = KSslCertificateManager::self();
518     KSslCertificateRule rule = cm->rule(peerCertificationChain.first(), d->host);
519 
520     // remove previously seen and acknowledged errors
521     const QList<QSslError> remainingErrors = rule.filterErrors(d->sslErrors);
522     if (remainingErrors.isEmpty()) {
523         // qDebug() << "Error list empty after removing errors to be ignored. Continuing.";
524         return ResultOk | ResultOverridden;
525     }
526 
527     //### We don't ask to permanently reject the certificate
528 
529     QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host);
530     for (const QSslError &err : std::as_const(d->sslErrors)) {
531         message += err.errorString() + QLatin1Char('\n');
532     }
533     message = message.trimmed();
534 
535     int msgResult;
536     QDateTime ruleExpiry = QDateTime::currentDateTime();
537     do {
538         msgResult = messageBox(WarningYesNoCancel, message, i18n("Server Authentication"), i18n("&Details"), i18n("Co&ntinue"));
539         switch (msgResult) {
540         case SlaveBase::Yes:
541             // Details was chosen- show the certificate and error details
542             messageBox(SSLMessageBox /*the SSL info dialog*/, d->host);
543             break;
544         case SlaveBase::No: {
545             // fall through on SlaveBase::No
546             const int result = messageBox(WarningYesNoCancel,
547                                           i18n("Would you like to accept this "
548                                                "certificate forever without "
549                                                "being prompted?"),
550                                           i18n("Server Authentication"),
551                                           i18n("&Forever"),
552                                           i18n("&Current Session only"));
553             if (result == SlaveBase::Yes) {
554                 // accept forever ("for a very long time")
555                 ruleExpiry = ruleExpiry.addYears(1000);
556             } else if (result == SlaveBase::No) {
557                 // accept "for a short time", half an hour.
558                 ruleExpiry = ruleExpiry.addSecs(30 * 60);
559             } else {
560                 msgResult = SlaveBase::Yes;
561             }
562             break;
563         }
564         case SlaveBase::Cancel:
565             return ResultFailed;
566         default:
567             qCWarning(KIO_CORE) << "Unexpected MessageBox response received:" << msgResult;
568             return ResultFailed;
569         }
570     } while (msgResult == SlaveBase::Yes);
571 
572     // TODO special cases for wildcard domain name in the certificate!
573     // rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
574 
575     rule.setExpiryDateTime(ruleExpiry);
576     rule.setIgnoredErrors(d->sslErrors);
577     cm->setRule(rule);
578 
579     return ResultOk | ResultOverridden;
580 }
581 
isConnected() const582 bool TCPSlaveBase::isConnected() const
583 {
584     // QSslSocket::isValid() is shady...
585     return d->socket.state() == QAbstractSocket::ConnectedState;
586 }
587 
waitForResponse(int t)588 bool TCPSlaveBase::waitForResponse(int t)
589 {
590     if (d->socket.bytesAvailable()) {
591         return true;
592     }
593     return d->socket.waitForReadyRead(t * 1000);
594 }
595 
setBlocking(bool b)596 void TCPSlaveBase::setBlocking(bool b)
597 {
598     if (!b) {
599         qCWarning(KIO_CORE) << "Caller requested non-blocking mode, but that doesn't work";
600         return;
601     }
602     d->isBlocking = b;
603 }
604 
virtual_hook(int id,void * data)605 void TCPSlaveBase::virtual_hook(int id, void *data)
606 {
607     if (id == SlaveBase::AppConnectionMade) {
608         d->sendSslMetaData();
609     } else {
610         SlaveBase::virtual_hook(id, data);
611     }
612 }
613