vmime/src/net/smtp/SMTPTransport.cpp

647 lines
14 KiB
C++
Raw Normal View History

//
// VMime library (http://www.vmime.org)
2006-02-05 10:22:59 +00:00
// Copyright (C) 2002-2006 Vincent Richard <vincent@vincent-richard.net>
//
// 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 2 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.
//
2005-09-17 10:10:29 +00:00
// You should have received a copy of the GNU General Public License along along
// with this program; if not, write to the Free Software Foundation, Inc., Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA..
//
#include "vmime/net/smtp/SMTPTransport.hpp"
#include "vmime/net/smtp/SMTPResponse.hpp"
#include "vmime/exception.hpp"
#include "vmime/platformDependant.hpp"
#include "vmime/encoderB64.hpp"
#include "vmime/mailboxList.hpp"
#include "vmime/utility/filteredStream.hpp"
2005-09-17 09:08:45 +00:00
#include "vmime/utility/stringUtils.hpp"
#include "vmime/net/defaultConnectionInfos.hpp"
2005-09-17 09:08:45 +00:00
#if VMIME_HAVE_SASL_SUPPORT
#include "vmime/security/sasl/SASLContext.hpp"
#endif // VMIME_HAVE_SASL_SUPPORT
2005-10-03 21:29:04 +00:00
#if VMIME_HAVE_TLS_SUPPORT
#include "vmime/net/tls/TLSSession.hpp"
#include "vmime/net/tls/TLSSecuredConnectionInfos.hpp"
2005-10-03 21:29:04 +00:00
#endif // VMIME_HAVE_TLS_SUPPORT
// Helpers for service properties
#define GET_PROPERTY(type, prop) \
2005-10-03 21:29:04 +00:00
(getInfos().getPropertyValue <type>(getSession(), \
dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
#define HAS_PROPERTY(prop) \
2005-10-03 21:29:04 +00:00
(getInfos().hasProperty(getSession(), \
dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
namespace vmime {
namespace net {
namespace smtp {
2005-10-03 21:29:04 +00:00
SMTPTransport::SMTPTransport(ref <session> sess, ref <security::authenticator> auth, const bool secured)
: transport(sess, getInfosInstance(), auth), m_socket(NULL),
2005-10-03 21:29:04 +00:00
m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL),
m_isSMTPS(secured), m_secured(false)
{
}
SMTPTransport::~SMTPTransport()
{
2005-09-17 09:08:45 +00:00
try
{
if (isConnected())
disconnect();
else if (m_socket)
internalDisconnect();
}
catch (vmime::exception&)
{
// Ignore
}
}
const string SMTPTransport::getProtocolName() const
{
return "smtp";
}
void SMTPTransport::connect()
{
if (isConnected())
throw exceptions::already_connected();
const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS);
const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT);
// Create the time-out handler
if (getTimeoutHandlerFactory())
m_timeoutHandler = getTimeoutHandlerFactory()->create();
// Create and connect the socket
2005-10-04 18:34:25 +00:00
m_socket = getSocketFactory()->create();
2005-10-03 21:29:04 +00:00
#if VMIME_HAVE_TLS_SUPPORT
if (m_isSMTPS) // dedicated port/SMTPS
2005-10-03 21:29:04 +00:00
{
ref <tls::TLSSession> tlsSession =
vmime::create <tls::TLSSession>(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
m_socket = tlsSocket;
m_secured = true;
m_cntInfos = vmime::create <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket);
2005-10-03 21:29:04 +00:00
}
else
#endif // VMIME_HAVE_TLS_SUPPORT
{
m_cntInfos = vmime::create <defaultConnectionInfos>(address, port);
}
2005-10-03 21:29:04 +00:00
m_socket->connect(address, port);
// Connection
//
// eg: C: <connection to server>
// --- S: 220 smtp.domain.com Service ready
ref <SMTPResponse> resp;
if ((resp = readResponse())->getCode() != 220)
{
internalDisconnect();
throw exceptions::connection_greeting_error(resp->getText());
}
// Identification
helo();
2005-10-03 21:29:04 +00:00
#if VMIME_HAVE_TLS_SUPPORT
// Setup secured connection, if requested
const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS)
&& GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS);
const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED)
&& GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED);
2006-05-05 11:13:11 +00:00
if (!m_isSMTPS && tls) // only if not SMTPS
2005-10-03 21:29:04 +00:00
{
try
{
startTLS();
}
// Non-fatal error
catch (exceptions::command_error&)
{
if (tlsRequired)
{
throw;
}
else
{
// TLS is not required, so don't bother
}
}
// Fatal error
catch (...)
{
throw;
}
// Must reissue a EHLO command [RFC-2487, 5.2]
helo();
2005-10-03 21:29:04 +00:00
}
#endif // VMIME_HAVE_TLS_SUPPORT
// Authentication
if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH))
2005-09-17 09:08:45 +00:00
authenticate();
else
m_authentified = true;
2005-09-17 09:08:45 +00:00
}
void SMTPTransport::helo()
{
// First, try Extended SMTP (ESMTP)
//
// eg: C: EHLO thismachine.ourdomain.com
// S: 250-smtp.theserver.com
// S: 250 AUTH CRAM-MD5 DIGEST-MD5
sendRequest("EHLO " + platformDependant::getHandler()->getHostName());
ref <SMTPResponse> resp;
if ((resp = readResponse())->getCode() != 250)
{
// Next, try "Basic" SMTP
//
// eg: C: HELO thismachine.ourdomain.com
// S: 250 OK
sendRequest("HELO " + platformDependant::getHandler()->getHostName());
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::connection_greeting_error(resp->getLastLine().getText());
}
m_extendedSMTP = false;
m_extendedSMTPResponse.clear();
}
else
{
m_extendedSMTP = true;
m_extendedSMTPResponse = resp->getText();
}
}
2005-09-17 09:08:45 +00:00
void SMTPTransport::authenticate()
{
if (!m_extendedSMTP)
{
2005-09-17 09:08:45 +00:00
internalDisconnect();
throw exceptions::command_error("AUTH", "ESMTP not supported.");
}
getAuthenticator()->setService(thisRef().dynamicCast <service>());
#if VMIME_HAVE_SASL_SUPPORT
// First, try SASL authentication
if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL))
{
try
{
authenticateSASL();
m_authentified = true;
return;
}
catch (exceptions::authentication_error& e)
{
if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK))
{
// Can't fallback on normal authentication
internalDisconnect();
throw e;
}
else
{
// Ignore, will try normal authentication
}
}
catch (exception& e)
{
internalDisconnect();
2005-09-17 09:08:45 +00:00
throw e;
}
2005-09-17 09:08:45 +00:00
}
#endif // VMIME_HAVE_SASL_SUPPORT
2005-09-17 09:08:45 +00:00
// No other authentication method is possible
throw exceptions::authentication_error("All authentication methods failed");
}
2005-09-17 09:08:45 +00:00
#if VMIME_HAVE_SASL_SUPPORT
void SMTPTransport::authenticateSASL()
{
if (!getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>())
throw exceptions::authentication_error("No SASL authenticator available.");
// Obtain SASL mechanisms supported by server from EHLO response
std::vector <string> saslMechs;
std::istringstream iss(m_extendedSMTPResponse);
while (!iss.eof())
{
string line;
std::getline(iss, line);
std::istringstream liss(line);
string word;
bool inAuth = false;
while (liss >> word)
{
2005-09-17 09:08:45 +00:00
if (word.length() == 4 &&
(word[0] == 'A' || word[0] == 'a') &&
(word[1] == 'U' || word[1] == 'u') &&
(word[2] == 'T' || word[2] == 't') &&
(word[3] == 'H' || word[3] == 'h'))
{
2005-09-17 09:08:45 +00:00
inAuth = true;
}
else if (inAuth)
{
2005-09-17 09:08:45 +00:00
saslMechs.push_back(word);
}
}
}
2005-09-17 09:08:45 +00:00
if (saslMechs.empty())
throw exceptions::authentication_error("No SASL mechanism available.");
2005-09-17 09:08:45 +00:00
std::vector <ref <security::sasl::SASLMechanism> > mechList;
2005-09-17 09:08:45 +00:00
ref <security::sasl::SASLContext> saslContext =
vmime::create <security::sasl::SASLContext>();
2005-09-17 09:08:45 +00:00
for (unsigned int i = 0 ; i < saslMechs.size() ; ++i)
{
try
{
mechList.push_back
(saslContext->createMechanism(saslMechs[i]));
}
catch (exceptions::no_such_mechanism&)
{
// Ignore mechanism
}
}
2005-09-17 09:08:45 +00:00
if (mechList.empty())
throw exceptions::authentication_error("No SASL mechanism available.");
2005-09-17 09:08:45 +00:00
// Try to suggest a mechanism among all those supported
ref <security::sasl::SASLMechanism> suggestedMech =
saslContext->suggestMechanism(mechList);
2005-09-17 09:08:45 +00:00
if (!suggestedMech)
throw exceptions::authentication_error("Unable to suggest SASL mechanism.");
2005-09-17 09:08:45 +00:00
// Allow application to choose which mechanisms to use
mechList = getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()->
getAcceptableMechanisms(mechList, suggestedMech);
2005-09-17 09:08:45 +00:00
if (mechList.empty())
throw exceptions::authentication_error("No SASL mechanism available.");
2005-09-17 09:08:45 +00:00
// Try each mechanism in the list in turn
for (unsigned int i = 0 ; i < mechList.size() ; ++i)
{
ref <security::sasl::SASLMechanism> mech = mechList[i];
ref <security::sasl::SASLSession> saslSession =
saslContext->createSession("smtp", getAuthenticator(), mech);
saslSession->init();
sendRequest("AUTH " + mech->getName());
for (bool cont = true ; cont ; )
{
ref <SMTPResponse> response = readResponse();
2005-09-17 09:08:45 +00:00
switch (response->getCode())
2005-09-17 09:08:45 +00:00
{
case 235:
{
m_socket = saslSession->getSecuredSocket(m_socket);
return;
}
case 334:
{
2006-04-18 19:04:30 +00:00
byte_t* challenge = 0;
2005-09-17 09:08:45 +00:00
int challengeLen = 0;
2006-04-18 19:04:30 +00:00
byte_t* resp = 0;
2005-09-17 09:08:45 +00:00
int respLen = 0;
try
{
// Extract challenge
saslContext->decodeB64(response->getText(), &challenge, &challengeLen);
2005-09-17 09:08:45 +00:00
// Prepare response
saslSession->evaluateChallenge
(challenge, challengeLen, &resp, &respLen);
// Send response
sendRequest(saslContext->encodeB64(resp, respLen));
}
catch (exceptions::sasl_exception& e)
{
if (challenge)
{
2005-09-17 09:08:45 +00:00
delete [] challenge;
challenge = NULL;
}
2005-09-17 09:08:45 +00:00
if (resp)
{
2005-09-17 09:08:45 +00:00
delete [] resp;
resp = NULL;
}
2005-09-17 09:08:45 +00:00
// Cancel SASL exchange
sendRequest("*");
}
catch (...)
{
if (challenge)
delete [] challenge;
if (resp)
delete [] resp;
throw;
}
2005-09-17 09:08:45 +00:00
if (challenge)
delete [] challenge;
if (resp)
delete [] resp;
break;
}
2005-09-17 09:08:45 +00:00
default:
2005-09-17 09:08:45 +00:00
cont = false;
break;
}
}
}
2005-09-17 09:08:45 +00:00
throw exceptions::authentication_error
("Could not authenticate using SASL: all mechanisms failed.");
}
2005-09-17 09:08:45 +00:00
#endif // VMIME_HAVE_SASL_SUPPORT
2005-10-03 21:29:04 +00:00
#if VMIME_HAVE_TLS_SUPPORT
void SMTPTransport::startTLS()
{
try
{
sendRequest("STARTTLS");
ref <SMTPResponse> resp = readResponse();
2005-10-03 21:29:04 +00:00
if (resp->getCode() != 220)
throw exceptions::command_error("STARTTLS", resp->getText());
2005-10-03 21:29:04 +00:00
ref <tls::TLSSession> tlsSession =
vmime::create <tls::TLSSession>(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
tlsSocket->handshake(m_timeoutHandler);
m_socket = tlsSocket;
m_secured = true;
m_cntInfos = vmime::create <tls::TLSSecuredConnectionInfos>
(m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket);
2005-10-03 21:29:04 +00:00
}
catch (exceptions::command_error&)
{
// Non-fatal error
throw;
}
catch (exception&)
{
// Fatal error
internalDisconnect();
throw;
}
}
#endif // VMIME_HAVE_TLS_SUPPORT
const bool SMTPTransport::isConnected() const
{
return (m_socket && m_socket->isConnected() && m_authentified);
}
const bool SMTPTransport::isSecuredConnection() const
{
return m_secured;
}
ref <connectionInfos> SMTPTransport::getConnectionInfos() const
{
return m_cntInfos;
}
void SMTPTransport::disconnect()
{
if (!isConnected())
throw exceptions::not_connected();
internalDisconnect();
}
void SMTPTransport::internalDisconnect()
{
2005-11-30 12:12:01 +00:00
try
{
sendRequest("QUIT");
}
catch (exception&)
{
// Not important
}
m_socket->disconnect();
m_socket = NULL;
m_timeoutHandler = NULL;
m_authentified = false;
m_extendedSMTP = false;
m_secured = false;
m_cntInfos = NULL;
}
void SMTPTransport::noop()
{
if (!isConnected())
throw exceptions::not_connected();
2005-09-17 09:08:45 +00:00
sendRequest("NOOP");
ref <SMTPResponse> resp = readResponse();
if (resp->getCode() != 250)
throw exceptions::command_error("NOOP", resp->getText());
}
void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients,
utility::inputStream& is, const utility::stream::size_type size,
utility::progressListener* progress)
{
if (!isConnected())
throw exceptions::not_connected();
// If no recipient/expeditor was found, throw an exception
if (recipients.isEmpty())
throw exceptions::no_recipient();
else if (expeditor.isEmpty())
throw exceptions::no_expeditor();
// Emit the "MAIL" command
ref <SMTPResponse> resp;
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 (int 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());
}
}
// Send the message data
sendRequest("DATA");
if ((resp = readResponse())->getCode() != 354)
{
internalDisconnect();
throw exceptions::command_error("DATA", resp->getText());
}
// Stream copy with "\n." to "\n.." transformation
utility::outputStreamSocketAdapter sos(*m_socket);
utility::dotFilteredOutputStream fos(sos);
utility::bufferedStreamCopy(is, fos, size, progress);
fos.flush();
// Send end-of-data delimiter
m_socket->sendRaw("\r\n.\r\n", 5);
if ((resp = readResponse())->getCode() != 250)
{
internalDisconnect();
throw exceptions::command_error("DATA", resp->getText());
}
}
void SMTPTransport::sendRequest(const string& buffer, const bool end)
{
m_socket->send(buffer);
if (end) m_socket->send("\r\n");
}
ref <SMTPResponse> SMTPTransport::readResponse()
2005-09-17 09:08:45 +00:00
{
return SMTPResponse::readResponse(m_socket, m_timeoutHandler);
}
// Service infos
2005-10-03 21:29:04 +00:00
SMTPServiceInfos SMTPTransport::sm_infos(false);
const serviceInfos& SMTPTransport::getInfosInstance()
{
2005-10-03 21:29:04 +00:00
return sm_infos;
}
const serviceInfos& SMTPTransport::getInfos() const
{
2005-10-03 21:29:04 +00:00
return sm_infos;
}
} // smtp
} // net
} // vmime