Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
build/
dist/
__pycache__/
*.py[co]
.*.swp
.*.swo
*.egg-info/
6 changes: 6 additions & 0 deletions netsnmp/client_intf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,12 @@ netsnmp_create_session_v3(PyObject *self, PyObject *args)
end:
free (session.securityEngineID);
free (session.contextEngineID);
/* snmp_sess_open() duplicates the security protocol OIDs into the session
it returns, so the copies made above are ours to release. Freeing them
here also covers the early "goto end" paths, where the privacy protocol
is rejected after the authentication protocol was already duplicated. */
free (session.securityAuthProto);
free (session.securityPrivProto);

if (PyErr_Occurred()) {
return NULL;
Expand Down
15 changes: 15 additions & 0 deletions netsnmp/tests/system/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@
WRITE_ARGS = READ_ARGS.copy()
WRITE_ARGS['Community'] = 'private'

# SNMPv3 authPriv credentials. Keep in sync with the createUser/rwuser
# directives in netsnmp/tests/system/snmpd.conf.
V3_ARGS = {
'Version': 3,
'DestHost': HOST,
'SecLevel': 'authPriv',
'SecName': 'testuser',
'AuthProto': 'SHA',
'AuthPass': 'auth_pass',
'PrivProto': 'AES',
'PrivPass': 'priv_pass',
'Timeout': 1000000,
'Retries': 0,
}

SYS_DESCR = '.1.3.6.1.2.1.1.1'
SYS_UPTIME = '.1.3.6.1.2.1.1.3'
SYS_LOCATION = '.1.3.6.1.2.1.1.6'
Expand Down
14 changes: 14 additions & 0 deletions netsnmp/tests/system/snmpd.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
agentAddress udp:127.0.0.1:1161
rocommunity public 127.0.0.1
rwcommunity private 127.0.0.1

# SNMPv3 USM user used by test_v3.py. Passphrases must be at least 8
# characters for net-snmp to accept them. Keep in sync with V3_ARGS in
# netsnmp/tests/system/common.py.
createUser testuser SHA "auth_pass" AES "priv_pass"
rwuser testuser authPriv

# Only ever contacted with a deliberately wrong passphrase, by
# test_wrong_auth_password_is_rejected. It must not be used by any passing
# test: net-snmp caches localized USM keys per (engineID, username) for the
# life of the process, so a successful auth would prime that cache and mask
# the rejection we are asserting.
createUser baduser SHA "correct_auth_pass" AES "correct_priv_pass"
rouser baduser authPriv
73 changes: 73 additions & 0 deletions netsnmp/tests/system/test_v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import unittest

import netsnmp

from netsnmp.tests.system.common import (
SYS_DESCR,
SYS_LOCATION,
SYSTEM,
V3_ARGS,
assert_value,
)


class SnmpV3Tests(unittest.TestCase):
"""SNMPv3 authPriv coverage (USM authentication and privacy)."""

def test_get(self):
values = netsnmp.snmpget(netsnmp.Varbind(SYS_DESCR, '0'), **V3_ARGS)
assert_value(self, values)

def test_getnext(self):
values = netsnmp.snmpgetnext(netsnmp.Varbind(SYSTEM), **V3_ARGS)
assert_value(self, values)

def test_getbulk(self):
session = netsnmp.Session(**V3_ARGS)
varlist = netsnmp.VarList(netsnmp.Varbind(SYSTEM))
assert_value(self, session.getbulk(0, 4, varlist))
self.assertGreater(len(varlist), 1)

def test_set(self):
value = b'snmp-v3'
result = netsnmp.snmpset(
netsnmp.Varbind(SYS_LOCATION, '0', value, 'OCTETSTR'), **V3_ARGS)
self.assertEqual(result, 1)
self.assertEqual(netsnmp.snmpget(
netsnmp.Varbind(SYS_LOCATION, '0'), **V3_ARGS)[0], value)

def test_walk(self):
varlist = netsnmp.VarList(netsnmp.Varbind(SYSTEM))
assert_value(self, netsnmp.snmpwalk(varlist, **V3_ARGS))
self.assertGreater(len(varlist), 1)

def test_session_get(self):
session = netsnmp.Session(**V3_ARGS)
varlist = netsnmp.VarList(netsnmp.Varbind(SYS_DESCR, '0'))
assert_value(self, session.get(varlist))

def test_unknown_user_is_rejected(self):
session = netsnmp.Session(**dict(V3_ARGS, SecName='nosuchuser'))
varlist = netsnmp.VarList(netsnmp.Varbind(SYS_DESCR, '0'))
values = session.get(varlist)
self.assertFalse(values and values[0])

def test_wrong_auth_password_is_rejected(self):
"""Proves the agent really enforces authPriv, so the tests above are
not silently passing over an unauthenticated session.

Uses a dedicated user rather than the one above: net-snmp caches
localized USM keys per (engineID, username) process-wide, so reusing
a name that already authenticated successfully would reuse the good
key and the wrong passphrase would never reach the agent.
"""
session = netsnmp.Session(
**dict(V3_ARGS, SecName='baduser', AuthPass='wrong_auth_pass',
PrivPass='wrong_priv_pass'))
varlist = netsnmp.VarList(netsnmp.Varbind(SYS_DESCR, '0'))
values = session.get(varlist)
self.assertFalse(values and values[0])


if __name__ == '__main__':
unittest.main()
Loading
Loading