blob: 2a734e8c0d53baeda95a3b56dbd01de585a03809 [file] [log] [blame]
/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*-
SNMP.xs -- Perl 5 interface to the Net-SNMP toolkit
written by G. S. Marzot (marz@users.sourceforge.net)
Copyright (c) 1995-2006 G. S. Marzot. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
*/
#define WIN32SCK_IS_STDSCK
#if defined(_WIN32) && !defined(_WIN32_WINNT)
#define _WIN32_WINNT 0x501
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#ifndef MSVC_PERL
#include <signal.h>
#endif
#include <stdio.h>
#include <ctype.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#endif
#include <netdb.h>
#include <stdlib.h>
#ifndef MSVC_PERL
#include <unistd.h>
#endif
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif
#ifndef __P
#define __P(x) x
#endif
#ifndef na
#define na PL_na
#endif
#ifndef sv_undef
#define sv_undef PL_sv_undef
#endif
#ifndef stack_base
#define stack_base PL_stack_base
#endif
#ifndef G_VOID
#define G_VOID G_DISCARD
#endif
#include "perlsnmp.h"
#define SUCCESS 1
#define FAILURE 0
#define ZERO_BUT_TRUE "0 but true"
#define SNMP_API_TRADITIONAL 0
#define SNMP_API_SINGLE 1
#define VARBIND_TAG_F 0
#define VARBIND_IID_F 1
#define VARBIND_VAL_F 2
#define VARBIND_TYPE_F 3
#define TYPE_UNKNOWN 0
#define MAX_TYPE_NAME_LEN 32
#define STR_BUF_SIZE (MAX_TYPE_NAME_LEN * MAX_OID_LEN)
#define ENG_ID_BUF_SIZE 32
#define SYS_UPTIME_OID_LEN 9
#define SNMP_TRAP_OID_LEN 11
#define NO_RETRY_NOSUCH 0
static oid sysUpTime[SYS_UPTIME_OID_LEN] = {1, 3, 6, 1, 2, 1, 1, 3, 0};
static oid snmpTrapOID[SNMP_TRAP_OID_LEN] = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0};
/* Internal flag to determine snmp_main_loop() should return after callback */
static int mainloop_finish = 0;
/* Internal flag to determine which API we're using */
static int api_mode = SNMP_API_TRADITIONAL;
/* these should be part of transform_oids.h ? */
#define USM_AUTH_PROTO_MD5_LEN 10
#define USM_AUTH_PROTO_SHA_LEN 10
#define USM_PRIV_PROTO_DES_LEN 10
/* why does ucd-snmp redefine sockaddr_in ??? */
#define SIN_ADDR(snmp_addr) (((struct sockaddr_in *) &(snmp_addr))->sin_addr)
typedef netsnmp_session SnmpSession;
typedef struct tree SnmpMibNode;
typedef struct snmp_xs_cb_data {
SV* perl_cb;
SV* sess_ref;
} snmp_xs_cb_data;
static void __recalc_timeout _((struct timeval*,struct timeval*,
struct timeval*,struct timeval*, int* ));
static int __is_numeric_oid _((char*));
static int __is_leaf _((struct tree*));
static int __translate_appl_type _((char*));
static int __translate_asn_type _((int));
static int __snprint_value _((char *, size_t,
netsnmp_variable_list*, struct tree *,
int, int));
static int __sprint_num_objid _((char *, oid *, int));
static int __scan_num_objid _((char *, oid *, size_t *));
static int __get_type_str _((int, char *));
static int __get_label_iid _((char *, char **, char **, int));
static int __oid_cmp _((oid *, size_t, oid *, size_t));
static int __tp_sprint_num_objid _((char*,SnmpMibNode *));
static SnmpMibNode * __get_next_mib_node _((SnmpMibNode *));
static struct tree * __tag2oid _((char *, char *, oid *, size_t *, int *, int));
static int __concat_oid_str _((oid *, size_t *, char *));
static int __add_var_val_str _((netsnmp_pdu *, oid *, size_t, char *,
int, int));
static int __send_sync_pdu _((netsnmp_session *, netsnmp_pdu *,
netsnmp_pdu **, int , SV *, SV *, SV *));
static int __snmp_xs_cb __P((int, netsnmp_session *, int,
netsnmp_pdu *, void *));
static SV* __push_cb_args2 _((SV * sv, SV * esv, SV * tsv));
#define __push_cb_args(a,b) __push_cb_args2(a,b,NULL)
static int __call_callback _((SV * sv, int flags));
static char* __av_elem_pv _((AV * av, I32 key, char *dflt));
#define USE_NUMERIC_OIDS 0x08
#define NON_LEAF_NAME 0x04
#define USE_LONG_NAMES 0x02
#define FAIL_ON_NULL_IID 0x01
#define NO_FLAGS 0x00
/* Structures used by snmp_bulkwalk method to track requested OID's/subtrees. */
typedef struct bulktbl {
oid req_oid[MAX_OID_LEN]; /* The OID originally requested. */
oid last_oid[MAX_OID_LEN]; /* Last-seen OID under this branch. */
AV *vars; /* Array of Varbinds for this OID. */
size_t req_len; /* Length of requested OID. */
size_t last_len; /* Length of last-seen OID. */
char norepeat; /* Is this a non-repeater OID? */
char complete; /* Non-zero if this tree complete. */
char ignore; /* Ignore this OID, not requested. */
} bulktbl;
/* Context for bulkwalk() sessions. Used to store state across callbacks. */
typedef struct walk_context {
SV *sess_ref; /* Reference to Perl SNMP session object. */
SV *perl_cb; /* Pointer to Perl callback func or array. */
bulktbl *req_oids; /* Pointer to bulktbl[] for requested OIDs. */
bulktbl *repbase; /* Pointer to first repeater in req_oids[]. */
bulktbl *reqbase; /* Pointer to start of requests req_oids[]. */
int nreq_oids; /* Number of valid bulktbls in req_oids[]. */
int req_remain; /* Number of outstanding requests remaining */
int non_reps; /* Number of nonrepeater vars in req_oids[] */
int repeaters; /* Number of repeater vars in req_oids[]. */
int max_reps; /* Maximum repetitions of variable per PDU. */
int exp_reqid; /* Expect a response to this request only. */
int getlabel_f; /* Flag long/numeric names for get_label(). */
int sprintval_f; /* Flag enum/sprint values for sprintval(). */
int pkts_exch; /* Number of packet exchanges with agent. */
int oid_total; /* Total number of OIDs received this walk. */
int oid_saved; /* Total number of OIDs saved as results. */
} walk_context;
/* Prototypes for bulkwalk support functions. */
static netsnmp_pdu *_bulkwalk_send_pdu _((walk_context *context));
static int _bulkwalk_done _((walk_context *context));
static int _bulkwalk_recv_pdu _((walk_context *context, netsnmp_pdu *pdu));
static int _bulkwalk_finish _((walk_context *context, int okay));
static int _bulkwalk_async_cb _((int op, SnmpSession *ss, int reqid,
netsnmp_pdu *pdu, void *context_ptr));
/* Prototype for error handler */
void snmp_return_err( struct snmp_session *ss, SV *err_str, SV *err_num, SV *err_ind );
/* Structure to hold valid context sessions. */
struct valid_contexts {
walk_context **valid; /* Array of valid walk_context pointers. */
int sz_valid; /* Maximum size of valid contexts array. */
int num_valid; /* Count of valid contexts in the array. */
};
static struct valid_contexts *_valid_contexts = NULL;
static int _context_add _((walk_context *context));
static int _context_del _((walk_context *context));
static int _context_okay _((walk_context *context));
/* Wrapper around fprintf(stderr, ...) for clean and easy debug output. */
#ifdef DEBUGGING
static int _debug_level = 0;
#define DBOUT PerlIO_stderr(),
#define DBPRT(severity, otherargs) \
do { \
if (_debug_level && severity <= _debug_level) { \
(void)PerlIO_printf otherargs; \
} \
} while (/*CONSTCOND*/0)
char _debugx[1024]; /* Space to sprintf() into - used by sprint_objid(). */
/* wrapper around snprint_objid to snprint_objid to return the pointer
instead of length */
static char *
__snprint_oid(const oid *objid, size_t objidlen) {
snprint_objid(_debugx, sizeof(_debugx), objid, objidlen);
return _debugx;
}
#define DBDCL(x) x
#else /* DEBUGGING */
#define DBDCL(x)
#define DBOUT
/* Do nothing but in such a way that the compiler sees "otherargs". */
#define DBPRT(severity, otherargs) \
do { if (0) printf otherargs; } while(0)
static char *
__snprint_oid(const oid *objid, size_t objidlen)
{
return "(debugging is disabled)";
}
#endif /* DEBUGGING */
void
__libraries_init(char *appname)
{
static int have_inited = 0;
if (have_inited)
return;
have_inited = 1;
SOCK_STARTUP;
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_QUICK_PRINT, 1);
init_snmp(appname);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_BREAKDOWN_OIDS, 1);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_SUFFIX_ONLY, 1);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
NETSNMP_OID_OUTPUT_SUFFIX);
}
static void
__recalc_timeout (tvp, ctvp, ltvp, itvp, block)
struct timeval* tvp;
struct timeval* ctvp;
struct timeval* ltvp;
struct timeval* itvp;
int *block;
{
struct timeval now;
if (!timerisset(itvp)) return; /* interval zero means loop forever */
*block = 0;
gettimeofday(&now,(struct timezone *)0);
if (ctvp->tv_sec < 0) { /* first time or callback just fired */
timersub(&now,ltvp,ctvp);
timersub(ctvp,itvp,ctvp);
timersub(itvp,ctvp,ctvp);
timeradd(ltvp,itvp,ltvp);
} else {
timersub(&now,ltvp,ctvp);
timersub(itvp,ctvp,ctvp);
}
/* flag is set for callback but still hasnt fired so set to something
* small and we will service packets first if there are any ready
* (also guard against negative timeout - should never happen?)
*/
if (!timerisset(ctvp) || ctvp->tv_sec < 0 || ctvp->tv_usec < 0) {
ctvp->tv_sec = 0;
ctvp->tv_usec = 10;
}
/* if snmp timeout > callback timeout or no more requests to process */
if (timercmp(tvp, ctvp, >) || !timerisset(tvp)) {
*tvp = *ctvp; /* use the smaller non-zero timeout */
timerclear(ctvp); /* used as a flag to let callback fire on timeout */
}
}
static int
__is_numeric_oid (oidstr)
char* oidstr;
{
if (!oidstr) return 0;
for (; *oidstr; oidstr++) {
if (isalpha((int)*oidstr)) return 0;
}
return(1);
}
static int
__is_leaf (tp)
struct tree* tp;
{
char buf[MAX_TYPE_NAME_LEN];
return (tp && __get_type_str(tp->type,buf));
}
static SnmpMibNode*
__get_next_mib_node (tp)
SnmpMibNode* tp;
{
/* printf("tp = %lX, parent = %lX, peer = %lX, child = %lX\n",
tp, tp->parent, tp->next_peer, tp->child_list); */
if (tp->child_list) return(tp->child_list);
if (tp->next_peer) return(tp->next_peer);
if (!tp->parent) return(NULL);
for (tp = tp->parent; !tp->next_peer; tp = tp->parent) {
if (!tp->parent) return(NULL);
}
return(tp->next_peer);
}
static int
__translate_appl_type(typestr)
char* typestr;
{
if (typestr == NULL || *typestr == '\0') return TYPE_UNKNOWN;
if (!strncasecmp(typestr,"INTEGER32",8))
return(TYPE_INTEGER32);
if (!strncasecmp(typestr,"INTEGER",3))
return(TYPE_INTEGER);
if (!strncasecmp(typestr,"UNSIGNED32",3))
return(TYPE_UNSIGNED32);
if (!strcasecmp(typestr,"COUNTER")) /* check all in case counter64 */
return(TYPE_COUNTER);
if (!strncasecmp(typestr,"GAUGE",3))
return(TYPE_GAUGE);
if (!strncasecmp(typestr,"IPADDR",3))
return(TYPE_IPADDR);
if (!strncasecmp(typestr,"OCTETSTR",3))
return(TYPE_OCTETSTR);
if (!strncasecmp(typestr,"TICKS",3))
return(TYPE_TIMETICKS);
if (!strncasecmp(typestr,"OPAQUE",3))
return(TYPE_OPAQUE);
if (!strncasecmp(typestr,"OBJECTID",3))
return(TYPE_OBJID);
if (!strncasecmp(typestr,"NETADDR",3))
return(TYPE_NETADDR);
if (!strncasecmp(typestr,"COUNTER64",3))
return(TYPE_COUNTER64);
if (!strncasecmp(typestr,"NULL",3))
return(TYPE_NULL);
if (!strncasecmp(typestr,"BITS",3))
return(TYPE_BITSTRING);
if (!strncasecmp(typestr,"ENDOFMIBVIEW",3))
return(SNMP_ENDOFMIBVIEW);
if (!strncasecmp(typestr,"NOSUCHOBJECT",7))
return(SNMP_NOSUCHOBJECT);
if (!strncasecmp(typestr,"NOSUCHINSTANCE",7))
return(SNMP_NOSUCHINSTANCE);
if (!strncasecmp(typestr,"UINTEGER",3))
return(TYPE_UINTEGER); /* historic - should not show up */
/* but it does? */
if (!strncasecmp(typestr, "NOTIF", 3))
return(TYPE_NOTIFTYPE);
if (!strncasecmp(typestr, "TRAP", 4))
return(TYPE_TRAPTYPE);
return(TYPE_UNKNOWN);
}
static int
__translate_asn_type(type)
int type;
{
switch (type) {
case ASN_INTEGER:
return(TYPE_INTEGER);
break;
case ASN_OCTET_STR:
return(TYPE_OCTETSTR);
break;
case ASN_OPAQUE:
return(TYPE_OPAQUE);
break;
case ASN_OBJECT_ID:
return(TYPE_OBJID);
break;
case ASN_TIMETICKS:
return(TYPE_TIMETICKS);
break;
case ASN_GAUGE:
return(TYPE_GAUGE);
break;
case ASN_COUNTER:
return(TYPE_COUNTER);
break;
case ASN_IPADDRESS:
return(TYPE_IPADDR);
break;
case ASN_BIT_STR:
return(TYPE_BITSTRING);
break;
case ASN_NULL:
return(TYPE_NULL);
break;
/* no translation for these exception type values */
case SNMP_ENDOFMIBVIEW:
case SNMP_NOSUCHOBJECT:
case SNMP_NOSUCHINSTANCE:
return(type);
break;
case ASN_UINTEGER:
return(TYPE_UINTEGER);
break;
case ASN_COUNTER64:
return(TYPE_COUNTER64);
break;
default:
warn("translate_asn_type: unhandled asn type (%d)\n",type);
return(TYPE_OTHER);
break;
}
}
#define USE_BASIC 0
#define USE_ENUMS 1
#define USE_SPRINT_VALUE 2
static int
__snprint_value (buf, buf_len, var, tp, type, flag)
char * buf;
size_t buf_len;
netsnmp_variable_list * var;
struct tree * tp;
int type;
int flag;
{
int len = 0;
u_char* ip;
struct enum_list *ep;
buf[0] = '\0';
if (flag == USE_SPRINT_VALUE) {
snprint_value(buf, buf_len, var->name, var->name_length, var);
len = strlen(buf);
} else {
switch (var->type) {
case ASN_INTEGER:
if (flag == USE_ENUMS) {
for(ep = tp->enums; ep; ep = ep->next) {
if (ep->value == *var->val.integer) {
strlcpy(buf, ep->label, buf_len);
len = strlen(buf);
break;
}
}
}
if (!len) {
snprintf(buf, buf_len, "%ld", *var->val.integer);
buf[buf_len-1] = '\0';
len = strlen(buf);
}
break;
case ASN_GAUGE:
case ASN_COUNTER:
case ASN_TIMETICKS:
case ASN_UINTEGER:
snprintf(buf, buf_len, "%lu", (unsigned long) *var->val.integer);
buf[buf_len-1] = '\0';
len = strlen(buf);
break;
case ASN_OCTET_STR:
case ASN_OPAQUE:
len = var->val_len;
if ( len > buf_len )
len = buf_len;
memcpy(buf, (char*)var->val.string, len);
break;
case ASN_IPADDRESS:
ip = (u_char*)var->val.string;
snprintf(buf, buf_len, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
buf[buf_len-1] = '\0';
len = strlen(buf);
break;
case ASN_NULL:
break;
case ASN_OBJECT_ID:
__sprint_num_objid(buf, (oid *)(var->val.objid),
var->val_len/sizeof(oid));
len = strlen(buf);
break;
case SNMP_ENDOFMIBVIEW:
snprintf(buf, buf_len, "%s", "ENDOFMIBVIEW");
break;
case SNMP_NOSUCHOBJECT:
snprintf(buf, buf_len, "%s", "NOSUCHOBJECT");
break;
case SNMP_NOSUCHINSTANCE:
snprintf(buf, buf_len, "%s", "NOSUCHINSTANCE");
break;
case ASN_COUNTER64:
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
case ASN_OPAQUE_COUNTER64:
case ASN_OPAQUE_U64:
#endif
printU64(buf,(struct counter64 *)var->val.counter64);
len = strlen(buf);
break;
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
case ASN_OPAQUE_I64:
printI64(buf,(struct counter64 *)var->val.counter64);
len = strlen(buf);
break;
#endif
case ASN_BIT_STR:
snprint_bitstring(buf, buf_len, var, NULL, NULL, NULL);
len = strlen(buf);
break;
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
case ASN_OPAQUE_FLOAT:
if (var->val.floatVal)
snprintf(buf, buf_len, "%f", *var->val.floatVal);
break;
case ASN_OPAQUE_DOUBLE:
if (var->val.doubleVal)
snprintf(buf, buf_len, "%f", *var->val.doubleVal);
break;
#endif
case ASN_NSAP:
default:
warn("snprint_value: asn type not handled %d\n",var->type);
}
}
return(len);
}
static int
__sprint_num_objid (buf, objid, len)
char *buf;
oid *objid;
int len;
{
int i;
buf[0] = '\0';
for (i=0; i < len; i++) {
sprintf(buf,".%" NETSNMP_PRIo "u",*objid++);
buf += strlen(buf);
}
return SUCCESS;
}
static int
__tp_sprint_num_objid (buf, tp)
char *buf;
SnmpMibNode *tp;
{
oid newname[MAX_OID_LEN], *op;
/* code taken from get_node in snmp_client.c */
for (op = newname + MAX_OID_LEN - 1; op >= newname; op--) {
*op = tp->subid;
tp = tp->parent;
if (tp == NULL) break;
}
return __sprint_num_objid(buf, op, newname + MAX_OID_LEN - op);
}
static int
__scan_num_objid (buf, objid, len)
char *buf;
oid *objid;
size_t *len;
{
char *cp;
*len = 0;
if (*buf == '.') buf++;
cp = buf;
while (*buf) {
if (*buf++ == '.') {
sscanf(cp, "%" NETSNMP_PRIo "u", objid++);
/* *objid++ = atoi(cp); */
(*len)++;
cp = buf;
} else {
if (isalpha((int)*buf)) {
return FAILURE;
}
}
}
sscanf(cp, "%" NETSNMP_PRIo "u", objid++);
/* *objid++ = atoi(cp); */
(*len)++;
return SUCCESS;
}
static int
__get_type_str (type, str)
int type;
char * str;
{
switch (type) {
case TYPE_OBJID:
strcpy(str, "OBJECTID");
break;
case TYPE_OCTETSTR:
strcpy(str, "OCTETSTR");
break;
case TYPE_INTEGER:
strcpy(str, "INTEGER");
break;
case TYPE_INTEGER32:
strcpy(str, "INTEGER32");
break;
case TYPE_UNSIGNED32:
strcpy(str, "UNSIGNED32");
break;
case TYPE_NETADDR:
strcpy(str, "NETADDR");
break;
case TYPE_IPADDR:
strcpy(str, "IPADDR");
break;
case TYPE_COUNTER:
strcpy(str, "COUNTER");
break;
case TYPE_GAUGE:
strcpy(str, "GAUGE");
break;
case TYPE_TIMETICKS:
strcpy(str, "TICKS");
break;
case TYPE_OPAQUE:
strcpy(str, "OPAQUE");
break;
case TYPE_COUNTER64:
strcpy(str, "COUNTER64");
break;
case TYPE_NULL:
strcpy(str, "NULL");
break;
case SNMP_ENDOFMIBVIEW:
strcpy(str, "ENDOFMIBVIEW");
break;
case SNMP_NOSUCHOBJECT:
strcpy(str, "NOSUCHOBJECT");
break;
case SNMP_NOSUCHINSTANCE:
strcpy(str, "NOSUCHINSTANCE");
break;
case TYPE_UINTEGER:
strcpy(str, "UINTEGER"); /* historic - should not show up */
/* but it does? */
break;
case TYPE_NOTIFTYPE:
strcpy(str, "NOTIF");
break;
case TYPE_BITSTRING:
strcpy(str, "BITS");
break;
case TYPE_TRAPTYPE:
strcpy(str, "TRAP");
break;
case TYPE_OTHER: /* not sure if this is a valid leaf type?? */
case TYPE_NSAPADDRESS:
default: /* unsupported types for now */
strcpy(str, "");
return(FAILURE);
}
return SUCCESS;
}
/* does a destructive disection of <label1>...<labeln>.<iid> returning
<labeln> and <iid> in seperate strings (note: will destructively
alter input string, 'name') */
static int
__get_label_iid (name, last_label, iid, flag)
char * name;
char ** last_label;
char ** iid;
int flag;
{
char *lcp;
char *icp;
int len = strlen(name);
int found_label = 0;
*last_label = *iid = NULL;
if (len == 0) return(FAILURE);
/* Handle case where numeric oid's have been requested. The input 'name'
** in this case should be a numeric OID -- return failure if not.
*/
if ((flag & USE_NUMERIC_OIDS)) {
if (!__is_numeric_oid(name))
return(FAILURE);
/* Walk backward through the string, looking for first two '.' chars */
lcp = &(name[len]);
icp = NULL;
while (lcp > name) {
if (*lcp == '.') {
/* If this is the first occurence of '.', note it in icp.
** Otherwise, this must be the second occurrence, so break
** out of the loop.
*/
if (icp == NULL)
icp = lcp;
else
break;
}
lcp --;
}
/* Make sure we found at least a label and index. */
if (!icp)
return(FAILURE);
/* Push forward past leading '.' chars and separate the strings. */
lcp ++;
*icp ++ = '\0';
*last_label = (flag & USE_LONG_NAMES) ? name : lcp;
*iid = icp;
return(SUCCESS);
}
lcp = icp = &(name[len]);
while (lcp > name) {
if (*lcp == '.') {
if (found_label) {
lcp++;
break;
} else {
icp = lcp;
}
}
if (!found_label && isalpha((unsigned char)*lcp)) found_label = 1;
lcp--;
}
if (!found_label
|| ((icp + 1 >= name + len || !isdigit((unsigned char)*(icp+1)))
&& (flag & FAIL_ON_NULL_IID)))
return(FAILURE);
if (flag & NON_LEAF_NAME) { /* dont know where to start instance id */
/* put the whole thing in label */
icp = &(name[len]);
flag |= USE_LONG_NAMES;
/* special hack in case no mib loaded - object identifiers will
* start with .iso.<num>.<num>...., in which case it is preferable
* to make the label entirely numeric (i.e., convert "iso" => "1")
*/
if (*lcp == '.' && lcp == name) {
if (!strncmp(".ccitt.",lcp,7)) {
name += 2;
*name = '.';
*(name+1) = '0';
} else if (!strncmp(".iso.",lcp,5)) {
name += 2;
*name = '.';
*(name+1) = '1';
} else if (!strncmp(".joint-iso-ccitt.",lcp,17)) {
name += 2;
*name = '.';
*(name+1) = '2';
}
}
} else if (*icp) {
*(icp++) = '\0';
}
*last_label = (flag & USE_LONG_NAMES ? name : lcp);
*iid = icp;
return(SUCCESS);
}
static int
__oid_cmp(oida_arr, oida_arr_len, oidb_arr, oidb_arr_len)
oid *oida_arr;
size_t oida_arr_len;
oid *oidb_arr;
size_t oidb_arr_len;
{
for (;oida_arr_len && oidb_arr_len;
oida_arr++, oida_arr_len--, oidb_arr++, oidb_arr_len--) {
if (*oida_arr == *oidb_arr) continue;
return(*oida_arr > *oidb_arr ? 1 : -1);
}
if (oida_arr_len == oidb_arr_len) return(0);
return(oida_arr_len > oidb_arr_len ? 1 : -1);
}
/* Convert a tag (string) to an OID array */
/* Tag can be either a symbolic name, or an OID string */
static struct tree *
__tag2oid(tag, iid, oid_arr, oid_arr_len, type, best_guess)
char * tag;
char * iid;
oid * oid_arr;
size_t * oid_arr_len;
int * type;
int best_guess;
{
struct tree *tp = NULL;
struct tree *rtp = NULL;
oid newname[MAX_OID_LEN], *op;
size_t newname_len = 0;
if (type) *type = TYPE_UNKNOWN;
if (oid_arr_len) *oid_arr_len = 0;
if (!tag) goto done;
/*********************************************************/
/* best_guess = 0 - same as no switches (read_objid) */
/* if multiple parts, or uses find_node */
/* if a single leaf */
/* best_guess = 1 - same as -Ib (get_wild_node) */
/* best_guess = 2 - same as -IR (get_node) */
/*********************************************************/
/* numeric scalar (1,2) */
/* single symbolic (1,2) */
/* single regex (1) */
/* partial full symbolic (2) */
/* full symbolic (2) */
/* module::single symbolic (2) */
/* module::partial full symbolic (2) */
if (best_guess == 1 || best_guess == 2) {
if (!__scan_num_objid(tag, newname, &newname_len)) { /* make sure it's not a numeric tag */
newname_len = MAX_OID_LEN;
if (best_guess == 2) { /* Random search -IR */
if (get_node(tag, newname, &newname_len)) {
rtp = tp = get_tree(newname, newname_len, get_tree_head());
}
}
else if (best_guess == 1) { /* Regex search -Ib */
clear_tree_flags(get_tree_head());
if (get_wild_node(tag, newname, &newname_len)) {
rtp = tp = get_tree(newname, newname_len, get_tree_head());
}
}
}
else {
rtp = tp = get_tree(newname, newname_len, get_tree_head());
}
if (type) *type = (tp ? tp->type : TYPE_UNKNOWN);
if ((oid_arr == NULL) || (oid_arr_len == NULL)) return rtp;
memcpy(oid_arr,(char*)newname,newname_len*sizeof(oid));
*oid_arr_len = newname_len;
}
/* if best_guess is off and multi part tag or module::tag */
/* numeric scalar */
/* module::single symbolic */
/* module::partial full symbolic */
/* FULL symbolic OID */
else if (strchr(tag,'.') || strchr(tag,':')) {
if (!__scan_num_objid(tag, newname, &newname_len)) { /* make sure it's not a numeric tag */
newname_len = MAX_OID_LEN;
if (read_objid(tag, newname, &newname_len)) { /* long name */
rtp = tp = get_tree(newname, newname_len, get_tree_head());
}
}
else {
rtp = tp = get_tree(newname, newname_len, get_tree_head());
}
if (type) *type = (tp ? tp->type : TYPE_UNKNOWN);
if ((oid_arr == NULL) || (oid_arr_len == NULL)) return rtp;
memcpy(oid_arr,(char*)newname,newname_len*sizeof(oid));
*oid_arr_len = newname_len;
}
/* else best_guess is off and it is a single leaf */
/* single symbolic */
else {
rtp = tp = find_node(tag, get_tree_head());
if (tp) {
if (type) *type = tp->type;
if ((oid_arr == NULL) || (oid_arr_len == NULL)) return rtp;
/* code taken from get_node in snmp_client.c */
for(op = newname + MAX_OID_LEN - 1; op >= newname; op--){
*op = tp->subid;
tp = tp->parent;
if (tp == NULL)
break;
}
*oid_arr_len = newname + MAX_OID_LEN - op;
memcpy(oid_arr, op, *oid_arr_len * sizeof(oid));
} else {
return(rtp); /* HACK: otherwise, concat_oid_str confuses things */
}
}
done:
if (iid && *iid && oid_arr_len) __concat_oid_str(oid_arr, oid_arr_len, iid);
return(rtp);
}
/* function: __concat_oid_str
*
* This function converts a dotted-decimal string, soid_str, to an array
* of oid types and concatenates them on doid_arr begining at the index
* specified by doid_arr_len.
*
* returns : SUCCESS, FAILURE
*/
static int
__concat_oid_str(doid_arr, doid_arr_len, soid_str)
oid *doid_arr;
size_t *doid_arr_len;
char * soid_str;
{
char *soid_buf;
char *cp;
char *st;
if (!soid_str || !*soid_str) return SUCCESS;/* successfully added nothing */
if (*soid_str == '.') soid_str++;
soid_buf = netsnmp_strdup(soid_str);
if (!soid_buf)
return FAILURE;
cp = strtok_r(soid_buf,".",&st);
while (cp) {
sscanf(cp, "%" NETSNMP_PRIo "u", &(doid_arr[(*doid_arr_len)++]));
/* doid_arr[(*doid_arr_len)++] = atoi(cp); */
cp = strtok_r(NULL,".",&st);
}
netsnmp_free(soid_buf);
return(SUCCESS);
}
/*
* add a varbind to PDU
*/
static int
__add_var_val_str(pdu, name, name_length, val, len, type)
netsnmp_pdu *pdu;
oid *name;
size_t name_length;
char * val;
int len;
int type;
{
netsnmp_variable_list *vars;
oid oidbuf[MAX_OID_LEN];
int ret = SUCCESS;
if (pdu->variables == NULL){
pdu->variables = vars
= netsnmp_calloc(1, sizeof(netsnmp_variable_list));
} else {
for(vars = pdu->variables;
vars->next_variable;
vars = vars->next_variable)
/*EXIT*/;
vars->next_variable = netsnmp_calloc(1, sizeof(netsnmp_variable_list));
vars = vars->next_variable;
}
vars->next_variable = NULL;
vars->name = netsnmp_malloc(name_length * sizeof(oid));
memcpy((char *)vars->name, (char *)name, name_length * sizeof(oid));
vars->name_length = name_length;
switch (type) {
case TYPE_INTEGER:
case TYPE_INTEGER32:
vars->type = ASN_INTEGER;
vars->val.integer = netsnmp_malloc(sizeof(long));
if (val)
*(vars->val.integer) = strtol(val,NULL,0);
else {
ret = FAILURE;
*(vars->val.integer) = 0;
}
vars->val_len = sizeof(long);
break;
case TYPE_GAUGE:
case TYPE_UNSIGNED32:
vars->type = ASN_GAUGE;
goto as_uint;
case TYPE_COUNTER:
vars->type = ASN_COUNTER;
goto as_uint;
case TYPE_TIMETICKS:
vars->type = ASN_TIMETICKS;
goto as_uint;
case TYPE_UINTEGER:
vars->type = ASN_UINTEGER;
as_uint:
vars->val.integer = netsnmp_malloc(sizeof(long));
if (val)
sscanf(val,"%lu",vars->val.integer);
else {
ret = FAILURE;
*(vars->val.integer) = 0;
}
vars->val_len = sizeof(long);
break;
case TYPE_OCTETSTR:
vars->type = ASN_OCTET_STR;
goto as_oct;
case TYPE_BITSTRING:
vars->type = ASN_OCTET_STR;
goto as_oct;
case TYPE_OPAQUE:
vars->type = ASN_OCTET_STR;
as_oct:
vars->val.string = netsnmp_malloc(len);
vars->val_len = len;
if (val && len)
memcpy((char *)vars->val.string, val, len);
else {
ret = FAILURE;
vars->val.string = (u_char *) netsnmp_strdup("");
vars->val_len = 0;
}
break;
case TYPE_IPADDR:
vars->type = ASN_IPADDRESS;
vars->val.integer = netsnmp_malloc(sizeof(in_addr_t));
if (val)
*((in_addr_t *)vars->val.integer) = inet_addr(val);
else {
ret = FAILURE;
*(vars->val.integer) = 0;
}
vars->val_len = sizeof(in_addr_t);
break;
case TYPE_OBJID:
vars->type = ASN_OBJECT_ID;
vars->val_len = MAX_OID_LEN;
/* if (read_objid(val, oidbuf, &(vars->val_len))) { */
/* tp = __tag2oid(val,NULL,oidbuf,&(vars->val_len),NULL,0); */
if (!val || !snmp_parse_oid(val, oidbuf, &vars->val_len)) {
vars->val.objid = NULL;
ret = FAILURE;
} else {
vars->val_len *= sizeof(oid);
vars->val.objid = netsnmp_malloc(vars->val_len);
memcpy((char *)vars->val.objid, (char *)oidbuf, vars->val_len);
}
break;
default:
vars->type = ASN_NULL;
vars->val_len = 0;
vars->val.string = NULL;
ret = FAILURE;
}
return ret;
}
/* takes ss and pdu as input and updates the 'response' argument */
/* the input 'pdu' argument will be freed */
static int
__send_sync_pdu(ss, pdu, response, retry_nosuch,
err_str_sv, err_num_sv, err_ind_sv)
netsnmp_session *ss;
netsnmp_pdu *pdu;
netsnmp_pdu **response;
int retry_nosuch;
SV * err_str_sv;
SV * err_num_sv;
SV * err_ind_sv;
{
int status;
long command = pdu->command;
*response = NULL;
retry:
if(api_mode == SNMP_API_SINGLE)
{
status = snmp_sess_synch_response(ss, pdu, response);
} else {
status = snmp_synch_response(ss, pdu, response);
};
if ((*response == NULL) && (status == STAT_SUCCESS)) status = STAT_ERROR;
switch (status) {
case STAT_SUCCESS:
switch ((*response)->errstat) {
case SNMP_ERR_NOERROR:
break;
case SNMP_ERR_NOSUCHNAME:
if (retry_nosuch && (pdu = snmp_fix_pdu(*response, command))) {
if (*response) snmp_free_pdu(*response);
goto retry;
}
/* Pv1, SNMPsec, Pv2p, v2c, v2u, v2*, and SNMPv3 PDUs */
case SNMP_ERR_TOOBIG:
case SNMP_ERR_BADVALUE:
case SNMP_ERR_READONLY:
case SNMP_ERR_GENERR:
/* in SNMPv2p, SNMPv2c, SNMPv2u, SNMPv2*, and SNMPv3 PDUs */
case SNMP_ERR_NOACCESS:
case SNMP_ERR_WRONGTYPE:
case SNMP_ERR_WRONGLENGTH:
case SNMP_ERR_WRONGENCODING:
case SNMP_ERR_WRONGVALUE:
case SNMP_ERR_NOCREATION:
case SNMP_ERR_INCONSISTENTVALUE:
case SNMP_ERR_RESOURCEUNAVAILABLE:
case SNMP_ERR_COMMITFAILED:
case SNMP_ERR_UNDOFAILED:
case SNMP_ERR_AUTHORIZATIONERROR:
case SNMP_ERR_NOTWRITABLE:
/* in SNMPv2c, SNMPv2u, SNMPv2*, and SNMPv3 PDUs */
case SNMP_ERR_INCONSISTENTNAME:
default:
sv_catpv(err_str_sv,
(char*)snmp_errstring((*response)->errstat));
sv_setiv(err_num_sv, (*response)->errstat);
sv_setiv(err_ind_sv, (*response)->errindex);
status = (*response)->errstat;
break;
}
break;
case STAT_TIMEOUT:
case STAT_ERROR:
snmp_return_err(ss, err_str_sv, err_num_sv, err_ind_sv);
break;
default:
snmp_return_err(ss, err_str_sv, err_num_sv, err_ind_sv);
sv_catpv(err_str_sv, "send_sync_pdu: unknown status");
break;
}
return(status);
}
static int
__snmp_xs_cb (op, ss, reqid, pdu, cb_data)
int op;
netsnmp_session *ss;
int reqid;
netsnmp_pdu *pdu;
void *cb_data;
{
SV *varlist_ref;
AV *varlist;
SV *varbind_ref;
AV *varbind;
SV *traplist_ref = NULL;
AV *traplist = NULL;
netsnmp_variable_list *vars;
struct tree *tp;
int len;
SV *tmp_sv;
int type;
char tmp_type_str[MAX_TYPE_NAME_LEN];
char str_buf[STR_BUF_SIZE], *str_bufp = str_buf;
size_t str_buf_len = sizeof(str_buf);
size_t out_len = 0;
int buf_over = 0;
char *label;
char *iid;
char *cp;
int getlabel_flag = NO_FLAGS;
int sprintval_flag = USE_BASIC;
netsnmp_pdu *reply_pdu;
int old_numeric, old_printfull, old_format;
netsnmp_transport *transport = NULL;
SV* cb = ((struct snmp_xs_cb_data*)cb_data)->perl_cb;
SV* sess_ref = ((struct snmp_xs_cb_data*)cb_data)->sess_ref;
SV **err_str_svp = hv_fetch((HV*)SvRV(sess_ref), "ErrorStr", 8, 1);
SV **err_num_svp = hv_fetch((HV*)SvRV(sess_ref), "ErrorNum", 8, 1);
SV **err_ind_svp = hv_fetch((HV*)SvRV(sess_ref), "ErrorInd", 8, 1);
ENTER;
SAVETMPS;
if (cb_data != ss->callback_magic)
free(cb_data);
sv_setpv(*err_str_svp, (char*)snmp_errstring(pdu->errstat));
sv_setiv(*err_num_svp, pdu->errstat);
sv_setiv(*err_ind_svp, pdu->errindex);
varlist_ref = &sv_undef; /* Prevent unintialized use below. */
switch (op) {
case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE:
traplist_ref = NULL;
switch (pdu->command) {
case SNMP_MSG_INFORM:
/*
* Ideally, we would use the return value from the callback to
* decide what response, if any, we send, and what the error status
* and error index should be.
*/
reply_pdu = snmp_clone_pdu(pdu);
if (reply_pdu) {
reply_pdu->command = SNMP_MSG_RESPONSE;
reply_pdu->reqid = pdu->reqid;
reply_pdu->errstat = reply_pdu->errindex = 0;
if(api_mode == SNMP_API_SINGLE)
{
snmp_sess_send(ss, reply_pdu);
} else {
snmp_send(ss, reply_pdu);
}
} else {
warn("Couldn't clone PDU for inform response");
}
/* FALLTHRU */
case SNMP_MSG_TRAP:
case SNMP_MSG_TRAP2:
traplist = newAV();
traplist_ref = newRV_noinc((SV*)traplist);
#if 0
/* of dubious utility... */
av_push(traplist, newSViv(pdu->command));
#endif
av_push(traplist, newSViv(pdu->reqid));
if ((transport = snmp_sess_transport(snmp_sess_pointer(ss))) != NULL) {
cp = transport->f_fmtaddr(transport, pdu->transport_data,
pdu->transport_data_length);
av_push(traplist, newSVpv(cp, strlen(cp)));
netsnmp_free(cp);
} else {
/* This shouldn't ever happen; every session has a transport. */
av_push(traplist, newSVpv("", 0));
}
av_push(traplist, newSVpv((char*) pdu->community, pdu->community_len));
if (pdu->command == SNMP_MSG_TRAP) {
/* SNMP v1 only trap fields */
snprint_objid(str_buf, sizeof(str_buf), pdu->enterprise, pdu->enterprise_length);
av_push(traplist, newSVpv(str_buf,strlen(str_buf)));
cp = inet_ntoa(*((struct in_addr *) pdu->agent_addr));
av_push(traplist, newSVpv(cp,strlen(cp)));
av_push(traplist, newSViv(pdu->trap_type));
av_push(traplist, newSViv(pdu->specific_type));
/* perl didn't have perlSVuv until 5.6.0 */
tmp_sv=newSViv(0);
sv_setuv(tmp_sv, pdu->time);
av_push(traplist, tmp_sv);
}
/* FALLTHRU */
case SNMP_MSG_RESPONSE:
{
varlist = newAV();
varlist_ref = newRV_noinc((SV*)varlist);
/*
** Set up for numeric OID's, if necessary. Save the old values
** so that they can be restored when we finish -- these are
** library-wide globals, and have to be set/restored for each
** session.
*/
old_numeric = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS);
old_printfull = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID);
old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);
if (SvIV(*hv_fetch((HV*)SvRV(sess_ref),"UseLongNames", 12, 1))) {
getlabel_flag |= USE_LONG_NAMES;
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID, 1);
}
/* Setting UseNumeric forces UseLongNames on so check for UseNumeric
after UseLongNames (above) to make sure the final outcome of
NETSNMP_DS_LIB_OID_OUTPUT_FORMAT is NETSNMP_OID_OUTPUT_NUMERIC */
if (SvIV(*hv_fetch((HV*)SvRV(sess_ref),"UseNumeric", 10, 1))) {
getlabel_flag |= USE_NUMERIC_OIDS;
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, 1);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, NETSNMP_OID_OUTPUT_NUMERIC);
}
sv_bless(varlist_ref, gv_stashpv("SNMP::VarList",0));
for(vars = (pdu?pdu->variables:NULL); vars; vars = vars->next_variable) {
int local_getlabel_flag = getlabel_flag;
varbind = newAV();
varbind_ref = newRV_noinc((SV*)varbind);
sv_bless(varbind_ref, gv_stashpv("SNMP::Varbind",0));
av_push(varlist, varbind_ref);
*str_buf = '.';
*(str_buf+1) = '\0';
out_len = 0;
tp = netsnmp_sprint_realloc_objid_tree((u_char**)&str_bufp,
&str_buf_len,
&out_len, 0, &buf_over,
vars->name,vars->name_length);
str_buf[sizeof(str_buf)-1] = '\0';
if (__is_leaf(tp)) {
type = tp->type;
} else {
local_getlabel_flag |= NON_LEAF_NAME;
type = __translate_asn_type(vars->type);
}
__get_label_iid(str_buf,&label,&iid,local_getlabel_flag);
if (label) {
av_store(varbind, VARBIND_TAG_F,
newSVpv(label, strlen(label)));
} else {
av_store(varbind, VARBIND_TAG_F,
newSVpv("", 0));
}
if (iid) {
av_store(varbind, VARBIND_IID_F,
newSVpv(iid, strlen(iid)));
} else {
av_store(varbind, VARBIND_IID_F,
newSVpv("", 0));
}
__get_type_str(type, tmp_type_str);
tmp_sv = newSVpv(tmp_type_str, strlen(tmp_type_str));
av_store(varbind, VARBIND_TYPE_F, tmp_sv);
len = __snprint_value(str_buf, sizeof(str_buf),
vars, tp, type, sprintval_flag);
tmp_sv = newSVpv((char*)str_buf, len);
av_store(varbind, VARBIND_VAL_F, tmp_sv);
} /* for */
/* Reset the library's behavior for numeric/symbolic OID's. */
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, old_numeric );
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID, old_printfull);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, old_format);
} /* case SNMP_MSG_RESPONSE */
break;
default:;
} /* switch pdu->command */
break;
case NETSNMP_CALLBACK_OP_TIMED_OUT:
varlist_ref = &sv_undef;
sv_setpv(*err_str_svp, (char*)snmp_api_errstring(SNMPERR_TIMEOUT));
sv_setiv(*err_num_svp, SNMPERR_TIMEOUT);
break;
default:;
} /* switch op */
if (cb_data != ss->callback_magic)
sv_2mortal(cb);
cb = __push_cb_args2(cb,
(SvTRUE(varlist_ref) ? sv_2mortal(varlist_ref):varlist_ref),
(SvTRUE(traplist_ref) ? sv_2mortal(traplist_ref):traplist_ref));
__call_callback(cb, G_DISCARD);
FREETMPS;
LEAVE;
if (cb_data != ss->callback_magic)
sv_2mortal(sess_ref);
return 1;
}
static SV *
__push_cb_args2(sv,esv,tsv)
SV *sv;
SV *esv;
SV *tsv;
{
dSP;
if (SvTYPE(SvRV(sv)) != SVt_PVCV) sv = SvRV(sv);
PUSHMARK(sp);
if (SvTYPE(sv) == SVt_PVAV) {
AV *av = (AV *) sv;
int n = av_len(av) + 1;
SV **x = av_fetch(av, 0, 0);
if (x) {
int i = 1;
sv = *x;
for (i = 1; i < n; i++) {
x = av_fetch(av, i, 0);
if (x) {
SV *arg = *x;
XPUSHs(sv_mortalcopy(arg));
} else {
XPUSHs(&sv_undef);
}
}
} else {
sv = &sv_undef;
}
}
if (esv) XPUSHs(sv_mortalcopy(esv));
if (tsv) XPUSHs(sv_mortalcopy(tsv));
PUTBACK;
return sv;
}
static int
__call_callback(sv, flags)
SV *sv;
int flags;
{
I32 myframe = TOPMARK;
I32 count;
ENTER;
if (SvTYPE(sv) == SVt_PVCV)
{
count = perl_call_sv(sv, flags);
}
else if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVCV)
{
count = perl_call_sv(SvRV(sv), flags);
}
else
{
SV **top = stack_base + myframe + 1;
SV *obj = *top;
if (SvPOK(sv) && SvROK(obj) && SvOBJECT(SvRV(obj)))
{
count = perl_call_method(SvPV(sv, na), flags);
}
else if (SvPOK(obj) && SvROK(sv) && SvOBJECT(SvRV(sv)))
{
/* We have obj method ...
Used to be used instead of LangMethodCall()
*/
*top = sv;
count = perl_call_method(SvPV(obj, na), flags);
}
else
{
count = perl_call_sv(sv, flags);
}
}
LEAVE;
return count;
}
/* Bulkwalk support routines */
/* Add a context pointer to the list of valid pointers. Place it in the first
** NULL slot in the array.
*/
static int
_context_add(walk_context *context)
{
int i, j, new_sz;
if ((i = _context_okay(context)) != 0) /* Already exists? Okay. */
return i;
/* Initialize the array if necessary. */
if (_valid_contexts == NULL) {
/* Create the _valid_contexts structure. */
Newz(0, _valid_contexts, 1, struct valid_contexts);
assert(_valid_contexts != NULL);
/* Populate the original valid contexts array. */
Newz(0, _valid_contexts->valid, 4, walk_context *);
assert(_valid_contexts->valid != NULL);
/* Computer number of slots in the array. */
_valid_contexts->sz_valid = sizeof(*_valid_contexts->valid) /
sizeof(walk_context *);
for (i = 0; i < _valid_contexts->sz_valid; i++)
_valid_contexts->valid[i] = NULL;
DBPRT(3, (DBOUT "Created valid_context array 0x%p (%d slots)\n",
_valid_contexts->valid, _valid_contexts->sz_valid));
}
/* Search through the list, looking for NULL's -- unused slots. */
for (i = 0; i < _valid_contexts->sz_valid; i++)
if (_valid_contexts->valid[i] == NULL)
break;
/* Did we walk off the end of the list? Need to grow the list. Double
** it for now.
*/
if (i == _valid_contexts->sz_valid) {
new_sz = _valid_contexts->sz_valid * 2;
Renew(_valid_contexts->valid, new_sz, walk_context *);
assert(_valid_contexts->valid != NULL);
DBPRT(3, (DBOUT "Resized valid_context array 0x%p from %d to %d slots\n",
_valid_contexts->valid, _valid_contexts->sz_valid, new_sz));
_valid_contexts->sz_valid = new_sz;
/* Initialize the new half of the resized array. */
for (j = i; j < new_sz; j++)
_valid_contexts->valid[j] = NULL;
}
/* Store the context pointer in the array and return 0 (success). */
_valid_contexts->valid[i] = context;
DBPRT(3,(DBOUT "Add context 0x%p to valid context list\n", context));
return 0;
}
/* Remove a context pointer from the valid list. Replace the pointer with
** NULL in the valid pointer list.
*/
static int
_context_del(walk_context *context)
{
int i;
if (_valid_contexts == NULL) /* Make sure it was initialized. */
return 1;
for (i = 0; i < _valid_contexts->sz_valid; i++) {
if (_valid_contexts->valid[i] == context) {
DBPRT(3,(DBOUT "Remove context 0x%p from valid context list\n", context));
_valid_contexts->valid[i] = NULL; /* Remove it from the list. */
return 0; /* Return successful status. */
}
}
return 1;
}
/* Check if a specific context pointer is in the valid list. Return true (1)
** if the context is still in the valid list, or 0 if not (or context is NULL).
*/
static int
_context_okay(walk_context *context)
{
int i;
if (_valid_contexts == NULL) /* Make sure it was initialized. */
return 0;
if (context == NULL) /* Asked about a NULL context? Fail. */
return 0;
for (i = 0; i < _valid_contexts->sz_valid; i++)
if (_valid_contexts->valid[i] == context)
return 1; /* Found it! */
return 0; /* No match -- return failure. */
}
/* Check if the walk is completed, based upon the context. Also set the
** ignore flag on any completed variables -- this prevents them from being
** being sent in later packets.
*/
static int
_bulkwalk_done(walk_context *context)
{
int is_done = 1;
int i;
bulktbl *bt_entry; /* bulktbl requested OID entry */
/* Don't consider walk done until at least one packet has been exchanged. */
if (context->pkts_exch == 0)
return 0;
/* Fix up any requests that have completed. If the complete flag is set,
** or it is a non-repeater OID, set the ignore flag so that it will not
** be considered further. Assume we are done with the walk, and note
** otherwise if we aren't. Return 1 if all requests are complete, or 0
** if there's more to do.
*/
for (i = 0; i < context->nreq_oids; i ++) {
bt_entry = &context->req_oids[i];
if (bt_entry->complete || bt_entry->norepeat) {
/* This request is complete. Remove it from list of
** walks still in progress.
*/
DBPRT(1, (DBOUT "Ignoring %s request oid %s\n",
bt_entry->norepeat ? "nonrepeater" : "completed",
__snprint_oid(bt_entry->req_oid, bt_entry->req_len)));
/* Ignore this OID in any further packets. */
bt_entry->ignore = 1;
}
/* If any OID is not being ignored, the walk is not done. Must loop
** through all requests to do the fixup -- no early return possible.
*/
if (!bt_entry->ignore)
is_done = 0;
}
return is_done; /* Did the walk complete? */
}
/* Callback registered with SNMP. Return 1 from this callback to cause the
** current request to be deleted from the retransmit queue.
*/
static int
_bulkwalk_async_cb(int op,
SnmpSession *ss,
int reqid,
netsnmp_pdu *pdu,
void *context_ptr)
{
walk_context *context;
int done = 0;
SV **err_str_svp;
SV **err_num_svp;
/* Handle callback request for asynchronous bulkwalk. If the bulkwalk has
** not completed, and has not timed out, send the next request packet in
** the walk.
**
** Return 0 to indicate success (caller ignores return value).
*/
DBPRT(2, (DBOUT "bulkwalk_async_cb(op %d, reqid 0x%08X, context 0x%p)\n",
op, reqid, context_ptr));
context = (walk_context *)context_ptr;
/* Make certain this is a valid context pointer. This pdu may
** have been retransmitted after the bulkwalk was completed
** (and the context was destroyed). If so, just return.
*/
if (!_context_okay(context)) {
DBPRT(2,(DBOUT "Ignoring PDU for dead context 0x%p...\n", context));
return 1;
}
/* Is this a retransmission of a request we've already seen or some
** unexpected request id? If so, just ignore it.
*/
if (reqid != context->exp_reqid) {
DBPRT(2,
(DBOUT "Got reqid 0x%08X, expected reqid 0x%08X. Ignoring...\n", reqid,
context->exp_reqid));
return 1;
}
/* Ignore any future packets for this reqid. */
context->exp_reqid = -1;
err_str_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorStr", 8, 1);
err_num_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorNum", 8, 1);
switch (op) {
case NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE:
{
DBPRT(1,(DBOUT "Received message for reqid 0x%08X ...\n", reqid));
switch (pdu->command)
{
case SNMP_MSG_RESPONSE:
{
DBPRT(2, (DBOUT "Calling bulkwalk_recv_pdu(context 0x%p, pdu 0x%p)\n",
context_ptr, pdu));
/* Handle the response PDU. If an error occurs or there were
** no variables in the response, consider the walk done. If
** the response was okay, check if we have any more to do after
** this response.
*/
if (_bulkwalk_recv_pdu(context, pdu) <= 0)
done = 1;
else
done = _bulkwalk_done(context); /* Also set req ignore flags */
break;
}
default:
{
DBPRT(1,(DBOUT "unexpected pdu->command %d\n", pdu->command));
done = 1; /* "This can't happen!", so bail out when it does. */
break;
}
}
break;
}
case NETSNMP_CALLBACK_OP_TIMED_OUT:
{
DBPRT(1,(DBOUT "\n*** Timeout for reqid 0x%08X\n\n", reqid));
sv_setpv(*err_str_svp, (char*)snmp_api_errstring(SNMPERR_TIMEOUT));
sv_setiv(*err_num_svp, SNMPERR_TIMEOUT);
/* Timeout means something bad has happened. Return a not-okay
** result to the async callback.
*/
_bulkwalk_finish(context, 0 /* NOT OKAY */);
return 1;
}
default:
{
DBPRT(1,(DBOUT "unexpected callback op %d\n", op));
sv_setpv(*err_str_svp, (char*)snmp_api_errstring(SNMPERR_GENERR));
sv_setiv(*err_num_svp, SNMPERR_GENERR);
_bulkwalk_finish(context, 0 /* NOT OKAY */);
return 1;
}
}
/* We have either timed out, or received and parsed in a response. Now,
** if we have more variables to test, call bulkwalk_send_pdu() to enqueue
** another async packet, and return.
**
** If, however, the bulkwalk has completed (or an error has occurred that
** cuts the walk short), call bulkwalk_finish() to push the results onto
** the Perl call stack. Then explicitly call the Perl callback that was
** passed in by the user oh-so-long-ago.
*/
if (!done) {
DBPRT(1,(DBOUT "bulkwalk not complete -- send next pdu from callback\n"));
if (_bulkwalk_send_pdu(context) != NULL)
return 1;
DBPRT(1,(DBOUT "send_pdu() failed!\n"));
/* Fall through and return what we have so far. */
}
/* Call the perl callback with the return values and we're done. */
_bulkwalk_finish(context, 1 /* OKAY */);
return 1;
}
static netsnmp_pdu *
_bulkwalk_send_pdu(walk_context *context)
{
netsnmp_pdu *pdu = NULL;
netsnmp_pdu *response = NULL;
struct bulktbl *bt_entry;
int nvars = 0;
int reqid;
int status;
int i;
/* Send a pdu requesting any remaining variables in the context.
**
** In synchronous mode, returns a pointer to the response packet.
**
** In asynchronous mode, it returns the request ID, cast to a struct snmp *,
** not a valid SNMP response packet. The async code should not be trying
** to get variables out of this "response".
**
** In either case, return a NULL pointer on error or failure.
*/
SV **sess_ptr_sv = hv_fetch((HV*)SvRV(context->sess_ref), "SessPtr", 7, 1);
netsnmp_session *ss = (SnmpSession *)SvIV((SV*)SvRV(*sess_ptr_sv));
SV **err_str_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorStr", 8, 1);
SV **err_num_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorNum", 8, 1);
SV **err_ind_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorInd", 8, 1);
/* Create a new PDU and send the remaining set of requests to the agent. */
pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
if (pdu == NULL) {
sv_setpv(*err_str_svp, "snmp_pdu_create(GETBULK) failed: ");
sv_catpv(*err_str_svp, strerror(errno));
sv_setiv(*err_num_svp, SNMPERR_MALLOC);
goto err;
}
/* Request non-repeater variables only in the first packet exchange. */
pdu->errstat = (context->pkts_exch == 0) ? context->non_reps : 0;
pdu->errindex = context->max_reps;
for (i = 0; i < context->nreq_oids; i++) {
bt_entry = &context->req_oids[i];
if (bt_entry->ignore)
continue;
assert(bt_entry->complete == 0);
if (!snmp_add_null_var(pdu, bt_entry->last_oid, bt_entry->last_len)) {
sv_setpv(*err_str_svp, "snmp_add_null_var() failed");
sv_setiv(*err_num_svp, SNMPERR_GENERR);
sv_setiv(*err_ind_svp, i);
goto err;
}
nvars ++;
DBPRT(1, (DBOUT " Add %srepeater %s\n", bt_entry->norepeat ? "non" : "",
__snprint_oid(bt_entry->last_oid, bt_entry->last_len)));
}
/* Make sure variables are actually being requested in the packet. */
assert (nvars != 0);
context->pkts_exch ++;
DBPRT(1, (DBOUT "Sending %ssynchronous request %d...\n",
SvTRUE(context->perl_cb) ? "a" : "", context->pkts_exch));
/* We handle the asynchronous and synchronous requests differently here.
** For async, we simply enqueue the packet with a callback to handle the
** returned response, then return. Note that this we call the bulkwalk
** callback, and hand it the walk_context, not the Perl callback. The
** snmp_async_send() function returns the reqid on success, 0 on failure.
*/
if (SvTRUE(context->perl_cb)) {
if(api_mode == SNMP_API_SINGLE)
{
reqid = snmp_sess_async_send(ss, pdu, _bulkwalk_async_cb, (void *)context);
} else {
reqid = snmp_async_send(ss, pdu, _bulkwalk_async_cb, (void *)context);
}
DBPRT(2,(DBOUT "bulkwalk_send_pdu(): snmp_async_send => 0x%08X\n", reqid));
if (reqid == 0) {
snmp_return_err(ss, *err_num_svp, *err_ind_svp, *err_str_svp);
goto err;
}
/* Make a note of the request we expect to be answered. */
context->exp_reqid = reqid;
/* Callbacks take care of the rest. Let the caller know how many vars
** we sent in this request. Note that this is not a valid SNMP PDU,
** but that's because a response has not yet been received.
*/
return (netsnmp_pdu *)(intptr_t)reqid;
}
/* This code is for synchronous mode support.
**
** Send the PDU and block awaiting the response. Return the response
** packet back to the caller. Note that snmp_sess_read() frees the pdu.
*/
status = __send_sync_pdu(ss, pdu, &response, NO_RETRY_NOSUCH,
*err_str_svp, *err_num_svp, *err_ind_svp);
pdu = NULL;
/* Check for a failed request. __send_sync_pdu() will set the appropriate
** values in the error string and number SV's.
*/
if (status != STAT_SUCCESS) {
DBPRT(1,(DBOUT "__send_sync_pdu() -> %d\n",(int)status));
goto err;
}
DBPRT(1, (DBOUT "%d packets exchanged, response 0x%p\n", context->pkts_exch,
response));
return response;
err:
if (pdu)
snmp_free_pdu(pdu);
return NULL;
}
/* Handle an incoming GETBULK response PDU. This function just pulls the
** variables off of the PDU and builds up the arrays of returned values
** that are stored in the context.
**
** Returns the number of variables found in this packet, or -1 on error.
** Note that the caller is expected to free the pdu.
*/
static int
_bulkwalk_recv_pdu(walk_context *context, netsnmp_pdu *pdu)
{
netsnmp_variable_list *vars;
struct tree *tp;
char type_str[MAX_TYPE_NAME_LEN];
char str_buf[STR_BUF_SIZE], *str_bufp = str_buf;
size_t str_buf_len = sizeof(str_buf);
size_t out_len = 0;
int buf_over = 0;
char *label;
char *iid;
bulktbl *expect = NULL;
int old_numeric;
int old_printfull;
int old_format;
int getlabel_flag;
int type;
int pix;
int len;
int i;
AV *varbind;
SV *rv;
SV **sess_ptr_sv = hv_fetch((HV*)SvRV(context->sess_ref), "SessPtr", 7, 1);
SV **err_str_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorStr", 8, 1);
SV **err_num_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorNum", 8, 1);
SV **err_ind_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorInd", 8, 1);
int check = SvIV(*hv_fetch((HV*)SvRV(context->sess_ref), "NonIncreasing",13,1));
DBPRT(3, (DBOUT "bulkwalk: sess_ref = 0x%p, sess_ptr_sv = 0x%p\n",
context->sess_ref, sess_ptr_sv));
/* Set up for numeric OID's, if necessary. Save the old values
** so that they can be restored when we finish -- these are
** library-wide globals, and have to be set/restored for each
** session.
*/
old_numeric = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS);
old_printfull = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID);
old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);
if (context->getlabel_f & USE_NUMERIC_OIDS) {
DBPRT(2,(DBOUT "Using numeric oid's\n"));
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, 1);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID, 1);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, NETSNMP_OID_OUTPUT_NUMERIC);
}
/* Parse through the list of variables returned, adding each return to
** the appropriate array (as a VarBind). Also keep track of which
** repeated OID we're expecting to see, and check if that tree walk has
** been completed (i.e. we've walked past the root of our request). If
** so, mark the request complete so that we don't send it again in any
** subsequent request packets.
*/
if (context->pkts_exch == 1)
context->reqbase = context->req_oids; /* Request with non-repeaters */
else
context->reqbase = context->repbase; /* Request only repeater vars */
/* Note the first variable we expect to see. Should be reqbase. */
expect = context->reqbase;
for (vars = pdu->variables, pix = 0;
vars != NULL;
vars = vars->next_variable, pix ++)
{
/* If no outstanding requests remain, we're done. This works, but it
** causes the reported total variable count to be wrong (since the
** remaining vars on the last packet are not counted). In practice
** this is probably worth the win, but for debugging it's not.
*/
if (context->req_remain == 0) {
DBPRT(2,(DBOUT "No outstanding requests remain. Terminating processing.\n"));
while (vars) {
pix ++;
vars = vars->next_variable;
}
break;
}
/* Determine which OID we expect to see next. We assert that the OID's
** must be returned in the expected order. The first nreq_oids returns
** should match the req_oids array, after that, we must cycle through
** the repeaters in order. Non-repeaters are not included in later
** packets, so cannot have the "ignore" flag set.
*/
if (context->oid_saved < context->non_reps) {
assert(context->pkts_exch == 1);
expect = context->reqbase ++;
assert(expect->norepeat);
} else {
/* Must be a repeater. Look for the first one that is not being
** ignored. Make sure we don't loop around to where we started.
** If we get here but everything is being ignored, there's a problem.
**
** Note that we *do* accept completed but not ignored OID's -- these
** are OID's for trees that have been completed sometime in this
** response, but must be looked at to maintain ordering.
*/
/* In previous version we started from 1st repeater any time when
** pix == 0. But if 1st repeater is ignored we can get wrong results,
** because it was not included in 2nd and later request. So we set
** expect to repbase-1 and then search for 1st non-ignored repeater.
** repbase-1 is nessessary because we're starting search in loop below
** from ++expect and it will be exactly repbase on 1st search pass.
*/
if (pix == 0)
expect = context->repbase - 1;
/* Find the repeater OID we expect to see. Ignore any
** OID's marked 'ignore' -- these have been completed
** and were not requested in this iteration.
*/
for (i = 0; i < context->repeaters; i++) {
/* Loop around to first repeater if we hit the end. */
if (++ expect == &context->req_oids[context->nreq_oids])
expect = context->reqbase = context->repbase;
/* Stop if this OID is not being ignored. */
if (!expect->ignore)
break;
}
}
DBPRT(2, (DBOUT "Var %03d request %s\n", pix,
__snprint_oid(expect->req_oid, expect->req_len)));
/* Did we receive an error condition for this variable?
** If it's a repeated variable, mark it as complete and
** fall through to the block below.
*/
if ((vars->type == SNMP_ENDOFMIBVIEW) ||
(vars->type == SNMP_NOSUCHOBJECT) ||
(vars->type == SNMP_NOSUCHINSTANCE))
{
DBPRT(2,(DBOUT "error type %d\n", (int)vars->type));
/* ENDOFMIBVIEW should be okay for a repeater - just walked off the
** end of the tree. Mark the request as complete, and go on to the
** next one.
*/
if ((context->oid_saved >= context->non_reps) &&
(vars->type == SNMP_ENDOFMIBVIEW))
{
expect->complete = 1;
DBPRT(2, (DBOUT "Ran out of tree for oid %s\n",
__snprint_oid(vars->name,vars->name_length)));
context->req_remain --;
/* Go on to the next variable. */
continue;
}
sv_setpv(*err_str_svp,
(char*)snmp_api_errstring(SNMPERR_UNKNOWN_OBJID));
sv_setiv(*err_num_svp, SNMPERR_UNKNOWN_OBJID);
sv_setiv(*err_ind_svp, pix);
goto err;
}
/* If this is not the first packet, skip any duplicated OID values, if
** present. These should be the seed values copied from the last OID's
** of the previous packet. In practice we don't see this, but it is
** easy enough to do, and will avoid confusion for the caller from mis-
** behaving agents (badly misbehaving... ;^).
*/
if (context->pkts_exch > 1) {
if (__oid_cmp(vars->name, vars->name_length,
expect->last_oid, expect->last_len) <= 0)
{
if (check)
{
DBPRT(2, (DBOUT "Error: OID not increasing: %s\n",
__snprint_oid(vars->name,vars->name_length)));
sv_setpv(*err_str_svp, (char*)snmp_api_errstring(SNMPERR_OID_NONINCREASING));
sv_setiv(*err_num_svp, SNMPERR_OID_NONINCREASING);
sv_setiv(*err_ind_svp, pix);
goto err;
}
DBPRT(2, (DBOUT "Ignoring repeat oid: %s\n",
__snprint_oid(vars->name,vars->name_length)));
continue;
}
}
context->oid_total ++; /* Count each variable received. */
/* If this is a non-repeater, handle it. Otherwise, if it is a
** repeater, has the walk wandered off of the requested tree? If so,
** this request is complete, so mark it as such. Ignore any other
** variables in a completed request. In order to maintain the correct
** ordering of which variables we expect to see in this packet, we must
** not set the ignore flags immediately. It is done in bulkwalk_done().
*/
if (context->oid_saved < context->non_reps) {
DBPRT(2, (DBOUT " expected var %s (nonrepeater %d/%d)\n",
__snprint_oid(expect->req_oid, expect->req_len),
pix, context->non_reps));
DBPRT(2, (DBOUT " received var %s\n",
__snprint_oid(vars->name, vars->name_length)));
/* This non-repeater has now been seen, so mark the sub-tree as
** completed. Note that this may not be the same oid as requested,
** since non-repeaters act like GETNEXT requests, not GET's. <sigh>
*/
context->req_oids[pix].complete = 1;
context->req_remain --;
} else { /* Must be a repeater variable. */
DBPRT(2, (DBOUT " received oid %s\n",
__snprint_oid(vars->name, vars->name_length)));
/* Are we already done with this tree? If so, just ignore this
** variable and move on to the next expected variable.
*/
if (expect->complete) {
DBPRT(2,(DBOUT " this branch is complete - ignoring.\n"));
continue;
}
/* If the base oid of this variable doesn't match the expected oid,
** assume that we've walked past the end of the subtree. Set this
** subtree to be completed, and go on to the next variable.
*/
if ((vars->name_length < expect->req_len) ||
(memcmp(vars->name, expect->req_oid, expect->req_len*sizeof(oid))))
{
DBPRT(2,(DBOUT " walked off branch - marking subtree as complete.\n"));
expect->complete = 1;
context->req_remain --;
continue;
}
/* Still interested in the tree -- we need to keep track of the
** last-seen value in case we need to send an additional request
** packet.
*/
(void)memcpy(expect->last_oid, vars->name,
vars->name_length * sizeof(oid));
expect->last_len = vars->name_length;
}
/* Create a new Varbind and populate it with the parsed information
** returned by the agent. This Varbind is then pushed onto the arrays
** maintained for each request OID in the context. These varbinds are
** collected into a return array by bulkwalk_finish().
*/
varbind = (AV*) newAV();
if (varbind == NULL) {
sv_setpv(*err_str_svp, "newAV() failed: ");
sv_catpv(*err_str_svp, (char*)strerror(errno));
sv_setiv(*err_num_svp, SNMPERR_MALLOC);
goto err;
}
*str_buf = '.';
*(str_buf+1) = '\0';
out_len = 0;
tp = netsnmp_sprint_realloc_objid_tree((u_char**)&str_bufp, &str_buf_len,
&out_len, 0, &buf_over,
vars->name,vars->name_length);
str_buf[sizeof(str_buf)-1] = '\0';
getlabel_flag = context->getlabel_f;
if (__is_leaf(tp)) {
type = tp->type;
} else {
getlabel_flag |= NON_LEAF_NAME;
type = __translate_asn_type(vars->type);
}
if (__get_label_iid(str_buf, &label, &iid, getlabel_flag) == FAILURE) {
label = str_buf;
iid = label + strlen(label);
}
DBPRT(2,(DBOUT " save var %s.%s = ", label, iid));
av_store(varbind, VARBIND_TAG_F, newSVpv(label, strlen(label)));
av_store(varbind, VARBIND_IID_F, newSVpv(iid, strlen(iid)));
__get_type_str(type, type_str);
av_store(varbind, VARBIND_TYPE_F, newSVpv(type_str, strlen(type_str)));
len=__snprint_value(str_buf, sizeof(str_buf),
vars, tp, type, context->sprintval_f);
av_store(varbind, VARBIND_VAL_F, newSVpv(str_buf, len));
str_buf[len] = '\0';
DBPRT(3,(DBOUT "'%s' (%s)\n", str_buf, type_str));
#if 0
/* huh? */
/* If necessary, store a timestamp as the semi-documented 5th element. */
if (sv_timestamp)
av_store(varbind, VARBIND_TIME_F, SvREFCNT_inc(sv_timestamp));
#endif
/* Push ref to the varbind onto the list of vars for OID. */
rv = newRV_noinc((SV *)varbind);
sv_bless(rv, gv_stashpv("SNMP::Varbind", 0));
av_push(expect->vars, rv);
context->oid_saved ++; /* Count this as a saved variable. */
} /* next variable in response packet */
DBPRT(1, (DBOUT "-- pkt %d saw %d vars, total %d (%d saved)\n", context->pkts_exch,
pix, context->oid_total, context->oid_saved));
/* We assert that all non-repeaters must be returned in
** the initial response (they are not repeated in additional
** packets, so would be dropped). If nonrepeaters still
** exist, consider it a fatal error.
*/
if ((context->pkts_exch == 1) && (context->oid_saved < context->non_reps)) {
/* Re-use space from the value string for error message. */
sprintf(str_buf, "%d non-repeaters went unanswered", context->non_reps);
sv_setpv(*err_str_svp, str_buf);
sv_setiv(*err_num_svp, SNMPERR_GENERR);
sv_setiv(*err_num_svp, context->oid_saved);
goto err;
}
/* Reset the library's behavior for numeric/symbolic OID's. */
if (context->getlabel_f & USE_NUMERIC_OIDS) {
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, old_numeric);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_FULL_OID, old_printfull);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, old_format);
}
return pix;
err:
return -1;
}
/* Once the bulkwalk has completed, extend the stack and push references to
** each of the arrays of SNMP::Varbind's onto the stack. Return the number
** of arrays pushed on the stack. The caller should return to Perl, or call
** the Perl callback function.
**
** Note that this function free()'s the walk_context and request bulktbl's.
*/
static int
_bulkwalk_finish(walk_context *context, int okay)
{
dSP;
int npushed = 0;
int i;
int async = 0;
bulktbl *bt_entry;
AV *ary = NULL;
SV *rv;
SV *perl_cb;
SV **err_str_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorStr", 8, 1);
SV **err_num_svp = hv_fetch((HV*)SvRV(context->sess_ref), "ErrorNum", 8, 1);
async = SvTRUE(context->perl_cb);
/* XXX
_bulkwalk_finish() was originally intended to be called from XS code, and
would extend the caller's stack with result. Later it was changed into
an asynchronous version that calls perl code instead. These two branches
differ significantly in how they treat perl stack. Due to these differences,
often implicit (f.ex. dMARK calls POPMARK ), it would be a good idea
to write two different procedures, _bulkwalk_finish_sync and _bulkwalk_finish_async
for cleaner separation. */
if (async) PUSHMARK(sp);
{
#ifdef dITEMS
dMARK;
dITEMS;
#else
/* unfortunately this may pop a mark, which is not what we want */
/* older perl versions don't declare dITEMS though and the
following declars it but also uses dAXMARK instead of dMARK
which is the bad popping version */
dMARK;
/* err... This is essentially what the newer dITEMS does */
I32 items = sp - mark;
#endif
/* Successfully completed the bulkwalk. For synchronous calls, push each
** of the request value arrays onto the stack, and return the number of
** items pushed onto the stack. For async, create a new array and push
** the references onto it. The array is then passed to the Perl callback.
*/
if(!async)
SP -= items;
DBPRT(1, (DBOUT "Bulwalk %s (saved %d/%d), ", okay ? "completed" : "had error",
context->oid_saved, context->oid_total));
if (okay) {
DBPRT(1, (DBOUT "%s %d varbind refs %s\n",
async ? "pass ref to array of" : "return",
context->nreq_oids,
async ? "to callback" : "on stack to caller"));
/* Create the array to hold the responses for the asynchronous callback,
** or pre-extend the stack enough to hold responses for synch return.
*/
if (async) {
ary = (AV *)newAV();
if (ary == NULL) {
sv_setpv(*err_str_svp, "newAV(): ");
sv_catpv(*err_str_svp, (char *)strerror(errno));
sv_setiv(*err_num_svp, errno);
}
/* NULL ary pointer is okay -- we'll handle it below... */
} else {
EXTEND(sp, context->nreq_oids);
}
/* Push a reference to each array of varbinds onto the stack, in
** the order requested. Note that these arrays may be empty.
*/
for (i = 0; i < context->nreq_oids; i++) {
bt_entry = &context->req_oids[i];
DBPRT(2, (DBOUT " %sreq #%d (%s) => %d var%s\n",
bt_entry->complete ? "" : "incomplete ", i,
__snprint_oid(bt_entry->req_oid, bt_entry->req_len),
(int)av_len(bt_entry->vars) + 1,
(int)av_len(bt_entry->vars) > 0 ? "s" : ""));
if (async && ary == NULL) {
DBPRT(2,(DBOUT " [dropped due to newAV() failure]\n"));
continue;
}
/* Get a reference to the varlist, and push it onto array or stack */
rv = newRV_noinc((SV *)bt_entry->vars);
sv_bless(rv, gv_stashpv("SNMP::VarList",0));
if (async)
av_push(ary, rv);
else
PUSHs(sv_2mortal((SV *)rv));
npushed ++;
}
} else { /* Not okay -- push a single undef on the stack if not async */
if (!async) {
XPUSHs(&sv_undef);
npushed = 1;
} else {
for (i = 0; i < context->nreq_oids; i++) {
sv_2mortal((SV *) (context->req_oids[i].vars));
}
}
}
/* XXX Future enhancement -- make statistics (pkts exchanged, vars
** saved vs. received, total time, etc) available to caller so they
** can adjust their request parameters and/or re-order requests.
*/
if(!async)
SP -= items;
PUTBACK;
if (async) {
/* Asynchronous callback. Push the caller's arglist onto the stack,
** and follow it with the contents of the array (or undef if newAV()
** failed or the session had an error). Then mortalize the Perl
** callback pointer, and call the callback.
*/
if (!okay || ary == NULL)
rv = &sv_undef;
else
rv = newRV_noinc((SV *)ary);
sv_2mortal(perl_cb = context->perl_cb);
perl_cb = __push_cb_args(perl_cb, (SvTRUE(rv) ? sv_2mortal(rv) : rv));
__call_callback(perl_cb, G_DISCARD);
}
sv_2mortal(context->sess_ref);
/* Free the allocated space for the request states and return number of
** variables found. Remove the context from the valid context list.
*/
_context_del(context);
DBPRT(2,(DBOUT "Free() context->req_oids\n"));
Safefree(context->req_oids);
DBPRT(2,(DBOUT "Free() context 0x%p\n", context));
Safefree(context);
return npushed;
}}
/* End of bulkwalk support routines */
static char *
__av_elem_pv(AV *av, I32 key, char *dflt)
{
SV **elem = av_fetch(av, key, 0);
return (elem && SvOK(*elem)) ? SvPV(*elem, na) : dflt;
}
static int
not_here(const char *s)
{
warn("%s not implemented on this architecture", s);
return -1;
}
#define TEST_CONSTANT(value, name, C) \
if (strEQ(name, #C)) { \
*value = C; \
return 0; \
}
#define TEST_CONSTANT2(value, name, C, V) \
if (strEQ(name, #C)) { \
*value = V; \
return 0; \
}
static int constant(double *value, const char * const name, const int arg)
{
switch (*name) {
case 'N':
TEST_CONSTANT(value, name, NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE);
TEST_CONSTANT(value, name, NETSNMP_CALLBACK_OP_TIMED_OUT);
break;
case 'S':
TEST_CONSTANT(value, name, SNMPERR_BAD_ADDRESS);
TEST_CONSTANT(value, name, SNMPERR_BAD_LOCPORT);
TEST_CONSTANT(value, name, SNMPERR_BAD_SESSION);
TEST_CONSTANT(value, name, SNMPERR_GENERR);
TEST_CONSTANT(value, name, SNMPERR_TOO_LONG);
TEST_CONSTANT(value, name, SNMP_DEFAULT_ADDRESS);
TEST_CONSTANT(value, name, SNMP_DEFAULT_COMMUNITY_LEN);
TEST_CONSTANT(value, name, SNMP_DEFAULT_ENTERPRISE_LENGTH);
TEST_CONSTANT(value, name, SNMP_DEFAULT_ERRINDEX);
TEST_CONSTANT(value, name, SNMP_DEFAULT_ERRSTAT);
TEST_CONSTANT2(value, name, SNMP_DEFAULT_PEERNAME, 0);
TEST_CONSTANT(value, name, SNMP_DEFAULT_REMPORT);
TEST_CONSTANT(value, name, SNMP_DEFAULT_REQID);
TEST_CONSTANT(value, name, SNMP_DEFAULT_RETRIES);
TEST_CONSTANT(value, name, SNMP_DEFAULT_TIME);
TEST_CONSTANT(value, name, SNMP_DEFAULT_TIMEOUT);
TEST_CONSTANT2(value, name, SNMP_DEFAULT_VERSION,
NETSNMP_DEFAULT_SNMP_VERSION);
TEST_CONSTANT(value, name, SNMP_API_SINGLE);
TEST_CONSTANT(value, name, SNMP_API_TRADITIONAL);
break;
case 'X':
goto not_there;
break;
default:
break;
}
return EINVAL;
not_there:
not_here(name);
return ENOENT;
}
/*
Since s_snmp_errno can't be trusted with Single Session, this calls either
snmp_error or snmp_sess_error to populate ErrorStr,ErrorNum, and ErrorInd
in SNMP::Session objects
*/
void snmp_return_err( struct snmp_session *ss, SV *err_str, SV *err_num, SV *err_ind )
{
int err;
int liberr;
char *errstr;
if(ss == NULL)
return;
if(api_mode == SNMP_API_SINGLE)
{
snmp_sess_error(ss, &err, &liberr, &errstr);
} else {
snmp_error(ss, &err, &liberr, &errstr);
}
sv_catpv(err_str, errstr);
sv_setiv(err_num, liberr);
sv_setiv(err_ind, err);
netsnmp_free(errstr);
}
/*
int snmp_api_mode( int mode )
Returns or sets static int api_mode for reference by functions to determine
whether to use Traditional (non-threadsafe) or Single-Session (threadsafe)
SNMP API calls.
Call with (int)NULL to return the current mode, or with SNMP_API_TRADITIONAL
or SNMP_API_SINGLE to set the current mode. (defined above)
pm side call defaults to (int)NULL
*/
int snmp_api_mode( int mode )
{
if (mode == 0)
return api_mode;
api_mode = mode;
return api_mode;
}
MODULE = SNMP PACKAGE = SNMP PREFIX = snmp
void
constant(name,arg)
char * name
int arg
INIT:
int status;
double value;
PPCODE:
value = 0;
status = constant(&value, name, arg);
XPUSHs(sv_2mortal(newSVuv(status)));
XPUSHs(sv_2mortal(newSVnv(value)));
long
snmp_sys_uptime()
CODE:
RETVAL = get_uptime();
OUTPUT:
RETVAL
void
init_snmp(appname)
char *appname
CODE:
__libraries_init(appname);
#----------------------------------------------------------------------
# Perl call defaults to (int)NULL when given no args, so it will return
# the current api_mode values
#----------------------------------------------------------------------
int
snmp_api_mode(mode=0)
int mode
SnmpSession *
snmp_new_session(version, community, peer, lport, retries, timeout)
char * version
char * community
char * peer
int lport
int retries
int timeout
CODE:
{
SnmpSession session = {0};
SnmpSession *ss = NULL;
int verbose = SvIV(perl_get_sv("SNMP::verbose", 0x01 | 0x04));
__libraries_init("perl");
session.version = -1;
#ifndef NETSNMP_DISABLE_SNMPV1
if (!strcmp(version, "1")) {
session.version = SNMP_VERSION_1;
}
#endif
#ifndef NETSNMP_DISABLE_SNMPV2C
if ((!strcmp(version, "2")) || (!strcmp(version, "2c"))) {
session.version = SNMP_VERSION_2c;
}
#endif
if (!strcmp(version, "3")) {
session.version = SNMP_VERSION_3;
}
if (session.version == -1) {
if (verbose)
warn("error:snmp_new_session:Unsupported SNMP version (%s)\n", version);
goto end;
}
session.community_len = strlen((char *)community);
session.community = (u_char *)community;
session.peername = peer;
session.local_port = lport;
session.retries = retries; /* 5 */
session.timeout = timeout; /* 1000000L */
session.authenticator = NULL;
if(api_mode == SNMP_API_SINGLE)
{
ss = snmp_sess_open(&session);
} else {
ss = snmp_open(&session);
}
if (ss == NULL) {
if (verbose) warn("error:snmp_new_session: Couldn't open SNMP session");
}
end:
RETVAL = ss;
}
OUTPUT:
RETVAL
SnmpSession *
snmp_new_v3_session(version, peer, retries, timeout, sec_name, sec_level, sec_eng_id, context_eng_id, context, auth_proto, auth_pass, priv_proto, priv_pass, eng_boots, eng_time, auth_master_key, auth_master_key_len, priv_master_key, priv_master_key_len, auth_localized_key, auth_localized_key_len, priv_localized_key, priv_localized_key_len)
int version
char * peer
int retries
int timeout
char * sec_name
int sec_level
char * sec_eng_id
char * context_eng_id
char * context
char * auth_proto
char * auth_pass
char * priv_proto
char * priv_pass
int eng_boots
int eng_time
char * auth_master_key
size_t auth_master_key_len
char * priv_master_key
size_t priv_master_key_len
char * auth_localized_key
size_t auth_localized_key_len
char * priv_localized_key
size_t priv_localized_key_len
CODE:
{
SnmpSession session = {0};
SnmpSession *ss = NULL;
int verbose = SvIV(perl_get_sv("SNMP::verbose", 0x01 | 0x04));
__libraries_init("perl");
if (version == 3) {
session.version = SNMP_VERSION_3;
} else {
if (verbose)
warn("error:snmp_new_v3_session:Unsupported SNMP version (%d)\n", version);
goto end;
}
session.peername = peer;
session.retries = retries; /* 5 */
session.timeout = timeout; /* 1000000L */
session.authenticator = NULL;
session.contextNameLen = strlen(context);
session.contextName = context;
session.securityNameLen = strlen(sec_name);
session.securityName = sec_name;
session.securityLevel = sec_level;
session.securityModel = USM_SEC_MODEL_NUMBER;
session.securityEngineIDLen =
hex_to_binary2((u_char*)sec_eng_id, strlen(sec_eng_id),
(char **) &session.securityEngineID);
session.contextEngineIDLen =
hex_to_binary2((u_char*)context_eng_id, strlen(context_eng_id),
(char **) &session.contextEngineID);
session.engineBoots = eng_boots;
session.engineTime = eng_time;
#ifndef NETSNMP_DISABLE_MD5
if (!strcmp(auth_proto, "MD5")) {
session.securityAuthProto =
snmp_duplicate_objid(usmHMACMD5AuthProtocol,
USM_AUTH_PROTO_MD5_LEN);
session.securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN;
} else
#endif
if (!strcmp(auth_proto, "SHA")) {
session.securityAuthProto =
snmp_duplicate_objid(usmHMACSHA1AuthProtocol,
USM_AUTH_PROTO_SHA_LEN);
session.securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN;
} else if (!strcmp(auth_proto, "DEFAULT")) {
const oid *theoid =
get_default_authtype(&session.securityAuthProtoLen);
session.securityAuthProto =
snmp_duplicate_objid(theoid, session.securityAuthProtoLen);
} else {
if (verbose)
warn("error:snmp_new_v3_session:Unsupported authentication protocol(%s)\n", auth_proto);
goto end;
}
if (session.securityLevel >= SNMP_SEC_LEVEL_AUTHNOPRIV) {
if (auth_localized_key_len) {
session.securityAuthLocalKey =
netsnmp_memdup(auth_localized_key,
auth_localized_key_len);
session.securityAuthLocalKeyLen = auth_localized_key_len;
} else if (auth_master_key_len) {
session.securityAuthKeyLen =
SNMP_MIN(auth_master_key_len,
sizeof(session.securityAuthKey));
memcpy(session.securityAuthKey, auth_master_key,
session.securityAuthKeyLen);