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 }