/* * 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. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "pop3.h" #define GREETING_BUF_LEN 1024 #define MAX_RESPONSE_LEN 512 #define MAX_COMMANDS 10 #define POP3_DEBUG kdDebug(7105) extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } using namespace TDEIO; #ifdef HAVE_LIBSASL2 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 } }; #endif int kdemain(int argc, char **argv) { if (argc != 4) { POP3_DEBUG << "Usage: tdeio_pop3 protocol domain-socket1 domain-socket2" << endl; return -1; } #ifdef HAVE_LIBSASL2 if ( sasl_client_init( NULL ) != SASL_OK ) { fprintf(stderr, "SASL library initialization failed!\n"); return -1; } #endif TDEInstance instance("tdeio_pop3"); POP3Protocol *slave; // Are we looking to use SSL? 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; #ifdef HAVE_LIBSASL2 sasl_done(); #endif return 0; } POP3Protocol::POP3Protocol(const TQCString & pool, const TQCString & app, bool isSSL) : TCPSlaveBase((isSSL ? 995 : 110), (isSSL ? "pop3s" : "pop3"), pool, app, isSSL) { POP3_DEBUG << "POP3Protocol::POP3Protocol()" << endl; m_bIsSSL = isSSL; 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() { POP3_DEBUG << "POP3Protocol::~POP3Protocol()" << endl; closeConnection(); } void POP3Protocol::setHost(const TQString & _host, int _port, const TQString & _user, const TQString & _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(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, const char *cmd) { 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); // 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), TQMIN(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), TQMIN(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); } TQString command = TQString::fromLatin1(cmd); TQString serverMsg = TQString::fromLatin1(buf).mid(5).stripWhiteSpace(); if (command.left(4) == "PASS") { command = i18n("PASS "); } m_sError = i18n("The server said: \"%1\"").arg(serverMsg); delete[]buf; return Err; } else if (strncmp(buf, "+ ", 2) == 0) { if (r_buf && r_len) { memcpy(r_buf, buf + 2, TQMIN(r_len, recv_len - 4)); r_buf[TQMIN(r_len - 1, recv_len - 4)] = '\0'; } delete[]buf; return Cont; } else { POP3_DEBUG << "Invalid POP3 response received!" << endl; if (r_buf && r_len) { memcpy(r_buf, buf, TQMIN(r_len, recv_len)); } if (!buf || !*buf) { m_sError = i18n("The server terminated the connection."); } else { m_sError = i18n("Invalid response from server:\n\"%1\"").arg(buf); } delete[]buf; return Invalid; } } bool POP3Protocol::sendCommand(const char *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 (!isConnectionValid()) return false; char *cmdrn = new char[strlen(cmd) + 3]; sprintf(cmdrn, "%s\r\n", (cmd) ? cmd : ""); if (write(cmdrn, strlen(cmdrn)) != static_cast < ssize_t > (strlen(cmdrn))) { m_sError = i18n("Could not send to server.\n"); delete[]cmdrn; return false; } delete[]cmdrn; return true; } POP3Protocol::Resp POP3Protocol::command(const char *cmd, char *recv_buf, unsigned int len) { sendCommand(cmd); return getResponse(recv_buf, len, cmd); } void POP3Protocol::openConnection() { m_try_apop = !hasMetaData("auth") || metaData("auth") == "APOP"; m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; if (!pop3_open()) { POP3_DEBUG << "pop3_open failed" << endl; } 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"); closeDescriptor(); readBufferLen = 0; m_sOldUser = m_sOldPass = m_sOldServer = ""; opened = false; } int POP3Protocol::loginAPOP( char *challenge, TDEIO::AuthInfo &ai ) { char buf[512]; TQString apop_string = TQString::fromLatin1("APOP "); if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPassDlg(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; POP3_DEBUG << "APOP challenge: " << challenge << endl; // Generate digest ctx.update(challenge, strlen(challenge)); ctx.update(m_sPass.latin1() ); // Genenerate APOP command apop_string.append(" "); apop_string.append(ctx.hexDigest()); if (command(apop_string.local8Bit(), buf, sizeof(buf)) == Ok) { return 0; } POP3_DEBUG << "Couldn't login via APOP. Falling back to USER/PASS" << endl; 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"). arg(m_sServer). arg(m_sError)); return -1; } return 1; } bool POP3Protocol::saslInteract( void *in, AuthInfo &ai ) { #ifdef HAVE_LIBSASL2 POP3_DEBUG << "sasl_interact" << endl; 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 (!openPassDlg(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 ) { POP3_DEBUG << "SASL_INTERACT id: " << interact->id << endl; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: POP3_DEBUG << "SASL_CB_[USER|AUTHNAME]: " << m_sUser << endl; interact->result = strdup( m_sUser.utf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: POP3_DEBUG << "SASL_CB_PASS: [hidden] " << endl; interact->result = strdup( m_sPass.utf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = NULL; interact->len = 0; break; } interact++; } return true; #else return false; #endif } #define SASLERROR closeConnection(); \ error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occured during authentication: %1").arg \ ( TQString::fromUtf8( sasl_errdetail( conn ) ))); \ int POP3Protocol::loginSASL( TDEIO::AuthInfo &ai ) { #ifdef HAVE_LIBSASL2 char buf[512]; TQString sasl_buffer = TQString::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.latin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { POP3_DEBUG << "sasl_client_new failed with: " << result << endl; 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.local8Bit()) == Ok ) { TQStringList sasl_list; if (hasMetaData("sasl")) { sasl_list.append(metaData("sasl").latin1()); } 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) { 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(" ").latin1(), &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 ) { POP3_DEBUG << "sasl_client_start failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return -1; } POP3_DEBUG << "Preferred authentication method is " << mechusing << "." << endl; TQByteArray challenge, tmp; TQString firstCommand = "AUTH " + TQString::fromLatin1( mechusing ); challenge.setRawData( out, outlen ); KCodecs::base64Encode( challenge, tmp ); challenge.resetRawData( out, outlen ); if ( !tmp.isEmpty() ) { firstCommand += " "; firstCommand += TQString::fromLatin1( tmp.data(), tmp.size() ); } challenge.resize( 2049 ); resp = command( firstCommand.latin1(), challenge.data(), 2049 ); while( resp == Cont ) { challenge.resize(challenge.find(0)); // POP3_DEBUG << "S: " << TQCString(challenge.data(),challenge.size()+1) << endl; KCodecs::base64Decode( challenge, tmp ); do { result = sasl_client_step(conn, tmp.isEmpty() ? 0 : tmp.data(), tmp.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 ) { POP3_DEBUG << "sasl_client_step failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return -1; } challenge.setRawData( out, outlen ); KCodecs::base64Encode( challenge, tmp ); challenge.resetRawData( out, outlen ); // POP3_DEBUG << "C: " << TQCString(tmp.data(),tmp.size()+1) << endl; tmp.resize(tmp.size()+1); tmp[tmp.size()-1] = '\0'; challenge.resize(2049); resp = command( tmp.data(), challenge.data(), 2049 ); } sasl_dispose( &conn ); if ( resp == Ok ) { POP3_DEBUG << "SASL authenticated" << endl; 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"). arg(mechusing).arg(mechusing).arg(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; #else if (metaData("auth") == "SASL") { closeConnection(); error(ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into tdeio_pop3.")); return -1; } return 1; //if SASL not explicitly required, try another method (USER/PASS) #endif } bool POP3Protocol::loginPASS( TDEIO::AuthInfo &ai ) { char buf[512]; if (m_sUser.isEmpty() || m_sPass.isEmpty()) { // Prompt for usernames if (!openPassDlg(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; TQString one_string = TQString::fromLatin1("USER "); one_string.append( m_sUser ); if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { POP3_DEBUG << "Couldn't login. Bad username Sorry" << endl; m_sError = i18n("Could not login to %1.\n\n").arg(m_sServer) + m_sError; error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } one_string = TQString::fromLatin1("PASS "); one_string.append(m_sPass); if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { POP3_DEBUG << "Couldn't login. Bad password Sorry." << endl; m_sError = i18n ("Could not login to %1. The password may be wrong.\n\n%2"). arg(m_sServer).arg(m_sError); error(ERR_COULD_NOT_LOGIN, m_sError); closeConnection(); return false; } POP3_DEBUG << "USER/PASS login succeeded" << endl; return true; } bool POP3Protocol::pop3_open() { POP3_DEBUG << "pop3_open()" << endl; char *greeting_buf; if ((m_iOldPort == port(m_iPort)) && (m_sOldServer == m_sServer) && (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { POP3_DEBUG << "Reusing old connection" << endl; return true; } do { closeConnection(); if (!connectToHost(m_sServer.ascii(), 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").arg(m_sServer) + ((!greeting_buf || !*greeting_buf) ? i18n("The server terminated the connection immediately.") : i18n("Server does not respond properly:\n%1\n"). arg(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 } TQCString greeting(greeting_buf); delete[]greeting_buf; if (greeting.length() > 0) { greeting.truncate(greeting.length() - 2); } // Does the server support APOP? TQString apop_cmd; TQRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", false); POP3_DEBUG << "greeting: " << greeting << endl; int apop_pos = greeting.find(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 ) { int tlsrc = startTLS(); if (tlsrc == 1) { POP3_DEBUG << "TLS mode has been enabled." << endl; } else { if (tlsrc != -3) { POP3_DEBUG << "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; } TDEIO::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 ) { POP3_DEBUG << "Trying APOP" << endl; int retval = loginAPOP( greeting.data() + apop_pos, authInfo ); switch ( retval ) { case 0: return true; case -1: return false; default: m_try_apop = false; } } else if ( m_try_sasl ) { POP3_DEBUG << "Trying SASL" << endl; 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 POP3_DEBUG << "Trying USER/PASS" << endl; return loginPASS( authInfo ); } } while ( true ); } size_t POP3Protocol::realGetSize(unsigned int msg_num) { char *buf; TQCString cmd; size_t ret = 0; buf = new char[MAX_RESPONSE_LEN]; memset(buf, 0, MAX_RESPONSE_LEN); cmd.sprintf("LIST %u", msg_num); if ( command(cmd.data(), buf, MAX_RESPONSE_LEN) != Ok ) { delete[]buf; return 0; } else { cmd = buf; cmd.remove(0, cmd.find(" ")); ret = cmd.toLong(); } delete[]buf; return ret; } void POP3Protocol::special(const TQByteArray & aData) { TQString result; char buf[MAX_PACKET_LEN]; TQDataStream stream(aData, IO_ReadOnly); int tmp; stream >> tmp; if (tmp != 'c') return; for (int i = 0; i < 2; i++) { TQCString cmd = (i) ? "AUTH" : "CAPA"; if ( command(cmd) != Ok ) continue; while (true) { myReadLine(buf, MAX_PACKET_LEN - 1); if (qstrcmp(buf, ".\r\n") == 0) break; result += " " + TQString(buf).left(strlen(buf) - 2) .replace(" ", "-"); } } if (supports_apop) result += " APOP"; result = result.mid(1); infoMessage(result); finished(); } 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]; TQByteArray array; TQString cmd, path = url.path(); int maxCommands = (metaData("pipelining") == "on") ? MAX_COMMANDS : 1; if (path.at(0) == '/') path.remove(0, 1); if (path.isEmpty()) { POP3_DEBUG << "We should be a dir!!" << endl; error(ERR_IS_DIRECTORY, url.url()); m_cmd = CMD_NONE; return; } if (((path.find('/') == -1) && (path != "index") && (path != "uidl") && (path != "commit"))) { error(ERR_MALFORMED_URL, url.url()); m_cmd = CMD_NONE; return; } cmd = path.left(path.find('/')); path.remove(0, path.find('/') + 1); if (!pop3_open()) { POP3_DEBUG << "pop3_open failed" << endl; 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) { break; // End of data } // sanders, changed -2 to -1 below int bufStrLen = strlen(buf); buf[bufStrLen - 2] = '\0'; size += bufStrLen; array.setRawData(buf, bufStrLen); data(array); array.resetRawData(buf, bufStrLen); totalSize(size); } } POP3_DEBUG << "Finishing up list" << endl; data(TQByteArray()); finished(); } else if (cmd == "remove") { TQStringList waitingCommands = TQStringList::split(',', path); int activeCommands = 0; TQStringList::Iterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(("DELE " + *it).latin1()); activeCommands++; it++; } getResponse(buf, sizeof(buf) - 1, ""); activeCommands--; } finished(); m_cmd = CMD_NONE; } else if (cmd == "download" || cmd == "headers") { TQStringList waitingCommands = TQStringList::split(',', path); bool noProgress = (metaData("progress") == "off" || waitingCommands.count() > 1); int p_size = 0; unsigned int msg_len = 0; TQString list_cmd("LIST "); list_cmd += path; memset(buf, 0, sizeof(buf)); if ( !noProgress ) { if ( command(list_cmd.ascii(), buf, sizeof(buf) - 1) == Ok ) { list_cmd = buf; // We need a space, otherwise we got an invalid reply if (!list_cmd.find(" ")) { POP3_DEBUG << "List command needs a space? " << list_cmd << endl; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } list_cmd.remove(0, list_cmd.find(" ") + 1); msg_len = list_cmd.toUInt(&ok); if (!ok) { POP3_DEBUG << "LIST command needs to return a number? :" << list_cmd << ":" << endl; closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else { closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } int activeCommands = 0; TQStringList::Iterator it = waitingCommands.begin(); while (it != waitingCommands.end() || activeCommands > 0) { while (activeCommands < maxCommands && it != waitingCommands.end()) { sendCommand(((cmd == "headers") ? "TOP " + *it + " 0" : "RETR " + *it).latin1()); 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 (isConnectionValid()) 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) { array.setRawData(destbuf, buf2 - destbuf); data(array); array.resetRawData(destbuf, buf2 - destbuf); } if (endOfMail) break; if (!noProgress) { p_size += readlen; processedSize(p_size); } } infoMessage("message complete"); } else { POP3_DEBUG << "Couldn't login. Bad RETR Sorry" << endl; closeConnection(); error(ERR_COULD_NOT_READ, m_sError); return; } } POP3_DEBUG << "Finishing up" << endl; data(TQByteArray()); finished(); } else if ((cmd == "uid") || (cmd == "list")) { TQString 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.ascii(), buf, sizeof(buf) - 1) == Ok ) { const int len = strlen(buf); mimeType("text/plain"); totalSize(len); array.setRawData(buf, len); data(array); array.resetRawData(buf, len); processedSize(len); POP3_DEBUG << buf << endl; POP3_DEBUG << "Finishing up uid" << endl; data(TQByteArray()); finished(); } else { closeConnection(); error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); return; } } else if (cmd == "commit") { POP3_DEBUG << "Issued QUIT" << endl; closeConnection(); finished(); m_cmd = CMD_NONE; return; } } void POP3Protocol::listDir(const KURL &) { bool isINT; int num_messages = 0; char buf[MAX_RESPONSE_LEN]; TQCString q_buf; // Try and open a connection if (!pop3_open()) { POP3_DEBUG << "pop3_open failed" << endl; 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 memset(buf, 0, MAX_RESPONSE_LEN); if ( command("STAT", buf, MAX_RESPONSE_LEN) != Ok ) { error(ERR_INTERNAL, "??"); return; } POP3_DEBUG << "The stat buf is :" << buf << ":" << endl; q_buf = buf; if (q_buf.find(" ") == -1) { error(ERR_INTERNAL, "Invalid POP3 response, we should have at least one space!"); closeConnection(); return; } q_buf.remove(q_buf.find(" "), q_buf.length()); num_messages = q_buf.toUInt(&isINT); if (!isINT) { error(ERR_INTERNAL, "Invalid POP3 STAT response!"); closeConnection(); return; } UDSEntry entry; UDSAtom atom; TQString fname; for (int i = 0; i < num_messages; i++) { fname = "Message %1"; atom.m_uds = UDS_NAME; atom.m_long = 0; atom.m_str = fname.arg(i + 1); entry.append(atom); atom.m_uds = UDS_MIME_TYPE; atom.m_long = 0; atom.m_str = "text/plain"; entry.append(atom); POP3_DEBUG << "Mimetype is " << atom.m_str.ascii() << endl; atom.m_uds = UDS_URL; KURL uds_url; if (m_bIsSSL) { 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(TQString::fromLatin1("/download/%1").arg(i + 1)); atom.m_str = uds_url.url(); atom.m_long = 0; entry.append(atom); atom.m_uds = UDS_FILE_TYPE; atom.m_str = ""; atom.m_long = S_IFREG; entry.append(atom); atom.m_uds = UDS_SIZE; atom.m_str = ""; atom.m_long = realGetSize(i + 1); entry.append(atom); atom.m_uds = TDEIO::UDS_ACCESS; atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR; entry.append (atom); listEntry(entry, false); entry.clear(); } listEntry(entry, true); // ready finished(); } void POP3Protocol::stat(const KURL & url) { TQString _path = url.path(); if (_path.at(0) == '/') _path.remove(0, 1); UDSEntry entry; UDSAtom atom; atom.m_uds = UDS_NAME; atom.m_str = _path; entry.append(atom); atom.m_uds = UDS_FILE_TYPE; atom.m_str = ""; atom.m_long = S_IFREG; entry.append(atom); atom.m_uds = UDS_MIME_TYPE; atom.m_str = "message/rfc822"; entry.append(atom); // TODO: maybe get the size of the message? statEntry(entry); finished(); } void POP3Protocol::del(const KURL & url, bool /*isfile */ ) { TQString invalidURI = TQString::null; bool isInt; if (!pop3_open()) { POP3_DEBUG << "pop3_open failed" << endl; error(ERR_COULD_NOT_CONNECT, m_sServer); return; } TQString _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.ascii()) != Ok ) { invalidURI = _path; } } POP3_DEBUG << "POP3Protocol::del " << _path << endl; finished(); }