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 | |
37 | using Poco::DigestEngine; |
38 | using Poco::HMACEngine; |
39 | using Poco::MD5Engine; |
40 | using Poco::SHA1Engine; |
41 | using Poco::DigestOutputStream; |
42 | using Poco::StreamCopier; |
43 | using Poco::Base64Encoder; |
44 | using Poco::Base64Decoder; |
45 | using Poco::Environment; |
46 | |
47 | |
48 | namespace Poco { |
49 | namespace Net { |
50 | |
51 | |
52 | SMTPClientSession::SMTPClientSession(const StreamSocket& socket): |
53 | _socket(socket), |
54 | _isOpen(false) |
55 | { |
56 | } |
57 | |
58 | |
59 | SMTPClientSession::SMTPClientSession(const std::string& host, Poco::UInt16 port): |
60 | _socket(SocketAddress(host, port)), |
61 | _isOpen(false) |
62 | { |
63 | } |
64 | |
65 | |
66 | SMTPClientSession::~SMTPClientSession() |
67 | { |
68 | try |
69 | { |
70 | close(); |
71 | } |
72 | catch (...) |
73 | { |
74 | } |
75 | } |
76 | |
77 | |
78 | void SMTPClientSession::setTimeout(const Poco::Timespan& timeout) |
79 | { |
80 | _socket.setReceiveTimeout(timeout); |
81 | } |
82 | |
83 | |
84 | Poco::Timespan SMTPClientSession::getTimeout() const |
85 | { |
86 | return _socket.getReceiveTimeout(); |
87 | } |
88 | |
89 | |
90 | void 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 | |
100 | void SMTPClientSession::login(const std::string& hostname) |
101 | { |
102 | std::string response; |
103 | login(hostname, response); |
104 | } |
105 | |
106 | |
107 | void SMTPClientSession::login() |
108 | { |
109 | login(Environment::nodeName()); |
110 | } |
111 | |
112 | |
113 | void 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 | |
120 | void 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 | |
127 | void 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 | |
158 | void 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 | |
211 | void 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 | |
225 | void 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 | |
239 | void SMTPClientSession::login(LoginMethod loginMethod, const std::string& username, const std::string& password) |
240 | { |
241 | login(Environment::nodeName(), loginMethod, username, password); |
242 | } |
243 | |
244 | |
245 | void 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 | |
297 | void 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 | |
309 | void 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 | |
321 | void 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 | |
368 | void 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 | |
401 | void 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 | |
409 | void SMTPClientSession::sendMessage(const MailMessage& message) |
410 | { |
411 | sendCommands(message); |
412 | transportMessage(message); |
413 | } |
414 | |
415 | |
416 | void SMTPClientSession::sendMessage(const MailMessage& message, const Recipients& recipients) |
417 | { |
418 | sendCommands(message, &recipients); |
419 | transportMessage(message); |
420 | } |
421 | |
422 | |
423 | void 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 | |
437 | int SMTPClientSession::sendCommand(const std::string& command, std::string& response) |
438 | { |
439 | _socket.sendMessage(command); |
440 | return _socket.receiveStatusMessage(response); |
441 | } |
442 | |
443 | |
444 | int 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 | |
451 | void 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 | |