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 }