Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ https://github.com/networkupstools/nut/milestone/13
`ups.realpower`. [issue #3580, PR #3581]

- `snmp-ups` driver updates:
* Fixed a NULL pointer dereference when setting exact outlet mappings
without a corresponding numbered outlet template, and restored handling
of exact server-side outlet variables and commands. [issue #3360,
PR #3610]
* Extended the XPPC-MIB subdriver (enterprise 935) to expose
`battery.runtime`, `battery.voltage`, `input.frequency`,
`output.voltage.nominal` and `ups.firmware.aux` from Phoenixtec
Expand Down
28 changes: 23 additions & 5 deletions drivers/snmp-ups.c
Original file line number Diff line number Diff line change
Expand Up @@ -3957,6 +3957,7 @@ static int su_setOID(int mode, const char *varname, const char *val)
/* normal (default), outlet, or outlet group variable */
snmp_info_flags_t vartype = 0;
int daisychain_device_number = -1;
bool_t is_template_instance = FALSE;
/* variable without the potential "device.X" prefix, to find the template */
char *tmp_varname = NULL;
const char *val_practical = NULL;
Expand Down Expand Up @@ -4042,9 +4043,9 @@ static int su_setOID(int mode, const char *varname, const char *val)
return (mode==SU_MODE_INSTCMD ? (int)STAT_INSTCMD_INVALID : (int)STAT_SET_INVALID);
}

/* Check if it is outlet / outlet.group, or standard variable */
if (strncmp(tmp_varname, "outlet", 6)) {
su_info_p = su_find_info(tmp_varname);
/* Check for an exact match before treating outlet names as templates */
su_info_p = su_find_info(tmp_varname);
if (su_info_p || strncmp(tmp_varname, "outlet", 6)) {
/* what if e.g. "device.x.contact" is not found as a "contact"? */
if (!su_info_p && strcmp(tmp_varname, varname)) {
upsdebugx(2,
Expand Down Expand Up @@ -4117,9 +4118,14 @@ static int su_setOID(int mode, const char *varname, const char *val)
upsdebugx(3, "%s: searching for template\"%s\"", __func__, item_varname);
tmp_info_p = su_find_info(item_varname);
free(item_varname);
if (!tmp_info_p) {
free(tmp_varname);
return (mode==SU_MODE_INSTCMD ? (int)STAT_INSTCMD_UNKNOWN : (int)STAT_SET_UNKNOWN);
}

/* for an snmp_info_t instance */
su_info_p = instantiate_info(tmp_info_p, su_info_p);
is_template_instance = TRUE;

/* check if default value is also a template */
if ((su_info_p->dfl != NULL) &&
Expand Down Expand Up @@ -4197,7 +4203,8 @@ static int su_setOID(int mode, const char *varname, const char *val)
upsdebugx(2, "%s: info element unavailable %s", __func__, varname);

/* Free template (outlet and outlet.group) */
free_info(su_info_p);
if (is_template_instance)
free_info(su_info_p);

if (tmp_varname != NULL)
free(tmp_varname);
Expand All @@ -4216,6 +4223,16 @@ static int su_setOID(int mode, const char *varname, const char *val)
}

val_practical = val ? val : su_info_p->dfl;
if (mode == SU_MODE_SETVAR
&& (su_info_p->info_flags & ST_FLAG_RW)
&& (su_info_p->flags & SU_FLAG_ABSENT)
&& su_info_p->OID == NULL
) {
dstate_setinfo(varname, "%s", val_practical);
retval = STAT_SET_HANDLED;
goto cleanup;
}

if (su_info_p->info_flags & ST_FLAG_STRING) {
status = nut_snmp_set_str(su_info_p->OID, val_practical);
}
Expand Down Expand Up @@ -4279,8 +4296,9 @@ static int su_setOID(int mode, const char *varname, const char *val)
}
}

cleanup:
/* Free template (outlet and outlet.group) */
if (!strncmp(tmp_varname, "outlet", 6))
if (is_template_instance)
free_info(su_info_p);
free(tmp_varname);

Expand Down
40 changes: 40 additions & 0 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,46 @@ endif ENABLE_SHARED_PRIVATE_LIBS
driver_methods_utest_LDADD += $(top_builddir)/drivers/libdummy_mockdrv.la
driver_methods_utest_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/tests -DDRIVERS_MAIN_WITHOUT_MAIN=1

if WITH_SNMP
SNMP_UPS_TEST_DRIVER_SOURCES = snmp-ups.c snmp-ups-helpers.c \
apc-mib.c apc-pdu-mib.c apc-epdu-mib.c \
baytech-mib.c baytech-rpc3nc-mib.c bestpower-mib.c \
compaq-mib.c cyberpower-mib.c \
delta_ups-mib.c \
eaton-pdu-genesis2-mib.c eaton-pdu-marlin-mib.c eaton-pdu-marlin-helpers.c \
eaton-pdu-pulizzi-mib.c eaton-pdu-revelation-mib.c eaton-pdu-nlogic-mib.c \
eaton-ats16-nmc-mib.c eaton-ats16-nm2-mib.c apc-ats-mib.c eaton-ats30-mib.c \
eaton-ups-pwnm2-mib.c eaton-ups-pxg-mib.c \
emerson-avocent-pdu-mib.c \
hpe-pdu-mib.c hpe-pdu3-cis-mib.c huawei-mib.c \
ietf-mib.c \
mge-mib.c \
netvision-mib.c \
raritan-pdu-mib.c raritan-px2-mib.c \
vertiv-mib.c voltronic-mib.c \
xppc-mib.c

LINKED_SOURCE_FILES += $(SNMP_UPS_TEST_DRIVER_SOURCES)

$(SNMP_UPS_TEST_DRIVER_SOURCES):
test -s '$@' || ln -s -f "$(top_srcdir)/drivers/$@" '$@'

TESTS += snmp-ups-setvar-test
snmp_ups_setvar_test_SOURCES = snmp-ups-setvar-test.c
nodist_snmp_ups_setvar_test_SOURCES = $(SNMP_UPS_TEST_DRIVER_SOURCES)
snmp_ups_setvar_test_CFLAGS = $(AM_CFLAGS) $(LIBNETSNMP_CFLAGS)
snmp_ups_setvar_test_LDADD = $(top_builddir)/drivers/libdummy_mockdrv.la $(LIBNETSNMP_LIBS) -lm
snmp_ups_setvar_test_LDFLAGS = $(AM_LDFLAGS)
if WITH_SSL
if !WITH_OPENSSL
snmp_ups_setvar_test_CFLAGS += -UNETSNMP_USE_OPENSSL
endif !WITH_OPENSSL
snmp_ups_setvar_test_CFLAGS += $(LIBSSL_CFLAGS)
snmp_ups_setvar_test_LDADD += $(LIBSSL_LIBS)
snmp_ups_setvar_test_LDFLAGS += $(LIBSSL_LDFLAGS_RPATH)
endif WITH_SSL
endif WITH_SNMP

### Optional tests which can not be built everywhere
# List of src files for CppUnit tests
CPPUNITTESTSRC = example.cpp nutclienttest.cpp
Expand Down
75 changes: 75 additions & 0 deletions tests/snmp-ups-setvar-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* snmp-ups-setvar-test.c - focused tests for SNMP outlet settings
*
* Copyright (C) 2026 Network UPS Tools contributors
*
* 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 "config.h"
#include "main.h"
#include "dstate.h"
#include "snmp-ups.h"

static int expect_result(const char *name, int actual, int expected)
{
if (actual == expected)
return 0;

fprintf(stderr, "%s: got %d, expected %d\n", name, actual, expected);
return 1;
}

int main(void)
{
static snmp_info_t test_snmp_info[] = {
snmp_info_default("outlet.desc", ST_FLAG_RW | ST_FLAG_STRING,
20, NULL, "All outlets", SU_FLAG_ABSENT | SU_FLAG_OK, NULL),
snmp_info_default("outlet.%i.desc", ST_FLAG_RW | ST_FLAG_STRING,
SU_INFOSIZE, "not-an-oid.%i", NULL, SU_OUTLET | SU_FLAG_OK, NULL),
snmp_info_default("outlet.unavailable", ST_FLAG_RW | ST_FLAG_STRING,
20, NULL, "Unavailable", SU_FLAG_ABSENT, NULL),
snmp_info_default("outlet.load.off", 0, 1,
"not-an-oid", "0", SU_TYPE_CMD | SU_FLAG_OK, NULL),
snmp_info_sentinel
};
const char *value;
int failed = 0;

snmp_info = test_snmp_info;
dstate_setinfo("outlet.count", "%d", 1);
dstate_setinfo("outlet.desc", "%s", "All outlets");

failed += expect_result("server-side outlet.desc",
su_setvar("outlet.desc", "All rack outlets"), STAT_SET_HANDLED);
value = dstate_getinfo("outlet.desc");
if (!value || strcmp(value, "All rack outlets")) {
fprintf(stderr, "server-side outlet.desc: got '%s'\n", NUT_STRARG(value));
failed++;
}

failed += expect_result("numbered outlet template",
su_setvar("outlet.1.desc", "Rack outlet 1"), STAT_SET_FAILED);
failed += expect_result("unknown numbered outlet template",
su_setvar("outlet.1.missing", "unused"), STAT_SET_UNKNOWN);
failed += expect_result("unavailable exact outlet mapping",
su_setvar("outlet.unavailable", "unused"), STAT_SET_UNKNOWN);
failed += expect_result("exact outlet command",
su_instcmd("outlet.load.off", NULL), STAT_INSTCMD_FAILED);

dstate_free();
snmp_info = NULL;

return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
Loading