xref: /illumos-gate/usr/src/cmd/fm/notify/smtp-notify/common/smtp-notify.c (revision 69a119caa6570c7077699161b7c28b6ee9f8b0f4)
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
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 *
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
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
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
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
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
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
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
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
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
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
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
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