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.
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.
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]
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
26 #include <sys/fm/protocol.h>
27 #include <fm/fmd_snmp.h>
28 #include <fm/fmd_msg.h>
29 #include <fm/libfmevent.h>
30 #include <net-snmp/net-snmp-config.h>
31 #include <net-snmp/net-snmp-includes.h>
32 #include <net-snmp/agent/net-snmp-agent-includes.h>
42 #include <priv_utils.h>
44 #include "libfmnotify.h"
47 * Debug messages can be enabled by setting the debug property to true
49 * # svccfg -s svc:/system/fm/snmp-notify setprop config/debug=true
51 #define SVCNAME "system/fm/snmp-notify"
53 typedef struct ireport_trap
{
62 boolean_t is_stn_event
;
65 static nd_hdl_t
*nhdl
;
66 static const char optstr
[] = "dfR:";
67 static const char SNMP_SUPPCONF
[] = "fmd-trapgen";
68 static char hostname
[MAXHOSTNAMELEN
+ 1];
71 usage(const char *pname
)
73 (void) fprintf(stderr
, "Usage: %s [-df] [-R <altroot>]\n", pname
);
75 (void) fprintf(stderr
,
76 "\t-d enable debug mode\n"
77 "\t-f stay in foreground\n"
78 "\t-R specify alternate root\n");
84 * If someone does an "svcadm refresh" on us, then this function gets called,
85 * which rereads our service configuration.
93 s
= nd_get_boolean_prop(nhdl
, SVCNAME
, "config", "debug", &val
);
96 s
+= nd_get_astring_prop(nhdl
, SVCNAME
, "config", "rootdir",
100 nd_error(nhdl
, "Failed to read retrieve service "
105 nd_sighandler(int sig
)
114 get_snmp_prefs(nd_hdl_t
*nhdl
, nvlist_t
**pref_nvl
, uint_t npref
)
121 * For SMF state transition events, pref_nvl contain two sets of
122 * preferences, which will have to be merged.
124 * The "snmp" nvlist currently only supports a single boolean member,
125 * "active" which will be set to true, if it is true in either set
128 r
= nvlist_lookup_boolean_array(pref_nvl
[0], "active", &a1
, &n
);
129 r
+= nvlist_lookup_boolean_array(pref_nvl
[1], "active", &a2
,
132 nd_debug(nhdl
, "Malformed snmp notification "
134 nd_dump_nvlist(nhdl
, pref_nvl
[0]);
135 nd_dump_nvlist(nhdl
, pref_nvl
[1]);
137 } else if (!a1
[0] && !a2
[0]) {
138 nd_debug(nhdl
, "SNMP notification is disabled");
142 if (nvlist_lookup_boolean_array(pref_nvl
[0], "active",
144 nd_debug(nhdl
, "Malformed snmp notification "
146 nd_dump_nvlist(nhdl
, pref_nvl
[0]);
149 nd_debug(nhdl
, "SNMP notification is disabled");
157 send_ireport_trap(ireport_trap_t
*t
)
159 static const oid sunIreportTrap_oid
[] =
160 { SUNIREPORTTRAP_OID
};
161 const size_t sunIreportTrap_len
=
162 OID_LENGTH(sunIreportTrap_oid
);
164 static const oid sunIreportHostname_oid
[] =
165 { SUNIREPORTHOSTNAME_OID
};
166 static const oid sunIreportMsgid_oid
[] =
167 { SUNIREPORTMSGID_OID
};
168 static const oid sunIreportDescription_oid
[] =
169 { SUNIREPORTDESCRIPTION_OID
};
170 static const oid sunIreportTime_oid
[] =
171 { SUNIREPORTTIME_OID
};
173 static const oid sunIreportSmfFmri_oid
[] =
174 { SUNIREPORTSMFFMRI_OID
};
175 static const oid sunIreportSmfFromState_oid
[] =
176 { SUNIREPORTSMFFROMSTATE_OID
};
177 static const oid sunIreportSmfToState_oid
[] =
178 { SUNIREPORTSMFTOSTATE_OID
};
179 static const oid sunIreportSmfTransitionReason_oid
[] =
180 { SUNIREPORTTRANSITIONREASON_OID
};
182 sunIreport_base_len
= OID_LENGTH(sunIreportHostname_oid
);
184 size_t var_len
= sunIreport_base_len
+ 1;
185 oid var_name
[MAX_OID_LEN
];
187 netsnmp_variable_list
*notification_vars
= NULL
;
190 uchar_t dt
[11], *tdt
;
191 time_t ts
= t
->tstamp
;
193 tdt
= date_n_time(&ts
, &dt_len
);
195 * We know date_n_time is broken, it returns a buffer from
196 * its stack. So we copy before we step over it!
198 for (int i
= 0; i
< dt_len
; ++i
)
201 if (var_len
> MAX_OID_LEN
) {
202 nd_error(nhdl
, "var_len %d > MAX_OID_LEN %d\n", var_len
,
207 (void) memcpy(var_name
, sunIreportHostname_oid
, sunIreport_base_len
*
209 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
210 sunIreport_base_len
+ 1, ASN_OCTET_STR
, (uchar_t
*)t
->host
,
213 (void) memcpy(var_name
, sunIreportMsgid_oid
,
214 sunIreport_base_len
* sizeof (oid
));
215 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
216 sunIreport_base_len
+ 1, ASN_OCTET_STR
, (uchar_t
*)t
->msgid
,
219 (void) memcpy(var_name
, sunIreportDescription_oid
,
220 sunIreport_base_len
* sizeof (oid
));
221 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
222 sunIreport_base_len
+ 1, ASN_OCTET_STR
, (uchar_t
*)t
->desc
,
225 (void) memcpy(var_name
, sunIreportTime_oid
, sunIreport_base_len
*
227 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
228 sunIreport_base_len
+ 1, ASN_OCTET_STR
, dt
, dt_len
);
230 if (t
->is_stn_event
) {
231 (void) memcpy(var_name
, sunIreportSmfFmri_oid
,
232 sunIreport_base_len
* sizeof (oid
));
233 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
234 sunIreport_base_len
+ 1, ASN_OCTET_STR
, (uchar_t
*)t
->fmri
,
237 (void) memcpy(var_name
, sunIreportSmfFromState_oid
,
238 sunIreport_base_len
* sizeof (oid
));
239 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
240 sunIreport_base_len
+ 1, ASN_INTEGER
,
241 (uchar_t
*)&t
->from_state
, sizeof (uint32_t));
243 (void) memcpy(var_name
, sunIreportSmfToState_oid
,
244 sunIreport_base_len
* sizeof (oid
));
245 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
246 sunIreport_base_len
+ 1, ASN_INTEGER
,
247 (uchar_t
*)&t
->to_state
, sizeof (uint32_t));
249 (void) memcpy(var_name
, sunIreportSmfTransitionReason_oid
,
250 sunIreport_base_len
* sizeof (oid
));
251 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
,
252 sunIreport_base_len
+ 1, ASN_OCTET_STR
,
253 (uchar_t
*)t
->reason
, strlen(t
->reason
));
257 * This function is capable of sending both v1 and v2/v3 traps.
258 * Which is sent to a specific destination is determined by the
259 * configuration file(s).
261 send_enterprise_trap_vars(SNMP_TRAP_ENTERPRISESPECIFIC
,
262 sunIreportTrap_oid
[sunIreportTrap_len
- 1],
263 (oid
*)sunIreportTrap_oid
, sunIreportTrap_len
- 2,
265 nd_debug(nhdl
, "Sent SNMP trap for %s", t
->msgid
);
267 snmp_free_varbind(notification_vars
);
273 send_fm_trap(const char *uuid
, const char *code
, const char *url
)
275 static const oid sunFmProblemTrap_oid
[] = { SUNFMPROBLEMTRAP_OID
};
276 const size_t sunFmProblemTrap_len
= OID_LENGTH(sunFmProblemTrap_oid
);
278 static const oid sunFmProblemUUID_oid
[] =
279 { SUNFMPROBLEMTABLE_OID
, 1, SUNFMPROBLEM_COL_UUID
};
280 static const oid sunFmProblemCode_oid
[] =
281 { SUNFMPROBLEMTABLE_OID
, 1, SUNFMPROBLEM_COL_CODE
};
282 static const oid sunFmProblemURL_oid
[] =
283 { SUNFMPROBLEMTABLE_OID
, 1, SUNFMPROBLEM_COL_URL
};
285 const size_t sunFmProblem_base_len
= OID_LENGTH(sunFmProblemUUID_oid
);
287 size_t uuid_len
= strlen(uuid
);
288 size_t var_len
= sunFmProblem_base_len
+ 1 + uuid_len
;
289 oid var_name
[MAX_OID_LEN
];
291 netsnmp_variable_list
*notification_vars
= NULL
;
294 * The format of our trap varbinds' oids is as follows:
296 * +-----------------------+---+--------+----------+------+
297 * | SUNFMPROBLEMTABLE_OID | 1 | column | uuid_len | uuid |
298 * +-----------------------+---+--------+----------+------+
301 * A common mistake here is to send the trap with varbinds that
302 * do not contain the index. All the indices are the same, and
303 * all the oids are the same length, so the only thing we need to
304 * do for each varbind is set the table and column parts of the
308 if (var_len
> MAX_OID_LEN
)
311 var_name
[sunFmProblem_base_len
] = (oid
)uuid_len
;
312 for (int i
= 0; i
< uuid_len
; i
++)
313 var_name
[i
+ sunFmProblem_base_len
+ 1] = (oid
)uuid
[i
];
316 * Ordinarily, we would need to add the OID of the trap itself
317 * to the head of the variable list; this is required by SNMP v2.
318 * However, send_enterprise_trap_vars does this for us as a part
319 * of converting between v1 and v2 traps, so we skip directly to
320 * the objects we're sending.
323 (void) memcpy(var_name
, sunFmProblemUUID_oid
,
324 sunFmProblem_base_len
* sizeof (oid
));
325 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
, var_len
,
326 ASN_OCTET_STR
, (uchar_t
*)uuid
, strlen(uuid
));
327 (void) memcpy(var_name
, sunFmProblemCode_oid
,
328 sunFmProblem_base_len
* sizeof (oid
));
329 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
, var_len
,
330 ASN_OCTET_STR
, (uchar_t
*)code
, strlen(code
));
331 (void) memcpy(var_name
, sunFmProblemURL_oid
,
332 sunFmProblem_base_len
* sizeof (oid
));
333 (void) snmp_varlist_add_variable(¬ification_vars
, var_name
, var_len
,
334 ASN_OCTET_STR
, (uchar_t
*)url
, strlen(url
));
337 * This function is capable of sending both v1 and v2/v3 traps.
338 * Which is sent to a specific destination is determined by the
339 * configuration file(s).
341 send_enterprise_trap_vars(SNMP_TRAP_ENTERPRISESPECIFIC
,
342 sunFmProblemTrap_oid
[sunFmProblemTrap_len
- 1],
343 (oid
*)sunFmProblemTrap_oid
, sunFmProblemTrap_len
- 2,
345 nd_debug(nhdl
, "Sent SNMP trap for %s", code
);
347 snmp_free_varbind(notification_vars
);
351 * The SUN-IREPORT-MIB declares the following enum to represent SMF service
354 * offline(0), online(1), degraded(2), disabled(3), maintenance(4),
357 * This function converts a string representation of an SMF service state
358 * to it's corresponding enum val.
361 state_to_val(char *statestr
, uint32_t *stateval
)
363 if (strcmp(statestr
, "offline") == 0)
365 else if (strcmp(statestr
, "online") == 0)
367 else if (strcmp(statestr
, "degraded") == 0)
369 else if (strcmp(statestr
, "disabled") == 0)
371 else if (strcmp(statestr
, "maintenance") == 0)
373 else if (strcmp(statestr
, "uninitialized") == 0)
382 ireport_cb(fmev_t ev
, const char *class, nvlist_t
*nvl
, void *arg
)
384 nvlist_t
**pref_nvl
= NULL
;
385 nd_ev_info_t
*ev_info
= NULL
;
386 ireport_trap_t swtrap
;
390 nd_debug(nhdl
, "Received event of class %s", class);
392 ret
= nd_get_notify_prefs(nhdl
, "snmp", ev
, &pref_nvl
, &npref
);
393 if (ret
== SCF_ERROR_NOT_FOUND
) {
395 * No snmp notification preferences specified for this type of
396 * event, so we're done
399 } else if (ret
!= 0) {
400 nd_error(nhdl
, "Failed to retrieve notification preferences "
405 if (get_snmp_prefs(nhdl
, pref_nvl
, npref
) != 0)
408 if (nd_get_event_info(nhdl
, class, ev
, &ev_info
) != 0)
411 swtrap
.host
= hostname
;
412 swtrap
.msgid
= ev_info
->ei_diagcode
;
413 swtrap
.desc
= ev_info
->ei_descr
;
414 swtrap
.tstamp
= (time_t)fmev_time_sec(ev
);
416 if (strncmp(class, "ireport.os.smf", 14) == 0) {
417 swtrap
.fmri
= ev_info
->ei_fmri
;
418 if (state_to_val(ev_info
->ei_from_state
, &swtrap
.from_state
)
420 state_to_val(ev_info
->ei_to_state
, &swtrap
.to_state
) < 0) {
421 nd_error(nhdl
, "Malformed event - invalid svc state");
422 nd_dump_nvlist(nhdl
, ev_info
->ei_payload
);
425 swtrap
.reason
= ev_info
->ei_reason
;
426 swtrap
.is_stn_event
= B_TRUE
;
428 send_ireport_trap(&swtrap
);
431 nd_free_event_info(ev_info
);
432 nd_free_nvlarray(pref_nvl
, npref
);
437 list_cb(fmev_t ev
, const char *class, nvlist_t
*nvl
, void *arg
)
441 nd_ev_info_t
*ev_info
= NULL
;
442 nvlist_t
**pref_nvl
= NULL
;
447 nd_debug(nhdl
, "Received event of class %s", class);
449 ret
= nd_get_notify_prefs(nhdl
, "snmp", ev
, &pref_nvl
, &npref
);
450 if (ret
== SCF_ERROR_NOT_FOUND
) {
452 * No snmp notification preferences specified for this type of
453 * event, so we're done
456 } else if (ret
!= 0) {
457 nd_error(nhdl
, "Failed to retrieve notification preferences "
462 if (get_snmp_prefs(nhdl
, pref_nvl
, npref
) != 0)
465 if (nd_get_event_info(nhdl
, class, ev
, &ev_info
) != 0)
469 * If the message payload member is set to 0, then it's an event we
470 * typically suppress messaging on, so we won't send a trap for it.
472 if (nvlist_lookup_boolean_value(ev_info
->ei_payload
, FM_SUSPECT_MESSAGE
,
473 &domsg
) == 0 && !domsg
) {
474 nd_debug(nhdl
, "Messaging suppressed for this event");
478 if (nvlist_lookup_uint8(ev_info
->ei_payload
, FM_VERSION
, &version
)
479 != 0 || version
> FM_SUSPECT_VERSION
) {
480 nd_error(nhdl
, "invalid event version: %u", version
);
484 (void) nvlist_lookup_string(ev_info
->ei_payload
, FM_SUSPECT_UUID
,
487 if (strcmp(ev_info
->ei_url
, ND_UNKNOWN
) != 0)
488 send_fm_trap(uuid
, ev_info
->ei_diagcode
, ev_info
->ei_url
);
490 nd_error(nhdl
, "failed to format url for %s", uuid
);
492 nd_free_nvlarray(pref_nvl
, npref
);
494 nd_free_event_info(ev_info
);
503 * The only place we could possibly log is syslog, but the
504 * full agent doesn't normally log there. It would be confusing
505 * if this agent did so; therefore we disable logging entirely.
510 * Net-SNMP has a provision for reading an arbitrary number of
511 * configuration files. A configuration file is read if it has
512 * had any handlers registered for it, or if it's the value in
513 * of NETSNMP_DS_LIB_APPTYPE. Our objective here is to read
514 * both snmpd.conf and fmd-trapgen.conf.
516 if ((err
= netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID
,
517 NETSNMP_DS_AGENT_ROLE
, 0 /* MASTER_AGENT */)) != SNMPERR_SUCCESS
)
520 init_agent_read_config("snmpd");
521 if ((err
= netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID
,
522 NETSNMP_DS_LIB_APPTYPE
, SNMP_SUPPCONF
)) != SNMPERR_SUCCESS
)
524 if (register_app_config_handler("trapsink", snmpd_parse_config_trapsink
,
525 snmpd_free_trapsinks
, "host [community] [port]") == NULL
)
526 return (SNMPERR_MALLOC
);
527 if (register_app_config_handler("trap2sink",
528 snmpd_parse_config_trap2sink
, NULL
, "host [community] [port]") ==
530 return (SNMPERR_MALLOC
);
531 if (register_app_config_handler("trapsess", snmpd_parse_config_trapsess
,
532 NULL
, "[snmpcmdargs] host") == NULL
)
533 return (SNMPERR_MALLOC
);
536 init_snmp(SNMP_SUPPCONF
);
538 return (SNMPERR_SUCCESS
);
542 main(int argc
, char *argv
[])
545 struct sigaction act
;
548 boolean_t run_fg
= B_FALSE
;
550 if ((nhdl
= malloc(sizeof (nd_hdl_t
))) == NULL
) {
551 (void) fprintf(stderr
, "Failed to allocate space for notifyd "
552 "handle (%s)", strerror(errno
));
555 bzero(nhdl
, sizeof (nd_hdl_t
));
556 nhdl
->nh_keep_running
= B_TRUE
;
557 nhdl
->nh_log_fd
= stderr
;
558 nhdl
->nh_pname
= argv
[0];
563 * In the case where we get started outside of SMF, args passed on the
564 * command line override SMF property setting
566 while (optind
< argc
) {
567 while ((c
= getopt(argc
, argv
, optstr
)) != -1) {
570 nhdl
->nh_debug
= B_TRUE
;
576 nhdl
->nh_rootdir
= strdup(optarg
);
580 return (usage(nhdl
->nh_pname
));
586 * Set up a signal handler for SIGTERM (and SIGINT if we'll
587 * be running in the foreground) to ensure sure we get a chance to exit
588 * in an orderly fashion. We also catch SIGHUP, which will be sent to
589 * us by SMF if the service is refreshed.
591 (void) sigfillset(&set
);
592 (void) sigfillset(&act
.sa_mask
);
593 act
.sa_handler
= nd_sighandler
;
596 (void) sigaction(SIGTERM
, &act
, NULL
);
597 (void) sigdelset(&set
, SIGTERM
);
598 (void) sigaction(SIGHUP
, &act
, NULL
);
599 (void) sigdelset(&set
, SIGHUP
);
602 (void) sigaction(SIGINT
, &act
, NULL
);
603 (void) sigdelset(&set
, SIGINT
);
607 rlim
.rlim_cur
= RLIM_INFINITY
;
608 rlim
.rlim_max
= RLIM_INFINITY
;
609 (void) setrlimit(RLIMIT_CORE
, &rlim
);
612 * We need to be root initialize our libfmevent handle (because that
613 * involves reading/writing to /dev/sysevent), so we do this before
614 * calling __init_daemon_priv.
616 nhdl
->nh_evhdl
= fmev_shdl_init(LIBFMEVENT_VERSION_2
, NULL
, NULL
, NULL
);
617 if (nhdl
->nh_evhdl
== NULL
) {
619 nd_abort(nhdl
, "failed to initialize libfmevent: %s",
620 fmev_strerror(fmev_errno
));
624 * If we're in the global zone, reset all of our privilege sets to
625 * the minimum set of required privileges. We also change our
626 * uid/gid to noaccess/noaccess
628 * __init_daemon_priv will also set the process core path for us
631 if (getzoneid() == GLOBAL_ZONEID
)
632 if (__init_daemon_priv(
633 PU_RESETGROUPS
| PU_LIMITPRIVS
| PU_INHERITPRIVS
,
634 60002, 60002, PRIV_FILE_DAC_READ
, NULL
) != 0)
635 nd_abort(nhdl
, "additional privileges required to run");
637 nhdl
->nh_msghdl
= fmd_msg_init(nhdl
->nh_rootdir
, FMD_MSG_VERSION
);
638 if (nhdl
->nh_msghdl
== NULL
)
639 nd_abort(nhdl
, "failed to initialize libfmd_msg");
641 if (init_sma() != SNMPERR_SUCCESS
)
642 nd_abort(nhdl
, "SNMP initialization failed");
644 (void) gethostname(hostname
, MAXHOSTNAMELEN
+ 1);
646 * Set up our event subscriptions. We subscribe to everything and then
647 * consult libscf when we receive an event to determine what (if any)
648 * notification to send.
650 nd_debug(nhdl
, "Subscribing to ireport.os.smf.* events");
651 if (fmev_shdl_subscribe(nhdl
->nh_evhdl
, "ireport.os.smf.*",
652 ireport_cb
, NULL
) != FMEV_SUCCESS
) {
653 nd_abort(nhdl
, "fmev_shdl_subscribe failed: %s",
654 fmev_strerror(fmev_errno
));
657 nd_debug(nhdl
, "Subscribing to list.* events");
658 if (fmev_shdl_subscribe(nhdl
->nh_evhdl
, "list.*", list_cb
,
659 NULL
) != FMEV_SUCCESS
) {
660 nd_abort(nhdl
, "fmev_shdl_subscribe failed: %s",
661 fmev_strerror(fmev_errno
));
665 * We run until someone kills us
667 while (nhdl
->nh_keep_running
)
668 (void) sigsuspend(&set
);
671 * snmp_shutdown, which we would normally use here, calls free_slots,
672 * a callback that is supposed to tear down the pkcs11 state; however,
673 * it abuses C_Finalize, causing fmd to drop core on shutdown. Avoid
674 * this by shutting down the library piecemeal.
676 snmp_store(SNMP_SUPPCONF
);
677 snmp_alarm_unregister_all();
678 (void) snmp_close_sessions();
680 unregister_all_config_handlers();
681 netsnmp_ds_shutdown();
683 free(nhdl
->nh_rootdir
);