1//
2// SMTPClientSession.cpp
3//
4// Library: Net
5// Package: Mail
6// Module: SMTPClientSession
7//
8// Copyright (c) 2005-2008, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/Net/SMTPClientSession.h"
16#include "Poco/Net/MailMessage.h"
17#include "Poco/Net/MailRecipient.h"
18#include "Poco/Net/MailStream.h"
19#include "Poco/Net/SocketAddress.h"
20#include "Poco/Net/SocketStream.h"
21#include "Poco/Net/NetException.h"
22#include "Poco/Environment.h"
23#include "Poco/Net/NetworkInterface.h"
24#include "Poco/Net/NTLMCredentials.h"
25#include "Poco/Environment.h"
26#include "Poco/HMACEngine.h"
27#include "Poco/MD5Engine.h"
28#include "Poco/SHA1Engine.h"
29#include "Poco/DigestStream.h"
30#include "Poco/StreamCopier.h"
31#include "Poco/Base64Encoder.h"
32#include "Poco/Base64Decoder.h"
33#include "Poco/String.h"
34#include <sstream>
35#include <fstream>
36#include <iostream>
37
38
39using Poco::DigestEngine;
40using Poco::HMACEngine;
41using Poco::MD5Engine;
42using Poco::SHA1Engine;
43using Poco::DigestOutputStream;
44using Poco::StreamCopier;
45using Poco::Base64Encoder;
46using Poco::Base64Decoder;
47using Poco::Environment;
48
49
50namespace Poco {
51namespace Net {
52
53
54SMTPClientSession::SMTPClientSession(const StreamSocket& socket):
55 _socket(socket),
56 _isOpen(false)
57{
58}
59
60
61SMTPClientSession::SMTPClientSession(const std::string& host, Poco::UInt16 port):
62 _socket(SocketAddress(host, port)),
63 _isOpen(false)
64{
65}
66
67
68SMTPClientSession::~SMTPClientSession()
69{
70 try
71 {
72 close();
73 }
74 catch (...)
75 {
76 }
77}
78
79
80void SMTPClientSession::setTimeout(const Poco::Timespan& timeout)
81{
82 _socket.setReceiveTimeout(timeout);
83}
84
85
86Poco::Timespan SMTPClientSession::getTimeout() const
87{
88 return _socket.getReceiveTimeout();
89}
90
91
92void SMTPClientSession::login(const std::string& hostname, std::string& response)
93{
94 open();
95 int status = sendCommand("EHLO", hostname, response);
96 if (isPermanentNegative(status))
97 status = sendCommand("HELO", hostname, response);
98 if (!isPositiveCompletion(status)) throw SMTPException("Login failed", response, status);
99}
100
101
102void SMTPClientSession::login(const std::string& hostname)
103{
104 std::string response;
105 login(hostname, response);
106}
107
108
109void SMTPClientSession::login()
110{
111 login(Environment::nodeName());
112}
113
114
115void SMTPClientSession::loginUsingCRAMMD5(const std::string& username, const std::string& password)
116{
117 HMACEngine<MD5Engine> hmac(password);
118 loginUsingCRAM(username, "CRAM-MD5", hmac);
119}
120
121
122void SMTPClientSession::loginUsingCRAMSHA1(const std::string& username, const std::string& password)
123{
124 HMACEngine<SHA1Engine> hmac(password);
125 loginUsingCRAM(username, "CRAM-SHA1", hmac);
126}
127
128
129void SMTPClientSession::loginUsingCRAM(const std::string& username, const std::string& method, Poco::DigestEngine& hmac)
130{
131 std::string response;
132 int status = sendCommand(std::string("AUTH ") + method, response);
133
134 if (!isPositiveIntermediate(status)) throw SMTPException(std::string("Cannot authenticate using ") + method, response, status);
135 std::string challengeBase64 = response.substr(4);
136
137 std::istringstream istr(challengeBase64);
138 Base64Decoder decoder(istr);
139 std::string challenge;
140 StreamCopier::copyToString(decoder, challenge);
141
142 hmac.update(challenge);
143
144 const DigestEngine::Digest& digest = hmac.digest();
145 std::string digestString(DigestEngine::digestToHex(digest));
146
147 std::string challengeResponse = username + " " + digestString;
148
149 std::ostringstream challengeResponseBase64;
150 Base64Encoder encoder(challengeResponseBase64);
151 encoder.rdbuf()->setLineLength(0);
152 encoder << challengeResponse;
153 encoder.close();
154
155 status = sendCommand(challengeResponseBase64.str(), response);
156 if (!isPositiveCompletion(status)) throw SMTPException(std::string("Login using ") + method + " failed", response, status);
157}
158
159
160void SMTPClientSession::loginUsingLogin(const std::string& username, const std::string& password)
161{
162 std::string response;
163 int status = sendCommand("AUTH LOGIN", response);
164 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot authenticate using LOGIN", response, status);
165
166 std::ostringstream usernameBase64;
167 Base64Encoder usernameEncoder(usernameBase64);
168 usernameEncoder.rdbuf()->setLineLength(0);
169 usernameEncoder << username;
170 usernameEncoder.close();
171
172 std::ostringstream passwordBase64;
173 Base64Encoder passwordEncoder(passwordBase64);
174 passwordEncoder.rdbuf()->setLineLength(0);
175 passwordEncoder << password;
176 passwordEncoder.close();
177
178 //Server request for username/password not defined could be either
179 //S: login:
180 //C: user_login
181 //S: password:
182 //C: user_password
183 //or
184 //S: password:
185 //C: user_password
186 //S: login:
187 //C: user_login
188
189 std::string decodedResponse;
190 std::istringstream responseStream(response.substr(4));
191 Base64Decoder responseDecoder(responseStream);
192 StreamCopier::copyToString(responseDecoder, decodedResponse);
193
194 if (Poco::icompare(decodedResponse, 0, 8, "username") == 0) // username first (md5("Username:"))
195 {
196 status = sendCommand(usernameBase64.str(), response);
197 if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN username failed", response, status);
198
199 status = sendCommand(passwordBase64.str(), response);
200 if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN password failed", response, status);
201 }
202 else if (Poco::icompare(decodedResponse, 0, 8, "password") == 0) // password first (md5("Password:"))
203 {
204 status = sendCommand(passwordBase64.str(), response);
205 if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN password failed", response, status);
206
207 status = sendCommand(usernameBase64.str(), response);
208 if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN username failed", response, status);
209 }
210}
211
212
213void SMTPClientSession::loginUsingPlain(const std::string& username, const std::string& password)
214{
215 std::ostringstream credentialsBase64;
216 Base64Encoder credentialsEncoder(credentialsBase64);
217 credentialsEncoder.rdbuf()->setLineLength(0);
218 credentialsEncoder << '\0' << username << '\0' << password;
219 credentialsEncoder.close();
220
221 std::string response;
222 int status = sendCommand("AUTH PLAIN", credentialsBase64.str(), response);
223 if (!isPositiveCompletion(status)) throw SMTPException("Login using PLAIN failed", response, status);
224}
225
226
227void SMTPClientSession::loginUsingXOAUTH2(const std::string& username, const std::string& password)
228{
229 std::ostringstream credentialsBase64;
230 Base64Encoder credentialsEncoder(credentialsBase64);
231 credentialsEncoder.rdbuf()->setLineLength(0);
232 credentialsEncoder << "user=" << username << "\001auth=Bearer " << password << "\001\001";
233 credentialsEncoder.close();
234
235 std::string response;
236 int status = sendCommand("AUTH XOAUTH2", credentialsBase64.str(), response);
237 if (!isPositiveCompletion(status)) throw SMTPException("Login using XOAUTH2 failed", response, status);
238}
239
240
241void SMTPClientSession::loginUsingNTLM(const std::string& username, const std::string& password)
242{
243 // Implementation is based on:
244 // [MS-SMTPNTLM]: NT LAN Manager (NTLM) Authentication: Simple Mail Transfer Protocol (SMTP) Extension
245 // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smtpntlm/50c668f6-5ffc-4616-96df-b5a3f4b3311d
246
247 NTLMCredentials::NegotiateMessage negotiateMsg;
248 std::string user;
249 std::string domain;
250 NTLMCredentials::splitUsername(username, user, domain);
251 negotiateMsg.domain = domain;
252 std::vector<unsigned char> negotiateBuf = NTLMCredentials::formatNegotiateMessage(negotiateMsg);
253
254 std::string response;
255 int status = sendCommand("AUTH NTLM", NTLMCredentials::toBase64(negotiateBuf), response);
256 if (status == 334)
257 {
258 std::vector<unsigned char> buffer = NTLMCredentials::fromBase64(response.substr(4));
259 if (buffer.empty()) throw SMTPException("Invalid NTLM challenge");
260 NTLMCredentials::ChallengeMessage challengeMsg;
261 if (NTLMCredentials::parseChallengeMessage(&buffer[0], buffer.size(), challengeMsg))
262 {
263 if ((challengeMsg.flags & NTLMCredentials::NTLM_FLAG_NEGOTIATE_NTLM2_KEY) == 0)
264 {
265 throw SMTPException("Server does not support NTLMv2 authentication");
266 }
267
268 NTLMCredentials::AuthenticateMessage authenticateMsg;
269 authenticateMsg.flags = challengeMsg.flags;
270 authenticateMsg.target = challengeMsg.target;
271 authenticateMsg.username = user;
272
273 std::vector<unsigned char> lmNonce = NTLMCredentials::createNonce();
274 std::vector<unsigned char> ntlmNonce = NTLMCredentials::createNonce();
275 Poco::UInt64 timestamp = NTLMCredentials::createTimestamp();
276 std::vector<unsigned char> ntlm2Hash = NTLMCredentials::createNTLMv2Hash(user, challengeMsg.target, password);
277
278 authenticateMsg.lmResponse = NTLMCredentials::createLMv2Response(ntlm2Hash, challengeMsg.challenge, lmNonce);
279 authenticateMsg.ntlmResponse = NTLMCredentials::createNTLMv2Response(ntlm2Hash, challengeMsg.challenge, ntlmNonce, challengeMsg.targetInfo, timestamp);
280
281 std::vector<unsigned char> authenticateBuf = NTLMCredentials::formatAuthenticateMessage(authenticateMsg);
282
283 status = sendCommand(NTLMCredentials::toBase64(authenticateBuf), response);
284 if (status != 235) throw SMTPException("NTLM authentication failed", response, status);
285 }
286 else throw SMTPException("Invalid NTLM challenge");
287 }
288 else throw SMTPException("Server does not support NTLM authentication");
289}
290
291
292void SMTPClientSession::login(LoginMethod loginMethod, const std::string& username, const std::string& password)
293{
294 login(Environment::nodeName(), loginMethod, username, password);
295}
296
297
298void SMTPClientSession::login(const std::string& hostname, LoginMethod loginMethod, const std::string& username, const std::string& password)
299{
300 std::string response;
301 login(hostname, response);
302
303 if (loginMethod == AUTH_CRAM_MD5)
304 {
305 if (response.find("CRAM-MD5", 0) != std::string::npos)
306 {
307 loginUsingCRAMMD5(username, password);
308 }
309 else throw SMTPException("The mail service does not support CRAM-MD5 authentication", response);
310 }
311 else if (loginMethod == AUTH_CRAM_SHA1)
312 {
313 if (response.find("CRAM-SHA1", 0) != std::string::npos)
314 {
315 loginUsingCRAMSHA1(username, password);
316 }
317 else throw SMTPException("The mail service does not support CRAM-SHA1 authentication", response);
318 }
319 else if (loginMethod == AUTH_LOGIN)
320 {
321 if (response.find("LOGIN", 0) != std::string::npos)
322 {
323 loginUsingLogin(username, password);
324 }
325 else throw SMTPException("The mail service does not support LOGIN authentication", response);
326 }
327 else if (loginMethod == AUTH_PLAIN)
328 {
329 if (response.find("PLAIN", 0) != std::string::npos)
330 {
331 loginUsingPlain(username, password);
332 }
333 else throw SMTPException("The mail service does not support PLAIN authentication", response);
334 }
335 else if (loginMethod == AUTH_XOAUTH2)
336 {
337 if (response.find("XOAUTH2", 0) != std::string::npos)
338 {
339 loginUsingXOAUTH2(username, password);
340 }
341 else throw SMTPException("The mail service does not support XOAUTH2 authentication", response);
342 }
343 else if (loginMethod == AUTH_NTLM)
344 {
345 if (response.find("NTLM", 0) != std::string::npos)
346 {
347 loginUsingNTLM(username, password);
348 }
349 else throw SMTPException("The mail service does not support NTLM authentication", response);
350 }
351 else if (loginMethod != AUTH_NONE)
352 {
353 throw SMTPException("The autentication method is not supported");
354 }
355}
356
357
358void SMTPClientSession::open()
359{
360 if (!_isOpen)
361 {
362 std::string response;
363 int status = _socket.receiveStatusMessage(response);
364 if (!isPositiveCompletion(status)) throw SMTPException("The mail service is unavailable", response, status);
365 _isOpen = true;
366 }
367}
368
369
370void SMTPClientSession::close()
371{
372 if (_isOpen)
373 {
374 std::string response;
375 sendCommand("QUIT", response);
376 _socket.close();
377 _isOpen = false;
378 }
379}
380
381
382void SMTPClientSession::sendCommands(const MailMessage& message, const Recipients* pRecipients)
383{
384 std::string response;
385 int status = 0;
386 const std::string& fromField = message.getSender();
387 std::string::size_type emailPos = fromField.find('<');
388 if (emailPos == std::string::npos)
389 {
390 std::string sender("<");
391 sender.append(fromField);
392 sender.append(">");
393 status = sendCommand("MAIL FROM:", sender, response);
394 }
395 else
396 {
397 status = sendCommand("MAIL FROM:", fromField.substr(emailPos, fromField.size() - emailPos), response);
398 }
399
400 if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status);
401
402 std::ostringstream recipient;
403 if (pRecipients)
404 {
405 for (Recipients::const_iterator it = pRecipients->begin(); it != pRecipients->end(); ++it)
406 {
407 recipient << '<' << *it << '>';
408 int status2 = sendCommand("RCPT TO:", recipient.str(), response);
409 if (!isPositiveCompletion(status2)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status2);
410 recipient.str("");
411 }
412 }
413 else
414 {
415 for (MailMessage::Recipients::const_iterator it = message.recipients().begin(); it != message.recipients().end(); ++it)
416 {
417 recipient << '<' << it->getAddress() << '>';
418 int status2 = sendCommand("RCPT TO:", recipient.str(), response);
419 if (!isPositiveCompletion(status2)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status2);
420 recipient.str("");
421 }
422 }
423
424 status = sendCommand("DATA", response);
425 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot send message data", response, status);
426}
427
428
429void SMTPClientSession::sendAddresses(const std::string& from, const Recipients& recipients)
430{
431 std::string response;
432 int status = 0;
433
434 std::string::size_type emailPos = from.find('<');
435 if (emailPos == std::string::npos)
436 {
437 std::string sender("<");
438 sender.append(from);
439 sender.append(">");
440 status = sendCommand("MAIL FROM:", sender, response);
441 }
442 else
443 {
444 status = sendCommand("MAIL FROM:", from.substr(emailPos, from.size() - emailPos), response);
445 }
446
447 if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status);
448
449 std::ostringstream recipient;
450
451 for (Recipients::const_iterator it = recipients.begin(); it != recipients.end(); ++it)
452 {
453
454 recipient << '<' << *it << '>';
455 int status2 = sendCommand("RCPT TO:", recipient.str(), response);
456 if (!isPositiveCompletion(status2)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status2);
457 recipient.str("");
458 }
459}
460
461
462void SMTPClientSession::sendData()
463{
464 std::string response;
465 int status = sendCommand("DATA", response);
466 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot send message data", response, status);
467}
468
469
470void SMTPClientSession::sendMessage(const MailMessage& message)
471{
472 sendCommands(message);
473 transportMessage(message);
474}
475
476
477void SMTPClientSession::sendMessage(const MailMessage& message, const Recipients& recipients)
478{
479 sendCommands(message, &recipients);
480 transportMessage(message);
481}
482
483
484void SMTPClientSession::transportMessage(const MailMessage& message)
485{
486 SocketOutputStream socketStream(_socket);
487 MailOutputStream mailStream(socketStream);
488 message.write(mailStream);
489 mailStream.close();
490 socketStream.flush();
491
492 std::string response;
493 int status = _socket.receiveStatusMessage(response);
494 if (!isPositiveCompletion(status)) throw SMTPException("The server rejected the message", response, status);
495}
496
497
498int SMTPClientSession::sendCommand(const std::string& command, std::string& response)
499{
500 _socket.sendMessage(command);
501 return _socket.receiveStatusMessage(response);
502}
503
504
505int SMTPClientSession::sendCommand(const std::string& command, const std::string& arg, std::string& response)
506{
507 _socket.sendMessage(command, arg);
508 return _socket.receiveStatusMessage(response);
509}
510
511
512void SMTPClientSession::sendMessage(std::istream& istr)
513{
514 std::string response;
515 int status = 0;
516
517 SocketOutputStream socketStream(_socket);
518 MailOutputStream mailStream(socketStream);
519 StreamCopier::copyStream(istr, mailStream);
520 mailStream.close();
521 socketStream.flush();
522 status = _socket.receiveStatusMessage(response);
523 if (!isPositiveCompletion(status)) throw SMTPException("The server rejected the message", response, status);
524}
525
526
527} } // namespace Poco::Net
528