blob: 1a696d5f9a55f0dc387e82ddf6311020fd80a0b4 [file] [log] [blame]
/*
* Mailer management.
*
* Copyright 2015 Horms Solutions Ltd, Simon Horman <horms@verge.net.au>
* Copyright 2020 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <stdlib.h>
#include <haproxy/action-t.h>
#include <haproxy/api.h>
#include <haproxy/check.h>
#include <haproxy/errors.h>
#include <haproxy/list.h>
#include <haproxy/mailers.h>
#include <haproxy/pool.h>
#include <haproxy/proxy-t.h>
#include <haproxy/server-t.h>
#include <haproxy/task.h>
#include <haproxy/tcpcheck.h>
#include <haproxy/thread.h>
#include <haproxy/time.h>
#include <haproxy/tools.h>
struct mailers *mailers = NULL;
DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
/****************************** Email alerts ******************************/
/* NOTE: It may be pertinent to use an applet to handle email alerts */
/* instead of a tcp-check ruleset */
/**************************************************************************/
void email_alert_free(struct email_alert *alert)
{
struct tcpcheck_rule *rule, *back;
if (!alert)
return;
if (alert->rules.list) {
list_for_each_entry_safe(rule, back, alert->rules.list, list) {
LIST_DELETE(&rule->list);
free_tcpcheck(rule, 1);
}
free_tcpcheck_vars(&alert->rules.preset_vars);
ha_free(&alert->rules.list);
}
pool_free(pool_head_email_alert, alert);
}
static struct task *process_email_alert(struct task *t, void *context, unsigned int state)
{
struct check *check = context;
struct email_alertq *q;
struct email_alert *alert;
q = container_of(check, typeof(*q), check);
HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
while (1) {
if (!(check->state & CHK_ST_ENABLED)) {
if (LIST_ISEMPTY(&q->email_alerts)) {
/* All alerts processed, queue the task */
t->expire = TICK_ETERNITY;
task_queue(t);
goto end;
}
alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
LIST_DELETE(&alert->list);
t->expire = now_ms;
check->tcpcheck_rules = &alert->rules;
check->status = HCHK_STATUS_INI;
check->state |= CHK_ST_ENABLED;
}
process_chk(t, context, state);
if (check->state & CHK_ST_INPROGRESS)
break;
alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
email_alert_free(alert);
check->tcpcheck_rules = NULL;
check->server = NULL;
check->state &= ~CHK_ST_ENABLED;
}
end:
HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
return t;
}
/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
*
* The function returns 1 in success case, otherwise, it returns 0 and err is
* filled.
*/
int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
{
struct mailer *mailer;
struct email_alertq *queues;
const char *err_str;
int i = 0;
if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
memprintf(err, "out of memory while allocating mailer alerts queues");
goto fail_no_queue;
}
for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
struct email_alertq *q = &queues[i];
struct check *check = &q->check;
struct task *t;
LIST_INIT(&q->email_alerts);
HA_SPIN_INIT(&q->lock);
check->obj_type = OBJ_TYPE_CHECK;
check->inter = mls->timeout.mail;
check->rise = DEF_AGENT_RISETIME;
check->proxy = p;
check->fall = DEF_AGENT_FALLTIME;
if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
memprintf(err, "%s", err_str);
goto error;
}
check->xprt = mailer->xprt;
check->addr = mailer->addr;
check->port = get_host_port(&mailer->addr);
if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
memprintf(err, "out of memory while allocating mailer alerts task");
goto error;
}
check->task = t;
t->process = process_email_alert;
t->context = check;
/* check this in one ms */
t->expire = TICK_ETERNITY;
check->start = now;
task_queue(t);
}
mls->users++;
free(p->email_alert.mailers.name);
p->email_alert.mailers.m = mls;
p->email_alert.queues = queues;
return 0;
error:
for (i = 0; i < mls->count; i++) {
struct email_alertq *q = &queues[i];
struct check *check = &q->check;
free_check(check);
}
free(queues);
fail_no_queue:
return 1;
}
static int enqueue_one_email_alert(struct proxy *p, struct server *s,
struct email_alertq *q, const char *msg)
{
struct email_alert *alert;
struct tcpcheck_rule *tcpcheck;
struct check *check = &q->check;
if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
goto error;
LIST_INIT(&alert->list);
alert->rules.flags = TCPCHK_RULES_TCP_CHK;
alert->rules.list = calloc(1, sizeof(*alert->rules.list));
if (!alert->rules.list)
goto error;
LIST_INIT(alert->rules.list);
LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
alert->srv = s;
if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
goto error;
tcpcheck->action = TCPCHK_ACT_CONNECT;
tcpcheck->comment = NULL;
LIST_APPEND(alert->rules.list, &tcpcheck->list);
if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
goto error;
{
const char * const strs[4] = { "HELO ", p->email_alert.myhostname, "\r\n" };
if (!add_tcpcheck_send_strs(&alert->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
goto error;
{
const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
if (!add_tcpcheck_send_strs(&alert->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
goto error;
{
const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
if (!add_tcpcheck_send_strs(&alert->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
goto error;
{
const char * const strs[2] = { "DATA\r\n" };
if (!add_tcpcheck_send_strs(&alert->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
goto error;
{
struct tm tm;
char datestr[48];
const char * const strs[18] = {
"From: ", p->email_alert.from, "\r\n",
"To: ", p->email_alert.to, "\r\n",
"Date: ", datestr, "\r\n",
"Subject: [HAProxy Alert] ", msg, "\r\n",
"\r\n",
msg, "\r\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->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
goto error;
{
const char * const strs[2] = { "QUIT\r\n" };
if (!add_tcpcheck_send_strs(&alert->rules, strs))
goto error;
}
if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
goto error;
HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
task_wakeup(check->task, TASK_WOKEN_MSG);
LIST_APPEND(&q->email_alerts, &alert->list);
HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
return 1;
error:
email_alert_free(alert);
return 0;
}
static void enqueue_email_alert(struct proxy *p, struct server *s, 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, s, &p->email_alert.queues[i], msg)) {
ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
return;
}
}
return;
}
void make_literal(char const *input, char *output) {
// the following two arrays must be maintained in matching order:
static char inputs[] = "\a\b\f\n\r\t\v\\\"\'";
static char outputs[] = "abfnrtv\\\"\'";
char *pos;
for (;*input;input++) {
if (NULL!= (pos=strchr(inputs, *input))) {
*output++ = '\\';
*output++ = outputs[pos-inputs];
}
else
*output++ = *input;
}
*output = '\0';
}
void Emaila(const char *fmt, ...)
{
va_list argp;
char tmp[256];
char buf[500];
char alertcmd[1024];
int sysreturn;
size_t len = 0, len1 = 0, len2 = 0, len3 = 0;
if (global.email_alert) {
va_start(argp, fmt);
vsnprintf(tmp,128,fmt, argp);
make_literal(tmp, buf);
len1 = strlen(global.email_to[0]);
len2 = strlen(global.email_to[1]);
len3 = strlen(global.email_to[2]);
memcpy(tmp, global.email_to[0], len1);
tmp[len1] = ' ';
len = len1 + 1;
if (len2 > 0) {
memcpy(tmp + len, global.email_to[1], len2); // includes terminating null
len += len2;
tmp[len] = ' ';
len += 1;
if (len3 > 0) {
memcpy(tmp + len, global.email_to[2], len3); // includes terminating null
len += len3;
tmp[len] = ' ';
len += 1;
}
}
tmp[len] = '\0';
snprintf(alertcmd, 1024, "echo \"%s\" | mail -s \"$(echo -e \"[HAProxy alert %s] %.60s...\nFrom:HAProxy <%s>\n\")\" %s&", buf, global.email_from, buf, global.email_from, tmp);
sysreturn = system(alertcmd);
if (sysreturn == -1) {
ha_warning("There was an error sending the email alert");
}
vfprintf(stderr, fmt, argp);
fflush(stderr);
va_end(argp);
}
}
/*
* Send email alert if configured.
*/
void send_email_alert(struct server *s, int level, const char *format, ...)
{
va_list argp;
char buf[1024];
int len;
struct proxy *p = s->proxy;
if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
return;
va_start(argp, format);
len = vsnprintf(buf, sizeof(buf), format, argp);
va_end(argp);
if (len < 0 || len >= sizeof(buf)) {
ha_alert("Email alert [%s] could not format message\n", p->id);
return;
}
enqueue_email_alert(p, s, buf);
}