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