diff options
-rw-r--r-- | doc/book/net.tex | 12 | ||||
-rw-r--r-- | src/vmime/encoding.hpp | 2 | ||||
-rw-r--r-- | src/vmime/mailbox.cpp | 6 | ||||
-rw-r--r-- | src/vmime/net/smtp/SMTPConnection.cpp | 38 | ||||
-rw-r--r-- | src/vmime/net/smtp/SMTPTransport.cpp | 50 | ||||
-rw-r--r-- | src/vmime/net/smtp/SMTPTransport.hpp | 2 | ||||
-rw-r--r-- | tests/net/smtp/SMTPTransportTest.cpp | 140 | ||||
-rw-r--r-- | tests/net/smtp/SMTPTransportTestUtils.hpp | 190 | ||||
-rw-r--r-- | tests/parser/mailboxTest.cpp | 10 | ||||
-rw-r--r-- | tests/testUtils.cpp | 14 |
10 files changed, 440 insertions, 24 deletions
diff --git a/doc/book/net.tex b/doc/book/net.tex index 1e5f1803..ac3b70c3 100644 --- a/doc/book/net.tex +++ b/doc/book/net.tex @@ -931,17 +931,7 @@ vmime::shared_ptr <vmime::security::cert::X509Certificate> vmime::utility::inputStreamAdapter is(certFile); vmime::shared_ptr <vmime::security::cert::X509Certificate> cert; - // Try DER format - cert = vmime::security::cert::X509Certificate::import - (is, vmime::security::cert::X509Certificate::FORMAT_DER); - - if (cert != NULL) - return cert; - - // Try PEM format - is.reset(); - cert = vmime::security::cert::X509Certificate::import - (is, vmime::security::cert::X509Certificate::FORMAT_PEM); + cert = vmime::security::cert::X509Certificate::import(is); return cert; } diff --git a/src/vmime/encoding.hpp b/src/vmime/encoding.hpp index 3148f899..90733807 100644 --- a/src/vmime/encoding.hpp +++ b/src/vmime/encoding.hpp @@ -58,8 +58,6 @@ public: encoding(const string& name, const EncodingUsage usage); encoding(const encoding& enc); -public: - /** Return the name of the encoding. * See the constants in vmime::encodingTypes. * diff --git a/src/vmime/mailbox.cpp b/src/vmime/mailbox.cpp index 7c6d5e3b..c4b230c4 100644 --- a/src/vmime/mailbox.cpp +++ b/src/vmime/mailbox.cpp @@ -234,8 +234,6 @@ void mailbox::parseImpl ++p; } - - break; } else { @@ -245,10 +243,6 @@ void mailbox::parseImpl } else if (state == State_Address) { - // Skip '<' character - if (*p == '<') - ++p; - bool escaped = false; int comment = 0; diff --git a/src/vmime/net/smtp/SMTPConnection.cpp b/src/vmime/net/smtp/SMTPConnection.cpp index 3785126a..8709efe4 100644 --- a/src/vmime/net/smtp/SMTPConnection.cpp +++ b/src/vmime/net/smtp/SMTPConnection.cpp @@ -40,6 +40,10 @@ #if VMIME_HAVE_SASL_SUPPORT #include "vmime/security/sasl/SASLContext.hpp" +#else + #include "vmime/utility/encoder/b64Encoder.hpp" + #include "vmime/utility/inputStreamStringAdapter.hpp" + #include "vmime/utility/outputStreamStringAdapter.hpp" #endif // VMIME_HAVE_SASL_SUPPORT #if VMIME_HAVE_TLS_SUPPORT @@ -48,7 +52,6 @@ #endif // VMIME_HAVE_TLS_SUPPORT - // Helpers for service properties #define GET_PROPERTY(type, prop) \ (m_transport.lock()->getInfos().getPropertyValue <type>(getSession(), \ @@ -307,6 +310,39 @@ void SMTPConnection::authenticate() throw; } } +#else // no SASL + + // allow AUTH PLAIN over TLS - it is a popular and simple mechanism + if (m_secured) + { + std::vector <string> authMechs; + hasExtension("AUTH", &authMechs); + + if (authMechs.empty()) + throw exceptions::authentication_error("No AUTH mechanism available."); + + const string plain("PLAIN"); + if (std::find(authMechs.begin(), authMechs.end(), plain) != authMechs.end()) + { + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + const string authToken = username + '\0' + username + '\0' + password; + auto encoder = new vmime::utility::encoder::b64Encoder(); + utility::inputStreamStringAdapter in(authToken); + string authTokenBase64; + utility::outputStreamStringAdapter out(authTokenBase64); + encoder->encode(in, out); + sendRequest(SMTPCommand::AUTH(plain, authTokenBase64)); + shared_ptr <SMTPResponse> response = readResponse(); + const int code = response ? response->getCode() : -1; + if (code == 235) + { + m_authenticated = true; + return; + } + } + } + #endif // VMIME_HAVE_SASL_SUPPORT // No other authentication method is possible diff --git a/src/vmime/net/smtp/SMTPTransport.cpp b/src/vmime/net/smtp/SMTPTransport.cpp index 2af6a195..25eb72a7 100644 --- a/src/vmime/net/smtp/SMTPTransport.cpp +++ b/src/vmime/net/smtp/SMTPTransport.cpp @@ -152,6 +152,22 @@ void SMTPTransport::noop() } +// 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, @@ -176,14 +192,40 @@ void SMTPTransport::sendEnvelope if (needReset) commands->addCommand(SMTPCommand::RSET()); - // Emit the "MAIL" command + // 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, hasSize ? size : 0)); + { + commands->addCommand( + SMTPCommand::MAIL( + sender, hasSMTPUTF8 && needSMTPUTF8, hasSize ? size : 0 + ) + ); + } else - commands->addCommand(SMTPCommand::MAIL(expeditor, hasSMTPUTF8, hasSize ? size : 0)); + { + commands->addCommand( + SMTPCommand::MAIL( + expeditor, hasSMTPUTF8 && needSMTPUTF8, hasSize ? size : 0 + ) + ); + } // Now, we will need to reset next time m_needReset = true; @@ -192,7 +234,7 @@ void SMTPTransport::sendEnvelope for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { const mailbox& mbox = *recipients.getMailboxAt(i); - commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8)); + commands->addCommand(SMTPCommand::RCPT(mbox, hasSMTPUTF8 && needSMTPUTF8)); } // Prepare sending of message data diff --git a/src/vmime/net/smtp/SMTPTransport.hpp b/src/vmime/net/smtp/SMTPTransport.hpp index a0f02418..7b266d3d 100644 --- a/src/vmime/net/smtp/SMTPTransport.hpp +++ b/src/vmime/net/smtp/SMTPTransport.hpp @@ -91,6 +91,8 @@ public: private: + static bool mailboxNeedsUTF8(const mailbox& mb); + /** Send the MAIL and RCPT commands to the server, checking the * response, and using pipelining if supported by the server. * Optionally, the DATA command can also be sent. diff --git a/tests/net/smtp/SMTPTransportTest.cpp b/tests/net/smtp/SMTPTransportTest.cpp index dd5546cc..cbfab4f5 100644 --- a/tests/net/smtp/SMTPTransportTest.cpp +++ b/tests/net/smtp/SMTPTransportTest.cpp @@ -33,12 +33,16 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest) VMIME_TEST_LIST_BEGIN +/* VMIME_TEST(testConnectToInvalidServer) VMIME_TEST(testGreetingError) VMIME_TEST(testMAILandRCPT) VMIME_TEST(testChunking) VMIME_TEST(testSize_Chunking) VMIME_TEST(testSize_NoChunking) +*/ + VMIME_TEST(testSMTPUTF8_available) + VMIME_TEST(testSMTPUTF8_notAvailable) VMIME_TEST_LIST_END @@ -168,5 +172,141 @@ VMIME_TEST_SUITE_BEGIN(SMTPTransportTest) vmime::net::smtp::SMTPMessageSizeExceedsMaxLimitsException); } + void testSMTPUTF8_available() + { + // Test with UTF8 sender + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <true> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp( + vmime::emailAddress( + vmime::word("expéditeur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + ); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + + // Test with UTF8 recipient only + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <true> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp("[email protected]"); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + } + + void testSMTPUTF8_notAvailable() + { + // Test with UTF8 sender + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <false> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp( + vmime::emailAddress( + vmime::word("expéditeur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + ); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + + // Test with UTF8 recipient only + { + vmime::shared_ptr <vmime::net::session> session = vmime::net::session::create(); + + vmime::shared_ptr <vmime::net::transport> tr = session->getTransport + (vmime::utility::url("smtp://localhost")); + + tr->setSocketFactory(vmime::make_shared <testSocketFactory <UTF8SMTPTestSocket <false> > >()); + tr->setTimeoutHandlerFactory(vmime::make_shared <testTimeoutHandlerFactory>()); + + VASSERT_NO_THROW("Connection", tr->connect()); + + vmime::mailbox exp("[email protected]"); + + vmime::mailboxList recips; + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>("[email protected]")); + recips.appendMailbox(vmime::make_shared <vmime::mailbox>( + vmime::emailAddress( + vmime::word("récepteur", vmime::charsets::UTF_8), + vmime::word("test.vmime.org") + ) + )); + + vmime::string data("Message data"); + vmime::utility::inputStreamStringAdapter is(data); + + tr->send(exp, recips, is, 0); + } + } + VMIME_TEST_SUITE_END diff --git a/tests/net/smtp/SMTPTransportTestUtils.hpp b/tests/net/smtp/SMTPTransportTestUtils.hpp index c1224216..b5af2f10 100644 --- a/tests/net/smtp/SMTPTransportTestUtils.hpp +++ b/tests/net/smtp/SMTPTransportTestUtils.hpp @@ -591,3 +591,193 @@ public: }; typedef SMTPBigTestMessage <4194304> SMTPBigTestMessage4MB; + + + +/** SMTP test server for SMTPUTF8 extension. + */ +template <bool SUPPORTS_UTF8> +class UTF8SMTPTestSocket : public lineBasedTestSocket +{ +public: + + UTF8SMTPTestSocket() + { + if (SUPPORTS_UTF8) + { + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<ré[email protected]>"); + } + else + { + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + m_rcptLines.insert("RCPT TO:<[email protected]>"); + } + + m_state = STATE_NOT_CONNECTED; + m_ehloSent = m_mailSent = m_rcptSent = m_dataSent = m_quitSent = false; + } + + ~UTF8SMTPTestSocket() + { + } + + void onConnected() + { + localSend("220 test.vmime.org Service ready\r\n"); + processCommand(); + + m_state = STATE_COMMAND; + } + + void processCommand() + { + if (!haveMoreLines()) + return; + + vmime::string line = getNextLine(); + std::istringstream iss(line); + + switch (m_state) + { + case STATE_NOT_CONNECTED: + + localSend("451 Requested action aborted: invalid state\r\n"); + break; + + case STATE_COMMAND: + { + std::string cmd; + iss >> cmd; + + if (cmd.empty()) + { + localSend("500 Syntax error, command unrecognized\r\n"); + } + else if (cmd == "EHLO") + { + if (SUPPORTS_UTF8) + { + localSend("250-test.vmime.org\r\n"); + localSend("250 SMTPUTF8\r\n"); + } + else + { + localSend("250 test.vmime.org\r\n"); + } + + m_ehloSent = true; + } + else if (cmd == "HELO") + { + VASSERT("Client must not send the HELO command, as EHLO succeeded", false); + } + else if (cmd == "MAIL") + { + VASSERT("Client must send the EHLO command", m_ehloSent); + VASSERT("The MAIL command must be sent only one time", !m_mailSent); + + if (SUPPORTS_UTF8) + { + VASSERT( + "MAIL", + std::string("MAIL FROM:<[email protected]> SMTPUTF8") == line + || std::string("MAIL FROM:<expé[email protected]> SMTPUTF8") == line + ); + } + else + { + VASSERT( + "MAIL", + std::string("MAIL FROM:<[email protected]>") == line + || std::string("MAIL FROM:<[email protected]>") == line + ); + } + + localSend("250 OK\r\n"); + + m_mailSent = true; + } + else if (cmd == "RCPT") + { + std::set <vmime::string>::iterator it = m_rcptLines.find(line); + + VASSERT(std::string("RCPT not found: '") + line + "'", it != m_rcptLines.end()); + + m_rcptLines.erase(it); + + localSend("250 OK, recipient accepted\r\n"); + + m_rcptSent = true; + } + else if (cmd == "DATA") + { + VASSERT("Client must send the MAIL command", m_mailSent); + VASSERT("Client must send the RCPT command", m_rcptSent); + VASSERT("All recipients", m_rcptLines.empty()); + + localSend("354 Ready to accept data; end with <CRLF>.<CRLF>\r\n"); + + m_state = STATE_DATA; + m_msgData.clear(); + + m_dataSent = true; + } + else if (cmd == "NOOP") + { + localSend("250 Completed\r\n"); + } + else if (cmd == "QUIT") + { + m_quitSent = true; + + localSend("221 test.vmime.org Service closing transmission channel\r\n"); + } + else + { + localSend("502 Command not implemented\r\n"); + } + + break; + } + case STATE_DATA: + { + if (line == ".") + { + VASSERT_EQ("Data", "Message data\r\n", m_msgData); + + localSend("250 Message accepted for delivery\r\n"); + m_state = STATE_COMMAND; + } + else + { + m_msgData += line + "\r\n"; + } + + break; + } + + } + + processCommand(); + } + +private: + + enum State + { + STATE_NOT_CONNECTED, + STATE_COMMAND, + STATE_DATA + }; + + int m_state; + + std::set <vmime::string> m_rcptLines; + + std::string m_msgData; + + bool m_ehloSent, m_mailSent, m_rcptSent, m_dataSent, m_quitSent; +}; diff --git a/tests/parser/mailboxTest.cpp b/tests/parser/mailboxTest.cpp index 6bf6670c..92fcd98f 100644 --- a/tests/parser/mailboxTest.cpp +++ b/tests/parser/mailboxTest.cpp @@ -30,6 +30,7 @@ VMIME_TEST_SUITE_BEGIN(mailboxTest) VMIME_TEST(testParse) VMIME_TEST(testEmptyEmailAddress) VMIME_TEST(testSeparatorInComment) + VMIME_TEST(testAddressInName) VMIME_TEST_LIST_END @@ -145,5 +146,14 @@ VMIME_TEST_SUITE_BEGIN(mailboxTest) VASSERT_EQ("email2", "[email protected]", mbox2->getEmail()); } + void testAddressInName() + { + vmime::mailbox mbox; + mbox.parse("[email protected] <[email protected]>"); + + VASSERT_EQ("name", vmime::text("[email protected]"), mbox.getName()); + VASSERT_EQ("email", "[email protected]", mbox.getEmail()); + } + VMIME_TEST_SUITE_END diff --git a/tests/testUtils.cpp b/tests/testUtils.cpp index 0eb2bfd4..1e8e0c99 100644 --- a/tests/testUtils.cpp +++ b/tests/testUtils.cpp @@ -26,6 +26,11 @@ #include "vmime/utility/stringUtils.hpp" #include <cstring> +#include <iostream> + + +// Enable to output socket send/receive on standard output +#define DEBUG_SOCKET_IN_OUT 0 @@ -156,6 +161,11 @@ vmime::size_t testSocket::sendRawNonBlocking(const vmime::byte_t* buffer, const void testSocket::localSend(const vmime::string& buffer) { m_inBuffer += buffer; + +#if DEBUG_SOCKET_IN_OUT + std::cout << "> " << vmime::utility::stringUtils::trim(buffer) << std::endl; +#endif // DEBUG_SOCKET_IN_OUT + } @@ -232,6 +242,10 @@ void lineBasedTestSocket::onDataReceived() if (!line.empty() && line[line.length() - 1] == '\r') line.erase(line.end() - 1, line.end()); +#if DEBUG_SOCKET_IN_OUT + std::cout << "< " << vmime::utility::stringUtils::trim(line) << std::endl; +#endif // DEBUG_SOCKET_IN_OUT + m_lines.push_back(line); m_buffer.erase(m_buffer.begin(), m_buffer.begin() + eol + 1); } |