// // VMime library (http://www.vmime.org) // Copyright (C) 2002 Vincent Richard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 3 of // the License, or (at your option) any later version. // // 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. // // Linking this library statically or dynamically with other modules is making // a combined work based on this library. Thus, the terms and conditions of // the GNU General Public License cover the whole combination. // #include "vmime/config.hpp" #if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP #include "vmime/net/smtp/SMTPTransport.hpp" #include "vmime/net/smtp/SMTPResponse.hpp" #include "vmime/net/smtp/SMTPCommand.hpp" #include "vmime/net/smtp/SMTPCommandSet.hpp" #include "vmime/net/smtp/SMTPChunkingOutputStreamAdapter.hpp" #include "vmime/net/smtp/SMTPExceptions.hpp" #include "vmime/exception.hpp" #include "vmime/mailboxList.hpp" #include "vmime/message.hpp" #include "vmime/utility/filteredStream.hpp" #include "vmime/utility/stringUtils.hpp" #include "vmime/utility/outputStreamSocketAdapter.hpp" #include "vmime/utility/streamUtils.hpp" #include "vmime/utility/outputStreamAdapter.hpp" #include "vmime/utility/inputStreamStringAdapter.hpp" namespace vmime { namespace net { namespace smtp { SMTPTransport::SMTPTransport( const shared_ptr & sess, const shared_ptr & auth, const bool secured ) : transport(sess, getInfosInstance(), auth), m_isSMTPS(secured), m_needReset(false) { } SMTPTransport::~SMTPTransport() { try { if (isConnected()) { disconnect(); } } catch (...) { // Don't throw in destructor } } const string SMTPTransport::getProtocolName() const { return "smtp"; } bool SMTPTransport::isSMTPS() const { return m_isSMTPS; } void SMTPTransport::connect() { if (isConnected()) { throw exceptions::already_connected(); } m_connection = make_shared ( dynamicCast (shared_from_this()), getAuthenticator() ); m_connection->connect(); } bool SMTPTransport::isConnected() const { return m_connection && m_connection->isConnected(); } bool SMTPTransport::isSecuredConnection() const { if (!m_connection) { return false; } return m_connection->isSecuredConnection(); } shared_ptr SMTPTransport::getConnectionInfos() const { if (!m_connection) { return null; } return m_connection->getConnectionInfos(); } shared_ptr SMTPTransport::getConnection() { return m_connection; } void SMTPTransport::disconnect() { if (!isConnected()) { throw exceptions::not_connected(); } m_connection->disconnect(); m_connection = null; } void SMTPTransport::noop() { if (!isConnected()) { throw exceptions::not_connected(); } m_connection->sendRequest(SMTPCommand::NOOP()); shared_ptr resp = m_connection->readResponse(); if (resp->getCode() != 250) { throw SMTPCommandError("NOOP", resp->getText(), resp->getCode(), resp->getEnhancedCode()); } } // static bool SMTPTransport::mailboxNeedsUTF8(const mailbox& mb) { bool all7bit = utility::stringUtils::is7bit(mb.getEmail().getLocalName().getBuffer()) && utility::stringUtils::is7bit(mb.getEmail().getDomainName().getBuffer()); for (size_t i = 0, n = mb.getName().getWordCount() ; all7bit && i != n ; ++i) { all7bit = utility::stringUtils::is7bit(mb.getName().getWordAt(i)->getBuffer()); } return !all7bit; } void SMTPTransport::sendEnvelope( const mailbox& expeditor, const mailboxList& recipients, const mailbox& sender, bool sendDATACommand, const size_t size, const dsnAttributes& dsnAttrs ) { // If no recipient/expeditor was found, throw an exception if (recipients.isEmpty()) { throw exceptions::no_recipient(); } else if (expeditor.isEmpty()) { throw exceptions::no_expeditor(); } // If DSN extension is used, ensure it is supported by the server if (!dsnAttrs.isEmpty() && !m_connection->hasExtension("DSN")) { throw SMTPDSNExtensionNotSupportedException(); } const bool needReset = m_needReset; const bool hasPipelining = m_connection->hasExtension("PIPELINING") && getInfos().getPropertyValue (getSession(), dynamic_cast (getInfos()).getProperties().PROPERTY_OPTIONS_PIPELINING); shared_ptr resp; shared_ptr commands = SMTPCommandSet::create(hasPipelining); // Emit a "RSET" command if we previously sent a message on this connection if (needReset) { commands->addCommand(SMTPCommand::RSET()); } // Check whether we need SMTPUTF8 const bool hasSMTPUTF8 = m_connection->hasExtension("SMTPUTF8"); bool needSMTPUTF8 = false; if (!sender.isEmpty()) { needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(sender); } else { needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(expeditor); } for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { const mailbox& mbox = *recipients.getMailboxAt(i); needSMTPUTF8 = needSMTPUTF8 || mailboxNeedsUTF8(mbox); } // Emit the "MAIL" command const bool hasSize = m_connection->hasExtension("SIZE"); if (!sender.isEmpty()) { commands->addCommand( SMTPCommand::MAIL( sender, hasSMTPUTF8 && needSMTPUTF8, hasSize ? size : 0, dsnAttrs.getNotificationConditions(), dsnAttrs.getEnvelopId() ) ); } else { commands->addCommand( SMTPCommand::MAIL( expeditor, hasSMTPUTF8 && needSMTPUTF8, hasSize ? size : 0, dsnAttrs.getNotificationConditions(), dsnAttrs.getEnvelopId() ) ); } // Now, we will need to reset next time m_needReset = true; // Emit a "RCPT TO" command for each recipient for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { const mailbox& mbox = *recipients.getMailboxAt(i); commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8 && needSMTPUTF8, dsnAttrs.getNotificationConditions())); } // Prepare sending of message data if (sendDATACommand) { commands->addCommand(SMTPCommand::DATA()); } // Read response for "RSET" command if (needReset) { commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); resp = m_connection->readResponse(); if (resp->getCode() != 250 && resp->getCode() != 200) { // RFC-876: << In reply to a RSET and/or a NOOP command, // some servers reply "200" >> disconnect(); throw SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ); } } // Read response for "MAIL" command commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); if ((resp = m_connection->readResponse())->getCode() != 250) { // SIZE extension: insufficient system storage if (resp->getCode() == 452) { throw SMTPMessageSizeExceedsCurLimitsException( SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ) ); // SIZE extension: message size exceeds fixed maximum message size } else if (resp->getCode() == 552) { throw SMTPMessageSizeExceedsMaxLimitsException( SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ) ); // Other error } else { throw SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ); } } // Read responses for "RCPT TO" commands for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); resp = m_connection->readResponse(); if (resp->getCode() != 250 && resp->getCode() != 251) { // SIZE extension: insufficient system storage if (resp->getCode() == 452) { throw SMTPMessageSizeExceedsCurLimitsException( SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ) ); // SIZE extension: message size exceeds fixed maximum message size } else if (resp->getCode() == 552) { throw SMTPMessageSizeExceedsMaxLimitsException( SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ) ); // Other error } else { throw SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ); } } } // Read response for "DATA" command if (sendDATACommand) { commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); if ((resp = m_connection->readResponse())->getCode() != 354) { throw SMTPCommandError( commands->getLastCommandSent()->getText(), resp->getText(), resp->getCode(), resp->getEnhancedCode() ); } } } void SMTPTransport::send( const mailbox& expeditor, const mailboxList& recipients, utility::inputStream& is, const size_t size, utility::progressListener* progress, const mailbox& sender, const dsnAttributes& dsnAttrs ) { if (!isConnected()) { throw exceptions::not_connected(); } // Send message envelope sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ true, size, dsnAttrs); // Send the message data // Stream copy with "\n." to "\n.." transformation utility::outputStreamSocketAdapter sos(*m_connection->getSocket()); utility::dotFilteredOutputStream fos(sos); utility::bufferedStreamCopy(is, fos, size, progress); fos.flush(); // Send end-of-data delimiter m_connection->getSocket()->send("\r\n.\r\n"); if (m_connection->getTracer()) { m_connection->getTracer()->traceSendBytes(size); m_connection->getTracer()->traceSend("."); } shared_ptr resp; if ((resp = m_connection->readResponse())->getCode() != 250) { throw SMTPCommandError("DATA", resp->getText(), resp->getCode(), resp->getEnhancedCode()); } } void SMTPTransport::send( const shared_ptr & msg, const mailbox& expeditor, const mailboxList& recipients, utility::progressListener* progress, const mailbox& sender, const dsnAttributes& dsnAttrs ) { if (!isConnected()) { throw exceptions::not_connected(); } // Generate the message with Internationalized Email support, // if this is supported by the SMTP server generationContext ctx(generationContext::getDefaultContext()); ctx.setInternationalizedEmailSupport(m_connection->hasExtension("SMTPUTF8")); // If CHUNKING is not supported, generate the message to a temporary // buffer then use the send() method which takes an inputStream if (!m_connection->hasExtension("CHUNKING") || !getInfos().getPropertyValue (getSession(), dynamic_cast (getInfos()).getProperties().PROPERTY_OPTIONS_CHUNKING)) { std::ostringstream oss; utility::outputStreamAdapter ossAdapter(oss); msg->generate(ctx, ossAdapter); const string& str(oss.str()); utility::inputStreamStringAdapter isAdapter(str); send(expeditor, recipients, isAdapter, str.length(), progress, sender, dsnAttrs); return; } // Send message envelope const size_t msgSize = msg->getGeneratedSize(ctx); sendEnvelope(expeditor, recipients, sender, /* sendDATACommand */ false, msgSize, dsnAttrs); // Send the message by chunks SMTPChunkingOutputStreamAdapter chunkStream(m_connection, msgSize, progress); msg->generate(ctx, chunkStream); chunkStream.flush(); } // Service infos SMTPServiceInfos SMTPTransport::sm_infos(false); const serviceInfos& SMTPTransport::getInfosInstance() { return sm_infos; } const serviceInfos& SMTPTransport::getInfos() const { return sm_infos; } } // smtp } // net } // vmime #endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP