/* client.c - snmp client interface routines
** 
** HUBADMIN PROJECT
** Network Development
**
** Copyright (c) 1994 - Carnegie Mellon University - All Rights Reserved
**
** Erikas Aras Napjus (erikas+@cmu.edu)
** Ryan Troll (ryan@andrew.cmu.edu)
*/

/**********************************************************************
 *
 *           Copyright 1996 by Carnegie Mellon University
 * 
 *                       All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of CMU not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * 
 * Author: Erikas Aras Napjus <erikas+@cmu.edu>
 *         Ryan Troll <ryan@andrew.cmu.edu>
 * 
 **********************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>

#ifdef HAVE_TERMIOS_H
# include <termios.h>
#endif /* HAVE_TERMIOS_H */

#include <curses.h>

#ifdef HAVE_STRINGS_H
# include <strings.h>
#else /* HAVE_STRINGS_H */
# include <string.h>
#endif /* HAVE_STRINGS_H */

#ifdef HAVE_MALLOC_H
# include <malloc.h>
#endif /* HAVE_MALLOC_H */

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */

/* CMU SNMP Library */
#include <snmp/snmp.h>

#include "screen.h"
#include "client.h"
#include "query.h"

static char rcsid[] = 
"$Id: client.c,v 1.4 1997/03/11 22:13:25 ryan Exp $";

extern int  errno;

/**********************************************************************/

/* The return array
 */
static void **ReturnArray;
static int ArraySize = 0;

/* Shouldn't have to set it to NULL, as this is called when we are about
 * to fill it back in.
 */
#define RETURN_FREE(x) if (ReturnArray[x] != NULL) free(ReturnArray[x]);

void init_client(int m)
{
  ArraySize = m;
  ReturnArray = (void *)malloc(sizeof(void *)*m);
  /* Set all pointers to NULL */
  memset(ReturnArray, '\0', sizeof(void *)*m);
}

void close_client(void)
{
  int x;
  for (x=0; x<ArraySize; x++) RETURN_FREE(x);
}

/**********************************************************************/

/* Copy and NULL terminate, cause SNMP doesn't have to. */
char *snmpstrdup(u_char *src, int srclen)
{
  char *dest;

  dest = (char *)malloc(srclen+1);
  if (dest) {
    memcpy(dest, src, srclen*sizeof(char));
    dest[srclen] = '\0';
  }
  return(dest);
}

void *snmpopen(char *gateway, char *community)
{
  struct snmp_session session, *ss;

  memset((char *)&session, '\0', sizeof(struct snmp_session));
  session.peername = (char *) gateway;
  session.community = (u_char *)community;
  session.community_len = strlen((char *)community);
  session.retries = SNMP_DEFAULT_RETRIES;
  session.timeout = SNMP_DEFAULT_TIMEOUT;
  session.authenticator = NULL;
  snmp_synch_setup(&session);
  ss = snmp_open(&session);
  if (ss == NULL) {
    screen_error("Unable to Open SNMP");
    return NULL;
  }
  return((void *)ss);
}

void snmpclose(void *ss)
{
  snmp_close((struct snmp_session *)ss);
}

/**********************************************************************/

/* snmpset - set a variable on the specified host
 *
 * WARNING: this will only set one variable at a time
 * WARNING: this will only reliably set INTEGER-style variables
 *
 * Returns 1 upon success, 0 otherwise.
 */
int snmpset(char *gateway, char *community, 
	    char *query, int value,
	    void *old_ss)
{
  struct snmp_session *ss;
  struct snmp_pdu *pdu, *response;
  int	count = 0;
  oid *nameP;
  int name_length;
  int status;

  char *names[128];
  int current_name = 0;

  /* The query */
  names[current_name++] = query;

  /* Use a cached session */
  if (old_ss == NULL) {
    ss = (struct snmp_session *)snmpopen(gateway, community);
    if (ss == NULL)
      return(0);
  } else {
    ss = (struct snmp_session *)old_ss;
  }

  /* Create the set PDU */
  pdu = snmp_pdu_create(SNMP_PDU_SET);

  /* Add this OID. */
  for(count = 0; count < current_name; count++) {
    name_length = MAX_NAME_LEN;
    if (!mib_TxtToOid(names[count], &nameP, &name_length)) {
      screen_error("Internal HUBADMIN Error");
      goto Failure;
    }
    snmp_add_null_var(pdu, nameP, name_length);
    free(nameP);
  }

Retry:
  /* Set it */
  pdu->variables->type = INTEGER;
  pdu->variables->val.integer = (int *) malloc(sizeof(int));
  *(pdu->variables->val.integer) = value;
  pdu->variables->val_len = sizeof(int);

  /* Send the request */
  status = snmp_synch_response(ss, pdu, &response);
  if (status == STAT_SUCCESS){
    if (response->errstat != SNMP_ERR_NOERROR) {
      screen_error("Error in SNMP Packet");

      /* Remove the bad part of the request */
      pdu = snmp_fix_pdu(response, SNMP_PDU_GET);

	/* If there is still something to do, send it */
      if (pdu != NULL)
	goto Retry;
      else
	goto Failure;
    }

  } else if (status == STAT_TIMEOUT) {
    screen_error("No Response from Host");
    goto Failure;

  } else {
    /* status == STAT_ERROR */
    screen_error("SNMP Error");
    goto Failure;
  }

  if (response)
    snmp_free_pdu(response);

  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);

  return 1;

Failure:
  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return 0;
}

/**********************************************************************/

/* snmpget - requests specified variable(s)
 *
 * (this supports both multiple requests and all normal types)
 *
 * Returns a pointer to the answer array, or NULL.
 */
void **snmpget(char *gateway, char *community, 
	      int NumQueries, char **Queries,
	      void *old_ss)
{
  struct snmp_session *ss;
  struct snmp_pdu *pdu, *response;
  struct variable_list *vars;
  oid *nameP;
  int name_length;
  int status;

  /* Number of items returned */
  int count;

  /* Use a cached session */
  if (old_ss == NULL) {
    ss = (struct snmp_session *)snmpopen(gateway, community);
    if (ss == NULL)
      return(NULL);
  } else {
    ss = (struct snmp_session *)old_ss;
  }

  /* Create the get PDU */
  pdu = snmp_pdu_create(SNMP_PDU_GET);

  /* Add all of the requests */
  for(count = 0; count < NumQueries; count++) {
    if (!mib_TxtToOid((void *) Queries[count], &nameP, &name_length)) {
      screen_error("Internal HUBADMIN Error");
      goto Failure;
    }
    snmp_add_null_var(pdu, nameP, name_length);
    free(nameP);
  }
  
  /* Now send it */
  count = 0;
  status = snmp_synch_response(ss, pdu, &response);
  if (status == STAT_SUCCESS) {
    if (response->errstat == SNMP_ERR_NOERROR) {
      for (vars = response->variables; vars; vars = vars->next_variable) {
	RETURN_FREE(count);
	switch (vars->type) {
	case INTEGER:
	case SMI_COUNTER32:
	case SMI_GAUGE32:
	case SMI_TIMETICKS:
	  ReturnArray[count] = (void *) vars->val.integer;
	  break;
	case STRING:
	case SMI_IPADDRESS:
	  ReturnArray[count] = (void *) snmpstrdup(vars->val.string,
						   vars->val_len);
	  break;
	case OBJID:
	  ReturnArray[count] = (void *) vars->val.objid;
	  break;
	default:
	  screen_error("Internal HUBADMIN Error");
	  goto Failure;
	}
	count++;

	/* This will stop the actual value pointer from being
	 * freed with the variable list is freed.
	 */
	vars->val.objid = NULL;
      }
    } else {
      screen_error("Error in SNMP Packet");
      goto Failure;
    }
  }
  else {
    if (status == STAT_TIMEOUT)
      screen_error("No Response from Host");
    else
      screen_error("SNMP Error");
    goto Failure;
  }

  if (response)
    snmp_free_pdu(response);

  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);

  return ReturnArray;

Failure:
  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return NULL;
}

/**********************************************************************/

/* snmpgetnext - requests the next variable after the specified oid
**
** WARNING: this only supports single queries at this time
** WARNING: this only works reliably with STRING-type variables
**
** (return value is 0 on failure; otherwise, length of string)
*/
int snmpgetnext(char *gateway, char *community, 
		oid *query, int query_len, 
		oid *ResponseOID, u_char *ResponseData,
		void *old_ss)
{
  struct snmp_session *ss;
  struct snmp_pdu *pdu, *response;
  struct variable_list *vars;
  int length;
  int	incr;
  int status;

  /* Use a cached session */
  if (old_ss == NULL) {
    ss = (struct snmp_session *)snmpopen(gateway, community);
    if (ss == NULL)
      return(0);
  } else {
    ss = (struct snmp_session *)old_ss;
  }

  pdu = snmp_pdu_create(SNMP_PDU_GETNEXT);

  snmp_add_null_var(pdu, query, query_len);

retry:
  status = snmp_synch_response(ss, pdu, &response);
  if (status == STAT_SUCCESS) {
    if (response->errstat == SNMP_ERR_NOERROR) {
      for (vars = response->variables; vars; vars = vars->next_variable) {

	/* Copy the OID name */
	memcpy((char *)ResponseOID, (char *)vars->name, 	       
	       vars->name_length * sizeof(oid));
	length = vars->name_length;

	switch (vars->type) {
	case SMI_GAUGE32:
	case SMI_COUNTER32:
	case ASN_INTEGER:
	  /* Assume size of ResponseData is > 4 */
	  *(int *)ResponseData = (int)*vars->val.integer;
	  break;
	case STRING:
	case SMI_IPADDRESS:
	default:
	  for (incr=0; incr<vars->val_len; incr++)
	    ResponseData[incr] = vars->val.string[incr];
	  ResponseData[incr] = NULL;
	  break;
	  /*		    screen_error("Internal HUBADMIN Error");
			    return -1;*/
	}
      }
    } else {
      screen_error("Error in SNMP Packet");
      if ((pdu = snmp_fix_pdu(response, SNMP_PDU_GET)) != NULL)
	goto retry;
      else
	goto Failure;
    }
  } else {
    if (status == STAT_TIMEOUT)
      screen_error("No Response from Host");
    else 
      screen_error("SNMP Error");
    goto Failure;
  }
  
  if (response)
    snmp_free_pdu(response);


  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);

  return length;

Failure:
  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return(0);
}

/**********************************************************************/

/* snmpset - set a variable on the specified host
 *
 * WARNING: this will only set one variable at a time
 * WARNING: this will only reliably set INTEGER-style variables
 *
 * Returns 1 upon success, 0 otherwise.
 */
int snmpsettime(char *gateway, char *community, 
		char *query, int value,
		void *old_ss)
{
  struct snmp_session *ss;
  struct snmp_pdu *pdu, *response;
  int	count, current_name = 0;
  char *names[128];
  oid *nameP;
  int name_length;
  int status;

  names[current_name++] = query;

  /* Use a cached session */
  if (old_ss == NULL) {
    ss = (struct snmp_session *)snmpopen(gateway, community);
    if (ss == NULL)
      return(0);
  } else {
    ss = (struct snmp_session *)old_ss;
  }

  pdu = snmp_pdu_create(SNMP_PDU_SET);

  for(count = 0; count < current_name; count++) {
    if (!mib_TxtToOid(names[count], &nameP, &name_length)) {
      screen_error("Internal HUBADMIN Error");
      goto Failure;
    }
    snmp_add_null_var(pdu, nameP, name_length);
    free(nameP);
  }

Retry:
  pdu->variables->type = SMI_TIMETICKS;
  pdu->variables->val.integer = (int *) malloc(sizeof(int));
  *(pdu->variables->val.integer) = value;
  pdu->variables->val_len = sizeof(int);

  status = snmp_synch_response(ss, pdu, &response);
  if (status == STAT_SUCCESS) {
    if (response->errstat != SNMP_ERR_NOERROR) {
      screen_error("Error in SNMP Packet");
      pdu = snmp_fix_pdu(response, SNMP_PDU_GET);
      if (pdu != NULL)
	goto Retry;
      else
	goto Failure;
      }
  } else {

    if (status == STAT_TIMEOUT)
      screen_error("No Response from Host");
    else     /* status == STAT_ERROR */
      screen_error("SNMP Error");
    goto Failure;
  }

  if (response)
    snmp_free_pdu(response);

  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return 1;

Failure:
  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return 0;
}

/* snmpsetstr - set a display string variable on the specified host
**
** (returns -1 on failure; otherwise 0)
*/
int snmpsetstr(char *gateway, char *community, 
	       char *query, char *value, int length,
	       void *old_ss)
{
  struct snmp_session *ss;
  struct snmp_pdu *pdu, *response;
  int count, current_name = 0;
  char *names[128];
  oid *nameP;
  int name_length;
  int status;
  
  names[current_name++] = query;
  
  /* Use a cached session */
  if (old_ss == NULL) {
    ss = (struct snmp_session *)snmpopen(gateway, community);
    if (ss == NULL)
      return(0);
  } else {
    ss = (struct snmp_session *)old_ss;
  }

  pdu = snmp_pdu_create(SNMP_PDU_SET);
  
  for(count = 0; count < current_name; count++) {
    if (!mib_TxtToOid(names[count], &nameP, &name_length)) {
      return -1;
    }
    snmp_add_null_var(pdu, nameP, name_length);
    free(nameP);
  }
  
Retry:
  pdu->variables->type = STRING;
  pdu->variables->val.string = (u_char *) malloc(length);
  strcpy((char *)pdu->variables->val.string, value);
  pdu->variables->val_len = length;

  status = snmp_synch_response(ss, pdu, &response);
  if (status == STAT_SUCCESS) {
    if (response->errstat != SNMP_ERR_NOERROR) {
      pdu = snmp_fix_pdu(response, SNMP_PDU_GET);
      if (pdu != NULL)
	goto Retry;
      else
	goto Failure;
    }
  } else
    goto Failure;
  
  if (response)
    snmp_free_pdu(response);

  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return 1;

Failure:
  /* Only close the session if it wasn't cached */
  if (old_ss == NULL)
    snmpclose(ss);
  return 0;
}
