/* snmp-ups.c - NUT Generic SNMP driver core (supports different MIBs) * * Based on NetSNMP API (Simple Network Management Protocol v1-2c-3) * * Copyright (C) * 2002 - 2014 Arnaud Quette * 2015 - 2021 Eaton (author: Arnaud Quette ) * 2016 - 2022 Eaton (author: Jim Klimov ) * 2002 - 2006 Dmitry Frolov * J.W. Hoogervorst * Niels Baggesen * 2009 - 2010 Arjen de Korte * * Sponsored by Eaton * and originally by MGE UPS SYSTEMS * * 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 * */ /* NUT SNMP common functions */ #include "main.h" /* includes "config.h" which must be the first header */ #include "nut_float.h" #include "nut_stdint.h" #include "snmp-ups.h" #include "parseconf.h" #include /* for isprint() */ /* include all known mib2nut lookup tables */ #include "apc-mib.h" #include "mge-mib.h" #include "netvision-mib.h" #include "powerware-mib.h" #include "eaton-pdu-genesis2-mib.h" #include "eaton-pdu-marlin-mib.h" #include "eaton-pdu-pulizzi-mib.h" #include "eaton-pdu-revelation-mib.h" #include "raritan-pdu-mib.h" #include "raritan-px2-mib.h" #include "baytech-mib.h" #include "compaq-mib.h" #include "bestpower-mib.h" #include "cyberpower-mib.h" #include "delta_ups-mib.h" #include "huawei-mib.h" #include "ietf-mib.h" #include "xppc-mib.h" #include "eaton-ats16-nmc-mib.h" #include "eaton-ats16-nm2-mib.h" #include "apc-ats-mib.h" #include "apc-pdu-mib.h" #include "eaton-ats30-mib.h" #include "emerson-avocent-pdu-mib.h" #include "hpe-pdu-mib.h" /* Address API change */ #if ( ! NUT_HAVE_LIBNETSNMP_usmAESPrivProtocol ) && ( ! defined usmAESPrivProtocol ) #define usmAESPrivProtocol usmAES128PrivProtocol #endif #ifdef USM_PRIV_PROTO_AES_LEN # define NUT_securityPrivProtoLen USM_PRIV_PROTO_AES_LEN #else # ifdef USM_PRIV_PROTO_AES128_LEN # define NUT_securityPrivProtoLen USM_PRIV_PROTO_AES128_LEN # else /* FIXME: Find another way to get the size of array(?) to avoid: * error: division 'sizeof (oid * {aka long unsigned int *}) / sizeof (oid {aka long unsigned int})' does not compute the number of array elements [-Werror=sizeof-pointer-div] * See also https://bugs.php.net/bug.php?id=37564 for context * which is due to most values in /usr/include/net-snmp/librarytransform_oids.h * being defined as "oid[10]" or similar arrays, and "backwards compatibility" * usmAESPrivProtocol name is an "oid *" pointer. */ # define NUT_securityPrivProtoLen (sizeof(usmAESPrivProtocol)/sizeof(oid)) # endif #endif static mib2nut_info_t *mib2nut[] = { &apc_ats, /* This struct comes from : apc-ats-mib.c */ &apc_pdu_rpdu, /* This struct comes from : apc-pdu-mib.c */ &apc_pdu_rpdu2, /* This struct comes from : apc-pdu-mib.c */ &apc_pdu_msp, /* This struct comes from : apc-pdu-mib.c */ &apc, /* This struct comes from : apc-mib.c */ &baytech, /* This struct comes from : baytech-mib.c */ &bestpower, /* This struct comes from : bestpower-mib.c */ &compaq, /* This struct comes from : compaq-mib.c */ &cyberpower, /* This struct comes from : cyberpower-mib.c */ &delta_ups, /* This struct comes from : delta_ups-mib.c */ &eaton_ats16_nmc, /* This struct comes from : eaton-ats16-nmc-mib.c */ &eaton_ats16_nm2, /* This struct comes from : eaton-ats16-nm2-mib.c */ &eaton_ats30, /* This struct comes from : eaton-ats30-mib.c */ &eaton_marlin, /* This struct comes from : eaton-mib.c */ &emerson_avocent_pdu, /* This struct comes from : emerson-avocent-pdu-mib.c */ &aphel_revelation, /* This struct comes from : eaton-mib.c */ &aphel_genesisII, /* This struct comes from : eaton-mib.c */ &pulizzi_switched1, /* This struct comes from : eaton-mib.c */ &pulizzi_switched2, /* This struct comes from : eaton-mib.c */ &hpe_pdu, /* This struct comes from : hpe-pdu-mib.c */ &huawei, /* This struct comes from : huawei-mib.c */ &mge, /* This struct comes from : mge-mib.c */ &netvision, /* This struct comes from : netvision-mib.c */ &powerware, /* This struct comes from : powerware-mib.c */ &pxgx_ups, /* This struct comes from : powerware-mib.c */ &raritan, /* This struct comes from : raritan-pdu-mib.c */ &raritan_px2, /* This struct comes from : raritan-px2-mib.c */ &xppc, /* This struct comes from : xppc-mib.c */ /* * Prepend vendor specific MIB mappings before IETF, so that * if a device supports both IETF and vendor specific MIB, * the vendor specific one takes precedence (when mibs=auto) */ &tripplite_ietf, /* This struct comes from : ietf-mib.c */ &ietf, /* This struct comes from : ietf-mib.c */ /* end of structure. */ NULL }; struct snmp_session g_snmp_sess, *g_snmp_sess_p; const char *OID_pwr_status; int g_pwr_battery; int pollfreq; /* polling frequency */ int semistaticfreq; /* semistatic entry update frequency */ static int semistatic_countdown = 0; static int quirk_symmetra_threephase = 0; /* Number of device(s): standard is "1", but talking * to a daisychain (master device) means more than 1 * (a directly addressable member of a daisy chain * would be seen as a single-device chain though) */ static long devices_count = 1; /* global var to handle daisychain iterations - * changed by loops in snmp_ups_walk() and su_addcmd(); * may be 0 for addressing certain values/commands * across all chain devices via master (1); * also may be 0 for non-daisychained devices */ static int current_device_number = 0; /* global var to handle daisychain iterations - * made TRUE if we resolved a "device.count" value */ static bool_t daisychain_enabled = FALSE; static daisychain_info_t **daisychain_info = NULL; /* pointer to the Snmp2Nut lookup table */ mib2nut_info_t *mib2nut_info; /* FIXME: to be trashed */ snmp_info_t *snmp_info; alarms_info_t *alarms_info; static const char *mibname; static const char *mibvers; #define DRIVER_NAME "Generic SNMP UPS driver" #define DRIVER_VERSION "1.21" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Arnaud Quette \n" \ "Arnaud Quette \n" \ "Dmitry Frolov \n" \ "J.W. Hoogervorst \n" \ "Niels Baggesen \n" \ "Jim Klimov \n" \ "Arjen de Korte ", DRV_STABLE, { NULL } }; /* FIXME: integrate MIBs info? do the same as for usbhid-ups! */ static time_t lastpoll = 0; /* Communication status handling */ #define COMM_UNKNOWN 0 #define COMM_OK 1 #define COMM_LOST 2 static int comm_status = COMM_UNKNOWN; /* template OIDs index start with 0 or 1 (estimated stable for a MIB), * automatically guessed at the first pass */ static int template_index_base = -1; /* Not that stable in the end... */ static int device_template_index_base = -1; /* OID index of the 1rst daisychained device */ static int outlet_template_index_base = -1; static int outletgroup_template_index_base = -1; static int ambient_template_index_base = -1; static int device_template_offset = -1; /* sysOID location */ #define SYSOID_OID ".1.3.6.1.2.1.1.2.0" /* Forward functions declarations */ static void disable_transfer_oids(void); bool_t get_and_process_data(int mode, snmp_info_t *su_info_p); int extract_template_number(snmp_info_flags_t template_type, const char* varname); snmp_info_flags_t get_template_type(const char* varname); /* --------------------------------------------- * driver functions implementations * --------------------------------------------- */ void upsdrv_initinfo(void) { snmp_info_t *su_info_p; upsdebugx(1, "SNMP UPS driver: entering %s()", __func__); dstate_setinfo("driver.version.data", "%s MIB %s", mibname, mibvers); if (snmp_info == NULL) { fatalx(EXIT_FAILURE, "%s: snmp_info is not initialized", __func__); } if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty", __func__); } /* add instant commands to the info database. * outlet (and groups) commands are processed later, during initial walk */ for (su_info_p = &snmp_info[0]; (su_info_p != NULL && su_info_p->info_type != NULL) ; su_info_p++) { if (su_info_p->flags == 0UL) { upsdebugx(4, "SNMP UPS driver: %s: MIB2NUT mapping '%s' (OID '%s') did not define flags bits. " "Entry would be treated as SU_FLAG_OK if available in returned data.", __func__, (su_info_p->info_type ? su_info_p->info_type : ""), (su_info_p->OID ? su_info_p->OID : "") ); /* Treat as OK if avail, otherwise discarded */ } su_info_p->flags |= SU_FLAG_OK; if ((SU_TYPE(su_info_p) == SU_TYPE_CMD) && !(su_info_p->flags & SU_OUTLET) && !(su_info_p->flags & SU_OUTLET_GROUP)) { /* first check that this OID actually exists */ /* FIXME: daisychain commands support! */ su_addcmd(su_info_p); /* if (nut_snmp_get(su_info_p->OID) != NULL) { dstate_addcmd(su_info_p->info_type); upsdebugx(1, "upsdrv_initinfo(): adding command '%s'", su_info_p->info_type); } */ } } if (testvar("notransferoids")) disable_transfer_oids(); if (testvar("symmetrathreephase")) quirk_symmetra_threephase = 1; else quirk_symmetra_threephase = 0; /* initialize all other INFO_ fields from list */ if (snmp_ups_walk(SU_WALKMODE_INIT) == TRUE) { dstate_dataok(); comm_status = COMM_OK; } else { dstate_datastale(); comm_status = COMM_LOST; } /* setup handlers for instcmd and setvar functions */ upsh.setvar = su_setvar; upsh.instcmd = su_instcmd; } void upsdrv_updateinfo(void) { upsdebugx(1,"SNMP UPS driver: entering %s()", __func__); /* only update every pollfreq */ /* FIXME: only update status (SU_STATUS_*), à la usbhid-ups, in between */ if (time(NULL) > (lastpoll + pollfreq)) { alarm_init(); status_init(); /* update all dynamic info fields */ if (snmp_ups_walk(SU_WALKMODE_UPDATE)) { upsdebugx(1, "%s: pollfreq: Data OK", __func__); dstate_dataok(); comm_status = COMM_OK; } else { upsdebugx(1, "%s: pollfreq: Data STALE", __func__); dstate_datastale(); comm_status = COMM_LOST; } /* Commit status first, otherwise in daisychain mode, "device.0" may * clear the alarm count since it has an empty alarm buffer and if there * is only one device that has alarms! */ if (daisychain_enabled == FALSE) alarm_commit(); status_commit(); if (daisychain_enabled == TRUE) alarm_commit(); /* store timestamp */ lastpoll = time(NULL); } else { /* Just tell the same status to upsd */ if (comm_status == COMM_OK) dstate_dataok(); else dstate_datastale(); } } void upsdrv_shutdown(void) { /* This driver will probably never support this. In order to be any use, the driver should be called near the end of the system halt script. By that time we in all likelyhood we won't have network capabilities anymore, so we could never send this command to the UPS. This is not an error, but a limitation of the interface used. */ upsdebugx(1, "%s...", __func__); /* set shutdown and autostart delay */ set_delays(); /* Try to shutdown with delay */ if (su_instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) { /* Shutdown successful */ return; } /* If the above doesn't work, try shutdown.reboot */ if (su_instcmd("shutdown.reboot", NULL) == STAT_INSTCMD_HANDLED) { /* Shutdown successful */ return; } /* If the above doesn't work, try load.off.delay */ if (su_instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) { /* Shutdown successful */ return; } fatalx(EXIT_FAILURE, "Shutdown failed!"); } void upsdrv_help(void) { upsdebugx(1, "entering %s", __func__); } /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { upsdebugx(1, "entering %s()", __func__); addvar(VAR_VALUE, SU_VAR_MIBS, "NOTE: You can run the driver binary with '-x mibs=--list' for an up to date listing)\n" "Set MIB compliance (default=ietf, allowed: mge,apcc,netvision,pw,cpqpower,...)"); addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_COMMUNITY, "Set community name (default=public)"); addvar(VAR_VALUE, SU_VAR_VERSION, "Set SNMP version (default=v1, allowed: v2c,v3)"); addvar(VAR_VALUE, SU_VAR_POLLFREQ, "Set polling frequency in seconds, to reduce network flow (default=30)"); addvar(VAR_VALUE, SU_VAR_SEMISTATICFREQ, "Set semistatic value update frequency in update cycles, to reduce network flow (default=10)"); addvar(VAR_VALUE, SU_VAR_RETRIES, "Specifies the number of Net-SNMP retries to be used in the requests (default=5)"); addvar(VAR_VALUE, SU_VAR_TIMEOUT, "Specifies the Net-SNMP timeout in seconds between retries (default=1)"); addvar(VAR_FLAG, "notransferoids", "Disable transfer OIDs (use on APCC Symmetras)"); addvar(VAR_FLAG, "symmetrathreephase", "Enable APCC three phase Symmetra quirks (use on APCC three phase Symmetras)"); addvar(VAR_VALUE, SU_VAR_SECLEVEL, "Set the securityLevel used for SNMPv3 messages (default=noAuthNoPriv, allowed: authNoPriv,authPriv)"); addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_SECNAME, "Set the securityName used for authenticated SNMPv3 messages (no default)"); addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_AUTHPASSWD, "Set the authentication pass phrase used for authenticated SNMPv3 messages (no default)"); addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_PRIVPASSWD, "Set the privacy pass phrase used for encrypted SNMPv3 messages (no default)"); /* Construct addvar() for SU_VAR_AUTHPROT: */ { int comma = 0; char tmp_buf[SU_LARGEBUF]; char *p = tmp_buf; char *pn; /* proto name to add */ size_t remain = sizeof(tmp_buf) - 1; int ret; NUT_UNUSED_VARIABLE(comma); /* potentially, if no protocols are available */ tmp_buf[0] = '\0'; ret = snprintf(p, remain, "%s", "Set the authentication protocol ("); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar()"); } p += ret; remain -= (size_t)ret; #if NUT_HAVE_LIBNETSNMP_usmHMACMD5AuthProtocol pn = "MD5"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_usmHMACSHA1AuthProtocol pn = "SHA"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC192SHA256AuthProtocol pn = "SHA256"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC256SHA384AuthProtocol pn = "SHA384"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC384SHA512AuthProtocol pn = "SHA512"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif pn = "none supported"; ret = snprintf(p, remain, "%s", (comma++ ? "" : pn) ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; ret = snprintf(p, remain, "%s", ") used for authenticated SNMPv3 messages (default=MD5 if available)"); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar()"); } p += ret; remain -= (size_t)ret; addvar(VAR_VALUE, SU_VAR_AUTHPROT, tmp_buf); } /* Construct addvar() for AUTHPROTO */ /* Construct addvar() for SU_VAR_PRIVPROT: */ { int comma = 0; char tmp_buf[SU_LARGEBUF]; char *p = tmp_buf; char *pn; /* proto name to add */ size_t remain = sizeof(tmp_buf) - 1; int ret; NUT_UNUSED_VARIABLE(comma); /* potentially, if no protocols are available */ tmp_buf[0] = '\0'; ret = snprintf(p, remain, "%s", "Set the privacy protocol ("); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar()"); } p += ret; remain -= (size_t)ret; #if NUT_HAVE_LIBNETSNMP_usmDESPrivProtocol pn = "DES"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_usmAESPrivProtocol || NUT_HAVE_LIBNETSNMP_usmAES128PrivProtocol pn = "AES"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; #endif #if NUT_HAVE_LIBNETSNMP_DRAFT_BLUMENTHAL_AES_04 # if NUT_HAVE_LIBNETSNMP_usmAES192PrivProtocol pn = "AES192"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; # endif # if NUT_HAVE_LIBNETSNMP_usmAES256PrivProtocol pn = "AES256"; ret = snprintf(p, remain, "%s%s", (comma++ ? ", " : ""), pn ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; # endif #endif /* NUT_HAVE_LIBNETSNMP_DRAFT_BLUMENTHAL_AES_04 */ pn = "none supported"; ret = snprintf(p, remain, "%s", (comma++ ? "" : pn) ); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar(%s)", pn); } p += ret; remain -= (size_t)ret; ret = snprintf(p, remain, "%s", ") used for encrypted SNMPv3 messages (default=DES if available)"); if (ret < 0 || (uintmax_t)ret > (uintmax_t)remain || (uintmax_t)ret > SIZE_MAX) { fatalx(EXIT_FAILURE, "Could not addvar()"); } p += ret; remain -= (size_t)ret; addvar(VAR_VALUE, SU_VAR_PRIVPROT, tmp_buf); } /* Construct addvar() for PRIVPROTO */ addvar(VAR_VALUE, SU_VAR_ONDELAY, "Set start delay time after shutdown"); addvar(VAR_VALUE, SU_VAR_OFFDELAY, "Set delay time before shutdown "); } void upsdrv_initups(void) { snmp_info_t *su_info_p, *cur_info_p; char model[SU_INFOSIZE]; bool_t status= FALSE; const char *mibs; int curdev = 0; upsdebugx(1, "SNMP UPS driver: entering %s()", __func__); /* Retrieve user's parameters */ mibs = testvar(SU_VAR_MIBS) ? getval(SU_VAR_MIBS) : "auto"; if (!strcmp(mibs, "--list")) { printf("The 'mibs' argument is '%s', so just listing the mappings this driver knows,\n" "and for 'mibs=auto' these mappings will be tried in the following order until\n" "the first one matches your device\n\n", mibs); int i; printf("%7s\t%-23s\t%-7s\t%-31s\t%-s\n", "NUMBER", "MAPPING NAME", "VERSION", "ENTRY POINT OID", "AUTO CHECK OID"); for (i=0; mib2nut[i] != NULL; i++) { printf(" %4d \t%-23s\t%7s\t%-31s\t%-s\n", (i+1), mib2nut[i]->mib_name ? mib2nut[i]->mib_name : "" , mib2nut[i]->mib_version ? mib2nut[i]->mib_version : "" , mib2nut[i]->sysOID ? mib2nut[i]->sysOID : "" , mib2nut[i]->oid_auto_check ? mib2nut[i]->oid_auto_check : "" ); } printf("\nOverall this driver has loaded %d MIB-to-NUT mapping tables\n", i); exit(EXIT_SUCCESS); } /* init SNMP library, etc... */ nut_snmp_init(progname, device_path); /* FIXME: first test if the device is reachable to avoid timeouts! */ /* Load the SNMP to NUT translation data */ load_mib2nut(mibs); /* init polling frequency */ if (getval(SU_VAR_POLLFREQ)) pollfreq = atoi(getval(SU_VAR_POLLFREQ)); else pollfreq = DEFAULT_POLLFREQ; /* init semistatic update frequency */ if (getval(SU_VAR_SEMISTATICFREQ)) semistaticfreq = atoi(getval(SU_VAR_SEMISTATICFREQ)); else semistaticfreq = DEFAULT_SEMISTATICFREQ; if (semistaticfreq < 1) { upsdebugx(1, "Bad %s value provided, setting to default", SU_VAR_SEMISTATICFREQ); semistaticfreq = DEFAULT_SEMISTATICFREQ; } semistatic_countdown = semistaticfreq; /* Get UPS Model node to see if there's a MIB */ /* FIXME: extend and use match_model_OID(char *model) */ su_info_p = su_find_info("ups.model"); /* Try to get device.model if ups.model is not available */ if (su_info_p == NULL) su_info_p = su_find_info("device.model"); if (su_info_p != NULL) { /* Daisychain specific: we may have a template (including formatting * string) that needs to be adapted! */ if (strchr(su_info_p->OID, '%') != NULL) { upsdebugx(2, "Found template, need to be adapted"); cur_info_p = (snmp_info_t *)malloc(sizeof(snmp_info_t)); cur_info_p->info_type = (char *)xmalloc(SU_INFOSIZE); cur_info_p->OID = (char *)xmalloc(SU_INFOSIZE); snprintf((char*)cur_info_p->info_type, SU_INFOSIZE, "%s", su_info_p->info_type); /* Use the daisychain master (0) / 1rst device index */ #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char*)cur_info_p->OID, SU_INFOSIZE, su_info_p->OID, 0); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } else { upsdebugx(2, "Found entry, not a template %s", su_info_p->OID); /* Otherwise, just point at what we found */ cur_info_p = su_info_p; } /* Actually get the data */ status = nut_snmp_get_str(cur_info_p->OID, model, sizeof(model), NULL); /* Free our malloc, if it was dynamic */ if (strchr(su_info_p->OID, '%') != NULL) { if (cur_info_p->info_type != NULL) free((char*)cur_info_p->info_type); if (cur_info_p->OID != NULL) free((char*)cur_info_p->OID); if (cur_info_p != NULL) free((char*)cur_info_p); } } if (status == TRUE) upslogx(0, "Detected %s on host %s (mib: %s %s)", model, device_path, mibname, mibvers); else fatalx(EXIT_FAILURE, "%s MIB wasn't found on %s", mibs, g_snmp_sess.peername); /* FIXME: "No supported device detected" */ /* Init daisychain and check if support is required */ daisychain_init(); /* Allocate / init the daisychain info structure (for phases only for now) * daisychain_info[0] is the whole chain! (added +1) */ daisychain_info = (daisychain_info_t**)malloc( sizeof(daisychain_info_t) * (size_t)(devices_count + 1) ); for (curdev = 0 ; curdev <= devices_count ; curdev++) { daisychain_info[curdev] = (daisychain_info_t*)malloc(sizeof(daisychain_info_t)); daisychain_info[curdev]->input_phases = (long)-1; daisychain_info[curdev]->output_phases = (long)-1; daisychain_info[curdev]->bypass_phases = (long)-1; } /* FIXME: also need daisychain awareness (so init)! * i.e load.off.delay+load.off + device.1.load.off.delay+device.1.load.off + ... */ /* FIXME: daisychain commands support! */ if (su_find_info("load.off.delay")) { /* Adds default with a delay value of '0' (= immediate) */ dstate_addcmd("load.off"); } if (su_find_info("load.on.delay")) { /* Adds default with a delay value of '0' (= immediate) */ dstate_addcmd("load.on"); } if (su_find_info("load.off.delay") && su_find_info("load.on.delay")) { /* Add composite instcmds (require setting multiple OID values) */ dstate_addcmd("shutdown.return"); dstate_addcmd("shutdown.stayoff"); } /* Publish sysDescr, sysContact and sysLocation (from IETF standard paths) * for all subdrivers that do not have one defined in their mapping * tables (note: for lack of better knowledge, defined as read-only * entries here, and also read-once - not updated during driver uptime) */ if (NULL == dstate_getinfo("device.description") && NULL == dstate_getinfo("device.1.description") ) { /* sysDescr.0 */ if (nut_snmp_get_str(".1.3.6.1.2.1.1.1.0", model, sizeof(model), NULL) == TRUE) { upsdebugx(2, "Using IETF-MIB default to get and publish sysDescr for device.description (once)"); dstate_setinfo("device.description", "%s", model); } else { upsdebugx(2, "Can't get and publish sysDescr for device.description"); } } if (NULL == dstate_getinfo("device.contact") && NULL == dstate_getinfo("device.1.contact") ) { /* sysContact.0 */ if (nut_snmp_get_str(".1.3.6.1.2.1.1.4.0", model, sizeof(model), NULL) == TRUE) { upsdebugx(2, "Using IETF-MIB default to get and publish sysContact for device.contact (once)"); dstate_setinfo("device.contact", "%s", model); } else { upsdebugx(2, "Can't get and publish sysContact for device.contact"); } } if (NULL == dstate_getinfo("device.location") && NULL == dstate_getinfo("device.1.location") ) { /* sysLocation.0 */ if (nut_snmp_get_str(".1.3.6.1.2.1.1.6.0", model, sizeof(model), NULL) == TRUE) { upsdebugx(2, "Using IETF-MIB default to get and publish sysLocation for device.location (once)"); dstate_setinfo("device.location", "%s", model); } else { upsdebugx(2, "Can't get and publish sysLocation for device.location"); } } /* set shutdown and autostart delay */ set_delays(); } void upsdrv_cleanup(void) { /* General cleanup */ if (daisychain_info) free(daisychain_info); /* Net-SNMP specific cleanup */ nut_snmp_cleanup(); } /* ----------------------------------------------------------- * SNMP functions. * ----------------------------------------------------------- */ void nut_snmp_init(const char *type, const char *hostname) { char *ns_options = NULL; const char *community, *version; const char *secLevel = NULL, *authPassword, *privPassword; const char *authProtocol, *privProtocol; int snmp_retries = DEFAULT_NETSNMP_RETRIES; long snmp_timeout = DEFAULT_NETSNMP_TIMEOUT; upsdebugx(2, "SNMP UPS driver: entering %s(%s)", __func__, type); /* Force numeric OIDs resolution (ie, do not resolve to textual names) * This is mostly for the convenience of debug output */ ns_options = snmp_out_toggle_options("n"); if (ns_options != NULL) { upsdebugx(2, "Failed to enable numeric OIDs resolution"); } /* Initialize the SNMP library */ init_snmp(type); /* Initialize session */ snmp_sess_init(&g_snmp_sess); g_snmp_sess.peername = xstrdup(hostname); /* Net-SNMP timeout and retries */ if (testvar(SU_VAR_RETRIES)) { snmp_retries = atoi(getval(SU_VAR_RETRIES)); } g_snmp_sess.retries = snmp_retries; upsdebugx(2, "Setting SNMP retries to %i", snmp_retries); if (testvar(SU_VAR_TIMEOUT)) { snmp_timeout = atol(getval(SU_VAR_TIMEOUT)); } /* We have to convert from seconds to microseconds */ g_snmp_sess.timeout = snmp_timeout * ONE_SEC; upsdebugx(2, "Setting SNMP timeout to %ld second(s)", snmp_timeout); /* Retrieve user parameters */ version = testvar(SU_VAR_VERSION) ? getval(SU_VAR_VERSION) : "v1"; /* Older CLANG (e.g. clang-3.4) sees short strings in str{n}cmp() * arguments as arrays and claims out-of-bounds accesses */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ARRAY_BOUNDS) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #endif #ifdef __clang__ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Warray-bounds" #endif if ((strcmp(version, "v1") == 0) || (strcmp(version, "v2c") == 0)) { g_snmp_sess.version = (strcmp(version, "v1") == 0) ? SNMP_VERSION_1 : SNMP_VERSION_2c; community = testvar(SU_VAR_COMMUNITY) ? getval(SU_VAR_COMMUNITY) : "public"; g_snmp_sess.community = (unsigned char *)xstrdup(community); g_snmp_sess.community_len = strlen(community); } else if (strcmp(version, "v3") == 0) { #ifdef __clang__ # pragma clang diagnostic pop #endif #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ARRAY_BOUNDS) # pragma GCC diagnostic pop #endif /* SNMP v3 related init */ g_snmp_sess.version = SNMP_VERSION_3; /* Security level */ if (testvar(SU_VAR_SECLEVEL)) { secLevel = getval(SU_VAR_SECLEVEL); if (strcmp(secLevel, "noAuthNoPriv") == 0) g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH; else if (strcmp(secLevel, "authNoPriv") == 0) g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV; else if (strcmp(secLevel, "authPriv") == 0) g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; else fatalx(EXIT_FAILURE, "Bad SNMPv3 securityLevel: %s", secLevel); } else g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH; /* Security name */ if (testvar(SU_VAR_SECNAME)) { g_snmp_sess.securityName = xstrdup(getval(SU_VAR_SECNAME)); g_snmp_sess.securityNameLen = strlen(g_snmp_sess.securityName); } else fatalx(EXIT_FAILURE, "securityName is required for SNMPv3"); /* Process mandatory fields, based on the security level */ authPassword = testvar(SU_VAR_AUTHPASSWD) ? getval(SU_VAR_AUTHPASSWD) : NULL; privPassword = testvar(SU_VAR_PRIVPASSWD) ? getval(SU_VAR_PRIVPASSWD) : NULL; switch (g_snmp_sess.securityLevel) { case SNMP_SEC_LEVEL_AUTHNOPRIV: if (authPassword == NULL) fatalx(EXIT_FAILURE, "authPassword is required for SNMPv3 in %s mode", secLevel); break; case SNMP_SEC_LEVEL_AUTHPRIV: if ((authPassword == NULL) || (privPassword == NULL)) fatalx(EXIT_FAILURE, "authPassword and privPassword are required for SNMPv3 in %s mode", secLevel); break; default: case SNMP_SEC_LEVEL_NOAUTH: /* nothing else needed */ break; } /* Process authentication protocol and key */ g_snmp_sess.securityAuthKeyLen = USM_AUTH_KU_LEN; authProtocol = testvar(SU_VAR_AUTHPROT) ? getval(SU_VAR_AUTHPROT) : "MD5"; #if NUT_HAVE_LIBNETSNMP_usmHMACMD5AuthProtocol if (strcmp(authProtocol, "MD5") == 0) { g_snmp_sess.securityAuthProto = usmHMACMD5AuthProtocol; g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid); } else #endif #if NUT_HAVE_LIBNETSNMP_usmHMACSHA1AuthProtocol if (strcmp(authProtocol, "SHA") == 0) { g_snmp_sess.securityAuthProto = usmHMACSHA1AuthProtocol; g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid); } else #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC192SHA256AuthProtocol if (strcmp(authProtocol, "SHA256") == 0) { g_snmp_sess.securityAuthProto = usmHMAC192SHA256AuthProtocol; g_snmp_sess.securityAuthProtoLen = sizeof(usmHMAC192SHA256AuthProtocol)/sizeof(oid); } else #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC256SHA384AuthProtocol if (strcmp(authProtocol, "SHA384") == 0) { g_snmp_sess.securityAuthProto = usmHMAC256SHA384AuthProtocol; g_snmp_sess.securityAuthProtoLen = sizeof(usmHMAC256SHA384AuthProtocol)/sizeof(oid); } else #endif #if NUT_HAVE_LIBNETSNMP_usmHMAC384SHA512AuthProtocol if (strcmp(authProtocol, "SHA512") == 0) { g_snmp_sess.securityAuthProto = usmHMAC384SHA512AuthProtocol; g_snmp_sess.securityAuthProtoLen = sizeof(usmHMAC384SHA512AuthProtocol)/sizeof(oid); } else #endif fatalx(EXIT_FAILURE, "Bad SNMPv3 authProtocol: %s", authProtocol); /* set the authentication key to a MD5/SHA1 hashed version of our * passphrase (must be at least 8 characters long) */ if (g_snmp_sess.securityLevel != SNMP_SEC_LEVEL_NOAUTH) { #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS # pragma GCC diagnostic ignored "-Wtype-limits" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif /* NOTE: Net-SNMP headers just are weird like that, in the same release: net-snmp/types.h: size_t securityAuthProtoLen; net-snmp/library/keytools.h: int generate_Ku(const oid * hashtype, u_int hashtype_len, ... * Should we match in configure like for "getnameinfo()" arg types? * Currently we cast one to another, below (detecting target type could help). */ if ((uintmax_t)g_snmp_sess.securityAuthProtoLen > UINT_MAX) { #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic pop #endif fatalx(EXIT_FAILURE, "Bad SNMPv3 securityAuthProtoLen: %zu", g_snmp_sess.securityAuthProtoLen); } if (generate_Ku(g_snmp_sess.securityAuthProto, (u_int)g_snmp_sess.securityAuthProtoLen, (const unsigned char *) authPassword, strlen(authPassword), g_snmp_sess.securityAuthKey, &g_snmp_sess.securityAuthKeyLen) != SNMPERR_SUCCESS) { fatalx(EXIT_FAILURE, "Error generating Ku from authentication pass phrase"); } } privProtocol = testvar(SU_VAR_PRIVPROT) ? getval(SU_VAR_PRIVPROT) : "DES"; #if NUT_HAVE_LIBNETSNMP_usmDESPrivProtocol if (strcmp(privProtocol, "DES") == 0) { g_snmp_sess.securityPrivProto = usmDESPrivProtocol; g_snmp_sess.securityPrivProtoLen = sizeof(usmDESPrivProtocol)/sizeof(oid); } else #endif #if NUT_HAVE_LIBNETSNMP_usmAESPrivProtocol || NUT_HAVE_LIBNETSNMP_usmAES128PrivProtocol if (strcmp(privProtocol, "AES") == 0) { g_snmp_sess.securityPrivProto = usmAESPrivProtocol; g_snmp_sess.securityPrivProtoLen = NUT_securityPrivProtoLen; } else #endif #if NUT_HAVE_LIBNETSNMP_DRAFT_BLUMENTHAL_AES_04 # if NUT_HAVE_LIBNETSNMP_usmAES192PrivProtocol if (strcmp(privProtocol, "AES192") == 0) { g_snmp_sess.securityPrivProto = usmAES192PrivProtocol; g_snmp_sess.securityPrivProtoLen = (sizeof(usmAES192PrivProtocol)/sizeof(oid)); } else # endif # if NUT_HAVE_LIBNETSNMP_usmAES256PrivProtocol if (strcmp(privProtocol, "AES256") == 0) { g_snmp_sess.securityPrivProto = usmAES256PrivProtocol; g_snmp_sess.securityPrivProtoLen = (sizeof(usmAES256PrivProtocol)/sizeof(oid)); } else # endif #endif /* NUT_HAVE_LIBNETSNMP_DRAFT_BLUMENTHAL_AES_04 */ fatalx(EXIT_FAILURE, "Bad SNMPv3 privProtocol: %s", privProtocol); /* set the privacy key to a MD5/SHA1 hashed version of our * passphrase (must be at least 8 characters long) */ if (g_snmp_sess.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) { g_snmp_sess.securityPrivKeyLen = USM_PRIV_KU_LEN; #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS # pragma GCC diagnostic ignored "-Wtype-limits" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif /* See comment on generate_Ku() a few dozen lines above */ if ((uintmax_t)g_snmp_sess.securityAuthProtoLen > UINT_MAX) { #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) # pragma GCC diagnostic pop #endif fatalx(EXIT_FAILURE, "Bad SNMPv3 securityAuthProtoLen: %zu", g_snmp_sess.securityAuthProtoLen); } if (generate_Ku(g_snmp_sess.securityAuthProto, (u_int)g_snmp_sess.securityAuthProtoLen, (const unsigned char *) privPassword, strlen(privPassword), g_snmp_sess.securityPrivKey, &g_snmp_sess.securityPrivKeyLen) != SNMPERR_SUCCESS) { fatalx(EXIT_FAILURE, "Error generating Ku from privacy pass phrase"); } } } else fatalx(EXIT_FAILURE, "Bad SNMP version: %s", version); /* Open the session */ SOCK_STARTUP; /* MS Windows wrapper, not really needed on Unix! */ g_snmp_sess_p = snmp_open(&g_snmp_sess); /* establish the session */ if (g_snmp_sess_p == NULL) { nut_snmp_perror(&g_snmp_sess, 0, NULL, "nut_snmp_init: snmp_open"); fatalx(EXIT_FAILURE, "Unable to establish communication"); } } void nut_snmp_cleanup(void) { /* close snmp session. */ if (g_snmp_sess_p) { snmp_close(g_snmp_sess_p); g_snmp_sess_p = NULL; } SOCK_CLEANUP; /* wrapper not needed on Unix! */ } /* Free a struct snmp_pdu * returned by nut_snmp_walk */ static void nut_snmp_free(struct snmp_pdu ** array_to_free) { struct snmp_pdu ** current_element; if (array_to_free != NULL) { current_element = array_to_free; while (*current_element != NULL) { snmp_free_pdu(*current_element); current_element++; } free( array_to_free ); } } /* Return a NULL terminated array of snmp_pdu * */ static struct snmp_pdu **nut_snmp_walk(const char *OID, int max_iteration) { int status; struct snmp_pdu *pdu, *response = NULL; oid name[MAX_OID_LEN]; size_t name_len = MAX_OID_LEN; oid * current_name; size_t current_name_len; static unsigned int numerr = 0; int nb_iteration = 0; struct snmp_pdu ** ret_array = NULL; int type = SNMP_MSG_GET; upsdebugx(3, "%s(%s)", __func__, OID); upsdebugx(4, "%s: max. iteration = %i", __func__, max_iteration); /* create and send request. */ if (!snmp_parse_oid(OID, name, &name_len)) { upsdebugx(2, "[%s] %s: %s: %s", upsname?upsname:device_name, __func__, OID, snmp_api_errstring(snmp_errno)); return NULL; } current_name = name; current_name_len = name_len; while( nb_iteration < max_iteration ) { /* Going to a shorter OID means we are outside our sub-tree */ if( current_name_len < name_len ) { break; } pdu = snmp_pdu_create(type); if (pdu == NULL) { fatalx(EXIT_FAILURE, "Not enough memory"); } snmp_add_null_var(pdu, current_name, current_name_len); status = snmp_synch_response(g_snmp_sess_p, pdu, &response); if (!response) { break; } if (!((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))) { if (mibname == NULL) { /* We are probing for proper mib - ignore errors */ snmp_free_pdu(response); nut_snmp_free(ret_array); return NULL; } numerr++; if ((numerr == SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0)) { upslogx(LOG_WARNING, "[%s] Warning: excessive poll " "failures, limiting error reporting (OID = %s)", upsname?upsname:device_name, OID); } if ((numerr < SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0)) { if (type == SNMP_MSG_GETNEXT) { upsdebugx(2, "=> No more OID, walk complete"); } else { nut_snmp_perror(g_snmp_sess_p, status, response, "%s: %s", __func__, OID); } } snmp_free_pdu(response); break; } else { numerr = 0; } nb_iteration++; /* +1 is for the terminating NULL */ struct snmp_pdu ** new_ret_array = realloc( ret_array, sizeof(struct snmp_pdu*) * ((size_t)nb_iteration+1) ); if (new_ret_array == NULL) { upsdebugx(1, "%s: Failed to realloc thread", __func__); break; } else { ret_array = new_ret_array; } ret_array[nb_iteration-1] = response; ret_array[nb_iteration]=NULL; current_name = response->variables->name; current_name_len = response->variables->name_length; type = SNMP_MSG_GETNEXT; } return ret_array; } struct snmp_pdu *nut_snmp_get(const char *OID) { struct snmp_pdu ** pdu_array; struct snmp_pdu * ret_pdu; if (OID == NULL) return NULL; upsdebugx(3, "%s(%s)", __func__, OID); pdu_array = nut_snmp_walk(OID,1); if(pdu_array == NULL) { return NULL; } ret_pdu = snmp_clone_pdu(*pdu_array); nut_snmp_free(pdu_array); return ret_pdu; } static bool_t decode_str(struct snmp_pdu *pdu, char *buf, size_t buf_len, info_lkp_t *oid2info) { size_t len = 0; char tmp_buf[SU_LARGEBUF]; /* zero out buffer. */ memset(buf, 0, buf_len); switch (pdu->variables->type) { case ASN_OCTET_STR: case ASN_OPAQUE: len = pdu->variables->val_len > buf_len - 1 ? buf_len - 1 : pdu->variables->val_len; /* Test for hexadecimal values */ int hex = 0, x; unsigned char *cp; for(cp = pdu->variables->val.string, x = 0; x < (int)pdu->variables->val_len; x++, cp++) { if (!(isprint((size_t)*cp) || isspace((size_t)*cp))) { hex = 1; } } if (hex) snprint_hexstring(buf, buf_len, pdu->variables->val.string, pdu->variables->val_len); else { memcpy(buf, pdu->variables->val.string, len); buf[len] = '\0'; } break; case ASN_INTEGER: case ASN_COUNTER: case ASN_GAUGE: if(oid2info) { const char *str; /* See union netsnmp_vardata in net-snmp/types.h: "integer" is a "long*" */ assert(sizeof(pdu->variables->val.integer) == sizeof(long*)); /* If in future net-snmp headers val becomes not-a-pointer, * compiler should complain about (void*) arg casting here */ if((str = su_find_infoval(oid2info, pdu->variables->val.integer))) { strncpy(buf, str, buf_len-1); } /* when oid2info returns NULL, don't publish the variable! */ else { /* strncpy(buf, "UNKNOWN", buf_len-1); */ return FALSE; } buf[buf_len-1]='\0'; } else { int ret = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer); if (ret < 0) upsdebugx(3, "Failed to retrieve ASN_GAUGE"); else len = (size_t)ret; } break; case ASN_TIMETICKS: /* convert timeticks to seconds */ { int ret = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer / 100); if (ret < 0) upsdebugx(3, "Failed to retrieve ASN_TIMETICKS"); else len = (size_t)ret; } break; case ASN_OBJECT_ID: snprint_objid (tmp_buf, sizeof(tmp_buf), pdu->variables->val.objid, pdu->variables->val_len / sizeof(oid)); upsdebugx(2, "Received an OID value: %s", tmp_buf); /* Try to get the value of the pointed OID */ if (nut_snmp_get_str(tmp_buf, buf, buf_len, oid2info) == FALSE) { upsdebugx(3, "Failed to retrieve OID value, using fallback"); /* Otherwise return the last part of the returned OID (ex: 1.2.3 => 3) */ char *oid_leaf = strrchr(tmp_buf, '.'); snprintf(buf, buf_len, "%s", oid_leaf+1); upsdebugx(3, "Fallback value: %s", buf); } break; default: return FALSE; } return TRUE; } bool_t nut_snmp_get_str(const char *OID, char *buf, size_t buf_len, info_lkp_t *oid2info) { struct snmp_pdu *pdu; bool_t ret; upsdebugx(3, "Entering %s()", __func__); pdu = nut_snmp_get(OID); if (pdu == NULL) return FALSE; ret = decode_str(pdu,buf,buf_len,oid2info); if(ret == FALSE) { upsdebugx(2, "[%s] unhandled ASN 0x%x received from %s", upsname?upsname:device_name, pdu->variables->type, OID); } snmp_free_pdu(pdu); return ret; } static bool_t decode_oid(struct snmp_pdu *pdu, char *buf, size_t buf_len) { /* zero out buffer. */ memset(buf, 0, buf_len); switch (pdu->variables->type) { case ASN_OBJECT_ID: snprint_objid (buf, buf_len, pdu->variables->val.objid, pdu->variables->val_len / sizeof(oid)); upsdebugx(2, "OID value: %s", buf); break; default: return FALSE; } return TRUE; } /* Return the value stored in OID, which is an OID (sysOID for example) * and don't try to get the value pointed by this OID (no follow). * To achieve the latter behavior, use standard nut_snmp_get_{str,int}() */ bool_t nut_snmp_get_oid(const char *OID, char *buf, size_t buf_len) { struct snmp_pdu *pdu; bool_t ret = FALSE; /* zero out buffer. */ memset(buf, 0, buf_len); upsdebugx(3, "Entering %s()", __func__); pdu = nut_snmp_get(OID); if (pdu == NULL) return FALSE; ret = decode_oid(pdu, buf, buf_len); if(ret == FALSE) { upsdebugx(2, "[%s] unhandled ASN 0x%x received from %s", upsname?upsname:device_name, pdu->variables->type, OID); } snmp_free_pdu(pdu); return ret; } bool_t nut_snmp_get_int(const char *OID, long *pval) { char tmp_buf[SU_LARGEBUF]; struct snmp_pdu *pdu; long value; char *buf; upsdebugx(3, "Entering %s()", __func__); pdu = nut_snmp_get(OID); if (pdu == NULL) return FALSE; switch (pdu->variables->type) { case ASN_OCTET_STR: case ASN_OPAQUE: buf = xmalloc(pdu->variables->val_len + 1); memcpy(buf, pdu->variables->val.string, pdu->variables->val_len); buf[pdu->variables->val_len] = '\0'; value = strtol(buf, NULL, 0); free(buf); break; case ASN_INTEGER: case ASN_COUNTER: case ASN_GAUGE: value = *pdu->variables->val.integer; break; case ASN_TIMETICKS: /* convert timeticks to seconds */ value = *pdu->variables->val.integer / 100; break; case ASN_OBJECT_ID: snprint_objid (tmp_buf, sizeof(tmp_buf), pdu->variables->val.objid, pdu->variables->val_len / sizeof(oid)); upsdebugx(2, "Received an OID value: %s", tmp_buf); /* Try to get the value of the pointed OID */ if (nut_snmp_get_int(tmp_buf, &value) == FALSE) { upsdebugx(3, "Failed to retrieve OID value, using fallback"); /* Otherwise return the last part of the returned OID (ex: 1.2.3 => 3) */ char *oid_leaf = strrchr(tmp_buf, '.'); value = strtol(oid_leaf+1, NULL, 0); upsdebugx(3, "Fallback value: %ld", value); } break; default: upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x received from %s", upsname?upsname:device_name, pdu->variables->type, OID); return FALSE; } snmp_free_pdu(pdu); if (pval != NULL) *pval = value; return TRUE; } bool_t nut_snmp_set(const char *OID, char type, const char *value) { int status; bool_t ret = FALSE; struct snmp_pdu *pdu, *response = NULL; oid name[MAX_OID_LEN]; size_t name_len = MAX_OID_LEN; upsdebugx(1, "entering %s(%s, %c, %s)", __func__, OID, type, value); if (!snmp_parse_oid(OID, name, &name_len)) { upslogx(LOG_ERR, "[%s] %s: %s: %s", upsname?upsname:device_name, __func__, OID, snmp_api_errstring(snmp_errno)); return FALSE; } pdu = snmp_pdu_create(SNMP_MSG_SET); if (pdu == NULL) fatalx(EXIT_FAILURE, "Not enough memory"); if (snmp_add_var(pdu, name, name_len, type, value)) { upslogx(LOG_ERR, "[%s] %s: %s: %s", upsname?upsname:device_name, __func__, OID, snmp_api_errstring(snmp_errno)); return FALSE; } status = snmp_synch_response(g_snmp_sess_p, pdu, &response); if ((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR)) ret = TRUE; else nut_snmp_perror(g_snmp_sess_p, status, response, "%s: can't set %s", __func__, OID); snmp_free_pdu(response); return ret; } bool_t nut_snmp_set_str(const char *OID, const char *value) { return nut_snmp_set(OID, 's', value); } bool_t nut_snmp_set_int(const char *OID, long value) { char buf[SU_BUFSIZE]; snprintf(buf, sizeof(buf), "%ld", value); return nut_snmp_set(OID, 'i', buf); } bool_t nut_snmp_set_time(const char *OID, long value) { char buf[SU_BUFSIZE]; snprintf(buf, SU_BUFSIZE, "%ld", value * 100); return nut_snmp_set(OID, 't', buf); } /* log descriptive SNMP error message. */ void nut_snmp_perror(struct snmp_session *sess, int status, struct snmp_pdu *response, const char *fmt, ...) { va_list va; int cliberr, snmperr; char *snmperrstr; char buf[SU_LARGEBUF]; va_start(va, fmt); vsnprintf(buf, sizeof(buf), fmt, va); va_end(va); if (response == NULL) { snmp_error(sess, &cliberr, &snmperr, &snmperrstr); upslogx(LOG_ERR, "[%s] %s: %s", upsname?upsname:device_name, buf, snmperrstr); free(snmperrstr); } else if (status == STAT_SUCCESS) { /* Net-SNMP headers provide and consume errstat with different types: net-snmp/output_api.h: const char *snmp_errstring(int snmp_errorno); net-snmp/types.h: long errstat; * Should we match in configure like for "getnameinfo()" arg types? * Currently we cast one to another, below (detecting target type could help). */ switch (response->errstat) { case SNMP_ERR_NOERROR: break; case SNMP_ERR_NOSUCHNAME: /* harmless */ upsdebugx(2, "[%s] %s: %s", upsname?upsname:device_name, buf, (response->errstat > INT_MAX ? "(Net-SNMP errstat value is out of range)" : snmp_errstring((int)response->errstat) )); break; default: upslogx(LOG_ERR, "[%s] %s: Error in packet: %s", upsname?upsname:device_name, buf, (response->errstat > INT_MAX ? "(Net-SNMP errstat value is out of range)" : snmp_errstring((int)response->errstat) )); break; } } else if (status == STAT_TIMEOUT) { upslogx(LOG_ERR, "[%s] %s: Timeout: no response from %s", upsname?upsname:device_name, buf, sess->peername); } else { snmp_sess_error(sess, &cliberr, &snmperr, &snmperrstr); upslogx(LOG_ERR, "[%s] %s: %s", upsname?upsname:device_name, buf, snmperrstr); free(snmperrstr); } } /* ----------------------------------------------------------- * utility functions. * ----------------------------------------------------------- */ /* deal with APCC weirdness on Symmetras */ static void disable_transfer_oids(void) { snmp_info_t *su_info_p; upslogx(LOG_INFO, "Disabling transfer OIDs"); if (snmp_info == NULL) { fatalx(EXIT_FAILURE, "%s: snmp_info is not initialized", __func__); } if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty", __func__); } for (su_info_p = &snmp_info[0]; (su_info_p != NULL && su_info_p->info_type != NULL) ; su_info_p++) { if (!strcasecmp(su_info_p->info_type, "input.transfer.low")) { su_info_p->flags &= ~SU_FLAG_OK; continue; } if (!strcasecmp(su_info_p->info_type, "input.transfer.high")) { su_info_p->flags &= ~SU_FLAG_OK; continue; } } } /* Universal function to add or update info element. * If value is NULL, use the default one (su_info_p->dfl) if provided */ void su_setinfo(snmp_info_t *su_info_p, const char *value) { info_lkp_t *info_lkp; char info_type[128]; /* We tweak incoming "su_info_p->info_type" value in some cases */ /* FIXME: Replace hardcoded 128 with a macro above (use {SU_}LARGEBUF?), * and same macro or sizeof(info_type) below (also more 128 cases below)? */ upsdebugx(1, "entering %s(%s, %s)", __func__, su_info_p->info_type, (value)?value:""); /* FIXME: This 20 seems very wrong (should be "128", macro or sizeof? see above) */ memset(info_type, 0, 20); /* pre-fill with the device name for checking */ snprintf(info_type, 128, "device.%i", current_device_number); /* Daisy-chain template magic should only apply to defaulted * entries (oid==null) or templated entries (contains .%i); * however at this point we see exact OIDs handed down from * su_ups_get() which instantiates a template (if needed - * and knows it was a template) and calls su_setinfo(). * NOTE: For setting the values (or commands) from clients * like `upsrw`, see su_setOID() method. Here we change our * device state records based on readings from a device. */ if ((daisychain_enabled == TRUE) && (devices_count > 1)) { if (su_info_p->OID != NULL && strstr(su_info_p->OID, ".%i") != NULL ) { /* Only inform, do not react so far, * need more understanding if and when * such situation might happen at all: */ upsdebugx(5, "%s: in a daisy-chained device, " "got a templated OID %s for type %s", __func__, su_info_p->OID, su_info_p->info_type); } /* Only append "device.X" for master and slaves, if not already done! */ if ((current_device_number > 0) && (strstr(su_info_p->info_type, info_type) == NULL)) { /* Special case: we remove "device" from the device collection not to * get "device.X.device.", but "device.X." */ if (!strncmp(su_info_p->info_type, "device.", 7)) { upsdebugx(6, "%s: in a daisy-chained device, " "OID %s: TRIM 'device.' from type %s (value %s)", __func__, su_info_p->OID, su_info_p->info_type, (value)?value:""); snprintf(info_type, 128, "device.%i.%s", current_device_number, su_info_p->info_type + 7); } else { upsdebugx(6, "%s: in a daisy-chained device, " "OID %s is templated: for type %s (value %s)", __func__, su_info_p->OID, su_info_p->info_type, (value)?value:""); snprintf(info_type, 128, "device.%i.%s", current_device_number, su_info_p->info_type); } } else { upsdebugx(6, "%s: in a daisy-chained device, " "OID %s: for type %s (value %s) " "device %d is not positive or type already " "contains the prepared expectation: %s", __func__, su_info_p->OID, su_info_p->info_type, (value)?value:"", current_device_number, info_type ); snprintf(info_type, 128, "%s", su_info_p->info_type); } } else { upsdebugx(6, "%s: NOT in a daisy-chained device", __func__); snprintf(info_type, 128, "%s", su_info_p->info_type); } upsdebugx(1, "%s: using info_type '%s'", __func__, info_type); if (SU_TYPE(su_info_p) == SU_TYPE_CMD) return; /* ups.status and {ups, Lx, outlet, outlet.group}.alarm have special * handling, not here! */ if ((strcasecmp(su_info_p->info_type, "ups.status")) && (strcasecmp(strrchr(su_info_p->info_type, '.'), ".alarm"))) { if (value != NULL) dstate_setinfo(info_type, "%s", value); else if (su_info_p->dfl != NULL) dstate_setinfo(info_type, "%s", su_info_p->dfl); else { upsdebugx(3, "%s: no value nor default provided, aborting...", __func__); return; } dstate_setflags(info_type, su_info_p->info_flags); dstate_setaux(info_type, su_info_p->info_len); /* Set enumerated values, only if the data has ST_FLAG_RW and there * are lookup values */ /* FIXME: daisychain settings support: check if applicable */ if ((su_info_p->info_flags & ST_FLAG_RW) && su_info_p->oid2info) { upsdebugx(3, "%s: adding enumerated values", __func__); /* Loop on all existing values */ for (info_lkp = su_info_p->oid2info; info_lkp != NULL && info_lkp->info_value != NULL; info_lkp++) { dstate_addenum(info_type, "%s", info_lkp->info_value); } } /* Commit the current value, to avoid staleness with huge * data collections on slow devices */ dstate_dataok(); } } void su_status_set(snmp_info_t *su_info_p, long value) { const char *info_value = NULL; upsdebugx(2, "SNMP UPS driver: entering %s()", __func__); if ((info_value = su_find_infoval(su_info_p->oid2info, &value)) != NULL) { if (info_value[0] != '\0') { status_set(info_value); } } /* TODO: else */ } void su_alarm_set(snmp_info_t *su_info_p, long value) { const char *info_value = NULL; const char *info_type = NULL; char alarm_info_value[SU_LARGEBUF]; /* number of the outlet or phase */ int item_number = -1; upsdebugx(2, "SNMP UPS driver: entering %s(%s)", __func__, su_info_p->info_type); /* daisychain handling * extract the template part to get the relevant 'info_type' part * ex: device.6.L1.alarm => L1.alarm * ex: device.6.outlet.1.alarm => outlet.1.alarm */ if (!strncmp(su_info_p->info_type, "device.", 7)) { info_type = strchr(su_info_p->info_type + 7, '.') + 1; } else info_type = su_info_p->info_type; upsdebugx(2, "%s: using definition %s", __func__, info_type); if ((info_value = su_find_infoval(su_info_p->oid2info, &value)) != NULL && info_value[0] != 0) { /* Special handling for outlet & outlet groups alarms */ if ((su_info_p->flags & SU_OUTLET) || (su_info_p->flags & SU_OUTLET_GROUP)) { /* Extract template number */ item_number = extract_template_number(su_info_p->flags, info_type); upsdebugx(2, "%s: appending %s %i", __func__, (su_info_p->flags & SU_OUTLET_GROUP) ? "outlet group" : "outlet", item_number); /* Inject in the alarm string */ snprintf(alarm_info_value, sizeof(alarm_info_value), "outlet%s %i %s", (su_info_p->flags & SU_OUTLET_GROUP) ? " group" : "", item_number, info_value); info_value = &alarm_info_value[0]; } /* Special handling for phase alarms * Note that SU_*PHASE flags are cleared, so match the 'Lx' * start of path */ if (info_type[0] == 'L') { /* Extract phase number */ item_number = atoi(info_type+1); char alarm_info_value_more[SU_LARGEBUF + 32]; /* can sprintf() SU_LARGEBUF plus markup into here */ upsdebugx(2, "%s: appending phase L%i", __func__, item_number); /* Inject in the alarm string */ snprintf(alarm_info_value_more, sizeof(alarm_info_value_more), "phase L%i %s", item_number, info_value); info_value = &alarm_info_value_more[0]; } /* Set the alarm value */ alarm_set(info_value); } /* TODO: else */ } /* find info element definition in my info array. */ snmp_info_t *su_find_info(const char *type) { snmp_info_t *su_info_p; if (snmp_info == NULL) { fatalx(EXIT_FAILURE, "%s: snmp_info is not initialized", __func__); } if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty", __func__); } for (su_info_p = &snmp_info[0]; (su_info_p != NULL && su_info_p->info_type != NULL) ; su_info_p++) if (!strcasecmp(su_info_p->info_type, type)) { upsdebugx(3, "%s: \"%s\" found", __func__, type); return su_info_p; } upsdebugx(3, "%s: unknown info type (%s)", __func__, type); return NULL; } /* Counter match the sysOID using {device,ups}.model OID * Return TRUE if this OID can be retrieved, FALSE otherwise */ static bool_t match_model_OID() { bool_t retCode = FALSE; snmp_info_t *su_info_p, *cur_info_p; char testOID_buf[LARGEBUF]; /* Try to get device.model first */ su_info_p = su_find_info("device.model"); /* Otherwise, try to get ups.model */ if (su_info_p == NULL) su_info_p = su_find_info("ups.model"); if (su_info_p != NULL) { /* Daisychain specific: we may have a template (including formatting * string) that needs to be adapted! */ if (strchr(su_info_p->OID, '%') != NULL) { upsdebugx(2, "Found template, need to be adapted"); cur_info_p = (snmp_info_t *)malloc(sizeof(snmp_info_t)); cur_info_p->info_type = (char *)xmalloc(SU_INFOSIZE); cur_info_p->OID = (char *)xmalloc(SU_INFOSIZE); snprintf((char*)cur_info_p->info_type, SU_INFOSIZE, "%s", su_info_p->info_type); /* Use the daisychain master (0) / 1rst device index */ #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char*)cur_info_p->OID, SU_INFOSIZE, su_info_p->OID, 0); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } else { upsdebugx(2, "Found entry, not a template %s", su_info_p->OID); /* Otherwise, just point at what we found */ cur_info_p = su_info_p; } upsdebugx(2, "Testing %s using OID %s", cur_info_p->info_type, cur_info_p->OID); retCode = nut_snmp_get_str(cur_info_p->OID, testOID_buf, LARGEBUF, NULL); /* Free our malloc, if it was dynamic */ if (strchr(su_info_p->OID, '%') != NULL) { if (cur_info_p->info_type != NULL) free((char*)cur_info_p->info_type); if (cur_info_p->OID != NULL) free((char*)cur_info_p->OID); if (cur_info_p != NULL) free((char*)cur_info_p); } } return retCode; } /* Try to find the MIB using sysOID matching. * Return a pointer to a mib2nut definition if found, NULL otherwise */ static mib2nut_info_t *match_sysoid() { char sysOID_buf[LARGEBUF]; oid device_sysOID[MAX_OID_LEN]; size_t device_sysOID_len = MAX_OID_LEN; oid mib2nut_sysOID[MAX_OID_LEN]; size_t mib2nut_sysOID_len = MAX_OID_LEN; int i; /* Retrieve sysOID value of this device */ if (nut_snmp_get_oid(SYSOID_OID, sysOID_buf, sizeof(sysOID_buf)) == TRUE) { upsdebugx(1, "%s: device sysOID value = %s", __func__, sysOID_buf); /* Build OIDs for comparison */ if (!read_objid(sysOID_buf, device_sysOID, &device_sysOID_len)) { upsdebugx(2, "%s: can't build device_sysOID %s: %s", __func__, sysOID_buf, snmp_api_errstring(snmp_errno)); return NULL; } /* Now, iterate on mib2nut definitions */ for (i = 0; mib2nut[i] != NULL; i++) { upsdebugx(1, "%s: checking MIB %s", __func__, mib2nut[i]->mib_name); if (mib2nut[i]->sysOID == NULL) continue; /* Clear variables */ memset(mib2nut_sysOID, 0, sizeof(mib2nut_sysOID)); mib2nut_sysOID_len = MAX_OID_LEN; if (!read_objid(mib2nut[i]->sysOID, mib2nut_sysOID, &mib2nut_sysOID_len)) { upsdebugx(2, "%s: can't build OID %s: %s", __func__, sysOID_buf, snmp_api_errstring(snmp_errno)); /* Try to continue anyway! */ continue; } /* Now compare these */ upsdebugx(1, "%s: comparing %s with %s", __func__, sysOID_buf, mib2nut[i]->sysOID); if (!netsnmp_oid_equals(device_sysOID, device_sysOID_len, mib2nut_sysOID, mib2nut_sysOID_len)) { upsdebugx(2, "%s: sysOID matches MIB '%s'!", __func__, mib2nut[i]->mib_name); /* Counter verify, using {ups,device}.model */ snmp_info = mib2nut[i]->snmp_info; if (snmp_info == NULL) { upsdebugx(0, "%s: WARNING: snmp_info is not initialized " "for mapping table entry #%d \"%s\"", __func__, i, mib2nut[i]->mib_name ); continue; } else if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty " "for mapping table entry #%d \"%s\"", __func__, i, mib2nut[i]->mib_name); } if (match_model_OID() != TRUE) { upsdebugx(2, "%s: testOID provided and doesn't match MIB '%s'!", __func__, mib2nut[i]->mib_name); snmp_info = NULL; continue; } else upsdebugx(2, "%s: testOID provided and matches MIB '%s'!", __func__, mib2nut[i]->mib_name); return mib2nut[i]; } } /* Yell all to call for user report */ upslogx(LOG_ERR, "No matching MIB found for sysOID '%s'!\n" \ "Please report it to NUT developers, with an 'upsc' output for your device.\n" \ "Going back to the classic MIB detection method.", sysOID_buf); } else upsdebugx(2, "Can't get sysOID value"); return NULL; } /* Load the right snmp_info_t structure matching mib parameter */ bool_t load_mib2nut(const char *mib) { int i; mib2nut_info_t *m2n = NULL; /* Below we have many checks for "auto"; avoid redundant string walks: */ bool_t mibIsAuto = (0 == strcmp(mib, "auto")); bool_t mibSeen = FALSE; /* Did we see the MIB name while walking mib2nut[]? */ upsdebugx(1, "SNMP UPS driver: entering %s(%s) to detect " "proper MIB for device [%s] (host %s)", __func__, mib, upsname ? upsname : device_name, device_path /* the "port" from config section is hostname/IP for networked drivers */ ); /* First, try to match against sysOID, if no MIB was provided. * This should speed up init stage * (Note: sysOID points the device main MIB entry point) */ if (mibIsAuto) { upsdebugx(2, "%s: trying the new match_sysoid() method with %s", __func__, mib); /* Retry at most 3 times, to maximise chances */ for (i = 0; i < 3 ; i++) { upsdebugx(3, "%s: trying the new match_sysoid() method: attempt #%d", __func__, (i+1)); if ((m2n = match_sysoid()) != NULL) break; if (m2n == NULL) upsdebugx(3, "%s: failed with new match_sysoid() method", __func__); else upsdebugx(3, "%s: found something with new match_sysoid() method", __func__); } } /* Otherwise, revert to the classic method */ if (m2n == NULL) { for (i = 0; mib2nut[i] != NULL; i++) { /* Is there already a MIB name provided? */ upsdebugx(4, "%s: checking against mapping table entry #%d \"%s\"", __func__, i, mib2nut[i]->mib_name); if (!mibIsAuto && strcmp(mib, mib2nut[i]->mib_name)) { /* "mib" is neither "auto" nor the name in mapping table */ upsdebugx(2, "%s: skip the \"%s\" entry which " "is neither \"auto\" nor a valid name in the mapping table", __func__, mib); continue; } upsdebugx(2, "%s: trying classic sysOID matching method with '%s' mib", __func__, mib2nut[i]->mib_name); /* Classic method: test an OID specific to this MIB */ snmp_info = mib2nut[i]->snmp_info; if (snmp_info == NULL) { upsdebugx(0, "%s: WARNING: snmp_info is not initialized " "for mapping table entry #%d \"%s\"", __func__, i, mib2nut[i]->mib_name ); continue; } else if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty " "for mapping table entry #%d \"%s\"", __func__, i, mib2nut[i]->mib_name); } /* Device might not support this MIB, but we want to * track that the name string is valid for diags below */ if (!mibIsAuto) { mibSeen = TRUE; } if (match_model_OID() != TRUE) { upsdebugx(3, "%s: testOID provided and doesn't match MIB '%s'!", __func__, mib2nut[i]->mib_name); snmp_info = NULL; continue; } else upsdebugx(3, "%s: testOID provided and matches MIB '%s'!", __func__, mib2nut[i]->mib_name); /* MIB found */ m2n = mib2nut[i]; break; } } /* Store the result, if any */ if (m2n != NULL) { snmp_info = m2n->snmp_info; OID_pwr_status = m2n->oid_pwr_status; mibname = m2n->mib_name; mibvers = m2n->mib_version; alarms_info = m2n->alarms_info; upsdebugx(1, "%s: using %s MIB for device [%s] (host %s)", __func__, mibname, upsname ? upsname : device_name, device_path); return TRUE; } /* Did we find something or is it really an unknown mib */ if (!mibIsAuto) { if (mibSeen) { fatalx(EXIT_FAILURE, "Requested 'mibs' value '%s' " "did not match this device [%s] (host %s)", mib, upsname ? upsname : device_name, device_path); } else { /* String not seen during mib2nut[] walk - * and if we had no hits, we walked it all */ fatalx(EXIT_FAILURE, "Unknown 'mibs' value: %s", mib); } } else { fatalx(EXIT_FAILURE, "No supported device detected at [%s] (host %s)", upsname ? upsname : device_name, device_path); } /* Should not get here thanks to fatalx() above, but need to silence a warning */ return FALSE; } /* find the OID value matching that INFO_* value */ long su_find_valinfo(info_lkp_t *oid2info, const char* value) { info_lkp_t *info_lkp; for (info_lkp = oid2info; (info_lkp != NULL) && (strcmp(info_lkp->info_value, "NULL")); info_lkp++) { if (!(strcmp(info_lkp->info_value, value))) { upsdebugx(1, "%s: found %s (value: %s)", __func__, info_lkp->info_value, value); return info_lkp->oid_value; } } upsdebugx(1, "%s: no matching INFO_* value for this OID value (%s)", __func__, value); return -1; } /* String reformating function */ const char *su_find_strval(info_lkp_t *oid2info, void *value) { #if WITH_SNMP_LKP_FUN /* First test if we have a generic lookup function */ if ( (oid2info != NULL) && (oid2info->fun_vp2s != NULL) ) { upsdebugx(2, "%s: using generic lookup function (string reformatting)", __func__); const char * retvalue = oid2info->fun_vp2s(value); upsdebugx(2, "%s: got value '%s'", __func__, retvalue); return retvalue; } upsdebugx(1, "%s: no result value for this OID string value (%s)", __func__, (char*)value); #else NUT_UNUSED_VARIABLE(oid2info); upsdebugx(1, "%s: no mapping function for this OID string value (%s)", __func__, (char*)value); #endif // WITH_SNMP_LKP_FUN return NULL; } /* find the INFO_* value matching that OID numeric (long) value */ const char *su_find_infoval(info_lkp_t *oid2info, void *raw_value) { info_lkp_t *info_lkp; long value = *((long *)raw_value); #if WITH_SNMP_LKP_FUN /* First test if we have a generic lookup function */ if ( (oid2info != NULL) && (oid2info->fun_vp2s != NULL) ) { upsdebugx(2, "%s: using generic lookup function", __func__); const char * retvalue = oid2info->fun_vp2s(raw_value); upsdebugx(2, "%s: got value '%s'", __func__, retvalue); return retvalue; } #endif // WITH_SNMP_LKP_FUN /* Otherwise, use the simple values mapping */ for (info_lkp = oid2info; (info_lkp != NULL) && (info_lkp->info_value != NULL) && (strcmp(info_lkp->info_value, "NULL")); info_lkp++) { if (info_lkp->oid_value == value) { upsdebugx(1, "%s: found %s (value: %ld)", __func__, info_lkp->info_value, value); return info_lkp->info_value; } } upsdebugx(1, "%s: no matching INFO_* value for this OID value (%ld)", __func__, value); return NULL; } /* FIXME: doesn't work with templates! */ static void disable_competition(snmp_info_t *entry) { snmp_info_t *p; if (snmp_info == NULL) { fatalx(EXIT_FAILURE, "%s: snmp_info is not initialized", __func__); } if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty", __func__); } for(p = snmp_info; (p != NULL && p->info_type != NULL) ; p++) { if(p!=entry && !strcmp(p->info_type, entry->info_type)) { upsdebugx(2, "%s: disabling %s %s", __func__, p->info_type, p->OID); p->flags &= ~SU_FLAG_OK; } } } /* set shutdown and/or start delays */ void set_delays(void) { int ondelay, offdelay; char su_scratch_buf[255]; if (getval(SU_VAR_ONDELAY)) ondelay = atoi(getval(SU_VAR_ONDELAY)); else ondelay = -1; if (getval(SU_VAR_OFFDELAY)) offdelay = atoi(getval(SU_VAR_OFFDELAY)); else offdelay = -1; if (ondelay >= 0) { sprintf(su_scratch_buf, "%d", ondelay); su_setvar("ups.delay.start", su_scratch_buf); } if (offdelay >= 0) { sprintf(su_scratch_buf, "%d", offdelay); su_setvar("ups.delay.shutdown", su_scratch_buf); } } /*********************************************************************** * Template handling functions **********************************************************************/ /* Test if the template is a multiple one, i.e. with a formatting string that * contains multiple "%i". * Return TRUE if yes (multiple "%i" found), FALSE otherwise */ static bool_t is_multiple_template(const char *OID_template) { bool_t retCode = FALSE; char *format_char = NULL; if (OID_template) { format_char = strchr(OID_template, '%'); upsdebugx(4, "%s(%s)", __func__, OID_template); } else upsdebugx(4, "%s(NULL)", __func__); if (format_char != NULL) { if (strchr(format_char + 1, '%') != NULL) { retCode = TRUE; } } upsdebugx(4, "%s: has %smultiple template definition", __func__, (retCode == FALSE)?"not ":""); return retCode; } /* Instantiate an snmp_info_t from a template. * Useful for device, outlet, outlet.group and ambient templates. * Note: remember to adapt info_type, OID and optionaly dfl */ static snmp_info_t *instantiate_info(snmp_info_t *info_template, snmp_info_t *new_instance) { upsdebugx(1, "%s(%s)", __func__, info_template ? info_template->info_type : "n/a"); /* sanity check */ if (info_template == NULL) return NULL; if (new_instance == NULL) new_instance = (snmp_info_t *)xmalloc(sizeof(snmp_info_t)); /* TOTHINK: Should there be an "else" to free() * the fields which we (re-)allocate below? */ new_instance->info_type = (char *)xmalloc(SU_INFOSIZE); if (new_instance->info_type) memset((char *)new_instance->info_type, 0, SU_INFOSIZE); if (info_template->OID != NULL) { new_instance->OID = (char *)xmalloc(SU_INFOSIZE); if (new_instance->OID) memset((char *)new_instance->OID, 0, SU_INFOSIZE); } else { new_instance->OID = NULL; } new_instance->info_flags = info_template->info_flags; new_instance->info_len = info_template->info_len; /* FIXME: check if we need to adapt this one... */ new_instance->dfl = info_template->dfl; new_instance->flags = info_template->flags; new_instance->oid2info = info_template->oid2info; upsdebugx(2, "instantiate_info: template instantiated"); return new_instance; } /* Free a dynamically allocated snmp_info_t. * Useful for outlet and outlet.group templates */ static void free_info(snmp_info_t *su_info_p) { /* sanity check */ if (su_info_p == NULL) return; if (su_info_p->info_type != NULL) free ((char *)su_info_p->info_type); if (su_info_p->OID != NULL) free ((char *)su_info_p->OID); free (su_info_p); } /* return the base SNMP index (0 or 1) to start template iteration on * the MIB, based on a test using a template OID */ static int base_snmp_template_index(const snmp_info_t *su_info_p) { if (!su_info_p) return -1; int base_index = -1; char test_OID[SU_INFOSIZE]; snmp_info_flags_t template_type = get_template_type(su_info_p->info_type); if (!su_info_p->OID) return base_index; upsdebugx(3, "%s: OID template = %s", __func__, su_info_p->OID); /* Try to differentiate between template types which may have * different indexes ; and store it to not redo it again */ switch (template_type) { case SU_OUTLET: template_index_base = outlet_template_index_base; break; case SU_OUTLET_GROUP: template_index_base = outletgroup_template_index_base; break; case SU_DAISY: template_index_base = device_template_index_base; break; case SU_AMBIENT_TEMPLATE: template_index_base = ambient_template_index_base; break; default: /* we should never fall here! */ upsdebugx(3, "%s: unknown template type '%" PRI_SU_FLAGS "' for %s", __func__, template_type, su_info_p->info_type); } base_index = template_index_base; if (template_index_base == -1) { /* not initialised yet */ for (base_index = 0 ; base_index < 2 ; base_index++) { #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif /* Test if this template also includes daisychain, in which case * we just use the current device index */ if (is_multiple_template(su_info_p->OID) == TRUE) { if (su_info_p->flags & SU_TYPE_DAISY_1) { snprintf(test_OID, sizeof(test_OID), su_info_p->OID, current_device_number + device_template_offset, base_index); } else { snprintf(test_OID, sizeof(test_OID), su_info_p->OID, base_index, current_device_number + device_template_offset); } } else { snprintf(test_OID, sizeof(test_OID), su_info_p->OID, base_index); } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif if (nut_snmp_get(test_OID) != NULL) { if (su_info_p->flags & SU_FLAG_ZEROINVALID) { long value; if ((nut_snmp_get_int(test_OID, &value)) && (value!=0)) { break; } } else if (su_info_p->flags & SU_FLAG_NAINVALID) { char value[SU_BUFSIZE]; if ((nut_snmp_get_str(test_OID, value, SU_BUFSIZE, NULL)) && (strncmp(value, "N/A", 3))) { break; } } else { break; } } } /* Only store if it's a template for outlets or outlets groups, * not for daisychain (which has different index) */ if (su_info_p->flags & SU_OUTLET) outlet_template_index_base = base_index; else if (su_info_p->flags & SU_OUTLET_GROUP) outletgroup_template_index_base = base_index; else if (su_info_p->flags & SU_AMBIENT_TEMPLATE) ambient_template_index_base = base_index; else device_template_index_base = base_index; } upsdebugx(3, "%s: template_index_base = %i", __func__, base_index); return base_index; } /* Try to determine the number of items (outlets, outlet groups, ...), * using a template definition. Walk through the template until we can't * get anymore values. I.e., if we can iterate up to 8 item, return 8 */ static int guestimate_template_count(snmp_info_t *su_info_p) { int base_index = 0; char test_OID[SU_INFOSIZE]; int base_count; const char *OID_template = su_info_p->OID; upsdebugx(1, "%s(%s)", __func__, OID_template); /* Determine if OID index starts from 0 or 1? */ #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf(test_OID, sizeof(test_OID), OID_template, base_index); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif if (nut_snmp_get(test_OID) == NULL) { base_index++; } else { if (su_info_p->flags & SU_FLAG_ZEROINVALID) { long value; if ((nut_snmp_get_int(test_OID, &value)) && (value==0)) { base_index++; } } } /* Now, actually iterate */ for (base_count = 0 ; ; base_count++) { #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf(test_OID, sizeof(test_OID), OID_template, base_index + base_count); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif if (nut_snmp_get(test_OID) == NULL) break; } upsdebugx(3, "%s: %i", __func__, base_count); return base_count; } /* Process template definition, instantiate and get data or register * command * type: outlet, outlet.group, device */ static bool_t process_template(int mode, const char* type, snmp_info_t *su_info_p) { /* Default to TRUE, and leave to get_and_process_data() to set * to FALSE when actually getting data from devices, to avoid false * negative with server side data */ bool_t status = TRUE; int cur_template_number = 1; int cur_nut_index = 0; int template_count = 0; int base_snmp_index = 0; snmp_info_t cur_info_p; char template_count_var[SU_BUFSIZE * 2]; /* Needed *2 to fit a max size_t in snprintf() below, * even if that should never happen */ char tmp_buf[SU_INFOSIZE]; upsdebugx(1, "%s template definition found (%s)...", type, su_info_p->info_type); if ((strncmp(type, "device", 6)) && (devices_count > 1) && (current_device_number > 0)) { snprintf(template_count_var, sizeof(template_count_var), "device.%i.%s.count", current_device_number, type); } else { snprintf(template_count_var, sizeof(template_count_var), "%s.count", type); } if(dstate_getinfo(template_count_var) == NULL) { /* FIXME: should we disable it? * su_info_p->flags &= ~SU_FLAG_OK; * or rely on guestimation? */ template_count = guestimate_template_count(su_info_p); /* Publish the count estimation */ if (template_count > 0) { dstate_setinfo(template_count_var, "%i", template_count); } } else { template_count = atoi(dstate_getinfo(template_count_var)); } upsdebugx(1, "%i instances found...", template_count); /* Only instantiate templates if needed! */ if (template_count > 0) { /* general init of data using the template */ instantiate_info(su_info_p, &cur_info_p); base_snmp_index = base_snmp_template_index(su_info_p); for (cur_template_number = base_snmp_index ; cur_template_number < (template_count + base_snmp_index) ; cur_template_number++) { upsdebugx(1, "Processing instance %i/%i...", cur_template_number, template_count); /* Special processing for daisychain: * append 'device.x' to the NUT variable name, except for the * whole daisychain ("device.0") */ if (!strncmp(type, "device", 6)) { /* Device(s) 1-N (master + slave(s)) need to append 'device.x' */ if (current_device_number > 0) { char *ptr = NULL; /* Another special processing for daisychain * device collection needs special appending */ if (!strncmp(su_info_p->info_type, "device.", 7)) ptr = (char*)&su_info_p->info_type[7]; else ptr = (char*)su_info_p->info_type; snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, "device.%i.%s", current_device_number, ptr); } else { /* Device 1 ("device.0", whole daisychain) needs no * special processing */ cur_nut_index = cur_template_number; #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, su_info_p->info_type, cur_nut_index); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } } else if (!strncmp(type, "outlet", 6)) /* Outlet and outlet groups templates */ { /* Get the index of the current template instance */ cur_nut_index = cur_template_number; #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif /* Special processing for daisychain */ if (daisychain_enabled == TRUE) { /* Device(s) 1-N (master + slave(s)) need to append 'device.x' */ if ((devices_count > 1) && (current_device_number > 0)) { memset(&tmp_buf[0], 0, SU_INFOSIZE); strcat(&tmp_buf[0], "device.%i."); strcat(&tmp_buf[0], su_info_p->info_type); upsdebugx(4, "FORMATTING STRING = %s", &tmp_buf[0]); snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, &tmp_buf[0], current_device_number, cur_nut_index); } else { /* FIXME: daisychain-whole, what to do? */ snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, su_info_p->info_type, cur_nut_index); } } else { snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, su_info_p->info_type, cur_nut_index); } } else if (!strncmp(type, "ambient", 7)) { /* FIXME: can be grouped with outlet* above */ /* Get the index of the current template instance */ cur_nut_index = cur_template_number; /* Special processing for daisychain */ if (daisychain_enabled == TRUE) { /* Only publish on the daisychain host */ if ( (su_info_p->flags & SU_TYPE_DAISY_MASTER_ONLY) && (current_device_number != 1) ) { upsdebugx(2, "discarding variable due to daisychain master flag"); continue; } /* Device(s) 1-N (master + slave(s)) need to append 'device.x' */ if ((devices_count > 1) && (current_device_number > 0)) { memset(&tmp_buf[0], 0, SU_INFOSIZE); strcat(&tmp_buf[0], "device.%i."); strcat(&tmp_buf[0], su_info_p->info_type); upsdebugx(4, "FORMATTING STRING = %s", &tmp_buf[0]); snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, &tmp_buf[0], current_device_number, cur_nut_index); } else { /* FIXME: daisychain-whole, what to do? */ snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, su_info_p->info_type, cur_nut_index); } } else { snprintf((char*)cur_info_p.info_type, SU_INFOSIZE, su_info_p->info_type, cur_nut_index); } } else upsdebugx(4, "Error: unknown template type '%s", type); /* check if default value is also a template */ if ((cur_info_p.dfl != NULL) && (strstr(su_info_p->dfl, "%i") != NULL)) { cur_info_p.dfl = (char *)xmalloc(SU_INFOSIZE); snprintf((char *)cur_info_p.dfl, SU_INFOSIZE, su_info_p->dfl, cur_nut_index); } if (cur_info_p.OID != NULL) { /* Special processing for daisychain */ if (!strncmp(type, "device", 6)) { if (current_device_number > 0) { snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, current_device_number + device_template_offset); } /*else * FIXME: daisychain-whole, what to do? */ } else { /* Special processing for daisychain: * these outlet | outlet groups also include formatting info, * so we have to check if the daisychain is enabled, and if * the formatting info for it are in 1rst or 2nd position */ if (daisychain_enabled == TRUE) { if (su_info_p->flags & SU_TYPE_DAISY_1) { snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, current_device_number + device_template_offset, cur_template_number); } else if (su_info_p->flags & SU_TYPE_DAISY_2) { snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, cur_template_number + device_template_offset, current_device_number - device_template_offset); } else { /* Note: no device daisychain templating (SU_TYPE_DAISY_MASTER_ONLY)! */ snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, cur_template_number); } } else { snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, cur_template_number); } } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif /* add instant commands to the info database. */ if (SU_TYPE(su_info_p) == SU_TYPE_CMD) { upsdebugx(1, "Adding template command %s", cur_info_p.info_type); /* FIXME: only add if "su_ups_get(cur_info_p) == TRUE" */ if (mode == SU_WALKMODE_INIT) dstate_addcmd(cur_info_p.info_type); } else /* get and process this data */ status = get_and_process_data(mode, &cur_info_p); } else { /* server side (ABSENT) data */ su_setinfo(&cur_info_p, NULL); } /* set back the flag */ su_info_p->flags = cur_info_p.flags; } free((char*)cur_info_p.info_type); if (cur_info_p.OID != NULL) free((char*)cur_info_p.OID); if ((cur_info_p.dfl != NULL) && (strstr(su_info_p->dfl, "%i") != NULL)) free((char*)cur_info_p.dfl); } else { upsdebugx(1, "No %s present, discarding template definition...", type); } return status; } /* Return the type of template, according to a variable name. * Return: SU_OUTLET_GROUP, SU_OUTLET or 0 if not a template */ snmp_info_flags_t get_template_type(const char* varname) { if (!strncmp(varname, "outlet.group", 12)) { upsdebugx(4, "outlet.group template"); return SU_OUTLET_GROUP; } else if (!strncmp(varname, "outlet", 6)) { upsdebugx(4, "outlet template"); return SU_OUTLET; } else if (!strncmp(varname, "device", 6)) { upsdebugx(4, "device template"); return SU_DAISY; } else if (!strncmp(varname, "ambient", 7)) { upsdebugx(4, "ambient template"); return SU_AMBIENT_TEMPLATE; } else { upsdebugx(2, "Unknown template type: %s", varname); return 0; } } /* Extract the id number of an instantiated template. * Example: return '1' for type = 'outlet.1.desc', -1 if unknown */ int extract_template_number(snmp_info_flags_t template_type, const char* varname) { const char* item_number_ptr = NULL; int item_number = -1; if (template_type & SU_OUTLET_GROUP) item_number_ptr = &varname[12]; else if (template_type & SU_OUTLET) item_number_ptr = &varname[6]; else if (template_type & SU_DAISY) item_number_ptr = &varname[6]; else if (template_type & SU_AMBIENT_TEMPLATE) item_number_ptr = &varname[7]; else return -1; item_number = atoi(++item_number_ptr); upsdebugx(3, "%s: item %i", __func__, item_number); return item_number; } /* Extract the id number of a template from a variable name. * Example: return '1' for type = 'outlet.1.desc' */ static int extract_template_number_from_snmp_info_t(const char* varname) { return extract_template_number(get_template_type(varname), varname); } /* end of template functions */ /* process a single data from a walk */ bool_t get_and_process_data(int mode, snmp_info_t *su_info_p) { bool_t status = FALSE; upsdebugx(1, "%s: %s (%s)", __func__, su_info_p->info_type, su_info_p->OID); /* ok, update this element. */ status = su_ups_get(su_info_p); upsdebugx(4, "%s: su_ups_get returned %d", __func__, status); /* set stale flag if data is stale, clear if not. */ if (status == TRUE) { if (su_info_p->flags & SU_FLAG_STALE) { upslogx(LOG_INFO, "[%s] %s: data resumed for %s", upsname?upsname:device_name, __func__, su_info_p->info_type); su_info_p->flags &= ~SU_FLAG_STALE; } if(su_info_p->flags & SU_FLAG_UNIQUE) { /* We should be the only provider of this */ upsdebugx(4, "%s: unique flag", __func__); disable_competition(su_info_p); su_info_p->flags &= ~SU_FLAG_UNIQUE; } dstate_dataok(); } else { if (mode == SU_WALKMODE_INIT) { /* handle unsupported vars */ su_info_p->flags &= ~SU_FLAG_OK; } else { if (!(su_info_p->flags & SU_FLAG_STALE)) { upslogx(LOG_INFO, "[%s] snmp_ups_walk: data stale for %s", upsname?upsname:device_name, su_info_p->info_type); su_info_p->flags |= SU_FLAG_STALE; } dstate_datastale(); } } return status; } /*********************************************************************** * Daisychain handling functions **********************************************************************/ /*! * Daisychained devices support init: * Determine the number of device(s) and if daisychain support has to be enabled * Set the values of devices_count (internal) and "device.count" (public) * Return TRUE if daisychain support is enabled, FALSE otherwise */ bool_t daisychain_init() { snmp_info_t *su_info_p = NULL; upsdebugx(1, "Checking if daisychain support has to be enabled"); su_info_p = su_find_info("device.count"); if (su_info_p != NULL) { upsdebugx(1, "Found device.count entry..."); /* Enable daisychain if there is a device.count entry. * This means that will have templates for entries */ daisychain_enabled = TRUE; /* Try to get the OID value, if it's not a template */ upsdebugx(3, "OID for device.count is %s", su_info_p->OID ? su_info_p->OID : ""); if ((su_info_p->OID != NULL) && (strstr(su_info_p->OID, "%i") == NULL)) { #if WITH_SNMP_LKP_FUN devices_count = -1; /* First test if we have a generic lookup function * FIXME: Check if the field type is a string? */ /* TODO: backport the 2x2 mapping function support * and this would be "fun_s2l" in resulting codebase */ if ( (su_info_p->oid2info != NULL) && (su_info_p->oid2info->nuf_s2l != NULL) ) { char buf[1024]; upsdebugx(2, "%s: using generic string-to-long lookup function", __func__); if (TRUE == nut_snmp_get_str(su_info_p->OID, buf, sizeof(buf), su_info_p->oid2info)) { devices_count = su_info_p->oid2info->nuf_s2l(buf); upsdebugx(2, "%s: got value '%ld'", __func__, devices_count); } } if (devices_count == -1) { #endif /* WITH_SNMP_LKP_FUN */ if (nut_snmp_get_int(su_info_p->OID, &devices_count) == TRUE) upsdebugx(1, "There are %ld device(s) present", devices_count); else { upsdebugx(1, "Error: can't get the number of device(s) present!"); upsdebugx(1, "Falling back to 1 device!"); devices_count = 1; } #if WITH_SNMP_LKP_FUN } #endif /* WITH_SNMP_LKP_FUN */ } /* Otherwise (template), use the guesstimation function to get * the number of devices present */ else { devices_count = guestimate_template_count(su_info_p); upsdebugx(1, "Guesstimation: there are %ld device(s) present", devices_count); } /* Sanity check before data publication */ if (devices_count < 1) { devices_count = 1; daisychain_enabled = FALSE; upsdebugx(1, "Devices count is less than 1!"); upsdebugx(1, "Falling back to 1 device and disabling daisychain support!"); } else { /* Publish the device(s) count - even if just one * device was recognized at this moment */ dstate_setinfo("device.count", "%ld", devices_count); /* Also publish the default value for mfr and a forged model * for device.0 (whole daisychain) */ su_info_p = su_find_info("device.mfr"); if (su_info_p != NULL) { su_info_p = su_find_info("ups.mfr"); if (su_info_p != NULL) { su_setinfo(su_info_p, NULL); } } /* Forge model using device.type and number */ su_info_p = su_find_info("device.type"); if ((su_info_p != NULL) && (su_info_p->dfl != NULL)) { dstate_setinfo("device.model", "daisychain %s (1+%ld)", su_info_p->dfl, devices_count - 1); dstate_setinfo("device.type", "%s", su_info_p->dfl); } else { dstate_setinfo("device.model", "daisychain (1+%ld)", devices_count - 1); } } } else { daisychain_enabled = FALSE; upsdebugx(1, "No device.count entry found, daisychain support not needed"); } /* Finally, compute and store the base OID index and NUT offset */ su_info_p = su_find_info("device.model"); if (su_info_p != NULL) { device_template_index_base = base_snmp_template_index(su_info_p); upsdebugx(1, "%s: device_template_index_base = %i", __func__, device_template_index_base); device_template_offset = device_template_index_base - 1; upsdebugx(1, "%s: device_template_offset = %i", __func__, device_template_offset); } else { upsdebugx(1, "%s: No device.model entry found.", __func__); } upsdebugx(1, "%s: daisychain support is %s", __func__, (daisychain_enabled==TRUE)?"enabled":"disabled"); return daisychain_enabled; } /*********************************************************************** * SNMP handling functions **********************************************************************/ /* Process a data with regard to SU_OUTPHASES, SU_INPHASES and SU_BYPPHASES. * 3phases related data are disabled if the unit is 1ph, and conversely. * If the related phases data (input, output, bypass) is not yet valued, * retrieve it first. * * type: input, output, bypass * su_info_p: variable to process flags on * Return 0 if OK, 1 if the caller needs to "continue" the walk loop (i.e. * skip the present data) */ static int process_phase_data(const char* type, long *nb_phases, snmp_info_t *su_info_p) { snmp_info_t *tmp_info_p; char tmpOID[SU_INFOSIZE]; char tmpInfo[SU_INFOSIZE]; long tmpValue; snmp_info_flags_t phases_flag = 0, single_phase_flag = 0, three_phase_flag = 0; /* Phase specific data */ if (!strncmp(type, "input", 5)) { phases_flag = SU_INPHASES; single_phase_flag = SU_INPUT_1; three_phase_flag = SU_INPUT_3; } else if (!strncmp(type, "output", 6)) { phases_flag = SU_OUTPHASES; single_phase_flag = SU_OUTPUT_1; three_phase_flag = SU_OUTPUT_3; } else if (!strncmp(type, "input.bypass", 12)) { phases_flag = SU_BYPPHASES; single_phase_flag = SU_BYPASS_1; three_phase_flag = SU_BYPASS_3; } else { upsdebugx(2, "%s: unknown type '%s'", __func__, type); return 1; } /* Init the phase(s) info for this device, if not already done */ if (*nb_phases == -1) { upsdebugx(2, "%s phases information not initialized for device %i", type, current_device_number); memset(tmpInfo, 0, SU_INFOSIZE); /* daisychain specifics... */ if ( (daisychain_enabled == TRUE) && (current_device_number > 0) ) { /* Device(s) 2-N (slave(s)) need to append 'device.x' */ snprintf(tmpInfo, SU_INFOSIZE, "device.%i.%s.phases", current_device_number, type); } else { snprintf(tmpInfo, SU_INFOSIZE, "%s.phases", type); } if (dstate_getinfo(tmpInfo) == NULL) { /* {input,output,bypass}.phases is not yet published, * try to get the template for it */ snprintf(tmpInfo, SU_INFOSIZE, "%s.phases", type); tmp_info_p = su_find_info(tmpInfo); if (tmp_info_p != NULL) { memset(tmpOID, 0, SU_INFOSIZE); /* Daisychain specific: we may have a template (including * formatting string) that needs to be adapted! */ if (strchr(tmp_info_p->OID, '%') != NULL) { upsdebugx(2, "Found template, need to be adapted"); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char*)tmpOID, SU_INFOSIZE, tmp_info_p->OID, current_device_number + device_template_offset); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } else { /* Otherwise, just point at what we found */ upsdebugx(2, "Found entry, not a template %s", tmp_info_p->OID); snprintf((char*)tmpOID, SU_INFOSIZE, "%s", tmp_info_p->OID); } /* Actually get the data */ if (nut_snmp_get_int(tmpOID, &tmpValue) == TRUE) { *nb_phases = tmpValue; } else { upsdebugx(2, "Can't get %s value. Defaulting to 1 %s.phase", tmpInfo, type); *nb_phases = 1; /* FIXME: return something or process using default?! */ } } else { upsdebugx(2, "No %s entry. Defaulting to 1 %s.phase", tmpInfo, type); *nb_phases = 1; /* FIXME: return something or process using default?! */ } } else { *nb_phases = atoi(dstate_getinfo(tmpInfo)); } /* Publish the number of phase(s) */ dstate_setinfo(tmpInfo, "%ld", *nb_phases); upsdebugx(2, "device %i has %ld %s.phases", current_device_number, *nb_phases, type); } /* FIXME: what to do here? else if (*nb_phases == 0) { return 1; } */ /* Actual processing of phases related data */ /* FIXME: don't clear SU_INPHASES in daisychain mode!!! ??? */ if (su_info_p->flags & single_phase_flag) { if (*nb_phases == 1) { upsdebugx(1, "%s_phases is 1", type); su_info_p->flags &= ~phases_flag; } else { upsdebugx(1, "%s_phases is not 1", type); su_info_p->flags &= ~SU_FLAG_OK; return 1; } } else if (su_info_p->flags & three_phase_flag) { if (*nb_phases == 3) { upsdebugx(1, "%s_phases is 3", type); su_info_p->flags &= ~phases_flag; } else { upsdebugx(1, "%s_phases is not 3", type); su_info_p->flags &= ~SU_FLAG_OK; return 1; } } else { upsdebugx(1, "%s_phases is %ld", type, *nb_phases); } return 0; /* FIXME: remap EXIT_SUCCESS to RETURN_SUCCESS */ } /* walk ups variables and set elements of the info array. */ bool_t snmp_ups_walk(int mode) { long *walked_input_phases, *walked_output_phases, *walked_bypass_phases; static unsigned long iterations = 0; snmp_info_t *su_info_p; bool_t status = FALSE; if (mode == SU_WALKMODE_UPDATE) { semistatic_countdown--; if (semistatic_countdown < 0) semistatic_countdown = semistaticfreq; } /* Loop through all device(s) */ /* Note: considering "unitary" and "daisy-chained" devices, we have * several variables (and their values) that can come into play: * devices_count == 1 (default) AND daisychain_enabled == FALSE => unitary * devices_count > 1 (AND/OR?) daisychain_enabled == TRUE => a daisy-chain * The current_device_number == 0 in context of daisy-chain means the * "whole device" with composite or summary values that refer to the * chain as a virtual power device (e.g. might be a sum of outlet counts). * If daisychain_enabled == TRUE and current_device_number == 1 then we * are looking at the "master" device (one that we have direct/networked * connectivity to; the current_device_number > 1 is a slave (chained by * some proprietary link not visible from the outside) and represented * through the master - the slaves are not addressable directly. If the * master dies/reboots, connection to the whole chain is interrupted. * The dstate string names for daisychained sub-devices have the prefix * "device." and number embedded (e.g. "device.3.input.phases") except * for the whole (#0) virtual device, so it *seems* similar to unitary. */ for (current_device_number = (daisychain_enabled == FALSE && devices_count == 1 ? 1 : 0) ; current_device_number <= devices_count; current_device_number++) { upsdebugx(1, "%s: walking device %d", __func__, current_device_number); /* reinit the alarm buffer, before */ if (devices_count > 1) device_alarm_init(); /* better safe than sorry, check sanity on every loop cycle */ if (snmp_info == NULL) { fatalx(EXIT_FAILURE, "%s: snmp_info is not initialized", __func__); } if (snmp_info[0].info_type == NULL) { upsdebugx(1, "%s: WARNING: snmp_info is empty", __func__); } /* Loop through all mapping entries for the current_device_number */ for (su_info_p = &snmp_info[0]; (su_info_p != NULL && su_info_p->info_type != NULL) ; su_info_p++) { /* NOTE: Effectively below we do this: * switch(current_device_number) { * case 0: devtype = "daisychain whole" * case 1: devtype = "daisychain master" * default: devtype = "daisychain slave" * } * with a consideration for directly-addressable * slave devices (can be seen in chain via master, * but also queryable alone with an IP connection) * NOTE: until proven otherwise, "single" may mean * both (either) a daisy-chain enabled master device * without further connected "slave" devices, and * a directly addressable (IP-connected) "slave". * Possibly also an ePDU etc. that serves a MIB * which resolves "device.count" with the selected * subdriver. */ if (daisychain_enabled == TRUE) { upsdebugx(1, "%s: processing daisy-chain device %i (%s)", __func__, current_device_number, (current_device_number == 1) ? (devices_count > 1 ? "master" : "single") : (current_device_number > 1 ? "slave" : "whole") ); } else { upsdebugx(1, "%s: processing unitary device (%i)", __func__, current_device_number); } /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) { upsdebugx(1, "%s: aborting because exit_flag was set", __func__); return TRUE; } /* Skip daisychain data count */ if (mode == SU_WALKMODE_INIT && (!strncmp(su_info_p->info_type, "device.count", 12))) { su_info_p->flags &= ~SU_FLAG_OK; continue; } /* FIXME: daisychain-whole, what to do? */ /* Note that when addressing the FIXME above, * if (current_device_number == 0 && daisychain_enabled == FALSE) * then we'd skip it still (unitary device is at current_device_number == 1)... */ /* skip the whole-daisychain for now */ if (current_device_number == 0 && daisychain_enabled == TRUE) { upsdebugx(1, "Skipping daisychain device.0 for now..."); continue; } /* skip instcmd, not linked to outlets */ if ((SU_TYPE(su_info_p) == SU_TYPE_CMD) && !(su_info_p->flags & SU_OUTLET) && !(su_info_p->flags & SU_OUTLET_GROUP)) { upsdebugx(1, "SU_CMD_MASK => %s", su_info_p->OID); continue; } /* skip elements we shouldn't show in update mode */ if ((mode == SU_WALKMODE_UPDATE) && !(su_info_p->flags & SU_FLAG_OK)) continue; /* skip semi-static elements in update mode: only parse when countdown reaches 0 */ if ((mode == SU_WALKMODE_UPDATE) && (su_info_p->flags & SU_FLAG_SEMI_STATIC)) { if (semistatic_countdown != 0) continue; upsdebugx(1, "Refreshing semi-static entry %s", su_info_p->OID); } /* skip static elements in update mode */ if ((mode == SU_WALKMODE_UPDATE) && (su_info_p->flags & SU_FLAG_STATIC)) continue; /* Set default value if we cannot fetch it */ /* and set static flag on this element. * Not applicable to outlets (need SU_FLAG_STATIC tagging) */ if ((su_info_p->flags & SU_FLAG_ABSENT) && !(su_info_p->flags & SU_OUTLET) && !(su_info_p->flags & SU_OUTLET_GROUP) && !(su_info_p->flags & SU_AMBIENT_TEMPLATE)) { if (mode == SU_WALKMODE_INIT) { if (su_info_p->dfl) { if ((daisychain_enabled == TRUE) && (devices_count > 1)) { if (current_device_number == 0) { su_setinfo(su_info_p, NULL); /* FIXME: daisychain-whole, what to do? */ } else { status = process_template(mode, "device", su_info_p); } } else { /* Set default value if we cannot fetch it from ups. */ su_setinfo(su_info_p, NULL); } } su_info_p->flags |= SU_FLAG_STATIC; } continue; } /* check stale elements only on each PN_STALE_RETRY iteration. */ /* if ((su_info_p->flags & SU_FLAG_STALE) && (iterations % SU_STALE_RETRY) != 0) continue; */ /* Filter 1-phase Vs 3-phase according to {input,output,bypass}.phase. * Non matching items are disabled, and flags are cleared at init * time */ /* Process input phases information */ walked_input_phases = &daisychain_info[current_device_number]->input_phases; if (su_info_p->flags & SU_INPHASES) { upsdebugx(1, "Check input_phases (%ld)", *walked_input_phases); if (process_phase_data("input", walked_input_phases, su_info_p) == 1) continue; } /* Process output phases information */ walked_output_phases = &daisychain_info[current_device_number]->output_phases; if (su_info_p->flags & SU_OUTPHASES) { upsdebugx(1, "Check output_phases (%ld)", *walked_output_phases); if (process_phase_data("output", walked_output_phases, su_info_p) == 1) continue; } /* Process bypass phases information */ walked_bypass_phases = &daisychain_info[current_device_number]->bypass_phases; if (su_info_p->flags & SU_BYPPHASES) { upsdebugx(1, "Check bypass_phases (%ld)", *walked_bypass_phases); if (process_phase_data("input.bypass", walked_bypass_phases, su_info_p) == 1) continue; } /* process template (outlet, outlet group, inc. daisychain) definition */ if (su_info_p->flags & SU_OUTLET) { /* Skip commands after init */ if ((SU_TYPE(su_info_p) == SU_TYPE_CMD) && (mode == SU_WALKMODE_UPDATE)) continue; else status = process_template(mode, "outlet", su_info_p); } else if (su_info_p->flags & SU_OUTLET_GROUP) { /* Skip commands after init */ if ((SU_TYPE(su_info_p) == SU_TYPE_CMD) && (mode == SU_WALKMODE_UPDATE)) continue; else status = process_template(mode, "outlet.group", su_info_p); } else if (su_info_p->flags & SU_AMBIENT_TEMPLATE) { /* Skip commands after init */ if ((SU_TYPE(su_info_p) == SU_TYPE_CMD) && (mode == SU_WALKMODE_UPDATE)) continue; else status = process_template(mode, "ambient", su_info_p); } else { /* if (daisychain_enabled == TRUE) { status = process_template(mode, "device", su_info_p); } else { */ /* get and process this data, including daisychain adaptation */ status = get_and_process_data(mode, su_info_p); /* } */ } } /* for (su_info_p... */ if (devices_count > 1) { /* commit the device alarm buffer */ device_alarm_commit(current_device_number); /* reinit the alarm buffer, after, not to pollute "device.0" */ device_alarm_init(); } } iterations++; return status; } bool_t su_ups_get(snmp_info_t *su_info_p) { static char buf[SU_INFOSIZE]; bool_t status; long value; double dvalue; const char *strValue = NULL; struct snmp_pdu ** pdu_array; struct snmp_pdu * current_pdu; alarms_info_t * alarms; int index = 0; char *format_char = NULL; int saved_current_device_number = -1; snmp_info_t *tmp_info_p = NULL; upsdebugx(2, "%s: %s %s", __func__, su_info_p->info_type, su_info_p->OID); /* Check if this is a daisychain template */ if (su_info_p->OID != NULL && (format_char = strchr(su_info_p->OID, '%')) != NULL ) { upsdebugx(3, "%s: calling instantiate_info() for " "daisy-chain template", __func__); tmp_info_p = instantiate_info(su_info_p, tmp_info_p); if (tmp_info_p != NULL) { upsdebugx(3, "%s: instantiate_info() returned " "non-null OID: %s", __func__, tmp_info_p->OID); /* adapt the OID */ if (su_info_p->OID != NULL) { #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char *)tmp_info_p->OID, SU_INFOSIZE, su_info_p->OID, current_device_number + device_template_offset); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif upsdebugx(3, "%s: OID %s adapted into %s", __func__, su_info_p->OID, tmp_info_p->OID); } else { free_info(tmp_info_p); return FALSE; } /* adapt info_type */ if (su_info_p->info_type != NULL) { snprintf((char *)tmp_info_p->info_type, SU_INFOSIZE, "%s", su_info_p->info_type); } else { free_info(tmp_info_p); return FALSE; } su_info_p = tmp_info_p; } else { upsdebugx(2, "%s: can't instantiate template", __func__); return FALSE; } } else { /* Non-templated OID, still may be aimed at a * daisy-chained device (master of the chain * makes sense for IETF device.contact etc.). * BUT: It could be a direct request for earlier * resolved OID. So check for "device.N." too. */ if (daisychain_enabled == TRUE && devices_count > 1 && current_device_number > 0 ) { /* So we had a literal OID string, originally * Check for "device.N." in the string: */ char * varname = su_info_p->info_type; if (!strncmp(varname, "device.", 7) && (varname[7] >= '0' && varname[7] <= '9') ) { upsdebugx(2, "%s: keeping original " "current device == %d for " "non-templated daisy value", __func__, current_device_number); } else { upsdebugx(2, "%s: would fake " "current device == 1 for " "non-templated daisy value " "instead of %d", __func__, current_device_number); saved_current_device_number = current_device_number; } /* At this point we applied no hacks yet, * just stashed a non-negative value into * saved_current_device_number */ } } if (!strcasecmp(su_info_p->info_type, "ups.status")) { /* FIXME: daisychain status support! */ upsdebugx(2, "%s: requesting nut_snmp_get_int() for " "ups.status, with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_int(su_info_p->OID, &value); if (status == TRUE) { su_status_set(su_info_p, value); upsdebugx(2, "=> value: %ld", value); } else upsdebugx(2, "=> Failed"); free_info(tmp_info_p); return status; } /* Handle 'ups.alarm', 'outlet.n.alarm' and 3phase 'Lx.alarm', * nothing else! */ if (!strcmp(strrchr(su_info_p->info_type, '.'), ".alarm")) { upsdebugx(2, "Processing alarm: %s", su_info_p->info_type); /* FIXME: daisychain alarms support! */ upsdebugx(2, "%s: requesting nut_snmp_get_int() for " "some alarm, with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_int(su_info_p->OID, &value); if (status == TRUE) { su_alarm_set(su_info_p, value); upsdebugx(2, "=> value: %ld", value); } else upsdebugx(2, "=> Failed"); free_info(tmp_info_p); return status; } /* Walk a subtree (array) of alarms, composed of OID references. * The object referenced should not be accessible, but rather when * present, this means that the alarm condition is TRUE. * Only present in powerware-mib.c for now */ if (!strcasecmp(su_info_p->info_type, "ups.alarms")) { upsdebugx(2, "%s: requesting nut_snmp_get_int() for " "ups.alarms, with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_int(su_info_p->OID, &value); if (status == TRUE) { upsdebugx(2, "=> %ld alarms present", value); if( value > 0 ) { pdu_array = nut_snmp_walk(su_info_p->OID, INT_MAX); if(pdu_array == NULL) { upsdebugx(2, "=> Walk failed"); return FALSE; } current_pdu = pdu_array[index]; while(current_pdu) { /* Retrieve the OID name, for comparison */ if (decode_oid(current_pdu, buf, sizeof(buf)) == TRUE) { alarms = alarms_info; while( alarms->OID ) { if(!strcmp(buf, alarms->OID)) { upsdebugx(3, "Alarm OID found => %s", alarms->OID); /* Check for ups.status value */ if (alarms->status_value) { upsdebugx(3, "Alarm value (status) found => %s", alarms->status_value); status_set(alarms->status_value); } /* Check for ups.alarm value */ if (alarms->alarm_value) { upsdebugx(3, "Alarm value (alarm) found => %s", alarms->alarm_value); alarm_set(alarms->alarm_value); } break; } alarms++; } } index++; current_pdu = pdu_array[index]; } nut_snmp_free(pdu_array); } } else { upsdebugx(2, "=> Failed"); } free_info(tmp_info_p); return status; } /* another special case */ if (!strcasecmp(su_info_p->info_type, "ambient.temperature")) { float temp=0; upsdebugx(2, "%s: requesting nut_snmp_get_int() for " "ambient.temperature, with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_int(su_info_p->OID, &value); if(status != TRUE) { free_info(tmp_info_p); return status; } /* only do this if using the IEM sensor */ if (!strcmp(su_info_p->OID, APCC_OID_IEM_TEMP)) { int su; long units; su = nut_snmp_get_int(APCC_OID_IEM_TEMP_UNIT, &units); /* no response, or units == F */ if ((su == FALSE) || (units == APCC_IEM_FAHRENHEIT)) temp = (value - 32) / 1.8; else temp = value; } else { temp = value * su_info_p->info_len; } snprintf(buf, sizeof(buf), "%.1f", temp); su_setinfo(su_info_p, buf); free_info(tmp_info_p); return TRUE; } /* special treatment for element without oid but with default value */ if (su_info_p->OID == NULL && su_info_p->dfl != NULL) { status = TRUE; /* FIXME: strlcpy() would fit here safer; not used in NUT yet */ strncpy(buf, su_info_p->dfl, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; } else if (su_info_p->info_flags & ST_FLAG_STRING) { upsdebugx(2, "%s: requesting nut_snmp_get_str(), " "with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_str(su_info_p->OID, buf, sizeof(buf), su_info_p->oid2info); if (status == TRUE) { if (quirk_symmetra_threephase) { if (!strcasecmp(su_info_p->info_type, "input.transfer.low") || !strcasecmp(su_info_p->info_type, "input.transfer.high")) { /* Convert from three phase line-to-line voltage to line-to-neutral voltage */ double tmp_dvalue = atof(buf); tmp_dvalue = tmp_dvalue * 0.707; snprintf(buf, sizeof(buf), "%.2f", tmp_dvalue); } } /* Check if there is a string reformating function */ const char *fmt_buf = NULL; if ((fmt_buf = su_find_strval(su_info_p->oid2info, buf)) != NULL) { snprintf(buf, sizeof(buf), "%s", fmt_buf); } } } else { upsdebugx(2, "%s: requesting nut_snmp_get_int(), " "with%s daisy template originally", __func__, (format_char!=NULL ? "" : "out")); status = nut_snmp_get_int(su_info_p->OID, &value); if (status == TRUE) { if ((su_info_p->flags&SU_FLAG_NEGINVALID && value<0) || (su_info_p->flags&SU_FLAG_ZEROINVALID && value==0)) { su_info_p->flags &= ~SU_FLAG_OK; if(su_info_p->flags&SU_FLAG_UNIQUE) { disable_competition(su_info_p); su_info_p->flags &= ~SU_FLAG_UNIQUE; } free_info(tmp_info_p); return FALSE; } /* Check if there is a value to be looked up */ if ((strValue = su_find_infoval(su_info_p->oid2info, &value)) != NULL) snprintf(buf, sizeof(buf), "%s", strValue); else { /* Check if there is a need to publish decimal too, * i.e. if switching to integer does not cause a * loss of precision. * FIXME: Use remainder? is (dvalue%1.0)>0 cleaner? */ dvalue = value * su_info_p->info_len; if (f_equal((int)dvalue, dvalue)) snprintf(buf, sizeof(buf), "%i", (int)dvalue); else snprintf(buf, sizeof(buf), "%.2f", (float)dvalue); } } } if (status == TRUE) { if (saved_current_device_number >= 0) { current_device_number = 1; } su_setinfo(su_info_p, buf); if (saved_current_device_number >= 0) { current_device_number = saved_current_device_number; } upsdebugx(2, "=> value: %s", buf); } else { upsdebugx(2, "=> Failed"); } free_info(tmp_info_p); return status; } /* Common function for setting OIDs, from a NUT variable name, * used by su_setvar() and su_instcmd() * Params: * @mode: SU_MODE_INSTCMD for instant commands, SU_MODE_SETVAR for settings * @varname: name of variable or command to set the OID from * @val: value for settings, NULL for commands * Returns * STAT_SET_HANDLED if OK, * STAT_SET_INVALID or STAT_SET_UNKNOWN if the command / setting is not supported * STAT_SET_FAILED otherwise */ static int su_setOID(int mode, const char *varname, const char *val) { snmp_info_t *su_info_p = NULL; bool_t status; int retval = STAT_SET_FAILED; int cmd_offset = 0; long value = -1; /* normal (default), outlet, or outlet group variable */ snmp_info_flags_t vartype = 0; int daisychain_device_number = -1; /* variable without the potential "device.X" prefix, to find the template */ char *tmp_varname = NULL; char setOID[SU_INFOSIZE]; /* Used for potentially appending "device.X." to {outlet,outlet.group}.count */ char template_count_var[SU_BUFSIZE]; upsdebugx(2, "entering %s(%s, %s, %s)", __func__, (mode==SU_MODE_INSTCMD)?"instcmd":"setvar", varname, val); memset(setOID, 0, SU_INFOSIZE); memset(template_count_var, 0, SU_BUFSIZE); /* Check if it's a daisychain setting (device.x.varname), * or a non-daisy setting/value/cmd for the "master" unit * (as un-numbered device.varname), or something else? */ if (!strncmp(varname, "device", 6)) { if (varname[7] >= '0' && varname[7] <= '9') { /* Extract the (single-digit) device number * TODO: use strtol() or similar to support multi-digit * chains and offset tmp_varname inside varname properly */ daisychain_device_number = atoi(&varname[7]); /* Point at the command, without the "device.x" prefix */ tmp_varname = strdup(&varname[9]); snprintf(template_count_var, 10, "%s", varname); upsdebugx(2, "%s: got a daisychain %s (%s) for device %i", __func__, (mode==SU_MODE_INSTCMD)?"command":"setting", tmp_varname, daisychain_device_number); if (daisychain_device_number > devices_count) upsdebugx(2, "%s: item is out of bound (%i / %ld)", __func__, daisychain_device_number, devices_count); } else { /* Note: below we check if "OID" contains ".%i" template text * like we do in su_setinfo(); eventually we might get vendor * MIBs that do expose device.contact/location/description * for each link in the chain... */ if (daisychain_enabled == TRUE) { /* Is the original "device.varname" backed by a templated * OID string, so we can commonly set e.g. device.contact * for everything in the chain? */ su_info_p = su_find_info(varname); if (su_info_p && (su_info_p->OID == NULL || strstr(su_info_p->OID, ".%i") != NULL) ) { /* is templated or defaulted */ daisychain_device_number = 0; } else { daisychain_device_number = 1; } upsdebugx(2, "%s: got an un-numbered daisychain %s (%s), " "directing it to %s", __func__, (mode==SU_MODE_INSTCMD)?"command":"setting", varname, (daisychain_device_number==1)?"'master' device":"all devices" ); } else { /* No daisy, no poppy */ daisychain_device_number = 0; } tmp_varname = strdup(varname); } } else { daisychain_device_number = 0; tmp_varname = strdup(varname); } /* skip the whole-daisychain for now: * will send the settings to all devices in the daisychain */ if ((daisychain_enabled == TRUE) && (devices_count > 1) && (daisychain_device_number == 0)) { upsdebugx(2, "daisychain %s for device.0 are not yet supported!", (mode==SU_MODE_INSTCMD)?"command":"setting"); free(tmp_varname); return STAT_SET_INVALID; } /* Check if it is outlet / outlet.group, or standard variable */ if (strncmp(tmp_varname, "outlet", 6)) { su_info_p = su_find_info(tmp_varname); /* what if e.g. "device.x.contact" is not found as a "contact"? */ if (!su_info_p && strcmp(tmp_varname, varname)) { upsdebugx(2, "%s: did not find info for daisychained entry %s, " "retrying with original varname %s", __func__, tmp_varname, varname); su_info_p = su_find_info(varname); /* Still nothing? Try to revert from "device.1." * as a daisychain master? */ if (!su_info_p && daisychain_enabled == TRUE && devices_count > 1 && daisychain_device_number == 1 && !strncmp(varname, "device.1.", 9) ) { char tmp_buf[SU_INFOSIZE]; snprintf(tmp_buf, sizeof(tmp_buf), "device.%s", (varname + 9)); su_info_p = su_find_info(tmp_buf); if (su_info_p) { upsdebugx(2, "%s: finally found " "as daisy master revert %s", __func__, tmp_buf); free(tmp_varname); tmp_varname = strdup(tmp_buf); } } } } else { /* is indeed an outlet.* or device.x.outlet.* */ snmp_info_t *tmp_info_p; /* Point the outlet or outlet group number in the string */ const char *item_number_ptr = NULL; /* Store the target outlet or group number */ int item_number = extract_template_number_from_snmp_info_t(tmp_varname); /* Store the total number of outlets or outlet groups */ int total_items = -1; /* Check if it is outlet / outlet.group */ vartype = get_template_type(tmp_varname); if (vartype == SU_OUTLET_GROUP) { snprintfcat(template_count_var, SU_BUFSIZE, "outlet.group.count"); total_items = atoi(dstate_getinfo(template_count_var)); item_number_ptr = &tmp_varname[12]; } else { snprintfcat(template_count_var, SU_BUFSIZE, "outlet.count"); total_items = atoi(dstate_getinfo(template_count_var)); item_number_ptr = &tmp_varname[6]; } upsdebugx(3, "Using count variable '%s'", template_count_var); item_number = atoi(++item_number_ptr); upsdebugx(3, "%s: item %i / %i", __func__, item_number, total_items); /* ensure the item number is supported (filtered upstream though)! */ if (item_number > total_items) { /* out of bound item number */ upsdebugx(2, "%s: item is out of bound (%i / %i)", __func__, item_number, total_items); return STAT_SET_INVALID; } /* find back the item template */ char *item_varname = (char *)xmalloc(SU_INFOSIZE); snprintf(item_varname, SU_INFOSIZE, "%s.%s%s", (vartype == SU_OUTLET)?"outlet":"outlet.group", "%i", strchr(item_number_ptr++, '.')); upsdebugx(3, "%s: searching for template\"%s\"", __func__, item_varname); tmp_info_p = su_find_info(item_varname); free(item_varname); /* for an snmp_info_t instance */ su_info_p = instantiate_info(tmp_info_p, su_info_p); /* check if default value is also a template */ if ((su_info_p->dfl != NULL) && (strstr(tmp_info_p->dfl, "%i") != NULL)) { su_info_p->dfl = (char *)xmalloc(SU_INFOSIZE); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif snprintf((char *)su_info_p->dfl, SU_INFOSIZE, tmp_info_p->dfl, item_number); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } /* adapt the OID */ if (su_info_p->OID != NULL) { if (mode==SU_MODE_INSTCMD) { /* Workaround buggy Eaton Pulizzi implementation * which have different offsets index for data & commands! */ if (su_info_p->flags & SU_CMD_OFFSET) { upsdebugx(3, "Adding command offset"); cmd_offset++; } } /* Special processing for daisychain: * these outlet | outlet groups also include formatting info, * so we have to check if the daisychain is enabled, and if * the formatting info for it are in 1rst or 2nd position */ #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif if (daisychain_enabled == TRUE) { /* Note: daisychain_enabled == TRUE means that we have * daisychain template. However: * * when there are multiple devices, offset "-1" applies * since device.0 is a fake and actual devices start at * index 1 * * when there is only 1 device, offset doesn't apply since * the device index is "0" */ int daisychain_offset = 0; if (devices_count > 1) daisychain_offset = 1; if (su_info_p->flags & SU_TYPE_DAISY_1) { snprintf((char *)su_info_p->OID, SU_INFOSIZE, tmp_info_p->OID, daisychain_device_number - daisychain_offset, item_number); } else { snprintf((char *)su_info_p->OID, SU_INFOSIZE, tmp_info_p->OID, item_number, daisychain_device_number - daisychain_offset); } } else { snprintf((char *)su_info_p->OID, SU_INFOSIZE, tmp_info_p->OID, item_number); } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } /* else, don't return STAT_SET_INVALID for mode==SU_MODE_SETVAR since we * can be setting a server side variable! */ else { if (mode==SU_MODE_INSTCMD) { free_info(su_info_p); return STAT_INSTCMD_UNKNOWN; } else { /* adapt info_type */ if (su_info_p->info_type != NULL) snprintf((char *)su_info_p->info_type, SU_INFOSIZE, "%s", tmp_varname); } } } /* Sanity check */ if (!su_info_p || !su_info_p->info_type || !(su_info_p->flags & SU_FLAG_OK)) { upsdebugx(2, "%s: info element unavailable %s", __func__, varname); /* Free template (outlet and outlet.group) */ free_info(su_info_p); if (tmp_varname != NULL) free(tmp_varname); return STAT_SET_UNKNOWN; } /* set value into the device, using the provided one, or the default one otherwise */ if (mode==SU_MODE_INSTCMD) { /* Sanity check: commands should either have a value or a default */ if ( (val == NULL) && (su_info_p->dfl == NULL) ) { upsdebugx(1, "%s: cannot execute command '%s': a provided or default value is needed!", __func__, varname); return STAT_SET_INVALID; } } if (su_info_p->info_flags & ST_FLAG_STRING) { status = nut_snmp_set_str(su_info_p->OID, val ? val : su_info_p->dfl); } else { if (mode==SU_MODE_INSTCMD) { if ( !str_to_long(val ? val : su_info_p->dfl, &value, 10) ) { upsdebugx(1, "%s: cannot execute command '%s': value is not a number!", __func__, varname); return STAT_SET_INVALID; } } else { /* non string data may imply a value lookup */ if (su_info_p->oid2info) { value = su_find_valinfo(su_info_p->oid2info, val ? val : su_info_p->dfl); } else { /* Convert value and apply multiplier */ if ( !str_to_long(val, &value, 10) ) { upsdebugx(1, "%s: cannot set '%s': value is not a number!", __func__, varname); return STAT_SET_INVALID; } value = (long)((double)value / su_info_p->info_len); } } /* Actually apply the new value */ if (SU_TYPE(su_info_p) == SU_TYPE_TIME) { status = nut_snmp_set_time(su_info_p->OID, value); } else { status = nut_snmp_set_int(su_info_p->OID, value); } } /* Process result */ if (status == FALSE) { if (mode==SU_MODE_INSTCMD) upsdebugx(1, "%s: cannot execute command '%s'", __func__, varname); else upsdebugx(1, "%s: cannot set value %s on OID %s", __func__, val, su_info_p->OID); retval = STAT_SET_FAILED; } else { retval = STAT_SET_HANDLED; if (mode==SU_MODE_INSTCMD) upsdebugx(1, "%s: successfully sent command %s", __func__, varname); else { upsdebugx(1, "%s: successfully set %s to \"%s\"", __func__, varname, val); /* update info array: call dstate_setinfo, since flags and aux are * already published, and this saves us some processing */ dstate_setinfo(varname, "%s", val); } } /* Free template (outlet and outlet.group) */ if (!strncmp(tmp_varname, "outlet", 6)) free_info(su_info_p); free(tmp_varname); return retval; } /* set r/w INFO_ element to a value. * FIXME: make a common function with su_instcmd! */ int su_setvar(const char *varname, const char *val) { return su_setOID(SU_MODE_SETVAR, varname, val); } /* Daisychain-aware function to add instant commands: * Every command that is valid for a device has to be added for device.0 * This then allows to composite commands, called on device.0 and executed * on all devices of the daisychain */ int su_addcmd(snmp_info_t *su_info_p) { upsdebugx(2, "entering %s(%s)", __func__, su_info_p->info_type); if (daisychain_enabled == TRUE) { /* FIXME?: daisychain */ for (current_device_number = 1 ; current_device_number <= devices_count ; current_device_number++) { process_template(SU_WALKMODE_INIT, "device", su_info_p); } } else { if (nut_snmp_get(su_info_p->OID) != NULL) { dstate_addcmd(su_info_p->info_type); upsdebugx(1, "%s: adding command '%s'", __func__, su_info_p->info_type); } } return 0; } /* process instant command and take action. */ int su_instcmd(const char *cmdname, const char *extradata) { return su_setOID(SU_MODE_INSTCMD, cmdname, extradata); } /* FIXME: the below functions can be removed since these were for loading * the mib2nut information from a file instead of the .h definitions... */ /* return 1 if usable, 0 if not */ static int parse_mibconf_args(size_t numargs, char **arg) { bool_t ret; /* everything below here uses up through arg[1] */ if (numargs < 6) return 0; /* */ /* special case for setting some OIDs value at driver startup */ if (!strcmp(arg[0], "init")) { /* set value. */ if (!strcmp(arg[1], "str")) { ret = nut_snmp_set_str(arg[3], arg[4]); } else { ret = nut_snmp_set_int(arg[3], strtol(arg[4], NULL, 0)); } if (ret == FALSE) upslogx(LOG_ERR, "%s: cannot set value %s for %s", __func__, arg[4], arg[3]); else upsdebugx(1, "%s: successfully set %s to \"%s\"", __func__, arg[0], arg[4]); return 1; } /* TODO: create the lookup table */ upsdebugx(2, "%s, %s, %s, %s, %s, %s", arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]); return 1; } /* called for fatal errors in parseconf like malloc failures */ static void mibconf_err(const char *errmsg) { upslogx(LOG_ERR, "Fatal error in parseconf (*mib.conf): %s", errmsg); } /* load *mib.conf into an snmp_info_t structure */ void read_mibconf(char *mib) { char fn[SMALLBUF]; PCONF_CTX_t ctx; upsdebugx(2, "SNMP UPS driver: entering %s(%s)", __func__, mib); snprintf(fn, sizeof(fn), "%s/snmp/%s.conf", CONFPATH, mib); pconf_init(&ctx, mibconf_err); if (!pconf_file_begin(&ctx, fn)) fatalx(EXIT_FAILURE, "%s", ctx.errmsg); while (pconf_file_next(&ctx)) { if (pconf_parse_error(&ctx)) { upslogx(LOG_ERR, "Parse error: %s:%d: %s", fn, ctx.linenum, ctx.errmsg); continue; } if (ctx.numargs < 1) continue; if (!parse_mibconf_args(ctx.numargs, ctx.arglist)) { unsigned int i; char errmsg[SMALLBUF]; snprintf(errmsg, sizeof(errmsg), "mib.conf: invalid directive"); for (i = 0; i < ctx.numargs; i++) snprintfcat(errmsg, sizeof(errmsg), " %s", ctx.arglist[i]); upslogx(LOG_WARNING, "%s", errmsg); } } pconf_finish(&ctx); }