/* 1320, Sat 24 Feb 01 (PST)

   NMC.C:  First attempt at a manager for the AU Accounting Meter

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

/*
 * $Log: nmc.c,v $
 * Revision 1.1.1.2.2.11  2002/02/23 01:57:19  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.6  2000/08/08 19:44:44  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.4  2000/06/06 03:38:11  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.1  2000/01/12 02:57:03  nevil
 * Implement 'packet pair matched' turnaroundtime distribution attributes.
 * Fix ASN-related bugs in NeTraMet, distribution-related bugs in fd_filter.
 *
 * Revision 1.1.1.2  1999/10/03 21:06:16  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.5  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.4  1999/09/14 00:46:46  nevil
 * 4.3 Release ..
 *  - Implement -D option to run NeMaC as a daemon
 *  - Tidy up the on-line help displays
 *  - Make IPv4 the default PeerType for sfmt_attribute()
 *
 * Revision 1.1.1.1.2.3  1999/05/25 22:30:49  nevil
 * Make sure IPv6 managers interwork properly with IPv4 meters
 * - Determine meter's RULE_ADDR_SIZE by reading mask from rule 1
 *   of default ruleset.
 * - Print warning if meter rulesize < manager rulesize.
 * - Use smaller of these when downloading rules.
 *
 * Revision 1.1.1.1.2.2  1999/01/08 01:38:30  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1.2.1  1998/12/23 01:14:10  nevil
 * Distribution file for 4.3b6
 *
 * 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.2.2  1998/10/26 22:18:22  nevil
 * Tidy up help message a little
 *
 * Revision 1.1.3.2.2.1  1998/10/23 03:57:01  nevil
 * Include set_mibfile() prototype from parse.h
 *
 * 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:21  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.15  1998/10/08 09:22:30  nguba
 * -Added -b switch to NeMaC so that MIBs can be loaded from the command
 *  line instead of relying on the current working directory or an
 *  environment variable.  I have a mib in /etc/mib.txt but it gives me
 *  errors.  Being able to give a MIB via command line helps.
 * -I also had to increase the size of fn[64] to 256, which is --I
 *  belive-- the maximum a Unix command line can take.  I wonder whether
 *  Unix systems define this one somewhere...  Anyway, this variable is
 *  #define'd via MAXLINE.
 * -Use the function set_mibfile(char*) to set the mibfile via
 *  command-line.  This function is defined in parse.h, implemented in
 *  mib.c and is available via libsnmp.a
 *
 * Revision 1.14  1998/10/02 14:59:12  nguba
 * Added command line options/help screen for NeMaC
 *
 * Revision 1.12  1998/06/03 04:52:11  rtfm
 * Don't set snmp_delay (only done in nmc_snmp.c)
 * Use LastTime instead of sysUptime
 *
 * Revision 1.8  1998/04/23 00:48:20  rtfm
 * Fix bad call to printfd.  Nevil
 *
 * Revision 1.6  1998/04/22 22:36:14  kawai
 * -Restructured flow data output. All writes to flowdata file now
 *  go through printfd new routine to flush flow files flush_flows.
 * -Putting '+' on end of flowdata file name causes output to be
 *  piped to stdout too.
 * -Deleted the 'restart' logic from monitor -- it never worked.
 *
 * Revision 1.5  1998/03/17 21:16:18  kawai
 * added signal handler for USR2 to turn testing on and off
 *
 * Revision 1.4  1998/03/17 03:32:03  kawai
 * -Signal USR1 now causes NeMaC to roll over the flow files
 *  (same as flag file).
 * -Modified signal handling to raise SIGALRM so that sleep exits
 *  and requested action is performed immediately
 * -Added code to boundcheck all strings passed from the command line or
 *  config file.  Over-long strings are simply truncated (no errors issued)
 *
 * Revision 1.3  1998/03/13 03:07:29  kawai
 * Moved initial open of log file to main line.
 * If append_log then don't create new logfile.
 *
 * Revision 1.2  1998/03/11 02:44:33  kawai
 * Moved the event scheduling code out of create_meter into mainline.
 * Changed var syslog to syslog_name and made it global.
 *
 * Revision 1.1  1998/03/09 23:05:58  kawai
 * -Changed all printf to fprintf(stderr, ...
 * -Changed all 
 *      check_log(1)
 *        fprintf(logfl,...
 *      check_log(0);
 *  to: log_msg( LOG_INFO, FALSE, .....
 * -Changed create_meter to increment nmeters even if meter does
 *  not respond.  changed main loop to try and create_meters if
 *  status != MT_MANAGE
 */

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

#define TEST_MIR          0

static char rcsid[] = "$Id: nmc.c,v 1.1.1.2.2.11 2002/02/23 01:57:19 nevil Exp $";

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

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <errno.h>
#include <stdarg.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"
#include "parse.h"

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

char *ver = "NeMaC: NeTraMet Manager & Controller " ver_str "\n";

int request_stop = 0;
int request_rollover = 0;

void sigint_handler(int x)  /* INT (keyboard) and TERM (kill) */
{                /* To make ^c send INT, use   stty intr '^c' */
   request_stop = 1;
   }

void sigusr1_handler(int x)  /* kill -USR1 */
{
   request_rollover = 1;
   }

void sigusr2_handler(int x)  /* kill -USR2 */
{
   testing = !testing;
   }


void no_write_warning(struct meter_status *ms)
{
   if (ms->no_write_warned) return;
   fprintf(stderr, "Community %s doesn't have write access to meter %s!\n"
      "   Collections won't trigger recovery of idle flows <<<\n",
      ms->community,ms->name);
   log_msg(LOG_INFO, FALSE,
      "Community %s doesn't have write access to meter!\n"
      "   Collections won't trigger recovery of idle flows <<<\n",
      ms->community,ms->name);
   if (ms->statsreqd) {
      fprintf(stderr,
	      "   Meter statistics won't be zeroed after collections <<<\n");
      log_msg(LOG_INFO, FALSE,
         "   Meter statistics won't be zeroed after collections <<<\n");
      }
   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 */
   }

/* functions to deal with flow data files */

void flush_flows (struct meter_status *ms) {

   if (ms->append_flowdata) {
      fclose(ms->flows);
      ms->flows = NULL;
      }
   else fflush(ms->flows);
   }

void printfd(struct meter_status *ms, char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   vfprintf(ms->flows, fmt, ap);
   }

int open_datafile(struct meter_status *ms)
{
   char filename[NAME_LN*2], *cp;
   unsigned char a,b, n;
   time_t t;  char *ts;

   if (!(ms->status & MT_UP))  /* Meter not reachable! */
      return 0;  /* We've already displayed a message */

   if (ms->user_flowdatafile)  /* User specified data file name */
      ms->flows = fopen(ms->flowdatafile,"w");
      /* Note: Don't open "a" here.  Doing so produces a file with a
         ## record in the middle, and possibly with a group of flow
         records truncated at a block boundary as well.  It's better
         to rename such a 'crashed' file in a startup script. */
   else {
      sprintf(filename, "%s.flows", ms->name);  /* Open flows file */
      ms->flows = wfopen(ms->flowdatafile,filename);
      }
   if (ms->flows == NULL) {
      fprintf(stderr, "Failed to open flow data file for meter %s!\n",
         ms->name);
      log_msg(LOG_INFO, FALSE,
         "Failed to open flow data file for meter %s!\n", ms->name);
      return 0;
      }

   time(&t);  ts = fmt_time(&t);
   printfd(ms, "##NeTraMet v%s:  -", ms->version);
   if (ms->trace_interval == 0) printfd(ms, "c%d", ms->sample_interval);
   else {
      if (ms->crl_info != NULL) ms->trace_interval = atoi(ms->crl_info);
      printfd(ms, "T%d", ms->trace_interval);
      }
   printfd(ms, " -r %s ", ms->c_ruleset.filename);
   if (ms->s_ruleset.filename[0] != 0) 
      printfd(ms, "-e %s ",ms->s_ruleset.filename);
   printfd(ms, " %s %s  %u flows  starting at %s%s\n",
      ms->name, ms->interface, ms->MaxFlows, ts, utc_time ? " (UTC)" : "");

   if (ms->crl_info != NULL) {
      for (cp = ms->crl_info; *cp != ' '; ++cp);
      printfd(ms, "#CoralReef %s\n", cp+1);
      }

   printfd(ms, "#Format: ");
   if ((n = ms->format[a = 0]) != 0) for (;;) {
      for (b = 1; b <= SZ_ATTRIBS && attribs[b].index != n; ++b) ;
      if (b <= SZ_ATTRIBS) printfd(ms, attribs[b].name);
      else printfd(ms,"attr*%u", n);  /* Unknown attribute */
      if ((n = ms->format[a+1]) == 0) {
         if (strcmp(ms->separator[a]," ") != 0)
            printfd(ms, ms->separator[a]);
         break;
         }
      printfd(ms, ms->separator[a++]);
      }
   printfd(ms, "\n");

   flush_flows(ms);
   ms->restarting = 1;  /* Write #Ruleset records */
   return 1;
   }

void ruleset_row(struct meter_status *ms, struct row_info *rip)
{
   struct meter_rule_info mri;
   if (testing)
      fprintf(stderr, ">> ruleset %d:  Status=%d, Owner=%s, Name=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner, rip->tr_Name);

   if (!ms->use_meter_rows) {  /* Clear meter rows */
      if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
         mri.ri_Index = rip->tr_Index;
         ruleset_util(ms, RU_DESTROY, &mri);
         }
      else if (strcmp(rip->tr_Owner, "NeTraMet") == 0) {
         /* We've found NeTraMet meter's default ruleset */
         ms->d_ruleset.ri_Index = rip->tr_Index; 
         }
      }
   else {  /* Use rows already in meter */
      if (ms->download_level != 2) {  /* We are the 'master' manager */
         if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
            if (rip->tr_Index == ms->c_ruleset.ri_Index) {
               ms->c_ruleset.ri_RulesReady = rip->tr_Status == RS_ACTIVE;
               if (strcmp(rip->tr_Name, ms->c_ruleset.ri_Name) != 0)
                  fprintf(stderr,
		     "Old 'current' ruleset name != SET name <<<\n");
	       }
            if (rip->tr_Index == ms->s_ruleset.ri_Index) {
               ms->s_ruleset.ri_RulesReady = rip->tr_Status == RS_ACTIVE;
               if (strcmp(rip->tr_Name, ms->s_ruleset.ri_Name) != 0)
                  fprintf(stderr,
		     "Old 'standby' ruleset name != SET name <<<\n");
	       }
	    }
	 }
      else {  /* We are a 'secondary' manager */
         if(strcmp(rip->tr_Name, ms->c_ruleset.ri_Name) == 0) {
            ms->c_ruleset.ri_Index = rip->tr_Index;
            ms->c_ruleset.ri_RulesReady = rip->tr_Status == RS_ACTIVE;
	    fprintf(stderr,
	       "   Current rule set is row %d\n", ms->c_ruleset.ri_Index);
	    }
         if(strcmp(rip->tr_Name, ms->s_ruleset.ri_Name) == 0) {
            ms->s_ruleset.ri_Index = rip->tr_Index;
            ms->s_ruleset.ri_RulesReady = rip->tr_Status == RS_ACTIVE;
	    fprintf(stderr,
	       "   Standby rule set is row %d\n", ms->s_ruleset.ri_Index);
	    }
	 }
      }
   }

void manager_row(struct meter_status *ms, struct row_info *rip)
{
   if (testing)
      fprintf(stderr,
         ">> manager %d:  Status=%d, Owner=%s,  Current=%d, Standby=%d\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner,
      rip->tr_Current, rip->tr_Standby);

   if (!ms->use_meter_rows) {  /* Clear meter rows */
      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);
         }
      }
   else {  /* Use rows already in meter */
      if (ms->download_level != 2) {  /* We are the 'master' manager */
         if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
            ms->mi_Index = rip->tr_Index;
            ms->c_ruleset.ri_Index = rip->tr_Current;
            ms->s_ruleset.ri_Index = rip->tr_Standby;
	    }
	 }
      else {  /* We are a 'secondary' manager */
         /* Have to discover the ruleset rows, then make a new task */
	 }
      }
   }

void reader_row(struct meter_status *ms, struct row_info *rip)
{
   if (testing)
      fprintf(stderr,
         ">> reader %d:  Status=%d, Owner=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner);
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
      if (ms->download_level == 2) fprintf(stderr,
         "Download level 2: Another manager running for this Owner <<<\n");
      else {
         ms->ci_u_Index = rip->tr_Index;
         reader_util(ms, CU_DESTROY, CU_UTIL);
         }
      }
   }

int create_meter(struct meter_status *ms, time_t first_t, char *pn)
{
   int n, syntax, reachable;

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

   ++nmeters;

   if (!start_snmp_session(ms)) {
      log_msg(LOG_INFO, FALSE,
         "Meter %s failed to respond: it may be dead, unreachable or "
         "the community string may be wrong\n",
         ms->name); 
      return 0;
      }
   ms->OurLastCollectTime = 0;  /* Used to be 1L */

   ms->status = MT_MANAGE;
   if (reachable = meter_info(ms)) {
      ms->status |= (MT_UP | MT_INFO);
      meter_print(stdout,ms);
      if (testing) {
         meter_print(NULL,ms);  /* Print to the log file */
         }
#ifndef _AIX
      if (!meter_is_current(ms)) {
         fprintf(stderr, "Warning: meter %s (version %s) not same as %s!\n",
            ms->name,ms->version, pn);
	 log_msg(LOG_INFO, FALSE,
            "Meter %s (version %s) not same as %s!\n",
            ms->name,ms->version, pn);
         }
#endif
#if V6
      if (ms->meter_ra_len < RULE_ADDR_LEN) {
         fprintf(stdout, "Meter %s is not IPv6 capable!\n", ms->name);
	 log_msg(LOG_WARNING, FALSE,
            "Meter %s is not IPv6 capable!\n", ms->name);
         }
#endif
      }
   else {
      ms->status &= ~MT_UP;  /* Mark as 'down' */
      fprintf(stderr, "Couldn't get meter info from %s!\n", ms->name);
      fprintf(stderr,
         "   Does community %s have read or write access to the meter?\n",
         ms->community);
      log_msg(LOG_INFO, FALSE,
         "Couldn't get meter info from %s!\n", ms->name);
      log_msg(LOG_INFO, FALSE,
         "   Does community %s have read or write access to the meter?\n",
         ms->community);
      }

   if (testing) 
      fprintf(stderr,
         "t=%u, next_event=%u, next_keepalive=%u, next_sample=%u\n",
         first_t, ms->next_event, ms->next_keepalive, ms->next_sample);

   if (reachable) {
      if (ms->trace_interval != 0) {  /* User expceted a Trace File */
         if (meter_maj(ms) < 4 || meter_min(ms) < 4) {
            fprintf(stderr,"Meter doesn't support trace files <<<\n");
            log_msg(LOG_INFO, FALSE,
               "Meter doesn't support trace files <<<");
            goto abort;
            }
         else if (ms->crl_info == NULL) {
            fprintf(stderr,"Meter isn't reading a trace file !!!\n");
            log_msg(LOG_INFO, FALSE,
               "Meter isn't reading a trace file !!!");
            goto abort;
            }
         }

      if (ms->download_level == 0) set_meter_params(ms);

      search_table(ms, ST_READER, reader_row);
      if (ms->download_level == 0) {
         search_table(ms, ST_MANAGER, manager_row);
         search_table(ms, ST_RULESET, ruleset_row);
	 }
      else ms->use_meter_rows = 1;

      syntax = ms->download_level != 0;  /* 0 => initial download of rules */
      if (testing) fprintf(stderr, "standby rf %s, current rf %s\n",
         ms->s_ruleset.filename, ms->c_ruleset.filename);
      if (ms->s_ruleset.filename[0] != '\0') {
         n = parse_rulefile(ms,listrules,syntax,1,0);  /* 0 => initial load */
         if (!n || rferrors != 0) goto abort;
         }
      if (ms->c_ruleset.filename[0] == '\0')  /* No rule file specified */
         ms->ruleset = ms->CurrentRuleSet;
      else {  /* Download the rules we want to use */
         n = parse_rulefile(ms,listrules,syntax,0,0);  /* Initial load */
         if (!n || rferrors != 0) goto abort;
         }

      if (ms->use_meter_rows && ms->MIBver == 4) {  /* Use running rule sets */
         if (ms->download_level == 2 ) {
            search_table(ms, ST_RULESET, ruleset_row);  /* Find rulesets */
            if (ms->c_ruleset.ri_Index == 0) {
               fprintf(stderr, "Couldn't find ruleset %s on meter %s\n",
                  ms->c_ruleset.ri_Name, ms->name);
               log_msg(LOG_INFO, FALSE, "Couldn't find ruleset %s on meter %s\n",
                  ms->c_ruleset.ri_Name, ms->name);
               goto abort;
               }
            task_util(ms, TU_INIT);  /* Create new manager task to use them */
            if (ms->s_ruleset.filename[0] != '\0')
               task_util(ms, TU_STANDBY);
            task_util(ms, TU_CURRENT);
	    }
         else {  
            search_table(ms, ST_MANAGER, manager_row);
            search_table(ms, ST_RULESET, ruleset_row);
	    }
         }

      reader_util(ms, CU_INIT, CU_CURRENT);
      reader_util(ms, CU_SET_MINPDUS, CU_CURRENT);
      if (ms->s_ruleset.filename[0] != '\0')
         reader_util(ms, CU_INIT, CU_STANDBY);

      if (!ms->write_OK) no_write_warning(ms);
      if (!open_datafile(ms)) goto abort;  
      }

   if (testing) fprintf(stderr, 
      "create_meter(%s): UpTime=%lu\n", ms->name, ms->uptime);
   return 1;

 abort:
   --nmeters;
   return 0;
   }

void make_new_data_files(void)
{
   struct meter_status *ms;
   if (testing) fprintf(stderr, "Making new data files\n");
   for (ms = first_meter; ms; ms = ms->next) {
      if(ms->flows) fclose(ms->flows);  /* Close old flow data file */
      ms->flows = NULL;
      open_datafile(ms);  /* Open new one */
      }
   unlink(FLAGFILE);
   }

char cfname[NAME_LN], fdfname[NAME_LN], rfname[NAME_LN], srfname[NAME_LN],
   meter[NAME_LN], owner[NAME_LN], community[NAME_LN];

int def_sync, def_sample_interval, def_keepalive_interval,
   def_ci_Timeout, def_ci_MinPDUs,
   def_GCIntervalReqd, def_InactivityTime,
   def_HighWaterMark, def_FloodMark,
   def_SamplingRate, def_lag, def_download,
   def_no_write_meter, def_append_flowdata,
   def_trace_interval, def_download_only;

int main(int argc, char *argv[])
{
   int syntax, daemon;
   char *ap, arg[NAME_LN];
   int a, j, cs;
   time_t t1,t2;  int busy_seconds;
   struct meter_status *ms, *nms;
   struct calendar_entry *cp;
   struct stat stat_buf;
   char have_config_file;  FILE *cnf;
#define DEBUG_TRACE_STATE  0
#if DEBUG_TRACE_STATE
   int last_cs = -1;
#endif

   if (argc < 2) {  /* Help Screen when called with no args */
      fprintf(stderr, 
         "\nUsage: %s [OPTION]... meter-hostname community owner-name\n"
         "Combined manager and collector for the NeTraMet meter:\n\n"
         "  -a SEC \t Seconds collections are to lag after their\n"
         "         \t   synchronised time\n"
         "  -b MBF \t Read in alternate mib file, MBF\n"
         "  -c SEC \t Required collection interval (seconds)\n"
         "         \t If SEC is zero program will download rule files\n"
         "         \t   to the meter, then exit without collecting any\n"
         "         \t   flow data\n"
         "  -e RLE \t Read in rules from file RLE\n"
         "  -f CFG \t Read in configuration from file CFG\n"
         "  -g GCI \t Specify garbage collection interval (seconds)\n"
         "  -h HWM \t Set high water mark as percentage of flow space\n"
         "  -i ITO \t Inactivity timeout interval (seconds)\n"
         "  -k KII \t Keepalive interval (seconds)\n"
         "  -l     \t List rules as they are processed\n"
         "  -m PNO \t Specify UDP port to use for communication with meter\n"
         "  -o FLM \t Set flood mark as percentage of flow space\n"
         "  -p     \t Open-append-close flow data and log files\n"
         "         \t   Start new data file if old one is moved or renamed\n"
         "  -r RLE \t Give name of rule file RLE, to be read and\n"
         "         \t   downloaded to one or more meters\n"
         "  -s     \t Check syntax of rule file but don't download to meter\n"
         "  -t     \t Provide extra diagnostic output for testing\n"
         "  -u     \t Samples should be unsyncronized, i.e. every SEC\n"
         "         \t   seconds from NeMaC startup\n"
         "  -v     \t Verbose mode\n"
         "  -w DLL \t Download level\n"
         "         \t   DLL=0, download after startup and meter restart\n"
         "         \t   DLL=1, download only after meter restart\n"
         "         \t   DLL=2, never download\n"
         "  -x     \t Don't write anything to the meter\n"
         "  -D     \t Run NeMaC as a daemon\n"
         "  -E SEC \t Timeout in seconds for reader rows in meter\n"
         "  -F FDF \t Name of flow data file to write\n"
         "  -L NAM \t Name of log file to write\n"
         "  -M MPK \t Only retrieve flows with MPK packets to or from\n"
         "  -P     \t Open-append-close log file (subset of -p)\n"
         "\n"
         "For more information see the NeTraMet Reference Manual and User Guide.\n"
         "\n"
         "Report bugs to netramet@auckland.ac.nz\n", argv[0]);
      exit(0);
      }

   signal(SIGINT, sigint_handler);
   signal(SIGTERM, sigint_handler);
   signal(SIGUSR1, sigusr1_handler);
   signal(SIGUSR2, sigusr2_handler);

   syslog_name[0] = '\0';
   incl_depth = syntax = daemon = verbose = 
      testing = listrules = standard = utc_time = 0;
   def_sync = 1;  /* Samples synchronised with TOD clock */
   def_sample_interval = 120;  /* Default 2 minutes */
   def_lag = 0;  /* No time lag for collections */
   def_download = 0;  /* Download rule sets on startup */
   def_keepalive_interval = 0;  /* Default no keepalives */
   def_ci_Timeout = 0;  /* NeMaC collector rows never time out */
   def_ci_MinPDUs = 0;  /* Reader MinPDU filter value */
   def_HighWaterMark = 65;  /* NeMaC uses old (v3) default */
   def_GCIntervalReqd =  /* Use meter defaults for MIB variables */ 
      def_InactivityTime = def_SamplingRate = def_FloodMark = 0;
   def_append_flowdata = def_no_write_meter = 0;
   def_trace_interval = def_download_only = 0;
   strcpy(cfname, CFGFILE);
   srfname[0] = rfname[0] = fdfname[0] = meter[0] = 
      community[0] = owner[0] = '\0';
   user_logfile[0] = '\0';  append_log = 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 'b':
	   if (*ap == '\0') ap = argv[++a];
	   set_mibfile(ap);
	   break;
	 case 'c':
            if (*ap == '\0') ap = argv[++a];
	    j = atoi(ap);
            if (j == 0) def_download_only = 1;
            else def_sample_interval = j;
	    break;
	 case 'd':
	    snmp_dump_packet++;
	    break;
	 case 'e':
            if (*ap == '\0') ap = argv[++a];
	    strcpy(srfname, ap);  /* Standby rule file name */
	    break;
	 case 'f':
            if (*ap == '\0') ap = argv[++a];
	    strncpy(cfname, ap, NAME_LN);  /* Config file name */
	    cfname[NAME_LN-1] = '\0';
	    break;
         case 'g':
            if (*ap == '\0') ap = argv[++a];
	    def_GCIntervalReqd = atoi(ap);
            break;
         case 'h':
            if (*ap == '\0') ap = argv[++a];
	    def_HighWaterMark = atoi(ap);
            break;
         case 'i':
            if (*ap == '\0') ap = argv[++a];
	    def_InactivityTime = atoi(ap);
            break;
	 case 'k':
            if (*ap == '\0') ap = argv[++a];
	    def_keepalive_interval = atoi(ap);
	    break;
	 case 'l':
	    listrules++;
	    break;
	 case 'm':
 	    if (*ap == '\0') ap = argv[++a];
	    au_snmp_port = atoi(ap);
	    break;
         case 'n':
            if (*ap == '\0') ap = argv[++a];
	    def_SamplingRate = atoi(ap);
            break;
         case 'o':
            if (*ap == '\0') ap = argv[++a];
	    def_FloodMark = atoi(ap);
            break;
         case 'p':
	    def_append_flowdata++;  append_log++;
            break;
	 case 'r':
            if (*ap == '\0') ap = argv[++a];
	    strncpy(rfname, ap, NAME_LN);  /* Rule file name */
	    rfname[NAME_LN-1] = '\0';
	    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 'C':  /* It's a CoralReef tracefile meter */
	    def_trace_interval = 1;
            break;
         case 'D':
            daemon++;
            break;
         case 'E':
            if (*ap == '\0') ap = argv[++a];
	    def_ci_Timeout = atoi(ap);
            break;
	 case 'F':
            if (*ap == '\0') ap = argv[++a];
	    strncpy(fdfname, ap, NAME_LN);  /* Name of flow data file */
	    fdfname[NAME_LN-1] = '\0';
	    break;
	 case 'L':
            if (*ap == '\0') ap = argv[++a];
	    strncpy(user_logfile, ap, NAME_LN );  /* Name of log file */
	    user_logfile[NAME_LN-1] = '\0';
	    break;
         case 'M':
            if (*ap == '\0') ap = argv[++a];
	    def_ci_MinPDUs = atoi(ap);
            break;
         case 'P':
	    append_log++;  /* Only append to log file, not data file(s) */
            break;
	 case 'S':
	    standard++;
	    break;
	 case 'U':
	    utc_time++;
	    break;
	 case 'Y':
            if (*ap == '\0') ap = argv[++a];
	    strncpy(syslog_name, ap, NAME_LN);
	    syslog_name[NAME_LN-1] = '\0';
	    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') {
	 strncpy(community,argv[a], NAME_LN);
	 community[NAME_LN-1] = '\0';
         }
      else if (owner[0] == '\0') {
         strncpy(owner,argv[a],NAMESZ);  owner[NAMESZ-1] = '\0';
         }
      else {
         fprintf(stderr, "Identifier '%s' follows owner-name\n", argv[a]);
         exit(0);
         }
      }
   if (owner[0] == '\0')  /* No owner-name specified */
      strcpy(owner, "NeMaC");

   if (syntax) {  /* Test a rule file */
      fprintf(stderr, ver);
      ms = (struct meter_status *)calloc(sizeof(struct meter_status), 1);
      strcpy(ms->c_ruleset.filename,rfname);
      if (ms->s_ruleset.filename[0] != '\0')
         parse_rulefile(ms,listrules,1,1,0);
      parse_rulefile(ms,listrules,1,0,0);
      fprintf(stderr, "\n%d errors in rule file(s) %s\n\n", rferrors,rfname);
      exit(0);
      }

#ifdef LOG_LOCAL
   if (*syslog_name) openlog(syslog_name, LOG_PID, LOG_LOCAL);
#endif

   init_mib();  /* Initialise SNMP handler */

   time(&t1);
   have_config_file = 0;
   if (!parse_open(cfname)) {  /* No config file */
      first_meter = (struct meter_status *)calloc
         (sizeof(struct meter_status), 1);
      strcpy(first_meter->c_ruleset.filename,rfname);
      strcpy(first_meter->s_ruleset.filename,srfname);
      strcpy(first_meter->name,meter);
      strcpy((char *)first_meter->community,community);
      strcpy((char *)first_meter->owner_name,owner);
      if (fdfname[0] != '\0') {
         strcpy(first_meter->flowdatafile,fdfname);
         first_meter->user_flowdatafile = 1;
         }
      first_meter->append_flowdata = def_append_flowdata;
      first_meter->synchronised = def_sync;
      first_meter->keepalive_interval = def_keepalive_interval;
      first_meter->write_OK = 1;
      first_meter->ci_Timeout = def_ci_Timeout;
      first_meter->ci_MinPDUs = def_ci_MinPDUs;
      first_meter->GCIntervalReqd = def_GCIntervalReqd;
      first_meter->InactivityTime = def_InactivityTime;
      first_meter->SamplingRate = def_SamplingRate;
      first_meter->lag_seconds = def_lag;
      first_meter->HighWaterMark = def_HighWaterMark;
      first_meter->FloodMark = def_FloodMark;
      first_meter->download_level = def_download;
      first_meter->download_only = def_download_only;
      first_meter->no_write_meter = def_no_write_meter;
      if (first_meter->trace_interval = def_trace_interval)
         first_meter->sample_interval = 1;
      else first_meter->sample_interval = def_sample_interval;
      }
   else for (first_meter = NULL; ; ) {  /* Parse config file */
      have_config_file = 1;
      ms = (struct meter_status *)calloc(sizeof(struct meter_status), 1);
      strcpy(ms->c_ruleset.filename,rfname);
      strcpy(ms->s_ruleset.filename,srfname);
      if (fdfname[0] != '\0') {
         strcpy(ms->flowdatafile,fdfname);
         ms->user_flowdatafile = 1;
         }
      ms->synchronised = def_sync;
      ms->keepalive_interval = def_keepalive_interval;
      ms->write_OK = 1;
      ms->GCIntervalReqd = def_GCIntervalReqd;
      ms->InactivityTime = def_InactivityTime;
      ms->lag_seconds = def_lag;
      ms->SamplingRate = def_SamplingRate;
      ms->HighWaterMark = def_HighWaterMark;
      ms->FloodMark = def_FloodMark;
      ms->download_level = def_download;
      ms->download_only = def_download_only;
      ms->append_flowdata = def_append_flowdata;
      ms->no_write_meter = def_no_write_meter;
      if (ms->trace_interval = def_trace_interval)
         ms->sample_interval = 1;
      else ms->sample_interval = def_sample_interval;
      while (lic != '\n') {  /* Start with first char of a line */
	 nextchar();
	 if (end_of_file()) break;
	 }
      if (end_of_file()) break;
      getarg(arg);
      if (end_of_file()) break;
      for ( ; arg[0] == '-'; ) {
      int l;
         ap = &arg[2];
         switch (arg[1]) {
         case 'a':
            if (*ap == '\0') getarg(ap = arg);
	    ms->lag_seconds = atoi(ap);
            break;
         case 'c':
            if (*ap == '\0') getarg(ap = arg);
	    j = atoi(ap);
            if (j == 0) ms->download_only = 1;
            else ms->sample_interval = j;
            break;
         case 'e':
            if (*ap == '\0') getarg(ap = arg);
	    strcpy(ms->s_ruleset.filename, ap);  /* Standby rule file name */
            break;
         case 'g':
            if (*ap == '\0') getarg(ap = arg);
	    ms->GCIntervalReqd = atoi(ap);
            break;
         case 'h':
            if (*ap == '\0') getarg(ap = arg);
	    ms->HighWaterMark = atoi(ap);
            break;
         case 'i':
            if (*ap == '\0') getarg(ap = arg);
	    ms->InactivityTime = atoi(ap);
            break;
         case 'k':
            if (*ap == '\0') getarg(ap = arg);
	    ms->keepalive_interval = atoi(ap);
            break;
         case 'n':
            if (*ap == '\0') getarg(ap = arg);
	    ms->SamplingRate = atoi(ap);
            break;
         case 'o':
            if (*ap == '\0') getarg(ap = arg);
	    ms->FloodMark = atoi(ap);
            break;
         case 'p':
	    ms->append_flowdata++;
            break;
         case 'r':
            if (*ap == '\0') getarg(ap = arg);
	    strcpy(ms->c_ruleset.filename, ap);  /* Rule file name */
            break;
         case 'u':
	    ms->synchronised = 0;  /* Unsynchronised */
            break;
         case 'w':
            if (*ap == '\0') getarg(ap = arg);
	    ms->download_level = atoi(ap);
            break;
	 case 'x':
	    ms->no_write_meter++;
	    break;
         case 'C':  /* It's a CoralReef tracefile meter */
	    ms->trace_interval = 1;  /* This is a 'CoralReef file' meter */
            ms->sample_interval = 1;  /* Check ms state every 1s */
            break;
         case 'E':
            if (*ap == '\0') ap = argv[++a];
	    ms->ci_Timeout = atoi(ap);
            break;
	 case 'F':
            if (*ap == '\0') getarg(ap = arg);
	    strcpy(ms->flowdatafile, ap);  /* Flow data file name */
	    l = strlen(ms->flowdatafile);
            ms->user_flowdatafile = 1;
	    break;
	 case 'L':
            if (*ap == '\0') getarg(ap = arg);
	    strcpy(user_logfile, ap);  /* Log file name */
	    break;
         case 'M':
            if (*ap == '\0') ap = argv[++a];
	    ms->ci_MinPDUs = atoi(ap);
            break;
         case 'P':
	    append_log++;
            break;
	 default:
	    fprintf(stderr, "Invalid option in config file: %s\n", arg);
	    break;
            }
         getarg(arg);
         }

      if (strcmp(arg,"default") == 0) {  /* Set defaults from config record */
         strcpy(rfname,ms->c_ruleset.filename);
         strcpy(srfname,ms->s_ruleset.filename);
         if (ms->flowdatafile[0] != '\0')
            strcpy(fdfname,ms->flowdatafile);
         def_sync = ms->synchronised;
         def_sample_interval = ms->sample_interval;
         def_keepalive_interval = ms->keepalive_interval;
         def_ci_Timeout = ms->ci_Timeout;
         def_ci_MinPDUs = ms->ci_MinPDUs;
         def_GCIntervalReqd = ms->GCIntervalReqd;
         def_InactivityTime = ms->InactivityTime;
         def_lag = ms->lag_seconds;
         def_SamplingRate = ms->SamplingRate;
         def_HighWaterMark = ms->HighWaterMark;
         def_FloodMark = ms->FloodMark;
         def_download = ms->download_level;
         def_append_flowdata = ms->append_flowdata;
         def_no_write_meter = ms->no_write_meter;
         def_download_only = ms->download_only;
         def_trace_interval = ms->trace_interval;
         free(ms);
         if (trailing_arg()) {
            getarg(community);
            if (trailing_arg()) {
               getarg(arg);
               strncpy(owner, arg, NAMESZ );
	       owner[NAMESZ] = '\0';
	       }
	    }
         }
      else {  /* Config record specified a meter */
         strcpy(ms->name, arg);
         if (trailing_arg()) {
            getarg((char *)ms->community);
            if (trailing_arg()) {
               getarg(arg);
	       strncpy(ms->owner_name, arg, NAMESZ);
	       ms->owner_name[NAMESZ] = '\0';
	       }
            else strcpy((char *)ms->owner_name, owner);
	    }
         else {
            strcpy((char *)ms->community, community);
            strcpy((char *)ms->owner_name, owner);
	    }
         if (first_meter == NULL) first_meter = ms;
         else for (nms = first_meter; ; nms = nms->next) {
            if (nms->next == NULL) {
               nms->next = ms;  break;
               }
            }
         }
      }

   if (daemon) {  /* User asked to run NeMaC as daemon
                     (suggested by Glen Eustace, 15 Jul 99) */
      switch (fork()) {
      case 0:  /* Child (left running on it's own) */
         break;
      case -1:
         fprintf(stderr, "Fork failed!!\n");
         exit(1);
      default:  /* Parent */
         exit(0);
         }
      setsid();  /* Move into a new session */
      }

   /* Open log file */

   if (user_logfile[0] != '\0') {  
      strcpy(logfile,user_logfile);
      if (!append_log) {
	 if ((logfl = fopen(logfile,"w")) == 0) {
	    if (*syslog_name)
               syslog(LOG_ERR, "Failed to open logfile '%s':%s\n",
                  logfile, strerror(errno));
	    else fprintf(stderr,"Failed to open logfile '%s':%s\n",
		  logfile, strerror(errno));
	    }
         }
      }
   else if (!*syslog_name) logfl = wfopen(logfile, LOGFILE);

   log_msg(LOG_INFO, FALSE, "Starting %s", ver);
      /* Check on meter version done in nmc_snmp.c */

   /* Create meter processes */

   time(&t1);
   for (nmeters = 0, ms = first_meter; ms; ms = ms->next) {
      struct calendar_entry *entry;
      ms->status = 0;
      if (create_meter(ms, t1, argv[0]) == 0) {
	 fprintf(stderr,
            "Warning!! Failed to start meter %s; check log for details\n",
	     ms->name);
         }
      entry =
	(struct calendar_entry *)calloc(sizeof(struct calendar_entry), 1);
      if (ms->status &  MT_MANAGE) {
	 ms->next_event = ms->next_sample = t1; /* Get first sample now */
         } 
      else {
	 ms->next_event = ms->next_keepalive = ms->keepalive_interval;
         }
      add_event(entry,ms);
      }

   if (nmeters == 0) {
      fprintf(stderr, "No meters to monitor !!!\n");
      exit(0);
      }

   unlink(FLAGFILE);  /* Make sure there isn't an old one lying about */
   if (have_config_file) {  /* Write 'pid' file */
      sprintf(arg,"%s.pid",cfname);
      cnf = fopen(arg,"w");
      a = getpid();
      fprintf(cnf,"%d  # %s started %s, config file %s",
         a, argv[0], fmt_time(&t1),cfname);
      fclose(cnf);
      }

   for (;;) {    /* Main event loop */
      while (calendar != NULL && 
            calendar->next_event <= time(&t1)) {  /* Things to do */
         cp = calendar;  calendar = cp->next;  /* Take entry from queue */
         ms = cp->ms;

         if (ms->trace_interval == 0) {  /* Normal 'live' meter */
            if (ms->download_only) {  /* Shut down tAsk and rEader */
               if (ms->mi_Index != 0) {
                  reader_util(ms, CU_DESTROY, CU_CURRENT);
                  if (ms->s_ruleset.filename[0] != '\0')
                     reader_util(ms, CU_DESTROY, CU_STANDBY);
                  ms->mi_u_Index = ms->mi_Index;  task_util(ms, TU_DESTROY);
                  }
               if (--nmeters == 0) exit(0);
               }
            else {
               if (ms->status & MT_MANAGE) monitor(ms);
               else {
                  create_meter(ms, t1, argv[0]);
                  if (ms->status &  MT_MANAGE) {  /* Get first sample now */
                     ms->next_event = ms->next_sample = t1;
                     } 
                  else {
                     ms->next_event = ms->next_keepalive = t1 +
                        (ms->keepalive_interval ? ms->keepalive_interval : 120);
                     }
                  }
               add_event(cp,ms);
               }
	    }
         else {  /* Meter reading tracefile (Dag or CoralReef) */
            if (!TrState_util(ms, CSU_READ, &cs)) {
               printf(">>> Couldn't read meter CrlState <<<\n");
               log_msg(LOG_INFO, FALSE, "Couldn't read CrlState!\n");
               }
            else {
#if DEBUG_TRACE_STATE
               if (cs != last_cs) {
                  printf(">>> trace: state=%d, ", cs);
                  last_cs = cs;
                  }
#endif
               if (cs == CT_WAIT_FLOWS_READ) {
                  if (ms->status & MT_MANAGE)
		     monitor(ms);  /* Read flow data from meter */
                  cs = CT_PROCESS_FLOWS;
                  if (!TrState_util(ms, CSU_WRITE, &cs))
                     log_msg(LOG_INFO, FALSE, "Couldn't write CrlState!\n");
                  }
               else if (cs == CT_TRACE_EOF) {
                  request_stop = 1;  /* Will read last set of flows */
 	          }
	       }
            if (!request_stop) {
               ms->next_event = ms->next_keepalive = t1 + 2;
               add_event(cp,ms);
               }
#if DEBUG_TRACE_STATE
            printf("new_cs=%d, req_stop=%d, next_event=%d\n",
               cs,request_stop,ms->next_event);
#endif
            }
         }      

     /* Finished processing scheduled events */

      time(&t1);

      if (!request_stop && !request_rollover && stat(FLAGFILE,&stat_buf) != 0
	  && calendar->next_event > t1)	 sleep(calendar->next_event-t1);

      if (request_rollover || stat(FLAGFILE,&stat_buf) == 0) {
	 log_msg(LOG_INFO, FALSE, "Rolling over flow files\n");
	 make_new_data_files();
	 if (request_rollover) {
	    request_rollover = 0;
	    }
         }
      if (request_stop) {  /* Set by sigint_handler() */
         for (ms = first_meter; ms; ms = ms->next) {
            if (ms->status & MT_MANAGE) monitor(ms);
               /* Don't loose flow data gathered since last collection! */
            if (ms->mi_Index != 0) {
                  /* All NeMaC shutdowns destroy their tasks and readers */
               reader_util(ms, CU_DESTROY, CU_CURRENT);
               if (ms->s_ruleset.filename[0] != '\0')
                  reader_util(ms, CU_DESTROY, CU_STANDBY);
               ms->mi_u_Index = ms->mi_Index;  task_util(ms, TU_DESTROY);
               }
            if (ms->download_level != 2) {  /* Only 'master' mgrs destroy rulesets */
               ruleset_util(ms, RU_DESTROY, &ms->c_ruleset);
               if (ms->s_ruleset.filename[0] != '\0')
                  ruleset_util(ms, RU_DESTROY, &ms->s_ruleset);
               } 

            if (ms->trace_interval != 0) {  /* Meter running CoralReef tracefile */
               cs = CT_SHUTDOWN;
               if (!TrState_util(ms, CSU_WRITE, &cs))
                  log_msg(LOG_INFO, FALSE, "Couldn't write CrlState!\n");
               }
            }
	 log_msg( LOG_INFO, FALSE, "NeMaC Shutting down\n");
         exit(1);
         }
      }
   }

struct meter_status *cms;  /* Current meter info */
int activeflows;  /* Nbr of flows active this collection */


void process_row(struct flow_info *fp)
{
   char fdfbuf[12000], *fbp;
   int a, col;

   fbp = fdfbuf;  /* Build flow data file line */
   for (col = cms->format[a = 0]; ; ) {
      if (col != '\0') fbp = sfmt_attrib(fbp, fp,col);
      if ((col = cms->format[a+1]) == '\0') {
         if (strcmp(cms->separator[a]," ") != 0)
            fbp = strmov(fbp,cms->separator[a]);
         break;
         }
      fbp = strmov(fbp,cms->separator[a++]);
      }
   *fbp = '\0';  printfd(cms, fdfbuf);
   printfd(cms, "\n");
   ++activeflows;
   }

void monitor(struct meter_status *ms)
   /* Called every interval for each meter */
{
   time_t t, nst,si;  char *ts;
   int mir, reachable, keepalive, s_statsreqd;
   unsigned long aps,apb, last_uptime;
   unsigned int n, i,j;
   struct stat statbuf;
   int crl_h,crl_m,crl_s;

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

   reachable = 1;
   if (keepalive) {
      ms->statsreqd = 0;  /* Don't get stats for a keepalive */
      mir = meter_info(ms);
#if TEST_MIR
      fprintf(stderr, " @@ monitor(1): keepalive, mir=%d\n", mir);
#endif
      }
   else { 
      if (ms->MIBver == 4) {  /* Tell meter we want to start a collection */
         if (ms->c_ruleset.ri_Index != 0) {
            mir = reader_util(ms, CU_SET_TIME, CU_CURRENT);
#if TEST_MIR
            fprintf(stderr, " @@ monitor(2): current, mir=%d\n", mir);
#endif
            if (mir && ms->s_ruleset.ri_Index != 0) {
               mir = reader_util(ms, CU_SET_TIME, CU_STANDBY);
#if TEST_MIR
               fprintf(stderr, " @@ monitor(3): standby, mir=%d\n", mir);
#endif
               }
	    }
         else mir = 1;  /* No current ruleset (yet) */
         }
      else {  /* V3 MIB */
         mir = reader_util(ms, CU_SET_TIME, CU_CURRENT);
#if TEST_MIR
         fprintf(stderr, " @@ monitor(4): V3, mir=%d\n", mir);
#endif
         }
      if (mir) {  /* LastTime was set OK */
         mir = meter_info(ms);  /* ms->uptime = LastTime */
#if TEST_MIR
         fprintf(stderr, " @@ monitor(5): info, mir=%d\n", mir);
#endif
         }
      }

   if (mir == 0) {  /* Lost contact */
      if (restart_snmp_session(ms)) {
         ms->uptime = 0;
         mir = meter_info(ms);
         }
#if TEST_MIR
      fprintf(stderr,
         " @@ monitor(6): restart, MIBver=%d, uptime=%lu, mir=%d\n",
         ms->MIBver, ms->uptime, mir);
#endif
      }
   if (mir == 0) {  /* Couldn't re-establish contact */
      log_msg(LOG_INFO, FALSE,"%s: No response\n", ms->name);
      reachable = 0;
      }

   ms->statsreqd = s_statsreqd;  /* Restore stats request from rule file */

   if (testing) fprintf(stderr,
      "reachable=%d, ms->uptime=%lu, last_uptime=%lu\n",
      reachable, ms->uptime, last_uptime);

   /* Meter processing .. */

   if (reachable) {
      if (ms->uptime < last_uptime ) {  /* Meter has restarted */
	 log_msg(LOG_INFO, FALSE,
		"%s: Meter has restarted\n", ms->name);

         ms->write_OK = 1;  ms->no_write_warned = 0;
         ms->OurLastCollectTime = 0;  /* Was 1L */

         if (ms->download_level != 2) set_meter_params(ms);

         /* Clear old (e.g. NeTraMet) tasks from meter */
         search_table(ms, ST_READER,  reader_row);
         if (ms->download_level != 2) {
            ms->use_meter_rows = 0;
            search_table(ms, ST_MANAGER, manager_row);
            search_table(ms, ST_RULESET, ruleset_row);
	    }
         else ms->use_meter_rows = 1;

         /* Forget our control rows - mustn't confuse the download */
         ms->ci_c_Index = ms->ci_s_Index = 0;
         ms->mi_Index = 0;
         ms->c_ruleset.ri_Index = ms->s_ruleset.ri_Index = 0;
         if (ms->s_ruleset.filename[0] != '\0') {
            n = parse_rulefile(ms,listrules,0,1,1);  /* Reload */
            if (!n || rferrors != 0) return;
            }
         if (ms->c_ruleset.filename[0] != '\0') {
               /* Rule file was specified */
            n = parse_rulefile(ms,listrules,0,0,1);  /* Reload */
            if (!n || rferrors != 0) return;
            }

         if (ms->download_level == 2 ) {
            search_table(ms, ST_RULESET, ruleset_row);  /* Find rulesets */
            if (ms->c_ruleset.ri_Index == 0) {  /* Rulestes not there yet */
               ms->mi_Index = 0;  /* Try again */
	       }
            else {
               if (ms->c_ruleset.ri_Status == RS_ACTIVE) {
                  task_util(ms, TU_INIT);  /* Create 'non-master' task */
                  task_util(ms, TU_STANDBY);
                  task_util(ms, TU_CURRENT);
		  }
               else {  /* Try again */
                  ms->mi_Index = 0;
		  }
	       }
	    }

         if (ms->mi_Index != 0) {
            reader_util(ms, CU_INIT, CU_CURRENT);
            if (ms->s_ruleset.filename[0] != '\0')
               reader_util(ms, CU_INIT, CU_STANDBY);
            if (stat(ms->flowdatafile,&statbuf) != 0)  /* File doesn't exist */
               open_datafile(ms);
            if (!ms->write_OK) no_write_warning(ms);
	    }

         ms->restarting = 1;  /* Write #Ruleset records */
         if (!keepalive) {  /* Tell meter we want to start a collection */
            if (ms->MIBver == 4) {
               if (ms->c_ruleset.ri_Index != 0)
                  reader_util(ms, CU_SET_TIME, CU_CURRENT);
               if (ms->s_ruleset.ri_Index != 0)
                  reader_util(ms, CU_SET_TIME, CU_STANDBY);
               }
            else reader_util(ms, CU_SET_TIME, CU_CURRENT);  /* V3 MIB */
            }
         meter_info(ms);
         }
      if (ms->mi_Index == 0 && ms->MIBver == 4)
         ms->uptime = last_uptime;  /* Haven't got our task running yet */
      }

   if (keepalive) {
      ms->next_keepalive += ms->keepalive_interval;
      if (testing) fprintf(stderr, "Keepalive: t=%u, next_keepalive=%u\n",
         ms->next_event, ms->next_keepalive);
      ms->next_event = ms->next_keepalive < ms->next_sample ? 
         ms->next_keepalive : ms->next_sample;
      return;
      }

   if (reachable &&
         (ms->mi_Index != 0  /* We have a task running */
         || ms->MIBver == 3)) {  /* v3 doesn't know about tasks */

      if (ms->append_flowdata) {
         if (stat(ms->flowdatafile,&statbuf) != 0)  /* File doesn't exist */
            open_datafile(ms);
         if ((ms->flows = fopen(ms->flowdatafile,"a")) == 0) {
	    fprintf(stderr, "failed to open '%s' for appending:%s\n",
	       ms->flowdatafile, strerror(errno));
	    log_msg( LOG_ERR, TRUE, "failed to open '%s' for appending:%s\n",
	       ms->flowdatafile, strerror(errno));
	    }
         }
      if (ms->trace_interval == 0) {  /* Normal 'live' meter */
         printfd(ms, "#Time: %s %s Flows from %lu to %lu\n",
	    ts,ms->name, ms->OurLastCollectTime,ms->uptime);
         }
      else {  /* Meter reading tracefile */
         if (ms->trt == 0) {  /* Can't get packet timestamps */
            crl_h = (ms->uptime+50)/100;  /* Round to seconds */
            crl_s = crl_h%60;  crl_h /= 60;
            crl_m = crl_h%60;  crl_h /= 60;
            printfd(ms, "#Time: %02d:%02d:%02d", crl_h,crl_m,crl_s);
            }
         else {
            ts = fmt_time(&ms->trt);
            printfd(ms, "#Time: %s", ts);
            }
         printfd(ms, " in tracefile Flows from %lu to %lu\n",
            ms->OurLastCollectTime,ms->uptime);
         }

      if (ms->restarting) {
         printfd(ms, "#Ruleset: %d  %s %s  %s\n", ms->c_ruleset.ri_Index, 
	    ms->c_ruleset.ri_Name, ms->c_ruleset.filename, ms->owner_name);
         if (ms->s_ruleset.ri_Index != 0)
            printfd(ms, "#Ruleset: %d  %s %s  %s\n", ms->s_ruleset.ri_Index, 
               ms->s_ruleset.ri_Name, ms->s_ruleset.filename, ms->owner_name);
         ms->restarting = 0;
         }

      if (ms->statsreqd) {
         if (ms->StatsTime != 0) {
            aps = (ms->NbrPackets*10+5L)/(ms->StatsTime*10L);
            apb = (ms->TotPktBacklog*10+5L)/(ms->StatsTime*10L);
            }
         else {
            aps = apb = 0;
            }
         printfd(ms,"#Stats: aps=%lu apb=%lu mps=%lu mpb=%lu lsp=%lu", 
            aps,apb, ms->MaxPktRate,ms->MaxPktBacklog, ms->LostPackets);
         printfd(ms," avi=%u.%u mni=%u.%u",
            ms->AvIdle1000/10,ms->AvIdle1000%10,
            ms->MinIdle1000/10,ms->MinIdle1000%10);
         if (ms->NbrPackets != 0)
            i = (ms->RuleMatches*100L+5L)/(ms->NbrPackets*10L);
         else i = 0;
         printfd(ms," fiu=%u frc=%lu gci=%u rpp=%u.%u",
            ms->NbrFlows,ms->FlowsRecovered,ms->GCInterval, i/10,i%10);
         if (ms->NbrPackets != 0)
            i = (ms->HashSearches*100L+5L)/(ms->NbrPackets*10L);
         else i = 0;
         if (ms->HashSearches != 0) {
            j = (ms->HashCompares*100L+5L)/(ms->HashSearches*10L);
            }
         else j = 0;
         printfd(ms," tpp=%u.%u cpt=%u.%u tts=%u tsu=%u\n",
	    i/10,i%10, j/10,j%10, ms->TotalHashSize,ms->NbrHashEntries);
         }

      if (ms->format[0] != 0) {  /* Collect flow data */
         activeflows = 0;
         cms = ms;  /* Tell process_row about this meter */

         if (ms->MIBver == 4) {
            if (ms->c_ruleset.ri_FlowRecords) {
               get_package_info(ms, ms->c_ruleset.ri_Index,
                  ms->OurLastCollectTime, process_row);
	       }
            if (ms->s_ruleset.ri_Index != 0) {
               if (ms->s_ruleset.ri_FlowRecords) {
                  get_package_info(ms, ms->s_ruleset.ri_Index,
                     ms->OurLastCollectTime, process_row);
	          }
               }

            if (ms->mi_RunningStandby && ms->download_level != 2) {
               j = ms->HighWaterMark - 5;
               if (j < 0) j = 0;
               if (ms->ActivePercent <= j)  /* About 5% below HighWaterMark */
                  task_util(ms, TU_NOSTAND);  /* Switch back to 'current' */
	       }
	    }
         else {  /* Version 3: can't tell whether running standby */
            get_colblob_data(ms,  /* Collects data for ALL rulesets */
               ms->c_ruleset.ri_Index, ms->OurLastCollectTime, process_row);
	    }
         }

      printfd(ms, "#EndData: %s\n", ms->name);

      flush_flows(ms);

      if (verbose) fprintf(stderr, 
         "%s %s: %d active flows from %lu to %lu\n",
         ts,ms->name, activeflows, ms->OurLastCollectTime,ms->uptime);

      ms->OurLastCollectTime = ms->uptime - 1L;
         /* -1 to make sure we don't miss any flows.  We may
            collect some of them twice, but we don't mind that */
      }

   if (testing) fprintf(stderr, "Sample: t=%u",ms->next_event);
   do {
      if (ms->keepalive_interval == 0) {  /* No keepalives */
         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;
         }
      else {
         ms->next_keepalive = ms->next_sample + ms->keepalive_interval;
         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_keepalive < ms->next_sample ?
            ms->next_keepalive : ms->next_sample;
         }
      time(&t);  /* Wall-clock time now */
      } while (ms->next_event < t);  /* Ignore missed collections */
   if (testing)
      fprintf(stderr, ", sync=%d, next_sample=%u, next_keepalive=%u\n",
         ms->synchronised, ms->next_sample, ms->next_keepalive);
   }

void meter_print(FILE *f,struct meter_status *ms)
{
   time_t t;  char tsbuf[32];
   struct timeval tv;
   char buf[32];

   if (!verbose) return;

   time(&t);  strcpy(tsbuf,fmt_time(&t));
   gettimeofday(&tv, (struct timezone *)0);
   tv.tv_sec -= ms->uptime / 100;
   if ((ms->uptime % 100)*10000 > tv.tv_usec) {
      tv.tv_sec--;
      tv.tv_usec += 1000000;
      }
   tv.tv_usec -= (ms->uptime % 100)*10000;
   if (f) {
      fprintf(f,"%s -- %s: %s\n\tUp %s (since %s)\n",
	     tsbuf,ms->name, ms->descr, uptime_string(ms->uptime, buf),
	     fmt_time((time_t *)&tv.tv_sec));
      }
   else {
      log_msg(LOG_INFO, FALSE, "%s -- %s: %s\n\tUp %s (since %s)\n",
	     tsbuf,ms->name, ms->descr, uptime_string(ms->uptime, buf),
	     fmt_time((time_t *)&tv.tv_sec));
      }
   }
