📄 smtptransport.cpp
字号:
//// VMime library (http://www.vmime.org)// Copyright (C) 2002-2008 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.//// 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/net/smtp/SMTPTransport.hpp"#include "vmime/net/smtp/SMTPResponse.hpp"#include "vmime/exception.hpp"#include "vmime/platform.hpp"#include "vmime/mailboxList.hpp"#include "vmime/utility/filteredStream.hpp"#include "vmime/utility/stringUtils.hpp"#include "vmime/net/defaultConnectionInfos.hpp"#if VMIME_HAVE_SASL_SUPPORT #include "vmime/security/sasl/SASLContext.hpp"#endif // VMIME_HAVE_SASL_SUPPORT#if VMIME_HAVE_TLS_SUPPORT #include "vmime/net/tls/TLSSession.hpp" #include "vmime/net/tls/TLSSecuredConnectionInfos.hpp"#endif // VMIME_HAVE_TLS_SUPPORT// Helpers for service properties#define GET_PROPERTY(type, prop) \ (getInfos().getPropertyValue <type>(getSession(), \ dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))#define HAS_PROPERTY(prop) \ (getInfos().hasProperty(getSession(), \ dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))namespace vmime {namespace net {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){}SMTPTransport::~SMTPTransport(){ 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 m_socket = getSocketFactory()->create();#if VMIME_HAVE_TLS_SUPPORT if (m_isSMTPS) // dedicated port/SMTPS { 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); } else#endif // VMIME_HAVE_TLS_SUPPORT { m_cntInfos = vmime::create <defaultConnectionInfos>(address, port); } 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();#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); if (!m_isSMTPS && tls) // only if not SMTPS { 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(); }#endif // VMIME_HAVE_TLS_SUPPORT // Authentication if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH)) authenticate(); else m_authentified = true;}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 // S: 250-PIPELINING // S: 250 SIZE 2555555555 sendRequest("EHLO " + platform::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 " + platform::getHandler()->getHostName()); if ((resp = readResponse())->getCode() != 250) { internalDisconnect(); throw exceptions::connection_greeting_error(resp->getLastLine().getText()); } m_extendedSMTP = false; m_extensions.clear(); } else { m_extendedSMTP = true; m_extensions.clear(); // Get supported extensions from SMTP response // One extension per line, format is: EXT PARAM1 PARAM2... for (int i = 1, n = resp->getLineCount() ; i < n ; ++i) { const string line = resp->getLineAt(i).getText(); std::istringstream iss(line); string ext; iss >> ext; std::vector <string> params; string param; // Special case: some servers send "AUTH=MECH [MECH MECH...]" if (ext.length() >= 5 && utility::stringUtils::toUpper(ext.substr(0, 5)) == "AUTH=") { params.push_back(utility::stringUtils::toUpper(ext.substr(5))); ext = "AUTH"; } while (iss >> param) params.push_back(utility::stringUtils::toUpper(param)); m_extensions[ext] = params; } }}void SMTPTransport::authenticate(){ if (!m_extendedSMTP) { 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(); throw e; } }#endif // VMIME_HAVE_SASL_SUPPORT // No other authentication method is possible throw exceptions::authentication_error("All authentication methods failed");}#if VMIME_HAVE_SASL_SUPPORTvoid SMTPTransport::authenticateSASL(){ if (!getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()) throw exceptions::authentication_error("No SASL authenticator available."); // Obtain SASL mechanisms supported by server from ESMTP extensions const std::vector <string> saslMechs = (m_extensions.find("AUTH") != m_extensions.end()) ? m_extensions["AUTH"] : std::vector <string>(); if (saslMechs.empty()) throw exceptions::authentication_error("No SASL mechanism available."); std::vector <ref <security::sasl::SASLMechanism> > mechList; ref <security::sasl::SASLContext> saslContext = vmime::create <security::sasl::SASLContext>(); for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) { try { mechList.push_back (saslContext->createMechanism(saslMechs[i])); } catch (exceptions::no_such_mechanism&) { // Ignore mechanism } } if (mechList.empty()) throw exceptions::authentication_error("No SASL mechanism available."); // Try to suggest a mechanism among all those supported ref <security::sasl::SASLMechanism> suggestedMech = saslContext->suggestMechanism(mechList); if (!suggestedMech) throw exceptions::authentication_error("Unable to suggest SASL mechanism."); // Allow application to choose which mechanisms to use mechList = getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()-> getAcceptableMechanisms(mechList, suggestedMech); if (mechList.empty()) throw exceptions::authentication_error("No SASL mechanism available."); // 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(); switch (response->getCode()) { case 235: { m_socket = saslSession->getSecuredSocket(m_socket); return; } case 334: { byte_t* challenge = 0; int challengeLen = 0; byte_t* resp = 0; int respLen = 0; try { // Extract challenge saslContext->decodeB64(response->getText(), &challenge, &challengeLen); // Prepare response saslSession->evaluateChallenge (challenge, challengeLen, &resp, &respLen); // Send response sendRequest(saslContext->encodeB64(resp, respLen)); } catch (exceptions::sasl_exception& e) { if (challenge) { delete [] challenge; challenge = NULL; } if (resp) { delete [] resp; resp = NULL; } // Cancel SASL exchange sendRequest("*"); } catch (...) { if (challenge) delete [] challenge; if (resp) delete [] resp; throw; } if (challenge) delete [] challenge; if (resp) delete [] resp; break; } default: cont = false; break; } } } throw exceptions::authentication_error ("Could not authenticate using SASL: all mechanisms failed.");}#endif // VMIME_HAVE_SASL_SUPPORT#if VMIME_HAVE_TLS_SUPPORTvoid SMTPTransport::startTLS(){ try { sendRequest("STARTTLS"); ref <SMTPResponse> resp = readResponse(); if (resp->getCode() != 220) throw exceptions::command_error("STARTTLS", resp->getText()); 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); } catch (exceptions::command_error&) { // Non-fatal error throw; } catch (exception&) { // Fatal error internalDisconnect(); throw; }}#endif // VMIME_HAVE_TLS_SUPPORTbool SMTPTransport::isConnected() const{ return (m_socket && m_socket->isConnected() && m_authentified);}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(){ 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(); 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(){ return SMTPResponse::readResponse(m_socket, m_timeoutHandler);}// Service infosSMTPServiceInfos SMTPTransport::sm_infos(false);const serviceInfos& SMTPTransport::getInfosInstance(){ return sm_infos;}const serviceInfos& SMTPTransport::getInfos() const{ return sm_infos;}} // smtp} // net} // vmime
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -