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

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "config-options.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu-network/url-socket.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/string-files.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/file-contents.h"
#include "tla/libawk/trim.h"
#include "tla/libarch/archives.h"
#include "tla/libarch/pfs-ftp.h"




struct arch_pfs_ftp_session
{
  struct arch_pfs_session pfs;

  char * cwd;
  int in_fd;
  int out_fd;
  int is_wu;

  t_uchar * remote_pwd;
};




/* __STDC__ prototypes for static functions */
static int pfs_ftp_file_exists (struct arch_pfs_session * p, t_uchar * path);
static int pfs_ftp_is_dir (struct arch_pfs_session * p, t_uchar * path);
static t_uchar * pfs_ftp_file_contents (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_ftp_get_file (struct arch_pfs_session * p, int file_out_fd, t_uchar * path, int soft_errors);
static rel_table pfs_ftp_directory_files (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_ftp_put_file (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int file_in_fd, int soft_errors);
static int pfs_ftp_mkdir (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int soft_errors);
static int pfs_ftp_rename (struct arch_pfs_session * p, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors);
static int pfs_ftp_rmdir (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_ftp_rm (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int ftp_client_greet (alloc_limits limits,
                             int * errn,
                             int * code,
                             t_uchar ** text,
                             long * len,
                             int in_fd,
                             int out_fd);
static int ftp_client_login (alloc_limits limits,
                             int * errn,
                             int * code,
                             t_uchar ** text,
                             long * text_len,
                             int in_fd,
                             int out_fd,
                             t_uchar * name,
                             t_uchar * passwd);
static int ftp_client_read_reply (alloc_limits limits,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int fd);
static int ftp_client_read_simple_cmd_reply (alloc_limits limit,
                                             int * errn,
                                             int * code,
                                             t_uchar ** text,
                                             long * text_len,
                                             int in_fd);
static int ftp_client_printfmt (int * errn, int fd, char * fmt, ...);
static int ftp_client_pasv (alloc_limits limits,
                            int * errn,
                            int * code,
                            t_ulong * host,
                            t_uint16 * port,
                            t_uchar ** text,
                            long * text_len,
                            int in_fd,
                            int out_fd);
static int ftp_client_type_image (alloc_limits limits,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int in_fd,
                                  int out_fd);
static int ftp_client_begin_retr (alloc_limits limits,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int in_fd,
                                  int out_fd,
                                  t_uchar * path);
static int ftp_client_end_retr (alloc_limits limits,
                                int * errn,
                                int * code,
                                t_uchar ** text,
                                long * text_len,
                                int in_fd,
                                int out_fd);
static int ftp_client_begin_stor (alloc_limits limits,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int in_fd,
                                  int out_fd,
                                  t_uchar * path);
static int ftp_client_end_stor (alloc_limits limits,
                                int * errn,
                                int * code,
                                t_uchar ** text,
                                long * text_len,
                                int in_fd,
                                int out_fd);
static int ftp_client_begin_nlst (alloc_limits limits,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int in_fd,
                                  int out_fd,
                                  int is_wu_ftp,
                                  t_uchar * path,
                                  t_ulong host_addr,
                                  t_uint16 port);
static int ftp_client_end_nlst (alloc_limits limits,
                                int * errn,
                                int * code,
                                t_uchar ** text,
                                long * text_len,
                                int in_fd,
                                int out_fd);
static int ftp_client_mkd (alloc_limits limits,
                           int * errn,
                           int * code,
                           t_uchar ** text,
                           long * text_len,
                           int in_fd,
                           int out_fd,
                           t_uchar * path);
static int ftp_client_rename (alloc_limits limits,
                              int * errn,
                              int * code,
                              t_uchar ** text,
                              long * text_len,
                              int in_fd,
                              int out_fd,
                              t_uchar * from,
                              t_uchar * to);
static int ftp_client_dele (alloc_limits limits,
                            int * errn,
                            int * code,
                            t_uchar ** text,
                            long * text_len,
                            int in_fd,
                            int out_fd,
                            t_uchar * path);
static int ftp_client_rmd (alloc_limits limits,
                           int * errn,
                           int * code,
                           t_uchar ** text,
                           long * text_len,
                           int in_fd,
                           int out_fd,
                           t_uchar * path);
static int ftp_client_cwd (alloc_limits limits,
                           int * errn,
                           int * code,
                           t_uchar ** text,
                           long * text_len,
                           int in_fd,
                           int out_fd,
                           t_uchar * path);
static int ftp_client_pwd (alloc_limits limits,
                           int * errn,
                           int * code,
                           t_uchar ** path,
                           long * path_len,
                           t_uchar ** text,
                           long * text_len,
                           int in_fd,
                           int out_fd);



struct arch_pfs_vtable ftp_pfs_fns =
{
  pfs_ftp_file_exists,
  pfs_ftp_is_dir,

  pfs_ftp_file_contents,
  pfs_ftp_get_file,
  pfs_ftp_directory_files,

  pfs_ftp_put_file,

  pfs_ftp_mkdir,
  pfs_ftp_rename,

  pfs_ftp_rmdir,
  pfs_ftp_rm,
};




int
arch_pfs_ftp_supported_protocol (t_uchar * uri)
{
  if (!str_cmp_prefix ("ftp://", uri) || !str_cmp_prefix ("wu-ftp://", uri))
    return 1;
  else
    return 0;
}



struct arch_pfs_session *
arch_pfs_ftp_connect (t_uchar * uri)
{
  int errn;
  int code = 0;
  struct arch_pfs_ftp_session * answer = 0;
  t_uchar * host = 0;
  t_uchar * user = 0;
  t_uchar * passwd = 0;
  t_uchar * hostname = 0;
  int status = -1;
  t_uchar * text = 0;
  long text_len = 0;


  answer = (struct arch_pfs_ftp_session *)lim_malloc (0, sizeof (*answer));
  mem_set0 ((t_uchar *)answer, sizeof (*answer));
  answer->pfs.vtable = &ftp_pfs_fns;

  {
    t_uchar * prefix;
    t_uchar * start;
    t_uchar * stop;

    if (!str_cmp_prefix ("ftp://", uri))
      prefix = "ftp://";
    else
      {
        invariant (!str_cmp_prefix ("wu-ftp://", uri));
        prefix = "wu-ftp://";
        answer->is_wu = 1;
      }

    start = uri + str_length (prefix);
    stop = str_chr_index (start, '/');
    host = str_save_n (0, start, stop - start);
    answer->cwd = str_save (0, stop);
  }

  hostname = str_chr_index (host, '@');
  if (!hostname)
    {
      hostname = str_save (0, host);
      user = str_save (0, "anonymous");
      passwd = str_save (0, "arch(tla version: " cfg__std__package ")");
    }
  else
    {
      user = host;
      passwd = str_chr_index_n (user, hostname - user, ':');

      if (passwd)
        {
          ++passwd;
          user = str_save_n (0, user, (passwd - 1) - user);
          passwd = str_save_n (0, passwd, hostname - passwd);
          hostname = str_save (0, hostname + 1);
        }
      else
        {
          user = str_save_n (0, user, hostname - user);
          passwd = str_save (0, "ftp-utils");
          hostname = str_save (0, hostname + 1);
        }
    }

  /* hostname, user, passwd allocated strings.
   */
  answer->in_fd = url_inet_client (&errn, hostname, 21);

  if (answer->in_fd < 0)
    {
      status = -1;
      text = str_save (0, "Unable to connect to server");
      text_len = str_length (text);
    }
  else
    {
      int e;

      answer->out_fd = vu_dup (&errn, answer->in_fd);

      if (answer->out_fd < 0)
        {
          vu_close (&e, answer->in_fd);
          text = str_save (0, "Unable to duplicate the socket descriptor");
          text_len = str_length (text);
          status = -1;
        }
      else
        {
          if (   (0 > vfdbuf_buffer_fd (&errn, answer->in_fd, 0, O_RDONLY, 0))
              || (0 > vfdbuf_buffer_fd (&errn, answer->out_fd, 0, O_WRONLY, 0))
              || (0 > ftp_client_greet (0, &errn, &code, &text, &text_len, answer->in_fd, answer->out_fd))
              || (0 > ftp_client_login (0, &errn, &code, &text, &text_len, answer->in_fd, answer->out_fd, user, passwd)))
            {
              vu_close (&e, answer->in_fd);
              vu_close (&e, answer->out_fd);
              status = -1;
            }
          else
            {
              long dir_len;

              if (0 > ftp_client_pwd (0, &errn, &code, &answer->remote_pwd, &dir_len, &text, &text_len, answer->in_fd, answer->out_fd))
                {
                  vu_close (&e, answer->in_fd);
                  vu_close (&e, answer->out_fd);
                  status = -1;
                }
              else
                {
                  answer->remote_pwd = lim_realloc (0, answer->remote_pwd, dir_len + 1);
                  answer->remote_pwd[dir_len] = 0;
                  status = 0;
                }
            }
        }
    }

  lim_free (0, host);
  lim_free (0, user);
  lim_free (0, passwd);

  if (status)
    {
      safe_printfmt (2, "ftp_client_connect: ftp error connecting to %s\n", hostname);
      safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
      exit (2);
    }

  lim_free (0, hostname);

  return (struct arch_pfs_session *)answer;
}


static int
pfs_ftp_file_exists (struct arch_pfs_session * p, t_uchar * path)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  t_uchar * abs_path = 0;
  t_uchar * abs_path_dir = 0;
  t_uchar * path_tail = 0;
  rel_table nlst = 0;
  int x;
  int answer;

  abs_path = file_name_in_vicinity (0, arch->cwd, path);
  abs_path_dir = file_name_directory_file (0, abs_path);
  path_tail = file_name_tail (0, abs_path);

  nlst = pfs_ftp_directory_files (p, abs_path_dir, 1);
  answer = 0;
  for (x = 0; x < rel_n_records (nlst); ++x)
    {
      if (!str_cmp (path_tail, nlst[x][0]))
        {
          answer = 1;
          break;
        }
    }

  if (!answer)
    {
      /* On some servers the NLST command does not return dot files. A simple
       * workaround is to NLST directly the file we expect to find */
      nlst = pfs_ftp_directory_files (p, abs_path, 1);
      for (x = 0; x < rel_n_records (nlst); ++x)
        {
          if (!str_cmp (path_tail, nlst[x][0]))
            {
              answer = 1;
              break;
            }
        }
    }

  lim_free (0, abs_path);
  lim_free (0, abs_path_dir);
  lim_free (0, path_tail);
  rel_free_table (nlst);

  return answer;
}

static int
pfs_ftp_is_dir (struct arch_pfs_session * p, t_uchar * path)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text;
  long text_len;
  t_uchar * abs_path = 0;
  int chdir_stat;
  int return_chdir_stat;

  abs_path = file_name_in_vicinity (0, arch->cwd, path);

  chdir_stat = ftp_client_cwd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, abs_path);
  lim_free (0, text);

  return_chdir_stat = ftp_client_cwd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, arch->remote_pwd);
  lim_free (0, text);

  if (return_chdir_stat < 0)
    {
      safe_printfmt (2, "ftp_client_is_dir: unrecoverable chdir error for path %s\n", arch->remote_pwd);
      exit (2);
    }

  lim_free (0, abs_path);

  return (chdir_stat >= 0);
}




static t_uchar *
pfs_ftp_file_contents (struct arch_pfs_session * p, t_uchar * path, int soft_errors)
{
  int string_fd;
  t_uchar * answer = 0;
  int status;

  string_fd = make_output_to_string_fd ();

  status = pfs_ftp_get_file (p, string_fd, path, soft_errors);
  answer = string_fd_close (string_fd);

  if (status < 0)
    {
      lim_free (0, answer);
      answer = 0;
    }

  return answer;
}


static int
pfs_ftp_get_file (struct arch_pfs_session * p, int file_out_fd, t_uchar * path, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status = -1;
  t_uchar * get_path = 0;

  get_path = file_name_in_vicinity (0, arch->cwd, path);

  if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
    {
    put_error:
      if (!soft_errors)
        {
          safe_printfmt (2, "ftp_client_get: error getting file %s\n", get_path);
          safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
          exit (2);
        }
      status = -1;
    }
  else
    {
      t_ulong host_addr;
      t_uint16 port;

      lim_free (0, text);
      text = 0;

      if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd))
        {
          goto put_error;
        }
      else
        {
          int file_in_fd;

          lim_free (0, text);
          text = 0;

          file_in_fd = url_inet_client_addr (&errn, host_addr, (int)port);

          if (file_in_fd < 0)
            {
              text = str_save (0, errno_to_string (errn));
              text_len = str_length (text);
              goto put_error;
            }
          else
            {
              if (0 > ftp_client_begin_retr (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, get_path))
                {
                  goto put_error;
                }
              else
                {
                  copy_fd (file_in_fd, file_out_fd);

                  safe_close (file_in_fd);

                  if (0 > ftp_client_end_retr (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
                    {
                      goto put_error;
                    }
                  else
                    {
                      lim_free (0, text);
                      text = 0;
                      status = 0;
                    }
                }
            }
        }
    }

  lim_free (0, text);
  lim_free (0, get_path);
  return status;
}



static rel_table
pfs_ftp_directory_files (struct arch_pfs_session * p, t_uchar * path, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  rel_table answer = 0;
  t_uchar * nlst_path = 0;
  t_uchar * nlst_path_lastc = 0;

  nlst_path = file_name_in_vicinity (0, arch->cwd, path);
  /* filter out trailing "/." since publicfile handles dots specially
   */
  if (*nlst_path && *(nlst_path+1))
  {
      nlst_path_lastc = nlst_path + strlen(nlst_path) - 1;
      if ((*nlst_path_lastc == '.') && (*(nlst_path_lastc-1) == '/'))
          *(nlst_path_lastc - 1) = 0;
  }

  if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
    {
    put_error:
      if (!soft_errors)
        {
          safe_printfmt (2, "ftp_client_directory_files: error listing file %s\n", nlst_path);
          safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
          exit (2);
        }
    }
  else
    {
      t_ulong host_addr;
      t_uint16 port;

      lim_free (0, text);
      text = 0;

      if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd))
        {
          goto put_error;
        }
      else
        {
          int file_in_fd;

          lim_free (0, text);
          text = 0;

          file_in_fd = ftp_client_begin_nlst (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, arch->is_wu, nlst_path, host_addr, port);

          if (0 > file_in_fd)
            {
              /* Workaround for buggy ftp servers that respond a 450 or 550 code for empty directories.
               * 450 is reported by proftpd:
               *    "450 No files found"
               * The pfs_ftp_is_dir ensures the directory exists as it chdir to the given path.
               * Btw, we could not have used pfs_ftp_file_exists otherwise we would have created
               * an infinte function call loop because pfs_ftp_file_exists relies on
               * pfs_ftp_directory_files */
              if ((code != 450 && code != 550) || !pfs_ftp_is_dir (p, path))
                goto put_error;
            }
          else
            {
              t_uchar * raw_list = 0;
              int file_out_fd;

              file_out_fd = make_output_to_string_fd ();
              copy_fd (file_in_fd, file_out_fd);
              safe_close (file_in_fd);
              raw_list = string_fd_close (file_out_fd);

              if (0 > ftp_client_end_nlst (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
                {
                  lim_free (0, raw_list);
                  goto put_error;
                }
              else
                {
                  int x;

                  lim_free (0, text);
                  text = 0;

                  raw_list = trim_surrounding_ws (raw_list);
                  answer = rel_ws_split (raw_list);

                  lim_free (0, raw_list);

                  for (x = 0; x < rel_n_records (answer); ++x)
                    {
                      t_uchar * t = 0;

                      t = file_name_tail (0, answer[x][0]);
                      lim_free (0, answer[x][0]);
                      answer[x][0] = t;
                    }
                }
            }
        }
    }

  lim_free (0, text);
  lim_free (0, nlst_path);
  return answer;
}



static int
pfs_ftp_put_file (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int file_in_fd, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status = -1;
  t_uchar * put_path = 0;

  put_path = file_name_in_vicinity (0, arch->cwd, path);

  if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
    {
    put_error:
      if (!soft_errors)
        {
          safe_printfmt (2, "ftp_client_put: error putting file %s\n", put_path);
          safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
          exit (2);
        }
      status = -1;
    }
  else
    {
      t_ulong host_addr;
      t_uint16 port;

      lim_free (0, text);
      text = 0;

      if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd))
        {
          goto put_error;
        }
      else
        {
          int file_out_fd;

          lim_free (0, text);
          text = 0;

          file_out_fd = url_inet_client_addr (&errn, host_addr, (int)port);

          if (file_out_fd < 0)
            {
              text = str_save (0, errno_to_string (errn));
              text_len = str_length (text);
              goto put_error;
            }
          else
            {
              if (0 > ftp_client_begin_stor (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, put_path))
                {
                  goto put_error;
                }
              else
                {
                  copy_fd (file_in_fd, file_out_fd);

                  safe_close (file_out_fd);

                  if (0 > ftp_client_end_stor (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd))
                    {
                      goto put_error;
                    }
                  else
                    {
                      lim_free (0, text);
                      text = 0;
                      status = 0;
                    }
                }
            }
        }
    }

  lim_free (0, text);
  lim_free (0, put_path);
  return status;
}




static int
pfs_ftp_mkdir (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status;
  t_uchar * mkdir_path = 0;

  mkdir_path = file_name_in_vicinity (0, arch->cwd, path);

  status = ftp_client_mkd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, mkdir_path);

  if (status && !soft_errors)
    {
      safe_printfmt (2, "ftp_client_mkdir: error making directory %s\n", mkdir_path);
      safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
      exit (2);
    }

  lim_free (0, text);
  lim_free (0, mkdir_path);
  return status;
}


static int
pfs_ftp_rename (struct arch_pfs_session * p, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status;
  t_uchar * from_path = 0;
  t_uchar * to_path = 0;



  from_path = file_name_in_vicinity (0, arch->cwd, from);
  to_path = file_name_in_vicinity (0, arch->cwd, to);

  status = ftp_client_rename (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, from_path, to_path);

  if (status && !soft_errors)
    {
      safe_printfmt (2, "ftp_client_rename: error renaming %s to %s\n", from_path, to_path);
      safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
      exit (2);
    }

  lim_free (0, text);
  lim_free (0, from_path);
  lim_free (0, to_path);
  return status;
}


static int
pfs_ftp_rmdir (struct arch_pfs_session * p, t_uchar * path, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status;
  t_uchar * dele_path = 0;



  dele_path = file_name_in_vicinity (0, arch->cwd, path);

  status = ftp_client_rmd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, dele_path);

  if (status && !soft_errors)
    {
      safe_printfmt (2, "ftp_client_rmdir: error deleting %s\n", dele_path);
      safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
      exit (2);
    }

  lim_free (0, text);
  lim_free (0, dele_path);
  return status;
}


static int
pfs_ftp_rm (struct arch_pfs_session * p, t_uchar * path, int soft_errors)
{
  struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p;
  int errn;
  int code;
  t_uchar * text = 0;
  long text_len;
  int status;
  t_uchar * dele_path = 0;



  dele_path = file_name_in_vicinity (0, arch->cwd, path);

  status = ftp_client_dele (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, dele_path);

  if (status && !soft_errors)
    {
      safe_printfmt (2, "ftp_client_rm: error deleting %s\n", dele_path);
      safe_printfmt (2, "    %d: %.*s\n", code, (int)text_len, text);
      exit (2);
    }

  lim_free (0, text);
  lim_free (0, dele_path);
  return status;
}





static int
ftp_client_greet (alloc_limits limits,
                  int * errn,
                  int * code,
                  t_uchar ** text,
                  long * len,
                  int in_fd,
                  int out_fd)
{
  int delayed;

  delayed = 0;

 retry:

  if (0 > ftp_client_read_reply (limits, errn, code, text, len, in_fd))
    return -1;

  if (*code == 120)
    {
      if (delayed)
        {
          *errn = EINVAL;
          return -1;
        }
      else
        {
          delayed = 1;
          goto retry;
        }
    }

  if (*code == 220)
    return 0;

  if (*code == 421)
    {
      *errn = 0;
      return -1;
    }

  *errn = EINVAL;
  return -1;
}


/****************************************************************
 *
 * ftp_login
 *             USER <SP> <username> <CRLF>
 *             PASS <SP> <password> <CRLF>
 *
 * not implemented
 *             ACCT <SP> <account-information> <CRLF>
 *
 */


static int
ftp_client_login (alloc_limits limits,
                  int * errn,
                  int * code,
                  t_uchar ** text,
                  long * text_len,
                  int in_fd,
                  int out_fd,
                  t_uchar * name,
                  t_uchar * passwd)
{
  int user_repl_code;
  t_uchar * user_repl_text;
  long user_repl_len;
  int pass_repl_code;
  t_uchar * pass_repl_text;
  long pass_repl_len;

  if (0 > ftp_client_printfmt (errn, out_fd, "USER %s\r\n", name))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &user_repl_code, &user_repl_text, &user_repl_len, in_fd))
    return -1;

  *code = user_repl_code;

  if (!text)
    lim_free (limits, user_repl_text);
  else
    {
      *text = user_repl_text;
      *text_len = user_repl_len;
    }


  {
    int first_digit;

    first_digit = user_repl_code / 100;

    switch (first_digit)
      {
      default:
      case 1:
        *errn = EINVAL;
        return -1;

      case 2:
        return 0;

      case 3:
        break;

      case 4:
      case 5:
        *errn = 0;
        return -1;
      }
  }


  if (0 > ftp_client_printfmt (errn, out_fd, "PASS %s\r\n", passwd))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &pass_repl_code, &pass_repl_text, &pass_repl_len, in_fd))
    return -1;

  *code = pass_repl_code;

  if (!text)
    lim_free (limits, pass_repl_text);
  else
    {
      *text = lim_realloc (limits, *text, user_repl_len + pass_repl_len + 1);
      mem_move (*text + user_repl_len, pass_repl_text, pass_repl_len);
      (*text)[user_repl_len + pass_repl_len] = 0;
      *text_len += pass_repl_len;
      lim_free (limits, pass_repl_text);
    }

  {
    int first_digit;

    first_digit = pass_repl_code / 100;

    switch (first_digit)
      {
      default:
      case 1:
        *errn = EINVAL;
        return -1;

      case 2:
        return 0;

      case 3:
        *errn = ENOSYS;
        return -1;

      case 4:
      case 5:
        *errn = 0;
        return -1;
      }
  }
}




/****************************************************************
 * From rfc 959
 *
 * An FTP reply consists of a three digit number (transmitted as
 * three alphanumeric characters) followed by some text.  The number
 * is intended for use by automata to determine what state to enter
 * next; the text is intended for the human user.  It is intended
 * that the three digits contain enough encoded information that the
 * user-process (the User-PI) will not need to examine the text and
 * may either discard it or pass it on to the user, as appropriate.
 * In particular, the text may be server-dependent, so there are
 * likely to be varying texts for each reply code.
 *
 * A reply is defined to contain the 3-digit code, followed by Space
 * <SP>, followed by one line of text (where some maximum line length
 * has been specified), and terminated by the Telnet end-of-line
 * code.  There will be cases however, where the text is longer than
 * a single line.  In these cases the complete text must be bracketed
 * so the User-process knows when it may stop reading the reply (i.e.
 * stop processing input on the control connection) and go do other
 * things.  This requires a special format on the first line to
 * indicate that more than one line is coming, and another on the
 * last line to designate it as the last.  At least one of these must
 * contain the appropriate reply code to indicate the state of the
 * transaction.  To satisfy all factions, it was decided that both
 * the first and last line codes should be the same.
 *
 * Thus the format for multi-line replies is that the first line will
 * begin with the exact required reply code, followed immediately by a
 * Hyphen, "-" (also known as Minus), followed by text.  The last line
 * will begin with the same code, followed immediately by Space <SP>,
 * optionally some text, and the Telnet end-of-line code.
 *
 * For example:
 *                           123-First line
 *                           Second line
 *                             234 A line beginning with numbers
 *                           123 The last line
 *
 * The user-process then simply needs to search for the second
 * occurrence of the same reply code, followed by <SP> (Space), at the
 * beginning of a line, and ignore all intermediary lines.  If an
 * intermediary line begins with a 3-digit number, the Server must pad
 * the front to avoid confusion.
 *
 * This scheme allows standard system routines to be used for reply
 * information (such as for the STAT reply), with "artificial" first
 * and last lines tacked on.  In rare cases where these routines are
 * able to generate three digits and a Space at the beginning of any
 * line, the beginning of each text line should be offset by some
 * neutral text, like Space.
 *
 * This scheme assumes that multi-line replies may not be nested.
 */


static int
ftp_client_read_reply (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int fd)
{
  t_uchar * line;
  long len;
  t_uchar c0;
  t_uchar c1;
  t_uchar c2;



  if (vfdbuf_next_line (errn, &line, &len, fd))
    return -1;

  if (   (len < 4)
      || !char_is_digit(line[0])
      || !char_is_digit(line[1])
      || !char_is_digit(line[2])
      || ((line[3] != ' ') && (line[3] != '-')))
    {
      *errn = EINVAL;
      return -1;
    }

  *code = (  (line[0] - '0') * 100
           + (line[1] - '0') * 10
           + (line[2] - '0'));

  c0 = line[0];
  c1 = line[1];
  c2 = line[2];

  if (text)
    {
      *text = str_save_n (limits, line, len);
      *text_len = len;
    }


  if (line[3] != '-')
    {
      return 0;
    }

  while (1)
    {
      if (vfdbuf_next_line (errn, &line, &len, fd))
        {
          if (text)
            lim_free (limits, *text);
          return -1;
        }

      if (   (len >= 4)
          && (char_is_digit(line[0]) && c0)
          && (char_is_digit(line[1]) && c1)
          && (char_is_digit(line[2]) && c2)
          && (line[3] == ' '))
        {
          /* Got the last line of the reply.
           */
          if (text)
            {
              *text = lim_realloc (limits, *text, len + *text_len + 1);
              mem_move (*text + *text_len, line, len);
              (*text)[*text_len + len] = 0;
              *text_len = len + *text_len;
            }
          return 0;
        }
      else
        {
          if (text)
            {
              *text = lim_realloc (limits, *text, len + *text_len + 1);
              mem_move (*text + *text_len, line, len);
              (*text)[*text_len + len] = 0;
              *text_len = len + *text_len;
            }
        }
    }

  /* not reached */

  panic ("not reached in ftp_read_reply");
  return -1;
}


/****************************************************************
 *
 *    We first present the diagram that represents the largest group of FTP
 *    commands:
 *
 *
 *                                1,3    +---+
 *                           ----------->| E |
 *                          |            +---+
 *                          |
 *       +---+    cmd    +---+    2      +---+
 *       | B |---------->| W |---------->| S |
 *       +---+           +---+           +---+
 *                          |
 *                          |     4,5    +---+
 *                           ----------->| F |
 *                                       +---+
 *
 *
 *       This diagram models the commands:
 *
 *          ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV,
 *          QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE.
 *
 */

static int
ftp_client_read_simple_cmd_reply (alloc_limits limit,
                                  int * errn,
                                  int * code,
                                  t_uchar ** text,
                                  long * text_len,
                                  int in_fd)
{
  if (0 > ftp_client_read_reply (limit, errn, code, text, text_len, in_fd))
    return -1;

  {
    int first_digit;

    first_digit = *code / 100;

    switch (first_digit)
      {
      default:
      case 1:
      case 3:
        *errn = EINVAL;
        return -1;

      case 2:
        return 0;

      case 4:
      case 5:
        *errn = 0;
        return -1;
      }
  }
}

static int
ftp_client_printfmt (int * errn, int fd, char * fmt, ...)
{
  int answer;
  va_list ap;

#if 0
  printfmt (errn, 2, "sending: ");
  va_start (ap, fmt);
  printfmt_va_list (errn, 2, fmt, ap);
  va_end (ap);
#endif

  va_start (ap, fmt);
  answer = printfmt_va_list (errn, fd, fmt, ap);
  va_end (ap);

  if (   (answer >= 0)
      && vfdbuf_is_buffered (fd)
      && (0 > vfdbuf_flush (errn, fd)))
    return -1;

  return answer;
}




/****************************************************************
 * ftp_port
 *
 *             PORT <SP> <host-port> <CRLF>
 *
 */
#if 0
static int
ftp_client_port (alloc_limits limits,
                 int * errn,
                 int * code,
                 t_uchar ** text,
                 long * text_len,
                 int in_fd,
                 int out_fd,
                 t_ulong host,
                 t_uint16 port)

{
  if (0 > ftp_client_printfmt (errn, out_fd, "PORT %d,%d,%d,%d,%d,%d\r\n",
                               (int)((host >> 24) & 0xff),
                               (int)((host >> 16) & 0xff),
                               (int)((host >> 8) & 0xff),
                               (int)(host & 0xff),
                               (port >> 8) & 0xff,
                               port & 0xff))

    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}
#endif

/****************************************************************
 * ftp_client_pasv_parse_equal
 *
 *             =ipdec1,ipdec2,ipdec3,ipdec4,portdec1,portdec2
 *
 */
static int
ftp_client_pasv_parse_equal (char *text, t_ulong *h, t_uint16 *p)
{
    int er;
    t_uchar * start_byte;
    t_uchar * end_byte;
    t_uint h1;
    t_uint h2;
    t_uint h3;
    t_uint h4;
    t_uint p1;
    t_uint p2;

    start_byte = str_chr_rindex (text, '=');
    if (!start_byte)
        return 1;

    ++start_byte;

    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &h1, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &h2, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &h3, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &h4, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &p1, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = start_byte;
    while ((*end_byte >= '0') && (*end_byte <= '9'))
           end_byte++;
    if (!end_byte)
        return 1;
    cvt_decimal_to_uint (&er, &p2, start_byte, end_byte - start_byte);

    *h = (h1 << 24) | (h2 << 16) | (h3 << 8) | h4;
    *p = (p1 << 8) | p2;

    return 0;
}

/****************************************************************
 * ftp_client_pasv_parse_parenthesis
 *
 *             (ipdec1,ipdec2,ipdec3,ipdec4,portdec1,portdec2)
 *
 */
static int
ftp_client_pasv_parse_parenthesis (char *text, t_ulong *h, t_uint16 *p)
{
    int er;
    t_uchar * start_byte;
    t_uchar * end_byte;
    t_uint h1;
    t_uint h2;
    t_uint h3;
    t_uint h4;
    t_uint p1;
    t_uint p2;

    start_byte = str_chr_rindex (text, '(');
    if (!start_byte)
        return -1;

    ++start_byte;

    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &h1, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &h2, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &h3, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &h4, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ',');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &p1, start_byte, end_byte - start_byte);

    start_byte = end_byte + 1;
    end_byte = str_chr_index (start_byte, ')');
    if (!end_byte)
        return -1;
    cvt_decimal_to_uint (&er, &p2, start_byte, end_byte - start_byte);

    *h = (h1 << 24) | (h2 << 16) | (h3 << 8) | h4;
    *p = (p1 << 8) | p2;

    return 0;
}

/****************************************************************
 * ftp_pasv
 *
 *             PASV <CRLF>
 */
static int
ftp_client_pasv (alloc_limits limits,
                 int * errn,
                 int * code,
                 t_ulong * host,
                 t_uint16 * port,
                 t_uchar ** text,
                 long * text_len,
                 int in_fd,
                 int out_fd)
{
  t_uchar * t;
  long tl;
  int status;

  if (0 > ftp_client_printfmt (errn, out_fd, "PASV\r\n"))
    return -1;

  t = 0;
  tl = 0;

  if (!text)
    {
      text = &t;
      text_len = &tl;
    }

  if (0 > ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd))
    return -1;


  status = 0;

  if (*code != 227)
    {
      switch (*code / 100)
        {
        default:
        case 1:
        case 2:
        case 3:
        bogus_reply:
          *errn = EINVAL;
          status = -1;
          break;

        case 4:
        case 5:
          *errn = 0;
          status = -1;
          break;
        }
    }
  else
    {
      t_ulong h;
      t_uint16 p;

      if (str_chr_rindex (*text, '('))
      {
          if (0 > ftp_client_pasv_parse_parenthesis(*text, &h, &p))
              goto bogus_reply;
      }
      else if (str_chr_rindex (*text, '='))
      {
          if (0 > ftp_client_pasv_parse_equal(*text, &h, &p))
              goto bogus_reply;
      }
      else
          goto bogus_reply;

      *host = h;
      *port = p;

#if 0
      if (host)
        *host = htonl (h);

      if (port)
        *port = htons (p);
#endif
    }

  if (t)
    lim_free (limits, t);

  return status;
}


/****************************************************************
 * ftp_type_image
 * ftp_type_ascii
 *
 *             TYPE <SP> <type-code> <CRLF>
 *
 */
static int
ftp_client_type_image (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int in_fd,
                       int out_fd)
{
  if (0 > ftp_client_printfmt (errn, out_fd, "TYPE I\r\n"))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}


#if 0
static int
ftp_client_type_ascii (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int in_fd,
                       int out_fd)

{
  if (0 > ftp_client_printfmt (errn, out_fd, "TYPE A\r\n"))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}
#endif

/****************************************************************
 * not implemented
 *
 *             STRU <SP> <structure-code> <CRLF>
 *
 */


/****************************************************************
 * ftp_stream_mode
 *
 *             MODE <SP> <mode-code> <CRLF>
 *
 */
#if 0
static int
ftp_client_stream_mode (alloc_limits limits,
                        int * errn,
                        int * code,
                        t_uchar ** text,
                        long * text_len,
                        int in_fd,
                        int out_fd)

{
  if (0 > ftp_client_printfmt (errn, out_fd, "MODE S\r\n"))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}
#endif

/****************************************************************
 * ftp_client_begin_retr
 * ftp_client_end_retr
 *
 *             RETR <SP> <pathname> <CRLF>
 *
 */
static int
ftp_client_begin_retr (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int in_fd,
                       int out_fd,
                       t_uchar * path)
{
  int c;

  if (0 > ftp_client_printfmt (errn, out_fd, "RETR %s\r\n", path))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd))
    return -1;

  if (code)
    *code = c;

  switch (c / 100)
    {
    case 1:
      /* Not doing anything special for markers yet.
       *
       * 125 Data connection already open; transfer starting.
       * 150 File status okay; about to open data connection.
       * 110 Restart marker reply.
       *     In this case, the text is exact and not left to the
       *     particular implementation; it must read:
       *          MARK yyyy = mmmm
       *     Where yyyy is User-process data stream marker, and mmmm
       *     server's equivalent marker (note the spaces between markers
       *     and "=").
       */
      return 0;

    default:
    case 2:
    case 4:
    case 5:
      /*
       * 226 Closing data connection.
       *     Requested file action successful (for example, file
       *     transfer or file abort).
       * 250 Requested file action okay, completed.
       *
       * 421 Service not available, closing control connection.
       *     This may be a reply to any command if the service knows it
       *     must shut down.
       * 425 Can't open data connection.
       * 426 Connection closed; transfer aborted.
       * 451 Requested action aborted: local error in processing.
       * 450 Requested file action not taken.
       *     File unavailable (e.g., file busy).
       *
       * 550 Requested action not taken.
       *     File unavailable (e.g., file not found, no access).
       * 500 Syntax error, command unrecognized.
       *     This may include errors such as command line too long.
       * 501 Syntax error in parameters or arguments.
       * 530 Not logged in.
       */
      *errn = 0;
      return -1;

    case 3:
      *errn = EINVAL;
      return -1;
    }
}

static int
ftp_client_end_retr (alloc_limits limits,
                     int * errn,
                     int * code,
                     t_uchar ** text,
                     long * text_len,
                     int in_fd,
                     int out_fd)
{
  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}



/****************************************************************
 * ftp_client_begin_stor
 * ftp_client_end_stor
 *
 *             STOR <SP> <pathname> <CRLF>
 *
 */
static int
ftp_client_begin_stor (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int in_fd,
                       int out_fd,
                       t_uchar * path)
{
  int c;

  if (0 > ftp_client_printfmt (errn, out_fd, "STOR %s\r\n", path))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd))
    return -1;

  if (code)
    *code = c;

  switch (c / 100)
    {
    case 1:
      /* Not doing anything special for markers yet.
       */
      return 0;

    default:
    case 2:
    case 4:
    case 5:
      *errn = 0;
      return -1;

    case 3:
      *errn = EINVAL;
      return -1;
    }
}


static int
ftp_client_end_stor (alloc_limits limits,
                     int * errn,
                     int * code,
                     t_uchar ** text,
                     long * text_len,
                     int in_fd,
                     int out_fd)
{
  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}


/****************************************************************
 * ftp_client_begin_nlst
 * ftp_client_end_nlst
 *
 *             NLST [<SP> <pathname>] <CRLF>
 *
 *
 * with wu-ftp:
 *
 *             NLST <SP> -a [<SP> <pathname>] <CRLF>
 *
 */

static int
ftp_client_begin_nlst (alloc_limits limits,
                       int * errn,
                       int * code,
                       t_uchar ** text,
                       long * text_len,
                       int in_fd,
                       int out_fd,
                       int is_wu_ftp,
                       t_uchar * path,
                       t_ulong host_addr,
                       t_uint16 port)
{
  int c;
  int data_fd = 0;

  if (host_addr)
    {
      data_fd = url_inet_client_addr (errn, host_addr, (int)port);
      if (data_fd < 0)
        {
          safe_printfmt (2, "ftp_client_begin_nlst: %s to port %d\n", errno_to_string (*errn), (int) port);
        }
    }

  if (path && path[0])
    {
      if (0 > ftp_client_printfmt (errn, out_fd, "NLST%s %s\r\n", (is_wu_ftp ? " -a" : ""), path))
        goto leave;
    }
  else
    {
      if (0 > ftp_client_printfmt (errn, out_fd, "NLST%s\r\n", (is_wu_ftp ? " -a" : "")))
        goto leave;
    }

  if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd))
    {
    leave:
      if (host_addr && data_fd >= 0)
        safe_close (data_fd);
      return -1;
    }

  if (code)
    *code = c;
  else
    {
    }
  switch (c / 100)
    {
    case 1:
      return data_fd;

    default:
    case 2:
    case 4:
    case 5:
      *errn = 0;
      if (host_addr && (data_fd >= 0))
        safe_close (data_fd);
      return -1;

    case 3:
      *errn = EINVAL;
      if (host_addr && (data_fd >= 0))
        safe_close (data_fd);
      return -1;
    }
}


static int
ftp_client_end_nlst (alloc_limits limits,
                     int * errn,
                     int * code,
                     t_uchar ** text,
                     long * text_len,
                     int in_fd,
                     int out_fd)
{
  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}






/****************************************************************
 * ftp_mkd
 *             MKD  <SP> <pathname> <CRLF>
 *
 */
static int
ftp_client_mkd (alloc_limits limits,
                int * errn,
                int * code,
                t_uchar ** text,
                long * text_len,
                int in_fd,
                int out_fd,
                t_uchar * path)
{
  if (0 > ftp_client_printfmt (errn, out_fd, "MKD %s\r\n", path))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}

/****************************************************************
 * ftp_rename
 *
 *             RNFR <SP> <pathname> <CRLF>
 *             RNTO <SP> <pathname> <CRLF>
 *
 */
static int
ftp_client_rename (alloc_limits limits,
                   int * errn,
                   int * code,
                   t_uchar ** text,
                   long * text_len,
                   int in_fd,
                   int out_fd,
                   t_uchar * from,
                   t_uchar * to)
{
  int rnfr_code;
  t_uchar * rnfr_text;
  long rnfr_len;
  int rnto_code;
  t_uchar * rnto_text;
  long rnto_len;

  if (0 > ftp_client_printfmt (errn, out_fd, "RNFR %s\r\n", from))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &rnfr_code, &rnfr_text, &rnfr_len, in_fd))
    return -1;

  *code = rnfr_code;

  if (!text)
    lim_free (limits, rnfr_text);
  else
    {
      *text = rnfr_text;
      *text_len = rnfr_len;
    }

  {
    int first_digit;

    first_digit = rnfr_code / 100;

    switch (first_digit)
      {
      default:
      case 1:
      case 2:
        *errn = EINVAL;
        return -1;

      case 3:
        break;

      case 4:
      case 5:
        *errn = 0;
        return -1;
      }
  }

  if (0 > ftp_client_printfmt (errn, out_fd, "RNTO %s\r\n", to))
    return -1;

  if (0 > ftp_client_read_reply (limits, errn, &rnto_code, &rnto_text, &rnto_len, in_fd))
    return -1;

  *code = rnto_code;

  if (!text)
    lim_free (limits, rnto_text);
  else
    {
      *text = lim_realloc (limits, *text, rnfr_len + rnto_len + 1);
      mem_move (*text + rnfr_len, rnto_text, rnto_len);
      (*text)[rnfr_len + rnto_len] = 0;
      *text_len += rnto_len;
      lim_free (limits, rnto_text);
    }

  {
    int first_digit;

    first_digit = rnto_code / 100;

    switch (first_digit)
      {
      default:
      case 1:
      case 3:
        *errn = EINVAL;
        return -1;

      case 2:
        return 0;

      case 4:
      case 5:
        *errn = 0;
        return -1;
      }
  }
}

static int
ftp_client_dele (alloc_limits limits,
                 int * errn,
                 int * code,
                 t_uchar ** text,
                 long * text_len,
                 int in_fd,
                 int out_fd,
                 t_uchar * path)
{
  if (0 > ftp_client_printfmt (errn, out_fd, "DELE %s\r\n", path))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}


/****************************************************************
 * ftp_rmd
 *
 *             RMD  <SP> <pathname> <CRLF>
 *
 */
static int
ftp_client_rmd (alloc_limits limits,
                int * errn,
                int * code,
                t_uchar ** text,
                long * text_len,
                int in_fd,
                int out_fd,
                t_uchar * path)
{
  if (0 > ftp_client_printfmt (errn, out_fd, "RMD %s\r\n", path))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}


/****************************************************************
 * ftp_cwd
 *             CWD  <SP> <pathname> <CRLF>
 */

static int
ftp_client_cwd (alloc_limits limits,
                int * errn,
                int * code,
                t_uchar ** text,
                long * text_len,
                int in_fd,
                int out_fd,
                t_uchar * path)
{
  if (0 > ftp_client_printfmt (errn, out_fd, "CWD %s\r\n", path))
    return -1;

  return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd);
}


/****************************************************************
 * ftp_client_pwd
 *
 *             PWD  <CRLF>
 *
 */
static int
ftp_client_pwd (alloc_limits limits,
                int * errn,
                int * code,
                t_uchar ** path,
                long * path_len,
                t_uchar ** text,
                long * text_len,
                int in_fd,
                int out_fd)
{
  t_uchar * t;
  long tl;
  int status;

  if (0 > ftp_client_printfmt (errn, out_fd, "PWD\r\n"))
    return -1;

  t = 0;
  tl = 0;

  if (!text)
    {
      text = &t;
      text_len = &tl;
    }

  if (0 > ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd))
    return -1;


  status = 0;

  if (*code != 257)
    {
      switch (*code / 100)
        {
        default:
        case 1:
        case 2:
        case 3:
        bogus_reply:
          *errn = EINVAL;
          status = -1;
          break;

        case 4:
        case 5:
          *errn = 0;
          status = -1;
          break;
        }
    }
  else
    {
      t_uchar * path_start;
      t_uchar * path_end;
      long doubled_quotes;

      path_start = str_chr_index (*text, '"');

      if (!path_start)
        goto bogus_reply;

      path_start += 1;
      path_end = path_start;
      doubled_quotes = 0;

      while (1)
        {
          if (path_end == (*text + *text_len))
            goto bogus_reply;
          else if (*path_end == '"')
            {
              if (((path_end + 1) < (*text + *text_len)) && (*(path_end + 1) == '"'))
                {
                  ++doubled_quotes;
                  path_end += 2;
                }
              else
                break;
            }
          else
            ++path_end;

        }

      if (path)
        {
          t_uchar * p;
          *path = lim_malloc (limits, 1 + (path_end - path_start) - doubled_quotes);
          p = *path;

          while (path_start < path_end)
            {
              *p = *path_start;
              ++p;
              if (*path_start == '"')
                ++path_start;
              ++path_start;
            }
          *p = 0;

          *path_len = p - *path;
        }

      status = 0;
    }

  if (t)
    lim_free (limits, t);

  return status;
}





/* tag: Tom Lord Mon Jun 16 21:58:44 2003 (pfs-ftp.c)
 */
