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