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/HMACEngine.h"
25#include "Poco/MD5Engine.h"
26#include "Poco/SHA1Engine.h"
27#include "Poco/DigestStream.h"
28#include "Poco/StreamCopier.h"
29#include "Poco/Base64Encoder.h"
30#include "Poco/Base64Decoder.h"
31#include "Poco/String.h"
32#include <sstream>
33#include <fstream>
34#include <iostream>
35
36
37using Poco::DigestEngine;
38using Poco::HMACEngine;
39using Poco::MD5Engine;
40using Poco::SHA1Engine;
41using Poco::DigestOutputStream;
42using Poco::StreamCopier;
43using Poco::Base64Encoder;
44using Poco::Base64Decoder;
45using Poco::Environment;
46
47
48namespace Poco {
49namespace Net {
50
51
52SMTPClientSession::SMTPClientSession(const StreamSocket& socket):
53 _socket(socket),
54 _isOpen(false)
55{
56}
57
58
59SMTPClientSession::SMTPClientSession(const std::string& host, Poco::UInt16 port):
60 _socket(SocketAddress(host, port)),
61 _isOpen(false)
62{
63}
64
65
66SMTPClientSession::~SMTPClientSession()
67{
68 try
69 {
70 close();
71 }
72 catch (...)
73 {
74 }
75}
76
77
78void SMTPClientSession::setTimeout(const Poco::Timespan& timeout)
79{
80 _socket.setReceiveTimeout(timeout);
81}
82
83
84Poco::Timespan SMTPClientSession::getTimeout() const
85{
86 return _socket.getReceiveTimeout();
87}
88
89
90void SMTPClientSession::login(const std::string& hostname, std::string& response)
91{
92 open();
93 int status = sendCommand("EHLO", hostname, response);
94 if (isPermanentNegative(status))
95 status = sendCommand("HELO", hostname, response);
96 if (!isPositiveCompletion(status)) throw SMTPException("Login failed", response, status);
97}
98
99
100void SMTPClientSession::login(const std::string& hostname)
101{
102 std::string response;
103 login(hostname, response);
104}
105
106
107void SMTPClientSession::login()
108{
109 login(Environment::nodeName());
110}
111
112
113void SMTPClientSession::loginUsingCRAMMD5(const std::string& username, const std::string& password)
114{
115 HMACEngine<MD5Engine> hmac(password);
116 loginUsingCRAM(username, "CRAM-MD5", hmac);
117}
118
119
120void SMTPClientSession::loginUsingCRAMSHA1(const std::string& username, const std::string& password)
121{
122 HMACEngine<SHA1Engine> hmac(password);
123 loginUsingCRAM(username, "CRAM-SHA1", hmac);
124}
125
126
127void SMTPClientSession::loginUsingCRAM(const std::string& username, const std::string& method, Poco::DigestEngine& hmac)
128{
129 std::string response;
130 int status = sendCommand(std::string("AUTH ") + method, response);
131
132 if (!isPositiveIntermediate(status)) throw SMTPException(std::string("Cannot authenticate using ") + method, response, status);
133 std::string challengeBase64 = response.substr(4);
134
135 std::istringstream istr(challengeBase64);
136 Base64Decoder decoder(istr);
137 std::string challenge;
138 StreamCopier::copyToString(decoder, challenge);
139
140 hmac.update(challenge);
141
142 const DigestEngine::Digest& digest = hmac.digest();
143 std::string digestString(DigestEngine::digestToHex(digest));
144
145 std::string challengeResponse = username + " " + digestString;
146
147 std::ostringstream challengeResponseBase64;
148 Base64Encoder encoder(challengeResponseBase64);
149 encoder.rdbuf()->setLineLength(0);
150 encoder << challengeResponse;
151 encoder.close();
152
153 status = sendCommand(challengeResponseBase64.str(), response);
154 if (!isPositiveCompletion(status)) throw SMTPException(std::string("Login using ") + method + " failed", response, status);
155}
156
157
158void SMTPClientSession::loginUsingLogin(const std::string& username, const std::string& password)
159{
160 std::string response;
161 int status = sendCommand("AUTH LOGIN", response);
162 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot authenticate using LOGIN", response, status);
163
164 std::ostringstream usernameBase64;
165 Base64Encoder usernameEncoder(usernameBase64);
166 usernameEncoder.rdbuf()->setLineLength(0);
167 usernameEncoder << username;
168 usernameEncoder.close();
169
170 std::ostringstream passwordBase64;
171 Base64Encoder passwordEncoder(passwordBase64);
172 passwordEncoder.rdbuf()->setLineLength(0);
173 passwordEncoder << password;
174 passwordEncoder.close();
175
176 //Server request for username/password not defined could be either
177 //S: login:
178 //C: user_login
179 //S: password:
180 //C: user_password
181 //or
182 //S: password:
183 //C: user_password
184 //S: login:
185 //C: user_login
186
187 std::string decodedResponse;
188 std::istringstream responseStream(response.substr(4));
189 Base64Decoder responseDecoder(responseStream);
190 StreamCopier::copyToString(responseDecoder, decodedResponse);
191
192 if (Poco::icompare(decodedResponse, 0, 8, "username") == 0) // username first (md5("Username:"))
193 {
194 status = sendCommand(usernameBase64.str(), response);
195 if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN username failed", response, status);
196
197 status = sendCommand(passwordBase64.str(), response);
198 if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN password failed", response, status);
199 }
200 else if (Poco::icompare(decodedResponse, 0, 8, "password") == 0) // password first (md5("Password:"))
201 {
202 status = sendCommand(passwordBase64.str(), response);
203 if (!isPositiveIntermediate(status)) throw SMTPException("Login using LOGIN password failed", response, status);
204
205 status = sendCommand(usernameBase64.str(), response);
206 if (!isPositiveCompletion(status)) throw SMTPException("Login using LOGIN username failed", response, status);
207 }
208}
209
210
211void SMTPClientSession::loginUsingPlain(const std::string& username, const std::string& password)
212{
213 std::ostringstream credentialsBase64;
214 Base64Encoder credentialsEncoder(credentialsBase64);
215 credentialsEncoder.rdbuf()->setLineLength(0);
216 credentialsEncoder << '\0' << username << '\0' << password;
217 credentialsEncoder.close();
218
219 std::string response;
220 int status = sendCommand("AUTH PLAIN", credentialsBase64.str(), response);
221 if (!isPositiveCompletion(status)) throw SMTPException("Login using PLAIN failed", response, status);
222}
223
224
225void SMTPClientSession::loginUsingXOAUTH2(const std::string& username, const std::string& password)
226{
227 std::ostringstream credentialsBase64;
228 Base64Encoder credentialsEncoder(credentialsBase64);
229 credentialsEncoder.rdbuf()->setLineLength(0);
230 credentialsEncoder << "user=" << username << "\001auth=Bearer " << password << "\001\001";
231 credentialsEncoder.close();
232
233 std::string response;
234 int status = sendCommand("AUTH XOAUTH2", credentialsBase64.str(), response);
235 if (!isPositiveCompletion(status)) throw SMTPException("Login using XOAUTH2 failed", response, status);
236}
237
238
239void SMTPClientSession::login(LoginMethod loginMethod, const std::string& username, const std::string& password)
240{
241 login(Environment::nodeName(), loginMethod, username, password);
242}
243
244
245void SMTPClientSession::login(const std::string& hostname, LoginMethod loginMethod, const std::string& username, const std::string& password)
246{
247 std::string response;
248 login(hostname, response);
249
250 if (loginMethod == AUTH_CRAM_MD5)
251 {
252 if (response.find("CRAM-MD5", 0) != std::string::npos)
253 {
254 loginUsingCRAMMD5(username, password);
255 }
256 else throw SMTPException("The mail service does not support CRAM-MD5 authentication", response);
257 }
258 else if (loginMethod == AUTH_CRAM_SHA1)
259 {
260 if (response.find("CRAM-SHA1", 0) != std::string::npos)
261 {
262 loginUsingCRAMSHA1(username, password);
263 }
264 else throw SMTPException("The mail service does not support CRAM-SHA1 authentication", response);
265 }
266 else if (loginMethod == AUTH_LOGIN)
267 {
268 if (response.find("LOGIN", 0) != std::string::npos)
269 {
270 loginUsingLogin(username, password);
271 }
272 else throw SMTPException("The mail service does not support LOGIN authentication", response);
273 }
274 else if (loginMethod == AUTH_PLAIN)
275 {
276 if (response.find("PLAIN", 0) != std::string::npos)
277 {
278 loginUsingPlain(username, password);
279 }
280 else throw SMTPException("The mail service does not support PLAIN authentication", response);
281 }
282 else if (loginMethod == AUTH_XOAUTH2)
283 {
284 if (response.find("XOAUTH2", 0) != std::string::npos)
285 {
286 loginUsingXOAUTH2(username, password);
287 }
288 else throw SMTPException("The mail service does not support XOAUTH2 authentication", response);
289 }
290 else if (loginMethod != AUTH_NONE)
291 {
292 throw SMTPException("The autentication method is not supported");
293 }
294}
295
296
297void SMTPClientSession::open()
298{
299 if (!_isOpen)
300 {
301 std::string response;
302 int status = _socket.receiveStatusMessage(response);
303 if (!isPositiveCompletion(status)) throw SMTPException("The mail service is unavailable", response, status);
304 _isOpen = true;
305 }
306}
307
308
309void SMTPClientSession::close()
310{
311 if (_isOpen)
312 {
313 std::string response;
314 sendCommand("QUIT", response);
315 _socket.close();
316 _isOpen = false;
317 }
318}
319
320
321void SMTPClientSession::sendCommands(const MailMessage& message, const Recipients* pRecipients)
322{
323 std::string response;
324 int status = 0;
325 const std::string& fromField = message.getSender();
326 std::string::size_type emailPos = fromField.find('<');
327 if (emailPos == std::string::npos)
328 {
329 std::string sender("<");
330 sender.append(fromField);
331 sender.append(">");
332 status = sendCommand("MAIL FROM:", sender, response);
333 }
334 else
335 {
336 status = sendCommand("MAIL FROM:", fromField.substr(emailPos, fromField.size() - emailPos), response);
337 }
338
339 if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status);
340
341 std::ostringstream recipient;
342 if (pRecipients)
343 {
344 for (Recipients::const_iterator it = pRecipients->begin(); it != pRecipients->end(); ++it)
345 {
346 recipient << '<' << *it << '>';
347 int status = sendCommand("RCPT TO:", recipient.str(), response);
348 if (!isPositiveCompletion(status)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status);
349 recipient.str("");
350 }
351 }
352 else
353 {
354 for (MailMessage::Recipients::const_iterator it = message.recipients().begin(); it != message.recipients().end(); ++it)
355 {
356 recipient << '<' << it->getAddress() << '>';
357 int status = sendCommand("RCPT TO:", recipient.str(), response);
358 if (!isPositiveCompletion(status)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status);
359 recipient.str("");
360 }
361 }
362
363 status = sendCommand("DATA", response);
364 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot send message data", response, status);
365}
366
367
368void SMTPClientSession::sendAddresses(const std::string& from, const Recipients& recipients)
369{
370 std::string response;
371 int status = 0;
372
373 std::string::size_type emailPos = from.find('<');
374 if (emailPos == std::string::npos)
375 {
376 std::string sender("<");
377 sender.append(from);
378 sender.append(">");
379 status = sendCommand("MAIL FROM:", sender, response);
380 }
381 else
382 {
383 status = sendCommand("MAIL FROM:", from.substr(emailPos, from.size() - emailPos), response);
384 }
385
386 if (!isPositiveCompletion(status)) throw SMTPException("Cannot send message", response, status);
387
388 std::ostringstream recipient;
389
390 for (Recipients::const_iterator it = recipients.begin(); it != recipients.end(); ++it)
391 {
392
393 recipient << '<' << *it << '>';
394 int status = sendCommand("RCPT TO:", recipient.str(), response);
395 if (!isPositiveCompletion(status)) throw SMTPException(std::string("Recipient rejected: ") + recipient.str(), response, status);
396 recipient.str("");
397 }
398}
399
400
401void SMTPClientSession::sendData()
402{
403 std::string response;
404 int status = sendCommand("DATA", response);
405 if (!isPositiveIntermediate(status)) throw SMTPException("Cannot send message data", response, status);
406}
407
408
409void SMTPClientSession::sendMessage(const MailMessage& message)
410{
411 sendCommands(message);
412 transportMessage(message);
413}
414
415
416void SMTPClientSession::sendMessage(const MailMessage& message, const Recipients& recipients)
417{
418 sendCommands(message, &recipients);
419 transportMessage(message);
420}
421
422
423void SMTPClientSession::transportMessage(const MailMessage& message)
424{
425 SocketOutputStream socketStream(_socket);
426 MailOutputStream mailStream(socketStream);
427 message.write(mailStream);
428 mailStream.close();
429 socketStream.flush();
430
431 std::string response;
432 int status = _socket.receiveStatusMessage(response);
433 if (!isPositiveCompletion(status)) throw SMTPException("The server rejected the message", response, status);
434}
435
436
437int SMTPClientSession::sendCommand(const std::string& command, std::string& response)
438{
439 _socket.sendMessage(command);
440 return _socket.receiveStatusMessage(response);
441}
442
443
444int SMTPClientSession::sendCommand(const std::string& command, const std::string& arg, std::string& response)
445{
446 _socket.sendMessage(command, arg);
447 return _socket.receiveStatusMessage(response);
448}
449
450
451void SMTPClientSession::sendMessage(std::istream& istr)
452{
453 std::string response;
454 int status = 0;
455
456 SocketOutputStream socketStream(_socket);
457 MailOutputStream mailStream(socketStream);
458 StreamCopier::copyStream(istr, mailStream);
459 mailStream.close();
460 socketStream.flush();
461 status = _socket.receiveStatusMessage(response);
462 if (!isPositiveCompletion(status)) throw SMTPException("The server rejected the message", response, status);
463}
464
465
466} } // namespace Poco::Net
467