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 }