2266 lines
65 KiB
C
2266 lines
65 KiB
C
/* snmp-ups.c - NUT Meta SNMP driver (support different MIBS)
|
|
*
|
|
* Based on NetSNMP API (Simple Network Management Protocol V1-2)
|
|
*
|
|
* Copyright (C)
|
|
* 2002 - 2014 Arnaud Quette <arnaud.quette@free.fr>
|
|
* 2015 - 2016 Arnaud Quette <ArnaudQuette@Eaton.com>
|
|
* 2002 - 2006 Dmitry Frolov <frolov@riss-telecom.ru>
|
|
* J.W. Hoogervorst <jeroen@hoogervorst.net>
|
|
* Niels Baggesen <niels@baggesen.net>
|
|
* 2009 - 2010 Arjen de Korte <adkorte-guest@alioth.debian.org>
|
|
*
|
|
* Sponsored by Eaton <http://www.eaton.com>
|
|
* and originally by MGE UPS SYSTEMS <http://www.mgeups.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <limits.h>
|
|
#include <ctype.h> /* for isprint() */
|
|
|
|
/* NUT SNMP common functions */
|
|
#include "main.h"
|
|
#include "snmp-ups.h"
|
|
#include "parseconf.h"
|
|
|
|
/* include all known mib2nut lookup tables */
|
|
#include "apc-mib.h"
|
|
#include "mge-mib.h"
|
|
#include "netvision-mib.h"
|
|
#include "powerware-mib.h"
|
|
#include "eaton-mib.h"
|
|
#include "raritan-pdu-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-ats-mib.h"
|
|
#include "apc-ats-mib.h"
|
|
|
|
/* Address API change */
|
|
#ifndef usmAESPrivProtocol
|
|
#define usmAESPrivProtocol usmAES128PrivProtocol
|
|
#endif
|
|
|
|
static mib2nut_info_t *mib2nut[] = {
|
|
&apc,
|
|
&mge,
|
|
&netvision,
|
|
&powerware,
|
|
&pxgx_ups,
|
|
&aphel_genesisII,
|
|
&aphel_revelation,
|
|
&eaton_marlin,
|
|
&pulizzi_switched1,
|
|
&pulizzi_switched2,
|
|
&raritan,
|
|
&baytech,
|
|
&compaq,
|
|
&bestpower,
|
|
&cyberpower,
|
|
&delta_ups,
|
|
&xppc,
|
|
&huawei,
|
|
&tripplite_ietf,
|
|
&eaton_ats,
|
|
&apc_ats,
|
|
/*
|
|
* 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 mib=auto)
|
|
*/
|
|
&ietf,
|
|
/* 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 input_phases, output_phases, bypass_phases;
|
|
|
|
/* 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;
|
|
const char *mibname;
|
|
const char *mibvers;
|
|
|
|
#define DRIVER_NAME "Generic SNMP UPS driver"
|
|
#define DRIVER_VERSION "0.97"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Arnaud Quette <arnaud.quette@free.fr>\n" \
|
|
"Arnaud Quette <ArnaudQuette@Eaton.com>\n" \
|
|
"Dmitry Frolov <frolov@riss-telecom.ru>\n" \
|
|
"J.W. Hoogervorst <jeroen@hoogervorst.net>\n" \
|
|
"Niels Baggesen <niels@baggesen.net>\n" \
|
|
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
|
|
DRV_STABLE,
|
|
{ NULL }
|
|
};
|
|
/* FIXME: integrate MIBs info? do the same as for usbhid-ups! */
|
|
|
|
time_t lastpoll = 0;
|
|
|
|
/* template OIDs index start with 0 or 1 (estimated stable for a MIB),
|
|
* automatically guessed at the first pass */
|
|
int template_index_base = -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(int 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);
|
|
|
|
/* 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->info_type != NULL ; su_info_p++)
|
|
{
|
|
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 */
|
|
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();
|
|
|
|
/* initialize all other INFO_ fields from list */
|
|
if (snmp_ups_walk(SU_WALKMODE_INIT) == TRUE)
|
|
dstate_dataok();
|
|
else
|
|
dstate_datastale();
|
|
|
|
/* 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))
|
|
dstate_dataok();
|
|
else
|
|
dstate_datastale();
|
|
|
|
alarm_commit();
|
|
status_commit();
|
|
|
|
/* store timestamp */
|
|
lastpoll = time(NULL);
|
|
}
|
|
}
|
|
|
|
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__);
|
|
|
|
/* 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,
|
|
"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)");
|
|
addvar(VAR_VALUE, SU_VAR_POLLFREQ,
|
|
"Set polling frequency in seconds, to reduce network flow (default=30)");
|
|
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_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)");
|
|
addvar(VAR_VALUE, SU_VAR_AUTHPROT,
|
|
"Set the authentication protocol (MD5 or SHA) used for authenticated SNMPv3 messages (default=MD5)");
|
|
addvar(VAR_VALUE, SU_VAR_PRIVPROT,
|
|
"Set the privacy protocol (DES or AES) used for encrypted SNMPv3 messages (default=DES)");
|
|
}
|
|
|
|
void upsdrv_initups(void)
|
|
{
|
|
snmp_info_t *su_info_p;
|
|
char model[SU_INFOSIZE];
|
|
bool_t status= FALSE;
|
|
const char *mibs;
|
|
|
|
upsdebugx(1, "SNMP UPS driver: entering %s()", __func__);
|
|
|
|
/* Retrieve user's parameters */
|
|
mibs = testvar(SU_VAR_MIBS) ? getval(SU_VAR_MIBS) : "auto";
|
|
|
|
/* 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;
|
|
|
|
/* Get UPS Model node to see if there's a MIB */
|
|
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)
|
|
status = nut_snmp_get_str(su_info_p->OID, model, sizeof(model), NULL);
|
|
|
|
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" */
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
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";
|
|
|
|
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) {
|
|
/* 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 (strcmp(authProtocol, "MD5") == 0) {
|
|
g_snmp_sess.securityAuthProto = usmHMACMD5AuthProtocol;
|
|
g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
|
|
}
|
|
else if (strcmp(authProtocol, "SHA") == 0) {
|
|
g_snmp_sess.securityAuthProto = usmHMACSHA1AuthProtocol;
|
|
g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid);
|
|
}
|
|
else
|
|
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 (generate_Ku(g_snmp_sess.securityAuthProto,
|
|
g_snmp_sess.securityAuthProtoLen,
|
|
(u_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 (strcmp(privProtocol, "DES") == 0) {
|
|
g_snmp_sess.securityPrivProto = usmDESPrivProtocol;
|
|
g_snmp_sess.securityPrivProtoLen = sizeof(usmDESPrivProtocol)/sizeof(oid);
|
|
}
|
|
else if (strcmp(privProtocol, "AES") == 0) {
|
|
g_snmp_sess.securityPrivProto = usmAESPrivProtocol;
|
|
g_snmp_sess.securityPrivProtoLen = sizeof(usmAESPrivProtocol)/sizeof(oid);
|
|
}
|
|
else
|
|
fatalx(EXIT_FAILURE, "Bad SNMPv3 authProtocol: %s", authProtocol);
|
|
|
|
/* 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 (generate_Ku(g_snmp_sess.securityAuthProto,
|
|
g_snmp_sess.securityAuthProtoLen,
|
|
(u_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 */
|
|
void nut_snmp_free(struct snmp_pdu ** array_to_free)
|
|
{
|
|
struct snmp_pdu ** current_element;
|
|
|
|
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 * */
|
|
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);
|
|
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 */
|
|
ret_array = realloc(ret_array,sizeof(struct snmp_pdu*)*(nb_iteration+1));
|
|
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;
|
|
if (len > 0) {
|
|
/* Test for hexadecimal values */
|
|
if (!isprint(pdu->variables->val.string[0]))
|
|
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;
|
|
if((str=su_find_infoval(oid2info, *pdu->variables->val.integer))) {
|
|
strncpy(buf, str, buf_len-1);
|
|
}
|
|
else {
|
|
strncpy(buf, "UNKNOWN", buf_len-1);
|
|
}
|
|
buf[buf_len-1]='\0';
|
|
}
|
|
else {
|
|
len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer);
|
|
}
|
|
break;
|
|
case ASN_TIMETICKS:
|
|
/* convert timeticks to seconds */
|
|
len = snprintf(buf, buf_len, "%ld", *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_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);
|
|
}
|
|
else
|
|
snprintf(buf, buf_len, "%s", tmp_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;
|
|
|
|
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) {
|
|
switch (response->errstat)
|
|
{
|
|
case SNMP_ERR_NOERROR:
|
|
break;
|
|
case SNMP_ERR_NOSUCHNAME: /* harmless */
|
|
upsdebugx(2, "[%s] %s: %s",
|
|
upsname?upsname:device_name, buf, snmp_errstring(response->errstat));
|
|
break;
|
|
default:
|
|
upslogx(LOG_ERR, "[%s] %s: Error in packet: %s",
|
|
upsname?upsname:device_name, buf, snmp_errstring(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");
|
|
|
|
for (su_info_p = &snmp_info[0]; 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. */
|
|
void su_setinfo(snmp_info_t *su_info_p, const char *value)
|
|
{
|
|
info_lkp_t *info_lkp;
|
|
|
|
upsdebugx(1, "entering %s(%s)", __func__, su_info_p->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(su_info_p->info_type, "%s", value);
|
|
else
|
|
dstate_setinfo(su_info_p->info_type, "%s", su_info_p->dfl);
|
|
|
|
dstate_setflags(su_info_p->info_type, su_info_p->info_flags);
|
|
dstate_setaux(su_info_p->info_type, su_info_p->info_len);
|
|
|
|
/* Set enumerated values, only if the data has ST_FLAG_RW and there
|
|
* are lookup values */
|
|
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(su_info_p->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 (strcmp(info_value, "")) {
|
|
status_set(info_value);
|
|
}
|
|
}
|
|
/* TODO: else */
|
|
}
|
|
|
|
void su_alarm_set(snmp_info_t *su_info_p, long value)
|
|
{
|
|
const char *info_value = 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);
|
|
|
|
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, su_info_p->info_type);
|
|
|
|
/* 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 (su_info_p->info_type[0] == 'L') {
|
|
/* Extract phase number */
|
|
item_number = atoi(su_info_p->info_type+1);
|
|
|
|
/* Inject in the alarm string */
|
|
snprintf(alarm_info_value, sizeof(alarm_info_value),
|
|
"phase L%i %s", item_number, info_value);
|
|
info_value = &alarm_info_value[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;
|
|
|
|
for (su_info_p = &snmp_info[0]; 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;
|
|
}
|
|
|
|
/* Try to find the MIB using sysOID matching.
|
|
* Return a pointer to a mib2nut definition if found, NULL otherwise */
|
|
mib2nut_info_t *match_sysoid()
|
|
{
|
|
snmp_info_t *su_info_p;
|
|
char sysOID_buf[LARGEBUF];
|
|
char testOID_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 FALSE;
|
|
}
|
|
|
|
/* 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, MAX_OID_LEN);
|
|
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;
|
|
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) {
|
|
upsdebugx(2, "Testing %s using OID %s", su_info_p->info_type, su_info_p->OID);
|
|
if (nut_snmp_get_str(su_info_p->OID, testOID_buf, LARGEBUF, NULL) != 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;
|
|
char buf[LARGEBUF];
|
|
snmp_info_t *su_info_p;
|
|
mib2nut_info_t *m2n = NULL;
|
|
|
|
upsdebugx(2, "SNMP UPS driver: entering %s(%s)", __func__, mib);
|
|
|
|
/* 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 (!strcmp(mib, "auto"))
|
|
{
|
|
upsdebugx(1, "trying the new match_sysoid() method");
|
|
m2n = match_sysoid();
|
|
}
|
|
|
|
/* Otherwise, revert to the classic method */
|
|
if (m2n == NULL)
|
|
{
|
|
for (i = 0; mib2nut[i] != NULL; i++) {
|
|
/* Is there already a MIB name provided? */
|
|
if (strcmp(mib, "auto") && strcmp(mib, mib2nut[i]->mib_name)) {
|
|
continue;
|
|
}
|
|
upsdebugx(1, "load_mib2nut: trying classic method with '%s' mib", mib2nut[i]->mib_name);
|
|
|
|
/* Classic method: test an OID specific to this MIB */
|
|
snmp_info = mib2nut[i]->snmp_info;
|
|
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) {
|
|
upsdebugx(2, "Testing %s using OID %s", su_info_p->info_type, su_info_p->OID);
|
|
if (nut_snmp_get_str(su_info_p->OID, buf, LARGEBUF, NULL) != 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);
|
|
}
|
|
/* 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, "load_mib2nut: using %s mib", mibname);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Did we find something or is it really an unknown mib */
|
|
if (strcmp(mib, "auto") != 0) {
|
|
fatalx(EXIT_FAILURE, "Unknown mibs value: %s", mib);
|
|
} else {
|
|
fatalx(EXIT_FAILURE, "No supported device detected");
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* find the INFO_* value matching that OID value */
|
|
const char *su_find_infoval(info_lkp_t *oid2info, long value)
|
|
{
|
|
info_lkp_t *info_lkp;
|
|
|
|
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;
|
|
|
|
for(p=snmp_info; 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Template handling functions
|
|
**********************************************************************/
|
|
|
|
/* Instantiate an snmp_info_t from a template.
|
|
* Useful for outlet and outlet.group templates.
|
|
* Note: remember to adapt info_type, OID and optionaly dfl */
|
|
snmp_info_t *instantiate_info(snmp_info_t *info_template, snmp_info_t *new_instance)
|
|
{
|
|
upsdebugx(1, "%s(%s)", __func__, info_template->info_type);
|
|
|
|
/* sanity check */
|
|
if (info_template == NULL)
|
|
return NULL;
|
|
|
|
if (new_instance == NULL)
|
|
new_instance = (snmp_info_t *)xmalloc(sizeof(snmp_info_t));
|
|
|
|
new_instance->info_type = (char *)xmalloc(SU_INFOSIZE);
|
|
if (info_template->OID != NULL)
|
|
new_instance->OID = (char *)xmalloc(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;
|
|
new_instance->setvar = info_template->setvar;
|
|
|
|
upsdebugx(2, "instantiate_info: template instantiated");
|
|
return new_instance;
|
|
}
|
|
|
|
/* Free a dynamically allocated snmp_info_t.
|
|
* Useful for outlet and outlet.group templates */
|
|
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 */
|
|
int base_snmp_template_index(const char *OID_template)
|
|
{
|
|
int base_index = template_index_base;
|
|
char test_OID[SU_INFOSIZE];
|
|
|
|
if (template_index_base == -1)
|
|
{
|
|
/* not initialised yet */
|
|
for (base_index = 0 ; base_index < 2 ; base_index++) {
|
|
snprintf(test_OID, sizeof(test_OID), OID_template, base_index);
|
|
if (nut_snmp_get(test_OID) != NULL)
|
|
break;
|
|
}
|
|
template_index_base = base_index;
|
|
}
|
|
upsdebugx(3, "%s: %i", __func__, template_index_base);
|
|
return base_index;
|
|
}
|
|
|
|
/* return the NUT offset (increment) based on template_index_base
|
|
* ie (template_index_base == 0) => increment +1
|
|
* (template_index_base == 1) => increment +0 */
|
|
int base_nut_template_offset(void)
|
|
{
|
|
return (template_index_base==0)?1:0;
|
|
}
|
|
|
|
/* 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(const char *OID_template)
|
|
{
|
|
int base_index = 0;
|
|
char test_OID[SU_INFOSIZE];
|
|
int base_count;
|
|
|
|
upsdebugx(1, "%s(%s)", __func__, OID_template);
|
|
|
|
/* Determine if OID index starts from 0 or 1? */
|
|
snprintf(test_OID, sizeof(test_OID), OID_template, base_index);
|
|
if (nut_snmp_get(test_OID) == NULL)
|
|
base_index++;
|
|
|
|
/* Now, actually iterate */
|
|
for (base_count = 0 ; ; base_count++) {
|
|
snprintf(test_OID, sizeof(test_OID), OID_template, base_index + base_count);
|
|
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 */
|
|
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;
|
|
snmp_info_t cur_info_p;
|
|
char template_count_var[SU_BUFSIZE];
|
|
|
|
upsdebugx(1, "%s template definition found (%s)...", type, su_info_p->info_type);
|
|
|
|
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->OID);
|
|
/* Publish the count estimation */
|
|
dstate_setinfo(template_count_var, "%i", template_count);
|
|
}
|
|
else {
|
|
template_count = atoi(dstate_getinfo(template_count_var));
|
|
}
|
|
|
|
/* Only instantiate templates if needed! */
|
|
if (template_count > 0) {
|
|
/* general init of data using the template */
|
|
instantiate_info(su_info_p, &cur_info_p);
|
|
|
|
for (cur_template_number = base_snmp_template_index(su_info_p->OID) ;
|
|
cur_template_number < (template_count + base_snmp_template_index(su_info_p->OID)) ;
|
|
cur_template_number++)
|
|
{
|
|
cur_nut_index = cur_template_number + base_nut_template_offset();
|
|
snprintf((char*)cur_info_p.info_type, SU_INFOSIZE,
|
|
su_info_p->info_type, cur_nut_index);
|
|
|
|
/* 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) {
|
|
snprintf((char *)cur_info_p.OID, SU_INFOSIZE, su_info_p->OID, cur_template_number);
|
|
|
|
/* 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 */
|
|
int get_template_type(const char* varname)
|
|
{
|
|
/* Check if it is outlet / outlet.group */
|
|
if (!strncmp(varname, "outlet.group", 12)) {
|
|
return SU_OUTLET_GROUP;
|
|
}
|
|
else if (!strncmp(varname, "outlet", 6)) {
|
|
return SU_OUTLET_GROUP;
|
|
}
|
|
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(int 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
|
|
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' */
|
|
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, "getting data: %s (%s)", su_info_p->info_type, su_info_p->OID);
|
|
|
|
/* ok, update this element. */
|
|
status = su_ups_get(su_info_p);
|
|
|
|
/* 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] snmp_ups_walk: data resumed for %s",
|
|
upsname?upsname:device_name, 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 */
|
|
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;
|
|
}
|
|
|
|
/* walk ups variables and set elements of the info array. */
|
|
bool_t snmp_ups_walk(int mode)
|
|
{
|
|
static unsigned long iterations = 0;
|
|
snmp_info_t *su_info_p;
|
|
bool_t status = FALSE;
|
|
|
|
for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++) {
|
|
|
|
/* Check if we are asked to stop (reactivity++) */
|
|
if (exit_flag != 0)
|
|
return TRUE;
|
|
|
|
/* 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 */
|
|
if (!(su_info_p->flags & SU_FLAG_OK))
|
|
continue;
|
|
|
|
/* 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)) {
|
|
if (mode == SU_WALKMODE_INIT) {
|
|
if (su_info_p->dfl) {
|
|
/* 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}.phase.
|
|
* Non matching items are disabled, and flags are cleared at
|
|
* init time */
|
|
if (su_info_p->flags & SU_INPHASES) {
|
|
upsdebugx(1, "Check input_phases (%i)", input_phases);
|
|
if (input_phases == 0) {
|
|
/* FIXME: to get from input.phases
|
|
* this would avoid the use of the SU_FLAG_SETINT flag
|
|
* and potential human-error to not declare the right way.
|
|
* It would also free the slot for SU_OUTLET_GROUP */
|
|
continue;
|
|
}
|
|
if (su_info_p->flags & SU_INPUT_1) {
|
|
if (input_phases == 1) {
|
|
upsdebugx(1, "input_phases is 1");
|
|
su_info_p->flags &= ~SU_INPHASES;
|
|
} else {
|
|
upsdebugx(1, "input_phases is not 1");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else if (su_info_p->flags & SU_INPUT_3) {
|
|
if (input_phases == 3) {
|
|
upsdebugx(1, "input_phases is 3");
|
|
su_info_p->flags &= ~SU_INPHASES;
|
|
} else {
|
|
upsdebugx(1, "input_phases is not 3");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else {
|
|
upsdebugx(1, "input_phases is %d", input_phases);
|
|
}
|
|
}
|
|
|
|
if (su_info_p->flags & SU_OUTPHASES) {
|
|
upsdebugx(1, "Check output_phases");
|
|
if (output_phases == 0) {
|
|
/* FIXME: same as for input_phases */
|
|
continue;
|
|
}
|
|
if (su_info_p->flags & SU_OUTPUT_1) {
|
|
if (output_phases == 1) {
|
|
upsdebugx(1, "output_phases is 1");
|
|
su_info_p->flags &= ~SU_OUTPHASES;
|
|
} else {
|
|
upsdebugx(1, "output_phases is not 1");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else if (su_info_p->flags & SU_OUTPUT_3) {
|
|
if (output_phases == 3) {
|
|
upsdebugx(1, "output_phases is 3");
|
|
su_info_p->flags &= ~SU_OUTPHASES;
|
|
} else {
|
|
upsdebugx(1, "output_phases is not 3");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else {
|
|
upsdebugx(1, "output_phases is %d", output_phases);
|
|
}
|
|
}
|
|
|
|
if (su_info_p->flags & SU_BYPPHASES) {
|
|
upsdebugx(1, "Check bypass_phases");
|
|
if (bypass_phases == 0) {
|
|
/* FIXME: same as for input_phases */
|
|
continue;
|
|
}
|
|
if (su_info_p->flags & SU_BYPASS_1) {
|
|
if (bypass_phases == 1) {
|
|
upsdebugx(1, "bypass_phases is 1");
|
|
su_info_p->flags &= ~SU_BYPPHASES;
|
|
} else {
|
|
upsdebugx(1, "bypass_phases is not 1");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else if (su_info_p->flags & SU_BYPASS_3) {
|
|
if (input_phases == 3) {
|
|
upsdebugx(1, "bypass_phases is 3");
|
|
su_info_p->flags &= ~SU_BYPPHASES;
|
|
} else {
|
|
upsdebugx(1, "bypass_phases is not 3");
|
|
su_info_p->flags &= ~SU_FLAG_OK;
|
|
continue;
|
|
}
|
|
} else {
|
|
upsdebugx(1, "bypass_phases is %d", bypass_phases);
|
|
}
|
|
}
|
|
|
|
/* process outlet template 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 {
|
|
/* get and process this data */
|
|
status = get_and_process_data(mode, su_info_p);
|
|
}
|
|
} /* for (su_info_p... */
|
|
|
|
iterations++;
|
|
return status;
|
|
}
|
|
|
|
bool_t su_ups_get(snmp_info_t *su_info_p)
|
|
{
|
|
static char buf[SU_INFOSIZE];
|
|
bool_t status;
|
|
long value;
|
|
const char *strValue = NULL;
|
|
struct snmp_pdu ** pdu_array;
|
|
struct snmp_pdu * current_pdu;
|
|
alarms_info_t * alarms;
|
|
int index = 0;
|
|
|
|
upsdebugx(2, "%s: %s %s", __func__, su_info_p->info_type, su_info_p->OID);
|
|
|
|
if (!strcasecmp(su_info_p->info_type, "ups.status")) {
|
|
|
|
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");
|
|
|
|
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);
|
|
|
|
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");
|
|
|
|
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")) {
|
|
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");
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/* another special case */
|
|
if (!strcasecmp(su_info_p->info_type, "ambient.temperature")) {
|
|
float temp=0;
|
|
|
|
status = nut_snmp_get_int(su_info_p->OID, &value);
|
|
|
|
if(status != TRUE) {
|
|
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);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (su_info_p->info_flags & ST_FLAG_STRING) {
|
|
status = nut_snmp_get_str(su_info_p->OID, buf, sizeof(buf), su_info_p->oid2info);
|
|
} else {
|
|
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_OK;
|
|
if(su_info_p->flags&SU_FLAG_UNIQUE) {
|
|
disable_competition(su_info_p);
|
|
su_info_p->flags &= ~SU_FLAG_UNIQUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
if (su_info_p->flags & SU_FLAG_SETINT) {
|
|
upsdebugx(1, "setvar %s", su_info_p->OID);
|
|
*su_info_p->setvar = value;
|
|
}
|
|
/* 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
|
|
snprintf(buf, sizeof(buf), "%.2f", value * su_info_p->info_len);
|
|
}
|
|
}
|
|
|
|
if (status == TRUE) {
|
|
su_setinfo(su_info_p, buf);
|
|
upsdebugx(2, "=> value: %s", buf);
|
|
}
|
|
else
|
|
upsdebugx(2, "=> Failed");
|
|
|
|
return status;
|
|
}
|
|
|
|
/* set r/w INFO_ element to a value. */
|
|
int su_setvar(const char *varname, const char *val)
|
|
{
|
|
snmp_info_t *su_info_p = NULL;
|
|
bool_t status;
|
|
int retval = STAT_SET_FAILED;
|
|
long value = -1;
|
|
/* normal (default), outlet, or outlet group variable */
|
|
int vartype = get_template_type(varname);
|
|
|
|
upsdebugx(2, "entering %s(%s, %s)", __func__, varname, val);
|
|
|
|
/* Check if it is outlet / outlet.group */
|
|
if (strncmp(varname, "outlet", 6))
|
|
su_info_p = su_find_info(varname);
|
|
else {
|
|
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(varname);
|
|
/* Store the total number of outlets or outlet groups */
|
|
int total_items = -1;
|
|
|
|
/* Check if it is outlet / outlet.group */
|
|
if (vartype == SU_OUTLET_GROUP) {
|
|
total_items = atoi(dstate_getinfo("outlet.group.count"));
|
|
item_number_ptr = &varname[12];
|
|
}
|
|
else {
|
|
total_items = atoi(dstate_getinfo("outlet.count"));
|
|
item_number_ptr = &varname[6];
|
|
}
|
|
|
|
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);
|
|
snprintf((char *)su_info_p->dfl, sizeof(su_info_p->dfl), tmp_info_p->dfl,
|
|
item_number - base_nut_template_offset());
|
|
}
|
|
/* adapt the OID */
|
|
if (su_info_p->OID != NULL) {
|
|
snprintf((char *)su_info_p->OID, sizeof(su_info_p->OID), tmp_info_p->OID,
|
|
item_number - base_nut_template_offset());
|
|
}
|
|
/* else, don't return STAT_SET_INVALID since we can be setting
|
|
* a server side variable! */
|
|
|
|
/* adapt info_type */
|
|
if (su_info_p->info_type != NULL)
|
|
snprintf((char *)su_info_p->info_type, sizeof(su_info_p->info_type), "%s", varname);
|
|
}
|
|
|
|
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) */
|
|
if (vartype != 0)
|
|
free_info(su_info_p);
|
|
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
if (!(su_info_p->info_flags & ST_FLAG_RW) || su_info_p->OID == NULL) {
|
|
upsdebugx(2, "%s: not writable %s", __func__, varname);
|
|
|
|
/* Free template (outlet and outlet.group) */
|
|
if (vartype != 0)
|
|
free_info(su_info_p);
|
|
|
|
return STAT_SET_INVALID;
|
|
}
|
|
|
|
/* set value into the device */
|
|
if (su_info_p->info_flags & ST_FLAG_STRING) {
|
|
status = nut_snmp_set_str(su_info_p->OID, val);
|
|
} else {
|
|
/* non string data may imply a value lookup */
|
|
if (su_info_p->oid2info) {
|
|
value = su_find_valinfo(su_info_p->oid2info, val);
|
|
}
|
|
else {
|
|
/* Convert value and apply multiplier */
|
|
value = atof(val) / su_info_p->info_len;
|
|
}
|
|
/* Actually apply the new value */
|
|
status = nut_snmp_set_int(su_info_p->OID, value);
|
|
}
|
|
|
|
if (status == FALSE)
|
|
upsdebugx(1, "%s: cannot set value %s for %s", __func__, val, su_info_p->OID);
|
|
else {
|
|
retval = STAT_SET_HANDLED;
|
|
upsdebugx(1, "%s: successfully set %s to \"%s\"", __func__, varname, val);
|
|
|
|
/* update info array
|
|
* FIXME: we'd better call su_ups_get() to refresh! */
|
|
su_setinfo(su_info_p, val);
|
|
}
|
|
/* Free template (outlet and outlet.group) */
|
|
if (vartype != 0)
|
|
free_info(su_info_p);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* process instant command and take action. */
|
|
int su_instcmd(const char *cmdname, const char *extradata)
|
|
{
|
|
snmp_info_t *su_info_p = NULL;
|
|
int status;
|
|
int retval = STAT_INSTCMD_FAILED;
|
|
int cmd_offset = 0;
|
|
/* normal (default), outlet, or outlet group variable */
|
|
int vartype = get_template_type(cmdname);
|
|
|
|
upsdebugx(2, "entering %s(%s, %s)", __func__, cmdname, extradata);
|
|
|
|
/* FIXME: this should only apply if strchr(%)! */
|
|
if (strncmp(cmdname, "outlet", 6)) {
|
|
su_info_p = su_find_info(cmdname);
|
|
}
|
|
else {
|
|
/* FIXME: common with su_setvar(), apart from upsdebugx */
|
|
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(cmdname);
|
|
/* Store the total number of outlets or outlet groups */
|
|
int total_items = -1;
|
|
|
|
/* Check if it is outlet / outlet.group */
|
|
if (vartype == SU_OUTLET_GROUP) {
|
|
total_items = atoi(dstate_getinfo("outlet.group.count"));
|
|
item_number_ptr = &cmdname[12];
|
|
}
|
|
else {
|
|
total_items = atoi(dstate_getinfo("outlet.count"));
|
|
item_number_ptr = &cmdname[6];
|
|
}
|
|
|
|
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);
|
|
snprintf((char *)su_info_p->dfl, sizeof(su_info_p->dfl), tmp_info_p->dfl,
|
|
item_number - base_nut_template_offset());
|
|
}
|
|
/* FIXME: </end> common with su_setvar(), apart from upsdebugx */
|
|
|
|
/* adapt the OID */
|
|
if (su_info_p->OID != NULL) {
|
|
/* 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++;
|
|
}
|
|
|
|
snprintf((char *)su_info_p->OID, sizeof(su_info_p->OID), tmp_info_p->OID,
|
|
item_number - base_nut_template_offset() + cmd_offset);
|
|
} else {
|
|
free_info(su_info_p);
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/* Sanity check */
|
|
if (!su_info_p || !su_info_p->info_type || !(su_info_p->flags & SU_FLAG_OK)) {
|
|
|
|
/* Check for composite commands */
|
|
if (!strcasecmp(cmdname, "load.on")) {
|
|
return su_instcmd("load.on.delay", "0");
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "load.off")) {
|
|
return su_instcmd("load.off.delay", "0");
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.return")) {
|
|
int ret;
|
|
|
|
/* Ensure "ups.start.auto" is set to "yes", if supported */
|
|
if (dstate_getinfo("ups.start.auto")) {
|
|
su_setvar("ups.start.auto", "yes");
|
|
}
|
|
|
|
ret = su_instcmd("load.on.delay", dstate_getinfo("ups.delay.start"));
|
|
if (ret != STAT_INSTCMD_HANDLED) {
|
|
return ret;
|
|
}
|
|
|
|
return su_instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
|
int ret;
|
|
|
|
/* Ensure "ups.start.auto" is set to "no", if supported */
|
|
if (dstate_getinfo("ups.start.auto")) {
|
|
su_setvar("ups.start.auto", "no");
|
|
}
|
|
|
|
ret = su_instcmd("load.on.delay", "-1");
|
|
if (ret != STAT_INSTCMD_HANDLED) {
|
|
return ret;
|
|
}
|
|
|
|
return su_instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
|
|
}
|
|
|
|
upsdebugx(2, "%s: %s unavailable", __func__, cmdname);
|
|
|
|
if (!strncmp(cmdname, "outlet", 6))
|
|
free_info(su_info_p);
|
|
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
}
|
|
|
|
/* set value, using the provided one, or the default one otherwise */
|
|
if (su_info_p->info_flags & ST_FLAG_STRING) {
|
|
status = nut_snmp_set_str(su_info_p->OID, extradata ? extradata : su_info_p->dfl);
|
|
} else {
|
|
status = nut_snmp_set_int(su_info_p->OID, extradata ? atoi(extradata) : su_info_p->info_len);
|
|
}
|
|
|
|
if (status == FALSE)
|
|
upsdebugx(1, "%s: cannot set value for %s", __func__, cmdname);
|
|
else {
|
|
retval = STAT_INSTCMD_HANDLED;
|
|
upsdebugx(1, "%s: successfully sent command %s", __func__, cmdname);
|
|
}
|
|
|
|
if (!strncmp(cmdname, "outlet", 6))
|
|
free_info(su_info_p);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* 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(int numargs, char **arg)
|
|
{
|
|
bool_t ret;
|
|
|
|
/* everything below here uses up through arg[1] */
|
|
if (numargs < 6)
|
|
return 0;
|
|
|
|
/* <info type> <info flags> <info len> <OID name> <default value> <value lookup> */
|
|
|
|
/* 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);
|
|
}
|