diff --git a/kioslave/pop3/CMakeLists.txt b/kioslave/pop3/CMakeLists.txt index 9d225de00..997d47bd6 100644 --- a/kioslave/pop3/CMakeLists.txt +++ b/kioslave/pop3/CMakeLists.txt @@ -1,56 +1,63 @@ kde4_header() project(kioslave-pop3) -macro_optional_find_package(Sasl2) - set(pop3_optional_includes) set(pop3_optional_libs) -IF(SASL2_FOUND) - set(pop3_optional_includes ${pop3_optional_includes} ${SASL2_INCLUDE_DIR}) - set(pop3_optional_libs ${pop3_optional_libs} ${SASL2_LIBRARIES}) -ENDIF(SASL2_FOUND) + +macro_optional_find_package(Sasl2) + +if(SASL2_FOUND) + set(pop3_optional_includes ${pop3_optional_includes} ${SASL2_INCLUDE_DIR}) + set(pop3_optional_libs ${pop3_optional_libs} ${SASL2_LIBRARIES}) + set(HAVE_LIBSASL2 1) +else(SASL2_FOUND) + set(HAVE_LIBSASL2 0) +endif(SASL2_FOUND) + +configure_file(pop3-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/pop3-config.h ) + include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${pop3_optional_includes} ${KDE4_INCLUDES} ) ########### next target ############### set(kio_pop3_PART_SRCS pop3.cc ) kde4_automoc(${kio_pop3_PART_SRCS}) kde4_add_plugin(kio_pop3 ${kio_pop3_PART_SRCS}) kde4_install_libtool_file( ${PLUGIN_INSTALL_DIR} kio_pop3 ) target_link_libraries(kio_pop3 ${KDE4_KIO_LIBS} ${pop3_optional_libs}) install_targets(${LIB_INSTALL_DIR}/kde4 kio_pop3 ) ########### install files ############### install_files( ${SERVICES_INSTALL_DIR} FILES pop3.protocol pop3s.protocol ) kde4_footer() #original Makefile.am contents follow: #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-config.h.cmake b/kioslave/pop3/pop3-config.h.cmake new file mode 100644 index 000000000..bdbd66e95 --- /dev/null +++ b/kioslave/pop3/pop3-config.h.cmake @@ -0,0 +1,5 @@ +/* pop3-config.h. Generated by cmake from pop3-config.h.cmake */ + +/* Define if you have cyrus-sasl2 libraries */ +#cmakedefine HAVE_LIBSASL2 1 + diff --git a/kioslave/pop3/pop3.cc b/kioslave/pop3/pop3.cc index d8dbc6c86..4e393358d 100644 --- a/kioslave/pop3/pop3.cc +++ b/kioslave/pop3/pop3.cc @@ -1,1203 +1,1204 @@ /* * 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 +#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 "pop3.h" #define GREETING_BUF_LEN 1024 #define MAX_RESPONSE_LEN 512 #define MAX_COMMANDS 10 #define POP3_DEBUG kDebug(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_GETOPT, 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( callbacks ) != 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 QByteArray & pool, const QByteArray & 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((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); // 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\"").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 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 (!isConnectionValid()) return false; QByteArray cmdrn = cmd + "\r\n"; 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()) { 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.toLatin1() ); // Genenerate APOP command apop_string.append(" "); apop_string.append(ctx.hexDigest()); if (command(apop_string.toLocal8Bit(), 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.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: POP3_DEBUG << "SASL_CB_PASS: [hidden] " << endl; 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; #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.toLatin1(), 0, 0, NULL, 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.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) { 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 ) { POP3_DEBUG << "sasl_client_start failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return -1; } POP3_DEBUG << "Preferred authentication method is " << mechusing << "." << endl; 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(msg.indexOf((char)0)); // POP3_DEBUG << "S: " << QCString(challenge.data(),challenge.size()+1) << endl; 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 ) { POP3_DEBUG << "sasl_client_step failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return -1; } msg = QByteArray::fromRawData( out, outlen ).toBase64(); // POP3_DEBUG << "C: " << QCString(tmp.data(),tmp.size()+1) << endl; tmp.resize(2049); resp = command( msg, tmp.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.toLocal8Bit(), 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.toLocal8Bit(), 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(QString::number(m_iPort)).toInt()) && (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.toLatin1(), QString::number(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 } 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); POP3_DEBUG << "greeting: " << greeting << endl; 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 ) { 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.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 ) { 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; 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::special(const QByteArray & aData) { QString result; char buf[MAX_PACKET_LEN]; QDataStream stream(aData); int tmp; stream >> tmp; if (tmp != 'c') return; for (int i = 0; i < 2; i++) { QByteArray 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]; 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.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()) { 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; data(QByteArray::fromRawData(buf, bufStrLen)); totalSize(size); } } POP3_DEBUG << "Finishing up list" << endl; data(QByteArray()); finished(); } else if (cmd == "remove") { QStringList waitingCommands = path.split(','); int activeCommands = 0; QStringList::Iterator 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") { QStringList waitingCommands = path.split(','); 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(" ")) { 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.indexOf(" ") + 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).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 (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) { data(QByteArray::fromRawData(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.toAscii(), buf, sizeof(buf) - 1) == Ok ) { const int len = strlen(buf); mimeType("text/plain"); totalSize(len); data(QByteArray::fromRawData(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; QByteArray q_buf(MAX_RESPONSE_LEN, 0); // 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 if ( command("STAT", q_buf.data(), MAX_RESPONSE_LEN) != Ok ) { error(ERR_INTERNAL, "??"); return; } POP3_DEBUG << "The stat buf is :" << q_buf << ":" << endl; if (q_buf.indexOf(" ") == -1) { error(ERR_INTERNAL, "Invalid POP3 response, we 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, "Invalid POP3 STAT response!"); closeConnection(); return; } UDSEntry entry; QString fname; for (int i = 0; i < num_messages; i++) { fname = "Message %1"; entry.insert(UDS_NAME, fname.arg(i + 1)); entry.insert(UDS_MIME_TYPE, QString::fromLatin1("text/plain")); 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)); entry.insert(UDS_URL, uds_url.url()); entry.insert(UDS_FILE_TYPE, S_IFREG); entry.insert(UDS_SIZE, realGetSize(i + 1)); entry.insert(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(UDS_NAME, _path); entry.insert(UDS_FILE_TYPE, S_IFREG); entry.insert(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 = QString(); 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.toAscii()) != Ok ) { invalidURI = _path; } } POP3_DEBUG << "POP3Protocol::del " << _path << endl; finished(); } diff --git a/kioslave/smtp/CMakeLists.txt b/kioslave/smtp/CMakeLists.txt index 4886f86c2..e9ae884b8 100644 --- a/kioslave/smtp/CMakeLists.txt +++ b/kioslave/smtp/CMakeLists.txt @@ -1,138 +1,146 @@ kde4_header() project(kioslave-smtp) -macro_optional_find_package(Sasl2) set(smtp_optional_includes) set(smtp_optional_libs) -IF(SASL2_FOUND) - set(smtp_optional_includes ${smtp_optional_includes} ${SASL2_INCLUDE_DIR}) - set(smtp_optional_libs ${smtp_optional_libs} ${SASL2_LIBRARIES}) -ENDIF(SASL2_FOUND) + +macro_optional_find_package(Sasl2) + +if (SASL2_FOUND) + set(smtp_optional_includes ${smtp_optional_includes} ${SASL2_INCLUDE_DIR}) + set(smtp_optional_libs ${smtp_optional_libs} ${SASL2_LIBRARIES}) + set(HAVE_LIBSASL2 1) +else (SASL2_FOUND) + set(HAVE_LIBSASL2 0) +endif (SASL2_FOUND) + +configure_file(smtp-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/smtp-config.h ) + include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${smtp_optional_includes} ${KDE4_INCLUDES} ) ########### next target ############### set(kio_smtp_PART_SRCS smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc ) kde4_automoc(${kio_smtp_PART_SRCS}) kde4_add_plugin(kio_smtp ${kio_smtp_PART_SRCS}) kde4_install_libtool_file( ${PLUGIN_INSTALL_DIR} kio_smtp ) target_link_libraries(kio_smtp ${KDE4_KIO_LIBS} ${smtp_optional_libs}) install_targets(${LIB_INSTALL_DIR}/kde4 kio_smtp ) ########### next target ############### set(test_responseparser_SRCS test_responseparser.cc ) kde4_automoc(test_responseparser_SRCS}) if(KDE4_BUILD_TESTS) kde4_add_executable(test_responseparser ${test_responseparser_SRCS}) target_link_libraries(test_responseparser ${KDE4_KDECORE_LIBS} ) endif(KDE4_BUILD_TESTS) ########### next target ############### set(test_headergeneration_SRCS test_headergeneration.cc ) kde4_automoc(test_headergeneration_SRCS}) if(KDE4_BUILD_TESTS) kde4_add_executable(test_headergeneration ${test_headergeneration_SRCS}) target_link_libraries(test_headergeneration ${KDE4_KDECORE_LIBS} ) endif(KDE4_BUILD_TESTS) ########### next target ############### set(test_commands_SRCS test_commands.cc ) kde4_automoc(test_commands_SRCS}) if(KDE4_BUILD_TESTS) kde4_add_executable(test_commands ${test_commands_SRCS}) target_link_libraries(test_commands ${KDE4_KDECORE_LIBS} ) endif(KDE4_BUILD_TESTS) ########### next target ############### set(interactivesmtpserver_SRCS interactivesmtpserver.cc ) kde4_automoc(${interactivesmtpserver_SRCS}) if(KDE4_BUILD_TESTS) kde4_add_executable(interactivesmtpserver ${interactivesmtpserver_SRCS}) target_link_libraries(interactivesmtpserver ${KDE4_KDECORE_LIBS} ) endif(KDE4_BUILD_TESTS) ########### install files ############### install_files( ${SERVICES_INSTALL_DIR} FILES smtp.protocol smtps.protocol ) kde4_footer() #original Makefile.am contents follow: # #INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes) # #kde_module_LTLIBRARIES = kio_smtp.la # #kio_smtp_la_SOURCES = smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc #kio_smtp_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) #kio_smtp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) # #noinst_HEADERS = smtp.h request.h response.h capabilities.h command.h transactionstate.h # #kdelnk_DATA = smtp.protocol smtps.protocol #kdelnkdir = $(kde_servicesdir) # #TESTS = test_headergeneration test_responseparser test_commands # #check_PROGRAMS = $(TESTS) interactivesmtpserver # #test_headergeneration_SOURCES = test_headergeneration.cc #test_headergeneration_LDADD = $(LIB_KDECORE) #test_headergeneration_LDFLAGS = $(all_libraries) # #test_responseparser_SOURCES = test_responseparser.cc #test_responseparser_LDADD = $(LIB_KDECORE) #test_responseparser_LDFLAGS = $(all_libraries) # #test_commands_SOURCES = test_commands.cc #test_commands_LDADD = $(kio_smtp_la_LIBADD) #test_commands_LDFLAGS = $(all_libraries) # #interactivesmtpserver_SOURCES = interactivesmtpserver.cc #interactivesmtpserver_LDADD = $(LIB_QT) #interactivesmtpserver_LDFLAGS = $(all_libraries) #interactivesmtpserver_METASOURCES = AUTO # #messages: # $(XGETTEXT) *.cc -o $(podir)/kio_smtp.pot diff --git a/kioslave/smtp/command.cc b/kioslave/smtp/command.cc index 18647883d..a89ca25f9 100644 --- a/kioslave/smtp/command.cc +++ b/kioslave/smtp/command.cc @@ -1,587 +1,588 @@ /* -*- c++ -*- command.cc This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include +#include #include "command.h" #include "smtp.h" #include "response.h" #include "transactionstate.h" #include #include #include #include #include // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase #include namespace KioSMTP { // // Command (base class) // Command::Command( SMTPProtocol * smtp, int flags ) : mSMTP( smtp ), mComplete( false ), mNeedResponse( false ), mFlags( flags ) { assert( smtp ); } Command::~Command() {} bool Command::processResponse( const Response & r, TransactionState * ) { mComplete = true; mNeedResponse = false; return r.isOk(); } void Command::ungetCommandLine( const QByteArray &, TransactionState * ) { mComplete = false; } Command * Command::createSimpleCommand( int which, SMTPProtocol * smtp ) { switch ( which ) { case STARTTLS: return new StartTLSCommand( smtp ); case DATA: return new DataCommand( smtp ); case NOOP: return new NoopCommand( smtp ); case RSET: return new RsetCommand( smtp ); case QUIT: return new QuitCommand( smtp ); default: return 0; } } // // relay methods: // void Command::parseFeatures( const Response & r ) { mSMTP->parseFeatures( r ); } int Command::startTLS() { return mSMTP->startTLS(); } bool Command::usingSSL() const { return mSMTP->usingSSL(); } bool Command::usingTLS() const { return mSMTP->usingTLS(); } bool Command::haveCapability( const char * cap ) const { return mSMTP->haveCapability( cap ); } // // EHLO / HELO // QByteArray EHLOCommand::nextCommandLine( TransactionState * ) { mNeedResponse = true; mComplete = mEHLONotSupported; const char * cmd = mEHLONotSupported ? "HELO " : "EHLO " ; return cmd + KIDNA::toAsciiCString( mHostname ) + "\r\n"; } bool EHLOCommand::processResponse( const Response & r, TransactionState * ) { mNeedResponse = false; // "command not {recognized,implemented}" response: if ( r.code() == 500 || r.code() == 502 ) { if ( mEHLONotSupported ) { // HELO failed... mSMTP->error( KIO::ERR_INTERNAL_SERVER, i18n("The server rejected both EHLO and HELO commands " "as unknown or unimplemented.\n" "Please contact the server's system administrator.") ); return false; } mEHLONotSupported = true; // EHLO failed, but that's ok. return true; } mComplete = true; if ( r.code() / 10 == 25 ) { // 25x: success parseFeatures( r ); return true; } mSMTP->error( KIO::ERR_UNKNOWN, i18n("Unexpected server response to %1 command.\n%2") .arg( mEHLONotSupported ? "HELO" : "EHLO" ) .arg( r.errorMessage() ) ); return false; } // // STARTTLS - rfc 3207 // QByteArray StartTLSCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; return "STARTTLS\r\n"; } bool StartTLSCommand::processResponse( const Response & r, TransactionState * ) { mNeedResponse = false; if ( r.code() != 220 ) { mSMTP->error( r.errorCode(), i18n("Your SMTP server does not support TLS. " "Disable TLS, if you want to connect " "without encryption.") ); return false; } int tlsrc = startTLS(); if ( tlsrc == 1 ) return true; if ( tlsrc != -3 ) //kDebug(7112) << "TLS negotiation failed!" << endl; mSMTP->messageBox(KIO::SlaveBase::Information, i18n("Your SMTP server claims to " "support TLS, but negotiation " "was unsuccessful.\nYou can " "disable TLS in KDE using the " "crypto settings module."), i18n("Connection Failed")); return false; } #define SASLERROR mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, \ i18n("An error occured during authentication: %1").arg \ ( QString::fromUtf8( sasl_errdetail( conn ) ))); // // AUTH - rfc 2554 // AuthCommand::AuthCommand( SMTPProtocol * smtp, const char *mechanisms, const QString &aFQDN, KIO::AuthInfo &ai ) : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), mAi( &ai ), mFirstTime( true ) { #ifdef HAVE_LIBSASL2 int result; mMechusing = 0; conn = 0; client_interact = 0; mOut = 0; mOutlen = 0; mOneStep = false; result = sasl_client_new( "smtp", aFQDN.toLatin1(), 0, 0, NULL, 0, &conn ); if ( result != SASL_OK ) { SASLERROR return; } do { result = sasl_client_start(conn, mechanisms, &client_interact, &mOut, &mOutlen, &mMechusing); if (result == SASL_INTERACT) if ( !saslInteract( client_interact ) ) { return; }; } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { SASLERROR return; } if ( result == SASL_OK ) mOneStep = true; kDebug(7112) << "Mechanism: " << mMechusing << " one step: " << mOneStep << endl; #else mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication support is not compiled into kio_smtp.")); #endif } AuthCommand::~AuthCommand() { #ifdef HAVE_LIBSASL2 if ( conn ) { kDebug(7112) << "dispose sasl connection" << endl; sasl_dispose( &conn ); conn = 0; } #endif } bool AuthCommand::saslInteract( void *in ) { #ifdef HAVE_LIBSASL2 kDebug(7112) << "saslInteract: " << 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 ( mAi->username.isEmpty() || mAi->password.isEmpty()) { if (!mSMTP->openPassDlg(*mAi)) { mSMTP->error(KIO::ERR_ABORTED, i18n("No authentication details supplied.")); return false; } } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug(7112) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username << endl; interact->result = strdup( mAi->username.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug(7112) << "SASL_CB_PASS: [HIDDEN]" << endl; interact->result = strdup( mAi->password.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = NULL; interact->len = 0; break; } interact++; } return true; #else return false; #endif } bool AuthCommand::doNotExecute( const TransactionState * ) const { return !mMechusing; } void AuthCommand::ungetCommandLine( const QByteArray & s, TransactionState * ) { mUngetSASLResponse = s; mComplete = false; } QByteArray AuthCommand::nextCommandLine( TransactionState * ) { mNeedResponse = true; QByteArray cmd; #ifdef HAVE_LIBSASL2 QByteArray challenge; if ( !mUngetSASLResponse.isNull() ) { // implement un-ungetCommandLine cmd = mUngetSASLResponse; mUngetSASLResponse = 0; } else if ( mFirstTime ) { QString firstCommand = "AUTH " + QString::fromLatin1( mMechusing ); challenge = QByteArray::fromRawData( mOut, mOutlen ).toBase64(); if ( !challenge.isEmpty() ) { firstCommand += " "; firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); } cmd = firstCommand.toLatin1(); if ( mOneStep ) mComplete = true; } else { // kDebug(7112) << "SS: '" << mLastChallenge << "'" << endl; challenge = QByteArray::fromBase64( mLastChallenge ); int result; do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &mOut, &mOutlen); if (result == SASL_INTERACT) if ( !saslInteract( client_interact ) ) { return ""; }; } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug(7112) << "sasl_client_step failed with: " << result << endl; SASLERROR return ""; } cmd = QByteArray::fromRawData( mOut, mOutlen ).toBase64(); // kDebug(7112) << "CC: '" << cmd << "'" << endl; mComplete = ( result == SASL_OK ); } #endif //HAVE_LIBSASL2 cmd += "\r\n"; return cmd; } bool AuthCommand::processResponse( const Response & r, TransactionState * ) { if ( !r.isOk() ) { if ( mFirstTime ) if ( haveCapability( "AUTH" ) ) mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, i18n("Your SMTP server does not support %1.\nChoose a different authentication method.\n%2") .arg( mMechusing ).arg( r.errorMessage() ) ); else mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, i18n("Your SMTP server does not support authentication.\n" " %2").arg( r.errorMessage() ) ); else mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, i18n("Authentication failed.\n" "Most likely the password is wrong.\n" "%1").arg( r.errorMessage() ) ); return false; } mFirstTime = false; mLastChallenge = r.lines().front(); // ### better join all lines with \n? mNeedResponse = false; return true; } // // MAIL FROM: // QByteArray MailFromCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; QByteArray cmdLine = "MAIL FROM:<" + mAddr + '>'; if ( m8Bit && haveCapability("8BITMIME") ) cmdLine += " BODY=8BITMIME"; if ( mSize && haveCapability("SIZE") ) cmdLine += " SIZE=" + QByteArray().setNum( mSize ); return cmdLine + "\r\n"; } bool MailFromCommand::processResponse( const Response & r, TransactionState * ts ) { assert( ts ); mNeedResponse = false; if ( r.code() == 250 ) return true; ts->setMailFromFailed( mAddr, r ); return false; } // // RCPT TO: // QByteArray RcptToCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; return "RCPT TO:<" + mAddr + ">\r\n"; } bool RcptToCommand::processResponse( const Response & r, TransactionState * ts ) { assert( ts ); mNeedResponse = false; if ( r.code() == 250 ) { ts->setRecipientAccepted(); return true; } ts->addRejectedRecipient( mAddr, r.errorMessage() ); return false; } // // DATA (only initial processing!) // QByteArray DataCommand::nextCommandLine( TransactionState * ts ) { assert( ts ); mComplete = true; mNeedResponse = true; ts->setDataCommandIssued( true ); return "DATA\r\n"; } void DataCommand::ungetCommandLine( const QByteArray &, TransactionState * ts ) { assert( ts ); mComplete = false; ts->setDataCommandIssued( false ); } bool DataCommand::processResponse( const Response & r, TransactionState * ts ) { assert( ts ); mNeedResponse = false; if ( r.code() == 354 ) { ts->setDataCommandSucceeded( true, r ); return true; } ts->setDataCommandSucceeded( false, r ); return false; } // // DATA (data transfer) // void TransferCommand::ungetCommandLine( const QByteArray & cmd, TransactionState * ) { if ( cmd.isEmpty() ) return; // don't change state when we can't detect the unget in // the next nextCommandLine !! mWasComplete = mComplete; mComplete = false; mNeedResponse = false; mUngetBuffer = cmd; } bool TransferCommand::doNotExecute( const TransactionState * ts ) const { assert( ts ); return ts->failed(); } QByteArray TransferCommand::nextCommandLine( TransactionState * ts ) { assert( ts ); // let's rely on it ( at least for the moment ) assert( !isComplete() ); assert( !ts->failed() ); static const QByteArray dotCRLF = ".\r\n"; static const QByteArray CRLFdotCRLF = "\r\n.\r\n"; if ( !mUngetBuffer.isEmpty() ) { const QByteArray ret = mUngetBuffer; mUngetBuffer = 0; if ( mWasComplete ) { mComplete = true; mNeedResponse = true; } return ret; // don't prepare(), it's slave-generated or already prepare()d } // normal processing: kDebug(7112) << "requesting data" << endl; mSMTP->dataReq(); QByteArray ba; int result = mSMTP->readData( ba ); kDebug(7112) << "got " << result << " bytes" << endl; if ( result > 0 ) return prepare( ba ); else if ( result < 0 ) { ts->setFailedFatally( KIO::ERR_INTERNAL, i18n("Could not read data from application.") ); mComplete = true; mNeedResponse = true; return 0; } mComplete = true; mNeedResponse = true; return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF ; } bool TransferCommand::processResponse( const Response & r, TransactionState * ts ) { mNeedResponse = false; assert( ts ); ts->setComplete(); if ( !r.isOk() ) { ts->setFailed(); mSMTP->error( r.errorCode(), i18n("The message content was not accepted.\n" "%1").arg( r.errorMessage() ) ); return false; } return true; } static QByteArray dotstuff_lf2crlf( const QByteArray & ba, char & last ) { QByteArray result( ba.size() * 2 + 1, 0 ); // worst case: repeated "[.]\n" const char * s = ba.data(); const char * const send = ba.data() + ba.size(); char * d = result.data(); while ( s < send ) { const char ch = *s++; if ( ch == '\n' && last != '\r' ) *d++ = '\r'; // lf2crlf else if ( ch == '.' && last == '\n' ) *d++ = '.'; // dotstuff last = *d++ = ch; } result.truncate( d - result.data() ); return result; } QByteArray TransferCommand::prepare( const QByteArray & ba ) { if ( ba.isEmpty() ) return 0; if ( mSMTP->metaData("lf2crlf+dotstuff") == "slave" ) { kDebug(7112) << "performing dotstuffing and LF->CRLF transformation" << endl; return dotstuff_lf2crlf( ba, mLastChar ); } else { mLastChar = ba[ ba.size() - 1 ]; return QByteArray( ba.data(), ba.size() + 1 ); } } // // NOOP // QByteArray NoopCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; return "NOOP\r\n"; } // // RSET // QByteArray RsetCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; return "RSET\r\n"; } // // QUIT // QByteArray QuitCommand::nextCommandLine( TransactionState * ) { mComplete = true; mNeedResponse = true; return "QUIT\r\n"; } } // namespace KioSMTP diff --git a/kioslave/smtp/command.h b/kioslave/smtp/command.h index 4bd1cd649..a58559f14 100644 --- a/kioslave/smtp/command.h +++ b/kioslave/smtp/command.h @@ -1,281 +1,282 @@ /* -*- c++ -*- command.h This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KIOSMTP_COMMAND_H__ #define __KIOSMTP_COMMAND_H__ #include +#include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include class SMTPProtocol; namespace KioSMTP { class Response; class TransactionState; /** * @short Represents an SMTP command * * Semantics: A command consists of a series of "command lines" * (though that's stretching it a bit for @ref TransferJob and @ref * AuthCommand) and responses. There's typically one response for * one command line and the command is completed. * * However, some commands consist of a dialog (command line, * response, command line, response,...) where each successive * command line is dependant on the previously received response * (and thus those commands are not pipelinable). That's why each * command signals completion by having it's @ref #isComplete() * method return true @em after the last command line to be sent, * but @em before the last response to receive. @ref AuthCommand is * the principal representative of this kind of command. Because * @ref EHLOCommand automatically falls back to HELO in case EHLO * isn't supported, it is also of this kind. If completion is * signalled before the first command line is issued, it is not to * be executed at all. * * Other commands need to send multiple "command lines" before * receiving a single (final) response. @ref TransferCommand is the * only representative of this kind of "command". That's why each * command signals whether it now expects a response before being * able to issue the next command line (if any) by having it's @ref * #needsResponse() method return true. * * Commands whose @ref #nextCommandLine() does not support being * called multiple times in a row without changing command state, * must reimplement @ref #ungetCommandLine(). **/ class Command { public: enum Flags { OnlyLastInPipeline = 1, OnlyFirstInPipeline = 2, CloseConnectionOnError = 4 }; Command( SMTPProtocol * smtp, int flags=0 ); virtual ~Command(); enum Type { STARTTLS, DATA, NOOP, RSET, QUIT }; static Command * createSimpleCommand( int which, SMTPProtocol * smtp ); virtual QByteArray nextCommandLine( TransactionState * ts=0 ) = 0; /* Reimplement this if your @ref #nextCommandLine() implementation changes state other than @ref mComplete. The default implementation just resets @ref mComplete to false. */ virtual void ungetCommandLine( const QByteArray & cmdLine, TransactionState * ts=0 ); /* Reimplement this if your command need more sophisicated response processing than just checking for @ref Response::isOk(). The default implementation sets @ref mComplete to true, @ref mNeedResponse to false and returns whether the response isOk(). */ virtual bool processResponse( const Response & response, TransactionState * ts=0 ); virtual bool doNotExecute( const TransactionState * ) const { return false; } bool isComplete() const { return mComplete; } /** @return whether the command expects a response now. Some commands (most notably AUTH) may consist of a series of commands and associated responses until they are complete. Others (most notably @ref TransferCommand usually send multiple "command lines" before expecting a response. */ bool needsResponse() const { return mNeedResponse; } /** @return whether an error in executing this command is so fatal that closing the connection is the only option */ bool closeConnectionOnError() const { return mFlags & CloseConnectionOnError; } bool mustBeLastInPipeline() const { return mFlags & OnlyLastInPipeline; } bool mustBeFirstInPipeline() const { return mFlags & OnlyFirstInPipeline; } protected: SMTPProtocol * mSMTP; bool mComplete; bool mNeedResponse; const int mFlags; protected: // only relay methods to enable access to slave-protected methods // for subclasses of Command: void parseFeatures( const Response & r ); int startTLS(); bool usingSSL() const; bool usingTLS() const; bool haveCapability( const char * cap ) const; }; class EHLOCommand : public Command { public: EHLOCommand( SMTPProtocol * smtp, const QString & hostname ) : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), mEHLONotSupported( false ), mHostname( hostname.trimmed() ) {} QByteArray nextCommandLine( TransactionState * ); bool processResponse( const Response & response, TransactionState * ); private: bool mEHLONotSupported; QString mHostname; }; class StartTLSCommand : public Command { public: StartTLSCommand( SMTPProtocol * smtp ) : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} QByteArray nextCommandLine( TransactionState * ); bool processResponse( const Response & response, TransactionState * ); }; class AuthCommand : public Command { public: AuthCommand( SMTPProtocol * smtp, const char *mechanisms, const QString &aFQDN, KIO::AuthInfo &ai ); ~AuthCommand(); bool doNotExecute( const TransactionState * ts ) const; QByteArray nextCommandLine( TransactionState * ); void ungetCommandLine( const QByteArray & cmdLine, TransactionState * ); bool processResponse( const Response & response, TransactionState * ); private: bool saslInteract( void *in ); #ifdef HAVE_LIBSASL2 sasl_conn_t *conn; sasl_interact_t *client_interact; #endif const char *mOut, *mMechusing; uint mOutlen; bool mOneStep; KIO::AuthInfo *mAi; QByteArray mLastChallenge; QByteArray mUngetSASLResponse; bool mFirstTime; }; class MailFromCommand : public Command { public: MailFromCommand( SMTPProtocol * smtp, const QByteArray & addr, bool eightBit=false, unsigned int size=0 ) : Command( smtp ), mAddr( addr ), m8Bit( eightBit ), mSize( size ) {} QByteArray nextCommandLine( TransactionState * ); bool processResponse( const Response & response, TransactionState * ); private: QByteArray mAddr; bool m8Bit; unsigned int mSize; }; class RcptToCommand : public Command { public: RcptToCommand( SMTPProtocol * smtp, const QByteArray & addr ) : Command( smtp ), mAddr( addr ) {} QByteArray nextCommandLine( TransactionState * ); bool processResponse( const Response & response, TransactionState * ); private: QByteArray mAddr; }; /** Handles only the initial intermediate response and compltetes at the point where the mail contents need to be sent */ class DataCommand : public Command { public: DataCommand( SMTPProtocol * smtp ) : Command( smtp, OnlyLastInPipeline ) {} QByteArray nextCommandLine( TransactionState * ); void ungetCommandLine( const QByteArray & cmd, TransactionState * ts ); bool processResponse( const Response & response, TransactionState * ); }; /** Handles the data transfer following a successful DATA command */ class TransferCommand : public Command { public: TransferCommand( SMTPProtocol * smtp, const QByteArray & initialBuffer ) : Command( smtp, OnlyFirstInPipeline ), mUngetBuffer( initialBuffer ), mLastChar( '\n' ), mWasComplete( false ) {} bool doNotExecute( const TransactionState * ts ) const; QByteArray nextCommandLine( TransactionState * ); void ungetCommandLine( const QByteArray & cmd, TransactionState * ts ); bool processResponse( const Response & response, TransactionState * ); private: QByteArray prepare( const QByteArray & ba ); QByteArray mUngetBuffer; char mLastChar; bool mWasComplete; // ... before ungetting }; class NoopCommand : public Command { public: NoopCommand( SMTPProtocol * smtp ) : Command( smtp, OnlyLastInPipeline ) {} QByteArray nextCommandLine( TransactionState * ); }; class RsetCommand : public Command { public: RsetCommand( SMTPProtocol * smtp ) : Command( smtp, CloseConnectionOnError ) {} QByteArray nextCommandLine( TransactionState * ); }; class QuitCommand : public Command { public: QuitCommand( SMTPProtocol * smtp ) : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} QByteArray nextCommandLine( TransactionState * ); }; } // namespace KioSMTP #endif // __KIOSMTP_COMMAND_H__ diff --git a/kioslave/smtp/smtp-config.h.cmake b/kioslave/smtp/smtp-config.h.cmake new file mode 100644 index 000000000..bd4e80e9d --- /dev/null +++ b/kioslave/smtp/smtp-config.h.cmake @@ -0,0 +1,5 @@ +/* smtp-config.h. Generated by cmake from smtp-config.h.cmake */ + +/* Define if you have cyrus-sasl2 libraries */ +#cmakedefine HAVE_LIBSASL2 1 + diff --git a/kioslave/smtp/smtp.cc b/kioslave/smtp/smtp.cc index 58d65d130..2034fd65e 100644 --- a/kioslave/smtp/smtp.cc +++ b/kioslave/smtp/smtp.cc @@ -1,665 +1,666 @@ /* * Copyright (c) 2000, 2001 Alex Zepeda * Copyright (c) 2001 Michael H�kel * Copyright (c) 2002 Aaron J. Seigo * Copyright (c) 2003 Marc Mutz * 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 +#include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include "smtp.h" #include "request.h" #include "response.h" #include "transactionstate.h" #include "command.h" using KioSMTP::Capabilities; using KioSMTP::Command; using KioSMTP::EHLOCommand; using KioSMTP::AuthCommand; using KioSMTP::MailFromCommand; using KioSMTP::RcptToCommand; using KioSMTP::DataCommand; using KioSMTP::TransferCommand; using KioSMTP::Request; using KioSMTP::Response; using KioSMTP::TransactionState; #include #warning Port to KNetwork #if 0 #include #endif #include #include #include #include #include #include #include #include using std::auto_ptr; #include #include #include #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_SOCKET_H # include #endif #include #ifndef NI_NAMEREQD // FIXME for KDE 3.3: fake defintion // API design flaw in KExtendedSocket::resolve # define NI_NAMEREQD 0 #endif #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_GETOPT, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; #endif extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KInstance instance("kio_smtp"); if (argc != 4) { fprintf(stderr, "Usage: kio_smtp protocol domain-socket1 domain-socket2\n"); exit(-1); } #ifdef HAVE_LIBSASL2 if ( sasl_client_init( callbacks ) != SASL_OK ) { fprintf(stderr, "SASL library initialization failed!\n"); exit(-1); } #endif SMTPProtocol slave( argv[2], argv[3], qstricmp( argv[1], "smtps" ) == 0 ); slave.dispatchLoop(); #ifdef HAVE_LIBSASL2 sasl_done(); #endif return 0; } SMTPProtocol::SMTPProtocol(const QByteArray & pool, const QByteArray & app, bool useSSL) : TCPSlaveBase(useSSL ? 465 : 25, useSSL ? "smtps" : "smtp", pool, app, useSSL), m_sOldPort( "0" ), m_opened(false) { //kDebug(7112) << "SMTPProtocol::SMTPProtocol" << endl; } unsigned int SMTPProtocol::sendBufferSize() const { // ### how much is eaten by SSL/TLS overhead? #warning Port to KNetwork const int fd = 0L; //fileno( fp ); int value = -1; kde_socklen_t len = sizeof(value); if ( fd < 0 || ::getsockopt( fd, SOL_SOCKET, SO_SNDBUF, (char*)&value, &len ) ) value = 1024; // let's be conservative kDebug(7112) << "send buffer size seems to be " << value << " octets." << endl; return value > 0 ? value : 1024 ; } SMTPProtocol::~SMTPProtocol() { //kDebug(7112) << "SMTPProtocol::~SMTPProtocol" << endl; smtp_close(); } void SMTPProtocol::openConnection() { if ( smtp_open() ) connected(); else closeConnection(); } void SMTPProtocol::closeConnection() { smtp_close(); } void SMTPProtocol::special( const QByteArray & aData ) { QDataStream s( aData ); int what; s >> what; if ( what == 'c' ) { infoMessage( createSpecialResponse() ); #ifndef NDEBUG kDebug(7112) << "special('c') returns \"" << createSpecialResponse() << "\"" << endl; #endif } else if ( what == 'N' ) { if ( !execute( Command::NOOP ) ) return; } else { error( KIO::ERR_INTERNAL, i18n("The application sent an invalid request.") ); return; } finished(); } // Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah // If smtphost is the name of a profile, it'll use the information // provided by that profile. If it's not a profile name, it'll use it as // nature intended. // One can also specify in the query: // headers=0 (turns off header generation) // to=emailaddress // cc=emailaddress // bcc=emailaddress // subject=text // profile=text (this will override the "host" setting) // hostname=text (used in the HELO) // body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension) void SMTPProtocol::put(const KUrl & url, int /*permissions */ , bool /*overwrite */ , bool /*resume */ ) { Request request = Request::fromURL( url ); // parse settings from URL's query KEMailSettings mset; KUrl open_url = url; if ( !request.hasProfile() ) { //kDebug(7112) << "kio_smtp: Profile is null" << endl; bool hasProfile = mset.profiles().contains( open_url.host() ); if ( hasProfile ) { mset.setProfile(open_url.host()); open_url.setHost(mset.getSetting(KEMailSettings::OutServer)); m_sUser = mset.getSetting(KEMailSettings::OutServerLogin); m_sPass = mset.getSetting(KEMailSettings::OutServerPass); if (m_sUser.isEmpty()) m_sUser.clear(); if (m_sPass.isEmpty()) m_sPass.clear(); open_url.setUser(m_sUser); open_url.setPass(m_sPass); m_sServer = open_url.host(); m_port = open_url.port(); } else { mset.setProfile(mset.defaultProfileName()); } } else { mset.setProfile( request.profileName() ); } // Check KEMailSettings to see if we've specified an E-Mail address // if that worked, check to see if we've specified a real name // and then format accordingly (either: emailaddress@host.com or // Real Name ) if ( !request.hasFromAddress() ) { const QString from = mset.getSetting( KEMailSettings::EmailAddress ); if ( !from.isNull() ) request.setFromAddress( from ); else if ( request.emitHeaders() ) { error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); return; } } if ( !smtp_open( request.heloHostname() ) ) { error(KIO::ERR_SERVICE_NOT_AVAILABLE, i18n("SMTPProtocol::smtp_open failed (%1)") // ### better error message? .arg(open_url.path())); return; } if ( request.is8BitBody() && !haveCapability("8BITMIME") && metaData("8bitmime") != "on" ) { error( KIO::ERR_SERVICE_NOT_AVAILABLE, i18n("Your server does not support sending of 8-bit messages.\n" "Please use base64 or quoted-printable encoding.") ); return; } queueCommand( new MailFromCommand( this, request.fromAddress().toLatin1(), request.is8BitBody(), request.size() ) ); // Loop through our To and CC recipients, and send the proper // SMTP commands, for the benefit of the server. QStringList recipients = request.recipients(); for ( QStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it ) queueCommand( new RcptToCommand( this, (*it).toLatin1() ) ); queueCommand( Command::DATA ); queueCommand( new TransferCommand( this, request.headerFields( mset.getSetting( KEMailSettings::RealName ) ) ) ); TransactionState ts; if ( !executeQueuedCommands( &ts ) ) { if ( ts.errorCode() ) error( ts.errorCode(), ts.errorMessage() ); } else finished(); } void SMTPProtocol::setHost(const QString & host, int port, const QString & user, const QString & pass) { m_sServer = host; m_port = QString::number( port ); m_sUser = user; m_sPass = pass; } bool SMTPProtocol::sendCommandLine( const QByteArray & cmdline ) { //kDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); //kDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>" << endl; kDebug( 7112) << "C: <" << cmdline.length() << " bytes>" << endl; ssize_t cmdline_len = cmdline.length(); if ( write( cmdline.data(), cmdline_len ) != cmdline_len ) { error( KIO::ERR_COULD_NOT_WRITE, m_sServer ); return false; } return true; } Response SMTPProtocol::getResponse( bool * ok ) { if ( ok ) *ok = false; Response response; char buf[2048]; int recv_len = 0; do { // wait for data... if ( !waitForResponse( 600 ) ) { error( KIO::ERR_SERVER_TIMEOUT, m_sServer ); return response; } // ...read data... recv_len = readLine( buf, sizeof(buf) - 1 ); if ( recv_len < 1 && !isConnectionValid() ) { error( KIO::ERR_CONNECTION_BROKEN, m_sServer ); return response; } kDebug(7112) << "S: " << QByteArray( buf, recv_len + 1 ).data(); // ...and parse lines... response.parseLine( buf, recv_len ); // ...until the response is complete or the parser is so confused // that it doesn't think a RSET would help anymore: } while ( !response.isComplete() && response.isWellFormed() ); if ( !response.isValid() ) { error( KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.").arg(response.code()) ); return response; } if ( ok ) *ok = true; return response; } bool SMTPProtocol::executeQueuedCommands( TransactionState * ts ) { assert( ts ); kDebug( canPipelineCommands(), 7112 ) << "using pipelining" << endl; while( !mPendingCommandQueue.isEmpty() ) { QByteArray cmdline = collectPipelineCommands( ts ); if ( ts->failedFatally() ) { smtp_close( false ); // _hard_ shutdown return false; } if ( ts->failed() ) break; if ( cmdline.isEmpty() ) continue; if ( !sendCommandLine( cmdline ) || !batchProcessResponses( ts ) || ts->failedFatally() ) { smtp_close( false ); // _hard_ shutdown return false; } } if ( ts->failed() ) { if ( !execute( Command::RSET ) ) smtp_close( false ); return false; } return true; } QByteArray SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { assert( ts ); QByteArray cmdLine; unsigned int cmdLine_len = 0; while ( !mPendingCommandQueue.isEmpty() ) { Command * cmd = mPendingCommandQueue.head(); if ( cmd->doNotExecute( ts ) ) { delete mPendingCommandQueue.dequeue(); if ( cmdLine_len ) break; else continue; } if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) break; if ( cmdLine_len && !canPipelineCommands() ) break; while ( !cmd->isComplete() && !cmd->needsResponse() ) { const QByteArray currentCmdLine = cmd->nextCommandLine( ts ); if ( ts->failedFatally() ) return cmdLine; const unsigned int currentCmdLine_len = currentCmdLine.length(); if ( cmdLine_len && cmdLine_len + currentCmdLine_len > sendBufferSize() ) { // must all fit into the send buffer, else connection deadlocks, // but we need to have at least _one_ command to send cmd->ungetCommandLine( currentCmdLine, ts ); return cmdLine; } cmdLine_len += currentCmdLine_len; cmdLine += currentCmdLine; } mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() ); if ( cmd->mustBeLastInPipeline() ) break; } return cmdLine; } bool SMTPProtocol::batchProcessResponses( TransactionState * ts ) { assert( ts ); while ( !mSentCommandQueue.isEmpty() ) { Command * cmd = mSentCommandQueue.head(); assert( cmd->isComplete() ); bool ok = false; Response r = getResponse( &ok ); if ( !ok ) return false; cmd->processResponse( r, ts ); if ( ts->failedFatally() ) return false; delete mSentCommandQueue.dequeue(); } return true; } void SMTPProtocol::queueCommand( int type ) { queueCommand( Command::createSimpleCommand( type, this ) ); } bool SMTPProtocol::execute( int type, TransactionState * ts ) { auto_ptr cmd( Command::createSimpleCommand( type, this ) ); kFatal( !cmd.get(), 7112 ) << "Command::createSimpleCommand( " << type << " ) returned null!" << endl; return execute( cmd.get(), ts ); } // ### fold into pipelining engine? How? (execute() is often called // ### when command queues are _not_ empty!) bool SMTPProtocol::execute( Command * cmd, TransactionState * ts ) { kFatal( !cmd, 7112 ) << "SMTPProtocol::execute() called with no command to run!" << endl; if ( cmd->doNotExecute( ts ) ) return true; do { while ( !cmd->isComplete() && !cmd->needsResponse() ) { const QByteArray cmdLine = cmd->nextCommandLine( ts ); if ( ts && ts->failedFatally() ) { smtp_close( false ); return false; } if ( cmdLine.isEmpty() ) continue; if ( !sendCommandLine( cmdLine ) ) { smtp_close( false ); return false; } } bool ok = false; Response r = getResponse( &ok ); if ( !ok ) { smtp_close( false ); return false; } if ( !cmd->processResponse( r, ts ) ) { if ( ts && ts->failedFatally() || cmd->closeConnectionOnError() || !execute( Command::RSET ) ) smtp_close( false ); return false; } } while ( !cmd->isComplete() ); return true; } bool SMTPProtocol::smtp_open(const QString& fakeHostname) { if (m_opened && m_sOldPort == port(m_port) && m_sOldServer == m_sServer && m_sOldUser == m_sUser && (fakeHostname.isNull() || m_hostname == fakeHostname)) return true; smtp_close(); if (!connectToHost(m_sServer, m_port)) return false; // connectToHost has already send an error message. m_opened = true; bool ok = false; Response greeting = getResponse( &ok ); if ( !ok || !greeting.isOk() ) { if ( ok ) error( KIO::ERR_COULD_NOT_LOGIN, i18n("The server did not accept the connection.\n" "%1").arg( greeting.errorMessage() ) ); smtp_close(); return false; } if (!fakeHostname.isNull()) { m_hostname = fakeHostname; } else { QString tmpPort; #warning Port to KNetwork #if 0 KSocketAddress* addr = KExtendedSocket::localAddress(m_iSock); // perform name lookup. NI_NAMEREQD means: don't return a numeric // value (we need to know when we get have the IP address, so we // can enclose it in sqaure brackets (domain-literal). Failure to // do so is normally harmless with IPv4, but fails for IPv6: if (KExtendedSocket::resolve(addr, m_hostname, tmpPort, NI_NAMEREQD) != 0) // FQDN resolution failed // use the IP address as domain-literal m_hostname = '[' + addr->nodeName() + ']'; delete addr; #endif if(m_hostname.isEmpty()) { m_hostname = "localhost.invalid"; } } EHLOCommand ehloCmdPreTLS( this, m_hostname ); if ( !execute( &ehloCmdPreTLS ) ) { smtp_close(); return false; } if ( ( haveCapability("STARTTLS") && canUseTLS() && metaData("tls") != "off" ) || metaData("tls") == "on" ) { // For now we're gonna force it on. if ( execute( Command::STARTTLS ) ) { // re-issue EHLO to refresh the capability list (could be have // been faked before TLS was enabled): EHLOCommand ehloCmdPostTLS( this, m_hostname ); if ( !execute( &ehloCmdPostTLS ) ) { smtp_close(); return false; } } } // Now we try and login if (!authenticate()) { smtp_close(); return false; } m_sOldPort = m_port; m_sOldServer = m_sServer; m_sOldUser = m_sUser; m_sOldPass = m_sPass; return true; } bool SMTPProtocol::authenticate() { // return with success if the server doesn't support SMTP-AUTH or an user // name is not specified and metadata doesn't tell us to force it. if ( (m_sUser.isEmpty() || !haveCapability( "AUTH" )) && metaData( "sasl" ).isEmpty() ) return true; KIO::AuthInfo authInfo; authInfo.username = m_sUser; authInfo.password = m_sPass; authInfo.prompt = i18n("Username and password for your SMTP account:"); QStringList strList; if (!metaData("sasl").isEmpty()) strList.append(metaData("sasl").toLatin1()); else strList = mCapabilities.saslMethodsQSL(); AuthCommand authCmd( this, strList.join(" ").toLatin1(), m_sServer, authInfo ); bool ret = execute( &authCmd ); m_sUser = authInfo.username; m_sPass = authInfo.password; return ret; } void SMTPProtocol::parseFeatures( const Response & ehloResponse ) { mCapabilities = Capabilities::fromResponse( ehloResponse ); QString category = usingTLS() ? "TLS" : usingSSL() ? "SSL" : "PLAIN" ; setMetaData( category + " AUTH METHODS", mCapabilities.authMethodMetaData() ); setMetaData( category + " CAPABILITIES", mCapabilities.asMetaDataString() ); #ifndef NDEBUG kDebug(7112) << "parseFeatures() " << category << " AUTH METHODS:" << '\n' + mCapabilities.authMethodMetaData() << endl << "parseFeatures() " << category << " CAPABILITIES:" << '\n' + mCapabilities.asMetaDataString() << endl; #endif } void SMTPProtocol::smtp_close( bool nice ) { if (!m_opened) // We're already closed return; if ( nice ) execute( Command::QUIT ); kDebug( 7112 ) << "closing connection" << endl; closeDescriptor(); m_sOldServer.clear(); m_sOldUser.clear(); m_sOldPass.clear(); mCapabilities.clear(); qDeleteAll( mPendingCommandQueue ); mPendingCommandQueue.clear(); qDeleteAll( mSentCommandQueue ); mSentCommandQueue.clear(); m_opened = false; } void SMTPProtocol::stat(const KUrl & url) { QString path = url.path(); error(KIO::ERR_DOES_NOT_EXIST, url.path()); }