/* 1219, Fri 1 May 98

   NM_ST.C:  DIsplay status of a NeTraMet meter

   Copyright (C) 1997-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: nm_st.c,v $
 * Revision 1.1.1.2.2.12  2002/02/23 01:57:19  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.8  2000/08/08 19:44:44  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.3  2000/06/06 03:38:10  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2  1999/10/03 21:06:16  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.2  1999/09/22 05:38:35  nevil
 * Improve code to work properly on 64-bit machines
 * - Add OS=ALPHA handling to configure.in
 * - Clean up the Alpha compiler warnings
 * - Change all the snmp-related code to use Bit32 instead of unsigned long
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:38:29  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:27  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:00  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1.2.1  1998/11/11 23:14:38  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:24  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2  1998/10/18 23:44:08  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:20  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:28  nguba
 * NetraMet 4.2 Original Distribution
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#include <string.h>

#define EXTSNMP
#include "ausnmp.h"

#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"
#include "snmpclnt.h"
#include "mib.h"

#define EXTERN
#include "nmc.h"
#include "nmc_c64.h"

int request_stop = 0;

void sigint_handler(int x)
{
   request_stop = 1;
   }

unsigned int max_flows;

char meter[NAME_LN], owner[NAME_LN], community[NAME_LN];
unsigned int def_sync, def_sample_interval, def_lag,
   def_download, def_no_write_meter;
int no_user_format, plain_format;

void no_write_warning(struct meter_status *ms)
{
   if (ms->no_write_warned) return;
   fprintf(stdout,"Community %s doesn't have write access to meter %s!\n"
      "   Collections won't trigger recovery of idle flows <<<\n",
      ms->community,ms->name);
   ms->no_write_warned = 1;
   }

void add_event(struct calendar_entry *entry, struct meter_status *ms)
{
   struct calendar_entry *cp, *lcp;
   entry->next_event = ms->next_event;  entry->ms = ms;
   entry->next = NULL;
   if (calendar == NULL) {  /* First entry in queue */
      calendar = entry;
      return;
      }
   lcp = NULL;  cp = calendar;
   do {
      if (ms->next_event < cp->next_event) {
         entry->next = cp;
         if (lcp == NULL) calendar = entry;  /* Add to head */
         else lcp->next = entry;  /* Link into queue */
         return;
         }
      lcp = cp;  cp = cp->next;
      } while (cp != NULL);
   lcp->next = entry;  /* Add to tail */
   }

void ruleset_row(struct meter_status *ms, struct row_info *rip)
{
   struct meter_rule_info mri;
   printf("rUleset %d:  Status=%d, Owner=%s, Name='%s'\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner, rip->tr_Name);
   if (strcmp(rip->tr_Owner, "NeTraMet") == 0) {
         /* We've found NeTraMet meter's default ruleset */
      ms->d_ruleset.ri_Index = rip->tr_Index; 
      }
#if 0
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
      mri.ri_Index = rip->tr_Index;
      ruleset_util(ms, RU_DESTROY, &mri);
      }
#endif
   }

void manager_row(struct meter_status *ms, struct row_info *rip)
{
   printf("mAnager %d:  Status=%d, Owner=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner);
#if 0
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0
      || strcmp(rip->tr_Owner, "NeTraMet") == 0) {
      ms->mi_u_Index = rip->tr_Index;
      task_util(ms, TU_DESTROY);
      }
#endif
   }

void reader_row(struct meter_status *ms, struct row_info *rip)
{
   printf("rEader %d:  Status=%d, Owner=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner);
#if 0
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
      ms->ci_u_Index = rip->tr_Index;
      reader_util(ms, CU_DESTROY, CU_UTIL);
      }
#endif
   }

int create_meter(struct meter_status *ms, time_t first_t, char *pn)
{
   struct calendar_entry *entry;
   int a,b,n, syntax;

   if (ms->name[0] == '\0' || ms->community[0] == '\0') {
      fprintf(stdout,"Meter name or community not specified !!!\n");
      return 0;
      }
   if (testing) printf("About to create_meter name=%s, community=%s\n", 
      ms->name, ms->community);

   if (!start_snmp_session(ms)) return 0;

   ms->statsreqd = 1;  /* Always want stats for nm_st! */
   ms->status = MT_MANAGE;
   if (meter_info(ms)) {
      ms->status |= (MT_UP | MT_INFO);
      ms->next_event = ms->next_sample = 
         first_t;  /* Get first sample immediately */
      entry = (struct calendar_entry *)calloc(
         sizeof(struct calendar_entry), 1);
      add_event(entry,ms);
      if (testing) 
         printf("t=%u, next_event=%u, next_keepalive=%u, next_sample=%u\n",
            first_t, ms->next_event, ms->next_keepalive, ms->next_sample);
#ifndef _AIX
      if (!meter_is_current(ms)) {
         fprintf(stdout,"Warning: meter %s (version %s) not same as %s!\n",
            ms->name,ms->version, pn);
         }
#endif
      if (ms->MaxFlows > max_flows) max_flows = ms->MaxFlows;
      }
   else {
      fprintf(stdout,"Couldn't get meter info from %s!\n"
         "   Does community %s have read or write access to the meter?\n",
         ms->name,ms->community);
      return 0;
      }

   set_meter_params(ms);

   search_table(ms, ST_RULESET, ruleset_row);
   search_table(ms, ST_MANAGER, manager_row);
   search_table(ms, ST_READER,  reader_row);

   ++nmeters;
   }

int main(int argc, char *argv[])
{
   int syntax;
   char *ap, arg[NAME_LN];
   int a, j;
   time_t t1,t2;  int busy_seconds;
   struct meter_status *ms, *nms;
   struct calendar_entry *cp;
   struct meter_rule_info mri;
   struct stat stat_buf;
   char have_config_file;  FILE *cnf;
   char download_only;

   if (argc < 2) {
      fprintf(stderr,"%s [options] meter-name community\n\n", argv[0]);
      exit(0);
      }

   signal(SIGINT, sigint_handler);
   signal(SIGTERM, sigint_handler);

   fprintf(stdout,"nm_st: Meter status display for NeTraMet: v" ver_str "\n");
      /* Check on meter version done in nmc_snmp.c */

   incl_depth = syntax = verbose = testing = listrules = standard = 0;
   def_sync = 1;  /* Samples synchronised with TOD clock */
   def_sample_interval = 120;  /* Default 2 minutes */
   def_lag = 0;  /* No time lag for collections */
   meter[0] = owner[0] = community[0] = '\0';
   max_flows = 0;
   def_download = 0;  /* Download rule sets on startup */
   def_no_write_meter = 0;
   plain_format = 0;

   for (a = 1; a < argc; ++a) {
      if (argv[a][0] == '-') {
         ap = argv[a]+2;
	 switch (argv[a][1]) {
	 case 'a':
            if (*ap == '\0') ap = argv[++a];
	    def_lag = atoi(ap);
	    break;
	 case 'c':
            if (*ap == '\0') ap = argv[++a];
	    j = atoi(ap);
            if (j == 0) download_only = 1;
            else def_sample_interval = j;
	    break;
	 case 'd':
	    snmp_dump_packet++;
	    break;
	 case 'm':
 	    if (*ap == '\0') ap = argv[++a];
	    au_snmp_port = atoi(ap);
	    break;
         case 'p':
            plain_format++;
            break;
	 case 's':
	    syntax++;
	    break;
	 case 't':
	    testing++;
	    break;
	 case 'u':
	    def_sync = 0;  /* Unsynchronised */
	    break;
	 case 'v':
	    verbose++;
	    break;
         case 'w':
            if (*ap == '\0') ap = argv[++a];
	    def_download = atoi(ap);
            break;
	 case 'x':
	    def_no_write_meter++;
	    break;
	 case 'S':
	    standard++;
	    break;
 	 default:
	    fprintf(stderr,"Invalid option: -%c\n", argv[a][1]);
	    break;
	    }
	 continue;
	 }
      if (meter[0] == '\0') strcpy(meter,argv[a]);
      else if (community[0] == '\0') strcpy(community,argv[a]);
      else if (owner[0] == '\0') {
         strncpy(owner,argv[a],NAMESZ);  owner[NAMESZ] = '\0';
         }
      }

   init_mib();  /* Initialise SNMP handler */

   first_meter = (struct meter_status *)calloc
      (sizeof(struct meter_status), 1);
   strcpy(first_meter->name,meter);
   strcpy((char *)first_meter->owner_name,
      owner[0] != '\0' ? owner : "nm_rc");
   strcpy((char *)first_meter->community,
      community[0] != '\0' ? community : "public");
   first_meter->synchronised = def_sync;
   first_meter->sample_interval = def_sample_interval;
   first_meter->lag_seconds = def_lag;
   first_meter->download_level = def_download;
   first_meter->no_write_meter = 1;
   first_meter->write_OK = 1;
   first_meter->lag_seconds = def_lag;

   time(&t1);
   for (nmeters = 0, ms = first_meter; ms; ms = ms->next)
      create_meter(ms, t1, argv[0]);
   if (nmeters == 0) {
      fprintf(stderr,"No meters to monitor !!!\n");
      exit(0);
      }

   for (;;) {
      for (;;) {
         time(&t1);
         if (calendar->next_event > t1) break;  /* Wait a while */
         cp = calendar;  calendar = cp->next;  /* Take entry from queue */
         ms = cp->ms;
         if (ms->status & MT_MANAGE) monitor(ms);
         add_event(cp,ms);
         }      
      if (testing) fflush(stdout);
      time(&t1);
      if (calendar->next_event > t1) sleep(calendar->next_event-t1);
      if (request_stop) {  /* Set by sigint_handler() */
#if 0
         reader_util(ms, CU_DESTROY, CU_CURRENT);
/*         reader_util(ms, CU_DESTROY, CU_DEFAULT);  */
         ms->mi_u_Index = ms->mi_Index;  task_util(ms, TU_DESTROY);
         ruleset_util(ms, RU_DESTROY, &ms->c_ruleset);
#endif
         exit(1);
         }
      }
   }

void monitor(struct meter_status *ms)
   /* Called every interval for each meter */
{
   time_t t, nst,si;  char *ts;
   int a,col, reachable, keepalive, s_statsreqd, i,j;
   unsigned long last_uptime, tb, tp;
   char fdfbuf[250], *fbp;
   struct flow_info *fp;
   unsigned int fi, t_count,x;
   struct display_data *ddp, *mdp;
   char buf[50], *bp;

   time(&t);  ts = fmt_time(&t);
   if (!ms->write_OK) no_write_warning(ms);
   last_uptime = ms->uptime;
   s_statsreqd = ms->statsreqd;  /* Save stats request through keepalive */

   reachable = 1;
   if (meter_info(ms) == 0) {  /* Lost contact */
      if (ms->status & MT_UP) {  /* Was up */
	 fprintf(stdout,"%s -- %s: No response\n", ts,ms->name);
	 }
      ms->status &= ~MT_UP;  /* Mark as 'down' */
      reachable = 0;
      }
   else if (!(ms->status & MT_UP)) {  /* Have contact now, was down */
      fprintf(stdout,"%s -- %s: Regained contact\n", ts,ms->name);
      ms->write_OK = 1;  ms->no_write_warned = 0;
      }
   ms->status |= MT_UP;
   ms->statsreqd = s_statsreqd;  /* Restore stats request from rule file */

   /* Meter processing .. */

   if (reachable) {
      ts[8] = '\0';  /* Only want hh:mm:ss */
      fprintf(stdout,"%s:  %u flows, %u recovered, %u.%u%% idle\n",
         ts, ms->NbrFlows, ms->FlowsRecovered, 
         ms->MinIdle1000/10,ms->MinIdle1000%10);
      }

   if (testing) printf("Sample: t=%u",ms->next_event);
   if (ms->synchronised) {
      nst = ms->next_sample + (si = ms->sample_interval);
      ms->next_sample = (nst / si) * si + ms->lag_seconds;
      }
   else ms->next_sample += ms->sample_interval;
   ms->next_event = ms->next_sample;
   if (testing) printf(", sync=%d, next_sample=%u, next_keepalive=%u\n",
      ms->synchronised, ms->next_sample, ms->next_keepalive);
   }
