//
// 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