2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
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.
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.
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.
28 #define SMTP_BUFFER_SIZE 1024
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
41 #define STATE_INITIAL 0
46 #define STATE_MAIL_BODY 5
48 #define STATE_FINISHED 7
52 * Mail envelope structure
56 char rcptAddr
[MAX_RCPT_ADDR_LEN
];
57 char subject
[MAX_EMAIL_SUBJECT_LEN
];
66 static TCHAR m_szSmtpServer
[MAX_PATH
] = _T("localhost");
67 static WORD m_wSmtpPort
= 25;
68 static char m_szFromAddr
[MAX_PATH
] = "netxms@localhost";
69 static char m_szFromName
[MAX_PATH
] = "NetXMS Server";
70 static Queue
*m_pMailerQueue
= NULL
;
71 static THREAD m_hThread
= INVALID_THREAD_HANDLE
;
74 * Find end-of-line character
76 static char *FindEOL(char *pszBuffer
, int nLen
)
80 for(i
= 0; i
< nLen
; i
++)
81 if (pszBuffer
[i
] == '\n')
87 * Read line from socket
89 static BOOL
ReadLineFromSocket(SOCKET hSocket
, char *pszBuffer
, int *pnBufPos
, char *pszLine
)
96 ptr
= FindEOL(pszBuffer
, *pnBufPos
);
99 nRet
= RecvEx(hSocket
, &pszBuffer
[*pnBufPos
], SMTP_BUFFER_SIZE
- *pnBufPos
, 0, 30000);
104 } while(ptr
== NULL
);
106 strcpy(pszLine
, pszBuffer
);
107 *pnBufPos
-= (int)(ptr
- pszBuffer
+ 1);
108 memmove(pszBuffer
, ptr
+ 1, *pnBufPos
);
113 * Read SMTP response code from socket
115 static int GetSMTPResponse(SOCKET hSocket
, char *pszBuffer
, int *pnBufPos
)
117 char szLine
[SMTP_BUFFER_SIZE
];
121 if (!ReadLineFromSocket(hSocket
, pszBuffer
, pnBufPos
, szLine
))
123 if (strlen(szLine
) < 4)
125 if (szLine
[3] == ' ')
137 static UINT32
SendMail(const char *pszRcpt
, const char *pszSubject
, const char *pszText
, const char *encoding
)
140 char szBuffer
[SMTP_BUFFER_SIZE
];
141 int iResp
, iState
= STATE_INITIAL
, nBufPos
= 0;
145 InetAddress addr
= InetAddress::resolveHostName(m_szSmtpServer
);
146 if (!addr
.isValidUnicast())
147 return SMTP_ERR_BAD_SERVER_NAME
;
150 hSocket
= socket(addr
.getFamily(), SOCK_STREAM
, 0);
151 if (hSocket
== INVALID_SOCKET
)
152 return SMTP_ERR_COMM_FAILURE
;
156 addr
.fillSockAddr(&sa
, m_wSmtpPort
);
157 if (ConnectEx(hSocket
, (struct sockaddr
*)&sa
, SA_LEN((struct sockaddr
*)&sa
), 3000) == 0)
159 while((iState
!= STATE_FINISHED
) && (iState
!= STATE_ERROR
))
161 iResp
= GetSMTPResponse(hSocket
, szBuffer
, &nBufPos
);
162 DbgPrintf(8, _T("SMTP RESPONSE: %03d (state=%d)"), iResp
, iState
);
168 // Server should send 220 text after connect
171 iState
= STATE_HELLO
;
172 SendEx(hSocket
, "HELO netxms\r\n", 13, 0, NULL
);
176 iState
= STATE_ERROR
;
180 // Server should respond with 250 text to our HELO command
184 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "MAIL FROM: <%s>\r\n", m_szFromAddr
);
185 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
189 iState
= STATE_ERROR
;
193 // Server should respond with 250 text to our MAIL FROM command
197 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "RCPT TO: <%s>\r\n", pszRcpt
);
198 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
202 iState
= STATE_ERROR
;
206 // Server should respond with 250 text to our RCPT TO command
210 SendEx(hSocket
, "DATA\r\n", 6, 0, NULL
);
214 iState
= STATE_ERROR
;
218 // Server should respond with 354 text to our DATA command
221 iState
= STATE_MAIL_BODY
;
225 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "From: %s <%s>\r\n", m_szFromName
, m_szFromAddr
);
226 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
228 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "To: <%s>\r\n", pszRcpt
);
229 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
231 bool encodeSubject
= false;
232 for(const char *p
= pszSubject
; *p
!= 0; p
++)
235 encodeSubject
= true;
240 char *encodedSubject
= NULL
;
241 base64_encode_alloc(pszSubject
, strlen(pszSubject
), &encodedSubject
);
242 if (encodedSubject
!= NULL
)
244 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "Subject: =?%s?B?%s?=\r\n", encoding
, encodedSubject
);
245 free(encodedSubject
);
250 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "Subject: %s\r\n", pszSubject
);
255 snprintf(szBuffer
, SMTP_BUFFER_SIZE
, "Subject: %s\r\n", pszSubject
);
257 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
261 struct tm
*pCurrentTM
;
263 #ifdef HAVE_LOCALTIME_R
265 localtime_r(¤tTime
, ¤tTM
);
266 pCurrentTM
= ¤tTM
;
268 pCurrentTM
= localtime(¤tTime
);
271 strftime(szBuffer
, sizeof(szBuffer
), "Date: %a, %d %b %Y %H:%M:%S ", pCurrentTM
);
273 TIME_ZONE_INFORMATION tzi
;
274 UINT32 tzType
= GetTimeZoneInformation(&tzi
);
278 case TIME_ZONE_ID_STANDARD
:
279 effectiveBias
= tzi
.Bias
+ tzi
.StandardBias
;
281 case TIME_ZONE_ID_DAYLIGHT
:
282 effectiveBias
= tzi
.Bias
+ tzi
.DaylightBias
;
284 case TIME_ZONE_ID_UNKNOWN
:
285 effectiveBias
= tzi
.Bias
;
289 DbgPrintf(4, _T("GetTimeZoneInformation() call failed"));
292 int offset
= abs(effectiveBias
);
293 sprintf(&szBuffer
[strlen(szBuffer
)], "%c%02d%02d\r\n", effectiveBias
<= 0 ? '+' : '-', offset
/ 60, offset
% 60);
295 strftime(szBuffer
, sizeof(szBuffer
), "Date: %a, %d %b %Y %H:%M:%S %z\r\n", pCurrentTM
);
298 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
300 snprintf(szBuffer
, SMTP_BUFFER_SIZE
,
301 "Content-Type: text/plain; charset=%s\r\n"
302 "Content-Transfer-Encoding: 8bit\r\n\r\n", encoding
);
303 SendEx(hSocket
, szBuffer
, strlen(szBuffer
), 0, NULL
);
306 SendEx(hSocket
, pszText
, strlen(pszText
), 0, NULL
);
307 SendEx(hSocket
, "\r\n.\r\n", 5, 0, NULL
);
311 iState
= STATE_ERROR
;
314 case STATE_MAIL_BODY
:
315 // Server should respond with 250 to our mail body
319 SendEx(hSocket
, "QUIT\r\n", 6, 0, NULL
);
323 iState
= STATE_ERROR
;
327 // Server should respond with 221 text to our QUIT command
330 iState
= STATE_FINISHED
;
334 iState
= STATE_ERROR
;
338 iState
= STATE_ERROR
;
344 iState
= STATE_ERROR
;
347 // Shutdown communication channel
348 shutdown(hSocket
, SHUT_RDWR
);
350 dwRetCode
= (iState
== STATE_FINISHED
) ? SMTP_ERR_SUCCESS
: SMTP_ERR_PROTOCOL_FAILURE
;
354 dwRetCode
= SMTP_ERR_COMM_FAILURE
;
359 closesocket(hSocket
);
368 static THREAD_RESULT THREAD_CALL
MailerThread(void *pArg
)
370 static const TCHAR
*m_szErrorText
[] =
372 _T("Sent successfully"),
373 _T("Unable to resolve SMTP server name"),
374 _T("Communication failure"),
375 _T("SMTP conversation failure")
378 DbgPrintf(1, _T("SMTP mailer thread started"));
381 MAIL_ENVELOPE
*pEnvelope
= (MAIL_ENVELOPE
*)m_pMailerQueue
->getOrBlock();
382 if (pEnvelope
== INVALID_POINTER_VALUE
)
385 DbgPrintf(6, _T("SMTP(%p): new envelope, rcpt=%hs"), pEnvelope
, pEnvelope
->rcptAddr
);
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);
392 UINT32 dwResult
= SendMail(pEnvelope
->rcptAddr
, pEnvelope
->subject
, pEnvelope
->text
, pEnvelope
->encoding
);
393 if (dwResult
!= SMTP_ERR_SUCCESS
)
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)
400 m_pMailerQueue
->put(pEnvelope
);
404 PostEvent(EVENT_SMTP_FAILURE
, g_dwMgmtNode
, "dsmm", dwResult
,
405 m_szErrorText
[dwResult
], pEnvelope
->rcptAddr
, pEnvelope
->subject
);
406 free(pEnvelope
->text
);
412 DbgPrintf(6, _T("SMTP(%p): mail sent successfully"), pEnvelope
);
413 free(pEnvelope
->text
);
421 * Initialize mailer subsystem
425 m_pMailerQueue
= new Queue
;
426 m_hThread
= ThreadCreateEx(MailerThread
, 0, NULL
);
432 void ShutdownMailer()
434 m_pMailerQueue
->clear();
435 m_pMailerQueue
->put(INVALID_POINTER_VALUE
);
436 if (m_hThread
!= INVALID_THREAD_HANDLE
)
437 ThreadJoin(m_hThread
);
438 delete m_pMailerQueue
;
442 * Post e-mail to queue
444 void NXCORE_EXPORTABLE
PostMail(const TCHAR
*pszRcpt
, const TCHAR
*pszSubject
, const TCHAR
*pszText
)
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"));
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
);
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
);
467 nx_strncpy(envelope
->rcptAddr
, pszRcpt
, MAX_RCPT_ADDR_LEN
);
468 nx_strncpy(envelope
->subject
, pszSubject
, MAX_EMAIL_SUBJECT_LEN
);
469 envelope
->text
= strdup(pszText
);
472 envelope
->retryCount
= ConfigReadInt(_T("SMTPRetryCount"), 1);
473 m_pMailerQueue
->put(envelope
);