1 module snmp.v3;
2 
3 import c.net_snmp;
4 import snmp.types;
5 import snmp.oid;
6 
7 private alias size_t = object.size_t;
8 import snmp.session : SNMPSession;
9 import snmp.pdu : SNMPResponse;
10 
11 enum AuthProtocol { none, md5, sha1, sha256, sha384, sha512 }
12 enum PrivProtocol { none, des, aes128, aes192, aes256 }
13 
14 enum SecurityLevel : int
15 {
16     noAuthNoPriv = SNMP_SEC_LEVEL_NOAUTH,
17     authNoPriv   = SNMP_SEC_LEVEL_AUTHNOPRIV,
18     authPriv     = SNMP_SEC_LEVEL_AUTHPRIV,
19 }
20 
21 private void authProtoOID(AuthProtocol p, out const(oid)* optr, out size_t olen) @trusted @nogc nothrow
22 {
23     final switch (p)
24     {
25     case AuthProtocol.none:
26         optr = usmNoAuthProtocol.ptr;          olen = usmNoAuthProtocol.length;          break;
27     case AuthProtocol.md5:
28         optr = usmHMACMD5AuthProtocol.ptr;     olen = usmHMACMD5AuthProtocol.length;     break;
29     case AuthProtocol.sha1:
30         optr = usmHMACSHA1AuthProtocol.ptr;    olen = usmHMACSHA1AuthProtocol.length;    break;
31     case AuthProtocol.sha256:
32         optr = usmHMAC192SHA256AuthProtocol.ptr; olen = usmHMAC192SHA256AuthProtocol.length; break;
33     case AuthProtocol.sha384:
34         optr = usmHMAC384SHA512AuthProtocol.ptr; olen = usmHMAC384SHA512AuthProtocol.length; break;
35     case AuthProtocol.sha512:
36         optr = usmHMAC384SHA512AuthProtocol.ptr; olen = usmHMAC384SHA512AuthProtocol.length; break;
37     }
38 }
39 
40 private void privProtoOID(PrivProtocol p, out const(oid)* optr, out size_t olen) @trusted @nogc nothrow
41 {
42     final switch (p)
43     {
44     case PrivProtocol.none:
45         optr = usmNoPrivProtocol.ptr;   olen = usmNoPrivProtocol.length;   break;
46     case PrivProtocol.des:
47         optr = usmDESPrivProtocol.ptr;  olen = usmDESPrivProtocol.length;  break;
48     case PrivProtocol.aes128:
49         optr = usmAESPrivProtocol.ptr;  olen = usmAESPrivProtocol.length;  break;
50     case PrivProtocol.aes192:
51         version (OSX) { optr = usmAESPrivProtocol.ptr; olen = usmAESPrivProtocol.length; }
52         else           { optr = usmAES192PrivProtocol.ptr; olen = usmAES192PrivProtocol.length; }
53         break;
54     case PrivProtocol.aes256:
55         version (OSX) { optr = usmAESPrivProtocol.ptr; olen = usmAESPrivProtocol.length; }
56         else           { optr = usmAES256PrivProtocol.ptr; olen = usmAES256PrivProtocol.length; }
57         break;
58     }
59 }
60 
61 /// SNMPv3/USM session. RAII via SNMPSession base destructor.
62 struct SNMPV3Session
63 {
64     SNMPSession base;
65     alias base this;
66 
67     @disable this(this);
68 
69     static SNMPV3Session open(
70         scope const(char)* peername,
71         scope const(char)* user,
72         AuthProtocol auth      = AuthProtocol.sha1,
73         scope const(char)* authPass = null,
74         PrivProtocol priv      = PrivProtocol.none,
75         scope const(char)* privPass = null,
76         SecurityLevel level    = SecurityLevel.authNoPriv) @trusted @nogc nothrow
77     {
78         SNMPV3Session s;
79 
80         if (!peername || !user || !authPass)
81             return s;
82 
83         import core.stdc.string : strlen;
84 
85         netsnmp_session sess;
86         snmp_sess_init(&sess);
87 
88         // `version` is a D keyword — access via __traits
89         __traits(getMember, sess, "version") = cast(long) SNMP_VERSION_3;
90         sess.peername        = cast(char*) peername;
91         sess.securityName    = cast(char*) user;
92         sess.securityNameLen = strlen(user);
93         sess.securityLevel   = cast(int) level;
94 
95         const(oid)* authOIDPtr;
96         size_t authOIDLen;
97         authProtoOID(auth, authOIDPtr, authOIDLen);
98         sess.securityAuthProto    = cast(oid*) authOIDPtr;
99         sess.securityAuthProtoLen = authOIDLen;
100         sess.securityAuthKeyLen   = USM_AUTH_KU_LEN;
101 
102         if (auth != AuthProtocol.none && authPass)
103         {
104             if (generate_Ku(sess.securityAuthProto,
105                     cast(uint) sess.securityAuthProtoLen,
106                     cast(ubyte*) authPass, strlen(authPass),
107                     sess.securityAuthKey.ptr,
108                     &sess.securityAuthKeyLen) != SNMPERR_SUCCESS)
109                 return s;
110         }
111 
112         const(oid)* privOIDPtr;
113         size_t privOIDLen;
114         privProtoOID(priv, privOIDPtr, privOIDLen);
115         sess.securityPrivProto    = cast(oid*) privOIDPtr;
116         sess.securityPrivProtoLen = privOIDLen;
117         sess.securityPrivKeyLen   = USM_PRIV_KU_LEN;
118 
119         if (priv != PrivProtocol.none && privPass)
120         {
121             if (generate_Ku(sess.securityAuthProto,
122                     cast(uint) sess.securityAuthProtoLen,
123                     cast(ubyte*) privPass, strlen(privPass),
124                     sess.securityPrivKey.ptr,
125                     &sess.securityPrivKeyLen) != SNMPERR_SUCCESS)
126                 return s;
127         }
128 
129         s.base.handle = snmp_open(&sess);
130         return s;
131     }
132 
133     static SNMPV3Session open(
134         string peername,
135         string user,
136         AuthProtocol auth  = AuthProtocol.sha1,
137         string authPass    = null,
138         PrivProtocol priv  = PrivProtocol.none,
139         string privPass    = null,
140         SecurityLevel level = SecurityLevel.authNoPriv) @trusted nothrow
141     {
142         import std.string : toStringz;
143 
144         return open(peername.toStringz, user.toStringz, auth,
145             authPass ? authPass.toStringz : null, priv,
146             privPass ? privPass.toStringz : null, level);
147     }
148 }
149 
150 unittest
151 {
152     init_snmp("snmp-d-v3-test");
153 
154     auto s = SNMPV3Session.open(
155         cast(const(char)*) null, "noUser",
156         AuthProtocol.sha1, "badPass",
157         PrivProtocol.none, cast(const(char)*) null,
158         SecurityLevel.authNoPriv);
159     assert(!s.isOpen);
160 
161     auto s2 = SNMPV3Session.open(
162         "udp:127.0.0.1:161", "noUser",
163         AuthProtocol.sha1, "short",
164         PrivProtocol.none, cast(const(char)*) null,
165         SecurityLevel.authNoPriv);
166     s2.close();
167 }