/* ialive - Program to test whether the Internet is up and active.
   Rick Smereka, Copyright (C) 2006.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Program syntax:

      ialive [-c nn] [-b] [-q] [-p ping_dest] [-l [log_file]]

   Where:

   'c' is the number of pings to send ('nn')
   'b' record stats in 'bbuuzzb' table (see below)
   'q' run quietly
   'p' host name or IP address to ping
   'l' log name or destination

   This program works by pinging a specific host (default host is used if
   no host if specified using the 'p' switch. The results can be output to
   the screen, to a log file, and/or to a database table using the 'bbuuzzb'
   database. You may output to any combination of these three destinations.
   Output to the 'bbuuzzb' database uses the table name listed in the global
   constants. The data is stored where a single day is contained within a 
   record. The field layout is as follows:
 
   field subfield subsubfield description
 
     1                        date (julian number)
     2       *         1      time (24hr, no colon)
     2       *         2      number of pings sent
     2       *         3      number of pings successfully received

   Since there is no support for 'exec' under MacOS, this program will
   not run under MacOS.

   Linux version based on original tcl program. Aug/2006, Rick Smereka */
   
#include "stdhead.h"
#include "flsocket.h"
#include "appinit.h"
#include "dbmess.h"
#include "dbiocode.h"
#include "dbcomm.h"

#ifdef MULTIUSER
#ifdef IPC_TCP
#include "socloc.h"
#include "sloc.h"
#include "slconfig.h"
#include "sliocode.h"
#ifdef OS_WIN32
WSADATA wsaData;
#endif
#endif
#endif

/* high level database API changes depending
   on whether we are compiling for stand-alone
   or multiuser */

#ifdef MULTIUSER
#include "dbcs.h"
#include "dbcscfg.h"
#else
#include "dbeng.h"
#include "dblocal.h"
#include "dbengcfg.h"
#include "dblocfg.h"
#endif

#define VERSION "1.21-2006.09.07"

#ifdef MULTIUSER
#define APNAME "ialivem"
#else
#define APNAME "ialive"
#endif

// #define DEBUG 1
#define DEFAULT_HOST "www.yahoo.com"
#define BBUUZZB_TABLE "ialive"
#define DEFAULT_LOG_FILE "ialive.log"
#define PING_OUTPUT_FILE "/work/logs/ping.log"

// database field and subsubfield definitions

#define FIELD_DATE 1
#define FIELD_PING_DATA 2
#define SUBSUBFIELD_TIME 1
#define SUBSUBFIELD_PINGS_SENT 2
#define SUBSUBFIELD_PINGS_RECV 3

// global variables

int tid;
char log_dest[128];
char ping_dest[128];
int log_output = FALSE;
int console_output = TRUE;
int ping_count = 4;
int use_bbuuzzb = FALSE;

// function prototypes

int main(int, char **);
void process(void);
void find_stats(char *);
int get_sent_recv(char *, int, int *);
int parse_comline(int, char **);
int c_initialize(void);
void app_out(char *,...);
void term_app(void);

#ifdef MULTIUSER
#ifdef IPC_TCP
int c_ipc_socloc_init(void);
#endif
#endif

int main(int argc, char **argv)
{
   char mes[128], mname[] = "main";
   int ret, lstatus;

   /* parse command line */

   if (!parse_comline(argc, argv))
      return(0);

   if (use_bbuuzzb)
      {
      // register application name with 'appinit'

      if (!appinit_register_name(APNAME))
         {
         printf("%s:fatal error registering app name with appinit\n", APNAME);
         return(0);
         }

      if (!c_initialize())
         {
         term_app();
         return(0);
         }
      }

   if (log_output)
      {
      if (logman_start(log_dest, APNAME))
         {
         printf("%s:unable to start log manager,program abort\n", APNAME);
         return(0);
         }

      logman_console(console_output);
      }

   /* build logo string based on platform */

#ifndef OS_UNIX
   /* non-Unix */

   app_out("%s for %s Version %s", APNAME, PLATFORM_STRING,
           VERSION);
#else
   /* Unix */

   app_out("%s for %s Version %s", APNAME, SUB_PLATFORM_STRING,
           VERSION);
#endif

   app_out("By Rick Smereka, Copyright (c) 2006");
   app_out("%s comes with ABSOLUTELY NO WARRANTY", APNAME);
   app_out("This is free software, and you are welcome to redistribute it");
   app_out("under certain conditions; see 'gpl.txt' for information.");

   if (logman_is_active())
      {
      app_out("%s:logging is on", APNAME);
      app_out("%s:log name/destination is '%s'", APNAME, log_dest);
      }
   else
      app_out("%s:logging is off");

   app_out("%s:ping destination is '%s'", APNAME, ping_dest);
   app_out("%s:ping count is %d", APNAME, ping_count);
   (void)flag_2_logic_string(console_output, mes);
   app_out("%s:console output is %s", APNAME, mes);
   (void)flag_2_logic_string(use_bbuuzzb, mes);
   app_out("%s:Bbuuzzb database mode is %s", APNAME, mes);

   if (use_bbuuzzb)
      app_out("%s:output Bbuuzzb table is %s", APNAME, BBUUZZB_TABLE);

   process();
   term_app();
   return(0);
}
 
void process(void)
{
   /* Execute the 'ping' command sending all program output to an
      output file. Read back the 'ping' output file to determine
      the response and possibly record the response in the
      'bbuuzzb' table. */

   char line[75], curdatime[15], curdate[9], curtime[5], mname[] = "process";
   char wrd[50];
   int ret, subfield, pings_sent, pings_recv;

   // build the command line

#ifdef OS_UNIX
   sprintf(line, "ping -c %d %s >%s", ping_count, ping_dest, PING_OUTPUT_FILE);
#else
   sprintf(line, "ping -n %d %s >%s", ping_count, ping_dest, PING_OUTPUT_FILE);
#endif

   // execute the command

   app_out("%s:command=%s", mname, line);
   (void)system(line);

   // locate the stats line in the ping output file

   find_stats(line);

   if (!strlen(line))
      {
      // if there is no output from the 'ping' command, assume process gave up
   
      app_out("%s:output 'ping' line is empty,assuming nothing received", 
              mname);

      // mock-up output line

      sprintf(line, "sent %d,recv 0", ping_count);
      }

   app_out("%s:line=%s", mname, line);

   if (!use_bbuuzzb)
      return;

   /* store sent and received stats in daily record
      get the date and time now, use the hour and minute in the record and
      convert the date to julian */

   get_time(curdatime);
   strncpy(curtime, &curdatime[8], 4);
   curtime[4] = EOS;
   strncpy(curdate, curdatime, 8);
   curdate[8] = EOS;

   if ((ret = db_open(BBUUZZB_TABLE, &tid)) != DBENG_OK)
      {
      app_out("%s:unable to open table '%s'", mname, BBUUZZB_TABLE);
      return;
      }

   // attempt to locate existing record, if not present, create new record
   
   if ((ret = db_find_field(tid, 0, curdate, FIELD_DATE)) != DBENG_OK)
      {
      if (ret == DBENG_EOF)
         {
         app_out("%s:daily record does not exist,creating it", mname);

         if ((ret = db_new(tid)) != DBENG_OK)
            {
            app_out("%s:bad rc[%d] from db_new", mname, ret);
            return;
            }

         if ((ret = db_put_field(tid, FIELD_DATE, curdate)) != DBENG_OK)
            {
            app_out("%s:bad rc[%d] from db_put_field[date]", mname, ret);
            return;
            }

         subfield = 1;
         }
      else
         {
         app_out("%s:bad rc[%d] from db_find_field[date]", mname, ret);
         return;
         }
      }
   else
      {
      if ((ret = db_get_nsubfields(tid, FIELD_PING_DATA, &subfield))
          != DBENG_OK)
         {
         app_out("%s:bad rc[%d] from db_get_nsubfields", mname, ret);
         return;
         }

      subfield++;
      }

   // new subfields is assed onto the end of the list

   if ((ret = db_put_subsubfield(tid, FIELD_PING_DATA, subfield,
                                 SUBSUBFIELD_TIME, curtime)) != DBENG_OK)
      {
      app_out("%s:bad rc[%d] from db_put_subsubfield[time]", mname, ret);
      return;
      }

   /* extract send and received stats from ping output
      method chosen works on all platforms by grabbing
      the first number in the first two (comma delimited)
      words on the line */

   if (!get_sent_recv(line, 1, &pings_sent))
      return;

   if (!get_sent_recv(line, 2, &pings_recv))
      return;

   // put the rest of the table data and write

   sprintf(wrd, "%d", pings_sent);

   if ((ret = db_put_subsubfield(tid, FIELD_PING_DATA, subfield,
                                 SUBSUBFIELD_PINGS_SENT, wrd)) != DBENG_OK)
      {
      app_out("%s:bad rc[%d] from db_put_subsubfield[pings_sent]", mname, ret);
      return;
      }

   sprintf(wrd, "%d", pings_recv);

   if ((ret = db_put_subsubfield(tid, FIELD_PING_DATA, subfield,
                                 SUBSUBFIELD_PINGS_RECV, wrd)) != DBENG_OK)
      {
      app_out("%s:bad rc[%d] from db_put_subsubfield[pings_recv]", mname, ret);
      return;
      }

   if ((ret = db_write(tid)) != DBENG_OK)
      {
      app_out("%s:bad rc[%d] from db_write", mname, ret);
      return;
      }

   app_out("%s:curdate=%s,curtime=%s,subfield=%d", mname, curdate, curtime,
           subfield);
   app_out("%s:pings_sent=%d,pings_recv=%d", mname, pings_sent, pings_recv);
}
      
void find_stats(char *line)
{
   /* Locate the line containing the stats. Returns the line containing
      the send and receive status (in 'line') upon success, an empty
      string otherwise. */

   FILE *in;
   char buf[128], mname[] = "find_stats";
   int nwords;

   line[0] = EOS;

   // open 'ping' output file

   if ((in = fopen(PING_OUTPUT_FILE, "r")) == (FILE *)NULL)
      {
      app_out("%s:unable to open ping output file '%s'", 
               mname, PING_OUTPUT_FILE);
      return;
      }

   /* read each line looking for the stats line (the first line with at
      least two comma delimited words) */
      
   get_rec(in, buf);

   while(!feof(in))
      {
      nwords = ll_words(buf, ',');

      if (nwords > 1)
         {
         strcpy(line, buf);
         fclose(in);
         return;
         }

      get_rec(in, buf);
      }

   fclose(in);
}

int get_sent_recv(char *line, int which, int *result)
{
   /* Get the sent or received stats from the ping data. Returns 'TRUE'
      upon success with the result loaded into 'result', returns
      'FALSE' otherwise. */

   char parm[50], wrd[50], mname[] = "get_sent_recv";
   int nwords, i, tmp;

   *result = 0;

   if (!ll_word(line, parm, which, ','))
      {
      app_out("%s:bad rc(0) from ll_word[line(%d)]", mname, which);
      return(FALSE);
      }

   trim(parm, wrd);
   strcpy(parm, wrd);
   nwords = words(parm);

   for(i = 1; i <= nwords; i++)
      {
      if (!word(parm, wrd, i))
         {
         app_out("%s:bad rc[0] from word[%d]", mname, i);
         return(FALSE);
         }

      if (qatoi(wrd, &tmp))
         {
         *result = tmp;
         return(TRUE);
         }
      }

   app_out("%s:exit[0],count not found", mname);
   return(FALSE);
}

int parse_comline(int c_count, char **c_parm)
{
   /* Parse the command line for parameters. Function
      returns 'TRUE' if no error was detected, 'FALSE'
      otherwise. */

   char mname[] = "parse_comline", tmp[25];
   int parms = 1, done = FALSE;

   /* set default log file */

   strcpy(log_dest, DEFAULT_LOG_FILE);
   strcpy(ping_dest, DEFAULT_HOST);

   if (c_count == 1)
      done = TRUE;

   while(!done)
      {
      if (c_parm[parms][0] == SWITCH_CHAR)
         {
         switch(toupper(c_parm[parms][1]))
            {
            case 'L':
               if (strlen(c_parm[parms]) > 2)
                  printf("%s:%s:extraneous input with log switch, "
                         "ignoring\n", APNAME, mname);

               parms++;

               if (c_count > parms)
                  strcpy(log_dest, c_parm[parms++]);

               log_output = TRUE;
               break;

            case 'C':
               if (strlen(c_parm[parms]) > 2)
                  printf("%s:%s:extraneous input with ping count switch, "
                         "ignoring\n", APNAME, mname);

               parms++;

               if (c_count <= parms)
                  {
                  printf("%s:%s:'c' switch present without ping count,"
                         "program abort\n", APNAME, mname);
                  return(FALSE);
                  }

               strcpy(tmp, c_parm[parms++]);

               if (!qatoi(tmp, &ping_count))
                  {
                  printf("%s:%s:non-numeric ping count,program abort\n",
                         APNAME, mname);
                  return(FALSE);
                  }

               if (ping_count < 1)
                  {
                  printf("%s:%s:out of range ping count,s/b >0,abort\n",
                         APNAME, mname);
                  return(FALSE);
                  }

               break;

            case 'B':
               use_bbuuzzb = TRUE;
               parms++;
               break;

            case 'Q':
               console_output = FALSE;
               parms++;
               break;

            case 'P':
               if (strlen(c_parm[parms]) > 2)
                  printf("%s:%s:extraneous input with ping destination switch, "
                         "ignoring\n", APNAME, mname);

               parms++;

               if (c_count <= parms)
                  {
                  printf("%s:%s:'p' switch present without ping destination,"
                         "program abort\n", APNAME, mname);
                  return(FALSE);
                  }

               strcpy(ping_dest, c_parm[parms++]);
               break;

            default:
               printf("%s:%s:unknown switch[%s],program abort\n",
                      APNAME, mname, c_parm[parms]);
               return(FALSE);
            };
         }

      if (parms >= c_count)
         done = TRUE;
      }


   return(TRUE);
}

#ifdef MULTIUSER
int c_initialize(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only), update the config list and connect
      to a 'Bbuuzzb' server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char mes[128];
   int ret;

#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif

   /* attempt to locate a 'Bbuuzzb' server and connect to it */

   if ((ret = db_initialize()) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:error connecting to the Bbuuzzb server,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* display current connected server host name and
      TCP port number */

#ifdef IPC_TCP
   (void)db_get_active(mes, &ret);
   printf("%s:connected to Bbuuzzb server %s on TCP port %d (decimal)\n",
          mname, mes, ret);
#endif

   return(TRUE);
}
#endif

#ifndef MULTIUSER
int c_initialize(void)
{
   /* Start the 'DBENG' local engine, read the config file
      and display the result settings. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char *mes;
   int ret, flag;

#ifdef DEBUG
#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif
#endif

   if ((mes = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
      {
      printf("%s:alloc fail[mes]. Program abort\n", mname);
      return(FALSE);
      }

   ret = db_initialize();

   /* interpret code from init which may be
      a fatal config error */

   switch(ret)
      {
      case DBENG_OK:
         printf("%s:read config file successfully\n", mname);
         break;

      case DBENG_CONFIG_UNABLE_TO_OPEN:
         printf("%s:config file not found, loaded defaults\n", mname);
         break;

      default:
         db_io_code_string(ret, mes);
         printf("%s:fatal config error[%s]\n", mname, mes);
         printf("%s:terminating\n", mname);
         free(mes);
         return(FALSE);
         break;
      };

   (void)db_config_get_tmp_path(mes);
   printf("%s:tmp path is '%s'\n", mname, mes);
   (void)db_config_get_log(mes);
   printf("%s:log is '%s'\n", mname, mes);

   if ((ret = db_config_get_catalog(mes)) == DBENG_OK)
      printf("%s:catalog is '%s'\n", mname, mes);
   else
      printf("%s:catalog name not loaded\n", mname);

   (void)db_config_get_catalog_flag(&flag);

   if (flag_2_logic_string(flag, mes))
      printf("%s:catalog is %s\n", mname, mes);

   free(mes);
   return(TRUE);
}
#endif

#ifdef MULTIUSER
#ifdef IPC_TCP
int c_ipc_socloc_init(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only) and update the config list.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_ipc_socloc_init";
   char *thelist, mes[128];
   int ret;

   /* startup WinSock (if Windoze) */

#ifdef OS_WIN32
   if (WSAStartup(WINSOCK_VERSION, &wsaData))
      {
      printf("%s:unable to start WinSock. "
            "Program abort\n", mname);
      return(FALSE);
      }
#endif

   /* attempt to connect to a 'socloc' server */

   if ((ret = sloc_initialize()) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error initializing socloc interface,rc[%s]. "
            "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* get config list from 'socloc' server and update
      the client list with it */

   if ((thelist = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      printf("%s:alloc fail[thelist]. Program abort\n", mname);
      return(FALSE);
      }

   if ((ret = sloc_config_get_list(thelist)) != SL_OK)
      {
      free(thelist);
      sl_code_string(ret, mes);
      printf("%s:error getting socloc config list,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   if ((ret = sl_config_put_list(thelist)) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error putting socloc config list,rc[%s]. ",
             "Program abort\n", mname, mes);
      free(thelist);
      return(FALSE);
      }

   free(thelist);
   return(TRUE);
}
#endif
#endif

void app_out(char *fmt,...)
{
   /* Output a message to the console and/or the log file. */

   va_list argptr;
   char *mes, mname[] = "app_out";
   int ret;

   /* if no output, return */

   if (!console_output && !log_output)
      return;

   va_start(argptr, fmt);

   if ((mes = (char *)malloc(BUFSIZE)) == (char *)NULL)
      {
      logman("%s:%s:alloc fail[mes]", APNAME, mname);
      return;
      }

   /* format message */

   memset(mes, 0, BUFSIZE);
   vsprintf(mes, fmt, argptr);

   if (log_output)
      logman_nf(mes);

   if (!log_output && console_output)
      printf("%s\n", mes);

   free(mes);
}

void term_app(void)
{
   // Prepare to terminate the application. Shutdown all IPC API's.

   if (logman_is_active())
      logman_end();

   if (use_bbuuzzb)
      {
      (void)db_close(tid);
      db_end();

#ifdef MULTIUSER
#ifdef IPC_TCP
      if (sloc_is_init())
         sloc_term_api();

#ifdef OS_WIN32
      WSACleanup();
#endif
#endif
#endif

      (void)appinit_remove_name();
      }
}
