diff options
author | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
---|---|---|
committer | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
commit | aa4d426b4d3527d7e166df1a05058c9a4a0f6683 (patch) | |
tree | 4ff17ce8b89a2321b9d0ed4bcfc37c447bcb6820 /vmime-master/src/vmime/net/smtp/SMTPTransport.cpp | |
download | smtps-and-pop3s-console-program-master.tar.gz smtps-and-pop3s-console-program-master.zip |
Diffstat (limited to 'vmime-master/src/vmime/net/smtp/SMTPTransport.cpp')
-rw-r--r-- | vmime-master/src/vmime/net/smtp/SMTPTransport.cpp | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/net/smtp/SMTPTransport.cpp b/vmime-master/src/vmime/net/smtp/SMTPTransport.cpp new file mode 100644 index 0000000..561bd59 --- /dev/null +++ b/vmime-master/src/vmime/net/smtp/SMTPTransport.cpp @@ -0,0 +1,502 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002 Vincent Richard <vincent@vmime.org> +// +// 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 <session>& sess, + const shared_ptr <security::authenticator>& 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 <SMTPConnection>( + dynamicCast <SMTPTransport>(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 <connectionInfos> SMTPTransport::getConnectionInfos() const { + + if (!m_connection) { + return null; + } + + return m_connection->getConnectionInfos(); +} + + +shared_ptr <SMTPConnection> 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 <SMTPResponse> 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 <bool>(getSession(), + dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().PROPERTY_OPTIONS_PIPELINING); + + shared_ptr <SMTPResponse> resp; + shared_ptr <SMTPCommandSet> 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 <SMTPResponse> resp; + + if ((resp = m_connection->readResponse())->getCode() != 250) { + throw SMTPCommandError("DATA", resp->getText(), resp->getCode(), resp->getEnhancedCode()); + } +} + + +void SMTPTransport::send( + const shared_ptr <vmime::message>& 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 <bool>(getSession(), + dynamic_cast <const SMTPServiceInfos&>(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 + |