/* socloc - A socket locate server. This TCP connection oriented
   iterative socket server will register the name, port and IP
   address of other socket servers. Client applications can then
   easily find the host name, port and IP address of these socket
   servers. 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

   This socket locate server maintains two singly linked lists. One
   (called the config list) is the list of currently available
   socket locate servers. The other is a list of currently
   available socket servers. All additions and deletions (of
   both lists) are populated to all known 'socloc' servers giving
   automatic dynamic 'failover' protection.

   You can have as many 'socloc' servers running as you like. The only
   limitations are that each 'socloc' server must occupy a unique
   TCP port and not more than one 'socloc' server can be running
   on each host machine. You may also have as many socket servers
   running as you desire. Each socket server is identified by a
   'service name'. You may seach just by service name (in which
   case, the first entry with the same service name will match).
   You may also qualify the search with a host name, TCP port
   or the IP address. Throughout 'socloc', the IP
   address is completly optional. The only limitation is that each
   socket server must be occupy a unique TCP port. You may
   even have multiple socket servers with the same 'service
   name' running on the same host machine.

   Note that this socket server (unlike my other socket servers)
   does not use the 'sys_log' system logging server. This
   is due to the fact that it is assumed that this server is the
   first one started and 'sys_log' will not be running. This server
   logs all information into the private log file 'SL_LOG_FILE' by
   default.

   Original Windows 32bit version under CodeWarrior V4. Feb/2000,
   Rick Smereka

   Modified to have server log status query return the name of the
   current log file if active. Mar/2000, Rick Smereka

   Re-compiled after re-structuring the definition of the common
   send and repy codes. Added '-l' command line option to specify
   the output log file and '-q' quiet command line option to
   supress console output. Program syntax now is:

      socloc [-q] [-l log_file]

   Apr/2000, Rick Smereka
   
   Modified function 'srv_initialize' to obtain the local machine
   IP address and pass this to 'sloc_bcast_config_add'. Changed
   function 'do_comm' to send a reply code when encountering a
   send code that is out of range. Jul/2000, Rick Smereka

   Re-compiled based on new version of 'socloc' API.
   Jul/2001, Rick Smereka

   Fixed bug in 's_ll_put_list' which called 'log_file_date' with an
   improper number of parameters. Feb/2002, Rick Smereka

   Fixed bug in 's_find' that incorrectly calculated the length of the
   reply string. Mar/2002, 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'.
   Changed the method that this server uses to talk to other
   socloc and socket servers. This server now uses the
   'slocsrv' API. May/2003, Rick Smereka

   Added function 'term_app' to perform server shutdown.
   Jun/2003, Rick Smereka

   Code cleanup. Dec/2004, 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 "socloc.h"                   /* socket locate defines */
#include "slconfig.h"                 /* socket locate config management */
#include "ipcsrv.h"                   /* TCP server routines */
#include "ip.h"                       /* IP library routines */
#include "slocsrv.h"                  /* client API for socloc server */
#include "sconnect.h"                 /* 'sconnect' parse functions */
#include "appinit.h"                  /* app init API */

#define VERSION "1.08.01-2006.02.23"  /* version ID */
#define SL_TERM_PASSWD "rdsWin32"     /* terminate server password */
#define SL_LOG_FILE "socloc.log"      /* default log file name */

/* WinSock specific global data structure */

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

/* global data */

int server_port = 0;                  /* TCP port that server is using */
int console_output = TRUE;            /* echo messages to console? */
char s_log_file[128];                 /* current log file */

struct sloc_entry                     /* link list of socket servers */
   {
   char *service_name;                /* name of socket service */
   char *host_name;                   /* machine host name */
   int port_number;                   /* port that is being serviced */
   char *ip_addr;                     /* IP address (might be null) */
   struct sloc_entry *next;           /* next link */
   };

struct sloc_entry *sloc_base;         /* root/base node pointer */

#define SLOC_NULL ((struct sloc_entry *)NULL)

/* function prototypes */

int main(int, char **);
int parse_comline(int, char **);
int srv_initialize(void);
int process_request(void);
int do_comm(int, char *);
void s_log_off(void);
void s_log_on(char *);
void s_log_status(void);
void s_add(char *);
void s_delete(char *);
void s_config_add(char *);
void s_config_delete(char *);
void s_find(char *);
void s_config_find(char *);
void s_find_list(char *);
void s_get_list(void);
void s_put_list(char *);
void s_config_get_list(void);
void s_config_put_list(char *);
int s_terminate(char *);
void s_trans_num(void);
void s_connect_num(void);
int do_reply_int(int, int);
int do_reply_long(int, long);
int do_reply_code(int);
int do_reply_char(int, char *char_value);
int s_ll_put_list(char *);
int s_ll_get_first(char *, char *, int *, char *);
int s_ll_get_nentries(int *);
int s_ll_get_nth(int, char *, char *, int *, char *);
int s_ll_add(char *, char *, int, char *);
int s_ll_port_exist(int);
int s_ll_delete(int);
void s_ll_delete_all(void);
void s_ll_debug(void);
void s_config_debug(void);
void term_app(void);

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

   /* indicate initialization in progress */

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

   /* set default log file name */

   strcpy(s_log_file, SL_LOG_FILE);

   /* parse any command line parameters */

   if (argc > 1)
      if (!parse_comline(argc, argv))
         {
         (void)appinit_stop(SL_SERVICE_NAME);
         return(0);
         }

   /* initialize private logging */

   if (!log_start(s_log_file))
      {
      printf("%s:error initializing log file. Program abort\n",
             SL_SERVICE_NAME);
      (void)appinit_stop(SL_SERVICE_NAME);
      return(0);
      }

   /* set logger console output flag */

   log_console(console_output);

   /* build logo string based on platform */

#ifndef OS_UNIX
   /* non-Unix */

   log_file_date("%s Socket Locate Server for %s Version %s",
                 SL_SERVICE_NAME, PLATFORM_STRING, VERSION);
#else
   /* Unix */

   log_file_date("%s Socket Locate Server for %s Version %s",
                 SL_SERVICE_NAME, SUB_PLATFORM_STRING,VERSION);
#endif

   log_file_date("By Rick Smereka, Copyright (c) 2000-2006");

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

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

   /* initialize the server on the TCP socket level, note that
      the hostname 'localhost' is returned to us */

   if (!ipc_init(localHost, server_port))
      {
      (void)appinit_stop(SL_SERVICE_NAME);
      term_app();
      return(6);
      }

   log_file_date("%s:server on \"%s\" servicing TCP port %d (decimal)",
                 SL_SERVICE_NAME, localHost, server_port);
   log_file_date("%s:socket locate server started", SL_SERVICE_NAME);
   log_file_date("%s comes with ABSOLUTELY NO WARRANTY", SL_SERVICE_NAME);
   log_file_date("This is free software, and you are welcome to redistribute it");
   log_file_date("under certain conditions; see \"gpl.txt\" for information.");

   /* turn off log as this is used only for debugging */

   //log_end();

   /* indicate initialization complete */

   (void)appinit_stop(SL_SERVICE_NAME);

   // 'ipc_server_wait' is an infinite loop

   ipc_server_wait();
   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;

   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 file switch, "
                         "ignoring\n", SL_SERVICE_NAME);

               parms++;

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

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

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

            default:
               printf("%s:unknown switch[%s], program abort\n",
                      SL_SERVICE_NAME, c_parm[parms]);
               return(FALSE);
            };
         }
      else
         {
         printf("%s:unknown parameter[%s], program abort\n",
                SL_SERVICE_NAME, c_parm[parms]);
         return(FALSE);
         }

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

   return(TRUE);
}

int srv_initialize(void)
{
   /* Initialize the server by reading the list of socket
      locate servers and making sure there is an entry
      for this server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char mname[] = "srv_initialize";
   char localhost[128], mes[128], hname[128], ip_ad[50];
   char *thelist;
   int i, ret, nentries, port_num;

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

   /* load list of socket locate servers */

   if ((ret = sl_config_read(SL_CONFIG_FILE_NAME)) != SL_OK)
      {
      sl_code_string(ret, mes);
      log_file_date("%s:error reading socket locate "
                    "server list,rc=%s. Program abort", mname, mes);
      return(FALSE);
      }

   /* startup WinSock (if Windoze) */

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

	/* get host name of this machine */

   if (gethostname(localhost, 127))
      {
      log_file_date("%s:unable to resolve local "
                    "host name. Program abort", mname);
      return(FALSE);
      }

   log_file_date("%s:host name is '%s'", mname, localhost);

   /* get the number of socket locate servers from config file */

   if ((ret = sl_config_get_nentries(&nentries)) != SL_OK)
      {
      sl_code_string(ret, mes);
      log_file_date("%s:error getting number of "
                    "socket locate servers. Program "
                    "abort,rc=%s", mname, mes);
      return(FALSE);
      }

   log_file_date("%s:number of config entries is %d", mname, nentries);

   /* go through the list looking for a host name match */

   for(i = 1; i <= nentries; i++)
      {
      if ((ret = sl_config_get_nth(i, hname, &port_num, ip_ad)) != SL_OK)
         {
         sl_code_string(ret, mes);
         log_file_date("%s:error socket locate server "
                       "entry #%d. Program abort,rc=%s", mname, i, mes);
         return(FALSE);
         }

      /* if the host name matches, load the port number used */

      if (!strcmp(hname, localhost))
         server_port = port_num;
      }

   /* if all socket locate servers were checked and no host
      name match, error */

   if (server_port == 0)
      {
      log_file_date("%s:no matching host name found in '%s'. "
                    "Program abort.", mname,
                    SL_CONFIG_FILE_NAME);
      return(FALSE);
      }

   /* tell config API that this is the local server */

   if ((ret = sl_config_set_ils(server_port, TRUE)) != SL_OK)
      {
      sl_code_string(ret, mes);
      log_file_date("%s:bad rc from 'sl_config_set_ils',rc=%s",
                    mname, mes);
      return(FALSE);
      }

   sloc_base = SLOC_NULL;

   /* use 'socloc' client API to find first active 'socloc' server
      its okay if there are no other 'socloc' servers present */

   if ((ret = sloc_find_first_active()) != SL_OK)
      {
      sl_code_string(ret, mes);
      log_file_date("%s:no other active 'socloc' servers,rc=%s",
                    mname, mes);
      }
   else
      {
      /* when other 'socloc' servers are present, don't trust their
         'ipc' config files, send a config add command to each active
         'socloc' server then update this server's 'ipc' config file
         and list from the current active 'socloc' server, re-set
         the 'is_local_server' flag for this server and finally,
         get and load the current socket server list */

      log_file_date("%s:found other active 'socloc' servers", mname);
      
      /* get this machine IP address */
      
      if (!ip_host2ip(localhost, ip_ad))
         {
         log_file_date("%s:unable to obtain this machine IP address",
                       mname);
         return(FALSE);
         }
                      
      (void)sloc_bcast_config_add(localhost, server_port, ip_ad);

      /* commented out the update of the config file after noticing
         some cases of the config file being updated from a
         more incomplete version */

      /* if ((ret = sloc_config_update()) != SL_OK)
         {
         sl_code_string(ret, mes);
         log_file_date("%s:bad rc from 'sloc_config_update',rc=%s",
                       mname, mes);
         return(FALSE);
         } */

      if ((thelist = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
         {
         log_file_date("%s:alloc fail[thelist]", mname);
         return(FALSE);
         }

      /* get the config list from the first active server */

      if ((ret = sloc_config_get_list(thelist)) != SL_OK)
         {
         log_file_date("%s:bad rc[%d] from 'sloc_config_get_list'",
                    mname, ret);
         free(thelist);
         return(FALSE);
         }

      /* create a new list in memory */

      if ((ret = sl_config_put_list(thelist)) != SL_OK)
         {
         log_file_date("%s:bad rc[%d] from 'sl_config_put_list'",
                    mname, ret);
         free(thelist);
         return(FALSE);
         }

      if ((ret = sl_config_set_ils(server_port, TRUE)) != SL_OK)
         {
         sl_code_string(ret, mes);
         log_file_date("%s:bad rc from 'sl_config_set_ils',rc=%s",
                       mname, mes);
         return(FALSE);
         }

      /* if list is empty, that's ok */

      if ((ret = sloc_get_list(thelist)) != SL_OK)
         if (ret == SL_NOT_FOUND)
            log_file_date("%s:socket server list is empty", mname);
         else
            {
            free(thelist);
            sl_code_string(ret, mes);
            log_file_date("%s:bad rc[%s] from 'sloc_get_list'", mname, mes);
            return(FALSE);
            }

      if (ret == SL_OK)
         if ((ret = s_ll_put_list(thelist)) != SL_OK)
            {
            free(thelist);
            sl_code_string(ret, mes);
            log_file_date("%s:bad rc[%s] from 's_ll_put_list'", mname, mes);
            return(FALSE);
            }

      free(thelist);
      }

   sl_config_debug();
   s_ll_debug();
   log_file_date("%s:normal exit[TRUE]", mname);
   return(TRUE);
}

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;

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

   if ((messbuf = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      (void)do_reply_code(SL_MEMORY_FAIL);
      log_file_date("%s:alloc fail[messbuf]", mname);
      return(FALSE);
      }

   memset(messbuf, 0, SL_MAXCOMMAND);

   // get the message from the client

   if ((nbytes = ipc_recv_data(messbuf)) == 0)
      {
      free(messbuf);
      log_file_date("%s:connection closed by client", mname);
      return(-1);
      }

   messbuf[nbytes] = EOS;
   log_file_date("%s:recv %s,l=%d", mname, messbuf, strlen(messbuf));
   nwords = command_words(messbuf);

   // no command words, empty string

   if (!nwords)
      {
      (void)do_reply_code(SL_INVALID_FUNCTION);
      free(messbuf);
      log_file_date("%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(SL_INTERNAL_ERROR);
      log_file_date("%s:error getting first command word", mname);
      return(FALSE);
      }

   if (!qatoi(tmp, &mestype))
      {
      (void)do_reply_code(SL_INVALID_FUNCTION);
      log_file_date("%s:command signature not a number", mname);
      free(messbuf);
      return(FALSE);
      }

   /* pass entire string including command number */

   ret = do_comm(mestype, messbuf);
   free(messbuf);
   log_file_date("%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;

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

   switch(mestype)
      {
      case SL_SEND_ADD:
         s_add(slm);
         break;

      case SL_SEND_DELETE:
         s_delete(slm);
         break;

      case SL_SEND_CONFIG_ADD:
         s_config_add(slm);
         break;

      case SL_SEND_CONFIG_DELETE:
         s_config_delete(slm);
         break;

      case SL_SEND_FIND:
         s_find(slm);
         break;

      case SL_SEND_CONFIG_FIND:
         s_config_find(slm);
         break;

      case SL_SEND_FIND_LIST:
         s_find_list(slm);
         break;

      case SL_SEND_GET_LIST:
         s_get_list();
         break;

      case SL_SEND_PUT_LIST:
         s_put_list(slm);
         break;

      case SL_SEND_CONFIG_GET_LIST:
         s_config_get_list();
         break;

      case SL_SEND_CONFIG_PUT_LIST:
         s_config_put_list(slm);
         break;

      case SL_SEND_STATUS:
         (void)do_reply_code(SL_OK);
         break;

      case SL_SEND_LOG_OFF:
         s_log_off();
         break;

      case SL_SEND_LOG_ON:
         s_log_on(slm);
         break;

      case SL_SEND_LOG_STATUS:
         s_log_status();
         break;

      case SL_SEND_SERVICE_NAME:
         (void)do_reply_char(SL_OK, SL_SERVICE_NAME);
         break;

      case SL_SEND_VERSION:
         (void)do_reply_char(SL_OK, VERSION);
         break;

      case SL_SEND_DUMP_DEBUG:
         s_ll_debug();
         (void)do_reply_code(SL_OK);
         break;

      case SL_SEND_CONFIG_DUMP_DEBUG:
         s_config_debug();
         (void)do_reply_code(SL_OK);
         break;

      case SL_SEND_TRANS_NUM:
         s_trans_num();
         break;

      case SL_SEND_CONNECT_NUM:
         s_connect_num();
         break;

      case SL_SEND_TERM:
         if (s_terminate(slm))
            ret = TRUE;

         break;

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

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

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

   char mname[] = "s_log_off";
   int ret;

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

   if (!is_log_active())
      ret = SL_LOG_ALREADY_OFF;
   else
      {
      log_end();
      ret = SL_OK;
      }

   (void)do_reply_code(ret);
   log_file_date("%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;
   
   log_file_date("%s:enter", mname);
   
   if (is_log_active())
      ret = SL_LOG_ALREADY_ON;
   else
      {
      if (command_words(comm) > 1)
         if (!command_word(comm, s_log_file, 2))
            {
            log_file_date("%s:error getting log file name", mname);
            (void)do_reply_code(SL_INTERNAL_ERROR);
            return;
            }
                     
      if (!log_start(s_log_file))
         {
         (void)do_reply_code(SL_LOG_ERROR);
         log_file_date("%s:error starting log", mname);
         return;
         }
         
      ret = SL_OK;
      }
       
   (void)do_reply_code(ret);
   log_file_date("%s:normal exit rc[%d]", mname, ret);
}

void s_log_status(void)
{
   /* Get the current log status (on or off) along with the
      current log file name. */
   
   char mname[] = "s_log_status";
   char reply[128];
   
   log_file_date("%s:enter", mname);
   
   if (is_log_active())
      {      
      if (words(s_log_file) > 1)
         sprintf(reply, "1 '%s'", s_log_file);
      else
         sprintf(reply, "1 %s", s_log_file);
         
      (void)do_reply_char(SL_OK, reply);
      }
   else
      {
      if (words(s_log_file) > 1)
         sprintf(reply, "0 '%s'", s_log_file);
      else
         sprintf(reply, "0 %s", s_log_file);
         
      (void)do_reply_char(SL_OK, reply);
      }
      
   log_file_date("%s:normal exit rc[0],reply=%s", mname, reply);
}

void s_add(char *comm)
{
   /* Add an entry to the list of available socket servers.
      Syntax:
      
         SL_SEND_ADD orig_flag service_name host_name port_number[ ip_addr]
         
      Where 'orig_flag' indicates the origin (0=server,1=client).
      If the origin is the client, a broadcast will be sent to
      all other known 'socloc' servers. Line will be parsed with
      'command_word' functions so any parameter with a space must
      be quoted. */
   
   char mname[] = "s_add";
   char *sname, *hname, *port_char, *ip_ad;
   char orig_flag_char[25], mess[150];
   int orig_flag, port, len, ret, sockrc;
   
   log_file_date("%s:enter", mname);
   
   /* must be at least 5 command words */
   
   if (command_words(comm) < 5)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
   
   /* get 'orig_flag' */
   
   if (!command_word(comm, orig_flag_char, 2))
      {
      log_file_date("%s:parse error[orig_flag_char]", mname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }

   if (!qatoi(orig_flag_char, &orig_flag))
      {
      log_file_date("%s:'orig_flag' not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
            
   if (orig_flag != 0 && orig_flag != 1)
      {
      log_file_date("%s:'orig_flag' must be 0 or 1", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
      
   /* get service name */
   
   if ((len = command_wordlen(comm, 3)) <= 0)
      {
      log_file_date("%s:service name invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
      
   if ((sname = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[sname]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
      
   if (!command_word(comm, sname, 3))
      {
      log_file_date("%s:parse error[sname]", mname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }
    
   //log_file_date("%s:service name=%s", mname, sname);
     
   /* get host name */
   
   if ((len = command_wordlen(comm, 4)) <= 0)
      {
      log_file_date("%s:host name invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      free(sname);
      return;
      }
      
   if ((hname = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[hname]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      free(sname);
      return;
      }
      
   if (!command_word(comm, hname, 4))
      {
      log_file_date("%s:parse error[hname]", mname);
      free(sname);
      free(hname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }   
   
   //log_file_date("%s:host name=%s", mname, hname);
      
   /* get port number */      
   
   if ((len = command_wordlen(comm, 5)) <= 0)
      {
      log_file_date("%s:port number invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);      
      free(sname);
      free(hname);
      return;
      }
      
   if ((port_char = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[port_char]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      free(sname);
      free(hname);
      return;
      }
      
   if (!command_word(comm, port_char, 5))
      {
      log_file_date("%s:parse error[port_char]", mname);
      free(sname);
      free(hname);
      free(port_char);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }
      
   if (!qatoi(port_char, &port))
      {
      log_file_date("%s:port not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      free(sname);
      free(hname);
      free(port_char);
      }
      
   free(port_char);
   
   /* get ip address (if present) */
   
   if (command_words(comm) > 5)
      {
      if ((len = command_wordlen(comm, 6)) <= 0)
         {
         log_file_date("%s:ip address invalid", mname);
         (void)do_reply_code(SL_INVALID_PARAMETER);      
         free(sname);
         free(hname);
         return;
         }
      
      if ((ip_ad = (char *)malloc(len + 1)) == (char *)NULL)
         {
         log_file_date("%s:alloc fail[ip_ad]", mname);
         (void)do_reply_code(SL_MEMORY_FAIL);
         free(sname);
         free(hname);
         return;
         }
      
      if (!command_word(comm, ip_ad, 6))
         {
         log_file_date("%s:parse error[ip_ad]", mname);
         free(sname);
         free(hname);
         free(ip_ad);
         (void)do_reply_code(SL_INTERNAL_ERROR);
         return;
         }
         
      if (!isip(ip_ad))
         {
         log_file_date("%s:invalid ip address", mname);
         free(sname);
         free(hname);
         free(ip_ad);
         (void)do_reply_code(SL_INVALID_PARAMETER);
         return;
         }
      }
   else
      ip_ad = (char *)NULL;

   ret = s_ll_add(sname, hname, port, ip_ad);
   
   if ((sockrc = do_reply_code(ret)) != SL_OK)
      log_file_date("%s:bad rc[%d] from 'do_reply_code'", mname, sockrc);
   
   /* if add was successful and the 'orig_flag' indicates
      a client source, broadcast this addition to
      all other 'socloc' servers */
      
   if (ret == SL_OK && orig_flag)
      if ((sockrc = sloc_bcast_add(sname, hname, port, ip_ad)) != SL_OK)
         log_file_date("%s:bad rc[%d] from 'sloc_bcast_add'", mname, sockrc);
      
   if (ip_ad != (char *)NULL)
      free(ip_ad);

   free(sname);
   free(hname);
   s_ll_debug();
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit[%s]", mname, mess);
}

void s_delete(char *comm)
{
   /* Delete a socket server entry.
      Syntax:
      
         SL_SEND_DELETE orig_flag port_number
         
    */
   
   char mname[] = "s_delete";
   char *port_char;
   char orig_flag_char[25], mess[150];
   int orig_flag, port, len, ret, sockrc;
   
   log_file_date("%s:enter", mname);
   
   /* must be at least 3 command words */
   
   if (command_words(comm) < 3)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }

   /* get 'orig_flag' */
   
   if (!command_word(comm, orig_flag_char, 2))
      {
      log_file_date("%s:parse error[orig_flag_char]", mname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }

   if (!qatoi(orig_flag_char, &orig_flag))
      {
      log_file_date("%s:'orig_flag' not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
            
   if (orig_flag != 0 && orig_flag != 1)
      {
      log_file_date("%s:'orig_flag' must be 0 or 1", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
            
   /* get port number */      
   
   if ((len = command_wordlen(comm, 3)) <= 0)
      {
      log_file_date("%s:port number invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);      
      return;
      }
      
   if ((port_char = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[port_char]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
      
   if (!command_word(comm, port_char, 3))
      {
      log_file_date("%s:parse error[port_char]", mname);
      free(port_char);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }
      
   if (!qatoi(port_char, &port))
      {
      log_file_date("%s:port not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      free(port_char);
      }
      
   free(port_char);
   ret = s_ll_delete(port);

   if ((sockrc = do_reply_code(ret)) != SL_OK)
      log_file_date("%s:bad rc[%d] from 'do_reply_code'", mname, sockrc);
         
   /* if delete was successful and the 'orig_flag' indicates
      a client source, broadcast this deletion to all
      other 'socloc' servers */
      
   if (ret == SL_OK && orig_flag)
      if ((sockrc = sloc_bcast_delete(port)) != SL_OK)
         log_file_date("%s:bad rc[%d] from 'sloc_bcast_delete'", mname, sockrc);         
    
   s_ll_debug();  
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc[%s]", mname, mess);
}

void s_config_add(char *comm)
{
   /* Add an entry to the config list of available socloc servers.
      Syntax:
      
         SL_SEND_CONFIG_ADD orig_flag host_name port_number[ ip_addr]
         
      Line will be parsed with 'command_word' functions so any
      parameter with a space must be quoted. */
   
   char mname[] = "s_config_add";
   char *hname, *port_char, *ip_ad;
   char mess[150], orig_flag_char[50];
   int port, len, ret, orig_flag, sockrc;
   
   log_file_date("%s:enter", mname);
   
   /* must be at least 4 command words */
   
   if (command_words(comm) < 4)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
        
   /* get 'orig_flag' */
   
   if (!command_word(comm, orig_flag_char, 2))
      {
      log_file_date("%s:parse error[orig_flag_char]", mname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }

   if (!qatoi(orig_flag_char, &orig_flag))
      {
      log_file_date("%s:'orig_flag' not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
            
   if (orig_flag != 0 && orig_flag != 1)
      {
      log_file_date("%s:'orig_flag' must be 0 or 1", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }

   /* get host name */   
   
   if ((len = command_wordlen(comm, 3)) <= 0)
      {
      log_file_date("%s:host name invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
      
   if ((hname = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[hname]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
      
   if (!command_word(comm, hname, 3))
      {
      log_file_date("%s:parse error[hname]", mname);
      free(hname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }   
   
   //log_file_date("%s:host name=%s", mname, hname);
      
   /* get port number */      
   
   if ((len = command_wordlen(comm, 4)) <= 0)
      {
      log_file_date("%s:port number invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);      
      free(hname);
      return;
      }
      
   if ((port_char = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[port_char]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      free(hname);
      return;
      }
      
   if (!command_word(comm, port_char, 4))
      {
      log_file_date("%s:parse error[port_char]", mname);
      free(hname);
      free(port_char);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }
      
   if (!qatoi(port_char, &port))
      {
      log_file_date("%s:port not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      free(hname);
      free(port_char);
      }
      
   free(port_char);
   
   /* get ip address (if present) */
   
   if (command_words(comm) > 4)
      {
      if ((len = command_wordlen(comm, 5)) <= 0)
         {
         log_file_date("%s:ip address invalid", mname);
         (void)do_reply_code(SL_INVALID_PARAMETER);      
         free(hname);
         return;
         }
      
      if ((ip_ad = (char *)malloc(len + 1)) == (char *)NULL)
         {
         log_file_date("%s:alloc fail[ip_ad]", mname);
         (void)do_reply_code(SL_MEMORY_FAIL);
         free(hname);
         return;
         }
      
      if (!command_word(comm, ip_ad, 5))
         {
         log_file_date("%s:parse error[ip_ad]", mname);
         free(hname);
         free(ip_ad);
         (void)do_reply_code(SL_INTERNAL_ERROR);
         return;
         }
         
      if (!isip(ip_ad))
         {
         log_file_date("%s:invalid ip address", mname);
         free(hname);
         free(ip_ad);
         (void)do_reply_code(SL_INVALID_PARAMETER);
         return;
         }
      }
   else
      ip_ad = (char *)NULL;

   /* since 'sl_config_add' only checks for duplicate 'socloc'
      ports, we check for duplicate in socket server list  */
   
   if ((ret = s_ll_port_exist(port)) != SL_OK)
      {
      log_file_date("%s:bad ret from s_ll_port_exist,rc=%d",
                    mname, ret);
      free(hname);
      free(ip_ad);
      (void)do_reply_code(ret);
      return;
      }
      
   ret = sl_config_add(hname, port, ip_ad);   

   if ((sockrc = do_reply_code(ret)) != SL_OK)
      log_file_date("%s:bad rc[%d] from 'do_reply_code'", mname, sockrc);
         
   if (ret == SL_OK && orig_flag)
      if ((sockrc = sloc_bcast_config_add(hname, port, ip_ad)) != SL_OK)
         log_file_date("%s:bad rc[%d] from 'sloc_bcast_config_add'",
                       mname, sockrc);

   free(hname);
   
   if (ip_ad != (char *)NULL)
      free(ip_ad);
      
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc=%s", mname, mess);
   sl_config_debug();
}

void s_config_delete(char *comm)
{
   /* Delete a config socket server entry.
      Syntax:
      
         SL_SEND_CONFIG_DELETE orig_flag port_number
         
    */
   
   char mname[] = "s_config_delete";
   char *port_char;
   char mess[150], orig_flag_char[50];
   int port, len, ret, orig_flag, sockrc;
   
   log_file_date("%s:enter", mname);
   
   /* must be at least 3 command words */
   
   if (command_words(comm) < 3)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
      
   /* get 'orig_flag' */
   
   if (!command_word(comm, orig_flag_char, 2))
      {
      log_file_date("%s:parse error[orig_flag_char]", mname);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }

   if (!qatoi(orig_flag_char, &orig_flag))
      {
      log_file_date("%s:'orig_flag' not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
            
   if (orig_flag != 0 && orig_flag != 1)
      {
      log_file_date("%s:'orig_flag' must be 0 or 1", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }

   /* get port number */      
   
   if ((len = command_wordlen(comm, 3)) <= 0)
      {
      log_file_date("%s:port number invalid", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);      
      return;
      }
      
   if ((port_char = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[port_char]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
      
   if (!command_word(comm, port_char, 3))
      {
      log_file_date("%s:parse error[port_char]", mname);
      free(port_char);
      (void)do_reply_code(SL_INTERNAL_ERROR);
      return;
      }
      
   if (!qatoi(port_char, &port))
      {
      log_file_date("%s:port not numeric", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      free(port_char);
      }
      
   free(port_char);
   ret = sl_config_delete(port);

   if ((sockrc = do_reply_code(ret)) != SL_OK)
      log_file_date("%s:bad rc[%d] from 'do_reply_code'", mname, sockrc);
         
   if (ret == SL_OK && orig_flag)
      if ((sockrc = sloc_bcast_config_delete(port)) != SL_OK)
         log_file_date("%s:bad rc[%d] from 'sloc_bcast_config_delete'",
                       mname, sockrc);
   
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc[%s]", mname, mess);
   sl_config_debug();
}

void s_find(char *comm)
{
   /* Find a socket server by one or more details. All parameters
      are optional but at least one parameter must be supplied. Syntax:
      
      SL_SEND_FIND [service sname][ host hname][ port port_num][ ip ip_ad]
      
      Any parameter containing spaces must be quoted. The first server
      matching the supplied parameters will be used.
         
      Function replies with the following string upon success:
      
         'service_name' 'host_name' port_number[ ip_addr]
         
      Note that the string is space delimited. Since there is a
      possibility of the host name having
      spaces, this word will always be quoted. Function will check
      the status of the matching server. If the server is not responding,
      the entry will be deleted from this and all other 'socloc'
      servers. */
   
   char mname[] = "s_find";
   char *reply, *sname, *hname, *ip_ad;
   char mess[150];
   int pos, nwords, port, len, ret, i, found = FALSE;
   
   log_file_date("%s:enter", mname);
   // s_ll_debug();
   // s_config_debug();
   nwords = command_words(comm);
   
   /* must be at least 3 command words */
   
   if (nwords < 3)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_char(SL_INVALID_PARAMETER, "nok");
      return;
      }                      

   /* alloc details */

   if ((sname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[sname]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
         
   if ((hname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[hname]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      free(sname);
      return;
      }
         
   if ((ip_ad = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[ip_ad]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      free(sname);
      free(hname);
      return;
      }

   /* set starting position in command string of parameters */
   
   pos = command_indxword(comm, 2);
   
   /* if the first char in the second word is a quote,
      'command_indxword' will skip over this, we need to
      backup one position */
      
   if (comm[pos - 1] == '\"' || comm[pos - 1] == '\'')
      pos--;

   /* loop looking for a match and checking the status of the
      matching server, since the same vars are used from parse
      and match, we re-parse every iteration (sloppy, I know) */
      
   while(!found)
      {
      sname[0] = EOS;
      hname[0] = EOS;
      ip_ad[0] = EOS;
      port = 0;
   
      if ((ret = sconnect_parse(&comm[pos], TRUE, sname, hname,
                                &port, ip_ad)) != SP_OK)
         {
         sp_code_string(ret, mess);
         log_file_date("%s:bad rc[%s] from 'sconnect_parse", mname, mess);
         (void)do_reply_char(SL_INVALID_PARAMETER, mess);
         free(sname);
         free(hname);
         free(ip_ad);
         return;
         }

      if ((ret = s_ll_get_first(sname, hname, &port, ip_ad)) != SL_OK)
         {
         sl_code_string(ret, mess);
         log_file_date("%s:s_ll_get_first failed,rc=%s", mname, mess);
         (void)do_reply_char(ret, mess);
         free(sname);
         free(hname);
         free(ip_ad);
         return;
         }

      log_file_date("%s:found match,hname=%s,port=%d", mname, hname, port);
      
      /* we have found a match, check the server status */
   
      if ((ret = sloc_custom_status(hname, port)) != SL_OK)
         {
         sl_code_string(ret, mess);
         log_file_date("%s:bad rc[%s] from 'sloc_custom_status'", mname,
                       mess);
      
         /* status failed, delete the entry and broadcast
            this delete to all 'socloc' servers */
         
         if (s_ll_delete(port) == SL_OK)   
            if ((i = sloc_bcast_delete(port)) != SL_OK)
               log_file_date("%s:bad rc[%d] from 'sloc_bcast_delete'",
                             mname, i);
         }
      else
         found = TRUE;
      }
      
   len = strlen(sname) + strlen(hname) + strlen(ip_ad) + 25;
   
   if ((reply = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[reply]", mname);
      (void)do_reply_char(SL_INTERNAL_ERROR, "nok");
      free(sname);
      free(hname);
      free(ip_ad);
      return;
      }
      
   if (!strlen(ip_ad))
      sprintf(reply, "'%s' '%s' %d", sname, hname, port);
   else
      sprintf(reply, "'%s' '%s' %d %s", sname, hname, port, ip_ad);

   free(sname);      
   (void)do_reply_char(ret, reply);
   free(hname);
   free(ip_ad);
   free(reply);
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc=%s", mname, mess);
}

void s_config_find(char *comm)
{
   /* Find a 'socloc' server by one or more details. At least one
      optional parameter must be present. Syntax:
      
      SL_SEND_CONFIG_FIND[ host host_name][ port port_num][ ip ip_ad]
      
      Any parameter containing spaces must be quoted.
         
      Function replies with the following string upon success:
      
         'host_name' port_number[ ip_addr]
         
      Note that the string is space delimited. Since there is a
      possibility of the host name having spaces, this word will
      always be quoted. Since it is possible that the find will
      match this 'socloc' server, the status of the matched server
      is not checked. */
   
   char mname[] = "s_config_find";
   char *reply, *hname, *ip_ad;
   char mess[150];
   int pos, nwords, port, len, ret, found = FALSE;
   
   log_file_date("%s:enter", mname);
   nwords = command_words(comm);
   
   /* must be at least 3 command words */
   
   if (nwords < 3)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_char(SL_INVALID_PARAMETER, "nok");
      return;
      }          

   /* alloc details */
   
   if ((hname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[hname]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
         
   if ((ip_ad = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[ip_ad]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      free(hname);
      return;
      }

   /* set starting position in command string of parameters */
      
   pos = command_indxword(comm, 2);
   
   /* if the first char in the second word is a quote,
      'command_indxword' will skip over this, we need to
      backup one position */
      
   if (comm[pos - 1] == '\"' || comm[pos - 1] == '\'')
      pos--;      
   
   hname[0] = EOS;
   ip_ad[0] = EOS;
   port = 0;
   
   /* detect parameters */
      
   if ((ret = sconnect_parse(&comm[pos], FALSE, (char *)NULL, hname,
                             &port, ip_ad)) != SP_OK)
      {
      sp_code_string(ret, mess);
      log_file_date("%s:bad rc[%s] from 'sconnect_parse", mname, mess);
      (void)do_reply_char(SL_INVALID_PARAMETER, mess);
      free(hname);
      free(ip_ad);
      return;
      }

   if ((ret = sl_config_find(hname, &port, ip_ad)) != SL_OK)
      {
      sl_code_string(ret, mess);
      log_file_date("%s:bad rc[%s] from 'sl_config_find'", mname, mess);
      (void)do_reply_char(ret, mess);
      free(hname);
      free(ip_ad);
      return;
      }

   log_file_date("%s:found match,hname=%s,port=%d", mname, hname, port);        
   len = strlen(hname) + strlen(ip_ad) + 10;
   
   if ((reply = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[reply]", mname);
      (void)do_reply_char(SL_INTERNAL_ERROR, "nok");
      free(hname);
      free(ip_ad);
      return;
      }
      
   if (!strlen(ip_ad))
      sprintf(reply, "'%s' %d", hname, port);
   else
      sprintf(reply, "'%s' %d %s", hname, port, ip_ad);

   (void)do_reply_char(ret, reply);
   free(hname);
   free(ip_ad);
   free(reply);
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc=%s", mname, mess);
}

void s_find_list(char *comm)
{
   /* Find one or more socket servers by one or more details. Syntax:
      
      SL_SEND_FIND_LIST[ service service_name][ host host_name]
                       [ port port_num][ ip ip_ad]
      
      Any parameter containing spaces must be quoted. At least one
      keyword and its value must be present. Function will locate and
      build a list of all socket servers matching the details.
         
      Function replies with a list of socket servers matching. Each
      list entry consists of the following string:
      
         'host_name' port_number[ ip_addr]
         
      List entries are delimited by 'SL_LIST_DELIM'. Since there is a
      possibility of the host name having
      spaces, this word will always be quoted. */

   struct sloc_entry *rov = sloc_base;   
   char mname[] = "s_find_list";
   char *anentry, *reply, *sname, *hname, *ip_ad;
   char mess[150];
   int pos, nwords, port, len, ret, i, found;
   int nentries = 0, out_pos = 0;
   
   log_file_date("%s:enter", mname);
   nwords = command_words(comm);
   
   /* must be at least 3 command words (command code, keyword and value) */
   
   if (nwords < 3)
      {
      log_file_date("%s:incorrect number of parameters", mname);
      (void)do_reply_char(SL_INVALID_PARAMETER, "nok");
      return;
      }

   /* make sure list is not empty */
   
   if (rov == SLOC_NULL)
      {
      log_file_date("%s:locate server list empty", mname);
      (void)do_reply_char(SL_NOT_FOUND, "nok");
      return;
      }
             
   /* alloc details */
   
   if ((sname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[sname]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }

   if ((hname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[hname]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      free(sname);
      return;
      }
         
   if ((ip_ad = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[ip_ad]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      free(sname);
      free(hname);
      return;
      }

   sname[0] = EOS;
   hname[0] = EOS;
   ip_ad[0] = EOS;
   
   /* get the position of the second command word */
   
   pos = command_indxword(comm, 2);
   
   /* if the first char in the second word is a quote,
      'command_indxword' will skip over this, we need to
      backup one position */
      
   if (comm[pos - 1] == '\"' || comm[pos - 1] == '\'')
      pos--;

   /* parse details */
               
   if ((ret = sconnect_parse(&comm[pos], TRUE, sname, hname, &port,
        ip_ad)) != SP_OK)
      {
      sp_code_string(ret, mess);
      log_file_date("%s:bad rc[%s] from 'sconnect_parse", mname, mess);
      free(sname);
      free(hname);
      free(ip_ad);
      (void)do_reply_char(SL_INVALID_PARAMETER, mess);
      return;
      }

   if ((reply = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[reply]", mname);
      (void)do_reply_char(SL_INTERNAL_ERROR, "nok");
      free(sname);
      free(hname);
      free(ip_ad);
      return;
      }

   if ((anentry = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[anentry]", mname);
      free(sname);
      free(hname);
      free(ip_ad);
      free(reply);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }

   reply[0] = EOS;
      
   /* loop through list looking for matches */   
   
   while(rov != SLOC_NULL)
      {
      found = TRUE;
      
      if (strlen(sname))
         if (!strcmp(sname, rov->service_name))
            found = TRUE;
         else
            found = FALSE;
         
      if (found && strlen(hname))
         if (!strcmp(hname, rov->host_name))
            found = TRUE;
         else
            found = FALSE;      

      if (found && port > 0)
         if (port == rov->port_number)
            found = TRUE;
         else
            found = FALSE;
            
      if (found && ip_ad != (char *)NULL && strlen(ip_ad))
         if (rov->ip_addr == (char *)NULL)
            found = FALSE;
         else
            if (!strlen(rov->ip_addr))
               found = FALSE;
            else
               if (!strcmp(ip_ad, rov->ip_addr))
                  found = TRUE;
               else
                   found = FALSE;
                                      
      if (found)
         {
         nentries++;
         
         if (rov->ip_addr != (char *)NULL && strlen(rov->ip_addr))
            sprintf(anentry, "'%s' '%s' %d %s", rov->service_name,
                    rov->host_name, rov->port_number, rov->ip_addr);
         else
            sprintf(anentry, "'%s' '%s' %d", rov->service_name,
                    rov->host_name, rov->port_number);

         if (nentries > 1)
            reply[out_pos++] = SL_LIST_DELIM;

         /* load this entry into 'reply' */

         len = strlen(anentry);
             
         for(i = 0; i < len; i++, out_pos++)
            reply[out_pos] = anentry[i];         
         }                      

      rov = rov->next;
      }

   if (!nentries)
      {
      log_file_date("%s:no match", mname);
      (void)do_reply_char(SL_NOT_FOUND, "nok");
      free(sname);
      free(hname);
      free(ip_ad);
      free(reply);
      free(anentry);
      return;
      }

   free(sname);
   free(hname);
   free(ip_ad);
   free(anentry);
   (void)do_reply_char(ret, reply);
   free(reply);
   sl_code_string(ret, mess);
   log_file_date("%s:normal exit,rc[%s]", mname, mess);
}

void s_get_list(void)
{
   /* Get the 'socloc' list. The list is transmitted with
      each entry delimited by 'SL_LIST_DELIM'. Syntax:
      
         SL_SEND_GET_LIST */
   
   char mname[] = "s_get_list";
   char *reply, *anentry, *sname, *hname, ip_ad[25];
   int ret, nentries, i, j, port, len, out_pos = 0;
   
   log_file_date("%s:enter", mname);
   
   if ((ret = s_ll_get_nentries(&nentries)) != SL_OK)
      {
      log_file_date("%s:bad rc from 's_ll_get_nentries',rc=%d",
                    mname, ret);
      (void)do_reply_char(ret, "nok");
      return;
      }
      
   if ((reply = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[reply]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
      
   if ((anentry = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[anentry]", mname);
      free(reply);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
      
   if ((sname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[sname]", mname);
      free(reply);
      free(anentry);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
      
   if ((hname = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[hname]", mname);
      free(reply);
      free(anentry);
      free(sname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }
      
   reply[0] = EOS;
   
   for(i = 1; i <= nentries; i++)
      {
      if ((ret = s_ll_get_nth(i, sname, hname, &port, ip_ad)) != SL_OK)
         {
         log_file_date("%s:bad rc from 's_ll_get_nth',entry=%d,rc=%d",
                       mname, i, ret);
         free(reply);
         free(anentry);
         free(sname);
         free(hname);
         (void)do_reply_char(ret, "nok");
         return;
         }
         
      if (strlen(ip_ad))
         sprintf(anentry, "'%s' '%s' %d %s", sname, hname, port, ip_ad);
      else
         sprintf(anentry, "'%s' '%s' %d", sname, hname, port);
         
      /* load this entry into 'reply' */

      len = strlen(anentry);
            
      for(j = 0; j < len; j++, out_pos++)
         reply[out_pos] = anentry[j];
         
      if (i < nentries)
         reply[out_pos++] = SL_LIST_DELIM;
      }

   reply[out_pos] = EOS;      
   free(anentry);
   free(sname);
   free(hname);
   (void)do_reply_char(SL_OK, reply);
   free(reply);
   log_file_date("%s:normal exit,rc=SL_OK", mname);
}

void s_put_list(char *comm)
{
   /* Take the received 'socloc' list and write/put it.
      A list entry is expected in the format:
      
         sname hname port[ ip_ad]
         
      Where 'sname' is the service name, 'hname' is the
      host name, port is the TCP port number and the
      optional 'ip_ad' is the IP address. If the
      service name or host name contains spaces, they
      must be quoted.
     
      Each entry is delimited by 'SL_LIST_DELIM'.
      Syntax:
      
         SL_SEND_PUT_LIST list */
         
   char mname[] = "s_put_list";
   char *thelist;
   int pos, len, ret;
   
   log_file_date("%s:enter", mname);
   
   if (command_words(comm) < 2)
      {
      log_file_date("%s:no list", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
     
   /* load just the list */
      
   pos = command_indxword(comm, 2);
   
   /* if the first word in the first entry is a quote,
      'command_indxword' will skip over this, we need
      to backup one position */
      
   if (comm[pos - 1] == '\"' || comm[pos - 1] == '\'')
      pos--;
      
   len = strlen(comm) - pos;
   
   if ((thelist = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[thelist]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
        
   strcpy(thelist, &comm[pos]);
   ret = s_ll_put_list(thelist);
   free(thelist);
   (void)do_reply_code(ret);
   log_file_date("%s:normal exit reply[%d]", mname, ret);
}   

void s_config_get_list(void)
{
   /* Get the 'socloc' config list. The list is transmitted with
      each entry delimited by 'SL_LIST_DELIM'. Syntax:
      
         SL_SEND_GET_LIST */
   
   char mname[] = "s_config_get_list";
   char *reply;
   int ret;
   
   log_file_date("%s:enter", mname);
   
   if ((reply = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      log_file_date("%s:alloc error[reply]", mname);
      (void)do_reply_char(SL_MEMORY_FAIL, "nok");
      return;
      }      

   if ((ret = sl_config_get_list(reply)) != SL_OK)
      (void)do_reply_char(ret, "nok");
   else
      (void)do_reply_char(ret, reply);

   free(reply);
   log_file_date("%s:normal exit,rc=%d", mname, ret);      
}

void s_config_put_list(char *comm)
{
   /* Put/write the 'socloc' config list. Syntax:
      
         SL_SEND_CONFIG_PUT_LIST list
         
      Where 'list' is one or more delimited entries (delimited
      by 'SL_LIST_DELIM') with each being:
      
         hname port[ ip_ad]
         
      where 'hname' is the host name. If it contains spaces, it
      must be quoted. 'Port' is the decimal TCP port number
      and 'ip_ad' is the optional IP address. A reply code
      (without any other value) is sent. */
      
   char mname[] = "s_config_put_list";
   char *thelist;
   int pos, len, ret;
   
   log_file_date("%s:enter", mname);
   
   if (command_words(comm) < 2)
      {
      log_file_date("%s:no list", mname);
      (void)do_reply_code(SL_INVALID_PARAMETER);
      return;
      }
     
   /* load just the list */
      
   pos = command_indxword(comm, 2);
   
   /* if the first word in the first entry is a quote,
      'command_indxword' will skip over this, we need
      to backup one position */
      
   if (comm[pos - 1] == '\"' || comm[pos - 1] == '\'')
      pos--;
      
   len = strlen(comm) - pos;
   
   if ((thelist = (char *)malloc(len + 1)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail[thelist]", mname);
      (void)do_reply_code(SL_MEMORY_FAIL);
      return;
      }
        
   strcpy(thelist, &comm[pos]);
   ret = sl_config_put_list(thelist);
   free(thelist);
   (void)do_reply_code(ret);
   log_file_date("%s:normal exit,rc=%d", mname, 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;

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

   if (!command_word(comm, wrd, 2))
      {
      (void)do_reply_code(SL_INTERNAL_ERROR);
      log_file_date("%s:error getting password", mname);
      return(FALSE);
      }
         
   if (!strcmp(wrd, SL_TERM_PASSWD))
      {            
      (void)do_reply_code(SL_OK);
      return(TRUE);
      }
            
   log_file_date("%s:illegal terminate request", mname);
   (void)do_reply_code(SL_ACCESS_DENIED);
   return(FALSE);
}

void s_trans_num(void)
{
   /* Get the current transaction count. Syntax:
      
         SL_SEND_TRANS_NUM */
   
   char mname[] = "s_trans_num";
   long count;
   
   log_file_date("%s:enter", mname);
   count = ipc_trans_num();
   (void)do_reply_long(SL_OK, count);
   log_file_date("%s:normal exit,rc=0", mname);      
}

void s_connect_num(void)
{
   /* Get the current connection count. Syntax:
      
         SL_SEND_CONNECT_NUM */
   
   char mname[] = "s_connect_num";
   int count;
   
   log_file_date("%s:enter", mname);
   count = ipc_connect_num();
   (void)do_reply_int(SL_OK, count);
   log_file_date("%s:normal exit,rc=0", mname);      
}

/* IPC primatives */

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

   char reply_char[50];
   int anint = 0;
   
   if (reply_code == SL_OK)
      anint = int_value;
      
   memset(reply_char, 0, 50);
   sprintf(reply_char, "%d %d", reply_code, anint);
   log_file_date("do_reply_int:reply=%s,l=%d", reply_char,
                 strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(SL_VC_ERROR);
      
   return(SL_OK);
}

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

   char reply_char[50];
   int along = 0L;
   
   if (reply_code == SL_OK)
      along = long_value;
      
   memset(reply_char, 0, 50);
   sprintf(reply_char, "%d %ld", reply_code, along);
   log_file_date("do_reply_long:reply=%s,l=%d", reply_char, strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(SL_VC_ERROR);
      
   return(SL_OK);
}

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);
   log_file_date("do_reply_code:reply=%s,l=%d", reply_char, 
                 strlen(reply_char));
   
   if (ipc_send_data(reply_char) == 0)
      return(SL_VC_ERROR);
      
   return(SL_OK);
}

int do_reply_char(int reply_code, char *char_value)
{
   /* Send a reply to a request that returns a string as well as
      the reply code. Function returns 'SL_OK' upon success, an
      error code otherwise. */

   char *reply_char;
   int len;
         
   if (char_value == (char *)NULL)
      return(SL_INVALID_FUNCTION);
   
   len = strlen(char_value);
   
   // alloc reply string, note that 'char_value' might be empty
   
   if ((reply_char = (char *)malloc(len + 10)) == (char *)NULL)
      return(SL_MEMORY_FAIL);
      
   memset(reply_char, 0, len + 10);
   sprintf(reply_char, "%d %s", reply_code, char_value);
   log_file_date("do_reply_char:reply=%s,l=%d", reply_char, strlen(reply_char));

   if (ipc_send_data(reply_char) == 0)
      {
      free(reply_char);
      return(SL_VC_ERROR);
      }
      
   free(reply_char);
   return(SL_OK);
}

/* low level functions */

int s_ll_put_list(char *comm)
{
   /* Take the input 'socloc' list and write/put it.
      A list entry is expected in the format:
      
         sname hname port[ ip_ad]
         
      Where 'sname' is the service name, 'hname' is the
      host name, port is the TCP port number and the
      optional 'ip_ad' is the IP address. If the
      service name or host name contains spaces, they
      must be quoted.
     
      Each entry is delimited by 'SL_LIST_DELIM'. */
         
   char mname[] = "s_ll_put_list";
   char *entry, *sname, *hname, *port_char, *ip_ad;
   int nentries, nwords, port, len, i, ret;
   
   log_file_date("%s:enter", mname);
   
   if (comm == (char *)NULL || !strlen(comm))
      {
      log_file_date("%s:[comm] null or empty", mname);
      return(SL_INVALID_PARAMETER);
      }
      
   len = strlen(comm);
      
   /* get number of delimited entries */
   
   nentries = ll_words(comm, SL_LIST_DELIM);
   log_file_date("%s:found %d entries in input list", mname, nentries);
   
   if (nentries < 1)
      {
      log_file_date("%s:wacky value from 'll_words'", mname);
      return(SL_INTERNAL_ERROR);
      }
      
   /* delete the existing list in memory */
   
   s_ll_delete_all();   

   /* add each entry in the list */
   
   for(i = 1; i <= nentries; i++)
      {
      len = ll_wordlen(comm, i, SL_LIST_DELIM);
      log_file_date("%s:entry[%d]:len=%d", mname, i, len);
      
      if (len < 1)
         {
         log_file_date("%s:entry[%d]:wacky value from 'll_wordlen", mname, i);
         return(SL_INTERNAL_ERROR);
         }
         
      if ((entry = (char *)malloc(len + 1)) == (char *)NULL)
         {
         log_file_date("%s:entry[%d]:alloc fail[entry]", mname, i);
         return(SL_MEMORY_FAIL);
         }
         
      if (!ll_word(comm, entry, i, SL_LIST_DELIM))
         {
         log_file_date("%s:entry[%d]:bad rc from 'll_word'", mname, i);
         free(entry);
         return(SL_INTERNAL_ERROR);
         }

      log_file_date("%s:entry[%d]=%s", mname, i, entry);
               
      /* must have at least 3 command words in each entry */
      
      if ((nwords = command_words(entry)) < 3)
         {
         log_file_date("%s:entry[%d]:wrong number of parms", mname, i);
         free(entry);
         return(SL_INVALID_PARAMETER);
         }
         
      /* since no single entry value can exceed the length of
         the entry, we get sloppy and allocate to length
         of entry */
         
      if ((sname = initstring(entry)) == (char *)NULL)
         {
         log_file_date("%s:entry[%d]NULL from 'initstring'[sname]", mname, i);
         free(entry);
         return(SL_MEMORY_FAIL);
         }
         
      if ((hname = initstring(entry)) == (char *)NULL)
         {
         log_file_date("%s:entry[%d]:NULL from 'initstring'[hname]", mname, i);
         free(entry);
         free(sname);
         return(SL_MEMORY_FAIL);
         }
         
      if ((port_char = initstring(entry)) == (char *)NULL)
         {
         log_file_date("%s:entry[%d]:NULL from 'initstring'[port_char]", mname, i);
         free(entry);
         free(sname);
         free(hname);
         return(SL_MEMORY_FAIL);
         }      
         
      if ((ip_ad = initstring(entry)) == (char *)NULL)
         {
         log_file_date("%s:entry[%d]:NULL from 'initstring'[ip_ad]", mname, i);
         free(entry);
         free(sname);
         free(hname);
         free(port_char);
         return(SL_MEMORY_FAIL);
         }
         
      sname[0] = EOS;
      hname[0] = EOS;
      port_char[0] = EOS;
      ip_ad[0] = EOS;
      
      if (!command_word(entry, sname, 1))
         {
         log_file_date("%s:entry[%d]:bad ret from 'command_word'[sname]",
                       mname, i);
         free(entry);
         free(sname);
         free(hname);
         free(port_char);
         free(ip_ad);
         return(SL_INTERNAL_ERROR);
         }
         
      log_file_date("%s:entry[%d]:service name=%s", mname, i, sname);
      
      if (!command_word(entry, hname, 2))
         {
         log_file_date("%s:entry[%d]:bad ret from 'command_word'[hname]",
                       mname, i);
         free(entry);
         free(sname);
         free(hname);
         free(port_char);
         free(ip_ad);
         return(SL_INTERNAL_ERROR);
         }
         
      log_file_date("%s:entry[%d]:host name=%s", mname, i, hname);
      
      if (!command_word(entry, port_char, 3))
         {
         log_file_date("%s:entry[%d]bad ret from 'command_word'[port_char]",
                       mname, i);
         free(entry);
         free(sname);
         free(hname);
         free(port_char);
         free(ip_ad);
         return(SL_INTERNAL_ERROR);
         }
       
      log_file_date("%s:entry[%d]:port_char=%s", mname, i, port_char);
        
      if (!qatoi(port_char, &port))
         {
         log_file_date("%s:entry[%d]:port not numeric", mname, i);
         free(entry);
         free(sname);
         free(hname);
         free(port_char);
         free(ip_ad);
         return(SL_INVALID_PARAMETER);
         }
         
      free(port_char);
      
      if (nwords > 3)
         {
         if (!command_word(entry, ip_ad, 4))
            {
            log_file_date("%s:entry[%d]:bad ret from 'command_word'[port_char]",
                          mname, i);
            free(entry);
            free(sname);
            free(hname);
            free(ip_ad);
            return(SL_INTERNAL_ERROR);
            }
            
         if (!isip(ip_ad))
            {
            log_file_date("%s:entry[%d]:IP address invalid", mname, i);
            free(entry);
            free(sname);
            free(hname);
            free(ip_ad);
            return(SL_INVALID_PARAMETER);
            }
            
         log_file_date("%s:entry[%d]:ip=%s", mname, i, ip_ad);
         }
      else
         log_file_date("%s:entry[%d]:no ip present", mname, i);
         
      /* finally, after all that, we are ready to add the entry */
      
      if ((ret = s_ll_add(sname, hname, port, ip_ad)) != SL_OK)
         {
         log_file_date("%s:entry[%d]:bad rc[%d] from 's_ll_add'", mname,
                       i, ret);
         free(entry);
         free(sname);
         free(hname);
         free(ip_ad);
         return(ret);
         }

      free(entry);         
      free(sname);
      free(hname);
      free(ip_ad);
      }
      
   log_file_date("%s:normal exit,rc[SL_OK]", mname);
   return(SL_OK);
}

/* link list management functions */

int s_ll_get_first(char *sname, char *hname, int *port_num,
                   char *ip_ad)
{
   /* Return the first matching socket server.At least one parameter
      must be supplied. Function will locate first match based on
      supplied parameters. 'ip_ad' is the only parameter that may
      be NULL. Upon success, missing details will be loaded. Service
      name, host name and IP address (if present) must be allocated
      to sufficient size by the caller. Function returns 'SL_OK' upon
      success, a socket locate code otherwise. */

   struct sloc_entry *rov = sloc_base;
   char mname[] = "s_ll_get_first";
   int found;

   log_file_date("%s:enter", mname);
   
   if (sname == (char *)NULL || hname == (char *)NULL ||
       port_num == (int *)NULL)
      {
      log_file_date("%s:null parm", mname);
      return(SL_INVALID_PARAMETER);
      }
      
   if (!strlen(sname) && !strlen(hname) && *port_num <= 0 &&
       (ip_ad == (char *)NULL || !strlen(ip_ad)))
      {
      log_file_date("%s:at least one parameter must be supplied", mname);
      return(SL_INVALID_PARAMETER);
      }
      
   if (rov == SLOC_NULL)
      {
      log_file_date("%s:locate server list empty", mname);
      return(SL_NOT_FOUND);
      }

   while(rov != SLOC_NULL)
      {
      found = TRUE;
      
      if (strlen(sname))
         if (!strcmp(sname, rov->service_name))
            found = TRUE;
         else
            found = FALSE;
         
      if (found && strlen(hname))
         if (!strcmp(hname, rov->host_name))
            found = TRUE;
         else
            found = FALSE;      

      if (found && *port_num > 0)
         if (*port_num == rov->port_number)
            found = TRUE;
         else
            found = FALSE;
            
      if (found && ip_ad != (char *)NULL && strlen(ip_ad))
         if (rov->ip_addr == (char *)NULL)
            found = FALSE;
         else
            if (!strlen(rov->ip_addr))
               found = FALSE;
            else
               if (!strcmp(ip_ad, rov->ip_addr))
                  found = TRUE;
               else
                   found = FALSE;
                   
      if (found)
         {
         if (!strlen(sname))
            strcpy(sname, rov->service_name);
            
         if (!strlen(hname))
            strcpy(hname, rov->host_name);
               
         if (*port_num == 0)
            *port_num = rov->port_number;                           

         if (ip_ad != (char *)NULL && !strlen(ip_ad))
            if (rov->ip_addr != (char *)NULL && strlen(rov->ip_addr))
               strcpy(ip_ad, rov->ip_addr);
            else
               ip_ad[0] = EOS;
               
         break;
         }                      
      else
         rov = rov->next;
      }

   if (rov == SLOC_NULL)
      {
      log_file_date("%s:no match", mname);
      return(SL_NOT_FOUND);
      }
      
   log_file_date("%s:found match", mname);
   return(SL_OK);
}

int s_ll_get_nentries(int *nentries)
{
   /* Get the number of entries in the link list. Function returns
      'SL_OK' with the number of entries loaded into
      'nentries' upon success, a socket locate error
      otherwise. */

   struct sloc_entry *rov = sloc_base;
   char mname[] = "s_ll_get_nentries";
   int cnt = 0;

   log_file_date("%s:enter", mname);
   *nentries = 0;   

   while(rov != SLOC_NULL)
      {
      cnt++;
      rov = rov->next;
      }

   if (!cnt)
      {
      log_file_date("%s:locate server list empty", mname);
      return(SL_NOT_FOUND);
      }
      
   *nentries = cnt;
   log_file_date("%s:normal exit,nentries=%d", mname, cnt);
   return(SL_OK);
}

int s_ll_get_nth(int pos, char *sname, char *hname, int *port_num,
                 char *ip_ad)
{
   /* Get a specific socket server entry. Function returns
      'SL_OK' upon success with the service name loaded into
      'sname', host name loaded into 'hname',
      the port number loaded into 'port_num' and the IP address loaded
      into 'ip_ad'. These character variables must previously be
      allocated to sufficient size by the caller. Function returns
      a socket locate config error upon failure. */

   struct sloc_entry *rov;
   char mname[] = "s_ll_get_nth";
   int i, nentries, ret;

   log_file_date("%s:enter", mname);
   
   /* get total number of entries and validate */

   if ((ret = s_ll_get_nentries(&nentries)) != SL_OK)
      {
      log_file_date("%s:bad ret from s_ll_get_nentries,rc=%d",
                    mname, ret);
      return(ret);
      }

   if (pos > nentries)
      {
      log_file_date("%s:request out of range", mname);
      return(SL_NOT_FOUND);
      }

   for(rov = sloc_base, i = 1; i <= pos; rov = rov->next, i++)
      if (i == pos)
         {
         strcpy(sname, rov->service_name);
         strcpy(hname, rov->host_name);
         *port_num = rov->port_number;

         /* if the ip address parameter is null, do not attempt
            to load the ip address even if there is one */

         if (ip_ad == (char *)NULL)
            break;

         if (rov->ip_addr != (char *)NULL)
            strcpy(ip_ad, rov->ip_addr);
         else
            /* if no ip address is present in the entry and the
               ip address parameter is not null, zero out the
               ip address parameter */

            ip_ad[0] = EOS;

         break;
         }

   log_file_date("%s:normal exit", mname);
   return(SL_OK);
}

int s_ll_add(char *sname, char *hname, int port_num, char *ip_ad)
{
   /* Add a socket server entry. Function returns
      'SL_OK' upon success, a socket locate error code otherwise. */

   struct sloc_entry *sloc;
   struct sloc_entry *rov;
   char mname[] = "s_ll_add";
   int ret, len, size;

   log_file_date("%s:enter", mname);
   size = sizeof(struct sloc_entry);

   /* reasonableness checks */

   if (sname == (char *)NULL || !strlen(sname))
      {
      log_file_date("%s:null or empty service name", mname);
      return(SL_INVALID_PARAMETER);
      }
   
   if (hname == (char *)NULL || !strlen(hname))
      {
      log_file_date("%s:null or empty host name", mname);
      return(SL_INVALID_PARAMETER);
      }
      
   if (port_num <= 0)
      {
      log_file_date("%s:port out of range", mname);
      return(SL_INVALID_PARAMETER);
      }
   
   /* check for duplicate port number */
   
   if ((ret = s_ll_port_exist(port_num)) != SL_OK)
      {
      log_file_date("%s:bad ret from s_ll_port_exist,rc=%d",
                    mname, ret);
      return(ret);
      }
            
   if ((sloc = (struct sloc_entry *)malloc(size)) == SLOC_NULL)
      {
      log_file_date("%s:alloc fail[sloc]", mname);
      return(SL_MEMORY_FAIL);
      }

   len = strlen(sname);
   
   if ((sloc->service_name = (char *)malloc(len + 1)) == (char *)NULL)
      {
      free(sloc);
      log_file_date("%s:alloc fail[sloc->service_name]", mname);
      return(SL_MEMORY_FAIL);
      }
      
   strcpy(sloc->service_name, sname);
   len = strlen(hname);

   if ((sloc->host_name = (char *)malloc(len + 1)) == (char *)NULL)
      {
      free(sloc->service_name);
      free(sloc);
      log_file_date("%s:alloc fail[sloc->host_name]", mname);
      return(SL_MEMORY_FAIL);
      }

   strcpy(sloc->host_name, hname);
   sloc->port_number = port_num;

   if (ip_ad != (char *)NULL)
      {
      len = strlen(ip_ad);

      if ((sloc->ip_addr = (char *)malloc(len + 1)) == (char *)NULL)
         {
         free(sloc->service_name);
         free(sloc->host_name);
         free(sloc);
         log_file_date("%s:alloc fail[sloc->ip_addr]", mname);
         return(SL_MEMORY_FAIL);
         }

      strcpy(sloc->ip_addr, ip_ad);
      }
   else
      sloc->ip_addr = (char *)NULL;
      
   sloc->next = SLOC_NULL;

   /* set head of list if this is the first entry */

   if (sloc_base == SLOC_NULL)
      {
      sloc_base = sloc;
      log_file_date("%s:normal exit with head of list", mname);
      return(SL_OK);
      }

   /* find last entry and hook into it */

   rov = sloc_base;

   while(rov->next != SLOC_NULL)
      rov = rov->next;

   rov->next = sloc;
   log_file_date("%s:normal exit", mname);
   return(SL_OK);
}

int s_ll_port_exist(int port_num)
{
   /* Determine whether there already is a socket server or
      'socloc' server using the specified port number. Check
      port used by socket servers and 'socloc' servers.
      Function returns 'SL_OK' if the port does not already
      exist and 'SL_PORT_EXIST' if the port does already
      exist/in use. */
      
   struct sloc_entry *rov = sloc_base;
   char mname[] = "s_ll_port_exist";
   int ret = SL_OK;
   
   log_file_date("%s:enter", mname);
   
   while(rov != SLOC_NULL)
      {
      if (port_num == rov->port_number)
         {
         ret = SL_PORT_EXIST;
         log_file_date("%s:port already in use by socket server", mname);
         break;
         }
       
       rov = rov->next;
       }

   if (ret == SL_OK)
      if ((ret = sl_config_port_exist(port_num)) != SL_OK)
         log_file_date("%s:port already in use by 'socloc'", mname);
         
   log_file_date("%s:normal exit,rc=%d", mname, ret);
   return(ret);
}

int s_ll_delete(int port_num)
{
   /* Delete a socket server entry. Since the TCP
      port is unique, this is used as the entry key.
      Function returns 'SL_OK' upon success, a
      socket locate config error otherwise. */

   struct sloc_entry *rov;
   struct sloc_entry *prev;
   char mname[] = "s_ll_delete";

   log_file_date("%s:enter,port=%d", mname, port_num);
   rov = prev = sloc_base;

   while(rov != SLOC_NULL)
      {
      if (port_num == rov->port_number)
         break;
      else
         {
         prev = rov;
         rov = rov->next;
         }
      }

   /* if no match, error */

   if (rov == SLOC_NULL)
      {
      log_file_date("%s:exit,no match", mname);
      return(SL_NOT_FOUND);
      }

   /* if we are deleting the head entry, set new head */

   if (rov == sloc_base)
      sloc_base = rov->next;
   else
      prev->next = rov->next;

   free(rov->service_name);
   free(rov->host_name);

   if (rov->ip_addr != (char *)NULL)
      free(rov->ip_addr);

   free(rov);
   s_ll_debug();
   log_file_date("%s:normal exit,rc[0]", mname);
   return(SL_OK);
}

void s_ll_delete_all(void)
{
   /* Delete all entries. */

   struct sloc_entry *rov = sloc_base;
   struct sloc_entry *tmp;
   char mname[] = "s_ll_delete_all";

   log_file_date("%s:enter", mname);
   
   while(rov != SLOC_NULL)
      {
      free(rov->service_name);
      free(rov->host_name);

      if (rov->ip_addr != (char *)NULL)
         free(rov->ip_addr);

      tmp = rov;
      rov = rov->next;
      free(tmp);
      }

   log_file_date("%s:normal exit", mname);
   sloc_base = SLOC_NULL;      
}

void s_ll_debug(void)
{
   /* Print the contents of the link list. Log file is
      forced active. */
   
   struct sloc_entry *rov = sloc_base;
   char mname[] = "s_ll_debug";
   int i = 1, islog;
   
   if ((islog = is_log_active()) == FALSE)
      log_start(s_log_file);

   log_file_date("%s:enter", mname);
      
   while(rov != SLOC_NULL)
      {
      log_file_date("%s[%d]:sname='%s',host='%s',port=%d,ip='%s'",
                    mname, i, rov->service_name, rov->host_name,
                    rov->port_number, rov->ip_addr);
      rov = rov->next;
      i++;
      }
      
   if (i == 1)
      log_file_date("%s:list empty", mname);
      
   log_file_date("%s:normal exit", mname);
   
   if (!islog)
      log_end();
}

void s_config_debug(void)
{
   /* Print the contents of the config link list. Log file is
      forced active. */
      
   int islog;
   
   if ((islog = is_log_active()) == FALSE)
      log_start(s_log_file);

   sl_config_debug();
   
   if (!islog)
      log_end();
}

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

   (void)sloc_bcast_config_delete(server_port);
   s_ll_delete_all();   
#ifdef OS_WIN32
   WSACleanup();
#endif

   log_file_date("term_app:server down");

   if (is_log_active())
      log_end();
}
