1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26 /*
27 * Copyright (c) 2018, Joyent, Inc.
28 */
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <alloca.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <libscf.h>
37 #include <priv_utils.h>
38 #include <netdb.h>
39 #include <signal.h>
40 #include <strings.h>
41 #include <time.h>
42 #include <unistd.h>
43 #include <zone.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <fm/fmd_msg.h>
47 #include <fm/libfmevent.h>
48 #include "libfmnotify.h"
49
50 #define SENDMAIL "/usr/sbin/sendmail"
51 #define SVCNAME "system/fm/smtp-notify"
52
53 #define XHDR_HOSTNAME "X-FMEV-HOSTNAME"
54 #define XHDR_CLASS "X-FMEV-CLASS"
55 #define XHDR_UUID "X-FMEV-UUID"
56 #define XHDR_MSGID "X-FMEV-CODE"
57 #define XHDR_SEVERITY "X-FMEV-SEVERITY"
58 #define XHDR_FMRI "X-FMEV-FMRI"
59 #define XHDR_FROM_STATE "X-FMEV-FROM-STATE"
60 #define XHDR_TO_STATE "X-FMEV-TO-STATE"
61
62 /*
63 * Debug messages can be enabled by setting the debug property to true
64 *
65 * # svccfg -s svc:/system/fm/smtp-notify setprop config/debug=true
66 *
67 * Debug messages will be spooled to the service log at:
68 * <root>/var/svc/log/system-fm-smtp-notify:default.log
69 */
70 #define PP_SCRIPT "usr/lib/fm/notify/process_msg_template.sh"
71
72 typedef struct email_pref
73 {
74 int ep_num_recips;
75 char **ep_recips;
76 char *ep_reply_to;
77 char *ep_template_path;
78 char *ep_template;
79 } email_pref_t;
80
81 static nd_hdl_t *nhdl;
82 static char hostname[MAXHOSTNAMELEN + 1];
83 static const char optstr[] = "dfR:";
84 static const char DEF_SUBJ_TEMPLATE[] = "smtp-notify-subject-template";
85 static const char SMF_SUBJ_TEMPLATE[] = "smtp-notify-smf-subject-template";
86 static const char FM_SUBJ_TEMPLATE[] = "smtp-notify-fm-subject-template";
87 static const char IREPORT_MSG_TEMPLATE[] = "ireport-msg-template";
88 static const char SMF_MSG_TEMPLATE[] = "ireport.os.smf-msg-template";
89
90 static int
usage(const char * pname)91 usage(const char *pname)
92 {
93 (void) fprintf(stderr, "Usage: %s [-df] [-R <altroot>]\n", pname);
94
95 (void) fprintf(stderr,
96 "\t-d enable debug mode\n"
97 "\t-f stay in foreground\n"
98 "\t-R specify alternate root\n");
99
100 return (1);
101 }
102
103 /*
104 * This function simply reads the file specified by "template" into a buffer
105 * and returns a pointer to that buffer (or NULL on failure). The caller is
106 * responsible for free'ing the returned buffer.
107 */
108 static char *
read_template(const char * template)109 read_template(const char *template)
110 {
111 int fd;
112 struct stat statb;
113 char *buf;
114
115 if (stat(template, &statb) != 0) {
116 nd_error(nhdl, "Failed to stat %s (%s)", template,
117 strerror(errno));
118 return (NULL);
119 }
120 if ((fd = open(template, O_RDONLY)) < 0) {
121 nd_error(nhdl, "Failed to open %s (%s)", template,
122 strerror(errno));
123 return (NULL);
124 }
125 if ((buf = malloc(statb.st_size + 1)) == NULL) {
126 nd_error(nhdl, "Failed to allocate %d bytes", statb.st_size);
127 (void) close(fd);
128 return (NULL);
129 }
130 if (read(fd, buf, statb.st_size) < 0) {
131 nd_error(nhdl, "Failed to read in template (%s)",
132 strerror(errno));
133 free(buf);
134 (void) close(fd);
135 return (NULL);
136 }
137 buf[statb.st_size] = '\0';
138 (void) close(fd);
139 return (buf);
140 }
141
142 /*
143 * This function runs a user-supplied message body template through a script
144 * which replaces the "committed" expansion macros with actual libfmd_msg
145 * expansion macros.
146 */
147 static int
process_template(nd_ev_info_t * ev_info,email_pref_t * eprefs)148 process_template(nd_ev_info_t *ev_info, email_pref_t *eprefs)
149 {
150 char pp_script[PATH_MAX], tmpfile[PATH_MAX], pp_cli[PATH_MAX];
151 int ret = -1;
152
153 (void) snprintf(pp_script, sizeof (pp_script), "%s%s",
154 nhdl->nh_rootdir, PP_SCRIPT);
155 (void) snprintf(tmpfile, sizeof (tmpfile), "%s%s",
156 nhdl->nh_rootdir, tmpnam(NULL));
157
158 /*
159 * If it's an SMF event, then the diagcode and severity won't be part
160 * of the event payload and so libfmd_msg won't be able to expand them.
161 * Therefore we pass the code and severity into the script and let the
162 * script do the expansion.
163 */
164 /* LINTED: E_SEC_SPRINTF_UNBOUNDED_COPY */
165 (void) sprintf(pp_cli, "%s %s %s %s %s", pp_script,
166 eprefs->ep_template_path, tmpfile, ev_info->ei_diagcode,
167 ev_info->ei_severity);
168
169 nd_debug(nhdl, "Executing %s", pp_cli);
170 if (system(pp_cli) != -1)
171 if ((eprefs->ep_template = read_template(tmpfile)) != NULL)
172 ret = 0;
173
174 (void) unlink(tmpfile);
175 return (ret);
176 }
177
178 /*
179 * If someone does an "svcadm refresh" on us, then this function gets called,
180 * which rereads our service configuration.
181 */
182 static void
get_svc_config()183 get_svc_config()
184 {
185 int s = 0;
186 uint8_t val;
187
188 s = nd_get_boolean_prop(nhdl, SVCNAME, "config", "debug", &val);
189 nhdl->nh_debug = val;
190
191 s += nd_get_astring_prop(nhdl, SVCNAME, "config", "rootdir",
192 &(nhdl->nh_rootdir));
193
194 if (s != 0)
195 nd_error(nhdl, "Failed to read retrieve service "
196 "properties\n");
197 }
198
199 static void
nd_sighandler(int sig)200 nd_sighandler(int sig)
201 {
202 if (sig == SIGHUP)
203 get_svc_config();
204 else
205 nd_cleanup(nhdl);
206 }
207
208 /*
209 * This function constructs all the email headers and puts them into the
210 * "headers" buffer handle. The caller is responsible for free'ing this
211 * buffer.
212 */
213 static int
build_headers(nd_hdl_t * nhdl,nd_ev_info_t * ev_info,email_pref_t * eprefs,char ** headers)214 build_headers(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs,
215 char **headers)
216 {
217 const char *subj_key;
218 char *subj_fmt, *subj = NULL;
219 size_t len;
220 boolean_t is_smf_event = B_FALSE, is_fm_event = B_FALSE;
221
222 /*
223 * Fetch and format the email subject.
224 */
225 if (strncmp(ev_info->ei_class, "list.", 5) == 0) {
226 is_fm_event = B_TRUE;
227 subj_key = FM_SUBJ_TEMPLATE;
228 } else if (strncmp(ev_info->ei_class, "ireport.os.smf", 14) == 0) {
229 is_smf_event = B_TRUE;
230 subj_key = SMF_SUBJ_TEMPLATE;
231 } else {
232 subj_key = DEF_SUBJ_TEMPLATE;
233 }
234
235 if ((subj_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
236 FMNOTIFY_MSG_DOMAIN, subj_key)) == NULL) {
237 nd_error(nhdl, "Failed to contruct subject format");
238 return (-1); /* libfmd_msg error */
239 }
240
241 if (is_fm_event) {
242 /* LINTED: E_SEC_PRINTF_VAR_FMT */
243 len = snprintf(NULL, 0, subj_fmt, hostname,
244 ev_info->ei_diagcode);
245 subj = alloca(len + 1);
246 /* LINTED: E_SEC_PRINTF_VAR_FMT */
247 (void) snprintf(subj, len + 1, subj_fmt, hostname,
248 ev_info->ei_diagcode);
249 } else if (is_smf_event) {
250 /* LINTED: E_SEC_PRINTF_VAR_FMT */
251 len = snprintf(NULL, 0, subj_fmt, hostname, ev_info->ei_fmri,
252 ev_info->ei_from_state, ev_info->ei_to_state);
253 subj = alloca(len + 1);
254 /* LINTED: E_SEC_PRINTF_VAR_FMT */
255 (void) snprintf(subj, len + 1, subj_fmt, hostname,
256 ev_info->ei_fmri, ev_info->ei_from_state,
257 ev_info->ei_to_state);
258 } else {
259 /* LINTED: E_SEC_PRINTF_VAR_FMT */
260 len = snprintf(NULL, 0, subj_fmt, hostname);
261 subj = alloca(len + 1);
262 /* LINTED: E_SEC_PRINTF_VAR_FMT */
263 (void) snprintf(subj, len + 1, subj_fmt, hostname);
264 }
265
266 /*
267 * Here we add some X-headers to our mail message for use by mail
268 * filtering agents. We add headers for the following bits of event
269 * data for all events
270 *
271 * hostname
272 * msg id (diagcode)
273 * event class
274 * event severity
275 * event uuid
276 *
277 * For SMF transition events, we'll have the following add'l X-headers
278 *
279 * from-state
280 * to-state
281 * service fmri
282 *
283 * We follow the X-headers with standard Reply-To and Subject headers.
284 */
285 if (is_fm_event) {
286 len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
287 "%s: %s\nReply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
288 hostname, XHDR_CLASS, ev_info->ei_class, XHDR_UUID,
289 ev_info->ei_uuid, XHDR_MSGID, ev_info->ei_diagcode,
290 XHDR_SEVERITY, ev_info->ei_severity, eprefs->ep_reply_to,
291 subj);
292
293 *headers = calloc(len + 1, sizeof (char));
294
295 (void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
296 "%s: %s\n%s: %s\nReply-To: %s\nSubject: %s\n\n",
297 XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
298 XHDR_UUID, ev_info->ei_uuid, XHDR_MSGID,
299 ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
300 eprefs->ep_reply_to, subj);
301 } else if (is_smf_event) {
302 len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
303 "%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
304 "Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
305 ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
306 XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
307 ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
308 XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
309 subj);
310
311 *headers = calloc(len + 1, sizeof (char));
312
313 (void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
314 "%s: %s\n%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
315 "Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
316 ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
317 XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
318 ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
319 XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
320 subj);
321 } else {
322 len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
323 "Reply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
324 hostname, XHDR_CLASS, ev_info->ei_class, XHDR_MSGID,
325 ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
326 eprefs->ep_reply_to, subj);
327
328 *headers = calloc(len + 1, sizeof (char));
329
330 (void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
331 "%s: %s\nReply-To: %s\nSubject: %s\n\n",
332 XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
333 XHDR_MSGID, ev_info->ei_diagcode, XHDR_SEVERITY,
334 ev_info->ei_severity, eprefs->ep_reply_to, subj);
335 }
336 return (0);
337 }
338
339 static void
send_email(nd_hdl_t * nhdl,const char * headers,const char * body,const char * recip)340 send_email(nd_hdl_t *nhdl, const char *headers, const char *body,
341 const char *recip)
342 {
343 FILE *mp;
344 char sm_cli[PATH_MAX];
345
346 /*
347 * Open a pipe to sendmail and pump out the email message
348 */
349 (void) snprintf(sm_cli, PATH_MAX, "%s -t %s", SENDMAIL, recip);
350
351 nd_debug(nhdl, "Sending email notification to %s", recip);
352 if ((mp = popen(sm_cli, "w")) == NULL) {
353 nd_error(nhdl, "Failed to open pipe to %s (%s)", SENDMAIL,
354 strerror(errno));
355 return;
356 }
357 if (fprintf(mp, "%s", headers) < 0)
358 nd_error(nhdl, "Failed to write to pipe (%s)", strerror(errno));
359
360 if (fprintf(mp, "%s\n.\n", body) < 0)
361 nd_error(nhdl, "Failed to write to pipe (%s)",
362 strerror(errno));
363
364 (void) pclose(mp);
365 }
366
367 static void
send_email_template(nd_hdl_t * nhdl,nd_ev_info_t * ev_info,email_pref_t * eprefs)368 send_email_template(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs)
369 {
370 char *msg, *headers;
371
372 if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
373 return;
374
375 /*
376 * If the user specified a message body template, then we pass it
377 * through a private interface in libfmd_msg, which will return a string
378 * with any expansion tokens decoded.
379 */
380 if ((msg = fmd_msg_decode_tokens(ev_info->ei_payload,
381 eprefs->ep_template, ev_info->ei_url)) == NULL) {
382 nd_error(nhdl, "Failed to parse msg template");
383 free(headers);
384 return;
385 }
386 for (int i = 0; i < eprefs->ep_num_recips; i++)
387 send_email(nhdl, headers, msg, eprefs->ep_recips[i]);
388
389 free(msg);
390 free(headers);
391 }
392
393 static int
get_email_prefs(nd_hdl_t * nhdl,fmev_t ev,email_pref_t ** eprefs)394 get_email_prefs(nd_hdl_t *nhdl, fmev_t ev, email_pref_t **eprefs)
395 {
396 nvlist_t **p_nvl = NULL;
397 email_pref_t *ep;
398 uint_t npref, tn1 = 0, tn2 = 0;
399 char **tmparr1, **tmparr2;
400 int r, ret = -1;
401
402 r = nd_get_notify_prefs(nhdl, "smtp", ev, &p_nvl, &npref);
403 if (r == SCF_ERROR_NOT_FOUND) {
404 /*
405 * No email notification preferences specified for this type of
406 * event, so we're done
407 */
408 return (-1);
409 } else if (r != 0) {
410 nd_error(nhdl, "Failed to retrieve notification preferences "
411 "for this event");
412 return (-1);
413 }
414
415 if ((ep = malloc(sizeof (email_pref_t))) == NULL) {
416 nd_error(nhdl, "Failed to allocate space for email preferences "
417 "(%s)", strerror(errno));
418 goto eprefs_done;
419 }
420 (void) memset(ep, 0, sizeof (email_pref_t));
421
422 /*
423 * For SMF state transition events, pref_nvl may contain two sets of
424 * preferences, which will have to be merged.
425 *
426 * The "smtp" nvlist can contain up to four members:
427 *
428 * "active" - boolean - used to toggle notfications
429 * "to" - a string array of email recipients
430 * "reply-to" - a string array containing the reply-to addresses
431 * - this is optional and defaults to root@localhost
432 * "msg_template" - the pathname of a user-supplied message body
433 * template
434 *
435 * In the case that we have two sets of preferences, we will merge them
436 * using the following rules:
437 *
438 * "active" will be set to true, if it is true in either set
439 *
440 * The "reply-to" and "to" lists will be merged, with duplicate email
441 * addresses removed.
442 */
443 if (npref == 2) {
444 boolean_t *act1, *act2;
445 char **arr1, **arr2, **strarr, **reparr1, **reparr2;
446 uint_t n1, n2, arrsz, repsz;
447
448 r = nvlist_lookup_boolean_array(p_nvl[0], "active", &act1, &n1);
449 r += nvlist_lookup_boolean_array(p_nvl[1], "active", &act2,
450 &n2);
451 r += nvlist_lookup_string_array(p_nvl[0], "to", &arr1, &n1);
452 r += nvlist_lookup_string_array(p_nvl[1], "to", &arr2, &n2);
453
454 if (r != 0) {
455 nd_error(nhdl, "Malformed email notification "
456 "preferences");
457 nd_dump_nvlist(nhdl, p_nvl[0]);
458 nd_dump_nvlist(nhdl, p_nvl[1]);
459 goto eprefs_done;
460 } else if (!act1[0] && !act2[0]) {
461 nd_debug(nhdl, "Email notification is disabled");
462 goto eprefs_done;
463 }
464
465 if (nd_split_list(nhdl, arr1[0], ",", &tmparr1, &tn1) != 0 ||
466 nd_split_list(nhdl, arr2[0], ",", &tmparr2, &tn2) != 0) {
467 nd_error(nhdl, "Error parsing \"to\" lists");
468 nd_dump_nvlist(nhdl, p_nvl[0]);
469 nd_dump_nvlist(nhdl, p_nvl[1]);
470 goto eprefs_done;
471 }
472
473 if ((ep->ep_num_recips = nd_merge_strarray(nhdl, tmparr1, tn1,
474 tmparr2, tn2, &ep->ep_recips)) < 0) {
475 nd_error(nhdl, "Error merging email recipient lists");
476 goto eprefs_done;
477 }
478
479 r = nvlist_lookup_string_array(p_nvl[0], "reply-to", &arr1,
480 &n1);
481 r += nvlist_lookup_string_array(p_nvl[1], "reply-to", &arr2,
482 &n2);
483 repsz = n1 = n2 = 0;
484 if (!r &&
485 nd_split_list(nhdl, arr1[0], ",", &reparr1, &n1) != 0 ||
486 nd_split_list(nhdl, arr2[0], ",", &reparr2, &n2) != 0 ||
487 (repsz = nd_merge_strarray(nhdl, tmparr1, n1, tmparr2, n2,
488 &strarr)) != 0 ||
489 nd_join_strarray(nhdl, strarr, repsz, &ep->ep_reply_to)
490 != 0) {
491
492 ep->ep_reply_to = strdup("root@localhost");
493 }
494 if (n1)
495 nd_free_strarray(reparr1, n1);
496 if (n2)
497 nd_free_strarray(reparr2, n2);
498 if (repsz > 0)
499 nd_free_strarray(strarr, repsz);
500
501 if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
502 &strarr, &arrsz) == 0)
503 ep->ep_template_path = strdup(strarr[0]);
504 } else {
505 char **strarr, **tmparr;
506 uint_t arrsz;
507 boolean_t *active;
508
509 /*
510 * Both the "active" and "to" notification preferences are
511 * required, so if we have trouble looking either of these up
512 * we return an error. We will also return an error if "active"
513 * is set to false. Returning an error will cause us to not
514 * send a notification for this event.
515 */
516 r = nvlist_lookup_boolean_array(p_nvl[0], "active", &active,
517 &arrsz);
518 r += nvlist_lookup_string_array(p_nvl[0], "to", &strarr,
519 &arrsz);
520
521 if (r != 0) {
522 nd_error(nhdl, "Malformed email notification "
523 "preferences");
524 nd_dump_nvlist(nhdl, p_nvl[0]);
525 goto eprefs_done;
526 } else if (!active[0]) {
527 nd_debug(nhdl, "Email notification is disabled");
528 goto eprefs_done;
529 }
530
531 if (nd_split_list(nhdl, strarr[0], ",", &tmparr, &arrsz)
532 != 0) {
533 nd_error(nhdl, "Error parsing \"to\" list");
534 goto eprefs_done;
535 }
536 ep->ep_num_recips = arrsz;
537 ep->ep_recips = tmparr;
538
539 if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
540 &strarr, &arrsz) == 0)
541 ep->ep_template_path = strdup(strarr[0]);
542
543 if (nvlist_lookup_string_array(p_nvl[0], "reply-to", &strarr,
544 &arrsz) == 0)
545 ep->ep_reply_to = strdup(strarr[0]);
546 else
547 ep->ep_reply_to = strdup("root@localhost");
548 }
549 ret = 0;
550 *eprefs = ep;
551 eprefs_done:
552 if (ret != 0) {
553 if (ep->ep_recips)
554 nd_free_strarray(ep->ep_recips, ep->ep_num_recips);
555 if (ep->ep_reply_to)
556 free(ep->ep_reply_to);
557 free(ep);
558 }
559 if (tn1)
560 nd_free_strarray(tmparr1, tn1);
561 if (tn2)
562 nd_free_strarray(tmparr2, tn2);
563 nd_free_nvlarray(p_nvl, npref);
564
565 return (ret);
566 }
567
568 /*ARGSUSED*/
569 static void
irpt_cbfunc(fmev_t ev,const char * class,nvlist_t * nvl,void * arg)570 irpt_cbfunc(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
571 {
572 char *body_fmt, *headers = NULL, *body = NULL, tstamp[32];
573 struct tm ts;
574 size_t len;
575 nd_ev_info_t *ev_info = NULL;
576 email_pref_t *eprefs;
577
578 nd_debug(nhdl, "Received event of class %s", class);
579
580 if (get_email_prefs(nhdl, ev, &eprefs) < 0)
581 return;
582
583 if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
584 goto irpt_done;
585
586 /*
587 * If the user specified a template, then we pass it through a script,
588 * which post-processes any expansion macros. Then we attempt to read
589 * it in and then send the message. Otherwise we carry on with the rest
590 * of this function which will contruct the message body from one of the
591 * default templates.
592 */
593 if (eprefs->ep_template != NULL)
594 free(eprefs->ep_template);
595
596 if (eprefs->ep_template_path != NULL &&
597 process_template(ev_info, eprefs) == 0) {
598 send_email_template(nhdl, ev_info, eprefs);
599 goto irpt_done;
600 }
601
602 /*
603 * Fetch and format the event timestamp
604 */
605 if (fmev_localtime(ev, &ts) == NULL) {
606 nd_error(nhdl, "Malformed event: failed to retrieve "
607 "timestamp");
608 goto irpt_done;
609 }
610 (void) strftime(tstamp, sizeof (tstamp), NULL, &ts);
611
612 /*
613 * We have two message body templates to choose from. One for SMF
614 * service transition events and a generic one for any other
615 * uncommitted ireport.
616 */
617 if (strncmp(class, "ireport.os.smf", 14) == 0) {
618 /*
619 * For SMF state transition events we have a standard message
620 * template that we fill in based on the payload of the event.
621 */
622 if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
623 FMNOTIFY_MSG_DOMAIN, SMF_MSG_TEMPLATE)) == NULL) {
624 nd_error(nhdl, "Failed to format message body");
625 goto irpt_done;
626 }
627
628 /* LINTED: E_SEC_PRINTF_VAR_FMT */
629 len = snprintf(NULL, 0, body_fmt, hostname, tstamp,
630 ev_info->ei_fmri, ev_info->ei_from_state,
631 ev_info->ei_to_state, ev_info->ei_descr,
632 ev_info->ei_reason);
633 body = calloc(len, sizeof (char));
634 /* LINTED: E_SEC_PRINTF_VAR_FMT */
635 (void) snprintf(body, len, body_fmt, hostname, tstamp,
636 ev_info->ei_fmri, ev_info->ei_from_state,
637 ev_info->ei_to_state, ev_info->ei_descr,
638 ev_info->ei_reason);
639 } else {
640 if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
641 FMNOTIFY_MSG_DOMAIN, IREPORT_MSG_TEMPLATE)) == NULL) {
642 nd_error(nhdl, "Failed to format message body");
643 goto irpt_done;
644 }
645 /* LINTED: E_SEC_PRINTF_VAR_FMT */
646 len = snprintf(NULL, 0, body_fmt, hostname, tstamp, class);
647 body = calloc(len, sizeof (char));
648 /* LINTED: E_SEC_PRINTF_VAR_FMT */
649 (void) snprintf(body, len, body_fmt, hostname, tstamp, class);
650 }
651
652 if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
653 goto irpt_done;
654
655 /*
656 * Everything is ready, so now we just iterate through the list of
657 * recipents, sending an email notification to each one.
658 */
659 for (int i = 0; i < eprefs->ep_num_recips; i++)
660 send_email(nhdl, headers, body, eprefs->ep_recips[i]);
661
662 irpt_done:
663 free(headers);
664 free(body);
665 if (ev_info)
666 nd_free_event_info(ev_info);
667 if (eprefs->ep_recips)
668 nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
669 if (eprefs->ep_reply_to)
670 free(eprefs->ep_reply_to);
671 free(eprefs);
672 }
673
674 /*
675 * There is a lack of uniformity in how the various entries in our diagnosis
676 * are terminated. Some end with one newline, others with two. This makes the
677 * output look a bit ugly. Therefore we postprocess the message before sending
678 * it, removing consecutive occurences of newlines.
679 */
680 static void
postprocess_msg(char * msg)681 postprocess_msg(char *msg)
682 {
683 int i = 0, j = 0;
684 char *buf;
685
686 if ((buf = malloc(strlen(msg) + 1)) == NULL)
687 return;
688
689 buf[j++] = msg[i++];
690 for (i = 1; i < strlen(msg); i++) {
691 if (!(msg[i] == '\n' && msg[i - 1] == '\n'))
692 buf[j++] = msg[i];
693 }
694 buf[j] = '\0';
695 (void) strncpy(msg, buf, j+1);
696 free(buf);
697 }
698
699 /*ARGSUSED*/
700 static void
listev_cb(fmev_t ev,const char * class,nvlist_t * nvl,void * arg)701 listev_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
702 {
703 char *body = NULL, *headers = NULL;
704 nd_ev_info_t *ev_info = NULL;
705 boolean_t domsg;
706 email_pref_t *eprefs;
707
708 nd_debug(nhdl, "Received event of class %s", class);
709
710 if (get_email_prefs(nhdl, ev, &eprefs) < 0)
711 return;
712
713 if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
714 goto listcb_done;
715
716 /*
717 * If the message payload member is set to 0, then it's an event we
718 * typically suppress messaging on, so we won't send an email for it.
719 */
720 if (nvlist_lookup_boolean_value(ev_info->ei_payload, FM_SUSPECT_MESSAGE,
721 &domsg) == 0 && !domsg) {
722 nd_debug(nhdl, "Messaging suppressed for this event");
723 goto listcb_done;
724 }
725
726 /*
727 * If the user specified a template, then we pass it through a script,
728 * which post-processes any expansion macros. Then we attempt to read
729 * it in and then send the message. Otherwise we carry on with the rest
730 * of this function which will contruct the message body from one of the
731 * default templates.
732 */
733 if (eprefs->ep_template != NULL)
734 free(eprefs->ep_template);
735
736 if (eprefs->ep_template_path != NULL &&
737 process_template(ev_info, eprefs) == 0) {
738 send_email_template(nhdl, ev_info, eprefs);
739 goto listcb_done;
740 }
741
742 /*
743 * Format the message body
744 *
745 * For FMA list.* events we use the same message that the
746 * syslog-msgs agent would emit as the message body
747 *
748 */
749 if ((body = fmd_msg_gettext_nv(nhdl->nh_msghdl, NULL,
750 ev_info->ei_payload)) == NULL) {
751 nd_error(nhdl, "Failed to format message body");
752 nd_dump_nvlist(nhdl, ev_info->ei_payload);
753 goto listcb_done;
754 }
755 postprocess_msg(body);
756
757 if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
758 goto listcb_done;
759
760 /*
761 * Everything is ready, so now we just iterate through the list of
762 * recipents, sending an email notification to each one.
763 */
764 for (int i = 0; i < eprefs->ep_num_recips; i++)
765 send_email(nhdl, headers, body, eprefs->ep_recips[i]);
766
767 listcb_done:
768 free(headers);
769 free(body);
770 if (ev_info)
771 nd_free_event_info(ev_info);
772 if (eprefs->ep_recips)
773 nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
774 if (eprefs->ep_reply_to)
775 free(eprefs->ep_reply_to);
776 free(eprefs);
777 }
778
779 int
main(int argc,char * argv[])780 main(int argc, char *argv[])
781 {
782 struct rlimit rlim;
783 struct sigaction act;
784 sigset_t set;
785 int c;
786 boolean_t run_fg = B_FALSE;
787
788 if ((nhdl = malloc(sizeof (nd_hdl_t))) == NULL) {
789 (void) fprintf(stderr, "Failed to allocate space for notifyd "
790 "handle (%s)", strerror(errno));
791 return (1);
792 }
793 (void) memset(nhdl, 0, sizeof (nd_hdl_t));
794
795 nhdl->nh_keep_running = B_TRUE;
796 nhdl->nh_log_fd = stderr;
797 nhdl->nh_pname = argv[0];
798
799 get_svc_config();
800
801 /*
802 * In the case where we get started outside of SMF, args passed on the
803 * command line override SMF property setting
804 */
805 while (optind < argc) {
806 while ((c = getopt(argc, argv, optstr)) != -1) {
807 switch (c) {
808 case 'd':
809 nhdl->nh_debug = B_TRUE;
810 break;
811 case 'f':
812 run_fg = B_TRUE;
813 break;
814 case 'R':
815 nhdl->nh_rootdir = strdup(optarg);
816 break;
817 default:
818 free(nhdl);
819 return (usage(argv[0]));
820 }
821 }
822 }
823
824 /*
825 * Set up a signal handler for SIGTERM (and SIGINT if we'll
826 * be running in the foreground) to ensure sure we get a chance to exit
827 * in an orderly fashion. We also catch SIGHUP, which will be sent to
828 * us by SMF if the service is refreshed.
829 */
830 (void) sigfillset(&set);
831 (void) sigfillset(&act.sa_mask);
832 act.sa_handler = nd_sighandler;
833 act.sa_flags = 0;
834
835 (void) sigaction(SIGTERM, &act, NULL);
836 (void) sigdelset(&set, SIGTERM);
837 (void) sigaction(SIGHUP, &act, NULL);
838 (void) sigdelset(&set, SIGHUP);
839
840 if (run_fg) {
841 (void) sigaction(SIGINT, &act, NULL);
842 (void) sigdelset(&set, SIGINT);
843 } else
844 nd_daemonize(nhdl);
845
846 rlim.rlim_cur = RLIM_INFINITY;
847 rlim.rlim_max = RLIM_INFINITY;
848 (void) setrlimit(RLIMIT_CORE, &rlim);
849
850 /*
851 * We need to be root to initialize our libfmevent handle (because that
852 * involves reading/writing to /dev/sysevent), so we do this before
853 * calling __init_daemon_priv.
854 */
855 nhdl->nh_evhdl = fmev_shdl_init(LIBFMEVENT_VERSION_2, NULL, NULL, NULL);
856 if (nhdl->nh_evhdl == NULL) {
857 (void) sleep(5);
858 nd_abort(nhdl, "failed to initialize libfmevent: %s",
859 fmev_strerror(fmev_errno));
860 }
861
862 /*
863 * If we're in the global zone, reset all of our privilege sets to
864 * the minimum set of required privileges. Since we've already
865 * initialized our libmevent handle, we no no longer need to run as
866 * root, so we change our uid/gid to noaccess (60002).
867 *
868 * __init_daemon_priv will also set the process core path for us
869 *
870 */
871 if (getzoneid() == GLOBAL_ZONEID)
872 if (__init_daemon_priv(
873 PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
874 60002, 60002, PRIV_PROC_SETID, NULL) != 0)
875 nd_abort(nhdl, "additional privileges required to run");
876
877 nhdl->nh_msghdl = fmd_msg_init(nhdl->nh_rootdir, FMD_MSG_VERSION);
878 if (nhdl->nh_msghdl == NULL)
879 nd_abort(nhdl, "failed to initialize libfmd_msg");
880
881 (void) gethostname(hostname, MAXHOSTNAMELEN + 1);
882 /*
883 * Set up our event subscriptions. We subscribe to everything and then
884 * consult libscf when we receive an event to determine whether to send
885 * an email notification.
886 */
887 nd_debug(nhdl, "Subscribing to ireport.* events");
888 if (fmev_shdl_subscribe(nhdl->nh_evhdl, "ireport.*", irpt_cbfunc,
889 NULL) != FMEV_SUCCESS) {
890 nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
891 fmev_strerror(fmev_errno));
892 }
893
894 nd_debug(nhdl, "Subscribing to list.* events");
895 if (fmev_shdl_subscribe(nhdl->nh_evhdl, "list.*", listev_cb,
896 NULL) != FMEV_SUCCESS) {
897 nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
898 fmev_strerror(fmev_errno));
899 }
900
901 /*
902 * We run until someone kills us
903 */
904 while (nhdl->nh_keep_running)
905 (void) sigsuspend(&set);
906
907 free(nhdl->nh_rootdir);
908 free(nhdl);
909
910 return (0);
911 }
912