File indexing completed on 2024-05-12 05:21:35

0001 /*
0002   SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
0003   SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
0004   SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "session.h"
0010 #include "job.h"
0011 #include "ksmtp_debug.h"
0012 #include "loginjob.h"
0013 #include "sendjob.h"
0014 #include "serverresponse_p.h"
0015 #include "session_p.h"
0016 #include "sessionthread_p.h"
0017 
0018 #include <QHostAddress>
0019 #include <QHostInfo>
0020 #include <QPointer>
0021 #include <QUrl>
0022 
0023 using namespace KSmtp;
0024 
0025 Q_DECLARE_METATYPE(QSsl::SslProtocol)
0026 Q_DECLARE_METATYPE(KSslErrorUiData)
0027 
0028 SessionPrivate::SessionPrivate(Session *session)
0029     : QObject(session)
0030     , q(session)
0031 {
0032     qRegisterMetaType<QSsl::SslProtocol>();
0033     qRegisterMetaType<KSslErrorUiData>();
0034 }
0035 
0036 SessionPrivate::~SessionPrivate()
0037 {
0038     m_thread->quit();
0039     m_thread->wait(10000);
0040     delete m_thread;
0041 }
0042 
0043 void SessionPrivate::handleSslError(const KSslErrorUiData &data)
0044 {
0045     QPointer<SessionThread> _t = m_thread;
0046     const bool ignore = m_uiProxy && m_uiProxy->ignoreSslError(data);
0047     if (_t) {
0048         _t->handleSslErrorResponse(ignore);
0049     }
0050 }
0051 
0052 void SessionPrivate::setAuthenticationMethods(const QList<QByteArray> &authMethods)
0053 {
0054     for (const QByteArray &method : authMethods) {
0055         QString m = QString::fromLatin1(method);
0056         if (!m_authModes.contains(m)) {
0057             m_authModes.append(m);
0058         }
0059     }
0060 }
0061 
0062 void SessionPrivate::startHandshake()
0063 {
0064     QString hostname = m_customHostname;
0065 
0066     if (hostname.isEmpty()) {
0067         // FIXME: QHostInfo::fromName can get a FQDN, but does a DNS lookup
0068         hostname = QHostInfo::localHostName();
0069         if (hostname.isEmpty()) {
0070             hostname = QStringLiteral("localhost.invalid");
0071         } else if (!hostname.contains(QLatin1Char('.'))) {
0072             hostname += QStringLiteral(".localnet");
0073         }
0074     }
0075 
0076     QByteArray cmd;
0077     if (!m_ehloRejected) {
0078         cmd = "EHLO ";
0079     } else {
0080         cmd = "HELO ";
0081     }
0082     setState(Session::Handshake);
0083     sendData(cmd + QUrl::toAce(hostname));
0084 }
0085 
0086 Session::Session(const QString &hostName, quint16 port, QObject *parent)
0087     : QObject(parent)
0088     , d(new SessionPrivate(this))
0089 {
0090     qRegisterMetaType<KSmtp::ServerResponse>("KSmtp::ServerResponse");
0091 
0092     QHostAddress ip;
0093     QString saneHostName = hostName;
0094     if (ip.setAddress(hostName)) {
0095         // saneHostName = QStringLiteral("[%1]").arg(hostName);
0096     }
0097 
0098     d->m_thread = new SessionThread(saneHostName, port, this);
0099     d->m_thread->start();
0100 
0101     connect(d->m_thread, &SessionThread::sslError, d, &SessionPrivate::handleSslError);
0102 }
0103 
0104 Session::~Session() = default;
0105 
0106 void Session::setUseNetworkProxy(bool useProxy)
0107 {
0108     d->m_thread->setUseNetworkProxy(useProxy);
0109 }
0110 
0111 void Session::setUiProxy(const SessionUiProxy::Ptr &uiProxy)
0112 {
0113     d->m_uiProxy = uiProxy;
0114 }
0115 
0116 SessionUiProxy::Ptr Session::uiProxy() const
0117 {
0118     return d->m_uiProxy;
0119 }
0120 
0121 QString Session::hostName() const
0122 {
0123     return d->m_thread->hostName();
0124 }
0125 
0126 quint16 Session::port() const
0127 {
0128     return d->m_thread->port();
0129 }
0130 
0131 Session::State Session::state() const
0132 {
0133     return d->m_state;
0134 }
0135 
0136 Session::EncryptionMode Session::encryptionMode() const
0137 {
0138     return d->m_encryptionMode;
0139 }
0140 
0141 void Session::setEncryptionMode(Session::EncryptionMode mode)
0142 {
0143     d->m_encryptionMode = mode;
0144 }
0145 
0146 bool Session::allowsTls() const
0147 {
0148     return d->m_allowsTls;
0149 }
0150 
0151 bool Session::allowsDsn() const
0152 {
0153     return d->m_allowsDsn;
0154 }
0155 
0156 QStringList Session::availableAuthModes() const
0157 {
0158     return d->m_authModes;
0159 }
0160 
0161 int Session::sizeLimit() const
0162 {
0163     return d->m_size;
0164 }
0165 
0166 void Session::setSocketTimeout(int ms)
0167 {
0168     bool timerActive = d->m_socketTimer.isActive();
0169 
0170     if (timerActive) {
0171         d->stopSocketTimer();
0172     }
0173 
0174     d->m_socketTimerInterval = ms;
0175 
0176     if (timerActive) {
0177         d->startSocketTimer();
0178     }
0179 }
0180 
0181 int Session::socketTimeout() const
0182 {
0183     return d->m_socketTimerInterval;
0184 }
0185 
0186 void Session::setCustomHostname(const QString &hostname)
0187 {
0188     d->m_customHostname = hostname;
0189 }
0190 
0191 QString Session::customHostname() const
0192 {
0193     return d->m_customHostname;
0194 }
0195 
0196 void Session::open()
0197 {
0198     d->m_sslVersion = QSsl::UnknownProtocol;
0199     d->m_thread->setConnectWithTls(d->m_encryptionMode == Session::TLS);
0200     QTimer::singleShot(0, d->m_thread, &SessionThread::reconnect);
0201     d->startSocketTimer();
0202 }
0203 
0204 void Session::quit()
0205 {
0206     if (d->m_state == Session::Disconnected) {
0207         return;
0208     }
0209 
0210     d->setState(Quitting);
0211     d->sendData("QUIT");
0212 }
0213 
0214 void SessionPrivate::setState(Session::State s)
0215 {
0216     if (m_state == s) {
0217         return;
0218     }
0219 
0220     m_state = s;
0221     Q_EMIT q->stateChanged(m_state);
0222 }
0223 
0224 void SessionPrivate::sendData(const QByteArray &data)
0225 {
0226     QMetaObject::invokeMethod(
0227         m_thread,
0228         [this, data] {
0229             m_thread->sendData(data);
0230         },
0231         Qt::QueuedConnection);
0232 }
0233 
0234 void SessionPrivate::responseReceived(const ServerResponse &r)
0235 {
0236     qCDebug(KSMTP_LOG) << "S:: [" << r.code() << "]" << (r.isMultiline() ? "-" : " ") << r.text();
0237 
0238     if (m_state == Session::Quitting) {
0239         m_thread->closeSocket();
0240         return;
0241     }
0242 
0243     if (m_state == Session::Handshake) {
0244         if (r.isCode(500) || r.isCode(502)) {
0245             if (!m_ehloRejected) {
0246                 setState(Session::Ready);
0247                 m_ehloRejected = true;
0248             } else {
0249                 qCWarning(KSMTP_LOG) << "KSmtp::Session: Handshake failed with both EHLO and HELO";
0250                 q->quit();
0251                 return;
0252             }
0253         } else if (r.isCode(25)) {
0254             if (r.text().startsWith("SIZE ")) { // krazy:exclude=strings
0255                 m_size = r.text().remove(0, QByteArray("SIZE ").size()).toInt();
0256             } else if (r.text() == "STARTTLS") {
0257                 m_allowsTls = true;
0258             } else if (r.text().startsWith("AUTH ")) { // krazy:exclude=strings
0259                 setAuthenticationMethods(r.text().remove(0, QByteArray("AUTH ").size()).split(' '));
0260             } else if (r.text() == "DSN") {
0261                 m_allowsDsn = true;
0262             }
0263 
0264             if (!r.isMultiline()) {
0265                 if (m_encryptionMode == Session::STARTTLS && m_sslVersion == QSsl::UnknownProtocol) {
0266                     if (m_allowsTls) {
0267                         m_starttlsSent = true;
0268                         sendData(QByteArrayLiteral("STARTTLS"));
0269                     } else {
0270                         qCWarning(KSMTP_LOG) << "STARTTLS not supported by the server!";
0271                         q->quit();
0272                     }
0273                 } else {
0274                     setState(Session::NotAuthenticated);
0275                     startNext();
0276                 }
0277             }
0278         } else if (r.isCode(220) && m_starttlsSent) { // STARTTLS accepted
0279             m_starttlsSent = false;
0280             startSsl();
0281         }
0282     }
0283 
0284     if (m_state == Session::Ready) {
0285         if (r.isCode(22) || m_ehloRejected) {
0286             startHandshake();
0287             return;
0288         }
0289     }
0290 
0291     if (m_currentJob) {
0292         m_currentJob->handleResponse(r);
0293     }
0294 }
0295 
0296 void SessionPrivate::socketConnected()
0297 {
0298     stopSocketTimer();
0299     m_sslVersion = QSsl::UnknownProtocol;
0300     setState(Session::Ready);
0301 }
0302 
0303 void SessionPrivate::socketDisconnected()
0304 {
0305     qCDebug(KSMTP_LOG) << "Socket disconnected";
0306     setState(Session::Disconnected);
0307     m_thread->closeSocket();
0308 
0309     if (m_currentJob) {
0310         m_currentJob->connectionLost();
0311     } else if (!m_queue.isEmpty()) {
0312         m_currentJob = m_queue.takeFirst();
0313         m_currentJob->connectionLost();
0314     }
0315 
0316     auto copy = m_queue;
0317     qDeleteAll(copy);
0318     m_queue.clear();
0319 }
0320 
0321 void SessionPrivate::startSsl()
0322 {
0323     QMetaObject::invokeMethod(m_thread, &SessionThread::startSsl, Qt::QueuedConnection);
0324 }
0325 
0326 QSsl::SslProtocol SessionPrivate::negotiatedEncryption() const
0327 {
0328     return m_sslVersion;
0329 }
0330 
0331 void SessionPrivate::encryptionNegotiationResult(bool encrypted, QSsl::SslProtocol version)
0332 {
0333     if (encrypted) {
0334         // Get the updated auth methods
0335         startHandshake();
0336     }
0337 
0338     m_sslVersion = version;
0339 }
0340 
0341 void SessionPrivate::addJob(Job *job)
0342 {
0343     m_queue.append(job);
0344     // Q_EMIT q->jobQueueSizeChanged( q->jobQueueSize() );
0345 
0346     connect(job, &KJob::result, this, &SessionPrivate::jobDone);
0347     connect(job, &KJob::destroyed, this, &SessionPrivate::jobDestroyed);
0348 
0349     if (m_state >= Session::NotAuthenticated) {
0350         startNext();
0351     } else {
0352         m_thread->reconnect();
0353     }
0354 }
0355 
0356 void SessionPrivate::startNext()
0357 {
0358     QTimer::singleShot(0, this, [this]() {
0359         doStartNext();
0360     });
0361 }
0362 
0363 void SessionPrivate::doStartNext()
0364 {
0365     if (m_queue.isEmpty() || m_jobRunning || m_state == Session::Disconnected) {
0366         return;
0367     }
0368 
0369     startSocketTimer();
0370     m_jobRunning = true;
0371 
0372     m_currentJob = m_queue.dequeue();
0373     m_currentJob->doStart();
0374 
0375     // sending can take a while depending on bandwidth - don't fail with timeout
0376     // if it takes longer
0377     if (qobject_cast<const KSmtp::SendJob *>(m_currentJob)) {
0378         stopSocketTimer();
0379     }
0380 }
0381 
0382 void SessionPrivate::jobDone(KJob *job)
0383 {
0384     Q_UNUSED(job)
0385     Q_ASSERT(job == m_currentJob);
0386 
0387     // If we're in disconnected state it's because we ended up
0388     // here because the inactivity timer triggered, so no need to
0389     // stop it (it is single shot)
0390     if (m_state != Session::Disconnected) {
0391         if (!qobject_cast<const KSmtp::SendJob *>(m_currentJob)) {
0392             stopSocketTimer();
0393         }
0394     }
0395 
0396     m_jobRunning = false;
0397     m_currentJob = nullptr;
0398     // Q_EMIT q->jobQueueSizeChanged( q->jobQueueSize() );
0399     startNext();
0400 }
0401 
0402 void SessionPrivate::jobDestroyed(QObject *job)
0403 {
0404     m_queue.removeAll(static_cast<Job *>(job));
0405     if (m_currentJob == job) {
0406         m_currentJob = nullptr;
0407     }
0408 }
0409 
0410 void SessionPrivate::startSocketTimer()
0411 {
0412     if (m_socketTimerInterval < 0) {
0413         return;
0414     }
0415     Q_ASSERT(!m_socketTimer.isActive());
0416 
0417     connect(&m_socketTimer, &QTimer::timeout, this, &SessionPrivate::onSocketTimeout);
0418 
0419     m_socketTimer.setSingleShot(true);
0420     m_socketTimer.start(m_socketTimerInterval);
0421 }
0422 
0423 void SessionPrivate::stopSocketTimer()
0424 {
0425     if (m_socketTimerInterval < 0) {
0426         return;
0427     }
0428     Q_ASSERT(m_socketTimer.isActive());
0429 
0430     m_socketTimer.stop();
0431     disconnect(&m_socketTimer, &QTimer::timeout, this, &SessionPrivate::onSocketTimeout);
0432 }
0433 
0434 void SessionPrivate::onSocketTimeout()
0435 {
0436     socketDisconnected();
0437 }
0438 
0439 ServerResponse::ServerResponse(int code, const QByteArray &text, bool multiline)
0440     : m_text(text)
0441     , m_code(code)
0442     , m_multiline(multiline)
0443 {
0444 }
0445 
0446 bool ServerResponse::isMultiline() const
0447 {
0448     return m_multiline;
0449 }
0450 
0451 int ServerResponse::code() const
0452 {
0453     return m_code;
0454 }
0455 
0456 QByteArray ServerResponse::text() const
0457 {
0458     return m_text;
0459 }
0460 
0461 bool ServerResponse::isCode(int other) const
0462 {
0463     int otherCpy = other;
0464     int codeLength = 0;
0465 
0466     if (other == 0) {
0467         codeLength = 1;
0468     } else {
0469         while (otherCpy > 0) {
0470             otherCpy /= 10;
0471             codeLength++;
0472         }
0473     }
0474 
0475     int div = 1;
0476     for (int i = 0; i < 3 - codeLength; i++) {
0477         div *= 10;
0478     }
0479 
0480     return m_code / div == other;
0481 }
0482 
0483 #include "moc_session_p.cpp"
0484 
0485 #include "moc_session.cpp"