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