#!/usr/bin/env bash # # an auxiliary script to produce a "stub" snmp-ups subdriver from # SNMP data from a real agent or from dump files # # Version: 0.18 # # See also: docs/snmp-subdrivers.txt # # Copyright (C) # 2011 - 2012 Arnaud Quette # 2015 - 2022 Eaton (author: Arnaud Quette ) # 2011 - 2025 Jim Klimov # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # TODO: # - Prepend sysDescription (.1.3.6.1.2.1.1.1.0) to have some more visibility # - extend to SNMP v3 (auth.) usage() { echo "Usage: $0 [options] [file]" echo "Options:" echo " -h, --help -- show this message and quit" echo " -n name -- subdriver name (use natural capitalization)" echo " -M DIRLIST -- colon separated list of directories to also search for MIBs" echo " -k -- keep temporary files (for debugging)" echo "" echo "mode 1: get SNMP data from a real agent" echo " -H host_address -- SNMP host IP address or name" echo " -c community -- SNMP v1 community name (default: public)" echo " -s XXXX -- override SNMP OID entry point (sysOID). Ex: '.1.3.6.1.4.1.534.10'" echo "" echo "mode 2: get data from files (snmpwalk dumps of 'sysOID' subtree)" echo " -s XXXX -- SNMP OID entry point (sysOID). Ex: '.1.3.6.1.4.1.534.6.6.7'" echo " file1 file2 -- read from files instead of an host (using Net SNMP)" echo " file1: numeric SNMP walk (snmpwalk -On ... )" echo " file2: string SNMP walk (snmpwalk -Os ... )" echo "" echo "mode 3: get data from 1 file (numeric snmpwalk dump of the whole SNMP tree)" echo " The sysOID is extracted from the dump, and only the pointed subtree is used" echo " A MIB file MUST be provided, and is used to produce the string SNMP walk" echo " file1 -- read from file instead of an host (using Net SNMP)" echo " file1: numeric SNMP walk (snmpwalk -On ... )" echo "" echo "Notes:" echo " For both modes, prefer to copy the specific MIB file(s) for your device in the $0 script directory" echo " So that it is automatically taken into account for the string name resolution of OIDs" echo " Otherwise, use \"-M.\" option" echo "" echo "Example:" echo "mode 1: $0 -H 192.168.0.1 -n mibname -c mycommunity" echo "mode 2: (using sysOID .1.3.6.1.4.1.534.6.6.7)" echo " snmpwalk -On -v1 -c mycommunity 192.168.0.1 .1.3.6.1.4.1.534.6.6.7 2>/dev/null 1> numeric-walk-file" echo " snmpwalk -Os -v1 -m ALL -M+. -c mycommunity 192.168.0.1 .1.3.6.1.4.1.534.6.6.7 2>/dev/null 1> string-walk-file" echo " $0 -s .1.3.6.1.4.1.534.6.6.7 numeric-walk-file string-walk-file" echo "mode 3:" echo " snmpwalk -On -v1 -c mycommunity 192.168.0.1 .1 2>/dev/null 1> numeric-walk-file" echo " $0 numeric-walk-file" echo "" echo " You may alos need to install additional packages:" echo " - 'snmp' package (on Debian) for the base commands (snmpget, snmpwalk, snmptranslate)" echo " - 'snmp-mibs-downloader' package (on Debian) to get all standard MIBs" } # tools [ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } [ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } # variables DRIVER="" KEEP="" HOSTNAME="" MIBS_DIRLIST="+." COMMUNITY="public" DEVICE_SYSOID="" SYSOID="" MODE=0 if (command -v mktemp) >/dev/null ; then true ; else # Have a simple (unsafe, unfeatured) fallback implementation: mktemp() { if [ x"$1" = x"-d" ] ; then shift mkdir -p "$1.$$" || return else cat /dev/null > "$1.$$" || return fi echo "$1.$$" } fi # constants NAME=gen-snmp-subdriver TMPDIR="${TEMPDIR:-/tmp}" SYSOID_NUMBER=".1.3.6.1.2.1.1.2.0" DEBUG="`mktemp \"$TMPDIR/$NAME-DEBUG.XXXXXX\"`" DFL_NUMWALKFILE="`mktemp \"$TMPDIR/$NAME-NUMWALK.XXXXXX\"`" DFL_STRWALKFILE="`mktemp \"$TMPDIR/$NAME-STRWALK.XXXXXX\"`" TMP_NUMWALKFILE="`mktemp \"$TMPDIR/$NAME-TMP-NUMWALK.XXXXXX\"`" TMP_STRWALKFILE="`mktemp \"$TMPDIR/$NAME-TMP-STRWALK.XXXXXX\"`" # Platforms vary with tooling abilitites... TOLOWER="cat" for TR_VARIANT in "tr 'A-Z' 'a-z'" "tr '[:upper:]' '[:lower:]'" "tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'" ; do if [ x"`echo C | $TR_VARIANT`" = xc ] ; then TOLOWER="$TR_VARIANT" break fi done TOUPPER="cat" for TR_VARIANT in "tr 'a-z' 'A-Z'" "tr '[:lower:]' '[:upper:]'" "tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ; do if [ x"`echo c | $TR_VARIANT`" = xC ] ; then TOUPPER="$TR_VARIANT" break fi done get_snmp_data() { # 1) get the sysOID (points the mfr specif MIB), apart if there's an override if [ -z "$SYSOID" ] then SYSOID="`snmpget -On -v1 -c \"$COMMUNITY\" -Ov \"$HOSTNAME\" \"$SYSOID_NUMBER\" | cut -d' ' -f2`" echo "sysOID retrieved: ${SYSOID}" else echo "Using the provided sysOID override ($SYSOID)" fi DEVICE_SYSOID="$SYSOID" OID_COUNT=0 while (test "$OID_COUNT" -eq 0) do # 2) get the content of the mfr specif MIB echo "Retrieving SNMP information. This may take some time" snmpwalk -On -v1 -c "$COMMUNITY" "$HOSTNAME" "$SYSOID" 2>/dev/null 1> "$DFL_NUMWALKFILE" snmpwalk -Os -v1 -m ALL -M"$MIBS_DIRLIST" -c "$COMMUNITY" "$HOSTNAME" "$SYSOID" 2>/dev/null 1> "$DFL_STRWALKFILE" # 3) test return value of the walk, and possibly ramp-up the path to get something. # The sysOID mechanism only works if we're pointed somehow in the right direction # i.e. doesn't work if sysOID is .1.3.6.1.4.1.705.1 and data is at .1.3.6.1.4.1.534... # Ex: sysOID = ".1.X.Y.Z" # try with ".1.X.Y.Z", if fails try with .1.X.Y", if fails try with .1.X"... OID_COUNT="`cat $NUMWALKFILE | wc -l`" if [ $OID_COUNT -eq 0 ]; then # ramp-up the provided sysOID by removing the last .x part SYSOID=${SYSOID%.*} echo "Warning: sysOID provided no data! Trying with a level up using $SYSOID" fi done return $OID_COUNT } generate_C() { # create file names, lowercase LDRIVER="`echo \"$DRIVER\" | $TOLOWER`" UDRIVER="`echo \"$DRIVER\" | $TOUPPER`" # keep dashes in name for files CFILE="$LDRIVER-mib.c" HFILE="$LDRIVER-mib.h" # but replace with underscores for the structures and defines LDRIVER="`echo \"$LDRIVER\" | tr - _`" UDRIVER="`echo \"$UDRIVER\" | tr - _`" # generate header file # NOTE: with <<-EOF leading TABs are all stripped echo "Creating $HFILE" cat > "$HFILE" <<-EOF /* ${HFILE} - subdriver to monitor ${DRIVER} SNMP devices with NUT * * Copyright (C) * 2011 - 2016 Arnaud Quette * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef ${UDRIVER}_MIB_H #define ${UDRIVER}_MIB_H #include "main.h" #include "snmp-ups.h" extern mib2nut_info_t ${LDRIVER}; #endif /* ${UDRIVER}_MIB_H */ EOF # generate source file # create heading boilerblate # NOTE: with <<-EOF leading TABs are all stripped echo "Creating $CFILE" cat > "$CFILE" <<-EOF /* ${CFILE} - subdriver to monitor ${DRIVER} SNMP devices with NUT * * Copyright (C) * 2011 - 2016 Arnaud Quette * * Note: this subdriver was initially generated as a "stub" by the * gen-snmp-subdriver script. It must be customized! * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "${HFILE}" #define ${UDRIVER}_MIB_VERSION "0.01" #define ${UDRIVER}_SYSOID "${DEVICE_SYSOID}" /* To create a value lookup structure (as needed on the 2nd line of the example * below), use the following kind of declaration, outside of the present snmp_info_t[]: * static info_lkp_t onbatt_info[] = { * info_lkp_default(1, "OB"), * info_lkp_default(2, "OL"), * info_lkp_sentinel * }; */ /* ${UDRIVER} Snmp2NUT lookup table */ static snmp_info_t ${LDRIVER}_mib[] = { /* Data format: * snmp_info_default(info_type, info_flags, info_len, OID, dfl, flags, oid2info), * * info_type: NUT INFO_ or CMD_ element name * info_flags: flags to set in addinfo * info_len: length of strings if ST_FLAG_STRING, multiplier otherwise * OID: SNMP OID or NULL * dfl: default value * flags: snmp-ups internal flags (FIXME: ...) * oid2info: lookup table between OID and NUT values * * Example: * snmp_info_default("input.voltage", 0, 0.1, ".1.3.6.1.4.1.705.1.6.2.1.2.1", "", SU_INPUT_1, NULL), * snmp_info_default("ups.status", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.705.1.7.3.0", "", SU_FLAG_OK | SU_STATUS_BATT, onbatt_info), * * To create a value lookup structure (as needed on the 2nd line), use the * following kind of declaration, outside of the present snmp_info_t[]: * static info_lkp_t onbatt_info[] = { * info_lkp_default(1, "OB"), * info_lkp_default(2, "OL"), * info_lkp_sentinel * }; */ /* standard MIB items; if the vendor MIB contains better OIDs for * this (e.g. with daisy-chain support), consider adding those here */ EOF # Same file, indented text (TABs not stripped): cat >> "$CFILE" < /dev/null if [ $? -eq 0 ]; then ST_FLAG_TYPE="ST_FLAG_STRING" SU_INFOSIZE="SU_INFOSIZE" else ST_FLAG_TYPE="0" SU_INFOSIZE="1" fi # get the matching numeric OID NUM_OID="`sed -n \"${LINENB}p\" \"${NUMWALKFILE}\" | cut -d' ' -f1`" printf "\t/* ${FULL_STR_OID} */\n\tsnmp_info_default(\"unmapped.${STR_OID}\", ${ST_FLAG_TYPE}, ${SU_INFOSIZE}, \"${NUM_OID}\", NULL, SU_FLAG_OK, NULL),\n" done < "${STRWALKFILE}" >> "${CFILE}" # append footer (TABs not stripped): cat >> "$CFILE" </dev/null ; then if [ $# -gt 1 ]; then NUMWALKFILE="$1" shift STRWALKFILE="$1" shift else NUMWALKFILE="$1" shift #usage #exit 1 fi elif [ "$1" = "--help" -o "$1" = "-h" ]; then usage exit 0 else echo "Illegal option $1. Try --help for more info." >&2 exit 1 fi done # check that the needed parameters are provided, depending on the mode if [ -z "$NUMWALKFILE" ]; then # mode 1: directly get SNMP data from a real agent echo "Mode 1 selected" MODE=1 NUMWALKFILE="$DFL_NUMWALKFILE" STRWALKFILE="$DFL_STRWALKFILE" # check if Net SNMP is available if [ -z "`command -v snmpget`" -o -z "`command -v snmpwalk`" ] && \ [ -z "`which snmpget`" -o -z "`which snmpwalk`" ]; then echo "Net SNMP not found! snmpget and snmpwalk commands are required." >&2 exit 1 fi # hostname is also mandatory while [ -z "$HOSTNAME" ]; do printf "\n\tPlease enter the SNMP host IP address or name.\n" read -p "SNMP host IP name or address: " HOSTNAME < /dev/tty if echo "$HOSTNAME" | ${EGREP} '[^a-zA-Z0-9.-]' >/dev/null ; then echo "Please use only letters, digits, dash and period character" HOSTNAME="" fi done # get data from the agent get_snmp_data else # no string walk provided, so mode 3 if [ -z "$STRWALKFILE" ]; then # mode 3: get data from 1 file, # Filter according to sysOID on the specific subtree # Generate the numeric SNMP walk using this output # then use snmptranslate to get the string OIDs and generated the string SNMP walk echo "Mode 3 selected" MODE=3 RAWWALKFILE="$NUMWALKFILE" NUMWALKFILE="$DFL_NUMWALKFILE" STRWALKFILE="$DFL_STRWALKFILE" # check for actual file existence if [ ! -f "$RAWWALKFILE" ]; then echo "SNMP walk dump file is missing on disk. Try --help for more info." >&2 exit 1 fi # Extract the sysOID # Format is "1.3.6.1.2.1.1.2.0 = OID: 1.3.6.1.4.1.4555.1.1.1" DEVICE_SYSOID="`${GREP} 1.3.6.1.2.1.1.2.0 \"$RAWWALKFILE\" | cut -d' ' -f4`" if [ -n "$DEVICE_SYSOID" ]; then echo "Found sysOID $DEVICE_SYSOID" else echo "SNMP sysOID is missing in file. Try --help for more info." >&2 exit 1 fi # Switch to the entry point, and extract the subtree # Extract the numeric walk echo -n "Extracting numeric SNMP walk..." ${GREP} "$DEVICE_SYSOID" "$RAWWALKFILE" | ${EGREP} -v "1.3.6.1.2.1.1.2.0" 2>/dev/null 1> "$NUMWALKFILE" echo " done" # Create the string walk from a translation of the numeric one echo -n "Converting string SNMP walk..." while IFS=' = ' read NUM_OID OID_VALUE do STR_OID="`snmptranslate -Os -m ALL -M+. \"$NUM_OID\" 2>/dev/null`" # Uncomment the below line to get debug logs #echo "Got: $STR_OID = $OID_VALUE" printf "." echo "$STR_OID = $OID_VALUE" >> "$STRWALKFILE" done < "$NUMWALKFILE" echo " done" else # mode 2: get data from files echo "Mode 2 selected" MODE=2 # get sysOID value from command line, if needed while [ -z "$SYSOID" ]; do echo " Please enter the value of sysOID, as displayed by snmp-ups. For example '.1.3.6.1.4.1.2254.2.4'. You can get it using: snmpget -v1 -c XXX $SYSOID_NUMBER" read -p "Value of sysOID: " SYSOID < /dev/tty if echo "$SYSOID" | ${EGREP} '[^0-9.]' >/dev/null ; then echo "Please use only the numeric form, with dots and digits" SYSOID="" fi done # check for actual files existence if [ ! -f "$NUMWALKFILE" -o ! -f "$STRWALKFILE" ]; then echo "SNMP walk dump files are missing on disk. Try --help for more info." >&2 exit 1 fi fi fi # delete temporary files: this is called just before exiting. cleanup () { rm -f "$DEBUG $DFL_NUMWALKFILE $TMP_NUMWALKFILE $DFL_STRWALKFILE $TMP_STRWALKFILE" } if [ -n "$KEEP" ]; then trap cleanup EXIT fi # prompt use for name of driver while [ -z "$DRIVER" ]; do echo " Please enter a name for this driver. Use only letters and numbers. Use natural (upper- and lowercase) capitalization, e.g., 'Belkin', 'APC'." read -p "Name of subdriver: " DRIVER < /dev/tty if echo "$DRIVER" | ${EGREP} '[^a-zA-Z0-9]' >/dev/null ; then echo "Please use only letters and digits" DRIVER="" fi done # remove blank and "End of MIB" lines TABCHAR="`printf '\t'`" ${EGREP} "^[ ${TABCHAR}]?\$" | ${GREP} "End of MIB" | ${GREP} -v "${NUMWALKFILE}" > "${TMP_NUMWALKFILE}" ${EGREP} "^[ ${TABCHAR}]?\$" | ${GREP} "End of MIB" | ${GREP} -v "${STRWALKFILE}" > "${TMP_STRWALKFILE}" NUMWALKFILE="${TMP_NUMWALKFILE}" STRWALKFILE="${TMP_STRWALKFILE}" # FIXME: sanity checks (! -z contents -a same `wc -l`) NUM_OID_COUNT="`cat \"$NUMWALKFILE\" | wc -l`" STR_OID_COUNT="`cat \"$STRWALKFILE\" | wc -l`" echo "SNMP OIDs extracted = $NUM_OID_COUNT / $NUM_OID_COUNT" generate_C # Display the remaining tasks cat <