smaller timeout on SMTP connect
[public/netxms.git] / src / server / core / email.cpp
CommitLineData
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 54struct 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 66static TCHAR m_szSmtpServer[MAX_PATH] = _T("localhost");
5039dede
AK
67static WORD m_wSmtpPort = 25;
68static char m_szFromAddr[MAX_PATH] = "netxms@localhost";
c9b85b77 69static char m_szFromName[MAX_PATH] = "NetXMS Server";
5039dede
AK
70static Queue *m_pMailerQueue = NULL;
71static THREAD m_hThread = INVALID_THREAD_HANDLE;
72
244c65ef
VK
73/**
74 * Find end-of-line character
75 */
5039dede
AK
76static 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
89static 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
115static 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 137static 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);
b9f4d2f4 157 if (ConnectEx(hSocket, (struct sockaddr *)&sa, SA_LEN((struct sockaddr *)&sa), 3000) == 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(&currentTime);
263#ifdef HAVE_LOCALTIME_R
264 struct tm currentTM;
265 localtime_r(&currentTime, &currentTM);
266 pCurrentTM = &currentTM;
267#else
268 pCurrentTM = localtime(&currentTime);
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
368static 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 423void 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 432void 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 444void 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);
f41406b4 469 envelope->text = strdup(pszText);
b9bcd02c 470 }
35f836fe 471#endif
b9bcd02c
VK
472 envelope->retryCount = ConfigReadInt(_T("SMTPRetryCount"), 1);
473 m_pMailerQueue->put(envelope);
5039dede 474}