/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxmedia.h" #include "zbxstr.h" #include "zbxcomms.h" #include "zbxcrypto.h" #include "zbxalgo.h" #include "zbxcurl.h" /* number of characters per line when wrapping Base64 data in Email */ #define ZBX_EMAIL_B64_MAXLINE 76 /* number of characters per "encoded-word" in RFC-2047 message header */ #define ZBX_EMAIL_B64_MAXWORD_RFC2047 75 /* multiple 'encoded-word's should be separated by <CR><LF><SPACE> */ #define ZBX_EMAIL_ENCODED_WORD_SEPARATOR "\r\n " /* separator for multipart mixed messages */ #define ZBX_MULTIPART_MIXED_BOUNDARY "MULTIPART-MIXED-BOUNDARY" #define OK_250 "250" /****************************************************************************** * * * Purpose: Encode a string into a base64 string as required by rfc2047. * * Used for encoding e-mail headers. * * * * Parameters: src - [IN] a null-terminated UTF-8 string to encode * * p_base64 - [OUT] a pointer to the encoded string * * * * Comments: Based on the patch submitted by * * Jairo Eduardo Lopez Fuentes Nacarino * * * ******************************************************************************/ static void str_base64_encode_rfc2047(const char *src, char **p_base64) { const char *p0; /* pointer in src to start encoding from */ const char *p1; /* pointer in src: 1st byte of UTF-8 character */ size_t c_len; /* length of UTF-8 character sequence */ size_t p_base64_alloc; /* allocated memory size for subject */ size_t p_base64_offset = 0; /* offset for writing into subject */ assert(src); assert(NULL == *p_base64); /* do not accept already allocated memory */ p_base64_alloc = ZBX_EMAIL_B64_MAXWORD_RFC2047 + sizeof(ZBX_EMAIL_ENCODED_WORD_SEPARATOR); *p_base64 = (char *)zbx_malloc(NULL, p_base64_alloc); **p_base64 = '\0'; for (p0 = src; '\0' != *p0; p0 = p1) { /* Max length of line is 76 characters (without line separator). */ /* Max length of "encoded-word" is 75 characters (without word separator). */ /* 3 characters are taken by word separator "<CR><LF><Space>" which also includes the line separator. */ /* 12 characters are taken by header "=?UTF-8?B?" and trailer "?=". */ /* So, one "encoded-word" can hold up to 63 characters of Base64-encoded string. */ /* Encoding 45 bytes produces a 61 byte long Base64-encoded string which meets the limit. */ /* Encoding 46 bytes produces a 65 byte long Base64-encoded string which exceeds the limit. */ for (p1 = p0; '\0' != *p1; p1 += c_len) { /* an invalid UTF-8 character or length of a string more than 45 bytes */ if (0 == (c_len = zbx_utf8_char_len(p1)) || 45 < p1 - p0 + c_len) break; } if (0 < p1 - p0) { /* 12 characters are taken by header "=?UTF-8?B?" and trailer "?=" plus '\0' */ char b64_buf[ZBX_EMAIL_B64_MAXWORD_RFC2047 - 12 + 1]; zbx_base64_encode(p0, b64_buf, p1 - p0); if (0 != p_base64_offset) /* not the first "encoded-word" ? */ { zbx_strcpy_alloc(p_base64, &p_base64_alloc, &p_base64_offset, ZBX_EMAIL_ENCODED_WORD_SEPARATOR); } zbx_snprintf_alloc(p_base64, &p_base64_alloc, &p_base64_offset, "=?UTF-8?B?%s?=", b64_buf); } else break; } } /****************************************************************************** * * * Comments: reads until '\n' * * * ******************************************************************************/ static int smtp_readln(zbx_socket_t *s, const char **buf) { while (NULL != (*buf = zbx_tcp_recv_line(s)) && 4 <= strlen(*buf) && 0 != isdigit((*buf)[0]) && 0 != isdigit((*buf)[1]) && 0 != isdigit((*buf)[2]) && '-' == (*buf)[3]) ; return NULL == *buf ? FAIL : SUCCEED; } /******************************************************************************** * * * Purpose: 1. Extract a display name and an angle address from mailbox string * * for using in "MAIL FROM:", "RCPT TO:", "From:" and "To:" fields. * * 2. If the display name contains multibyte UTF-8 characters encode * * it into a base64 string as required by rfc2047. The encoding is * * also applied if the display name looks like a base64-encoded * * word. * * * * Parameters: mailbox - [IN] a null-terminated UTF-8 string * * error - [IN] pointer to string for reporting errors * * mailaddrs - [OUT] array of mail addresses * * * * Comments: The function is very much simplified in comparison with full * * RFC 5322-compliant parser. It does not recognize: * * - comments, * * - quoted strings and quoted pairs, * * - folding whitespace. * * For example, '<' and '@' are not supported in the display name * * and the local part of email address. * * * ********************************************************************************/ static int smtp_parse_mailbox(const char *mailbox, char **error, zbx_vector_ptr_t *mailaddrs) { const char *p, *pstart, *angle_addr_start, *domain_start, *utf8_end; const char *base64_like_start, *base64_like_end, *token; char *base64_buf, *tmp_mailbox; size_t size_angle_addr = 0, offset_angle_addr = 0, len, i; int ret = FAIL; zbx_mailaddr_t *mailaddr = NULL; tmp_mailbox = zbx_strdup(NULL, mailbox); token = strtok(tmp_mailbox, "\n"); while (token != NULL) { angle_addr_start = NULL; domain_start = NULL; utf8_end = NULL; base64_like_start = NULL; base64_like_end = NULL; base64_buf = NULL; p = token; while (' ' == *p || '\t' == *p) p++; pstart = p; while ('\0' != *p) { len = zbx_utf8_char_len(p); if (1 == len) /* ASCII character */ { switch (*p) { case '<': angle_addr_start = p; break; case '@': domain_start = p; break; /* if mailbox contains a sequence '=?'.*'?=' which looks like a Base64-encoded word */ case '=': if ('?' == *(p + 1)) base64_like_start = p++; break; case '?': if (NULL != base64_like_start && '=' == *(p + 1)) base64_like_end = p++; } p++; } else if (1 < len) /* multibyte UTF-8 character */ { for (i = 1; i < len; i++) { if ('\0' == *(p + i)) { *error = zbx_dsprintf(*error, "invalid UTF-8 character in email" " address: %s", token); goto out; } } utf8_end = p + len - 1; p += len; } else if (0 == len) /* invalid UTF-8 character */ { *error = zbx_dsprintf(*error, "invalid UTF-8 character in email address: %s", token); goto out; } } if (NULL == domain_start) { *error = zbx_dsprintf(*error, "no '@' in email address: %s", token); goto out; } if (utf8_end > angle_addr_start) { *error = zbx_dsprintf(*error, "email address local or domain part contains UTF-8 character: %s", token); goto out; } mailaddr = (zbx_mailaddr_t *)zbx_malloc(NULL, sizeof(zbx_mailaddr_t)); memset(mailaddr, 0, sizeof(zbx_mailaddr_t)); if (NULL != angle_addr_start) { zbx_snprintf_alloc(&mailaddr->addr, &size_angle_addr, &offset_angle_addr, "%s", angle_addr_start); if (pstart < angle_addr_start) /* display name */ { mailaddr->disp_name = (char *)zbx_malloc(mailaddr->disp_name, (size_t)(angle_addr_start - pstart + 1)); memcpy(mailaddr->disp_name, pstart, (size_t)(angle_addr_start - pstart)); *(mailaddr->disp_name + (angle_addr_start - pstart)) = '\0'; /* UTF-8 or Base64-looking display name */ if (NULL != utf8_end || (NULL != base64_like_end && angle_addr_start - 1 > base64_like_end)) { str_base64_encode_rfc2047(mailaddr->disp_name, &base64_buf); zbx_free(mailaddr->disp_name); mailaddr->disp_name = base64_buf; } } } else { zbx_snprintf_alloc(&mailaddr->addr, &size_angle_addr, &offset_angle_addr, "<%s>", pstart); } zbx_vector_ptr_append(mailaddrs, mailaddr); token = strtok(NULL, "\n"); } ret = SUCCEED; out: zbx_free(tmp_mailbox); return ret; } static char *email_encode_part(const char *data, size_t data_size) { char *base64 = NULL, *part; zbx_base64_encode_dyn(data, &base64, data_size); part = zbx_str_linefeed(base64, ZBX_EMAIL_B64_MAXLINE, "\r\n"); zbx_free(base64); return part; } static char *smtp_prepare_payload(zbx_vector_ptr_t *from_mails, zbx_vector_ptr_t *to_mails, const char *inreplyto, const char *mailsubject, const char *mailbody, unsigned char message_format) { char *tmp = NULL, *base64 = NULL; char *localsubject = NULL, *localbody = NULL, *from = NULL, *to = NULL; char str_time[MAX_STRING_LEN]; struct tm *local_time; time_t email_time; int i; size_t from_alloc = 0, from_offset = 0, to_alloc = 0, to_offset = 0, tmp_alloc = 0, tmp_offset = 0; /* prepare subject */ tmp = zbx_string_replace(mailsubject, "\r\n", " "); localsubject = zbx_string_replace(tmp, "\n", " "); zbx_free(tmp); if (FAIL == zbx_is_ascii_string(localsubject)) { /* split subject into multiple RFC 2047 "encoded-words" */ str_base64_encode_rfc2047(localsubject, &base64); zbx_free(localsubject); localsubject = base64; base64 = NULL; } /* prepare body */ if (ZBX_MEDIA_MESSAGE_FORMAT_MULTI != message_format) { char *tmp_body; tmp = zbx_string_replace(mailbody, "\r\n", "\n"); tmp_body = zbx_string_replace(tmp, "\n", "\r\n"); localbody = email_encode_part(tmp_body, strlen(tmp_body)); zbx_free(tmp_body); zbx_free(tmp); } else localbody = (char *)mailbody; /* prepare date */ time(&email_time); local_time = localtime(&email_time); strftime(str_time, MAX_STRING_LEN, "%a, %d %b %Y %H:%M:%S %z", local_time); for (i = 0; i < from_mails->values_num; i++) { zbx_snprintf_alloc(&from, &from_alloc, &from_offset, "%s%s", ZBX_NULL2EMPTY_STR(((zbx_mailaddr_t *)from_mails->values[i])->disp_name), ((zbx_mailaddr_t *)from_mails->values[i])->addr); if (from_mails->values_num - 1 > i) zbx_strcpy_alloc(&from, &from_alloc, &from_offset, ","); } for (i = 0; i < to_mails->values_num; i++) { zbx_snprintf_alloc(&to, &to_alloc, &to_offset, "%s%s", ZBX_NULL2EMPTY_STR(((zbx_mailaddr_t *)to_mails->values[i])->disp_name), ((zbx_mailaddr_t *)to_mails->values[i])->addr); if (to_mails->values_num - 1 > i) zbx_strcpy_alloc(&to, &to_alloc, &to_offset, ","); } /* e-mails are sent in 'SMTP/MIME e-mail' format because UTF-8 is used both in mailsubject and mailbody */ /* =?charset?encoding?encoded text?= format must be used for subject field */ zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "From: %s\r\n" "To: %s\r\n" "In-Reply-To: %s\r\n" "Date: %s\r\n" "Subject: %s\r\n" "MIME-Version: 1.0\r\n", from, to, inreplyto, str_time, localsubject); if (ZBX_MEDIA_MESSAGE_FORMAT_MULTI == message_format) { zbx_strcpy_alloc(&tmp, &tmp_alloc, &tmp_offset, "Content-Type: multipart/mixed; boundary=" ZBX_MULTIPART_MIXED_BOUNDARY "\r\n"); } else { zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "Content-Type: %s; charset=\"UTF-8\"\r\n" "Content-Transfer-Encoding: base64\r\n", ZBX_MEDIA_MESSAGE_FORMAT_HTML == message_format ? "text/html" : "text/plain"); } zbx_snprintf_alloc(&tmp, &tmp_alloc, &tmp_offset, "\r\n" "%s", localbody); zbx_free(localsubject); if (localbody != mailbody) zbx_free(localbody); zbx_free(from); zbx_free(to); return tmp; } #ifdef HAVE_LIBCURL typedef struct { char *payload; size_t payload_len; size_t provided_len; } smtp_payload_status_t; static size_t smtp_provide_payload(void *buffer, size_t size, size_t nmemb, void *instream) { size_t current_len; smtp_payload_status_t *payload_status = (smtp_payload_status_t *)instream; current_len = MIN(size * nmemb, payload_status->payload_len - payload_status->provided_len); memcpy(buffer, payload_status->payload + payload_status->provided_len, current_len); payload_status->provided_len += current_len; return current_len; } static int smtp_debug_function(CURL *easyhandle, curl_infotype type, char *data, size_t size, void *userptr) { const char labels[3] = {'*', '<', '>'}; ZBX_UNUSED(easyhandle); ZBX_UNUSED(userptr); if (CURLINFO_TEXT != type && CURLINFO_HEADER_IN != type && CURLINFO_HEADER_OUT != type) goto out; while (0 < size && ('\r' == data[size - 1] || '\n' == data[size - 1])) size--; zabbix_log(LOG_LEVEL_TRACE, "%c %.*s", labels[type], (int)size, data); out: return 0; } #endif static char *smtp_get_helo_from_system(void) { struct utsname name; if (-1 == uname(&name)) return NULL; return zbx_strdup(NULL, name.nodename); } static char *smtp_get_helo_from_addr(const char *addr) { const char *domain; char *helo_addr; size_t addr_len; if (NULL == addr || '\0' == *addr || NULL == (domain = strrchr(addr, '@'))) return NULL; addr_len = strlen(domain + 1); if (1 == addr_len && '>' == *(domain + 1)) return NULL; helo_addr = zbx_strdup(NULL, domain + 1); helo_addr[addr_len - 1] = '\0'; return helo_addr; } static int send_smtp_helo_plain(const char *addr, const char *helo, zbx_socket_t *s, char **error) { char cmd[MAX_STRING_LEN], *helo_parsed = NULL; const char *response; int ret = SUCCEED; if ('\0' != *helo) { zbx_snprintf(cmd, sizeof(cmd), "HELO %s\r\n", helo); } else { if (NULL == (helo_parsed = smtp_get_helo_from_addr(addr))) { zabbix_log(LOG_LEVEL_DEBUG, "%s() HELO is not specified and failed to parse HELO from email " "address, trying to form HELO command using system's hostname", __func__); if (NULL == (helo_parsed = smtp_get_helo_from_system())) { *error = zbx_strdup(*error, "failed to retrieve domain name for HELO command"); ret = FAIL; goto out; } } zbx_snprintf(cmd, sizeof(cmd), "HELO %s\r\n", helo_parsed); } if (-1 == zbx_tcp_send_raw(s, cmd)) { *error = zbx_dsprintf(*error, "error sending HELO to mailserver: %s", zbx_strerror(errno)); ret = FAIL; goto out; } if (FAIL == smtp_readln(s, &response)) { *error = zbx_dsprintf(*error, "error receiving answer on HELO request: %s", zbx_strerror(errno)); ret = FAIL; goto out; } if (0 != strncmp(response, OK_250, ZBX_CONST_STRLEN(OK_250))) { *error = zbx_dsprintf(*error, "wrong answer on HELO \"%s\"", response); ret = FAIL; goto out; } out: zbx_free(helo_parsed); return ret; } static const char *socket_error(zbx_socket_t *s, int socket_errno) { if (SUCCEED != zbx_socket_check_deadline(s)) return "timeout error"; return zbx_strerror(socket_errno); } static int 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 *inreplyto, const char *mailsubject, const char *mailbody, unsigned char message_format, int timeout, const char *config_source_ip, char **error) { #define OK_220 "220" #define OK_251 "251" #define OK_354 "354" zbx_socket_t s; int err, ret = FAIL, i; char cmd[MAX_STRING_LEN], *cmdp = NULL, *helo_addr = NULL; const char *response; /* connect to and receive an initial greeting from SMTP server */ if (FAIL == zbx_tcp_connect(&s, config_source_ip, smtp_server, smtp_port, timeout, ZBX_TCP_SEC_UNENCRYPTED, NULL, NULL)) { *error = zbx_dsprintf(*error, "cannot connect to SMTP server \"%s\": %s", smtp_server, zbx_socket_strerror()); goto out; } if (FAIL == smtp_readln(&s, &response)) { *error = zbx_dsprintf(*error, "error receiving initial string from SMTP server: %s", socket_error(&s, errno)); goto close; } if (0 != strncmp(response, OK_220, ZBX_CONST_STRLEN(OK_220))) { *error = zbx_dsprintf(*error, "no welcome message 220* from SMTP server \"%s\"", response); goto close; } /* send HELO */ if (0 != from_mails->values_num) helo_addr = ((zbx_mailaddr_t *)from_mails->values[0])->addr; if (FAIL == send_smtp_helo_plain(helo_addr, smtp_helo, &s, error)) goto close; /* send MAIL FROM */ for (i = 0; i < from_mails->values_num; i++) { zbx_snprintf(cmd, sizeof(cmd), "MAIL FROM:%s\r\n", ((zbx_mailaddr_t *)from_mails->values[i])->addr); if (-1 == zbx_tcp_send_raw(&s, cmd)) { *error = zbx_dsprintf(*error, "error sending MAIL FROM to mailserver: %s", socket_error(&s, errno)); goto close; } if (FAIL == smtp_readln(&s, &response)) { *error = zbx_dsprintf(*error, "error receiving answer on MAIL FROM request: %s", socket_error(&s, errno)); goto close; } if (0 != strncmp(response, OK_250, ZBX_CONST_STRLEN(OK_250))) { *error = zbx_dsprintf(*error, "wrong answer on MAIL FROM \"%s\"", response); goto close; } } /* send RCPT TO */ for (i = 0; i < to_mails->values_num; i++) { zbx_snprintf(cmd, sizeof(cmd), "RCPT TO:%s\r\n", ((zbx_mailaddr_t *)to_mails->values[i])->addr); if (-1 == zbx_tcp_send_raw(&s, cmd)) { *error = zbx_dsprintf(*error, "error sending RCPT TO to mailserver: %s", socket_error(&s, errno)); goto close; } if (FAIL == smtp_readln(&s, &response)) { *error = zbx_dsprintf(*error, "error receiving answer on RCPT TO request: %s", socket_error(&s, errno)); goto close; } /* May return 251 as well: User not local; will forward to <forward-path>. See RFC825. */ if (0 != strncmp(response, OK_250, ZBX_CONST_STRLEN(OK_250)) && 0 != strncmp(response, OK_251, ZBX_CONST_STRLEN(OK_251))) { *error = zbx_dsprintf(*error, "wrong answer on RCPT TO \"%s\"", response); goto close; } } /* send DATA */ zbx_snprintf(cmd, sizeof(cmd), "DATA\r\n"); if (-1 == zbx_tcp_send_raw(&s, cmd)) { *error = zbx_dsprintf(*error, "error sending DATA to mailserver: %s", socket_error(&s, errno)); goto close; } if (FAIL == smtp_readln(&s, &response)) { *error = zbx_dsprintf(*error, "error receiving answer on DATA request: %s", socket_error(&s, errno)); goto close; } if (0 != strncmp(response, OK_354, ZBX_CONST_STRLEN(OK_354))) { *error = zbx_dsprintf(*error, "wrong answer on DATA \"%s\"", response); goto close; } cmdp = smtp_prepare_payload(from_mails, to_mails, inreplyto, mailsubject, mailbody, message_format); err = zbx_tcp_send_raw(&s, cmdp); zbx_free(cmdp); if (-1 == err) { *error = zbx_dsprintf(*error, "error sending headers and mail body to mailserver: %s", socket_error(&s, errno)); goto close; } /* send . */ zbx_snprintf(cmd, sizeof(cmd), "\r\n.\r\n"); if (-1 == zbx_tcp_send_raw(&s, cmd)) { *error = zbx_dsprintf(*error, "error sending . to mailserver: %s", socket_error(&s, errno)); goto close; } if (FAIL == smtp_readln(&s, &response)) { *error = zbx_dsprintf(*error, "error receiving answer on . request: %s", socket_error(&s, errno)); goto close; } if (0 != strncmp(response, OK_250, ZBX_CONST_STRLEN(OK_250))) { *error = zbx_dsprintf(*error, "wrong answer on end of data \"%s\"", response); goto close; } /* send QUIT */ zbx_snprintf(cmd, sizeof(cmd), "QUIT\r\n"); if (-1 == zbx_tcp_send_raw(&s, cmd)) { *error = zbx_dsprintf(*error, "error sending QUIT to mailserver: %s", socket_error(&s, errno)); goto close; } ret = SUCCEED; close: zbx_tcp_close(&s); out: return ret; #undef OK_220 #undef OK_251 #undef OK_354 } /* SMTP security options */ #define SMTP_SECURITY_NONE 0 #define SMTP_SECURITY_STARTTLS 1 #define SMTP_SECURITY_SSL 2 static int 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 *inreplyto, 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, unsigned char message_format, int timeout, const char *config_source_ip, const char *config_ssl_ca_location, char **error) { #ifdef HAVE_LIBCURL int ret = FAIL, i; CURL *easyhandle; CURLcode err; char url[MAX_STRING_LEN], errbuf[CURL_ERROR_SIZE] = ""; size_t url_offset= 0; struct curl_slist *recipients = NULL; smtp_payload_status_t payload_status; if (SMTP_SECURITY_NONE != smtp_security && SUCCEED != zbx_curl_has_ssl(error)) goto out; if (SMTP_AUTHENTICATION_NONE != smtp_authentication && SUCCEED != zbx_curl_has_smtp_auth(error)) goto out; if (NULL == (easyhandle = curl_easy_init())) { *error = zbx_strdup(*error, "cannot initialize cURL library"); goto out; } memset(&payload_status, 0, sizeof(payload_status)); if (SMTP_SECURITY_SSL == smtp_security) { if (SUCCEED != zbx_curl_protocol("smtps", error)) goto clean; url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "smtps://"); } else url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "smtp://"); url_offset += zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "%s:%hu", smtp_server, smtp_port); if ('\0' != *smtp_helo) { zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "/%s", smtp_helo); } else { char *helo_domain = NULL; if (0 != from_mails->values_num) { if (NULL == (helo_domain = smtp_get_helo_from_addr(((zbx_mailaddr_t *)from_mails->values[0])->addr))) { zabbix_log(LOG_LEVEL_DEBUG, "%s() HELO is not specified and failed to parse HELO " "from email address, trying to form HELO command using system's " "hostname", __func__); } } if (NULL == helo_domain) { if (NULL == (helo_domain = smtp_get_helo_from_system())) { *error = zbx_strdup(*error, "failed to retrieve domain name for HELO command"); goto clean; } } zbx_snprintf(url + url_offset, sizeof(url) - url_offset, "/%s", helo_domain); zbx_free(helo_domain); } if (SUCCEED != zbx_curl_setopt_smtps(easyhandle, error)) goto clean; if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, url))) goto error; if (SMTP_SECURITY_NONE != smtp_security) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYPEER, 0 == smtp_verify_peer ? 0L : 1L)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST, 0 == smtp_verify_host ? 0L : 2L))) { goto error; } if (0 != smtp_verify_peer && NULL != config_ssl_ca_location) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CAPATH, config_ssl_ca_location))) goto error; } if (SMTP_SECURITY_STARTTLS == smtp_security) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL))) goto error; } } if (SMTP_AUTHENTICATION_NORMAL_PASSWORD == smtp_authentication) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERNAME, username)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PASSWORD, password))) { goto error; } /* Don't specify preferred authentication mechanism implying AUTH=* and let libcurl choose the best */ /* one (in its mind) among supported by SMTP server. If someday we decide to let user choose their */ /* preferred authentication mechanism one should know that: */ /* - versions 7.20.0 to 7.30.0 do not support specifying login options */ /* - versions 7.31.0 to 7.33.0 support login options in CURLOPT_USERPWD */ /* - versions 7.34.0 and above support explicit CURLOPT_LOGIN_OPTIONS */ } if (0 >= from_mails->values_num) { zabbix_log(LOG_LEVEL_DEBUG, "%s() sender's address is not specified", __func__); } else if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAIL_FROM, ((zbx_mailaddr_t *)from_mails->values[0])->addr))) { goto error; } if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ACCEPT_ENCODING, ""))) goto error; for (i = 0; i < to_mails->values_num; i++) recipients = curl_slist_append(recipients, ((zbx_mailaddr_t *)to_mails->values[i])->addr); if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_MAIL_RCPT, recipients))) goto error; payload_status.payload = smtp_prepare_payload(from_mails, to_mails, inreplyto, mailsubject, mailbody, message_format); payload_status.payload_len = strlen(payload_status.payload); if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, smtp_provide_payload)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_READDATA, &payload_status)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, (long)timeout)) || CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, errbuf))) { goto error; } if (NULL != config_source_ip) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_INTERFACE, config_source_ip))) goto error; } if (SUCCEED == ZBX_CHECK_LOG_LEVEL(LOG_LEVEL_TRACE)) { if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_VERBOSE, 1L))) goto error; if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_DEBUGFUNCTION, smtp_debug_function))) goto error; } if (CURLE_OK != (err = curl_easy_perform(easyhandle))) { *error = zbx_dsprintf(*error, "%s%s%s", curl_easy_strerror(err), ('\0' != *errbuf ? ": " : ""), errbuf); goto clean; } ret = SUCCEED; goto clean; error: *error = zbx_strdup(*error, curl_easy_strerror(err)); clean: zbx_free(payload_status.payload); curl_slist_free_all(recipients); curl_easy_cleanup(easyhandle); out: return ret; #else ZBX_UNUSED(smtp_server); ZBX_UNUSED(smtp_port); ZBX_UNUSED(smtp_helo); ZBX_UNUSED(from_mails); ZBX_UNUSED(to_mails); ZBX_UNUSED(inreplyto); ZBX_UNUSED(mailsubject); ZBX_UNUSED(mailbody); ZBX_UNUSED(smtp_security); ZBX_UNUSED(smtp_verify_peer); ZBX_UNUSED(smtp_verify_host); ZBX_UNUSED(smtp_authentication); ZBX_UNUSED(username); ZBX_UNUSED(password); ZBX_UNUSED(message_format); ZBX_UNUSED(timeout); ZBX_UNUSED(config_source_ip); ZBX_UNUSED(config_ssl_ca_location); *error = zbx_strdup(*error, "Zabbix server was compiled without cURL library required for SMTP authentication"); return FAIL; #endif } /****************************************************************************** * * * Purpose: frees the mail address object * * * * Parameters: mailaddr - [IN] the mail address * * * ******************************************************************************/ static void zbx_mailaddr_free(zbx_mailaddr_t *mailaddr) { zbx_free(mailaddr->addr); zbx_free(mailaddr->disp_name); zbx_free(mailaddr); } int send_email(const char *smtp_server, unsigned short smtp_port, const char *smtp_helo, const char *smtp_email, const char *mailto, const char *inreplyto, 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, unsigned char message_format, int timeout, const char *config_source_ip, const char *config_ssl_ca_location, char **error) { int ret = FAIL; zbx_vector_ptr_t from_mails, to_mails; zabbix_log(LOG_LEVEL_DEBUG, "In %s() smtp_server:'%s' smtp_port:%hu smtp_security:%d smtp_authentication:%d", __func__, smtp_server, smtp_port, (int)smtp_security, (int)smtp_authentication); zbx_vector_ptr_create(&from_mails); zbx_vector_ptr_create(&to_mails); /* validate addresses before connecting to the server */ if (SUCCEED != smtp_parse_mailbox(smtp_email, error, &from_mails)) goto clean; if (SUCCEED != smtp_parse_mailbox(mailto, error, &to_mails)) goto clean; /* choose appropriate method for sending the email */ if (SMTP_SECURITY_NONE == smtp_security && SMTP_AUTHENTICATION_NONE == smtp_authentication) { ret = send_email_plain(smtp_server, smtp_port, smtp_helo, &from_mails, &to_mails, inreplyto, mailsubject, mailbody, message_format, timeout, config_source_ip, error); } else { ret = send_email_curl(smtp_server, smtp_port, smtp_helo, &from_mails, &to_mails, inreplyto, mailsubject, mailbody, smtp_security, smtp_verify_peer, smtp_verify_host, smtp_authentication, username, password, message_format, timeout, config_source_ip, config_ssl_ca_location, error); } clean: zbx_vector_ptr_clear_ext(&from_mails, (zbx_clean_func_t)zbx_mailaddr_free); zbx_vector_ptr_destroy(&from_mails); zbx_vector_ptr_clear_ext(&to_mails, (zbx_clean_func_t)zbx_mailaddr_free); zbx_vector_ptr_destroy(&to_mails); if (FAIL == ret) zabbix_log(LOG_LEVEL_WARNING, "failed to send email: %s", *error); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } #undef SMTP_SECURITY_NONE #undef SMTP_SECURITY_STARTTLS #undef SMTP_SECURITY_SSL char *zbx_email_make_body(const char *message, unsigned char message_format, const char *attachment_name, const char *attachment_type, const char *attachment, size_t attachment_size) { size_t body_alloc = 0, body_offset = 0; char *body = NULL, *localbody, *tmp, *tmp_body, *localattachment; tmp = zbx_string_replace(message, "\r\n", "\n"); tmp_body = zbx_string_replace(tmp, "\n", "\r\n"); localbody = email_encode_part(tmp_body, strlen(tmp_body)); zbx_free(tmp_body); zbx_free(tmp); zbx_snprintf_alloc(&body, &body_alloc, &body_offset, "--" ZBX_MULTIPART_MIXED_BOUNDARY "\r\n" "Content-Type: %s; charset=\"UTF-8\"\r\n" "Content-Transfer-Encoding: base64\r\n" "\r\n" "%s\r\n" "\r\n", ZBX_MEDIA_MESSAGE_FORMAT_HTML == message_format ? "text/html" : "text/plain", localbody); zbx_free(localbody); localattachment = email_encode_part(attachment, attachment_size); zbx_snprintf_alloc(&body, &body_alloc, &body_offset, "--" ZBX_MULTIPART_MIXED_BOUNDARY "\r\n" "Content-Type: %s\r\n" "Content-Transfer-Encoding: base64\r\n" "Content-Disposition: attachment; filename=\"%s\"\r\n" "\r\n" "%s\r\n" "\r\n" "--" ZBX_MULTIPART_MIXED_BOUNDARY "--\r\n", attachment_type, attachment_name, localattachment); zbx_free(localattachment); return body; }