"Fossies" - the Fresh Open Source Software Archive 
Member "netxms-3.8.166/src/server/core/email.cpp" (23 Feb 2021, 15410 Bytes) of package /linux/misc/netxms-3.8.166.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "email.cpp" see the
Fossies "Dox" file reference documentation.
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2019 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: email.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Receive buffer size
27 */
28 #define SMTP_BUFFER_SIZE 1024
29
30 /**
31 * Sender errors
32 */
33 #define SMTP_ERR_SUCCESS 0
34 #define SMTP_ERR_BAD_SERVER_NAME 1
35 #define SMTP_ERR_COMM_FAILURE 2
36 #define SMTP_ERR_PROTOCOL_FAILURE 3
37
38 /**
39 * Mail sender states
40 */
41 #define STATE_INITIAL 0
42 #define STATE_HELLO 1
43 #define STATE_FROM 2
44 #define STATE_RCPT 3
45 #define STATE_DATA 4
46 #define STATE_MAIL_BODY 5
47 #define STATE_QUIT 6
48 #define STATE_FINISHED 7
49 #define STATE_ERROR 8
50
51 /**
52 * Mail envelope structure
53 */
54 struct MAIL_ENVELOPE
55 {
56 char rcptAddr[MAX_RCPT_ADDR_LEN];
57 char subject[MAX_EMAIL_SUBJECT_LEN];
58 char *text;
59 char encoding[64];
60 bool isHtml;
61 bool isUtf8;
62 int retryCount;
63 };
64
65 /**
66 * Static data
67 */
68 static ObjectQueue<MAIL_ENVELOPE> s_mailerQueue(64, Ownership::False);
69 static THREAD s_mailerThread = INVALID_THREAD_HANDLE;
70
71 /**
72 * Find end-of-line character
73 */
74 static char *FindEOL(char *pszBuffer, size_t len)
75 {
76 for(size_t i = 0; i < len; i++)
77 if (pszBuffer[i] == '\n')
78 return &pszBuffer[i];
79 return nullptr;
80 }
81
82 /**
83 * Read line from socket
84 */
85 static bool ReadLineFromSocket(SOCKET hSocket, char *pszBuffer, size_t *pnBufPos, char *pszLine)
86 {
87 char *ptr;
88 do
89 {
90 ptr = FindEOL(pszBuffer, *pnBufPos);
91 if (ptr == nullptr)
92 {
93 ssize_t bytes = RecvEx(hSocket, &pszBuffer[*pnBufPos], SMTP_BUFFER_SIZE - *pnBufPos, 0, 30000);
94 if (bytes <= 0)
95 return false;
96 *pnBufPos += bytes;
97 }
98 } while(ptr == nullptr);
99 *ptr = 0;
100 strcpy(pszLine, pszBuffer);
101 *pnBufPos -= (int)(ptr - pszBuffer + 1);
102 memmove(pszBuffer, ptr + 1, *pnBufPos);
103 return true;
104 }
105
106 /**
107 * Read SMTP response code from socket
108 */
109 static int GetSMTPResponse(SOCKET hSocket, char *pszBuffer, size_t *pnBufPos)
110 {
111 char szLine[SMTP_BUFFER_SIZE];
112
113 while(1)
114 {
115 if (!ReadLineFromSocket(hSocket, pszBuffer, pnBufPos, szLine))
116 return -1;
117 if (strlen(szLine) < 4)
118 return -1;
119 if (szLine[3] == ' ')
120 {
121 szLine[3] = 0;
122 break;
123 }
124 }
125 return atoi(szLine);
126 }
127
128 /**
129 * Encode SMTP header
130 */
131 static char *EncodeHeader(const char *header, const char *encoding, const char *data, char *buffer, size_t bufferSize)
132 {
133 bool encode = false;
134 for(const char *p = data; *p != 0; p++)
135 if (*p & 0x80)
136 {
137 encode = true;
138 break;
139 }
140 if (encode)
141 {
142 char *encodedData = NULL;
143 base64_encode_alloc(data, strlen(data), &encodedData);
144 if (encodedData != NULL)
145 {
146 if (header != NULL)
147 snprintf(buffer, bufferSize, "%s: =?%s?B?%s?=\r\n", header, encoding, encodedData);
148 else
149 snprintf(buffer, bufferSize, "=?%s?B?%s?=", encoding, encodedData);
150 free(encodedData);
151 }
152 else
153 {
154 // fallback
155 if (header != NULL)
156 snprintf(buffer, bufferSize, "%s: %s\r\n", header, data);
157 else
158 strlcpy(buffer, data, bufferSize);
159 }
160 }
161 else
162 {
163 if (header != NULL)
164 snprintf(buffer, bufferSize, "%s: %s\r\n", header, data);
165 else
166 strlcpy(buffer, data, bufferSize);
167 }
168 return buffer;
169 }
170
171 /**
172 * Send e-mail
173 */
174 static UINT32 SendMail(const char *pszRcpt, const char *pszSubject, const char *pszText, const char *encoding, bool isHtml, bool isUtf8)
175 {
176 TCHAR smtpServer[256];
177 char fromName[256], fromAddr[256], localHostName[256];
178 ConfigReadStr(_T("SMTP.Server"), smtpServer, 256, _T("localhost"));
179 ConfigReadStrA(_T("SMTP.FromAddr"), fromAddr, 256, "netxms@localhost");
180 if (isUtf8)
181 {
182 ConfigReadStrUTF8(_T("SMTP.FromName"), fromName, 256, "NetXMS Server");
183 }
184 else
185 {
186 ConfigReadStrA(_T("SMTP.FromName"), fromName, 256, "NetXMS Server");
187 }
188 uint16_t smtpPort = static_cast<uint16_t>(ConfigReadInt(_T("SMTP.Port"), 25));
189 ConfigReadStrA(_T("SMTP.LocalHostName"), localHostName, 256, "");
190 if (localHostName[0] == 0)
191 {
192 #ifdef UNICODE
193 WCHAR localHostNameW[256] = L"";
194 GetLocalHostName(localHostNameW, 256, true);
195 wchar_to_utf8(localHostNameW, -1, localHostName, 256);
196 #else
197 GetLocalHostName(localHostName, 256, true);
198 #endif
199 if (localHostName[0] == 0)
200 strcpy(localHostName, "localhost");
201 }
202
203 // Resolve hostname
204 InetAddress addr = InetAddress::resolveHostName(smtpServer);
205 if (!addr.isValid() || addr.isBroadcast() || addr.isMulticast())
206 return SMTP_ERR_BAD_SERVER_NAME;
207
208 // Create socket and connect to server
209 SOCKET hSocket = ConnectToHost(addr, smtpPort, 3000);
210 if (hSocket == INVALID_SOCKET)
211 return SMTP_ERR_COMM_FAILURE;
212
213 char szBuffer[SMTP_BUFFER_SIZE];
214 size_t nBufPos = 0;
215 int iState = STATE_INITIAL;
216 while((iState != STATE_FINISHED) && (iState != STATE_ERROR))
217 {
218 int iResp = GetSMTPResponse(hSocket, szBuffer, &nBufPos);
219 DbgPrintf(8, _T("SMTP RESPONSE: %03d (state=%d)"), iResp, iState);
220 if (iResp > 0)
221 {
222 switch(iState)
223 {
224 case STATE_INITIAL:
225 // Server should send 220 text after connect
226 if (iResp == 220)
227 {
228 iState = STATE_HELLO;
229 char command[280];
230 snprintf(command, 280, "HELO %s\r\n", localHostName);
231 SendEx(hSocket, command, strlen(command), 0, nullptr);
232 }
233 else
234 {
235 iState = STATE_ERROR;
236 }
237 break;
238 case STATE_HELLO:
239 // Server should respond with 250 text to our HELO command
240 if (iResp == 250)
241 {
242 iState = STATE_FROM;
243 snprintf(szBuffer, SMTP_BUFFER_SIZE, "MAIL FROM: <%s>\r\n", fromAddr);
244 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
245 }
246 else
247 {
248 iState = STATE_ERROR;
249 }
250 break;
251 case STATE_FROM:
252 // Server should respond with 250 text to our MAIL FROM command
253 if (iResp == 250)
254 {
255 iState = STATE_RCPT;
256 snprintf(szBuffer, SMTP_BUFFER_SIZE, "RCPT TO: <%s>\r\n", pszRcpt);
257 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
258 }
259 else
260 {
261 iState = STATE_ERROR;
262 }
263 break;
264 case STATE_RCPT:
265 // Server should respond with 250 text to our RCPT TO command
266 if (iResp == 250)
267 {
268 iState = STATE_DATA;
269 SendEx(hSocket, "DATA\r\n", 6, 0, NULL);
270 }
271 else
272 {
273 iState = STATE_ERROR;
274 }
275 break;
276 case STATE_DATA:
277 // Server should respond with 354 text to our DATA command
278 if (iResp == 354)
279 {
280 iState = STATE_MAIL_BODY;
281
282 // Mail headers
283 // from
284 char from[512];
285 snprintf(szBuffer, SMTP_BUFFER_SIZE, "From: \"%s\" <%s>\r\n", EncodeHeader(NULL, encoding, fromName, from, 512), fromAddr);
286 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
287 // to
288 snprintf(szBuffer, SMTP_BUFFER_SIZE, "To: <%s>\r\n", pszRcpt);
289 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
290 // subject
291 EncodeHeader("Subject", encoding, pszSubject, szBuffer, SMTP_BUFFER_SIZE);
292 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
293
294 // date
295 time_t currentTime;
296 struct tm *pCurrentTM;
297 time(¤tTime);
298 #ifdef HAVE_LOCALTIME_R
299 struct tm currentTM;
300 localtime_r(¤tTime, ¤tTM);
301 pCurrentTM = ¤tTM;
302 #else
303 pCurrentTM = localtime(¤tTime);
304 #endif
305 #ifdef _WIN32
306 strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S ", pCurrentTM);
307
308 TIME_ZONE_INFORMATION tzi;
309 UINT32 tzType = GetTimeZoneInformation(&tzi);
310 LONG effectiveBias;
311 switch(tzType)
312 {
313 case TIME_ZONE_ID_STANDARD:
314 effectiveBias = tzi.Bias + tzi.StandardBias;
315 break;
316 case TIME_ZONE_ID_DAYLIGHT:
317 effectiveBias = tzi.Bias + tzi.DaylightBias;
318 break;
319 case TIME_ZONE_ID_UNKNOWN:
320 effectiveBias = tzi.Bias;
321 break;
322 default: // error
323 effectiveBias = 0;
324 DbgPrintf(4, _T("GetTimeZoneInformation() call failed"));
325 break;
326 }
327 int offset = abs(effectiveBias);
328 sprintf(&szBuffer[strlen(szBuffer)], "%c%02d%02d\r\n", effectiveBias <= 0 ? '+' : '-', offset / 60, offset % 60);
329 #else
330 strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S %z\r\n", pCurrentTM);
331 #endif
332
333 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
334 // content-type
335 snprintf(szBuffer, SMTP_BUFFER_SIZE,
336 "Content-Type: text/%s; charset=%s\r\n"
337 "Content-Transfer-Encoding: 8bit\r\n\r\n", isHtml ? "html" : "plain", encoding);
338 SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
339
340 // Mail body
341 SendEx(hSocket, pszText, strlen(pszText), 0, NULL);
342 SendEx(hSocket, "\r\n.\r\n", 5, 0, NULL);
343 }
344 else
345 {
346 iState = STATE_ERROR;
347 }
348 break;
349 case STATE_MAIL_BODY:
350 // Server should respond with 250 to our mail body
351 if (iResp == 250)
352 {
353 iState = STATE_QUIT;
354 SendEx(hSocket, "QUIT\r\n", 6, 0, NULL);
355 }
356 else
357 {
358 iState = STATE_ERROR;
359 }
360 break;
361 case STATE_QUIT:
362 // Server should respond with 221 text to our QUIT command
363 if (iResp == 221)
364 {
365 iState = STATE_FINISHED;
366 }
367 else
368 {
369 iState = STATE_ERROR;
370 }
371 break;
372 default:
373 iState = STATE_ERROR;
374 break;
375 }
376 }
377 else
378 {
379 iState = STATE_ERROR;
380 }
381 }
382
383 // Shutdown communication channel
384 shutdown(hSocket, SHUT_RDWR);
385 closesocket(hSocket);
386
387 return (iState == STATE_FINISHED) ? SMTP_ERR_SUCCESS : SMTP_ERR_PROTOCOL_FAILURE;
388 }
389
390 /**
391 * Mailer thread
392 */
393 static THREAD_RESULT THREAD_CALL MailerThread(void *pArg)
394 {
395 static const TCHAR *m_szErrorText[] =
396 {
397 _T("Sent successfully"),
398 _T("Unable to resolve SMTP server name"),
399 _T("Communication failure"),
400 _T("SMTP conversation failure")
401 };
402
403 ThreadSetName("Mailer");
404 DbgPrintf(1, _T("SMTP mailer thread started"));
405 while(1)
406 {
407 MAIL_ENVELOPE *pEnvelope = s_mailerQueue.getOrBlock();
408 if (pEnvelope == INVALID_POINTER_VALUE)
409 break;
410
411 nxlog_debug(6, _T("SMTP(%p): new envelope, rcpt=%hs"), pEnvelope, pEnvelope->rcptAddr);
412
413 UINT32 dwResult = SendMail(pEnvelope->rcptAddr, pEnvelope->subject, pEnvelope->text, pEnvelope->encoding, pEnvelope->isHtml, pEnvelope->isUtf8);
414 if (dwResult != SMTP_ERR_SUCCESS)
415 {
416 pEnvelope->retryCount--;
417 DbgPrintf(6, _T("SMTP(%p): Failed to send e-mail, remaining retries: %d"), pEnvelope, pEnvelope->retryCount);
418 if (pEnvelope->retryCount > 0)
419 {
420 // Try posting again
421 s_mailerQueue.put(pEnvelope);
422 }
423 else
424 {
425 PostSystemEvent(EVENT_SMTP_FAILURE, g_dwMgmtNode, "dsmm", dwResult,
426 m_szErrorText[dwResult], pEnvelope->rcptAddr, pEnvelope->subject);
427 MemFree(pEnvelope->text);
428 MemFree(pEnvelope);
429 }
430 }
431 else
432 {
433 DbgPrintf(6, _T("SMTP(%p): mail sent successfully"), pEnvelope);
434 MemFree(pEnvelope->text);
435 MemFree(pEnvelope);
436 }
437 }
438 return THREAD_OK;
439 }
440
441 /**
442 * Initialize mailer subsystem
443 */
444 void InitMailer()
445 {
446 s_mailerThread = ThreadCreateEx(MailerThread, 0, NULL);
447 }
448
449 /**
450 * Shutdown mailer
451 */
452 void ShutdownMailer()
453 {
454 s_mailerQueue.clear();
455 s_mailerQueue.put(INVALID_POINTER_VALUE);
456 ThreadJoin(s_mailerThread);
457 }
458
459 /**
460 * Post e-mail to queue
461 */
462 void NXCORE_EXPORTABLE PostMail(const TCHAR *pszRcpt, const TCHAR *pszSubject, const TCHAR *pszText, bool isHtml)
463 {
464 auto envelope = MemAllocStruct<MAIL_ENVELOPE>();
465 ConfigReadStrA(_T("MailEncoding"), envelope->encoding, 64, "utf8");
466 envelope->isUtf8 = isHtml || !stricmp(envelope->encoding, "utf-8") || !stricmp(envelope->encoding, "utf8");
467
468 #ifdef UNICODE
469 WideCharToMultiByte(envelope->isUtf8 ? CP_UTF8 : CP_ACP, envelope->isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN, NULL, NULL);
470 envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0;
471 WideCharToMultiByte(envelope->isUtf8 ? CP_UTF8 : CP_ACP, envelope->isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN, NULL, NULL);
472 envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0;
473 envelope->text = envelope->isUtf8 ? UTF8StringFromWideString(pszText) : MBStringFromWideString(pszText);
474 #else
475 if (envelope->isUtf8)
476 {
477 mb_to_utf8(pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN);
478 envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0;
479 mb_to_utf8(pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN);
480 envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0;
481 envelope->text = UTF8StringFromMBString(pszText);
482 }
483 else
484 {
485 nx_strncpy(envelope->rcptAddr, pszRcpt, MAX_RCPT_ADDR_LEN);
486 nx_strncpy(envelope->subject, pszSubject, MAX_EMAIL_SUBJECT_LEN);
487 envelope->text = strdup(pszText);
488 }
489 #endif
490 envelope->retryCount = ConfigReadInt(_T("SMTP.RetryCount"), 1);
491 envelope->isHtml = isHtml;
492 s_mailerQueue.put(envelope);
493 }