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

#include "config-options.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/stdarg.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/vu/vu-utils.h"
#include "hackerlab/vu/printfmt-va.h"
#include "hackerlab/vu-network/url-socket.h"
#include "ftp-utils/wftp/wftp-client.h"

#ifndef SOCK_MAXADDRLEN
#undef MAX
#define MAX(A,B)  (((A)>=(B))?(A):(B))
#define SOCK_MAXADDRLEN	MAX(sizeof(struct sockaddr_un), sizeof (struct sockaddr_in))
#endif


/* __STDC__ prototypes for static functions */
static int wftp_read_reply (alloc_limits limits,
			    int * errn,
			    int * cmd_ok,
			    t_uchar ** msg, long * msg_len,
			    int in_fd);



static int use_passive_mode = 0;




int
wftp_client_init (int * errn,
		  int * in_fd,
		  int * out_fd,
		  t_uchar * with_ftp,
		  int passive_mode)
{
  char * secret;

  use_passive_mode = passive_mode;

  *in_fd = vu_open (errn, with_ftp, O_RDWR, 0);
  if (*in_fd < 0)
    return -1;

  *out_fd = vu_dup (errn, *in_fd);
  if (*out_fd < 0)
    {
      int e;
      vu_close (&e, *in_fd);
      return -1;
    }

  secret = getenv ("WFTP_SECRET");
  if (!secret)
    panic ("WFTP_SECRET not set");

  safe_printfmt (*out_fd, "%s\n", secret);

  if (   (0 > vfdbuf_buffer_fd (errn, *in_fd, 0, O_RDONLY, 0))
      || (0 > vfdbuf_buffer_fd (errn, *out_fd, 0, O_WRONLY, 0)))
    {
      int e;
      vu_close (&e, *in_fd);
      vu_close (&e, *out_fd);
      return -1;
    }

  return 0;
}



int
wftp_client_simple_cmd (alloc_limits limits,
			int * errn,
			int * cmd_ok,
			t_uchar ** msg, long * msg_len,
			int in_fd, int out_fd,
			t_uchar * fmt, ...)
{
  va_list ap;

  /* Send the command. */

  va_start (ap, fmt);

  if (   (0 > printfmt_va_list (errn, out_fd, fmt, ap))
      || (   vfdbuf_is_buffered (out_fd)
	  && (0 > vfdbuf_flush (errn, out_fd))))
    return -1;

  va_end (ap);


  /* read the reply */
  return wftp_read_reply (limits, errn, cmd_ok, msg, msg_len, in_fd);
}



int
wftp_client_get_data_cmd (alloc_limits limits,
			  int * errn,
			  int * cmd_ok,
			  t_uchar ** msg, long * msg_len,
			  int in_fd, int out_fd,
			  int data_source_fd,
			  int data_sync_fd,
			  int strip_cr,
			  t_uchar * cmd,
			  t_uchar * fmt, ...)
{
  va_list ap;
  int server_fd;
  t_ulong server_addr;
  int server_port;
  int data_fd;
  int ign;
  t_uchar * my_addr_name;
  

  /* create server for data port
   */
  if (!use_passive_mode)
    {
      my_addr_name = getenv ("WFTP_MY_ADDR");

      if (!my_addr_name)
	my_addr_name = "INADDR_ANY";
  
      server_fd = url_inet_server (limits, &server_addr, 0, &server_port, errn, my_addr_name, 0);
      if (0 > server_fd)
	return -1;

      /* Send the command.
       */
      va_start (ap, fmt);

      if (   (0 > printfmt (errn, out_fd, "%s %lu,%d ", cmd, server_addr, server_port))
	     || (0 > printfmt_va_list (errn, out_fd, fmt, ap))
	     || (   vfdbuf_is_buffered (out_fd)
		    && (0 > vfdbuf_flush (errn, out_fd))))
	{
	  vu_close (&ign, server_fd);
	  return -1;
	}
    }
  else
    {
      va_start (ap, fmt);

      if (   (0 > printfmt (errn, out_fd, "%s pasv ", cmd))
	     || (0 > printfmt_va_list (errn, out_fd, fmt, ap))
	     || (   vfdbuf_is_buffered (out_fd)
		    && (0 > vfdbuf_flush (errn, out_fd))))
	{
	  vu_close (&ign, server_fd);
	  return -1;
	}

      {
	t_uchar * data_server_host_spec;
	long data_server_host_spec_len;
	t_uchar * x;

	if (0 > vfdbuf_next_line (errn, &data_server_host_spec, &data_server_host_spec_len, in_fd))
	  {
	  syntax:
	    panic ("ill formted address in wftp_client_get_data_cmd");
	  }
	x = str_chr_index_n (data_server_host_spec, data_server_host_spec_len, '\n');
	if (!x)
	  goto syntax;
	data_server_host_spec = str_save_n (0, data_server_host_spec, x - data_server_host_spec);
	data_fd = safe_open (data_server_host_spec, O_RDWR, 0);
      }
    }

  va_end (ap);


  /* Get the preliminary reply. */
  *msg = 0;

  if (0 > wftp_read_reply (limits, errn, cmd_ok, msg, msg_len, in_fd))
    return -1;
  else
    {
      if (!*cmd_ok)
	{
	  static char bsd_msg[] = "550 No files found.";

	  /* The FreeBSD ftpd, labeled "ftpd 8.4", does something stupid
	   * to avoid opening a data connection for an empty file list.
	   * It sends "550 No files found.".  Automated clients can't 
	   * easily distinguish this from legitimate errors, such 
	   * as what you get for sending "NLST nosuchfile".
	   * 
	   * NLST of an empty directory should not return an error, so
	   * we try to compensate here.  
	   */

	  if (str_cmp (cmd, "nlst")
	      || ((sizeof (bsd_msg) - 1) > *msg_len)
	      || str_cmp_n (bsd_msg, sizeof (bsd_msg) - 1, *msg, sizeof (bsd_msg) - 1))
	    return 0;
	  else
	    {
	      *cmd_ok = 1;
	      return 0;
	    }
	}
    }


  /* Read data from data port until EOF, copying it to the data sync.
   *
   * Ths should be vfdbufized.
   */
  {
    int from_fd;
    int to_fd;
    
    if (!use_passive_mode)
      data_fd = url_inet_server_accept (errn, server_fd);

    if (*msg)
      lim_free (limits, *msg);
    *msg = 0;

    if (data_fd < 0)
      {
	if (!use_passive_mode)
	  vu_close (&ign, server_fd);
	return -1;
      }

    if (!use_passive_mode)
      {
	char addr_space[SOCK_MAXADDRLEN];
	struct sockaddr * addr;
	int size;


	addr = (struct sockaddr *)addr_space;
	size = SOCK_MAXADDRLEN;
	if (0 > getpeername (data_fd, addr, &size))
	  {
	    safe_printfmt (2, "wftp-client: getpeername error\n");
	    safe_printfmt (2, "  error %d: %s\n", errno, errno_to_string (errno));
	  crack:
	    vu_write (errn, out_fd, "crack attempt?\n", sizeof ("crack attempt?\n") - 1);
	    vfdbuf_flush (errn, out_fd);
	    sleep (2);
	    panic_msg ("");
	    panic_msg ("ATTENTION:");
	    panic_msg ("");
	    panic_msg ("with-ftp client: REMOTE DATA PORT HAS WRONG ADDRESS");
	    panic_msg ("");
	    panic_msg ("IS THIS A CRACK ATTEMPT?");
	    panic_msg ("");
	    panic ("exitting");
	  }

	if (addr->sa_family == AF_INET)
	  {
	    char * expected_addr_name;
	    struct sockaddr_in expected_addr;

	    expected_addr_name = getenv ("WFTP_SITE_ADDR");

	    if (!expected_addr_name)
	      goto crack;

	    expected_addr.sin_addr.s_addr = inet_addr (expected_addr_name);
	    if (expected_addr.sin_addr.s_addr == (t_ulong)-1)
	      goto crack;

	    if (expected_addr.sin_addr.s_addr != ((struct sockaddr_in *)addr)->sin_addr.s_addr)
	      goto crack;
	  }
      }

    if (data_source_fd >= 0)
      {
	from_fd = data_source_fd;
	to_fd = data_fd;
      }
    else
      {
	from_fd = data_fd;
 	to_fd = data_sync_fd;
      }
    
    safe_buffer_fd (from_fd, 0, O_RDONLY, vfdbuf_auto_shift);

    while (1)
      {
	t_uchar * bufpos;
	long buffered;

	safe_getbuf (0, 0, &bufpos, &buffered, 0, from_fd);
	if (!buffered && !safe_more (0, 0, &bufpos, &buffered, 0, from_fd, 0))
	  break;

	{
	  t_uchar * end_burst;

	  if (strip_cr)
	    end_burst = str_chr_index_n (bufpos, buffered, '\r');
	  else
	    end_burst = 0;

	  if (!end_burst)
	    {
	      if (0 > vu_write_retry (errn, to_fd, bufpos, buffered))
		{
		  if (*errn != EPIPE)
		    panic ("I/O error in wftp_client_get_data_cmd (if (!end_burst))");
		}
	      safe_advance (from_fd, buffered);
	      continue;
	    }
	  else if (end_burst == (bufpos + buffered))
	    {
	      if (!safe_more (0, 0, &bufpos, &buffered, 0, from_fd, 0))
		{
		  if (0 > vu_write_retry (errn, to_fd, bufpos, buffered))
		    {
		      if (*errn != EPIPE)
			panic ("I/O error in wftp_client_get_data_cmd (if (!safe_more))");
		    }
		  safe_advance (from_fd, buffered);
		  break;
		}
	      else
                continue;
	    }

	  if (end_burst[1] != '\n')
	    {
	      if (0 > vu_write_retry (errn, to_fd, bufpos, buffered))
		{
		  if (*errn != EPIPE)
		    panic ("I/O error in wftp_client_get_data_cmd (end_burst[1] != '\\n'))");
		}
	      safe_advance (from_fd, buffered);
	      continue;
	    }
	  else
	    {
	      if (0 > vu_write_retry (errn, to_fd, bufpos, end_burst - bufpos))
		{
		  if (*errn != EPIPE)
		    panic ("I/O error in wftp_client_get_data_cmd (end_burst[1] == '\\n'))");
		}
	      safe_advance (from_fd, 1 + end_burst - bufpos);
	      continue;
	    }
	}
      }

    if (!use_passive_mode)
      vu_close (&ign, server_fd);
    vu_close (&ign, data_fd);
  }

  /* server and data ports closed.  get the status reply from wftp server */
  
  return wftp_read_reply (limits, errn, cmd_ok, msg, msg_len, in_fd);
}





static int
wftp_read_reply (alloc_limits limits,
		 int * errn,
		 int * cmd_ok,
		 t_uchar ** msg, long * msg_len,
		 int in_fd)
{
  t_uchar * reply_code;
  long reply_code_len;
  t_uchar * reply_message;
  long reply_message_len;

  reply_code = 0;

  if (0 > vfdbuf_next_line (errn, &reply_code, &reply_code_len, in_fd))
    return -1;
  else
    {
      t_uchar * len;
      t_uchar * len_end;
      t_ulong amt;
      
      *cmd_ok = !str_cmp_prefix ("OK ", reply_code);

      len = str_chr_index_n (reply_code, reply_code_len, ' ');
      if (!len)
	{
	bad_format:
	  *errn = EINVAL;
	  return  -1;
	}
      ++len;
      len_end = str_chr_index_n (len, reply_code + reply_code_len - len, '\n');
      if (!len_end)
	goto bad_format;
      if (0 > cvt_decimal_to_ulong (errn, &amt, len, len_end - len))
	goto bad_format;

      reply_message_len = (long)amt;
      reply_message = lim_malloc (limits, reply_message_len);
      if (reply_message_len != vu_read_retry (errn, in_fd, reply_message, reply_message_len))
	{
	  lim_free (limits, reply_message);
	  return -1;
	}

      if (msg)
	{
	  *msg = reply_message;
	  *msg_len = reply_message_len;
	}
      else
	lim_free (limits, reply_message);

      return 0;
    }
}


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