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"