/* Nessus
 * Copyright (C) 1998 Renaud Deraison
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 */ 
 
#include <includes.h>

#include "auth.h"
#include "threads.h"
#include "log.h"
#include "piic.h"

/*
 * Initializes the global table of threads (GTOT)
 */
struct nessusd_threads * nessusd_threads_init()
{
 struct nessusd_threads * threads  = emalloc(sizeof(struct nessusd_threads));
 return(threads);
}

/*
 * Register a thread in the GTOT
 */
void nessusd_thread_register(threads, hostname, pid)
 struct nessusd_threads ** threads;
 char * hostname;
 nthread_t pid;
{  
  struct nessusd_threads * t = *threads;
  
  while(t && t->next)t = t->next;
  if(!t)return;
  t->hostname = emalloc(strlen(hostname)+1);
  strncpy(t->hostname, hostname, strlen(hostname));
  t->pid = pid;
  t->next = emalloc(sizeof(struct nessusd_threads));
  t->next->previous = t;
}

/*
 * Remove a thread from the GTOT
 */
void nessusd_thread_unregister(threads, thread)
   struct nessusd_threads ** threads;
   struct nessusd_threads * thread;
{
  if(!thread)return;
  if(thread->previous)thread->previous->next = thread->next;
  else {
  if(thread->next)*threads = thread->next;
  else *threads = emalloc(sizeof(struct nessusd_threads));
  }
  if(thread->next)thread->next->previous = thread->previous;
  efree(&thread->hostname);
  efree(&thread);
}

/* 
 * Kill all the threads that are in charge of the 
 * host <hostname>
 */
void nessusd_thread_kill_by_name(threads, thread, hostname)
     struct nessusd_threads ** threads;
     struct nessusd_threads * thread;
     char * hostname;
{
  struct nessusd_threads * t = thread;
  while(t && t->next && t->hostname && strcmp(hostname, t->hostname))t = t->next;
  if(t && t->hostname && !strcmp(hostname, t->hostname))
    {
      struct nessusd_threads * next = t->next;
      TERMINATE_THREAD(t->pid);
      nessusd_thread_unregister(threads, t);
      nessusd_thread_kill_by_name(threads, next, hostname); 
    }
}


/*
 * Kill all the threads
 */
void nessusd_thread_kill_all(threads)
     struct nessusd_threads * threads;
{
 struct nessusd_threads * t = threads;
 
 while(t && t->next)
 {
  TERMINATE_THREAD(t->pid);
  nessusd_thread_unregister(&threads, t);
  t = t->next;
 }
}
  
   
/*
 * Returns the number of threads, and waits for
 * those which are not finished yet.
 */
int
wait_for_a_thread(threads)
  struct nessusd_threads ** threads;
{
  struct nessusd_threads * t = *threads;
  int num_threads = 0;
  
  while(t && t->next)
    {
     struct nessusd_threads * next = t->next;
#ifdef USE_FORK_THREADS
     int status = 0;
     int code = waitpid(t->pid, &status, WNOHANG);
#endif

#ifdef USE_PTHREADS
      int code = pthread_mutex_trylock(&t->pid->mutex);
#endif

     num_threads++;
     
#ifdef USE_FORK_THREADS
     if(code==-1 || (code>0 && WIFEXITED(status)))
#endif

#ifdef USE_PTHREADS
     if(!code)
#endif

#ifdef USE_NT_THREADS
     if((WaitForSingleObject(t->pid, 250))!=WAIT_TIMEOUT)
#endif

	{ 
	      nessusd_thread_unregister(threads, t);
	      num_threads--;
	}
      t = next;
    }
  return(num_threads);
}
   
/*
 * Create a thread
 */
nthread_t
create_thread(function, argument)
  void * function;
  void * argument;
{
#ifdef USE_NT_THREADS
 int pid;
 HANDLE thread;
 thread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)function, 
                  (void *)argument,
                  0, &pid);
 return(thread);
#endif


#ifdef USE_FORK_THREADS
 int pid = fork();
 thread_func_t func = (thread_func_t)function;
 if(!pid)
 {
  (*func)(argument);
  EXIT(0);
 }
 else return(pid);
 return(0);
#endif

#ifdef USE_PTHREADS
 struct thread_args * args;
 pthread_attr_t attr;
 pthread_mutexattr_t mutex_attr;
 nthread_t ret = emalloc(sizeof(_nthread_t));
 args = emalloc(sizeof(struct thread_args));
 
 pthread_attr_init(&attr);
 pthread_mutexattr_init(&mutex_attr);
 pthread_mutex_init(&ret->mutex,&mutex_attr);
 pthread_mutex_unlock(&ret->mutex);
 args->mutex = &ret->mutex;
 pthread_mutex_lock(&ret->mutex);
 args->func = function;
 args->arg = argument;
 pthread_create(&ret->thread, &attr, (void *)start_pthread, (void *)args);
 return(ret);
#endif /* USE_PTHREADS */

} 


/*
 * Set up a timeout for a thread and kill it 
 */

void
thread_timeout(nthread_t thread, int timeout, int pi, struct arglist * args)
{
#ifndef USE_NT_THREADS
  int code = 0;
  struct timeval tv;
  int total_time = 0;
  int i = 0;
  char * buf = emalloc(255);
  while((code !=-1) &&(total_time < timeout))
  {
   bzero(buf, 255);
   tv.tv_sec = 0;
   tv.tv_usec = 100;
#ifdef USE_FORK_THREADS
   code = waitpid(thread, NULL, WNOHANG);
#else
   code = pthread_mutex_trylock(&thread->mutex);
   if(!code)code = -1;
   else code = 0;
#endif
   select(0, NULL, NULL, NULL, &tv);
   i++;
   read(pi, buf, 255);
   if(strlen(buf))piic_parse(args, pi, buf);
   if(i==100){
       i = 0;
       total_time++;
      }
  }
  efree(&buf);
  if(code != -1)

#else /* NT_THREADS */
  if((WaitForSingleObject(thread, timeout*1000))==WAIT_TIMEOUT)
#endif
          {
                log_write("Current plugin is slow to finish -- killing it");
                TERMINATE_THREAD(thread);
          }
}

#ifdef USE_PTHREADS
/*
 * Posix Threads specific functions...
 */
void exit_pthread(i)
 int i;
{
 pthread_exit(&i);
}


void start_pthread(args)
 struct thread_args * args;
{
 thread_func_t func = (thread_func_t)args->func;
 pthread_cleanup_push((void *)cleanup_pthread, (void *)args);
#ifdef BROKEN_PTHREAD_CLEANUP_PUSH
 }
#endif
 (*func)(args->arg);
 EXIT(0);
}
 
void cleanup_pthread(args)
 struct thread_args * args;
{
 pthread_mutex_unlock(args->mutex);
 efree(&args);
}

#endif /* USE_PTHREADS */

