diff usr/src/cmd/fm/notify/smtp-notify/common/smtp-notify.c @ 12979:ab9ae749152f

PSARC/2009/617 Software Events Notification Parameters CLI PSARC/2009/618 snmp-notify: SNMP Notification Daemon for Software Events PSARC/2009/619 smtp-notify: Email Notification Daemon for Software Events PSARC/2010/225 fmd for non-global Solaris zones PSARC/2010/226 Solaris Instance UUID PSARC/2010/227 nvlist_nvflag(3NVPAIR) PSARC/2010/228 libfmevent additions PSARC/2010/257 sysevent_evc_setpropnvl and sysevent_evc_getpropnvl PSARC/2010/265 FMRI and FMA Event Stabilty, 'ireport' category 1 event class, and the 'sw' FMRI scheme PSARC/2010/278 FMA/SMF integration: instance state transitions PSARC/2010/279 Modelling panics within FMA PSARC/2010/290 logadm.conf upgrade 6392476 fmdump needs to pretty-print 6393375 userland ereport/ireport event generation interfaces 6445732 Add email notification agent for FMA and software events 6804168 RFE: Allow an efficient means to monitor SMF services status changes 6866661 scf_values_destroy(3SCF) will segfault if is passed NULL 6884709 Add snmp notification agent for FMA and software events 6884712 Add private interface to tap into libfmd_msg macro expansion capabilities 6897919 fmd to run in a non-global zone 6897937 fmd use of non-private doors is not safe 6900081 add a UUID to Solaris kernel image for use in crashdump identification 6914884 model panic events as a defect diagnosis in FMA 6944862 fmd_case_open_uuid, fmd_case_uuisresolved, fmd_nvl_create_defect 6944866 log legacy sysevents in fmd 6944867 enumerate svc scheme in topo 6944868 software-diagnosis and software-response fmd modules 6944870 model SMF maintenance state as a defect diagnosis in FMA 6944876 savecore runs in foreground for systems with zfs root and dedicated dump 6965796 Implement notification parameters for SMF state transitions and FMA events 6968287 SUN-FM-MIB.mib needs to be updated to reflect Oracle information 6972331 logadm.conf upgrade PSARC/2010/290
author Gavin Maltby <gavin.maltby@oracle.com>
date Fri, 30 Jul 2010 17:04:17 +1000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/fm/notify/smtp-notify/common/smtp-notify.c	Fri Jul 30 17:04:17 2010 +1000
@@ -0,0 +1,906 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libscf.h>
+#include <priv_utils.h>
+#include <netdb.h>
+#include <signal.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <zone.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fm/fmd_msg.h>
+#include <fm/libfmevent.h>
+#include "libfmnotify.h"
+
+#define	SENDMAIL	"/usr/sbin/sendmail"
+#define	SVCNAME		"system/fm/smtp-notify"
+
+#define	XHDR_HOSTNAME		"X-FMEV-HOSTNAME"
+#define	XHDR_CLASS		"X-FMEV-CLASS"
+#define	XHDR_UUID		"X-FMEV-UUID"
+#define	XHDR_MSGID		"X-FMEV-CODE"
+#define	XHDR_SEVERITY		"X-FMEV-SEVERITY"
+#define	XHDR_FMRI		"X-FMEV-FMRI"
+#define	XHDR_FROM_STATE		"X-FMEV-FROM-STATE"
+#define	XHDR_TO_STATE		"X-FMEV-TO-STATE"
+
+/*
+ * Debug messages can be enabled by setting the debug property to true
+ *
+ * # svccfg -s svc:/system/fm/smtp-notify setprop config/debug=true
+ *
+ * Debug messages will be spooled to the service log at:
+ * <root>/var/svc/log/system-fm-smtp-notify:default.log
+ */
+#define	PP_SCRIPT "usr/lib/fm/notify/process_msg_template.sh"
+
+typedef struct email_pref
+{
+	int ep_num_recips;
+	char **ep_recips;
+	char *ep_reply_to;
+	char *ep_template_path;
+	char *ep_template;
+} email_pref_t;
+
+static nd_hdl_t *nhdl;
+static char hostname[MAXHOSTNAMELEN + 1];
+static const char optstr[] = "dfR:";
+static const char DEF_SUBJ_TEMPLATE[] = "smtp-notify-subject-template";
+static const char SMF_SUBJ_TEMPLATE[] = "smtp-notify-smf-subject-template";
+static const char FM_SUBJ_TEMPLATE[] = "smtp-notify-fm-subject-template";
+static const char IREPORT_MSG_TEMPLATE[] = "ireport-msg-template";
+static const char SMF_MSG_TEMPLATE[] = "ireport.os.smf-msg-template";
+
+static int
+usage(const char *pname)
+{
+	(void) fprintf(stderr, "Usage: %s [-df] [-R <altroot>]\n", pname);
+
+	(void) fprintf(stderr,
+	    "\t-d  enable debug mode\n"
+	    "\t-f  stay in foreground\n"
+	    "\t-R  specify alternate root\n");
+
+	return (1);
+}
+
+/*
+ * This function simply reads the file specified by "template" into a buffer
+ * and returns a pointer to that buffer (or NULL on failure).  The caller is
+ * responsible for free'ing the returned buffer.
+ */
+static char *
+read_template(const char *template)
+{
+	int fd;
+	struct stat statb;
+	char *buf;
+
+	if (stat(template, &statb) != 0) {
+		nd_error(nhdl, "Failed to stat %s (%s)", template,
+		    strerror(errno));
+		return (NULL);
+	}
+	if ((fd = open(template, O_RDONLY)) < 0) {
+		nd_error(nhdl, "Failed to open %s (%s)", template,
+		    strerror(errno));
+		return (NULL);
+	}
+	if ((buf = malloc(statb.st_size + 1)) == NULL) {
+		nd_error(nhdl, "Failed to allocate %d bytes", statb.st_size);
+		(void) close(fd);
+		return (NULL);
+	}
+	if (read(fd, buf, statb.st_size) < 0) {
+		nd_error(nhdl, "Failed to read in template (%s)",
+		    strerror(errno));
+		free(buf);
+		(void) close(fd);
+		return (NULL);
+	}
+	buf[statb.st_size] = '\0';
+	(void) close(fd);
+	return (buf);
+}
+
+/*
+ * This function runs a user-supplied message body template through a script
+ * which replaces the "committed" expansion macros with actual libfmd_msg
+ * expansion macros.
+ */
+static int
+process_template(nd_ev_info_t *ev_info, email_pref_t *eprefs)
+{
+	char pp_script[PATH_MAX], tmpfile[PATH_MAX], pp_cli[PATH_MAX];
+	int ret = -1;
+
+	(void) snprintf(pp_script, sizeof (pp_script), "%s%s",
+	    nhdl->nh_rootdir, PP_SCRIPT);
+	(void) snprintf(tmpfile, sizeof (tmpfile), "%s%s",
+	    nhdl->nh_rootdir, tmpnam(NULL));
+
+	/*
+	 * If it's an SMF event, then the diagcode and severity won't be part
+	 * of the event payload and so libfmd_msg won't be able to expand them.
+	 * Therefore we pass the code and severity into the script and let the
+	 * script do the expansion.
+	 */
+	/* LINTED: E_SEC_SPRINTF_UNBOUNDED_COPY */
+	(void) sprintf(pp_cli, "%s %s %s %s %s", pp_script,
+	    eprefs->ep_template_path, tmpfile, ev_info->ei_diagcode,
+	    ev_info->ei_severity);
+
+	nd_debug(nhdl, "Executing %s", pp_cli);
+	if (system(pp_cli) != -1)
+		if ((eprefs->ep_template = read_template(tmpfile)) != NULL)
+			ret = 0;
+
+	(void) unlink(tmpfile);
+	return (ret);
+}
+
+/*
+ * If someone does an "svcadm refresh" on us, then this function gets called,
+ * which rereads our service configuration.
+ */
+static void
+get_svc_config()
+{
+	int s = 0;
+	uint8_t val;
+
+	s = nd_get_boolean_prop(nhdl, SVCNAME, "config", "debug", &val);
+	nhdl->nh_debug = val;
+
+	s += nd_get_astring_prop(nhdl, SVCNAME, "config", "rootdir",
+	    &(nhdl->nh_rootdir));
+
+	if (s != 0)
+		nd_error(nhdl, "Failed to read retrieve service "
+		    "properties\n");
+}
+
+static void
+nd_sighandler(int sig)
+{
+	if (sig == SIGHUP)
+		get_svc_config();
+	else
+		nd_cleanup(nhdl);
+}
+
+/*
+ * This function constructs all the email headers and puts them into the
+ * "headers" buffer handle.  The caller is responsible for free'ing this
+ * buffer.
+ */
+static int
+build_headers(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs,
+    char **headers)
+{
+	const char *subj_key;
+	char *subj_fmt, *subj = NULL;
+	size_t len;
+	boolean_t is_smf_event = B_FALSE, is_fm_event = B_FALSE;
+
+	/*
+	 * Fetch and format the email subject.
+	 */
+	if (strncmp(ev_info->ei_class, "list.", 5) == 0) {
+		is_fm_event = B_TRUE;
+		subj_key = FM_SUBJ_TEMPLATE;
+	} else if (strncmp(ev_info->ei_class, "ireport.os.smf", 14) == 0) {
+		is_smf_event = B_TRUE;
+		subj_key = SMF_SUBJ_TEMPLATE;
+	} else {
+		subj_key = DEF_SUBJ_TEMPLATE;
+	}
+
+	if ((subj_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
+	    FMNOTIFY_MSG_DOMAIN, subj_key)) == NULL) {
+		nd_error(nhdl, "Failed to contruct subject format");
+		return (-1); /* libfmd_msg error */
+	}
+
+	if (is_fm_event) {
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		len = snprintf(NULL, 0, subj_fmt, hostname,
+		    ev_info->ei_diagcode);
+		subj = alloca(len + 1);
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		(void) snprintf(subj, len + 1, subj_fmt, hostname,
+		    ev_info->ei_diagcode);
+	} else if (is_smf_event) {
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		len = snprintf(NULL, 0, subj_fmt, hostname, ev_info->ei_fmri,
+		    ev_info->ei_from_state, ev_info->ei_to_state);
+		subj = alloca(len + 1);
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		(void) snprintf(subj, len + 1, subj_fmt, hostname,
+		    ev_info->ei_fmri, ev_info->ei_from_state,
+		    ev_info->ei_to_state);
+	} else {
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		len = snprintf(NULL, 0, subj_fmt, hostname);
+		subj = alloca(len + 1);
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		(void) snprintf(subj, len + 1, subj_fmt, hostname);
+	}
+
+	/*
+	 * Here we add some X-headers to our mail message for use by mail
+	 * filtering agents.  We add headers for the following bits of event
+	 * data for all events
+	 *
+	 * hostname
+	 * msg id (diagcode)
+	 * event class
+	 * event severity
+	 * event uuid
+	 *
+	 * For SMF transition events, we'll have the following add'l X-headers
+	 *
+	 * from-state
+	 * to-state
+	 * service fmri
+	 *
+	 * We follow the X-headers with standard Reply-To and Subject headers.
+	 */
+	if (is_fm_event) {
+		len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
+		    "%s: %s\nReply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
+		    hostname, XHDR_CLASS, ev_info->ei_class, XHDR_UUID,
+		    ev_info->ei_uuid, XHDR_MSGID, ev_info->ei_diagcode,
+		    XHDR_SEVERITY, ev_info->ei_severity, eprefs->ep_reply_to,
+		    subj);
+
+		*headers = calloc(len + 1, sizeof (char));
+
+		(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
+		    "%s: %s\n%s: %s\nReply-To: %s\nSubject: %s\n\n",
+		    XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
+		    XHDR_UUID, ev_info->ei_uuid, XHDR_MSGID,
+		    ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
+		    eprefs->ep_reply_to, subj);
+	} else if (is_smf_event) {
+		len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
+		    "%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
+		    "Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
+		    ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
+		    XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
+		    ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
+		    XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
+		    subj);
+
+		*headers = calloc(len + 1, sizeof (char));
+
+		(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
+		    "%s: %s\n%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
+		    "Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
+		    ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
+		    XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
+		    ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
+		    XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
+		    subj);
+	} else {
+		len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
+		    "Reply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
+		    hostname, XHDR_CLASS, ev_info->ei_class, XHDR_MSGID,
+		    ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
+		    eprefs->ep_reply_to, subj);
+
+		*headers = calloc(len + 1, sizeof (char));
+
+		(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
+		    "%s: %s\nReply-To: %s\nSubject: %s\n\n",
+		    XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
+		    XHDR_MSGID, ev_info->ei_diagcode, XHDR_SEVERITY,
+		    ev_info->ei_severity, eprefs->ep_reply_to, subj);
+	}
+	return (0);
+}
+
+static void
+send_email(nd_hdl_t *nhdl, const char *headers, const char *body,
+    const char *recip)
+{
+	FILE *mp;
+	char sm_cli[PATH_MAX];
+
+	/*
+	 * Open a pipe to sendmail and pump out the email message
+	 */
+	(void) snprintf(sm_cli, PATH_MAX, "%s -t %s", SENDMAIL, recip);
+
+	nd_debug(nhdl, "Sending email notification to %s", recip);
+	if ((mp = popen(sm_cli, "w")) == NULL) {
+		nd_error(nhdl, "Failed to open pipe to %s (%s)", SENDMAIL,
+		    strerror(errno));
+		return;
+	}
+	if (fprintf(mp, "%s", headers) < 0)
+		nd_error(nhdl, "Failed to write to pipe (%s)", strerror(errno));
+
+	if (fprintf(mp, "%s\n.\n", body) < 0)
+		nd_error(nhdl, "Failed to write to pipe (%s)",
+		    strerror(errno));
+
+	(void) pclose(mp);
+}
+
+static void
+send_email_template(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs)
+{
+	char *msg, *headers;
+
+	if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
+		return;
+
+	/*
+	 * If the user specified a message body template, then we pass it
+	 * through a private interface in libfmd_msg, which will return a string
+	 * with any expansion tokens decoded.
+	 */
+	if ((msg = fmd_msg_decode_tokens(ev_info->ei_payload,
+	    eprefs->ep_template, ev_info->ei_url)) == NULL) {
+		nd_error(nhdl, "Failed to parse msg template");
+		free(headers);
+		return;
+	}
+	for (int i = 0; i < eprefs->ep_num_recips; i++)
+		send_email(nhdl, headers, msg, eprefs->ep_recips[i]);
+
+	free(msg);
+	free(headers);
+}
+
+static int
+get_email_prefs(nd_hdl_t *nhdl, fmev_t ev, email_pref_t **eprefs)
+{
+	nvlist_t **p_nvl = NULL;
+	email_pref_t *ep;
+	uint_t npref, tn1 = 0, tn2 = 0;
+	char **tmparr1, **tmparr2;
+	int r, ret = -1;
+
+	r = nd_get_notify_prefs(nhdl, "smtp", ev, &p_nvl, &npref);
+	if (r == SCF_ERROR_NOT_FOUND) {
+		/*
+		 * No email notification preferences specified for this type of
+		 * event, so we're done
+		 */
+		return (-1);
+	} else if (r != 0) {
+		nd_error(nhdl, "Failed to retrieve notification preferences "
+		    "for this event");
+		return (-1);
+	}
+
+	if ((ep = malloc(sizeof (email_pref_t))) == NULL) {
+		nd_error(nhdl, "Failed to allocate space for email preferences "
+		    "(%s)", strerror(errno));
+		goto eprefs_done;
+	}
+	(void) memset(ep, 0, sizeof (email_pref_t));
+
+	/*
+	 * For SMF state transition events, pref_nvl may contain two sets of
+	 * preferences, which will have to be merged.
+	 *
+	 * The "smtp" nvlist can contain up to four members:
+	 *
+	 * "active"	- boolean - used to toggle notfications
+	 * "to"		- a string array of email recipients
+	 * "reply-to"	- a string array containing the reply-to addresses
+	 *		- this is optional and defaults to root@localhost
+	 * "msg_template" - the pathname of a user-supplied message body
+	 *		template
+	 *
+	 * In the case that we have two sets of preferences, we will merge them
+	 * using the following rules:
+	 *
+	 * "active" will be set to true, if it is true in either set
+	 *
+	 * The "reply-to" and "to" lists will be merged, with duplicate email
+	 * addresses removed.
+	 */
+	if (npref == 2) {
+		boolean_t *act1, *act2;
+		char **arr1, **arr2, **strarr, **reparr1, **reparr2;
+		uint_t n1, n2, arrsz, repsz;
+
+		r = nvlist_lookup_boolean_array(p_nvl[0], "active", &act1, &n1);
+		r += nvlist_lookup_boolean_array(p_nvl[1], "active", &act2,
+		    &n2);
+		r += nvlist_lookup_string_array(p_nvl[0], "to", &arr1, &n1);
+		r += nvlist_lookup_string_array(p_nvl[1], "to", &arr2, &n2);
+
+		if (r != 0) {
+			nd_error(nhdl, "Malformed email notification "
+			    "preferences");
+			nd_dump_nvlist(nhdl, p_nvl[0]);
+			nd_dump_nvlist(nhdl, p_nvl[1]);
+			goto eprefs_done;
+		} else if (!act1[0] && !act2[0]) {
+			nd_debug(nhdl, "Email notification is disabled");
+			goto eprefs_done;
+		}
+
+		if (nd_split_list(nhdl, arr1[0], ",", &tmparr1, &tn1) != 0 ||
+		    nd_split_list(nhdl, arr2[0], ",", &tmparr2, &tn2) != 0) {
+			nd_error(nhdl, "Error parsing \"to\" lists");
+			nd_dump_nvlist(nhdl, p_nvl[0]);
+			nd_dump_nvlist(nhdl, p_nvl[1]);
+			goto eprefs_done;
+		}
+
+		if ((ep->ep_num_recips = nd_merge_strarray(nhdl, tmparr1, tn1,
+		    tmparr2, tn2, &ep->ep_recips)) < 0) {
+			nd_error(nhdl, "Error merging email recipient lists");
+			goto eprefs_done;
+		}
+
+		r = nvlist_lookup_string_array(p_nvl[0], "reply-to", &arr1,
+		    &n1);
+		r += nvlist_lookup_string_array(p_nvl[1], "reply-to", &arr2,
+		    &n2);
+		repsz = n1 = n2 = 0;
+		if (!r &&
+		    nd_split_list(nhdl, arr1[0], ",", &reparr1, &n1) != 0 ||
+		    nd_split_list(nhdl, arr2[0], ",", &reparr2, &n2) != 0 ||
+		    (repsz = nd_merge_strarray(nhdl, tmparr1, n1, tmparr2, n2,
+		    &strarr)) != 0 ||
+		    nd_join_strarray(nhdl, strarr, repsz, &ep->ep_reply_to)
+		    != 0) {
+
+			ep->ep_reply_to = strdup("root@localhost");
+		}
+		if (n1)
+			nd_free_strarray(reparr1, n1);
+		if (n2)
+			nd_free_strarray(reparr2, n2);
+		if (repsz > 0)
+			nd_free_strarray(strarr, repsz);
+
+		if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
+		    &strarr, &arrsz) == 0)
+			ep->ep_template_path = strdup(strarr[0]);
+	} else {
+		char **strarr, **tmparr;
+		uint_t arrsz;
+		boolean_t *active;
+
+		/*
+		 * Both the "active" and "to" notification preferences are
+		 * required, so if we have trouble looking either of these up
+		 * we return an error.  We will also return an error if "active"
+		 * is set to false.  Returning an error will cause us to not
+		 * send a notification for this event.
+		 */
+		r = nvlist_lookup_boolean_array(p_nvl[0], "active", &active,
+		    &arrsz);
+		r += nvlist_lookup_string_array(p_nvl[0], "to", &strarr,
+		    &arrsz);
+
+		if (r != 0) {
+			nd_error(nhdl, "Malformed email notification "
+			    "preferences");
+			nd_dump_nvlist(nhdl, p_nvl[0]);
+			goto eprefs_done;
+		} else if (!active[0]) {
+			nd_debug(nhdl, "Email notification is disabled");
+			goto eprefs_done;
+		}
+
+		if (nd_split_list(nhdl, strarr[0], ",", &tmparr, &arrsz)
+		    != 0) {
+			nd_error(nhdl, "Error parsing \"to\" list");
+			goto eprefs_done;
+		}
+		ep->ep_num_recips = arrsz;
+		ep->ep_recips = tmparr;
+
+		if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
+		    &strarr, &arrsz) == 0)
+			ep->ep_template_path = strdup(strarr[0]);
+
+		if (nvlist_lookup_string_array(p_nvl[0], "reply-to", &strarr,
+		    &arrsz) == 0)
+			ep->ep_reply_to = strdup(strarr[0]);
+		else
+			ep->ep_reply_to = strdup("root@localhost");
+	}
+	ret = 0;
+	*eprefs = ep;
+eprefs_done:
+	if (ret != 0) {
+		if (ep->ep_recips)
+			nd_free_strarray(ep->ep_recips, ep->ep_num_recips);
+		if (ep->ep_reply_to)
+			free(ep->ep_reply_to);
+		free(ep);
+	}
+	if (tn1)
+		nd_free_strarray(tmparr1, tn1);
+	if (tn2)
+		nd_free_strarray(tmparr2, tn2);
+	nd_free_nvlarray(p_nvl, npref);
+
+	return (ret);
+}
+
+/*ARGSUSED*/
+static void
+irpt_cbfunc(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
+{
+	char *body_fmt, *headers = NULL, *body = NULL, tstamp[32];
+	struct tm ts;
+	size_t len;
+	nd_ev_info_t *ev_info = NULL;
+	email_pref_t *eprefs;
+
+	nd_debug(nhdl, "Received event of class %s", class);
+
+	if (get_email_prefs(nhdl, ev, &eprefs) < 0)
+		return;
+
+	if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
+		goto irpt_done;
+
+	/*
+	 * If the user specified a template, then we pass it through a script,
+	 * which post-processes any expansion macros.  Then we attempt to read
+	 * it in and then send the message.  Otherwise we carry on with the rest
+	 * of this function which will contruct the message body from one of the
+	 * default templates.
+	 */
+	if (eprefs->ep_template != NULL)
+		free(eprefs->ep_template);
+
+	if (eprefs->ep_template_path != NULL &&
+	    process_template(ev_info, eprefs) == 0) {
+		send_email_template(nhdl, ev_info, eprefs);
+		goto irpt_done;
+	}
+
+	/*
+	 * Fetch and format the event timestamp
+	 */
+	if (fmev_localtime(ev, &ts) == NULL) {
+		nd_error(nhdl, "Malformed event: failed to retrieve "
+		    "timestamp");
+		goto irpt_done;
+	}
+	(void) strftime(tstamp, sizeof (tstamp), NULL, &ts);
+
+	/*
+	 * We have two message body templates to choose from.  One for SMF
+	 * service transition events and a generic one for any other
+	 * uncommitted ireport.
+	 */
+	if (strncmp(class, "ireport.os.smf", 14) == 0) {
+		/*
+		 * For SMF state transition events we have a standard message
+		 * template that we fill in based on the payload of the event.
+		 */
+		if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
+		    FMNOTIFY_MSG_DOMAIN, SMF_MSG_TEMPLATE)) == NULL) {
+			nd_error(nhdl, "Failed to format message body");
+			goto irpt_done;
+		}
+
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		len = snprintf(NULL, 0, body_fmt, hostname, tstamp,
+		    ev_info->ei_fmri, ev_info->ei_from_state,
+		    ev_info->ei_to_state, ev_info->ei_descr,
+		    ev_info->ei_reason);
+		body = calloc(len, sizeof (char));
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		(void) snprintf(body, len, body_fmt, hostname, tstamp,
+		    ev_info->ei_fmri, ev_info->ei_from_state,
+		    ev_info->ei_to_state, ev_info->ei_descr,
+		    ev_info->ei_reason);
+	} else {
+		if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
+		    FMNOTIFY_MSG_DOMAIN, IREPORT_MSG_TEMPLATE)) == NULL) {
+			nd_error(nhdl, "Failed to format message body");
+			goto irpt_done;
+		}
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		len = snprintf(NULL, 0, body_fmt, hostname, tstamp, class);
+		body = calloc(len, sizeof (char));
+		/* LINTED: E_SEC_PRINTF_VAR_FMT */
+		(void) snprintf(body, len, body_fmt, hostname, tstamp, class);
+	}
+
+	if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
+		goto irpt_done;
+
+	/*
+	 * Everything is ready, so now we just iterate through the list of
+	 * recipents, sending an email notification to each one.
+	 */
+	for (int i = 0; i < eprefs->ep_num_recips; i++)
+		send_email(nhdl, headers, body, eprefs->ep_recips[i]);
+
+irpt_done:
+	free(headers);
+	free(body);
+	if (ev_info)
+		nd_free_event_info(ev_info);
+	if (eprefs->ep_recips)
+		nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
+	if (eprefs->ep_reply_to)
+		free(eprefs->ep_reply_to);
+	free(eprefs);
+}
+
+/*
+ * There is a lack of uniformity in how the various entries in our diagnosis
+ * are terminated.  Some end with one newline, others with two.  This makes the
+ * output look a bit ugly.  Therefore we postprocess the message before sending
+ * it, removing consecutive occurences of newlines.
+ */
+static void
+postprocess_msg(char *msg)
+{
+	int i = 0, j = 0;
+	char *buf;
+
+	if ((buf = malloc(strlen(msg) + 1)) == NULL)
+		return;
+
+	buf[j++] = msg[i++];
+	for (i = 1; i < strlen(msg); i++) {
+		if (!(msg[i] == '\n' && msg[i - 1] == '\n'))
+			buf[j++] = msg[i];
+	}
+	buf[j] = '\0';
+	(void) strncpy(msg, buf, j+1);
+	free(buf);
+}
+
+/*ARGSUSED*/
+static void
+listev_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
+{
+	char *body = NULL, *headers = NULL;
+	nd_ev_info_t *ev_info = NULL;
+	boolean_t domsg;
+	email_pref_t *eprefs;
+
+	nd_debug(nhdl, "Received event of class %s", class);
+
+	if (get_email_prefs(nhdl, ev, &eprefs) < 0)
+		return;
+
+	if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
+		goto listcb_done;
+
+	/*
+	 * If the message payload member is set to 0, then it's an event we
+	 * typically suppress messaging on, so we won't send an email for it.
+	 */
+	if (nvlist_lookup_boolean_value(ev_info->ei_payload, FM_SUSPECT_MESSAGE,
+	    &domsg) == 0 && !domsg) {
+		nd_debug(nhdl, "Messaging suppressed for this event");
+		goto listcb_done;
+	}
+
+	/*
+	 * If the user specified a template, then we pass it through a script,
+	 * which post-processes any expansion macros.  Then we attempt to read
+	 * it in and then send the message.  Otherwise we carry on with the rest
+	 * of this function which will contruct the message body from one of the
+	 * default templates.
+	 */
+	if (eprefs->ep_template != NULL)
+		free(eprefs->ep_template);
+
+	if (eprefs->ep_template_path != NULL &&
+	    process_template(ev_info, eprefs) == 0) {
+		send_email_template(nhdl, ev_info, eprefs);
+		goto listcb_done;
+	}
+
+	/*
+	 * Format the message body
+	 *
+	 * For FMA list.* events we use the same message that the
+	 * syslog-msgs agent would emit as the message body
+	 *
+	 */
+	if ((body = fmd_msg_gettext_nv(nhdl->nh_msghdl, NULL,
+	    ev_info->ei_payload)) == NULL) {
+		nd_error(nhdl, "Failed to format message body");
+		nd_dump_nvlist(nhdl, ev_info->ei_payload);
+		goto listcb_done;
+	}
+	postprocess_msg(body);
+
+	if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
+		goto listcb_done;
+
+	/*
+	 * Everything is ready, so now we just iterate through the list of
+	 * recipents, sending an email notification to each one.
+	 */
+	for (int i = 0; i < eprefs->ep_num_recips; i++)
+		send_email(nhdl, headers, body, eprefs->ep_recips[i]);
+
+listcb_done:
+	free(headers);
+	free(body);
+	if (ev_info)
+		nd_free_event_info(ev_info);
+	if (eprefs->ep_recips)
+		nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
+	if (eprefs->ep_reply_to)
+		free(eprefs->ep_reply_to);
+	free(eprefs);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct rlimit rlim;
+	struct sigaction act;
+	sigset_t set;
+	char c;
+	boolean_t run_fg = B_FALSE;
+
+	if ((nhdl = malloc(sizeof (nd_hdl_t))) == NULL) {
+		(void) fprintf(stderr, "Failed to allocate space for notifyd "
+		    "handle (%s)", strerror(errno));
+		return (1);
+	}
+	(void) memset(nhdl, 0, sizeof (nd_hdl_t));
+
+	nhdl->nh_keep_running = B_TRUE;
+	nhdl->nh_log_fd = stderr;
+	nhdl->nh_pname = argv[0];
+
+	get_svc_config();
+
+	/*
+	 * In the case where we get started outside of SMF, args passed on the
+	 * command line override SMF property setting
+	 */
+	while (optind < argc) {
+		while ((c = getopt(argc, argv, optstr)) != -1) {
+			switch (c) {
+			case 'd':
+				nhdl->nh_debug = B_TRUE;
+				break;
+			case 'f':
+				run_fg = B_TRUE;
+				break;
+			case 'R':
+				nhdl->nh_rootdir = strdup(optarg);
+				break;
+			default:
+				free(nhdl);
+				return (usage(nhdl->nh_pname));
+			}
+		}
+	}
+
+	/*
+	 * Set up a signal handler for SIGTERM (and SIGINT if we'll
+	 * be running in the foreground) to ensure sure we get a chance to exit
+	 * in an orderly fashion.  We also catch SIGHUP, which will be sent to
+	 * us by SMF if the service is refreshed.
+	 */
+	(void) sigfillset(&set);
+	(void) sigfillset(&act.sa_mask);
+	act.sa_handler = nd_sighandler;
+	act.sa_flags = 0;
+
+	(void) sigaction(SIGTERM, &act, NULL);
+	(void) sigdelset(&set, SIGTERM);
+	(void) sigaction(SIGHUP, &act, NULL);
+	(void) sigdelset(&set, SIGHUP);
+
+	if (run_fg) {
+		(void) sigaction(SIGINT, &act, NULL);
+		(void) sigdelset(&set, SIGINT);
+	} else
+		nd_daemonize(nhdl);
+
+	rlim.rlim_cur = RLIM_INFINITY;
+	rlim.rlim_max = RLIM_INFINITY;
+	(void) setrlimit(RLIMIT_CORE, &rlim);
+
+	/*
+	 * We need to be root to initialize our libfmevent handle (because that
+	 * involves reading/writing to /dev/sysevent), so we do this before
+	 * calling __init_daemon_priv.
+	 */
+	nhdl->nh_evhdl = fmev_shdl_init(LIBFMEVENT_VERSION_2, NULL, NULL, NULL);
+	if (nhdl->nh_evhdl == NULL) {
+		(void) sleep(5);
+		nd_abort(nhdl, "failed to initialize libfmevent: %s",
+		    fmev_strerror(fmev_errno));
+	}
+
+	/*
+	 * If we're in the global zone, reset all of our privilege sets to
+	 * the minimum set of required privileges.  Since we've already
+	 * initialized our libmevent handle, we no no longer need to run as
+	 * root, so we change our uid/gid to noaccess (60002).
+	 *
+	 * __init_daemon_priv will also set the process core path for us
+	 *
+	 */
+	if (getzoneid() == GLOBAL_ZONEID)
+		if (__init_daemon_priv(
+		    PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
+		    60002, 60002, PRIV_PROC_SETID, NULL) != 0)
+			nd_abort(nhdl, "additional privileges required to run");
+
+	nhdl->nh_msghdl = fmd_msg_init(nhdl->nh_rootdir, FMD_MSG_VERSION);
+	if (nhdl->nh_msghdl == NULL)
+		nd_abort(nhdl, "failed to initialize libfmd_msg");
+
+	(void) gethostname(hostname, MAXHOSTNAMELEN + 1);
+	/*
+	 * Set up our event subscriptions.  We subscribe to everything and then
+	 * consult libscf when we receive an event to determine whether to send
+	 * an email notification.
+	 */
+	nd_debug(nhdl, "Subscribing to ireport.* events");
+	if (fmev_shdl_subscribe(nhdl->nh_evhdl, "ireport.*", irpt_cbfunc,
+	    NULL) != FMEV_SUCCESS) {
+		nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
+		    fmev_strerror(fmev_errno));
+	}
+
+	nd_debug(nhdl, "Subscribing to list.* events");
+	if (fmev_shdl_subscribe(nhdl->nh_evhdl, "list.*", listev_cb,
+	    NULL) != FMEV_SUCCESS) {
+		nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
+		    fmev_strerror(fmev_errno));
+	}
+
+	/*
+	 * We run until someone kills us
+	 */
+	while (nhdl->nh_keep_running)
+		(void) sigsuspend(&set);
+
+	free(nhdl->nh_rootdir);
+	free(nhdl);
+
+	return (0);
+}