diff --git a/kioslave/pop3/CMakeLists.txt b/kioslave/pop3/CMakeLists.txt index 3eb6959d4..88ac144e1 100644 --- a/kioslave/pop3/CMakeLists.txt +++ b/kioslave/pop3/CMakeLists.txt @@ -1,23 +1,23 @@ remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) ########### next target ############### set(kio_pop3_PART_SRCS pop3.cpp pop3_debug.cpp ) ecm_qt_declare_logging_category(kio_pop3_PART_SRCS HEADER pop3_debug.h IDENTIFIER POP3_LOG CATEGORY_NAME org.kde.pim.pop3) add_library(kio_pop3 MODULE ${kio_pop3_PART_SRCS}) -target_link_libraries(kio_pop3 KF5::KIOCore KF5::I18n Sasl2::Sasl2) +target_link_libraries(kio_pop3 KF5::KIOCore KF5::I18n Qt5::Network Sasl2::Sasl2) set_target_properties(kio_pop3 PROPERTIES OUTPUT_NAME "pop3") install(TARGETS kio_pop3 DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio/ ) ########### install files ############### install( FILES pop3.protocol pop3s.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/kioslave/pop3/pop3.cpp b/kioslave/pop3/pop3.cpp index d952449e8..389ca45ab 100644 --- a/kioslave/pop3/pop3.cpp +++ b/kioslave/pop3/pop3.cpp @@ -1,1156 +1,1173 @@ /* * Copyright (c) 1999-2001 Alex Zepeda * Copyright (c) 2001-2002 Michael Haeckel * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "pop3.h" #include "common.h" extern "C" { #include } #include #include #include #include "pop3_debug.h" #include #include #include +#include #include +#include #define GREETING_BUF_LEN 1024 #define MAX_RESPONSE_LEN 512 #define MAX_COMMANDS 10 extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv); } using namespace KIO; static const sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, nullptr, nullptr }, { SASL_CB_NOECHOPROMPT, nullptr, nullptr }, { SASL_CB_GETREALM, nullptr, nullptr }, { SASL_CB_USER, nullptr, nullptr }, { SASL_CB_AUTHNAME, nullptr, nullptr }, { SASL_CB_PASS, nullptr, nullptr }, { SASL_CB_CANON_USER, nullptr, nullptr }, { SASL_CB_LIST_END, nullptr, nullptr } }; int kdemain(int argc, char **argv) { if (argc != 4) { qCDebug(POP3_LOG) << "Usage: kio_pop3 protocol domain-socket1 domain-socket2"; return -1; } QCoreApplication app(argc, argv); // needed for QSocketNotifier app.setApplicationName(QStringLiteral("kio_pop3")); if (!initSASL()) { return -1; } // Are we looking to use SSL? POP3Protocol *slave; if (strcasecmp(argv[1], "pop3s") == 0) { slave = new POP3Protocol(argv[2], argv[3], true); } else { slave = new POP3Protocol(argv[2], argv[3], false); } slave->dispatchLoop(); delete slave; sasl_done(); return 0; } POP3Protocol::POP3Protocol(const QByteArray &pool, const QByteArray &app, bool isSSL) : TCPSlaveBase((isSSL ? "pop3s" : "pop3"), pool, app, isSSL) { qCDebug(POP3_LOG); //m_cmd = CMD_NONE; m_iOldPort = 0; m_tTimeout.tv_sec = 10; m_tTimeout.tv_usec = 0; supports_apop = false; m_try_apop = true; m_try_sasl = true; opened = false; readBufferLen = 0; } POP3Protocol::~POP3Protocol() { qCDebug(POP3_LOG); closeConnection(); } void POP3Protocol::setHost(const QString &_host, quint16 _port, const QString &_user, const QString &_pass) { m_sServer = _host; m_iPort = _port; m_sUser = _user; m_sPass = _pass; } ssize_t POP3Protocol::myRead(void *data, ssize_t len) { if (readBufferLen) { ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; memcpy(data, readBuffer, copyLen); readBufferLen -= copyLen; if (readBufferLen) { memmove(readBuffer, &readBuffer[copyLen], readBufferLen); } return copyLen; } waitForResponse(600); return read((char *)data, len); } ssize_t POP3Protocol::myReadLine(char *data, ssize_t len) { ssize_t copyLen = 0, readLen = 0; while (true) { while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') { copyLen++; } if (copyLen < readBufferLen || copyLen == len) { copyLen++; memcpy(data, readBuffer, copyLen); data[copyLen] = '\0'; readBufferLen -= copyLen; if (readBufferLen) { memmove(readBuffer, &readBuffer[copyLen], readBufferLen); } return copyLen; } waitForResponse(600); readLen = read(&readBuffer[readBufferLen], len - readBufferLen); readBufferLen += readLen; if (readLen <= 0) { data[0] = '\0'; return 0; } } } POP3Protocol::Resp POP3Protocol::getResponse(char *r_buf, unsigned int r_len) { char *buf = nullptr; unsigned int recv_len = 0; // fd_set FDs; // Give the buffer the appropriate size r_len = r_len ? r_len : MAX_RESPONSE_LEN; buf = new char[r_len]; // Clear out the buffer memset(buf, 0, r_len); myReadLine(buf, r_len - 1); //qCDebug(POP3_LOG) << "S:" << buf; // This is really a funky crash waiting to happen if something isn't // null terminated. recv_len = strlen(buf); /* * From rfc1939: * * Responses in the POP3 consist of a status indicator and a keyword * possibly followed by additional information. All responses are * terminated by a CRLF pair. Responses may be up to 512 characters * long, including the terminating CRLF. There are currently two status * indicators: positive ("+OK") and negative ("-ERR"). Servers MUST * send the "+OK" and "-ERR" in upper case. */ if (strncmp(buf, "+OK", 3) == 0) { if (r_buf && r_len) { memcpy(r_buf, (buf[3] == ' ' ? buf + 4 : buf + 3), qMin(r_len, (buf[3] == ' ' ? recv_len - 4 : recv_len - 3))); } delete[] buf; return Ok; } else if (strncmp(buf, "-ERR", 4) == 0) { if (r_buf && r_len) { memcpy(r_buf, (buf[4] == ' ' ? buf + 5 : buf + 4), qMin(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); } QString serverMsg = QString::fromLatin1(buf).mid(5).trimmed(); m_sError = i18n("The server said: \"%1\"", serverMsg); delete[] buf; return Err; } else if (strncmp(buf, "+ ", 2) == 0) { if (r_buf && r_len) { memcpy(r_buf, buf + 2, qMin(r_len, recv_len - 4)); r_buf[qMin(r_len - 1, recv_len - 4)] = '\0'; } delete[] buf; return Cont; } else { qCDebug(POP3_LOG) << "Invalid POP3 response received!"; if (r_buf && r_len) { memcpy(r_buf, buf, qMin(r_len, recv_len)); } if (!*buf) { m_sError = i18n("The server terminated the connection."); } else { m_sError = i18n("Invalid response from server:\n\"%1\"", QLatin1String(buf)); } delete[] buf; return Invalid; } } bool POP3Protocol::sendCommand(const QByteArray &cmd) { /* * From rfc1939: * * Commands in the POP3 consist of a case-insensitive keyword, possibly * followed by one or more arguments. All commands are terminated by a * CRLF pair. Keywords and arguments consist of printable ASCII * characters. Keywords and arguments are each separated by a single * SPACE character. Keywords are three or four characters long. Each * argument may be up to 40 characters long. */ if (!isConnected()) { return false; } QByteArray cmdrn = cmd + "\r\n"; // Show the command line the client sends, but make sure the password // doesn't show up in the debug output QByteArray debugCommand = cmd; if (!m_sPass.isEmpty()) { debugCommand.replace(m_sPass.toLatin1(), ""); } //qCDebug(POP3_LOG) << "C:" << debugCommand; // Now actually write the command to the socket if (write(cmdrn.data(), cmdrn.size()) != static_cast < ssize_t >(cmdrn.size())) { m_sError = i18n("Could not send to server.\n"); return false; } return true; } POP3Protocol::Resp POP3Protocol::command(const QByteArray &cmd, char *recv_buf, unsigned int len) { sendCommand(cmd); return getResponse(recv_buf, len); } void POP3Protocol::openConnection() { m_try_apop = !hasMetaData(QStringLiteral("auth")) || metaData(QStringLiteral("auth")) == QLatin1String("APOP"); m_try_sasl = !hasMetaData(QStringLiteral("auth")) || metaData(QStringLiteral("auth")) == QLatin1String("SASL"); if (!pop3_open()) { qCDebug(POP3_LOG) << "pop3_open failed"; } else { connected(); } } void POP3Protocol::closeConnection() { // If the file pointer exists, we can assume the socket is valid, // and to make sure that the server doesn't magically undo any of // our deletions and so-on, we should send a QUIT and wait for a // response. We don't care if it's positive or negative. Also // flush out any semblance of a persistant connection, i.e.: the // old username and password are now invalid. if (!opened) { return; } command("QUIT"); disconnectFromHost(); readBufferLen = 0; m_sOldUser = m_sOldPass = m_sOldServer = QLatin1String(""); opened = false; } int POP3Protocol::loginAPOP(const char *challenge, KIO::AuthInfo &ai) { char buf[512]; QString apop_string = QStringLiteral("APOP "); if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); closeConnection(); return -1; } else { m_sUser = ai.username; m_sPass = ai.password; } } m_sOldUser = m_sUser; m_sOldPass = m_sPass; apop_string.append(m_sUser); memset(buf, 0, sizeof(buf)); QCryptographicHash ctx(QCryptographicHash::Md5); qCDebug(POP3_LOG) << "APOP challenge: " << challenge; // Generate digest ctx.addData(challenge, strlen(challenge)); ctx.addData(m_sPass.toLatin1()); // Genenerate APOP command apop_string.append(QLatin1String(" ")); apop_string.append(QLatin1String(ctx.result().toHex())); if (command(apop_string.toLocal8Bit(), buf, sizeof(buf)) == Ok) { return 0; } qCDebug(POP3_LOG) << "Could not login via APOP. Falling back to USER/PASS"; closeConnection(); if (metaData(QStringLiteral("auth")) == QLatin1String("APOP")) { error(ERR_COULD_NOT_LOGIN, i18n ("Login via APOP failed. The server %1 may not support APOP, although it claims to support it, or the password may be wrong.\n\n%2", m_sServer, m_sError)); return -1; } return 1; } bool POP3Protocol::saslInteract(void *in, AuthInfo &ai) { qCDebug(POP3_LOG); sasl_interact_t *interact = (sasl_interact_t *)in; //some mechanisms do not require username && pass, so don't need a popup //window for getting this info for (; interact->id != SASL_CB_LIST_END; interact++) { if (interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS) { if (m_sUser.isEmpty() || m_sPass.isEmpty()) { if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); return false; } m_sUser = ai.username; m_sPass = ai.password; } break; } } interact = (sasl_interact_t *)in; while (interact->id != SASL_CB_LIST_END) { qCDebug(POP3_LOG) << "SASL_INTERACT id: " << interact->id; switch (interact->id) { case SASL_CB_USER: case SASL_CB_AUTHNAME: qCDebug(POP3_LOG) << "SASL_CB_[USER|AUTHNAME]: " << m_sUser; interact->result = strdup(m_sUser.toUtf8()); interact->len = strlen((const char *)interact->result); break; case SASL_CB_PASS: qCDebug(POP3_LOG) << "SASL_CB_PASS: [hidden] "; interact->result = strdup(m_sPass.toUtf8()); interact->len = strlen((const char *)interact->result); break; default: interact->result = nullptr; interact->len = 0; break; } interact++; } return true; } #define SASLERROR closeConnection(); \ error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1", \ QString::fromUtf8(sasl_errdetail(conn)))); \ int POP3Protocol::loginSASL(KIO::AuthInfo &ai) { char buf[512]; QString sasl_buffer = QStringLiteral("AUTH"); int result; sasl_conn_t *conn = nullptr; sasl_interact_t *client_interact = nullptr; const char *out = nullptr; uint outlen; const char *mechusing = nullptr; Resp resp; result = sasl_client_new("pop", m_sServer.toLatin1(), nullptr, nullptr, callbacks, 0, &conn); if (result != SASL_OK) { qCDebug(POP3_LOG) << "sasl_client_new failed with: " << result; SASLERROR return false; } // We need to check what methods the server supports... // This is based on RFC 1734's wisdom if (hasMetaData(QStringLiteral("sasl")) || command(sasl_buffer.toLocal8Bit()) == Ok) { QStringList sasl_list; if (hasMetaData(QStringLiteral("sasl"))) { sasl_list.append(metaData(QStringLiteral("sasl"))); } else { while (true /* !AtEOF() */) { memset(buf, 0, sizeof(buf)); myReadLine(buf, sizeof(buf) - 1); // HACK: This assumes fread stops at the first \n and not \r if ((buf[0] == 0) || (strcmp(buf, ".\r\n") == 0)) { break; // End of data } // sanders, changed -2 to -1 below buf[strlen(buf) - 2] = '\0'; sasl_list.append(QLatin1String(buf)); } } do { result = sasl_client_start(conn, sasl_list.join(QLatin1Char(' ')).toLatin1(), &client_interact, &out, &outlen, &mechusing); if (result == SASL_INTERACT) { if (!saslInteract(client_interact, ai)) { closeConnection(); sasl_dispose(&conn); return -1; } } } while (result == SASL_INTERACT); if (result != SASL_CONTINUE && result != SASL_OK) { qCDebug(POP3_LOG) << "sasl_client_start failed with: " << result; SASLERROR sasl_dispose(&conn); return -1; } qCDebug(POP3_LOG) << "Preferred authentication method is " << mechusing << "."; QByteArray msg, tmp; QString firstCommand = QLatin1String("AUTH ") + QString::fromLatin1(mechusing); msg = QByteArray::fromRawData(out, outlen).toBase64(); if (!msg.isEmpty()) { firstCommand += QLatin1Char(' '); firstCommand += QString::fromLatin1(msg.data(), msg.size()); } tmp.resize(2049); resp = command(firstCommand.toLatin1(), tmp.data(), 2049); while (resp == Cont) { tmp.resize(tmp.indexOf((char)0)); msg = QByteArray::fromBase64(tmp); do { result = sasl_client_step(conn, msg.isEmpty() ? nullptr : msg.data(), msg.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) { if (!saslInteract(client_interact, ai)) { closeConnection(); sasl_dispose(&conn); return -1; } } } while (result == SASL_INTERACT); if (result != SASL_CONTINUE && result != SASL_OK) { qCDebug(POP3_LOG) << "sasl_client_step failed with: " << result; SASLERROR sasl_dispose(&conn); return -1; } msg = QByteArray::fromRawData(out, outlen).toBase64(); tmp.resize(2049); resp = command(msg, tmp.data(), 2049); } sasl_dispose(&conn); if (resp == Ok) { qCDebug(POP3_LOG) << "SASL authenticated"; m_sOldUser = m_sUser; m_sOldPass = m_sPass; return 0; } if (metaData(QStringLiteral("auth")) == QLatin1String("SASL")) { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n ("Login via SASL (%1) failed. The server may not support %2, or the password may be wrong.\n\n%3", QLatin1String(mechusing), QLatin1String(mechusing), m_sError)); return -1; } } if (metaData(QStringLiteral("auth")) == QLatin1String("SASL")) { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server (%1) does not support SASL.\n" "Choose a different authentication method.", m_sServer)); return -1; } return 1; } bool POP3Protocol::loginPASS(KIO::AuthInfo &ai) { char buf[512]; if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPasswordDialog(ai)) { error(ERR_ABORTED, i18n("No authentication details supplied.")); closeConnection(); return false; } else { m_sUser = ai.username; m_sPass = ai.password; } } m_sOldUser = m_sUser; m_sOldPass = m_sPass; QString one_string = QStringLiteral("USER "); one_string.append(m_sUser); if (command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok) { qCDebug(POP3_LOG) << "Could not login. Bad username Sorry"; m_sError = i18n("Could not login to %1.\n\n", m_sServer) + m_sError; error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } one_string = QStringLiteral("PASS "); one_string.append(m_sPass); if (command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok) { qCDebug(POP3_LOG) << "Could not login. Bad password Sorry."; m_sError = i18n ("Could not login to %1. The password may be wrong.\n\n%2", m_sServer, m_sError); error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } qCDebug(POP3_LOG) << "USER/PASS login succeeded"; return true; } bool POP3Protocol::pop3_open() { qCDebug(POP3_LOG); char *greeting_buf; if ((m_iOldPort == m_iPort) && (m_sOldServer == m_sServer) && (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { qCDebug(POP3_LOG) << "Reusing old connection"; return true; } + + if (!hasMetaData(QStringLiteral("useProxy")) + || metaData(QStringLiteral("useProxy")) != QLatin1String("on")) { + qCDebug(POP3_LOG) << "requested to use no proxy"; + + KTcpSocket *sock = qobject_cast(socket()); + if (sock) { + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::NoProxy); + sock->setProxy(proxy); + } else { + qCWarning(POP3_LOG) << "no socket, cannot set no proxy"; + } + } + do { closeConnection(); if (!connectToHost((isAutoSsl() ? QStringLiteral("pop3s") : QStringLiteral("pop3")), m_sServer, m_iPort)) { // error(ERR_COULD_NOT_CONNECT, m_sServer); // ConnectToHost has already send an error message. return false; } opened = true; greeting_buf = new char[GREETING_BUF_LEN]; memset(greeting_buf, 0, GREETING_BUF_LEN); // If the server doesn't respond with a greeting if (getResponse(greeting_buf, GREETING_BUF_LEN) != Ok) { m_sError = i18n("Could not login to %1.\n\n", m_sServer) +((!greeting_buf || !*greeting_buf) ? i18n("The server terminated the connection immediately.") : i18n("Server does not respond properly:\n%1\n", QLatin1String(greeting_buf))); error(ERR_COULD_NOT_LOGIN, m_sError); delete[] greeting_buf; closeConnection(); return false; // we've got major problems, and possibly the // wrong port } QString greeting = QLatin1String(greeting_buf); delete[] greeting_buf; if (!greeting.isEmpty()) { greeting.truncate(greeting.length() - 2); } // Does the server support APOP? //QString apop_cmd; QRegExp re(QStringLiteral("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$"), Qt::CaseInsensitive); qCDebug(POP3_LOG) << "greeting: " << greeting; int apop_pos = greeting.indexOf(re); supports_apop = (bool)(apop_pos != -1); if (metaData(QStringLiteral("nologin")) == QLatin1String("on")) { return true; } if (metaData(QStringLiteral("auth")) == QLatin1String("APOP") && !supports_apop) { error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server (%1) does not support APOP.\n" "Choose a different authentication method.", m_sServer)); closeConnection(); return false; } m_iOldPort = m_iPort; m_sOldServer = m_sServer; // Try to go into TLS mode if ((metaData(QStringLiteral("tls")) == QLatin1String("on") /*### || (canUseTLS() && metaData("tls") != "off")*/) && command("STLS") == Ok) { if (startSsl()) { qCDebug(POP3_LOG) << "TLS mode has been enabled."; } else { qCDebug(POP3_LOG) << "TLS mode setup has failed. Aborting." << endl; error(ERR_SLAVE_DEFINED, i18n("Your POP3 server claims to " "support TLS but negotiation " "was unsuccessful.\nYou can " "disable TLS in the POP account settings dialog.")); closeConnection(); return false; } } else if (metaData(QStringLiteral("tls")) == QLatin1String("on")) { error(ERR_SLAVE_DEFINED, i18n("Your POP3 server (%1) does not support TLS. Disable " "TLS, if you want to connect without encryption.", m_sServer)); closeConnection(); return false; } KIO::AuthInfo authInfo; authInfo.username = m_sUser; authInfo.password = m_sPass; authInfo.prompt = i18n("Username and password for your POP3 account:"); if (supports_apop && m_try_apop) { qCDebug(POP3_LOG) << "Trying APOP"; int retval = loginAPOP(greeting.toLatin1().data() + apop_pos, authInfo); switch (retval) { case 0: return true; case -1: return false; default: m_try_apop = false; } } else if (m_try_sasl) { qCDebug(POP3_LOG) << "Trying SASL"; int retval = loginSASL(authInfo); switch (retval) { case 0: return true; case -1: return false; default: m_try_sasl = false; } } else { // Fall back to conventional USER/PASS scheme qCDebug(POP3_LOG) << "Trying USER/PASS"; return loginPASS(authInfo); } } while (true); } size_t POP3Protocol::realGetSize(unsigned int msg_num) { char *buf; QByteArray cmd; size_t ret = 0; buf = new char[MAX_RESPONSE_LEN]; memset(buf, 0, MAX_RESPONSE_LEN); cmd = "LIST " + QByteArray::number(msg_num); if (command(cmd, buf, MAX_RESPONSE_LEN) != Ok) { delete[] buf; return 0; } else { cmd = buf; cmd.remove(0, cmd.indexOf(" ")); ret = cmd.toLong(); } delete[] buf; return ret; } void POP3Protocol::get(const QUrl &url) { // List of supported commands // // URI Command Result // pop3://user:pass@domain/index LIST List message sizes // pop3://user:pass@domain/uidl UIDL List message UIDs // pop3://user:pass@domain/remove/#1 DELE #1 Mark a message for deletion // pop3://user:pass@domain/download/#1 RETR #1 Get message header and body // pop3://user:pass@domain/list/#1 LIST #1 Get size of a message // pop3://user:pass@domain/uid/#1 UIDL #1 Get UID of a message // pop3://user:pass@domain/commit QUIT Delete marked messages // pop3://user:pass@domain/headers/#1 TOP #1 Get header of message // // Notes: // Sizes are in bytes. // No support for the STAT command has been implemented. // commit closes the connection to the server after issuing the QUIT command. bool ok = true; char buf[MAX_PACKET_LEN]; char destbuf[MAX_PACKET_LEN]; QString cmd, path = url.path(); int maxCommands = (metaData(QStringLiteral("pipelining")) == QLatin1String("on")) ? MAX_COMMANDS : 1; if (path.at(0) == QLatin1Char('/')) { path.remove(0, 1); } if (path.isEmpty()) { qCDebug(POP3_LOG) << "We should be a dir!!"; error(ERR_IS_DIRECTORY, url.url()); //m_cmd = CMD_NONE; return; } if (((path.indexOf(QLatin1Char('/')) == -1) && (path != QLatin1String("index")) && (path != QLatin1String("uidl")) && (path != QLatin1String("commit")))) { error(ERR_MALFORMED_URL, url.url()); //m_cmd = CMD_NONE; return; } cmd = path.left(path.indexOf(QLatin1Char('/'))); path.remove(0, path.indexOf(QLatin1Char('/')) + 1); if (!pop3_open()) { qCDebug(POP3_LOG) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } if ((cmd == QLatin1String("index")) || (cmd == QLatin1String("uidl"))) { unsigned long size = 0; bool result; if (cmd == QLatin1String("index")) { result = (command("LIST") == Ok); } else { result = (command("UIDL") == Ok); } /* LIST +OK Mailbox scan listing follows 1 2979 2 1348 . */ if (result) { mimeType(QStringLiteral("text/plain")); while (true /* !AtEOF() */) { memset(buf, 0, sizeof(buf)); myReadLine(buf, sizeof(buf) - 1); // HACK: This assumes fread stops at the first \n and not \r if ((buf[0] == 0) || (strcmp(buf, ".\r\n") == 0)) { break; // End of data } // sanders, changed -2 to -1 below int bufStrLen = strlen(buf); buf[bufStrLen - 2] = '\0'; size += bufStrLen; data(QByteArray::fromRawData(buf, bufStrLen)); totalSize(size); } } qCDebug(POP3_LOG) << "Finishing up list"; data(QByteArray()); finished(); } else if (cmd == QLatin1String("remove")) { const QStringList waitingCommands = path.split(QLatin1Char(',')); int activeCommands = 0; QStringList::ConstIterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand((QLatin1String("DELE ") + *it).toLatin1()); activeCommands++; it++; } getResponse(buf, sizeof(buf) - 1); activeCommands--; } finished(); //m_cmd = CMD_NONE; } else if (cmd == QLatin1String("download") || cmd == QLatin1String("headers")) { const QStringList waitingCommands = path.split(QLatin1Char(','), QString::SkipEmptyParts); if (waitingCommands.isEmpty()) { qCDebug(POP3_LOG) << "tried to request" << cmd << "for" << path << "with no specific item to get"; closeConnection(); error(ERR_INTERNAL, m_sServer); return; } bool noProgress = (metaData(QStringLiteral("progress")) == QLatin1String("off") || waitingCommands.count() > 1); int p_size = 0; unsigned int msg_len = 0; QString list_cmd(QStringLiteral("LIST ")); list_cmd += path; memset(buf, 0, sizeof(buf)); if (!noProgress) { if (command(list_cmd.toLatin1(), buf, sizeof(buf) - 1) == Ok) { list_cmd = QLatin1String(buf); // We need a space, otherwise we got an invalid reply if (!list_cmd.indexOf(QLatin1Char(' '))) { qCDebug(POP3_LOG) << "List command needs a space? " << list_cmd; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } list_cmd.remove(0, list_cmd.indexOf(QLatin1Char(' ')) + 1); msg_len = list_cmd.toUInt(&ok); if (!ok) { qCDebug(POP3_LOG) << "LIST command needs to return a number? :" < 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(QString((cmd == QLatin1String("headers")) ? QString(QLatin1String("TOP ") + *it + QLatin1String(" 0")) : QString(QLatin1String("RETR ") + *it)).toLatin1()); activeCommands++; it++; } if (getResponse(buf, sizeof(buf) - 1) == Ok) { activeCommands--; if (firstCommand) { firstCommand = false; mimeType(QStringLiteral("message/rfc822")); } totalSize(msg_len); memset(buf, 0, sizeof(buf)); char ending = '\n'; bool endOfMail = false; bool eat = false; while (true /* !AtEOF() */) { ssize_t readlen = myRead(buf, sizeof(buf) - 1); if (readlen <= 0) { if (isConnected()) { error(ERR_SERVER_TIMEOUT, m_sServer); } else { error(ERR_CONNECTION_BROKEN, m_sServer); } closeConnection(); return; } if (ending == '.' && readlen > 1 && buf[0] == '\r' && buf[1] == '\n') { readBufferLen = readlen - 2; memcpy(readBuffer, &buf[2], readBufferLen); break; } bool newline = (ending == '\n'); if (buf[readlen - 1] == '\n') { ending = '\n'; } else if (buf[readlen - 1] == '.' && ((readlen > 1) ? buf[readlen - 2] == '\n' : ending == '\n')) { ending = '.'; } else { ending = ' '; } char *buf1 = buf, *buf2 = destbuf; // ".." at start of a line means only "." // "." means end of data for (ssize_t i = 0; i < readlen; i++) { if (*buf1 == '\r' && eat) { endOfMail = true; if (i == readlen - 1 /* && !AtEOF() */) { myRead(buf, 1); } else if (i < readlen - 2) { readBufferLen = readlen - i - 2; memcpy(readBuffer, &buf[i + 2], readBufferLen); } break; } else if (*buf1 == '\n') { newline = true; eat = false; } else if (*buf1 == '.' && newline) { newline = false; eat = true; } else { newline = false; eat = false; } if (!eat) { *buf2 = *buf1; buf2++; } buf1++; } if (buf2 > destbuf) { data(QByteArray::fromRawData(destbuf, buf2 - destbuf)); } if (endOfMail) { break; } if (!noProgress) { p_size += readlen; processedSize(p_size); } } infoMessage(QStringLiteral("message complete")); } else { qCDebug(POP3_LOG) << "Could not login. Bad RETR Sorry"; closeConnection(); error(ERR_SLAVE_DEFINED, i18n("Error during communication with the POP3 server while " "trying to download mail: %1", m_sError)); return; } } qCDebug(POP3_LOG) << "Finishing up"; data(QByteArray()); finished(); } else if ((cmd == QLatin1String("uid")) || (cmd == QLatin1String("list"))) { //QString qbuf; (void)path.toInt(&ok); if (!ok) { return; // We fscking need a number! } if (cmd == QLatin1String("uid")) { path.prepend(QLatin1String("UIDL ")); } else { path.prepend(QLatin1String("LIST ")); } memset(buf, 0, sizeof(buf)); if (command(path.toLatin1(), buf, sizeof(buf) - 1) == Ok) { const int len = strlen(buf); mimeType(QStringLiteral("text/plain")); totalSize(len); data(QByteArray::fromRawData(buf, len)); processedSize(len); qCDebug(POP3_LOG) << buf; qCDebug(POP3_LOG) << "Finishing up uid"; data(QByteArray()); finished(); } else { closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else if (cmd == QLatin1String("commit")) { qCDebug(POP3_LOG) << "Issued QUIT"; closeConnection(); finished(); //m_cmd = CMD_NONE; return; } } void POP3Protocol::listDir(const QUrl &url) { Q_UNUSED(url); bool isINT; int num_messages = 0; QByteArray q_buf(MAX_RESPONSE_LEN, 0); // Try and open a connection if (!pop3_open()) { qCDebug(POP3_LOG) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } // Check how many messages we have. STAT is by law required to // at least return +OK num_messages total_size if (command("STAT", q_buf.data(), MAX_RESPONSE_LEN) != Ok) { error(ERR_INTERNAL, i18n("The POP3 command 'STAT' failed")); return; } qCDebug(POP3_LOG) << "The stat buf is :" << q_buf << ":"; if (q_buf.indexOf(" ") == -1) { error(ERR_INTERNAL, i18n("Invalid POP3 response, should have at least one space.")); closeConnection(); return; } q_buf.remove(q_buf.indexOf(" "), q_buf.length()); num_messages = q_buf.toUInt(&isINT); if (!isINT) { error(ERR_INTERNAL, i18n("Invalid POP3 STAT response.")); closeConnection(); return; } UDSEntry entry; QString fname; for (int i = 0; i < num_messages; i++) { fname = QStringLiteral("Message %1"); entry.insert(KIO::UDSEntry::UDS_NAME, fname.arg(i + 1)); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/plain")); QUrl uds_url; if (isAutoSsl()) { uds_url.setScheme(QStringLiteral("pop3s")); } else { uds_url.setScheme(QStringLiteral("pop3")); } uds_url.setUserName(m_sUser); uds_url.setPassword(m_sPass); uds_url.setHost(m_sServer); uds_url.setPath(QStringLiteral("/download/%1").arg(i + 1)); entry.insert(KIO::UDSEntry::UDS_URL, uds_url.url()); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(KIO::UDSEntry::UDS_SIZE, realGetSize(i + 1)); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IWUSR); listEntry(entry); entry.clear(); } finished(); } void POP3Protocol::stat(const QUrl &url) { QString _path = url.path(); if (_path.at(0) == QLatin1Char('/')) { _path.remove(0, 1); } UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, _path); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("message/rfc822")); // TODO: maybe get the size of the message? statEntry(entry); finished(); } void POP3Protocol::del(const QUrl &url, bool /*isfile */) { QString invalidURI; bool isInt; if (!pop3_open()) { qCDebug(POP3_LOG) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } QString _path = url.path(); if (_path.at(0) == QLatin1Char('/')) { _path.remove(0, 1); } _path.toUInt(&isInt); if (!isInt) { invalidURI = _path; } else { _path.prepend(QLatin1String("DELE ")); if (command(_path.toLatin1()) != Ok) { invalidURI = _path; } } qCDebug(POP3_LOG) << "Path:" << _path; finished(); } diff --git a/resources/pop3/jobs.cpp b/resources/pop3/jobs.cpp index 021e89a72..738032891 100644 --- a/resources/pop3/jobs.cpp +++ b/resources/pop3/jobs.cpp @@ -1,502 +1,504 @@ /* Copyright 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "jobs.h" #include "settings.h" #include #include #include #include #include #include "pop3resource_debug.h" #include POPSession::POPSession(const QString &password) : mCurrentJob(nullptr) , mPassword(password) { KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *,int,QString)), this, SLOT(slotSlaveError(KIO::Slave *,int,QString))); } POPSession::~POPSession() { closeSession(); } void POPSession::slotSlaveError(KIO::Slave *slave, int errorCode, const QString &errorMessage) { Q_UNUSED(slave); qCWarning(POP3RESOURCE_LOG) << "Got a slave error:" << errorMessage; if (slave != mSlave) { return; } if (errorCode == KIO::ERR_SLAVE_DIED) { mSlave = nullptr; } // Explicitly disconnect the slave if the connection went down if (errorCode == KIO::ERR_CONNECTION_BROKEN && mSlave) { KIO::Scheduler::disconnectSlave(mSlave); mSlave = nullptr; } if (!mCurrentJob) { Q_EMIT slaveError(errorCode, errorMessage); } else { // Let the job deal with the problem mCurrentJob->slaveError(errorCode, errorMessage); } } void POPSession::setCurrentJob(SlaveBaseJob *job) { mCurrentJob = job; } KIO::MetaData POPSession::slaveConfig() const { KIO::MetaData m; m.insert(QStringLiteral("progress"), QStringLiteral("off")); m.insert(QStringLiteral("tls"), Settings::self()->useTLS() ? QStringLiteral("on") : QStringLiteral("off")); m.insert(QStringLiteral("pipelining"), (Settings::self()->pipelining()) ? QStringLiteral("on") : QStringLiteral("off")); + m.insert(QStringLiteral("useProxy"), Settings::self()->useProxy() ? QStringLiteral("on") : QStringLiteral("off")); int type = Settings::self()->authenticationMethod(); switch (type) { case MailTransport::Transport::EnumAuthenticationType::PLAIN: case MailTransport::Transport::EnumAuthenticationType::LOGIN: case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: case MailTransport::Transport::EnumAuthenticationType::NTLM: case MailTransport::Transport::EnumAuthenticationType::GSSAPI: m.insert(QStringLiteral("auth"), QStringLiteral("SASL")); m.insert(QStringLiteral("sasl"), authenticationToString(type)); break; case MailTransport::Transport::EnumAuthenticationType::CLEAR: m.insert(QStringLiteral("auth"), QStringLiteral("USER")); break; default: m.insert(QStringLiteral("auth"), authenticationToString(type)); break; } + return m; } QString POPSession::authenticationToString(int type) const { switch (type) { case MailTransport::Transport::EnumAuthenticationType::LOGIN: return QStringLiteral("LOGIN"); case MailTransport::Transport::EnumAuthenticationType::PLAIN: return QStringLiteral("PLAIN"); case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5: return QStringLiteral("CRAM-MD5"); case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5: return QStringLiteral("DIGEST-MD5"); case MailTransport::Transport::EnumAuthenticationType::GSSAPI: return QStringLiteral("GSSAPI"); case MailTransport::Transport::EnumAuthenticationType::NTLM: return QStringLiteral("NTLM"); case MailTransport::Transport::EnumAuthenticationType::CLEAR: return QStringLiteral("USER"); case MailTransport::Transport::EnumAuthenticationType::APOP: return QStringLiteral("APOP"); default: break; } return QString(); } QUrl POPSession::getUrl() const { QUrl url; if (Settings::self()->useSSL()) { url.setScheme(QStringLiteral("pop3s")); } else { url.setScheme(QStringLiteral("pop3")); } url.setUserName(Settings::self()->login()); url.setPassword(mPassword); url.setHost(Settings::self()->host()); url.setPort(Settings::self()->port()); return url; } bool POPSession::connectSlave() { mSlave = KIO::Scheduler::getConnectedSlave(getUrl(), slaveConfig()); return mSlave != nullptr; } void POPSession::abortCurrentJob() { if (mCurrentJob) { mCurrentJob->kill(KJob::Quietly); mCurrentJob = nullptr; } } void POPSession::closeSession() { if (mSlave) { KIO::Scheduler::disconnectSlave(mSlave); } } KIO::Slave *POPSession::getSlave() const { return mSlave; } static QByteArray cleanupListRespone(const QByteArray &response) { QByteArray ret = response.simplified(); // Workaround for Maillennium POP3/UNIBOX // Get rid of the null terminating character, if it exists int retSize = ret.size(); if (retSize > 0 && ret.at(retSize - 1) == 0) { ret.chop(1); } return ret; } static QString intListToString(const QList &intList) { QString idList; for (int id : intList) { idList += QString::number(id) + QLatin1Char(','); } idList.chop(1); return idList; } SlaveBaseJob::SlaveBaseJob(POPSession *POPSession) : mJob(nullptr) , mPOPSession(POPSession) { mPOPSession->setCurrentJob(this); } SlaveBaseJob::~SlaveBaseJob() { // Don't do that here, the job might be destroyed after another one was started // and therefore overwrite the current job //mPOPSession->setCurrentJob( 0 ); } bool SlaveBaseJob::doKill() { if (mJob) { return mJob->kill(); } else { return KJob::doKill(); } } void SlaveBaseJob::slotSlaveResult(KJob *job) { mPOPSession->setCurrentJob(nullptr); if (job->error()) { setError(job->error()); setErrorText(job->errorText()); } emitResult(); mJob = nullptr; } void SlaveBaseJob::slotSlaveData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); qCWarning(POP3RESOURCE_LOG) << "Got unexpected slave data:" << data.data(); } void SlaveBaseJob::slaveError(int errorCode, const QString &errorMessage) { // The slave experienced some problem while running our job. // Just treat this as an error. // Derived jobs can do something more sophisticated here setError(errorCode); setErrorText(errorMessage); emitResult(); mJob = nullptr; } void SlaveBaseJob::connectJob() { connect(mJob, &KIO::TransferJob::data, this, &SlaveBaseJob::slotSlaveData); connect(mJob, &KIO::TransferJob::result, this, &SlaveBaseJob::slotSlaveResult); } void SlaveBaseJob::startJob(const QString &path) { QUrl url = mPOPSession->getUrl(); url.setPath(path); mJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); KIO::Scheduler::assignJobToSlave(mPOPSession->getSlave(), mJob); connectJob(); } QString SlaveBaseJob::errorString() const { if (mJob) { return mJob->errorString(); } else { return KJob::errorString(); } } LoginJob::LoginJob(POPSession *popSession) : SlaveBaseJob(popSession) { } void LoginJob::start() { // This will create a connected slave, which means it will also try to login. KIO::Scheduler::connect(SIGNAL(slaveConnected(KIO::Slave *)), this, SLOT(slaveConnected(KIO::Slave *))); if (!mPOPSession->connectSlave()) { setError(KJob::UserDefinedError); setErrorText(i18n("Unable to create POP3 slave, aborting mail check.")); emitResult(); } } void LoginJob::slaveConnected(KIO::Slave *slave) { if (slave != mPOPSession->getSlave()) { // Odd, not our slave... return; } // Yeah it connected, so login was sucessful! emitResult(); } void LoginJob::slaveError(int errorCode, const QString &errorMessage) { setError(errorCode); setErrorText(errorMessage); mErrorString = KIO::buildErrorString(errorCode, errorMessage); emitResult(); } QString LoginJob::errorString() const { return mErrorString; } ListJob::ListJob(POPSession *popSession) : SlaveBaseJob(popSession) { } void ListJob::start() { startJob(QStringLiteral("/index")); } void ListJob::slotSlaveData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); // Silly slave, why are you sending us empty data? if (data.isEmpty()) { return; } QByteArray cleanData = cleanupListRespone(data); const int space = cleanData.indexOf(' '); if (space > 0) { QByteArray lengthString = cleanData.mid(space + 1); const int spaceInLengthPos = lengthString.indexOf(' '); if (spaceInLengthPos != -1) { lengthString.truncate(spaceInLengthPos); } const int length = lengthString.toInt(); QByteArray idString = cleanData.left(space); bool idIsNumber; int id = QString::fromLatin1(idString).toInt(&idIsNumber); if (idIsNumber) { mIdList.insert(id, length); } else { qCWarning(POP3RESOURCE_LOG) << "Got non-integer ID as part of the LIST response, ignoring" << idString.data(); } } else { qCWarning(POP3RESOURCE_LOG) << "Got invalid LIST response:" << data.data(); } } QMap ListJob::idList() const { return mIdList; } UIDListJob::UIDListJob(POPSession *popSession) : SlaveBaseJob(popSession) { } void UIDListJob::start() { startJob(QStringLiteral("/uidl")); } void UIDListJob::slotSlaveData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); // Silly slave, why are you sending us empty data? if (data.isEmpty()) { return; } QByteArray cleanData = cleanupListRespone(data); const int space = cleanData.indexOf(' '); if (space <= 0) { qCWarning(POP3RESOURCE_LOG) << "Invalid response to the UIDL command:" << data.data(); qCWarning(POP3RESOURCE_LOG) << "Ignoring this entry."; } else { QByteArray idString = cleanData.left(space); QByteArray uidString = cleanData.mid(space + 1); bool idIsNumber; int id = QString::fromLatin1(idString).toInt(&idIsNumber); if (idIsNumber) { const QString uidQString = QString::fromLatin1(uidString); if (!uidQString.isEmpty()) { mUidList.insert(id, uidQString); mIdList.insert(uidQString, id); } else { qCWarning(POP3RESOURCE_LOG) << "Got invalid/empty UID from the UIDL command:" << uidString.data(); qCWarning(POP3RESOURCE_LOG) << "The whole response was:" << data.data(); } } else { qCWarning(POP3RESOURCE_LOG) << "Got invalid ID from the UIDL command:" << idString.data(); qCWarning(POP3RESOURCE_LOG) << "The whole response was:" << data.data(); } } } QMap UIDListJob::uidList() const { return mUidList; } QMap UIDListJob::idList() const { return mIdList; } DeleteJob::DeleteJob(POPSession *popSession) : SlaveBaseJob(popSession) { } void DeleteJob::setDeleteIds(const QList &ids) { mIdsToDelete = ids; } void DeleteJob::start() { qCDebug(POP3RESOURCE_LOG) << "================= DeleteJob::start. ============================="; startJob(QLatin1String("/remove/") + intListToString(mIdsToDelete)); } QList DeleteJob::deletedIDs() const { // FIXME : The slave doesn't tell us which of the IDs were actually deleted, we // just assume all of them here return mIdsToDelete; } QuitJob::QuitJob(POPSession *popSession) : SlaveBaseJob(popSession) { } void QuitJob::start() { startJob(QStringLiteral("/commit")); } FetchJob::FetchJob(POPSession *session) : SlaveBaseJob(session) , mBytesDownloaded(0) , mTotalBytesToDownload(0) , mDataCounter(0) { } void FetchJob::setFetchIds(const QList &ids, const QList &sizes) { mIdsPendingDownload = ids; for (int size : qAsConst(sizes)) { mTotalBytesToDownload += size; } } void FetchJob::start() { startJob(QLatin1String("/download/") + intListToString(mIdsPendingDownload)); setTotalAmount(KJob::Bytes, mTotalBytesToDownload); } void FetchJob::connectJob() { SlaveBaseJob::connectJob(); connect(mJob, &KIO::TransferJob::infoMessage, this, &FetchJob::slotInfoMessage); } void FetchJob::slotSlaveData(KIO::Job *job, const QByteArray &data) { Q_UNUSED(job); mCurrentMessage += data; mBytesDownloaded += data.size(); mDataCounter++; if (mDataCounter % 5 == 0) { setProcessedAmount(KJob::Bytes, mBytesDownloaded); } } void FetchJob::slotInfoMessage(KJob *job, const QString &infoMessage, const QString &) { Q_UNUSED(job); if (infoMessage != QLatin1String("message complete")) { return; } KMime::Message::Ptr msg(new KMime::Message); msg->setContent(KMime::CRLFtoLF(mCurrentMessage)); msg->parse(); mCurrentMessage.clear(); const int idOfCurrentMessage = mIdsPendingDownload.takeFirst(); Q_EMIT messageFinished(idOfCurrentMessage, msg); } diff --git a/resources/pop3/settings.kcfg b/resources/pop3/settings.kcfg index 6cff78618..6e4ef23bc 100644 --- a/resources/pop3/settings.kcfg +++ b/resources/pop3/settings.kcfg @@ -1,93 +1,97 @@ 110 7 false false false + + + false + false -1 -1 -1 false 50000 -1 false 5