#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
SUBDIRS = \
smtp-notify
# Hammerhead: snmp-notify requires net-snmp (external package)
# snmp-notify
include ./Makefile.subdirs
include ../../Makefile.cmd
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
.KEEP_STATE:
all : TARGET += all
clean : TARGET += clean
clobber : TARGET += clobber
install : TARGET += install
install_h : TARGET += install_h
lint : TARGET += lint
all clean clobber install install_h lint: $(SUBDIRS)
$(filter-out .WAIT,$(SUBDIRS)): FRC
@cd $@; pwd; $(MAKE) $(TARGET)
FRC:
Solaris Fault Manager notification parameters
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
# Hammerhead: amd64-only
SUBDIRS = $(MACH64)
include ../Makefile.subdirs
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
.KEEP_STATE:
.SUFFIXES:
SRCS += smtp-notify.c
OBJS = $(SRCS:%.c=%.o)
LINTFILES = $(SRCS:%.c=%.ln)
PROG = smtp-notify
HELPER = process_msg_template.sh
ROOTLIBFM = $(ROOT)/usr/lib/fm
ROOTLIBNOTIFY = $(ROOT)/usr/lib/fm/notify
ROOTPROG = $(ROOTLIBNOTIFY)/$(PROG)
ROOTHELPER = $(ROOTLIBNOTIFY)/$(HELPER)
ROOTMANIFESTDIR = $(ROOTSVCSYSTEM)/fm
ROOTMANIFEST = $(ROOTMANIFESTDIR)/$(PROG).xml
ROOTNOTIFYPARAMS = $(ROOTMANIFESTDIR)/notify-params.xml
$(ROOTMANIFEST) : FILEMODE = 0444
$(ROOTNOTIFYPARAMS) : FILEMODE = 0444
$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
CPPFLAGS += -I. -I../common -I../../../../../lib/fm/libfmnotify/common
CSTD = $(CSTD_GNU99)
CFLAGS += $(CTF_FLAGS) $(CCVERBOSE)
CFLAGS64 += $(CTF_FLAGS_64) $(CCVERBOSE)
LDLIBS += -L$(ROOT)/usr/lib/fm -lnvpair -lfmevent -lfmd_msg -lfmnotify \
-lumem
LDFLAGS += -R/usr/lib/fm
# Hammerhead: GNU ld needs rpath-link for transitive deps (libtopo, libdiagcode)
LDFLAGS += -Wl,-rpath-link,$(ROOT)/usr/lib/fm
LINTFLAGS += -mnu
CERRWARN += -Wno-parentheses
.NOTPARALLEL:
.PARALLEL: $(OBJS) $(LINTFILES)
all: $(PROG) $(HELPER)
$(PROG): $(OBJS)
$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
$(CTFMERGE) -L VERSION -o $@ $(OBJS)
$(POST_PROCESS)
$(HELPER): ../common/$(HELPER)
$(CP) ../common/$(HELPER) .
%.o: ../common/%.c
$(COMPILE.c) $<
$(CTFCONVERT_O)
%.o: %.c
$(COMPILE.c) $<
$(CTFCONVERT_O)
clean:
$(RM) $(OBJS) $(HELPER) $(LINTFILES)
clobber: clean
$(RM) $(PROG)
%.ln: ../common/%.c
$(LINT.c) -c $<
%.ln: %.c
$(LINT.c) -c $<
lint: $(LINTFILES)
$(LINT) $(LINTFLAGS) $(LINTFILES)
$(ROOTLIBNOTIFY):
$(INS.dir)
$(ROOTLIBNOTIFY)/%: %
$(INS.file)
$(ROOTMANIFESTDIR):
$(INS.dir)
$(ROOTMANIFESTDIR)/%.xml: ../common/%.xml
$(INS.file)
$(ROOTMANIFESTDIR)/notify-params.xml: ../../notify-params.xml
$(INS.file)
install_h:
install: all $(ROOTLIBNOTIFY) $(ROOTPROG) $(ROOTHELPER) $(ROOTMANIFESTDIR) \
$(ROOTMANIFEST) $(ROOTNOTIFYPARAMS)
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#
# Copyright 2025 Hammerhead Project
#
# Hammerhead: Include Makefile.cmd first to load Makefile.master
# (defines COMPILE.c, COMPILE64.c needed by Makefile.com's pattern rules)
include $(SRC)/cmd/Makefile.cmd
include ../Makefile.com
include $(SRC)/cmd/Makefile.cmd.64
install: all $(ROOTPROG64)
#!/bin/sh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
#
# This is a simple helper script for smtp-notify which looks for certain
# expansion macros, which we've committed and converts them to valid
# libfmd_msg macros which directly reference event payload members.
#
# This allows us to change event payload names or alter the libfmd_msg
# expansion macro syntax without breaking user-supplied message body
# templates.
#
# We use all-caps for the committed macro names to avoid colliding
# with an actual event payload member name.
#
# Usage: process_msg_template.sh
#
#
# Verify template exists, is readable and is an ascii text file
#
if [ ! -e $1 ] || [ ! -r $1 ]; then
exit 1
fi
/usr/has/bin/file $1 | grep "ascii text" > /dev/null
if [ $? != 0 ]; then
exit 1
fi
tmpfile1=$2;
tmpfile2=`/usr/bin/mktemp -p /var/tmp`
cat $1 | sed s/\%\/$3/g > $tmpfile1
cat $tmpfile1 | sed s/\%\/\%\/g > $tmpfile2
cat $tmpfile2 | sed s/\%\/\%\/g > $tmpfile1
cat $tmpfile1 | sed s/\%\/$4/g > $tmpfile2
cat $tmpfile2 | sed s/\%\/svc\:\\/\%\\:\%\/g > $tmpfile1
cat $tmpfile1 | sed s/\%\/\%\/g > $tmpfile2
cat $tmpfile2 | sed s/\%\/\%\/g > $tmpfile1
cat $tmpfile1 | sed s/\%\/\%h/g > $tmpfile2
cat $tmpfile2 | sed s/\%\/\%s/g > $tmpfile1
rm -f $tmpfile2
exit 0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libfmnotify.h"
#define SENDMAIL "/usr/sbin/sendmail"
#define SVCNAME "system/fm/smtp-notify"
#define XHDR_HOSTNAME "X-FMEV-HOSTNAME"
#define XHDR_CLASS "X-FMEV-CLASS"
#define XHDR_UUID "X-FMEV-UUID"
#define XHDR_MSGID "X-FMEV-CODE"
#define XHDR_SEVERITY "X-FMEV-SEVERITY"
#define XHDR_FMRI "X-FMEV-FMRI"
#define XHDR_FROM_STATE "X-FMEV-FROM-STATE"
#define XHDR_TO_STATE "X-FMEV-TO-STATE"
/*
* Debug messages can be enabled by setting the debug property to true
*
* # svccfg -s svc:/system/fm/smtp-notify setprop config/debug=true
*
* Debug messages will be spooled to the service log at:
* /var/svc/log/system-fm-smtp-notify:default.log
*/
#define PP_SCRIPT "usr/lib/fm/notify/process_msg_template.sh"
typedef struct email_pref
{
int ep_num_recips;
char **ep_recips;
char *ep_reply_to;
char *ep_template_path;
char *ep_template;
} email_pref_t;
static nd_hdl_t *nhdl;
static char hostname[MAXHOSTNAMELEN + 1];
static const char optstr[] = "dfR:";
static const char DEF_SUBJ_TEMPLATE[] = "smtp-notify-subject-template";
static const char SMF_SUBJ_TEMPLATE[] = "smtp-notify-smf-subject-template";
static const char FM_SUBJ_TEMPLATE[] = "smtp-notify-fm-subject-template";
static const char IREPORT_MSG_TEMPLATE[] = "ireport-msg-template";
static const char SMF_MSG_TEMPLATE[] = "ireport.os.smf-msg-template";
static int
usage(const char *pname)
{
(void) fprintf(stderr, "Usage: %s [-df] [-R ]\n", pname);
(void) fprintf(stderr,
"\t-d enable debug mode\n"
"\t-f stay in foreground\n"
"\t-R specify alternate root\n");
return (1);
}
/*
* This function simply reads the file specified by "template" into a buffer
* and returns a pointer to that buffer (or NULL on failure). The caller is
* responsible for free'ing the returned buffer.
*/
static char *
read_template(const char *template)
{
int fd;
struct stat statb;
char *buf;
if (stat(template, &statb) != 0) {
nd_error(nhdl, "Failed to stat %s (%s)", template,
strerror(errno));
return (NULL);
}
if ((fd = open(template, O_RDONLY)) < 0) {
nd_error(nhdl, "Failed to open %s (%s)", template,
strerror(errno));
return (NULL);
}
if ((buf = malloc(statb.st_size + 1)) == NULL) {
nd_error(nhdl, "Failed to allocate %d bytes", statb.st_size);
(void) close(fd);
return (NULL);
}
if (read(fd, buf, statb.st_size) < 0) {
nd_error(nhdl, "Failed to read in template (%s)",
strerror(errno));
free(buf);
(void) close(fd);
return (NULL);
}
buf[statb.st_size] = '\0';
(void) close(fd);
return (buf);
}
/*
* This function runs a user-supplied message body template through a script
* which replaces the "committed" expansion macros with actual libfmd_msg
* expansion macros.
*/
static int
process_template(nd_ev_info_t *ev_info, email_pref_t *eprefs)
{
char pp_script[PATH_MAX], tmpfile[PATH_MAX], pp_cli[PATH_MAX];
int ret = -1;
(void) snprintf(pp_script, sizeof (pp_script), "%s%s",
nhdl->nh_rootdir, PP_SCRIPT);
(void) snprintf(tmpfile, sizeof (tmpfile), "%s%s",
nhdl->nh_rootdir, tmpnam(NULL));
/*
* If it's an SMF event, then the diagcode and severity won't be part
* of the event payload and so libfmd_msg won't be able to expand them.
* Therefore we pass the code and severity into the script and let the
* script do the expansion.
*/
/* LINTED: E_SEC_SPRINTF_UNBOUNDED_COPY */
(void) sprintf(pp_cli, "%s %s %s %s %s", pp_script,
eprefs->ep_template_path, tmpfile, ev_info->ei_diagcode,
ev_info->ei_severity);
nd_debug(nhdl, "Executing %s", pp_cli);
if (system(pp_cli) != -1)
if ((eprefs->ep_template = read_template(tmpfile)) != NULL)
ret = 0;
(void) unlink(tmpfile);
return (ret);
}
/*
* If someone does an "svcadm refresh" on us, then this function gets called,
* which rereads our service configuration.
*/
static void
get_svc_config()
{
int s = 0;
uint8_t val;
s = nd_get_boolean_prop(nhdl, SVCNAME, "config", "debug", &val);
nhdl->nh_debug = val;
s += nd_get_astring_prop(nhdl, SVCNAME, "config", "rootdir",
&(nhdl->nh_rootdir));
if (s != 0)
nd_error(nhdl, "Failed to read retrieve service "
"properties\n");
}
static void
nd_sighandler(int sig)
{
if (sig == SIGHUP)
get_svc_config();
else
nd_cleanup(nhdl);
}
/*
* This function constructs all the email headers and puts them into the
* "headers" buffer handle. The caller is responsible for free'ing this
* buffer.
*/
static int
build_headers(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs,
char **headers)
{
const char *subj_key;
char *subj_fmt, *subj = NULL;
size_t len;
boolean_t is_smf_event = B_FALSE, is_fm_event = B_FALSE;
/*
* Fetch and format the email subject.
*/
if (strncmp(ev_info->ei_class, "list.", 5) == 0) {
is_fm_event = B_TRUE;
subj_key = FM_SUBJ_TEMPLATE;
} else if (strncmp(ev_info->ei_class, "ireport.os.smf", 14) == 0) {
is_smf_event = B_TRUE;
subj_key = SMF_SUBJ_TEMPLATE;
} else {
subj_key = DEF_SUBJ_TEMPLATE;
}
if ((subj_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, subj_key)) == NULL) {
nd_error(nhdl, "Failed to contruct subject format");
return (-1); /* libfmd_msg error */
}
if (is_fm_event) {
/* LINTED: E_SEC_PRINTF_VAR_FMT */
len = snprintf(NULL, 0, subj_fmt, hostname,
ev_info->ei_diagcode);
subj = alloca(len + 1);
/* LINTED: E_SEC_PRINTF_VAR_FMT */
(void) snprintf(subj, len + 1, subj_fmt, hostname,
ev_info->ei_diagcode);
} else if (is_smf_event) {
/* LINTED: E_SEC_PRINTF_VAR_FMT */
len = snprintf(NULL, 0, subj_fmt, hostname, ev_info->ei_fmri,
ev_info->ei_from_state, ev_info->ei_to_state);
subj = alloca(len + 1);
/* LINTED: E_SEC_PRINTF_VAR_FMT */
(void) snprintf(subj, len + 1, subj_fmt, hostname,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state);
} else {
/* LINTED: E_SEC_PRINTF_VAR_FMT */
len = snprintf(NULL, 0, subj_fmt, hostname);
subj = alloca(len + 1);
/* LINTED: E_SEC_PRINTF_VAR_FMT */
(void) snprintf(subj, len + 1, subj_fmt, hostname);
}
/*
* Here we add some X-headers to our mail message for use by mail
* filtering agents. We add headers for the following bits of event
* data for all events
*
* hostname
* msg id (diagcode)
* event class
* event severity
* event uuid
*
* For SMF transition events, we'll have the following add'l X-headers
*
* from-state
* to-state
* service fmri
*
* We follow the X-headers with standard Reply-To and Subject headers.
*/
if (is_fm_event) {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\nReply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
hostname, XHDR_CLASS, ev_info->ei_class, XHDR_UUID,
ev_info->ei_uuid, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, eprefs->ep_reply_to,
subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\nReply-To: %s\nSubject: %s\n\n",
XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
XHDR_UUID, ev_info->ei_uuid, XHDR_MSGID,
ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
eprefs->ep_reply_to, subj);
} else if (is_smf_event) {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
"Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
"Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
subj);
} else {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"Reply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
hostname, XHDR_CLASS, ev_info->ei_class, XHDR_MSGID,
ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
eprefs->ep_reply_to, subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\nReply-To: %s\nSubject: %s\n\n",
XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
XHDR_MSGID, ev_info->ei_diagcode, XHDR_SEVERITY,
ev_info->ei_severity, eprefs->ep_reply_to, subj);
}
return (0);
}
static void
send_email(nd_hdl_t *nhdl, const char *headers, const char *body,
const char *recip)
{
FILE *mp;
char sm_cli[PATH_MAX];
/*
* Open a pipe to sendmail and pump out the email message
*/
(void) snprintf(sm_cli, PATH_MAX, "%s -t %s", SENDMAIL, recip);
nd_debug(nhdl, "Sending email notification to %s", recip);
if ((mp = popen(sm_cli, "w")) == NULL) {
nd_error(nhdl, "Failed to open pipe to %s (%s)", SENDMAIL,
strerror(errno));
return;
}
if (fprintf(mp, "%s", headers) < 0)
nd_error(nhdl, "Failed to write to pipe (%s)", strerror(errno));
if (fprintf(mp, "%s\n.\n", body) < 0)
nd_error(nhdl, "Failed to write to pipe (%s)",
strerror(errno));
(void) pclose(mp);
}
static void
send_email_template(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs)
{
char *msg, *headers;
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
return;
/*
* If the user specified a message body template, then we pass it
* through a private interface in libfmd_msg, which will return a string
* with any expansion tokens decoded.
*/
if ((msg = fmd_msg_decode_tokens(ev_info->ei_payload,
eprefs->ep_template, ev_info->ei_url)) == NULL) {
nd_error(nhdl, "Failed to parse msg template");
free(headers);
return;
}
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, msg, eprefs->ep_recips[i]);
free(msg);
free(headers);
}
static int
get_email_prefs(nd_hdl_t *nhdl, fmev_t ev, email_pref_t **eprefs)
{
nvlist_t **p_nvl = NULL;
email_pref_t *ep;
uint_t npref, tn1 = 0, tn2 = 0;
char **tmparr1, **tmparr2;
int r, ret = -1;
r = nd_get_notify_prefs(nhdl, "smtp", ev, &p_nvl, &npref);
if (r == SCF_ERROR_NOT_FOUND) {
/*
* No email notification preferences specified for this type of
* event, so we're done
*/
return (-1);
} else if (r != 0) {
nd_error(nhdl, "Failed to retrieve notification preferences "
"for this event");
return (-1);
}
if ((ep = malloc(sizeof (email_pref_t))) == NULL) {
nd_error(nhdl, "Failed to allocate space for email preferences "
"(%s)", strerror(errno));
goto eprefs_done;
}
(void) memset(ep, 0, sizeof (email_pref_t));
/*
* For SMF state transition events, pref_nvl may contain two sets of
* preferences, which will have to be merged.
*
* The "smtp" nvlist can contain up to four members:
*
* "active" - boolean - used to toggle notfications
* "to" - a string array of email recipients
* "reply-to" - a string array containing the reply-to addresses
* - this is optional and defaults to root@localhost
* "msg_template" - the pathname of a user-supplied message body
* template
*
* In the case that we have two sets of preferences, we will merge them
* using the following rules:
*
* "active" will be set to true, if it is true in either set
*
* The "reply-to" and "to" lists will be merged, with duplicate email
* addresses removed.
*/
if (npref == 2) {
boolean_t *act1, *act2;
char **arr1, **arr2, **strarr, **reparr1, **reparr2;
uint_t n1, n2, arrsz, repsz;
r = nvlist_lookup_boolean_array(p_nvl[0], "active", &act1, &n1);
r += nvlist_lookup_boolean_array(p_nvl[1], "active", &act2,
&n2);
r += nvlist_lookup_string_array(p_nvl[0], "to", &arr1, &n1);
r += nvlist_lookup_string_array(p_nvl[1], "to", &arr2, &n2);
if (r != 0) {
nd_error(nhdl, "Malformed email notification "
"preferences");
nd_dump_nvlist(nhdl, p_nvl[0]);
nd_dump_nvlist(nhdl, p_nvl[1]);
goto eprefs_done;
} else if (!act1[0] && !act2[0]) {
nd_debug(nhdl, "Email notification is disabled");
goto eprefs_done;
}
if (nd_split_list(nhdl, arr1[0], ",", &tmparr1, &tn1) != 0 ||
nd_split_list(nhdl, arr2[0], ",", &tmparr2, &tn2) != 0) {
nd_error(nhdl, "Error parsing \"to\" lists");
nd_dump_nvlist(nhdl, p_nvl[0]);
nd_dump_nvlist(nhdl, p_nvl[1]);
goto eprefs_done;
}
if ((ep->ep_num_recips = nd_merge_strarray(nhdl, tmparr1, tn1,
tmparr2, tn2, &ep->ep_recips)) < 0) {
nd_error(nhdl, "Error merging email recipient lists");
goto eprefs_done;
}
r = nvlist_lookup_string_array(p_nvl[0], "reply-to", &arr1,
&n1);
r += nvlist_lookup_string_array(p_nvl[1], "reply-to", &arr2,
&n2);
repsz = n1 = n2 = 0;
if (!r &&
nd_split_list(nhdl, arr1[0], ",", &reparr1, &n1) != 0 ||
nd_split_list(nhdl, arr2[0], ",", &reparr2, &n2) != 0 ||
(repsz = nd_merge_strarray(nhdl, tmparr1, n1, tmparr2, n2,
&strarr)) != 0 ||
nd_join_strarray(nhdl, strarr, repsz, &ep->ep_reply_to)
!= 0) {
ep->ep_reply_to = strdup("root@localhost");
}
if (n1)
nd_free_strarray(reparr1, n1);
if (n2)
nd_free_strarray(reparr2, n2);
if (repsz > 0)
nd_free_strarray(strarr, repsz);
if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
&strarr, &arrsz) == 0)
ep->ep_template_path = strdup(strarr[0]);
} else {
char **strarr, **tmparr;
uint_t arrsz;
boolean_t *active;
/*
* Both the "active" and "to" notification preferences are
* required, so if we have trouble looking either of these up
* we return an error. We will also return an error if "active"
* is set to false. Returning an error will cause us to not
* send a notification for this event.
*/
r = nvlist_lookup_boolean_array(p_nvl[0], "active", &active,
&arrsz);
r += nvlist_lookup_string_array(p_nvl[0], "to", &strarr,
&arrsz);
if (r != 0) {
nd_error(nhdl, "Malformed email notification "
"preferences");
nd_dump_nvlist(nhdl, p_nvl[0]);
goto eprefs_done;
} else if (!active[0]) {
nd_debug(nhdl, "Email notification is disabled");
goto eprefs_done;
}
if (nd_split_list(nhdl, strarr[0], ",", &tmparr, &arrsz)
!= 0) {
nd_error(nhdl, "Error parsing \"to\" list");
goto eprefs_done;
}
ep->ep_num_recips = arrsz;
ep->ep_recips = tmparr;
if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
&strarr, &arrsz) == 0)
ep->ep_template_path = strdup(strarr[0]);
if (nvlist_lookup_string_array(p_nvl[0], "reply-to", &strarr,
&arrsz) == 0)
ep->ep_reply_to = strdup(strarr[0]);
else
ep->ep_reply_to = strdup("root@localhost");
}
ret = 0;
*eprefs = ep;
eprefs_done:
if (ret != 0) {
if (ep->ep_recips)
nd_free_strarray(ep->ep_recips, ep->ep_num_recips);
if (ep->ep_reply_to)
free(ep->ep_reply_to);
free(ep);
}
if (tn1)
nd_free_strarray(tmparr1, tn1);
if (tn2)
nd_free_strarray(tmparr2, tn2);
nd_free_nvlarray(p_nvl, npref);
return (ret);
}
/*ARGSUSED*/
static void
irpt_cbfunc(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
char *body_fmt, *headers = NULL, *body = NULL, tstamp[32];
struct tm ts;
size_t len;
nd_ev_info_t *ev_info = NULL;
email_pref_t *eprefs;
nd_debug(nhdl, "Received event of class %s", class);
if (get_email_prefs(nhdl, ev, &eprefs) < 0)
return;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto irpt_done;
/*
* If the user specified a template, then we pass it through a script,
* which post-processes any expansion macros. Then we attempt to read
* it in and then send the message. Otherwise we carry on with the rest
* of this function which will contruct the message body from one of the
* default templates.
*/
if (eprefs->ep_template != NULL)
free(eprefs->ep_template);
if (eprefs->ep_template_path != NULL &&
process_template(ev_info, eprefs) == 0) {
send_email_template(nhdl, ev_info, eprefs);
goto irpt_done;
}
/*
* Fetch and format the event timestamp
*/
if (fmev_localtime(ev, &ts) == NULL) {
nd_error(nhdl, "Malformed event: failed to retrieve "
"timestamp");
goto irpt_done;
}
(void) strftime(tstamp, sizeof (tstamp), NULL, &ts);
/*
* We have two message body templates to choose from. One for SMF
* service transition events and a generic one for any other
* uncommitted ireport.
*/
if (strncmp(class, "ireport.os.smf", 14) == 0) {
/*
* For SMF state transition events we have a standard message
* template that we fill in based on the payload of the event.
*/
if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, SMF_MSG_TEMPLATE)) == NULL) {
nd_error(nhdl, "Failed to format message body");
goto irpt_done;
}
/* LINTED: E_SEC_PRINTF_VAR_FMT */
len = snprintf(NULL, 0, body_fmt, hostname, tstamp,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state, ev_info->ei_descr,
ev_info->ei_reason);
body = calloc(len, sizeof (char));
/* LINTED: E_SEC_PRINTF_VAR_FMT */
(void) snprintf(body, len, body_fmt, hostname, tstamp,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state, ev_info->ei_descr,
ev_info->ei_reason);
} else {
if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, IREPORT_MSG_TEMPLATE)) == NULL) {
nd_error(nhdl, "Failed to format message body");
goto irpt_done;
}
/* LINTED: E_SEC_PRINTF_VAR_FMT */
len = snprintf(NULL, 0, body_fmt, hostname, tstamp, class);
body = calloc(len, sizeof (char));
/* LINTED: E_SEC_PRINTF_VAR_FMT */
(void) snprintf(body, len, body_fmt, hostname, tstamp, class);
}
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
goto irpt_done;
/*
* Everything is ready, so now we just iterate through the list of
* recipents, sending an email notification to each one.
*/
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, body, eprefs->ep_recips[i]);
irpt_done:
free(headers);
free(body);
if (ev_info)
nd_free_event_info(ev_info);
if (eprefs->ep_recips)
nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
if (eprefs->ep_reply_to)
free(eprefs->ep_reply_to);
free(eprefs);
}
/*
* There is a lack of uniformity in how the various entries in our diagnosis
* are terminated. Some end with one newline, others with two. This makes the
* output look a bit ugly. Therefore we postprocess the message before sending
* it, removing consecutive occurences of newlines.
*/
static void
postprocess_msg(char *msg)
{
int i = 0, j = 0;
char *buf;
if ((buf = malloc(strlen(msg) + 1)) == NULL)
return;
buf[j++] = msg[i++];
for (i = 1; i < strlen(msg); i++) {
if (!(msg[i] == '\n' && msg[i - 1] == '\n'))
buf[j++] = msg[i];
}
buf[j] = '\0';
(void) strncpy(msg, buf, j+1);
free(buf);
}
/*ARGSUSED*/
static void
listev_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
char *body = NULL, *headers = NULL;
nd_ev_info_t *ev_info = NULL;
boolean_t domsg;
email_pref_t *eprefs;
nd_debug(nhdl, "Received event of class %s", class);
if (get_email_prefs(nhdl, ev, &eprefs) < 0)
return;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto listcb_done;
/*
* If the message payload member is set to 0, then it's an event we
* typically suppress messaging on, so we won't send an email for it.
*/
if (nvlist_lookup_boolean_value(ev_info->ei_payload, FM_SUSPECT_MESSAGE,
&domsg) == 0 && !domsg) {
nd_debug(nhdl, "Messaging suppressed for this event");
goto listcb_done;
}
/*
* If the user specified a template, then we pass it through a script,
* which post-processes any expansion macros. Then we attempt to read
* it in and then send the message. Otherwise we carry on with the rest
* of this function which will contruct the message body from one of the
* default templates.
*/
if (eprefs->ep_template != NULL)
free(eprefs->ep_template);
if (eprefs->ep_template_path != NULL &&
process_template(ev_info, eprefs) == 0) {
send_email_template(nhdl, ev_info, eprefs);
goto listcb_done;
}
/*
* Format the message body
*
* For FMA list.* events we use the same message that the
* syslog-msgs agent would emit as the message body
*
*/
if ((body = fmd_msg_gettext_nv(nhdl->nh_msghdl, NULL,
ev_info->ei_payload)) == NULL) {
nd_error(nhdl, "Failed to format message body");
nd_dump_nvlist(nhdl, ev_info->ei_payload);
goto listcb_done;
}
postprocess_msg(body);
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
goto listcb_done;
/*
* Everything is ready, so now we just iterate through the list of
* recipents, sending an email notification to each one.
*/
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, body, eprefs->ep_recips[i]);
listcb_done:
free(headers);
free(body);
if (ev_info)
nd_free_event_info(ev_info);
if (eprefs->ep_recips)
nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
if (eprefs->ep_reply_to)
free(eprefs->ep_reply_to);
free(eprefs);
}
int
main(int argc, char *argv[])
{
struct rlimit rlim;
struct sigaction act;
sigset_t set;
int c;
boolean_t run_fg = B_FALSE;
if ((nhdl = malloc(sizeof (nd_hdl_t))) == NULL) {
(void) fprintf(stderr, "Failed to allocate space for notifyd "
"handle (%s)", strerror(errno));
return (1);
}
(void) memset(nhdl, 0, sizeof (nd_hdl_t));
nhdl->nh_keep_running = B_TRUE;
nhdl->nh_log_fd = stderr;
nhdl->nh_pname = argv[0];
get_svc_config();
/*
* In the case where we get started outside of SMF, args passed on the
* command line override SMF property setting
*/
while (optind < argc) {
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
case 'd':
nhdl->nh_debug = B_TRUE;
break;
case 'f':
run_fg = B_TRUE;
break;
case 'R':
nhdl->nh_rootdir = strdup(optarg);
break;
default:
free(nhdl);
return (usage(argv[0]));
}
}
}
/*
* Set up a signal handler for SIGTERM (and SIGINT if we'll
* be running in the foreground) to ensure sure we get a chance to exit
* in an orderly fashion. We also catch SIGHUP, which will be sent to
* us by SMF if the service is refreshed.
*/
(void) sigfillset(&set);
(void) sigfillset(&act.sa_mask);
act.sa_handler = nd_sighandler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigdelset(&set, SIGTERM);
(void) sigaction(SIGHUP, &act, NULL);
(void) sigdelset(&set, SIGHUP);
if (run_fg) {
(void) sigaction(SIGINT, &act, NULL);
(void) sigdelset(&set, SIGINT);
} else
nd_daemonize(nhdl);
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
(void) setrlimit(RLIMIT_CORE, &rlim);
/*
* We need to be root to initialize our libfmevent handle (because that
* involves reading/writing to /dev/sysevent), so we do this before
* calling __init_daemon_priv.
*/
nhdl->nh_evhdl = fmev_shdl_init(LIBFMEVENT_VERSION_2, NULL, NULL, NULL);
if (nhdl->nh_evhdl == NULL) {
(void) sleep(5);
nd_abort(nhdl, "failed to initialize libfmevent: %s",
fmev_strerror(fmev_errno));
}
/*
* If we're in the global zone, reset all of our privilege sets to
* the minimum set of required privileges. Since we've already
* initialized our libmevent handle, we no no longer need to run as
* root, so we change our uid/gid to noaccess (60002).
*
* __init_daemon_priv will also set the process core path for us
*
*/
if (getzoneid() == GLOBAL_ZONEID)
if (__init_daemon_priv(
PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
60002, 60002, PRIV_PROC_SETID, NULL) != 0)
nd_abort(nhdl, "additional privileges required to run");
nhdl->nh_msghdl = fmd_msg_init(nhdl->nh_rootdir, FMD_MSG_VERSION);
if (nhdl->nh_msghdl == NULL)
nd_abort(nhdl, "failed to initialize libfmd_msg");
(void) gethostname(hostname, MAXHOSTNAMELEN + 1);
/*
* Set up our event subscriptions. We subscribe to everything and then
* consult libscf when we receive an event to determine whether to send
* an email notification.
*/
nd_debug(nhdl, "Subscribing to ireport.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "ireport.*", irpt_cbfunc,
NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
nd_debug(nhdl, "Subscribing to list.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "list.*", listev_cb,
NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
/*
* We run until someone kills us
*/
while (nhdl->nh_keep_running)
(void) sigsuspend(&set);
free(nhdl->nh_rootdir);
free(nhdl);
return (0);
}
Solaris Email Event Notification Agent
When set to true, smtp-notify will spool debug messages to /var/fm/notify/smtp-notify.log
The root directory that will be used for all pathnames evaluated by smtp-notify
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
# Hammerhead: amd64-only
SUBDIRS = $(MACH64)
include ../Makefile.subdirs
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
.KEEP_STATE:
.SUFFIXES:
SRCS += snmp-notify.c
OBJS = $(SRCS:%.c=%.o)
PROG = snmp-notify
ROOTLIBFM = $(ROOT)/usr/lib/fm
ROOTLIBNOTIFY = $(ROOT)/usr/lib/fm/notify
ROOTPROG = $(ROOTLIBNOTIFY)/$(PROG)
ROOTMANIFESTDIR = $(ROOTSVCSYSTEM)/fm
ROOTMANIFEST = $(ROOTMANIFESTDIR)/$(PROG).xml
ROOTNOTIFYPARAMS = $(ROOTMANIFESTDIR)/notify-params.xml
$(ROOTMANIFEST) : FILEMODE = 0444
$(ROOTNOTIFYPARAMS) : FILEMODE = 0444
$(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
CPPFLAGS += -I. -I../common -I../../../../../lib/fm/libfmnotify/common
CSTD = $(CSTD_GNU99)
CFLAGS += $(CTF_FLAGS) $(CCVERBOSE)
CFLAGS64 += $(CTF_FLAGS_64) $(CCVERBOSE)
LDLIBS += -L$(ROOT)/usr/lib/fm -lnvpair -lfmevent -lfmd_msg -lfmnotify \
-lumem -lnetsnmp -lnetsnmpagent
NATIVE_LIBS += libnetsnmp.so libnetsnmpagent.so
LDFLAGS += -R/usr/lib/fm
# Hammerhead: GNU ld needs rpath-link for transitive deps (libtopo, libdiagcode)
LDFLAGS += -Wl,-rpath-link,$(ROOT)/usr/lib/fm
CERRWARN += -Wno-parentheses
.NOTPARALLEL:
.PARALLEL: $(OBJS)
all: $(PROG)
$(PROG): $(OBJS)
$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
$(CTFMERGE) -L VERSION -o $@ $(OBJS)
$(POST_PROCESS)
%.o: ../common/%.c
$(COMPILE.c) $<
$(CTFCONVERT_O)
%.o: %.c
$(COMPILE.c) $<
$(CTFCONVERT_O)
clean:
$(RM) $(OBJS)
clobber: clean
$(RM) $(PROG)
$(ROOTLIBNOTIFY):
$(INS.dir)
$(ROOTLIBNOTIFY)/%: %
$(INS.file)
$(ROOTMANIFESTDIR):
$(INS.dir)
$(ROOTMANIFESTDIR)/%.xml: ../common/%.xml
$(INS.file)
$(ROOTMANIFESTDIR)/notify-params.xml: ../../notify-params.xml
$(INS.file)
install_h:
install: all $(ROOTLIBNOTIFY) $(ROOTPROG) $(ROOTMANIFESTDIR) $(ROOTMANIFEST) $(ROOTNOTIFYPARAMS)
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#
# Copyright 2025 Hammerhead Project
#
# Hammerhead: Include Makefile.cmd first to load Makefile.master
# (defines COMPILE.c, COMPILE64.c needed by Makefile.com's pattern rules)
include $(SRC)/cmd/Makefile.cmd
include ../Makefile.com
include $(SRC)/cmd/Makefile.cmd.64
install: all $(ROOTPROG64)
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Copyright (c) 2018, Joyent, Inc.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libfmnotify.h"
/*
* Debug messages can be enabled by setting the debug property to true
*
* # svccfg -s svc:/system/fm/snmp-notify setprop config/debug=true
*/
#define SVCNAME "system/fm/snmp-notify"
typedef struct ireport_trap {
char *host;
char *msgid;
char *desc;
long long tstamp;
char *fmri;
uint32_t from_state;
uint32_t to_state;
char *reason;
boolean_t is_stn_event;
} ireport_trap_t;
static nd_hdl_t *nhdl;
static const char optstr[] = "dfR:";
static const char SNMP_SUPPCONF[] = "fmd-trapgen";
static char hostname[MAXHOSTNAMELEN + 1];
static int
usage(const char *pname)
{
(void) fprintf(stderr, "Usage: %s [-df] [-R ]\n", pname);
(void) fprintf(stderr,
"\t-d enable debug mode\n"
"\t-f stay in foreground\n"
"\t-R specify alternate root\n");
return (1);
}
/*
* If someone does an "svcadm refresh" on us, then this function gets called,
* which rereads our service configuration.
*/
static void
get_svc_config()
{
int s = 0;
uint8_t val;
s = nd_get_boolean_prop(nhdl, SVCNAME, "config", "debug", &val);
nhdl->nh_debug = val;
s += nd_get_astring_prop(nhdl, SVCNAME, "config", "rootdir",
&(nhdl->nh_rootdir));
if (s != 0)
nd_error(nhdl, "Failed to read retrieve service "
"properties");
}
static void
nd_sighandler(int sig)
{
if (sig == SIGHUP)
get_svc_config();
else
nd_cleanup(nhdl);
}
static int
get_snmp_prefs(nd_hdl_t *nhdl, nvlist_t **pref_nvl, uint_t npref)
{
boolean_t *a1, *a2;
uint_t n;
int r;
/*
* For SMF state transition events, pref_nvl contain two sets of
* preferences, which will have to be merged.
*
* The "snmp" nvlist currently only supports a single boolean member,
* "active" which will be set to true, if it is true in either set
*/
if (npref == 2) {
r = nvlist_lookup_boolean_array(pref_nvl[0], "active", &a1, &n);
r += nvlist_lookup_boolean_array(pref_nvl[1], "active", &a2,
&n);
if (r != 0) {
nd_debug(nhdl, "Malformed snmp notification "
"preferences");
nd_dump_nvlist(nhdl, pref_nvl[0]);
nd_dump_nvlist(nhdl, pref_nvl[1]);
return (-1);
} else if (!a1[0] && !a2[0]) {
nd_debug(nhdl, "SNMP notification is disabled");
return (-1);
}
} else {
if (nvlist_lookup_boolean_array(pref_nvl[0], "active",
&a1, &n)) {
nd_debug(nhdl, "Malformed snmp notification "
"preferences");
nd_dump_nvlist(nhdl, pref_nvl[0]);
return (-1);
} else if (!a1[0]) {
nd_debug(nhdl, "SNMP notification is disabled");
return (-1);
}
}
return (0);
}
static void
send_ireport_trap(ireport_trap_t *t)
{
static const oid sunIreportTrap_oid[] =
{ SUNIREPORTTRAP_OID };
const size_t sunIreportTrap_len =
OID_LENGTH(sunIreportTrap_oid);
static const oid sunIreportHostname_oid[] =
{ SUNIREPORTHOSTNAME_OID };
static const oid sunIreportMsgid_oid[] =
{ SUNIREPORTMSGID_OID };
static const oid sunIreportDescription_oid[] =
{ SUNIREPORTDESCRIPTION_OID };
static const oid sunIreportTime_oid[] =
{ SUNIREPORTTIME_OID };
static const oid sunIreportSmfFmri_oid[] =
{ SUNIREPORTSMFFMRI_OID };
static const oid sunIreportSmfFromState_oid[] =
{ SUNIREPORTSMFFROMSTATE_OID };
static const oid sunIreportSmfToState_oid[] =
{ SUNIREPORTSMFTOSTATE_OID };
static const oid sunIreportSmfTransitionReason_oid[] =
{ SUNIREPORTTRANSITIONREASON_OID };
const size_t
sunIreport_base_len = OID_LENGTH(sunIreportHostname_oid);
size_t var_len = sunIreport_base_len + 1;
oid var_name[MAX_OID_LEN];
netsnmp_variable_list *notification_vars = NULL;
size_t dt_len;
uchar_t dt[11], *tdt;
time_t ts = t->tstamp;
tdt = date_n_time(&ts, &dt_len);
/*
* We know date_n_time is broken, it returns a buffer from
* its stack. So we copy before we step over it!
*/
for (int i = 0; i < dt_len; ++i)
dt[i] = tdt[i];
if (var_len > MAX_OID_LEN) {
nd_error(nhdl, "var_len %d > MAX_OID_LEN %d\n", var_len,
MAX_OID_LEN);
return;
}
(void) memcpy(var_name, sunIreportHostname_oid, sunIreport_base_len *
sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR, (uchar_t *)t->host,
strlen(t->host));
(void) memcpy(var_name, sunIreportMsgid_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR, (uchar_t *)t->msgid,
strlen(t->msgid));
(void) memcpy(var_name, sunIreportDescription_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR, (uchar_t *)t->desc,
strlen(t->desc));
(void) memcpy(var_name, sunIreportTime_oid, sunIreport_base_len *
sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR, dt, dt_len);
if (t->is_stn_event) {
(void) memcpy(var_name, sunIreportSmfFmri_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR, (uchar_t *)t->fmri,
strlen(t->fmri));
(void) memcpy(var_name, sunIreportSmfFromState_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_INTEGER,
(uchar_t *)&t->from_state, sizeof (uint32_t));
(void) memcpy(var_name, sunIreportSmfToState_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_INTEGER,
(uchar_t *)&t->to_state, sizeof (uint32_t));
(void) memcpy(var_name, sunIreportSmfTransitionReason_oid,
sunIreport_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name,
sunIreport_base_len + 1, ASN_OCTET_STR,
(uchar_t *)t->reason, strlen(t->reason));
}
/*
* This function is capable of sending both v1 and v2/v3 traps.
* Which is sent to a specific destination is determined by the
* configuration file(s).
*/
send_enterprise_trap_vars(SNMP_TRAP_ENTERPRISESPECIFIC,
sunIreportTrap_oid[sunIreportTrap_len - 1],
(oid *)sunIreportTrap_oid, sunIreportTrap_len - 2,
notification_vars);
nd_debug(nhdl, "Sent SNMP trap for %s", t->msgid);
snmp_free_varbind(notification_vars);
}
/*ARGSUSED*/
static void
send_fm_trap(const char *uuid, const char *code, const char *url)
{
static const oid sunFmProblemTrap_oid[] = { SUNFMPROBLEMTRAP_OID };
const size_t sunFmProblemTrap_len = OID_LENGTH(sunFmProblemTrap_oid);
static const oid sunFmProblemUUID_oid[] =
{ SUNFMPROBLEMTABLE_OID, 1, SUNFMPROBLEM_COL_UUID };
static const oid sunFmProblemCode_oid[] =
{ SUNFMPROBLEMTABLE_OID, 1, SUNFMPROBLEM_COL_CODE };
static const oid sunFmProblemURL_oid[] =
{ SUNFMPROBLEMTABLE_OID, 1, SUNFMPROBLEM_COL_URL };
const size_t sunFmProblem_base_len = OID_LENGTH(sunFmProblemUUID_oid);
size_t uuid_len = strlen(uuid);
size_t var_len = sunFmProblem_base_len + 1 + uuid_len;
oid var_name[MAX_OID_LEN];
netsnmp_variable_list *notification_vars = NULL;
/*
* The format of our trap varbinds' oids is as follows:
*
* +-----------------------+---+--------+----------+------+
* | SUNFMPROBLEMTABLE_OID | 1 | column | uuid_len | uuid |
* +-----------------------+---+--------+----------+------+
* \---- index ----/
*
* A common mistake here is to send the trap with varbinds that
* do not contain the index. All the indices are the same, and
* all the oids are the same length, so the only thing we need to
* do for each varbind is set the table and column parts of the
* variable name.
*/
if (var_len > MAX_OID_LEN)
return;
var_name[sunFmProblem_base_len] = (oid)uuid_len;
for (int i = 0; i < uuid_len; i++)
var_name[i + sunFmProblem_base_len + 1] = (oid)uuid[i];
/*
* Ordinarily, we would need to add the OID of the trap itself
* to the head of the variable list; this is required by SNMP v2.
* However, send_enterprise_trap_vars does this for us as a part
* of converting between v1 and v2 traps, so we skip directly to
* the objects we're sending.
*/
(void) memcpy(var_name, sunFmProblemUUID_oid,
sunFmProblem_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name, var_len,
ASN_OCTET_STR, (uchar_t *)uuid, strlen(uuid));
(void) memcpy(var_name, sunFmProblemCode_oid,
sunFmProblem_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name, var_len,
ASN_OCTET_STR, (uchar_t *)code, strlen(code));
(void) memcpy(var_name, sunFmProblemURL_oid,
sunFmProblem_base_len * sizeof (oid));
(void) snmp_varlist_add_variable(¬ification_vars, var_name, var_len,
ASN_OCTET_STR, (uchar_t *)url, strlen(url));
/*
* This function is capable of sending both v1 and v2/v3 traps.
* Which is sent to a specific destination is determined by the
* configuration file(s).
*/
send_enterprise_trap_vars(SNMP_TRAP_ENTERPRISESPECIFIC,
sunFmProblemTrap_oid[sunFmProblemTrap_len - 1],
(oid *)sunFmProblemTrap_oid, sunFmProblemTrap_len - 2,
notification_vars);
nd_debug(nhdl, "Sent SNMP trap for %s", code);
snmp_free_varbind(notification_vars);
}
/*
* The SUN-IREPORT-MIB declares the following enum to represent SMF service
* states.
*
* offline(0), online(1), degraded(2), disabled(3), maintenance(4),
* uninitialized(5)
*
* This function converts a string representation of an SMF service state
* to it's corresponding enum val.
*/
static int
state_to_val(char *statestr, uint32_t *stateval)
{
if (strcmp(statestr, "offline") == 0)
*stateval = 0;
else if (strcmp(statestr, "online") == 0)
*stateval = 1;
else if (strcmp(statestr, "degraded") == 0)
*stateval = 2;
else if (strcmp(statestr, "disabled") == 0)
*stateval = 3;
else if (strcmp(statestr, "maintenance") == 0)
*stateval = 4;
else if (strcmp(statestr, "uninitialized") == 0)
*stateval = 5;
else
return (-1);
return (0);
}
/*ARGSUSED*/
static void
ireport_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
nvlist_t **pref_nvl = NULL;
nd_ev_info_t *ev_info = NULL;
ireport_trap_t swtrap;
uint_t npref;
int ret;
nd_debug(nhdl, "Received event of class %s", class);
ret = nd_get_notify_prefs(nhdl, "snmp", ev, &pref_nvl, &npref);
if (ret == SCF_ERROR_NOT_FOUND) {
/*
* No snmp notification preferences specified for this type of
* event, so we're done
*/
return;
} else if (ret != 0) {
nd_error(nhdl, "Failed to retrieve notification preferences "
"for this event");
return;
}
if (get_snmp_prefs(nhdl, pref_nvl, npref) != 0)
goto irpt_done;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto irpt_done;
swtrap.host = hostname;
swtrap.msgid = ev_info->ei_diagcode;
swtrap.desc = ev_info->ei_descr;
swtrap.tstamp = (time_t)fmev_time_sec(ev);
if (strncmp(class, "ireport.os.smf", 14) == 0) {
swtrap.fmri = ev_info->ei_fmri;
if (state_to_val(ev_info->ei_from_state, &swtrap.from_state)
< 0 ||
state_to_val(ev_info->ei_to_state, &swtrap.to_state) < 0) {
nd_error(nhdl, "Malformed event - invalid svc state");
nd_dump_nvlist(nhdl, ev_info->ei_payload);
goto irpt_done;
}
swtrap.reason = ev_info->ei_reason;
swtrap.is_stn_event = B_TRUE;
}
send_ireport_trap(&swtrap);
irpt_done:
if (ev_info)
nd_free_event_info(ev_info);
nd_free_nvlarray(pref_nvl, npref);
}
/*ARGSUSED*/
static void
list_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
char *uuid;
uint8_t version;
nd_ev_info_t *ev_info = NULL;
nvlist_t **pref_nvl = NULL;
uint_t npref;
int ret;
boolean_t domsg;
nd_debug(nhdl, "Received event of class %s", class);
ret = nd_get_notify_prefs(nhdl, "snmp", ev, &pref_nvl, &npref);
if (ret == SCF_ERROR_NOT_FOUND) {
/*
* No snmp notification preferences specified for this type of
* event, so we're done
*/
return;
} else if (ret != 0) {
nd_error(nhdl, "Failed to retrieve notification preferences "
"for this event");
return;
}
if (get_snmp_prefs(nhdl, pref_nvl, npref) != 0)
goto listcb_done;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto listcb_done;
/*
* If the message payload member is set to 0, then it's an event we
* typically suppress messaging on, so we won't send a trap for it.
*/
if (nvlist_lookup_boolean_value(ev_info->ei_payload, FM_SUSPECT_MESSAGE,
&domsg) == 0 && !domsg) {
nd_debug(nhdl, "Messaging suppressed for this event");
goto listcb_done;
}
if (nvlist_lookup_uint8(ev_info->ei_payload, FM_VERSION, &version)
!= 0 || version > FM_SUSPECT_VERSION) {
nd_error(nhdl, "invalid event version: %u", version);
goto listcb_done;
}
(void) nvlist_lookup_string(ev_info->ei_payload, FM_SUSPECT_UUID,
&uuid);
if (strcmp(ev_info->ei_url, ND_UNKNOWN) != 0)
send_fm_trap(uuid, ev_info->ei_diagcode, ev_info->ei_url);
else
nd_error(nhdl, "failed to format url for %s", uuid);
listcb_done:
nd_free_nvlarray(pref_nvl, npref);
if (ev_info)
nd_free_event_info(ev_info);
}
static int
init_sma(void)
{
int err;
/*
* The only place we could possibly log is syslog, but the
* full agent doesn't normally log there. It would be confusing
* if this agent did so; therefore we disable logging entirely.
*/
snmp_disable_log();
/*
* Net-SNMP has a provision for reading an arbitrary number of
* configuration files. A configuration file is read if it has
* had any handlers registered for it, or if it's the value in
* of NETSNMP_DS_LIB_APPTYPE. Our objective here is to read
* both snmpd.conf and fmd-trapgen.conf.
*/
if ((err = netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_ROLE, 0 /* MASTER_AGENT */)) != SNMPERR_SUCCESS)
return (err);
init_agent_read_config("snmpd");
if ((err = netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE, SNMP_SUPPCONF)) != SNMPERR_SUCCESS)
return (err);
if (register_app_config_handler("trapsink", snmpd_parse_config_trapsink,
snmpd_free_trapsinks, "host [community] [port]") == NULL)
return (SNMPERR_MALLOC);
if (register_app_config_handler("trap2sink",
snmpd_parse_config_trap2sink, NULL, "host [community] [port]") ==
NULL)
return (SNMPERR_MALLOC);
if (register_app_config_handler("trapsess", snmpd_parse_config_trapsess,
NULL, "[snmpcmdargs] host") == NULL)
return (SNMPERR_MALLOC);
init_traps();
init_snmp(SNMP_SUPPCONF);
return (SNMPERR_SUCCESS);
}
int
main(int argc, char *argv[])
{
struct rlimit rlim;
struct sigaction act;
sigset_t set;
int c;
boolean_t run_fg = B_FALSE;
if ((nhdl = malloc(sizeof (nd_hdl_t))) == NULL) {
(void) fprintf(stderr, "Failed to allocate space for notifyd "
"handle (%s)", strerror(errno));
return (1);
}
bzero(nhdl, sizeof (nd_hdl_t));
nhdl->nh_keep_running = B_TRUE;
nhdl->nh_log_fd = stderr;
nhdl->nh_pname = argv[0];
get_svc_config();
/*
* In the case where we get started outside of SMF, args passed on the
* command line override SMF property setting
*/
while (optind < argc) {
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
case 'd':
nhdl->nh_debug = B_TRUE;
break;
case 'f':
run_fg = B_TRUE;
break;
case 'R':
nhdl->nh_rootdir = strdup(optarg);
break;
default:
free(nhdl);
return (usage(argv[0]));
}
}
}
/*
* Set up a signal handler for SIGTERM (and SIGINT if we'll
* be running in the foreground) to ensure sure we get a chance to exit
* in an orderly fashion. We also catch SIGHUP, which will be sent to
* us by SMF if the service is refreshed.
*/
(void) sigfillset(&set);
(void) sigfillset(&act.sa_mask);
act.sa_handler = nd_sighandler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigdelset(&set, SIGTERM);
(void) sigaction(SIGHUP, &act, NULL);
(void) sigdelset(&set, SIGHUP);
if (run_fg) {
(void) sigaction(SIGINT, &act, NULL);
(void) sigdelset(&set, SIGINT);
} else
nd_daemonize(nhdl);
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
(void) setrlimit(RLIMIT_CORE, &rlim);
/*
* We need to be root initialize our libfmevent handle (because that
* involves reading/writing to /dev/sysevent), so we do this before
* calling __init_daemon_priv.
*/
nhdl->nh_evhdl = fmev_shdl_init(LIBFMEVENT_VERSION_2, NULL, NULL, NULL);
if (nhdl->nh_evhdl == NULL) {
(void) sleep(5);
nd_abort(nhdl, "failed to initialize libfmevent: %s",
fmev_strerror(fmev_errno));
}
/*
* If we're in the global zone, reset all of our privilege sets to
* the minimum set of required privileges. We also change our
* uid/gid to noaccess/noaccess
*
* __init_daemon_priv will also set the process core path for us
*
*/
if (getzoneid() == GLOBAL_ZONEID)
if (__init_daemon_priv(
PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
60002, 60002, PRIV_FILE_DAC_READ, NULL) != 0)
nd_abort(nhdl, "additional privileges required to run");
nhdl->nh_msghdl = fmd_msg_init(nhdl->nh_rootdir, FMD_MSG_VERSION);
if (nhdl->nh_msghdl == NULL)
nd_abort(nhdl, "failed to initialize libfmd_msg");
if (init_sma() != SNMPERR_SUCCESS)
nd_abort(nhdl, "SNMP initialization failed");
(void) gethostname(hostname, MAXHOSTNAMELEN + 1);
/*
* Set up our event subscriptions. We subscribe to everything and then
* consult libscf when we receive an event to determine what (if any)
* notification to send.
*/
nd_debug(nhdl, "Subscribing to ireport.os.smf.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "ireport.os.smf.*",
ireport_cb, NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
nd_debug(nhdl, "Subscribing to list.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "list.*", list_cb,
NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
/*
* We run until someone kills us
*/
while (nhdl->nh_keep_running)
(void) sigsuspend(&set);
/*
* snmp_shutdown, which we would normally use here, calls free_slots,
* a callback that is supposed to tear down the pkcs11 state; however,
* it abuses C_Finalize, causing fmd to drop core on shutdown. Avoid
* this by shutting down the library piecemeal.
*/
snmp_store(SNMP_SUPPCONF);
snmp_alarm_unregister_all();
(void) snmp_close_sessions();
shutdown_mib();
unregister_all_config_handlers();
netsnmp_ds_shutdown();
free(nhdl->nh_rootdir);
free(nhdl);
return (0);
}
Solaris SNMP Event Notification Agent
When set to true, smtp-notify will spool debug messages to /var/fm/notify/snmp-notify.log
The root directory that will be used for all pathnames evaluated by snmp-notify