#include <Python.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>
#include <stdio.h>
#include <ctype.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#endif
#include <netdb.h>
#include <stdlib.h>

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif

#define SUCCESS 1
#define FAILURE 0

#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 NO_RETRY_NOSUCH 0

/* 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

#define STRLEN(x) (x ? strlen(x) : 0)

/* from perl/SNMP/perlsnmp.h: */
#ifndef timeradd
#define	timeradd(a, b, result)						      \
  do {									      \
    (result)->tv_sec = (a)->tv_sec + (b)->tv_sec;			      \
    (result)->tv_usec = (a)->tv_usec + (b)->tv_usec;			      \
    if ((result)->tv_usec >= 1000000)					      \
      {									      \
	++(result)->tv_sec;						      \
	(result)->tv_usec -= 1000000;					      \
      }									      \
  } while (0)
#endif

#ifndef timersub
#define	timersub(a, b, result)						      \
  do {									      \
    (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;			      \
    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;			      \
    if ((result)->tv_usec < 0) {					      \
      --(result)->tv_sec;						      \
      (result)->tv_usec += 1000000;					      \
    }									      \
  } while (0)
#endif

typedef netsnmp_session SnmpSession;
typedef struct tree SnmpMibNode;
static void __recalc_timeout (struct timeval*,struct timeval*,
                                struct timeval*,struct timeval*, int* );
static in_addr_t __parse_address (char*);
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 *, int, oid *, int);
static int __tp_sprint_num_objid (char*,SnmpMibNode *);
static SnmpMibNode * __get_next_mib_node (SnmpMibNode *);
static struct tree * __oid2tp (oid*, int, struct tree *, int*);
static struct tree * __tag2oid (char *, char *, oid  *, int  *, int *, int);
static int __concat_oid_str (oid *, int *, char *);
static int __add_var_val_str (netsnmp_pdu *, oid *, int, char *,
                                 int, int);
#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


/* Wrapper around fprintf(stderr, ...) for clean and easy debug output. */
static int _debug_level = 0;
#ifdef	DEBUGGING
#define	DBPRT(severity, otherargs)					\
	do {								\
	    if (_debug_level && severity <= _debug_level) {		\
	      (void)printf(otherargs);					\
	    }								\
	} while (/*CONSTCOND*/0)
#else	/* DEBUGGING */
#define	DBPRT(severity, otherargs)	/* Ignore */
#endif	/* DEBUGGING */

#define SAFE_FREE(x) do {if (x != NULL) free(x);} while(/*CONSTCOND*/0)

void
__libraries_init(char *appname)
{
  static int have_inited = 0;

  if (have_inited)
    return;
  have_inited = 1;

  snmp_set_quick_print(1);
  snmp_enable_stderrlog();
  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 in_addr_t
__parse_address(address)
char *address;
{
    in_addr_t addr;
    struct sockaddr_in saddr;
    struct hostent *hp;

    if ((addr = inet_addr(address)) != -1)
	return addr;
    hp = gethostbyname(address);
    if (hp == NULL){
        return (-1); /* error value */
    } else {
	memcpy(&saddr.sin_addr, hp->h_addr, hp->h_length);
	return saddr.sin_addr.s_addr;
    }

}

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) || 
		  (tp->parent && __get_type_str(tp->parent->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:
            return(TYPE_OTHER);
            fprintf(stderr, "translate_asn_type: unhandled asn type (%d)\n",type);
            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) {
                    strncpy(buf, ep->label, buf_len);
                    buf[buf_len -1] = 0;
                    len = STRLEN(buf);
                    break;
                 }
              }
           }
           if (!len) {
              snprintf(buf, buf_len, "%ld", *var->val.integer);
              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);
           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]);
          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 OPAQUE_SPECIAL_TYPES
        case ASN_OPAQUE_COUNTER64:
        case ASN_OPAQUE_U64:
#endif
          printU64(buf,(struct counter64 *)var->val.counter64);
          len = STRLEN(buf);
          break;

#ifdef 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 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:
	  fprintf(stderr,"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,".%lu",*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, "%lu", objid++);
         /* *objid++ = atoi(cp); */
         (*len)++;
         cp = buf;
      } else {
         if (isalpha((int)*buf)) {
	    return FAILURE;
         }
      }
   }
   sscanf(cp, "%lu", 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, "");
	   if (_debug_level) printf("__get_type_str:FAILURE(%d)\n", type);

           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((int)*lcp)) found_label = 1;
      lcp--;
   }

   if (!found_label || (!isdigit((int)*(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;
int oida_arr_len;
oid *oidb_arr;
int 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;
int  * 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;

   char str_buf[STR_BUF_SIZE];
   str_buf[0] = '\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) __concat_oid_str(oid_arr, oid_arr_len, iid);
   return(rtp);
}
/* searches down the mib tree for the given oid
   returns the last found tp and its index in lastind
 */
static struct tree *
__oid2tp (oidp, len, subtree, lastind)
oid* oidp;
int len;
struct tree * subtree;
int* lastind;
{
    struct tree    *return_tree = NULL;


    for (; subtree; subtree = subtree->next_peer) {
	if (*oidp == subtree->subid){
	    goto found;
	}
    }
    *lastind=0;
    return NULL;

found:
    if (len > 1){
       return_tree =
          __oid2tp(oidp + 1, len - 1, subtree->child_list, lastind);
       (*lastind)++;
    } else {
       *lastind=1;
    }
    if (return_tree)
	return return_tree;
    else
	return subtree;
}

/* 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;
int *doid_arr_len;
char * soid_str;
{
   char soid_buf[STR_BUF_SIZE];
   char *cp;
   char *st;

   if (!soid_str || !*soid_str) return SUCCESS;/* successfully added nothing */
   if (*soid_str == '.') soid_str++;
   strcpy(soid_buf, soid_str);
   cp = strtok_r(soid_buf,".",&st);
   while (cp) {
     sscanf(cp, "%lu", &(doid_arr[(*doid_arr_len)++]));
     /* doid_arr[(*doid_arr_len)++] =  atoi(cp); */
     cp = strtok_r(NULL,".",&st);
   }
   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;
    int 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_variable_list *)calloc(1,sizeof(netsnmp_variable_list));
    } else {
	for(vars = pdu->variables;
            vars->next_variable;
            vars = vars->next_variable)
	    /*EXIT*/;
	vars->next_variable =
           (netsnmp_variable_list *)calloc(1,sizeof(netsnmp_variable_list));
	vars = vars->next_variable;
    }

    vars->next_variable = NULL;
    vars->name = (oid *)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 = (long *)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 UINT;
      case TYPE_COUNTER:
        vars->type = ASN_COUNTER;
        goto UINT;
      case TYPE_TIMETICKS:
        vars->type = ASN_TIMETICKS;
        goto UINT;
      case TYPE_UINTEGER:
        vars->type = ASN_UINTEGER;
UINT:
        vars->val.integer = (long *)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 OCT;

      case TYPE_BITSTRING:
	vars->type = ASN_OCTET_STR;
	goto OCT;

      case TYPE_OPAQUE:
        vars->type = ASN_OCTET_STR;
OCT:
        vars->val.string = (u_char *)malloc(len);
        vars->val_len = len;
        if (val && len)
            memcpy((char *)vars->val.string, val, len);
        else {
            ret = FAILURE;
            vars->val.string = (u_char*)strdup("");
            vars->val_len = 0;
        }
        break;

      case TYPE_IPADDR:
        vars->type = ASN_IPADDRESS;
        vars->val.integer = (long *)malloc(sizeof(long));
        if (val)
            *(vars->val.integer) = inet_addr(val);
        else {
            ret = FAILURE;
            *(vars->val.integer) = 0;
        }
        vars->val_len = sizeof(long);
        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 = (oid *)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, err_num, err_ind)
netsnmp_session *ss;
netsnmp_pdu *pdu;
netsnmp_pdu **response;
int retry_nosuch;
char *err_str;
int *err_num;
int *err_ind;
{
   int status;
   long command = pdu->command;
   char *tmp_err_str;

   *err_num = 0;
   *err_ind = 0;
   *response = NULL;
   tmp_err_str = NULL;
   memset(err_str, '\0', STR_BUF_SIZE);

   if (ss == NULL) {
       *err_num = 0;
       *err_ind = SNMPERR_BAD_SESSION;
       strncpy(err_str, snmp_api_errstring(*err_ind), STR_BUF_SIZE - 1);
       goto done;
   }

   tmp_err_str = calloc(1, STR_BUF_SIZE);
   if (tmp_err_str == NULL) {
       *err_num = errno;
       *err_ind = SNMPERR_MALLOC;
       strncpy(err_str, snmp_api_errstring(*err_ind), STR_BUF_SIZE - 1);
       goto done;
   }

retry:

   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:
               strncpy(err_str, (char*)snmp_errstring((*response)->errstat),
		       STR_BUF_SIZE - 1);
               *err_num = (int)(*response)->errstat;
	       *err_ind = (*response)->errindex;
               status = (*response)->errstat;
               break;
	 }
         break;

      case STAT_TIMEOUT:
      case STAT_ERROR:
	  snmp_sess_error(ss, err_num, err_ind, &tmp_err_str);
	  strncpy(err_str, tmp_err_str, STR_BUF_SIZE - 1);
	  err_str[STR_BUF_SIZE - 1] = '\0';
         break;

      default:
         strcat(err_str, "send_sync_pdu: unknown status");
         *err_num = ss->s_snmp_errno;
         break;
   }
done:
   if (tmp_err_str) {
   	free(tmp_err_str);
   }
   if (_debug_level && *err_num) printf("XXX sync PDU: %s\n", err_str);
   return(status);
}

static PyObject * 
py_netsnmp_construct_varbind(void)
{
  PyObject *module;
  PyObject *dict;
  PyObject *callable;

  module = PyImport_ImportModule("netsnmp");
  dict = PyModule_GetDict(module);

  callable = PyDict_GetItemString(dict, "Varbind");

  return PyObject_CallFunction(callable, "");
}

static int
py_netsnmp_attr_string(PyObject *obj, char * attr_name, char **val,
    Py_ssize_t *len)
{
  *val = NULL;
  if (obj && attr_name && PyObject_HasAttrString(obj, attr_name)) {
    PyObject *attr = PyObject_GetAttrString(obj, attr_name);
    if (attr) {
      int retval;
      retval = PyString_AsStringAndSize(attr, val, len);
      Py_DECREF(attr);
      return retval;
    }
  }

  return -1;
}

static long long
py_netsnmp_attr_long(PyObject *obj, char * attr_name)
{
  long long val = -1;

  if (obj && attr_name  && PyObject_HasAttrString(obj, attr_name)) {
    PyObject *attr = PyObject_GetAttrString(obj, attr_name);
    if (attr) {
      val = PyInt_AsLong(attr);
      Py_DECREF(attr);
    }
  }

  return val;
}

static void *
py_netsnmp_attr_void_ptr(PyObject *obj, char * attr_name)
{
  void *val = NULL;

  if (obj && attr_name  && PyObject_HasAttrString(obj, attr_name)) {
    PyObject *attr = PyObject_GetAttrString(obj, attr_name);
    if (attr) {
      val = PyInt_AsVoidPtr(attr);
      Py_DECREF(attr);
    }
  }

  return val;
}

static int
py_netsnmp_verbose(void)
{
  int verbose = 0;
  PyObject *pkg = PyImport_ImportModule("netsnmp");
  if (pkg) {
    verbose = py_netsnmp_attr_long(pkg, "verbose");
    Py_DECREF(pkg);
  }

  return verbose;
}

static int
py_netsnmp_attr_set_string(PyObject *obj, char *attr_name, 
			   char *val, size_t len)
{
  int ret = -1;
  if (obj && attr_name) {
    PyObject* val_obj =  (val ? 
			  Py_BuildValue("s#", val, len) : 
			  Py_BuildValue(""));
    ret = PyObject_SetAttrString(obj, attr_name, val_obj);
    Py_DECREF(val_obj);
  }
  return ret;
}

/**
 * Update python session object error attributes.
 *
 * Copy the error info which may have been returned from __send_sync_pdu(...)
 * into the python object. This will allow the python code to determine if
 * an error occured during an snmp operation.
 *
 * Currently there are 3 attributes we care about
 *
 * ErrorNum - Copy of the value of netsnmp_session.s_errno. This is the system
 * errno that was generated during our last call into the net-snmp library.
 *
 * ErrorInd - Copy of the value of netsmp_session.s_snmp_errno. These error
 * numbers are separate from the system errno's and describe SNMP errors.
 *
 * ErrorStr - A string describing the ErrorInd that was returned during our last
 * operation.
 *
 * @param[in] session The python object that represents our current Session
 * @param[in|out] err_str A string describing err_ind
 * @param[in|out] err_num The system errno currently stored by our session
 * @param[in|out] err_ind The snmp errno currently stored by our session
 */
static void
__py_netsnmp_update_session_errors(PyObject *session, char *err_str,
                                    int err_num, int err_ind)
{
    PyObject *tmp_for_conversion; 

    py_netsnmp_attr_set_string(session, "ErrorStr", err_str, STRLEN(err_str));

    tmp_for_conversion = PyInt_FromLong(err_num);
    PyObject_SetAttrString(session, "ErrorNum", tmp_for_conversion);
    Py_DECREF(tmp_for_conversion);

    tmp_for_conversion = PyInt_FromLong(err_ind);
    PyObject_SetAttrString(session, "ErrorInd", tmp_for_conversion);
    Py_DECREF(tmp_for_conversion);
}

static PyObject *
netsnmp_create_session(PyObject *self, PyObject *args)
{
  int version;
  char *community;
  char *peer;
  int  lport;
  int  retries;
  int  timeout;
  SnmpSession session = {0};
  SnmpSession *ss = NULL;
  int verbose = py_netsnmp_verbose();

  if (!PyArg_ParseTuple(args, "issiii", &version,
			&community, &peer, &lport, 
			&retries, &timeout))
    return NULL;

  __libraries_init("python");

  session.version = -1;
#ifndef DISABLE_SNMPV1
  if (version == 1) {
    session.version = SNMP_VERSION_1;
  }
#endif
#ifndef DISABLE_SNMPV2C
  if (version == 2) {
    session.version = SNMP_VERSION_2c;
  }
#endif
  if (version == 3) {
    session.version = SNMP_VERSION_3;
  }
  if (session.version == -1) {
    if (verbose)
      printf("error:snmp_new_session:Unsupported SNMP version (%d)\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;

  ss = snmp_open(&session);

  if (ss == NULL) {
    if (verbose) 
      printf("error:snmp_new_session: Couldn't open SNMP session");
  }
 end:
  return PyLong_FromVoidPtr((void *)ss);
}

static PyObject *
netsnmp_create_session_v3(PyObject *self, PyObject *args)
{
  int version;
  char *peer;
  int  lport;
  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;
  SnmpSession session = {0};
  SnmpSession *ss = NULL;
  int verbose = py_netsnmp_verbose();

  if (!PyArg_ParseTuple(args, "isiiisisssssssii", &version,
			&peer, &lport, &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))
    return NULL;

  __libraries_init("python");
  snmp_sess_init(&session);

  if (version == 3) {
    session.version = SNMP_VERSION_3;
  } else {
    if (verbose)
      printf("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((unsigned char*)sec_eng_id, STRLEN(sec_eng_id),
		   (char **) &session.securityEngineID);
  session.contextEngineIDLen =
    hex_to_binary2((unsigned char*)sec_eng_id, STRLEN(sec_eng_id),
		   (char **) &session.contextEngineID);
  session.engineBoots = eng_boots;
  session.engineTime = eng_time;

#ifndef 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")) {
      session.securityAuthProto = 
	get_default_authtype(&session.securityAuthProtoLen);
    } else {
      if (verbose)
	printf("error:snmp_new_v3_session:Unsupported authentication protocol(%s)\n", auth_proto);
      goto end;
    }
  if (session.securityLevel >= SNMP_SEC_LEVEL_AUTHNOPRIV) {
    if (STRLEN(auth_pass) > 0) {
      session.securityAuthKeyLen = USM_AUTH_KU_LEN;
      if (generate_Ku(session.securityAuthProto,
		      session.securityAuthProtoLen,
		      (u_char *)auth_pass, STRLEN(auth_pass),
		      session.securityAuthKey,
		      &session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
	if (verbose)
	  printf("error:snmp_new_v3_session:Error generating Ku from authentication password.\n");
	goto end;
      }
    }
  }
#ifndef DISABLE_DES
  if (!strcmp(priv_proto, "DES")) {
    session.securityPrivProto =
      snmp_duplicate_objid(usmDESPrivProtocol,
			   USM_PRIV_PROTO_DES_LEN);
    session.securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN;
  } else
#endif
    if (!strncmp(priv_proto, "AES", 3)) {
      session.securityPrivProto =
	snmp_duplicate_objid(usmAESPrivProtocol,
			     USM_PRIV_PROTO_AES_LEN);
      session.securityPrivProtoLen = USM_PRIV_PROTO_AES_LEN;
    } else if (!strcmp(priv_proto, "DEFAULT")) {
      session.securityPrivProto = 
	get_default_privtype(&session.securityPrivProtoLen);
    } else {
      if (verbose)
	printf("error:snmp_new_v3_session:Unsupported privacy protocol(%s)\n", priv_proto);
      goto end;
    }

  if (session.securityLevel >= SNMP_SEC_LEVEL_AUTHPRIV) {
    session.securityPrivKeyLen = USM_PRIV_KU_LEN;
    if (generate_Ku(session.securityAuthProto,
		    session.securityAuthProtoLen,
		    (u_char *)priv_pass, STRLEN(priv_pass),
		    session.securityPrivKey,
		    &session.securityPrivKeyLen) != SNMPERR_SUCCESS) {
      if (verbose)
	printf("error:v3_session: couldn't gen Ku from priv pass phrase.\n");
      goto end;
    }
  }
  
  ss = snmp_sess_open(&session);

 end:
  if (ss == NULL) {
    if (verbose) 
      printf("error:v3_session: couldn't open SNMP session(%s).\n",
	     snmp_api_errstring(snmp_errno));
  }
  free (session.securityEngineID);
  free (session.contextEngineID);

  return PyLong_FromVoidPtr((void *)ss);
}

static PyObject *
netsnmp_delete_session(PyObject *self, PyObject *args)
{
  PyObject *session;
  SnmpSession *ss = NULL;

  if (!PyArg_ParseTuple(args, "O", &session)) {
    return NULL;
  }

  ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

  snmp_close(ss);
  return (Py_BuildValue(""));
}


static PyObject *
netsnmp_get(PyObject *self, PyObject *args)
{
  PyObject *session;
  PyObject *varlist;
  PyObject *varbind;
  PyObject *val_tuple = NULL;
  int varlist_len = 0;
  int varlist_ind;
  netsnmp_session *ss;
  netsnmp_pdu *pdu, *response;
  netsnmp_variable_list *vars;
  struct tree *tp;
  int len;
  oid *oid_arr;
  int oid_arr_len = MAX_OID_LEN;
  int type;
  char type_str[MAX_TYPE_NAME_LEN];
  int status;
  u_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 *tag;
  char *iid;
  int getlabel_flag = NO_FLAGS;
  int sprintval_flag = USE_BASIC;
  int verbose = py_netsnmp_verbose();
  int old_format;
  int best_guess;
  int retry_nosuch;
  int err_ind;
  int err_num;
  char err_str[STR_BUF_SIZE];
  char *tmpstr;
  Py_ssize_t tmplen;
	   
  oid_arr = calloc(MAX_OID_LEN, sizeof(oid));

  if (oid_arr && args) {

    if (!PyArg_ParseTuple(args, "OO", &session, &varlist)) {
      goto done;
    }

    ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

    if (py_netsnmp_attr_string(session, "ErrorStr", &tmpstr, &tmplen) < 0) {
      goto done;
    }

    if (py_netsnmp_attr_long(session, "UseLongNames"))
      getlabel_flag |= USE_LONG_NAMES;
    if (py_netsnmp_attr_long(session, "UseNumeric"))
      getlabel_flag |= USE_NUMERIC_OIDS;
    if (py_netsnmp_attr_long(session, "UseEnums"))
      sprintval_flag = USE_ENUMS;
    if (py_netsnmp_attr_long(session, "UseSprintValue"))
      sprintval_flag = USE_SPRINT_VALUE;	
    best_guess = py_netsnmp_attr_long(session, "BestGuess");
    retry_nosuch = py_netsnmp_attr_long(session, "RetryNoSuch");
      
    pdu = snmp_pdu_create(SNMP_MSG_GET);

    if (varlist) {
      PyObject *varlist_iter = PyObject_GetIter(varlist);

      while (varlist_iter && (varbind = PyIter_Next(varlist_iter))) {
	if (py_netsnmp_attr_string(varbind, "tag", &tag, NULL) < 0 ||
	    py_netsnmp_attr_string(varbind, "iid", &iid, NULL) < 0)
	{
	  oid_arr_len = 0;
	} else {
	  tp = __tag2oid(tag, iid, oid_arr, &oid_arr_len, NULL, best_guess);
	}

	if (oid_arr_len) {
	  snmp_add_null_var(pdu, oid_arr, oid_arr_len);
	  varlist_len++;
	} else {
	  if (verbose)
	    printf("error: get: unknown object ID (%s)",
		   (tag ? tag : "<null>"));
	  snmp_free_pdu(pdu);
	  goto done;
	}
	/* release reference when done */
	Py_DECREF(varbind);
      }

      Py_DECREF(varlist_iter);

      if (PyErr_Occurred()) {
	/* propagate error */
	if (verbose)
	  printf("error: get: unknown python error");
	snmp_free_pdu(pdu);
	goto done;
      }
    }

    status = __send_sync_pdu(ss, pdu, &response, retry_nosuch, 
			     err_str, &err_num, &err_ind);
    __py_netsnmp_update_session_errors(session, err_str, err_num, err_ind);

    /*
    ** Set up for numeric or full OID's, if necessary.  Save the old
    ** output format so that it can be restored when we finish -- this
    ** is a library-wide global, and has to be set/restored for each
    ** session.
    */
    old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
				    NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);

    if (py_netsnmp_attr_long(session, "UseLongNames")) {
      getlabel_flag |= USE_LONG_NAMES;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			 NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			 NETSNMP_OID_OUTPUT_FULL);
    }
    /* 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 (py_netsnmp_attr_long(session, "UseNumeric")) {
      getlabel_flag |= USE_LONG_NAMES;
      getlabel_flag |= USE_NUMERIC_OIDS;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			 NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			 NETSNMP_OID_OUTPUT_NUMERIC);
    }

    val_tuple = PyTuple_New(varlist_len);
    /* initialize return tuple */
    for (varlist_ind = 0; varlist_ind < varlist_len; varlist_ind++) {
      PyTuple_SetItem(val_tuple, varlist_ind, Py_BuildValue(""));
    }

    for(vars = (response ? response->variables : NULL), varlist_ind = 0;
	vars && (varlist_ind < varlist_len);
	vars = vars->next_variable, varlist_ind++) {

      varbind = PySequence_GetItem(varlist, varlist_ind);

      if (PyObject_HasAttrString(varbind, "tag")) {
	*str_buf = '.';
	*(str_buf+1) = '\0';
	out_len = 0;
	tp = netsnmp_sprint_realloc_objid_tree(&str_bufp, &str_buf_len,
					       &out_len, 0, &buf_over,
					       vars->name,vars->name_length);
	if (_debug_level) 
	  printf("netsnmp_get:str_bufp:%s:%d:%d\n",
		 str_bufp,str_buf_len,out_len);

	str_buf[sizeof(str_buf)-1] = '\0';

	if (__is_leaf(tp)) {
	  type = (tp->type ? tp->type : tp->parent->type);
	  getlabel_flag &= ~NON_LEAF_NAME;
	  if (_debug_level) printf("netsnmp_get:is_leaf:%d\n",type);
	} else {
	  getlabel_flag |= NON_LEAF_NAME;
	  type = __translate_asn_type(vars->type);
	  if (_debug_level) printf("netsnmp_get:!is_leaf:%d\n",tp->type);
	}
	
	if (_debug_level) printf("netsnmp_get:str_buf:%s\n",str_buf);

	__get_label_iid(str_buf, &tag, &iid, getlabel_flag);

	py_netsnmp_attr_set_string(varbind, "tag", tag, STRLEN(tag));
	py_netsnmp_attr_set_string(varbind, "iid", iid, STRLEN(iid));

	__get_type_str(type, type_str);

	py_netsnmp_attr_set_string(varbind, "type", type_str, 
				   STRLEN(type_str));

	len = __snprint_value(str_buf,sizeof(str_buf),
			    vars,tp,type,sprintval_flag);
	str_buf[len] = '\0';
	py_netsnmp_attr_set_string(varbind, "val", str_buf, len);

	/* save in return tuple as well */
	PyTuple_SetItem(val_tuple, varlist_ind, 
			(len ? Py_BuildValue("s#", str_buf, len) :
			 Py_BuildValue("")));

	Py_DECREF(varbind);
      } else {
	printf("netsnmp_get: bad varbind (%d)\n", varlist_ind);
      }	
    }

    /* Reset the library's behavior for numeric/symbolic OID's. */
    netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
		       NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
		       old_format);

    if (response) snmp_free_pdu(response);
  }

 done:
  SAFE_FREE(oid_arr);
  return (val_tuple ? val_tuple : Py_BuildValue(""));
}

static PyObject *
netsnmp_getnext(PyObject *self, PyObject *args)
{
  PyObject *session;
  PyObject *varlist;
  PyObject *varbind;
  PyObject *val_tuple = NULL;
  int varlist_len = 0;
  int varlist_ind;
  netsnmp_session *ss;
  netsnmp_pdu *pdu, *response;
  netsnmp_variable_list *vars;
  struct tree *tp;
  int len;
  oid *oid_arr;
  int oid_arr_len = MAX_OID_LEN;
  int type;
  char type_str[MAX_TYPE_NAME_LEN];
  int status;
  u_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 *tag;
  char *iid;
  int getlabel_flag = NO_FLAGS;
  int sprintval_flag = USE_BASIC;
  int verbose = py_netsnmp_verbose();
  int old_format;
  int best_guess;
  int retry_nosuch;
  int err_ind;
  int err_num;
  char err_str[STR_BUF_SIZE];
  char *tmpstr;
  Py_ssize_t tmplen;
	   
  oid_arr = calloc(MAX_OID_LEN, sizeof(oid));

  if (oid_arr && args) {

    if (!PyArg_ParseTuple(args, "OO", &session, &varlist)) {
      goto done;
    }

    ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

    if (py_netsnmp_attr_string(session, "ErrorStr", &tmpstr, &tmplen) < 0) {
      goto done;
    }
    memcpy(&err_str, tmpstr, tmplen);
    err_num = py_netsnmp_attr_long(session, "ErrorNum");
    err_ind = py_netsnmp_attr_long(session, "ErrorInd");

    if (py_netsnmp_attr_long(session, "UseLongNames"))
      getlabel_flag |= USE_LONG_NAMES;
    if (py_netsnmp_attr_long(session, "UseNumeric"))
      getlabel_flag |= USE_NUMERIC_OIDS;
    if (py_netsnmp_attr_long(session, "UseEnums"))
      sprintval_flag = USE_ENUMS;
    if (py_netsnmp_attr_long(session, "UseSprintValue"))
      sprintval_flag = USE_SPRINT_VALUE;	
    best_guess = py_netsnmp_attr_long(session, "BestGuess");
    retry_nosuch = py_netsnmp_attr_long(session, "RetryNoSuch");
      
    pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);

    if (varlist) {
      PyObject *varlist_iter = PyObject_GetIter(varlist);

      while (varlist_iter && (varbind = PyIter_Next(varlist_iter))) {
	if (py_netsnmp_attr_string(varbind, "tag", &tag, NULL) < 0 ||
	    py_netsnmp_attr_string(varbind, "iid", &iid, NULL) < 0)
	{
	  oid_arr_len = 0;
	} else {
	  tp = __tag2oid(tag, iid, oid_arr, &oid_arr_len, NULL, best_guess);
	}

	if (_debug_level) 
	  printf("netsnmp_getnext: filling request: %s:%s:%d:%d\n", 
		 tag, iid, oid_arr_len,best_guess);

	if (oid_arr_len) {
	  snmp_add_null_var(pdu, oid_arr, oid_arr_len);
	  varlist_len++;
	} else {
	  if (verbose)
	    printf("error: get: unknown object ID (%s)",
		   (tag ? tag : "<null>"));
	  snmp_free_pdu(pdu);
	  goto done;
	}
	/* release reference when done */
	Py_DECREF(varbind);
      }

      Py_DECREF(varlist_iter);

      if (PyErr_Occurred()) {
	/* propagate error */
	if (verbose)
	  printf("error: get: unknown python error");
	snmp_free_pdu(pdu);
	goto done;
      }
    }

    status = __send_sync_pdu(ss, pdu, &response, retry_nosuch, 
			     err_str, &err_num, &err_ind);
    __py_netsnmp_update_session_errors(session, err_str, err_num, err_ind);

    /*
    ** Set up for numeric or full OID's, if necessary.  Save the old
    ** output format so that it can be restored when we finish -- this
    ** is a library-wide global, and has to be set/restored for each
    ** session.
    */
    old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
				    NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);

    if (py_netsnmp_attr_long(session, "UseLongNames")) {
      getlabel_flag |= USE_LONG_NAMES;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			 NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			 NETSNMP_OID_OUTPUT_FULL);
    }
    /* 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 (py_netsnmp_attr_long(session, "UseNumeric")) {
      getlabel_flag |= USE_LONG_NAMES;
      getlabel_flag |= USE_NUMERIC_OIDS;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			 NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			 NETSNMP_OID_OUTPUT_NUMERIC);
    }

    val_tuple = PyTuple_New(varlist_len);
    /* initialize return tuple */
    val_tuple = PyTuple_New(varlist_len);
    for (varlist_ind = 0; varlist_ind < varlist_len; varlist_ind++) {
      PyTuple_SetItem(val_tuple, varlist_ind, Py_BuildValue(""));
    }

    for(vars = (response ? response->variables : NULL), varlist_ind = 0;
	vars && (varlist_ind < varlist_len);
	vars = vars->next_variable, varlist_ind++) {

      varbind = PySequence_GetItem(varlist, varlist_ind);

      if (PyObject_HasAttrString(varbind, "tag")) {
	*str_buf = '.';
	*(str_buf+1) = '\0';
	out_len = 0;
	tp = netsnmp_sprint_realloc_objid_tree(&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 ? tp->type : tp->parent->type);
	  getlabel_flag &= ~NON_LEAF_NAME;
	} else {
	  getlabel_flag |= NON_LEAF_NAME;
	  type = __translate_asn_type(vars->type);
	}

	__get_label_iid(str_buf, &tag, &iid, getlabel_flag);

	if (_debug_level) 
	  printf("netsnmp_getnext: filling response: %s:%s\n", tag, iid);

	py_netsnmp_attr_set_string(varbind, "tag", tag, STRLEN(tag));
	py_netsnmp_attr_set_string(varbind, "iid", iid, STRLEN(iid));

	__get_type_str(type, type_str);

	py_netsnmp_attr_set_string(varbind, "type", type_str, 
				   STRLEN(type_str));

	len = __snprint_value(str_buf,sizeof(str_buf),
			    vars,tp,type,sprintval_flag);
	str_buf[len] = '\0';

	py_netsnmp_attr_set_string(varbind, "val", str_buf, len);

	/* save in return tuple as well */
	PyTuple_SetItem(val_tuple, varlist_ind, 
			(len ? Py_BuildValue("s#", str_buf, len) :
			 Py_BuildValue("")));

	Py_DECREF(varbind);
      } else {
	printf("netsnmp_getnext: bad varbind (%d)\n", varlist_ind);
      }
    }

    /* Reset the library's behavior for numeric/symbolic OID's. */
    netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
		       NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
		       old_format);

    if (response) snmp_free_pdu(response);
  }

 done:
  SAFE_FREE(oid_arr);
  return (val_tuple ? val_tuple : Py_BuildValue(""));
}

static PyObject *
netsnmp_walk(PyObject *self, PyObject *args)
{
  PyObject *session;
  PyObject *varlist;
  PyObject *varlist_iter;
  PyObject *varbind;
  PyObject *val_tuple = NULL;
  PyObject *varbinds  = NULL;
  int varlist_len = 0;
  int varlist_ind;
  netsnmp_session *ss;
  netsnmp_pdu *pdu, *response;
  netsnmp_variable_list *vars;
  struct tree *tp;
  int len;
  oid *oid_arr;
  int oid_arr_len = MAX_OID_LEN;
  int type;
  char type_str[MAX_TYPE_NAME_LEN];
  int status;
  u_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 *tag;
  char *iid;
  int getlabel_flag = NO_FLAGS;
  int sprintval_flag = USE_BASIC;
  int verbose = py_netsnmp_verbose();
  int old_format;
  int best_guess;
  int retry_nosuch;
  int err_ind;
  int err_num;
  char err_str[STR_BUF_SIZE];
  int notdone = 1;
  int result_count = 0;
  char *tmpstr;
  Py_ssize_t tmplen;
	   
  oid_arr = calloc(MAX_OID_LEN, sizeof(oid));

  if (oid_arr && args) {

    if (!PyArg_ParseTuple(args, "OO", &session, &varlist)) {
      goto done;
    }

    if (!varlist) {
      goto done;
    }

    if ((varbinds = PyObject_GetAttrString(varlist, "varbinds")) == NULL) {
      goto done;
    }
    ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

    if (py_netsnmp_attr_string(session, "ErrorStr", &tmpstr, &tmplen) < 0) {
      goto done;
    }
    memcpy(&err_str, tmpstr, tmplen);
    err_num = py_netsnmp_attr_long(session, "ErrorNum");
    err_ind = py_netsnmp_attr_long(session, "ErrorInd");

    if (py_netsnmp_attr_long(session, "UseLongNames"))
      getlabel_flag |= USE_LONG_NAMES;
    if (py_netsnmp_attr_long(session, "UseNumeric"))
      getlabel_flag |= USE_NUMERIC_OIDS;
    if (py_netsnmp_attr_long(session, "UseEnums"))
      sprintval_flag = USE_ENUMS;
    if (py_netsnmp_attr_long(session, "UseSprintValue"))
      sprintval_flag = USE_SPRINT_VALUE;	
    best_guess = py_netsnmp_attr_long(session, "BestGuess");
    retry_nosuch = py_netsnmp_attr_long(session, "RetryNoSuch");
        
    varlist_iter = PyObject_GetIter(varlist);

    pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
    
    /* get the initial starting oids*/
    while (varlist_iter && (varbind = PyIter_Next(varlist_iter))) {
      if (py_netsnmp_attr_string(varbind, "tag", &tag, NULL) < 0 ||
         py_netsnmp_attr_string(varbind, "iid", &iid, NULL) < 0)
      {
        oid_arr_len = 0;
      } else {
        tp = __tag2oid(tag, iid, oid_arr, &oid_arr_len, NULL, best_guess);
      }

      if (_debug_level) 
	printf("netsnmp_walk: filling request: %s:%s:%d:%d\n", 
	       tag, iid, oid_arr_len,best_guess);

      if (oid_arr_len) {
        snmp_add_null_var(pdu, oid_arr, oid_arr_len);
        varlist_len++;
      } else {
        if (verbose)
          printf("error: walk: unknown object ID (%s)",
      	   (tag ? tag : "<null>"));
        snmp_free_pdu(pdu);
        goto done;
      }
      /* release reference when done */
      Py_DECREF(varbind);
    }

    Py_DECREF(varlist_iter);

    if (PyErr_Occurred()) {
      /* propagate error */
      if (verbose)
        printf("error: walk: unknown python error (varlist)");
      snmp_free_pdu(pdu);
      goto done;
    }

    /* pre-allocate the return tuples */
    val_tuple = PyTuple_New(0);

    if (!val_tuple) {
      /* propagate error */
      if (verbose)
        printf("error: walk: couldn't allocate a new value tuple");
      snmp_free_pdu(pdu);
      goto done;
    }        
    
    /*
    ** Set up for numeric or full OID's, if necessary.  Save the old
    ** output format so that it can be restored when we finish -- this
    ** is a library-wide global, and has to be set/restored for each
    ** session.
    */
    old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
                                    NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);

    if (py_netsnmp_attr_long(session, "UseLongNames")) {
      getlabel_flag |= USE_LONG_NAMES;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
                         NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
                         NETSNMP_OID_OUTPUT_FULL);
    }

    /* 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 (py_netsnmp_attr_long(session, "UseNumeric")) {
      getlabel_flag |= USE_LONG_NAMES;
      getlabel_flag |= USE_NUMERIC_OIDS;

      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
                         NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
                         NETSNMP_OID_OUTPUT_NUMERIC);
    }

    /* delete the existing varbinds that we'll replace */
    PySequence_DelSlice(varbinds, 0, PySequence_Length(varbinds));

    if (PyErr_Occurred()) {
      /* propagate error */
      if (verbose)
        printf("error: walk: deleting old varbinds failed\n");
      snmp_free_pdu(pdu);
      goto done;
    }

    while(notdone) {

      status = __send_sync_pdu(ss, pdu, &response, retry_nosuch, 
                               err_str, &err_num, &err_ind);
      __py_netsnmp_update_session_errors(session, err_str, err_num, err_ind);

      if (!response || !response->variables ||
          (response->variables->name_length < oid_arr_len) ||
          (memcmp(oid_arr, response->variables->name,
                  oid_arr_len * sizeof(oid))) ||
          status != STAT_SUCCESS ||
          response->errstat != SNMP_ERR_NOERROR) {
          notdone = 0;
      } else {
        for(vars = (response ? response->variables : NULL), varlist_ind = 0;
    	vars && (varlist_ind < varlist_len);
    	vars = vars->next_variable, varlist_ind++) {

          if ((vars->type == SNMP_ENDOFMIBVIEW) ||
              (vars->type == SNMP_NOSUCHOBJECT) ||
              (vars->type == SNMP_NOSUCHINSTANCE)) {
              notdone = 0;
              break;
          }

          varbind = py_netsnmp_construct_varbind();

          if (PyObject_HasAttrString(varbind, "tag")) {
    	  str_buf[0] = '.';
    	  str_buf[1] = '\0';
    	  out_len = 0;
    	  tp = netsnmp_sprint_realloc_objid_tree(&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 ? tp->type : tp->parent->type);
    	    getlabel_flag &= ~NON_LEAF_NAME;
    	  } else {
    	    getlabel_flag |= NON_LEAF_NAME;
    	    type = __translate_asn_type(vars->type);
    	  }

    	  __get_label_iid(str_buf, &tag, &iid, getlabel_flag);

    	  if (_debug_level) printf("netsnmp_walk: filling response: %s:%s\n", tag, iid);

	  py_netsnmp_attr_set_string(varbind, "tag", tag, STRLEN(tag));
	  py_netsnmp_attr_set_string(varbind, "iid", iid, STRLEN(iid));

    	  __get_type_str(type, type_str);

	  py_netsnmp_attr_set_string(varbind, "type", type_str, 
				     STRLEN(type_str));

    	  len = __snprint_value(str_buf,sizeof(str_buf),
				vars,tp,type,sprintval_flag);
    	  str_buf[len] = '\0';

	  py_netsnmp_attr_set_string(varbind, "val", str_buf, len);
            
	  /* push the varbind onto the return varbinds */
	  PyList_Append(varbinds, varbind);

    	  /* save in return tuple as well */
	  /* save in return tuple as well - steals ref */
	  _PyTuple_Resize(&val_tuple, result_count+1);
	  PyTuple_SetItem(val_tuple, result_count++, 
			  (len ? Py_BuildValue("s#", str_buf, len) :
			   Py_BuildValue("")));
            

          } else {
	    /* Return None for this variable. */
            _PyTuple_Resize(&val_tuple, result_count+1);
	    PyTuple_SetItem(val_tuple, result_count++, Py_BuildValue(""));
	    printf("netsnmp_walk: bad varbind (%d)\n", varlist_ind);
          }	
          Py_XDECREF(varbind);
        }
        /* reuse the response as the next pdu to send */
        pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
        snmp_add_null_var(pdu, response->variables->name,
                          response->variables->name_length);
      }
      if (response)
	snmp_free_pdu(response);
    }

    /* Reset the library's behavior for numeric/symbolic OID's. */
    netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
		       NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
		       old_format);


    if (PyErr_Occurred()) {
      /* propagate error */
      if (verbose)
	printf("error: walk response processing: unknown python error");
      Py_DECREF(val_tuple);
    } 
  }

 done:
  Py_XDECREF(varbinds);
  SAFE_FREE(oid_arr);
  return (val_tuple ? val_tuple : Py_BuildValue(""));
}


static PyObject *
netsnmp_getbulk(PyObject *self, PyObject *args)
{
  int nonrepeaters;
  int maxrepetitions;
  PyObject *session;
  PyObject *varlist;
  PyObject *varbinds;
  PyObject *varbind;
  PyObject *varbinds_iter;
  PyObject *val_tuple = NULL;
  int varbind_ind;
  netsnmp_session *ss;
  netsnmp_pdu *pdu, *response;
  netsnmp_variable_list *vars;
  struct tree *tp;
  int len;
  oid *oid_arr;
  int oid_arr_len = MAX_OID_LEN;
  int type;
  char type_str[MAX_TYPE_NAME_LEN];
  int status;
  u_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 *tag;
  char *iid;
  int getlabel_flag = NO_FLAGS;
  int sprintval_flag = USE_BASIC;
  int verbose = py_netsnmp_verbose();
  int old_format;
  int best_guess;
  int retry_nosuch;
  int err_ind;
  int err_num;
  char err_str[STR_BUF_SIZE];
  char *tmpstr;
  Py_ssize_t tmplen;
	   
  oid_arr = calloc(MAX_OID_LEN, sizeof(oid));

  if (oid_arr && args) {

    if (!PyArg_ParseTuple(args, "OiiO", &session, &nonrepeaters, 
			  &maxrepetitions, &varlist)) {
      goto done;
    }

    if (varlist && (varbinds = PyObject_GetAttrString(varlist, "varbinds"))) {
      
      ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

      if (py_netsnmp_attr_string(session, "ErrorStr", &tmpstr, &tmplen) < 0) {
        goto done;
      }
      memcpy(&err_str, tmpstr, tmplen);
      err_num = py_netsnmp_attr_long(session, "ErrorNum");
      err_ind = py_netsnmp_attr_long(session, "ErrorInd");

      if (py_netsnmp_attr_long(session, "UseLongNames"))
	getlabel_flag |= USE_LONG_NAMES;
      if (py_netsnmp_attr_long(session, "UseNumeric"))
	getlabel_flag |= USE_NUMERIC_OIDS;
      if (py_netsnmp_attr_long(session, "UseEnums"))
	sprintval_flag = USE_ENUMS;
      if (py_netsnmp_attr_long(session, "UseSprintValue"))
	sprintval_flag = USE_SPRINT_VALUE;	
      best_guess = py_netsnmp_attr_long(session, "BestGuess");
      retry_nosuch = py_netsnmp_attr_long(session, "RetryNoSuch");
      
      pdu = snmp_pdu_create(SNMP_MSG_GETBULK);

      pdu->errstat = nonrepeaters;
      pdu->errindex = maxrepetitions;

      varbinds_iter = PyObject_GetIter(varbinds);

      while (varbinds_iter && (varbind = PyIter_Next(varbinds_iter))) {
        if (py_netsnmp_attr_string(varbind, "tag", &tag, NULL) < 0 ||
          py_netsnmp_attr_string(varbind, "iid", &iid, NULL) < 0)
        {
          oid_arr_len = 0;
        } else {
          tp = __tag2oid(tag, iid, oid_arr, &oid_arr_len, NULL, best_guess);
        }

	if (oid_arr_len) {
	  snmp_add_null_var(pdu, oid_arr, oid_arr_len);
	} else {
	  if (verbose)
	    printf("error: get: unknown object ID (%s)",
		   (tag ? tag : "<null>"));
	  snmp_free_pdu(pdu);
	  goto done;
	}
	/* release reference when done */
	Py_DECREF(varbind);
      }

      Py_DECREF(varbinds_iter);

      if (PyErr_Occurred()) {
	/* propagate error */
	if (verbose)
	  printf("error: get: unknown python error");
	snmp_free_pdu(pdu);
	goto done;
      }

      status = __send_sync_pdu(ss, pdu, &response, retry_nosuch, 
			       err_str, &err_num, &err_ind);
      __py_netsnmp_update_session_errors(session, err_str, err_num, err_ind);

      /*
      ** Set up for numeric or full OID's, if necessary.  Save the old
      ** output format so that it can be restored when we finish -- this
      ** is a library-wide global, and has to be set/restored for each
      ** session.
      */
      old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
				      NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);

      if (py_netsnmp_attr_long(session, "UseLongNames")) {
	getlabel_flag |= USE_LONG_NAMES;

	netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			   NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			   NETSNMP_OID_OUTPUT_FULL);
      }
      /* 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 (py_netsnmp_attr_long(session, "UseNumeric")) {
	getlabel_flag |= USE_LONG_NAMES;
	getlabel_flag |= USE_NUMERIC_OIDS;

	netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			   NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			   NETSNMP_OID_OUTPUT_NUMERIC);
      }

      /* create tuple in which to return results */
      val_tuple = PyTuple_New(0);

      if(response && response->variables) {
	/* clear varlist to receive response varbinds*/
	PySequence_DelSlice(varbinds, 0, PySequence_Length(varbinds));

        if (PyErr_Occurred()) {
            /* propagate error */
            if (verbose)
                printf("error: bulk: deleting old varbinds failed\n");
            snmp_free_pdu(pdu);
            goto done;
        }

	for(vars = response->variables, varbind_ind=0; 
	    vars; 
	    vars = vars->next_variable, varbind_ind++) {

	  varbind = py_netsnmp_construct_varbind();

	  if (PyObject_HasAttrString(varbind, "tag")) {
	    *str_buf = '.';
	    *(str_buf+1) = '\0';
	    out_len = 0;
	    buf_over = 0;
	    str_bufp = str_buf;
	    tp = netsnmp_sprint_realloc_objid_tree(&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 ? tp->type : tp->parent->type);
	      getlabel_flag &= ~NON_LEAF_NAME;
	    } else {
	      getlabel_flag |= NON_LEAF_NAME;
	      type = __translate_asn_type(vars->type);
	    }

	    __get_label_iid(str_buf, &tag, &iid, getlabel_flag);

	    py_netsnmp_attr_set_string(varbind, "tag", tag, STRLEN(tag));
	    py_netsnmp_attr_set_string(varbind, "iid", iid, STRLEN(iid));

	    __get_type_str(type, type_str);

	    py_netsnmp_attr_set_string(varbind, "type", type_str, 
				       STRLEN(type_str));

	    len = __snprint_value(str_buf,sizeof(str_buf),
				  vars,tp,type,sprintval_flag);
	    str_buf[len] = '\0';

	    py_netsnmp_attr_set_string(varbind, "val", str_buf, len);

	    /* push varbind onto varbinds */
	    PyList_Append(varbinds, varbind);

	    /* save in return tuple as well - steals ref */
	    _PyTuple_Resize(&val_tuple, varbind_ind+1);
	    PyTuple_SetItem(val_tuple, varbind_ind, 
			       Py_BuildValue("s#", str_buf, len));

	    Py_DECREF(varbind);

	  } else {
	    PyObject *none = Py_BuildValue(""); /* new ref */
	    /* not sure why making vabind failed - should not happen*/
	    PyList_Append(varbinds, none); /* increments ref */
	    /* Return None for this variable. */
	    PyTuple_SetItem(val_tuple, varbind_ind, none); /* steals ref */
	  }	
	}
      }

      /* Reset the library's behavior for numeric/symbolic OID's. */
      netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID,
			 NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
			 old_format);

      if (response) snmp_free_pdu(response);

      Py_DECREF(varbinds);

    }

    if (PyErr_Occurred()) {
      /* propagate error */
      if (verbose)
	printf("error: getbulk response processing: unknown python error");
      Py_DECREF(val_tuple);
      val_tuple = NULL;
    }
  }

 done:
  SAFE_FREE(oid_arr);
  return (val_tuple ? val_tuple : Py_BuildValue(""));
}

static PyObject *
netsnmp_set(PyObject *self, PyObject *args)
{
  PyObject *session;
  PyObject *varlist;
  PyObject *varbind;
  PyObject *ret = NULL;
  netsnmp_session *ss;
  netsnmp_pdu *pdu, *response;
  struct tree *tp;
  char *tag;
  char *iid;
  char *val;
  char *type_str;
  int len;
  oid *oid_arr;
  int oid_arr_len = MAX_OID_LEN;
  int type;
  u_char tmp_val_str[STR_BUF_SIZE];
  int use_enums;
  struct enum_list *ep;
  int verbose = py_netsnmp_verbose();
  int best_guess;
  int status;
  int err_ind;
  int err_num;
  char err_str[STR_BUF_SIZE];
  char *tmpstr;
  Py_ssize_t tmplen;
	   
  oid_arr = calloc(MAX_OID_LEN, sizeof(oid));

  if (oid_arr && args) {

    if (!PyArg_ParseTuple(args, "OO", &session, &varlist)) {
      goto done;
    }

    ss = (SnmpSession *)py_netsnmp_attr_void_ptr(session, "sess_ptr");

    /* PyObject_SetAttrString(); */
    if (py_netsnmp_attr_string(session, "ErrorStr", &tmpstr, &tmplen) < 0) {
      goto done;
    }

    use_enums = py_netsnmp_attr_long(session, "UseEnums");
	
    best_guess = py_netsnmp_attr_long(session, "BestGuess");
      
    pdu = snmp_pdu_create(SNMP_MSG_SET);

    if (varlist) {
      PyObject *varlist_iter = PyObject_GetIter(varlist);

      while (varlist_iter && (varbind = PyIter_Next(varlist_iter))) {
        if (py_netsnmp_attr_string(varbind, "tag", &tag, NULL) < 0 ||
          py_netsnmp_attr_string(varbind, "iid", &iid, NULL) < 0)
        {
          oid_arr_len = 0;
        } else {
          tp = __tag2oid(tag, iid, oid_arr, &oid_arr_len, &type, best_guess);
        }

	if (oid_arr_len==0) {
	  if (verbose)
	    printf("error: set: unknown object ID (%s)",
		 (tag?tag:"<null>"));
	  snmp_free_pdu(pdu);
	  goto done;
	}

	if (type == TYPE_UNKNOWN) {
	  if (py_netsnmp_attr_string(varbind, "type", &type_str, NULL) < 0) {
	    snmp_free_pdu(pdu);
	    goto done;
	  }
	  type = __translate_appl_type(type_str);
	  if (type == TYPE_UNKNOWN) {
	    if (verbose)
	      printf("error: set: no type found for object");
	    snmp_free_pdu(pdu);
	    goto done;
	  }
	}

	if (py_netsnmp_attr_string(varbind, "val", &val, &tmplen) < 0) {
	  snmp_free_pdu(pdu);
	  goto done;
	}
	memcpy(tmp_val_str, val, tmplen);
	if (type==TYPE_INTEGER && use_enums && tp && tp->enums) {
	  for(ep = tp->enums; ep; ep = ep->next) {
	    if (val && !strcmp(ep->label, val)) {
	      strcpy(tmp_val_str, ep->value);
	      break;
	    }
	  }
	}
	len = (int)tmplen;
	status = __add_var_val_str(pdu, oid_arr, oid_arr_len,
				tmp_val_str, len, type);

	if (verbose && status == FAILURE)
	  printf("error: set: adding variable/value to PDU");

	/* release reference when done */
	Py_DECREF(varbind);
      }

      Py_DECREF(varlist_iter);

      if (PyErr_Occurred()) {
	/* propagate error */
	if (verbose)
	  printf("error: set: unknown python error");
	snmp_free_pdu(pdu);
	goto done;
      }
    }

    status = __send_sync_pdu(ss, pdu, &response, NO_RETRY_NOSUCH, 
			     err_str, &err_num, &err_ind);
    __py_netsnmp_update_session_errors(session, err_str, err_num, err_ind);

    if (response) snmp_free_pdu(response);

    if (status == STAT_SUCCESS)
      ret = Py_BuildValue("i",1); /* success, return True */
    else
      ret = Py_BuildValue("i",0); /* fail, return False */
  } 
 done:
  SAFE_FREE(oid_arr);
  return (ret ? ret : Py_BuildValue(""));
}


static PyMethodDef ClientMethods[] = {
  {"session",  netsnmp_create_session, METH_VARARGS,
   "create a netsnmp session."},
  {"session_v3",  netsnmp_create_session_v3, METH_VARARGS,
   "create a netsnmp session."},
  {"delete_session",  netsnmp_delete_session, METH_VARARGS,
   "create a netsnmp session."},
  {"get",  netsnmp_get, METH_VARARGS,
   "perform an SNMP GET operation."},
  {"getnext",  netsnmp_getnext, METH_VARARGS,
   "perform an SNMP GETNEXT operation."},
  {"getbulk",  netsnmp_getbulk, METH_VARARGS,
   "perform an SNMP GETBULK operation."},
  {"set",  netsnmp_set, METH_VARARGS,
   "perform an SNMP SET operation."},
  {"walk",  netsnmp_walk, METH_VARARGS,
   "perform an SNMP WALK operation."},
  {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC
initclient_intf(void)
{
    (void) Py_InitModule("client_intf", ClientMethods);
}





