Rewrote SMTP command sending. Better code for pipelining. Report full command text on MAIL/RCPT errors.

This commit is contained in:
Vincent Richard 2013-02-12 16:58:27 +01:00
parent f0e12cbadc
commit 83c5ba96b9
7 changed files with 568 additions and 119 deletions

View File

@ -243,6 +243,8 @@ libvmime_messaging_proto_sources = [
[
'smtp',
[
'net/smtp/SMTPCommand.cpp', 'net/smtp/SMTPCommand.hpp',
'net/smtp/SMTPCommandSet.cpp', 'net/smtp/SMTPCommandSet.hpp',
'net/smtp/SMTPResponse.cpp', 'net/smtp/SMTPResponse.hpp',
'net/smtp/SMTPServiceInfos.cpp', 'net/smtp/SMTPServiceInfos.hpp',
'net/smtp/SMTPTransport.cpp', 'net/smtp/SMTPTransport.hpp',

View File

@ -0,0 +1,155 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 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/SMTPCommand.hpp"
#include "vmime/net/socket.hpp"
#include "vmime/mailbox.hpp"
namespace vmime {
namespace net {
namespace smtp {
SMTPCommand::SMTPCommand(const std::string& text)
: m_text(text)
{
}
// static
ref <SMTPCommand> SMTPCommand::EHLO(const std::string& hostname)
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "EHLO " << hostname;
return createCommand(cmd.str());
}
// static
ref <SMTPCommand> SMTPCommand::HELO(const std::string& hostname)
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "HELO " << hostname;
return createCommand(cmd.str());
}
// static
ref <SMTPCommand> SMTPCommand::AUTH(const std::string& mechName)
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "AUTH " << mechName;
return createCommand(cmd.str());
}
// static
ref <SMTPCommand> SMTPCommand::STARTTLS()
{
return createCommand("STARTTLS");
}
// static
ref <SMTPCommand> SMTPCommand::MAIL(const mailbox& mbox)
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "MAIL FROM:<" << mbox.getEmail() << ">";
return createCommand(cmd.str());
}
// static
ref <SMTPCommand> SMTPCommand::RCPT(const mailbox& mbox)
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
cmd << "RCPT TO:<" << mbox.getEmail() << ">";
return createCommand(cmd.str());
}
// static
ref <SMTPCommand> SMTPCommand::DATA()
{
return createCommand("DATA");
}
// static
ref <SMTPCommand> SMTPCommand::NOOP()
{
return createCommand("NOOP");
}
// static
ref <SMTPCommand> SMTPCommand::QUIT()
{
return createCommand("QUIT");
}
// static
ref <SMTPCommand> SMTPCommand::createCommand(const std::string& text)
{
return vmime::create <SMTPCommand>(text);
}
const string SMTPCommand::getText() const
{
return m_text;
}
void SMTPCommand::writeToSocket(ref <socket> sok)
{
sok->send(m_text + "\r\n");
}
} // smtp
} // net
} // vmime
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP

View File

@ -0,0 +1,144 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 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/SMTPCommandSet.hpp"
#include "vmime/net/socket.hpp"
#include "vmime/mailbox.hpp"
#include <stdexcept>
namespace vmime {
namespace net {
namespace smtp {
SMTPCommandSet::SMTPCommandSet(const bool pipeline)
: SMTPCommand(""), m_pipeline(pipeline),
m_started(false), m_lastCommandSent()
{
}
// static
ref <SMTPCommandSet> SMTPCommandSet::create(const bool pipeline)
{
return vmime::create <SMTPCommandSet>(pipeline);
}
void SMTPCommandSet::addCommand(ref <SMTPCommand> cmd)
{
if (m_started)
{
throw std::runtime_error("Could not add command to pipeline: "
"one or more commands have already been sent to the server.");
}
m_commands.push_back(cmd);
}
void SMTPCommandSet::writeToSocket(ref <socket> sok)
{
if (m_pipeline)
{
if (!m_started)
{
// Send all commands at once
for (std::list <ref <SMTPCommand> >::const_iterator it = m_commands.begin() ;
it != m_commands.end() ; ++it)
{
ref <SMTPCommand> cmd = *it;
cmd->writeToSocket(sok);
}
}
if (!m_commands.empty())
{
// Advance the pointer to last command sent
ref <SMTPCommand> cmd = m_commands.front();
m_commands.pop_front();
m_lastCommandSent = cmd;
}
}
else
{
if (!m_commands.empty())
{
// Send only one command
ref <SMTPCommand> cmd = m_commands.front();
m_commands.pop_front();
cmd->writeToSocket(sok);
m_lastCommandSent = cmd;
}
}
m_started = true;
}
const string SMTPCommandSet::getText() const
{
std::ostringstream cmd;
cmd.imbue(std::locale::classic());
for (std::list <ref <SMTPCommand> >::const_iterator it = m_commands.begin() ;
it != m_commands.end() ; ++it)
{
cmd << (*it)->getText() << "\r\n";
}
return cmd.str();
}
const bool SMTPCommandSet::isFinished() const
{
return (m_pipeline && m_started) || (m_commands.size() == 0);
}
ref <SMTPCommand> SMTPCommandSet::getLastCommandSent() const
{
return m_lastCommandSent;
}
} // smtp
} // net
} // vmime
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP

View File

@ -29,6 +29,8 @@
#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/exception.hpp"
#include "vmime/platform.hpp"
@ -68,7 +70,7 @@ namespace smtp {
SMTPTransport::SMTPTransport(ref <session> sess, ref <security::authenticator> auth, const bool secured)
: transport(sess, getInfosInstance(), auth), m_socket(NULL),
m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL),
m_isSMTPS(secured), m_secured(false), m_pipelineStarted(false)
m_isSMTPS(secured), m_secured(false)
{
}
@ -202,7 +204,7 @@ void SMTPTransport::helo()
// S: 250-PIPELINING
// S: 250 SIZE 2555555555
sendRequest("EHLO " + platform::getHandler()->getHostName());
sendRequest(SMTPCommand::EHLO(platform::getHandler()->getHostName()));
ref <SMTPResponse> resp;
@ -213,7 +215,7 @@ void SMTPTransport::helo()
// eg: C: HELO thismachine.ourdomain.com
// S: 250 OK
sendRequest("HELO " + platform::getHandler()->getHostName());
sendRequest(SMTPCommand::HELO(platform::getHandler()->getHostName()));
if ((resp = readResponse())->getCode() != 250)
{
@ -365,7 +367,7 @@ void SMTPTransport::authenticateSASL()
saslSession->init();
sendRequest("AUTH " + mech->getName());
sendRequest(SMTPCommand::AUTH(mech->getName()));
for (bool cont = true ; cont ; )
{
@ -396,7 +398,7 @@ void SMTPTransport::authenticateSASL()
(challenge, challengeLen, &resp, &respLen);
// Send response
sendRequest(saslContext->encodeB64(resp, respLen));
m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n");
}
catch (exceptions::sasl_exception& e)
{
@ -413,7 +415,7 @@ void SMTPTransport::authenticateSASL()
}
// Cancel SASL exchange
sendRequest("*");
m_socket->sendRaw("*\r\n", 3);
}
catch (...)
{
@ -455,7 +457,7 @@ void SMTPTransport::startTLS()
{
try
{
sendRequest("STARTTLS");
sendRequest(SMTPCommand::STARTTLS());
ref <SMTPResponse> resp = readResponse();
@ -523,7 +525,7 @@ void SMTPTransport::internalDisconnect()
{
try
{
sendRequest("QUIT");
sendRequest(SMTPCommand::QUIT());
readResponse();
}
catch (exception&)
@ -549,7 +551,7 @@ void SMTPTransport::noop()
if (!isConnected())
throw exceptions::not_connected();
sendRequest("NOOP");
sendRequest(SMTPCommand::NOOP());
ref <SMTPResponse> resp = readResponse();
@ -572,87 +574,53 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients
throw exceptions::no_expeditor();
const bool hasPipelining =
m_extensions.find("PIPELINING") != m_extensions.end();
ref <SMTPResponse> resp;
ref <SMTPCommandSet> commands = SMTPCommandSet::create(hasPipelining);
if (m_extensions.find("PIPELINING") != m_extensions.end())
// Emit the "MAIL" command
commands->addCommand(SMTPCommand::MAIL(expeditor));
// Emit a "RCPT TO" command for each recipient
for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
{
beginCommandPipeline();
const mailbox& mbox = *recipients.getMailboxAt(i);
commands->addCommand(SMTPCommand::RCPT(mbox));
}
// Emit the "MAIL" command
sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">");
// Prepare sending of message data
commands->addCommand(SMTPCommand::DATA());
// Emit a "RCPT TO" command for each recipient
for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
{
const mailbox& mbox = *recipients.getMailboxAt(i);
// Read response for "MAIL" command
commands->writeToSocket(m_socket);
sendRequest("RCPT TO:<" + mbox.getEmail() + ">");
}
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
}
// Prepare sending of message data
sendRequest("DATA");
// Read responses for "RCPT TO" commands
for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
{
commands->writeToSocket(m_socket);
endCommandPipeline();
// Read response for "MAIL" command
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error("MAIL", resp->getText());
}
// Read responses for "RCPT TO" commands
for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
{
const mailbox& mbox = *recipients.getMailboxAt(i);
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error("RCPT TO", resp->getText(), mbox.getEmail());
}
}
// Read response for "DATA" command
if ((resp = readResponse())->getCode() != 354)
{
internalDisconnect();
throw exceptions::command_error("DATA", resp->getText());
throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
}
}
else
// Read response for "DATA" command
commands->writeToSocket(m_socket);
if ((resp = readResponse())->getCode() != 354)
{
// Emit the "MAIL" command
sendRequest("MAIL FROM:<" + expeditor.getEmail() + ">");
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error("MAIL", resp->getText());
}
// Emit a "RCPT TO" command for each recipient
for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
{
const mailbox& mbox = *recipients.getMailboxAt(i);
sendRequest("RCPT TO:<" + mbox.getEmail() + ">");
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error("RCPT TO", resp->getText(), mbox.getEmail());
}
}
// Prepare sending of message data
sendRequest("DATA");
if ((resp = readResponse())->getCode() != 354)
{
internalDisconnect();
throw exceptions::command_error("DATA", resp->getText());
}
internalDisconnect();
throw exceptions::command_error(commands->getLastCommandSent()->getText(), resp->getText());
}
// Send the message data
@ -675,41 +643,9 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients
}
void SMTPTransport::beginCommandPipeline()
void SMTPTransport::sendRequest(ref <SMTPCommand> cmd)
{
m_pipeline.clear();
m_pipelineStarted = true;
}
void SMTPTransport::endCommandPipeline()
{
if (m_pipelineStarted)
{
m_socket->send(m_pipeline.str());
m_pipeline.clear();
m_pipelineStarted = false;
}
}
void SMTPTransport::sendRequest(const string& buffer, const bool end)
{
if (m_pipelineStarted)
{
m_pipeline << buffer;
if (end)
m_pipeline << "\r\n";
}
else
{
if (end)
m_socket->send(buffer + "\r\n");
else
m_socket->send(buffer);
}
cmd->writeToSocket(m_socket);
}

View File

@ -0,0 +1,110 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 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.
//
#ifndef VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED
#define VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED
#include "vmime/config.hpp"
#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
#include "vmime/object.hpp"
#include "vmime/base.hpp"
namespace vmime {
class mailbox;
namespace net {
class socket;
class timeoutHandler;
namespace smtp {
/** A SMTP command, as sent to server.
*/
class SMTPCommand : public object
{
friend class vmime::creator;
public:
static ref <SMTPCommand> HELO(const std::string& hostname);
static ref <SMTPCommand> EHLO(const std::string& hostname);
static ref <SMTPCommand> AUTH(const std::string& mechName);
static ref <SMTPCommand> STARTTLS();
static ref <SMTPCommand> MAIL(const mailbox& mbox);
static ref <SMTPCommand> RCPT(const mailbox& mbox);
static ref <SMTPCommand> DATA();
static ref <SMTPCommand> NOOP();
static ref <SMTPCommand> QUIT();
/** Creates a new SMTP command with the specified text.
*
* @param text command text
* @return a new SMTPCommand object
*/
static ref <SMTPCommand> createCommand(const std::string& text);
/** Sends this command to the specified socket.
*
* @param sok socket to which the command will be written
*/
virtual void writeToSocket(ref <socket> sok);
/** Returns the full text of the command, including command name
* and parameters (if any).
*
* @return command text (eg. "RCPT TO:<vincent@kisli.com>")
*/
virtual const string getText() const;
protected:
SMTPCommand(const std::string& text);
SMTPCommand(const SMTPCommand&);
private:
std::string m_text;
};
} // smtp
} // net
} // vmime
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
#endif // VMIME_NET_SMTP_SMTPCOMMAND_HPP_INCLUDED

View File

@ -0,0 +1,107 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 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.
//
#ifndef VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED
#define VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED
#include "vmime/config.hpp"
#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
#include <list>
#include "vmime/net/smtp/SMTPCommand.hpp"
namespace vmime {
namespace net {
namespace smtp {
/** A set of SMTP commands, which may be sent all at once
* to the server if pipelining is supported.
*/
class SMTPCommandSet : public SMTPCommand
{
friend class vmime::creator;
public:
/** Creates a new set of SMTP commands.
*
* @param pipeline set to true if the server supports pipelining
* @return a new SMTPCommandSet object
*/
static ref <SMTPCommandSet> create(const bool pipeline);
/** Adds a new command to this set.
* If one or more comments have already been sent to the server,
* an exception will be thrown.
*
* @param cmd command to add
*/
void addCommand(ref <SMTPCommand> cmd);
/** Tests whether all commands have been sent.
*
* @return true if all commands have been sent,
* or false otherwise
*/
const bool isFinished() const;
/** Returns the last command which has been sent.
*
* @return a pointer to a SMTPCommand, of NULL if no command
* has been sent yet
*/
ref <SMTPCommand> getLastCommandSent() const;
void writeToSocket(ref <socket> sok);
const string getText() const;
private:
SMTPCommandSet(const bool pipeline);
SMTPCommandSet(const SMTPCommandSet&);
bool m_pipeline;
bool m_started;
std::list <ref <SMTPCommand> > m_commands;
ref <SMTPCommand> m_lastCommandSent;
};
} // smtp
} // net
} // vmime
#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
#endif // VMIME_NET_SMTP_SMTPCOMMANDSET_HPP_INCLUDED

View File

@ -44,6 +44,9 @@ namespace net {
namespace smtp {
class SMTPCommand;
/** SMTP transport service.
*/
@ -72,7 +75,7 @@ public:
private:
void sendRequest(const string& buffer, const bool end = true);
void sendRequest(ref <SMTPCommand> cmd);
ref <SMTPResponse> readResponse();
void internalDisconnect();
@ -102,14 +105,6 @@ private:
SMTPResponse::state m_responseState;
// Pipelining
std::ostringstream m_pipeline;
bool m_pipelineStarted;
void beginCommandPipeline();
void endCommandPipeline();
// Service infos
static SMTPServiceInfos sm_infos;
};