Last active
December 13, 2018 19:46
-
-
Save gvanem/6c7b43fc694f0e26e3e56e99714888c7 to your computer and use it in GitHub Desktop.
Windows SNMP client with libcurl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************************************************** | |
* _ _ ____ _ | |
* Project ___| | | | _ \| | | |
* / __| | | | |_) | | | |
* | (__| |_| | _ <| |___ | |
* \___|\___/|_| \_\_____| | |
* | |
* Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al. | |
* | |
* This software is licensed as described in the file COPYING, which | |
* you should have received as part of this distribution. The terms | |
* are also available at https://curl.haxx.se/docs/copyright.html. | |
* | |
* You may opt to use, copy, modify, merge, publish, distribute and/or sell | |
* copies of the Software, and permit persons to whom the Software is | |
* furnished to do so, under the terms of the COPYING file. | |
* | |
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | |
* KIND, either express or implied. | |
* | |
* Simple SNMP-client for Windows only. | |
* Example usage (using an URL-syntax): * | |
* snmpmgr snmp://<host>/1.3.6.1.2.1.1.1.0 -> returns the host's system name | |
* snmpmgr snmp://<host>/1.3.6.1.2.1.2.2.1.10.3 -> returns packet-count at iface 3: | |
* | |
* Or using plain host/OID syntax: | |
* snmpmgr <host> 1.3.6.1.2.1.1.1.0 -> same as above | |
* snmpmgr <host> 1.3.6.1.2.1.2.2.1.10.3 -> same as above | |
*/ | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdarg.h> | |
#include <stdlib.h> | |
#include <ctype.h> | |
#include <errno.h> | |
#include <curl/curl.h> | |
#include <windows.h> | |
#include <snmp.h> | |
#include <mgmtapi.h> | |
#if !CURL_AT_LEAST_VERSION(7, 62, 0) | |
#error "this example requires curl 7.62.0 or later" | |
#endif | |
/* Use MSVC CrtDebug in debug-mode ('-MD' or '-MT'). | |
* This MUST match what was used to build libcurl.dll. | |
*/ | |
#if defined(_DEBUG) | |
#define _CRTDBG_MAP_ALLOC | |
#undef _malloca /* Avoid MSVC-9 <malloc.h>/<crtdbg.h> name-clash */ | |
#include <crtdbg.h> | |
#else | |
/* | |
* Program bombs with this in.... | |
*/ | |
#if 0 | |
#include <curl_setup.h> | |
#include <memdebug.h> | |
#endif | |
#endif | |
#define DIM(array) (sizeof(array) / sizeof(array[0])) | |
#ifndef SNMP_PORT | |
#define SNMP_PORT 161 | |
#endif | |
#if (_WIN32_WINNT < 0x0600) | |
/* | |
* Borrowed from ../../lib/inet_ntop.c | |
*/ | |
extern char *Curl_inet_ntop (int af, const void *addr, char *buf, size_t size); | |
#define inet_ntop(af, addr, buf, size) Curl_inet_ntop(af, addr, buf, size) | |
#endif | |
typedef union { | |
CURLcode curl; | |
enum { | |
SNMP_ILL_OID = CURL_LAST + 1, | |
SNMP_GEN_ERR = CURL_LAST + 2 | |
} extension; | |
} CURLxcode; | |
struct search_list { | |
DWORD value; | |
const char *name; | |
}; | |
/* | |
* List of all WinSNMP errors libcurl is able to produce. | |
*/ | |
static const struct search_list snmp_errs[] = { | |
/* from <MgmtAPI.h> */ | |
{ SNMP_MGMTAPI_TIMEOUT, "Timeout" }, | |
{ SNMP_MGMTAPI_SELECT_FDERRORS, "Select Fd errors" }, | |
{ SNMP_MGMTAPI_TRAP_ERRORS, "Trap _Errors" }, | |
{ SNMP_MGMTAPI_TRAP_DUPINIT, "Trap Dupinit" }, | |
{ SNMP_MGMTAPI_NOTRAPS, "No traps" }, | |
{ SNMP_MGMTAPI_AGAIN, "Again" }, | |
{ SNMP_MGMTAPI_INVALID_CTL, "Invalid Control" }, | |
{ SNMP_MGMTAPI_INVALID_SESSION, "Invalid Session" }, | |
{ SNMP_MGMTAPI_INVALID_BUFFER, "Invalid Buffer" }, | |
/* from <Snmp.h> */ | |
{ SNMP_ERRORSTATUS_NOERROR, "No Error" }, | |
{ SNMP_ERRORSTATUS_TOOBIG, "Too Big" }, | |
{ SNMP_ERRORSTATUS_NOSUCHNAME, "No Such Name" }, | |
{ SNMP_ERRORSTATUS_BADVALUE, "Bad Value" }, | |
{ SNMP_ERRORSTATUS_READONLY, "Read Only" }, | |
{ SNMP_ERRORSTATUS_GENERR, "Gen Error" }, | |
{ SNMP_ERRORSTATUS_NOACCESS, "No Access" }, | |
{ SNMP_ERRORSTATUS_WRONGTYPE, "Wrong Type" }, | |
{ SNMP_ERRORSTATUS_WRONGLENGTH, "Wrong Length" }, | |
{ SNMP_ERRORSTATUS_WRONGENCODING, "Wrong Encoding" }, | |
{ SNMP_ERRORSTATUS_WRONGVALUE, "Wrong Value" }, | |
{ SNMP_ERRORSTATUS_NOCREATION, "No Creation" }, | |
{ SNMP_ERRORSTATUS_INCONSISTENTVALUE, "Inconsistent Value" }, | |
{ SNMP_ERRORSTATUS_RESOURCEUNAVAILABLE,"Resource Unavailable" }, | |
{ SNMP_ERRORSTATUS_COMMITFAILED, "Commit Failed" }, | |
{ SNMP_ERRORSTATUS_UNDOFAILED, "Undo Failed" }, | |
{ SNMP_ERRORSTATUS_AUTHORIZATIONERROR, "Authorization Error" }, | |
{ SNMP_ERRORSTATUS_NOTWRITABLE, "Not Writable" }, | |
{ SNMP_ERRORSTATUS_INCONSISTENTNAME, "Inconsistent name" } | |
}; | |
static SOCKET snmpmgr_getsock (void *clientp, | |
curlsocktype purpose, | |
struct curl_sockaddr *addr) | |
{ | |
(void) clientp; | |
(void) purpose; | |
(void) addr; | |
return socket (AF_INET, SOCK_DGRAM, 0); | |
} | |
static CURLxcode build_varbind_list (const char *string, SnmpVarBindList **listp) | |
{ | |
AsnObjectIdentifier oid; | |
SnmpVarBind *vb = NULL; | |
SnmpVarBindList *vb_list = NULL; | |
CURLxcode rc; | |
char *oid_str = malloc (strlen(string)+2); | |
if (!oid_str) | |
{ | |
rc.curl = CURLE_OUT_OF_MEMORY; | |
goto fail; | |
} | |
vb = calloc (sizeof(*vb), 1); | |
if (!vb) | |
{ | |
rc.curl = CURLE_OUT_OF_MEMORY; | |
goto fail; | |
} | |
vb_list = calloc (sizeof(*vb_list), 1); | |
if (!vb_list) | |
{ | |
rc.curl = CURLE_OUT_OF_MEMORY; | |
goto fail; | |
} | |
*oid_str = '.'; | |
strcpy (oid_str+1, string); | |
if (!SnmpMgrStrToOid(oid_str, &oid)) | |
{ | |
printf ("Illegal OID %s\n", oid_str); | |
rc.extension = SNMP_ILL_OID; | |
goto fail; | |
} | |
free (oid_str); | |
memcpy (&vb->name, &oid, sizeof(vb->name)); | |
vb->value.asnType = ASN_NULL; | |
vb_list->len = 1; | |
vb_list->list = vb; | |
*listp = vb_list; | |
rc.curl = CURLE_OK; | |
return (rc); | |
fail: | |
if (oid_str) | |
free (oid_str); | |
if (vb_list) | |
free (vb_list); | |
if (vb) | |
free (vb); | |
return (rc); | |
} | |
static void free_varbind_list (SnmpVarBindList **listp) | |
{ | |
SnmpVarBindList *vb_list; | |
if (!listp || !*listp) | |
return; | |
vb_list = *listp; | |
SnmpUtilOidFree (&vb_list->list->name); | |
vb_list->len = 0; | |
free (vb_list->list); | |
free (vb_list); | |
*listp = NULL; | |
} | |
static void dump_response (const AsnObjectSyntax *asn) | |
{ | |
char buf[1024], *p = buf; | |
const char *end = buf + sizeof(buf) - 1; | |
unsigned i, len; | |
BOOL ishex; | |
switch (asn->asnType) { | |
case ASN_OCTETSTRING: | |
p += sprintf (p, "String: "); | |
if (!asn->asnValue.string.stream) { | |
p += sprintf (p, "NULL"); | |
break; | |
} | |
ishex = !isprint (asn->asnValue.string.stream[0]); | |
for (i = 0; i < asn->asnValue.string.length; i++) | |
p += sprintf (p, ishex ? "%02X" : "%c", asn->asnValue.string.stream[i]); | |
break; | |
case ASN_UNSIGNED32: | |
p += sprintf (p, "Unsigned32: " | |
"%lu", asn->asnValue.unsigned32); | |
break; | |
case ASN_INTEGER32: | |
p += sprintf (p, "Integer32: " | |
"%lu", asn->asnValue.number); | |
break; | |
case ASN_TIMETICKS: | |
p += sprintf (p, "Ticks: " | |
"%lu", asn->asnValue.ticks); | |
break; | |
case ASN_GAUGE32: | |
p += sprintf (p, "Gauge32: " | |
"%lu", asn->asnValue.gauge); | |
break; | |
case ASN_COUNTER32: | |
p += sprintf (p, "Counter32: " | |
"%lu", asn->asnValue.counter); | |
break; | |
case ASN_COUNTER64: | |
p += sprintf (p, "Counter64: " | |
"%I64u", *(unsigned __int64*)&asn->asnValue.counter64); | |
break; | |
case ASN_IPADDRESS: | |
len = asn->asnValue.address.length; | |
if (len == 4) { | |
p += sprintf (p, "IPv4 addr: "); | |
inet_ntop (AF_INET, asn->asnValue.address.stream, p, end-p); | |
} | |
else if (len == 16) { | |
p += sprintf (p, "IPv6 addr: "); | |
inet_ntop (AF_INET6, asn->asnValue.address.stream, p, end-p); | |
} | |
else | |
p += sprintf (p, "IPv?? addr "); | |
break; | |
case ASN_OBJECTIDENTIFIER: | |
for (i = 0; i < asn->asnValue.object.idLength; i++) | |
p += sprintf (p, ".%u", asn->asnValue.object.ids[i]); | |
break; | |
case ASN_OPAQUE: | |
p += sprintf (p, "Opaque: "); | |
for (i = 0; i < asn->asnValue.arbitrary.length; i++) | |
p += sprintf (p, "%02X ", asn->asnValue.arbitrary.stream[i]); | |
break; | |
case ASN_NULL: | |
p += sprintf (p, "ASN NULL"); | |
break; | |
} | |
if (p == buf) | |
puts ("No data"); | |
else puts (buf); | |
} | |
/* | |
* Search 'list' for 'type' and return it's name. | |
*/ | |
static const char *list_lookup (DWORD value, const struct search_list *list, | |
int num) | |
{ | |
while (num > 0 && list->name) { | |
if (list->value == value) | |
return (list->name); | |
num--; | |
list++; | |
} | |
return (NULL); | |
} | |
static const char *win_strerror (DWORD err) | |
{ | |
static char buf[200]; | |
if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, | |
LANG_NEUTRAL, buf, sizeof(buf), NULL)) | |
_snprintf (buf, sizeof(buf), "Unknown error %lu (%#lx)", err, err); | |
return (buf); | |
} | |
/* | |
* Cannot use win_strerror() directly since WinSNMP errors overlaps | |
* with system errors. | |
*/ | |
static const char *snmp_strerror (int err) | |
{ | |
const char *p = list_lookup (err, snmp_errs, DIM(snmp_errs)); | |
if (p) | |
return (p); | |
return win_strerror (err); | |
} | |
/* | |
* The main SNMP function; build the var-list, connect, send the request | |
* and dump the response. | |
*/ | |
static CURLxcode snmp_main (const char *host, const char *oid) | |
{ | |
LPSNMP_MGR_SESSION session; | |
SnmpVarBindList *list = NULL; | |
AsnInteger errStatus = 0, errIndex = 0; | |
int timeout = 500; | |
int i, retries = 1; | |
CURLxcode rc; | |
if (*oid == '/') /* Ignore leading '/' from curl_url_get() */ | |
oid++; | |
rc = build_varbind_list (oid, &list); | |
if (rc.curl != CURLE_OK) | |
return (rc); | |
session = SnmpMgrOpen ((LPSTR)host, "public", timeout, retries); | |
if (!session) | |
{ | |
printf ("SNMP Mgr: %s\n", snmp_strerror(GetLastError())); | |
rc.extension = SNMP_GEN_ERR; | |
goto quit; | |
} | |
if (!SnmpMgrRequest(session, SNMP_PDU_GET, list, &errStatus, &errIndex) || | |
errStatus > 0) | |
{ | |
if (errStatus == SNMP_ERRORSTATUS_NOSUCHNAME) | |
printf ("SNMP Mgr: no such OID %s\n", oid); | |
else | |
printf ("SNMP Mgr: err-status %lu, err-index %lu, %s\n", | |
errStatus, errIndex, snmp_strerror(GetLastError())); | |
rc.extension = SNMP_GEN_ERR; | |
goto quit; | |
} | |
for (i = 0; i < (int)list->len; i++) | |
dump_response (&list->list[i].value); | |
rc.curl = CURLE_OK; | |
quit: | |
if (session) | |
SnmpMgrClose (session); | |
if (list) | |
free_varbind_list (&list); | |
return (rc); | |
} | |
#define SNMP_METHOD_GET 0 | |
#define SNMP_METHOD_WALK 1 | |
static void Usage (const char *my_name) | |
{ | |
printf ("Usage: %s [-v] [-w] [--get | --walk] <snmp://host/OID>\n", my_name); | |
exit (0); | |
} | |
int main (int argc, char **argv) | |
{ | |
CURL *data = NULL; | |
const char *my_name = argv[0]; | |
int url_syntax = 0; | |
int plain_syntax = 0; | |
int verbose = 0; | |
int winsnmp_only = 0; | |
int method = SNMP_METHOD_GET; | |
CURLU *uh = NULL; | |
CURLUcode uc1 = 0, uc2 = 0, uc3 = 0; | |
char *scheme = NULL; | |
char *host = NULL; | |
char *oid = NULL; | |
argc--; | |
argv++; | |
if (argc >= 1 && !strcmp(argv[0],"-v")) | |
{ | |
verbose = 1; | |
argc--; | |
argv++; | |
} | |
if (argc >= 1 && !strcmp(argv[0],"-w")) | |
{ | |
winsnmp_only = 1; | |
argc--; | |
argv++; | |
} | |
if (argc >= 1 && !strcmp(argv[0],"--get")) /* The default */ | |
{ | |
method = SNMP_METHOD_GET; | |
argc--; | |
argv++; | |
} | |
else if (argc >= 1 && !strcmp(argv[0],"--walk")) | |
{ | |
method = SNMP_METHOD_WALK; | |
argc--; | |
argv++; | |
} | |
#if 0 | |
printf ("argc: %d\n", argc); | |
printf ("argv[0]: '%s'\n", argv[0]); | |
printf ("argv[1]: '%s'\n", argv[1]); | |
#endif | |
if (argc == 2) | |
{ | |
plain_syntax = 1; | |
host = argv[0]; | |
oid = argv[1]; | |
} | |
else | |
{ | |
uh = curl_url(); | |
curl_url_set (uh, 0 /* CURLUPART_URL */, argv[0], CURLU_NON_SUPPORT_SCHEME); | |
uc1 = curl_url_get (uh, CURLUPART_SCHEME, &scheme, 0); | |
uc2 = curl_url_get (uh, CURLUPART_HOST, &host, 0); | |
uc3 = curl_url_get (uh, CURLUPART_PATH, &oid, 0); | |
} | |
if (uc1 || uc2 || uc3) | |
Usage (my_name); | |
if (!winsnmp_only) | |
{ | |
data = curl_easy_init(); | |
curl_easy_setopt (data, CURLOPT_CONNECT_ONLY, 1); | |
curl_easy_setopt (data, CURLOPT_URL, host); | |
curl_easy_setopt (data, CURLOPT_PORT, SNMP_PORT); | |
curl_easy_setopt (data, CURLOPT_OPENSOCKETFUNCTION, snmpmgr_getsock); | |
if (verbose) | |
curl_easy_setopt (data, CURLOPT_VERBOSE, 1); | |
curl_easy_perform (data); | |
} | |
snmp_main (host, oid); | |
if (!winsnmp_only) | |
curl_easy_cleanup (data); | |
curl_free (scheme); | |
curl_free (host); | |
curl_free (oid); | |
curl_url_cleanup (uh); | |
return (0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment