/* timesync - A date/time synchronization server for TCP sockets
   and QNX message passing IPC methods. Rick Smereka, Copyright (C) 2001-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

   This program does not attempt to implement any of the Internet
   date/time protocols. If absolute atomic time is required, this
   server should be run on a machine that runs a atomic time client.
   TCP program syntax:

      timesync [-q] [-l log_file] port

   QNX message passing syntax:

      timesync [-q] [-l log_file]
   
   Where the 'q' option will supress console output (quiet) and the 'l'
   (el) option specifies the server log file.

   The TCP IPC method version of this server requires at least one running
   'socloc' server. 

   Original QNX version, Nov/2001, Rick Smereka

   Ported to Red Hat Linux and Windows 32bit.
   Dec/2001, Rick Smereka

   Coded to use the 'appinit' API. May/2002, Rick Smereka

   Ported to Debian Linux. Jan/2003, Rick Smereka

   Modified to use the updated 'socloc' API ('sloc.c').
   Modified to use the updated server IPC API ('ipcsrv.c').
   Added function 'term_app' and modified exit points to use
   this function. Added support for the send codes
   'TS_SEND_TRANS_NUM' and 'TS_SEND_CONNECT_NUM'.
   Jun/2003, Rick Smereka

   Changed all logging calls to use the 'logman' API.
   Added the command line parameter '-s' to have this
   server wait for the system logging server to become
   available if it is also initializing. TCP program syntax:

      timesync [-q] [-s] [-l log_file] port

   QNX message passing syntax:

      timesync [-q] [-s] [-l log_file]

   Dec/2004, Rick Smereka

   Re-compile after changing the 'socloc' API.
   Jan/2005, Rick Smereka

   Re-compile after modifications to low level TCP socket communication
   module (ipcomm.c). Feb/2006, Rick Smereka */

#include "stdhead.h"
#include "flsocket.h"                 /* IPC generic definitions */
#include "ipcsrv.h"                   /* IPC server routines */
#include "timesync.h"                 /* timesync defines */
#include "appinit.h"                  /* application initialization API */
#ifdef IPC_TCP
#include "socloc.h"                   /* socket locate defines */
#include "ip.h"                       /* IP library routines */
#include "sloc.h"                     /* 'socloc' client API */
#endif

#define VERSION "1.07.01-2006.02.23"  /* version ID */
#define TS_TERM_PASSWD "rdsWin32"     /* terminate server password */
#define APNAME "timesync"

/* default private log file */

#define TS_LOG_FILE "timesync.log"

/* WinSock specific global data structure */

#ifdef OS_WIN32
WSADATA wsaData;                      /* struct used by 'WSAStartup()' */
#endif

/* global data */

int console_output = TRUE;            /* log messages to screen? */
int waitfor_syslog = FALSE;           /* wait for system logger upon init? */
char d_log_file[256];                 /* path/name of log file */
#ifdef IPC_TCP
int server_port = 0;                  /* TCP port that server is using */
char localHost[256];                  /* host name of this machine */
#else
// QNX transaction counter
long server_trans_num = 0L;
#endif

/* function prototypes */

int main(int, char **);
int parse_comline(int, char **);
int srv_initialize(void);
int process_request(void);
int do_comm(int, char *);
int s_terminate(char *);
void s_log_off(void);
void s_log_on(char *);
void s_log_status(void);
void s_get_datime(void);
void s_trans_num(void);
void s_connect_num(void);
int do_reply_code(int);
int do_reply_char(int, char *);
int do_reply_int(int, int);
int do_reply_long(int, long);
void term_app(void);
#ifdef IPC_TCP
void usage();
#endif
   
int main(int argc, char **argv)
{
   int isterm;

   /* indicate that we are initializing */

   if (!appinit_start(TS_SERVICE_NAME))
      {
      printf("%s:error creating 'appinit' lock file. Program abort\n", APNAME);
      return(0);
      }

   /* check for 'socloc' server initializing */

#ifdef IPC_TCP
   if (!appinit_waitfor(SL_SERVICE_NAME, 5))
      {
      printf("%s:error waiting for 'socloc' to initialize. "
             "Program abort\n", APNAME);
      return(0);
      }
#endif

#ifdef IPC_TCP
   if (argc == 1)
      {
      (void)appinit_stop(TS_SERVICE_NAME);
      usage();
      return(0);
      }
#endif

   if (!parse_comline(argc, argv))
      {
      (void)appinit_stop(TS_SERVICE_NAME);
      return(0);
      }

   // wait for system logger to init if requested

   if (waitfor_syslog)
      if (!appinit_waitfor(SYS_LOG_SERVICE_NAME, 1))
         {
         printf("%s:error waiting for 'sys_log' to initialize. "
                "Program abort\n", APNAME);
         (void)appinit_stop(TS_SERVICE_NAME);
         return(0);
         }

   /* set logger console output flag */
   
   logman_console(console_output);
   
   /* start server on IPC level, connect to a 'socloc' server and
      register this server */
      
   if (!srv_initialize())
      {
      (void)appinit_stop(TS_SERVICE_NAME);
      term_app();
      return(0);
      }

   /* initialize logging */

   if (logman_start(d_log_file, APNAME))
      {
      printf("%s:error initializing logging. Program abort\n", APNAME);
      (void)appinit_stop(TS_SERVICE_NAME);
      return(0);
      }

   /* build logo string based on platform */
   
#ifndef OS_UNIX
   /* non-Unix */
   
   logman("%s Server for %s Version %s", APNAME, PLATFORM_STRING, VERSION);
#else
   /* Unix */
   
   logman("%s Server for %s Version %s", APNAME, SUB_PLATFORM_STRING, VERSION);
#endif
   
   logman("By Rick Smereka, Copyright (c) 2001-2006");
   
   
#ifdef IPC_TCP
   logman("%s:server on \"%s\" servicing TCP port %d (decimal)",
                 APNAME, localHost, server_port);   
#endif
   logman("%s:server started", APNAME);   
   logman("%s comes with ABSOLUTELY NO WARRANTY", APNAME);   
   logman("This is free software, and you are welcome to redistribute it");   
   logman("under certain conditions; see \"gpl.txt\" for information."); 
   
   /* turn off log as this is used only for debugging */
   
   logman_end();

   /* indicate that the server has completed initialization */

   (void)appinit_stop(TS_SERVICE_NAME);
   
#ifdef IPC_TCP
   ipc_server_wait();
#else
   // QNX, process each connection (infinite loop)

   while(1)
      {
      isterm = process_request();
      server_trans_num++;

      if (isterm)
         break;
      }
#endif

   term_app();
   return(0);
}

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. */
      
   int parms = 1, done = FALSE;

   /* set default log file */
   
   strcpy(d_log_file, TS_LOG_FILE);
      
#ifdef IPC_QNX
   if (c_count == 1)
      return(TRUE);
#endif

   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:extraneous input with log switch, ignoring\n",
                         APNAME);
               
               parms++;
            
               if (parms >= c_count)
                  {
                  printf("%s:log switch with no file name, "
                         "program abort\n", APNAME);
                  return(FALSE);
                  }
               
               strcpy(d_log_file, c_parm[parms]);
               parms++;
               break;
               
            case 'Q':
               console_output = FALSE;
               parms++;
               break;
               
            case 'S':
               waitfor_syslog = TRUE;
               parms++;
               break;

            default:
               printf("%s:unknown switch[%s], program abort\n",
                      APNAME, c_parm[parms]);
               return(FALSE);
            };
         }
#ifdef IPC_TCP
      else
         {
         if (!qatoi(c_parm[parms], &server_port))
            {
            printf("%s:server port is non-numeric, "
                   "program abort\n", APNAME);
            return(FALSE);
            }
            
         if (server_port <= 0)
            {
            printf("%s:server port is out of range, "
                   "program abort\n", APNAME);
            return(FALSE);
            }
            
         parms++;
         }
#endif
         
      if (parms >= c_count)
         done = TRUE;
      }

#ifdef IPC_TCP
   if (server_port == 0)
      {
      printf("%s:server port missing, program abort\n", APNAME);
      return(FALSE);
      }
#endif
            
   return(TRUE);
}

int srv_initialize(void)
{
   /* Initialize the server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */
   
   char mname[] = "srv_initialize";      
   char 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        

   /* initialize the server on the IPC level, note that
      the hostname 'localhost' is returned to us (TCP only) */
      
#ifdef IPC_TCP
   if (!ipc_init(localHost, server_port))
#else
   if (!ipc_init(TS_SERVICE_NAME))
#endif
      {
      printf("%s:'ipc_init initialization failure, program abort\n", mname);
      return(FALSE);
      }

   /* initialize 'socloc' client API */
   
#ifdef IPC_TCP
   if ((ret = sloc_initialize()) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:bad rc[%s] from 'sloc_initialize'\n", mname, mes);
      return(FALSE);
      }
    
    /* get IP address of this machine */
   
   if (!ip_host2ip(localHost, mes))
      {
      printf("%s:unable to obtain IP address of this machine\n", mname);
      printf("%s:program abort\n", mname);
      return(FALSE);
      }
              
   /* register with 'socloc' */
   
   if ((ret = sloc_add(TS_SERVICE_NAME, localHost, server_port,
       mes)) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:bad rc[%s] from 'sloc_add'\n", mname, mes);
      return(FALSE);
      }
#endif
                                 
   return(TRUE);
}

void s_log_off(void)
{
   /* Turn all loggging off. Of course, check for logging
      already off. */

   char mname[] = "s_log_off";      
   int ret;
   
   logman("%s:enter", mname);
   
   if (!logman_is_active())
      ret = TS_LOG_ALREADY_OFF;
   else
      {
      logman_end();
      ret = TS_OK;
      }
      
   (void)do_reply_code(ret);
   logman("%s:normal exit rc[%d]", mname, ret);
}
 
void s_log_on(char *comm)
{
   /* Turn server logging on using either the supplied log file name
      or the previously loaded log file name. Of course, check for logging
      already on. */
      
   char mname[] = "s_log_on";
   int ret;
   
   logman("%s:enter", mname);
   
   if (logman_is_active())
      ret = TS_LOG_ALREADY_ON;
   else
      {
      if (command_words(comm) > 1)
         {
         if (!command_word(comm, d_log_file, 2))
            {
            logman("%s:error getting log file name", mname);
            (void)do_reply_code(TS_INTERNAL_ERROR);
            return;
            }
         }
                     
      if (logman_start(d_log_file, APNAME))
         {
         (void)do_reply_code(TS_LOG_ERROR);
         return;
         }
         
      ret = TS_OK;
      }
       
   (void)do_reply_code(ret);
   logman("%s:normal exit rc[%d]", mname, ret);
}

void s_log_status(void)
{
   /* Get the current log status (on or off). */
   
   char *reply, *wrd, mname[] = "s_log_status";
   int full_len;
   
   logman("%s:enter", mname);
   full_len = strlen(d_log_file) + 5;
   
   if ((reply = (char *)malloc(full_len)) == (char *)NULL)
      {
      (void)do_reply_char(TS_MEMORY_FAIL, (char *)NULL);
      logman("%s:alloc fail[reply]", mname);
      return;
      }

   if ((wrd = initqstring(d_log_file)) == (char *)NULL)
      {
      free(reply);
      (void)do_reply_char(TS_MEMORY_FAIL, (char *)NULL);
      logman("%s:alloc fail[wrd]", mname);
      return;
      } 

   if (logman_is_active())
      sprintf(reply, "1 %s", wrd);
   else
      sprintf(reply, "0 %s", wrd);
      
   free(wrd);
   (void)do_reply_char(TS_OK, reply);
   free(reply);
   logman("%s:normal exit rc[0]", mname);
}
      
int process_request(void)
{
   /* Process a single request. Function returns 'FALSE' if
      termination signal was not encountered, 'TRUE'
      otherwise. */

   char mname[] = "process_request";
   char *messbuf, tmp[128];
   int ret, nbytes, nwords, mestype;

   logman("%s:enter", mname);
   
   if ((messbuf = (char *)malloc(TS_MAXCOMMAND)) == (char *)NULL)
      {
      (void)do_reply_code(TS_MEMORY_FAIL);
      logman("%s:alloc fail[messbuf]", mname);
      return(FALSE);
      }
           
   memset(messbuf, 0, TS_MAXCOMMAND);
   
   // get the message from the client

#ifdef IPC_TCP
   if ((nbytes = ipc_recv_data(messbuf)) == 0)
#else
   if ((nbytes = ipc_recv_data(messbuf, TS_MAXCOMMAND)) == 0)
#endif
      {
      free(messbuf);
      logman("%s:connection closed by client", mname);
#ifdef IPC_TCP
      return(-1);
#else
      return(FALSE);
#endif
      }
    
   messbuf[nbytes] = EOS;
   logman("%s:recv %s,l=%d", mname, messbuf, strlen(messbuf));
   nwords = command_words(messbuf);
      
   // no command words, empty string
       
   if (!nwords)
      {
      (void)do_reply_code(TS_INVALID_FUNCTION);
      free(messbuf);
      logman("%s:empty command", mname);
      return(FALSE);
      }
   
   // first command word must be a number

   if (!command_word(messbuf, tmp, 1))
      {
      free(messbuf);
      (void)do_reply_code(TS_INTERNAL_ERROR);
      logman("%s:error getting first command word", mname);
      return(FALSE);
      }
   
   if (!qatoi(tmp, &mestype))
      {
      (void)do_reply_code(TS_INVALID_FUNCTION);
      logman("%s:command signature not a number", mname);
      free(messbuf);
      return(FALSE);
      }      
   
   /* pass entire string including command number */
      
   ret = do_comm(mestype, messbuf);
   free(messbuf);
   logman("%s:normal exit,rc=%d", mname, ret);
   return(ret);   
}

int do_comm(int mestype, char *slm)
{
   /* High level server command dispatcher. 
      Function returns 'TRUE' if a terminate
      request was encountered, 'FALSE'
      otherwise. */
   
   char mname[] = "do_comm";
   int ret;

   logman("%s:enter", mname);
   ret = FALSE;

   switch(mestype)
      {                               
      case TS_SEND_STATUS:
         (void)do_reply_code(TS_OK);
         break;

      case TS_SEND_GET_DATIME:
         s_get_datime();
         break;
                                                
      case TS_SEND_TERM:
         if (s_terminate(slm))
            ret = TRUE;
            
         break;
         
      case TS_SEND_LOG_OFF:
         s_log_off();
         break;
         
      case TS_SEND_LOG_ON:
         s_log_on(slm);
         break;

      case TS_SEND_LOG_STATUS:
         s_log_status();
         break;

      case TS_SEND_SERVICE_NAME:
         (void)do_reply_char(TS_OK, TS_SERVICE_NAME);
         break;

      case TS_SEND_VERSION:
         (void)do_reply_char(TS_OK, VERSION);
         break;
                           
      case TS_SEND_TRANS_NUM:
         s_trans_num();
         break;

      case TS_SEND_CONNECT_NUM:
         s_connect_num();
         break;

      default:
         logman("%s:received unknown code=%d", mname, mestype);
         (void)do_reply_code(TS_INVALID_FUNCTION);
         break;
      };

   logman("%s:exit[%d]", mname, ret);
   return(ret);
}

int s_terminate(char *comm)
{
   /* Terminate the server. Check the supplied password first.
      Syntax:
      
         TS_SEND_TERM password
         
      Function returns 'TRUE' if correct password was supplied,
      'FALSE' otherwise. */
      
   char mname[] = "s_terminate";
   char wrd[256];
   int ret;

   logman("%s:enter", mname);
      
   if (command_words(comm) < 2)
      {
      (void)do_reply_code(TS_INVALID_FUNCTION);
      logman("%s:no password", mname);
      return(FALSE);
      }

   if (!command_word(comm, wrd, 2))
      {
      (void)do_reply_code(TS_INTERNAL_ERROR);
      logman("%s:error getting password", mname);
      return(FALSE);
      }
         
   if (!strcmp(wrd, TS_TERM_PASSWD))
      {            
      (void)do_reply_code(TS_OK);
      return(TRUE);
      }
            
   logman("%s:illegal terminate request", mname);
   (void)do_reply_code(TS_ACCESS_DENIED);
   return(FALSE);
}

void s_get_datime(void)
{
   /* Obtain, format the current date and time and send it back
      to the client. Note that the year and month are properly
      adjusted. */

   time_t tod;
   struct tm *tmbuf;
   char mname[] = "s_get_datime", buf[15];

   logman("%s:enter", mname);
   tod = time(NULL);
   tmbuf = localtime(&tod);
   sprintf(buf, "%d%02d%02d%02d%02d%02d", tmbuf->tm_year + 1900, 
           tmbuf->tm_mon + 1, tmbuf->tm_mday, tmbuf->tm_hour, 
           tmbuf->tm_min, tmbuf->tm_sec);
   (void)do_reply_char(TS_OK, buf);
   logman("%s:normal exit", mname);
}

void s_trans_num(void)
{
   /* Get the current transaction count. Syntax:
      
         TS_SEND_TRANS_NUM */
   
   long count;
   
#ifdef IPC_TCP
   count = ipc_trans_num();
#else
   count = server_trans_num;
#endif

   (void)do_reply_long(TS_OK, count);
}

void s_connect_num(void)
{
   /* Get the current connection count. Syntax:
      
         TS_SEND_CONNECT_NUM */
   
#ifdef IPC_TCP
   int count;
   count = ipc_connect_num();
   (void)do_reply_int(TS_OK, count);
#else
   (void)do_reply_int(TS_NOT_IMPLEMENTED, 0);
#endif
}

int do_reply_code(int reply_code)
{
   /* Send a reply to a request which returns the reply code. Function
      returns 'TS_OK' upon success, an error code otherwise. */

   char reply_char[50], mname[] = "do_reply_code";
         
   memset(reply_char, 0, 50);
   sprintf(reply_char, "%d", reply_code);
   logman("%s:reply=%s,l=%d", mname, reply_char, strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      {
      logman("%s:bad rc[0] from ipc_send_data", mname);
      return(TS_VC_ERROR);
      }
      
   return(TS_OK);
}

int do_reply_char(int reply_code, char *parm)
{
   /* Send a reply to a request which returns the reply code and
      possibly a string. Function returns 'TS_OK' upon success,
      an error code otherwise. */

   char *reply, mname[] = "do_reply_char";
         
   if ((reply = (char *)malloc(TS_MAXCOMMAND)) == (char *)NULL)
      return(TS_MEMORY_FAIL);
         
   memset(reply, 0, TS_MAXCOMMAND);
   
   if (parm != (char *)NULL && strlen(parm))      
      sprintf(reply, "%d %s", reply_code, parm);
   else
      sprintf(reply, "%d", reply_code);
      
   logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));
   
   if (ipc_send_data(reply) == 0)
      {
      free(reply);
      logman("%s:bad rc[0] from ipc_send_data", mname);
      return(TS_VC_ERROR);
      }
      
   free(reply);
   return(TS_OK);
}
   
int do_reply_int(int reply_code, int parm)
{
   /* Send a reply to a request which returns the reply code and
      an integer. Function returns 'TS_OK' upon success,
      an error code otherwise. */

   char reply[50], mname[] = "do_reply_int";
         
   memset(reply, 0, 50);
   sprintf(reply, "%d %d", reply_code, parm);
   logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));
   
   if (ipc_send_data(reply) == 0)
      {
      logman("%s:bad rc[0] from ipc_send_data", mname);
      return(TS_VC_ERROR);
      }
      
   return(TS_OK);
}

int do_reply_long(int reply_code, long parm)
{
   /* Send a reply to a request which returns the reply code and
      a long integer. Function returns 'TS_OK' upon success,
      an error code otherwise. */

   char reply[50], mname[] = "do_reply_long";
         
   memset(reply, 0, 50);
   sprintf(reply, "%d %ld", reply_code, parm);
   logman("%s:reply=%s,l=%d", mname, reply, strlen(reply));
   
   if (ipc_send_data(reply) == 0)
      {
      logman("%s:bad rc[0] from ipc_send_data", mname);
      return(TS_VC_ERROR);
      }
      
   return(TS_OK);
}

void term_app(void)
{
   // Shutdown anything left open.

#ifdef IPC_TCP
   (void)sloc_delete(server_port);
   sloc_term_api();
#ifdef OS_WIN32
   WSACleanup();
#endif
#else
   (void)ipc_close();
#endif

   logman("term_app:server down");

   if (logman_is_active())
      logman_end();
}

#ifdef IPC_TCP
void usage(void)
{
   /* Display program usage. */
   
   printf("usage:%s [%cq] [%cs] [%cl log_file] port\n", APNAME, SWITCH_CHAR, 
          SWITCH_CHAR, SWITCH_CHAR);
}
#endif
