Signed-off-by: Simon Horman <ho...@verge.net.au> --- include/proto/checks.h | 2 + include/types/checks.h | 2 +- include/types/proxy.h | 18 ++- src/cfgparse.c | 26 ++-- src/checks.c | 321 +++++++++++++++++++++++++++++++++++++++++++++++++ src/server.c | 1 + 6 files changed, 356 insertions(+), 14 deletions(-)
diff --git a/include/proto/checks.h b/include/proto/checks.h index 24dec79..b4faed0 100644 --- a/include/proto/checks.h +++ b/include/proto/checks.h @@ -47,6 +47,8 @@ static inline void health_adjust(struct server *s, short status) const char *init_check(struct check *check, int type); void free_check(struct check *check); +void send_email_alert(struct server *s, const char *format, ...) + __attribute__ ((format(printf, 2, 3))); #endif /* _PROTO_CHECKS_H */ /* diff --git a/include/types/checks.h b/include/types/checks.h index 8162a06..4b35d30 100644 --- a/include/types/checks.h +++ b/include/types/checks.h @@ -181,7 +181,7 @@ struct check { char **envp; /* the environment to use if running a process-based check */ struct pid_list *curpid; /* entry in pid_list used for current process-based test, or -1 if not in test */ struct protocol *proto; /* server address protocol for health checks */ - struct sockaddr_storage addr; /* the address to check, if different from <addr> */ + struct sockaddr_storage addr; /* the address to check */ }; struct check_status { diff --git a/include/types/proxy.h b/include/types/proxy.h index 72d1024..230b804 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -208,6 +208,19 @@ struct error_snapshot { char buf[BUFSIZE]; /* copy of the beginning of the message */ }; +struct email_alert { + struct list list; + struct list tcpcheck_rules; +}; + +struct email_alertq { + struct list email_alerts; + struct check check; /* Email alerts are implemented using existing check + * code even though they are not checks. This structure + * is as a parameter to the check code. + * Each check corresponds to a mailer */ +}; + struct proxy { enum obj_type obj_type; /* object type == OBJ_TYPE_PROXY */ enum pr_state state; /* proxy state, one of PR_* */ @@ -386,9 +399,10 @@ struct proxy { struct mailers *m; /* Mailer to send email alerts via */ char *name; } mailers; - char *from; /* Address to send email allerts from */ - char *to; /* Address(es) to send email allerts to */ + char *from; /* Address to send email alerts from */ + char *to; /* Address(es) to send email alerts to */ char *myhostname; /* Identity to use in HELO command sent to mailer */ + struct email_alertq *queues; /* per-mailer alerts queues */ } email_alert; }; diff --git a/src/cfgparse.c b/src/cfgparse.c index de94074..3af0449 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2019,8 +2019,8 @@ int cfg_parse_mailers(const char *file, int linenum, char **args, int kwm) } proto = protocol_by_family(sk->ss_family); - if (!proto || !proto->connect) { - Alert("parsing [%s:%d] : '%s %s' : connect() not supported for this address family.\n", + if (!proto || !proto->connect || proto->sock_prot != IPPROTO_TCP) { + Alert("parsing [%s:%d] : '%s %s' : TCP not supported for this address family.\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; @@ -6607,15 +6607,19 @@ int check_config_validity() } } - if ( - (curproxy->email_alert.mailers.name || curproxy->email_alert.from || curproxy->email_alert.myhostname || curproxy->email_alert.to) && - !(curproxy->email_alert.mailers.name && curproxy->email_alert.from && curproxy->email_alert.to)) { - Warning("config : 'email-alert' will be ignored for %s '%s' (the presence any of " - "'email-alert from', 'email-alert mailer', 'email-alert hostname' or 'email-alert to' requrires each of" - "'email-alert from', 'email-alert mailer' and 'email-alert to' to be present).\n", - proxy_type_str(curproxy), curproxy->id); - err_code |= ERR_WARN; - free_email_alert(curproxy); + if ((curproxy->email_alert.mailers.name || curproxy->email_alert.from || + curproxy->email_alert.myhostname || curproxy->email_alert.to)) { + if (!(curproxy->email_alert.mailers.name && curproxy->email_alert.from && curproxy->email_alert.to)) { + Warning("config : 'email-alert' will be ignored for %s '%s' (the presence any of " + "'email-alert from', 'email-alert mailer', 'email-alert hostname' or 'email-alert to' " + "requrires each of 'email-alert from', 'email-alert mailer' and 'email-alert' " + "to be present).\n", + proxy_type_str(curproxy), curproxy->id); + err_code |= ERR_WARN; + free_email_alert(curproxy); + } + if (!curproxy->email_alert.myhostname) + curproxy->email_alert.myhostname = hostname; } if (curproxy->check_command) { diff --git a/src/checks.c b/src/checks.c index 0f99d47..ea167de 100644 --- a/src/checks.c +++ b/src/checks.c @@ -16,6 +16,7 @@ #include <errno.h> #include <fcntl.h> #include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -36,6 +37,7 @@ #include <common/time.h> #include <types/global.h> +#include <types/mailers.h> #ifdef USE_OPENSSL #include <types/ssl_sock.h> @@ -315,6 +317,7 @@ static void set_server_check_status(struct check *check, short status, const cha Warning("%s.\n", trash.str); send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.str); + send_email_alert(s, "%s", trash.str); } } @@ -2814,6 +2817,324 @@ void free_check(struct check *check) free(check->conn); } +void email_alert_free(struct email_alert *alert) +{ + struct tcpcheck_rule *rule, *back; + + if (!alert) + return; + + list_for_each_entry_safe(rule, back, &alert->tcpcheck_rules, list) + free(rule); + free(alert); +} + +static struct task *process_email_alert(struct task *t) +{ + struct check *check = t->context; + struct email_alertq *q; + + q = container_of(check, typeof(*q), check); + + if (!(check->state & CHK_ST_ENABLED)) { + if (LIST_ISEMPTY(&q->email_alerts)) { + /* All alerts processed, delete check */ + task_delete(t); + task_free(t); + check->task = NULL; + return NULL; + } else { + struct email_alert *alert; + + alert = LIST_NEXT(&q->email_alerts, typeof(alert), list); + check->tcpcheck_rules = &alert->tcpcheck_rules; + LIST_DEL(&alert->list); + + check->state |= CHK_ST_ENABLED; + } + + } + + process_chk(t); + + if (!(check->state & CHK_ST_INPROGRESS) && check->tcpcheck_rules) { + struct email_alert *alert; + + alert = container_of(check->tcpcheck_rules, typeof(*alert), tcpcheck_rules); + email_alert_free(alert); + + check->tcpcheck_rules = NULL; + check->state &= ~CHK_ST_ENABLED; + } + return t; +} + +static int init_email_alert_checks(struct server *s) +{ + int i; + struct mailer *mailer; + const char *err_str; + struct proxy *p = s->proxy; + + if (p->email_alert.queues) + /* Already initialised, nothing to do */ + return 1; + + p->email_alert.queues = calloc(p->email_alert.mailers.m->count, sizeof *p->email_alert.queues); + if (!p->email_alert.queues) { + err_str = "out of memory while allocating checks array"; + goto error_alert; + } + + for (i = 0, mailer = p->email_alert.mailers.m->mailer_list; + i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) { + struct email_alertq *q = &p->email_alert.queues[i]; + struct check *check = &q->check; + + + LIST_INIT(&q->email_alerts); + + check->inter = DEF_CHKINTR; /* XXX: Would like to Skip to the next alert, if any, ASAP. + * But need enough time so that timeouts don't occur + * during tcp check procssing. For now just us an arbitrary default. */ + check->rise = DEF_AGENT_RISETIME; + check->fall = DEF_AGENT_FALLTIME; + err_str = init_check(check, PR_O2_TCPCHK_CHK); + if (err_str) { + goto error_free; + } + + check->xprt = mailer->xprt; + if (!get_host_port(&mailer->addr)) + /* Default to submission port */ + check->port = 587; + check->proto = mailer->proto; + check->addr = mailer->addr; + check->server = s; + } + + return 1; + +error_free: + while (i-- > 1) + task_free(p->email_alert.queues[i].check.task); + free(p->email_alert.queues); + p->email_alert.queues = NULL; +error_alert: + Alert("Email alert [%s] could not be initialised: %s\n", p->id, err_str); + return 0; +} + + +static int add_tcpcheck_expect_str(struct list *list, const char *str) +{ + struct tcpcheck_rule *tcpcheck; + + tcpcheck = calloc(1, sizeof *tcpcheck); + if (!tcpcheck) + return 0; + + tcpcheck->action = TCPCHK_ACT_EXPECT; + tcpcheck->string = strdup(str); + if (!tcpcheck->string) { + free(tcpcheck); + return 0; + } + + LIST_ADDQ(list, &tcpcheck->list); + return 1; +} + +static int add_tcpcheck_send_strs(struct list *list, const char * const *strs) +{ + struct tcpcheck_rule *tcpcheck; + int i; + + tcpcheck = calloc(1, sizeof *tcpcheck); + if (!tcpcheck) + return 0; + + tcpcheck->action = TCPCHK_ACT_SEND; + + tcpcheck->string_len = 0; + for (i = 0; strs[i]; i++) + tcpcheck->string_len += strlen(strs[i]); + + tcpcheck->string = malloc(tcpcheck->string_len + 1); + if (!tcpcheck->string) { + free(tcpcheck); + return 0; + } + tcpcheck->string[0] = '\0'; + + for (i = 0; strs[i]; i++) + strcat(tcpcheck->string, strs[i]); + + LIST_ADDQ(list, &tcpcheck->list); + return 1; +} + +static int enqueue_one_email_alert(struct email_alertq *q, const char *msg) +{ + struct email_alert *alert = NULL; + struct tcpcheck_rule *tcpcheck; + struct check *check = &q->check; + struct proxy *p = check->server->proxy; + + alert = calloc(1, sizeof *alert); + if (!alert) { + goto error; + } + LIST_INIT(&alert->tcpcheck_rules); + + tcpcheck = calloc(1, sizeof *tcpcheck); + if (!tcpcheck) + goto error; + tcpcheck->action = TCPCHK_ACT_CONNECT; + LIST_ADDQ(&alert->tcpcheck_rules, &tcpcheck->list); + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "220 ")) + goto error; + + { + const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" }; + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "250 ")) + goto error; + + { + const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" }; + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "250 ")) + goto error; + + { + const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" }; + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "250 ")) + goto error; + + { + const char * const strs[2] = { "DATA\r\n" }; + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "354 ")) + goto error; + + { + struct tm tm; + char datestr[48]; + const char * const strs[18] = { + "From: ", p->email_alert.from, "\n", + "To: ", p->email_alert.to, "\n", + "Date: ", datestr, "\n", + "Subject: [HAproxy Alert] ", msg, "\n", + "\n", + msg, "\n", + ".\r\n", + "\r\n", + NULL + }; + + get_localtime(date.tv_sec, &tm); + + if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) { + goto error; + } + + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "250 ")) + goto error; + + { + const char * const strs[2] = { "QUIT\r\n" }; + if (!add_tcpcheck_send_strs(&alert->tcpcheck_rules, strs)) + goto error; + } + + if (!add_tcpcheck_expect_str(&alert->tcpcheck_rules, "221 ")) + goto error; + + if (!check->task) { + struct task *t; + + if ((t = task_new()) == NULL) + goto error; + + check->task = t; + t->process = process_email_alert; + t->context = check; + + /* check this in one ms */ + t->expire = tick_add(now_ms, MS_TO_TICKS(1)); + check->start = now; + task_queue(t); + } + + LIST_ADDQ(&q->email_alerts, &alert->list); + + return 1; + +error: + email_alert_free(alert); + return 0; +} + +static void enqueue_email_alert(struct proxy *p, const char *msg) +{ + int i; + struct mailer *mailer; + + for (i = 0, mailer = p->email_alert.mailers.m->mailer_list; + i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) { + if (!enqueue_one_email_alert(&p->email_alert.queues[i], msg)) { + Alert("Email alert [%s] could not be enqueued: out of memory\n", p->id); + return; + } + } + + return; +} + +/* + * Send email alert if configured. + */ +void send_email_alert(struct server *s, const char *format, ...) +{ + va_list argp; + char buf[1024]; + int len; + struct proxy *p = s->proxy; + + if (!p->email_alert.mailers.m || format == NULL || !init_email_alert_checks(s)) + return; + + va_start(argp, format); + len = vsnprintf(buf, sizeof(buf), format, argp); + va_end(argp); + + if (len < 0) { + Alert("Email alert [%s] could format message\n", p->id); + return; + } + + enqueue_email_alert(p, buf); +} + /* * Local variables: diff --git a/src/server.c b/src/server.c index 9a3bf23..6acd332 100644 --- a/src/server.c +++ b/src/server.c @@ -255,6 +255,7 @@ void srv_set_stopped(struct server *s, const char *reason) "%sServer %s/%s is DOWN", s->flags & SRV_F_BACKUP ? "Backup " : "", s->proxy->id, s->id); + send_email_alert(s, "%s.", trash.str); srv_append_status(&trash, s, reason, xferred, 0); Warning("%s.\n", trash.str); -- 2.1.4