From 84e570bbbb1188d2bcd3f03ff519fbc9217e624a Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Sun, 16 Mar 2014 22:52:40 +0100 Subject: [PATCH] Connection trace facility. --- examples/example6.cpp | 259 +++--------------- examples/example6_authenticator.hpp | 106 +++++++ examples/example6_certificateVerifier.hpp | 62 +++++ examples/example6_timeoutHandler.hpp | 48 ++++ examples/example6_tracer.hpp | 53 ++++ src/vmime/net/imap/IMAPCommand.cpp | 2 +- src/vmime/net/imap/IMAPCommand.hpp | 2 +- src/vmime/net/imap/IMAPConnection.cpp | 26 ++ src/vmime/net/imap/IMAPConnection.hpp | 5 + src/vmime/net/imap/IMAPFolder.cpp | 3 + src/vmime/net/imap/IMAPParser.hpp | 21 ++ src/vmime/net/pop3/POP3Command.cpp | 33 ++- src/vmime/net/pop3/POP3Command.hpp | 13 +- src/vmime/net/pop3/POP3Connection.cpp | 19 +- src/vmime/net/pop3/POP3Connection.hpp | 3 + src/vmime/net/pop3/POP3Message.cpp | 2 +- src/vmime/net/pop3/POP3Response.cpp | 40 ++- src/vmime/net/pop3/POP3Response.hpp | 6 +- src/vmime/net/service.cpp | 12 + src/vmime/net/service.hpp | 8 +- .../smtp/SMTPChunkingOutputStreamAdapter.cpp | 6 + src/vmime/net/smtp/SMTPCommand.cpp | 23 +- src/vmime/net/smtp/SMTPCommand.hpp | 16 +- src/vmime/net/smtp/SMTPCommandSet.cpp | 23 +- src/vmime/net/smtp/SMTPCommandSet.hpp | 3 +- src/vmime/net/smtp/SMTPConnection.cpp | 23 +- src/vmime/net/smtp/SMTPConnection.hpp | 3 + src/vmime/net/smtp/SMTPResponse.cpp | 16 +- src/vmime/net/smtp/SMTPResponse.hpp | 9 +- src/vmime/net/smtp/SMTPTransport.cpp | 14 +- src/vmime/net/tracer.cpp | 72 +++++ src/vmime/net/tracer.hpp | 109 ++++++++ tests/net/smtp/SMTPCommandSetTest.cpp | 26 +- tests/net/smtp/SMTPCommandTest.cpp | 4 +- tests/net/smtp/SMTPResponseTest.cpp | 27 +- 35 files changed, 810 insertions(+), 287 deletions(-) create mode 100644 examples/example6_authenticator.hpp create mode 100644 examples/example6_certificateVerifier.hpp create mode 100644 examples/example6_timeoutHandler.hpp create mode 100644 examples/example6_tracer.hpp create mode 100644 src/vmime/net/tracer.cpp create mode 100644 src/vmime/net/tracer.hpp diff --git a/examples/example6.cpp b/examples/example6.cpp index 95019c03..5d8c7e64 100644 --- a/examples/example6.cpp +++ b/examples/example6.cpp @@ -31,178 +31,17 @@ #include "vmime/vmime.hpp" #include "vmime/platforms/posix/posixHandler.hpp" +#include "example6_tracer.hpp" +#include "example6_authenticator.hpp" +#include "example6_certificateVerifier.hpp" +#include "example6_timeoutHandler.hpp" + // Global session object static vmime::shared_ptr g_session = vmime::make_shared (); -#if VMIME_HAVE_SASL_SUPPORT - -// SASL authentication handler -class interactiveAuthenticator : public vmime::security::sasl::defaultSASLAuthenticator -{ - const std::vector > getAcceptableMechanisms - (const std::vector >& available, - vmime::shared_ptr suggested) const - { - std::cout << std::endl << "Available SASL mechanisms:" << std::endl; - - for (unsigned int i = 0 ; i < available.size() ; ++i) - { - std::cout << " " << available[i]->getName(); - - if (suggested && available[i]->getName() == suggested->getName()) - std::cout << "(suggested)"; - } - - std::cout << std::endl << std::endl; - - return defaultSASLAuthenticator::getAcceptableMechanisms(available, suggested); - } - - void setSASLMechanism(vmime::shared_ptr mech) - { - std::cout << "Trying '" << mech->getName() << "' authentication mechanism" << std::endl; - - defaultSASLAuthenticator::setSASLMechanism(mech); - } - - const vmime::string getUsername() const - { - if (m_username.empty()) - m_username = getUserInput("Username"); - - return m_username; - } - - const vmime::string getPassword() const - { - if (m_password.empty()) - m_password = getUserInput("Password"); - - return m_password; - } - - static const vmime::string getUserInput(const std::string& prompt) - { - std::cout << prompt << ": "; - std::cout.flush(); - - vmime::string res; - std::getline(std::cin, res); - - return res; - } - -private: - - mutable vmime::string m_username; - mutable vmime::string m_password; -}; - -#else // !VMIME_HAVE_SASL_SUPPORT - -// Simple authentication handler -class interactiveAuthenticator : public vmime::security::defaultAuthenticator -{ - const vmime::string getUsername() const - { - if (m_username.empty()) - m_username = getUserInput("Username"); - - return m_username; - } - - const vmime::string getPassword() const - { - if (m_password.empty()) - m_password = getUserInput("Password"); - - return m_password; - } - - static const vmime::string getUserInput(const std::string& prompt) - { - std::cout << prompt << ": "; - std::cout.flush(); - - vmime::string res; - std::getline(std::cin, res); - - return res; - } - -private: - - mutable vmime::string m_username; - mutable vmime::string m_password; -}; - -#endif // VMIME_HAVE_SASL_SUPPORT - - -#if VMIME_HAVE_TLS_SUPPORT - -// Certificate verifier (TLS/SSL) -class interactiveCertificateVerifier : public vmime::security::cert::defaultCertificateVerifier -{ -public: - - void verify(vmime::shared_ptr chain, const vmime::string& hostname) - { - try - { - setX509TrustedCerts(m_trustedCerts); - - defaultCertificateVerifier::verify(chain, hostname); - } - catch (vmime::exceptions::certificate_verification_exception&) - { - // Obtain subject's certificate - vmime::shared_ptr cert = chain->getAt(0); - - std::cout << std::endl; - std::cout << "Server sent a '" << cert->getType() << "'" << " certificate." << std::endl; - std::cout << "Do you want to accept this certificate? (Y/n) "; - std::cout.flush(); - - std::string answer; - std::getline(std::cin, answer); - - if (answer.length() != 0 && - (answer[0] == 'Y' || answer[0] == 'y')) - { - // Accept it, and remember user's choice for later - if (cert->getType() == "X.509") - { - m_trustedCerts.push_back(vmime::dynamicCast - (cert)); - - setX509TrustedCerts(m_trustedCerts); - defaultCertificateVerifier::verify(chain, hostname); - } - - return; - } - - throw vmime::exceptions::certificate_verification_exception - ("User did not accept the certificate."); - } - } - -private: - - static std::vector > m_trustedCerts; -}; - - -std::vector > - interactiveCertificateVerifier::m_trustedCerts; - -#endif // VMIME_HAVE_TLS_SUPPORT - - /** Returns the messaging protocols supported by VMime. * * @param type service type (vmime::net::service::TYPE_STORE or @@ -290,53 +129,6 @@ static std::ostream& operator<<(std::ostream& os, const vmime::exception& e) } -/** Time out handler. - * Used to stop the current operation after too much time, or if the user - * requested cancellation. - */ -class timeoutHandler : public vmime::net::timeoutHandler -{ -public: - - bool isTimeOut() - { - // This is a cancellation point: return true if you want to cancel - // the current operation. If you return true, handleTimeOut() will - // be called just after this, and before actually cancelling the - // operation - return false; - } - - void resetTimeOut() - { - // Called at the beginning of an operation (eg. connecting, - // a read() or a write() on a socket...) - } - - bool handleTimeOut() - { - // If isTimeOut() returned true, this function will be called. This - // allows you to interact with the user, ie. display a prompt to - // know whether he wants to cancel the operation. - - // If you return true here, the operation will be actually cancelled. - // If not, the time out is reset and the operation continues. - return true; - } -}; - - -class timeoutHandlerFactory : public vmime::net::timeoutHandlerFactory -{ -public: - - vmime::shared_ptr create() - { - return vmime::make_shared (); - } -}; - - /** Print the MIME structure of a message on the standard output. * * @param s structure object @@ -445,8 +237,12 @@ static void sendMessage() vmime::utility::url url(urlString); - vmime::shared_ptr tr = - g_session->getTransport(url, vmime::make_shared ()); + vmime::shared_ptr tr; + + if (url.getUsername().empty() || url.getPassword().empty()) + tr = g_session->getTransport(url, vmime::make_shared ()); + else + tr = g_session->getTransport(url); #if VMIME_HAVE_TLS_SUPPORT @@ -465,7 +261,12 @@ static void sendMessage() // You can also set some properties (see example7 to know the properties // available for each service). For example, for SMTP: -// tr->setProperty("options.need-authentication", true); + if (!url.getUsername().empty() || !url.getPassword().empty()) + tr->setProperty("options.need-authentication", true); + + // Trace communication between client and server + vmime::shared_ptr traceStream = vmime::make_shared (); + tr->setTracerFactory(vmime::make_shared (traceStream)); // Information about the mail std::cout << "Enter email of the expeditor (eg. me@somewhere.com): "; @@ -520,6 +321,12 @@ static void sendMessage() // ... // tr->send(&msg); + // Display connection log + std::cout << std::endl; + std::cout << "Connection Trace:" << std::endl; + std::cout << "=================" << std::endl; + std::cout << traceStream->str(); + tr->disconnect(); } catch (vmime::exception& e) @@ -580,6 +387,10 @@ static void connectStore() #endif // VMIME_HAVE_TLS_SUPPORT + // Trace communication between client and server + vmime::shared_ptr traceStream = vmime::make_shared (); + st->setTracerFactory(vmime::make_shared (traceStream)); + // Connect to the mail store st->connect(); @@ -621,6 +432,7 @@ static void connectStore() choices.push_back("Change folder"); choices.push_back("Add message (to the current folder)"); choices.push_back("Copy message (into the current folder)"); + choices.push_back("Display trace output"); choices.push_back("Return to main menu"); const int choice = printMenu(choices); @@ -928,9 +740,18 @@ static void connectStore() break; } - // Main menu + // Display trace output case 12: + std::cout << std::endl; + std::cout << "Connection Trace:" << std::endl; + std::cout << "=================" << std::endl; + std::cout << traceStream->str(); + break; + + // Main menu + case 13: + f->close(true); // 'true' to expunge deleted messages cont = false; break; @@ -979,7 +800,9 @@ static void connectStore() std::cerr << std::endl; std::cerr << "std::exception: " << e.what() << std::endl; } - } + } // for(cont) + + st->disconnect(); } catch (vmime::exception& e) { diff --git a/examples/example6_authenticator.hpp b/examples/example6_authenticator.hpp new file mode 100644 index 00000000..b46f8ebd --- /dev/null +++ b/examples/example6_authenticator.hpp @@ -0,0 +1,106 @@ + + +#if VMIME_HAVE_SASL_SUPPORT + +// SASL authentication handler +class interactiveAuthenticator : public vmime::security::sasl::defaultSASLAuthenticator +{ + const std::vector > getAcceptableMechanisms + (const std::vector >& available, + vmime::shared_ptr suggested) const + { + std::cout << std::endl << "Available SASL mechanisms:" << std::endl; + + for (unsigned int i = 0 ; i < available.size() ; ++i) + { + std::cout << " " << available[i]->getName(); + + if (suggested && available[i]->getName() == suggested->getName()) + std::cout << "(suggested)"; + } + + std::cout << std::endl << std::endl; + + return defaultSASLAuthenticator::getAcceptableMechanisms(available, suggested); + } + + void setSASLMechanism(vmime::shared_ptr mech) + { + std::cout << "Trying '" << mech->getName() << "' authentication mechanism" << std::endl; + + defaultSASLAuthenticator::setSASLMechanism(mech); + } + + const vmime::string getUsername() const + { + if (m_username.empty()) + m_username = getUserInput("Username"); + + return m_username; + } + + const vmime::string getPassword() const + { + if (m_password.empty()) + m_password = getUserInput("Password"); + + return m_password; + } + + static const vmime::string getUserInput(const std::string& prompt) + { + std::cout << prompt << ": "; + std::cout.flush(); + + vmime::string res; + std::getline(std::cin, res); + + return res; + } + +private: + + mutable vmime::string m_username; + mutable vmime::string m_password; +}; + +#else // !VMIME_HAVE_SASL_SUPPORT + +// Simple authentication handler +class interactiveAuthenticator : public vmime::security::defaultAuthenticator +{ + const vmime::string getUsername() const + { + if (m_username.empty()) + m_username = getUserInput("Username"); + + return m_username; + } + + const vmime::string getPassword() const + { + if (m_password.empty()) + m_password = getUserInput("Password"); + + return m_password; + } + + static const vmime::string getUserInput(const std::string& prompt) + { + std::cout << prompt << ": "; + std::cout.flush(); + + vmime::string res; + std::getline(std::cin, res); + + return res; + } + +private: + + mutable vmime::string m_username; + mutable vmime::string m_password; +}; + +#endif // VMIME_HAVE_SASL_SUPPORT + diff --git a/examples/example6_certificateVerifier.hpp b/examples/example6_certificateVerifier.hpp new file mode 100644 index 00000000..65f0f4ad --- /dev/null +++ b/examples/example6_certificateVerifier.hpp @@ -0,0 +1,62 @@ + + +#if VMIME_HAVE_TLS_SUPPORT + +// Certificate verifier (TLS/SSL) +class interactiveCertificateVerifier : public vmime::security::cert::defaultCertificateVerifier +{ +public: + + void verify(vmime::shared_ptr chain, const vmime::string& hostname) + { + try + { + setX509TrustedCerts(m_trustedCerts); + + defaultCertificateVerifier::verify(chain, hostname); + } + catch (vmime::exceptions::certificate_verification_exception&) + { + // Obtain subject's certificate + vmime::shared_ptr cert = chain->getAt(0); + + std::cout << std::endl; + std::cout << "Server sent a '" << cert->getType() << "'" << " certificate." << std::endl; + std::cout << "Do you want to accept this certificate? (Y/n) "; + std::cout.flush(); + + std::string answer; + std::getline(std::cin, answer); + + if (answer.length() != 0 && + (answer[0] == 'Y' || answer[0] == 'y')) + { + // Accept it, and remember user's choice for later + if (cert->getType() == "X.509") + { + m_trustedCerts.push_back(vmime::dynamicCast + (cert)); + + setX509TrustedCerts(m_trustedCerts); + defaultCertificateVerifier::verify(chain, hostname); + } + + return; + } + + throw vmime::exceptions::certificate_verification_exception + ("User did not accept the certificate."); + } + } + +private: + + static std::vector > m_trustedCerts; +}; + + +std::vector > + interactiveCertificateVerifier::m_trustedCerts; + +#endif // VMIME_HAVE_TLS_SUPPORT + diff --git a/examples/example6_timeoutHandler.hpp b/examples/example6_timeoutHandler.hpp new file mode 100644 index 00000000..7c2eb0e2 --- /dev/null +++ b/examples/example6_timeoutHandler.hpp @@ -0,0 +1,48 @@ + + +/** Time out handler. + * Used to stop the current operation after too much time, or if the user + * requested cancellation. + */ +class timeoutHandler : public vmime::net::timeoutHandler +{ +public: + + bool isTimeOut() + { + // This is a cancellation point: return true if you want to cancel + // the current operation. If you return true, handleTimeOut() will + // be called just after this, and before actually cancelling the + // operation + return false; + } + + void resetTimeOut() + { + // Called at the beginning of an operation (eg. connecting, + // a read() or a write() on a socket...) + } + + bool handleTimeOut() + { + // If isTimeOut() returned true, this function will be called. This + // allows you to interact with the user, ie. display a prompt to + // know whether he wants to cancel the operation. + + // If you return true here, the operation will be actually cancelled. + // If not, the time out is reset and the operation continues. + return true; + } +}; + + +class timeoutHandlerFactory : public vmime::net::timeoutHandlerFactory +{ +public: + + vmime::shared_ptr create() + { + return vmime::make_shared (); + } +}; + diff --git a/examples/example6_tracer.hpp b/examples/example6_tracer.hpp new file mode 100644 index 00000000..19d0f040 --- /dev/null +++ b/examples/example6_tracer.hpp @@ -0,0 +1,53 @@ + +/** Tracer used to demonstrate logging communication between client and server. + */ + +class myTracer : public vmime::net::tracer +{ +public: + + myTracer(vmime::shared_ptr stream, + vmime::shared_ptr serv, const int connectionId) + : m_stream(stream), m_service(serv), m_connectionId(connectionId) + { + } + + void traceSend(const vmime::string& line) + { + *m_stream << "[" << m_service->getProtocolName() << ":" << m_connectionId + << "] C: " << line << std::endl; + } + + void traceReceive(const vmime::string& line) + { + *m_stream << "[" << m_service->getProtocolName() << ":" << m_connectionId + << "] S: " << line << std::endl; + } + +private: + + vmime::shared_ptr m_stream; + vmime::shared_ptr m_service; + const int m_connectionId; +}; + +class myTracerFactory : public vmime::net::tracerFactory +{ +public: + + myTracerFactory(vmime::shared_ptr stream) + : m_stream(stream) + { + } + + vmime::shared_ptr create + (vmime::shared_ptr serv, const int connectionId) + { + return vmime::make_shared (m_stream, serv, connectionId); + } + +private: + + vmime::shared_ptr m_stream; +}; + diff --git a/src/vmime/net/imap/IMAPCommand.cpp b/src/vmime/net/imap/IMAPCommand.cpp index 19c3b723..b1840d8d 100644 --- a/src/vmime/net/imap/IMAPCommand.cpp +++ b/src/vmime/net/imap/IMAPCommand.cpp @@ -56,7 +56,7 @@ shared_ptr IMAPCommand::LOGIN(const string& username, const string std::ostringstream trace; trace.imbue(std::locale::classic()); - trace << "LOGIN "; + trace << "LOGIN {username} {password}"; return createCommand(cmd.str(), trace.str()); } diff --git a/src/vmime/net/imap/IMAPCommand.hpp b/src/vmime/net/imap/IMAPCommand.hpp index c0a0d9b1..6003c227 100644 --- a/src/vmime/net/imap/IMAPCommand.hpp +++ b/src/vmime/net/imap/IMAPCommand.hpp @@ -98,7 +98,7 @@ public: /** Returns the full text of the command, suitable for outputing * to the tracer. * - * @return trace text (eg. "LOGIN myusername ***") + * @return trace text (eg. "LOGIN {username} {password}") */ virtual const string getTraceText() const; diff --git a/src/vmime/net/imap/IMAPConnection.cpp b/src/vmime/net/imap/IMAPConnection.cpp index 57f2ff5e..28a71662 100644 --- a/src/vmime/net/imap/IMAPConnection.cpp +++ b/src/vmime/net/imap/IMAPConnection.cpp @@ -71,10 +71,16 @@ IMAPConnection::IMAPConnection(shared_ptr store, shared_ptr (); + if (store->getTracerFactory()) + m_tracer = store->getTracerFactory()->create(store, ++connectionId); + m_parser = make_shared (); m_parser->setTag(m_tag); + m_parser->setTracer(m_tracer); } @@ -439,6 +445,9 @@ void IMAPConnection::authenticateSASL() const string respB64 = saslContext->encodeB64(resp, respLen) + "\r\n"; sendRaw(utility::stringUtils::bytesFromString(respB64), respB64.length()); + if (m_tracer) + m_tracer->traceSendBytes(respB64.length() - 2, "SASL exchange"); + // Server capabilities may change when logged in invalidateCapabilities(); } @@ -458,6 +467,9 @@ void IMAPConnection::authenticateSASL() // Cancel SASL exchange sendRaw(utility::stringUtils::bytesFromString("*\r\n"), 3); + + if (m_tracer) + m_tracer->traceSend("*"); } catch (...) { @@ -752,6 +764,14 @@ void IMAPConnection::sendCommand(shared_ptr cmd) m_socket->send("\r\n"); m_firstTag = false; + + if (m_tracer) + { + std::ostringstream oss; + oss << string(*m_tag) << " " << cmd->getText(); + + m_tracer->traceSend(oss.str()); + } } @@ -816,6 +836,12 @@ void IMAPConnection::setSocket(shared_ptr sok) } +shared_ptr IMAPConnection::getTracer() +{ + return m_tracer; +} + + shared_ptr IMAPConnection::getTag() { return m_tag; diff --git a/src/vmime/net/imap/IMAPConnection.hpp b/src/vmime/net/imap/IMAPConnection.hpp index da1b9ba4..b863ce33 100644 --- a/src/vmime/net/imap/IMAPConnection.hpp +++ b/src/vmime/net/imap/IMAPConnection.hpp @@ -33,6 +33,7 @@ #include "vmime/net/socket.hpp" #include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/tracer.hpp" #include "vmime/net/session.hpp" #include "vmime/net/connectionInfos.hpp" @@ -105,6 +106,8 @@ public: shared_ptr getSocket() const; void setSocket(shared_ptr sok); + shared_ptr getTracer(); + shared_ptr getTag(); bool isMODSEQDisabled() const; @@ -151,6 +154,8 @@ private: bool m_noModSeq; + shared_ptr m_tracer; + void internalDisconnect(); diff --git a/src/vmime/net/imap/IMAPFolder.cpp b/src/vmime/net/imap/IMAPFolder.cpp index e602ea6e..00fee93c 100644 --- a/src/vmime/net/imap/IMAPFolder.cpp +++ b/src/vmime/net/imap/IMAPFolder.cpp @@ -1124,6 +1124,9 @@ messageSet IMAPFolder::addMessage m_connection->sendRaw(utility::stringUtils::bytesFromString("\r\n"), 2); + if (m_connection->getTracer()) + m_connection->getTracer()->traceSendBytes(current); + if (progress) progress->stop(total); diff --git a/src/vmime/net/imap/IMAPParser.hpp b/src/vmime/net/imap/IMAPParser.hpp index ba0ea195..d38ac14a 100644 --- a/src/vmime/net/imap/IMAPParser.hpp +++ b/src/vmime/net/imap/IMAPParser.hpp @@ -49,6 +49,7 @@ #include "vmime/net/timeoutHandler.hpp" #include "vmime/net/socket.hpp" +#include "vmime/net/tracer.hpp" #include "vmime/net/imap/IMAPTag.hpp" @@ -281,6 +282,15 @@ public: m_timeoutHandler = toh; } + /** Set the tracer currently used by this parser. + * + * @param tr tracer + */ + void setTracer(shared_ptr tr) + { + m_tracer = tr; + } + /** Set whether we operate in strict mode (this may not work * with some servers which are not fully standard-compliant). * @@ -5687,6 +5697,7 @@ private: weak_ptr m_tag; weak_ptr m_socket; + shared_ptr m_tracer; utility::progressListener* m_progress; @@ -5731,6 +5742,13 @@ public: std::cout << std::endl << "Read line:" << std::endl << line << std::endl; #endif + if (m_tracer) + { + string::size_type len = line.length(); + while (len != 0 && (line[len - 1] == '\r' || line[len - 1] == '\n')) --len; + m_tracer->traceReceive(line.substr(0, len)); + } + return (line); } @@ -5860,6 +5878,9 @@ public: m_progress->progress(len, count); } + if (m_tracer) + m_tracer->traceReceiveBytes(count); + if (m_progress) m_progress->stop(count); } diff --git a/src/vmime/net/pop3/POP3Command.cpp b/src/vmime/net/pop3/POP3Command.cpp index 3c544fc0..9dfaf17a 100644 --- a/src/vmime/net/pop3/POP3Command.cpp +++ b/src/vmime/net/pop3/POP3Command.cpp @@ -42,8 +42,8 @@ namespace net { namespace pop3 { -POP3Command::POP3Command(const string& text) - : m_text(text) +POP3Command::POP3Command(const string& text, const string& traceText) + : m_text(text), m_traceText(traceText) { } @@ -109,7 +109,11 @@ shared_ptr POP3Command::USER(const string& username) cmd.imbue(std::locale::classic()); cmd << "USER " << username; - return createCommand(cmd.str()); + std::ostringstream trace; + trace.imbue(std::locale::classic()); + trace << "USER {username}"; + + return createCommand(cmd.str(), trace.str()); } @@ -120,7 +124,11 @@ shared_ptr POP3Command::PASS(const string& password) cmd.imbue(std::locale::classic()); cmd << "PASS " << password; - return createCommand(cmd.str()); + std::ostringstream trace; + trace.imbue(std::locale::classic()); + trace << "PASS {password}"; + + return createCommand(cmd.str(), trace.str()); } @@ -215,9 +223,13 @@ shared_ptr POP3Command::QUIT() // static -shared_ptr POP3Command::createCommand(const string& text) +shared_ptr POP3Command::createCommand + (const string& text, const string& traceText) { - return shared_ptr (new POP3Command(text)); + if (traceText.empty()) + return shared_ptr (new POP3Command(text, text)); + else + return shared_ptr (new POP3Command(text, traceText)); } @@ -227,9 +239,18 @@ const string POP3Command::getText() const } +const string POP3Command::getTraceText() const +{ + return m_traceText; +} + + void POP3Command::send(shared_ptr conn) { conn->getSocket()->send(m_text + "\r\n"); + + if (conn->getTracer()) + conn->getTracer()->traceSend(m_traceText); } diff --git a/src/vmime/net/pop3/POP3Command.hpp b/src/vmime/net/pop3/POP3Command.hpp index e34e4e2b..45aff661 100644 --- a/src/vmime/net/pop3/POP3Command.hpp +++ b/src/vmime/net/pop3/POP3Command.hpp @@ -76,9 +76,10 @@ public: /** Creates a new POP3 command with the specified text. * * @param text command text + * @param traceText trace text (if empty, command text is used) * @return a new POP3Command object */ - static shared_ptr createCommand(const string& text); + static shared_ptr createCommand(const string& text, const string& traceText = ""); /** Sends this command over the specified connection. * @@ -93,14 +94,22 @@ public: */ virtual const string getText() const; + /** Returns the full text of the command, suitable for outputing + * to the tracer. + * + * @return trace text (eg. "USER myusername") + */ + virtual const string getTraceText() const; + protected: - POP3Command(const string& text); + POP3Command(const string& text, const string& traceText); POP3Command(const POP3Command&); private: string m_text; + string m_traceText; }; diff --git a/src/vmime/net/pop3/POP3Connection.cpp b/src/vmime/net/pop3/POP3Connection.cpp index 45c668f5..0fd7e7ee 100644 --- a/src/vmime/net/pop3/POP3Connection.cpp +++ b/src/vmime/net/pop3/POP3Connection.cpp @@ -67,6 +67,10 @@ POP3Connection::POP3Connection(shared_ptr store, shared_ptr getTracerFactory()) + m_tracer = store->getTracerFactory()->create(store, ++connectionId); } @@ -500,7 +504,11 @@ void POP3Connection::authenticateSASL() (challenge, challengeLen, &resp, &respLen); // Send response - m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n"); + const string respB64 = saslContext->encodeB64(resp, respLen) + "\r\n"; + m_socket->sendRaw(utility::stringUtils::bytesFromString(respB64), respB64.length()); + + if (m_tracer) + m_tracer->traceSendBytes(respB64.length() - 2, "SASL exchange"); } catch (exceptions::sasl_exception& e) { @@ -518,6 +526,9 @@ void POP3Connection::authenticateSASL() // Cancel SASL exchange m_socket->send("*\r\n"); + + if (m_tracer) + m_tracer->traceSend("*"); } catch (...) { @@ -677,6 +688,12 @@ shared_ptr POP3Connection::getSocket() } +shared_ptr POP3Connection::getTracer() +{ + return m_tracer; +} + + shared_ptr POP3Connection::getTimeoutHandler() { return m_timeoutHandler; diff --git a/src/vmime/net/pop3/POP3Connection.hpp b/src/vmime/net/pop3/POP3Connection.hpp index 3622f745..f40f9562 100644 --- a/src/vmime/net/pop3/POP3Connection.hpp +++ b/src/vmime/net/pop3/POP3Connection.hpp @@ -37,6 +37,7 @@ #include "vmime/net/timeoutHandler.hpp" #include "vmime/net/session.hpp" #include "vmime/net/connectionInfos.hpp" +#include "vmime/net/tracer.hpp" #include "vmime/net/pop3/POP3Command.hpp" #include "vmime/net/pop3/POP3Response.hpp" @@ -80,6 +81,7 @@ public: virtual shared_ptr getTimeoutHandler(); virtual shared_ptr getAuthenticator(); virtual shared_ptr getSession(); + virtual shared_ptr getTracer(); private: @@ -104,6 +106,7 @@ private: shared_ptr m_auth; shared_ptr m_socket; shared_ptr m_timeoutHandler; + shared_ptr m_tracer; bool m_authenticated; bool m_secured; diff --git a/src/vmime/net/pop3/POP3Message.cpp b/src/vmime/net/pop3/POP3Message.cpp index ff1aa88f..0b7e6d37 100644 --- a/src/vmime/net/pop3/POP3Message.cpp +++ b/src/vmime/net/pop3/POP3Message.cpp @@ -149,7 +149,7 @@ void POP3Message::extract try { POP3Response::readLargeResponse - (store->getConnection(), os, progress, m_size); + (store->getConnection(), os, progress, m_size == -1 ? 0 : m_size); } catch (exceptions::command_error& e) { diff --git a/src/vmime/net/pop3/POP3Response.cpp b/src/vmime/net/pop3/POP3Response.cpp index bdbfb23c..de3c2cf3 100644 --- a/src/vmime/net/pop3/POP3Response.cpp +++ b/src/vmime/net/pop3/POP3Response.cpp @@ -46,8 +46,8 @@ namespace net { namespace pop3 { -POP3Response::POP3Response(shared_ptr sok, shared_ptr toh) - : m_socket(sok), m_timeoutHandler(toh) +POP3Response::POP3Response(shared_ptr sok, shared_ptr toh, shared_ptr tracer) + : m_socket(sok), m_timeoutHandler(toh), m_tracer(tracer) { } @@ -56,7 +56,7 @@ POP3Response::POP3Response(shared_ptr sok, shared_ptr shared_ptr POP3Response::readResponse(shared_ptr conn) { shared_ptr resp = shared_ptr - (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler(), conn->getTracer())); string buffer; resp->readResponseImpl(buffer, /* multiLine */ false); @@ -65,6 +65,9 @@ shared_ptr POP3Response::readResponse(shared_ptr resp->m_code = getResponseCode(buffer); stripResponseCode(buffer, resp->m_text); + if (resp->m_tracer) + resp->m_tracer->traceReceive(buffer); + return resp; } @@ -73,7 +76,7 @@ shared_ptr POP3Response::readResponse(shared_ptr shared_ptr POP3Response::readMultilineResponse(shared_ptr conn) { shared_ptr resp = shared_ptr - (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler(), conn->getTracer())); string buffer; resp->readResponseImpl(buffer, /* multiLine */ true); @@ -88,8 +91,20 @@ shared_ptr POP3Response::readMultilineResponse(shared_ptr m_tracer) + resp->m_tracer->traceReceive(firstLine); + while (std::getline(iss, line, '\n')) - resp->m_lines.push_back(utility::stringUtils::trim(line)); + { + line = utility::stringUtils::trim(line); + resp->m_lines.push_back(line); + + if (resp->m_tracer) + resp->m_tracer->traceReceive(line); + } + + if (resp->m_tracer) + resp->m_tracer->traceReceive("."); return resp; } @@ -101,15 +116,22 @@ shared_ptr POP3Response::readLargeResponse utility::progressListener* progress, const size_t predictedSize) { shared_ptr resp = shared_ptr - (new POP3Response(conn->getSocket(), conn->getTimeoutHandler())); + (new POP3Response(conn->getSocket(), conn->getTimeoutHandler(), conn->getTracer())); string firstLine; - resp->readResponseImpl(firstLine, os, progress, predictedSize); + const size_t length = resp->readResponseImpl(firstLine, os, progress, predictedSize); resp->m_firstLine = firstLine; resp->m_code = getResponseCode(firstLine); stripResponseCode(firstLine, resp->m_text); + if (resp->m_tracer) + { + resp->m_tracer->traceReceive(firstLine); + resp->m_tracer->traceReceiveBytes(length - firstLine.length()); + resp->m_tracer->traceReceive("."); + } + return resp; } @@ -230,7 +252,7 @@ void POP3Response::readResponseImpl(string& buffer, const bool multiLine) } -void POP3Response::readResponseImpl +size_t POP3Response::readResponseImpl (string& firstLine, utility::outputStream& os, utility::progressListener* progress, const size_t predictedSize) { @@ -327,6 +349,8 @@ void POP3Response::readResponseImpl if (progress) progress->stop(total); + + return current; } diff --git a/src/vmime/net/pop3/POP3Response.hpp b/src/vmime/net/pop3/POP3Response.hpp index 20477b5e..d85b5405 100644 --- a/src/vmime/net/pop3/POP3Response.hpp +++ b/src/vmime/net/pop3/POP3Response.hpp @@ -38,6 +38,7 @@ #include "vmime/utility/progressListener.hpp" #include "vmime/net/socket.hpp" +#include "vmime/net/tracer.hpp" namespace vmime { @@ -143,10 +144,10 @@ public: private: - POP3Response(shared_ptr sok, shared_ptr toh); + POP3Response(shared_ptr sok, shared_ptr toh, shared_ptr tracer); void readResponseImpl(string& buffer, const bool multiLine); - void readResponseImpl + size_t readResponseImpl (string& firstLine, utility::outputStream& os, utility::progressListener* progress, const size_t predictedSize); @@ -163,6 +164,7 @@ private: shared_ptr m_socket; shared_ptr m_timeoutHandler; + shared_ptr m_tracer; string m_firstLine; ResponseCode m_code; diff --git a/src/vmime/net/service.cpp b/src/vmime/net/service.cpp index 482b141c..3cf94d5e 100644 --- a/src/vmime/net/service.cpp +++ b/src/vmime/net/service.cpp @@ -136,6 +136,18 @@ shared_ptr service::getSocketFactory() } +void service::setTracerFactory(shared_ptr tf) +{ + m_tracerFactory = tf; +} + + +shared_ptr service::getTracerFactory() +{ + return m_tracerFactory; +} + + void service::setTimeoutHandlerFactory(shared_ptr thf) { m_toHandlerFactory = thf; diff --git a/src/vmime/net/service.hpp b/src/vmime/net/service.hpp index 59352dea..b00d15ac 100644 --- a/src/vmime/net/service.hpp +++ b/src/vmime/net/service.hpp @@ -40,6 +40,7 @@ #include "vmime/net/socket.hpp" #include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/tracer.hpp" #if VMIME_HAVE_TLS_SUPPORT #include "vmime/security/cert/certificateVerifier.hpp" @@ -183,6 +184,11 @@ public: */ shared_ptr getTimeoutHandlerFactory(); + + void setTracerFactory(shared_ptr tf); + + shared_ptr getTracerFactory(); + /** Set a property for this service (service prefix is added automatically). * * WARNING: this sets the property on the session object, so all service @@ -219,8 +225,8 @@ private: #endif // VMIME_HAVE_TLS_SUPPORT shared_ptr m_socketFactory; - shared_ptr m_toHandlerFactory; + shared_ptr m_tracerFactory; }; diff --git a/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp index 35ff4e0d..15e24cf1 100644 --- a/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp +++ b/src/vmime/net/smtp/SMTPChunkingOutputStreamAdapter.cpp @@ -73,6 +73,9 @@ void SMTPChunkingOutputStreamAdapter::sendChunk m_progress->progress(m_totalSent, m_totalSize); } + if (m_connection->getTracer()) + m_connection->getTracer()->traceSendBytes(count); + // If PIPELINING is not supported, read one response for this BDAT command if (!m_connection->hasExtension("PIPELINING")) { @@ -143,6 +146,9 @@ void SMTPChunkingOutputStreamAdapter::flush() if (m_progress) m_progress->stop(m_totalSize); + + if (m_connection->getTracer()) + m_connection->getTracer()->traceSendBytes(m_bufferSize); } diff --git a/src/vmime/net/smtp/SMTPCommand.cpp b/src/vmime/net/smtp/SMTPCommand.cpp index 2120caf5..27c8ec1b 100644 --- a/src/vmime/net/smtp/SMTPCommand.cpp +++ b/src/vmime/net/smtp/SMTPCommand.cpp @@ -30,6 +30,7 @@ #include "vmime/net/smtp/SMTPCommand.hpp" #include "vmime/net/socket.hpp" +#include "vmime/net/tracer.hpp" #include "vmime/mailbox.hpp" #include "vmime/utility/outputStreamAdapter.hpp" @@ -40,8 +41,8 @@ namespace net { namespace smtp { -SMTPCommand::SMTPCommand(const string& text) - : m_text(text) +SMTPCommand::SMTPCommand(const string& text, const string& traceText) + : m_text(text), m_traceText(traceText) { } @@ -199,9 +200,12 @@ shared_ptr SMTPCommand::QUIT() // static -shared_ptr SMTPCommand::createCommand(const string& text) +shared_ptr SMTPCommand::createCommand(const string& text, const string& traceText) { - return shared_ptr (new SMTPCommand(text)); + if (traceText.empty()) + return shared_ptr (new SMTPCommand(text, text)); + else + return shared_ptr (new SMTPCommand(text, traceText)); } @@ -211,9 +215,18 @@ const string SMTPCommand::getText() const } -void SMTPCommand::writeToSocket(shared_ptr sok) +const string SMTPCommand::getTraceText() const +{ + return m_traceText; +} + + +void SMTPCommand::writeToSocket(shared_ptr sok, shared_ptr tr) { sok->send(m_text + "\r\n"); + + if (tr) + tr->traceSend(m_traceText); } diff --git a/src/vmime/net/smtp/SMTPCommand.hpp b/src/vmime/net/smtp/SMTPCommand.hpp index a5b8cca5..7c00d156 100644 --- a/src/vmime/net/smtp/SMTPCommand.hpp +++ b/src/vmime/net/smtp/SMTPCommand.hpp @@ -46,6 +46,7 @@ namespace net { class socket; class timeoutHandler; +class tracer; namespace smtp { @@ -76,13 +77,14 @@ public: * @param text command text * @return a new SMTPCommand object */ - static shared_ptr createCommand(const string& text); + static shared_ptr createCommand(const string& text, const string& traceText = ""); /** Sends this command to the specified socket. * * @param sok socket to which the command will be written + * @param tr tracer */ - virtual void writeToSocket(shared_ptr sok); + virtual void writeToSocket(shared_ptr sok, shared_ptr tr); /** Returns the full text of the command, including command name * and parameters (if any). @@ -91,14 +93,22 @@ public: */ virtual const string getText() const; + /** Returns the full text of the command, suitable for outputing + * to the tracer. + * + * @return trace text (eg. "LOGIN myusername ***") + */ + virtual const string getTraceText() const; + protected: - SMTPCommand(const string& text); + SMTPCommand(const string& text, const string& traceText); SMTPCommand(const SMTPCommand&); private: string m_text; + string m_traceText; }; diff --git a/src/vmime/net/smtp/SMTPCommandSet.cpp b/src/vmime/net/smtp/SMTPCommandSet.cpp index 3e03427c..85f58fed 100644 --- a/src/vmime/net/smtp/SMTPCommandSet.cpp +++ b/src/vmime/net/smtp/SMTPCommandSet.cpp @@ -42,7 +42,7 @@ namespace smtp { SMTPCommandSet::SMTPCommandSet(const bool pipeline) - : SMTPCommand(""), m_pipeline(pipeline), + : SMTPCommand("", ""), m_pipeline(pipeline), m_started(false), m_lastCommandSent() { } @@ -67,7 +67,7 @@ void SMTPCommandSet::addCommand(shared_ptr cmd) } -void SMTPCommandSet::writeToSocket(shared_ptr sok) +void SMTPCommandSet::writeToSocket(shared_ptr sok, shared_ptr tr) { if (m_pipeline) { @@ -78,7 +78,7 @@ void SMTPCommandSet::writeToSocket(shared_ptr sok) it != m_commands.end() ; ++it) { shared_ptr cmd = *it; - cmd->writeToSocket(sok); + cmd->writeToSocket(sok, tr); } } @@ -99,7 +99,7 @@ void SMTPCommandSet::writeToSocket(shared_ptr sok) shared_ptr cmd = m_commands.front(); m_commands.pop_front(); - cmd->writeToSocket(sok); + cmd->writeToSocket(sok, tr); m_lastCommandSent = cmd; } @@ -124,6 +124,21 @@ const string SMTPCommandSet::getText() const } +const string SMTPCommandSet::getTraceText() const +{ + std::ostringstream cmd; + cmd.imbue(std::locale::classic()); + + for (std::list >::const_iterator it = m_commands.begin() ; + it != m_commands.end() ; ++it) + { + cmd << (*it)->getTraceText() << "\r\n"; + } + + return cmd.str(); +} + + bool SMTPCommandSet::isFinished() const { return (m_pipeline && m_started) || (m_commands.size() == 0 && m_started); diff --git a/src/vmime/net/smtp/SMTPCommandSet.hpp b/src/vmime/net/smtp/SMTPCommandSet.hpp index 8e744c2b..83c7fe46 100644 --- a/src/vmime/net/smtp/SMTPCommandSet.hpp +++ b/src/vmime/net/smtp/SMTPCommandSet.hpp @@ -78,9 +78,10 @@ public: shared_ptr getLastCommandSent() const; - void writeToSocket(shared_ptr sok); + void writeToSocket(shared_ptr sok, shared_ptr tr); const string getText() const; + const string getTraceText() const; private: diff --git a/src/vmime/net/smtp/SMTPConnection.cpp b/src/vmime/net/smtp/SMTPConnection.cpp index 4985b563..99a8e8f5 100644 --- a/src/vmime/net/smtp/SMTPConnection.cpp +++ b/src/vmime/net/smtp/SMTPConnection.cpp @@ -68,6 +68,10 @@ SMTPConnection::SMTPConnection(shared_ptr transport, shared_ptr : m_transport(transport), m_auth(auth), m_socket(null), m_timeoutHandler(null), m_authenticated(false), m_secured(false), m_extendedSMTP(false) { + static int connectionId = 0; + + if (transport->getTracerFactory()) + m_tracer = transport->getTracerFactory()->create(transport, ++connectionId); } @@ -416,7 +420,11 @@ void SMTPConnection::authenticateSASL() (challenge, challengeLen, &resp, &respLen); // Send response - m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n"); + const string respB64 = saslContext->encodeB64(resp, respLen) + "\r\n"; + m_socket->sendRaw(utility::stringUtils::bytesFromString(respB64), respB64.length()); + + if (m_tracer) + m_tracer->traceSendBytes(respB64.length() - 2, "SASL exchange"); } catch (exceptions::sasl_exception& e) { @@ -434,6 +442,9 @@ void SMTPConnection::authenticateSASL() // Cancel SASL exchange m_socket->send("*\r\n"); + + if (m_tracer) + m_tracer->traceSend("*"); } catch (...) { @@ -557,14 +568,14 @@ void SMTPConnection::internalDisconnect() void SMTPConnection::sendRequest(shared_ptr cmd) { - cmd->writeToSocket(m_socket); + cmd->writeToSocket(m_socket, m_tracer); } shared_ptr SMTPConnection::readResponse() { shared_ptr resp = SMTPResponse::readResponse - (m_socket, m_timeoutHandler, m_responseState); + (m_tracer, m_socket, m_timeoutHandler, m_responseState); m_responseState = resp->getCurrentState(); @@ -608,6 +619,12 @@ shared_ptr SMTPConnection::getSocket() } +shared_ptr SMTPConnection::getTracer() +{ + return m_tracer; +} + + shared_ptr SMTPConnection::getTimeoutHandler() { return m_timeoutHandler; diff --git a/src/vmime/net/smtp/SMTPConnection.hpp b/src/vmime/net/smtp/SMTPConnection.hpp index cc59ef34..c7614920 100644 --- a/src/vmime/net/smtp/SMTPConnection.hpp +++ b/src/vmime/net/smtp/SMTPConnection.hpp @@ -37,6 +37,7 @@ #include "vmime/net/timeoutHandler.hpp" #include "vmime/net/session.hpp" #include "vmime/net/connectionInfos.hpp" +#include "vmime/net/tracer.hpp" #include "vmime/net/smtp/SMTPCommand.hpp" #include "vmime/net/smtp/SMTPResponse.hpp" @@ -80,6 +81,7 @@ public: virtual shared_ptr getTimeoutHandler(); virtual shared_ptr getAuthenticator(); virtual shared_ptr getSession(); + virtual shared_ptr getTracer(); void sendRequest(shared_ptr cmd); shared_ptr readResponse(); @@ -106,6 +108,7 @@ private: shared_ptr m_auth; shared_ptr m_socket; shared_ptr m_timeoutHandler; + shared_ptr m_tracer; SMTPResponse::state m_responseState; diff --git a/src/vmime/net/smtp/SMTPResponse.cpp b/src/vmime/net/smtp/SMTPResponse.cpp index 3d8bf15c..75dbbf6b 100644 --- a/src/vmime/net/smtp/SMTPResponse.cpp +++ b/src/vmime/net/smtp/SMTPResponse.cpp @@ -34,6 +34,7 @@ #include "vmime/net/socket.hpp" #include "vmime/net/timeoutHandler.hpp" +#include "vmime/net/tracer.hpp" #include @@ -43,8 +44,10 @@ namespace net { namespace smtp { -SMTPResponse::SMTPResponse(shared_ptr sok, shared_ptr toh, const state& st) - : m_socket(sok), m_timeoutHandler(toh), +SMTPResponse::SMTPResponse + (shared_ptr tr, shared_ptr sok, + shared_ptr toh, const state& st) + : m_socket(sok), m_timeoutHandler(toh), m_tracer(tr), m_responseBuffer(st.responseBuffer), m_responseContinues(false) { } @@ -95,9 +98,11 @@ const string SMTPResponse::getText() const // static shared_ptr SMTPResponse::readResponse - (shared_ptr sok, shared_ptr toh, const state& st) + (shared_ptr tr, shared_ptr sok, + shared_ptr toh, const state& st) { - shared_ptr resp = shared_ptr (new SMTPResponse(sok, toh, st)); + shared_ptr resp = + shared_ptr (new SMTPResponse(tr, sok, toh, st)); resp->readResponse(); @@ -142,6 +147,9 @@ const string SMTPResponse::readResponseLine() currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); m_responseBuffer = currentBuffer; + if (m_tracer) + m_tracer->traceReceive(line); + return line; } diff --git a/src/vmime/net/smtp/SMTPResponse.hpp b/src/vmime/net/smtp/SMTPResponse.hpp index 000448ac..4d88b6b5 100644 --- a/src/vmime/net/smtp/SMTPResponse.hpp +++ b/src/vmime/net/smtp/SMTPResponse.hpp @@ -41,6 +41,7 @@ namespace net { class socket; class timeoutHandler; +class tracer; namespace smtp { @@ -95,6 +96,7 @@ public: /** Receive and parse a new SMTP response from the * specified socket. * + * @param tr tracer * @param sok socket from which to read * @param toh time-out handler * @param st previous state of response parser for the specified socket @@ -102,7 +104,9 @@ public: * @throws exceptions::operation_timed_out if no data * has been received within the granted time */ - static shared_ptr readResponse(shared_ptr sok, shared_ptr toh, const state& st); + static shared_ptr readResponse + (shared_ptr tr, shared_ptr sok, + shared_ptr toh, const state& st); /** Return the SMTP response code. * @@ -150,7 +154,7 @@ public: private: - SMTPResponse(shared_ptr sok, shared_ptr toh, const state& st); + SMTPResponse(shared_ptr tr, shared_ptr sok, shared_ptr toh, const state& st); SMTPResponse(const SMTPResponse&); void readResponse(); @@ -166,6 +170,7 @@ private: shared_ptr m_socket; shared_ptr m_timeoutHandler; + shared_ptr m_tracer; string m_responseBuffer; bool m_responseContinues; diff --git a/src/vmime/net/smtp/SMTPTransport.cpp b/src/vmime/net/smtp/SMTPTransport.cpp index 56adfa21..ed775442 100644 --- a/src/vmime/net/smtp/SMTPTransport.cpp +++ b/src/vmime/net/smtp/SMTPTransport.cpp @@ -210,7 +210,7 @@ void SMTPTransport::sendEnvelope // Read response for "RSET" command if (needReset) { - commands->writeToSocket(m_connection->getSocket()); + commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); resp = m_connection->readResponse(); @@ -227,7 +227,7 @@ void SMTPTransport::sendEnvelope } // Read response for "MAIL" command - commands->writeToSocket(m_connection->getSocket()); + commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); if ((resp = m_connection->readResponse())->getCode() != 250) { @@ -257,7 +257,7 @@ void SMTPTransport::sendEnvelope // Read responses for "RCPT TO" commands for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i) { - commands->writeToSocket(m_connection->getSocket()); + commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); resp = m_connection->readResponse(); @@ -291,7 +291,7 @@ void SMTPTransport::sendEnvelope // Read response for "DATA" command if (sendDATACommand) { - commands->writeToSocket(m_connection->getSocket()); + commands->writeToSocket(m_connection->getSocket(), m_connection->getTracer()); if ((resp = m_connection->readResponse())->getCode() != 354) { @@ -326,6 +326,12 @@ void SMTPTransport::send // Send end-of-data delimiter m_connection->getSocket()->send("\r\n.\r\n"); + if (m_connection->getTracer()) + { + m_connection->getTracer()->traceSendBytes(size); + m_connection->getTracer()->traceSend("."); + } + shared_ptr resp; if ((resp = m_connection->readResponse())->getCode() != 250) diff --git a/src/vmime/net/tracer.cpp b/src/vmime/net/tracer.cpp new file mode 100644 index 00000000..b3610cdd --- /dev/null +++ b/src/vmime/net/tracer.cpp @@ -0,0 +1,72 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2014 Vincent Richard +// +// 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 + + +#include "tracer.hpp" + + +#include + + +namespace vmime { +namespace net { + + +void tracer::traceReceiveBytes(const size_t count, const string& state) +{ + std::ostringstream oss; + oss << "{..."; + + if (!state.empty()) + oss << state << ": "; + + oss << count << " bytes of data...}"; + + traceReceive(oss.str()); +} + + +void tracer::traceSendBytes(const size_t count, const string& state) +{ + std::ostringstream oss; + oss << "{..."; + + if (!state.empty()) + oss << state << ": "; + + oss << count << " bytes of data...}"; + + traceSend(oss.str()); +} + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES diff --git a/src/vmime/net/tracer.hpp b/src/vmime/net/tracer.hpp new file mode 100644 index 00000000..e30c823c --- /dev/null +++ b/src/vmime/net/tracer.hpp @@ -0,0 +1,109 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2014 Vincent Richard +// +// 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_TRACER_HPP_INCLUDED +#define VMIME_NET_TRACER_HPP_INCLUDED + + +#include "vmime/config.hpp" + + +#if VMIME_HAVE_MESSAGING_FEATURES + + +#include "vmime/base.hpp" + + +namespace vmime { +namespace net { + + +class service; + + +/** Base class for an object used to trace network communication + * between the client and the server. + */ + +class VMIME_EXPORT tracer : public object +{ +public: + + virtual ~tracer() { } + + /** Trace raw bytes which have been received. + * + * @param count number of bytes + * @param state protocol state (eg. "SASL exchange"), or empty + */ + virtual void traceReceiveBytes(const size_t count, const string& state = ""); + + /** Trace raw bytes which have been sent. + * + * @param count number of bytes + * @param state protocol state (eg. "SASL exchange"), or empty + */ + virtual void traceSendBytes(const size_t count, const string& state = ""); + + /** Trace a command line which has been sent. + * + * @param line command line + */ + virtual void traceSend(const string& line) = 0; + + /** Trace a response line which has been received. + * + * @param line response line + */ + virtual void traceReceive(const string& line) = 0; +}; + + + +/** A class to create 'tracer' objects. + */ + +class VMIME_EXPORT tracerFactory : public object +{ +public: + + virtual ~tracerFactory() { } + + /** Creates a tracer for the specified service. + * + * @param serv messaging service + * @param connectionId an identifier for the connection to distinguate between + * different connections used by a service + * @return a new tracer + */ + virtual shared_ptr create(shared_ptr serv, const int connectionId) = 0; +}; + + +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES + +#endif // VMIME_NET_TRACER_HPP_INCLUDED diff --git a/tests/net/smtp/SMTPCommandSetTest.cpp b/tests/net/smtp/SMTPCommandSetTest.cpp index af250078..f419eb40 100644 --- a/tests/net/smtp/SMTPCommandSetTest.cpp +++ b/tests/net/smtp/SMTPCommandSetTest.cpp @@ -69,15 +69,16 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) VASSERT_NO_THROW("No throw 2", cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2"))); VASSERT_EQ("Text", "MY_COMMAND1\r\nMY_COMMAND2\r\n", cset->getText()); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_FALSE("Finished", cset->isFinished()); // Can't add commands when writing to socket has started VASSERT_THROW("Throw", cset->addCommand(SMTPCommand::createCommand("MY_COMMAND3")), std::runtime_error); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_TRUE("Finished", cset->isFinished()); } @@ -90,10 +91,11 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) VASSERT_NO_THROW("No throw 2", cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2"))); VASSERT_EQ("Text", "MY_COMMAND1\r\nMY_COMMAND2\r\n", cset->getText()); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); vmime::string response; - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_TRUE("Finished", cset->isFinished()); sok->localReceive(response); @@ -110,15 +112,16 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) cset->addCommand(SMTPCommand::createCommand("MY_COMMAND1")); cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2")); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); vmime::string response; - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); sok->localReceive(response); VASSERT_EQ("Receive cmd 1", "MY_COMMAND1\r\n", response); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); sok->localReceive(response); VASSERT_EQ("Receive cmd 2", "MY_COMMAND2\r\n", response); @@ -131,10 +134,11 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) cset->addCommand(SMTPCommand::createCommand("MY_COMMAND1")); cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2")); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); vmime::string response; - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); sok->localReceive(response); VASSERT_EQ("Receive cmds", "MY_COMMAND1\r\nMY_COMMAND2\r\n", response); @@ -147,12 +151,13 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) cset->addCommand(SMTPCommand::createCommand("MY_COMMAND1")); cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2")); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_EQ("Cmd 1", "MY_COMMAND1", cset->getLastCommandSent()->getText()); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_EQ("Cmd 2", "MY_COMMAND2", cset->getLastCommandSent()->getText()); } @@ -163,12 +168,13 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandSetTest) cset->addCommand(SMTPCommand::createCommand("MY_COMMAND1")); cset->addCommand(SMTPCommand::createCommand("MY_COMMAND2")); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_EQ("Cmd 1", "MY_COMMAND1", cset->getLastCommandSent()->getText()); - cset->writeToSocket(sok); + cset->writeToSocket(sok, tracer); VASSERT_EQ("Cmd 2", "MY_COMMAND2", cset->getLastCommandSent()->getText()); } diff --git a/tests/net/smtp/SMTPCommandTest.cpp b/tests/net/smtp/SMTPCommandTest.cpp index 9480948c..9b3daa73 100644 --- a/tests/net/smtp/SMTPCommandTest.cpp +++ b/tests/net/smtp/SMTPCommandTest.cpp @@ -231,8 +231,10 @@ VMIME_TEST_SUITE_BEGIN(SMTPCommandTest) { vmime::shared_ptr cmd = SMTPCommand::createCommand("MY_COMMAND param1 param2"); + vmime::shared_ptr tracer; vmime::shared_ptr sok = vmime::make_shared (); - cmd->writeToSocket(sok); + + cmd->writeToSocket(sok, tracer); vmime::string response; sok->localReceive(response); diff --git a/tests/net/smtp/SMTPResponseTest.cpp b/tests/net/smtp/SMTPResponseTest.cpp index 352f46c2..d47e31e9 100644 --- a/tests/net/smtp/SMTPResponseTest.cpp +++ b/tests/net/smtp/SMTPResponseTest.cpp @@ -43,6 +43,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testSingleLineResponse() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -52,7 +53,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 123, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount()); @@ -61,6 +62,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testSingleLineResponseLF() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -70,7 +72,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 123, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount()); @@ -79,6 +81,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testMultiLineResponse() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -92,7 +95,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 123, resp->getCode()); VASSERT_EQ("Lines", 2, resp->getLineCount()); @@ -107,6 +110,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testMultiLineResponseDifferentCode() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -120,7 +124,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 0, resp->getCode()); VASSERT_EQ("Lines", 2, resp->getLineCount()); @@ -135,6 +139,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testIncompleteMultiLineResponse() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (1); @@ -149,12 +154,13 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; VASSERT_THROW("Incomplete response", - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState), + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState), vmime::exceptions::operation_timed_out); } void testNoResponseText() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (1); @@ -167,7 +173,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 250, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount()); @@ -176,6 +182,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testEnhancedStatusCode() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -185,7 +192,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 250, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount()); @@ -197,6 +204,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testNoEnhancedStatusCode() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -206,7 +214,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 354, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount()); @@ -218,6 +226,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) void testInvalidEnhancedStatusCode() { + vmime::shared_ptr tracer; vmime::shared_ptr socket = vmime::make_shared (); vmime::shared_ptr toh = vmime::make_shared (); @@ -227,7 +236,7 @@ VMIME_TEST_SUITE_BEGIN(SMTPResponseTest) vmime::net::smtp::SMTPResponse::state responseState; vmime::shared_ptr resp = - vmime::net::smtp::SMTPResponse::readResponse(socket, toh, responseState); + vmime::net::smtp::SMTPResponse::readResponse(tracer, socket, toh, responseState); VASSERT_EQ("Code", 250, resp->getCode()); VASSERT_EQ("Lines", 1, resp->getLineCount());