1
/* snmp-ups.c - NUT Meta SNMP driver (support different MIBS)
3
* Based on NetSNMP API (Simple Network Management Protocol V1-2)
6
* 2002 - 2014 Arnaud Quette <arnaud.quette@free.fr>
7
* 2002 - 2006 Dmitry Frolov <frolov@riss-telecom.ru>
8
* J.W. Hoogervorst <jeroen@hoogervorst.net>
9
* Niels Baggesen <niels@baggesen.net>
10
* 2009 - 2010 Arjen de Korte <adkorte-guest@alioth.debian.org>
12
* Sponsored by Eaton <http://www.eaton.com>
13
* and originally by MGE UPS SYSTEMS <http://www.mgeups.com/>
15
* This program is free software; you can redistribute it and/or modify
16
* it under the terms of the GNU General Public License as published by
17
* the Free Software Foundation; either version 2 of the License, or
18
* (at your option) any later version.
20
* This program is distributed in the hope that it will be useful,
21
* but WITHOUT ANY WARRANTY; without even the implied warranty of
22
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
* GNU General Public License for more details.
26
* You should have received a copy of the GNU General Public License
27
* along with this program; if not, write to the Free Software
28
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34
/* NUT SNMP common functions */
37
#include "parseconf.h"
39
/* include all known mib2nut lookup tables */
42
#include "netvision-mib.h"
43
#include "powerware-mib.h"
44
#include "eaton-mib.h"
45
#include "raritan-pdu-mib.h"
46
#include "baytech-mib.h"
47
#include "compaq-mib.h"
48
#include "bestpower-mib.h"
49
#include "cyberpower-mib.h"
50
#include "delta_ups-mib.h"
54
/* Address API change */
55
#ifndef usmAESPrivProtocol
56
#define usmAESPrivProtocol usmAES128PrivProtocol
59
static mib2nut_info_t *mib2nut[] = {
77
* Prepend vendor specific MIB mappings before IETF, so that
78
* if a device supports both IETF and vendor specific MIB,
79
* the vendor specific one takes precedence (when mib=auto)
82
/* end of structure. */
86
struct snmp_session g_snmp_sess, *g_snmp_sess_p;
87
const char *OID_pwr_status;
89
int pollfreq; /* polling frequency */
90
int input_phases, output_phases, bypass_phases;
92
/* pointer to the Snmp2Nut lookup table */
93
mib2nut_info_t *mib2nut_info;
94
/* FIXME: to be trashed */
95
snmp_info_t *snmp_info;
96
alarms_info_t *alarms_info;
100
static void disable_transfer_oids(void);
102
#define DRIVER_NAME "Generic SNMP UPS driver"
103
#define DRIVER_VERSION "0.72"
105
/* driver description structure */
106
upsdrv_info_t upsdrv_info = {
109
"Arnaud Quette <arnaud.quette@free.fr>\n" \
110
"Dmitry Frolov <frolov@riss-telecom.ru>\n" \
111
"J.W. Hoogervorst <jeroen@hoogervorst.net>\n" \
112
"Niels Baggesen <niels@baggesen.net>\n" \
113
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
117
/* FIXME: integrate MIBs info? do the same as for usbhid-ups! */
121
/* outlet OID index start with 0 or 1,
122
* automatically guessed at the first pass */
123
int outlet_index_base = -1;
125
/* sysOID location */
126
#define SYSOID_OID ".1.3.6.1.2.1.1.2.0"
128
/* ---------------------------------------------
129
* driver functions implementations
130
* --------------------------------------------- */
131
void upsdrv_initinfo(void)
133
snmp_info_t *su_info_p;
135
upsdebugx(1, "SNMP UPS driver : entering upsdrv_initinfo()");
137
dstate_setinfo("driver.version.data", "%s MIB %s", mibname, mibvers);
139
/* add instant commands to the info database.
140
* outlet commands are processed later, during initial walk */
141
for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++)
143
su_info_p->flags |= SU_FLAG_OK;
144
if ((SU_TYPE(su_info_p) == SU_TYPE_CMD)
145
&& !(su_info_p->flags & SU_OUTLET)) {
146
/* first check that this OID actually exists */
147
if (nut_snmp_get(su_info_p->OID) != NULL) {
148
dstate_addcmd(su_info_p->info_type);
149
upsdebugx(1, "upsdrv_initinfo(): adding command '%s'", su_info_p->info_type);
154
if (testvar("notransferoids"))
155
disable_transfer_oids();
157
/* initialize all other INFO_ fields from list */
158
if (snmp_ups_walk(SU_WALKMODE_INIT))
163
/* setup handlers for instcmd and setvar functions */
164
upsh.setvar = su_setvar;
165
upsh.instcmd = su_instcmd;
168
void upsdrv_updateinfo(void)
170
upsdebugx(1,"SNMP UPS driver : entering upsdrv_updateinfo()");
172
/* only update every pollfreq */
173
/* FIXME: only update status (SU_STATUS_*), Ć la usbhid-ups, in between */
174
if (time(NULL) > (lastpoll + pollfreq)) {
178
/* update all dynamic info fields */
179
if (snmp_ups_walk(SU_WALKMODE_UPDATE))
186
/* store timestamp */
187
lastpoll = time(NULL);
191
void upsdrv_shutdown(void)
194
This driver will probably never support this. In order to
195
be any use, the driver should be called near the end of
196
the system halt script. By that time we in all likelyhood
197
we won't have network capabilities anymore, so we could
198
never send this command to the UPS. This is not an error,
199
but a limitation of the interface used.
202
upsdebugx(1, "upsdrv_shutdown...");
204
/* Try to shutdown with delay */
205
if (su_instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
206
/* Shutdown successful */
210
/* If the above doesn't work, try shutdown.reboot */
211
if (su_instcmd("shutdown.reboot", NULL) == STAT_INSTCMD_HANDLED) {
212
/* Shutdown successful */
216
/* If the above doesn't work, try load.off.delay */
217
if (su_instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) {
218
/* Shutdown successful */
222
fatalx(EXIT_FAILURE, "Shutdown failed!");
225
void upsdrv_help(void)
227
upsdebugx(1, "entering upsdrv_help");
230
/* list flags and values that you want to receive via -x */
231
void upsdrv_makevartable(void)
233
upsdebugx(1, "entering upsdrv_makevartable()");
235
addvar(VAR_VALUE, SU_VAR_MIBS,
236
"Set MIB compliance (default=ietf, allowed: mge,apcc,netvision,pw,cpqpower,...)");
237
addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_COMMUNITY,
238
"Set community name (default=public)");
239
addvar(VAR_VALUE, SU_VAR_VERSION,
240
"Set SNMP version (default=v1, allowed v2c)");
241
addvar(VAR_VALUE, SU_VAR_POLLFREQ,
242
"Set polling frequency in seconds, to reduce network flow (default=30)");
243
addvar(VAR_FLAG, "notransferoids",
244
"Disable transfer OIDs (use on APCC Symmetras)");
245
addvar(VAR_VALUE, SU_VAR_SECLEVEL,
246
"Set the securityLevel used for SNMPv3 messages (default=noAuthNoPriv, allowed: authNoPriv,authPriv)");
247
addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_SECNAME,
248
"Set the securityName used for authenticated SNMPv3 messages (no default)");
249
addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_AUTHPASSWD,
250
"Set the authentication pass phrase used for authenticated SNMPv3 messages (no default)");
251
addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_PRIVPASSWD,
252
"Set the privacy pass phrase used for encrypted SNMPv3 messages (no default)");
253
addvar(VAR_VALUE, SU_VAR_AUTHPROT,
254
"Set the authentication protocol (MD5 or SHA) used for authenticated SNMPv3 messages (default=MD5)");
255
addvar(VAR_VALUE, SU_VAR_PRIVPROT,
256
"Set the privacy protocol (DES or AES) used for encrypted SNMPv3 messages (default=DES)");
259
void upsdrv_initups(void)
261
snmp_info_t *su_info_p;
262
char model[SU_INFOSIZE];
266
upsdebugx(1, "SNMP UPS driver : entering upsdrv_initups()");
268
/* Retrieve user's parameters */
269
mibs = testvar(SU_VAR_MIBS) ? getval(SU_VAR_MIBS) : "auto";
271
/* init SNMP library, etc... */
272
nut_snmp_init(progname, device_path);
274
/* FIXME: first test if the device is reachable to avoid timeouts! */
276
/* Load the SNMP to NUT translation data */
279
/* init polling frequency */
280
if (getval(SU_VAR_POLLFREQ))
281
pollfreq = atoi(getval(SU_VAR_POLLFREQ));
283
pollfreq = DEFAULT_POLLFREQ;
285
/* Get UPS Model node to see if there's a MIB */
286
su_info_p = su_find_info("ups.model");
287
status = nut_snmp_get_str(su_info_p->OID, model, sizeof(model), NULL);
290
upslogx(0, "Detected %s on host %s (mib: %s %s)",
291
model, device_path, mibname, mibvers);
293
fatalx(EXIT_FAILURE, "%s MIB wasn't found on %s", mibs, g_snmp_sess.peername);
294
/* FIXME: "No supported device detected" */
296
if (su_find_info("load.off.delay")) {
297
/* Adds default with a delay value of '0' (= immediate) */
298
dstate_addcmd("load.off");
301
if (su_find_info("load.on.delay")) {
302
/* Adds default with a delay value of '0' (= immediate) */
303
dstate_addcmd("load.on");
306
if (su_find_info("load.off.delay") && su_find_info("load.on.delay")) {
307
/* Add composite instcmds (require setting multiple OID values) */
308
dstate_addcmd("shutdown.return");
309
dstate_addcmd("shutdown.stayoff");
313
void upsdrv_cleanup(void)
318
/* -----------------------------------------------------------
320
* ----------------------------------------------------------- */
322
void nut_snmp_init(const char *type, const char *hostname)
324
char *ns_options = NULL;
325
const char *community, *version;
326
const char *secLevel = NULL, *authPassword, *privPassword;
327
const char *authProtocol, *privProtocol;
329
upsdebugx(2, "SNMP UPS driver : entering nut_snmp_init(%s)", type);
331
/* Force numeric OIDs resolution (ie, do not resolve to textual names)
332
* This is mostly for the convenience of debug output */
333
ns_options = snmp_out_toggle_options("n");
334
if (ns_options != NULL) {
335
upsdebugx(2, "Failed to enable numeric OIDs resolution");
338
/* Initialize the SNMP library */
341
/* Initialize session */
342
snmp_sess_init(&g_snmp_sess);
344
g_snmp_sess.peername = xstrdup(hostname);
346
/* Retrieve user parameters */
347
version = testvar(SU_VAR_VERSION) ? getval(SU_VAR_VERSION) : "v1";
349
if ((strcmp(version, "v1") == 0) || (strcmp(version, "v2c") == 0)) {
350
g_snmp_sess.version = (strcmp(version, "v1") == 0) ? SNMP_VERSION_1 : SNMP_VERSION_2c;
351
community = testvar(SU_VAR_COMMUNITY) ? getval(SU_VAR_COMMUNITY) : "public";
352
g_snmp_sess.community = (unsigned char *)xstrdup(community);
353
g_snmp_sess.community_len = strlen(community);
355
else if (strcmp(version, "v3") == 0) {
356
/* SNMP v3 related init */
357
g_snmp_sess.version = SNMP_VERSION_3;
360
if (testvar(SU_VAR_SECLEVEL)) {
361
secLevel = getval(SU_VAR_SECLEVEL);
363
if (strcmp(secLevel, "noAuthNoPriv") == 0)
364
g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
365
else if (strcmp(secLevel, "authNoPriv") == 0)
366
g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
367
else if (strcmp(secLevel, "authPriv") == 0)
368
g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
370
fatalx(EXIT_FAILURE, "Bad SNMPv3 securityLevel: %s", secLevel);
373
g_snmp_sess.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
376
if (testvar(SU_VAR_SECNAME)) {
377
g_snmp_sess.securityName = xstrdup(getval(SU_VAR_SECNAME));
378
g_snmp_sess.securityNameLen = strlen(g_snmp_sess.securityName);
381
fatalx(EXIT_FAILURE, "securityName is required for SNMPv3");
383
/* Process mandatory fields, based on the security level */
384
authPassword = testvar(SU_VAR_AUTHPASSWD) ? getval(SU_VAR_AUTHPASSWD) : NULL;
385
privPassword = testvar(SU_VAR_PRIVPASSWD) ? getval(SU_VAR_PRIVPASSWD) : NULL;
387
switch (g_snmp_sess.securityLevel) {
388
case SNMP_SEC_LEVEL_AUTHNOPRIV:
389
if (authPassword == NULL)
390
fatalx(EXIT_FAILURE, "authPassword is required for SNMPv3 in %s mode", secLevel);
392
case SNMP_SEC_LEVEL_AUTHPRIV:
393
if ((authPassword == NULL) || (privPassword == NULL))
394
fatalx(EXIT_FAILURE, "authPassword and privPassword are required for SNMPv3 in %s mode", secLevel);
397
case SNMP_SEC_LEVEL_NOAUTH:
398
/* nothing else needed */
402
/* Process authentication protocol and key */
403
g_snmp_sess.securityAuthKeyLen = USM_AUTH_KU_LEN;
404
authProtocol = testvar(SU_VAR_AUTHPROT) ? getval(SU_VAR_AUTHPROT) : "MD5";
406
if (strcmp(authProtocol, "MD5") == 0) {
407
g_snmp_sess.securityAuthProto = usmHMACMD5AuthProtocol;
408
g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
410
else if (strcmp(authProtocol, "SHA") == 0) {
411
g_snmp_sess.securityAuthProto = usmHMACSHA1AuthProtocol;
412
g_snmp_sess.securityAuthProtoLen = sizeof(usmHMACSHA1AuthProtocol)/sizeof(oid);
415
fatalx(EXIT_FAILURE, "Bad SNMPv3 authProtocol: %s", authProtocol);
417
/* set the authentication key to a MD5/SHA1 hashed version of our
418
* passphrase (must be at least 8 characters long) */
419
if(g_snmp_sess.securityLevel != SNMP_SEC_LEVEL_NOAUTH) {
420
if (generate_Ku(g_snmp_sess.securityAuthProto,
421
g_snmp_sess.securityAuthProtoLen,
422
(u_char *) authPassword, strlen(authPassword),
423
g_snmp_sess.securityAuthKey,
424
&g_snmp_sess.securityAuthKeyLen) !=
426
fatalx(EXIT_FAILURE, "Error generating Ku from authentication pass phrase");
430
privProtocol = testvar(SU_VAR_PRIVPROT) ? getval(SU_VAR_PRIVPROT) : "DES";
432
if (strcmp(privProtocol, "DES") == 0) {
433
g_snmp_sess.securityPrivProto = usmDESPrivProtocol;
434
g_snmp_sess.securityPrivProtoLen = sizeof(usmDESPrivProtocol)/sizeof(oid);
436
else if (strcmp(privProtocol, "AES") == 0) {
437
g_snmp_sess.securityPrivProto = usmAESPrivProtocol;
438
g_snmp_sess.securityPrivProtoLen = sizeof(usmAESPrivProtocol)/sizeof(oid);
441
fatalx(EXIT_FAILURE, "Bad SNMPv3 authProtocol: %s", authProtocol);
443
/* set the privacy key to a MD5/SHA1 hashed version of our
444
* passphrase (must be at least 8 characters long) */
445
if(g_snmp_sess.securityLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
446
g_snmp_sess.securityPrivKeyLen = USM_PRIV_KU_LEN;
447
if (generate_Ku(g_snmp_sess.securityAuthProto,
448
g_snmp_sess.securityAuthProtoLen,
449
(u_char *) privPassword, strlen(privPassword),
450
g_snmp_sess.securityPrivKey,
451
&g_snmp_sess.securityPrivKeyLen) !=
453
fatalx(EXIT_FAILURE, "Error generating Ku from privacy pass phrase");
458
fatalx(EXIT_FAILURE, "Bad SNMP version: %s", version);
460
/* Open the session */
461
SOCK_STARTUP; /* MS Windows wrapper, not really needed on Unix! */
462
g_snmp_sess_p = snmp_open(&g_snmp_sess); /* establish the session */
463
if (g_snmp_sess_p == NULL) {
464
nut_snmp_perror(&g_snmp_sess, 0, NULL, "nut_snmp_init: snmp_open");
465
fatalx(EXIT_FAILURE, "Unable to establish communication");
469
void nut_snmp_cleanup(void)
471
/* close snmp session. */
473
snmp_close(g_snmp_sess_p);
474
g_snmp_sess_p = NULL;
476
SOCK_CLEANUP; /* wrapper not needed on Unix! */
479
/* Free a struct snmp_pdu * returned by nut_snmp_walk */
480
void nut_snmp_free(struct snmp_pdu ** array_to_free)
482
struct snmp_pdu ** current_element;
484
current_element = array_to_free;
486
while (*current_element != NULL) {
487
snmp_free_pdu(*current_element);
491
free( array_to_free );
494
/* Return a NULL terminated array of snmp_pdu * */
495
struct snmp_pdu **nut_snmp_walk(const char *OID, int max_iteration)
498
struct snmp_pdu *pdu, *response = NULL;
499
oid name[MAX_OID_LEN];
500
size_t name_len = MAX_OID_LEN;
502
size_t current_name_len;
503
static unsigned int numerr = 0;
504
int nb_iteration = 0;
505
struct snmp_pdu ** ret_array = NULL;
506
int type = SNMP_MSG_GET;
508
upsdebugx(3, "nut_snmp_walk(%s)", OID);
510
/* create and send request. */
511
if (!snmp_parse_oid(OID, name, &name_len)) {
512
upsdebugx(2, "[%s] nut_snmp_walk: %s: %s",
513
upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
518
current_name_len = name_len;
520
while( nb_iteration < max_iteration ) {
521
/* Going to a shorter OID means we are outside our sub-tree */
522
if( current_name_len < name_len ) {
526
pdu = snmp_pdu_create(type);
529
fatalx(EXIT_FAILURE, "Not enough memory");
532
snmp_add_null_var(pdu, current_name, current_name_len);
534
status = snmp_synch_response(g_snmp_sess_p, pdu, &response);
540
if (!((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))) {
541
if (mibname == NULL) {
542
/* We are probing for proper mib - ignore errors */
543
snmp_free_pdu(response);
549
if ((numerr == SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0)) {
550
upslogx(LOG_WARNING, "[%s] Warning: excessive poll "
551
"failures, limiting error reporting",
552
upsname?upsname:device_name);
555
if ((numerr < SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0)) {
556
if (type == SNMP_MSG_GETNEXT) {
557
upsdebugx(2, "=> No more OID, walk complete");
560
nut_snmp_perror(g_snmp_sess_p, status, response,
561
"nut_snmp_walk: %s", OID);
565
snmp_free_pdu(response);
572
/* +1 is for the terminating NULL */
573
ret_array = realloc(ret_array,sizeof(struct snmp_pdu*)*(nb_iteration+1));
574
ret_array[nb_iteration-1] = response;
575
ret_array[nb_iteration]=NULL;
577
current_name = response->variables->name;
578
current_name_len = response->variables->name_length;
580
type = SNMP_MSG_GETNEXT;
586
struct snmp_pdu *nut_snmp_get(const char *OID)
588
struct snmp_pdu ** pdu_array;
589
struct snmp_pdu * ret_pdu;
594
upsdebugx(3, "nut_snmp_get(%s)", OID);
596
pdu_array = nut_snmp_walk(OID,1);
598
if(pdu_array == NULL) {
602
ret_pdu = snmp_clone_pdu(*pdu_array);
604
nut_snmp_free(pdu_array);
609
static bool_t decode_str(struct snmp_pdu *pdu, char *buf, size_t buf_len, info_lkp_t *oid2info) {
612
/* zero out buffer. */
613
memset(buf, 0, buf_len);
615
switch (pdu->variables->type) {
618
len = pdu->variables->val_len > buf_len - 1 ?
619
buf_len - 1 : pdu->variables->val_len;
620
memcpy(buf, pdu->variables->val.string, len);
628
if((str=su_find_infoval(oid2info, *pdu->variables->val.integer))) {
629
strncpy(buf, str, buf_len-1);
632
strncpy(buf, "UNKNOWN", buf_len-1);
637
len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer);
641
/* convert timeticks to seconds */
642
len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer / 100);
645
len = snprint_objid (buf, buf_len, pdu->variables->val.objid, pdu->variables->val_len / sizeof(oid));
654
bool_t nut_snmp_get_str(const char *OID, char *buf, size_t buf_len, info_lkp_t *oid2info)
656
struct snmp_pdu *pdu;
659
upsdebugx(3, "Entering nut_snmp_get_str()");
661
pdu = nut_snmp_get(OID);
665
ret = decode_str(pdu,buf,buf_len,oid2info);
668
upsdebugx(2, "[%s] unhandled ASN 0x%x received from %s",
669
upsname?upsname:device_name, pdu->variables->type, OID);
677
bool_t nut_snmp_get_int(const char *OID, long *pval)
679
struct snmp_pdu *pdu;
683
pdu = nut_snmp_get(OID);
687
switch (pdu->variables->type) {
690
buf = xmalloc(pdu->variables->val_len + 1);
691
memcpy(buf, pdu->variables->val.string, pdu->variables->val_len);
692
buf[pdu->variables->val_len] = '\0';
693
value = strtol(buf, NULL, 0);
699
value = *pdu->variables->val.integer;
702
/* convert timeticks to seconds */
703
value = *pdu->variables->val.integer / 100;
706
upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x received from %s",
707
upsname?upsname:device_name, pdu->variables->type, OID);
720
bool_t nut_snmp_set(const char *OID, char type, const char *value)
724
struct snmp_pdu *pdu, *response = NULL;
725
oid name[MAX_OID_LEN];
726
size_t name_len = MAX_OID_LEN;
728
upsdebugx(1, "entering nut_snmp_set (%s, %c, %s)", OID, type, value);
730
if (!snmp_parse_oid(OID, name, &name_len)) {
731
upslogx(LOG_ERR, "[%s] nut_snmp_set: %s: %s",
732
upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
736
pdu = snmp_pdu_create(SNMP_MSG_SET);
738
fatalx(EXIT_FAILURE, "Not enough memory");
740
if (snmp_add_var(pdu, name, name_len, type, value)) {
741
upslogx(LOG_ERR, "[%s] nut_snmp_set: %s: %s",
742
upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
747
status = snmp_synch_response(g_snmp_sess_p, pdu, &response);
749
if ((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))
752
nut_snmp_perror(g_snmp_sess_p, status, response,
753
"nut_snmp_set: can't set %s", OID);
755
snmp_free_pdu(response);
759
bool_t nut_snmp_set_str(const char *OID, const char *value)
761
return nut_snmp_set(OID, 's', value);
764
bool_t nut_snmp_set_int(const char *OID, long value)
766
char buf[SU_BUFSIZE];
768
snprintf(buf, sizeof(buf), "%ld", value);
769
return nut_snmp_set(OID, 'i', buf);
772
bool_t nut_snmp_set_time(const char *OID, long value)
774
char buf[SU_BUFSIZE];
776
snprintf(buf, SU_BUFSIZE, "%ld", value * 100);
777
return nut_snmp_set(OID, 't', buf);
780
/* log descriptive SNMP error message. */
781
void nut_snmp_perror(struct snmp_session *sess, int status,
782
struct snmp_pdu *response, const char *fmt, ...)
785
int cliberr, snmperr;
787
char buf[SU_LARGEBUF];
790
vsnprintf(buf, sizeof(buf), fmt, va);
793
if (response == NULL) {
794
snmp_error(sess, &cliberr, &snmperr, &snmperrstr);
795
upslogx(LOG_ERR, "[%s] %s: %s",
796
upsname?upsname:device_name, buf, snmperrstr);
798
} else if (status == STAT_SUCCESS) {
799
switch (response->errstat)
801
case SNMP_ERR_NOERROR:
803
case SNMP_ERR_NOSUCHNAME: /* harmless */
804
upsdebugx(2, "[%s] %s: %s",
805
upsname?upsname:device_name, buf, snmp_errstring(response->errstat));
808
upslogx(LOG_ERR, "[%s] %s: Error in packet: %s",
809
upsname?upsname:device_name, buf, snmp_errstring(response->errstat));
812
} else if (status == STAT_TIMEOUT) {
813
upslogx(LOG_ERR, "[%s] %s: Timeout: no response from %s",
814
upsname?upsname:device_name, buf, sess->peername);
816
snmp_sess_error(sess, &cliberr, &snmperr, &snmperrstr);
817
upslogx(LOG_ERR, "[%s] %s: %s",
818
upsname?upsname:device_name, buf, snmperrstr);
823
/* -----------------------------------------------------------
825
* ----------------------------------------------------------- */
827
/* deal with APCC weirdness on Symmetras */
828
static void disable_transfer_oids(void)
830
snmp_info_t *su_info_p;
832
upslogx(LOG_INFO, "Disabling transfer OIDs");
834
for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++) {
835
if (!strcasecmp(su_info_p->info_type, "input.transfer.low")) {
836
su_info_p->flags &= ~SU_FLAG_OK;
840
if (!strcasecmp(su_info_p->info_type, "input.transfer.high")) {
841
su_info_p->flags &= ~SU_FLAG_OK;
847
/* universal function to add or update info element. */
848
void su_setinfo(snmp_info_t *su_info_p, const char *value)
850
upsdebugx(1, "entering su_setinfo(%s)", su_info_p->info_type);
852
if (SU_TYPE(su_info_p) == SU_TYPE_CMD)
855
if (strcasecmp(su_info_p->info_type, "ups.status"))
858
dstate_setinfo(su_info_p->info_type, "%s", value);
860
dstate_setinfo(su_info_p->info_type, "%s", su_info_p->dfl);
862
dstate_setflags(su_info_p->info_type, su_info_p->info_flags);
863
dstate_setaux(su_info_p->info_type, su_info_p->info_len);
865
/* Commit the current value, to avoid staleness with huge
866
* data collections on slow devices */
871
void su_status_set(snmp_info_t *su_info_p, long value)
873
const char *info_value = NULL;
875
upsdebugx(2, "SNMP UPS driver : entering su_status_set()");
877
if ((info_value = su_find_infoval(su_info_p->oid2info, value)) != NULL)
879
if (strcmp(info_value, "")) {
880
status_set(info_value);
886
/* find info element definition in my info array. */
887
snmp_info_t *su_find_info(const char *type)
889
snmp_info_t *su_info_p;
891
for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++)
892
if (!strcasecmp(su_info_p->info_type, type)) {
893
upsdebugx(3, "su_find_info: \"%s\" found", type);
897
upsdebugx(3, "su_find_info: unknown info type (%s)", type);
901
/* Try to find the MIB using sysOID matching.
902
* Return a pointer to a mib2nut definition if found, NULL otherwise */
903
mib2nut_info_t *match_sysoid()
905
char sysOID_buf[LARGEBUF];
906
oid device_sysOID[MAX_OID_LEN];
907
size_t device_sysOID_len = MAX_OID_LEN;
908
oid mib2nut_sysOID[MAX_OID_LEN];
909
size_t mib2nut_sysOID_len = MAX_OID_LEN;
912
/* Retrieve sysOID value of this device */
913
if (nut_snmp_get_str(SYSOID_OID, sysOID_buf, sizeof(sysOID_buf), NULL))
915
upsdebugx(1, "match_sysoid: device sysOID value = %s", sysOID_buf);
917
/* Build OIDs for comparison */
918
if (!read_objid(sysOID_buf, device_sysOID, &device_sysOID_len))
920
upsdebugx(2, "match_sysoid: can't build device_sysOID %s: %s",
921
sysOID_buf, snmp_api_errstring(snmp_errno));
926
/* Now, iterate on mib2nut definitions */
927
for (i = 0; mib2nut[i] != NULL; i++)
929
upsdebugx(1, "match_sysoid: checking MIB %s", mib2nut[i]->mib_name);
931
if (mib2nut[i]->sysOID == NULL)
934
/* Clear variables */
935
memset(mib2nut_sysOID, 0, MAX_OID_LEN);
936
mib2nut_sysOID_len = MAX_OID_LEN;
938
if (!read_objid(mib2nut[i]->sysOID, mib2nut_sysOID, &mib2nut_sysOID_len))
940
upsdebugx(2, "match_sysoid: can't build OID %s: %s",
941
sysOID_buf, snmp_api_errstring(snmp_errno));
943
/* Try to continue anyway! */
946
/* Now compare these */
947
upsdebugx(1, "match_sysoid: comparing %s with %s", sysOID_buf, mib2nut[i]->sysOID);
948
if (!netsnmp_oid_equals(device_sysOID, device_sysOID_len, mib2nut_sysOID, mib2nut_sysOID_len))
950
upsdebugx(2, "match_sysoid: sysOID matches MIB '%s'!", mib2nut[i]->mib_name);
954
/* Yell all to call for user report */
955
upslogx(LOG_ERR, "No matching MIB found for sysOID '%s'!\n" \
956
"Please report it to NUT developers, with an 'upsc' output for your device.\n" \
957
"Going back to the classic MIB detection method.",
961
upsdebugx(2, "Can't get sysOID value");
966
/* Load the right snmp_info_t structure matching mib parameter */
967
bool_t load_mib2nut(const char *mib)
971
mib2nut_info_t *m2n = NULL;
973
upsdebugx(2, "SNMP UPS driver : entering load_mib2nut(%s)", mib);
975
/* First, try to match against sysOID, if no MIB was provided.
976
* This should speed up init stage
977
* (Note: sysOID points the device main MIB entry point) */
978
if (!strcmp(mib, "auto"))
980
upsdebugx(1, "trying the new match_sysoid() method");
981
m2n = match_sysoid();
984
/* Otherwise, revert to the classic method */
987
for (i = 0; mib2nut[i] != NULL; i++) {
988
/* Is there already a MIB name provided? */
989
if (strcmp(mib, "auto") && strcmp(mib, mib2nut[i]->mib_name)) {
992
upsdebugx(1, "load_mib2nut: trying classic method with '%s' mib", mib2nut[i]->mib_name);
994
/* Classic method: test an OID specific to this MIB */
995
if (!nut_snmp_get_str(mib2nut[i]->oid_auto_check, buf, sizeof(buf), NULL)) {
1004
/* Store the result, if any */
1007
snmp_info = m2n->snmp_info;
1008
OID_pwr_status = m2n->oid_pwr_status;
1009
mibname = m2n->mib_name;
1010
mibvers = m2n->mib_version;
1011
alarms_info = m2n->alarms_info;
1012
upsdebugx(1, "load_mib2nut: using %s mib", mibname);
1016
/* Did we find something or is it really an unknown mib */
1017
if (strcmp(mib, "auto") != 0) {
1018
fatalx(EXIT_FAILURE, "Unknown mibs value: %s", mib);
1020
fatalx(EXIT_FAILURE, "No supported device detected");
1024
/* find the OID value matching that INFO_* value */
1025
long su_find_valinfo(info_lkp_t *oid2info, const char* value)
1027
info_lkp_t *info_lkp;
1029
for (info_lkp = oid2info; (info_lkp != NULL) &&
1030
(strcmp(info_lkp->info_value, "NULL")); info_lkp++) {
1032
if (!(strcmp(info_lkp->info_value, value))) {
1033
upsdebugx(1, "su_find_valinfo: found %s (value: %s)",
1034
info_lkp->info_value, value);
1036
return info_lkp->oid_value;
1039
upsdebugx(1, "su_find_valinfo: no matching INFO_* value for this OID value (%s)", value);
1043
/* find the INFO_* value matching that OID value */
1044
const char *su_find_infoval(info_lkp_t *oid2info, long value)
1046
info_lkp_t *info_lkp;
1048
for (info_lkp = oid2info; (info_lkp != NULL) &&
1049
(strcmp(info_lkp->info_value, "NULL")) && (info_lkp->info_value != NULL); info_lkp++) {
1051
if (info_lkp->oid_value == value) {
1052
upsdebugx(1, "su_find_infoval: found %s (value: %ld)",
1053
info_lkp->info_value, value);
1055
return info_lkp->info_value;
1058
upsdebugx(1, "su_find_infoval: no matching INFO_* value for this OID value (%ld)", value);
1062
static void disable_competition(snmp_info_t *entry)
1066
for(p=snmp_info; p->info_type!=NULL; p++) {
1067
if(p!=entry && !strcmp(p->info_type, entry->info_type)) {
1068
upsdebugx(2, "disable_competition: disabling %s %s",
1069
p->info_type, p->OID);
1070
p->flags &= ~SU_FLAG_OK;
1075
/* instantiate an snmp_info_t from a template.
1076
* mostly (only?) useful for outlet templates.
1077
* Note: remember to adapt info_type, OID and optionaly dfl */
1078
snmp_info_t *instantiate_info(snmp_info_t *info_template, snmp_info_t *new_instance)
1081
if (info_template == NULL)
1084
if (new_instance == NULL)
1085
new_instance = (snmp_info_t *)xmalloc(sizeof(snmp_info_t));
1087
new_instance->info_type = (char *)xmalloc(SU_INFOSIZE);
1088
if (info_template->OID != NULL)
1089
new_instance->OID = (char *)xmalloc(SU_INFOSIZE);
1091
new_instance->OID = NULL;
1092
new_instance->info_flags = info_template->info_flags;
1093
new_instance->info_len = info_template->info_len;
1094
/* FIXME: check if we need to adapt this one... */
1095
new_instance->dfl = info_template->dfl;
1096
new_instance->flags = info_template->flags;
1097
new_instance->oid2info = info_template->oid2info;
1098
new_instance->setvar = info_template->setvar;
1100
return new_instance;
1103
/* free a dynamically allocated snmp_info_t.
1104
* mostly (only?) useful for outlet templates */
1105
void free_info(snmp_info_t *su_info_p)
1108
if (su_info_p == NULL)
1111
if (su_info_p->info_type != NULL)
1112
free ((char *)su_info_p->info_type);
1114
if (su_info_p->OID != NULL)
1115
free ((char *)su_info_p->OID);
1120
/* return the base SNMP index (0 or 1) to start outlet iteration on the MIB,
1121
* based on a test using a template OID */
1122
int base_snmp_outlet_index(const char *OID_template)
1124
int base_index = outlet_index_base;
1125
char test_OID[SU_INFOSIZE];
1127
if (outlet_index_base == -1)
1129
/* not initialised yet */
1130
for (base_index = 0 ; base_index < 2 ; base_index++) {
1131
sprintf(test_OID, OID_template, base_index);
1132
if (nut_snmp_get(test_OID) != NULL)
1135
outlet_index_base = base_index;
1137
upsdebugx(3, "base_snmp_outlet_index: %i", outlet_index_base);
1141
/* return the NUT offset (increment) based on outlet_index_base
1142
* ie (outlet_index_base == 0) => increment +1
1143
* (outlet_index_base == 1) => increment +0 */
1144
int base_nut_outlet_offset(void)
1146
return (outlet_index_base==0)?1:0;
1149
/* try to determine the number of outlets, using a template definition,
1150
* that we walk, until we can't get anymore values */
1151
static int guestimate_outlet_count(const char *OID_template)
1154
char test_OID[SU_INFOSIZE];
1157
upsdebugx(1, "guestimate_outlet_count(%s)", OID_template);
1159
/* Determine if OID index starts from 0 or 1? */
1160
sprintf(test_OID, OID_template, base_index);
1161
if (nut_snmp_get(test_OID) == NULL)
1164
/* Now, actually iterate */
1165
for (base_count = 0 ; ; base_count++) {
1166
sprintf(test_OID, OID_template, base_index + base_count);
1167
if (nut_snmp_get(test_OID) == NULL)
1171
upsdebugx(3, "guestimate_outlet_count: %i", base_count);
1175
/* process a single data from a walk */
1176
bool_t get_and_process_data(int mode, snmp_info_t *su_info_p)
1178
bool_t status = FALSE;
1180
upsdebugx(1, "getting data: %s (%s)", su_info_p->info_type, su_info_p->OID);
1182
/* ok, update this element. */
1183
status = su_ups_get(su_info_p);
1185
/* set stale flag if data is stale, clear if not. */
1186
if (status == TRUE) {
1187
if (su_info_p->flags & SU_FLAG_STALE) {
1188
upslogx(LOG_INFO, "[%s] snmp_ups_walk: data resumed for %s",
1189
upsname?upsname:device_name, su_info_p->info_type);
1190
su_info_p->flags &= ~SU_FLAG_STALE;
1192
if(su_info_p->flags & SU_FLAG_UNIQUE) {
1193
/* We should be the only provider of this */
1194
disable_competition(su_info_p);
1195
su_info_p->flags &= ~SU_FLAG_UNIQUE;
1199
if (mode == SU_WALKMODE_INIT) {
1200
/* handle unsupported vars */
1201
su_info_p->flags &= ~SU_FLAG_OK;
1203
if (!(su_info_p->flags & SU_FLAG_STALE)) {
1204
upslogx(LOG_INFO, "[%s] snmp_ups_walk: data stale for %s",
1205
upsname?upsname:device_name, su_info_p->info_type);
1206
su_info_p->flags |= SU_FLAG_STALE;
1214
/* walk ups variables and set elements of the info array. */
1215
bool_t snmp_ups_walk(int mode)
1217
static unsigned long iterations = 0;
1218
snmp_info_t *su_info_p;
1219
bool_t status = FALSE;
1221
for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++) {
1223
/* Check if we are asked to stop (reactivity++) */
1227
/* skip instcmd, not linked to outlets */
1228
if ((SU_TYPE(su_info_p) == SU_TYPE_CMD)
1229
&& !(su_info_p->flags & SU_OUTLET)) {
1230
upsdebugx(1, "SU_CMD_MASK => %s", su_info_p->OID);
1233
/* skip elements we shouldn't show */
1234
if (!(su_info_p->flags & SU_FLAG_OK))
1237
/* skip static elements in update mode */
1238
if (mode == SU_WALKMODE_UPDATE &&
1239
su_info_p->flags & SU_FLAG_STATIC)
1242
/* set default value if we cannot fetch it */
1243
/* and set static flag on this element.
1244
* not applicable to outlets (need SU_FLAG_STATIC tagging) */
1245
if ((su_info_p->flags & SU_FLAG_ABSENT)
1246
&& !(su_info_p->flags & SU_OUTLET)) {
1247
if (mode == SU_WALKMODE_INIT) {
1248
if (su_info_p->dfl) {
1249
/* Set default value if we cannot fetch it from ups. */
1250
su_setinfo(su_info_p, NULL);
1252
su_info_p->flags |= SU_FLAG_STATIC;
1257
/* check stale elements only on each PN_STALE_RETRY iteration. */
1258
if ((su_info_p->flags & SU_FLAG_STALE) &&
1259
(iterations % SU_STALE_RETRY) != 0)
1262
if (su_info_p->flags & SU_INPHASES) {
1263
upsdebugx(1, "Check input_phases");
1264
if (input_phases == 0) {
1267
if (su_info_p->flags & SU_INPUT_1) {
1268
if (input_phases == 1) {
1269
upsdebugx(1, "input_phases is 1");
1270
su_info_p->flags &= ~SU_INPHASES;
1272
upsdebugx(1, "input_phases is not 1");
1273
su_info_p->flags &= ~SU_FLAG_OK;
1276
} else if (su_info_p->flags & SU_INPUT_3) {
1277
if (input_phases == 3) {
1278
upsdebugx(1, "input_phases is 3");
1279
su_info_p->flags &= ~SU_INPHASES;
1281
upsdebugx(1, "input_phases is not 3");
1282
su_info_p->flags &= ~SU_FLAG_OK;
1286
upsdebugx(1, "input_phases is %d", input_phases);
1290
if (su_info_p->flags & SU_OUTPHASES) {
1291
upsdebugx(1, "Check output_phases");
1292
if (output_phases == 0) {
1295
if (su_info_p->flags & SU_OUTPUT_1) {
1296
if (output_phases == 1) {
1297
upsdebugx(1, "output_phases is 1");
1298
su_info_p->flags &= ~SU_OUTPHASES;
1300
upsdebugx(1, "output_phases is not 1");
1301
su_info_p->flags &= ~SU_FLAG_OK;
1304
} else if (su_info_p->flags & SU_OUTPUT_3) {
1305
if (output_phases == 3) {
1306
upsdebugx(1, "output_phases is 3");
1307
su_info_p->flags &= ~SU_OUTPHASES;
1309
upsdebugx(1, "output_phases is not 3");
1310
su_info_p->flags &= ~SU_FLAG_OK;
1314
upsdebugx(1, "output_phases is %d", output_phases);
1318
if (su_info_p->flags & SU_BYPPHASES) {
1319
upsdebugx(1, "Check bypass_phases");
1320
if (bypass_phases == 0) {
1323
if (su_info_p->flags & SU_BYPASS_1) {
1324
if (bypass_phases == 1) {
1325
upsdebugx(1, "bypass_phases is 1");
1326
su_info_p->flags &= ~SU_BYPPHASES;
1328
upsdebugx(1, "bypass_phases is not 1");
1329
su_info_p->flags &= ~SU_FLAG_OK;
1332
} else if (su_info_p->flags & SU_BYPASS_3) {
1333
if (input_phases == 3) {
1334
upsdebugx(1, "bypass_phases is 3");
1335
su_info_p->flags &= ~SU_BYPPHASES;
1337
upsdebugx(1, "bypass_phases is not 3");
1338
su_info_p->flags &= ~SU_FLAG_OK;
1342
upsdebugx(1, "bypass_phases is %d", bypass_phases);
1346
/* process outlet template definition */
1347
if (su_info_p->flags & SU_OUTLET) {
1348
upsdebugx(1, "outlet template definition found (%s)...", su_info_p->info_type);
1349
int cur_outlet_number = 1;
1350
int cur_nut_index = 0;
1351
int outlet_count = 0;
1352
snmp_info_t cur_info_p;
1354
if(dstate_getinfo("outlet.count") == NULL) {
1355
/* FIXME: should we disable it?
1356
* su_info_p->flags &= ~SU_FLAG_OK;
1357
* or rely on guestimation? */
1358
if ((outlet_count = guestimate_outlet_count(su_info_p->OID)) == -1) {
1363
/* Publish the count estimation */
1364
dstate_setinfo("outlet.count", "%i", outlet_count);
1368
outlet_count = atoi(dstate_getinfo("outlet.count"));
1371
/* Only instantiate outlets if needed! */
1372
if (outlet_count > 0) {
1373
/* general init of data using the template */
1374
instantiate_info(su_info_p, &cur_info_p);
1376
for (cur_outlet_number = base_snmp_outlet_index(su_info_p->OID) ;
1377
cur_outlet_number < (outlet_count + base_snmp_outlet_index(su_info_p->OID)) ;
1378
cur_outlet_number++)
1380
cur_nut_index = cur_outlet_number + base_nut_outlet_offset();
1381
sprintf((char*)cur_info_p.info_type, su_info_p->info_type,
1384
/* check if default value is also a template */
1385
if ((cur_info_p.dfl != NULL) &&
1386
(strstr(su_info_p->dfl, "%i") != NULL)) {
1387
cur_info_p.dfl = (char *)xmalloc(SU_INFOSIZE);
1388
sprintf((char *)cur_info_p.dfl, su_info_p->dfl, cur_nut_index);
1391
if (cur_info_p.OID != NULL) {
1392
sprintf((char *)cur_info_p.OID, su_info_p->OID, cur_outlet_number);
1394
/* add outlet instant commands to the info database. */
1395
if (SU_TYPE(su_info_p) == SU_TYPE_CMD) {
1396
/* FIXME: only add if "su_ups_get(cur_info_p) == TRUE" */
1397
if (mode == SU_WALKMODE_INIT)
1398
dstate_addcmd(cur_info_p.info_type);
1400
else /* get and process this data */
1401
status = get_and_process_data(mode, &cur_info_p);
1403
/* server side (ABSENT) data */
1404
su_setinfo(&cur_info_p, NULL);
1406
/* set back the flag */
1407
su_info_p->flags = cur_info_p.flags;
1409
free((char*)cur_info_p.info_type);
1410
if (cur_info_p.OID != NULL)
1411
free((char*)cur_info_p.OID);
1412
if ((cur_info_p.dfl != NULL) &&
1413
(strstr(su_info_p->dfl, "%i") != NULL))
1414
free((char*)cur_info_p.dfl);
1417
upsdebugx(1, "No outlet present, discarding template definition...");
1421
/* get and process this data */
1422
status = get_and_process_data(mode, su_info_p);
1424
} /* for (su_info_p... */
1431
bool_t su_ups_get(snmp_info_t *su_info_p)
1433
static char buf[SU_INFOSIZE];
1436
struct snmp_pdu ** pdu_array;
1437
struct snmp_pdu * current_pdu;
1438
alarms_info_t * alarms;
1441
upsdebugx(2, "su_ups_get: %s %s", su_info_p->info_type, su_info_p->OID);
1443
if (!strcasecmp(su_info_p->info_type, "ups.status")) {
1445
status = nut_snmp_get_int(su_info_p->OID, &value);
1448
su_status_set(su_info_p, value);
1449
upsdebugx(2, "=> value: %ld", value);
1451
else upsdebugx(2, "=> Failed");
1456
if (!strcasecmp(su_info_p->info_type, "ups.alarms")) {
1457
status = nut_snmp_get_int(su_info_p->OID, &value);
1458
if (status == TRUE) {
1459
upsdebugx(2, "=> value: %ld", value);
1461
pdu_array = nut_snmp_walk(su_info_p->OID,INT_MAX);
1462
if(pdu_array == NULL) {
1463
upsdebugx(2, "=> Walk failed");
1467
current_pdu = pdu_array[index];
1468
while(current_pdu) {
1469
decode_str(current_pdu,buf,sizeof(buf),NULL);
1470
alarms = alarms_info;
1471
while( alarms->OID ) {
1472
if(!strcmp(buf+1,alarms_info->OID)) {
1473
upsdebugx(3, "Alarm OID %s found => %s", alarms->OID, alarms->info_value);
1474
status_set(alarms->info_value);
1480
current_pdu = pdu_array[index];
1482
nut_snmp_free(pdu_array);
1487
upsdebugx(2, "=> Failed");
1493
/* another special case */
1494
if (!strcasecmp(su_info_p->info_type, "ambient.temperature")) {
1497
status = nut_snmp_get_int(su_info_p->OID, &value);
1499
if(status != TRUE) {
1503
/* only do this if using the IEM sensor */
1504
if (!strcmp(su_info_p->OID, APCC_OID_IEM_TEMP)) {
1508
su = nut_snmp_get_int(APCC_OID_IEM_TEMP_UNIT, &units);
1510
/* no response, or units == F */
1511
if ((su == FALSE) || (units == APCC_IEM_FAHRENHEIT))
1512
temp = (value - 32) / 1.8;
1517
temp = value * su_info_p->info_len;
1520
snprintf(buf, sizeof(buf), "%.1f", temp);
1521
su_setinfo(su_info_p, buf);
1526
if (su_info_p->info_flags == 0) {
1527
status = nut_snmp_get_int(su_info_p->OID, &value);
1528
if (status == TRUE) {
1529
if (su_info_p->flags&SU_FLAG_NEGINVALID && value<0) {
1530
su_info_p->flags &= ~SU_FLAG_OK;
1531
if(su_info_p->flags&SU_FLAG_UNIQUE) {
1532
disable_competition(su_info_p);
1533
su_info_p->flags &= ~SU_FLAG_UNIQUE;
1537
if (su_info_p->flags & SU_FLAG_SETINT) {
1538
upsdebugx(1, "setvar %s", su_info_p->OID);
1539
*su_info_p->setvar = value;
1541
snprintf(buf, sizeof(buf), "%.2f", value * su_info_p->info_len);
1544
status = nut_snmp_get_str(su_info_p->OID, buf, sizeof(buf), su_info_p->oid2info);
1547
if (status == TRUE) {
1548
su_setinfo(su_info_p, buf);
1549
upsdebugx(2, "=> value: %s", buf);
1552
upsdebugx(2, "=> Failed");
1557
/* set r/w INFO_ element to a value. */
1558
int su_setvar(const char *varname, const char *val)
1560
snmp_info_t *su_info_p = NULL;
1562
int retval = STAT_SET_FAILED;
1565
upsdebugx(2, "entering su_setvar(%s, %s)", varname, val);
1567
if (strncmp(varname, "outlet", 6))
1568
su_info_p = su_find_info(varname);
1570
snmp_info_t *tmp_info_p;
1571
char *outlet_number_ptr = strchr(varname, '.');
1572
int outlet_number = atoi(++outlet_number_ptr);
1573
if (dstate_getinfo("outlet.count") == NULL) {
1574
upsdebugx(2, "su_setvar: can't get outlet.count...");
1575
return STAT_SET_INVALID;
1578
upsdebugx(3, "su_setvar: outlet %i / %i", outlet_number,
1579
atoi(dstate_getinfo("outlet.count")));
1581
/* ensure the outlet number is supported (filtered upstream though)! */
1582
if (outlet_number > atoi(dstate_getinfo("outlet.count"))) {
1583
/* out of bound outlet number */
1584
upsdebugx(2, "su_setvar: outlet is out of bound (%i / %s)",
1585
outlet_number, dstate_getinfo("outlet.count"));
1586
return STAT_SET_INVALID;
1588
/* find back the outlet template */
1589
char *outlet_varname = (char *)xmalloc(SU_INFOSIZE);
1590
sprintf(outlet_varname, "outlet.%s%s", "%i", strchr(outlet_number_ptr++, '.'));
1591
upsdebugx(3, "su_setvar: searching for template\"%s\"", outlet_varname);
1592
tmp_info_p = su_find_info(outlet_varname);
1593
free(outlet_varname);
1595
/* for an snmp_info_t instance */
1596
su_info_p = instantiate_info(tmp_info_p, su_info_p);
1598
/* check if default value is also a template */
1599
if ((su_info_p->dfl != NULL) &&
1600
(strstr(tmp_info_p->dfl, "%i") != NULL)) {
1601
su_info_p->dfl = (char *)xmalloc(SU_INFOSIZE);
1602
sprintf((char *)su_info_p->dfl, tmp_info_p->dfl,
1603
outlet_number - base_nut_outlet_offset());
1606
if (su_info_p->OID != NULL) {
1607
sprintf((char *)su_info_p->OID, tmp_info_p->OID,
1608
outlet_number - base_nut_outlet_offset());
1610
/* else, don't return STAT_SET_INVALID since we can be setting
1611
* a server side variable! */
1613
/* adapt info_type */
1614
if (su_info_p->info_type != NULL)
1615
sprintf((char *)su_info_p->info_type, "%s", varname);
1618
if (!su_info_p || !su_info_p->info_type || !(su_info_p->flags & SU_FLAG_OK)) {
1619
upsdebugx(2, "su_setvar: info element unavailable %s", varname);
1621
if (!strncmp(varname, "outlet", 6))
1622
free_info(su_info_p);
1624
return STAT_SET_UNKNOWN;
1627
if (!(su_info_p->info_flags & ST_FLAG_RW) || su_info_p->OID == NULL) {
1628
upsdebugx(2, "su_setvar: not writable %s", varname);
1630
if (!strncmp(varname, "outlet", 6))
1631
free_info(su_info_p);
1633
return STAT_SET_INVALID;
1636
/* set value into the device */
1637
if (su_info_p->info_flags & ST_FLAG_STRING) {
1638
status = nut_snmp_set_str(su_info_p->OID, val);
1640
/* non string data may imply a value lookup */
1641
if (su_info_p->oid2info) {
1642
value = su_find_valinfo(su_info_p->oid2info, val);
1645
value = strtol(val, NULL, 0);
1647
/* Actually apply the new value */
1648
status = nut_snmp_set_int(su_info_p->OID, value);
1651
if (status == FALSE)
1652
upsdebugx(1, "su_setvar: cannot set value %s for %s", val, su_info_p->OID);
1654
retval = STAT_SET_HANDLED;
1655
upsdebugx(1, "su_setvar: successfully set %s to \"%s\"", varname, val);
1657
/* update info array */
1658
su_setinfo(su_info_p, val);
1660
if (!strncmp(varname, "outlet", 6))
1661
free_info(su_info_p);
1666
/* process instant command and take action. */
1667
int su_instcmd(const char *cmdname, const char *extradata)
1669
snmp_info_t *su_info_p = NULL;
1671
int retval = STAT_INSTCMD_FAILED;
1674
upsdebugx(2, "entering su_instcmd(%s, %s)", cmdname, extradata);
1676
/* FIXME: this should only apply if strchr(%)! */
1677
if (strncmp(cmdname, "outlet", 6)) {
1678
su_info_p = su_find_info(cmdname);
1681
snmp_info_t *tmp_info_p;
1682
char *outlet_number_ptr = strchr(cmdname, '.');
1683
int outlet_number = atoi(++outlet_number_ptr);
1684
if (dstate_getinfo("outlet.count") == NULL) {
1685
upsdebugx(2, "su_instcmd: can't get outlet.count...");
1686
return STAT_INSTCMD_UNKNOWN;
1689
upsdebugx(3, "su_instcmd: outlet %i / %i", outlet_number,
1690
atoi(dstate_getinfo("outlet.count")));
1692
/* ensure the outlet number is supported! */
1693
if (outlet_number > atoi(dstate_getinfo("outlet.count"))) {
1694
/* out of bound outlet number */
1695
upsdebugx(2, "su_instcmd: outlet is out of bound (%i / %s)",
1696
outlet_number, dstate_getinfo("outlet.count"));
1697
return STAT_INSTCMD_INVALID;
1700
/* find back the outlet template */
1701
char *outlet_cmdname = (char *)xmalloc(SU_INFOSIZE);
1702
sprintf(outlet_cmdname, "outlet.%s%s", "%i", strchr(outlet_number_ptr++, '.'));
1703
upsdebugx(3, "su_instcmd: searching for template\"%s\"", outlet_cmdname);
1704
tmp_info_p = su_find_info(outlet_cmdname);
1705
free(outlet_cmdname);
1707
/* for an snmp_info_t instance */
1708
su_info_p = instantiate_info(tmp_info_p, su_info_p);
1710
/* check if default value is also a template */
1711
if ((su_info_p->dfl != NULL) &&
1712
(strstr(tmp_info_p->dfl, "%i") != NULL)) {
1713
su_info_p->dfl = (char *)xmalloc(SU_INFOSIZE);
1714
sprintf((char *)su_info_p->dfl, tmp_info_p->dfl,
1715
outlet_number - base_nut_outlet_offset());
1718
if (su_info_p->OID != NULL) {
1719
/* Workaround buggy Eaton Pulizzi implementation
1720
* which have different offsets index for data & commands! */
1721
if (su_info_p->flags & SU_CMD_OFFSET) {
1722
upsdebugx(3, "Adding command offset");
1726
sprintf((char *)su_info_p->OID, tmp_info_p->OID,
1727
outlet_number - base_nut_outlet_offset() + cmd_offset);
1729
free_info(su_info_p);
1730
return STAT_INSTCMD_UNKNOWN;
1735
if (!su_info_p || !su_info_p->info_type || !(su_info_p->flags & SU_FLAG_OK)) {
1737
/* Check for composite commands */
1738
if (!strcasecmp(cmdname, "load.on")) {
1739
return su_instcmd("load.on.delay", "0");
1742
if (!strcasecmp(cmdname, "load.off")) {
1743
return su_instcmd("load.off.delay", "0");
1746
if (!strcasecmp(cmdname, "shutdown.return")) {
1749
/* Ensure "ups.start.auto" is set to "yes", if supported */
1750
if (dstate_getinfo("ups.start.auto")) {
1751
su_setvar("ups.start.auto", "yes");
1754
ret = su_instcmd("load.on.delay", dstate_getinfo("ups.delay.start"));
1755
if (ret != STAT_INSTCMD_HANDLED) {
1759
return su_instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
1762
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
1765
/* Ensure "ups.start.auto" is set to "no", if supported */
1766
if (dstate_getinfo("ups.start.auto")) {
1767
su_setvar("ups.start.auto", "no");
1770
ret = su_instcmd("load.on.delay", "-1");
1771
if (ret != STAT_INSTCMD_HANDLED) {
1775
return su_instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));
1778
upsdebugx(2, "su_instcmd: %s unavailable", cmdname);
1780
if (!strncmp(cmdname, "outlet", 6))
1781
free_info(su_info_p);
1783
return STAT_INSTCMD_UNKNOWN;
1786
/* set value, using the provided one, or the default one otherwise */
1787
if (su_info_p->info_flags & ST_FLAG_STRING) {
1788
status = nut_snmp_set_str(su_info_p->OID, extradata ? extradata : su_info_p->dfl);
1790
status = nut_snmp_set_int(su_info_p->OID, extradata ? atoi(extradata) : su_info_p->info_len);
1793
if (status == FALSE)
1794
upsdebugx(1, "su_instcmd: cannot set value for %s", cmdname);
1796
retval = STAT_INSTCMD_HANDLED;
1797
upsdebugx(1, "su_instcmd: successfully sent command %s", cmdname);
1800
if (!strncmp(cmdname, "outlet", 6))
1801
free_info(su_info_p);
1806
/* FIXME: the below functions can be removed since these were for loading
1807
* the mib2nut information from a file instead of the .h definitions... */
1808
/* return 1 if usable, 0 if not */
1809
static int parse_mibconf_args(int numargs, char **arg)
1813
/* everything below here uses up through arg[1] */
1817
/* <info type> <info flags> <info len> <OID name> <default value> <value lookup> */
1819
/* special case for setting some OIDs value at driver startup */
1820
if (!strcmp(arg[0], "init")) {
1822
if (!strcmp(arg[1], "str")) {
1823
ret = nut_snmp_set_str(arg[3], arg[4]);
1825
ret = nut_snmp_set_int(arg[3], strtol(arg[4], NULL, 0));
1829
upslogx(LOG_ERR, "su_setvar: cannot set value %s for %s", arg[4], arg[3]);
1831
upsdebugx(1, "su_setvar: successfully set %s to \"%s\"", arg[0], arg[4]);
1836
/* TODO: create the lookup table */
1837
upsdebugx(2, "%s, %s, %s, %s, %s, %s", arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]);
1841
/* called for fatal errors in parseconf like malloc failures */
1842
static void mibconf_err(const char *errmsg)
1844
upslogx(LOG_ERR, "Fatal error in parseconf (*mib.conf): %s", errmsg);
1846
/* load *mib.conf into an snmp_info_t structure */
1847
void read_mibconf(char *mib)
1852
upsdebugx(2, "SNMP UPS driver : entering read_mibconf(%s)", mib);
1854
snprintf(fn, sizeof(fn), "%s/snmp/%s.conf", CONFPATH, mib);
1856
pconf_init(&ctx, mibconf_err);
1858
if (!pconf_file_begin(&ctx, fn))
1859
fatalx(EXIT_FAILURE, "%s", ctx.errmsg);
1861
while (pconf_file_next(&ctx)) {
1862
if (pconf_parse_error(&ctx)) {
1863
upslogx(LOG_ERR, "Parse error: %s:%d: %s",
1864
fn, ctx.linenum, ctx.errmsg);
1868
if (ctx.numargs < 1)
1871
if (!parse_mibconf_args(ctx.numargs, ctx.arglist)) {
1873
char errmsg[SMALLBUF];
1875
snprintf(errmsg, sizeof(errmsg),
1876
"mib.conf: invalid directive");
1878
for (i = 0; i < ctx.numargs; i++)
1879
snprintfcat(errmsg, sizeof(errmsg), " %s",
1882
upslogx(LOG_WARNING, "%s", errmsg);