xref: /illumos-gate/usr/src/cmd/fm/notify/smtp-notify/common/smtp-notify.c (revision 09e2ab34f6c69b170fe7478e8b011d6bb505e0d9)
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
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 *
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
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
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
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
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
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
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
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
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
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
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
780 main(int argc, char *argv[])
781 {
782 	struct rlimit rlim;
783 	struct sigaction act;
784 	sigset_t set;
785 	char 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