From 47fd596fc59b77bb7107041b84bc6abafd355a46 Mon Sep 17 00:00:00 2001 From: Quentin Armitage Date: Mon, 29 Jan 2024 14:20:37 +0000 Subject: [PATCH] global: Allow display names in email addresses Signed-off-by: Quentin Armitage --- doc/man/man5/keepalived.conf.5.in | 10 +-- keepalived/core/global_data.c | 67 +++++++++++++++++- keepalived/core/global_parser.c | 12 ++-- keepalived/core/smtp.c | 108 ++++++++++++++---------------- keepalived/core/snmp.c | 7 +- keepalived/include/global_data.h | 3 +- keepalived/include/smtp.h | 12 ---- 7 files changed, 135 insertions(+), 84 deletions(-) diff --git a/doc/man/man5/keepalived.conf.5.in b/doc/man/man5/keepalived.conf.5.in index c10bca44e6..b8cedce270 100644 --- a/doc/man/man5/keepalived.conf.5.in +++ b/doc/man/man5/keepalived.conf.5.in @@ -265,14 +265,16 @@ possibly following any cleanup actions needed. \fBshutdown_script\fR SCRIPT_NAME [username [groupname]] \fBshutdown_script_timeout\fR SECONDS # range [1,1000] - # Set of email To: notify + # Set of email To: notify. To include a display name, the whole email address + # must be included in double quotes("). \fBnotification_email \fR{ - admin@example1.com + admin@example1.com "My admin " ... } - # email from address that will be in the header - # (default: keepalived@) + # email from address that will be in the header (see comment above for + # including a display name). + # (default: keepalived@local_host_name) \fBnotification_email_from \fRadmin@example.com # Remote SMTP server used to send notification email. diff --git a/keepalived/core/global_data.c b/keepalived/core/global_data.c index 3343985327..c2b271d710 100644 --- a/keepalived/core/global_data.c +++ b/keepalived/core/global_data.c @@ -136,7 +136,7 @@ free_email_list(list_head_t *l) email_t *email, *email_tmp; list_for_each_entry_safe(email, email_tmp, l, e_list) { - FREE(email->addr); + FREE_CONST_PTR(email->addr); FREE(email); } } @@ -149,6 +149,69 @@ dump_email_list(FILE *fp, const list_head_t *l) conf_write(fp, " %s", email->addr); } +const char * +format_email_addr(const char *addr) +{ + char *new_addr; + size_t len = strlen(addr); + const char *end_description; + const char *quote_char; + unsigned num_esc; + const char *ip; + char *op; + + if (addr[len - 1] != '>') + return STRDUP(addr); + + if (!(end_description = strrchr(addr, '<'))) { + /* We don't have a starting < - at the moment log it and copy verbatim */ + log_message(LOG_INFO, "email address '%s' invalid", addr); + return STRDUP(addr); + } + + /* Skip over white-space before < */ + end_description--; + while (end_description > addr && + (*end_description == ' ' || + *end_description == '\t')) + end_description--; + + /* We can't have a '"' because alloc_strvec_r() doesn't support it. + * We might be able to use alloc_strvec_quoted_escaped(), in which + * case we probably can have embedded '"'s. */ + + /* Do we have any of the characters that need quoting - see RFC5322 3.2.3? */ + quote_char = strpbrk(addr, "()<>[]:;@\\,."); + if (!quote_char || quote_char > end_description) + return STRDUP(addr); + + /* We need to quote any embedded '"'s or '\'s */ + quote_char = addr; + num_esc = 0; + while ((quote_char = strpbrk(quote_char, "\"\\")) && + quote_char <= end_description) { + num_esc++; + quote_char++; + } + + new_addr = MALLOC(len + 2 + num_esc + 1); + + ip = addr; + op = new_addr; + *op++ = '"'; + while ((quote_char = strpbrk(ip, "\"\\")) && + quote_char <= end_description) { + strncpy(op, ip, quote_char - ip); + op += quote_char - ip; + *op++ = '\\'; + *op++ = *quote_char++; + ip = quote_char; + } + sprintf(op, "%.*s\"%s", (int)(end_description - ip + 1), ip, end_description + 1); + + return new_addr; +} + void alloc_email(const char *addr) { @@ -156,7 +219,7 @@ alloc_email(const char *addr) PMALLOC(email); INIT_LIST_HEAD(&email->e_list); - email->addr = STRDUP(addr); + email->addr = format_email_addr(addr); list_add_tail(&email->e_list, &global_data->email); } diff --git a/keepalived/core/global_parser.c b/keepalived/core/global_parser.c index a572771f39..4c11f39baa 100644 --- a/keepalived/core/global_parser.c +++ b/keepalived/core/global_parser.c @@ -173,10 +173,11 @@ emailfrom_handler(const vector_t *strvec) if (vector_size(strvec) < 2) { report_config_error(CONFIG_GENERAL_ERROR, "emailfrom missing - ignoring"); return; - } + } else if (vector_size(strvec) > 2) + report_config_error(CONFIG_GENERAL_ERROR, "emailfrom - ignoring extra entries '%s' ...", strvec_slot(strvec, 2)); FREE_CONST_PTR(global_data->email_from); - global_data->email_from = set_value(strvec); + global_data->email_from = format_email_addr(strvec_slot(strvec, 1)); } static void smtpto_handler(const vector_t *strvec) @@ -253,17 +254,14 @@ email_handler(const vector_t *strvec) { const vector_t *email_vec = read_value_block(strvec); unsigned int i; - char *str; if (!email_vec) { report_config_error(CONFIG_GENERAL_ERROR, "Warning - empty notification_email block"); return; } - for (i = 0; i < vector_size(email_vec); i++) { - str = vector_slot(email_vec, i); - alloc_email(str); - } + for (i = 0; i < vector_size(email_vec); i++) + alloc_email(vector_slot(email_vec, i)); free_strvec(email_vec); } diff --git a/keepalived/core/smtp.c b/keepalived/core/smtp.c index 726f627871..b44717c86a 100644 --- a/keepalived/core/smtp.c +++ b/keepalived/core/smtp.c @@ -40,7 +40,7 @@ #include "scheduler.h" #endif -/* If it suspected that one of subject, body, buffer or email_to +/* If it suspected that one of subject, body or buffer * is overflowing in the smtp_t structure, defining SMTP_MSG_ALLOC_DEBUG * when using --enable-mem-check should help identity the issue */ @@ -103,7 +103,6 @@ free_smtp_msg_data(smtp_t * smtp) FREE(smtp->buffer); FREE(smtp->subject); FREE(smtp->body); - FREE(smtp->email_to); #endif FREE(smtp); } @@ -119,13 +118,11 @@ alloc_smtp_msg_data(void) smtp->subject = (char *)MALLOC(MAX_HEADERS_LENGTH); smtp->body = (char *)MALLOC(MAX_BODY_LENGTH); smtp->buffer = (char *)MALLOC(SMTP_BUFFER_MAX); - smtp->email_to = (char *)MALLOC(SMTP_BUFFER_MAX); #else smtp = MALLOC(sizeof(smtp_t) + MAX_HEADERS_LENGTH + MAX_BODY_LENGTH + SMTP_BUFFER_MAX + SMTP_BUFFER_MAX); smtp->subject = (char *)smtp + sizeof(smtp_t); smtp->body = smtp->subject + MAX_HEADERS_LENGTH; smtp->buffer = smtp->body + MAX_BODY_LENGTH; - smtp->email_to = smtp->buffer + SMTP_BUFFER_MAX; #endif return smtp; @@ -378,14 +375,21 @@ smtp_read_thread(thread_ref_t thread) static void helo_cmd(__attribute__((unused)) thread_ref_t thread) { - snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), SMTP_HELO_CMD, (global_data->smtp_helo_name) ? global_data->smtp_helo_name : "localhost"); + snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), "HELO %s\r\n", (global_data->smtp_helo_name) ? global_data->smtp_helo_name : "localhost"); } /* MAIL command processing */ static void mail_cmd(__attribute__((unused)) thread_ref_t thread) { - snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), SMTP_MAIL_CMD, global_data->email_from); + size_t len; + const char *start; + + len = strlen(global_data->email_from); + if (global_data->email_from[len - 1] == '>' && (start = strrchr(global_data->email_from, '<'))) + snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), "MAIL FROM:%s\r\n", start); + else + snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), "MAIL FROM:<%s>\r\n", global_data->email_from); } /* RCPT command processing */ @@ -394,6 +398,8 @@ rcpt_cmd(thread_ref_t thread) { smtp_t *smtp = THREAD_ARG(thread); email_t *email = smtp->next_email_element; + size_t len; + const char *start; /* We send RCPT TO command multiple time to add all our email receivers. * --rfc821.3.1 @@ -403,7 +409,11 @@ rcpt_cmd(thread_ref_t thread) else smtp->next_email_element = list_entry(email->e_list.next, email_t, e_list); - snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), SMTP_RCPT_CMD, email->addr); + len = strlen(email->addr); + if (email->addr[len - 1] == '>' && (start = strrchr(email->addr, '<'))) + snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), "RCPT TO:%s\r\n", start); + else + snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), "RCPT TO:<%s>\r\n", email->addr); } static void rcpt_code(thread_ref_t thread) @@ -418,7 +428,7 @@ rcpt_code(thread_ref_t thread) static void data_cmd(__attribute__((unused)) thread_ref_t thread) { - strncpy(smtp_send_buffer, SMTP_DATA_CMD, sizeof(smtp_send_buffer)); + strncpy(smtp_send_buffer, "DATA\r\n", sizeof(smtp_send_buffer)); } /* BODY command processing. @@ -432,14 +442,36 @@ body_cmd(thread_ref_t thread) char rfc822[80]; /* Mon, 01 Mar 2021 09:44:08 +0000 */ time_t now; struct tm t; + size_t offs = 0; + email_t *email; time(&now); localtime_r(&now, &t); strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S %z", &t); /* send the DATA fields */ - snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), SMTP_HEADERS_CMD SMTP_BODY_CMD "%s", - rfc822, global_data->email_from, smtp->subject, smtp->email_to, smtp->body, SMTP_SEND_CMD); + offs = snprintf(smtp_send_buffer, sizeof(smtp_send_buffer), + "Date: %s\r\n" + "From: %s\r\n" + "Subject: %s\r\n" + "X-Mailer: Keepalived\r\n" + "To:", + rfc822, global_data->email_from, smtp->subject); + + /* Add the recipients */ + list_for_each_entry(email, &global_data->email, e_list) { + offs += snprintf(smtp_send_buffer + offs, sizeof(smtp_send_buffer) - offs, + "%s %s", + list_is_first(&email->e_list, &global_data->email) ? "" : ",\r\n", + email->addr); + } + + /* Now the message body */ + snprintf(smtp_send_buffer + offs, sizeof(smtp_send_buffer) - offs, + "\r\n\r\n" + "%s\r\n" + "\r\n.\r\n", + smtp->body); } static void body_code(thread_ref_t thread) @@ -456,7 +488,7 @@ body_code(thread_ref_t thread) static void quit_cmd(__attribute__((unused)) thread_ref_t thread) { - strncpy(smtp_send_buffer, SMTP_QUIT_CMD, sizeof(smtp_send_buffer)); + strncpy(smtp_send_buffer, "QUIT\r\n", sizeof(smtp_send_buffer)); } static void @@ -515,6 +547,7 @@ smtp_log_to_file(smtp_t *smtp) char time_buf[25]; int time_buf_len; const char *file_name; + email_t *email; file_name = make_tmp_filename("smtp-alert.log"); fp = fopen_safe(file_name, "a"); @@ -525,10 +558,15 @@ smtp_log_to_file(smtp_t *smtp) localtime_r(&now, &tm); time_buf_len = strftime(time_buf, sizeof time_buf, "%a %b %e %X %Y", &tm); - fprintf(fp, "%s: %s -> %s\n" + fprintf(fp, "%s: %s ->", time_buf, global_data->email_from); + list_for_each_entry(email, &global_data->email, e_list) + fprintf(fp, "%s %s", + list_is_first(&email->e_list, &global_data->email) ? "" : ",", + email->addr); + + fprintf(fp, "\n" "%*sSubject: %s\n" "%*sBody: %s\n\n", - time_buf, global_data->email_from, smtp->email_to, time_buf_len - 7, "", smtp->subject, time_buf_len - 7, "", smtp->body); @@ -539,48 +577,6 @@ smtp_log_to_file(smtp_t *smtp) } #endif -/* - * Build a comma separated string of smtp recipient email addresses - * for the email message To-header. - */ -static void -build_to_header_rcpt_addrs(smtp_t *smtp) -{ - char *email_to_addrs; - size_t bytes_available = SMTP_BUFFER_MAX - 1; - size_t bytes_to_write; - bool done_addr = false; - email_t *email; - - if (smtp == NULL) - return; - - email_to_addrs = smtp->email_to; - - list_for_each_entry(email, &global_data->email, e_list) { - bytes_to_write = strlen(email->addr); - if (done_addr) { - if (bytes_available < 2) - break; - - /* Prepend with a comma and space to all non-first email addresses */ - *email_to_addrs++ = ','; - *email_to_addrs++ = ' '; - bytes_available -= 2; - } - else - done_addr = true; - - if (bytes_available < bytes_to_write) - break; - - strcpy(email_to_addrs, email->addr); - - email_to_addrs += bytes_to_write; - bytes_available -= bytes_to_write; - } -} - /* Main entry point */ void smtp_alert(smtp_msg_t msg_type, void* data, const char *subject, const char *body) @@ -656,8 +652,6 @@ smtp_alert(smtp_msg_t msg_type, void* data, const char *subject, const char *bod strncpy(smtp->body, body, MAX_BODY_LENGTH - 1); smtp->body[MAX_BODY_LENGTH - 1]= '\0'; - build_to_header_rcpt_addrs(smtp); - #ifdef _SMTP_ALERT_DEBUG_ if (do_smtp_alert_debug) smtp_log_to_file(smtp); diff --git a/keepalived/core/snmp.c b/keepalived/core/snmp.c index b5c351d9d2..0a44b449eb 100644 --- a/keepalived/core/snmp.c +++ b/keepalived/core/snmp.c @@ -328,6 +328,10 @@ snmp_mail(struct variable *vp, oid *name, size_t *length, { email_t *email; list_head_t *e; + struct { /* We need to cast aware const */ + u_char *uc; + const u_char *cuc; + } ret; if ((e = snmp_header_list_head_table(vp, name, length, exact, var_len, write_method, @@ -339,7 +343,8 @@ snmp_mail(struct variable *vp, oid *name, size_t *length, switch (vp->magic) { case SNMP_MAIL_EMAILADDRESS: *var_len = strlen(email->addr); - return PTR_CAST(u_char, email->addr); + ret.cuc = PTR_CAST_CONST(u_char, email->addr); + return ret.uc; default: break; } diff --git a/keepalived/include/global_data.h b/keepalived/include/global_data.h index a0bbbb08a1..80750bd56e 100644 --- a/keepalived/include/global_data.h +++ b/keepalived/include/global_data.h @@ -75,7 +75,7 @@ extern void _start(void), etext(void); /* email link list */ typedef struct _email { - char *addr; + const char *addr; /* Linked list member */ list_head_t e_list; @@ -298,6 +298,7 @@ extern data_t *global_data; /* Global configuration data */ extern data_t *old_global_data; /* Old global configuration data - used during reload */ /* Prototypes */ +extern const char * format_email_addr(const char *); extern void alloc_email(const char *); extern data_t *alloc_global_data(void); extern void init_global_data(data_t *, data_t *, bool); diff --git a/keepalived/include/smtp.h b/keepalived/include/smtp.h index f8a969e628..3045fdd363 100644 --- a/keepalived/include/smtp.h +++ b/keepalived/include/smtp.h @@ -91,21 +91,9 @@ typedef struct _smtp { char *subject; char *body; char *buffer; - char *email_to; size_t buflen; } smtp_t; -/* SMTP command string processing */ -#define SMTP_HELO_CMD "HELO %s\r\n" -#define SMTP_MAIL_CMD "MAIL FROM:<%s>\r\n" -#define SMTP_RCPT_CMD "RCPT TO:<%s>\r\n" -#define SMTP_DATA_CMD "DATA\r\n" -#define SMTP_HEADERS_CMD "Date: %s\r\nFrom: %s\r\nSubject: %s\r\n" \ - "X-Mailer: Keepalived\r\nTo: %s\r\n\r\n" -#define SMTP_BODY_CMD "%s\r\n" -#define SMTP_SEND_CMD "\r\n.\r\n" -#define SMTP_QUIT_CMD "QUIT\r\n" - #define FMT_SMTP_HOST() inet_sockaddrtopair(&global_data->smtp_server) #ifdef _WITH_LVS_