Commit | Line | Data |
---|---|---|
5039dede AK |
1 | /* |
2 | ** NetXMS - Network Management System | |
b4cf3199 | 3 | ** Copyright (C) 2003-2016 Victor Kirhenshtein |
5039dede AK |
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 | ||
244c65ef VK |
25 | /** |
26 | * Receive buffer size | |
27 | */ | |
5039dede AK |
28 | #define SMTP_BUFFER_SIZE 1024 |
29 | ||
244c65ef VK |
30 | /** |
31 | * Sender errors | |
32 | */ | |
5039dede AK |
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 | ||
244c65ef VK |
38 | /** |
39 | * Mail sender states | |
40 | */ | |
5039dede AK |
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 | ||
244c65ef VK |
51 | /** |
52 | * Mail envelope structure | |
53 | */ | |
b9bcd02c | 54 | struct MAIL_ENVELOPE |
5039dede | 55 | { |
b9bcd02c VK |
56 | char rcptAddr[MAX_RCPT_ADDR_LEN]; |
57 | char subject[MAX_EMAIL_SUBJECT_LEN]; | |
58 | char *text; | |
59 | char encoding[64]; | |
60 | int retryCount; | |
61 | }; | |
5039dede | 62 | |
244c65ef VK |
63 | /** |
64 | * Static data | |
65 | */ | |
35f836fe | 66 | static TCHAR m_szSmtpServer[MAX_PATH] = _T("localhost"); |
5039dede AK |
67 | static WORD m_wSmtpPort = 25; |
68 | static char m_szFromAddr[MAX_PATH] = "netxms@localhost"; | |
c9b85b77 | 69 | static char m_szFromName[MAX_PATH] = "NetXMS Server"; |
5039dede AK |
70 | static Queue *m_pMailerQueue = NULL; |
71 | static THREAD m_hThread = INVALID_THREAD_HANDLE; | |
72 | ||
244c65ef VK |
73 | /** |
74 | * Find end-of-line character | |
75 | */ | |
5039dede AK |
76 | static char *FindEOL(char *pszBuffer, int nLen) |
77 | { | |
78 | int i; | |
79 | ||
80 | for(i = 0; i < nLen; i++) | |
81 | if (pszBuffer[i] == '\n') | |
82 | return &pszBuffer[i]; | |
83 | return NULL; | |
84 | } | |
85 | ||
244c65ef VK |
86 | /** |
87 | * Read line from socket | |
88 | */ | |
5039dede AK |
89 | static BOOL ReadLineFromSocket(SOCKET hSocket, char *pszBuffer, int *pnBufPos, char *pszLine) |
90 | { | |
91 | char *ptr; | |
92 | int nRet; | |
93 | ||
94 | do | |
95 | { | |
96 | ptr = FindEOL(pszBuffer, *pnBufPos); | |
97 | if (ptr == NULL) | |
98 | { | |
99 | nRet = RecvEx(hSocket, &pszBuffer[*pnBufPos], SMTP_BUFFER_SIZE - *pnBufPos, 0, 30000); | |
100 | if (nRet <= 0) | |
101 | return FALSE; | |
102 | *pnBufPos += nRet; | |
103 | } | |
104 | } while(ptr == NULL); | |
105 | *ptr = 0; | |
106 | strcpy(pszLine, pszBuffer); | |
107 | *pnBufPos -= (int)(ptr - pszBuffer + 1); | |
108 | memmove(pszBuffer, ptr + 1, *pnBufPos); | |
109 | return TRUE; | |
110 | } | |
111 | ||
1ebbeb18 VK |
112 | /** |
113 | * Read SMTP response code from socket | |
114 | */ | |
5039dede AK |
115 | static int GetSMTPResponse(SOCKET hSocket, char *pszBuffer, int *pnBufPos) |
116 | { | |
117 | char szLine[SMTP_BUFFER_SIZE]; | |
118 | ||
119 | while(1) | |
120 | { | |
121 | if (!ReadLineFromSocket(hSocket, pszBuffer, pnBufPos, szLine)) | |
122 | return -1; | |
123 | if (strlen(szLine) < 4) | |
124 | return -1; | |
125 | if (szLine[3] == ' ') | |
126 | { | |
127 | szLine[3] = 0; | |
128 | break; | |
129 | } | |
130 | } | |
131 | return atoi(szLine); | |
132 | } | |
133 | ||
110926c7 VK |
134 | /** |
135 | * Send e-mail | |
136 | */ | |
b9bcd02c | 137 | static UINT32 SendMail(const char *pszRcpt, const char *pszSubject, const char *pszText, const char *encoding) |
5039dede AK |
138 | { |
139 | SOCKET hSocket; | |
5039dede AK |
140 | char szBuffer[SMTP_BUFFER_SIZE]; |
141 | int iResp, iState = STATE_INITIAL, nBufPos = 0; | |
967893bb | 142 | UINT32 dwRetCode; |
5039dede | 143 | |
5039dede | 144 | // Resolve hostname |
b4cf3199 VK |
145 | InetAddress addr = InetAddress::resolveHostName(m_szSmtpServer); |
146 | if (!addr.isValidUnicast()) | |
5039dede AK |
147 | return SMTP_ERR_BAD_SERVER_NAME; |
148 | ||
149 | // Create socket | |
b4cf3199 | 150 | hSocket = socket(addr.getFamily(), SOCK_STREAM, 0); |
4c0c75c7 | 151 | if (hSocket == INVALID_SOCKET) |
5039dede AK |
152 | return SMTP_ERR_COMM_FAILURE; |
153 | ||
154 | // Connect to server | |
b4cf3199 VK |
155 | SockAddrBuffer sa; |
156 | addr.fillSockAddr(&sa, m_wSmtpPort); | |
157 | if (connect(hSocket, (struct sockaddr *)&sa, SA_LEN((struct sockaddr *)&sa)) == 0) | |
5039dede AK |
158 | { |
159 | while((iState != STATE_FINISHED) && (iState != STATE_ERROR)) | |
160 | { | |
161 | iResp = GetSMTPResponse(hSocket, szBuffer, &nBufPos); | |
53dec9a6 | 162 | DbgPrintf(8, _T("SMTP RESPONSE: %03d (state=%d)"), iResp, iState); |
5039dede AK |
163 | if (iResp > 0) |
164 | { | |
165 | switch(iState) | |
166 | { | |
167 | case STATE_INITIAL: | |
168 | // Server should send 220 text after connect | |
169 | if (iResp == 220) | |
170 | { | |
171 | iState = STATE_HELLO; | |
d3a7cf4c | 172 | SendEx(hSocket, "HELO netxms\r\n", 13, 0, NULL); |
5039dede AK |
173 | } |
174 | else | |
175 | { | |
176 | iState = STATE_ERROR; | |
177 | } | |
178 | break; | |
179 | case STATE_HELLO: | |
180 | // Server should respond with 250 text to our HELO command | |
181 | if (iResp == 250) | |
182 | { | |
183 | iState = STATE_FROM; | |
c9b85b77 | 184 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "MAIL FROM: <%s>\r\n", m_szFromAddr); |
d3a7cf4c | 185 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede AK |
186 | } |
187 | else | |
188 | { | |
189 | iState = STATE_ERROR; | |
190 | } | |
191 | break; | |
192 | case STATE_FROM: | |
193 | // Server should respond with 250 text to our MAIL FROM command | |
194 | if (iResp == 250) | |
195 | { | |
196 | iState = STATE_RCPT; | |
c9b85b77 | 197 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "RCPT TO: <%s>\r\n", pszRcpt); |
d3a7cf4c | 198 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede AK |
199 | } |
200 | else | |
201 | { | |
202 | iState = STATE_ERROR; | |
203 | } | |
204 | break; | |
205 | case STATE_RCPT: | |
206 | // Server should respond with 250 text to our RCPT TO command | |
207 | if (iResp == 250) | |
208 | { | |
209 | iState = STATE_DATA; | |
d3a7cf4c | 210 | SendEx(hSocket, "DATA\r\n", 6, 0, NULL); |
5039dede AK |
211 | } |
212 | else | |
213 | { | |
214 | iState = STATE_ERROR; | |
215 | } | |
216 | break; | |
217 | case STATE_DATA: | |
218 | // Server should respond with 354 text to our DATA command | |
219 | if (iResp == 354) | |
220 | { | |
221 | iState = STATE_MAIL_BODY; | |
222 | ||
223 | // Mail headers | |
224 | // from | |
c9b85b77 | 225 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "From: %s <%s>\r\n", m_szFromName, m_szFromAddr); |
d3a7cf4c | 226 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede | 227 | // to |
c9b85b77 | 228 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "To: <%s>\r\n", pszRcpt); |
d3a7cf4c | 229 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede | 230 | // subject |
b8735c5f | 231 | bool encodeSubject = false; |
b9bcd02c | 232 | for(const char *p = pszSubject; *p != 0; p++) |
b8735c5f VK |
233 | if (*p & 0x80) |
234 | { | |
235 | encodeSubject = true; | |
236 | break; | |
237 | } | |
b80c318b AK |
238 | if (encodeSubject) |
239 | { | |
240 | char *encodedSubject = NULL; | |
c7fbfe77 | 241 | base64_encode_alloc(pszSubject, strlen(pszSubject), &encodedSubject); |
b80c318b AK |
242 | if (encodedSubject != NULL) |
243 | { | |
b9bcd02c | 244 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "Subject: =?%s?B?%s?=\r\n", encoding, encodedSubject); |
b80c318b AK |
245 | free(encodedSubject); |
246 | } | |
247 | else | |
248 | { | |
249 | // fallback | |
250 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "Subject: %s\r\n", pszSubject); | |
251 | } | |
252 | } | |
253 | else | |
254 | { | |
255 | snprintf(szBuffer, SMTP_BUFFER_SIZE, "Subject: %s\r\n", pszSubject); | |
256 | } | |
d3a7cf4c | 257 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede AK |
258 | |
259 | // date | |
260 | time_t currentTime; | |
261 | struct tm *pCurrentTM; | |
262 | time(¤tTime); | |
263 | #ifdef HAVE_LOCALTIME_R | |
264 | struct tm currentTM; | |
265 | localtime_r(¤tTime, ¤tTM); | |
266 | pCurrentTM = ¤tTM; | |
267 | #else | |
268 | pCurrentTM = localtime(¤tTime); | |
269 | #endif | |
270 | #ifdef _WIN32 | |
271 | strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S ", pCurrentTM); | |
272 | ||
273 | TIME_ZONE_INFORMATION tzi; | |
967893bb | 274 | UINT32 tzType = GetTimeZoneInformation(&tzi); |
c2d2f983 VK |
275 | LONG effectiveBias; |
276 | switch(tzType) | |
277 | { | |
278 | case TIME_ZONE_ID_STANDARD: | |
279 | effectiveBias = tzi.Bias + tzi.StandardBias; | |
280 | break; | |
281 | case TIME_ZONE_ID_DAYLIGHT: | |
282 | effectiveBias = tzi.Bias + tzi.DaylightBias; | |
283 | break; | |
284 | case TIME_ZONE_ID_UNKNOWN: | |
285 | effectiveBias = tzi.Bias; | |
286 | break; | |
287 | default: // error | |
288 | effectiveBias = 0; | |
289 | DbgPrintf(4, _T("GetTimeZoneInformation() call failed")); | |
290 | break; | |
291 | } | |
292 | int offset = abs(effectiveBias); | |
44dac3da | 293 | sprintf(&szBuffer[strlen(szBuffer)], "%c%02d%02d\r\n", effectiveBias <= 0 ? '+' : '-', offset / 60, offset % 60); |
5039dede AK |
294 | #else |
295 | strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S %z\r\n", pCurrentTM); | |
296 | #endif | |
297 | ||
d3a7cf4c | 298 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede | 299 | // content-type |
c9b85b77 AK |
300 | snprintf(szBuffer, SMTP_BUFFER_SIZE, |
301 | "Content-Type: text/plain; charset=%s\r\n" | |
b9bcd02c | 302 | "Content-Transfer-Encoding: 8bit\r\n\r\n", encoding); |
d3a7cf4c | 303 | SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL); |
5039dede AK |
304 | |
305 | // Mail body | |
d3a7cf4c VK |
306 | SendEx(hSocket, pszText, strlen(pszText), 0, NULL); |
307 | SendEx(hSocket, "\r\n.\r\n", 5, 0, NULL); | |
5039dede AK |
308 | } |
309 | else | |
310 | { | |
311 | iState = STATE_ERROR; | |
312 | } | |
313 | break; | |
314 | case STATE_MAIL_BODY: | |
315 | // Server should respond with 250 to our mail body | |
316 | if (iResp == 250) | |
317 | { | |
318 | iState = STATE_QUIT; | |
d3a7cf4c | 319 | SendEx(hSocket, "QUIT\r\n", 6, 0, NULL); |
5039dede AK |
320 | } |
321 | else | |
322 | { | |
323 | iState = STATE_ERROR; | |
324 | } | |
325 | break; | |
326 | case STATE_QUIT: | |
327 | // Server should respond with 221 text to our QUIT command | |
328 | if (iResp == 221) | |
329 | { | |
330 | iState = STATE_FINISHED; | |
331 | } | |
332 | else | |
333 | { | |
334 | iState = STATE_ERROR; | |
335 | } | |
336 | break; | |
337 | default: | |
338 | iState = STATE_ERROR; | |
339 | break; | |
340 | } | |
341 | } | |
342 | else | |
343 | { | |
344 | iState = STATE_ERROR; | |
345 | } | |
346 | } | |
5039dede AK |
347 | // Shutdown communication channel |
348 | shutdown(hSocket, SHUT_RDWR); | |
5039dede AK |
349 | |
350 | dwRetCode = (iState == STATE_FINISHED) ? SMTP_ERR_SUCCESS : SMTP_ERR_PROTOCOL_FAILURE; | |
351 | } | |
352 | else | |
353 | { | |
354 | dwRetCode = SMTP_ERR_COMM_FAILURE; | |
355 | } | |
356 | ||
32ecc5d0 AK |
357 | if (hSocket != -1) |
358 | { | |
359 | closesocket(hSocket); | |
360 | } | |
361 | ||
5039dede AK |
362 | return dwRetCode; |
363 | } | |
364 | ||
110926c7 VK |
365 | /** |
366 | * Mailer thread | |
367 | */ | |
5039dede AK |
368 | static THREAD_RESULT THREAD_CALL MailerThread(void *pArg) |
369 | { | |
35f836fe | 370 | static const TCHAR *m_szErrorText[] = |
5039dede | 371 | { |
35f836fe VK |
372 | _T("Sent successfully"), |
373 | _T("Unable to resolve SMTP server name"), | |
374 | _T("Communication failure"), | |
375 | _T("SMTP conversation failure") | |
5039dede AK |
376 | }; |
377 | ||
53dec9a6 | 378 | DbgPrintf(1, _T("SMTP mailer thread started")); |
5039dede AK |
379 | while(1) |
380 | { | |
19dbc8ef | 381 | MAIL_ENVELOPE *pEnvelope = (MAIL_ENVELOPE *)m_pMailerQueue->getOrBlock(); |
5039dede AK |
382 | if (pEnvelope == INVALID_POINTER_VALUE) |
383 | break; | |
384 | ||
b9bcd02c | 385 | DbgPrintf(6, _T("SMTP(%p): new envelope, rcpt=%hs"), pEnvelope, pEnvelope->rcptAddr); |
53dec9a6 | 386 | |
35f836fe VK |
387 | ConfigReadStr(_T("SMTPServer"), m_szSmtpServer, MAX_PATH, _T("localhost")); |
388 | ConfigReadStrA(_T("SMTPFromAddr"), m_szFromAddr, MAX_PATH, "netxms@localhost"); | |
389 | ConfigReadStrA(_T("SMTPFromName"), m_szFromName, MAX_PATH, "NetXMS Server"); | |
390 | m_wSmtpPort = (WORD)ConfigReadInt(_T("SMTPPort"), 25); | |
5039dede | 391 | |
b9bcd02c | 392 | UINT32 dwResult = SendMail(pEnvelope->rcptAddr, pEnvelope->subject, pEnvelope->text, pEnvelope->encoding); |
5039dede AK |
393 | if (dwResult != SMTP_ERR_SUCCESS) |
394 | { | |
b9bcd02c VK |
395 | pEnvelope->retryCount--; |
396 | DbgPrintf(6, _T("SMTP(%p): Failed to send e-mail, remaining retries: %d"), pEnvelope, pEnvelope->retryCount); | |
397 | if (pEnvelope->retryCount > 0) | |
5039dede AK |
398 | { |
399 | // Try posting again | |
19dbc8ef | 400 | m_pMailerQueue->put(pEnvelope); |
5039dede AK |
401 | } |
402 | else | |
403 | { | |
e86f3432 | 404 | PostEvent(EVENT_SMTP_FAILURE, g_dwMgmtNode, "dsmm", dwResult, |
b9bcd02c VK |
405 | m_szErrorText[dwResult], pEnvelope->rcptAddr, pEnvelope->subject); |
406 | free(pEnvelope->text); | |
5039dede AK |
407 | free(pEnvelope); |
408 | } | |
409 | } | |
410 | else | |
411 | { | |
0d6a7062 | 412 | DbgPrintf(6, _T("SMTP(%p): mail sent successfully"), pEnvelope); |
b9bcd02c | 413 | free(pEnvelope->text); |
5039dede AK |
414 | free(pEnvelope); |
415 | } | |
416 | } | |
417 | return THREAD_OK; | |
418 | } | |
419 | ||
110926c7 VK |
420 | /** |
421 | * Initialize mailer subsystem | |
422 | */ | |
6ff21d27 | 423 | void InitMailer() |
5039dede AK |
424 | { |
425 | m_pMailerQueue = new Queue; | |
426 | m_hThread = ThreadCreateEx(MailerThread, 0, NULL); | |
427 | } | |
428 | ||
110926c7 VK |
429 | /** |
430 | * Shutdown mailer | |
431 | */ | |
6ff21d27 | 432 | void ShutdownMailer() |
5039dede | 433 | { |
19dbc8ef VK |
434 | m_pMailerQueue->clear(); |
435 | m_pMailerQueue->put(INVALID_POINTER_VALUE); | |
5039dede AK |
436 | if (m_hThread != INVALID_THREAD_HANDLE) |
437 | ThreadJoin(m_hThread); | |
438 | delete m_pMailerQueue; | |
439 | } | |
440 | ||
110926c7 VK |
441 | /** |
442 | * Post e-mail to queue | |
443 | */ | |
35f836fe | 444 | void NXCORE_EXPORTABLE PostMail(const TCHAR *pszRcpt, const TCHAR *pszSubject, const TCHAR *pszText) |
5039dede | 445 | { |
b9bcd02c VK |
446 | MAIL_ENVELOPE *envelope = (MAIL_ENVELOPE *)malloc(sizeof(MAIL_ENVELOPE)); |
447 | ConfigReadStrA(_T("MailEncoding"), envelope->encoding, 64, "utf8"); | |
448 | bool isUtf8 = (!stricmp(envelope->encoding, "utf-8") || !stricmp(envelope->encoding, "utf8")); | |
5039dede | 449 | |
35f836fe | 450 | #ifdef UNICODE |
b9bcd02c VK |
451 | WideCharToMultiByte(isUtf8 ? CP_UTF8 : CP_ACP, isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN, NULL, NULL); |
452 | envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0; | |
453 | WideCharToMultiByte(isUtf8 ? CP_UTF8 : CP_ACP, isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN, NULL, NULL); | |
454 | envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0; | |
455 | envelope->text = isUtf8 ? UTF8StringFromWideString(pszText) : MBStringFromWideString(pszText); | |
35f836fe | 456 | #else |
b9bcd02c VK |
457 | if (isUtf8) |
458 | { | |
459 | utf8_to_mb(pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN); | |
460 | envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0; | |
461 | utf8_to_mb(pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN); | |
462 | envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0; | |
463 | envelope->text = UTF8StringFromMBString(pszText); | |
464 | } | |
465 | else | |
466 | { | |
467 | nx_strncpy(envelope->rcptAddr, pszRcpt, MAX_RCPT_ADDR_LEN); | |
468 | nx_strncpy(envelope->subject, pszSubject, MAX_EMAIL_SUBJECT_LEN); | |
469 | envelope->text = strdup(text); | |
470 | } | |
35f836fe | 471 | #endif |
b9bcd02c VK |
472 | envelope->retryCount = ConfigReadInt(_T("SMTPRetryCount"), 1); |
473 | m_pMailerQueue->put(envelope); | |
5039dede | 474 | } |