#!/bin/sh
#############################################################################
#
# bmc-snmp-proxy:	Set SNMP proxy to BMC (Baseboard Management Controller)
#
# version:	0.6
#
# Authors:	Charles Rose <charles_rose@dell.com>
#		Jordan Hargrave <jordan_hargrave@dell.com>
#
# Description:  Script to set snmp proxy to the BMC for certain OID
#		See here for details:
#		https://fedoraproject.org/wiki/Features/AgentFreeManagement
#
# Assumptions:  This script will work only when /etc/snmp/ is writable.
#
#############################################################################
# GLOBALS
#############################################################################
SYSCONF_DIR="/etc/sysconfig"
CONFIG="${SYSCONF_DIR}/bmc-snmp-proxy"

SNMPD_LOCAL_CONF_DIR="/etc/snmp/bmc"
SNMPD_LOCAL_CONF="${SNMPD_LOCAL_CONF_DIR}/snmpd.local.conf"
TRAPD_LOCAL_CONF="${SNMPD_LOCAL_CONF_DIR}/snmptrapd.local.conf"

TRAPD_CONF="/etc/snmp/snmptrapd.conf"

LOCKFILE="/var/lock/subsys/bmc-snmp-proxy"
BMC_INFO="/var/run/bmc-info"

IPMITOOL=`which ipmitool`

#Default config
BMC_COMMUNITY="public"
BMC_OID=".1.3.6.1.4.1.674.10892.2"  # Dell iDRAC
TRAP_FORWARD="no"
RELOAD_SERVICES="yes"

#############################################################################

#TODO: Use inotify and daemonize when $BMC_INFO changes

# source config
[ -r ${CONFIG} ] && . ${CONFIG}

. gettext.sh

SCRIPT_NAME=$(basename $0)
RETVAL=0

# Check if bmc-info created by exchange-bmc-os-info
bmc_info_exists()
{
	if [ -r "${BMC_INFO}" ]; then
		. ${BMC_INFO}
	else
		RETVAL=2
	fi
	return $RETVAL
}

check_snmp()
{
	if [ ! -d /etc/snmp ] && [ ! -x /usr/sbin/snmpd ]; then
		RETVAL=12
	fi
	return $RETVAL
}

#############################################################################
# configure SNMP proxy
#############################################################################
write_snmp_conf()
{
	# SNMPv3 security: bmcview, bmc_ctx, bmc_sec, bmc_grp, bmc_cmty
	printf "###############################################\n"
	printf "# Automatically created by %s #\n" "${SCRIPT_NAME}"
	printf "###############################################\n"
	printf "view bmcview included %s 80\n" "${BMC_OID}"
	printf "com2sec -Cn bmc_ctx bmc_sec default bmc_cmty\n"
	printf "group bmc_grp v1 bmc_sec\n"
	printf "access bmc_grp bmc_ctx any noauth exact bmcview none none\n"
	printf "proxy -Cn bmc_ctx -v 1 %s\n" "${PROXY_TOKEN}"
	printf "###############################################\n"
}

valid_ip()
{
        #Thanks to mkyong.com
        octet="([01]?[[:digit:]][[:digit:]]?|2[0-4][[:digit:]]|25[0-5])"

        printf -- "%s" "${1}"| grep -Eq \
		"^${octet}\\.${octet}\\.${octet}\\.${octet}$"
        return $?
}

check_vars()
{
	[ -z ${BMC_COMMUNITY} ] && BMC_COMMUNITY="public"
	[ -z ${BMC_OID} ] && return 1

	if [ -n "${BMC_IPv4}" ] && valid_ip ${BMC_IPv4}; then
		return 0
	else
		return 1
	fi
}

set_snmp_proxy()
{
	if check_vars; then
		PROXY_TOKEN="-c ${BMC_COMMUNITY} ${BMC_IPv4} ${BMC_OID}"

		if [ ! -d ${SNMPD_LOCAL_CONF_DIR} ] && \
			 	mkdir ${SNMPD_LOCAL_CONF_DIR}; then
			write_snmp_conf > ${SNMPD_LOCAL_CONF}
			[ $? -ne 0 ] && RETVAL=4
		fi
	else
		RETVAL=3
	fi
}


set_snmpd_conf_path()
{
	for SYSCONF in ${SYSCONF_DIR}/snmp*d;
	do
		if grep -q "${SNMPD_LOCAL_CONF_DIR}" "${SYSCONF}" > \
				 /dev/null 2>&1; then
			continue
		else
			printf "SNMPCONFPATH=%s\n" "${SNMPD_LOCAL_CONF_DIR}" \
				>> ${SYSCONF} || RETVAL=7
		fi
	done
	return $RETVAL
}

disable_snmp_proxy()
{
	if [ -f ${SNMPD_LOCAL_CONF} ]; then
		rm -f ${SNMPD_LOCAL_CONF}
		[ $? -ne 0 ] && RETVAL=5
	fi
}
#############################################################################
# Trap Forwarding
#############################################################################

pick_alert_dest()
{
	test_ip="$1"
	for ALERT_DEST in `seq 1 4`
	do
		temp_ip=$(${IPMITOOL} lan alert print ${CHANNEL} ${ALERT_DEST}\
			2>/dev/null| sed -n "s#^Alert IP Address.*: ##p")

		[ "${temp_ip}" = "${test_ip}" ] && return 0
	done
	return 1
}

set_alert_dest_ip()
{
	${IPMITOOL} lan alert set ${CHANNEL} ${ALERT_DEST} ipaddr ${1} \
		retry 4 type pet >/dev/null 2>&1
	[ $? -ne 0 ] && RETVAL=8
}

bmc_alert_dest()
{
	# Pick the first active LAN channel
        for CHANNEL in `seq 1 14`
        do
                [ $(${IPMITOOL} -I open channel info ${CHANNEL} 2>/dev/null \
                        | grep -q "802\.3") ] || break
        done

	# If TRAPD_IP is already set as an alert dest,
	if pick_alert_dest "${TRAPD_IP}"; then
		# reset: reset it if we are called with reset
		[ "${1}" = "reset" ] && \
			set_alert_dest_ip "0.0.0.0"
	# else, find the next free alert dest,
	elif pick_alert_dest "0.0.0.0"; then
		[ "${1}" = "reset" ] && \
			return $RETVAL
		# set: the TRAPD_IP
		set_alert_dest_ip "${TRAPD_IP}"
	else
		# No free alert destinations
		RETVAL=9
	fi
	return $RETVAL
}

set_ipmi_alert()
{
	${IPMITOOL} lan set ${CHANNEL} alert "${1}" >/dev/null 2>&1
	[ $? -ne 0 ] && RETVAL=10
}

get_host_ip()
{
	# Get host's IP that the BMC can reach.
	IFACE=$(/usr/sbin/ip -o -f inet address |awk '!/: lo/ {print $2}')
	for dev in ${IFACE}
	do
		ping -c 1 -I ${dev} ${BMC_IPv4} > /dev/null 2>&1
	done
}

config_bmc_alert()
{
	# Get Host's IP that the BMC can send traps to
	TRAPD_IP=$(get_host_ip)

	# Set Host's IP as the alert destination in the BMC
	valid_ip ${TRAPD_IP} && bmc_alert_dest "${ACTION}"

	# Enable alerting on the LAN channel
	[ $RETVAL -eq 0 ] && set_ipmi_alert "${ACTION}"
}

write_trapd_conf()
{
	printf "###############################################\n"
	printf "# Automatically created by %s #\n" "${SCRIPT_NAME}"
	printf "forward %s %s\n" "${BMC_OID}*" "${FORWARD_HOST}"
	printf "###############################################\n"
}

config_trapd()
{
	# Proceed only if snmptrapd is available on the system
	if [ -f ${TRAPD_CONF} ]; then
		write_trapd_conf > ${TRAPD_LOCAL_CONF}
		[ $? -ne 0 ] && RETVAL=11
	else
		return 1
	fi
}

trap_sink_exists()
{
	# TODO: We only set the first match. We should be able to set
	# multiple
	FORWARD_HOST=$(awk '/^trap.*sink/{print $2}; /^informsink/{print $2}' \
			/etc/snmp/snmpd*conf | head -1)
	if [ -z "${FORWARD_HOST}" ]; then
		# there is no trapsink setup.
		return 1
	else
		return 0
	fi
}

# Forward SNMP traps from the BMC to trapsink.
trap_forward()
{
	NO_TRAP=0
	ACTION=${1} # set or reset

	if [ "${ACTION}" = "set" ]; then
		# Get trapd config,
		if trap_sink_exists; then
			config_trapd && config_bmc_alert
		else
			# exit silently if there is no sink
			NO_TRAP=1
		fi
	else
		if [ -f ${TRAPD_LOCAL_CONF} ]; then
			rm -f ${TRAPD_LOCAL_CONF} >/dev/null 2>&1
		else
			NO_TRAP=1
		fi
	fi
}

#############################################################################
service_reload()
{
	#TODO: do this in systemd
	if [ ${RETVAL} -eq 0 ] && [ "${RELOAD_SERVICES}" = "yes" ]; then
		service $1 reload
		[ $? -ne 0 ] && RETVAL=6
	fi
	return
}

#############################################################################
start()
{
	if bmc_info_exists && check_snmp; then
		touch ${LOCKFILE}
		set_snmpd_conf_path && set_snmp_proxy
		[ $RETVAL -eq 0 ] && service_reload snmpd

		if [ "${TRAP_FORWARD}" = "yes" ]; then
			trap_forward "set"
			[ $RETVAL -eq 0 ] && [ $NO_TRAP -eq 0 ] && \
				service_reload snmptrapd
		fi
	fi
}

#############################################################################
stop()
{
	[ ! -f ${LOCKFILE} ] && return
	if bmc_info_exists && check_snmp; then
		disable_snmp_proxy
		[ $RETVAL -eq 0 ] && service_reload snmpd

		if [ "${TRAP_FORWARD}" = "yes" ]; then
			trap_forward "reset"
			[ $RETVAL -eq 0 ] && [ $NO_TRAP -eq 0 ] && \
				service_reload snmptrapd
		fi
		rm -f ${LOCKFILE}
	fi
}

#############################################################################
status()
{
	eval_gettext "${SCRIPT_NAME}: snmp proxy to BMC is "
	# Checking for lockfile is better.
	#if grep -q "^proxy" "${SNMPD_LOCAL_CONF}" > /dev/null 2>&1 ; then
	if [ -f ${LOCKFILE} ]; then
		eval_gettext "set"
	else
		eval_gettext "not set"
	fi
	echo
	RETVAL=0
}

#############################################################################
usage()
{
	eval_gettext "Usage: $0 {start|stop|status}"; echo 1>&2
	RETVAL=1
}

#############################################################################
# MAIN
#############################################################################
case "$1" in
	start) start ;;
	stop)  stop ;;
	status)	status ;;
	*) usage ;;
esac

case "$RETVAL" in
	0|1) ;;
	2) eval_gettext "${SCRIPT_NAME}: failed to read ${BMC_INFO} " 1>&2 ;;
	3) eval_gettext "${SCRIPT_NAME}: failed to get proxy config." 1>&2 ;;
	4) eval_gettext "${SCRIPT_NAME}: failed to set ${SNMPD_LOCAL_CONF}." 1>&2 ;;
	5) eval_gettext "${SCRIPT_NAME}: failed to disable snmp proxy." 1>&2 ;;
	6) eval_gettext "${SCRIPT_NAME}: failed to reload snmpd." 1>&2 ;;
	7) eval_gettext "${SCRIPT_NAME}: failed to update ${SYSCONF}." 1>&2 ;;
	8) eval_gettext "${SCRIPT_NAME}: failed to set IPMI alert dest." 1>&2 ;;
	9) eval_gettext "${SCRIPT_NAME}: no free IPMI alert dest." 1>&2 ;;
	10) eval_gettext "${SCRIPT_NAME}: failed to set IPMI PEF." 1>&2 ;;
	11) eval_gettext "${SCRIPT_NAME}: failed to write snmptrapd.conf." 1>&2 ;;
	12) eval_gettext "${SCRIPT_NAME}: snmpd not found." 1>&2 ;;
	*) eval_gettext "${SCRIPT_NAME}: unknown error." 1>&2 ;;
esac

if [ ${RETVAL} -gt 1 ]; then
        eval_gettext " Return code: ${RETVAL}"; echo
fi
exit ${RETVAL}
#############################################################################
# end of file
#############################################################################