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 <alloca.h>
  26 
  27 #include "libfmnotify.h"
  28 
  29 /*ARGSUSED*/
  30 void
  31 nd_cleanup(nd_hdl_t *nhdl)
  32 {
  33         nd_debug(nhdl, "Cleaning up ...");
  34         if (nhdl->nh_evhdl)
  35                 (void) fmev_shdl_fini(nhdl->nh_evhdl);
  36 
  37         if (nhdl->nh_msghdl)
  38                 fmd_msg_fini(nhdl->nh_msghdl);
  39 
  40         nhdl->nh_keep_running = B_FALSE;
  41         (void) fclose(nhdl->nh_log_fd);
  42 }
  43 
  44 static void
  45 get_timestamp(char *buf, size_t bufsize)
  46 {
  47         time_t utc_time;
  48         struct tm *p_tm;
  49 
  50         (void) time(&utc_time);
  51         p_tm = localtime(&utc_time);
  52 
  53         (void) strftime(buf, bufsize, "%b %d %H:%M:%S", p_tm);
  54 }
  55 
  56 /* PRINTFLIKE2 */
  57 void
  58 nd_debug(nd_hdl_t *nhdl, const char *format, ...)
  59 {
  60         char timestamp[64];
  61         va_list ap;
  62 
  63         if (nhdl->nh_debug) {
  64                 get_timestamp(timestamp, sizeof (timestamp));
  65                 (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
  66                 va_start(ap, format);
  67                 (void) vfprintf(nhdl->nh_log_fd, format, ap);
  68                 va_end(ap);
  69                 (void) fprintf(nhdl->nh_log_fd, " ]\n");
  70         }
  71         (void) fflush(nhdl->nh_log_fd);
  72 }
  73 
  74 void
  75 nd_dump_nvlist(nd_hdl_t *nhdl, nvlist_t *nvl)
  76 {
  77         if (nhdl->nh_debug)
  78                 nvlist_print(nhdl->nh_log_fd, nvl);
  79 }
  80 
  81 /* PRINTFLIKE2 */
  82 void
  83 nd_error(nd_hdl_t *nhdl, const char *format, ...)
  84 {
  85         char timestamp[64];
  86         va_list ap;
  87 
  88         get_timestamp(timestamp, sizeof (timestamp));
  89         (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
  90         va_start(ap, format);
  91         (void) vfprintf(nhdl->nh_log_fd, format, ap);
  92         va_end(ap);
  93         (void) fprintf(nhdl->nh_log_fd, " ]\n");
  94         (void) fflush(nhdl->nh_log_fd);
  95 }
  96 
  97 /* PRINTFLIKE2 */
  98 void
  99 nd_abort(nd_hdl_t *nhdl, const char *format, ...)
 100 {
 101         char timestamp[64];
 102         va_list ap;
 103 
 104         get_timestamp(timestamp, sizeof (timestamp));
 105         (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
 106         va_start(ap, format);
 107         (void) vfprintf(nhdl->nh_log_fd, format, ap);
 108         va_end(ap);
 109         (void) fprintf(nhdl->nh_log_fd, " ]\n");
 110         (void) fflush(nhdl->nh_log_fd);
 111         nd_cleanup(nhdl);
 112 }
 113 
 114 void
 115 nd_daemonize(nd_hdl_t *nhdl)
 116 {
 117         pid_t pid;
 118 
 119         if ((pid = fork()) < 0)
 120                 nd_abort(nhdl, "Failed to fork child (%s)", strerror(errno));
 121         else if (pid > 0)
 122                 exit(0);
 123 
 124         (void) setsid();
 125         (void) close(0);
 126         (void) close(1);
 127         /*
 128          * We leave stderr open so we can write debug/err messages to the SMF
 129          * service log
 130          */
 131         nhdl->nh_is_daemon = B_TRUE;
 132 }
 133 
 134 /*
 135  * This function returns a pointer to the specified SMF property group for the
 136  * specified SMF service.  The caller is responsible for freeing the property
 137  * group.  On failure, the function returns NULL.
 138  */
 139 static scf_propertygroup_t *
 140 nd_get_pg(nd_hdl_t *nhdl, scf_handle_t *handle, const char *svcname,
 141     const char *pgname)
 142 {
 143         scf_scope_t *sc = NULL;
 144         scf_service_t *svc = NULL;
 145         scf_propertygroup_t *pg = NULL, *ret = NULL;
 146 
 147         sc = scf_scope_create(handle);
 148         svc = scf_service_create(handle);
 149         pg = scf_pg_create(handle);
 150 
 151         if (sc == NULL || svc == NULL || pg == NULL) {
 152                 nd_error(nhdl, "Failed to allocate libscf structures");
 153                 scf_pg_destroy(pg);
 154                 goto get_pg_done;
 155         }
 156 
 157         if (scf_handle_bind(handle) != -1 &&
 158             scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) != -1 &&
 159             scf_scope_get_service(sc, svcname, svc) != -1 &&
 160             scf_service_get_pg(svc, pgname, pg) != -1)
 161                 ret = pg;
 162         else
 163                 scf_pg_destroy(pg);
 164 
 165 get_pg_done:
 166         scf_service_destroy(svc);
 167         scf_scope_destroy(sc);
 168 
 169         return (ret);
 170 }
 171 
 172 int
 173 nd_get_astring_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
 174     const char *propname, char **val)
 175 {
 176         scf_handle_t *handle = NULL;
 177         scf_propertygroup_t *pg;
 178         scf_property_t *prop = NULL;
 179         scf_value_t *value = NULL;
 180         char strval[255];
 181         int ret = -1;
 182 
 183         if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
 184                 return (ret);
 185 
 186         if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
 187                 nd_error(nhdl, "Failed to read retrieve %s "
 188                     "property group for %s", pgname, svcname);
 189                 goto astring_done;
 190         }
 191         prop = scf_property_create(handle);
 192         value = scf_value_create(handle);
 193         if (prop == NULL || value == NULL) {
 194                 nd_error(nhdl, "Failed to allocate SMF structures");
 195                 goto astring_done;
 196         }
 197         if (scf_pg_get_property(pg, propname, prop) == -1 ||
 198             scf_property_get_value(prop, value) == -1 ||
 199             scf_value_get_astring(value, strval, 255) == -1) {
 200                 nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
 201                     scf_strerror(scf_error()));
 202                 goto astring_done;
 203         }
 204         *val = strdup(strval);
 205         ret = 0;
 206 
 207 astring_done:
 208         scf_value_destroy(value);
 209         scf_property_destroy(prop);
 210         scf_pg_destroy(pg);
 211         scf_handle_destroy(handle);
 212 
 213         return (ret);
 214 }
 215 
 216 int
 217 nd_get_boolean_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
 218     const char *propname, uint8_t *val)
 219 {
 220         scf_handle_t *handle = NULL;
 221         scf_propertygroup_t *pg;
 222         scf_property_t *prop = NULL;
 223         scf_value_t *value = NULL;
 224         int ret = -1;
 225 
 226         if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
 227                 return (ret);
 228 
 229         if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
 230                 nd_error(nhdl, "Failed to read retrieve %s "
 231                     "property group for %s", pgname, svcname);
 232                 goto bool_done;
 233         }
 234         prop = scf_property_create(handle);
 235         value = scf_value_create(handle);
 236         if (prop == NULL || value == NULL) {
 237                 nd_error(nhdl, "Failed to allocate SMF structures");
 238                 goto bool_done;
 239         }
 240         if (scf_pg_get_property(pg, propname, prop) == -1 ||
 241             scf_property_get_value(prop, value) == -1 ||
 242             scf_value_get_boolean(value, val) == -1) {
 243                 nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
 244                     scf_strerror(scf_error()));
 245                 goto bool_done;
 246         }
 247         ret = 0;
 248 
 249 bool_done:
 250         scf_value_destroy(value);
 251         scf_property_destroy(prop);
 252         scf_pg_destroy(pg);
 253         scf_handle_destroy(handle);
 254 
 255         return (ret);
 256 }
 257 
 258 char *
 259 nd_get_event_fmri(nd_hdl_t *nhdl, fmev_t ev)
 260 {
 261         nvlist_t *ev_nvl, *attr_nvl;
 262         char *svcname;
 263 
 264         if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
 265                 nd_error(nhdl, "Failed to lookup event attr nvlist");
 266                 return (NULL);
 267         }
 268         if (nvlist_lookup_nvlist(ev_nvl, "attr", &attr_nvl) ||
 269             nvlist_lookup_string(attr_nvl, "svc-string", &svcname)) {
 270                 nd_error(nhdl, "Malformed event 0x%p", (void *)ev_nvl);
 271                 return (NULL);
 272         }
 273 
 274         return (strdup((const char *)svcname));
 275 }
 276 
 277 int
 278 nd_get_notify_prefs(nd_hdl_t *nhdl, const char *mech, fmev_t ev,
 279     nvlist_t ***pref_nvl, uint_t *nprefs)
 280 {
 281         nvlist_t *ev_nvl, *top_nvl, **np_nvlarr, *mech_nvl;
 282         int ret = 1;
 283         uint_t nelem;
 284 
 285         if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
 286                 nd_error(nhdl, "Failed to lookup event attr nvlist");
 287                 return (-1);
 288         }
 289 
 290         if ((ret = smf_notify_get_params(&top_nvl, ev_nvl)) != SCF_SUCCESS) {
 291                 ret = scf_error();
 292                 if (ret == SCF_ERROR_NOT_FOUND) {
 293                         nd_debug(nhdl, "No notification preferences specified "
 294                             "for this event");
 295                         goto pref_done;
 296                 } else {
 297                         nd_error(nhdl, "Error looking up notification "
 298                             "preferences (%s)", scf_strerror(ret));
 299                         nd_dump_nvlist(nhdl, top_nvl);
 300                         goto pref_done;
 301                 }
 302         }
 303 
 304         if (nvlist_lookup_nvlist_array(top_nvl, SCF_NOTIFY_PARAMS, &np_nvlarr,
 305             &nelem) != 0) {
 306                 nd_error(nhdl, "Malformed nvlist");
 307                 nd_dump_nvlist(nhdl, top_nvl);
 308                 ret = 1;
 309                 goto pref_done;
 310         }
 311         *pref_nvl = malloc(nelem * sizeof (nvlist_t *));
 312         *nprefs = 0;
 313 
 314         for (int i = 0; i < nelem; i++) {
 315                 if (nvlist_lookup_nvlist(np_nvlarr[i], mech, &mech_nvl) == 0) {
 316                         (void) nvlist_dup(mech_nvl, *pref_nvl + *nprefs, 0);
 317                         ++*nprefs;
 318                 }
 319         }
 320 
 321         if (*nprefs == 0) {
 322                 nd_debug(nhdl, "No %s notification preferences specified",
 323                     mech);
 324                 free(*pref_nvl);
 325                 ret = SCF_ERROR_NOT_FOUND;
 326                 goto pref_done;
 327         }
 328         ret = 0;
 329 pref_done:
 330         nvlist_free(top_nvl);
 331         return (ret);
 332 }
 333 
 334 static int
 335 nd_seq_search(char *key, char **list, uint_t nelem)
 336 {
 337         for (int i = 0; i < nelem; i++)
 338                 if (strcmp(key, list[i]) == 0)
 339                         return (1);
 340         return (0);
 341 }
 342 
 343 /*
 344  * This function takes a single string list and splits it into
 345  * an string array (analogous to PERL split)
 346  *
 347  * The caller is responsible for freeing the array.
 348  */
 349 int
 350 nd_split_list(nd_hdl_t *nhdl, char *list, char *delim, char ***arr,
 351     uint_t *nelem)
 352 {
 353         char *item, *tmpstr;
 354         int i = 1, size = 1;
 355 
 356         tmpstr = strdup(list);
 357         item = strtok(tmpstr, delim);
 358         while (item && strtok(NULL, delim) != NULL)
 359                 size++;
 360         free(tmpstr);
 361 
 362         if ((*arr = calloc(size, sizeof (char *))) == NULL) {
 363                 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
 364                 return (-1);
 365         }
 366         if (size == 1)
 367                 (*arr)[0] = strdup(list);
 368         else {
 369                 tmpstr = strdup(list);
 370                 item = strtok(tmpstr, delim);
 371                 (*arr)[0] = strdup(item);
 372                 while ((item = strtok(NULL, delim)) != NULL)
 373                         (*arr)[i++] = strdup(item);
 374                 free(tmpstr);
 375         }
 376         *nelem = size;
 377         return (0);
 378 }
 379 
 380 /*
 381  * This function merges two string arrays into a single array, removing any
 382  * duplicates
 383  *
 384  * The caller is responsible for freeing the merged array.
 385  */
 386 int
 387 nd_merge_strarray(nd_hdl_t *nhdl, char **arr1, uint_t n1, char **arr2,
 388     uint_t n2, char ***buf)
 389 {
 390         char **tmparr;
 391         int uniq = -1;
 392 
 393         tmparr = alloca((n1 + n2) * sizeof (char *));
 394         bzero(tmparr, (n1 + n2) * sizeof (char *));
 395 
 396         while (++uniq < n1)
 397                 tmparr[uniq] = strdup(arr1[uniq]);
 398 
 399         for (int j = 0; j < n2; j++)
 400                 if (!nd_seq_search(arr2[j], tmparr, uniq))
 401                         tmparr[uniq++] = strdup(arr2[j]);
 402 
 403         if ((*buf = calloc(uniq, sizeof (char *))) == NULL) {
 404                 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
 405                 for (int j = 0; j < uniq; j++) {
 406                         if (tmparr[j])
 407                                 free(tmparr[j]);
 408                 }
 409                 return (-1);
 410         }
 411 
 412         bcopy(tmparr, *buf, uniq * sizeof (char *));
 413         return (uniq);
 414 }
 415 
 416 void
 417 nd_free_strarray(char **arr, uint_t arrsz)
 418 {
 419         for (uint_t i = 0; i < arrsz; i++)
 420                 free(arr[i]);
 421         free(arr);
 422 }
 423 
 424 /*
 425  * This function joins all the strings in a string array into a single string
 426  * Each element will be delimited by a comma
 427  *
 428  * The caller is responsible for freeing the joined string.
 429  */
 430 int
 431 nd_join_strarray(nd_hdl_t *nhdl, char **arr, uint_t arrsz, char **buf)
 432 {
 433         uint_t len = 0;
 434         char *jbuf;
 435         int i;
 436 
 437         /*
 438          * First, figure out how much space we need to allocate to store the
 439          * joined string.
 440          */
 441         for (i = 0; i < arrsz; i++)
 442                 len += strlen(arr[i]) + 1;
 443 
 444         if ((jbuf = calloc(len, sizeof (char))) == NULL) {
 445                 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
 446                 return (-1);
 447         }
 448 
 449         (void) snprintf(jbuf, len, "%s", arr[0]);
 450         for (i = 1; i < arrsz; i++) {
 451                 (void) strlcat(jbuf, ",", len);
 452                 (void) strlcat(jbuf, arr[i], len);
 453         }
 454 
 455         *buf = jbuf;
 456         return (0);
 457 }
 458 
 459 void
 460 nd_free_nvlarray(nvlist_t **arr, uint_t arrsz)
 461 {
 462         for (uint_t i = 0; i < arrsz; i++)
 463                 nvlist_free(arr[i]);
 464         free(arr);
 465 }
 466 
 467 /*
 468  * This function takes a dictionary name and event class and then uses
 469  * libdiagcode to compute the MSG ID.  We need this for looking up messages
 470  * for the committed ireport.* events.  For FMA list.* events, the MSG ID is
 471  * is contained in the event payload.
 472  */
 473 int
 474 nd_get_diagcode(nd_hdl_t *nhdl, const char *dict, const char *class, char *buf,
 475     size_t buflen)
 476 {
 477         fm_dc_handle_t *dhp;
 478         size_t dlen;
 479         char *dirpath;
 480         const char *key[2];
 481         int ret = 0;
 482 
 483         dlen = (strlen(nhdl->nh_rootdir) + strlen(ND_DICTDIR) + 2);
 484         dirpath = alloca(dlen);
 485         (void) snprintf(dirpath, dlen, "%s/%s", nhdl->nh_rootdir, ND_DICTDIR);
 486 
 487         if ((dhp = fm_dc_opendict(FM_DC_VERSION, dirpath, dict)) == NULL) {
 488                 nd_error(nhdl, "fm_dc_opendict failed for %s/%s",
 489                     dirpath, dict);
 490                 return (-1);
 491         }
 492 
 493         key[0] = class;
 494         key[1] = NULL;
 495         if (fm_dc_key2code(dhp, key, buf, buflen) < 0) {
 496                 nd_error(nhdl, "fm_dc_key2code failed for %s", key[0]);
 497                 ret = -1;
 498         }
 499         fm_dc_closedict(dhp);
 500         return (ret);
 501 }
 502 
 503 /*
 504  * This function takes an event and extracts the bits of the event payload that
 505  * are of interest to notification daemons and conveniently tucks them into a
 506  * single struct.
 507  *
 508  * The caller is responsible for freeing ev_info and any contained strings and
 509  * nvlists.  A convenience function, nd_free_event_info(), is provided for this
 510  * purpose.
 511  */
 512 int
 513 nd_get_event_info(nd_hdl_t *nhdl, const char *class, fmev_t ev,
 514     nd_ev_info_t **ev_info)
 515 {
 516         nvlist_t *ev_nvl, *attr_nvl;
 517         nd_ev_info_t *evi;
 518         char *code, *uuid, *fmri, *from_state, *to_state, *reason;
 519 
 520         if ((evi = calloc(1, sizeof (nd_ev_info_t))) == NULL) {
 521                 nd_error(nhdl, "Failed to allocate memory");
 522                 return (-1);
 523         }
 524 
 525         /*
 526          * Hold event; class and payload will be valid for as long as
 527          * we hold the event.
 528          */
 529         fmev_hold(ev);
 530         evi->ei_ev = ev;
 531         ev_nvl = fmev_attr_list(ev);
 532 
 533         /*
 534          * Lookup the MSGID, event description and severity and KA URL
 535          *
 536          * For FMA list.* events we just pull it out of the the event nvlist.
 537          * For all other events we call a utility function that computes the
 538          * diagcode using the dict name and class.
 539          */
 540         evi->ei_diagcode = calloc(32, sizeof (char));
 541         if ((nvlist_lookup_string(ev_nvl, FM_SUSPECT_DIAG_CODE, &code) == 0 &&
 542             strcpy(evi->ei_diagcode, code)) ||
 543             nd_get_diagcode(nhdl, "SMF", class, evi->ei_diagcode, 32)
 544             == 0) {
 545                 evi->ei_severity = fmd_msg_getitem_id(nhdl->nh_msghdl,
 546                     NULL, evi->ei_diagcode, FMD_MSG_ITEM_SEVERITY);
 547                 evi->ei_descr = fmd_msg_getitem_id(nhdl->nh_msghdl,
 548                     NULL, evi->ei_diagcode, FMD_MSG_ITEM_DESC);
 549                 evi->ei_url = fmd_msg_getitem_id(nhdl->nh_msghdl,
 550                     NULL, evi->ei_diagcode, FMD_MSG_ITEM_URL);
 551         } else
 552                 (void) strcpy(evi->ei_diagcode, ND_UNKNOWN);
 553 
 554         if (!evi->ei_severity)
 555                 evi->ei_severity = strdup(ND_UNKNOWN);
 556         if (!evi->ei_descr)
 557                 evi->ei_descr = strdup(ND_UNKNOWN);
 558         if (!evi->ei_url)
 559                 evi->ei_url = strdup(ND_UNKNOWN);
 560 
 561         evi->ei_payload = ev_nvl;
 562         evi->ei_class = fmev_class(ev);
 563         if (nvlist_lookup_string(ev_nvl, FM_SUSPECT_UUID, &uuid) == 0)
 564                 evi->ei_uuid = strdup(uuid);
 565         else {
 566                 nd_error(nhdl, "Malformed event");
 567                 nd_dump_nvlist(nhdl, evi->ei_payload);
 568                 nd_free_event_info(evi);
 569                 return (-1);
 570         }
 571 
 572         if (strncmp(class, "ireport.os.smf", 14) == 0) {
 573                 if ((fmri = nd_get_event_fmri(nhdl, ev)) == NULL) {
 574                         nd_error(nhdl, "Failed to get fmri from event payload");
 575                         nd_free_event_info(evi);
 576                         return (-1);
 577                 }
 578                 if (nvlist_lookup_nvlist(evi->ei_payload, "attr", &attr_nvl) ||
 579                     nvlist_lookup_string(attr_nvl, "from-state", &from_state) ||
 580                     nvlist_lookup_string(attr_nvl, "to-state", &to_state) ||
 581                     nvlist_lookup_string(attr_nvl, "reason-long", &reason)) {
 582                         nd_error(nhdl, "Malformed event");
 583                         nd_dump_nvlist(nhdl, evi->ei_payload);
 584                         nd_free_event_info(evi);
 585                         free(fmri);
 586                         return (-1);
 587                 }
 588                 evi->ei_fmri = fmri;
 589                 evi->ei_to_state = strdup(to_state);
 590                 evi->ei_from_state = strdup(from_state);
 591                 evi->ei_reason = strdup(reason);
 592         }
 593         *ev_info = evi;
 594         return (0);
 595 }
 596 
 597 void
 598 nd_free_event_info(nd_ev_info_t *ev_info)
 599 {
 600         free(ev_info->ei_severity);
 601         free(ev_info->ei_descr);
 602         free(ev_info->ei_diagcode);
 603         free(ev_info->ei_url);
 604         free(ev_info->ei_uuid);
 605         free(ev_info->ei_fmri);
 606         free(ev_info->ei_from_state);
 607         free(ev_info->ei_to_state);
 608         free(ev_info->ei_reason);
 609         fmev_rele(ev_info->ei_ev);
 610         free(ev_info);
 611 }