1 /*
2 * Copyright (C) Tildeslash Ltd. All rights reserved.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU Affero General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *
15 * In addition, as a special exception, the copyright holders give
16 * permission to link the code of portions of this program with the
17 * OpenSSL library under certain conditions as described in each
18 * individual source file, and distribute linked combinations
19 * including the two.
20 *
21 * You must obey the GNU Affero General Public License in all respects
22 * for all of the code used other than OpenSSL.
23 */
24
25 #include "config.h"
26
27 #ifdef HAVE_STDIO_H
28 #include <stdio.h>
29 #endif
30
31 #ifdef HAVE_STDLIB_H
32 #include <stdlib.h>
33 #endif
34
35 #ifdef HAVE_STDARG_H
36 #include <stdarg.h>
37 #endif
38
39 #ifdef HAVE_STRING_H
40 #include <string.h>
41 #endif
42
43 #ifdef HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46
47 #ifdef HAVE_SIGNAL_H
48 #include <signal.h>
49 #endif
50
51 #include "monit.h"
52 #include "event.h"
53 #include "alert.h"
54 #include "SMTP.h"
55
56 // libmonit
57 #include "system/Time.h"
58 #include "util/Str.h"
59 #include "exceptions/IOException.h"
60
61
62 /**
63 * Implementation of the alert module
64 *
65 * @file
66 */
67
68
69 /* ----------------------------------------------------------------- Private */
70
71
72 // If the host is not set already (cached), translate system hostname to FQDN or fallback to plain system hostname if failed
_getFQDNhostname(char host[256])73 static char *_getFQDNhostname(char host[256]) {
74 assert(host);
75 if (STR_UNDEF(host)) {
76 struct addrinfo *result = NULL, hints = {
77 .ai_family = AF_UNSPEC,
78 .ai_flags = AI_CANONNAME,
79 .ai_socktype = SOCK_STREAM
80 };
81 int status = getaddrinfo(Run.system->name, NULL, &hints, &result);
82 if (status == 0) {
83 for (struct addrinfo *r = result; r; r = r->ai_next) {
84 if (Str_startsWith(r->ai_canonname, Run.system->name)) {
85 strncpy(host, r->ai_canonname, 255);
86 break;
87 }
88 }
89 freeaddrinfo(result);
90 } else {
91 Log_warning("Cannot translate '%s' to FQDN name, please set a sender address using 'set mail-format' -- %s\n", Run.system->name, status == EAI_SYSTEM ? STRERROR : gai_strerror(status));
92 }
93 if (STR_UNDEF(host)) {
94 // Fallback
95 strncpy(host, Run.system->name, 255);
96 }
97 }
98 return host;
99 }
100
101
_substitute(Mail_T m,Event_T e)102 static void _substitute(Mail_T m, Event_T e) {
103 ASSERT(m);
104 ASSERT(e);
105
106 if (Str_sub(m->from->name, "$HOST"))
107 Util_replaceString(&m->from->name, "$HOST", _getFQDNhostname(m->host));
108 if (Str_sub(m->from->address, "$HOST"))
109 Util_replaceString(&m->from->address, "$HOST", _getFQDNhostname(m->host));
110
111 Util_replaceString(&m->subject, "$HOST", Run.system->name);
112 Util_replaceString(&m->message, "$HOST", Run.system->name);
113
114 char timestamp[26];
115 Time_string(e->collected.tv_sec, timestamp);
116 Util_replaceString(&m->subject, "$DATE", timestamp);
117 Util_replaceString(&m->message, "$DATE", timestamp);
118
119 Util_replaceString(&m->subject, "$SERVICE", e->source->name);
120 Util_replaceString(&m->message, "$SERVICE", e->source->name);
121
122 const char *description = Event_get_description(e);
123 Util_replaceString(&m->subject, "$EVENT", description);
124 Util_replaceString(&m->message, "$EVENT", description);
125
126 const char *message = NVLSTR(e->message);
127 Util_replaceString(&m->subject, "$DESCRIPTION", message);
128 Util_replaceString(&m->message, "$DESCRIPTION", message);
129
130 const char *action = Event_get_action_description(e);
131 Util_replaceString(&m->subject, "$ACTION", action);
132 Util_replaceString(&m->message, "$ACTION", action);
133 }
134
135
_escape(Mail_T m)136 static void _escape(Mail_T m) {
137 // replace bare linefeed
138 Util_replaceString(&m->message, "\r\n", "\n");
139 Util_replaceString(&m->message, "\n", "\r\n");
140 // escape ^.
141 Util_replaceString(&m->message, "\n.", "\n..");
142 // drop any CR|LF from the subject
143 Str_chomp(m->subject);
144 }
145
146
_copyMail(Mail_T n,Mail_T o)147 static void _copyMail(Mail_T n, Mail_T o) {
148 ASSERT(n);
149 ASSERT(o);
150
151 n->to = Str_dup(o->to);
152 if (o->from) {
153 n->from = Address_copy(o->from);
154 } else if (Run.MailFormat.from) {
155 n->from = Address_copy(Run.MailFormat.from);
156 } else {
157 n->from = Address_new();
158 n->from->address = Str_dup(ALERT_FROM);
159 }
160 n->replyto = o->replyto ? Address_copy(o->replyto) : Run.MailFormat.replyto ? Address_copy(Run.MailFormat.replyto) : NULL;
161 n->subject = o->subject ? Str_dup(o->subject) : Run.MailFormat.subject ? Str_dup(Run.MailFormat.subject) : Str_dup(ALERT_SUBJECT);
162 n->message = o->message ? Str_dup(o->message) : Run.MailFormat.message ? Str_dup(Run.MailFormat.message) : Str_dup(ALERT_MESSAGE);
163 }
164
165
166 // Append the alert to a notification list IFF:
167 // 1) is the given event type allowed for this recipient?
168 // 2a) state change notifications is always delivered
169 // 2b) failure notification is sent only of it matches reminder settings
_appendMail(List_T list,Mail_T m,Event_T e,char * host)170 static void _appendMail(List_T list, Mail_T m, Event_T e, char *host) {
171 if (IS_EVENT_SET(m->events, e->id) && (e->state_changed || (e->state && m->reminder && e->count % m->reminder == 0))) {
172 Mail_T tmp = NULL;
173 NEW(tmp);
174 tmp->host = host;
175 _copyMail(tmp, m);
176 _substitute(tmp, e);
177 _escape(tmp);
178 List_append(list, tmp);
179 DEBUG("Sending %s notification to %s\n", Event_get_description(e), m->to);
180 }
181 }
182
183
_connectMTA(void)184 static MailServer_T _connectMTA(void) {
185 if (! Run.mailservers)
186 THROW(IOException, "No mail servers are defined -- please see the 'set mailserver' statement in the manual");
187 MailServer_T mta = NULL;
188 for (mta = Run.mailservers; mta; mta = mta->next) {
189 DEBUG("Trying to send mail via %s:%i\n", mta->host, mta->port);
190 if (mta->ssl.flags == SSL_Enabled)
191 mta->socket = Socket_create(mta->host, mta->port, Socket_Tcp, Socket_Ip, &(mta->ssl), Run.mailserver_timeout);
192 else
193 mta->socket = Socket_new(mta->host, mta->port, Socket_Tcp, Socket_Ip, SSL_Disabled, Run.mailserver_timeout);
194 if (mta->socket)
195 break;
196 else
197 Log_error("Cannot open a connection to the mailserver %s:%i -- %s\n", mta->host, mta->port, STRERROR);
198 }
199 if (! mta || ! mta->socket)
200 THROW(IOException, "Delivery failed -- no mail server is available");
201 return mta;
202 }
203
204
_send(List_T list)205 static bool _send(List_T list) {
206 volatile bool failed = false;
207 if (List_length(list)) {
208 volatile Mail_T m = NULL;
209 volatile SMTP_T smtp = NULL;
210 volatile MailServer_T mta = NULL;
211 TRY
212 {
213 mta = _connectMTA();
214 smtp = SMTP_new(mta->socket);
215 SMTP_greeting(smtp);
216 SMTP_helo(smtp, Run.mail_hostname ? Run.mail_hostname : Run.system->name);
217 if (mta->ssl.flags == SSL_StartTLS)
218 SMTP_starttls(smtp, &(mta->ssl));
219 if (mta->username && mta->password)
220 SMTP_auth(smtp, mta->username, mta->password);
221 char now[STRLEN];
222 Time_gmtstring(Time_now(), now);
223 while ((m = List_pop(list))) {
224 SMTP_from(smtp, m->from->address);
225 SMTP_to(smtp, m->to);
226 SMTP_dataBegin(smtp);
227 if (
228 (m->replyto && ((m->replyto->name ? Socket_print(mta->socket, "Reply-To: \"%s\" <%s>\r\n", m->replyto->name, m->replyto->address) : Socket_print(mta->socket, "Reply-To: %s\r\n", m->replyto->address)) <= 0))
229 ||
230 ((m->from->name ? Socket_print(mta->socket, "From: \"%s\" <%s>\r\n", m->from->name, m->from->address) : Socket_print(mta->socket, "From: %s\r\n", m->from->address)) <= 0)
231 ||
232 Socket_print(mta->socket,
233 "To: %s\r\n"
234 "Subject: %s\r\n"
235 "Date: %s\r\n"
236 "X-Mailer: Monit %s\r\n"
237 "MIME-Version: 1.0\r\n"
238 "Content-Type: text/plain; charset=utf-8\r\n"
239 "Content-Transfer-Encoding: 8bit\r\n"
240 "Message-Id: <%lld.%llx@%s>\r\n"
241 "\r\n"
242 "%s",
243 m->to,
244 m->subject,
245 now,
246 VERSION,
247 (long long)Time_now(), System_randomNumber(), Run.mail_hostname ? Run.mail_hostname : Run.system->name,
248 m->message) <= 0
249 )
250 {
251 THROW(IOException, "Error sending data to mail server %s -- %s", mta->host, STRERROR);
252 }
253 SMTP_dataCommit(smtp);
254 gc_mail_list((Mail_T *)&m);
255 }
256 SMTP_quit(smtp);
257 }
258 ELSE
259 {
260 failed = true;
261 Log_error("Mail: %s\n", Exception_frame.message);
262 }
263 FINALLY
264 {
265 if (m)
266 gc_mail_list((Mail_T *)&m);
267 if (smtp)
268 SMTP_free((SMTP_T *)&smtp);
269 if (mta && mta->socket)
270 Socket_free(&(mta->socket));
271 }
272 END_TRY;
273 }
274 return failed;
275 }
276
277
_hasRecipient(Mail_T list,const char * recipient)278 static bool _hasRecipient(Mail_T list, const char *recipient) {
279 for (Mail_T l = list; l; l = l->next)
280 if (IS(recipient, l->to))
281 return true;
282 return false;
283 }
284
285
286 /* ------------------------------------------------------------------ Public */
287
288
289 /**
290 * Notify registered users about the event
291 * @param E An Event object
292 * @return If failed, return Handler_Alert flag or Handler_Succeeded if succeeded
293 */
handle_alert(Event_T E)294 Handler_Type handle_alert(Event_T E) {
295 ASSERT(E);
296
297 Handler_Type rv = Handler_Succeeded;
298 Service_T s = E->source;
299 if (s->maillist || Run.maillist) {
300 char host[256] = {};
301 List_T list = List_new();
302 // Build a mail-list with local recipients that has registered interest for this event
303 for (Mail_T m = s->maillist; m; m = m->next)
304 _appendMail(list, m, E, host);
305 // Build a mail-list with global recipients that has registered interest for this event. Recipients which are defined in the service localy overrides the same recipient events which are registered globaly.
306 for (Mail_T m = Run.maillist; m; m = m->next)
307 if (! _hasRecipient(s->maillist, m->to))
308 _appendMail(list, m, E, host);
309 if (List_length(list))
310 if (_send(list))
311 rv = Handler_Alert;
312 List_free(&list);
313 }
314 return rv;
315 }
316
317