MAPISMTPTransport.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. // based on src/net/smtp/SMTPTransport.cpp, but with additions
  18. // we cannot use a class derived from SMTPTransport, since that class has alot of privates
  19. //
  20. // VMime library (http://www.vmime.org)
  21. // Copyright (C) 2002-2009 Vincent Richard <vincent@vincent-richard.net>
  22. //
  23. // This program is free software; you can redistribute it and/or
  24. // modify it under the terms of the GNU General Public License as
  25. // published by the Free Software Foundation; either version 3 of
  26. // the License, or (at your option) any later version.
  27. //
  28. // This program is distributed in the hope that it will be useful,
  29. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  31. // General Public License for more details.
  32. //
  33. // You should have received a copy of the GNU General Public License along
  34. // with this program; if not, write to the Free Software Foundation, Inc.,
  35. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  36. //
  37. // Linking this library statically or dynamically with other modules is making
  38. // a combined work based on this library. Thus, the terms and conditions of
  39. // the GNU General Public License cover the whole combination.
  40. //
  41. #include <kopano/platform.h>
  42. #include <memory>
  43. #include <utility>
  44. #include <kopano/tie.hpp>
  45. #include <kopano/stringutil.h>
  46. #include "MAPISMTPTransport.h"
  47. #include <vmime/net/smtp/SMTPResponse.hpp>
  48. #include <vmime/exception.hpp>
  49. #include <vmime/platform.hpp>
  50. #include <vmime/mailboxList.hpp>
  51. #include <vmime/utility/filteredStream.hpp>
  52. #include <vmime/utility/outputStreamSocketAdapter.hpp>
  53. #include <vmime/utility/streamUtils.hpp>
  54. #include <vmime/utility/stringUtils.hpp>
  55. #include <vmime/net/defaultConnectionInfos.hpp>
  56. #include <kopano/ECDebugPrint.h>
  57. #include <kopano/ECLogger.h>
  58. #include <kopano/charset/traits.h>
  59. #if VMIME_HAVE_SASL_SUPPORT
  60. # include <vmime/security/sasl/SASLContext.hpp>
  61. #endif // VMIME_HAVE_SASL_SUPPORT
  62. #if VMIME_HAVE_TLS_SUPPORT
  63. # include <vmime/net/tls/TLSSession.hpp>
  64. # include <vmime/net/tls/TLSSecuredConnectionInfos.hpp>
  65. #endif // VMIME_HAVE_TLS_SUPPORT
  66. // Helpers for service properties
  67. #define GET_PROPERTY(type, prop) \
  68. (getInfos().getPropertyValue <type>(getSession(), \
  69. dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
  70. #define HAS_PROPERTY(prop) \
  71. (getInfos().hasProperty(getSession(), \
  72. dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
  73. // register new service, really hacked from (src/net/builtinServices.inl)
  74. #include "serviceRegistration.inl"
  75. REGISTER_SERVICE(smtp::MAPISMTPTransport, mapismtp, TYPE_TRANSPORT);
  76. using namespace KCHL;
  77. namespace vmime {
  78. namespace net {
  79. namespace smtp {
  80. MAPISMTPTransport::MAPISMTPTransport(vmime::shared_ptr<session> sess,
  81. vmime::shared_ptr<security::authenticator> auth, const bool secured) :
  82. transport(sess, getInfosInstance(), auth), m_isSMTPS(secured)
  83. {
  84. }
  85. MAPISMTPTransport::~MAPISMTPTransport()
  86. {
  87. try
  88. {
  89. if (isConnected())
  90. disconnect();
  91. else if (m_socket)
  92. internalDisconnect();
  93. }
  94. catch (vmime::exception&)
  95. {
  96. // Ignore
  97. }
  98. }
  99. void MAPISMTPTransport::connect()
  100. {
  101. if (isConnected())
  102. throw exceptions::already_connected();
  103. const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS);
  104. const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT);
  105. // Create the time-out handler
  106. if (getTimeoutHandlerFactory())
  107. m_timeoutHandler = getTimeoutHandlerFactory()->create();
  108. // Create and connect the socket
  109. // @note we don't want a timeout during the connect() call
  110. // because if we set this, the side-effect is when IPv6 is tried first, it will timeout
  111. // the handler will break the loop by returning false from the handleTimeOut() function.
  112. m_socket = getSocketFactory()->create();
  113. #if VMIME_HAVE_TLS_SUPPORT
  114. if (m_isSMTPS) // dedicated port/SMTPS
  115. {
  116. auto tlsSession = tls::TLSSession::create(getCertificateVerifier(), getSession()->getTLSProperties());
  117. auto tlsSocket = tlsSession->getSocket(m_socket);
  118. m_socket = tlsSocket;
  119. m_secured = true;
  120. m_cntInfos = vmime::make_shared<tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket);
  121. }
  122. else
  123. #endif // VMIME_HAVE_TLS_SUPPORT
  124. {
  125. m_cntInfos = vmime::make_shared<defaultConnectionInfos>(address, port);
  126. }
  127. ec_log_debug("SMTP connecting to %s:%d", address.c_str(), port);
  128. m_socket->connect(address, port);
  129. ec_log_debug("SMTP server connected.");
  130. // Connection
  131. //
  132. // eg: C: <connection to server>
  133. // --- S: 220 smtp.domain.com Service ready
  134. vmime::shared_ptr<SMTPResponse> resp;
  135. if ((resp = readResponse())->getCode() != 220)
  136. {
  137. internalDisconnect();
  138. throw exceptions::connection_greeting_error(resp->getText());
  139. }
  140. // Identification
  141. helo();
  142. #if VMIME_HAVE_TLS_SUPPORT
  143. // Setup secured connection, if requested
  144. const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS)
  145. && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS);
  146. const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED)
  147. && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED);
  148. if (!m_isSMTPS && tls) // only if not SMTPS
  149. {
  150. try
  151. {
  152. startTLS();
  153. }
  154. // Non-fatal error
  155. catch (exceptions::command_error&)
  156. {
  157. if (tlsRequired)
  158. {
  159. throw;
  160. }
  161. else
  162. {
  163. // TLS is not required, so don't bother
  164. }
  165. }
  166. // Fatal error
  167. catch (...)
  168. {
  169. throw;
  170. }
  171. // Must reissue a EHLO command [RFC-2487, 5.2]
  172. helo();
  173. }
  174. #endif // VMIME_HAVE_TLS_SUPPORT
  175. // Authentication
  176. if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH))
  177. authenticate();
  178. else
  179. m_authentified = true;
  180. }
  181. void MAPISMTPTransport::helo()
  182. {
  183. // First, try Extended SMTP (ESMTP)
  184. //
  185. // eg: C: EHLO thismachine.ourdomain.com
  186. // S: 250-smtp.theserver.com
  187. // S: 250-AUTH CRAM-MD5 DIGEST-MD5
  188. // S: 250-PIPELINING
  189. // S: 250 SIZE 2555555555
  190. sendRequest("EHLO " + platform::getHandler()->getHostName());
  191. vmime::shared_ptr<SMTPResponse> resp;
  192. if ((resp = readResponse())->getCode() != 250)
  193. {
  194. // Next, try "Basic" SMTP
  195. //
  196. // eg: C: HELO thismachine.ourdomain.com
  197. // S: 250 OK
  198. sendRequest("HELO " + platform::getHandler()->getHostName());
  199. if ((resp = readResponse())->getCode() != 250)
  200. {
  201. internalDisconnect();
  202. throw exceptions::connection_greeting_error(resp->getLastLine().getText());
  203. }
  204. m_extendedSMTP = false;
  205. m_extensions.clear();
  206. }
  207. else
  208. {
  209. m_extendedSMTP = true;
  210. m_extensions.clear();
  211. // Get supported extensions from SMTP response
  212. // One extension per line, format is: EXT PARAM1 PARAM2...
  213. for (int i = 1, n = resp->getLineCount() ; i < n ; ++i)
  214. {
  215. const string line = resp->getLineAt(i).getText();
  216. std::istringstream iss(line);
  217. string ext;
  218. iss >> ext;
  219. std::vector <string> params;
  220. string param;
  221. // Special case: some servers send "AUTH=MECH [MECH MECH...]"
  222. if (ext.length() >= 5 && utility::stringUtils::toUpper(ext.substr(0, 5)) == "AUTH=")
  223. {
  224. params.push_back(utility::stringUtils::toUpper(ext.substr(5)));
  225. ext = "AUTH";
  226. }
  227. while (iss >> param)
  228. params.push_back(utility::stringUtils::toUpper(param));
  229. m_extensions[ext] = params;
  230. }
  231. }
  232. }
  233. void MAPISMTPTransport::authenticate()
  234. {
  235. if (!m_extendedSMTP)
  236. {
  237. internalDisconnect();
  238. throw exceptions::command_error("AUTH", "ESMTP not supported.");
  239. }
  240. getAuthenticator()->setService(vmime::dynamicCast<service>(shared_from_this()));
  241. #if VMIME_HAVE_SASL_SUPPORT
  242. // First, try SASL authentication
  243. if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL))
  244. {
  245. try
  246. {
  247. authenticateSASL();
  248. m_authentified = true;
  249. return;
  250. }
  251. catch (exceptions::authentication_error& e)
  252. {
  253. if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK))
  254. {
  255. // Can't fallback on normal authentication
  256. internalDisconnect();
  257. throw e;
  258. }
  259. else
  260. {
  261. // Ignore, will try normal authentication
  262. }
  263. }
  264. catch (exception& e)
  265. {
  266. internalDisconnect();
  267. throw e;
  268. }
  269. }
  270. #endif // VMIME_HAVE_SASL_SUPPORT
  271. // No other authentication method is possible
  272. throw exceptions::authentication_error("All authentication methods failed");
  273. }
  274. #if VMIME_HAVE_SASL_SUPPORT
  275. void MAPISMTPTransport::authenticateSASL()
  276. {
  277. if (!vmime::dynamicCast<security::sasl::SASLAuthenticator>(getAuthenticator()))
  278. throw exceptions::authentication_error("No SASL authenticator available.");
  279. // Obtain SASL mechanisms supported by server from ESMTP extensions
  280. const std::vector <string> saslMechs =
  281. (m_extensions.find("AUTH") != m_extensions.end())
  282. ? m_extensions["AUTH"] : std::vector <string>();
  283. if (saslMechs.empty())
  284. throw exceptions::authentication_error("No SASL mechanism available.");
  285. std::vector<vmime::shared_ptr<security::sasl::SASLMechanism> > mechList;
  286. auto saslContext = security::sasl::SASLContext::create();
  287. for (unsigned int i = 0 ; i < saslMechs.size() ; ++i)
  288. {
  289. try
  290. {
  291. mechList.push_back
  292. (saslContext->createMechanism(saslMechs[i]));
  293. }
  294. catch (exceptions::no_such_mechanism&)
  295. {
  296. // Ignore mechanism
  297. }
  298. }
  299. if (mechList.empty())
  300. throw exceptions::authentication_error("No SASL mechanism available.");
  301. // Try to suggest a mechanism among all those supported
  302. auto suggestedMech = saslContext->suggestMechanism(mechList);
  303. if (!suggestedMech)
  304. throw exceptions::authentication_error("Unable to suggest SASL mechanism.");
  305. // Allow application to choose which mechanisms to use
  306. mechList = vmime::dynamicCast<security::sasl::SASLAuthenticator>(getAuthenticator())->
  307. getAcceptableMechanisms(mechList, suggestedMech);
  308. if (mechList.empty())
  309. throw exceptions::authentication_error("No SASL mechanism available.");
  310. // Try each mechanism in the list in turn
  311. for (unsigned int i = 0 ; i < mechList.size() ; ++i)
  312. {
  313. auto mech = mechList[i];
  314. auto saslSession = saslContext->createSession("smtp", getAuthenticator(), mech);
  315. saslSession->init();
  316. sendRequest("AUTH " + mech->getName());
  317. for (bool cont = true ; cont ; )
  318. {
  319. auto response = readResponse();
  320. switch (response->getCode())
  321. {
  322. case 235:
  323. {
  324. m_socket = saslSession->getSecuredSocket(m_socket);
  325. return;
  326. }
  327. case 334:
  328. {
  329. std::unique_ptr<byte_t[]> challenge, resp;
  330. size_t challengeLen = 0, respLen = 0;
  331. try
  332. {
  333. // Extract challenge
  334. saslContext->decodeB64(response->getText(), &unique_tie(challenge), &challengeLen);
  335. // Prepare response
  336. saslSession->evaluateChallenge(challenge.get(), challengeLen, &unique_tie(resp), &respLen);
  337. // Send response
  338. sendRequest(saslContext->encodeB64(resp.get(), respLen));
  339. }
  340. catch (exceptions::sasl_exception& e)
  341. {
  342. // Cancel SASL exchange
  343. sendRequest("*");
  344. }
  345. break;
  346. }
  347. default:
  348. cont = false;
  349. break;
  350. }
  351. }
  352. }
  353. throw exceptions::authentication_error
  354. ("Could not authenticate using SASL: all mechanisms failed.");
  355. }
  356. #endif // VMIME_HAVE_SASL_SUPPORT
  357. #if VMIME_HAVE_TLS_SUPPORT
  358. void MAPISMTPTransport::startTLS()
  359. {
  360. try
  361. {
  362. sendRequest("STARTTLS");
  363. auto resp = readResponse();
  364. if (resp->getCode() != 220)
  365. throw exceptions::command_error("STARTTLS", resp->getText());
  366. auto tlsSession = tls::TLSSession::create(getCertificateVerifier(), getSession()->getTLSProperties());
  367. auto tlsSocket = tlsSession->getSocket(m_socket);
  368. tlsSocket->handshake();
  369. m_socket = tlsSocket;
  370. m_secured = true;
  371. m_cntInfos = vmime::make_shared<tls::TLSSecuredConnectionInfos>
  372. (m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket);
  373. }
  374. catch (exceptions::command_error&)
  375. {
  376. // Non-fatal error
  377. throw;
  378. }
  379. catch (exception&)
  380. {
  381. // Fatal error
  382. internalDisconnect();
  383. throw;
  384. }
  385. }
  386. #endif // VMIME_HAVE_TLS_SUPPORT
  387. bool MAPISMTPTransport::isConnected() const
  388. {
  389. return (m_socket && m_socket->isConnected() && m_authentified);
  390. }
  391. void MAPISMTPTransport::disconnect()
  392. {
  393. if (!isConnected())
  394. throw exceptions::not_connected();
  395. internalDisconnect();
  396. }
  397. void MAPISMTPTransport::internalDisconnect()
  398. {
  399. try
  400. {
  401. sendRequest("QUIT");
  402. }
  403. catch (exception&)
  404. {
  405. // Not important
  406. }
  407. m_socket->disconnect();
  408. m_socket = NULL;
  409. m_timeoutHandler = NULL;
  410. m_authentified = false;
  411. m_extendedSMTP = false;
  412. m_secured = false;
  413. m_cntInfos = NULL;
  414. }
  415. void MAPISMTPTransport::noop()
  416. {
  417. if (!isConnected())
  418. throw exceptions::not_connected();
  419. sendRequest("NOOP");
  420. auto resp = readResponse();
  421. if (resp->getCode() != 250)
  422. throw exceptions::command_error("NOOP", resp->getText());
  423. }
  424. //
  425. // Only this function is altered, to return per recipient failure.
  426. //
  427. void MAPISMTPTransport::send(const mailbox &expeditor,
  428. const mailboxList &recipients, utility::inputStream &is, size_t size,
  429. utility::progressListener *progress, const mailbox &sender)
  430. {
  431. if (!isConnected())
  432. throw exceptions::not_connected();
  433. // If no recipient/expeditor was found, throw an exception
  434. if (recipients.isEmpty())
  435. throw exceptions::no_recipient();
  436. else if (expeditor.isEmpty())
  437. throw exceptions::no_expeditor();
  438. // Emit the "MAIL" command
  439. vmime::shared_ptr<SMTPResponse> resp;
  440. string strSend;
  441. bool bDSN = m_bDSNRequest;
  442. if(bDSN && m_extensions.find("DSN") == m_extensions.end()) {
  443. ec_log_notice("SMTP server does not support Delivery Status Notifications (DSN)");
  444. bDSN = false; // Disable DSN because the server does not support this.
  445. }
  446. strSend = "MAIL FROM: <" + expeditor.getEmail().toString() + ">";
  447. if (bDSN) {
  448. strSend += " RET=HDRS";
  449. if (!m_strDSNTrackid.empty())
  450. strSend += " ENVID=" + m_strDSNTrackid;
  451. }
  452. sendRequest(strSend);
  453. resp = readResponse();
  454. if (resp->getCode() / 10 != 25) {
  455. internalDisconnect();
  456. throw exceptions::command_error("MAIL", resp->getText());
  457. }
  458. // Emit a "RCPT TO" command for each recipient
  459. mTemporaryFailedRecipients.clear();
  460. mPermanentFailedRecipients.clear();
  461. for (size_t i = 0 ; i < recipients.getMailboxCount(); ++i) {
  462. const mailbox& mbox = *recipients.getMailboxAt(i);
  463. unsigned int code;
  464. strSend = "RCPT TO: <" + mbox.getEmail().toString() + ">";
  465. if (bDSN)
  466. strSend += " NOTIFY=SUCCESS,DELAY";
  467. sendRequest(strSend);
  468. resp = readResponse();
  469. code = resp->getCode();
  470. sFailedRecip entry;
  471. auto recip_name = mbox.getName().getConvertedText(charset(CHARSET_WCHAR));
  472. entry.strRecipName.assign(reinterpret_cast<const wchar_t *>(recip_name.c_str()), recip_name.length() / sizeof(wchar_t));
  473. entry.strRecipEmail = mbox.getEmail().toString();
  474. entry.ulSMTPcode = code;
  475. entry.strSMTPResponse = resp->getText();
  476. if (code / 10 == 25) {
  477. continue;
  478. } else if (code == 421) {
  479. /* 421 4.7.0 localhorse.lh Error: too many errors */
  480. ec_log_err("RCPT line gave SMTP error: %d %s. (and now?)",
  481. resp->getCode(), resp->getText().c_str());
  482. break;
  483. } else if (code / 100 == 5) {
  484. /*
  485. * Example Postfix codes:
  486. * 501 5.1.3 Bad recipient address syntax (RCPT TO: <with spaces>)
  487. * 550 5.1.1 <fox>: Recipient address rejected: User unknown in virtual mailbox table
  488. * 550 5.7.1 REJECT action without code by means of e.g. /etc/postfix/header_checks
  489. */
  490. mPermanentFailedRecipients.push_back(std::move(entry));
  491. ec_log_err("RCPT line gave SMTP error %d %s. (no retry)",
  492. resp->getCode(), resp->getText().c_str());
  493. continue;
  494. } else if (code / 100 != 4) {
  495. mPermanentFailedRecipients.push_back(std::move(entry));
  496. ec_log_err("RCPT line gave unexpected SMTP reply %d %s. (no retry)",
  497. resp->getCode(), resp->getText().c_str());
  498. continue;
  499. }
  500. /* Other 4xx codes (disk full, ... ?) */
  501. mTemporaryFailedRecipients.push_back(std::move(entry));
  502. ec_log_err("RCPT line gave SMTP error: %d %s. (will be retried)",
  503. resp->getCode(), resp->getText().c_str());
  504. }
  505. // Send the message data
  506. sendRequest("DATA");
  507. // we also stop here if all recipients failed before
  508. if ((resp = readResponse())->getCode() != 354)
  509. {
  510. internalDisconnect();
  511. throw exceptions::command_error("DATA", format("%d %s", resp->getCode(), resp->getText().c_str()));
  512. }
  513. // Stream copy with "\n." to "\n.." transformation
  514. utility::outputStreamSocketAdapter sos(*m_socket);
  515. utility::dotFilteredOutputStream fos(sos);
  516. utility::bufferedStreamCopy(is, fos, size, progress);
  517. fos.flush();
  518. // Send end-of-data delimiter
  519. m_socket->sendRaw(reinterpret_cast<const vmime::byte_t *>("\r\n.\r\n"), 5);
  520. if ((resp = readResponse())->getCode() != 250)
  521. {
  522. internalDisconnect();
  523. throw exceptions::command_error("DATA", format("%d %s", resp->getCode(), resp->getText().c_str()));
  524. return;
  525. }
  526. // postfix: 2.0.0 Ok: queued as B36E73608E
  527. // qmail: ok 1295860788 qp 29154
  528. // exim: OK id=1PhIZ9-0002Ko-Q8
  529. ec_log_debug("SMTP: %s", resp->getText().c_str());
  530. }
  531. void MAPISMTPTransport::requestDSN(BOOL bRequest, const std::string &strTrackid)
  532. {
  533. m_bDSNRequest = bRequest;
  534. m_strDSNTrackid = strTrackid;
  535. }
  536. void MAPISMTPTransport::sendRequest(const string& buffer, const bool end)
  537. {
  538. ec_log_debug("< %s", buffer.c_str());
  539. if (end)
  540. m_socket->send(buffer + "\r\n");
  541. else
  542. m_socket->send(buffer);
  543. }
  544. vmime::shared_ptr<SMTPResponse> MAPISMTPTransport::readResponse(void)
  545. {
  546. vmime::shared_ptr<tracer> t;
  547. auto resp = SMTPResponse::readResponse(t, m_socket, m_timeoutHandler, m_response_state);
  548. m_response_state = resp->getCurrentState();
  549. ec_log_debug("> %d %s", resp->getCode(), resp->getText().c_str());
  550. return resp;
  551. }
  552. // Service infos
  553. SMTPServiceInfos MAPISMTPTransport::sm_infos(false);
  554. } // smtp
  555. } // net
  556. } // vmime