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