diff options
Diffstat (limited to 'kioslave/pop3')
-rw-r--r-- | kioslave/pop3/Makefile.am | 17 | ||||
-rw-r--r-- | kioslave/pop3/pop3.cc | 1263 | ||||
-rw-r--r-- | kioslave/pop3/pop3.h | 133 | ||||
-rw-r--r-- | kioslave/pop3/pop3.protocol | 16 | ||||
-rw-r--r-- | kioslave/pop3/pop3s.protocol | 16 |
5 files changed, 1445 insertions, 0 deletions
diff --git a/kioslave/pop3/Makefile.am b/kioslave/pop3/Makefile.am new file mode 100644 index 000000000..03ce04104 --- /dev/null +++ b/kioslave/pop3/Makefile.am @@ -0,0 +1,17 @@ +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_pop3.la + +kio_pop3_la_SOURCES = pop3.cc +kio_pop3_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) +kio_pop3_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = pop3.h + +kdelnk_DATA = pop3.protocol pop3s.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cc -o $(podir)/kio_pop3.pot diff --git a/kioslave/pop3/pop3.cc b/kioslave/pop3/pop3.cc new file mode 100644 index 000000000..06266d7a4 --- /dev/null +++ b/kioslave/pop3/pop3.cc @@ -0,0 +1,1263 @@ +/* + * Copyright (c) 1999-2001 Alex Zepeda + * Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org> + * 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 <config.h> +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include <qcstring.h> +#include <qglobal.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kprotocolmanager.h> +#include <ksock.h> + +#include <kio/connection.h> +#include <kio/slaveinterface.h> +#include <kio/passdlg.h> +#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 KIO; + +#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: kio_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 + + KInstance instance("kio_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 QCString & pool, const QCString & 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 QString & _host, int _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(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, + 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), + 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 command = QString::fromLatin1(cmd); + QString serverMsg = QString::fromLatin1(buf).mid(5).stripWhiteSpace(); + + if (command.left(4) == "PASS") { + command = i18n("PASS <your password>"); + } + + 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, QMIN(r_len, recv_len - 4)); + r_buf[QMIN(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, QMIN(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, KIO::AuthInfo &ai ) +{ + char buf[512]; + + QString apop_string = QString::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 \ +( QString::fromUtf8( sasl_errdetail( conn ) ))); \ + +int POP3Protocol::loginSASL( KIO::AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + 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.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 ) { + + QStringList 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; + + QByteArray challenge, tmp; + + QString firstCommand = "AUTH " + QString::fromLatin1( mechusing ); + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); + if ( !tmp.isEmpty() ) { + firstCommand += " "; + firstCommand += QString::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: " << QCString(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: " << QCString(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 kio_pop3.")); + return -1; + } + return 1; //if SASL not explicitly required, try another method (USER/PASS) +#endif +} + +bool POP3Protocol::loginPASS( KIO::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; + + QString one_string = QString::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 = QString::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 + } + QCString 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\\.\\-_]+>$", 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; + } + + 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 ) { + 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; + QCString 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 QByteArray & aData) +{ + QString result; + char buf[MAX_PACKET_LEN]; + QDataStream stream(aData, IO_ReadOnly); + int tmp; + stream >> tmp; + + if (tmp != 'c') + return; + + for (int i = 0; i < 2; i++) { + QCString 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 += " " + QString(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]; + QByteArray array; + QString 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(QByteArray()); + finished(); + } else if (cmd == "remove") { + QStringList waitingCommands = QStringList::split(',', path); + int activeCommands = 0; + QStringList::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") { + QStringList waitingCommands = QStringList::split(',', path); + 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.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; + QStringList::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(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.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(QByteArray()); + 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]; + QCString 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; + QString 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(QString::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 = KIO::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) +{ + QString _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 */ ) +{ + QString invalidURI = QString::null; + bool isInt; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + 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.ascii()) != Ok ) { + invalidURI = _path; + } + } + + POP3_DEBUG << "POP3Protocol::del " << _path << endl; + finished(); +} diff --git a/kioslave/pop3/pop3.h b/kioslave/pop3/pop3.h new file mode 100644 index 000000000..94114f675 --- /dev/null +++ b/kioslave/pop3/pop3.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 1999,2000 Alex Zepeda + * 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. + * + */ + +#ifndef _POP3_H +#define _POP3_H + +#include <sys/types.h> +#include <sys/time.h> + +#include <stdio.h> + +#include <qstring.h> + +#include <kio/tcpslavebase.h> + +#define MAX_PACKET_LEN 4096 + +class POP3Protocol:public KIO::TCPSlaveBase { +public: + POP3Protocol(const QCString & pool, const QCString & app, bool SSL); + virtual ~ POP3Protocol(); + + virtual void setHost(const QString & host, int port, + const QString & user, const QString & pass); + + virtual void special(const QByteArray & aData); + virtual void get(const KURL & url); + virtual void stat(const KURL & url); + virtual void del(const KURL & url, bool isfile); + virtual void listDir(const KURL & url); + +protected: + + ssize_t myRead(void *data, ssize_t len); + ssize_t myReadLine(char *data, ssize_t len); + + /** + * This returns the size of a message as a long integer. + * This is useful as an internal member, because the "other" + * getSize command will emit a signal, which would be harder + * to trap when doing something like listing a directory. + */ + size_t realGetSize(unsigned int msg_num); + + /** + * Send a command to the server. Using this function, getResponse + * has to be called separately. + */ + bool sendCommand(const char *cmd); + + enum Resp{Err, Ok, Cont, Invalid}; + /** + * Send a command to the server, and wait for the one-line-status + * reply via getResponse. Similar rules apply. If no buffer is + * specified, no data is passed back. + */ + Resp command(const char *buf, char *r_buf = 0, unsigned int r_len = 0); + + /** + * All POP3 commands will generate a response. Each response will + * either be prefixed with a "+OK " or a "-ERR ". The getResponse + * function will wait until there's data to be read, and then read in + * the first line (the response), and copy the response sans +OK/-ERR + * into a buffer (up to len bytes) if one was passed to it. + */ + Resp getResponse(char *buf, unsigned int len, const char *command); + + /** Call int pop3_open() and report an error, if if fails */ + void openConnection(); + + /** + * Attempt to properly shut down the POP3 connection by sending + * "QUIT\r\n" before closing the socket. + */ + void closeConnection(); + + /** + * Attempt to initiate a POP3 connection via a TCP socket. If no port + * is passed, port 110 is assumed, if no user || password is + * specified, the user is prompted for them. + */ + bool pop3_open(); + /** + * Authenticate via APOP + */ + int loginAPOP( char *challenge, KIO::AuthInfo &ai ); + + bool saslInteract( void *in, KIO::AuthInfo &ai ); + /** + * Authenticate via SASL + */ + int loginSASL( KIO::AuthInfo &ai ); + /** + * Authenticate via traditional USER/PASS + */ + bool loginPASS( KIO::AuthInfo &ai ); + + int m_cmd; + unsigned short int m_iOldPort; + struct timeval m_tTimeout; + QString m_sOldServer, m_sOldPass, m_sOldUser; + QString m_sServer, m_sPass, m_sUser; + bool m_try_apop, m_try_sasl, opened, supports_apop; + QString m_sError; + char readBuffer[MAX_PACKET_LEN]; + ssize_t readBufferLen; +}; + +#endif diff --git a/kioslave/pop3/pop3.protocol b/kioslave/pop3/pop3.protocol new file mode 100644 index 000000000..ba20ae70d --- /dev/null +++ b/kioslave/pop3/pop3.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_pop3 +protocol=pop3 +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=false +deleting=true +source=true +makedir=false +linking=false +moving=false +DocPath=kioslave/pop3.html +Icon=folder_inbox diff --git a/kioslave/pop3/pop3s.protocol b/kioslave/pop3/pop3s.protocol new file mode 100644 index 000000000..2f9f848c2 --- /dev/null +++ b/kioslave/pop3/pop3s.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_pop3 +protocol=pop3s +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=false +deleting=true +source=true +makedir=false +linking=false +moving=false +DocPath=kioslave/pop3s.html +Icon=folder_inbox |