1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "log.h"
22 #include "comms.h"
23 #include "base64.h"
24 #include "zbxalgo.h"
25
26 #include "zbxmedia.h"
27
28 /* number of characters per line when wrapping Base64 data in Email */
29 #define ZBX_EMAIL_B64_MAXLINE 76
30
31 /* number of characters per "encoded-word" in RFC-2047 message header */
32 #define ZBX_EMAIL_B64_MAXWORD_RFC2047 75
33
34 /* multiple 'encoded-word's should be separated by <CR><LF><SPACE> */
35 #define ZBX_EMAIL_ENCODED_WORD_SEPARATOR "\r\n "
36
37 /******************************************************************************
38 * *
39 * Function: str_base64_encode_rfc2047 *
40 * *
41 * Purpose: Encode a string into a base64 string as required by rfc2047. *
42 * Used for encoding e-mail headers. *
43 * *
44 * Parameters: src - [IN] a null-terminated UTF-8 string to encode *
45 * p_base64 - [OUT] a pointer to the encoded string *
46 * *
47 * Comments: Based on the patch submitted by *
48 * Jairo Eduardo Lopez Fuentes Nacarino *
49 * *
50 ******************************************************************************/
str_base64_encode_rfc2047(const char * src,char ** p_base64)51 static void str_base64_encode_rfc2047(const char *src, char **p_base64)
52 {
53 const char *p0; /* pointer in src to start encoding from */
54 const char *p1; /* pointer in src: 1st byte of UTF-8 character */
55 size_t c_len; /* length of UTF-8 character sequence */
56 size_t p_base64_alloc; /* allocated memory size for subject */
57 size_t p_base64_offset = 0; /* offset for writing into subject */
58
59 assert(src);
60 assert(NULL == *p_base64); /* do not accept already allocated memory */
61
62 p_base64_alloc = ZBX_EMAIL_B64_MAXWORD_RFC2047 + sizeof(ZBX_EMAIL_ENCODED_WORD_SEPARATOR);
63 *p_base64 = (char *)zbx_malloc(NULL, p_base64_alloc);
64 **p_base64 = '\0';
65
66 for (p0 = src; '\0' != *p0; p0 = p1)
67 {
68 /* Max length of line is 76 characters (without line separator). */
69 /* Max length of "encoded-word" is 75 characters (without word separator). */
70 /* 3 characters are taken by word separator "<CR><LF><Space>" which also includes the line separator. */
71 /* 12 characters are taken by header "=?UTF-8?B?" and trailer "?=". */
72 /* So, one "encoded-word" can hold up to 63 characters of Base64-encoded string. */
73 /* Encoding 45 bytes produces a 61 byte long Base64-encoded string which meets the limit. */
74 /* Encoding 46 bytes produces a 65 byte long Base64-encoded string which exceeds the limit. */
75 for (p1 = p0, c_len = 0; '\0' != *p1; p1 += c_len)
76 {
77 /* an invalid UTF-8 character or length of a string more than 45 bytes */
78 if (0 == (c_len = zbx_utf8_char_len(p1)) || 45 < p1 - p0 + c_len)
79 break;
80 }
81
82 if (0 < p1 - p0)
83 {
84 /* 12 characters are taken by header "=?UTF-8?B?" and trailer "?=" plus '\0' */
85 char b64_buf[ZBX_EMAIL_B64_MAXWORD_RFC2047 - 12 + 1];
86
87 str_base64_encode(p0, b64_buf, p1 - p0);
88
89 if (0 != p_base64_offset) /* not the first "encoded-word" ? */
90 {
91 zbx_strcpy_alloc(p_base64, &p_base64_alloc, &p_base64_offset,
92 ZBX_EMAIL_ENCODED_WORD_SEPARATOR);
93 }
94
95 zbx_snprintf_alloc(p_base64, &p_base64_alloc, &p_base64_offset, "=?UTF-8?B?%s?=", b64_buf);
96 }
97 else
98 break;
99 }
100 }
101
102 /******************************************************************************
103 * *
104 * Comments: reads until '\n' *
105 * *
106 ******************************************************************************/
smtp_readln(zbx_socket_t * s,const char ** buf)107 static int smtp_readln(zbx_socket_t *s, const char **buf)
108 {
109 while (NULL != (*buf = zbx_tcp_recv_line(s)) &&
110 4 <= strlen(*buf) &&
111 0 != isdigit((*buf)[0]) &&
112 0 != isdigit((*buf)[1]) &&
113 0 != isdigit((*buf)[2]) &&
114 '-' == (*buf)[3])
115 ;
116
117 return NULL == *buf ? FAIL : SUCCEED;
118 }
119
120 /********************************************************************************
121 * *
122 * Function: smtp_parse_mailbox *
123 * *
124 * Purpose: 1. Extract a display name and an angle address from mailbox string *
125 * for using in "MAIL FROM:", "RCPT TO:", "From:" and "To:" fields. *
126 * 2. If the display name contains multibyte UTF-8 characters encode *
127 * it into a base64 string as required by rfc2047. The encoding is *
128 * also applied if the display name looks like a base64-encoded *
129 * word. *
130 * *
131 * Parameters: mailbox - [IN] a null-terminated UTF-8 string *
132 * error - [IN] pointer to string for reporting errors *
133 * max_error_len - [IN] size of 'error' string *
134 * mailaddrs - [OUT] array of mail addresses *
135 * *
136 * Comments: The function is very much simplified in comparison with full *
137 * RFC 5322-compliant parser. It does not recognize: *
138 * - comments, *
139 * - quoted strings and quoted pairs, *
140 * - folding whitespace. *
141 * For example, '<' and '@' are not supported in the display name *
142 * and the local part of email address. *
143 * *
144 ********************************************************************************/
smtp_parse_mailbox(const char * mailbox,char * error,size_t max_error_len,zbx_vector_ptr_t * mailaddrs)145 static int smtp_parse_mailbox(const char *mailbox, char *error, size_t max_error_len, zbx_vector_ptr_t *mailaddrs)
146 {
147 const char *p, *pstart, *angle_addr_start, *domain_start, *utf8_end;
148 const char *base64_like_start, *base64_like_end, *token;
149 char *base64_buf, *tmp_mailbox;
150 size_t size_angle_addr = 0, offset_angle_addr = 0, len, i;
151 int ret = FAIL;
152 zbx_mailaddr_t *mailaddr = NULL;
153
154 tmp_mailbox = zbx_strdup(NULL, mailbox);
155
156 token = strtok(tmp_mailbox, "\n");
157 while (token != NULL)
158 {
159 angle_addr_start = NULL;
160 domain_start = NULL;
161 utf8_end = NULL;
162 base64_like_start = NULL;
163 base64_like_end = NULL;
164 base64_buf = NULL;
165
166 p = token;
167
168 while (' ' == *p || '\t' == *p)
169 p++;
170
171 pstart = p;
172
173 while ('\0' != *p)
174 {
175 len = zbx_utf8_char_len(p);
176
177 if (1 == len) /* ASCII character */
178 {
179 switch (*p)
180 {
181 case '<':
182 angle_addr_start = p;
183 break;
184 case '@':
185 domain_start = p;
186 break;
187 /* if mailbox contains a sequence '=?'.*'?=' which looks like a Base64-encoded word */
188 case '=':
189 if ('?' == *(p + 1))
190 base64_like_start = p++;
191 break;
192 case '?':
193 if (NULL != base64_like_start && '=' == *(p + 1))
194 base64_like_end = p++;
195 }
196 p++;
197 }
198 else if (1 < len) /* multibyte UTF-8 character */
199 {
200 for (i = 1; i < len; i++)
201 {
202 if ('\0' == *(p + i))
203 {
204 zbx_snprintf(error, max_error_len, "invalid UTF-8 character in email"
205 " address: %s", token);
206 goto out;
207 }
208 }
209 utf8_end = p + len - 1;
210 p += len;
211 }
212 else if (0 == len) /* invalid UTF-8 character */
213 {
214 zbx_snprintf(error, max_error_len, "invalid UTF-8 character in email address: %s",
215 token);
216 goto out;
217 }
218 }
219
220 if (NULL == domain_start)
221 {
222 zbx_snprintf(error, max_error_len, "no '@' in email address: %s", token);
223 goto out;
224 }
225
226 if (utf8_end > angle_addr_start)
227 {
228 zbx_snprintf(error, max_error_len, "email address local or domain part contains UTF-8 character:"
229 " %s", token);
230 goto out;
231 }
232
233 mailaddr = (zbx_mailaddr_t *)zbx_malloc(NULL, sizeof(zbx_mailaddr_t));
234 memset(mailaddr, 0, sizeof(zbx_mailaddr_t));
235
236 if (NULL != angle_addr_start)
237 {
238 zbx_snprintf_alloc(&mailaddr->addr, &size_angle_addr, &offset_angle_addr, "%s",
239 angle_addr_start);
240
241 if (pstart < angle_addr_start) /* display name */
242 {
243 mailaddr->disp_name = (char *)zbx_malloc(mailaddr->disp_name,
244 (size_t)(angle_addr_start - pstart + 1));
245 memcpy(mailaddr->disp_name, pstart, (size_t)(angle_addr_start - pstart));
246 *(mailaddr->disp_name + (angle_addr_start - pstart)) = '\0';
247
248 /* UTF-8 or Base64-looking display name */
249 if (NULL != utf8_end || (NULL != base64_like_end &&
250 angle_addr_start - 1 > base64_like_end))
251 {
252 str_base64_encode_rfc2047(mailaddr->disp_name, &base64_buf);
253 zbx_free(mailaddr->disp_name);
254 mailaddr->disp_name = base64_buf;
255 }
256 }
257 }
258 else
259 {
260 zbx_snprintf_alloc(&mailaddr->addr, &size_angle_addr, &offset_angle_addr, "<%s>", pstart);
261 }
262
263 zbx_vector_ptr_append(mailaddrs, mailaddr);
264
265 token = strtok(NULL, "\n");
266 }
267
268 ret = SUCCEED;
269 out:
270 zbx_free(tmp_mailbox);
271
272 return ret;
273 }
274
smtp_prepare_payload(zbx_vector_ptr_t * from_mails,zbx_vector_ptr_t * to_mails,const char * mailsubject,const char * mailbody)275 static char *smtp_prepare_payload(zbx_vector_ptr_t *from_mails, zbx_vector_ptr_t *to_mails,
276 const char *mailsubject, const char *mailbody)
277 {
278 char *tmp = NULL, *base64 = NULL, *base64_lf;
279 char *localsubject = NULL, *localbody = NULL, *from = NULL, *to = NULL;
280 char str_time[MAX_STRING_LEN];
281 struct tm *local_time;
282 time_t email_time;
283 int i;
284 size_t from_alloc = 0, from_offset = 0, to_alloc = 0, to_offset = 0;
285
286 /* prepare subject */
287
288 tmp = string_replace(mailsubject, "\r\n", " ");
289 localsubject = string_replace(tmp, "\n", " ");
290 zbx_free(tmp);
291
292 if (FAIL == is_ascii_string(localsubject))
293 {
294 /* split subject into multiple RFC 2047 "encoded-words" */
295 str_base64_encode_rfc2047(localsubject, &base64);
296 zbx_free(localsubject);
297
298 localsubject = base64;
299 base64 = NULL;
300 }
301
302 /* prepare body */
303
304 tmp = string_replace(mailbody, "\r\n", "\n");
305 localbody = string_replace(tmp, "\n", "\r\n");
306 zbx_free(tmp);
307
308 str_base64_encode_dyn(localbody, &base64, strlen(localbody));
309
310 /* wrap base64 encoded data with linefeeds */
311 base64_lf = str_linefeed(base64, ZBX_EMAIL_B64_MAXLINE, "\r\n");
312 zbx_free(base64);
313 base64 = base64_lf;
314
315 zbx_free(localbody);
316 localbody = base64;
317 base64 = NULL;
318
319 /* prepare date */
320
321 time(&email_time);
322 local_time = localtime(&email_time);
323 strftime(str_time, MAX_STRING_LEN, "%a, %d %b %Y %H:%M:%S %z", local_time);
324
325 for (i = 0; i < from_mails->values_num; i++)
326 {
327 zbx_snprintf_alloc(&from, &from_alloc, &from_offset, "%s%s",
328 ZBX_NULL2EMPTY_STR(((zbx_mailaddr_t *)from_mails->values[i])->disp_name),
329 ((zbx_mailaddr_t *)from_mails->values[i])->addr);
330
331 if (from_mails->values_num - 1 > i)
332 zbx_strcpy_alloc(&from, &from_alloc, &from_offset, ",");
333 }
334
335 for (i = 0; i < to_mails->values_num; i++)
336 {
337 zbx_snprintf_alloc(&to, &to_alloc, &to_offset, "%s%s",
338 ZBX_NULL2EMPTY_STR(((zbx_mailaddr_t *)to_mails->values[i])->disp_name),
339 ((zbx_mailaddr_t *)to_mails->values[i])->addr);
340
341 if (to_mails->values_num - 1 > i)
342 zbx_strcpy_alloc(&to, &to_alloc, &to_offset, ",");
343 }
344
345 /* e-mails are sent in 'SMTP/MIME e-mail' format because UTF-8 is used both in mailsubject and mailbody */
346 /* =?charset?encoding?encoded text?= format must be used for subject field */
347
348 tmp = zbx_dsprintf(tmp,
349 "From: %s\r\n"
350 "To: %s\r\n"
351 "Date: %s\r\n"
352 "Subject: %s\r\n"
353 "MIME-Version: 1.0\r\n"
354 "Content-Type: text/plain; charset=\"UTF-8\"\r\n"
355 "Content-Transfer-Encoding: base64\r\n"
356 "\r\n"
357 "%s",
358 from, to,
359 str_time, localsubject, localbody);
360
361 zbx_free(localsubject);
362 zbx_free(localbody);
363 zbx_free(from);
364 zbx_free(to);
365
366 return tmp;
367 }
368
369 #ifdef HAVE_SMTP_AUTHENTICATION
370 typedef struct
371 {
372 char *payload;
373 size_t payload_len;
374 size_t provided_len;
375 }
376 smtp_payload_status_t;
377
smtp_provide_payload(void * buffer,size_t size,size_t nmemb,void * instream)378 static size_t smtp_provide_payload(void *buffer, size_t size, size_t nmemb, void *instream)
379 {
380 size_t current_len;
381 smtp_payload_status_t *payload_status = (smtp_payload_status_t *)instream;
382
383 current_len = MIN(size * nmemb, payload_status->payload_len - payload_status->provided_len);
384
385 memcpy(buffer, payload_status->payload + payload_status->provided_len, current_len);
386
387 payload_status->provided_len += current_len;
388
389 return current_len;
390 }
391
smtp_debug_function(CURL * easyhandle,curl_infotype type,char * data,size_t size,void * userptr)392 static int smtp_debug_function(CURL *easyhandle, curl_infotype type, char *data, size_t size, void *userptr)
393 {
394 const char labels[3] = {'*', '<', '>'};
395
396 ZBX_UNUSED(easyhandle);
397 ZBX_UNUSED(userptr);
398
399 if (CURLINFO_TEXT != type && CURLINFO_HEADER_IN != type && CURLINFO_HEADER_OUT != type)
400 goto out;
401
402 while (0 < size && ('\r' == data[size - 1] || '\n' == data[size - 1]))
403 size--;
404
405 zabbix_log(LOG_LEVEL_TRACE, "%c %.*s", labels[type], (int)size, data);
406 out:
407 return 0;
408 }
409 #endif
410
send_email_plain(const char * smtp_server,unsigned short smtp_port,const char * smtp_helo,zbx_vector_ptr_t * from_mails,zbx_vector_ptr_t * to_mails,const char * mailsubject,const char * mailbody,int timeout,char * error,size_t max_error_len)411 static int send_email_plain(const char *smtp_server, unsigned short smtp_port, const char *smtp_helo,
412 zbx_vector_ptr_t *from_mails, zbx_vector_ptr_t *to_mails, const char *mailsubject,
413 const char *mailbody, int timeout, char *error, size_t max_error_len)
414 {
415 zbx_socket_t s;
416 int err, ret = FAIL, i;
417 char cmd[MAX_STRING_LEN], *cmdp = NULL;
418
419 const char *OK_220 = "220";
420 const char *OK_250 = "250";
421 const char *OK_251 = "251";
422 const char *OK_354 = "354";
423 const char *response;
424
425 zbx_alarm_on(timeout);
426
427 /* connect to and receive an initial greeting from SMTP server */
428
429 if (FAIL == zbx_tcp_connect(&s, CONFIG_SOURCE_IP, smtp_server, smtp_port, 0, ZBX_TCP_SEC_UNENCRYPTED, NULL,
430 NULL))
431 {
432 zbx_snprintf(error, max_error_len, "cannot connect to SMTP server \"%s\": %s",
433 smtp_server, zbx_socket_strerror());
434 goto out;
435 }
436
437 if (FAIL == smtp_readln(&s, &response))
438 {
439 zbx_snprintf(error, max_error_len, "error receiving initial string from SMTP server: %s",
440 zbx_strerror(errno));
441 goto close;
442 }
443
444 if (0 != strncmp(response, OK_220, strlen(OK_220)))
445 {
446 zbx_snprintf(error, max_error_len, "no welcome message 220* from SMTP server \"%s\"", response);
447 goto close;
448 }
449
450 /* send HELO */
451
452 if ('\0' != *smtp_helo)
453 {
454 zbx_snprintf(cmd, sizeof(cmd), "HELO %s\r\n", smtp_helo);
455
456 if (-1 == write(s.socket, cmd, strlen(cmd)))
457 {
458 zbx_snprintf(error, max_error_len, "error sending HELO to mailserver: %s",
459 zbx_strerror(errno));
460 goto close;
461 }
462
463 if (FAIL == smtp_readln(&s, &response))
464 {
465 zbx_snprintf(error, max_error_len, "error receiving answer on HELO request: %s",
466 zbx_strerror(errno));
467 goto close;
468 }
469
470 if (0 != strncmp(response, OK_250, strlen(OK_250)))
471 {
472 zbx_snprintf(error, max_error_len, "wrong answer on HELO \"%s\"", response);
473 goto close;
474 }
475 }
476
477 /* send MAIL FROM */
478
479 for (i = 0; i < from_mails->values_num; i++)
480 {
481 zbx_snprintf(cmd, sizeof(cmd), "MAIL FROM:%s\r\n", ((zbx_mailaddr_t *)from_mails->values[i])->addr);
482
483 if (-1 == write(s.socket, cmd, strlen(cmd)))
484 {
485 zbx_snprintf(error, max_error_len, "error sending MAIL FROM to mailserver: %s", zbx_strerror(errno));
486 goto close;
487 }
488
489 if (FAIL == smtp_readln(&s, &response))
490 {
491 zbx_snprintf(error, max_error_len, "error receiving answer on MAIL FROM request: %s", zbx_strerror(errno));
492 goto close;
493 }
494
495 if (0 != strncmp(response, OK_250, strlen(OK_250)))
496 {
497 zbx_snprintf(error, max_error_len, "wrong answer on MAIL FROM \"%s\"", response);
498 goto close;
499 }
500 }
501
502 /* send RCPT TO */
503
504 for (i = 0; i < to_mails->values_num; i++)
505 {
506 zbx_snprintf(cmd, sizeof(cmd), "RCPT TO:%s\r\n", ((zbx_mailaddr_t *)to_mails->values[i])->addr);
507
508 if (-1 == write(s.socket, cmd, strlen(cmd)))
509 {
510 zbx_snprintf(error, max_error_len, "error sending RCPT TO to mailserver: %s", zbx_strerror(errno));
511 goto close;
512 }
513
514 if (FAIL == smtp_readln(&s, &response))
515 {
516 zbx_snprintf(error, max_error_len, "error receiving answer on RCPT TO request: %s", zbx_strerror(errno));
517 goto close;
518 }
519
520 /* May return 251 as well: User not local; will forward to <forward-path>. See RFC825. */
521 if (0 != strncmp(response, OK_250, strlen(OK_250)) && 0 != strncmp(response, OK_251, strlen(OK_251)))
522 {
523 zbx_snprintf(error, max_error_len, "wrong answer on RCPT TO \"%s\"", response);
524 goto close;
525 }
526 }
527
528 /* send DATA */
529
530 zbx_snprintf(cmd, sizeof(cmd), "DATA\r\n");
531
532 if (-1 == write(s.socket, cmd, strlen(cmd)))
533 {
534 zbx_snprintf(error, max_error_len, "error sending DATA to mailserver: %s", zbx_strerror(errno));
535 goto close;
536 }
537
538 if (FAIL == smtp_readln(&s, &response))
539 {
540 zbx_snprintf(error, max_error_len, "error receiving answer on DATA request: %s", zbx_strerror(errno));
541 goto close;
542 }
543
544 if (0 != strncmp(response, OK_354, strlen(OK_354)))
545 {
546 zbx_snprintf(error, max_error_len, "wrong answer on DATA \"%s\"", response);
547 goto close;
548 }
549
550 cmdp = smtp_prepare_payload(from_mails, to_mails, mailsubject, mailbody);
551 err = write(s.socket, cmdp, strlen(cmdp));
552 zbx_free(cmdp);
553
554 if (-1 == err)
555 {
556 zbx_snprintf(error, max_error_len, "error sending headers and mail body to mailserver: %s",
557 zbx_strerror(errno));
558 goto close;
559 }
560
561 /* send . */
562
563 zbx_snprintf(cmd, sizeof(cmd), "\r\n.\r\n");
564
565 if (-1 == write(s.socket, cmd, strlen(cmd)))
566 {
567 zbx_snprintf(error, max_error_len, "error sending . to mailserver: %s", zbx_strerror(errno));
568 goto close;
569 }
570
571 if (FAIL == smtp_readln(&s, &response))
572 {
573 zbx_snprintf(error, max_error_len, "error receiving answer on . request: %s", zbx_strerror(errno));
574 goto close;
575 }
576
577 if (0 != strncmp(response, OK_250, strlen(OK_250)))
578 {
579 zbx_snprintf(error, max_error_len, "wrong answer on end of data \"%s\"", response);
580 goto close;
581 }
582
583 /* send QUIT */
584
585 zbx_snprintf(cmd, sizeof(cmd), "QUIT\r\n");
586
587 if (-1 == write(s.socket, cmd, strlen(cmd)))
588 {
589 zbx_snprintf(error, max_error_len, "error sending QUIT to mailserver: %s", zbx_strerror(errno));
590 goto close;
591 }
592
593 ret = SUCCEED;
594 close:
595 zbx_tcp_close(&s);
596 out:
597 zbx_alarm_off();
598
599 return ret;
600 }
601
send_email_curl(const char * smtp_server,unsigned short smtp_port,const char * smtp_helo,zbx_vector_ptr_t * from_mails,zbx_vector_ptr_t * to_mails,const char * mailsubject,const char * mailbody,unsigned char smtp_security,unsigned char smtp_verify_peer,unsigned char smtp_verify_host,unsigned char smtp_authentication,const char * username,const char * password,int timeout,char * error,size_t max_error_len)602 static int send_email_curl(const char *smtp_server, unsigned short smtp_port, const char *smtp_helo,
603 zbx_vector_ptr_t *from_mails, zbx_vector_ptr_t *to_mails, const char *mailsubject,
604 const char *mailbody, unsigned char smtp_security, unsigned char smtp_verify_peer,
605 unsigned char smtp_verify_host, unsigned char smtp_authentication, const char *username,
606 const char *password, int timeout, char *error, size_t max_error_len)
607 {
608 #ifdef HAVE_SMTP_AUTHENTICATION
609 const char *__function_name = "send_email_curl";
610
611 int ret = FAIL, i;
612 CURL *easyhandle;
613 CURLcode err;
614 char url[MAX_STRING_LEN], errbuf[CURL_ERROR_SIZE] = "";
615 size_t url_offset= 0;
616 struct curl_slist *recipients = NULL;
617 smtp_payload_status_t payload_status;
618
619 if (NULL == (easyhandle = curl_easy_init()))
620 {
621 zbx_strlcpy(error, "cannot initialize cURL library", max_error_len);
622 goto out;
623 }
624
625 memset(&payload_status, 0, sizeof(payload_status));
626
627 if (SMTP_SECURITY_SSL == smtp_security)
628 url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "smtps://");
629 else
630 url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "smtp://");
631
632 url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "%s:%hu", smtp_server, smtp_port);
633
634 if ('\0' != *smtp_helo)
635 zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "/%s", smtp_helo);
636
637 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, url)))
638 goto error;
639
640 if (SMTP_SECURITY_NONE != smtp_security)
641 {
642 extern char *CONFIG_SSL_CA_LOCATION;
643
644 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYPEER,
645 0 == smtp_verify_peer ? 0L : 1L)) ||
646 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST,
647 0 == smtp_verify_host ? 0L : 2L)))
648 {
649 goto error;
650 }
651
652 if (0 != smtp_verify_peer && NULL != CONFIG_SSL_CA_LOCATION)
653 {
654 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CAPATH, CONFIG_SSL_CA_LOCATION)))
655 goto error;
656 }
657
658 if (SMTP_SECURITY_STARTTLS == smtp_security)
659 {
660 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL)))
661 goto error;
662 }
663 }
664
665 if (SMTP_AUTHENTICATION_NORMAL_PASSWORD == smtp_authentication)
666 {
667 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERNAME, username)) ||
668 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PASSWORD, password)))
669 {
670 goto error;
671 }
672
673 /* Don't specify preferred authentication mechanism implying AUTH=* and let libcurl choose the best */
674 /* one (in its mind) among supported by SMTP server. If someday we decide to let user choose their */
675 /* preferred authentication mechanism one should know that: */
676 /* - versions 7.20.0 to 7.30.0 do not support specifying login options */
677 /* - versions 7.31.0 to 7.33.0 support login options in CURLOPT_USERPWD */
678 /* - versions 7.34.0 and above support explicit CURLOPT_LOGIN_OPTIONS */
679 }
680
681 if (0 >= from_mails->values_num)
682 {
683 zabbix_log(LOG_LEVEL_DEBUG, "%s() sender's address is not specified", __function_name);
684 }
685 else if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAIL_FROM,
686 ((zbx_mailaddr_t *)from_mails->values[0])->addr)))
687 {
688 goto error;
689 }
690
691 for (i = 0; i < to_mails->values_num; i++)
692 recipients = curl_slist_append(recipients, ((zbx_mailaddr_t *)to_mails->values[i])->addr);
693
694 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAIL_RCPT, recipients)))
695 goto error;
696
697 payload_status.payload = smtp_prepare_payload(from_mails, to_mails, mailsubject, mailbody);
698 payload_status.payload_len = strlen(payload_status.payload);
699
700 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L)) ||
701 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, smtp_provide_payload)) ||
702 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_READDATA, &payload_status)) ||
703 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, (long)timeout)) ||
704 CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, errbuf)))
705 {
706 goto error;
707 }
708
709 if (NULL != CONFIG_SOURCE_IP)
710 {
711 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_INTERFACE, CONFIG_SOURCE_IP)))
712 goto error;
713 }
714
715 if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_TRACE))
716 {
717 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1L)))
718 goto error;
719
720 if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_DEBUGFUNCTION, smtp_debug_function)))
721 goto error;
722 }
723
724 if (CURLE_OK != (err = curl_easy_perform(easyhandle)))
725 {
726 zbx_snprintf(error, max_error_len, "%s%s%s", curl_easy_strerror(err), ('\0' != *errbuf ? ": " : ""),
727 errbuf);
728 goto clean;
729 }
730
731 ret = SUCCEED;
732 goto clean;
733 error:
734 zbx_strlcpy(error, curl_easy_strerror(err), max_error_len);
735 clean:
736 zbx_free(payload_status.payload);
737
738 curl_slist_free_all(recipients);
739 curl_easy_cleanup(easyhandle);
740 out:
741 return ret;
742 #else
743 ZBX_UNUSED(smtp_server);
744 ZBX_UNUSED(smtp_port);
745 ZBX_UNUSED(smtp_helo);
746 ZBX_UNUSED(from_mails);
747 ZBX_UNUSED(to_mails);
748 ZBX_UNUSED(mailsubject);
749 ZBX_UNUSED(mailbody);
750 ZBX_UNUSED(smtp_security);
751 ZBX_UNUSED(smtp_verify_peer);
752 ZBX_UNUSED(smtp_verify_host);
753 ZBX_UNUSED(smtp_authentication);
754 ZBX_UNUSED(username);
755 ZBX_UNUSED(password);
756 ZBX_UNUSED(timeout);
757
758 zbx_strlcpy(error, "Support for SMTP authentication was not compiled in", max_error_len);
759 return FAIL;
760 #endif
761 }
762
763 /******************************************************************************
764 * *
765 * Function: zbx_mailaddr_free *
766 * *
767 * Purpose: frees the mail address object *
768 * *
769 * Parameters: mailaddr - [IN] the mail address *
770 * *
771 ******************************************************************************/
zbx_mailaddr_free(zbx_mailaddr_t * mailaddr)772 static void zbx_mailaddr_free(zbx_mailaddr_t *mailaddr)
773 {
774 zbx_free(mailaddr->addr);
775 zbx_free(mailaddr->disp_name);
776 zbx_free(mailaddr);
777 }
778
send_email(const char * smtp_server,unsigned short smtp_port,const char * smtp_helo,const char * smtp_email,const char * mailto,const char * mailsubject,const char * mailbody,unsigned char smtp_security,unsigned char smtp_verify_peer,unsigned char smtp_verify_host,unsigned char smtp_authentication,const char * username,const char * password,int timeout,char * error,size_t max_error_len)779 int send_email(const char *smtp_server, unsigned short smtp_port, const char *smtp_helo,
780 const char *smtp_email, const char *mailto, const char *mailsubject, const char *mailbody,
781 unsigned char smtp_security, unsigned char smtp_verify_peer, unsigned char smtp_verify_host,
782 unsigned char smtp_authentication, const char *username, const char *password, int timeout,
783 char *error, size_t max_error_len)
784 {
785 const char *__function_name = "send_email";
786
787 int ret = FAIL;
788 zbx_vector_ptr_t from_mails, to_mails;
789
790 zabbix_log(LOG_LEVEL_DEBUG, "In %s() smtp_server:'%s' smtp_port:%hu smtp_security:%d smtp_authentication:%d",
791 __function_name, smtp_server, smtp_port, (int)smtp_security, (int)smtp_authentication);
792
793 *error = '\0';
794
795 zbx_vector_ptr_create(&from_mails);
796 zbx_vector_ptr_create(&to_mails);
797
798 /* validate addresses before connecting to the server */
799 if (SUCCEED != smtp_parse_mailbox(smtp_email, error, max_error_len, &from_mails))
800 goto clean;
801
802 if (SUCCEED != smtp_parse_mailbox(mailto, error, max_error_len, &to_mails))
803 goto clean;
804
805 /* choose appropriate method for sending the email */
806 if (SMTP_SECURITY_NONE == smtp_security && SMTP_AUTHENTICATION_NONE == smtp_authentication)
807 {
808 ret = send_email_plain(smtp_server, smtp_port, smtp_helo, &from_mails, &to_mails, mailsubject,
809 mailbody, timeout, error, max_error_len);
810 }
811 else
812 {
813 ret = send_email_curl(smtp_server, smtp_port, smtp_helo, &from_mails, &to_mails, mailsubject,
814 mailbody, smtp_security, smtp_verify_peer, smtp_verify_host, smtp_authentication,
815 username, password, timeout, error, max_error_len);
816 }
817
818 clean:
819
820 zbx_vector_ptr_clear_ext(&from_mails, (zbx_clean_func_t)zbx_mailaddr_free);
821 zbx_vector_ptr_destroy(&from_mails);
822
823 zbx_vector_ptr_clear_ext(&to_mails, (zbx_clean_func_t)zbx_mailaddr_free);
824 zbx_vector_ptr_destroy(&to_mails);
825
826 if ('\0' != *error)
827 zabbix_log(LOG_LEVEL_WARNING, "failed to send email: %s", error);
828
829 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
830
831 return ret;
832 }
833