1 /*
2  * Mailer management.
3  *
4  * Copyright 2015 Horms Solutions Ltd, Simon Horman <horms@verge.net.au>
5  * Copyright 2020 Willy Tarreau <w@1wt.eu>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version
10  * 2 of the License, or (at your option) any later version.
11  *
12  */
13 
14 #include <stdlib.h>
15 
16 #include <haproxy/action-t.h>
17 #include <haproxy/api.h>
18 #include <haproxy/check.h>
19 #include <haproxy/errors.h>
20 #include <haproxy/list.h>
21 #include <haproxy/mailers.h>
22 #include <haproxy/pool.h>
23 #include <haproxy/proxy-t.h>
24 #include <haproxy/server-t.h>
25 #include <haproxy/task.h>
26 #include <haproxy/tcpcheck.h>
27 #include <haproxy/thread.h>
28 #include <haproxy/time.h>
29 #include <haproxy/tools.h>
30 
31 
32 struct mailers *mailers = NULL;
33 
34 DECLARE_STATIC_POOL(pool_head_email_alert,   "email_alert",   sizeof(struct email_alert));
35 
36 /****************************** Email alerts ******************************/
37 /* NOTE: It may be pertinent to use an applet to handle email alerts      */
38 /*        instead of a tcp-check ruleset                                  */
39 /**************************************************************************/
email_alert_free(struct email_alert * alert)40 void email_alert_free(struct email_alert *alert)
41 {
42 	struct tcpcheck_rule *rule, *back;
43 
44 	if (!alert)
45 		return;
46 
47 	if (alert->rules.list) {
48 		list_for_each_entry_safe(rule, back, alert->rules.list, list) {
49 			LIST_DELETE(&rule->list);
50 			free_tcpcheck(rule, 1);
51 		}
52 		free_tcpcheck_vars(&alert->rules.preset_vars);
53 		ha_free(&alert->rules.list);
54 	}
55 	pool_free(pool_head_email_alert, alert);
56 }
57 
process_email_alert(struct task * t,void * context,unsigned int state)58 static struct task *process_email_alert(struct task *t, void *context, unsigned int state)
59 {
60 	struct check        *check = context;
61 	struct email_alertq *q;
62 	struct email_alert  *alert;
63 
64 	q = container_of(check, typeof(*q), check);
65 
66 	HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
67 	while (1) {
68 		if (!(check->state & CHK_ST_ENABLED)) {
69 			if (LIST_ISEMPTY(&q->email_alerts)) {
70 				/* All alerts processed, queue the task */
71 				t->expire = TICK_ETERNITY;
72 				task_queue(t);
73 				goto end;
74 			}
75 
76 			alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
77 			LIST_DELETE(&alert->list);
78 			t->expire             = now_ms;
79 			check->tcpcheck_rules = &alert->rules;
80 			check->status         = HCHK_STATUS_INI;
81 			check->state         |= CHK_ST_ENABLED;
82 		}
83 
84 		process_chk(t, context, state);
85 		if (check->state & CHK_ST_INPROGRESS)
86 			break;
87 
88 		alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
89 		email_alert_free(alert);
90 		check->tcpcheck_rules = NULL;
91 		check->server         = NULL;
92 		check->state         &= ~CHK_ST_ENABLED;
93 	}
94   end:
95 	HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
96 	return t;
97 }
98 
99 /* Initializes mailer alerts for the proxy <p> using <mls> parameters.
100  *
101  * The function returns 1 in success case, otherwise, it returns 0 and err is
102  * filled.
103  */
init_email_alert(struct mailers * mls,struct proxy * p,char ** err)104 int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
105 {
106 	struct mailer       *mailer;
107 	struct email_alertq *queues;
108 	const char          *err_str;
109 	int                  i = 0;
110 
111 	if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
112 		memprintf(err, "out of memory while allocating mailer alerts queues");
113 		goto fail_no_queue;
114 	}
115 
116 	for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
117 		struct email_alertq *q     = &queues[i];
118 		struct check        *check = &q->check;
119 		struct task         *t;
120 
121 		LIST_INIT(&q->email_alerts);
122 		HA_SPIN_INIT(&q->lock);
123 		check->inter = mls->timeout.mail;
124 		check->rise = DEF_AGENT_RISETIME;
125 		check->proxy = p;
126 		check->fall = DEF_AGENT_FALLTIME;
127 		if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
128 			memprintf(err, "%s", err_str);
129 			goto error;
130 		}
131 
132 		check->xprt = mailer->xprt;
133 		check->addr = mailer->addr;
134 		check->port = get_host_port(&mailer->addr);
135 
136 		if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
137 			memprintf(err, "out of memory while allocating mailer alerts task");
138 			goto error;
139 		}
140 
141 		check->task = t;
142 		t->process = process_email_alert;
143 		t->context = check;
144 
145 		/* check this in one ms */
146 		t->expire    = TICK_ETERNITY;
147 		check->start = now;
148 		task_queue(t);
149 	}
150 
151 	mls->users++;
152 	free(p->email_alert.mailers.name);
153 	p->email_alert.mailers.m = mls;
154 	p->email_alert.queues    = queues;
155 	return 0;
156 
157   error:
158 	for (i = 0; i < mls->count; i++) {
159 		struct email_alertq *q     = &queues[i];
160 		struct check        *check = &q->check;
161 
162 		free_check(check);
163 	}
164 	free(queues);
165   fail_no_queue:
166 	return 1;
167 }
168 
enqueue_one_email_alert(struct proxy * p,struct server * s,struct email_alertq * q,const char * msg)169 static int enqueue_one_email_alert(struct proxy *p, struct server *s,
170 				   struct email_alertq *q, const char *msg)
171 {
172 	struct email_alert   *alert;
173 	struct tcpcheck_rule *tcpcheck;
174 	struct check *check = &q->check;
175 
176 	if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
177 		goto error;
178 	LIST_INIT(&alert->list);
179 	alert->rules.flags = TCPCHK_RULES_TCP_CHK;
180 	alert->rules.list = calloc(1, sizeof(*alert->rules.list));
181 	if (!alert->rules.list)
182 		goto error;
183 	LIST_INIT(alert->rules.list);
184 	LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
185 	alert->srv = s;
186 
187 	if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
188 		goto error;
189 	tcpcheck->action       = TCPCHK_ACT_CONNECT;
190 	tcpcheck->comment      = NULL;
191 
192 	LIST_APPEND(alert->rules.list, &tcpcheck->list);
193 
194 	if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
195 		goto error;
196 
197 	{
198 		const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
199 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
200 			goto error;
201 	}
202 
203 	if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
204 		goto error;
205 
206 	{
207 		const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
208 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
209 			goto error;
210 	}
211 
212 	if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
213 		goto error;
214 
215 	{
216 		const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
217 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
218 			goto error;
219 	}
220 
221 	if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
222 		goto error;
223 
224 	{
225 		const char * const strs[2] = { "DATA\r\n" };
226 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
227 			goto error;
228 	}
229 
230 	if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
231 		goto error;
232 
233 	{
234 		struct tm tm;
235 		char datestr[48];
236 		const char * const strs[18] = {
237 			"From: ", p->email_alert.from, "\r\n",
238 			"To: ", p->email_alert.to, "\r\n",
239 			"Date: ", datestr, "\r\n",
240 			"Subject: [HAProxy Alert] ", msg, "\r\n",
241 			"\r\n",
242 			msg, "\r\n",
243 			"\r\n",
244 			".\r\n",
245 			NULL
246 		};
247 
248 		get_localtime(date.tv_sec, &tm);
249 
250 		if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
251 			goto error;
252 		}
253 
254 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
255 			goto error;
256 	}
257 
258 	if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
259 		goto error;
260 
261 	{
262 		const char * const strs[2] = { "QUIT\r\n" };
263 		if (!add_tcpcheck_send_strs(&alert->rules, strs))
264 			goto error;
265 	}
266 
267 	if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
268 		goto error;
269 
270 	HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
271 	task_wakeup(check->task, TASK_WOKEN_MSG);
272 	LIST_APPEND(&q->email_alerts, &alert->list);
273 	HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
274 	return 1;
275 
276 error:
277 	email_alert_free(alert);
278 	return 0;
279 }
280 
enqueue_email_alert(struct proxy * p,struct server * s,const char * msg)281 static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
282 {
283 	int i;
284 	struct mailer *mailer;
285 
286 	for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
287 	     i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
288 		if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
289 			ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
290 			return;
291 		}
292 	}
293 
294 	return;
295 }
296 
297 /*
298  * Send email alert if configured.
299  */
send_email_alert(struct server * s,int level,const char * format,...)300 void send_email_alert(struct server *s, int level, const char *format, ...)
301 {
302 	va_list argp;
303 	char buf[1024];
304 	int len;
305 	struct proxy *p = s->proxy;
306 
307 	if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
308 		return;
309 
310 	va_start(argp, format);
311 	len = vsnprintf(buf, sizeof(buf), format, argp);
312 	va_end(argp);
313 
314 	if (len < 0 || len >= sizeof(buf)) {
315 		ha_alert("Email alert [%s] could not format message\n", p->id);
316 		return;
317 	}
318 
319 	enqueue_email_alert(p, s, buf);
320 }
321