/* sys_log - A system log server. This program will take messages
   sent to it and log the message text in the system log file.

   Program works by receiving messages, acting on them
   and replying. Most messages are instructions to send
   a string to the system log file. There are actually
   two log files, the primary and alternate. When the
   primary log file is locked, data will be written
   to the alternate log file. The primary log file
   is normally left closed just in case. You can,
   however, manually open and close this file.
   Rick Smereka, Copyright (C) 1997-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

   Original QNX version Feb/97, Rick Smereka

   Changed to receive the node ID (nid) in the send
   message structure and print this and the calling
   process ID (pid) on the output line. Dec/97,
   Rick Smereka

   Resolved a bug that caused the incorrect 'nid' and
   'pid' of the system logger process to be output to
   the log. Added function 'output_log_local' which will
   preload the system logger process 'nid' and 'tid'.
   Jan/98, Rick Smereka

   Ported to 32bit Windows under CodeWarrior. See
   'sys_log.h' for message types and explaination
   of syntax. Server is TCP connection oriented
   iterative. TCP communication is accomplished
   through the library files 'ipcomm.c' (low level
   send/receive) and 'ipcsrv.c' (application
   level). TCP configuration is managed by
   'ipconfig.c'. Dec/98, Rick Smereka

   Ported to HP-UX under GNU C 2.8.1.
   Jan/99, Rick Smereka

   Implemented multi-platform program logo.
   Feb/99, Rick Smereka

   Ported to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Corrected situations in 'process_request' where a
   reply would not be sent back upon certain error
   conditions. Changed to use the 'socloc' interface
   instead of the 'sys_log.ipc' config file. Changed
   to allow command line options consisting of
   the TCP port number that the server will listen to.
   Also added processing of all common socket send codes.
   Program syntax now is:

      sys_log [-l log_file] [-a alternate_log_file] port

   Where 'port' is the TCP port to listen, '-l log_file'
   is the optional name of the server primary log file and
   '-a alternate_log_file' is the optional name of the server
   alternate log file. If no primary log file is given, the
   log file name will be 'SYS_LOG_FNAME' in the current
   directory. If no alternate log file is given, the
   log file name will be 'SYS_LOG_ALT_FNAME' in the current
   directory. Note that the switch character ('-')
   is based on the platform (see include file 'stdhead.h').
   Mar/2000, Rick Smereka

   Re-compiled after re-structuring the definition of the common
   send and repy codes. Added the option '-q' to control message
   output to the console. Program syntax now is:

      sys_log [-q] [-l log_file] [-a alt_log_file] port

   Default value for console output is on, the '-q' parameter
   (quiet) will turn it off. When console output is off, no
   messages will be output to 'stdout' or 'stderr'. All output
   will appear only in the log. Apr/2000, Rick Smereka

   Modified function 'srv_initialize' to obtain the IP address of
   the current machine and pass this to the socloc function
   'sloc_add'. Jul/2000, Rick Smereka

   Modified for use by both sockets and QNX message passing IPC.
   Since the QNX message passing variant does not use a TCP port
   number, its syntax is:

      sys_log [-q] [-l log_file] [-a alt_log_file]

   Oct/2001, Rick Smereka

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

   Modified function 'output_log' to obtain the date/time
   string from function 'get_time'. Sep/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'. Moved all shutdown code to this
   function. Placed call to this function upon termination.
   Re-wrote function 'output_log' to take a variable number of
   parameters and added function 'output_log_nf'.
   Jun/2003, Rick Smereka

   Changed message when unable to receive data from client
   connection in 'process_request'. Dec/2003,
   Rick Smereka

   Re-compile after changing the 'socloc' API. Added function
   's_message' and changed how message text is obtained. Everything
   passed the message command number is now taken as the message
   text. 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"
#include "ipcsrv.h"
#include "appinit.h"
#ifdef IPC_TCP
#include "socloc.h"
#include "sloc.h"
#include "slconfig.h"
#include "sliocode.h"
#include "ip.h"
#endif

#define VERSION "1.16.01-2006.02.23"
#define APNAME "sys_log"

#ifdef OS_UNIX
#define SYS_LOG_FNAME "/home/rick/logs/system.log"
#define SYS_LOG_ALT_FNAME "/home/rick/logs/altsys.log"
#else
#define SYS_LOG_FNAME "C:\\LOGS\\SYSTEM.LOG"
#define SYS_LOG_ALT_FNAME "C:\\LOGS\\ALTSYS.LOG"
#endif

#define SYS_LOG_TERM_PASSWD "rdsWin32"

int main(int, char **);
int parse_comline(int, char **);
int srv_initialize(void);
int process_request(void);
int s_message(char *);
int s_terminate(char *);
void s_trans_num(void);
void s_connect_num(void);
void sys_log_reply(int, char *);
int output_log(char *,...);
int output_log_nf(char *);
int open_alt_log(void);
int open_log(void);
void term_app(void);
#ifdef IPC_TCP
void usage();
#endif

/* global data */

int islocked;              /* is the primary log file locked */
int isopen;                /* is the log file already open */
int console_output = TRUE; /* output to console as well as log? */
FILE *out;                 /* output file pointer */
char server_log[128];      /* server log file name */
char server_alt_log[128];  /* server alternate log file name */
#ifdef IPC_TCP
char server_host[128];     /* server host name */
int server_port = 0;       /* TCP port that server is using */

/* WinSock specific global data structure */

#ifdef OS_WIN32
WSADATA wsaData;           /* struct used by 'WSAStartup()' */
#endif
#else
// QNX transaction counter
long server_trans_num = 0L;
#endif

int main(int argc, char **argv)
{
   char mess[128];
   int isterm;

   /* indicate that we are initializing */

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

   /* check for 'socloc' server (TCP) initializing and wait until this is
      complete */

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

   /* check for command line parameters */

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

   /* parse command line */

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

   // initialize

   islocked = isopen = FALSE;

   // validate primary log file by attempting to open it

   if (open_log() != SYS_LOG_OK)
      {
      printf("%s:unable to open log file %s, program abort\n", APNAME,
             SYS_LOG_FNAME);
      (void)appinit_stop(SYS_LOG_SERVICE_NAME);
      return(2);
      }

   fclose(out);

     /* build logo string based on platform */

#ifndef OS_UNIX
   /* non-Unix */

   (void)output_log("%s System Logger for %s Version %s", APNAME, 
                    PLATFORM_STRING, VERSION);
#else
   /* Unix */

   (void)output_log("%s System Logger for %s Version %s", APNAME, 
                    SUB_PLATFORM_STRING, VERSION);
#endif

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

   /* read IPC config file and validate other 'socloc' servers
      (if any) */

   if (!srv_initialize())
      {
      (void)appinit_stop(SYS_LOG_SERVICE_NAME);
      term_app();
      return(4);
      }

#ifdef IPC_TCP
   (void)output_log("%s:server on '%s' servicing TCP port %d (decimal)",
                    APNAME, server_host, server_port);
#endif
   (void)output_log("%s:primary log file is %s", APNAME, server_log);
   (void)output_log("%s:alternate log file is %s", APNAME, server_alt_log);
   (void)output_log("%s:log server started", APNAME);
   (void)appinit_stop(SYS_LOG_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 files */

   strcpy(server_log, SYS_LOG_FNAME);
   strcpy(server_alt_log, SYS_LOG_ALT_FNAME);

#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",
                         SYS_LOG_SERVICE_NAME);

               parms++;

               if (parms >= c_count)
                  {
                  printf("%s:log switch with no file name, "
                         "program abort\n", SYS_LOG_SERVICE_NAME);
                  return(FALSE);
                  }

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

            case 'A':
               if (strlen(c_parm[parms]) > 2)
                  printf("%s:extraneous input with alt log switch, "
                         "ignoring\n", SYS_LOG_SERVICE_NAME);

               parms++;

               if (parms >= c_count)
                  {
                  printf("%s:alt log switch with no file name, "
                         "program abort\n", SYS_LOG_SERVICE_NAME);
                  return(FALSE);
                  }

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

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

            default:
               printf("%s:unknown switch[%s], program abort\n",
                      SYS_LOG_SERVICE_NAME, 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", SYS_LOG_SERVICE_NAME);
            return(FALSE);
            }

         if (server_port <= 0)
            {
            printf("%s:server port is out of range, "
                   "program abort\n", SYS_LOG_SERVICE_NAME);
            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",
             SYS_LOG_SERVICE_NAME);
      return(FALSE);
      }
#endif

   /* make sure log file names are valid */

   if (open_log() != SYS_LOG_OK)
      {
      printf("%s:parse_comline:primary log file name is invalid, "
             "program abort\n", SYS_LOG_SERVICE_NAME);
      return(FALSE);
      }

   fclose(out);

   if (open_alt_log() != SYS_LOG_OK)
      {
      printf("%s:parse_comline:alternate log file name is invalid, "
             "program abort\n", SYS_LOG_SERVICE_NAME);
      return(FALSE);
      }

   fclose(out);
   return(TRUE);
}

int srv_initialize(void)
{
   /* Initialize the server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char mes[128], mname[] = "srv_initialize";
   int ret;

   /* startup WinSock (if Windoze) */

#ifdef OS_WIN32
   if (WSAStartup(WINSOCK_VERSION, &wsaData))
      {
      (void)output_log("%s:%s:unable to start WinSock, program abort",
                       APNAME, mname);
      return(FALSE);
      }
#endif

   /* initialize the server on the IPC level, note that
      the hostname 'server_host' is returned to us (TCP only) */

#ifdef IPC_TCP
   if (!ipc_init(server_host, server_port))
#else
   if (!ipc_init(SYS_LOG_SERVICE_NAME))
#endif
      {
      (void)output_log("%s:%s:ipc_init initialization failure, "
                       "program abort", APNAME, mname);
      return(FALSE);
      }

   /* initialize 'socloc' client API */

#ifdef IPC_TCP
   if ((ret = sloc_initialize()) != SL_OK)
      {
      sl_code_string(ret, mes);
      (void)output_log("%s:%s:socloc init failure,rc[%s]", APNAME, mname, mes);
      (void)output_log("%s:%s:program abort", APNAME, mname);
      return(FALSE);
      }

   /* get IP address of this machine */

   if (!ip_host2ip(server_host, mes))
      {
      (void)output_log("%s:%s:unable to obtain IP address "
                       "of this machine", APNAME, mname);
      (void)output_log("%s:%s:program abort", APNAME, mname);
      return(FALSE);
      }

   /* register with 'socloc' */

   if ((ret = sloc_add(SYS_LOG_SERVICE_NAME, server_host, server_port,
       mes)) != SL_OK)
      {
      sl_code_string(ret, mes);
      (void)output_log("%s:%s:socloc add failure,rc[%s]", APNAME, mname, mes);
      (void)output_log("%s:%s:program abort", APNAME, mname);
      return(FALSE);
      }
#endif

   return(TRUE);
}

int process_request(void)
{
   /* Process a single request. Function returns 'TRUE'
      upon success, 'FALSE' otherwise. */

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

   if ((messbuf = malloc(SYS_LOG_MAXMES)) == NULL)
      {
      (void)output_log("%s:%s:alloc fail[messbuf]", APNAME, mname);
      return(FALSE);
      }

   memset(messbuf, 0, SYS_LOG_MAXMES);

   // get the message from the client

#ifdef IPC_TCP
   if ((nbytes = ipc_recv_data(messbuf)) == 0)
#else
   if ((nbytes = ipc_recv_data(messbuf, SYS_LOG_MAXMES)) == 0)
#endif
      {
      free(messbuf);
      (void)output_log("%s:%s:connection closed by client", APNAME, mname);
#ifdef IPC_TCP
      return(-1);
#else
      return(FALSE);
#endif
      }

   messbuf[nbytes] = EOS;
   nwords = command_words(messbuf);

   // no command words, empty string

   if (!nwords)
      {
      sys_log_reply(SYS_LOG_UNKNOWN, (char *)NULL);
      free(messbuf);
      return(FALSE);
      }

   // first command word must be a number

   if ((tmp = (char *)malloc(SYS_LOG_MAXMES)) == NULL)
      {
      (void)output_log("%s:%s:alloc fail[tmp]", APNAME, mname);
      sys_log_reply(SYS_LOG_ALLOC_FAIL, (char *)NULL);
      free(messbuf);
      return(FALSE);
      }

   if (!command_word(messbuf, tmp, 1))
      {
      (void)output_log("%s:%s:error getting message type", APNAME, mname);
      sys_log_reply(SYS_LOG_PARM_ERROR, (char *)NULL);
      free(tmp);
      free(messbuf);
      return(FALSE);
      }

   if (!qatoi(tmp, &mestype))
      {
      (void)output_log("%s:%s:received an invalid message type",
                       APNAME, mname);
      sys_log_reply(SYS_LOG_UNKNOWN, (char *)NULL);
      free(tmp);
      free(messbuf);
      return(FALSE);
      }

   free(tmp);

   switch(mestype)
      {
      case SYS_LOG_SEND:
         if (nwords < 2)
            {
            (void)output_log("%s:%s:send:no message to log", APNAME, mname);
            sys_log_reply(SYS_LOG_NOMES, (char *)NULL);
            free(messbuf);
            return(FALSE);
            }

         slreply = s_message(messbuf);
         break;

      case SYS_LOG_SEND_STATUS:
         slreply = SYS_LOG_OK;
         break;

      case SYS_LOG_SEND_STOP:
         ret = s_terminate(messbuf);
         free(messbuf);
         return(ret);
         break;

      case SYS_LOG_SEND_LOCK:
         /* refuse to lock if already locked or log
            file has been left open */

         if (islocked || isopen)
            slreply = SYS_LOG_ALREADY_LOCKED;
         else
            {
            islocked = TRUE;
            slreply = SYS_LOG_OK;
            }

         break;

      case SYS_LOG_SEND_UNLOCK:
         if (!islocked)
            slreply = SYS_LOG_ALREADY_UNLOCKED;
         else
            {
            islocked = FALSE;
            slreply = SYS_LOG_OK;
            }

         break;

      case SYS_LOG_SEND_OPEN:
         /* refuse to open if already open or log
            file has been locked */

         if (isopen || islocked)
            slreply = SYS_LOG_ALREADY_OPEN;
         else
            {
            isopen = TRUE;
            slreply = open_log();
            }

         break;

      case SYS_LOG_SEND_CLOSE:
         /* refuse to close if already closed or log
            file has been locked */

         if (!isopen || islocked)
            slreply = SYS_LOG_ALREADY_CLOSED;
         else
            {
            isopen = FALSE;
            fclose(out);
            slreply = SYS_LOG_OK;
            }

         break;

      case SYS_LOG_SEND_DELETE:
         /* refuse to delete if file is open or locked */

         if (isopen)
            slreply = SYS_LOG_ALREADY_OPEN;
         else
            if (islocked)
               slreply = SYS_LOG_ALREADY_LOCKED;
            else
               {
               if ((tmp = (char *)malloc(SYS_LOG_MAXMES)) == NULL)
                  {
                  (void)output_log("%s:%s:delete:alloc fail[tmp]",
                                   APNAME, mname);
                  sys_log_reply(SYS_LOG_ALLOC_FAIL, (char *)NULL);
                  free(messbuf);
                  return(FALSE);
                  }

               if (!command_word(messbuf, tmp, 2))
                  {
                  (void)output_log("%s:%s:delete:error getting password",
                                   APNAME, mname);
                  sys_log_reply(SYS_LOG_PARM_ERROR, (char *)NULL);
                  free(tmp);
                  free(messbuf);
                  return(FALSE);
                  }

               if (strcmp(tmp, SYS_LOG_TERM_PASSWD))
                  slreply = SYS_LOG_NO_ACCESS;
               else
                  {
                  unlink(server_log);
                  (void)output_log("%s:%s:delete:primary log deleted",
                                   APNAME, mname);
                  slreply = SYS_LOG_OK;
                  }

               free(tmp);
               }

         break;

      case SYS_LOG_SEND_LOG_OFF:
         /* common socket code turn log off, does not make sense
            and is not implemented */

         slreply = SYS_LOG_NOT_IMPLEMENTED;
         break;

      case SYS_LOG_SEND_LOG_ON:
         /* common socket code turn log on, does not make sense
            and is not implemented */

         slreply = SYS_LOG_NOT_IMPLEMENTED;
         break;

      case SYS_LOG_SEND_LOG_STATUS:
         /* reply with ok and the primary and alternate log file
            names */

         if (words(server_log) > 1 || words(server_alt_log) > 1)
            sprintf(messbuf, "1 '%s,%s'", server_log, server_alt_log);
         else
            sprintf(messbuf, "1 %s,%s", server_log, server_alt_log);

         sys_log_reply(SYS_LOG_OK, messbuf);
         free(messbuf);
         return(FALSE);
         break;

      case SYS_LOG_SEND_SERVICE_NAME:
         sys_log_reply(SYS_LOG_OK, SYS_LOG_SERVICE_NAME);
         free(messbuf);
         return(FALSE);
         break;

      case SYS_LOG_SEND_VERSION:
         sys_log_reply(SYS_LOG_OK, VERSION);
         free(messbuf);
         return(FALSE);
         break;

      case SYS_LOG_SEND_TRANS_NUM:
         s_trans_num();
         free(messbuf);
         return(FALSE);
         break;

      case SYS_LOG_SEND_CONNECT_NUM:
         s_connect_num();
         free(messbuf);
         return(FALSE);
         break;

      default:
         (void)output_log("%s:%s:received unknown message", APNAME, mname);
         slreply = SYS_LOG_UNKNOWN;
         break;
      };

   sys_log_reply(slreply, (char *)NULL);
   free(messbuf);
   return(FALSE);
}

int s_message(char *comm)
{
   // Output a message to the log. Function returns a reply code.

   char mname[] = "s_message", *tmp;
   int ret, pos, len, newlen;

   if ((tmp = (char *)malloc(SYS_LOG_MAXMES)) == (char *)NULL)
      {
      (void)output_log("%s:%s:send:alloc fail[tmp]", APNAME, mname);
      sys_log_reply(SYS_LOG_ALLOC_FAIL, (char *)NULL);
      return(SYS_LOG_ALLOC_FAIL);
      }

   // message starts in second command word

   len = strlen(comm);
   pos = command_indxword(comm, 2);
   newlen = len - pos;

   // copy all of message into buffer

   strncpy(tmp, &comm[pos], newlen);
   tmp[newlen] = EOS;

   // if message ends in a quote (single or double), remove it

   if (tmp[newlen - 1] == '\'' || tmp[newlen - 1] == '\"')
      tmp[newlen - 1] = EOS;

   ret = output_log_nf(tmp);
   free(tmp);
   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 wrd[128], mname[] = "s_terminate";
   int ret;

   if (command_words(comm) < 2)
      {
      sys_log_reply(SYS_LOG_PARM_ERROR, (char *)NULL);
      (void)output_log("%s:%s:no password", APNAME, mname);
      return(FALSE);
      }

   if (!command_word(comm, wrd, 2))
      {
      sys_log_reply(SYS_LOG_PARM_ERROR, (char *)NULL);
      (void)output_log("%s:%s:error getting password", APNAME, mname);
      return(FALSE);
      }

   if (!strcmp(wrd, SYS_LOG_TERM_PASSWD))
      {
      sys_log_reply(SYS_LOG_OK, (char *)NULL);
      return(TRUE);
      }

   (void)output_log("%s:%s:illegal terminate request", APNAME, mname);
   sys_log_reply(SYS_LOG_NO_ACCESS, (char *)NULL);
   return(FALSE);
}

void s_trans_num(void)
{
   /* Get the current transaction count. Syntax:
      
         SYS_LOG_SEND_TRANS_NUM */
   
   char mess[128];
   long count;
   
#ifdef IPC_TCP
   count = ipc_trans_num();
#else
   count = server_trans_num;
#endif

   sprintf(mess, "%ld", count);
   sys_log_reply(SYS_LOG_OK, mess);
}

void s_connect_num(void)
{
   /* Get the current connection count. Syntax:
      
         SYS_LOG_SEND_CONNECT_NUM */
   
#ifdef IPC_TCP
   char mess[128];
   int count;
   count = ipc_connect_num();
   sprintf(mess, "%d", count);
   sys_log_reply(SYS_LOG_OK, mess);
#else
   sys_log_reply(SYS_LOG_NOT_IMPLEMENTED, (char *)NULL);
#endif
}

void sys_log_reply(int reply_type, char *mess)
{
   // Send the reply to the client.

   char *reply_char, mname[] = "sys_log_reply";
   int len;

   len = (mess == (char *)NULL) ? 25 : strlen(mess) + 25;

   if ((reply_char = (char *)malloc(len)) == (char *)NULL)
      {
      output_log("%s:%s:alloc fail[reply_char]", APNAME, mname);
      return;
      }

   memset(reply_char, 0, len);

   if (mess != (char *)NULL && strlen(mess))
      sprintf(reply_char, "%d %s", reply_type, mess);
   else
      sprintf(reply_char, "%d", reply_type);

   if (ipc_send_data(reply_char) == 0)
      output_log("%s:%s:error sending data", APNAME, mname);

   free(reply_char);
}

int output_log(char *fmt,...)
{
   /* Output 'mess' to the system log file and to the screen.
      The message will be prefixed with a header containing
      the current date and time. */

   va_list argptr;
   char *mes;
   int ret;

   va_start(argptr, fmt);

   if ((mes = (char *)malloc(SYS_LOG_MAXMES)) == (char *)NULL)
      return(SYS_LOG_ALLOC_FAIL);

   vsprintf(mes, fmt, argptr);
   ret = output_log_nf(mes);
   free(mes);
   return(ret);
}

int output_log_nf(char *mes)
{
   /* Output 'mess' to the system log file and to the screen.
      The message will be prefixed with a header containing
      the current date and time. */

   char tmch[15];   
   int ret;

   if (mes == (char *)NULL || !strlen(mes))
      return(SYS_LOG_PARM_ERROR);

   if (!isopen)
      if (islocked)
         {
         if ((out = fopen(server_alt_log, "a")) == NULL)
            return(SYS_LOG_WERR);
         }
      else
         if ((ret = open_log()) != SYS_LOG_OK)
            return(ret);

   get_time(tmch);
   fprintf(out, "[%s]%s\n", tmch, mes);

   if (console_output)
      printf("[%s]%s\n", tmch, mes);

   if (!isopen)
      fclose(out);

   return(SYS_LOG_OK);
}

int open_log(void)
{
   /* Open the primary log file. */

   if ((out = fopen(server_log, "a")) == NULL)
      return(SYS_LOG_WERR);

   return(SYS_LOG_OK);
}

int open_alt_log(void)
{
   /* Open the alternate log file. */

   if ((out = fopen(server_alt_log, "a")) == (FILE *)NULL)
      return(SYS_LOG_WERR);

   return(SYS_LOG_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

   (void)output_log("%s:term_app:server down", APNAME);
}
   
#ifdef IPC_TCP
void usage(void)
{
   /* Display program usage. */

   printf("usage:%s [%cq] [%cl server_log][%ca server_alt_log] port\n",
          APNAME, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR);
}
#endif
