#include <config.h>

#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

#include <signal.h>
#include <errno.h>

#include "mibincl.h"
#include "struct.h"
#include "pass_persist.h"
#include "extensible.h"
#include "util_funcs.h"
#include "read_config.h"
#include "system.h"

struct extensible *persistpassthrus=NULL;
int numpersistpassthrus=0;
struct persist_pipe_type {
  FILE *fIn, *fOut;
  int fdIn, fdOut;
  int pid;
} *persist_pipes = ( struct persist_pipe_type * ) NULL;
static int init_persist_pipes (void);
static void close_persist_pipe (int iindex);
static int open_persist_pipe (int iindex, char *command);
static void destruct_persist_pipes (void);
static int write_persist_pipe (int iindex, const char *data);

/*  These are defined in pass.c */
extern int asc2bin(char *p);
extern int bin2asc(char *p, size_t n);
extern int snmp_oid_min_compare(const oid *, size_t , const oid *, size_t );

/* the relocatable extensible commands variables */
struct variable2 extensible_persist_passthru_variables[] = {
  /* bogus entry.  Only some of it is actually used. */
  {MIBINDEX, ASN_INTEGER, RWRITE, var_extensible_pass_persist, 0, {MIBINDEX}},
};

void init_pass_persist(void) 
{
  snmpd_register_config_handler("pass_persist", pass_persist_parse_config,
                                pass_persist_free_config,"miboid program");
}

void pass_persist_parse_config(const char *token, char* cptr)
{
  struct extensible **ppass = &persistpassthrus, **etmp, *ptmp;
  char *tcptr;
  int i;

  if (*cptr == '.') cptr++;
  if (!isdigit(*cptr)) {
    config_perror("second token is not a OID");
    return;
  }
  numpersistpassthrus++;

  while(*ppass != NULL)
    ppass = &((*ppass)->next);
  (*ppass) = (struct extensible *) malloc(sizeof(struct extensible));
  if (*ppass == NULL)
    return;
  (*ppass)->type = PASSTHRU_PERSIST;

  (*ppass)->miblen = parse_miboid(cptr,(*ppass)->miboid);
  while (isdigit(*cptr) || *cptr == '.') cptr++;
  /* name */
  cptr = skip_white(cptr);
  if (cptr == NULL) {
    config_perror("No command specified on pass_persist line");
    (*ppass)->command[0] = 0;
  } else {
    for(tcptr=cptr; *tcptr != 0 && *tcptr != '#' && *tcptr != ';';
        tcptr++);
    strncpy((*ppass)->command,cptr,tcptr-cptr);
    (*ppass)->command[tcptr-cptr] = 0;
  }
  strcpy((*ppass)->name, (*ppass)->command);
  (*ppass)->next = NULL;

  register_mib("pass_persist", (struct variable *) extensible_persist_passthru_variables,
               sizeof(struct variable2),
               1, (*ppass)->miboid, (*ppass)->miblen);

  /* argggg -- pasthrus must be sorted */
  if (numpersistpassthrus > 0) {
    etmp = (struct extensible **)
      malloc(((sizeof(struct extensible *)) * numpersistpassthrus));
    if (etmp == NULL)
      return;
    for(i=0,ptmp = (struct extensible *) persistpassthrus;
        i < numpersistpassthrus && ptmp != 0;
        i++, ptmp = ptmp->next)
      etmp[i] = ptmp;
    qsort(etmp, numpersistpassthrus, sizeof(struct extensible *),
          pass_persist_compare);
    persistpassthrus = (struct extensible *) etmp[0];
    ptmp = (struct extensible *) etmp[0];

    for(i=0; i < numpersistpassthrus-1; i++) {
      ptmp->next = etmp[i+1];
      ptmp = ptmp->next;
    }
    ptmp->next = NULL;
    free(etmp);
  }
}

void pass_persist_free_config (void) 
{
  struct extensible *etmp, *etmp2;

  /* Close any open pipes to any programs */
  destruct_persist_pipes();

  for (etmp = persistpassthrus; etmp != NULL;) {
    etmp2 = etmp;
    etmp = etmp->next;
    unregister_mib(etmp2->miboid, etmp2->miblen);
    free(etmp2);
  }
  persistpassthrus = NULL;
  numpersistpassthrus = 0;
}

u_char *var_extensible_pass_persist(struct variable *vp,
					   oid *name,
					   size_t *length,
					   int exact,
					   size_t *var_len,
					   WriteMethod **write_method)
{
  oid newname[MAX_OID_LEN];
  int i, rtest, newlen;
  static long long_ret;
  char buf[SNMP_MAXBUF];
  static char buf2[SNMP_MAXBUF];
  static oid  objid[MAX_OID_LEN];
  struct extensible *persistpassthru;
  FILE *file;

  /* Make sure that our basic pipe structure is malloced */
  init_persist_pipes();

  long_ret = *length;
  for(i=1; i<= numpersistpassthrus; i++) {
    persistpassthru = get_exten_instance(persistpassthrus,i);
    rtest = snmp_oid_min_compare(name, *length,
                persistpassthru->miboid, persistpassthru->miblen);
    if ((exact && rtest == 0) || (!exact && rtest <= 0)) {
      /* setup args */
      if (persistpassthru->miblen >= *length || rtest < 0)
        sprint_mib_oid(buf, persistpassthru->miboid, persistpassthru->miblen);
      else
        sprint_mib_oid(buf, name, *length);

      /* Open our pipe if necessary */
      if( ! open_persist_pipe( i, persistpassthru->name ) ) {
        return(NULL);
      }

      if (exact)
        sprintf(persistpassthru->command,"get\n%s\n",buf);
      else
        sprintf(persistpassthru->command,"getnext\n%s\n",buf);

      DEBUGMSGTL(("ucd-snmp/pass_persist", "persistpass-sending:\n%s",persistpassthru->command));
      if( ! write_persist_pipe( i, persistpassthru->command ) ) {
        *var_len = 0;
        /* close_persist_pipes is called in write_persist_pipe */
        return(NULL);
      }

      /* valid call.  Exec and get output */
      if ((file = persist_pipes[i].fIn)) {
        if (fgets(buf,sizeof(buf),file) == NULL) {
          *var_len = 0;
          close_persist_pipe(i);
          return(NULL);
        }
        /* persistant scripts return "NONE\n" on invalid items */
        if( ! strncmp( buf, "NONE", 4 ) ) {
          *var_len = 0;
          return( NULL );
        }
        newlen = parse_miboid(buf,newname);

        /* its good, so copy onto name/length */
        memcpy( (char *)name,(char *) newname, (int)newlen * sizeof (oid));
        *length = newlen;

        /* set up return pointer for setable stuff */
        *write_method = setPassPersist;

        if (newlen == 0 || fgets(buf,sizeof(buf),file) == NULL
            || fgets(buf2,sizeof(buf2),file) == NULL) {
          *var_len = 0;
          close_persist_pipe(i);
          return(NULL);
        }

        /* buf contains the return type, and buf2 contains the data */
        if (!strncasecmp(buf,"string",6)) {
          buf2[strlen(buf2)-1] = 0;  /* zap the linefeed */
          *var_len = strlen(buf2);
          vp->type = ASN_OCTET_STR;
          return((unsigned char *) buf2);
        } else if (!strncasecmp(buf,"integer",7)) {
          *var_len = sizeof(long_ret);
          long_ret = strtol(buf2, NULL, 10);
          vp->type = ASN_INTEGER;
          return((unsigned char *) &long_ret);
        } else if (!strncasecmp(buf,"unsigned",7)) {
          *var_len = sizeof(long_ret);
          long_ret = strtoul(buf2, NULL, 10);
          vp->type = ASN_UNSIGNED;
          return((unsigned char *) &long_ret);
        } else if (!strncasecmp(buf,"counter",7)) {
          *var_len = sizeof(long_ret);
          long_ret = strtoul(buf2, NULL, 10);
          vp->type = ASN_COUNTER;
          return((unsigned char *) &long_ret);
        } else if (!strncasecmp(buf,"octet",5)) {
          *var_len = asc2bin(buf2);
          vp->type = ASN_OCTET_STR;
          return((unsigned char *) buf2);
        } else if (!strncasecmp(buf,"opaque",5)) {
          *var_len = asc2bin(buf2);
          vp->type = ASN_OPAQUE;
          return((unsigned char *) buf2);
        } else if (!strncasecmp(buf,"gauge",5)) {
          *var_len = sizeof(long_ret);
          long_ret = strtoul(buf2, NULL, 10);
          vp->type = ASN_GAUGE;
          return((unsigned char *) &long_ret);
        } else if (!strncasecmp(buf,"objectid",8)) {
          newlen = parse_miboid(buf2,objid);
          *var_len = newlen*sizeof(oid);
          vp->type = ASN_OBJECT_ID;
          return((unsigned char *) objid);
        } else if (!strncasecmp(buf,"timetick",8)) {
          *var_len = sizeof(long_ret);
          long_ret = strtoul(buf2, NULL, 10);
          vp->type = ASN_TIMETICKS;
          return((unsigned char *) &long_ret);
        } else if (!strncasecmp(buf,"ipaddress",9)) {
          newlen = parse_miboid(buf2,objid);
          if (newlen != 4) {
            snmp_log(LOG_ERR,"invalid ipaddress returned:  %s\n",buf2);
            *var_len = 0;
            return(NULL);
          }
          long_ret = (objid[0] << (8*3)) + (objid[1] << (8*2)) +
            (objid[2] << 8) + objid[3];
	  long_ret = htonl(long_ret);
          *var_len = sizeof(long_ret);
          vp->type = ASN_IPADDRESS;
          return((unsigned char *) &long_ret);
        }
      }
      *var_len = 0;
      return(NULL);
    }
  }
  if (var_len)
    *var_len = 0;
  *write_method = NULL;
  return(NULL);
}

int
setPassPersist(int action,
	       u_char *var_val,
	       u_char var_val_type,
	       size_t var_val_len,
	       u_char *statP,
	       oid *name,
	       size_t name_len)
{
  int i, rtest;
  struct extensible *persistpassthru;

  char buf[SNMP_MAXBUF], buf2[SNMP_MAXBUF];
  long tmp;
  unsigned long utmp;
  int itmp;

  /* Make sure that our basic pipe structure is malloced */
  init_persist_pipes();

  for(i=1; i<= numpersistpassthrus; i++) {
    persistpassthru = get_exten_instance(persistpassthrus,i);
    rtest = snmp_oid_min_compare(name, name_len,
                persistpassthru->miboid, persistpassthru->miblen);
    if (rtest <= 0) {
      if (action != COMMIT)
        return SNMP_ERR_NOERROR;
      /* setup args */
      if (persistpassthru->miblen >= name_len || rtest < 0)
        sprint_mib_oid(buf, persistpassthru->miboid, persistpassthru->miblen);
      else
        sprint_mib_oid(buf, name, name_len);
      sprintf(persistpassthru->command,"set\n%s\n ",buf);
      switch(var_val_type) {
        case ASN_INTEGER:
        case ASN_COUNTER:
        case ASN_GAUGE:
        case ASN_TIMETICKS:
          tmp = *((long *) var_val);
          switch (var_val_type) {
            case ASN_INTEGER:
              sprintf(buf,"integer %d",(int) tmp);
              break;
            case ASN_COUNTER:
              sprintf(buf,"counter %d",(int) tmp);
              break;
            case ASN_GAUGE:
              sprintf(buf,"gauge %d",(int) tmp);
              break;
            case ASN_TIMETICKS:
              sprintf(buf,"timeticks %d",(int) tmp);
              break;
          }
          break;
        case ASN_IPADDRESS:
          utmp = *((u_long *) var_val);
	  utmp = ntohl(utmp);
          sprintf(buf,"ipaddress %d.%d.%d.%d",
                  (int) ((utmp & 0xff000000) >> (8*3)),
                  (int) ((utmp & 0xff0000) >> (8*2)),
                  (int) ((utmp & 0xff00) >> (8)),
                  (int) ((utmp & 0xff)));
          break;
        case ASN_OCTET_STR:
	  itmp = sizeof(buf2);
          memcpy(buf2, var_val, var_val_len);
          if (var_val_len == 0)
              sprintf(buf,"string \"\"");
          else if (bin2asc(buf2, var_val_len) == (int)var_val_len)
              sprintf(buf,"string \"%s\"",buf2);
          else
              sprintf(buf,"octet \"%s\"",buf2);
          break;
        case ASN_OBJECT_ID:
          sprint_mib_oid(buf2, (oid *)var_val, var_val_len);
          sprintf(buf,"objectid \"%s\"",buf2);
          break;
      }
      strcat(persistpassthru->command,buf);
      strcat(persistpassthru->command,"\n");

      if( ! open_persist_pipe( i, persistpassthru->name ) ) {
        return SNMP_ERR_NOTWRITABLE;
      }

      DEBUGMSGTL(("ucd-snmp/pass_persist", "persistpass-writing:  %s\n",persistpassthru->command));
      if( ! write_persist_pipe( i, persistpassthru->command ) ) {
        close_persist_pipe(i);
        return SNMP_ERR_NOTWRITABLE;
      }

      if (fgets(buf,sizeof(buf),persist_pipes[i].fIn) == NULL) {
        close_persist_pipe(i);
        return SNMP_ERR_NOTWRITABLE;
      }

      if (!strncasecmp(buf,"not-writable",11)) {
        return SNMP_ERR_NOTWRITABLE;
      } else if (!strncasecmp(buf,"wrong-type",9)) {
        return SNMP_ERR_WRONGTYPE;
      }
      return SNMP_ERR_NOERROR;
    }
  }
  if (snmp_get_do_debugging()) {
    sprint_mib_oid(buf2,name,name_len);
    DEBUGMSGTL(("ucd-snmp/pass_persist", "persistpass-notfound:  %s\n",buf2));
  }
  return SNMP_ERR_NOSUCHNAME;
}

int pass_persist_compare(const void *a, const void *b)
{
  const struct extensible * const *ap, * const *bp;
  ap = (const struct extensible * const *) a;
  bp = (const struct extensible * const *) b;
  return snmp_oid_compare((*ap)->miboid,(*ap)->miblen,(*bp)->miboid,(*bp)->miblen);
}

/*
 * Initialize our persistant pipes
 *   - Returns 1 on success, 0 on failure.
 *   - Initializes all FILE pointers to NULL to indicate "closed"
 */
static int init_persist_pipes (void)
{
  int i;

  /* if we are already taken care of, just return */
  if ( persist_pipes ) {
    return persist_pipes ? 1 : 0;
  }

  /* Otherwise malloc and initialize */
  persist_pipes = (struct persist_pipe_type *)
                  malloc( sizeof(struct persist_pipe_type) *
                          (numpersistpassthrus+1) );
  if( persist_pipes ) {
    for( i = 0; i <= numpersistpassthrus; i++ ) {
      persist_pipes[i].fIn = persist_pipes[i].fOut = (FILE *) 0;
      persist_pipes[i].fdIn = persist_pipes[i].fdOut = -1;
      persist_pipes[i].pid = -1;
    }
  }
  return persist_pipes ? 1 : 0;
}

/*
 * Destruct our persistant pipes
 *
 */
static void destruct_persist_pipes (void)
{
  int i;

  /* Return if there are no pipes */
  if ( ! persist_pipes ) {
    return;
  }

  for( i = 0; i <= numpersistpassthrus; i++ ) {
    close_persist_pipe(i);
  }

  free( persist_pipes );
  persist_pipes = (struct persist_pipe_type *) 0;
}

/* returns 0 on failure, 1 on success */
static int open_persist_pipe(int iindex, char *command)
{
  static int recurse = 0;  /* used to allow one level of recursion */

  DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe(%d,'%s')\n",iindex, command));
  /* Open if it's not already open */
  if( persist_pipes[iindex].pid == -1 ) {
    int fdIn, fdOut, pid;
    get_exec_pipes( command, &fdIn, &fdOut, &pid );

    /* Did we fail? */
    if( pid == -1 ) {
      DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: pid == -1\n"));
      recurse = 0;
      return 0;
    }

    /* If not, fill out our structure */
    persist_pipes[iindex].pid = pid;
    persist_pipes[iindex].fdIn = fdIn;
    persist_pipes[iindex].fdOut = fdOut;
    persist_pipes[iindex].fIn = fdopen(fdIn,"r");
    persist_pipes[iindex].fOut = fdopen(fdOut,"w");

    /* Setup our -non-buffered-io- */
    setbuf( persist_pipes[iindex].fOut, (char *)0 );
  }

  /* Send test packet always so we can self-catch */
  {
    char buf[SNMP_MAXBUF];
    /* Should catch SIGPIPE around this call! */
    if( ! write_persist_pipe( iindex, "PING\n" ) ) {
      DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: Error writing PING\n"));
      close_persist_pipe(iindex);

      /* Recurse one time if we get a SIGPIPE */
      if( ! recurse ) {
        recurse = 1;
        return open_persist_pipe(iindex,command);
      }
      recurse = 0;
      return 0;
    }
    if (fgets(buf,sizeof(buf),persist_pipes[iindex].fIn) == NULL) {
      DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: Error reading for PONG\n"));
      close_persist_pipe(iindex);
      recurse = 0;
      return 0;
    }
    if ( strncmp( buf, "PONG", 4 ) ) {
      DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: PONG not received!\n"));
      close_persist_pipe(iindex);
      recurse = 0;
      return 0;
    }
  }

  recurse = 0;
  return 1;
}

#if STRUCT_SIGACTION_HAS_SA_SIGACTION
/* Generic handler */
void sigpipe_handler (int sig, siginfo_t *sip, void *uap)
{
  return;
}
#endif

static int write_persist_pipe(int iindex, const char *data)
{
#if HAVE_SIGNAL
  struct sigaction sa, osa;
  int wret = 0, werrno = 0;

  /* Don't write to a non-existant process */
  if( persist_pipes[iindex].pid == -1 ) {
    return 0;
  }

  /* Setup our signal action to catch SIGPIPEs */
  sa.sa_handler = NULL;
#if STRUCT_SIGACTION_HAS_SA_SIGACTION
  sa.sa_sigaction = &sigpipe_handler;
#endif
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  if( sigaction( SIGPIPE, &sa, &osa ) ) {
    DEBUGMSGTL(("ucd-snmp/pass_persist",
                "write_persist_pipe: sigaction failed: %d", errno));
  }

  /* Do the write */
  wret = write( persist_pipes[iindex].fdOut, data, strlen(data) );
  werrno = errno;

  /* Reset the signal handler */
  sigaction( SIGPIPE, &osa, (struct sigaction *) 0 );

  if( wret < 0 ) {
    if( werrno != EINTR ) {
      DEBUGMSGTL(("ucd-snmp/pass_persist",
                  "write_persist_pipe: write returned unknown error %d\n",
                  errno));
    }
    close_persist_pipe(iindex);
    return 0;
  }
#endif /* HAVE_SIGNAL */
  return 1;
}

static void close_persist_pipe(int iindex)
{

  /* Check and nix every item */
  if( persist_pipes[iindex].fOut ) {
    fclose( persist_pipes[iindex].fOut );
    persist_pipes[iindex].fOut = (FILE *) 0;
  }
  if( persist_pipes[iindex].fdOut != -1 ) {
    close( persist_pipes[iindex].fdOut );
    persist_pipes[iindex].fdOut = -1;
  }
  if( persist_pipes[iindex].fIn ) {
    fclose( persist_pipes[iindex].fIn );
    persist_pipes[iindex].fIn = (FILE *) 0;
  }
  if( persist_pipes[iindex].fdIn != -1 ) {
    close( persist_pipes[iindex].fdIn );
    persist_pipes[iindex].fdIn = -1;
  }
  if( persist_pipes[iindex].pid != -1 ) {
#if HAVE_SYS_WAIT_H
    waitpid(persist_pipes[iindex].pid,0,0);
#endif
    persist_pipes[iindex].pid = -1;
  }

}



