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