/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2006
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-process.c :: Midnight Code process library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>
#include <fcntl.h>
#include <linux/sockios.h>

#include "preferences.h"
#include "midnightcode-parse.h"
#include "midnightcode-terminal.h"
#include "midnightcode-process.h"



int
midnightcode_process_execute(char **args) {
  struct sigaction sig_action;
  char *execute_buf;
  int  execute_buf_size;
  int  sig_no;
  int  arg_index;


  if(strlen(args[1]) < 1)
    return -1;

  for(sig_no = 1; sig_no < NSIG; sig_no++)
    SETSIG(sig_action, sig_no, SIG_DFL, SA_RESTART);

  execvp(args[1], args + 1);

  if(errno == ENOEXEC) {
    execute_buf_size = 0;
    for(arg_index=0; arg_index <= 64 && args[arg_index] != NULL; arg_index++)
      execute_buf_size += strlen(args[arg_index]);
    execute_buf = malloc(execute_buf_size);
    if(!execute_buf)
      return -1;
    memset(execute_buf, 0, 4096);

    args[1] = "/bin/sh";
    args[2] = "-c";
    strcpy(execute_buf, "exec ");
    for(arg_index=0; arg_index <= 64 && args[arg_index] != NULL; arg_index++)
      strncat(execute_buf, args[arg_index],
              execute_buf_size - strlen(execute_buf) - 1);
    args[3] = execute_buf;
    args[4] = NULL;
    execvp(args[1], args + 1);

    free(execute_buf);
  }

  return -1;
}



int
midnightcode_process_execute_construct(int options, int terminal,
                                       char **args) {
  sigset_t sig_mask_old, sig_mask_new;
  struct sigaction sig_action;
  pid_t pid, wait_pid, pgrp;
  char terminal_name[12];
  int  status;
  int  console_sock;
  int  ret_i;


  sigemptyset(&sig_mask_new);
  sigaddset(&sig_mask_new, SIGCHLD);
  sigprocmask(SIG_BLOCK, &sig_mask_new, &sig_mask_old);

  switch (pid = fork()) {
    case -1:
      exit(-1);
      break;

    case 0:
      if(options & MIDNIGHTCODE_PROCESS_OPTION_NULLIO ||
         options & MIDNIGHTCODE_PROCESS_OPTION_CONSOLE ||
         terminal != 0) {
        close(0);
        close(1);
        close(2);
      }
      if(options & MIDNIGHTCODE_PROCESS_OPTION_NULLIO) {
        console_sock = open("/dev/null", O_RDWR);
        dup(console_sock);
        dup(console_sock);
      }
      sigprocmask(SIG_SETMASK, &sig_mask_old, NULL);
      if(options & MIDNIGHTCODE_PROCESS_OPTION_CONSOLE) {
        setsid();
        if(midnightcode_terminal_console_open(&console_sock) >= 0) {
          (void)ioctl(console_sock, TIOCSCTTY, 1);
          dup(console_sock);
          dup(console_sock);
        }
        switch (pid = fork()) {
          case -1:
            exit(-1);
            break;

          case 0:
            midnightcode_terminal_properties(
              MIDNIGHTCODE_TERMINAL_VIRTUAL_NULL,
              MIDNIGHTCODE_TERMINAL_CONTROL_UNIX);
            ret_i = midnightcode_process_execute(args);
            exit(ret_i);
            break;

          default:
            SETSIG(sig_action, SIGINT, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGTSTP, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGQUIT, SIG_IGN, SA_RESTART);
            SETSIG(sig_action, SIGCHLD, SIG_DFL, SA_RESTART);
            while((wait_pid = waitpid(pid, &status, 0)) != pid) {
              if (wait_pid < 0 && errno == ECHILD) {
                break;
              }
            }
            pgrp = tcgetpgrp(wait_pid);
            if (pgrp != getpid())
              exit(0);
            switch (pid = fork()) {
              case -1:
                exit(-1);
                break;

              case 0:
                setsid();
                (void)ioctl(console_sock, TIOCSCTTY, 1);
                exit(0);
                break;

              default:
                while((wait_pid = waitpid(pid, &status, 0)) != pid) {
                  if (wait_pid < 0 && errno == ECHILD) {
                    break;
                  }
                }
                exit(0);
                break;
            }
            break;
        }
      } else {
        setsid();
        if(terminal != 0) {
          if(terminal < 1 || terminal > 9) {
            console_sock = open("/dev/null", O_RDWR);
          } else {
            memset(terminal_name, 0, 12);
            sprintf(terminal_name, "/dev/tty%d", terminal);
            if ((console_sock = open(terminal_name, O_RDWR, 0777)) < 0) {
              console_sock = open("/dev/null", O_RDWR);
            }
          }
          midnightcode_terminal_properties(terminal,
            MIDNIGHTCODE_TERMINAL_CONTROL_UNIX);
        } else {
          if(midnightcode_terminal_console_open(&console_sock) < 0) {
            console_sock = open("/dev/null", O_RDWR);
          }
        }
        dup(console_sock);
        dup(console_sock);
      }
      ret_i = midnightcode_process_execute(args);
      exit(ret_i);
      break;

    default:
      sigprocmask(SIG_SETMASK, &sig_mask_old, NULL);
      if(options & MIDNIGHTCODE_PROCESS_OPTION_WAITTERM) {
        while((wait_pid = waitpid(pid, &status, 0)) != pid) {
          if (wait_pid < 0 && errno == ECHILD) {
            break;
          }
        }
      }
      break;
  }
  if(options & MIDNIGHTCODE_PROCESS_OPTION_WAITTERM) {
    if (WIFEXITED(status)) {
      if(options & MIDNIGHTCODE_PROCESS_OPTION_EXITCODE) {
        return(WEXITSTATUS(status));
      } else {
        return 0;
      }
    }
    if(WIFSIGNALED(status)) {
      return(WTERMSIG(status));
    }
  } else {
    if(options & MIDNIGHTCODE_PROCESS_OPTION_GETPID) {
      return pid;
    } else {
      return 0;
    }
  }

  return -1;
}



int
midnightcode_process_parser(char **execute_buf, char *process_buf,
                                       char **args) {
  char *parse_buf;
  int  parse_buf_size;
  int  arg_index;


  if(strlen(process_buf) < 1)
    return -1;
  parse_buf_size = strlen(process_buf) + 10;
  parse_buf = malloc(parse_buf_size);
  if(!parse_buf)
    return -1;

  memset(parse_buf, 0, parse_buf_size);

  if(midnightcode_parse_any(process_buf, "~`!$^&*()|{}[];'<>?")) {

    if(strlen("exec ") > (parse_buf_size - strlen(process_buf))) {
      free(parse_buf);
      return -1;
    }
    strncpy(parse_buf, "exec ", parse_buf_size - 1);
    strncat(parse_buf, process_buf, parse_buf_size - strlen(parse_buf) - 1);
    args[1] = "/bin/sh";
    args[2] = "-c";
    args[3] = parse_buf;
    args[4] = NULL;
    arg_index = 4;

  } else {

    if((parse_buf_size - (int)strlen(process_buf) - 1) < 0) {
      free(parse_buf);
      return -1;
    }
    strncpy(parse_buf, process_buf, parse_buf_size - 1);
    arg_index = midnightcode_parse_strtoarg(parse_buf, strlen(parse_buf),
                                            args, 63);
    if(arg_index < 0)
      free(parse_buf);
  }
  args[0] = args[1];
  *execute_buf = parse_buf;


  return arg_index;
}



int
midnightcode_process(char *command_buf, int options, int terminal) {
  char *args[64];
  char *execute_buf;
  int  ret_i;


  if(strlen(command_buf) < 1)
    return -1;

  if((ret_i = midnightcode_process_parser(&execute_buf, command_buf,
                                                     args)) < 0)
    return ret_i;

  if((ret_i = midnightcode_process_execute_construct(options, terminal,
                                                     args)) < 0) {
    if(execute_buf)
      free(execute_buf);
    return ret_i;
  }

  if(execute_buf)
    free(execute_buf);


  return ret_i;
}



int
midnightcode_process_sysctl(char * control, char * value) {
  char * buffer;
  int buffer_size;
  int fd;


  if(midnightcode_parse_replace_char(control, ":", "/") < 0)
    return -1;

  buffer_size = 1024;
  buffer = malloc(buffer_size);
  if(!buffer)
    return -1;
  memset(buffer, 0, buffer_size);
  snprintf(buffer, buffer_size, "/proc/%s", control);
  if(strlen(buffer) == (unsigned int)(buffer_size - 1)) {
    free(buffer);
    return -1;
  }

  if(access(buffer, W_OK) != 0) {
    free(buffer);
    return 0;
  }

  if((fd = open(buffer, O_WRONLY)) == -1) {
    free(buffer);
    return -1;
  }
  free(buffer);

  if(write(fd, value, strlen(value)) == -1)
    return(-1);

  close(fd);


  return 1;
}


