File indexing completed on 2024-06-09 05:21:30

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "IODeviceSocket.h"
0024 #include <stdexcept>
0025 #include <QNetworkProxy>
0026 #include <QNetworkProxyFactory>
0027 #include <QNetworkProxyQuery>
0028 #include <QSslConfiguration>
0029 #include <QSslSocket>
0030 #include <QTimer>
0031 #include "TrojitaZlibStatus.h"
0032 #if TROJITA_COMPRESS_DEFLATE
0033 #include "3rdparty/rfc1951.h"
0034 #endif
0035 #include "Common/InvokeMethod.h"
0036 
0037 namespace Streams {
0038 
0039 IODeviceSocket::IODeviceSocket(QIODevice *device): d(device), m_compressor(0), m_decompressor(0)
0040 {
0041     connect(d, &QIODevice::readyRead, this, &IODeviceSocket::handleReadyRead);
0042     connect(d, &QIODevice::readChannelFinished, this, &IODeviceSocket::handleStateChanged);
0043     delayedDisconnect = new QTimer();
0044     delayedDisconnect->setSingleShot(true);
0045     connect(delayedDisconnect, &QTimer::timeout, this, &IODeviceSocket::emitError);
0046     EMIT_LATER_NOARG(this, delayedStart);
0047 }
0048 
0049 IODeviceSocket::~IODeviceSocket()
0050 {
0051     d->deleteLater();
0052 #if TROJITA_COMPRESS_DEFLATE
0053     delete m_compressor;
0054     delete m_decompressor;
0055 #endif
0056 }
0057 
0058 bool IODeviceSocket::canReadLine()
0059 {
0060 #if TROJITA_COMPRESS_DEFLATE
0061     if (m_decompressor) {
0062         return m_decompressor->canReadLine();
0063     }
0064 #endif
0065     return d->canReadLine();
0066 }
0067 
0068 QByteArray IODeviceSocket::read(qint64 maxSize)
0069 {
0070 #if TROJITA_COMPRESS_DEFLATE
0071     if (m_decompressor) {
0072         return m_decompressor->read(maxSize);
0073     }
0074 #endif
0075     return d->read(maxSize);
0076 }
0077 
0078 QByteArray IODeviceSocket::readLine(qint64 maxSize)
0079 {
0080 #if TROJITA_COMPRESS_DEFLATE
0081     if (m_decompressor) {
0082         // FIXME: well, we apparently don't respect the maxSize argument...
0083         return m_decompressor->readLine();
0084     }
0085 #endif
0086     return d->readLine(maxSize);
0087 }
0088 
0089 qint64 IODeviceSocket::write(const QByteArray &byteArray)
0090 {
0091 #if TROJITA_COMPRESS_DEFLATE
0092     if (m_compressor) {
0093         m_compressor->write(d, &const_cast<QByteArray&>(byteArray));
0094         return byteArray.size();
0095     }
0096 #endif
0097     return d->write(byteArray);
0098 }
0099 
0100 void IODeviceSocket::startTls()
0101 {
0102     QSslSocket *sock = qobject_cast<QSslSocket *>(d);
0103     if (! sock)
0104         throw std::invalid_argument("This IODeviceSocket is not a QSslSocket, and therefore doesn't support STARTTLS.");
0105 #if TROJITA_COMPRESS_DEFLATE
0106     if (m_compressor || m_decompressor)
0107         throw std::invalid_argument("DEFLATE is already active, cannot STARTTLS");
0108 #endif
0109     sock->startClientEncryption();
0110 }
0111 
0112 void IODeviceSocket::startDeflate()
0113 {
0114     if (m_compressor || m_decompressor)
0115         throw std::invalid_argument("DEFLATE compression is already active");
0116 
0117 #if TROJITA_COMPRESS_DEFLATE
0118     m_compressor = new Rfc1951Compressor();
0119     m_decompressor = new Rfc1951Decompressor();
0120 #else
0121     throw std::invalid_argument("Trojita got built without zlib support");
0122 #endif
0123 }
0124 
0125 void IODeviceSocket::handleReadyRead()
0126 {
0127 #if TROJITA_COMPRESS_DEFLATE
0128     if (m_decompressor) {
0129         m_decompressor->consume(d);
0130     }
0131 #endif
0132     emit readyRead();
0133 }
0134 
0135 void IODeviceSocket::emitError()
0136 {
0137     emit disconnected(disconnectedMessage);
0138 }
0139 
0140 ProcessSocket::ProcessSocket(QProcess *proc, const QString &executable, const QStringList &args):
0141     IODeviceSocket(proc), executable(executable), args(args)
0142 {
0143     connect(proc, &QProcess::stateChanged, this, &ProcessSocket::handleStateChanged);
0144     connect(proc, &QProcess::errorOccurred, this, &ProcessSocket::handleProcessError);
0145 }
0146 
0147 ProcessSocket::~ProcessSocket()
0148 {
0149     close();
0150 }
0151 
0152 void ProcessSocket::close()
0153 {
0154     QProcess *proc = qobject_cast<QProcess *>(d);
0155     Q_ASSERT(proc);
0156     // Be nice to it, let it die peacefully before using an axe
0157     // QTBUG-5990, don't call waitForFinished() on a process which hadn't started
0158     if (proc->state() == QProcess::Running) {
0159         proc->terminate();
0160         proc->waitForFinished(200);
0161         proc->kill();
0162     }
0163 }
0164 
0165 bool ProcessSocket::isDead()
0166 {
0167     QProcess *proc = qobject_cast<QProcess *>(d);
0168     Q_ASSERT(proc);
0169     return proc->state() != QProcess::Running;
0170 }
0171 
0172 void ProcessSocket::handleProcessError(QProcess::ProcessError err)
0173 {
0174     Q_UNUSED(err);
0175     QProcess *proc = qobject_cast<QProcess *>(d);
0176     Q_ASSERT(proc);
0177     delayedDisconnect->stop();
0178     emit disconnected(tr("Disconnected: %1").arg(proc->errorString()));
0179 }
0180 
0181 void ProcessSocket::handleStateChanged()
0182 {
0183     /* Qt delivers the stateChanged() signal before the error() one.
0184     That's a problem because we really want to provide a nice error message
0185     to the user and QAbstractSocket::error() is not set yet by the time this
0186     function executes. That's why we have to delay the first disconnected() signal. */
0187 
0188     QProcess *proc = qobject_cast<QProcess *>(d);
0189     Q_ASSERT(proc);
0190     switch (proc->state()) {
0191     case QProcess::Running:
0192         emit stateChanged(Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS, tr("The process has started"));
0193         break;
0194     case QProcess::Starting:
0195         emit stateChanged(Imap::CONN_STATE_CONNECTING, tr("Starting process `%1 %2`").arg(executable, args.join(QStringLiteral(" "))));
0196         break;
0197     case QProcess::NotRunning: {
0198         if (delayedDisconnect->isActive())
0199             break;
0200         QString stdErr = QString::fromLocal8Bit(proc->readAllStandardError());
0201         if (stdErr.isEmpty())
0202             disconnectedMessage = tr("The process has exited with return code %1.").arg(
0203                                       proc->exitCode());
0204         else
0205             disconnectedMessage = tr("The process has exited with return code %1:\n\n%2").arg(
0206                                       proc->exitCode()).arg(stdErr);
0207         delayedDisconnect->start();
0208     }
0209     break;
0210     }
0211 }
0212 
0213 void ProcessSocket::delayedStart()
0214 {
0215     QProcess *proc = qobject_cast<QProcess *>(d);
0216     Q_ASSERT(proc);
0217     proc->start(executable, args);
0218 }
0219 
0220 SslTlsSocket::SslTlsSocket(QSslSocket *sock, const QString &host, const quint16 port, const bool startEncrypted):
0221     IODeviceSocket(sock), startEncrypted(startEncrypted), host(host), port(port), m_proxySettings(ProxySettings::RespectSystemProxy)
0222 {
0223     // The Qt API for deciding about whereabouts of a SSL connection is unfortunately blocking, ie. one is expected to
0224     // call a function from a slot attached to the sslErrors signal to tell the code whether to proceed or not.
0225     // In QML, one cannot display a dialog box with a nested event loop, so this means that we have to deal with SSL/TLS
0226     // establishing at higher level.
0227     sock->ignoreSslErrors();
0228     sock->setProtocol(QSsl::AnyProtocol);
0229     sock->setPeerVerifyMode(QSslSocket::QueryPeer);
0230 
0231     // In response to the attacks related to the SSL compression, Digia has decided to disable SSL compression starting in
0232     // Qt 4.8.4 -- see http://qt.digia.com/en/Release-Notes/security-issue-september-2012/.
0233     // I have brought this up on the imap-protocol mailing list; the consensus seemed to be that the likelihood of an
0234     // successful exploit on an IMAP conversation is very unlikely.  The compression itself is, on the other hand, a
0235     // very worthwhile goal, so we explicitly enable it again.
0236     // Unfortunately, this was backported to older Qt versions as well (see qt4.git's 3488f1db96dbf70bb0486d3013d86252ebf433e0),
0237     // but there is no way of enabling compression back again.
0238     QSslConfiguration sslConf = sock->sslConfiguration();
0239     sslConf.setSslOption(QSsl::SslOptionDisableCompression, false);
0240     sock->setSslConfiguration(sslConf);
0241 
0242     connect(sock, &QSslSocket::encrypted, this, &Socket::encrypted);
0243     connect(sock, &QAbstractSocket::stateChanged, this, &SslTlsSocket::handleStateChanged);
0244     connect(sock, &QAbstractSocket::errorOccurred, this, &SslTlsSocket::handleSocketError);
0245 }
0246 
0247 void SslTlsSocket::setProxySettings(const ProxySettings proxySettings, const QString &protocolTag)
0248 {
0249     m_proxySettings = proxySettings;
0250     m_protocolTag = protocolTag;
0251 }
0252 
0253 void SslTlsSocket::close()
0254 {
0255     QSslSocket *sock = qobject_cast<QSslSocket*>(d);
0256     Q_ASSERT(sock);
0257     sock->abort();
0258     emit disconnected(tr("Connection closed"));
0259 }
0260 
0261 void SslTlsSocket::handleStateChanged()
0262 {
0263     /* Qt delivers the stateChanged() signal before the error() one.
0264     That's a problem because we really want to provide a nice error message
0265     to the user and QAbstractSocket::error() is not set yet by the time this
0266     function executes. That's why we have to delay the first disconnected() signal. */
0267 
0268     QAbstractSocket *sock = qobject_cast<QAbstractSocket *>(d);
0269     Q_ASSERT(sock);
0270     QString proxyMsg;
0271     switch (sock->proxy().type()) {
0272     case QNetworkProxy::NoProxy:
0273         break;
0274     case QNetworkProxy::HttpCachingProxy:
0275         Q_ASSERT_X(false, "proxy detection",
0276                    "Qt should have returned a proxy capable of tunneling, but we got back an HTTP proxy.");
0277         break;
0278     case QNetworkProxy::FtpCachingProxy:
0279         Q_ASSERT_X(false, "proxy detection",
0280                    "Qt should have returned a proxy capable of tunneling, but we got back an FTP proxy.");
0281         break;
0282     case QNetworkProxy::DefaultProxy:
0283         proxyMsg = tr(" (via proxy %1)").arg(sock->proxy().hostName());
0284         break;
0285     case QNetworkProxy::Socks5Proxy:
0286         proxyMsg = tr(" (via SOCKS5 proxy %1)").arg(sock->proxy().hostName());
0287         break;
0288     case QNetworkProxy::HttpProxy:
0289         proxyMsg = tr(" (via HTTP proxy %1)").arg(sock->proxy().hostName());
0290         break;
0291     }
0292     switch (sock->state()) {
0293     case QAbstractSocket::HostLookupState:
0294         emit stateChanged(Imap::CONN_STATE_HOST_LOOKUP, tr("Looking up %1%2...").arg(host,
0295                               sock->proxy().capabilities().testFlag(QNetworkProxy::HostNameLookupCapability) ?
0296                               proxyMsg : QString()));
0297         break;
0298     case QAbstractSocket::ConnectingState:
0299         emit stateChanged(Imap::CONN_STATE_CONNECTING, tr("Connecting to %1:%2%3%4...").arg(
0300                               host, QString::number(port), startEncrypted ? tr(" (SSL)") : QString(),
0301                               sock->proxy().capabilities().testFlag(QNetworkProxy::TunnelingCapability) ?
0302                               proxyMsg : QString()));
0303         break;
0304     case QAbstractSocket::BoundState:
0305     case QAbstractSocket::ListeningState:
0306         break;
0307     case QAbstractSocket::ConnectedState:
0308         if (! startEncrypted) {
0309             emit stateChanged(Imap::CONN_STATE_CONNECTED_PRETLS_PRECAPS, tr("Connected"));
0310         } else {
0311             emit stateChanged(Imap::CONN_STATE_SSL_HANDSHAKE, tr("Negotiating encryption..."));
0312         }
0313         break;
0314     case QAbstractSocket::UnconnectedState:
0315     case QAbstractSocket::ClosingState:
0316         disconnectedMessage = tr("Socket is disconnected: %1").arg(sock->errorString());
0317         delayedDisconnect->start();
0318         break;
0319     }
0320 }
0321 
0322 void SslTlsSocket::handleSocketError(QAbstractSocket::SocketError err)
0323 {
0324     Q_UNUSED(err);
0325     QAbstractSocket *sock = qobject_cast<QAbstractSocket *>(d);
0326     Q_ASSERT(sock);
0327     delayedDisconnect->stop();
0328     emit disconnected(tr("The underlying socket is having troubles when processing connection to %1:%2: %3").arg(
0329                           host, QString::number(port), sock->errorString()));
0330 }
0331 
0332 bool SslTlsSocket::isDead()
0333 {
0334     QAbstractSocket *sock = qobject_cast<QAbstractSocket *>(d);
0335     Q_ASSERT(sock);
0336     return sock->state() != QAbstractSocket::ConnectedState;
0337 }
0338 
0339 void SslTlsSocket::delayedStart()
0340 {
0341     QSslSocket *sock = qobject_cast<QSslSocket *>(d);
0342     Q_ASSERT(sock);
0343 
0344     switch (m_proxySettings) {
0345     case Streams::ProxySettings::RespectSystemProxy:
0346     {
0347         QNetworkProxy setting;
0348         QNetworkProxyQuery query = QNetworkProxyQuery(host, port, m_protocolTag, QNetworkProxyQuery::TcpSocket);
0349 
0350         // set to true if a capable setting is found
0351         bool capableSettingFound = false;
0352 
0353         // set to true if at least one valid setting is found
0354         bool settingFound = false;
0355 
0356         // FIXME: this static function works particularly slow in Windows
0357         QList<QNetworkProxy> proxySettingsList = QNetworkProxyFactory::systemProxyForQuery(query);
0358 
0359         /* Proxy Settings are read from the user's environment variables by the above static method.
0360          * A peculiar case is with *nix systems, where an undefined environment variable is returned as
0361          * an empty string. Such entries *might* exist in our proxySettingsList, and shouldn't be processed.
0362          * One good check is to use hostName() of the QNetworkProxy object, and treat the Proxy Setting as invalid if
0363          * the host name is empty. */
0364         Q_FOREACH (setting, proxySettingsList) {
0365             if (!setting.hostName().isEmpty()) {
0366                 settingFound = true;
0367 
0368                 // now check whether setting has capabilities
0369                 if (setting.capabilities().testFlag(QNetworkProxy::TunnelingCapability)) {
0370                     sock->setProxy(setting);
0371                     capableSettingFound = true;
0372                     break;
0373                 }
0374             }
0375         }
0376 
0377         if (!settingFound || proxySettingsList.isEmpty()) {
0378             sock->setProxy(QNetworkProxy::NoProxy);
0379         } else if (!capableSettingFound) {
0380             emit disconnected(tr("The underlying socket is having troubles when processing connection to %1:%2: %3")
0381                               .arg(host, QString::number(port), QStringLiteral("Cannot find proxy setting capable of tunneling")));
0382         }
0383         break;
0384     }
0385     case Streams::ProxySettings::DirectConnect:
0386         sock->setProxy(QNetworkProxy::NoProxy);
0387         break;
0388     }
0389 
0390     if (startEncrypted)
0391         sock->connectToHostEncrypted(host, port);
0392     else
0393         sock->connectToHost(host, port);
0394 }
0395 
0396 QList<QSslCertificate> SslTlsSocket::sslChain() const
0397 {
0398     QSslSocket *sock = qobject_cast<QSslSocket *>(d);
0399     Q_ASSERT(sock);
0400     return sock->peerCertificateChain();
0401 }
0402 
0403 QList<QSslError> SslTlsSocket::sslErrors() const
0404 {
0405     QSslSocket *sock = qobject_cast<QSslSocket *>(d);
0406     Q_ASSERT(sock);
0407     return sock->sslHandshakeErrors();
0408 }
0409 
0410 bool SslTlsSocket::isConnectingEncryptedSinceStart() const
0411 {
0412     return startEncrypted;
0413 }
0414 
0415 }