diff --git a/kioslave/pop3/pop3.cpp b/kioslave/pop3/pop3.cpp index 94dce4858..e14460ab3 100644 --- a/kioslave/pop3/pop3.cpp +++ b/kioslave/pop3/pop3.cpp @@ -1,1147 +1,1147 @@ /* * 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" #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #define GREETING_BUF_LEN 1024 #define MAX_RESPONSE_LEN 512 #define MAX_COMMANDS 10 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } using namespace KIO; static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; int kdemain(int argc, char **argv) { if (argc != 4) { kDebug(7105) << "Usage: kio_pop3 protocol domain-socket1 domain-socket2"; return -1; } QCoreApplication app( argc, argv ); // needed for QSocketNotifier KComponentData componentData("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) { kDebug(7105); 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() { kDebug(7105); 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) memcpy(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) memcpy(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 = 0; 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); kDebug(7105) << "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 { kDebug(7105) << "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\"", 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.toAscii(),""); kDebug(7105) << "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("auth") || metaData("auth") == "APOP"; m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; if (!pop3_open()) { kDebug(7105) << "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 = ""; opened = false; } int POP3Protocol::loginAPOP( char *challenge, KIO::AuthInfo &ai ) { char buf[512]; QString apop_string = QString::fromLatin1("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)); KMD5 ctx; kDebug(7105) << "APOP challenge: " << challenge; // Generate digest ctx.update(challenge, strlen(challenge)); ctx.update(m_sPass.toLatin1() ); // Genenerate APOP command apop_string.append(" "); apop_string.append(ctx.hexDigest()); if (command(apop_string.toLocal8Bit(), buf, sizeof(buf)) == Ok) { return 0; } kDebug(7105) << "Could not login via APOP. Falling back to USER/PASS"; closeConnection(); if (metaData("auth") == "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 ) { kDebug(7105); 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 ) { kDebug(7105) << "SASL_INTERACT id: " << interact->id; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug(7105) << "SASL_CB_[USER|AUTHNAME]: " << m_sUser; interact->result = strdup( m_sUser.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug(7105) << "SASL_CB_PASS: [hidden] "; interact->result = strdup( m_sPass.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = NULL; 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 = QString::fromLatin1("AUTH"); int result; sasl_conn_t *conn = NULL; sasl_interact_t *client_interact = NULL; const char *out = NULL; uint outlen; const char *mechusing = NULL; Resp resp; result = sasl_client_new( "pop", m_sServer.toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kDebug(7105) << "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("sasl") || command(sasl_buffer.toLocal8Bit()) == Ok ) { QStringList sasl_list; if (hasMetaData("sasl")) { sasl_list.append(metaData("sasl").toLatin1()); } 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 (strcmp(buf, ".\r\n") == 0) { + 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(buf); } do { result = sasl_client_start(conn, sasl_list.join(" ").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 ) { kDebug(7105) << "sasl_client_start failed with: " << result; SASLERROR sasl_dispose( &conn ); return -1; } kDebug(7105) << "Preferred authentication method is " << mechusing << "."; QByteArray msg,tmp; QString firstCommand = "AUTH " + QString::fromLatin1( mechusing ); msg = QByteArray::fromRawData( out, outlen ).toBase64(); if ( !msg.isEmpty() ) { firstCommand += ' '; 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() ? 0 : 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 ) { kDebug(7105) << "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 ) { kDebug(7105) << "SASL authenticated"; m_sOldUser = m_sUser; m_sOldPass = m_sPass; return 0; } if (metaData("auth") == "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", mechusing, mechusing, m_sError)); return -1; } } if (metaData("auth") == "SASL") { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server does not support SASL.\n" "Choose a different authentication method.")); 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 = QString::fromLatin1("USER "); one_string.append( m_sUser ); if ( command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok ) { kDebug(7105) << "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 = QString::fromLatin1("PASS "); one_string.append(m_sPass); if ( command(one_string.toLocal8Bit(), buf, sizeof(buf)) != Ok ) { kDebug(7105) << "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; } kDebug(7105) << "USER/PASS login succeeded"; return true; } bool POP3Protocol::pop3_open() { kDebug(7105); char *greeting_buf; if ((m_iOldPort == m_iPort) && (m_sOldServer == m_sServer) && (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { kDebug(7105) << "Reusing old connection"; return true; } do { closeConnection(); if (!connectToHost((isAutoSsl() ? "pop3s" : "pop3"), m_sServer.toLatin1(), 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", 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(greeting_buf); delete[]greeting_buf; if (greeting.length() > 0) { greeting.truncate(greeting.length() - 2); } // Does the server support APOP? QString apop_cmd; QRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", Qt::CaseInsensitive); kDebug(7105) << "greeting: " << greeting; int apop_pos = greeting.indexOf(re); supports_apop = (bool) (apop_pos != -1); if (metaData("nologin") == "on") return true; if (metaData("auth") == "APOP" && !supports_apop) { error(ERR_COULD_NOT_LOGIN, i18n("Your POP3 server does not support APOP.\n" "Choose a different authentication method.")); closeConnection(); return false; } m_iOldPort = m_iPort; m_sOldServer = m_sServer; // Try to go into TLS mode if ((metaData("tls") == "on" /*### || (canUseTLS() && metaData("tls") != "off")*/) && command("STLS") == Ok ) { if (startSsl()) { kDebug(7105) << "TLS mode has been enabled."; } else { kDebug(7105) << "TLS mode setup has failed. Aborting." << endl; error(ERR_COULD_NOT_CONNECT, i18n("Your POP3 server claims to " "support TLS but negotiation " "was unsuccessful. You can " "disable TLS in KDE using the " "crypto settings module.")); closeConnection(); return false; } } else if (metaData("tls") == "on") { error(ERR_COULD_NOT_CONNECT, i18n("Your POP3 server does not support TLS. Disable " "TLS, if you want to connect without encryption.")); 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 ) { kDebug(7105) << "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 ) { kDebug(7105) << "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 kDebug(7105) << "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 KUrl & 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("pipelining") == "on") ? MAX_COMMANDS : 1; if (path.at(0) == '/') path.remove(0, 1); if (path.isEmpty()) { kDebug(7105) << "We should be a dir!!"; error(ERR_IS_DIRECTORY, url.url()); m_cmd = CMD_NONE; return; } if (((path.indexOf('/') == -1) && (path != "index") && (path != "uidl") && (path != "commit"))) { error(ERR_MALFORMED_URL, url.url()); m_cmd = CMD_NONE; return; } cmd = path.left(path.indexOf('/')); path.remove(0, path.indexOf('/') + 1); if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } if ((cmd == "index") || (cmd == "uidl")) { unsigned long size = 0; bool result; if (cmd == "index") { result = ( command("LIST") == Ok ); } else { result = ( command("UIDL") == Ok ); } /* LIST +OK Mailbox scan listing follows 1 2979 2 1348 . */ if (result) { 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 (strcmp(buf, ".\r\n") == 0) { + 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); } } kDebug(7105) << "Finishing up list"; data(QByteArray()); finished(); } else if (cmd == "remove") { const QStringList waitingCommands = path.split(','); int activeCommands = 0; QStringList::ConstIterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(("DELE " + *it).toLatin1()); activeCommands++; it++; } getResponse(buf, sizeof(buf) - 1); activeCommands--; } finished(); m_cmd = CMD_NONE; } else if (cmd == "download" || cmd == "headers") { const QStringList waitingCommands = path.split(',', QString::SkipEmptyParts); bool noProgress = (metaData("progress") == "off" || waitingCommands.count() > 1); int p_size = 0; unsigned int msg_len = 0; QString list_cmd("LIST "); list_cmd += path; memset(buf, 0, sizeof(buf)); if ( !noProgress ) { if ( command(list_cmd.toLatin1(), buf, sizeof(buf) - 1) == Ok ) { list_cmd = buf; // We need a space, otherwise we got an invalid reply if (!list_cmd.indexOf(" ")) { kDebug(7105) << "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(" ") + 1); msg_len = list_cmd.toUInt(&ok); if (!ok) { kDebug(7105) << "LIST command needs to return a number? :" << list_cmd << ":"; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else { closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } int activeCommands = 0; QStringList::ConstIterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(((cmd == "headers") ? "TOP " + *it + " 0" : "RETR " + *it).toLatin1()); activeCommands++; it++; } if ( getResponse(buf, sizeof(buf) - 1) == Ok ) { activeCommands--; mimeType("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("message complete"); } else { kDebug(7105) << "Could not login. Bad RETR Sorry"; closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } kDebug(7105) << "Finishing up"; data(QByteArray()); finished(); } else if ((cmd == "uid") || (cmd == "list")) { QString qbuf; (void) path.toInt(&ok); if (!ok) { return; // We fscking need a number! } if (cmd == "uid") { path.prepend("UIDL "); } else { path.prepend("LIST "); } memset(buf, 0, sizeof(buf)); if ( command(path.toAscii(), buf, sizeof(buf) - 1) == Ok ) { const int len = strlen(buf); mimeType("text/plain"); totalSize(len); data(QByteArray::fromRawData(buf, len)); processedSize(len); kDebug(7105) << buf; kDebug(7105) << "Finishing up uid"; data(QByteArray()); finished(); } else { closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else if (cmd == "commit") { kDebug(7105) << "Issued QUIT"; closeConnection(); finished(); m_cmd = CMD_NONE; return; } } void POP3Protocol::listDir(const KUrl &) { bool isINT; int num_messages = 0; QByteArray q_buf(MAX_RESPONSE_LEN, 0); // Try and open a connection if (!pop3_open()) { kDebug(7105) << "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; } kDebug(7105) << "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 = "Message %1"; entry.insert(KIO::UDSEntry::UDS_NAME, fname.arg(i + 1)); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("text/plain")); KUrl uds_url; if (isAutoSsl()) { uds_url.setProtocol("pop3s"); } else { uds_url.setProtocol("pop3"); } uds_url.setUser(m_sUser); uds_url.setPass(m_sPass); uds_url.setHost(m_sServer); uds_url.setPath(QString::fromLatin1("/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, false); entry.clear(); } listEntry(entry, true); // ready finished(); } void POP3Protocol::stat(const KUrl & url) { QString _path = url.path(); if (_path.at(0) == '/') _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, QString::fromLatin1("message/rfc822")); // TODO: maybe get the size of the message? statEntry(entry); finished(); } void POP3Protocol::del(const KUrl & url, bool /*isfile */ ) { QString invalidURI; bool isInt; if (!pop3_open()) { kDebug(7105) << "pop3_open failed"; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } QString _path = url.path(); if (_path.at(0) == '/') { _path.remove(0, 1); } _path.toUInt(&isInt); if (!isInt) { invalidURI = _path; } else { _path.prepend("DELE "); if ( command(_path.toAscii()) != Ok ) { invalidURI = _path; } } kDebug(7105) << "Path:" << _path; finished(); }