From fa84db6d6588f2ac72534932a3ad582df16bec58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Obiltschnig?= Date: Tue, 2 Apr 2019 16:47:12 +0200 Subject: [PATCH] added NTLM authentication to SMTPClientSession --- Net/include/Poco/Net/HTTPNTLMCredentials.h | 3 - Net/include/Poco/Net/NTLMCredentials.h | 10 ++ Net/include/Poco/Net/SMTPClientSession.h | 12 ++- Net/src/HTTPNTLMCredentials.cpp | 59 +----------- Net/src/NTLMCredentials.cpp | 48 ++++++++++ Net/src/SMTPClientSession.cpp | 101 ++++++++++++++++----- 6 files changed, 148 insertions(+), 85 deletions(-) diff --git a/Net/include/Poco/Net/HTTPNTLMCredentials.h b/Net/include/Poco/Net/HTTPNTLMCredentials.h index 48206d87e..7e42021f4 100644 --- a/Net/include/Poco/Net/HTTPNTLMCredentials.h +++ b/Net/include/Poco/Net/HTTPNTLMCredentials.h @@ -101,9 +101,6 @@ private: HTTPNTLMCredentials& operator = (const HTTPNTLMCredentials&); std::string createNTLMMessage(const std::string& ntlmChallengeBase64); - static void splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain); - static std::string toBase64(const std::vector& buffer); - static std::vector fromBase64(const std::string& base64); std::string _username; std::string _password; diff --git a/Net/include/Poco/Net/NTLMCredentials.h b/Net/include/Poco/Net/NTLMCredentials.h index 852486b7b..2037a0bdd 100644 --- a/Net/include/Poco/Net/NTLMCredentials.h +++ b/Net/include/Poco/Net/NTLMCredentials.h @@ -160,6 +160,16 @@ public: static void writeBufferDesc(Poco::BinaryWriter& writer, const BufferDesc& desc); /// Writes a buffer descriptor. + static void splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain); + /// Splits a username containing a domain into plain username and domain. + /// Supported formats are \ and @. + + static std::string toBase64(const std::vector& buffer); + /// Converts the buffer to a base64-encoded string. + + static std::vector fromBase64(const std::string& base64); + /// Decodes the given base64-encoded string. + static const std::string NTLMSSP; /// Message signature string. }; diff --git a/Net/include/Poco/Net/SMTPClientSession.h b/Net/include/Poco/Net/SMTPClientSession.h index 5edf99959..072317575 100644 --- a/Net/include/Poco/Net/SMTPClientSession.h +++ b/Net/include/Poco/Net/SMTPClientSession.h @@ -51,7 +51,8 @@ public: AUTH_CRAM_SHA1, AUTH_LOGIN, AUTH_PLAIN, - AUTH_XOAUTH2 + AUTH_XOAUTH2, + AUTH_NTLM }; explicit SMTPClientSession(const StreamSocket& socket); @@ -68,7 +69,7 @@ public: void setTimeout(const Poco::Timespan& timeout); /// Sets the timeout for socket read operations. - + Poco::Timespan getTimeout() const; /// Returns the timeout for socket read operations. @@ -92,7 +93,7 @@ public: void login(LoginMethod loginMethod, const std::string& username, const std::string& password); /// Logs in to the SMTP server using the given authentication method and the given /// credentials. - + void open(); /// Reads the initial response from the SMTP server. /// @@ -103,7 +104,7 @@ public: /// Does nothing if called more than once. void close(); - /// Sends a QUIT command and closes the connection to the server. + /// Sends a QUIT command and closes the connection to the server. /// /// Throws a SMTPException in case of a SMTP-specific error, or a /// NetException in case of a general network communication failure. @@ -169,7 +170,7 @@ protected: }; enum { - DEFAULT_TIMEOUT = 30000000 // 30 seconds default timeout for socket operations + DEFAULT_TIMEOUT = 30000000 // 30 seconds default timeout for socket operations }; static bool isPositiveCompletion(int status); @@ -184,6 +185,7 @@ protected: void loginUsingLogin(const std::string& username, const std::string& password); void loginUsingPlain(const std::string& username, const std::string& password); void loginUsingXOAUTH2(const std::string& username, const std::string& password); + void loginUsingNTLM(const std::string& username, const std::string& password); DialogSocket& socket(); private: diff --git a/Net/src/HTTPNTLMCredentials.cpp b/Net/src/HTTPNTLMCredentials.cpp index b28d9c74c..11fce2af4 100644 --- a/Net/src/HTTPNTLMCredentials.cpp +++ b/Net/src/HTTPNTLMCredentials.cpp @@ -21,10 +21,6 @@ #include "Poco/DateTime.h" #include "Poco/NumberFormatter.h" #include "Poco/Exception.h" -#include "Poco/Base64Encoder.h" -#include "Poco/Base64Decoder.h" -#include "Poco/MemoryStream.h" -#include namespace Poco { @@ -114,13 +110,13 @@ std::string HTTPNTLMCredentials::createNTLMMessage(const std::string& responseAu { NTLMCredentials::NegotiateMessage negotiateMsg; std::string username; - splitUsername(_username, username, negotiateMsg.domain); + NTLMCredentials::splitUsername(_username, username, negotiateMsg.domain); std::vector negotiateBuf = NTLMCredentials::formatNegotiateMessage(negotiateMsg); - return toBase64(negotiateBuf); + return NTLMCredentials::toBase64(negotiateBuf); } else { - std::vector buffer = fromBase64(responseAuthParams); + std::vector buffer = NTLMCredentials::fromBase64(responseAuthParams); NTLMCredentials::ChallengeMessage challengeMsg; if (NTLMCredentials::parseChallengeMessage(&buffer[0], buffer.size(), challengeMsg)) { @@ -131,7 +127,7 @@ std::string HTTPNTLMCredentials::createNTLMMessage(const std::string& responseAu std::string username; std::string domain; - splitUsername(_username, username, domain); + NTLMCredentials::splitUsername(_username, username, domain); NTLMCredentials::AuthenticateMessage authenticateMsg; authenticateMsg.flags = challengeMsg.flags; @@ -147,56 +143,11 @@ std::string HTTPNTLMCredentials::createNTLMMessage(const std::string& responseAu authenticateMsg.ntlmResponse = NTLMCredentials::createNTLMv2Response(ntlm2Hash, challengeMsg.challenge, ntlmNonce, challengeMsg.targetInfo, timestamp); std::vector authenticateBuf = NTLMCredentials::formatAuthenticateMessage(authenticateMsg); - return toBase64(authenticateBuf); + return NTLMCredentials::toBase64(authenticateBuf); } else throw HTTPException("Invalid NTLM challenge"); } } -void HTTPNTLMCredentials::splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain) -{ - std::string::size_type pos = usernameAndDomain.find('\\'); - if (pos != std::string::npos) - { - domain.assign(usernameAndDomain, 0, pos); - username.assign(usernameAndDomain, pos + 1, std::string::npos); - return; - } - else - { - pos = usernameAndDomain.find('@'); - if (pos != std::string::npos) - { - username.assign(usernameAndDomain, 0, pos); - domain.assign(usernameAndDomain, pos + 1, std::string::npos); - return; - } - } - username = usernameAndDomain; -} - - -std::string HTTPNTLMCredentials::toBase64(const std::vector& buffer) -{ - std::ostringstream ostr; - Poco::Base64Encoder base64(ostr); - base64.rdbuf()->setLineLength(0); - base64.write(reinterpret_cast(&buffer[0]), buffer.size()); - base64.close(); - return ostr.str(); -} - - -std::vector HTTPNTLMCredentials::fromBase64(const std::string& base64) -{ - Poco::MemoryInputStream istr(base64.data(), base64.size()); - Poco::Base64Decoder debase64(istr); - std::vector buffer(base64.size()); - debase64.read(reinterpret_cast(&buffer[0]), buffer.size()); - buffer.resize(static_cast(debase64.gcount())); - return buffer; -} - - } } // namespace Poco::Net diff --git a/Net/src/NTLMCredentials.cpp b/Net/src/NTLMCredentials.cpp index d114a5f8f..76e7d43bf 100644 --- a/Net/src/NTLMCredentials.cpp +++ b/Net/src/NTLMCredentials.cpp @@ -25,6 +25,9 @@ #include "Poco/Random.h" #include "Poco/Timestamp.h" #include "Poco/MemoryStream.h" +#include "Poco/Base64Encoder.h" +#include "Poco/Base64Decoder.h" +#include #include @@ -326,4 +329,49 @@ void NTLMCredentials::writeBufferDesc(Poco::BinaryWriter& writer, const BufferDe } +void NTLMCredentials::splitUsername(const std::string& usernameAndDomain, std::string& username, std::string& domain) +{ + std::string::size_type pos = usernameAndDomain.find('\\'); + if (pos != std::string::npos) + { + domain.assign(usernameAndDomain, 0, pos); + username.assign(usernameAndDomain, pos + 1, std::string::npos); + return; + } + else + { + pos = usernameAndDomain.find('@'); + if (pos != std::string::npos) + { + username.assign(usernameAndDomain, 0, pos); + domain.assign(usernameAndDomain, pos + 1, std::string::npos); + return; + } + } + username = usernameAndDomain; +} + + +std::string NTLMCredentials::toBase64(const std::vector& buffer) +{ + std::ostringstream ostr; + Poco::Base64Encoder base64(ostr); + base64.rdbuf()->setLineLength(0); + base64.write(reinterpret_cast(&buffer[0]), buffer.size()); + base64.close(); + return ostr.str(); +} + + +std::vector NTLMCredentials::fromBase64(const std::string& base64) +{ + Poco::MemoryInputStream istr(base64.data(), base64.size()); + Poco::Base64Decoder debase64(istr); + std::vector buffer(base64.size()); + debase64.read(reinterpret_cast(&buffer[0]), buffer.size()); + buffer.resize(static_cast(debase64.gcount())); + return buffer; +} + + } } // namespace Poco::Net diff --git a/Net/src/SMTPClientSession.cpp b/Net/src/SMTPClientSession.cpp index 20e330a8d..7fd31926a 100644 --- a/Net/src/SMTPClientSession.cpp +++ b/Net/src/SMTPClientSession.cpp @@ -19,8 +19,9 @@ #include "Poco/Net/SocketAddress.h" #include "Poco/Net/SocketStream.h" #include "Poco/Net/NetException.h" -#include "Poco/Environment.h" #include "Poco/Net/NetworkInterface.h" +#include "Poco/Net/NTLMCredentials.h" +#include "Poco/Environment.h" #include "Poco/HMACEngine.h" #include "Poco/MD5Engine.h" #include "Poco/SHA1Engine.h" @@ -80,7 +81,7 @@ void SMTPClientSession::setTimeout(const Poco::Timespan& timeout) _socket.setReceiveTimeout(timeout); } - + Poco::Timespan SMTPClientSession::getTimeout() const { return _socket.getReceiveTimeout(); @@ -131,27 +132,27 @@ void SMTPClientSession::loginUsingCRAM(const std::string& username, const std::s if (!isPositiveIntermediate(status)) throw SMTPException(std::string("Cannot authenticate using ") + method, response, status); std::string challengeBase64 = response.substr(4); - + std::istringstream istr(challengeBase64); Base64Decoder decoder(istr); std::string challenge; StreamCopier::copyToString(decoder, challenge); - + hmac.update(challenge); - + const DigestEngine::Digest& digest = hmac.digest(); std::string digestString(DigestEngine::digestToHex(digest)); - + std::string challengeResponse = username + " " + digestString; - + std::ostringstream challengeResponseBase64; Base64Encoder encoder(challengeResponseBase64); encoder.rdbuf()->setLineLength(0); encoder << challengeResponse; encoder.close(); - + status = sendCommand(challengeResponseBase64.str(), response); - if (!isPositiveCompletion(status)) throw SMTPException(std::string("Login using ") + method + " failed", response, status); + if (!isPositiveCompletion(status)) throw SMTPException(std::string("Login using ") + method + " failed", response, status); } @@ -160,19 +161,19 @@ void SMTPClientSession::loginUsingLogin(const std::string& username, const std:: std::string response; int status = sendCommand("AUTH LOGIN", response); if (!isPositiveIntermediate(status)) throw SMTPException("Cannot authenticate using LOGIN", response, status); - + std::ostringstream usernameBase64; Base64Encoder usernameEncoder(usernameBase64); usernameEncoder.rdbuf()->setLineLength(0); usernameEncoder << username; usernameEncoder.close(); - + std::ostringstream passwordBase64; Base64Encoder passwordEncoder(passwordBase64); passwordEncoder.rdbuf()->setLineLength(0); passwordEncoder << password; passwordEncoder.close(); - + //Server request for username/password not defined could be either //S: login: //C: user_login @@ -183,25 +184,25 @@ void SMTPClientSession::loginUsingLogin(const std::string& username, const std:: //C: user_password //S: login: //C: user_login - + std::string decodedResponse; std::istringstream responseStream(response.substr(4)); Base64Decoder responseDecoder(responseStream); StreamCopier::copyToString(responseDecoder, decodedResponse); - + if (Poco::icompare(decodedResponse, 0, 8, "username") == 0) // username first (md5("Username:")) { status = sendCommand(usernameBase64.str(), response); if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN username failed", response, status); - + status = sendCommand(passwordBase64.str(), response); - if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN password failed", response, status); + if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN password failed", response, status); } else if (Poco::icompare(decodedResponse, 0, 8, "password") == 0) // password first (md5("Password:")) { status = sendCommand(passwordBase64.str(), response); - if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN password failed", response, status); - + if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN password failed", response, status); + status = sendCommand(usernameBase64.str(), response); if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN username failed", response, status); } @@ -236,6 +237,52 @@ void SMTPClientSession::loginUsingXOAUTH2(const std::string& username, const std } +void SMTPClientSession::loginUsingNTLM(const std::string& username, const std::string& password) +{ + NTLMCredentials::NegotiateMessage negotiateMsg; + std::string user; + std::string domain; + NTLMCredentials::splitUsername(username, user, domain); + negotiateMsg.domain = domain; + std::vector negotiateBuf = NTLMCredentials::formatNegotiateMessage(negotiateMsg); + + std::string response; + int status = sendCommand("AUTH NTLM", NTLMCredentials::toBase64(negotiateBuf), response); + if (status == 334) + { + std::vector buffer = NTLMCredentials::fromBase64(response); + NTLMCredentials::ChallengeMessage challengeMsg; + if (NTLMCredentials::parseChallengeMessage(&buffer[0], buffer.size(), challengeMsg)) + { + if ((challengeMsg.flags & NTLMCredentials::NTLM_FLAG_NEGOTIATE_NTLM2_KEY) == 0) + { + throw SMTPException("Server does not support NTLMv2 authentication"); + } + + NTLMCredentials::AuthenticateMessage authenticateMsg; + authenticateMsg.flags = challengeMsg.flags; + authenticateMsg.target = challengeMsg.target; + authenticateMsg.username = user; + + std::vector lmNonce = NTLMCredentials::createNonce(); + std::vector ntlmNonce = NTLMCredentials::createNonce(); + Poco::UInt64 timestamp = NTLMCredentials::createTimestamp(); + std::vector ntlm2Hash = NTLMCredentials::createNTLMv2Hash(user, challengeMsg.target, password); + + authenticateMsg.lmResponse = NTLMCredentials::createLMv2Response(ntlm2Hash, challengeMsg.challenge, lmNonce); + authenticateMsg.ntlmResponse = NTLMCredentials::createNTLMv2Response(ntlm2Hash, challengeMsg.challenge, ntlmNonce, challengeMsg.targetInfo, timestamp); + + std::vector authenticateBuf = NTLMCredentials::formatAuthenticateMessage(authenticateMsg); + + status = sendCommand(NTLMCredentials::toBase64(authenticateBuf), response); + if (status != 235) throw SMTPException("NTLM authentication failed", response, status); + } + else throw SMTPException("Invalid NTLM challenge"); + } + else throw SMTPException("Server does not support NTLM authentication"); +} + + void SMTPClientSession::login(LoginMethod loginMethod, const std::string& username, const std::string& password) { login(Environment::nodeName(), loginMethod, username, password); @@ -246,7 +293,7 @@ void SMTPClientSession::login(const std::string& hostname, LoginMethod loginMeth { std::string response; login(hostname, response); - + if (loginMethod == AUTH_CRAM_MD5) { if (response.find("CRAM-MD5", 0) != std::string::npos) @@ -287,6 +334,14 @@ void SMTPClientSession::login(const std::string& hostname, LoginMethod loginMeth } else throw SMTPException("The mail service does not support XOAUTH2 authentication", response); } + else if (loginMethod == AUTH_NTLM) + { + if (response.find("NTLM", 0) != std::string::npos) + { + loginUsingNTLM(username, password); + } + else throw SMTPException("The mail service does not support NTLM authentication", response); + } else if (loginMethod != AUTH_NONE) { throw SMTPException("The autentication method is not supported"); @@ -337,7 +392,7 @@ void SMTPClientSession::sendCommands(const MailMessage& message, const Recipient } if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status); - + std::ostringstream recipient; if (pRecipients) { @@ -384,7 +439,7 @@ void SMTPClientSession::sendAddresses(const std::string& from, const Recipients& } if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status); - + std::ostringstream recipient; for (Recipients::const_iterator it = recipients.begin(); it != recipients.end(); ++it) @@ -427,7 +482,7 @@ void SMTPClientSession::transportMessage(const MailMessage& message) message.write(mailStream); mailStream.close(); socketStream.flush(); - + std::string response; int status = _socket.receiveStatusMessage(response); if (!isPositiveCompletion(status)) throw SMTPException("The server rejected the message", response, status); @@ -452,7 +507,7 @@ void SMTPClientSession::sendMessage(std::istream& istr) { std::string response; int status = 0; - + SocketOutputStream socketStream(_socket); MailOutputStream mailStream(socketStream); StreamCopier::copyStream(istr, mailStream);