/*
  $Source: /local/data/cvs/yellowbank/postgres/postnet/src/iplog.c,v $
  $Revision: 1.17 $
  $State: Exp $
  $Date: 2007/03/06 01:04:34 $
  $Author: yrp001 $
  $Locker:  $

  Copyright (c) 2007
  Ronald Peterson
  (Y) Yellowbank
  All rights reserved.  Applicable BSD license terms can be found in
  the associated LICENSE file.

  Promiscuously monitor an interface for traffic traversing a
  specified gateway.  Collect aggregate statistics about the monitored
  packets, and dump those statistics at a specified interval into a
  database.
*/

#define __USE_GNU
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

// headers required to dissassemble packets.
// from tcpdump sources
#include "tcpdump-stdinc.h"
#include "ether.h"
#include "ip.h"
#include "ipproto.h"
#include "tcp.h"
#include "udp.h"

// Packet capture library
#include <pcap.h>

// Command line argument processing
#include <getopt.h>

// hash table functions
#include <glib-1.2/glib.h>

// PostgreSQL Client Library
#include <libpq-fe.h>

// pthreads
#include <pthread.h>

// time functions
#include <time.h>

#define MAXDBLEN 32
#define MAXPASSLEN 32
#define MAXHOSTLEN 32
#define MAXUSERLEN 32
#define MAXPORTLEN 5
#define MAXIFLEN 4
#define MAXIPLEN 15
#define MAXMACLEN 17
#define MAXKEYLEN 40
#define MAX_THREADS 4
#define CONSTRLEN 512
#define SQLSTRLEN 512

// how many packets should we capture per loop before we check whether
// we have exceeded our specified capture interval?
#define CAPTURE_COUNT 10000

pthread_mutex_t mutexa = PTHREAD_MUTEX_INITIALIZER;

typedef unsigned long int ul_int;
typedef unsigned int u_int;

// The data we want to log.  This will be keyed on
// srcip+dstip+ipproto+dstport
typedef struct ld {
// key - only need to log corresponding data for first packet
      char logged[32];
      char source_ip[16];
      char destination_ip[16];
      char ip_protocol[5]; // TCP, UDP, ICMP, or ?
      u_int destination_port;
// data
      u_int interval; // seconds
// aggregate data
      ul_int packet_count;
      ul_int ethernet_caplen;
      ul_int ethernet_len;
      ul_int ip_total_size;   // all the bits
      ul_int ip_data_size;    // includes protocol overhead
      ul_int payload_size;    // actual user data
} log_data;


// arguments to thread process(es) function
typedef struct htd {
      GHashTable *hashtable;
      char connect_string[CONSTRLEN];
      int in_thread;
      int dump_to_db;
      int db_dump_in_transaction;
} dump_args;

// arguments to g_hash_table_foreach function to update
// timestamp data
typedef struct tp {
      char timestamp[32];  // ISO GMT time
      ul_int log_interval; // seconds
} time_data;


// arguments to g_hash_table_foreach function to write
// data to database
typedef struct cd {
      PGconn *connection;
      int transaction_ok;  // 0:not ok, 1:ok
      int in_transaction;  // are we in a begin/commit block?
} pgconnect;

// Marvel at C's string-fu power
static
char*
append_to_string( char *s, const char* const t ) {
   int len;
   if( s ) {
      len = strlen( s );
      len += strlen( t );
      len += 1;
      realloc( s, len );
      return strncat( s, t, strlen( t ) + 1 );
   } else {
      s = malloc( strlen( t ) + 1 );
      strncpy( s, t, strlen( t ) + 1 );
   }
   return s;
}

static
int
valid_dbname( const char* const dbname ) {
   // not done yet
   if( strlen( dbname ) <= MAXDBLEN ) {
      return 1;
   }
   return 0;
}

static
int
valid_username( const char* const username ) {
   // not done yet
   if( strlen( username ) < MAXUSERLEN ) {
      return 1;
   }
   return 0;
}

static
int
valid_host( const char* const host ) {
   // not done yet
   if( strlen( host ) <= MAXHOSTLEN ) {
      return 1;
   }
   return 0;
}

static
int
valid_port( const char* const port ) {
   // not done yet
   if( strlen( port ) <= MAXPORTLEN ) {
      return 1;
   }
   return 0;
}

static
int
valid_interface( const char* const interface ) {
   // not done yet
   if( strlen( interface ) <= MAXIFLEN ) {
      return 1;
   }
   return 0;
}

static
int
valid_ip( const char* const ip ) {
   int i, j, count;
   char c;
   j=count=0;
   char pattern[8];
   if( strlen( ip ) >= MAXIPLEN ) {
      return 0;
   }
   for( i = 0; i < strlen(ip); i++ ) {
      c = '0';
      if( '0' <= ip[i] && ip[i] <= '9' ) {
         if( ++count > 3 ) {
            return 0;
         }
         c = 'd';
      }
      if( ip[i] == '.' ) {
         count = 0;
         c = '.';
      }
      if( c == '0' ) {
         return 0;
      }
      if( (j > 6) && (c != 'd') ) {
         return 0;
      }
      if( ((j % 2) == 0) && (c == 'd') ) {
         // first digit or last character was a '.'
         pattern[j++] = c;
      }
      if( ((j % 2) != 0) && (c == '.') ) {
         // last character was a digit
         pattern[j++] = c;
      }
   }
   memset( pattern+7, '\0', 1 );
   if( strcmp( pattern, "d.d.d.d" ) == 0 ) {
      return 1;
   }
   return 0;
}

static
int
valid_mac( const char* const mac ) {
   // not done yet
   if( strlen( mac ) <= MAXMACLEN ) {
      return 1;
   }
   return 0;
}

/*
static
int
valid_count( const char* const count ) {
   // not done yet
   return 1;
}
*/

static
int
valid_interval( const char* const interval ) {
   long int i;
   if( strlen( interval ) > 7 ) {
      return 0;
   }
   i = atoi( interval );
   if( 2 <= i && i <= 30 ) {
      // between 2 and 30 minutes
      return 1;
   }
   return 0;
}


static
int
valid_password( const char* const password ) {
   // not done yet
   if( strlen( password ) <= MAXPASSLEN ) {
      return 1;
   }
   return 0;
}

static
void
pg_exit_nicely( PGconn *connection ) {
   PQfinish( connection );
   exit(1);
}

static
PGconn
*db_connect( const char *connect_string ) {
   PGconn *connection;
   connection = PQconnectdb( connect_string );
   if( PQstatus( connection ) != CONNECTION_OK )
   {
      fprintf(stderr, "Connection to database failed: %s",
              PQerrorMessage( connection ));
      pg_exit_nicely( connection );
   }
   return( connection );
}

static
void
db_disconnect( PGconn *connection ) {
   PQfinish( connection );
}

static
void
db_sql_exec( pgconnect *pgc, const char *sql ) {
   PGresult *result;
   result = PQexec( pgc->connection, sql );
   if( PQresultStatus( result ) != PGRES_COMMAND_OK )
   {
      PQclear( result );
      // We don't close the connection on failure,
      // but we mark the transaction bad.
      pgc->transaction_ok = 0;
      fprintf( stderr, "sql failure: %s\n", sql );
      fprintf( stderr,
               "sql failed: %s\n",
               PQerrorMessage( pgc->connection ) );
      return;
   }
   PQclear( result );
//   fprintf( stdout, "-- sql success: %s\n", sql );
}

static
void
free_key_value( gpointer key, gpointer value, gpointer user_data )
{
   g_free( key );
   g_free( value );
}

static
int
dummy_func()
{
   return 1;
}

static
GHashTable
*hashtable_init( GHashTable *ht ) {
   return( g_hash_table_new( g_str_hash, g_str_equal ) );
}

static
log_data
*hashtable_find( GHashTable *ht, char *key ) {
   return( (log_data *)g_hash_table_lookup( ht, key ) );
}

static
void
hashtable_insert( GHashTable *ht, char *key, log_data *data ) {
   // the hash table will now 'own' the key and data, and
   // will become responsible for deleting them.
   g_hash_table_insert( ht, g_strdup( key ), (gpointer)data );
}

// It would be more efficient to pass this data as user data to the
// log function.
static
void
set_time_data( gpointer key, gpointer data_p, gpointer user_data ) {
   time_data *tp;
   log_data *dp;
   tp = (time_data *)user_data;
   dp = (log_data *)data_p;
   strncpy( dp->logged, tp->timestamp, 32 );
   dp->interval = tp->log_interval;
}

/*
static
void
print_key_value( gpointer key, gpointer data_p, gpointer user_data ) {
   log_data *dp = (log_data *)data_p;
   fprintf( stdout, "\n--    key: %s\n", (char *)key );
   fprintf( stdout, "--  logged: %s\n", dp->logged );
   fprintf( stdout, "--interval: %d\n", dp->interval );
   fprintf( stdout, "--  src IP: %s\n", dp->source_ip );
   fprintf( stdout, "--  dst IP: %s\n", dp->destination_ip );
   fprintf( stdout, "--   proto: %s\n", dp->ip_protocol );
   fprintf( stdout, "--   dport: %d\n", dp->destination_port );
   fprintf( stdout, "--   count: %ld\n", dp->packet_count );
   fprintf( stdout, "-- ecaplen: %ld\n", dp->ethernet_caplen );
   fprintf( stdout, "--    elen: %ld\n", dp->ethernet_len );
   fprintf( stdout, "--  ipsize: %ld\n", dp->ip_total_size );
   fprintf( stdout, "--  ipdata: %ld\n", dp->ip_data_size );
   fprintf( stdout, "-- payload: %ld\n", dp->payload_size );
}
*/

void
create_log_insert( char *sql, log_data *dp ) {
      snprintf( sql,
                500,
                "INSERT INTO iplog ( logged, source_ip, destination_ip, ip_protocol, destination_port, timespan, packet_count, ethernet_caplen, ethernet_len, ip_total_size, ip_data_size, payload_size ) VALUES ( '%s', '%s', '%s', '%s', %d, '%d sec'::interval, %ld, %ld, %ld, %ld, %ld, %ld );",
                dp->logged,
                dp->source_ip,
                dp->destination_ip,
                dp->ip_protocol,
                dp->destination_port,
                dp->interval,
                dp->packet_count,
                dp->ethernet_caplen,
                dp->ethernet_len,
                dp->ip_total_size,
                dp->ip_data_size,
                dp->payload_size );
}

static
void
db_log_key_value( gpointer key, gpointer data_p, gpointer user_data ) {
   pgconnect *pgc;
   char sql[SQLSTRLEN];
   log_data *dp = (log_data *)data_p;

   pgc = (pgconnect *)user_data;
   if( pgc->in_transaction ) {
      // are we using a begin/end transaction block?
      if( pgc->transaction_ok ) {
         // don't waste time if the transaction has gone sour
         create_log_insert( sql, dp );
         db_sql_exec( pgc, sql );
      }
   } else {
      create_log_insert( sql, dp );
      db_sql_exec( pgc, sql );
   }
}

static
void
file_log_sql( gpointer key, gpointer data_p, gpointer user_data ) {
   char sql[SQLSTRLEN];
   FILE *fp;
   fp = (FILE *)user_data;
   create_log_insert( sql, (log_data *)data_p );
   fprintf( fp, "%s\n", sql );
}

/*
static
void
hashtable_log_all( GHashTable *ht, const char *connect_string ) {
   PGconn *connection;
   connection = db_connect( connect_string );
   db_sql_exec( connection, "BEGIN;" );
   g_hash_table_foreach( ht, db_log_key_value, (gpointer)connection );
   db_sql_exec( connection, "COMMIT;" );
   db_disconnect( connection );
}
*/

// The function we run in separate pthread so that we don't block
// our main process from continuing to collect data.  Write data
// to database and then free resources.
static
void
*hashtable_log_and_destroy( void *args ) {
   GHashTable *hashtable;
   char *connect_string;
   pgconnect pgconn_stat;
   PGresult *result;
   FILE *fp;
   dump_args *dump_proc_args;

   dump_proc_args = (dump_args *)args;

   if( dump_proc_args->in_thread ) {
      // pthread_mutex_lock( &mutex );
   }

   hashtable = dump_proc_args->hashtable;
   connect_string = dump_proc_args->connect_string;

//   fprintf( stderr, "dump: Dumping %d records...\n", g_hash_table_size( hashtable ) );

   if( dump_proc_args->dump_to_db ) {
      // database dump
      pgconn_stat.connection = db_connect( connect_string );
      pgconn_stat.transaction_ok = 1;
      if( dump_proc_args->db_dump_in_transaction ) {
         // use a begin/commit block
         pgconn_stat.in_transaction = 1;
         result = PQexec( pgconn_stat.connection, "BEGIN;" );
         if( PQresultStatus( result ) == PGRES_COMMAND_OK ) {
            // all the inserts happen here
            PQclear( result );
            g_hash_table_foreach( hashtable, db_log_key_value, (gpointer)&pgconn_stat );
            fprintf( stdout, "-- dump: done.\n" );
            if( pgconn_stat.transaction_ok ) {
               result = PQexec( pgconn_stat.connection, "COMMIT;" );
               PQclear( result );
            } else {
               result = PQexec( pgconn_stat.connection, "ROLLBACK;" );
               PQclear( result );
            }
         } else {
            PQclear( result );
            fprintf( stderr,
                     "dump: sql failed: %s",
                     PQerrorMessage( pgconn_stat.connection ) );
            result = PQexec( pgconn_stat.connection, "ROLLBACK;" );
            PQclear( result );
         }
      } else {
         pgconn_stat.in_transaction = 0;
         g_hash_table_foreach( hashtable, db_log_key_value, (gpointer)&pgconn_stat );
      }
      if( pgconn_stat.connection ) {
         db_disconnect( pgconn_stat.connection );
         pgconn_stat.connection = NULL;
      }
   } else {
      // text dump
      fp = fopen( "/root/tmp/iplog.insert.sql", "a" );
      fprintf( fp, "-- dumping %d records...\n", g_hash_table_size( hashtable ) );
      g_hash_table_foreach( hashtable, file_log_sql, (gpointer)fp );
      fprintf( fp, "-- dump: done.\n" );
      fclose( fp );
   }

   // cleanup
//   fprintf( stderr, "starting cleanup..." );
   g_hash_table_foreach( hashtable, free_key_value, NULL );
   
   if( dump_proc_args->in_thread ) {
      // don't free hash table unless in pthread   
      g_hash_table_destroy( hashtable );
   
      // this was dynamically allocated and then given to us to
      // deal with...
      free( dump_proc_args );

//      fprintf( stderr, " thread cleanup complete\n" );
      return NULL;
      // pthread_exit( NULL );
      // pthread_mutex_unlock( &mutex );
   } else {
//      fprintf( stderr, " cleanup complete\n" );
   }
   return NULL;
}

/*
static
void
hashtable_print_all( GHashTable *ht ) {
   g_hash_table_foreach( ht, print_key_value, NULL );
}
*/

/*
static
void
hashtable_destroy( GHashTable *ht ) {
   g_hash_table_foreach( ht, free_key_value, NULL );
   g_hash_table_destroy( ht );
}
*/

// If you call this function a million times, you win a million dollars.
static
void
process_packet( u_char *args,
                const struct pcap_pkthdr *header,
                const u_char *packet )
{
   static int count = 1;                   /* packet counter */

   const struct ether_header *ethernet;    /* The ethernet header [1] */
   const struct ip *ip;                    /* The IP header */
   const struct tcphdr *tcp;               /* The TCP header */
   const struct udphdr *udp;               /* The UDP header */

   int size_ip = 0;
   int size_ip_header;
   int size_ip_data = 0;
   int size_tcp;
   int size_tcp_header;
   int size_udp;
   int size_udp_header;
   u_int offset;

   char source_ip[16];
   char destination_ip[16];
   char ip_protocol[5];
   uint16_t dport = 0;
   char destination_port[6];
   ul_int payload_size = 0;

   GHashTable *hashtable;
   char key[50];
   log_data *data = NULL;

   bzero( source_ip, 16 );
   bzero( destination_ip, 16 );
   bzero( ip_protocol, 5 );
   bzero( destination_port, 6 );
   bzero( key, 50 );

   hashtable = (GHashTable *)(args);

   size_udp_header = 8;

   ethernet = (struct ether_header *)(packet);
   ip = (struct ip *)(packet + ETHER_HDRLEN);
   size_ip_header = IP_HL(ip) * 4;
   if (size_ip_header < 20) {
//      fprintf( stderr, "Error: Invalid IP header length: %u bytes\n", size_ip_header);
      return;
   }

   strncpy( source_ip, inet_ntoa( ip->ip_src ), 16 );
   strncpy( destination_ip, inet_ntoa( ip->ip_dst ), 16 );

   offset = ntohs(ip->ip_off);
   // if this is the first fragment (no 1's in first 13 bits)
   if( (offset & 0x1fff) == 0 ) {
      size_ip = ntohs(ip->ip_len);
      if( size_ip_header > size_ip ) {
//         fprintf( stderr, "Error: IP header larger than IP size\n" );
         return;
         // exit( EXIT_FAILURE );
      }
      size_ip_data = size_ip - size_ip_header;
   } else {
      if( (offset & 0x2000) != 0 ) {
//         fprintf( stdout, "-- last fragment" );
      }
   }

   switch(ip->ip_p) {
      case IPPROTO_TCP:
         strncpy( ip_protocol, "TCP", 4 );
         tcp = (struct tcphdr*)(packet + ETHER_HDRLEN + size_ip_header);
         size_tcp_header = TH_OFF(tcp) * 4;
         if( size_tcp_header < 20 ) {
//            fprintf( stderr, "Error: Invalid TCP header length: %u bytes\n", size_tcp_header );
            return;
            // exit( EXIT_FAILURE );
         }
         size_tcp = size_ip - size_ip_header;
         if( size_tcp_header > size_tcp ) {
//            fprintf( stderr, "Error: TCP header larger than TCP total\n" );
            return;
            // exit( EXIT_FAILURE );
         }
         payload_size = size_tcp - size_tcp_header;
         dport = ntohs(tcp->th_dport);
         break;
      case IPPROTO_UDP:
         strncpy( ip_protocol, "UDP", 4 );
         udp = (struct udphdr*)(packet + ETHER_HDRLEN + size_ip_header);
         dport = ntohs(udp->uh_dport);
         size_udp = ntohs(udp->uh_ulen);
         if( size_udp_header > size_udp ) {
//            fprintf( stderr, "Error: UDP header larger than UDP size\n" );
            return;
            // exit( EXIT_FAILURE );
         }
         payload_size = size_udp - size_udp_header;
         break;
      case IPPROTO_ICMP:
         strncpy( ip_protocol, "ICMP", 5 );
         break;
      case IPPROTO_IP:
         strncpy( ip_protocol, "IP", 3 );
         break;
      default:
         strncpy( ip_protocol, "?", 2 );
         break;
   }

   snprintf( destination_port, 6, "%d", dport );

   if( ! (source_ip &&
          destination_ip &&
          ip_protocol &&
          destination_port) ) {
      return;
   }

   if( (! valid_ip(source_ip)) || (! valid_ip(destination_ip)) ) {
      return;
   }

   strncpy( key, source_ip, strlen( source_ip ) );
   strncat( key, ",", 1 );
   strncat( key, destination_ip, strlen( destination_ip ) );
   strncat( key, ip_protocol, strlen( ip_protocol ) );
   strncat( key, destination_port, strlen( destination_port ) );

   data = hashtable_find( hashtable, key );

   if( data ) {
      // already exists
   } else {
      // new entry
      data = malloc( sizeof( log_data ) );
      hashtable_insert( hashtable, key, data );
      strncpy( data->source_ip, source_ip, 16 );
      strncpy( data->destination_ip, destination_ip, 16 );
      strncpy( data->ip_protocol, ip_protocol, 5 );
      data->destination_port = dport;
      data->packet_count = 0;
      data->ethernet_caplen = 0;
      data->ethernet_len = 0;
      data->ip_total_size = 0;
      data->ip_data_size = 0;
      data->payload_size = 0;
   }
   data->packet_count += 1;
   data->ethernet_caplen += header->caplen;
   data->ethernet_len += header->len;
   data->ip_total_size += size_ip;
   data->ip_data_size += size_ip_data;
   data->payload_size += payload_size;

   count++;
}

// data dump runs as separate thread so as not to block
// ongoing data collection.
static
void
dump_data_in_thread( dump_args *args ) {
   pthread_t *logger;
   logger = (pthread_t *)malloc( sizeof(pthread_t) );
   fprintf( stderr, "Creating new thread.\n" );
   pthread_create( logger,
                   NULL,
                   hashtable_log_and_destroy,
                   (void *)args );
   pthread_detach( *logger );
}

/*
// testing.  just dump data.
static
void
dump_data_no_thread( dump_args *args ) {
   hashtable_log_and_destroy( (void *)args );
}
*/

static
void
ip_capture( const char* const dev,
            const char* const interval,
            const char* const gatewayip,
            const char* const gatewaymac,
            const char* const connect_string ) {
   pcap_t *handle;
   char errbuf[PCAP_ERRBUF_SIZE];
   struct bpf_program fp;
   bpf_u_int32 mask;
   bpf_u_int32 net;
   char* filter;
   int capture_interval;
   dump_args *dump_proc_args;
   time_t start, end;
   int elapsed;
   int ticking = 0;
   time_data log_time;
   struct tm *ltime;
   pid_t ppid;
   int ti; // thread index
   pthread_t threads[MAX_THREADS];

   int io_in_thread = 2; // do io in separate thread? 0:no 1:thread 2:fork
   int dump_to_db = 1;   // dump to database or file?
   int db_dump_in_transaction = 1; // wrap database dump in transction?

   capture_interval = atoi( interval ) * 60;

   // Capture IP packets traversing the specified gateway.
   // E.g. "ip and ether host 00:04:c0:d3:24:70 and not host 138.110.100.1"
   //
   filter = NULL;
   filter = append_to_string( filter, "ip and ether host " );
   filter = append_to_string( filter, gatewaymac );
   filter = append_to_string( filter, " and not host " );
   filter = append_to_string( filter, gatewayip );

   if( pcap_lookupnet( dev, &net, &mask, errbuf ) == -1 ) {
      fprintf(stderr, "Couldn't get netmask for device %s: %s\n",
              dev,
              errbuf);
      net = 0;
      mask = 0;
//      exit( EXIT_FAILURE );
   }

/*
   handle = pcap_open_live( dev, BUFSIZ, 1, 1000, errbuf );
   if (handle == NULL) {
      fprintf( stderr, "Error: couldn't open device %s: %s\n",
               dev,
               errbuf);
      exit( EXIT_FAILURE );
   }

   if (pcap_datalink(handle) != DLT_EN10MB) {
      fprintf( stderr, "Error: %s is not an ethernet\n", dev);
      exit( EXIT_FAILURE );
   }

   if( pcap_compile( handle, &fp, filter, 0, net ) == -1 ) {
      fprintf( stderr, "Error: couldn't parse filter %s: %s\n",
               filter,
               pcap_geterr( handle ) );
      exit( EXIT_FAILURE );
   }

   if( pcap_setfilter( handle, &fp ) == -1 ) {
      fprintf( stderr, "Error: couldn't install filter %s: %s\n",
               filter,
               pcap_geterr( handle ) );
      exit( EXIT_FAILURE );
   }

   dump_proc_args = (dump_args *)malloc( sizeof(dump_args) );
   strncpy( dump_proc_args->connect_string, connect_string, CONSTRLEN );
   dump_proc_args->hashtable = NULL;
   dump_proc_args->hashtable = hashtable_init( dump_proc_args->hashtable );
   pcap_loop( handle, c, process_packet, (unsigned char *)dump_proc_args->hashtable );
   dump_data_no_thread( dump_proc_args );

   pcap_freecode( &fp );
   pcap_close( handle );
*/

   handle = pcap_open_live( dev, BUFSIZ, 1, 1000, errbuf );
   if (handle == NULL) {
      fprintf( stderr, "Error: couldn't open device %s: %s\n",
               dev,
               errbuf);
      exit( EXIT_FAILURE );
   }

   if (pcap_datalink(handle) != DLT_EN10MB) {
      fprintf( stderr, "Error: %s is not an ethernet\n", dev);
      exit( EXIT_FAILURE );
   }

   if( pcap_compile( handle, &fp, filter, 0, net ) == -1 ) {
      fprintf( stderr, "Error: couldn't parse filter %s: %s\n",
               filter,
               pcap_geterr( handle ) );
      exit( EXIT_FAILURE );
   }

   if( pcap_setfilter( handle, &fp ) == -1 ) {
      fprintf( stderr, "Error: couldn't install filter %s: %s\n",
               filter,
               pcap_geterr( handle ) );
      exit( EXIT_FAILURE );
   }


   if( (io_in_thread == 0) || (io_in_thread == 2) ) {
      // no threaded or forked io
      dump_proc_args = (dump_args *)malloc( sizeof(dump_args) );
      dump_proc_args->in_thread = io_in_thread;
      dump_proc_args->dump_to_db = dump_to_db;
      dump_proc_args->db_dump_in_transaction = db_dump_in_transaction;
      dump_proc_args->hashtable = NULL;
      dump_proc_args->hashtable = hashtable_init( dump_proc_args->hashtable );
      bzero( dump_proc_args->connect_string, CONSTRLEN );
      strncpy( dump_proc_args->connect_string, connect_string, CONSTRLEN );
   }

   if( io_in_thread == 2 ) {
      // don't care what happens to children
      signal(SIGCLD, SIG_IGN);
   }

   // are we going to dump to a file or to a database?


   while( 1 ) {
      if( ! ticking ) {
         start = time(NULL);
         ticking = 1;
         if( io_in_thread == 1 ) {
            dump_proc_args = (dump_args *)malloc( sizeof(dump_args) );
            dump_proc_args->in_thread = 1;
            dump_proc_args->dump_to_db = dump_to_db;
            dump_proc_args->db_dump_in_transaction = db_dump_in_transaction;
            dump_proc_args->hashtable = NULL;
            dump_proc_args->hashtable = hashtable_init( dump_proc_args->hashtable );
            bzero( dump_proc_args->connect_string, CONSTRLEN );
            strncpy( dump_proc_args->connect_string, connect_string, CONSTRLEN );
         } else {
            if( g_hash_table_size( dump_proc_args->hashtable ) > 0 ) {
               g_hash_table_foreach( dump_proc_args->hashtable, free_key_value, NULL );
               g_hash_table_foreach_remove( dump_proc_args->hashtable, dummy_func, NULL );
            }
         }
      }

      pcap_loop( handle, CAPTURE_COUNT, process_packet, (unsigned char *)dump_proc_args->hashtable );

      end = time(NULL);
      elapsed = end - start;

      if( elapsed >= capture_interval ) {
         // start a new data collection loop
         ticking = 0;

         // set timestamp info
         ltime = localtime( &end );
         strftime( log_time.timestamp, 32, "%F %T", ltime );
         log_time.log_interval = (ul_int)elapsed;
         g_hash_table_foreach( dump_proc_args->hashtable, set_time_data, (void *)&log_time );

         // Spawn an indepent thread to dump data.  Thread is responsible
         // for cleanup.  The whole idea of this program is that if we
         // aggregate data long enough, then on average we can dump the
         // results to a database in less time than it takes to accumlate
         // them.
         if( dump_proc_args->in_thread == 0 ) {
            // block data collection until we're done logging
            hashtable_log_and_destroy( dump_proc_args );
         } else
         if( dump_proc_args->in_thread == 1 ) {
            // run log process in its own thread
            ti = 0;
            while( pthread_create( &threads[ti],
                                   NULL,
                                   hashtable_log_and_destroy,
                                   (void *)dump_proc_args ) ) {
               // loop until we find a free thread
               ti = (ti + 1) % 4;
            }
            pthread_detach( threads[ti] );
            // dump_data_in_thread( dump_proc_args );
         } else 
         if( dump_proc_args->in_thread == 2 ) {
            // run log process as a child process
            ppid = fork ();
            if (ppid < 0) {
               fprintf( stderr, "fork bombed\n" );
               exit( 1 );
            } else if (ppid == 0) {
               // child
               hashtable_log_and_destroy( dump_proc_args );
            } else {
               // we could keep track of child ppid's, but so
               // far we have no need for them...
               continue;
            }
         }
      }
   }

   // never get here...

   if( ! dump_proc_args->in_thread ) {
      free( dump_proc_args );
   }

   pcap_freecode( &fp );
   pcap_close( handle );

   free( filter );

   printf( "\nCapture complete.\n" );
}

static
char*
malloc_option_arg( const char* optname,
                   const char* const arg,
                   int(*valfun)(const char*) ) {
   char* ret;
   ret = NULL;
   if( (*valfun)( arg ) ) {
      ret = (char*)malloc( strlen( arg ) + 1);
      if( ! ret ) {
         fprintf( stderr, "Out of memory error allocating option\n" );
         exit( EXIT_FAILURE );
      }
      strcpy( ret, arg );
   } else {
      fprintf( stderr, "Error: invalid argument for %s\n", optname );
      exit( EXIT_FAILURE );
   }
   return( ret );
}

static
void
print_help () {
   fprintf( stderr, "iplog -d dbname -u username\n" );
   fprintf( stderr, "      -h host [ -p port ]\n" );
   fprintf( stderr, "      -i interface -g gatewayip\n" );
   fprintf( stderr, "      -m gatewaymac -v interval (in minutes)\n" );
   fprintf( stderr, "      [ -t ident ] [ -? help ]\n" );
   fprintf( stderr, "\n" );
   fprintf( stderr, "iplog expects the database password on STDIN.\n" );
   fprintf( stderr, "\n" );
   fprintf( stderr, "Someday there will be a real help page.\n" );
}

int
main( int argc, char** argv ) {
   int c;
   int option_index = 0;
   unsigned int initial_passlen;
   int ident;
   ssize_t passlen;
   char connect_string[CONSTRLEN];

   char *dbname, *username, *password, *host, *hostaddr, *port, *interface, *gatewaymac, *gatewayip, *interval;

   static struct option long_options[] = {
      { "dbname", required_argument, NULL, 'd' },
      { "username", required_argument, NULL, 'u' },
      { "host", required_argument, NULL, 'h' },
      { "hostaddr", required_argument, NULL, 'a' },
      { "port", required_argument, NULL, 'p' },
      { "interface", required_argument, NULL, 'i' },
      { "gatewayip", required_argument, NULL, 'g' },
      { "gatewaymac", required_argument, NULL, 'm' },
      { "interval", required_argument, NULL, 'v' },
      { "ident", no_argument, NULL, 't' },
      { "help", no_argument, NULL, 'H' },
      { 0, 0, 0, 0 }
   };

   ident = 0;
   initial_passlen = 32;

   dbname = NULL;
   username = NULL;
   password = NULL;
   host = NULL;
   hostaddr = NULL;
   port = NULL;
   interface = NULL;
   gatewayip = NULL;
   gatewaymac = NULL;
   interval = NULL;

   while( (c = getopt_long( argc, argv, "d:u:h:a:p:i:g:m:v:tH", long_options, &option_index )) != -1 ) {

      switch( c ) {
         case 'd':
            dbname = malloc_option_arg( "--dbname", optarg, valid_dbname );
            break;
         case 'u':
            username = malloc_option_arg( "--username", optarg, valid_username );
            break;
         case 'a':
            hostaddr = malloc_option_arg( "--hostaddr", optarg, valid_ip );
            break;
         case 'h':
            host = malloc_option_arg( "--host", optarg, valid_host );
            break;
         case 'p':
            port = malloc_option_arg( "--port", optarg, valid_port );
            break;
         case 'i':
            interface = malloc_option_arg( "--interface", optarg, valid_interface );
            break;
         case 'g':
            gatewayip = malloc_option_arg( "--gatewayip", optarg, valid_ip );
            break;
         case 'm':
            gatewaymac = malloc_option_arg( "--gatewaymac", optarg, valid_mac );
            break;
         case 'v':
            interval = malloc_option_arg( "--interval", optarg, valid_interval );
            break;
         case 't':
            ident = 1;
            break;
         case 'H':
            print_help();
            exit( 0 );
            break;
         case '?':
            print_help();
            exit( 0 );
            break;
         default:
            printf ("?? getopt returned character code 0%o ??\n", c);
      }

   }

   if( ! ident ) {
      password = malloc( initial_passlen + 1 );
      if( ! password ) {
         fprintf( stderr, "Error allocating password string\n" );
         exit( EXIT_FAILURE );
      }
      passlen = getline( &password, &initial_passlen, stdin );
      if( passlen == -1 ) {
         fprintf( stderr, "Error reading password\n" );
         exit( EXIT_FAILURE );
      } else {
         if( valid_password( password ) ) {
            // fprintf( stderr, "Password len %d: %s\n", passlen, password );
         } else {
            fprintf( stderr, "Error: invalid password\n" );
            exit( EXIT_FAILURE );
         }
      }
   }

   if (optind < argc) {
      printf ("non-option ARGV-elements: ");
      while (optind < argc)
         printf ("%s ", argv[optind++]);
      printf ("\n");
   }

   if( ! ( interval &&
           interface &&
           gatewayip &&
           gatewaymac &&
           dbname &&
           username &&
           host &&
           password ) ) {
      print_help();
      exit( EXIT_FAILURE );
   }

   // create connect string from user provided arguments
   strncpy( connect_string, "dbname=", 9 );
   strncat( connect_string, dbname, 16 );
   strncat( connect_string, " user=", 9 );
   strncat( connect_string, username, 16 );
   strncat( connect_string, " host=", 13 );
   strncat( connect_string, host, 16 );
   if( port ) {
      strncat( connect_string, " port=", 9 );
      strncat( connect_string, port, 16 );
   }
   strncat( connect_string, " password=", 14 );
   strncat( connect_string, password, 16 );

   ip_capture( interface, interval, gatewayip, gatewaymac, connect_string );

   if( username )
      free( username );
   if( dbname )
      free( username );
   if( password )
      free( password );
   if( host )
      free( host );
   if( port )
      free( port );
   if( interface )
      free( interface );
   if( gatewayip )
      free( gatewayip );
   if( gatewaymac )
      free( gatewaymac );
   if( interval )
      free( interval );

   exit( 0 );
}

