/* with-ftp.c
 *
 ****************************************************************
 * Copyright (C) 2001, 2002, 2003  Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/stdlib.h"
#include "hackerlab/os/sys/time.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/os/sys/types.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/unistd.h"

#include "hackerlab/hash/hash-utils.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu-network/url-socket.h"

#include "ftp-utils/clients/ftp-protocol.h"

#include "ftp-utils/with-ftp/ftp-handler.h"
#include "ftp-utils/with-ftp/local-handler.h"
#include "ftp-utils/with-ftp/sftp-handler.h"
#include "ftp-utils/with-ftp/dav-handler.h"

#include "ftp-utils/with-ftp/with-ftp.h"




extern char ** environ;



/* __STDC__ prototypes for static functions */
static void child_changed (int sig);


/****************************************************************
 * Command Line Processing Data Structures
 * 
 * 
 */

static t_uchar * program_name = "with-ftp";
static t_uchar * usage = "with-ftp [options] site command args...";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
				   "\n"
				   "Copyright 2001, 2002, 2003 Tom Lord\n"
				   "Copyright 2002 Scott Parish\n"
				   "\n"
				   "This is free software; see the source for copying conditions.\n"
				   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
				   "PARTICULAR PURPOSE.\n"
				   "\n"
				   "Report bugs to " cfg__with_ftp_bug_mail ".\n"
				   "\n");

#define OPTS(OP, OP2) \
  OP (opt_help_msg, "h", "help", 0, \
      "Display a help message and exit.") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "Display a release identifier string") \
  OP2 (opt_version, 0, 0, 0, "and exit.")

static t_uchar long_help[] = ("invoke a command from with an FTP(-style) client session\n"
                              "Begin an FTP session or other file system client session with SITE\n"
                              "then invoke COMMAND with ARGS.\n"
                              "\n"
                              "COMMAND can in turn invoke the various \"wftp-*\" programs (such as\n"
                              "\"wftp-ls\") as part of the FTP session.\n"
                              "\n"
                              "SITE should be specified as a directory name (for local file systems)\n"
                              "or as a URL using one of these methdos:\n"
                              "\n"
                              "         ftp:\n"
                              "\n"
                              "Several environment variables are set when COMMAND is invoked.  These\n"
                              "are:\n"
                              "\n"
                              "         WFTP            a (non-standard) \"unix:\" method URL\n"
                              "                         specifying the unix domain socket over\n"
                              "                         which \"wftp-*\" programs speak to \"with-ftp\"\n"
                              "\n"
                              "         WFTP_SITE       the SITE argument to \"with-ftp\"\n"
                              "\n"
                              "         WFTP_SECRET     a (plain-text) authentication cookie that\n"
                              "                         \"wftp-*\" programs send to \"with-ftp\"\n"
                              "\n"
                              "         WFTP_SITE_ADDR  the IP address associated with SITE\n"
                              "\n"
                              "         WFTP_MY_ADDR    the (local) IP address this host is using to\n"
                              "                         talk with SITE\n");


enum options
{
  OPTS (OPT_ENUM, OPT_IGN)  
};

struct opt_desc opts[] = 
{
  OPTS (OPT_DESC, OPT_DESC)
    {-1, 0, 0, 0, 0}
};


/****************************************************************
 * Global with-ftp State
 * 
 */

/* data_server_fd, data_socket_path, my_data_url
 * 
 * If data connections have to be served by this process (e.g., for local 
 * file access rather than FTP access, then data_server_fd is a server
 * socket to which passive mode clients can connect.
 * 
 * my_data_url is a URI for data_socket_path.
 *
 */
t_uchar * my_data_url = 0;


/* What to do on SIGCHLD 
 */
sigjmp_buf sigchld_jmp;
sigjmp_buf sigchld_jmp_tool;
int tool_running = 0;

/* Paths to possible unix domain sockets that need to be cleaned up upon
 * exit.
 */
static t_uchar * socket_path = 0;
static t_uchar * data_socket_path = 0;




int
main (int argc, char * argv[])
{
  int errn;
  int o;
  struct opt_parsed * option;

  /* The site to which we are providing access via wftp- commands
   */
  t_uchar * site;

  /* Data used when starting the subprocess:
   */
  int home_fd;
  t_uchar ** new_env;
  char ** command_argv;
  int subproc_pid;

  /* The control socket (unix domain) for connection to wftp- clients:
   */
  t_uchar * my_url;
  int server_fd;
  t_uchar secret[65];


  /* The data socket (unix domain) for connection to wftp- clients
   * that need one (not used for genuine FTP sessions).
   */
  int data_server_fd;

  /* The handler used to interpret wftp protocol.
   */
  ftp_handler_t handle_command;


  /* Local Variable Initializations:
   */
  home_fd = safe_open (".", O_RDONLY, 0);
  new_env = 0;

  socket_path = 0;
  my_url = 0;
  server_fd = -1;

  data_socket_path = 0;
  data_server_fd = -1;


  /* VU initializations:
   */

  safe_buffer_fd (0, 0, O_RDONLY, 0);
  safe_buffer_fd (1, 0, O_WRONLY, 0);
  url_socket_push_server_handler (url_socket_unix, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);


  /* Signal mask initialization:
   */

  {
    sigset_t interesting_signal_mask;

    sigfillset (&interesting_signal_mask);
    sigdelset (&interesting_signal_mask, SIGHUP);
    sigdelset (&interesting_signal_mask, SIGINT);
    sigdelset (&interesting_signal_mask, SIGCHLD);
    sigdelset (&interesting_signal_mask, SIGTRAP);
    if (0 > sigprocmask (SIG_SETMASK, &interesting_signal_mask, 0))
      {
        panic ("sigprocmask(2)");
      }
  }

  
  /* parse command line options
   */
  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, version_string, long_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
	break;
      switch (o)
	{
	default:
	  safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
	  panic ("internal error parsing arguments");

	usage_error:
	  opt_usage (2, argv[0], program_name, usage, 1);
	  panic_exit ();

	/* bogus_arg: */
	  safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
	  goto usage_error;
	}
    }

  if (argc < 3)
    goto usage_error;

  /* parse ordinary arguments
   */

  site = argv[1];
  *(t_uchar **)ar_push ((void **)&new_env, 0, sizeof (t_uchar *)) = str_alloc_cat (0, "WFTP_SITE=", site);
  
  command_argv = argv + 2;
  

  /* Create a socket to which wftp clients can connect to talk
   * to this process.
   */
  {
    t_ulong my_pid;
    char my_pid_str[sizeof (t_ulong) * 4];
    
    my_pid = getpid ();
    cvt_ulong_to_decimal (my_pid_str, my_pid);
    
    socket_path = str_alloc_cat_many (0, getenv ("HOME"), "/.with-ftp", str_end);
    vu_mkdir (&errn, socket_path, 0700);
    socket_path = str_realloc_cat_many (0, socket_path, "/", my_pid_str, str_end);
    my_url = str_alloc_cat (0, "unix:", socket_path);
    *(t_uchar **)ar_push ((void **)&new_env, 0, sizeof (t_uchar *)) = str_alloc_cat (0, "WFTP=", my_url);
    server_fd = url_socket_create_server_socket (&errn, my_url);
    if (server_fd < 0)
      {
        safe_printfmt (2, "with-ftp: error creating server socket %s\n", socket_path);
        safe_printfmt (2, "  error %d: %s", errn, errno_to_string (errn));
        with_ftp_panic ("i/o error");
      }
  }
  
  if (!str_cmp_prefix ("ftp://", site))
    handle_command = ftp_init_handler (&new_env, site + 6, 0); /* "ftp://" has six characters */
  else   if (!str_cmp_prefix ("wu-ftp://", site))
    handle_command = ftp_init_handler (&new_env, site + 9, 1); /* "wu-ftp://" has nine characters */
  else
    {
      /* A non-FTP site:
       */

      /* Create a data socket for wftp-* clients.
       * 
       * This socket acts like an FTP data socket.
       */
      {
        data_socket_path = str_alloc_cat (0, socket_path, ".data");
        my_data_url = str_alloc_cat (0, my_url, ".data");
          
        vu_unlink (&errn, data_socket_path);
        data_server_fd = url_socket_create_server_socket (&errn, my_data_url);

        if (data_server_fd < 0)
          {
            safe_printfmt (2, "with-ftp: error creating server sockets\n");
            safe_printfmt (2, "  error %d: %s", errn, errno_to_string (errn));
            with_ftp_panic ("i/o error");
          }
      }
      
      if (!str_cmp_prefix ("http://", site))
        handle_command = dav_init_handler (&new_env, "http", site + 7, program_name);
      else if (!str_cmp_prefix ("https://", site))
        handle_command = dav_init_handler (&new_env, "https", site + 8, program_name);
      else if (!str_cmp_prefix ("sftp://", site))
        handle_command = sftp_init_handler (&new_env, site + 7);
      else
        handle_command = local_init_handler (&new_env, site);
    }
  
  
  
  
  /* prepare for the subprocess' eventual death
   */
  if (sigsetjmp (sigchld_jmp, 1))
    {
      int wait_pid;
      int wait_status;
	
      /* In the body of this sigsetjmp, nothing may happen which may
       * cause reentrancy problems.  This code can be reached asynchronously
       * from just about anywhere.
       */

      /* Did the subprocess exit?
       * If so, exit, with status 0 iff subpocess had status 0.
       * 
       * Note that the sftp_client creates some ambiguity here: it may be
       * the the user's subprocess that exited, or it may be the `sftp' 
       * subprocess.   That should print an error message;  currently it will
       * print a cryptic error message.
       * 
       */

      wait_pid = waitpid (subproc_pid, &wait_status, WUNTRACED);
      unlink (socket_path);	/* can not use vu_unlink here since that may call malloc */
      if (data_socket_path)
        unlink (data_socket_path);	/* can not use vu_unlink here since that may call malloc */

      if (wait_pid < 0)
        panic ("with-ftp: error waiting for subprocess\n");
      else if (wait_pid == 0)
        panic ("with-ftp: got SIGCHLD but waitpid returns 0\n");
      else if (WIFEXITED (wait_status) && !WEXITSTATUS (wait_status))
        {
          _exit (0);
        }
      else
        {
          _exit (1);
        }
    }
  else
    signal (SIGCHLD, child_changed);
  
  
  /* fire up the sub-process
   */
  {
    t_uchar * path;
    t_uchar ** path_dirs;
    t_uchar * executable;


    /* `with-ftp' operates as a server, accepting connections from
     * commands such as `wftp-cd' and `wftp-ls'.
     * 
     * As a mild security enhancement, we generate a random string
     * and pass it to the `with-ftp' subprocess in the environment.
     * `wftp-*' commands must authenticate themselves to `with-ftp'
     * by passing back that string.
     */
    {
      struct timeval time;
      unsigned long hash;
      int z;

      if (0 > gettimeofday (&time, 0))
        {
          with_ftp_panic ("gettimeofday failed");
        }

      srand ((unsigned int)(getpid() + (time.tv_sec ^ time.tv_usec)));

      for (z = 0; z < (sizeof (secret) - 1); ++z)
        {
          hash = (unsigned long)rand();
          secret[z] = (((hash & 0xf) < 10) ? '0' + (hash & 0xf) : 'a' + (hash & 0xf) - 10);
          hash >>= 4;
          secret[z] = (((hash & 0xf) < 10) ? '0' + (hash & 0xf) : 'a' + (hash & 0xf) - 10);
          hash >>= 4;
          hash = hash_ul (hash);
          if (   (0 > gettimeofday (&time, 0))
              || (0 > gettimeofday (&time, 0)))
            {
              with_ftp_panic ("gettimeofday failed");
            }
          hash ^= time.tv_sec ^ time.tv_usec;
          srand ((unsigned int)hash);
        }
      secret[z] = 0;
      *(t_uchar **)ar_push ((void **)&new_env, 0, sizeof (t_uchar *)) = str_alloc_cat (0, "WFTP_SECRET=", secret);
    }

    path = getenv ("PATH");
    if (!path)
      path = "/bin:/usr/bin";

    path_dirs = path_parse (0, path);

    executable = path_find_executable (0, path_dirs, command_argv[0]);

    if (!executable)
      {
        safe_printfmt (2, "with-ftp: %s not found\n", command_argv[0]);
        with_ftp_exit (1);
      }

    subproc_pid = fork ();

    if (subproc_pid < 0)
      {
        with_ftp_panic ("unable to fork");
      }

    if (subproc_pid == 0)
      {
        int x;

        safe_fchdir (home_fd);
        safe_close (home_fd);
	  
        for (x = 0; environ[x]; ++x)
          {
            if (   str_cmp_prefix ("WFTP_MY_ADDR=", environ[x])
                && str_cmp_prefix ("WFTP_SITE=", environ[x])
                && str_cmp_prefix ("WFTP_SECRET=", environ[x])
                && str_cmp_prefix ("WFTP_SITE_ADDR=", environ[x])
                && str_cmp_prefix ("WFTP=", environ[x]))
              *(t_uchar **)ar_push ((void **)&new_env, 0, sizeof (t_uchar *)) = environ[x];
          }

        *(t_uchar **)ar_push ((void **)&new_env, 0, sizeof (t_uchar *)) = 0;
	  
	  
        execve (executable, command_argv, (char **)new_env);
        safe_printfmt (2, "with-ftp: unable to exec %s\n", executable);
        exit (1);
      }
  }
  
  
  /* the main loop
   */
  
  while (1)
    {
      /* connect_in/connect_out
       * 
       * A RDONLY and WRONLY pair for the current wftp-ftp client connection.
       */
      int connect_in = -1;
      int connect_out = -1;


      /* Wait for a connection from a local client.
       * Meanwhile, ftp_keepalive runs on the alarm signal.
       */
    retry_accept:
      connect_in = safe_open (my_url, O_RDWR, 0);

      /* connect_in = url_inet_server_accept (&errn, server_fd); */

      if (0 > connect_in)
        {
          /* errn *shouldn't* ever be EINTR, but if it were, this
           * is how it should be handled, and this is safe.
           */
          if (errn == EINTR)
            goto retry_accept;

          safe_printfmt (2, "with-ftp: internal error during accept (%d)\n", errn);
          with_ftp_exit (1);
        }

      connect_out = safe_dup (connect_in);

      safe_buffer_fd (connect_in, 0, O_RDONLY, 0);
      safe_buffer_fd (connect_out, 0, O_WRONLY, 0);

      {
        t_uchar * command;
        long command_len;

        /* authenticate the client */
        safe_next_line (&command, &command_len, connect_in);

        if (str_cmp_n (secret, sizeof (secret) - 1, command, (command_len ? command_len - 1 : 0)))
          {
            safe_close (connect_in);
            safe_close (connect_out);
            panic_msg ("");
            panic_msg ("ATTENTION:");
            panic_msg ("");
            panic_msg ("with-ftp: INCORRECT AUTH TOKEN RECEIVED");
            panic_msg ("");
            panic_msg ("IS THIS A CRACK ATTEMPT?");
            panic_msg ("");
            kill (subproc_pid, SIGKILL);
            with_ftp_panic ("exitting");
          }
      }
	
      /* Read a sequence of single line commands from the local
       * client, handling each.  On end of file, close the local
       * client connection and move on.
       */
      while (1)
        {
          t_uchar * command;
          long command_len;

          safe_next_line (&command, &command_len, connect_in);

          if (!str_cmp_prefix ("crack", command))
            {
              panic_msg ("");
              panic_msg ("ATTENTION:");
              panic_msg ("");
              panic_msg ("with-ftp: CLIENT PROGRAM (e.g. wftp-ls) DETECTED BAD DATA PORT PEER");
              panic_msg ("");
              panic_msg ("IS THIS A CRACK ATTEMPT?");
              panic_msg ("");
              kill (subproc_pid, SIGKILL);
              with_ftp_panic ("exitting");
            }
		

          if (!command)
            {
              int ign;
              /* EOF on command connection
               */
              vu_close (&ign, connect_in);
              vu_close (&ign, connect_out);
              break;
            }
          else
            {
              handle_command (connect_in, connect_out, command, command_len);
            }
        }
    }
  
  return 0;
}



static void
child_changed (int sig)
{
  if (tool_running)
    siglongjmp (sigchld_jmp_tool, 1);
  else
    siglongjmp (sigchld_jmp, 1);
}



void
with_ftp_exit (int status)
{
  int errn;

  if (socket_path)
    vu_unlink (&errn, socket_path);
  if (data_socket_path)
    vu_unlink (&errn, data_socket_path);
  exit (status);
}

void
with_ftp_panic (t_uchar * message)
{
  int errn;

  if (socket_path)
    vu_unlink (&errn, socket_path);
  if (data_socket_path)
    vu_unlink (&errn, data_socket_path);
  panic (message);
}


/* tag: Tom Lord Tue Dec  4 14:41:23 2001 (with-ftp.c)
 */
