File indexing completed on 2024-12-22 05:03:15
0001 /* 0002 SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kmanagersieve_debug.h" 0008 #include "response.h" 0009 #include "session.h" 0010 #include "sessionthread_p.h" 0011 0012 #include <QSslCipher> 0013 #include <QThread> 0014 #include <QTimer> 0015 0016 #include <KLocalizedString> 0017 0018 #include <KIO/Global> 0019 #include <KIO/Job> 0020 0021 #include "sasl-common.h" 0022 #include <cstring> // strlen() 0023 0024 using namespace KManageSieve; 0025 0026 static const sasl_callback_t callbacks[] = {{SASL_CB_ECHOPROMPT, nullptr, nullptr}, 0027 {SASL_CB_NOECHOPROMPT, nullptr, nullptr}, 0028 {SASL_CB_GETREALM, nullptr, nullptr}, 0029 {SASL_CB_USER, nullptr, nullptr}, 0030 {SASL_CB_AUTHNAME, nullptr, nullptr}, 0031 {SASL_CB_PASS, nullptr, nullptr}, 0032 {SASL_CB_CANON_USER, nullptr, nullptr}, 0033 {SASL_CB_LIST_END, nullptr, nullptr}}; 0034 0035 SessionThread::SessionThread(Session *session, QObject *parent) 0036 : QObject(parent) 0037 , m_session(session) 0038 { 0039 static bool saslInitialized = false; 0040 if (!saslInitialized) { 0041 // Call initSASL() from main thread 0042 initSASL(); 0043 saslInitialized = true; 0044 } 0045 0046 auto thread = new QThread(); 0047 moveToThread(thread); 0048 thread->start(); 0049 QMetaObject::invokeMethod(this, "doInit"); 0050 } 0051 0052 SessionThread::~SessionThread() 0053 { 0054 QMetaObject::invokeMethod(this, &SessionThread::doDestroy, Qt::QueuedConnection); 0055 if (!thread()->wait(10 * 1000)) { 0056 thread()->terminate(); 0057 thread()->wait(); 0058 } 0059 0060 delete thread(); 0061 } 0062 0063 // Called in secondary thread 0064 void SessionThread::doInit() 0065 { 0066 Q_ASSERT(QThread::currentThread() == thread()); 0067 m_socket = std::make_unique<QSslSocket>(); 0068 connect(m_socket.get(), &QSslSocket::readyRead, this, &SessionThread::slotDataReceived); 0069 connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, &SessionThread::slotSocketError); 0070 connect(m_socket.get(), &QSslSocket::disconnected, this, &SessionThread::socketDisconnected); 0071 connect(m_socket.get(), &QSslSocket::connected, this, &SessionThread::socketConnected); 0072 } 0073 0074 // Called in secondary thread 0075 void SessionThread::doDestroy() 0076 { 0077 Q_ASSERT(QThread::currentThread() == thread()); 0078 0079 doDisconnectFromHost(false); 0080 m_socket.reset(); 0081 delete m_sslCheck; 0082 0083 thread()->quit(); 0084 } 0085 0086 // Called in main thread 0087 void SessionThread::connectToHost(const QUrl &url) 0088 { 0089 QMetaObject::invokeMethod( 0090 this, 0091 [this, url]() { 0092 doConnectToHost(url); 0093 }, 0094 Qt::QueuedConnection); 0095 } 0096 0097 // Called in secondary thread 0098 void SessionThread::doConnectToHost(const QUrl &url) 0099 { 0100 Q_ASSERT(QThread::currentThread() == thread()); 0101 0102 if (m_socket->state() == QAbstractSocket::ConnectedState || m_socket->state() == QAbstractSocket::ConnectingState) { 0103 return; 0104 } 0105 0106 m_url = url; 0107 m_socket->connectToHost(url.host(), url.port() ? url.port() : 4190); 0108 } 0109 0110 // Called in main thread 0111 void SessionThread::disconnectFromHost(bool sendLogout) 0112 { 0113 QMetaObject::invokeMethod( 0114 this, 0115 [this, sendLogout]() { 0116 doDisconnectFromHost(sendLogout); 0117 }, 0118 Qt::QueuedConnection); 0119 } 0120 0121 // Called in secondary thread 0122 void SessionThread::doDisconnectFromHost(bool sendLogout) 0123 { 0124 Q_ASSERT(QThread::currentThread() == thread()); 0125 0126 if (sendLogout) { 0127 doSendData("LOGOUT"); 0128 } 0129 m_socket->disconnectFromHost(); 0130 } 0131 0132 // Called in main thread 0133 void SessionThread::sendData(const QByteArray &data) 0134 { 0135 QMetaObject::invokeMethod( 0136 this, 0137 [this, data]() { 0138 doSendData(data); 0139 }, 0140 Qt::QueuedConnection); 0141 } 0142 0143 // Called in secondary thread 0144 void SessionThread::doSendData(const QByteArray &data) 0145 { 0146 Q_ASSERT(QThread::currentThread() == thread()); 0147 0148 qCDebug(KMANAGERSIEVE_LOG) << "C: " << data; 0149 m_socket->write(data); 0150 m_socket->write("\r\n"); 0151 } 0152 0153 // Called in secondary thread 0154 void SessionThread::slotDataReceived() 0155 { 0156 Q_ASSERT(QThread::currentThread() == thread()); 0157 if (m_pendingQuantity > 0) { 0158 const QByteArray buffer = m_socket->read(qMin(m_pendingQuantity, m_socket->bytesAvailable())); 0159 m_data += buffer; 0160 m_pendingQuantity -= buffer.size(); 0161 if (m_pendingQuantity <= 0) { 0162 qCDebug(KMANAGERSIEVE_LOG) << "S: " << m_data.trimmed(); 0163 Q_EMIT responseReceived(m_lastResponse, m_data); 0164 } else { 0165 return; // waiting for more data 0166 } 0167 } 0168 0169 while (m_socket->canReadLine()) { 0170 QByteArray line = m_socket->readLine(); 0171 if (line.endsWith("\r\n")) { // krazy:exclude=strings 0172 line.chop(2); 0173 } 0174 if (line.isEmpty()) { 0175 continue; // ignore CRLF after data blocks 0176 } 0177 qCDebug(KMANAGERSIEVE_LOG) << "S: " << line; 0178 Response r; 0179 if (!r.parseResponse(line)) { 0180 qCDebug(KMANAGERSIEVE_LOG) << "protocol violation!"; 0181 doDisconnectFromHost(false); 0182 } 0183 qCDebug(KMANAGERSIEVE_LOG) << r.type() << r.key() << r.value() << r.extra() << r.quantity(); 0184 0185 m_lastResponse = r; 0186 if (r.quantity() > 0) { 0187 m_data.clear(); 0188 m_pendingQuantity = r.quantity(); 0189 slotDataReceived(); // in case the data block is already completely in the buffer 0190 return; 0191 } else if (r.operationResult() == Response::Bye) { 0192 doDisconnectFromHost(false); 0193 return; 0194 } 0195 Q_EMIT responseReceived(r, QByteArray()); 0196 } 0197 } 0198 0199 // Called in secondary thread 0200 void SessionThread::slotSocketError() 0201 { 0202 Q_ASSERT(QThread::currentThread() == thread()); 0203 0204 qCWarning(KMANAGERSIEVE_LOG) << Q_FUNC_INFO << m_socket->error() << m_socket->errorString(); 0205 0206 Q_EMIT error(m_socket->error(), m_socket->errorString()); 0207 doDisconnectFromHost(false); 0208 } 0209 0210 // Called in main thread 0211 void SessionThread::startAuthentication() 0212 { 0213 QMetaObject::invokeMethod(this, &SessionThread::doStartAuthentication, Qt::QueuedConnection); 0214 } 0215 0216 // Called in secondary thread 0217 void SessionThread::handleSaslAuthError() 0218 { 0219 Q_EMIT error(QAbstractSocket::UnknownSocketError, KIO::buildErrorString(KIO::ERR_CANNOT_AUTHENTICATE, QString::fromUtf8(sasl_errdetail(m_sasl_conn)))); 0220 doDisconnectFromHost(true); 0221 } 0222 0223 // Called in secondary thread 0224 void SessionThread::doStartAuthentication() 0225 { 0226 Q_ASSERT(QThread::currentThread() == thread()); 0227 0228 int result; 0229 m_sasl_conn = nullptr; 0230 m_sasl_client_interact = nullptr; 0231 const char *out = nullptr; 0232 uint outlen; 0233 const char *mechusing = nullptr; 0234 0235 result = sasl_client_new("sieve", m_url.host().toLatin1().constData(), nullptr, nullptr, callbacks, 0, &m_sasl_conn); 0236 if (result != SASL_OK) { 0237 handleSaslAuthError(); 0238 return; 0239 } 0240 0241 do { 0242 result = sasl_client_start(m_sasl_conn, 0243 m_session->requestedSaslMethod().join(QLatin1Char(' ')).toLatin1().constData(), 0244 &m_sasl_client_interact, 0245 &out, 0246 &outlen, 0247 &mechusing); 0248 if (result == SASL_INTERACT) { 0249 if (!saslInteract(m_sasl_client_interact)) { 0250 handleSaslAuthError(); 0251 sasl_dispose(&m_sasl_conn); 0252 return; 0253 } 0254 } 0255 } while (result == SASL_INTERACT); 0256 0257 if (result != SASL_CONTINUE && result != SASL_OK) { 0258 handleSaslAuthError(); 0259 sasl_dispose(&m_sasl_conn); 0260 return; 0261 } 0262 0263 qCDebug(KMANAGERSIEVE_LOG) << "Preferred authentication method is " << mechusing << "."; 0264 0265 QByteArray authCommand = "AUTHENTICATE \"" + QByteArray(mechusing) + QByteArray("\""); 0266 const QByteArray challenge = QByteArray::fromRawData(out, outlen).toBase64(); 0267 if (!challenge.isEmpty()) { 0268 authCommand += " \""; 0269 authCommand += challenge; 0270 authCommand += '\"'; 0271 } 0272 doSendData(authCommand); 0273 } 0274 0275 // Called in main thread 0276 void SessionThread::continueAuthentication(const Response &response, const QByteArray &data) 0277 { 0278 QMetaObject::invokeMethod(this, "doContinueAuthentication", Qt::QueuedConnection, Q_ARG(KManageSieve::Response, response), Q_ARG(QByteArray, data)); 0279 } 0280 0281 // Called in secondary thread 0282 void SessionThread::doContinueAuthentication(const Response &response, const QByteArray &data) 0283 { 0284 Q_ASSERT(QThread::currentThread() == thread()); 0285 0286 if (response.operationResult() == Response::Other) { 0287 if (!saslClientStep(data)) { 0288 handleSaslAuthError(); 0289 return; 0290 } 0291 } else { 0292 sasl_dispose(&m_sasl_conn); 0293 if (response.operationSuccessful()) { 0294 qCDebug(KMANAGERSIEVE_LOG) << "Authentication complete."; 0295 Q_EMIT authenticationDone(); 0296 } else { 0297 Q_EMIT error(QAbstractSocket::UnknownSocketError, 0298 KIO::buildErrorString(KIO::ERR_CANNOT_AUTHENTICATE, 0299 i18n("Authentication failed.\nMost likely the password is wrong.\nThe server responded:\n%1", 0300 QString::fromLatin1(response.action())))); 0301 doDisconnectFromHost(true); 0302 } 0303 } 0304 } 0305 0306 // Called in secondary thread 0307 bool SessionThread::saslInteract(void *in) 0308 { 0309 Q_ASSERT(QThread::currentThread() == thread()); 0310 0311 qCDebug(KMANAGERSIEVE_LOG) << "SessionThread::saslInteract"; 0312 auto *interact = (sasl_interact_t *)in; 0313 0314 // some mechanisms do not require username && pass, so it doesn't need a popup 0315 // window for getting this info 0316 for (; interact->id != SASL_CB_LIST_END; ++interact) { 0317 if (interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS) { 0318 if (m_url.userName().isEmpty() || m_url.password().isEmpty()) { 0319 AuthDetails authData; 0320 QMetaObject::invokeMethod(m_session, 0321 "requestAuthDetails", 0322 Qt::BlockingQueuedConnection, 0323 Q_RETURN_ARG(KManageSieve::AuthDetails, authData), 0324 Q_ARG(QUrl, m_url)); 0325 0326 if (authData.valid) { 0327 m_url.setUserName(authData.username); 0328 m_url.setPassword(authData.password); 0329 } else { 0330 return false; 0331 } 0332 } 0333 break; 0334 } 0335 } 0336 0337 interact = (sasl_interact_t *)in; 0338 while (interact->id != SASL_CB_LIST_END) { 0339 qCDebug(KMANAGERSIEVE_LOG) << "SASL_INTERACT id: " << interact->id; 0340 switch (interact->id) { 0341 case SASL_CB_USER: 0342 case SASL_CB_AUTHNAME: 0343 qCDebug(KMANAGERSIEVE_LOG) << "SASL_CB_[AUTHNAME|USER]: '" << m_url.userName() << "'"; 0344 interact->result = strdup(m_url.userName().toUtf8().constData()); 0345 if (interact->result) { 0346 interact->len = strlen((const char *)interact->result); 0347 } else { 0348 interact->len = 0; 0349 } 0350 break; 0351 case SASL_CB_PASS: 0352 qCDebug(KMANAGERSIEVE_LOG) << "SASL_CB_PASS: [hidden] "; 0353 interact->result = strdup(m_url.password().toUtf8().constData()); 0354 if (interact->result) { 0355 interact->len = strlen((const char *)interact->result); 0356 } else { 0357 interact->len = 0; 0358 } 0359 break; 0360 default: 0361 interact->result = nullptr; 0362 interact->len = 0; 0363 break; 0364 } 0365 interact++; 0366 } 0367 return true; 0368 } 0369 0370 // Called in secondary thread 0371 bool SessionThread::saslClientStep(const QByteArray &challenge) 0372 { 0373 int result; 0374 const char *out = nullptr; 0375 uint outlen; 0376 0377 const QByteArray challenge_decoded = QByteArray::fromBase64(challenge); 0378 do { 0379 result = sasl_client_step(m_sasl_conn, 0380 challenge_decoded.isEmpty() ? nullptr : challenge_decoded.data(), 0381 challenge_decoded.size(), 0382 &m_sasl_client_interact, 0383 &out, 0384 &outlen); 0385 if (result == SASL_INTERACT) { 0386 if (!saslInteract(m_sasl_client_interact)) { 0387 sasl_dispose(&m_sasl_conn); 0388 return false; 0389 } 0390 } 0391 } while (result == SASL_INTERACT); 0392 0393 qCDebug(KMANAGERSIEVE_LOG) << "sasl_client_step: " << result; 0394 if (result != SASL_CONTINUE && result != SASL_OK) { 0395 qCDebug(KMANAGERSIEVE_LOG) << "sasl_client_step failed with: " << result << QString::fromUtf8(sasl_errdetail(m_sasl_conn)); 0396 sasl_dispose(&m_sasl_conn); 0397 return false; 0398 } 0399 0400 doSendData('\"' + QByteArray::fromRawData(out, outlen).toBase64() + '\"'); 0401 return true; 0402 } 0403 0404 // Called in main thread 0405 void SessionThread::startSsl() 0406 { 0407 QMetaObject::invokeMethod(this, &SessionThread::doStartSsl, Qt::QueuedConnection); 0408 } 0409 0410 // Called in secondary thread 0411 void SessionThread::doStartSsl() 0412 { 0413 Q_ASSERT(QThread::currentThread() == thread()); 0414 0415 qCDebug(KMANAGERSIEVE_LOG) << "SessionThread::doStartSsl()"; 0416 if (!m_sslCheck) { 0417 m_sslCheck = new QTimer(this); 0418 m_sslCheck->setInterval(60 * 1000); 0419 connect(m_sslCheck, &QTimer::timeout, this, &SessionThread::slotSslTimeout); 0420 } 0421 m_socket->setProtocol(QSsl::SecureProtocols); 0422 m_socket->ignoreSslErrors(); 0423 connect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::slotEncryptedDone); 0424 m_sslCheck->start(); 0425 m_socket->startClientEncryption(); 0426 } 0427 0428 // Called in secondary thread 0429 void SessionThread::slotSslTimeout() 0430 { 0431 Q_ASSERT(QThread::currentThread() == thread()); 0432 0433 disconnect(m_socket.get(), &QSslSocket::encrypted, this, &SessionThread::slotEncryptedDone); 0434 sslResult(false); 0435 } 0436 0437 // Called in secondary thread 0438 void SessionThread::slotEncryptedDone() 0439 { 0440 Q_ASSERT(QThread::currentThread() == thread()); 0441 0442 m_sslCheck->stop(); 0443 sslResult(true); 0444 } 0445 0446 // Called in secondary thread 0447 void SessionThread::sslResult(bool encrypted) 0448 { 0449 Q_ASSERT(QThread::currentThread() == thread()); 0450 0451 const QSslCipher cipher = m_socket->sessionCipher(); 0452 const int numberOfSslError = m_socket->sslHandshakeErrors().count(); 0453 if (!encrypted || numberOfSslError > 0 || !m_socket->isEncrypted() || cipher.isNull() || cipher.usedBits() == 0) { 0454 qCDebug(KMANAGERSIEVE_LOG) << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() 0455 << ", the socket says:" << m_socket->errorString() << "and the list of SSL errors contains" << numberOfSslError << "items."; 0456 0457 Q_EMIT sslError(KSslErrorUiData(m_socket.get())); 0458 } else { 0459 Q_EMIT sslDone(); 0460 } 0461 } 0462 0463 #include "moc_sessionthread_p.cpp"