Fixed bugs in server's SMTP sender
[public/netxms.git] / src / server / core / email.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003 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 ** $module: email.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25
26 //
27 // Receive buffer size
28 //
29
30 #define SMTP_BUFFER_SIZE 1024
31
32
33 //
34 // Sender errors
35 //
36
37 #define SMTP_ERR_SUCCESS 0
38 #define SMTP_ERR_BAD_SERVER_NAME 1
39 #define SMTP_ERR_COMM_FAILURE 2
40 #define SMTP_ERR_PROTOCOL_FAILURE 3
41
42
43 //
44 // Mail sender states
45 //
46
47 #define STATE_INITIAL 0
48 #define STATE_HELLO 1
49 #define STATE_FROM 2
50 #define STATE_RCPT 3
51 #define STATE_DATA 4
52 #define STATE_MAIL_BODY 5
53 #define STATE_QUIT 6
54 #define STATE_FINISHED 7
55 #define STATE_ERROR 8
56
57
58 //
59 // Mail envelope structure
60 //
61
62 typedef struct
63 {
64 char szRcptAddr[MAX_RCPT_ADDR_LEN];
65 char szSubject[MAX_EMAIL_SUBJECT_LEN];
66 char *pszText;
67 } MAIL_ENVELOPE;
68
69
70 //
71 // Static data
72 //
73
74 static char m_szSmtpServer[MAX_PATH] = "localhost";
75 static WORD m_wSmtpPort = 25;
76 static char m_szFromAddr[MAX_PATH] = "netxms@localhost";
77 static Queue *m_pMailerQueue = NULL;
78 static THREAD m_hThread = INVALID_THREAD_HANDLE;
79
80
81 //
82 // Find end-of-line character
83 //
84
85 static char *FindEOL(char *pszBuffer, int nLen)
86 {
87 int i;
88
89 for(i = 0; i < nLen; i++)
90 if (pszBuffer[i] == '\n')
91 return &pszBuffer[i];
92 return NULL;
93 }
94
95
96 //
97 // Read line from socket
98 //
99
100 static BOOL ReadLineFromSocket(SOCKET hSocket, char *pszBuffer, int *pnBufPos, char *pszLine)
101 {
102 char *ptr;
103 int nRet;
104
105 do
106 {
107 ptr = FindEOL(pszBuffer, *pnBufPos);
108 if (ptr == NULL)
109 {
110 nRet = RecvEx(hSocket, &pszBuffer[*pnBufPos], SMTP_BUFFER_SIZE - *pnBufPos, 0, 30000);
111 if (nRet <= 0)
112 return FALSE;
113 *pnBufPos += nRet;
114 }
115 } while(ptr == NULL);
116 *ptr = 0;
117 strcpy(pszLine, pszBuffer);
118 *pnBufPos -= (ptr - pszBuffer + 1);
119 memmove(pszBuffer, ptr + 1, *pnBufPos);
120 return TRUE;
121 }
122
123
124 //
125 // Read SMTP response code from socket
126 //
127
128 static int GetSMTPResponse(SOCKET hSocket, char *pszBuffer, int *pnBufPos)
129 {
130 char szLine[SMTP_BUFFER_SIZE];
131
132 while(1)
133 {
134 if (!ReadLineFromSocket(hSocket, pszBuffer, pnBufPos, szLine))
135 return -1;
136 if (strlen(szLine) < 4)
137 return -1;
138 if (szLine[3] == ' ')
139 {
140 szLine[3] = 0;
141 break;
142 }
143 }
144 return atoi(szLine);
145 }
146
147
148 //
149 // Send e-mail
150 //
151
152 static DWORD SendMail(char *pszRcpt, char *pszSubject, char *pszText)
153 {
154 SOCKET hSocket;
155 struct hostent *hs;
156 struct sockaddr_in sa;
157 char szBuffer[SMTP_BUFFER_SIZE];
158 int iResp, iState = STATE_INITIAL, nBufPos = 0;
159 DWORD dwRetCode;
160
161 // Fill in address structure
162 memset(&sa, 0, sizeof(sa));
163 sa.sin_family = AF_INET;
164 sa.sin_port = htons(m_wSmtpPort);
165
166 // Resolve hostname
167 hs = gethostbyname(m_szSmtpServer);
168 if (hs != NULL)
169 {
170 memcpy(&sa.sin_addr.s_addr, hs->h_addr, sizeof(DWORD));
171 }
172 else
173 {
174 sa.sin_addr.s_addr = inet_addr(m_szSmtpServer);
175 }
176
177 if ((sa.sin_addr.s_addr == INADDR_ANY) || (sa.sin_addr.s_addr == INADDR_NONE))
178 return SMTP_ERR_BAD_SERVER_NAME;
179
180 // Create socket
181 hSocket = socket(AF_INET, SOCK_STREAM, 0);
182 if (hSocket == -1)
183 return SMTP_ERR_COMM_FAILURE;
184
185 // Connect to server
186 if (connect(hSocket, (struct sockaddr *)&sa, sizeof(sa)) == 0)
187 {
188 while((iState != STATE_FINISHED) && (iState != STATE_ERROR))
189 {
190 iResp = GetSMTPResponse(hSocket, szBuffer, &nBufPos);
191 if (iResp > 0)
192 {
193 switch(iState)
194 {
195 case STATE_INITIAL:
196 // Server should send 220 text after connect
197 if (iResp == 220)
198 {
199 iState = STATE_HELLO;
200 SendEx(hSocket, "HELO netxms\r\n", 13, 0);
201 }
202 else
203 {
204 iState = STATE_ERROR;
205 }
206 break;
207 case STATE_HELLO:
208 // Server should respond with 250 text to our HELO command
209 if (iResp == 250)
210 {
211 iState = STATE_FROM;
212 sprintf(szBuffer, "MAIL FROM: <%s>\r\n", m_szFromAddr);
213 SendEx(hSocket, szBuffer, strlen(szBuffer), 0);
214 }
215 else
216 {
217 iState = STATE_ERROR;
218 }
219 break;
220 case STATE_FROM:
221 // Server should respond with 250 text to our MAIL FROM command
222 if (iResp == 250)
223 {
224 iState = STATE_RCPT;
225 sprintf(szBuffer, "RCPT TO: <%s>\r\n", pszRcpt);
226 SendEx(hSocket, szBuffer, strlen(szBuffer), 0);
227 }
228 else
229 {
230 iState = STATE_ERROR;
231 }
232 break;
233 case STATE_RCPT:
234 // Server should respond with 250 text to our RCPT TO command
235 if (iResp == 250)
236 {
237 iState = STATE_DATA;
238 SendEx(hSocket, "DATA\r\n", 6, 0);
239 }
240 else
241 {
242 iState = STATE_ERROR;
243 }
244 break;
245 case STATE_DATA:
246 // Server should respond with 354 text to our DATA command
247 if (iResp == 354)
248 {
249 iState = STATE_MAIL_BODY;
250
251 // Mail header
252 sprintf(szBuffer, "From: NetXMS Server <%s>\r\n", m_szFromAddr);
253 SendEx(hSocket, szBuffer, strlen(szBuffer), 0);
254 sprintf(szBuffer, "To: <%s>\r\n", pszRcpt);
255 SendEx(hSocket, szBuffer, strlen(szBuffer), 0);
256 sprintf(szBuffer, "Subject: %s\r\n\r\n", pszSubject);
257 SendEx(hSocket, szBuffer, strlen(szBuffer), 0);
258
259 // Mail body
260 SendEx(hSocket, pszText, strlen(pszText), 0);
261 SendEx(hSocket, "\r\n.\r\n", 5, 0);
262 }
263 else
264 {
265 iState = STATE_ERROR;
266 }
267 break;
268 case STATE_MAIL_BODY:
269 // Server should respond with 250 to our mail body
270 if (iResp == 250)
271 {
272 iState = STATE_QUIT;
273 SendEx(hSocket, "QUIT\r\n", 6, 0);
274 }
275 else
276 {
277 iState = STATE_ERROR;
278 }
279 break;
280 case STATE_QUIT:
281 // Server should respond with 221 text to our QUIT command
282 if (iResp == 221)
283 {
284 iState = STATE_FINISHED;
285 }
286 else
287 {
288 iState = STATE_ERROR;
289 }
290 break;
291 default:
292 iState = STATE_ERROR;
293 break;
294 }
295 }
296 else
297 {
298 iState = STATE_ERROR;
299 }
300 }
301
302 // Shutdown communication channel
303 shutdown(hSocket, SHUT_RDWR);
304 closesocket(hSocket);
305
306 dwRetCode = (iState == STATE_FINISHED) ? SMTP_ERR_SUCCESS : SMTP_ERR_PROTOCOL_FAILURE;
307 }
308 else
309 {
310 dwRetCode = SMTP_ERR_COMM_FAILURE;
311 }
312
313 return dwRetCode;
314 }
315
316
317 //
318 // Mailer thread
319 //
320
321 static THREAD_RESULT THREAD_CALL MailerThread(void *pArg)
322 {
323 MAIL_ENVELOPE *pEnvelope;
324 DWORD dwResult;
325 static char *m_szErrorText[] =
326 {
327 "Sended successfully",
328 "Unable to resolve SMTP server name",
329 "Communication failure",
330 "SMTP conversation failure"
331 };
332
333 while(1)
334 {
335 pEnvelope = (MAIL_ENVELOPE *)m_pMailerQueue->GetOrBlock();
336 if (pEnvelope == INVALID_POINTER_VALUE)
337 break;
338
339 ConfigReadStr("SMTPServer", m_szSmtpServer, MAX_PATH, "localhost");
340 ConfigReadStr("SMTPFromAddr", m_szFromAddr, MAX_PATH, "netxms@localhost");
341 m_wSmtpPort = (WORD)ConfigReadInt("SMTPPort", 25);
342
343 dwResult = SendMail(pEnvelope->szRcptAddr, pEnvelope->szSubject, pEnvelope->pszText);
344 if (dwResult != SMTP_ERR_SUCCESS)
345 PostEvent(EVENT_SMTP_FAILURE, g_dwMgmtNode, "dsss", dwResult,
346 m_szErrorText[dwResult], pEnvelope->szRcptAddr, pEnvelope->szSubject);
347
348 free(pEnvelope->pszText);
349 free(pEnvelope);
350 }
351 return THREAD_OK;
352 }
353
354
355 //
356 // Initialize mailer subsystem
357 //
358
359 void InitMailer(void)
360 {
361 m_pMailerQueue = new Queue;
362 m_hThread = ThreadCreateEx(MailerThread, 0, NULL);
363 }
364
365
366 //
367 // Shutdown mailer
368 //
369
370 void ShutdownMailer(void)
371 {
372 m_pMailerQueue->Clear();
373 m_pMailerQueue->Put(INVALID_POINTER_VALUE);
374 if (m_hThread != INVALID_THREAD_HANDLE)
375 ThreadJoin(m_hThread);
376 delete m_pMailerQueue;
377 }
378
379
380 //
381 // Post e-mail to queue
382 //
383
384 void NXCORE_EXPORTABLE PostMail(char *pszRcpt, char *pszSubject, char *pszText)
385 {
386 MAIL_ENVELOPE *pEnvelope;
387
388 pEnvelope = (MAIL_ENVELOPE *)malloc(sizeof(MAIL_ENVELOPE));
389 nx_strncpy(pEnvelope->szRcptAddr, pszRcpt, MAX_RCPT_ADDR_LEN);
390 nx_strncpy(pEnvelope->szSubject, pszSubject, MAX_EMAIL_SUBJECT_LEN);
391 pEnvelope->pszText = strdup(pszText);
392 m_pMailerQueue->Put(pEnvelope);
393 }