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

#include "hackerlab/os/errno.h"
#include "hackerlab/char/str.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/vu/vfdbuf.h"
#include "hackerlab/vu/printfmt.h"
#include "hackerlab/vu-network/url-socket.h"

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

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



/* __STDC__ prototypes for static functions */
static char * peer_addr_env (int fd);



/* Remote starting directory.
 * 
 * ftp://site/this
 *           ^^^^^
 */
static t_uchar * remote_home;

/* FTP fds 
 * 
 * These are the control connection to the remote FTP server.
 *
 */
static int in_fd;
static int out_fd;

/* rename_from_path
 * 
 * Between RNFR and RNTO, this holds the path name from RNFR.
 */
static t_uchar * rename_from_path;

/* Add -a to LIST and NLST commands?
 */

static is_wu_ftp = 0;



ftp_handler_t
ftp_init_handler (t_uchar *** new_env, t_uchar * site_spec, int use_wu_mode)
{
  t_uchar * site_spec_end;
  int errn;
  int code;
  t_uchar * repl_text;
  long repl_text_len;

  is_wu_ftp = use_wu_mode;

  /* connect to the FTP server
   */

  site_spec_end = str_chr_index (site_spec, '/');

  if (!site_spec_end)
    {
      remote_home = str_save (0, "/");
      site_spec = str_save (0, site_spec);
    }
  else
    {
      remote_home = str_save (0, site_spec_end);
      site_spec = str_save_n (0, site_spec, site_spec_end - site_spec);
    }

	
  repl_text = 0;
  if (0 > ftp_client_connect_and_login (&in_fd, &out_fd, 0, &errn, &code,
                                        &repl_text, &repl_text_len,
                                        site_spec))
					  
    {
      safe_printfmt (2, "unable to establish ftp connection to %s\n", site_spec);
      if (repl_text)
        safe_printfmt (2, "%.*s\n", (int)repl_text_len, repl_text);
      with_ftp_exit (1);
    }

  lim_free (0, repl_text);


  /* (remote) cd to the `remote_home' directory:
   */

  if (0 > ftp_client_cwd (0, &errn, &code, &repl_text, &repl_text_len, in_fd, out_fd, remote_home))
    {
      safe_printfmt (2, "unable to cd to remote directory: %s\n", remote_home);
      if (repl_text)
        safe_printfmt (2, "%.*s\n", (int)repl_text_len, repl_text);
      with_ftp_exit (1);
    }

  lim_free (0, repl_text);


  /* Get the inet addr of the remote server and add it to the subprocess
   * environment to help guard against spoofing.
   */
  *(t_uchar **)ar_push ((void **)new_env, 0, sizeof (t_uchar *)) = peer_addr_env (in_fd);


  /* Add our inet address as seen by the remote server to the environment 
   * so that wftp- clients can be sure to use the same address.
   */
  {
    struct sockaddr_in my_addr;
    int my_addr_len;
    t_uchar * my_addr_name;

    my_addr_len = sizeof (my_addr);
    if (0 > getsockname (out_fd, (struct sockaddr *)&my_addr, &my_addr_len))
      {
        with_ftp_panic ("getsockname failed?");
      }
	  
    my_addr_name = str_save (0, inet_ntoa (my_addr.sin_addr));
    *(t_uchar **)ar_push ((void **)new_env, 0, sizeof (t_uchar *)) = str_alloc_cat (0, "WFTP_MY_ADDR=", my_addr_name);
    lim_free (0, my_addr_name);
  }
      

  return ftp_handle_command;
}


void
ftp_handle_command (int connect_in, int connect_out, t_uchar * command, int command_len)
{
  int errn;
  int code;
  t_uchar * repl;
  long repl_len;

  command_len = trim_crnl (command, command_len);
  repl = 0;
  repl_len = 0;

  /* commands not requiring a data port */

  if (!str_cmp_prefix ("cd ", command))
    {
      /* "cd <path>" */
      if (0 > ftp_client_cwd (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, command + 3))
	{
	  if (errn)
	    {
	    ftp_protocol_error:
	      safe_printfmt (2, "with-ftp: FTP protocol error\n%s\n", repl ? repl : (t_uchar *)"  (no output from server)");
              with_ftp_exit(1);
	    }
	  else
	    {
	    ftp_command_error:
	      if ((0 > printfmt (&errn, connect_out, "ERROR %ld\n", repl_len))
		  || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len))))
		{
		  if (errn != EPIPE)
		    with_ftp_panic ("I/O error in ftp_handle_command (ftp_command_error)");
		}
	    }
	}
      else
	{
	ftp_command_ok:
	  if ((0 > printfmt (&errn, connect_out, "OK %ld\n", repl_len))
	      || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len))))
	    {
	      if (errn != EPIPE)
		with_ftp_panic ("I/O error in ftp_handle_command (ftp_command_ok)");
	    }
	}
    }
  else if (!str_cmp_prefix ("cdup", command))
    {
      /* "cdup" */
      if (0 > ftp_client_cdup (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("delete ", command))
    {
      /* "delete <path>" */
      if (0 > ftp_client_dele (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, command + 7))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("home", command))
    {
      /* "home" */
      if (0 > ftp_client_cwd (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, remote_home))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("rmdir ", command))
    {
      /* "rmdir <path>" */
      if (0 > ftp_client_rmd (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, command + 6))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("mkdir ", command))
    {
      /* "mkdir <path>" */
      if (0 > ftp_client_mkd (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, command + 6))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("noop", command))
    {
      /* "noop" */
      if (0 > ftp_client_noop (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("pwd", command))
    {
      t_uchar * pwd;
      long pwd_len;
      
      /* "pwd" */
      if (0 > ftp_client_pwd (0, &errn, &code, &pwd, &pwd_len, &repl, &repl_len, in_fd, out_fd))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	{
	  /* in with-ftp protocol, pwd generates a non-stanard reply
	   * to spare clients from parsing ftp replies.  We don't just
	   * send the full text of the FTP reply.  Instead, the reply
	   * is:
	   *
	   * 	OK <str_length(pwd) + 1>\n
	   *	pwd\n
	   * 
	   * Note the trailing newline which is not part of the pwd.
	   */
	  if (0 > printfmt (&errn, connect_out, "OK %ld\n%.*s\n", 1 + pwd_len, (int)pwd_len, pwd))
	    {
	      if (errn != EPIPE)
		with_ftp_panic ("I/O error in ftp_handle_command (pwd)");
	    }
	  lim_free (0, pwd);
	}
    }
  else if (!str_cmp_prefix ("rnfr ", command))
    {
      if (rename_from_path)
	lim_free (0, rename_from_path);

      rename_from_path = str_save (0, command + 5);
      goto ftp_command_ok;
    }
  else if (!str_cmp_prefix ("rnto ", command))
    {
      if (!rename_from_path)
	goto bad_command;
      
      if (0 > ftp_client_rename (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, rename_from_path, command + 5))
	{
	  if (errn)
	    goto ftp_protocol_error;
	  else
	    goto ftp_command_error;
	}
      else
	goto ftp_command_ok;
    }  


  /* commands requiring a data port */

  else
    {
      t_uchar * host;
      t_uchar * comma;
      t_uchar * port;
      t_uchar * args;
      t_ulong h;
      t_uint p;
      char * pasv_url;


      /* <cmd> host,port args...
       * or
       * <cmd> pasv args...
       */

      pasv_url = 0;
      host = str_chr_index (command, ' ');
      if (!host)
	goto bad_command;

      ++host;

      if (!str_cmp_prefix ("pasv ", host))
	{
	  t_ulong host_addr;
	  t_uint16 port;

	  /* passive mode */
	  /* block postcondition: args points at the list of args */
	  if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }

	  {
	    char * pos;
	    char addr[64];

	    host_addr = ntohl (host_addr);
	    port = ntohs (port);

	    pos = addr;
	    cvt_ulong_to_decimal (pos, (host_addr >> 24) & 0xff);
	    pos += str_length (pos);
	    *pos = '.';
	    ++pos;
	    cvt_ulong_to_decimal (pos, (host_addr >> 16) & 0xff);
	    pos += str_length (pos);
	    *pos = '.';
	    ++pos;
	    cvt_ulong_to_decimal (pos, (host_addr >> 8) & 0xff);
	    pos += str_length (pos);
	    *pos = '.';
	    ++pos;
	    cvt_ulong_to_decimal (pos, host_addr & 0xff);
	    pos += str_length (pos);
	    *pos = ':';
	    ++pos;
	    cvt_ulong_to_decimal (pos, port);
	    pos += str_length (pos);
	    *pos = 0;
	    pasv_url = str_alloc_cat (0, "inet://", addr);
	    if ((0 > printfmt (&errn, connect_out, "%s\n", pasv_url))
		|| (0 > vfdbuf_flush (&errn, connect_out)))
	      {
		if (errn != EPIPE)
		  with_ftp_panic ("I/O error in ftp_handle_command (pasv)");
	      }
	  }
	  args = host + 5;	/* "pasv " */
	}
      else
	{
	  /* active mode */
	  /* block postcondition: args points at the list of args */
	  comma = str_chr_index (host, ',');
	  if (!comma)
	    goto bad_command;

	  if (cvt_decimal_to_ulong (&errn, &h, host, comma - host))
	    goto bad_command;

	  port = comma + 1;
	  
	  args = str_chr_index (port, ' ');
	  if (!args)
	    goto bad_command;

	  if (cvt_decimal_to_uint (&errn, &p, port, args - port))
	    goto bad_command;

	  args += 1;

	  if (0 > ftp_client_port (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, h, p))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      lim_free (0, repl);
	      repl = 0;
	    }
	}

      /* args points at the list of args */
      
      if (!str_cmp_prefix ("get ", command))
	{
	  if (0 > ftp_client_type_image (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_begin_retr (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, args))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      if ((0 > printfmt (&errn, connect_out, "OK %ld\n", repl_len))
		  || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len)))
		  || (0 > vfdbuf_flush (&errn, connect_out)))
		{
		  if (errn != EPIPE)
		    with_ftp_panic ("I/O error in ftp_handle_command (get)");
		}
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_end_retr (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    goto ftp_command_ok;
	}
      else if (!str_cmp_prefix ("put ", command))
	{
	  if (0 > ftp_client_type_image (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_begin_stor (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, args))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      if ((0 > printfmt (&errn, connect_out, "OK %ld\n", repl_len))
		  || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len)))
		  || (0 > vfdbuf_flush (&errn, connect_out)))
		{
		  if (errn != EPIPE)
		    with_ftp_panic ("I/O error in ftp_handle_command (put)");
		}
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_end_stor (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    goto ftp_command_ok;
	}
      else if (!str_cmp_prefix ("nlist ", command))
	{
	  if (0 > ftp_client_type_ascii (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_begin_nlst (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, is_wu_ftp, args))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      if ((0 > printfmt (&errn, connect_out, "OK %ld\n", repl_len))
		  || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len)))
		  || (0 > vfdbuf_flush (&errn, connect_out)))
		{
		  if (errn != EPIPE)
		    with_ftp_panic ("I/O error in ftp_handle_command (nlist)");
		}
	      lim_free (0, repl);
	      repl = 0;
	    }

	  if (0 > ftp_client_end_nlst (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      goto ftp_command_ok;
	    }
	}
      else if (!str_cmp_prefix ("list ", command))
	{
	  if (0 > ftp_client_type_ascii (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      lim_free (0, repl);
	      repl = 0;
	    }
	  if (0 > ftp_client_begin_list (0, &errn, &code, &repl, &repl_len, in_fd, out_fd, is_wu_ftp, args))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      if ((0 > printfmt (&errn, connect_out, "OK %ld\n", repl_len))
		  || (repl && (0 > vu_write_retry (&errn, connect_out, repl, repl_len)))
		  || (0 > vfdbuf_flush (&errn, connect_out)))
		{
		  if (errn != EPIPE)
		    with_ftp_panic ("I/O error in ftp_handle_command (nlist)");
		}
	      lim_free (0, repl);
	      repl = 0;
	    }
	  if (0 > ftp_client_end_list (0, &errn, &code, &repl, &repl_len, in_fd, out_fd))
	    {
	      if (errn)
		goto ftp_protocol_error;
	      else
		goto ftp_command_error;
	    }
	  else
	    {
	      goto ftp_command_ok;
	    }
	}
      else
	{
	bad_command:
	  safe_printfmt (2, "with-ftp: bad command recieved (%s)\n", command);
	  with_ftp_panic ("with-ftp protocol error");
	}
    }
      

  lim_free (0, repl);
  if (0 > vfdbuf_flush (&errn, connect_out))
    with_ftp_panic ("vfdbuf_flush error in ftp_handle_command");
}


static char *
peer_addr_env (int fd)
{
  struct sockaddr_in addr;
  int size;
  char * addr_name;
	  

  size = sizeof (addr);
  if (0 > getpeername (fd, (struct sockaddr *)&addr, &size))
    with_ftp_panic ("unable to get servers address (getpeername)");

  addr_name = inet_ntoa (addr.sin_addr);
  if (!addr_name)
    with_ftp_panic ("inet_ntoa failed on server address");

  return str_alloc_cat (0, "WFTP_SITE_ADDR=", addr_name);
}




/* tag: Tom Lord Sat Jan 18 15:39:34 2003 (ftp-handler.c)
 */
