diff options
Diffstat (limited to 'kioslave/smtp/smtp.cc')
-rw-r--r-- | kioslave/smtp/smtp.cc | 647 |
1 files changed, 0 insertions, 647 deletions
diff --git a/kioslave/smtp/smtp.cc b/kioslave/smtp/smtp.cc deleted file mode 100644 index b1effa415..000000000 --- a/kioslave/smtp/smtp.cc +++ /dev/null @@ -1,647 +0,0 @@ -/* - * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy@sonic.net> - * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net> - * Copyright (c) 2002 Aaron J. Seigo <aseigo@olympusproject.org> - * Copyright (c) 2003 Marc Mutz <mutz@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. - * - */ - -#include <config.h> - -#ifdef HAVE_LIBSASL2 -extern "C" { -#include <sasl/sasl.h> -} -#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 <kemailsettings.h> -#include <ksock.h> -#include <kdebug.h> -#include <kinstance.h> -#include <kio/connection.h> -#include <kio/slaveinterface.h> -#include <klocale.h> - -#include <tqstring.h> -#include <tqstringlist.h> -#include <tqcstring.h> - -#include <memory> -using std::auto_ptr; - -#include <ctype.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <assert.h> - -#ifdef HAVE_SYS_TYPES_H -# include <sys/types.h> -#endif -#ifdef HAVE_SYS_SOCKET_H -# include <sys/socket.h> -#endif -#include <netdb.h> - -#ifndef NI_NAMEREQD -// FIXME for KDE 3.3: fake defintion -// API design flaw in KExtendedSocket::resolve -# define NI_NAMEREQD 0 -#endif - - -extern "C" { - KDE_EXPORT int kdemain(int argc, char **argv); -} - -int kdemain(int argc, char **argv) -{ - TDEInstance 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( NULL ) != SASL_OK ) { - fprintf(stderr, "SASL library initialization failed!\n"); - exit(-1); - } -#endif - SMTPProtocol slave( argv[2], argv[3], tqstricmp( argv[1], "smtps" ) == 0 ); - slave.dispatchLoop(); -#ifdef HAVE_LIBSASL2 - sasl_done(); -#endif - return 0; -} - -SMTPProtocol::SMTPProtocol(const TQCString & pool, const TQCString & app, - bool useSSL) -: TCPSlaveBase(useSSL ? 465 : 25, - useSSL ? "smtps" : "smtp", - pool, app, useSSL), - m_iOldPort(0), - m_opened(false) -{ - //kdDebug(7112) << "SMTPProtocol::SMTPProtocol" << endl; - mPendingCommandQueue.setAutoDelete( true ); - mSentCommandQueue.setAutoDelete( true ); -} - -unsigned int SMTPProtocol::sendBufferSize() const { - // ### how much is eaten by SSL/TLS overhead? - const int fd = 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 - kdDebug(7112) << "send buffer size seems to be " << value << " octets." << endl; - return value > 0 ? value : 1024 ; -} - -SMTPProtocol::~SMTPProtocol() { - //kdDebug(7112) << "SMTPProtocol::~SMTPProtocol" << endl; - smtp_close(); -} - -void SMTPProtocol::openConnection() { - if ( smtp_open() ) - connected(); - else - closeConnection(); -} - -void SMTPProtocol::closeConnection() { - smtp_close(); -} - -void SMTPProtocol::special( const TQByteArray & aData ) { - TQDataStream s( aData, IO_ReadOnly ); - int what; - s >> what; - if ( what == 'c' ) { - infoMessage( createSpecialResponse() ); -#ifndef NDEBUG - kdDebug(7112) << "special('c') returns \"" << createSpecialResponse() << "\"" << endl; -#endif - } else if ( what == 'N' ) { - if ( !execute( Command::NOOP ) ) - return; - } else { - error( TDEIO::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() ) { - //kdDebug(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 = TQString::null; - if (m_sPass.isEmpty()) - m_sPass = TQString::null; - open_url.setUser(m_sUser); - open_url.setPass(m_sPass); - m_sServer = open_url.host(); - m_iPort = 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 <emailaddress@host.com>) - if ( !request.hasFromAddress() ) { - const TQString from = mset.getSetting( KEMailSettings::EmailAddress ); - if ( !from.isNull() ) - request.setFromAddress( from ); - else if ( request.emitHeaders() ) { - error(TDEIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); - return; - } - } - - if ( !smtp_open( request.heloHostname() ) ) - { - error(TDEIO::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( TDEIO::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().latin1(), - request.is8BitBody(), request.size() ) ); - - // Loop through our To and CC recipients, and send the proper - // SMTP commands, for the benefit of the server. - TQStringList recipients = request.recipients(); - for ( TQStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it ) - queueCommand( new RcptToCommand( this, (*it).latin1() ) ); - - 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 TQString & host, int port, - const TQString & user, const TQString & pass) -{ - m_sServer = host; - m_iPort = port; - m_sUser = user; - m_sPass = pass; -} - -bool SMTPProtocol::sendCommandLine( const TQCString & cmdline ) { - //kdDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); - //kdDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>" << endl; - kdDebug( 7112) << "C: <" << cmdline.length() << " bytes>" << endl; - ssize_t cmdline_len = cmdline.length(); - if ( write( cmdline.data(), cmdline_len ) != cmdline_len ) { - error( TDEIO::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( TDEIO::ERR_SERVER_TIMEOUT, m_sServer ); - return response; - } - - // ...read data... - recv_len = readLine( buf, sizeof(buf) - 1 ); - if ( recv_len < 1 && !isConnectionValid() ) { - error( TDEIO::ERR_CONNECTION_BROKEN, m_sServer ); - return response; - } - - kdDebug(7112) << "S: " << TQCString( 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( TDEIO::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 ); - - kdDebug( canPipelineCommands(), 7112 ) << "using pipelining" << endl; - - while( !mPendingCommandQueue.isEmpty() ) { - TQCString 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; -} - -TQCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { - assert( ts ); - - TQCString cmdLine; - unsigned int cmdLine_len = 0; - - while ( mPendingCommandQueue.head() ) { - - 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 TQCString 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; - - mSentCommandQueue.remove(); - } - - return true; -} - -void SMTPProtocol::queueCommand( int type ) { - queueCommand( Command::createSimpleCommand( type, this ) ); -} - -bool SMTPProtocol::execute( int type, TransactionState * ts ) { - auto_ptr<Command> cmd( Command::createSimpleCommand( type, this ) ); - kdFatal( !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 ) -{ - kdFatal( !cmd, 7112 ) << "SMTPProtocol::execute() called with no command to run!" << endl; - - if (!cmd) - return false; - - if ( cmd->doNotExecute( ts ) ) - return true; - - do { - while ( !cmd->isComplete() && !cmd->needsResponse() ) { - const TQCString 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 TQString& fakeHostname) -{ - if (m_opened && - m_iOldPort == port(m_iPort) && - m_sOldServer == m_sServer && - m_sOldUser == m_sUser && - (fakeHostname.isNull() || m_hostname == fakeHostname)) - return true; - - smtp_close(); - if (!connectToHost(m_sServer, m_iPort)) - 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( TDEIO::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 - { - TQString tmpPort; - TDESocketAddress* 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; - - 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_iOldPort = m_iPort; - 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; - - TDEIO::AuthInfo authInfo; - authInfo.username = m_sUser; - authInfo.password = m_sPass; - authInfo.prompt = i18n("Username and password for your SMTP account:"); - - TQStringList strList; - - if (!metaData("sasl").isEmpty()) - strList.append(metaData("sasl").latin1()); - else - strList = mCapabilities.saslMethodsQSL(); - - AuthCommand authCmd( this, strList.join(" ").latin1(), 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 ); - - TQString category = usingTLS() ? "TLS" : usingSSL() ? "SSL" : "PLAIN" ; - setMetaData( category + " AUTH METHODS", mCapabilities.authMethodMetaData() ); - setMetaData( category + " CAPABILITIES", mCapabilities.asMetaDataString() ); -#ifndef NDEBUG - kdDebug(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 ); - kdDebug( 7112 ) << "closing connection" << endl; - closeDescriptor(); - m_sOldServer = TQString::null; - m_sOldUser = TQString::null; - m_sOldPass = TQString::null; - - mCapabilities.clear(); - mPendingCommandQueue.clear(); - mSentCommandQueue.clear(); - - m_opened = false; -} - -void SMTPProtocol::stat(const KURL & url) -{ - TQString path = url.path(); - error(TDEIO::ERR_DOES_NOT_EXIST, url.path()); -} - |