/* dumsocks - A dumb server. This TCP or QNX message passing connection 
   oriented iterative server is used for testing.
   Rick Smereka, Copyright (C) 2000-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

   Like the name says...dumb. This server will accept only a
   generic status request, a termination request and log on/off
   requests. This server is coded to use 'socloc'. It is a fatal
   error if there are no 'socloc' servers running. Syntax:
   
      dumsocks [-l log_file] port
   
   Where 'port' is the TCP port to listen and '-l log_file'
   is the optional name of the server log file. If no log file
   is given, the log file name will be 'DUM_LOG_FILE' in the
   current directory. Note that the switch character ('-')
   is based on the platform (see include file 'stdhead.h').
   
   Original Windows 32bit version under CodeWarrior V4. Feb/2000,
   Rick Smereka
   
   Re-compiled after re-structuring the definition of the common
   send and repy codes. Added '-q' comand line option to suppress
   console/screen output. Program syntax now:
   
      dumsocks [-q] [-l log_file] port
      
   Apr/2000, Rick Smereka
   
   Modified the function 'srv_initialize' to obtain the IP address
   and pass this to the 'sloc_add' socloc function. Jul/2000,
   Rick Smereka

   Corrected multiple definitions of 'localHost'. Jul/2001,
   Rick Smereka

   Modified for use with both sockets and QNX message passing IPC.
   Ported to QNX V4.23a. Since the QNX message passing variant does
   not require a port number, this parameter is absent. QNX message
   passing syntax is:

      dumsocks [-q] [-l log_file]

   Oct/2001, Rick Smereka

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

   Ported to Debian Linux. Jan/2003, Rick Smereka

   Changed to in accordance with the modifications in 'ipcsrv.c'.
   Modified for use with the updated socloc client API ('sloc.c').
   May/2003, Rick Smereka

   Added function 'term_app' to perform all shutdown tasks.
   Added support for the send codes 'DM_SEND_TRANS_NUM' and
   'DM_SEND_CONNECT_NUM'. Jun/2003, Rick Smereka

   Changed all logging calls to use 'logman' API. Rewrote function
   'term_app'. 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"                 /* generic socket definitions */
#include "appinit.h"                  /* application init API */
#include "ipcsrv.h"                   /* IPC server routines */
#include "dumsocks.h"                 /* dumb socket server defines */
#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.09.01-2006.02.23"  /* version ID */
#define DM_TERM_PASSWD "rdsWin32"     /* terminate server password */
#define APNAME "dumsocks"

// default log file definition

#define DUM_LOG_FILE "dumsocks.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? */
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 server transaction count

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_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(DM_SERVICE_NAME))
      {
      printf("%s:error creating 'appinit' lock file. Program abort\n",
             APNAME);
      return(0);
      }

   /* check for 'socloc' server (TCP) initializing */

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

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

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

   /* read IPC config file and validate other 'socloc' servers
      (if any) */
      
   if (!srv_initialize())
      {
      printf("%s:program abort\n", APNAME);
      (void)appinit_stop(DM_SERVICE_NAME);
      term_app();
      return(0);
      }

   // set console output

   logman_console(console_output);

   /* initialize logging */

   if (logman_start(d_log_file, APNAME))
      {
      printf("%s:error staring server logging. Program abort\n",
             APNAME);
      (void)appinit_stop(DM_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_nf("By Rick Smereka, Copyright (c) 2000-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_nf("This is free software, and you are welcome to redistribute it"); 
   logman_nf("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(DM_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. */
      
   char mname[] = "parse_comline";
   int parms = 1, done = FALSE;

   /* set default log file */
   
   strcpy(d_log_file, DUM_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",
                         mname);
               
               parms++;
            
               if (parms >= c_count)
                  {
                  printf("%s:log switch with no file name, "
                         "program abort\n", mname);
                  return(FALSE);
                  }
               
               strcpy(d_log_file, c_parm[parms]);
               parms++;
               break;
               
            case 'Q':
               console_output = FALSE;
               parms++;
               break;
               
            default:
               printf("%s:unknown switch[%s], program abort\n",
                      mname, 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", mname);
            return(FALSE);
            }
            
         if (server_port <= 0)
            {
            printf("%s:server port is out of range, "
                   "program abort\n", mname);
            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", mname);
      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\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(DM_SERVICE_NAME))
#endif
      {
      printf("%s:ipc_init initialization failure\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);
      return(FALSE);
      }
              
   /* register with 'socloc' */
   
   if ((ret = sloc_add(DM_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 (!is_log_active())
      ret = DM_LOG_ALREADY_OFF;
   else
      {
      logman_end();
      ret = DM_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 global log file name. Of course, check for logging
      already on. */
      
   char mname[] = "s_log_on";
   int ret;
   
   logman("%s:enter", mname);
   
   if (is_log_active())
      ret = DM_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(DM_INTERNAL_ERROR);
            return;
            }
         }
      else
         strcpy(d_log_file, DUM_LOG_FILE);
                     
      if (logman_start(d_log_file, APNAME))
         {
         (void)do_reply_code(DM_LOG_ERROR);
         return;
         }
         
      ret = DM_OK;
      }
       
   (void)do_reply_code(ret);
   logman("%s:normal exit rc[%d]", mname, ret);
}

void s_log_status(void)
{
   /* Get the status of the log including the flag value and the
      log name. */
   
   char mname[] = "s_log_status", *reply;
   int ret;
   
   logman("%s:enter", mname);
   ret = logman_is_active();
      
   if ((reply = (char *)malloc(strlen(d_log_file) + 3)) == (char *)NULL)
      {
      logman("%s:alloc fail[reply]", mname);
      (void)do_reply_char(DM_MEMORY_FAIL, "");
      return;
      }

   sprintf(reply, "%d %s", ret, d_log_file);
   (void)do_reply_char(DM_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(DM_MAXCOMMAND)) == (char *)NULL)
      {
      (void)do_reply_code(DM_MEMORY_FAIL);
      logman("%s:alloc fail[messbuf]", mname);
      return(FALSE);
      }
           
   memset(messbuf, 0, DM_MAXCOMMAND);
   
   // get the message from the client

#ifdef IPC_TCP
   if ((nbytes = ipc_recv_data(messbuf)) == 0)
#else
   if ((nbytes = ipc_recv_data(messbuf, DM_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(DM_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(DM_INTERNAL_ERROR);
      logman("%s:error getting first command word", mname);
      return(FALSE);
      }
   
   if (!qatoi(tmp, &mestype))
      {
      (void)do_reply_code(DM_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 DM_SEND_STATUS:
         (void)do_reply_code(DM_OK);
         break;
                                                
      case DM_SEND_TERM:
         if (s_terminate(slm))
            ret = TRUE;
            
         break;
         
      case DM_SEND_LOG_OFF:
         s_log_off();
         break;
         
      case DM_SEND_LOG_ON:
         s_log_on(slm);
         break;

      case DM_SEND_LOG_STATUS:
         s_log_status();
         break;

      case DM_SEND_SERVICE_NAME:
         (void)do_reply_char(DM_OK, DM_SERVICE_NAME);
         break;

      case DM_SEND_VERSION:
         (void)do_reply_char(DM_OK, VERSION);
         break;
                           
      case DM_SEND_TRANS_NUM:
         s_trans_num();
         break;

      case DM_SEND_CONNECT_NUM:
         s_connect_num();
         break;

      default:
         logman("%s:received unknown code=%d", mname, mestype);
         (void)do_reply_code(DM_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:
      
         SL_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(DM_INVALID_FUNCTION);
      logman("%s:no password", mname);
      return(FALSE);
      }

   if (!command_word(comm, wrd, 2))
      {
      (void)do_reply_code(DM_INTERNAL_ERROR);
      logman("%s:error getting password", mname);
      return(FALSE);
      }
         
   if (!strcmp(wrd, DM_TERM_PASSWD))
      {            
      (void)do_reply_code(DM_OK);
      return(TRUE);
      }
            
   logman("%s:illegal terminate request", mname);
   (void)do_reply_code(DM_ACCESS_DENIED);
   return(FALSE);
}

void s_trans_num(void)
{
   /* Get the current transaction count. Syntax:
      
         DM_SEND_TRANS_NUM */
   
   char mname[] = "s_trans_num";
   long count;
   
   logman("%s:enter", mname);

#ifdef IPC_TCP
   count = ipc_trans_num();
#else
   count = server_trans_num;
#endif

   (void)do_reply_long(DM_OK, count);
   logman("%s:normal exit", mname);   
}

void s_connect_num(void)
{
   /* Get the current connection count. Syntax:
      
         DM_SEND_CONNECT_NUM */
   
   char mname[] = "s_connect_num";
   int count;

   logman("%s:enter", mname);

#ifdef IPC_TCP
   count = ipc_connect_num();
   (void)do_reply_int(DM_OK, count);
#else
   do_reply_int(DM_NOT_IMPLEMENTED, 0);
#endif

   logman("%s:normal exit", mname);   
}

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

   char reply_char[50];
         
   memset(reply_char, 0, 50);
   sprintf(reply_char, "%d", reply_code);
   logman("do_reply_code:reply=%s,l=%d", reply_char, 
                 strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(DM_VC_ERROR);
      
   return(DM_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 'SL_OK' upon success,
      an error code otherwise. */

   char *reply;
         
   if ((reply = (char *)malloc(DM_MAXCOMMAND)) == (char *)NULL)
      return(DM_MEMORY_FAIL);
         
   memset(reply, 0, DM_MAXCOMMAND);
   
   if (parm != (char *)NULL && strlen(parm))      
      sprintf(reply, "%d '%s'", reply_code, parm);
   else
      sprintf(reply, "%d", reply_code);
      
   logman("do_reply_char:reply=%s,l=%d", reply, strlen(reply));
   
   if (ipc_send_data(reply) == 0)
      {
      free(reply);
      return(DM_VC_ERROR);
      }
      
   free(reply);
   return(DM_OK);
}
   
int do_reply_int(int reply_code, int ival)
{
   /* Send a reply to a request which returns the an integer as
      well as the reply code. Function returns 'SL_OK' upon 
      success, an error code otherwise. */

   char reply_char[100];
         
   memset(reply_char, 0, 100);
   sprintf(reply_char, "%d %d", reply_code, ival);
   logman("do_reply_int:reply=%s,l=%d", reply_char, strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(DM_VC_ERROR);
      
   return(DM_OK);
}

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

   char reply_char[100];
         
   memset(reply_char, 0, 100);
   sprintf(reply_char, "%d %ld", reply_code, lval);
   logman("do_reply_long:reply=%s,l=%d", reply_char, strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(DM_VC_ERROR);
      
   return(DM_OK);
}

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

   logman("term_app:server down");

   if (logman_is_active())
      logman_end();

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

   (void)appinit_remove_name();
}

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