1 /*
2  * Copyright 2008 Juan Lang
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 #include <stdio.h>
19 #include <stdarg.h>
20 #include <windef.h>
21 #include <winbase.h>
22 #include <snmp.h>
23 
24 #include "wine/test.h"
25 
26 static BOOL (WINAPI *pSnmpExtensionInit)(DWORD, HANDLE*, AsnObjectIdentifier*);
27 static BOOL (WINAPI *pSnmpExtensionQuery)(BYTE, SnmpVarBindList*, AsnInteger32*, AsnInteger32*);
28 
29 static HMODULE init_test_functions(void)
30 {
31     HMODULE mod = LoadLibraryA("inetmib1");
32 
33     ok(mod != NULL, "failed to load inetmib1.dll\n");
34 
35     if (!mod) return NULL;
36 
37     pSnmpExtensionInit = (void *)GetProcAddress(mod, "SnmpExtensionInit");
38     pSnmpExtensionQuery = (void *)GetProcAddress(mod, "SnmpExtensionQuery");
39 
40     return mod;
41 }
42 
43 static void uninit_test_functions(HMODULE mod)
44 {
45     FreeLibrary(mod);
46 }
47 
48 static void testInit(void)
49 {
50     BOOL ret;
51     HANDLE event;
52     AsnObjectIdentifier oid;
53 
54     if (!pSnmpExtensionInit)
55     {
56         win_skip("no SnmpExtensionInit\n");
57         return;
58     }
59 
60 if (0) /* crashes on native */
61 {
62     ret = pSnmpExtensionInit(0, NULL, NULL);
63     ret = pSnmpExtensionInit(0, NULL, &oid);
64     ret = pSnmpExtensionInit(0, &event, NULL);
65 }
66 
67     ret = pSnmpExtensionInit(0, &event, &oid);
68     ok(ret, "SnmpExtensionInit failed: %d\n", GetLastError());
69     ok(!strcmp("1.3.6.1.2.1.1", SnmpUtilOidToA(&oid)),
70         "Expected 1.3.6.1.2.1.1, got %s\n", SnmpUtilOidToA(&oid));
71 
72 
73 if (0)
74 {
75     /* Fails when called on win8, documentation suggests that
76        extension itself is responsible for freeing this oid */
77     SnmpUtilOidFree(&oid);
78 }
79 }
80 
81 static void testQuery(void)
82 {
83     BOOL ret, moreData, noChange;
84     SnmpVarBindList list;
85     AsnInteger32 error, index;
86     UINT bogus[] = { 1,2,3,4 };
87     UINT mib2System[] = { 1,3,6,1,2,1,1 };
88     UINT mib2If[] = { 1,3,6,1,2,1,2 };
89     UINT mib2IfTable[] = { 1,3,6,1,2,1,2,2 };
90     UINT mib2IfDescr[] = { 1,3,6,1,2,1,2,2,1,2 };
91     UINT mib2IfAdminStatus[] = { 1,3,6,1,2,1,2,2,1,7 };
92     UINT mib2IfOperStatus[] = { 1,3,6,1,2,1,2,2,1,8 };
93     UINT mib2IpAddr[] = { 1,3,6,1,2,1,4,20,1,1 };
94     UINT mib2IpRouteTable[] = { 1,3,6,1,2,1,4,21,1,1 };
95     UINT mib2UdpTable[] = { 1,3,6,1,2,1,7,5,1,1 };
96     SnmpVarBind vars[3], vars2[3], vars3[3];
97     UINT entry;
98 
99     if (!pSnmpExtensionQuery)
100     {
101         win_skip("couldn't find SnmpExtensionQuery\n");
102         return;
103     }
104 
105 if (0) /* crashes on native */
106 {
107     ret = pSnmpExtensionQuery(0, NULL, NULL, NULL);
108     ret = pSnmpExtensionQuery(0, NULL, &error, NULL);
109     ret = pSnmpExtensionQuery(0, NULL, NULL, &index);
110     ret = pSnmpExtensionQuery(0, &list, NULL, NULL);
111     ret = pSnmpExtensionQuery(0, &list, &error, NULL);
112 }
113     /* An empty list succeeds */
114     list.len = 0;
115     error = 0xdeadbeef;
116     index = 0xdeadbeef;
117     ret = pSnmpExtensionQuery(SNMP_PDU_GET, &list, &error, &index);
118     ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
119     ok(error == SNMP_ERRORSTATUS_NOERROR,
120         "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
121     ok(index == 0, "expected index 0, got %d\n", index);
122 
123     /* Oddly enough, this "succeeds," even though the OID is clearly
124      * unsupported.
125      */
126     vars[0].name.idLength = sizeof(bogus) / sizeof(bogus[0]);
127     vars[0].name.ids = bogus;
128     vars[0].value.asnType = 0;
129     list.len = 1;
130     list.list = vars;
131     SetLastError(0xdeadbeef);
132     error = 0xdeadbeef;
133     index = 0xdeadbeef;
134     ret = pSnmpExtensionQuery(SNMP_PDU_GET, &list, &error, &index);
135     ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
136     ok(error == SNMP_ERRORSTATUS_NOERROR ||
137         broken(error == ERROR_FILE_NOT_FOUND) /* NT4 */,
138         "expected SNMP_ERRORSTATUS_NOERROR or ERROR_FILE_NOT_FOUND, got %d\n",
139         error);
140     if (error == SNMP_ERRORSTATUS_NOERROR)
141         ok(index == 0, "expected index 0, got %d\n", index);
142     else if (error == ERROR_FILE_NOT_FOUND)
143         ok(index == 1, "expected index 1, got %d\n", index);
144     /* The OID isn't changed either: */
145     ok(!strcmp("1.2.3.4", SnmpUtilOidToA(&vars[0].name)),
146         "expected 1.2.3.4, got %s\n", SnmpUtilOidToA(&vars[0].name));
147 
148     /* The table is not an accessible variable, so it fails */
149     vars[0].name.idLength = sizeof(mib2IfTable) / sizeof(mib2IfTable[0]);
150     vars[0].name.ids = mib2IfTable;
151     SetLastError(0xdeadbeef);
152     error = 0xdeadbeef;
153     index = 0xdeadbeef;
154     ret = pSnmpExtensionQuery(SNMP_PDU_GET, &list, &error, &index);
155     ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
156     ok(error == SNMP_ERRORSTATUS_NOSUCHNAME,
157         "expected SNMP_ERRORSTATUS_NOSUCHNAME, got %d\n", error);
158     /* The index is 1-based rather than 0-based */
159     ok(index == 1, "expected index 1, got %d\n", index);
160 
161     /* A Get fails on something that specifies a table (but not a particular
162      * entry in it)...
163      */
164     vars[0].name.idLength = sizeof(mib2IfDescr) / sizeof(mib2IfDescr[0]);
165     vars[0].name.ids = mib2IfDescr;
166     vars[1].name.idLength =
167         sizeof(mib2IfAdminStatus) / sizeof(mib2IfAdminStatus[0]);
168     vars[1].name.ids = mib2IfAdminStatus;
169     vars[2].name.idLength =
170         sizeof(mib2IfOperStatus) / sizeof(mib2IfOperStatus[0]);
171     vars[2].name.ids = mib2IfOperStatus;
172     list.len = 3;
173     SetLastError(0xdeadbeef);
174     error = 0xdeadbeef;
175     index = 0xdeadbeef;
176     ret = pSnmpExtensionQuery(SNMP_PDU_GET, &list, &error, &index);
177     ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
178     ok(error == SNMP_ERRORSTATUS_NOSUCHNAME,
179         "expected SNMP_ERRORSTATUS_NOSUCHNAME, got %d\n", error);
180     ok(index == 1, "expected index 1, got %d\n", index);
181     /* but a GetNext succeeds with the same values, because GetNext gets the
182      * entry after the specified OID, not the entry specified by it.  The
183      * successor to the table is the first entry in the table.
184      * The OIDs need to be allocated, because GetNext modifies them to indicate
185      * the end of data.
186      */
187     SnmpUtilOidCpy(&vars2[0].name, &vars[0].name);
188     SnmpUtilOidCpy(&vars2[1].name, &vars[1].name);
189     SnmpUtilOidCpy(&vars2[2].name, &vars[2].name);
190     list.list = vars2;
191     moreData = TRUE;
192     noChange = FALSE;
193     entry = 0;
194     do {
195         SetLastError(0xdeadbeef);
196         error = 0xdeadbeef;
197         index = 0xdeadbeef;
198         ret = pSnmpExtensionQuery(SNMP_PDU_GETNEXT, &list, &error, &index);
199         ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
200         ok(error == SNMP_ERRORSTATUS_NOERROR,
201             "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
202         ok(index == 0, "expected index 0, got %d\n", index);
203         if (!ret)
204             moreData = FALSE;
205         else if (error)
206             moreData = FALSE;
207         else if (SnmpUtilOidNCmp(&vars2[0].name, &vars[0].name,
208             vars[0].name.idLength))
209             moreData = FALSE;
210         else if (SnmpUtilOidNCmp(&vars2[1].name, &vars[1].name,
211             vars[1].name.idLength))
212             moreData = FALSE;
213         else if (SnmpUtilOidNCmp(&vars2[2].name, &vars[2].name,
214             vars[2].name.idLength))
215             moreData = FALSE;
216         else if (!SnmpUtilOidCmp(&vars[0].name, &vars2[0].name) ||
217          !SnmpUtilOidCmp(&vars[1].name, &vars2[1].name) ||
218          !SnmpUtilOidCmp(&vars[2].name, &vars2[2].name))
219         {
220             /* If the OID isn't modified, the function isn't implemented on this
221              * platform, skip the remaining tests.
222              */
223             noChange = TRUE;
224         }
225         if (moreData)
226         {
227             UINT lastID;
228 
229             /* Check the OIDs.  For these types of values (display strings and
230              * integers) they should increase by 1 for each element of the table
231              * according to RFC 1158.  Windows sometimes has a weird value in the
232              * table, so allow any value as long as it's greater than the previous
233              * value on Windows.
234              */
235             ok(vars2[0].name.idLength == vars[0].name.idLength + 1,
236                 "expected length %d, got %d\n", vars[0].name.idLength + 1,
237                 vars2[0].name.idLength);
238             lastID = vars2[0].name.ids[vars2[0].name.idLength - 1];
239             ok(lastID == entry + 1 || broken(lastID > entry),
240                 "expected %d, got %d\n", entry + 1, lastID);
241             ok(vars2[1].name.idLength == vars[1].name.idLength + 1,
242                 "expected length %d, got %d\n", vars[1].name.idLength + 1,
243                 vars2[1].name.idLength);
244             lastID = vars2[1].name.ids[vars2[1].name.idLength - 1];
245             ok(lastID == entry + 1 || broken(lastID > entry),
246                 "expected %d, got %d\n", entry + 1, lastID);
247             ok(vars2[2].name.idLength == vars[2].name.idLength + 1,
248                 "expected length %d, got %d\n", vars[2].name.idLength + 1,
249                 vars2[2].name.idLength);
250             lastID = vars2[2].name.ids[vars2[2].name.idLength - 1];
251             ok(lastID == entry + 1 || broken(lastID > entry),
252                 "expected %d, got %d\n", entry + 1, lastID);
253             entry = lastID;
254             /* Check the types while we're at it */
255             ok(vars2[0].value.asnType == ASN_OCTETSTRING,
256                 "expected ASN_OCTETSTRING, got %02x\n", vars2[0].value.asnType);
257             ok(vars2[1].value.asnType == ASN_INTEGER,
258                 "expected ASN_INTEGER, got %02x\n", vars2[1].value.asnType);
259             ok(vars2[2].value.asnType == ASN_INTEGER,
260                 "expected ASN_INTEGER, got %02x\n", vars2[2].value.asnType);
261         }
262         else if (noChange)
263             skip("no change in OID, no MIB2 IF table implementation\n");
264     } while (moreData && !noChange);
265     SnmpUtilVarBindFree(&vars2[0]);
266     SnmpUtilVarBindFree(&vars2[1]);
267     SnmpUtilVarBindFree(&vars2[2]);
268 
269     /* Even though SnmpExtensionInit says this DLL supports the MIB2 system
270      * variables, on recent systems (at least Win2k) the first variable it
271      * returns a value for is the first interface.
272      */
273     vars[0].name.idLength = sizeof(mib2System) / sizeof(mib2System[0]);
274     vars[0].name.ids = mib2System;
275     SnmpUtilOidCpy(&vars2[0].name, &vars[0].name);
276     vars2[0].value.asnType = 0;
277     list.len = 1;
278     list.list = vars2;
279     noChange = FALSE;
280     ret = pSnmpExtensionQuery(SNMP_PDU_GETNEXT, &list, &error, &index);
281     ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
282     ok(error == SNMP_ERRORSTATUS_NOERROR,
283         "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
284     ok(index == 0, "expected index 0, got %d\n", index);
285     vars3[0].name.idLength = sizeof(mib2If) / sizeof(mib2If[0]);
286     vars3[0].name.ids = mib2If;
287     ok(!SnmpUtilOidNCmp(&vars2[0].name, &vars[0].name, vars[0].name.idLength) ||
288        !SnmpUtilOidNCmp(&vars2[0].name, &vars3[0].name, vars3[0].name.idLength),
289         "expected 1.3.6.1.2.1.1 or 1.3.6.1.2.1.2, got %s\n",
290         SnmpUtilOidToA(&vars2[0].name));
291     SnmpUtilVarBindFree(&vars2[0]);
292 
293     /* Check the type and OIDs of the IP address table */
294     vars[0].name.idLength = sizeof(mib2IpAddr) / sizeof(mib2IpAddr[0]);
295     vars[0].name.ids = mib2IpAddr;
296     SnmpUtilOidCpy(&vars2[0].name, &vars[0].name);
297     vars2[0].value.asnType = 0;
298     list.len = 1;
299     list.list = vars2;
300     moreData = TRUE;
301     do {
302         ret = pSnmpExtensionQuery(SNMP_PDU_GETNEXT, &list, &error, &index);
303         ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
304         ok(error == SNMP_ERRORSTATUS_NOERROR,
305             "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
306         ok(index == 0, "expected index 0, got %d\n", index);
307         if (!ret)
308             moreData = FALSE;
309         else if (error)
310             moreData = FALSE;
311         else if (SnmpUtilOidNCmp(&vars2[0].name, &vars[0].name,
312             vars[0].name.idLength))
313             moreData = FALSE;
314         else if (!SnmpUtilOidCmp(&vars2[0].name, &vars[0].name))
315         {
316             /* If the OID isn't modified, the function isn't implemented on this
317              * platform, skip the remaining tests.
318              */
319             noChange = TRUE;
320         }
321         if (moreData)
322         {
323             /* Make sure the size of the OID is right.
324              * FIXME: don't know if IPv6 addrs are shared with this table.
325              * Don't think so, but I'm not certain.
326              */
327             ok(vars2[0].name.idLength == vars[0].name.idLength + 4,
328                 "expected length %d, got %d\n", vars[0].name.idLength + 4,
329                 vars2[0].name.idLength);
330             /* Make sure the type is right */
331             ok(vars2[0].value.asnType == ASN_IPADDRESS,
332                 "expected type ASN_IPADDRESS, got %02x\n",
333                 vars2[0].value.asnType);
334             if (vars2[0].value.asnType == ASN_IPADDRESS)
335             {
336                 UINT i;
337 
338                 /* This looks uglier than it is:  the base OID for the IP
339                  * address, 1.3.6.1.2.1.4.20.1.1, is appended with the IP
340                  * address of the entry.  So e.g. the loopback address is
341                  * identified in MIB2 as 1.3.6.1.2.1.4.20.1.1.127.0.0.1
342                  */
343                 for (i = 0; i < vars2[0].value.asnValue.address.length; i++)
344                 {
345                     ok(vars2[0].value.asnValue.address.stream[i] ==
346                         vars2[0].name.ids[vars2[0].name.idLength - 4 + i],
347                         "expected ident byte %d to be %d, got %d\n", i,
348                         vars2[0].value.asnValue.address.stream[i],
349                         vars2[0].name.ids[vars2[0].name.idLength - 4 + i]);
350                 }
351             }
352         }
353         else if (noChange)
354             skip("no change in OID, no MIB2 IP address table implementation\n");
355     } while (moreData && !noChange);
356     SnmpUtilVarBindFree(&vars2[0]);
357 
358     /* Check the type and OIDs of the IP route table */
359     vars[0].name.idLength = DEFINE_SIZEOF(mib2IpRouteTable);
360     vars[0].name.ids = mib2IpRouteTable;
361     SnmpUtilOidCpy(&vars2[0].name, &vars[0].name);
362     vars2[0].value.asnType = 0;
363     list.len = 1;
364     list.list = vars2;
365     moreData = TRUE;
366     noChange = FALSE;
367     do {
368         ret = pSnmpExtensionQuery(SNMP_PDU_GETNEXT, &list, &error, &index);
369         ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
370         ok(error == SNMP_ERRORSTATUS_NOERROR,
371             "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
372         ok(index == 0, "expected index 0, got %d\n", index);
373         if (!ret)
374             moreData = FALSE;
375         else if (error)
376             moreData = FALSE;
377         else if (SnmpUtilOidNCmp(&vars2[0].name, &vars[0].name,
378             vars[0].name.idLength))
379             moreData = FALSE;
380         else if (!SnmpUtilOidCmp(&vars2[0].name, &vars[0].name))
381         {
382             /* If the OID isn't modified, the function isn't implemented on this
383              * platform, skip the remaining tests.
384              */
385             noChange = TRUE;
386         }
387         if (moreData)
388         {
389             /* Make sure the size of the OID is right.
390              * FIXME: don't know if IPv6 addrs are shared with this table.
391              * Don't think so, but I'm not certain.
392              */
393             ok(vars2[0].name.idLength == vars[0].name.idLength + 4,
394                 "expected length %d, got %d\n", vars[0].name.idLength + 4,
395                 vars2[0].name.idLength);
396             /* Make sure the type is right */
397             ok(vars2[0].value.asnType == ASN_IPADDRESS,
398                 "expected type ASN_IPADDRESS, got %02x\n",
399                 vars2[0].value.asnType);
400             if (vars2[0].value.asnType == ASN_IPADDRESS)
401             {
402                 UINT i;
403 
404                 /* The base OID for the route table, 1.3.6.1.2.1.4.21.1.1, is
405                  * appended with the dest IP address of the entry.  So e.g. a
406                  * route entry for 224.0.0.0 is identified in MIB2 as
407                  * 1.3.6.1.2.1.4.21.1.1.224.0.0.0
408                  */
409                 for (i = 0; i < vars2[0].value.asnValue.address.length; i++)
410                 {
411                     ok(vars2[0].value.asnValue.address.stream[i] ==
412                         vars2[0].name.ids[vars2[0].name.idLength - 4 + i],
413                         "expected ident byte %d to be %d, got %d\n", i,
414                         vars2[0].value.asnValue.address.stream[i],
415                         vars2[0].name.ids[vars2[0].name.idLength - 4 + i]);
416                 }
417             }
418         }
419         else if (noChange)
420             skip("no change in OID, no MIB2 IP route table implementation\n");
421     } while (moreData && !noChange);
422     SnmpUtilVarBindFree(&vars2[0]);
423 
424     /* Check the type and OIDs of the UDP table */
425     vars[0].name.idLength = DEFINE_SIZEOF(mib2UdpTable);
426     vars[0].name.ids = mib2UdpTable;
427     SnmpUtilOidCpy(&vars2[0].name, &vars[0].name);
428     vars2[0].value.asnType = 0;
429     list.len = 1;
430     list.list = vars2;
431     moreData = TRUE;
432     noChange = FALSE;
433     do {
434         ret = pSnmpExtensionQuery(SNMP_PDU_GETNEXT, &list, &error, &index);
435         ok(ret, "SnmpExtensionQuery failed: %d, %d\n", error, index);
436         /* FIXME:  error and index aren't checked here because the UDP table is
437          * the last OID currently supported by Wine, so the last GetNext fails.
438          * todo_wine is also not effective because it will succeed for all but
439          * the last GetNext.  Remove the if (0) if any later OID is supported
440          * by Wine.
441          */
442         if (0) {
443         ok(error == SNMP_ERRORSTATUS_NOERROR,
444             "expected SNMP_ERRORSTATUS_NOERROR, got %d\n", error);
445         ok(index == 0, "expected index 0, got %d\n", index);
446         }
447         if (!ret)
448             moreData = FALSE;
449         else if (error)
450             moreData = FALSE;
451         else if (SnmpUtilOidNCmp(&vars2[0].name, &vars[0].name,
452             vars[0].name.idLength))
453             moreData = FALSE;
454         else if (!SnmpUtilOidCmp(&vars2[0].name, &vars[0].name))
455         {
456             /* If the OID isn't modified, the function isn't implemented on this
457              * platform, skip the remaining tests.
458              */
459             noChange = TRUE;
460         }
461         if (moreData)
462         {
463             /* Make sure the size of the OID is right. */
464             ok(vars2[0].name.idLength == vars[0].name.idLength + 5,
465                 "expected length %d, got %d\n", vars[0].name.idLength + 5,
466                 vars2[0].name.idLength);
467             /* Make sure the type is right */
468             ok(vars2[0].value.asnType == ASN_IPADDRESS,
469                 "expected type ASN_IPADDRESS, got %02x\n",
470                 vars2[0].value.asnType);
471             if (vars2[0].value.asnType == ASN_IPADDRESS)
472             {
473                 UINT i;
474 
475                 /* Again with the ugly:  the base OID for the UDP table,
476                  * 1.3.6.1.2.1.7.5.1, is appended with the local IP address and
477                  * port number of the entry.  So e.g. an entry for
478                  * 192.168.1.1:4000 is identified in MIB2 as
479                  * 1.3.6.1.2.1.7.5.1.192.168.1.1.4000
480                  */
481                 for (i = 0; i < vars2[0].value.asnValue.address.length; i++)
482                 {
483                     ok(vars2[0].value.asnValue.address.stream[i] ==
484                         vars2[0].name.ids[vars2[0].name.idLength - 5 + i],
485                         "expected ident byte %d to be %d, got %d\n", i,
486                         vars2[0].value.asnValue.address.stream[i],
487                         vars2[0].name.ids[vars2[0].name.idLength - 5 + i]);
488                 }
489             }
490         }
491         else if (noChange)
492             skip("no change in OID, no MIB2 UDP table implementation\n");
493     } while (moreData && !noChange);
494     SnmpUtilVarBindFree(&vars2[0]);
495 }
496 
497 START_TEST(main)
498 {
499     HMODULE mod;
500 
501     if (!(mod = init_test_functions()))
502         return;
503 
504     testInit();
505     testQuery();
506 
507     uninit_test_functions(mod);
508 }
509