/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXCOMP, NX protocol compression and NX extensions to this software     */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE.nxcomp which comes in the       */
/* source distribution.                                                   */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <grp.h>

#include "Pipe.h"
#include "Misc.h"
#include "Fork.h"

//
// Set the verbosity level.
//

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG

extern void RegisterChild(int child);

static int Psplit(const char *command, char *parameters[], int limit);

//
// These are slightly modified versions of popen(3) and pclose(3)
// that don't rely on a shell to be available on the system, so
// that they can also work on Windows. As an additional benefit,
// these functions give up all privileges before running the com-
// mand. Code is taken from the X distribution and, in turn, is
// based on libc from FreeBSD 2.2.
//

static struct pid
{
  struct pid *next;
  FILE *fp;
  int self;

} *pidlist;

//
// A very unsofisticated attempt to parse the command line and
// split each parameter in distinct strings. This is not going
// to work when dealing with parameters containing spaces, even
// if they are enclosed in quotes.
// 

int Psplit(const char *command, char *parameters[], int limit)
{
  char *line;
  char *value;

  int number;

  //
  // Preapare the list of parameters.
  //

  for (number = 0; number < limit; number++)
  {
    parameters[number] = NULL;
  }

  //
  // Copy the command to get rid of the
  // const qualifier.
  //

  line = new char[strlen(command) + 1];

  if (line == NULL)
  {
    goto PsplitError;
  }

  strcpy(line, command);

  number = 0;

  value = strtok(line, " ");

  while (value != NULL && number < limit)
  {
    #ifdef DEBUG
    *logofs << "Psplit: Got parameter '" << value
            << "'.\n" << logofs_flush;
    #endif

    parameters[number] = new char[strlen(value) + 1];

    if (parameters[number] == NULL)
    {
      goto PsplitError;
    }

    strcpy(parameters[number], value);

    number++;

    //
    // If this is the first parameter, then
    // copy it in the second position and
    // use it as the name of the command.
    //

    if (number == 1)
    {
      parameters[number] = new char[strlen(value) + 1];

      if (parameters[number] == NULL)
      {
        goto PsplitError;
      }

      strcpy(parameters[number], value);

      number++;
    }

    value = strtok(NULL, " ");
  }

  //
  // Needs at least to have the command itself and
  // the first argument, being again the name of
  // the command.
  //

  if (number < 2)
  {
    goto PsplitError;
  }

  return number;

PsplitError:

  #ifdef PANIC
  *logofs << "Psplit: PANIC! Can't split command line '"
          << command << "'.\n" << logofs_flush;
  #endif

  cerr << "Error" << ": Can't split command line '"
       << command << "'.\n";

  delete [] line;

  return -1;
}

FILE *Popen(char * const parameters[], const char *type)
{
  FILE *iop;

  struct pid *cur;
  int pdes[2], pid;

  if (parameters == NULL || type == NULL)
  {
    return NULL;
  }

  if ((*type != 'r' && *type != 'w') || type[1])
  {
    return NULL;
  }

  if ((cur = (struct pid *) malloc(sizeof(struct pid))) == NULL)
  {
    return NULL;
  }

  if (pipe(pdes) < 0)
  {
    SAFE_FREE(cur);

    return NULL;
  }

  //
  // Block all signals until command is exited.
  // We need to gather information about the
  // child in Pclose().
  //

  DisableSignals();

  switch (pid = Fork())
  {
    case -1:
    {
      //
      // Error.
      //

      #ifdef PANIC
      *logofs << "Popen: PANIC! Function fork failed. "
              << "Error is " << EGET() << " '" << ESTR()
              << "'.\n" << logofs_flush;
      #endif

      cerr << "Error" << ": Function fork failed. "
           << "Error is " << EGET() << " '" << ESTR()
           << "'.\n";

      close(pdes[0]);
      close(pdes[1]);

      SAFE_FREE(cur);

      return NULL;
    }
    case 0:
    {
      //
      // Child.
      //

      struct passwd *pwent = getpwuid(getuid());
      if (pwent) initgroups(pwent->pw_name,getgid());
      if (setgid(getgid()) == -1)
      {
        _exit(127);
      }
      if (setuid(getuid()) == -1)
      {
        _exit(127);
      }

      if (*type == 'r')
      {
        if (pdes[1] != 1)
        {
          //
          // Set up stdout.
          //

          dup2(pdes[1], 1);
          close(pdes[1]);
        }

        close(pdes[0]);
      }
      else
      {
        if (pdes[0] != 0)
        {
          //
          // Set up stdin.
          //

          dup2(pdes[0], 0);
          close(pdes[0]);
        }

        close(pdes[1]);
      }

      execvp(parameters[0], parameters + 1);

      exit(127);
    }
  }

  //
  // Parent. Save data about the child.
  //

  RegisterChild(pid);

  if (*type == 'r')
  {
    iop = fdopen(pdes[0], type);

    close(pdes[1]);
  }
  else
  {
    iop = fdopen(pdes[1], type);

    close(pdes[0]);
  }

  cur -> fp = iop;
  cur -> self = pid;
  cur -> next = pidlist;

  pidlist = cur;

  #ifdef TEST
  *logofs << "Popen: Executing ";

  for (int i = 0; i < 256 && parameters[i] != NULL; i++)
  {
    *logofs << "[" << parameters[i] << "]";
  }

  *logofs << " with descriptor " << fileno(iop)
          << ".\n" << logofs_flush;
  #endif

  return iop;
}

FILE *Popen(const char *command, const char *type)
{
  char *parameters[256];

  if (Psplit(command, parameters, 256) > 0)
  {
    FILE *file = Popen(parameters, type);

    for (int i = 0; i < 256; i++)
    {
      delete [] parameters[i];
    }

    return file;
  }
  else
  {
    #ifdef PANIC
    *logofs << "Popen: PANIC! Failed to parse command '"
            << command << "'.\n" << logofs_flush;
    #endif

    cerr << "Error" << ": Failed to parse command '"
         << command << "'.\n";

    return NULL;
  }
}

int Pclose(FILE *iop)
{
  struct pid *cur, *last;

  int pstat;
  int pid;

  #ifdef TEST
  *logofs << "Pclose: Closing command with output "
          << "on descriptor " << fileno(iop) << ".\n"
          << logofs_flush;
  #endif

  fclose((FILE *) iop);

  for (last = NULL, cur = pidlist; cur; last = cur, cur = cur -> next)
  {
    if (cur -> fp == iop)
    {
      break;
    }
  }

  if (cur == NULL)
  {
    #ifdef PANIC
    *logofs << "Pclose: PANIC! Failed to find the process "
            << "for descriptor " << fileno(iop) << ".\n"
            << logofs_flush;
    #endif

    cerr << "Error" << ": Failed to find the process "
         << "for descriptor " << fileno(iop) << ".\n";

    return -1;
  }

  do
  {
    #ifdef TEST
    *logofs << "Pclose: Going to wait for process "
            << "with pid '" << cur -> self << "'.\n"
            << logofs_flush;
    #endif

    pid = waitpid(cur -> self, &pstat, 0);
  }
  while (pid == -1 && errno == EINTR);

  if (last == NULL)
  {
    pidlist = cur -> next;
  }
  else
  {
    last -> next = cur -> next;
  }

  SAFE_FREE(cur);

  //
  // Child has finished and we called the
  // waitpid(). We can enable signals again.
  //

  EnableSignals();

  return (pid == -1 ? -1 : pstat);
}
