/***************************************
  $Revision: 1.38 $

  Example code: A server for a client to connect to.

  Status: NOT REVUED, NOT TESTED

 Authors:       Chris Ottrey, Joao Damas

  +html+ <DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+   <LI>Based on <A HREF="http://iii.ripe.net/dbase/coding/new.code/progress/ottrey/code/java/src/DBServer.java">DBServer.java</A>
  +html+ </UL>
  +html+ </DL>
 
  ******************/ /******************
  Modification History:
        ottrey (02/03/1999) Created.
        ottrey (08/03/1999) Modified.
        joao   (22/06/1999) Modified.
  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 ***************************************/
#include <sys/socket.h>
#include <netinet/in.h>

#include <sys/wait.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/stat.h>

#include "thread.h"
#include "rxroutines.h"
#include "socket.h"
/*
#include "objects.h"
*/
#include "constants.h"

#include "ca_configFns.h"
#include "ca_dictSyms.h"
#include "ca_macros.h"
#include "ca_srcAttribs.h"

#include "mysql_driver.h"
#include "access_control.h"
#include "ud.h"
#include "server.h"

#include "rp.h"
#include "memwrap.h"

#include "ta.h"

#define RIPE_REG 17

/*+ String sizes +*/
#define STR_S   63
#define STR_M   255
#define STR_L   1023
#define STR_XL  4095
#define STR_XXL 16383


/* Storage for descriptors of the read side of the pipe */
int sv_lockfd[MAX_LOCKS];

/* Listening sockets */
int SV_whois_sock;
int SV_config_sock;
int SV_mirror_sock;

/* each updatable source has its own update thread and its own socket */
#define MAX_SOURCES 100
int SV_update_sock[MAX_SOURCES];

/*+ Mutex lock.  Used for synchronizing changes. +*/
pthread_mutex_t   Whois_thread_count_lock;
pthread_mutex_t   Config_thread_count_lock;
pthread_mutex_t   Mirror_thread_count_lock;

/*+ The number of threads. +*/
int       Whois_thread_count;
int       Config_thread_count;
int       Mirror_thread_count;


/*+ Server starting time +*/
time_t SV_starttime;

/* pthread_mutex_t radix_initializing_lock; */
/* XXX this is a workaround of a problem with mysql - it prevents the
update/nrtm threads from starting before the radix tree is loaded.

Apparently, even LOCK TABLES doesn't prevent the program from locking up 
*/

static void do_watchdog(void *arg);

/* Logging results */
static void log_print(const char *arg) {

  printf(arg);

} /* log_print() */


void radix_init(void){
  int i;
  ca_dbSource_t *source_hdl;

  wr_log_set(0);
  /* this needs to be done in two loops, 
     because the trees must be created asap (first loop)
     and then locked until they are populated in the second loop
  */
  
  for(i=0; (source_hdl = ca_get_SourceHandleByPosition(i))!=NULL ; i++){   
    dieif( RP_init_trees( source_hdl ) != RP_OK );
  }
  
  for(i=0; (source_hdl = ca_get_SourceHandleByPosition(i))!=NULL ; i++){   
    dieif( RP_sql_load_reg( source_hdl ) != RP_OK ); 
  }
  
#if 0
  {
      er_path_t erlogstr;
      
      erlogstr.fdes = stdout;
      erlogstr.asp  = ASP_SK_GEN; /* 0xfffff000; */
      erlogstr.fac  = FAC_SK;
      erlogstr.sev  = ER_SEV_D;
      erlogstr.mode = ER_M_SEVCHAR | ER_M_FACSYMB | ER_M_TEXTLONG | ER_M_THR_ID;
      
      ER_setpath(& erlogstr);  
  }
#endif
  wr_log_set(0); /* switch on/off the memory leak detector */
/*  pthread_mutex_unlock( &radix_initializing_lock );  */
  
  pthread_exit((void *)0);
}

/* main_loop() */
/*++++++++++++++++++++++++++++++++++++++

  Waits for an incoming connection on the and spawns a new thread to handle it.

  void *arg Pointer to a struct containing the socket to talk to the client and
            the function to call depending on the incoming connection.

  More:
  +html+ <PRE>
  Author:
        ottrey
	joao
	andrei (do_server)
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
static void  *main_loop(void *arg) {
  th_args *args = (th_args *)arg;
  int connected_socket;
  int do_server;

  while(do_server=CO_get_do_server()) {

    connected_socket = SK_accept_connection(args->sock);
    if(connected_socket==-1) break;


    ER_dbg_va(FAC_TH, ASP_TH_NEW, "Starting a new thread");

    /* Start a new thread. */


    TH_create((void *(*)(void *))(args->function), (void *)connected_socket);
//      
//    pthread_attr_init(&attr);    /* initialize attr with default attributes */
//    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//    pthread_create(&tid, &attr, (void *(*)(void *))(args->function), (void *)connected_socket); 
  }

   ER_dbg_va(FAC_TH, ASP_TH_NEW, "Exiting from the main loop");

} /* main_loop() */


/* SV_start() */
/*++++++++++++++++++++++++++++++++++++++

  Start the server.

  More:
  +html+ <PRE>
  Authors:
        ottrey
        joao
  +html+ </PRE>
  +html+ Starts up the server.
  +html+ <OL>
  +html+   <LI> Create sockets on the necessary ports (whois, config and mirror)
  +html+   <LI> Start new threads for each service.
  +html+ </OL>
  +html+ <A HREF=".DBrc">.properties</A>

  ++++++++++++++++++++++++++++++++++++++*/
void SV_start() {
  /* Make listening sockets global variables  */
  /*  int whois_sock,config_sock,mirror_sock,update_sock; */
  /* uint32_t whois_addr,sock_addr,mirror_addr; */
  int whois_port = -1;
  int config_port = -1;
  int mirror_port = -1; 
  int update_port = -1;
  int update_mode = 0;
  sigset_t sset;
  int fdes[2];
  struct timeval tval;
  ca_dbSource_t *source_hdl;
  char *source_name;
  int source;

  /* Store the starting time */
  gettimeofday(&tval, NULL);
  SV_starttime = tval.tv_sec;/* seconds since Jan. 1, 1970 */
  
  /* Create interrupt pipe */
  /* Writing to this pipe will cause sleeping threads */
  /* to wake up */
  fprintf(stderr, "Creating an interrupt pipe\n");
  if(pipe(fdes)==-1) {
   printf("Cannot open interrupt pipe\n");
   exit(-1);
  } 
  /* Save the pipe descriptors in sv_lock array */
  sv_lockfd[WLOCK_SHTDOWN]=fdes[0];
  sv_lockfd[LOCK_SHTDOWN]=fdes[1];

  /* Initialise modules */
  SK_init();
  
  /* Initialise the access control list. */
  AC_build();
  AC_acc_load();
  /* explicitly start the decay thread */
  TH_create((void *(*)(void *))AC_decay, NULL);

  /* Initialise the radix tree (separate thread[s])
     already can allow socket connections, because the trees will 
     be created locked, and will be unlocked when loaded */

/*   pthread_mutex_lock( &radix_initializing_lock );  */
  TH_create((void *(*)(void *))radix_init, NULL);
/*  pthread_mutex_lock( &radix_initializing_lock );  */
  
  
  /* XXX I have no idea how this is working!! It's wrong! - ottrey 30/7/99 */
  /* Get port information for each service */
  whois_port = SK_atoport(CO_get_whois_port(), "tcp");
  printf("XXX htons(whois_port)=%d\n", htons(whois_port));
  if(whois_port == -1) {
    printf("Invalid service/port: %d\n", htons(whois_port));
    exit(-1);
  }

  /* XXX I have no idea how this is working!! It's wrong! - ottrey 30/7/99 */
  config_port = SK_atoport(CO_get_config_port(), "tcp");
  printf("XXX htons(config_port)=%d\n", htons(config_port));
  if(config_port == -1) {
    printf("Invalid service/port: %d\n", htons(config_port));
    exit(-1); 
  }
  mirror_port = SK_atoport(CO_get_mirror_port(), "tcp");
  printf("XXX htons(mirror_port)=%d\n", htons(mirror_port));
  if(mirror_port == -1) {
    printf("Invalid service/port: %d\n", mirror_port);
    exit(-1);
  }

  /* XXX I have no idea how this is working!! It's wrong! - ottrey 30/7/99 */
/*  update_port = SK_atoport(CO_get_update_port(), "tcp"); */
/*  printf("XXX htons(update_port)=%d\n", htons(update_port)); */
/*  if(update_port == -1) { */
/*    printf("Invalid service/port: %d\n", htons(update_port)); */
/*    exit(-1); */
/*  } */



  /* 6. Create a socket on the necessary ports/addresses and bind to them. */
  /* whois socket */
  SV_whois_sock = SK_getsock(SOCK_STREAM, whois_port, INADDR_ANY);
/* Currently binds to INADDR_ANY. Will need to get specific address */
/*  SV_whois_sock = SK_getsock(SOCK_STREAM,whois_port,whois_addr); */
  /* config interface socket */
  SV_config_sock = SK_getsock(SOCK_STREAM, config_port, INADDR_ANY);
  /* nrt socket */
  SV_mirror_sock = SK_getsock(SOCK_STREAM,mirror_port,INADDR_ANY);
  
  
  
  /* update interface socket */
  /* we need first to create and bind all of them */
  /* so that in case of failure we do not start any */
  /* update thread */
  for(source=0; (source_hdl = ca_get_SourceHandleByPosition(source))!=NULL ; source++){
     update_mode = ca_get_srcmode(source_hdl);
     if(IS_UPDATE(update_mode)) {
       /* update_port = SK_atoport(CO_get_update_port(), "tcp"); */
       update_port = htons(ca_get_srcupdateport(source_hdl)); 
       printf("XXX htons(update_port)=%d\n", htons(update_port));
       /* XXX ask AMRM to change the name of the function */
 
       SV_update_sock[source] = SK_getsock(SOCK_STREAM, update_port, INADDR_ANY);
     }
     else SV_update_sock[source] = 0;
  }   
  SV_update_sock[source+1]=-1; /* end of socket array */
   
  /* Now.... accept() calls block until they get a connection
     so to listen on more than one port we need more
     than one thread */

  /* Create master thread for whois threads */
   SV_concurrent_server(SV_whois_sock, SV_do_whois);

  /* Create master thread for config threads */
   SV_concurrent_server(SV_config_sock, SV_do_config);
  /* Create master thread for mirror threads */
   SV_concurrent_server(SV_mirror_sock, SV_do_mirror);

/* Walk through the sources and */
/* run update thread for every source with CANUPD == 'y' */
   
   for(source=0; (source_hdl = ca_get_SourceHandleByPosition(source))!=NULL ; source++){
     update_mode = ca_get_srcmode(source_hdl);
     source_name= ca_get_srcname(source_hdl);

     if(IS_UPDATE(update_mode)) { 
     /* run RIPupdate thread */
       fprintf(stderr,"Source [%s] Mode UPDATE\n", source_name);
       TH_create((void *(*)(void *))UD_do_updates, (void *)source); 
     }
     else {
       /* start NRTM client */
       fprintf(stderr,"Source [%s] Mode NRTM\n", source_name);    
       TH_create((void *(*)(void *))UD_do_nrtm, (void *)source);
     }
     free(source_name); /* because ca_* functions return copies */   
   }    

   

  /* XXX Is this needed? */
  pthread_exit(NULL);

} /* SV_start() */

/* SV_shutdown() */
/*++++++++++++++++++++++++++++++++++++++

  Shutdown the server.

  More:
  +html+ <PRE>
  Authors:
        andrei
  +html+ </PRE>
  +html+ Stops the server.
  +html+ <OL>
  +html+   <LI> Close listening sockets (whois, config, mirror and updates)
  +html+   <LI> Stop all threads by triggering do_server variable.
  +html+ </OL>
  +html+ <A HREF=".DBrc">.properties</A>

  ++++++++++++++++++++++++++++++++++++++*/
void SV_shutdown() {
char print_buf[STR_M];
int source;
 
 sprintf(print_buf, "%d", 0);
 /* Stop updates */
 CO_set_const("UD.do_update", print_buf);
 /* Stop all servers */
 CO_set_const("SV.do_server", print_buf);
 sprintf(print_buf, "Stopping all servers\n");
 fprintf(stderr, print_buf);
 /*log_print(print_buf); */
 strcpy(print_buf, "");
 
 /* Wake up all sleeping threads */
 fprintf(stderr, "Going to wake sleeping threads up\n");
 write(sv_lockfd[WLOCK_SHTDOWN], " ", 1); 

 /* CLose all listening sockets, so accept call exits */
 close(SV_whois_sock);
 close(SV_config_sock);
 close(SV_mirror_sock);
 for (source=0; SV_update_sock[source]!=-1; source++)
	 if(SV_update_sock[source]!=0)close(SV_update_sock[source]);
 
 
} /* SV_shutdown() */


/* SV_sleep() */
/*++++++++++++++++++++++++++++++++++++++

  Sleep and wake up on special events.

  More:
  +html+ <PRE>
  Authors:
        andrei
  +html+ </PRE>
  +html+ Sleeps timeout but wakes up when an envent occures.

  ++++++++++++++++++++++++++++++++++++++*/
int SV_sleep(int lock, int sleeptime) {
struct timeval timeout;
struct stat st;
fd_set set;

 if (fstat(sv_lockfd[lock], &st) ==-1) {
  fprintf(stderr, "Error stat-ing the lock file\n");
  return(-1);
 } 
 
 timeout.tv_sec=sleeptime;
 timeout.tv_usec=0;
   
 FD_ZERO(&set);
 FD_SET(sv_lockfd[lock], &set);
 
 fprintf(stderr, "Going to sleep\n");
 select(sv_lockfd[lock]+1, &set, NULL, NULL, &timeout);
 
 fprintf(stderr, "Select returned\n");
      
 return(0);
}

/*++++++++++++++++++++++++++++++++++++++

  Handle signals.
  
  Changes the flags:
  	do_nrtm
  	do_update
  	do_whoisd

  More:
  +html+ <PRE>
  Author:
        andrei
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void *SV_signal_thread() {
char print_buf[STR_M];
sigset_t sset;
int sigReceived;
int do_update;

	sigemptyset(&sset);
	sigaddset(&sset, SIGTERM);
	sigaddset(&sset, SIGINT);
	sigaddset(&sset, SIGUSR1);
	/* This is a bit confusing, but is needed */
	/* For more information on signal handling in */
	/* threads see for example "Multithreading Programming */
	/* Techniques" by Shashi Prasad, ISBN 0-07-912250-7, pp. 94-101 */
	pthread_sigmask(SIG_BLOCK, &sset, NULL);
	/*	fprintf(stderr, "Signal handler installed\n");*/

	for(;;)
	{
	 sigwait(&sset, &sigReceived);
	 sprintf(print_buf, "Signal received [%d]\n", sigReceived);
	 log_print(print_buf); strcpy(print_buf, "");
	 /*	 fprintf(stderr, "Signal received [%d]\n", sigReceived); */
	 switch (sigReceived)
	 {
	   case SIGINT:
	   /* SIGINT stops all servers */
	        SV_shutdown();
                pthread_exit((void *)0);
  	        break;
  	        
  	   case SIGTERM:
  	   /* SIGTERM will switch the updates on and off */
  	        do_update=CO_get_do_update();
  	        if(do_update)do_update=0; else do_update=1;     
  	   	sprintf(print_buf, "%d", do_update);
		CO_set_const("UD.do_update", print_buf); 
		if(do_update)
		  sprintf(print_buf, "Starting updates\n");
		else   
		  sprintf(print_buf, "Stopping updates\n");
		log_print(print_buf); strcpy(print_buf, ""); 
		/*		fprintf(stderr, "Stopping updates (SIGTERM received)\n"); */
  	   	break; 
  	 }       
  	}
} /* SV_signal_thread() */

/* SV_concurrent_server() */
/*++++++++++++++++++++++++++++++++++++++

  This is the routine that creates the main threads. 

  int     sock        The socket to connect to.
  void *  do_function The function to call for each type of service

  More:
  +html+ <PRE>
  Author:
        ottrey
	joao
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void SV_concurrent_server(int sock, void *do_function(void *)) {
  th_args *args;
  pthread_t tid;
  pthread_attr_t attr;

  dieif( wr_calloc((void **)&args,1,sizeof(th_args)) != UT_OK);  

  args->function=(void *)do_function;
  args->sock=sock;

/*  pthread_mutex_init(&Whois_thread_count_lock,NULL); */

  /* Start a new thread. */

  TH_create(main_loop, (void *)args);

  
    /* Start a new thread. */
//  pthread_attr_init(&attr);     /* initialize attr with default attributes */
//  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//  pthread_create(&tid, &attr, main_thread, (void *)args);

} /* TH_run() */

/* SV_do_whois() */
/*++++++++++++++++++++++++++++++++++++++

  Handle whois connections.

  void *arg The socket to connect to. (It has to be passed in this way for this thread routine.)

  More:
  +html+ <PRE>
  Author:
        joao
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void *SV_do_whois(void *arg) { 
  int sock = (int)arg;

  ER_dbg_va(FAC_TH, ASP_TH_NEW,
	    "Whois: Child thread [%d]: Socket number = %d", 
	    pthread_self(), sock);

  /* Use a mutex to update the global whois thread counter. */
  pthread_mutex_lock(&Whois_thread_count_lock);
  Whois_thread_count++;
  ER_dbg_va(FAC_TH, ASP_TH_NEW, 
	    "Whois_thread_count++=%d", Whois_thread_count); 
  
  pthread_mutex_unlock(&Whois_thread_count_lock);

  TA_add(sock, "whois");
  PW_interact(sock);
  TA_delete();

  /* Use a mutex to update the global whois thread counter. */
  pthread_mutex_lock(&Whois_thread_count_lock);
  Whois_thread_count--;
  ER_dbg_va(FAC_TH, ASP_TH_NEW, 
	    "Whois_thread_count--=%d", Whois_thread_count); 
  pthread_mutex_unlock(&Whois_thread_count_lock);

  pthread_exit((void *)0);

} /* SV_do_whois() */

/* SV_do_mirror() */
/*++++++++++++++++++++++++++++++++++++++

  Handle NRTM connections.

  void *arg The socket to connect to. (It has to be passed in this way for this thread routine.)

  More:
  +html+ <PRE>
  Author:
        joao
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void *SV_do_mirror(void *arg) { 
  int sock = (int)arg;
  char print_buf[STR_M];

  sprintf(print_buf, "NRTM: Child thread [%d]: Socket number = %d\n", pthread_self(), sock); log_print(print_buf); strcpy(print_buf, "");

  /* Use a mutex to update the global mirror thread counter. */
  pthread_mutex_lock(&Mirror_thread_count_lock);
  Mirror_thread_count++;
  sprintf(print_buf, "Mirror_thread_count++=%d\n", Mirror_thread_count); log_print(print_buf); strcpy(print_buf, "");
  pthread_mutex_unlock(&Mirror_thread_count_lock);

  TA_add(sock, "mirror");
  PM_interact(sock);
  TA_delete();

  /* Use a mutex to update the global mirror thread counter. */
  pthread_mutex_lock(&Mirror_thread_count_lock);
  Mirror_thread_count--;
  sprintf(print_buf, "Mirror_thread_count--=%d\n", Mirror_thread_count); log_print(print_buf); strcpy(print_buf, "");
  pthread_mutex_unlock(&Mirror_thread_count_lock);

  pthread_exit((void *)0);

} /* SV_do_mirror() */

/* SV_do_config() */
/*++++++++++++++++++++++++++++++++++++++

  Handle config connections.

  void *arg The socket to connect to. (It has to be passed in this way for this
thread routine.)

  More:
  +html+ <PRE>
  Author:
        joao
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
void *SV_do_config(void *arg) {
  int sock = (int)arg;
  char print_buf[STR_M];

  sprintf(print_buf, "Config: Child thread [%d]: Socket number = %d\n", pthread_self(), sock); log_print(print_buf); strcpy(print_buf, "");

/*
  printf("Hi there, there is nothing to configure yet\nBye..... :-)\n");
  fflush(NULL);

  SK_close(sock);
*/
  TA_add(sock, "config");
  PC_interact(sock);
  TA_delete();

  pthread_exit((void *)0);

} /* SV_do_config() */


/*++++++++++++++++++++++++++++++++++++++

  This is the routine that creates a watchdog thread. 
  
  The watchdog will cancel (pthread_cancel()) the calling thread in case the
  socket is closed by the client (its read-half is closed). The calling
  thread should make necessaruy preparations when calling the watchdog:
  
  - the socket should be connected
  - cancellation points and cleanup routines should be defined
  
  In case the connection is closed by the calling thread itself, the
  watchdog just exits and no action against the calling thread is performed.

  wd_args - a pointer to wd_args_t structure containing
            data about socket and thread ID
  
  More:
  +html+ <PRE>
  Author:
        ottrey
	joao
	andrei
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/

void SV_watchdog(wd_args_t *wd_args) {
 pthread_t tid;
 pthread_attr_t attr;
 
 /* Start a new thread. */
 TH_create((void *(*)(void *))do_watchdog, (void *)wd_args);
 
// pthread_attr_init(&attr);     /* initialize attr with default attributes */
// pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// pthread_create(&tid, &attr, (void *(*)(void *))do_watchdog, (void *)wd_args);

}


/*++++++++++++++++++++++++++++++++++++++

The watchdog thread itself

The watchdog thread makes select() on the connected socket waiting until it
becomes readable. If this happens as a result of some input, it'll simply
dump it. Otherwise, this indicates that the client has closed the
connection. In this case watchdog will cancel (pthread_cancel()) the whois
thread (which in its turn will kill (mysql_kill()) mysql thread as part of
its cleanup routine).

More:
+html+ <PRE>
Author:
      andrei
+html+ </PRE>
++++++++++++++++++++++++++++++++++++++*/
static void do_watchdog(void *arg) {
  wd_args_t *wd_args = (wd_args_t *)arg;
  int socket;
  pthread_t tid;
  int nready;
  int n;
  fd_set rset;
  char buff[STR_S];
  
  socket = wd_args->connected_socket;
  tid = wd_args->tid;
  
  
  FD_ZERO(&rset);
  FD_SET(socket, &rset);
  
  while ((nready=select(socket+1, &rset, NULL, NULL, NULL))!=-1) {
   
   /* There was some input or client half of connection was closed */
   /* Check for the latter */
   if (( n=read(socket, buff, sizeof(buff))) == 0) {
   /* Connection was closed by client */
   /* Now send a cancellation request to the whois thread. */
   /* mysql thread will be terminated by thread cleanup routine */
   
   /* The only possible error is ESRCH, so we do not care about */
   pthread_cancel(tid);
   
   /* Exit the watchdog thread, passing NULL as we don't expect pthread_join() */
   pthread_exit(NULL);
   }
   
   /* Otherwise dump input and continue */
  }
  
  /* the only reason that we are here is that the socket has been */
  /* closed by the whois thread and not valid. Just exit the watchdog, */
  /* passing NULL as we don't expect pthread_join() */
   pthread_exit(NULL);
  
}  
