/*
Copyright (c) 2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/


/**	@file	fsnmpcmd.c	The fsnmpcmd module in the fsnmp program.
*/



/**	Inside the fsnmpcmd module.
*/
#define FSNMPCMD_C	1

#include "fsnmp.h"




#line 52 "fsnmpcmd.ctr"




/**	Name of environment variable for printcap entry.
*/
static char env_name_printcap_entry[] = { "PRINTCAP_ENTRY" };



/**	Printcap key for output filter.
*/
static char printcap_key_of[] = { ":of=" };



/**	Default configuration file name.
*/
static char default_config_file[] = { DK_SYSCONFDIR "/fsnmp/fsnmp.conf" };



/**	Command: lp. */
static char *cmd00[] = { "lp", NULL };

/**	Command: SNMP version. */
static char *cmd01[] = { "snmp", "version", NULL };

/**	Command: SNMP community. */
static char *cmd02[] = { "snmp", "community", NULL };

/**	Command: Ctrl-D at start. */
static char *cmd03[] = { "ctrl-d", "at", "start", NULL };

/**	Command: Ctrl-D at end. */
static char *cmd04[] = { "ctrl-d", "at", "end", NULL };

/**	Command: Shutdown data socket. */
static char *cmd05[] = { "shutdown", "data", "socket", NULL };

/**	Command: Minimum pagecount. */
static char *cmd06[] = { "minimum", "pagecount", "time", NULL };

/**	Command: Shutdown accounting socket. */
static char *cmd07[] = { "shutdown", "accounting", "socket", NULL };

/**	Command: Data transfer port range. */
static char *cmd08[] = { "data", "transfer", "port", "range", NULL };

/**	Command: accounting port. */
static char *cmd09[] = { "accounting", "port", "range", NULL };

/**	Command: accounting destination. */
static char *cmd10[] = { "accounting", "destination", NULL };



/**	Command in configuration file.
*/
static char **config_commands[] = {
  cmd00,
  cmd01,
  cmd02,
  cmd03,
  cmd04,
  cmd05,
  cmd06,
  cmd07,
  cmd08,
  cmd09,
  cmd10,
  NULL
};



/**	SNMP versions.
*/
static char *snmp_versions[] = { "1", "2", "2c", "2p", "3", NULL };



/**	Check whether log file is already open.
	@param	fc	Fsnmp job.
*/
static
void
check_logging DK_P1(FC *,fc)
{
  char *pce;
  if(!((fc->flags) & FSNMP_FLAG_OF)) {
    pce = getenv(env_name_printcap_entry);
    if(pce) {
      if(strstr(pce, printcap_key_of)) {
        fc->flags |= FSNMP_FLAG_HAVE_LOG;
      }
    }
  }
}


/**	Initialize fsnmp job.
	@param	fc	Fsnmp job to initialize.
*/
void
fsnmpcmd_init DK_P1(FC *,fc)
{
  
  /* default flags */
  fc->flags 	= FSNMP_FLAGS_DEFAULTS;

  /* assume normal operation as long as there is no error yet */
  fc->exit_code = EXIT_SUCCESS;

  fc->buffer_in = NULL; fc->sz_buffer_in = 0;
  fc->buffer_out = NULL; fc->sz_buffer_out = 0; fc->sz_bo_used = 0;
  fc->a_hostport = NULL; fc->hostname = NULL; fc->portnumber = 0;
  fc->a_acctdest = NULL;
  fc->temp_file = NULL; fc->sz_temp_file = 0;
  fc->a_community = NULL; fc->snmp_vers = SNMP_VERSION_1;
  fc->argv = NULL; fc->arg_0 = NULL;
  fc->os = -1;
  fc->last_timestamp = 0;
  (fc->shd).tv_sec = 0; (fc->shd).tv_usec = 0;
  (fc->shp).tv_sec = 0; (fc->shp).tv_usec = 0;
  fc->mintimepc = 60;
  fc->oid_ps = NULL; fc->oid_ds = NULL; fc->oid_pc = NULL;
  fc->sz_oid_ps = 0; fc->sz_oid_ds = 0; fc->sz_oid_pc = 0;
  fc->pc1 = 0UL; fc->pc2 = 0UL;
  fc->dt_pmin = 0; fc->dt_pmax = 0; fc->ac_pmin = 0; fc->ac_pmax = 0;
  
}



/**	Retrieve one command line option argument.
	@param	fc	Fsnmp job.
	@param	k	Key character to find argument for.
	@return	Argument on success, NULL on error.
*/
char *
fsnmpcmd_get_argv DK_P2(FC *,fc, char,k)
{
  char *back = NULL;
  
  if((k >= 'a') && (k <= 'z')) {
    back = (fc->argv)[k - 'a'];
  } else {
    if((k >= 'A') && (k <= 'Z')) {
      back = (fc->argv)[26 + k - 'A'];
    }
  }
  
  return back;
}



/**	Apply command line arguments.
	@param	fc	Fsnmp job.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
void
fsnmpcmd_apply_argv DK_P3(FC *,fc, int,argc, char **,argv)
{
  int i;
  char *ptr, *vptr, **lfdptr;
  

  /* traverse all arguments */
  lfdptr = argv; lfdptr++; i = 1;
  while(i < argc) {
    ptr = *lfdptr;	
    if(*ptr == '-') {
      ptr++; vptr = ptr; vptr++;
      if((*ptr >= 'a') && (*ptr <= 'z')) {
        (fc->argv)[*ptr - 'a'] = vptr;
      } else {
        if((*ptr >= 'A') && (*ptr <= 'Z')) {
	  (fc->argv)[26 + *ptr - 'A'] = vptr;
	}
      }
    } else {
      fc->arg_0 = ptr;
    }
    lfdptr++; i++;
  }

  /* if we have -Fo we are running as of filter */
  ptr = fsnmpcmd_get_argv(fc, 'F');
  if(ptr) {
    if(*ptr == 'o') {
      fc->flags |= FSNMP_FLAG_OF;
    }
  }

  /* only one of the filters may overwrite the status file */
  check_logging(fc);

  
}



/**	Create file name for temporary file.
	@param	fc	Fsnmp job.
*/
void
fsnmpcmd_create_temp_file_name DK_P1(FC *,fc)
{
  pid_t mypid;
  unsigned long test_number = 0UL;
  int cc;
  /* struct stat stbuf; */
  dk_stat_t	dkstatbuf;
  
  if(fc->exit_code == EXIT_SUCCESS) {
    fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[10]);
    cc = 1;
    mypid = getpid();
    do {
      /* create file name candidate */
      sprintf(fc->temp_file, "tempfile-%lu-%lu.dat", (unsigned long)mypid, test_number);
      /* check whether file name candidate can be used */
      if(!dkstat_get(&dkstatbuf, fc->temp_file)) {
        cc = 0;
      }
      /* increase number for next candidate, abort on number overflow */
      if(cc) {
        test_number++;
        if(test_number == 0UL) {
          cc = 0; fc->exit_code = EXIT_FAILED;
        }
      }
    } while(cc && fsnmp_cc(fc));
    if(fc->exit_code == EXIT_SUCCESS) {
      fsnmplog(fc, PRIO_INFO, fsnmp_kw[12], fc->temp_file);
    } else {
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[11]);
    }
  }
  
}



/**	Process one configuration entry.
	@param	fc	Fsnmp job.
	@param	l	Input line to process.
	@return	1 on success, 0 on error.
*/
static
int
add_config_line DK_P2(FC *,fc, char *,l)
{
  char *p1, *p2, *x, *nv, *splitted[16];
  size_t sz;
  int i = 0;
  int back = 0;
  
  p1 = l;
  p2 = dkstr_chr(p1, '=');
  if(p2) {
    *(p2++) = '\0';
    p2 = dkstr_start(p2, NULL);
    p1 = dkstr_start(p1, NULL);
    if(p1 && p2) {
      dkstr_chomp(p1, NULL);
      sz = dkstr_explode(splitted, 15, p1, NULL);
      if(sz > 0) {
        i = dkstr_find_multi_part_cmd(splitted, config_commands, 1);
	switch(i) {
	  case 0: {	/* lp */
	    nv = dkstr_dup(p2);
	    if(nv) {
	      if(fc->a_hostport) {
	        x = fc->a_hostport; dk_delete(x); fc->a_hostport = NULL;
	      }
	      fc->a_hostport = nv;	back = 1;
	    } else {
	      fc->exit_code = EXIT_FAILED;
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[52]);
	    }
	  } break;
	  case 1: {	/* snmp version */
	    switch(dkstr_array_index(snmp_versions, p2, 0)) {
	      case 0: fc->snmp_vers = SNMP_VERSION_1; back = 1; break;
	      case 1: case 2: fc->snmp_vers = SNMP_VERSION_2c; back = 1; break;
	      case 3: fc->snmp_vers = SNMP_VERSION_2p; back = 1; break;
	      case 4: fc->snmp_vers = SNMP_VERSION_3; back = 1; break;
	    }
	  } break;
	  case 2: {	/* snmp community */
	    back = 1;
	    nv = dkstr_dup(p2);
	    if(nv) {
	      if(fc->a_community) {
	        x = fc->a_community; dk_delete(x); fc->a_community = NULL;
	      }
	      fc->a_community = nv;
	    } else {
	      fc->exit_code = EXIT_FAILED;
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[52]);
	    }
	  } break;
	  case 3: {	/* ctrl-d at start */
	    if(dkstr_is_bool(p2)) {
	      back = 1;
	      if(dkstr_is_on(p2)) {
	        fc->flags |= FSNMP_FLAG_CTRL_D_START;
	      } else {
	        fc->flags &= (~(FSNMP_FLAG_CTRL_D_START));
	      }
	    }
	  } break;
	  case 4: {	/* ctrl-d at end */
	    if(dkstr_is_bool(p2)) {
	      back = 1;
	      if(dkstr_is_on(p2)) {
	        fc->flags |= FSNMP_FLAG_CTRL_D_END;
	      } else {
	        fc->flags &= (~(FSNMP_FLAG_CTRL_D_END));
	      }
	    }
	  } break;
	  case 5: {	/* shutdown data socket */
	    if(dkstr_is_bool(p2)) {
	      if(dkstr_is_on(p2)) {
	        fc->flags |= FSNMP_SHUTDOWN_TRANSFER;
	      } else {
	        fc->flags &= (~(FSNMP_SHUTDOWN_TRANSFER));
	      }
	      fc->flags &= (~(FSNMP_TIMEOUT_TRANSFER));
	      back = 1;
	    } else {
	      double d;
	      int ok;
	      if(sscanf(p2, "%lf", &d) == 1) {
	        back = 1; ok = 0;
		fc->flags |= (FSNMP_SHUTDOWN_TRANSFER | FSNMP_TIMEOUT_TRANSFER);
		d = fabs(d);
		(fc->shd).tv_sec = dkma_double_to_ul_ok(floor(d), &ok);
		(fc->shd).tv_usec = dkma_double_to_ul_ok(1000000.0 * (d - floor(d)), &ok);
		if(ok) {
		  fc->exit_code = EXIT_ABORT;
	          fsnmplog(fc, PRIO_ERROR, fsnmp_kw[53]);
		}
	      }
	    }
	  } break;
	  case 6: {	/* minimum pagecount time */
	    int i;
            if(sscanf(p2, "%d", &i) == 1) {
	      back = 1;
	      if(i < 0) i = 0 - i;
	      fc->mintimepc = i;
	    }
	  } break;
	  case 7: {	/* shutdown accounting socket */
	    if(dkstr_is_bool(p2)) {
	      if(dkstr_is_on(p2)) {
	        fc->flags |= FSNMP_SHUTDOWN_PAGECOUNT;
	      } else {
	        fc->flags &= (~(FSNMP_SHUTDOWN_PAGECOUNT));
	      }
	      fc->flags &= (~(FSNMP_TIMEOUT_PAGECOUNT));
	      back = 1;
	    } else {
	      double d; int ok;
	      if(sscanf(p2, "%lf", &d) == 1) {
	        back = 1; ok = 0;
		fc->flags |= (FSNMP_SHUTDOWN_PAGECOUNT | FSNMP_TIMEOUT_PAGECOUNT);
		d = fabs(d);
		(fc->shp).tv_sec = dkma_double_to_ul_ok(floor(d), &ok);
		(fc->shp).tv_usec = dkma_double_to_ul_ok(1000000.0 *(d - floor(d)), &ok);
		if(ok) {
		  fc->exit_code = EXIT_ABORT;
	          fsnmplog(fc, PRIO_ERROR, fsnmp_kw[53]);
		}
	      }
	    }
	  } break;
	  case 8: {	/* data transfer port range */
	    char *ptr1, *ptr2;
	    unsigned u1, u2;
	    if(p2) {
	      ptr1 = dkstr_start(p2, NULL);
	      if(ptr1) {
	        ptr2 = dkstr_next(ptr1, NULL);
		if(ptr2) {
		  if(sscanf(ptr1, "%u", &u1) == 1) {
		    if(sscanf(ptr2, "%u", &u2) == 1) {
		      back = 1;
		      fc->dt_pmin = u1; fc->dt_pmax = u2;
		    }
		  }
		}
	      }
	    }
	  } break;
	  case 9: {	/* accounting port range */
	    char *ptr1, *ptr2;
	    unsigned u1, u2;
	    if(p2) {
	      ptr1 = dkstr_start(p2, NULL);
	      if(ptr1) {
	        ptr2 = dkstr_next(ptr1, NULL);
		if(ptr2) {
		  if(sscanf(ptr1, "%u", &u1) == 1) {
		    if(sscanf(ptr2, "%u", &u2) == 1) {
		      back = 1;
		      fc->ac_pmin = u1; fc->ac_pmax = u2;
		    }
		  }
		}
	      }
	    }
	  } break;
	  case 10: {	/* accounting destination */
	    char *mynv;
	    mynv = dkstr_dup(p2);
	    if(mynv) {
	      if(fc->a_acctdest) {
	        char *x;
		x = fc->a_acctdest; dk_delete(x); fc->a_acctdest = NULL;
	      }
	      fc->a_acctdest = mynv; back = 1;
	    } else {
	      fc->exit_code = EXIT_FAILED;
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[52]);
	    }
	  } break;
	}
      } else {
      }
    }
  }
  
  return back;
}




/**	Get host address from text (IP4 address in dotted decimal notation).
	@param	hn	IP4 address as text.
	@return	IP4 address in host representation on success, 0UL on error.
*/
unsigned long
fsnmp_dotted_string_to_ip DK_P1(char *, hn)
{
  unsigned long back = 0UL;
  unsigned long u1 = 0UL, u2 = 0UL, u3 = 0UL, u4 = 0UL, u = 0UL;
  int ende, state; char *ptr;
  
  if(hn) {
    state = 0;
    u = u1 = u2 = u3 = u4 = 0UL;
    ptr = hn; ende = 0;
    while(!ende) {
      if(*ptr) {
	if(isdigit(*ptr)) {
	  u = 0UL;
	  switch(*ptr) {
	    case '0': u = 0UL; break;
	    case '1': u = 1UL; break;
	    case '2': u = 2UL; break;
	    case '3': u = 3UL; break;
	    case '4': u = 4UL; break;
	    case '5': u = 5UL; break;
	    case '6': u = 6UL; break;
	    case '7': u = 7UL; break;
	    case '8': u = 8UL; break;
	    case '9': u = 9UL; break;
	  }
	  switch(state) {
	    case 0: u1 = 10UL * u1 + u; break;
	    case 1: u2 = 10UL * u2 + u; break;
	    case 2: u3 = 10UL * u3 + u; break;
	    case 3: u4 = 10UL * u4 + u; break;
	  }
	} else {
	  if(*ptr == '.') {
	    state++;
	    if(state >= 4) {
	      ende = 1;
	    }
	  }
	}
	ptr++;
      } else {
	ende = 1;
      }
    }
  }
  u1 = u1 << 24; u1 = u1 & 0xFF000000UL;
  u2 = u2 << 16; u2 = u2 & 0x00FF0000UL;
  u3 = u3 <<  8; u3 = u3 & 0x0000FF00UL;
  u4 = u4 & 0x000000FFUL;
  back = u1 | u2 | u3 | u4;
  
  return back;
}



/**	Look up host name.
	@param	hn	Host name to search.
	@return	IP4 address on success, 0UL on error.
*/
unsigned long
fsnmp_lookup_host DK_P1(char *,hn)
{
  unsigned long back = 0UL, *ulptr;
  struct hostent *he;
  char **xptr;
  
  he = gethostbyname(hn);
  if(he) {
    if(he->h_addrtype == AF_INET) {
      if(he->h_length == 4) {
        if(he->h_addr_list) {
	  xptr = he->h_addr_list;
	  ulptr = (unsigned long *)(*xptr);
	  if(ulptr) {
	    back = *ulptr;
	    
	  }
	}
      }
    }
  }
  
  return back;
}



/**	Check host name and port.
	@param	fc	Fsnmp job.
*/
static
void
check_host_name_and_port DK_P1(FC *,fc)
{
  char *p1;
  unsigned u;
  unsigned long ipaddr;
  if(!((fc->flags) & FSNMP_FLAG_STDOUT)) {
    if(fc->a_hostport) {
      p1 = strchr(fc->a_hostport, '%');
      if(p1) {
        fc->hostname = fc->a_hostport;
	*(p1++) = '\0';
	if(sscanf(p1, "%u", &u) == 1) {
	  fc->portnumber = u;
	  p1 = dkstr_start(fc->a_hostport, NULL);
	  if(p1) {
	    dkstr_chomp(p1, NULL);
	    ipaddr = fsnmp_dotted_string_to_ip(fc->hostname);
	    if(ipaddr) {
	      ipaddr = htonl(ipaddr);
	    } else {
	      ipaddr = fsnmp_lookup_host(fc->hostname);
	    }
	    if(ipaddr) {
	      (fc->sain).sin_family = AF_INET;
	      (fc->sain).sin_port = htons(fc->portnumber);
	      (fc->sain).sin_addr.s_addr = ipaddr;
	    } else {
	      fc->exit_code = EXIT_ABORT;
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[54], fc->hostname);
	    }
	  } else {
	    fc->exit_code = EXIT_ABORT;
	    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[55]);
	  }
	} else {
	  fc->exit_code = EXIT_ABORT;
	  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[56], p1);
	}
      } else {
        fc->exit_code = EXIT_ABORT;
	fsnmplog(fc, PRIO_ERROR, fsnmp_kw[57]);
      }
    } else {
      fc->exit_code = EXIT_ABORT;
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[58]);
    }
  }
}



/**	 Read configuration file.
	@param	fc	Fsnmp job.
*/
void
fsnmpcmd_read_config_file DK_P1(FC *,fc)
{
  char *pn, *fn, *p1, *p2;
  char buffer[4096];
  FILE *fipo;
  int state;
  unsigned long lineno = 0UL;
  
  fn = default_config_file;
  pn = fsnmpcmd_get_argv(fc, 'P');
  if(!pn) {
    pn = fsnmpcmd_get_argv(fc, 'Q');
  }
  if(pn) {
    fsnmplog(fc, PRIO_INFO, fsnmp_kw[5], fn, pn);
    fipo = dksf_fopen(fn, "r");
    if(fipo) {
      state = 1;
      while(fgets(buffer, sizeof(buffer), fipo) && fsnmp_cc(fc)) {
        lineno++;
        p1 = dkstr_start(buffer, NULL);
        if(p1) {
          dkstr_delcomm(buffer, '#');
	  p1 = dkstr_start(p1, NULL);
	  if(p1) {
	    dkstr_chomp(p1, NULL);
	    if(*p1 == '[') {
	      state = 0;
	      p2 = dkstr_chr(p1, ']');
	      if(p2) {
	        *p2 = '\0'; p1++;
	        if(strcmp(p1, pn) == 0) { state = 1; }
	      }
	    } else {
	      if(state) {
	        if(!add_config_line(fc, p1)) {
		  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[8], fn, lineno);
		}
	      }
	    }
	  }
        }
      }
      fclose(fipo); fipo = NULL;
      check_host_name_and_port(fc);
    } else {
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[6]);
      fc->exit_code = EXIT_ABORT;
    }
  } else {
    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[7]);
    fc->exit_code = EXIT_ABORT;
  }
  
}



