1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "nel/misc/report.h"
23 #include "nel/misc/path.h"
25 #include "nel/net/tcp_sock.h"
26 #include "nel/net/email.h"
29 using namespace NLMISC
;
34 static string DefaultSMTPServer
, DefaultFrom
, DefaultTo
;
36 /* Conversion table. for base 64 */
37 static char tbl
[65] = {
38 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
39 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
40 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
41 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
42 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
43 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
44 'w', 'x', 'y', 'z', '0', '1', '2', '3',
45 '4', '5', '6', '7', '8', '9', '+', '/',
46 '=' /* termination character */
50 * Encode the string S of length LENGTH to base64 format and place it
51 * to STORE. STORE will be 0-terminated, and must point to a writable
52 * buffer of at least 1+BASE64_LENGTH(length) bytes.
53 * where BASE64_LENGTH(len) = (4 * ((LENGTH + 2) / 3))
55 static void uuencode (const char *s
, const char *store
, const int length
)
58 unsigned char *p
= (unsigned char *)store
;
59 unsigned char *us
= (unsigned char *)s
;
61 /* Transform the 3x8 bits to 4x6 bits, as required by base64. */
62 for (i
= 0; i
< length
; i
+= 3)
64 *p
++ = tbl
[us
[0] >> 2];
65 *p
++ = tbl
[((us
[0] & 3) << 4) + (us
[1] >> 4)];
66 *p
++ = tbl
[((us
[1] & 0xf) << 2) + (us
[2] >> 6)];
67 *p
++ = tbl
[us
[2] & 0x3f];
70 /* Pad the result if necessary... */
75 else if (i
== length
+ 2)
77 *(p
- 1) = *(p
- 2) = tbl
[64];
79 /* ...and zero-terminate it. */
83 bool sendEMailCommand (CTcpSock
&sock
, const std::string
&command
, uint32 code
= 250)
85 string buffer
= command
+ "\r\n";
86 uint32 size
= (uint32
)buffer
.size();
89 if (sock
.send ((uint8
*)buffer
.c_str(), size
) != CSock::Ok
)
91 nlwarning ("EMAIL: Can't send data to the server");
102 if (sock
.receive((uint8
*)&c
, size
, false) == CSock::Ok
)
111 nlwarning ("EMAIL: EMail command '%s' returned '%s' instead of code %d on sock %s", command
.substr(0, 20).c_str(), res
.substr(0, res
.size()-2).c_str(), code
, sock
.remoteAddr().asString().c_str());
119 nlwarning ("EMAIL: EMail connection closed before end of line, command '%s' returned '%s' on sock %s (code %d)", command
.substr(0, 20).c_str(), res
.c_str(), sock
.remoteAddr().asString().c_str(), code
);
126 bool sendEmail (const string
&smtpServer
, const string
&from
, const string
&to
, const string
&subject
, const string
&body
, const string
&attachedFile
, bool onlyCheck
)
135 string formatedSMTPServer
;
140 if (smtpServer
.empty())
142 if(DefaultSMTPServer
.empty())
144 nlwarning ("EMAIL: Can't send email because no SMTPServer was provided");
149 formatedSMTPServer
= DefaultSMTPServer
;
154 formatedSMTPServer
= smtpServer
;
157 sock
.connect(CInetAddress(formatedSMTPServer
, 25));
159 if (!sock
.connected())
161 nlwarning ("EMAIL: Can't connect to email server %s", formatedSMTPServer
.c_str());
167 if(DefaultTo
.empty())
169 nlwarning ("EMAIL: Can't send email because no To was provided");
174 formatedTo
= DefaultTo
;
184 if (DefaultFrom
.empty())
186 formatedFrom
= CInetAddress::localHost().hostName();
187 formatedFrom
+= "@gnu.org";
191 formatedFrom
= DefaultFrom
;
199 // we must skip the first line
200 formatedBody
= "\r\n";
202 // replace \n by \r\n
203 formatedBody
+= addSlashR(body
);
205 // add attachment if any
206 if (!attachedFile
.empty())
208 string ext
= CFile::getExtension(attachedFile
);
212 // mime header and main mail text
214 mimepart
+= "Mime-Version: 1.0\r\n";
215 mimepart
+= "Content-Type: multipart/mixed;\r\n";
216 mimepart
+= " boundary=\"Multipart_nel\"\r\n";
218 mimepart
+= "This is a multi-part message in MIME format.\r\n";
220 mimepart
+= "--Multipart_nel\r\n";
221 mimepart
+= "Content-Type: text/plain; charset=us-ascii\r\n";
222 mimepart
+= "Content-Transfer-Encoding: 7bit\r\n";
224 formatedBody
= mimepart
+ formatedBody
;
228 formatedBody
+= "--Multipart_nel\r\n";
229 formatedBody
+= "Content-Disposition: attachment;\r\n";
231 string lext
= toLowerAscii(ext
);
234 formatedBody
+= "Content-Type: image/x-targa;\r\n";
236 else if(lext
== "bmp")
238 formatedBody
+= "Content-Type: image/bmp;\r\n";
240 else if(lext
== "png")
242 formatedBody
+= "Content-Type: image/png;\r\n";
244 else if(lext
== "jpg" || lext
== "jpeg")
246 formatedBody
+= "Content-Type: image/jpeg;\r\n";
248 else if(lext
== "dmp")
250 formatedBody
+= "Content-Type: application/octet-stream;\r\n";
254 formatedBody
+= "Content-Type: text/plain; charset=us-ascii\r\n";
257 formatedBody
+= " name=\""+CFile::getFilename(attachedFile
)+"\"\r\n";
258 formatedBody
+= "Content-Transfer-Encoding: base64\r\n";
259 formatedBody
+= " filename=\""+CFile::getFilename(attachedFile
)+"\"\r\n";
260 // empty line to say that it s the end of the header
261 formatedBody
+= "\r\n";
263 static const size_t src_buf_size
= 45;// This *MUST* be a multiple of 3
264 static const size_t dst_buf_size
= 4 * ((src_buf_size
+ 2) / 3);
265 size_t write_size
= dst_buf_size
;
266 char src_buf
[src_buf_size
+ 1];
267 char dst_buf
[dst_buf_size
+ 1];
270 FILE *src_stream
= nlfopen (attachedFile
, "rb");
271 if (src_stream
== NULL
)
273 nlwarning ("EMAIL: Can't attach file '%s' to the email because the file can't be open", attachedFile
.c_str());
277 while ((size
= fread(src_buf
, 1, src_buf_size
, src_stream
)) > 0)
279 if (size
!= src_buf_size
)
281 /* write_size is always 60 until the last line */
282 write_size
=(4 * ((size
+ 2) / 3));
283 /* pad with 0s so we can just encode extra bits */
284 memset(&src_buf
[size
], 0, src_buf_size
- size
);
286 /* Encode the buffer we just read in */
287 uuencode(src_buf
, dst_buf
, (int)size
);
289 formatedBody
+= dst_buf
;
290 formatedBody
+= "\r\n";
294 formatedBody
+= "--Multipart_nel--";
297 // debug, display what we send into a file
298 // { FILE *fp = nlfopen (CFile::findNewFile(getLogDirectory() + "mail.txt"), "wb");
299 // fwrite (formatedBody.c_str(), 1, formatedBody.size(), fp);
302 if(!sendEMailCommand (sock
, "", 220)) goto end
;
306 if(!sendEMailCommand (sock
, "HELO localhost")) goto end
;
307 if(!sendEMailCommand (sock
, "MAIL FROM: " + formatedFrom
)) goto end
;
308 if(!sendEMailCommand (sock
, "RCPT TO: " + formatedTo
)) goto end
;
309 if(!sendEMailCommand (sock
, "QUIT", 221)) goto end
;
315 if(!sendEMailCommand (sock
, "HELO localhost")) goto end
;
316 if(!sendEMailCommand (sock
, "MAIL FROM: " + formatedFrom
)) goto end
;
317 if(!sendEMailCommand (sock
, "RCPT TO: " + formatedTo
)) goto end
;
318 if(!sendEMailCommand (sock
, "DATA", 354)) goto end
;
321 "From: " + formatedFrom
+ "\r\n"
322 "To: " + formatedTo
+ "\r\n"
323 "Subject: " + subject
+ "\r\n"
324 + formatedBody
+ "\r\n.";
326 if(!sendEMailCommand (sock
, buffer
)) goto end
;
327 if(!sendEMailCommand (sock
, "QUIT", 221)) goto end
;
332 catch (const Exception
&e
)
334 nlwarning ("EMAIL: Can't send email: %s", e
.what());
339 if (sock
.connected())
345 void setDefaultEmailParams (const std::string
&smtpServer
, const std::string
&from
, const std::string
&to
)
347 DefaultSMTPServer
= smtpServer
;