/* pfs-sftp.c:
 *
 ****************************************************************
 * Copyright (C) 2002 - 2004 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/arrays/ar.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/machine/types.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/file-contents.h"
#include "tla/libarch/archives.h"
#include "tla/libarch/exec.h"
#include "tla/libarch/pfs-sftp.h"
#include "tla/libarch/pfs-sftp-version.h"
#include "tla/libarch/sftp.h"




#undef FIXME
/* TODO: look through ssh/tcp/ethernet specks to find an optimal size */
#define SFTP_RWSIZE 16384

/* PipeLine-REQuests: num r/w requests open at once for get() and put() */
#define MAX_PL_REQS 16 

#undef MIN
#define MIN(A,B) ((A) < (B) ? (A) : (B))
#define LISTING_FILE ".listing"


#define WRITE_UINT4(str, ui) do { \
  (str)[0] = (ui) >> 24; \
  (str)[1] = (ui) >> 16; \
  (str)[2] = (ui) >> 8; \
  (str)[3] = (ui); } while (0)

#define WRITE_UINT8(str, ui) do { \
  (str)[0] = (ui) >> 56; \
  (str)[1] = (ui) >> 48; \
  (str)[2] = (ui) >> 40; \
  (str)[3] = (ui) >> 32; \
  (str)[4] = (ui) >> 24; \
  (str)[5] = (ui) >> 16; \
  (str)[6] = (ui) >> 8; \
  (str)[7] = (ui); } while (0)

#define READ_UINT4(str) ( \
  ((t_uint32)(unsigned char)((str)[0]) << 24) | \
  ((t_uint32)(unsigned char)((str)[1]) << 16) | \
  ((t_uint32)(unsigned char)((str)[2]) << 8) | \
  ((t_uint32)(unsigned char)((str)[3])) )

#define READ_UINT8(str) ( \
  ((unsigned long long int)(unsigned char)((str)[0]) << 56) | \
  ((unsigned long long int)(unsigned char)((str)[1]) << 48) | \
  ((unsigned long long int)(unsigned char)((str)[2]) << 40) | \
  ((unsigned long long int)(unsigned char)((str)[3]) << 32) | \
  ((unsigned long long int)(unsigned char)((str)[4]) << 24) | \
  ((unsigned long long int)(unsigned char)((str)[5]) << 16) | \
  ((unsigned long long int)(unsigned char)((str)[6]) << 8) | \
  ((unsigned long long int)(unsigned char)((str)[7])) )


struct arch_pfs_sftp_session
{
  struct arch_pfs_session pfs;

  char * cwd;
  t_uint32 request_id;
  t_uint32 plreqs;

  int in_fd;
  int out_fd;
};


struct sftp_attrs
{
  t_uint32 flags;
  unsigned long long int size;
  t_uint32 uid;
  t_uint32 gid;
  t_uint32 perms;
  t_uint32 atime;
  t_uint32 mtime;
  unsigned char *extended;
};






/* __STDC__ prototypes for static functions */
static int sftp_write_pkt (struct arch_pfs_sftp_session * p,
                           int soft_errors,
                           const char *fmt, ...);
static int sftp_export_attrs (struct sftp_attrs *a,
                              char ** pkt,
                              int * len);
static void sftp_client_do_init (struct arch_pfs_sftp_session * p,
                                 int soft_errors);
static int sftp_server_response (struct arch_pfs_sftp_session * p,
                                 char ** pkt,
                                 int * pkt_len,
                                 int expected_response,
                                 int soft_errors);
static int sftp_decode_status (char * pkt,
                               int pkt_len,
                               int i,
                               int soft_errors);
static t_uchar * sftp_abs_path (const t_uchar * cwd,
                                const t_uchar *path);
static int sftp_get (struct arch_pfs_sftp_session * p,
                     int data_fd,
                     const t_uchar * path,
                     int soft_errors);
static int pfs_get_file (struct arch_pfs_session * p,
                         int out_fd,
                         const t_uchar * path,
                         int soft_errors);
static t_uchar * pfs_file_contents (struct arch_pfs_session * p,
                                    const t_uchar * path,
                                    int soft_errors);
static rel_table pfs_directory_files (struct arch_pfs_session * p,
                                      const t_uchar * path,
                                      int soft_errors);
static int sftp_import_attrs (struct sftp_attrs *a,
                              char * pkt,
                              unsigned int pkt_len,
                              int soft_errors);
static int pfs_put_file (struct arch_pfs_session *p,
                         const t_uchar * path,
                         mode_t perms,
                         int data_fd,
                         int soft_errors);
static int pfs_mkdir (struct arch_pfs_session * p,
                      const t_uchar * path,
                      mode_t mode,
                      int soft_errors);
static int pfs_file_exists (struct arch_pfs_session * p,
                            const t_uchar * path);
static int pfs_is_dir (struct arch_pfs_session * p,
                       const t_uchar * path);
static int pfs_rename (struct arch_pfs_session * p,
                       t_uchar ** errstr,
                       const t_uchar * from,
                       const t_uchar * to,
                       int soft_errors);
static int pfs_rmdir (struct arch_pfs_session * p,
                      const t_uchar * path,
                      int soft_errors);
static int pfs_rm (struct arch_pfs_session * p,
                   const t_uchar * path,
                   int soft_errors);
static t_uchar * dirfold (t_uchar *dir);



struct arch_pfs_vtable sftp_pfs_fns =
  {
    pfs_file_exists,
    pfs_is_dir,

    pfs_file_contents,
    pfs_get_file,
    pfs_directory_files,

    pfs_put_file,

    pfs_mkdir,
    pfs_rename,

    pfs_rmdir,
    pfs_rm,
  };




int
arch_pfs_sftp_supported_protocol (const t_uchar * uri)
{
  if (!str_cmp_prefix ("sftp:", uri))
    return 1;
  else
    return 0;
}


static int
sftp_write_pkt (struct arch_pfs_sftp_session * p,
                int soft_errors,
                const char *fmt, ...)
{
  va_list ap;
  struct sftp_attrs *attrs;
  char *buf;
  int i;
  int errn = 0;
  unsigned int len, s_len, ret;
  t_uint32 l;
  unsigned long long ll;
  char *s;
  unsigned char c;

  va_start (ap, fmt);

  len = 4;
  buf = lim_malloc (0, len);
  i = 4;

  while (*fmt)
    switch (*fmt++)
      {
        case '8': /* unsigned long long */
          ll = va_arg (ap, unsigned long long);
          len += sizeof (ll);
          buf = lim_realloc (0, buf, len);
          WRITE_UINT8 (buf + i, ll);
          i += sizeof (ll);
          break;

        case '4': /* t_uint32 */
          l = va_arg (ap, t_uint32);
          len += sizeof (l);
          buf = lim_realloc (0, buf, len);
          WRITE_UINT4 (buf + i, l);
          i += sizeof (l);
          break;

        case '1': /* int */
          c = va_arg (ap, int);
          len += sizeof (c);
          buf = lim_realloc (0, buf, len);
          buf[i] = c;
          i += sizeof (c);
          break;

        case 's': /* char*,size_t  (IE: <int32:length> <string>) */
          s = va_arg (ap, char *);
          l = va_arg (ap, size_t);
          len += sizeof (l) + l;
          buf = lim_realloc (0, buf, len);
          WRITE_UINT4 (buf + i, l);
          i += sizeof (l);
          mem_move (buf + i, s, l);
          i += l;
          break;

        case 'a': /* struct sftp_attrs* */
            attrs = va_arg (ap, struct sftp_attrs *);
          sftp_export_attrs (attrs, &s, &s_len);
          len += s_len;
          buf = lim_realloc (0, buf, len);
          mem_move (buf + i, s, s_len);
          i += s_len;
          break;

        default:
          if (!soft_errors)
            {
              safe_printfmt (2, "Invalid fmt to sftp_write_pkt!\n");
              exit (2);
            }
          else
            return -1;
      }

  c = va_arg (ap, int);

  WRITE_UINT4 (buf, len - sizeof(len));
  ret = vu_write_retry (&errn, p->out_fd, buf, len);

  lim_free (0, buf);
  va_end (ap);

  return ret;
}



/* take the structure and create an sftp attr string, returns size of string */
static int
sftp_export_attrs (struct sftp_attrs *a,
                   char ** pkt,
                   int * len)
{
  int i = 0;

  *len = sizeof (a->flags)
    + (a->flags & SSH_FILEXFER_ATTR_SIZE ? sizeof (a->size) : 0)
    + (a->flags & SSH_FILEXFER_ATTR_UIDGID ? sizeof (a->uid)*2 : 0)
    + (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS ? sizeof (a->perms) : 0)
    + (a->flags & SSH_FILEXFER_ATTR_ACMODTIME ? sizeof (a->atime)*2 : 0);

  *pkt = lim_malloc (0, *len);

  WRITE_UINT4 (*pkt, a->flags);
  i += 4;

  if (a->flags & SSH_FILEXFER_ATTR_SIZE)
    {
      WRITE_UINT8 (*pkt + i, a->size);
      i += 8;
    }

  if (a->flags & SSH_FILEXFER_ATTR_UIDGID)
    {
      WRITE_UINT4 (*pkt + i, a->uid);
      i += 4;
      WRITE_UINT4 (*pkt + i, a->gid);
      i += 4;
    }

  if (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
    {
      WRITE_UINT4 (*pkt + i, a->perms);
      i += 4;
    }

  if (a->flags & SSH_FILEXFER_ATTR_ACMODTIME)
    {
      WRITE_UINT4 (*pkt + i, a->atime);
      i += 4;
      WRITE_UINT4 (*pkt + i, a->mtime);
      i += 4;
    }

  return *len;
}





struct arch_pfs_session *
arch_pfs_sftp_connect (const t_uchar * uri)
{
  struct arch_pfs_sftp_session * answer = 0;
  t_uchar * hostname = 0;
  t_uchar * port = 0;
  t_uchar * user = 0;
  t_uchar ** ssh_argv;
  int subproc;
  int pin[2], pout[2];
  int c_in_fd, c_out_fd;
  enum arch_ssh_type ssh_type = arch_pfs_sftp_ssh_type ();

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

  if (arch_pfs_sftp_parse_uri (&user, &hostname, &port, &answer->cwd, uri)!=0)
    {
      safe_printfmt (2, "Invalid sftp URL\n");
      /*safe_printfmt (2, "Invalid sftp URL: %s\n", uri); 
       * can't use URI (may be mangled by arch_pfs_pfs_make_archive, etc */
      exit (2);
    }

  ssh_argv = 0;
  *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = arch_pfs_sftp_ssh_binary (ssh_type);

  if (port)
    {
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-p";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = port;
    }

  if (user)
    {
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-l";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = user;
    }

  if (ssh_type == ssh_type_lsh)
    {
      /* `--no-x11-forward' is the default with `lshc', so we don't need to
	 make it explicit.  Furthermore, it is not supported by `lshg', so
	 we'd better not use it so that both `lshc' and `lshg' can be used
	 interchangeably.  */

      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "--subsystem=sftp";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = hostname;
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = 0;
    }
  else if (ssh_type == ssh_type_openssh)
    {
      /* From OpenSSH' ssh(1), when using `-s', "The subsystem is specified
	 as the remote command".  IOW, we are to perform an invokation along
	 the lines of:

	   $ ssh -l USER -2 -s REMOTE-HOST sftp

         We assume the use of `ssh', not `sftp'.  */

      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-oFallBackToRsh=no";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-oForwardX11=no";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-oForwardAgent=no";
      /*  *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-oClearAllForwardings=yes"; */
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-2";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-s";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = hostname;
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "sftp";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = 0;
    }
  else if (ssh_type == ssh_type_fsecure3)
    {
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-x";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-a";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "-s";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = "sftp";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = hostname;
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar *)) = 0;
    }
  else if (ssh_type == ssh_type_psftp)
    {
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar*)) = "-2";
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar*)) = hostname;
      *(t_uchar **)ar_push ((void **)&ssh_argv, 0, sizeof (t_uchar*)) = 0;
    }
  else
    panic ("undefined SSH implementation type");


  if ((pipe (pin) == -1) || (pipe (pout) == -1))
    panic ("Couldn't create pipe(s)");

  answer->out_fd = pout[1];
  answer->in_fd = pin[0];
  c_in_fd = pout[0];
  c_out_fd = pin[1];

  subproc = fork ();

  if (subproc < 0)
    panic ("unable to fork");

  if (subproc == 0)
    {
      if ((dup2 (c_in_fd, STDIN_FILENO) == -1) ||
          (dup2 (c_out_fd, STDOUT_FILENO) == -1))
        {
          safe_printfmt (2, "dup2: %s\n", errno_to_string (errno));
          exit (1);
        }

      close (answer->in_fd);
      close (answer->out_fd);
      close (c_in_fd);
      close (c_out_fd);
      arch_util_execvp (ssh_argv[0], ssh_argv);
      safe_printfmt (2, "exec: %s: %s\n", ssh_argv[0], errno_to_string (errno));
      exit (1);
    }

  close (c_in_fd);
  close (c_out_fd);

  sftp_client_do_init (answer, 0);
  if (!pfs_file_exists((struct arch_pfs_session *)answer, answer->cwd))
    {
      safe_printfmt (2, "No such file or directory: %s\n", answer->cwd);
      exit (2);
    }

  lim_free (0, hostname);
  lim_free (0, user);
  lim_free (0, port);

  return (struct arch_pfs_session *)answer;
}




static void
sftp_client_do_init (struct arch_pfs_sftp_session * p,
                     int soft_errors)
{
  int pkt_len;
  char *pkt;

  sftp_write_pkt (p, soft_errors, "14", SSH_FXP_INIT, 
		  (t_uint32)SFTP_VERSION, soft_errors);
  sftp_server_response (p, &pkt, &pkt_len, SSH_FXP_VERSION, soft_errors);

  lim_free (0, pkt);
}




static int
sftp_server_response (struct arch_pfs_sftp_session * p,
                      char ** pkt,
                      int * pkt_len,
                      int expected_response,
                      int soft_errors)
{
  int i, errn;
  unsigned int len, read_len, total_read, response;
  unsigned char *pl;
  unsigned char buf[4];

  errn = 0;

  read_len = vu_read_retry (&errn, p->in_fd, buf, 4);

  if (read_len < 4)
    {
      if (!soft_errors)
        {
          if (errn)
            safe_printfmt (2, "Error reading from server: %s\n", errno_to_string (errn));
          else
            safe_printfmt (2, "Error reading from server\n");
          exit (2);
        }
      else
        return -1;
    }

  *pkt_len = len = READ_UINT4 (buf);

  *pkt = pl = lim_malloc (0, len);

  total_read = 0;
  while (len)
    {
      read_len = vu_read_retry (&errn, p->in_fd, pl + total_read,
                                MIN(len, BUFSIZ));
      if (read_len <= 0 || read_len > len)
        {
          if (!soft_errors)
            {
              if (errn)
                safe_printfmt (2, "Error reading from server: %s\n", errno_to_string (errn));
              else
                safe_printfmt (2, "Error reading from server.\n");
              exit (2);
            }
          else
            return -1;
        }
      total_read += read_len;
      len -= read_len;
    }

  i = 0;
  response = pl[i++];

  if (response != SSH_FXP_VERSION)
    {
      int rq_id_offset = 0; /* request_id offset caused by pipe-lining */
      if (p->plreqs)
	rq_id_offset = p->plreqs - 1;

      if (READ_UINT4 (pl + i) != (p->request_id - rq_id_offset))
        {
          if (!soft_errors)
            {
              lim_free (0, pl);
              safe_printfmt (2, "Error: server returned wrong request id\n");
              exit(2);
            }
          else
            return -1;
        }
      i += 4;
    }

  if (response != expected_response)
    {
      if (response == SSH_FXP_STATUS)
        {
          return sftp_decode_status (pl, *pkt_len, i, soft_errors);
        }


      if (!soft_errors)
        {
          safe_printfmt (2, "Error: server returned wrong response\n");
          exit (2);
        }
      else
        lim_free (0, pl);
    }

  return i;
}




static int
sftp_decode_status (char * pkt,
                    int pkt_len,
                    int i,
                    int soft_errors)
{
  int code, err_len;
  const char *err_msg;

  code = READ_UINT4 (pkt + i);
  i += 4;

  if (code == SSH_FX_OK || code == SSH_FX_EOF)
    return 0;

  if ( i == pkt_len )
    {
      switch (code)
        {
          case SSH_FX_NO_SUCH_FILE:
            err_msg = "no such file or directory"; break;

          case SSH_FX_PERMISSION_DENIED:
            err_msg = "permission denied"; break;

          case SSH_FX_FAILURE:
            err_msg = "failure"; break;

          case SSH_FX_BAD_MESSAGE:
            err_msg = "bad message"; break;

          case SSH_FX_NO_CONNECTION:
            err_msg = "no connection"; break;

          case SSH_FX_CONNECTION_LOST:
            err_msg = "connection lost"; break;

          case SSH_FX_OP_UNSUPPORTED:
            err_msg = "operation not supported"; break;

          default:
            err_msg = "???"; break;
        }
      err_len = strlen(err_msg);
    }
  else if ( pkt_len - i < 4 ||
            pkt_len - i < (err_len = READ_UINT4 (pkt + i)) + 4 )
    {
      if (!soft_errors)
        {
          safe_printfmt (2, "sftp_decode_status: Packet too short(1): possibly garbage from server?\n");
          exit (2);
        }
      else
        return -1;
    }
  else
    {
      i += 4;
      err_msg = pkt + i;
    }

  if (!soft_errors)
    {
      safe_printfmt (2, "sftp status: %.*s\n", err_len, err_msg); 
      exit (2);
    }
  else
    return -1;

}



static t_uchar *
sftp_abs_path (const t_uchar * cwd,
               const t_uchar *path)
{
  t_uchar * ap;
  if (path[0] != '/')
    ap = str_alloc_cat_many (0, cwd, "/", path, str_end);
  else
    ap = str_save (0, path);

  return dirfold (ap);
}



static int
sftp_get (struct arch_pfs_sftp_session * p,
          int data_fd,
          const t_uchar * path,
          int soft_errors)
{
  struct sftp_attrs attrs;
  unsigned int pkt_len;
  char *pkt, *file;
  int ret, handle_len, i;
  unsigned long long total_read;
  char *handle;
  int len, errn, eof, numpkts;

  file = sftp_abs_path (p->cwd, path);

  attrs.flags = 0;
  ret = sftp_write_pkt (p, soft_errors,
                        "14s4a", SSH_FXP_OPEN, ++p->request_id,
                        file, str_length (file), 
			(t_uint32)SSH_FXF_READ, &attrs);
  lim_free (0, file);

  if (0 > ret)
    return -1;

  if (0 > (i = sftp_server_response (p, &pkt, &pkt_len, SSH_FXP_HANDLE,
                                     soft_errors)))
    return -1;

  handle_len = READ_UINT4 (pkt + i);
  i += 4;
  if (handle_len > pkt_len - i)
    { 
     if (!soft_errors)
        {
          safe_printfmt(2, "sftp_client_get: Packet too short(2): did we get garbage from server?\n");
          exit (2);
        }
      else
        lim_free (0, pkt);
        return -1;
    }
  handle = lim_malloc (0, handle_len);
  mem_move (handle, pkt + i, handle_len);

  lim_free (0, pkt);

  total_read = 0;
  eof = 0;
  numpkts = 0;
  while (1)
    {
      for (; ! eof && p->plreqs <= MIN(MAX_PL_REQS, numpkts + 1); p->plreqs++)
	{
	  ret = sftp_write_pkt (p, soft_errors, "14s84", SSH_FXP_READ,
				++p->request_id, handle, (size_t)handle_len,
				total_read + SFTP_RWSIZE * p->plreqs,
				(t_uint32)SFTP_RWSIZE);
	  if (0 > ret)
	    {
	      lim_free (0, handle);
	      return -1;
	    }
	}

      if (0 > (i = sftp_server_response (p, &pkt, &pkt_len, SSH_FXP_DATA,
                                         soft_errors)))
        {
          lim_free (0, handle);
          return -1;
        }

      p->plreqs--;
      numpkts++;

      if (i == 0)
        {
	  if (p->plreqs)
	    {
	      eof = 1;
	      continue;
	    }

          lim_free (0, pkt);
          break;
        }

      if (eof)
	{
	  safe_printfmt(2, "EOF already reached, but the server sent data!\n");
	  exit(2);
	}

      len = READ_UINT4 (pkt + i);
      i += 4;
      if (len > pkt_len - i)
        {
          if (!soft_errors) {
            safe_printfmt (2, "sftp_client_get: Packet too short(3): did we get garbage from server?\n");
            exit (2);
          }
          else {
            lim_free (0, handle);
            lim_free (0, pkt);
            return -1;
          }
        }

      if (0 > vu_write_retry (&errn, data_fd, pkt + i, len))
        {
          if (! soft_errors) {
            safe_printfmt(2, "Error while writing: %s\n", errno_to_string (errn));
            exit (2);
          }
          else
            return -1;
        }
      i += len;
      total_read += len;

      lim_free (0, pkt);
    }

  if (p->plreqs)
    {
      safe_printfmt (2, "sftp_client_get: Requests still in pipe-line!\n");
      exit (2);
    }

  if (0 > sftp_write_pkt (p, soft_errors,
                          "14s", SSH_FXP_CLOSE, ++p->request_id,
                          handle, (size_t)handle_len))
    return -1;

  lim_free (0, handle);

  if (0 > (i = sftp_server_response (p, &pkt, &pkt_len, SSH_FXP_STATUS,
                                     soft_errors)))
    return -1;

  if (i >= 0)
    lim_free (0, pkt);

  return i;
}




static int
pfs_get_file (struct arch_pfs_session * p,
              int out_fd,
              const t_uchar * path,
              int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;

  return sftp_get (pfs, out_fd, path, soft_errors);
}


static t_uchar *
pfs_file_contents (struct arch_pfs_session * p,
                   const t_uchar * path,
                   int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  t_uchar * tmp_path = 0;
  int fd;
  t_uchar * answer = 0;

  tmp_path = tmp_file_name ("/tmp", ",,pfs-sftp-file-contents");
  fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
  safe_unlink (tmp_path);

  if (0 > sftp_get (pfs, fd, path, soft_errors))
    {
      safe_close(fd);
      return 0;
    }

  safe_lseek (fd, (off_t)0, SEEK_SET);
  answer = fd_contents (fd);
  safe_close (fd);

  return answer;
}


static rel_table
pfs_directory_files (struct arch_pfs_session * p,
                     const t_uchar * path,
                     int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  struct sftp_attrs attrs;
  rel_table answer = rel_table_nil;
  unsigned int pkt_len, j, count;
  char *pkt, *dir, *file;
  int len, handle_len, dots, i;
  char *handle;

  dir = sftp_abs_path (pfs->cwd, path);

 chop_slashes:
  i = str_length (dir);
  if (i > 1 && dir[i - 1] == '/')
    {
      dir[i - 1] = 0;
      goto chop_slashes;
    }

  if (0 > sftp_write_pkt (pfs, soft_errors,
                          "14s", SSH_FXP_OPENDIR, ++pfs->request_id,
                          dir, str_length (dir)))
    return rel_table_nil;
  lim_free (0, dir);

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_HANDLE,
                                     soft_errors)))
    return rel_table_nil;

  handle_len = READ_UINT4 (pkt + i);
  i += 4;
  if (handle_len > pkt_len - i)
    {
      if (! soft_errors) {
        safe_printfmt(2, "sftp_client_list: Packet too short(4): did we get garbage from server?\n");
        exit (2);
      }
      lim_free (0, pkt);
      return rel_table_nil;
    }
  handle = lim_malloc (0, handle_len);
  mem_move (handle, pkt + i, handle_len);

  lim_free (0, pkt);

  while (1)
    {
      if (0 >sftp_write_pkt (pfs, soft_errors,
                             "14s", SSH_FXP_READDIR, ++pfs->request_id,
                             handle, (size_t)handle_len))
        return rel_table_nil;

      if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_NAME,
                                         soft_errors)))
        return rel_table_nil;
      if (i == 0)
        {
          lim_free (0, pkt);
          break;
        }

      count = READ_UINT4 (pkt + i);
      i += 4;

      for (j = 0; j < count; j++)
        {
          len = READ_UINT4 (pkt + i);
          i += 4;
          if (len > pkt_len - i)
            {
              if (!soft_errors)
                {
                  safe_printfmt(2, "sftp_client_list: Packet too short(5): did we get garbage from server? (%d > %d - %d)\n", len, pkt_len, i);
                  lim_free (0, handle);
                  lim_free (0, pkt);
                  exit (2);
                }
              return rel_table_nil;
            }

          dots = 0;
          if ((len == 1 && pkt[i] == '.') ||
              (len == 2 && pkt[i] == '.' && pkt[i+1] == '.'))
            dots = 1;

          if (!dots)
            {
              file = str_save_n (0, pkt+i, len);
              rel_add_records (&answer, rel_singleton_record_taking (rel_make_field_str (file)), rel_record_null);
            }

          i += len;

          len = READ_UINT4 (pkt + i);
          i += 4;
          if (len > pkt_len - i)
            {
              if (! soft_errors)
                {
                  safe_printfmt(2, "sftp_client_list: Packet too short(6): did we get garbage from server?\n");
                  lim_free (0, handle);
                  lim_free (0, pkt);
                  exit (2);
                }
              return rel_table_nil;
            }
          i += len;

          if (0 > (i += sftp_import_attrs (&attrs, pkt + i, pkt_len - i,
                                           soft_errors)))
            return rel_table_nil;
        }

      lim_free (0, pkt);
    }

  if (0 > sftp_write_pkt (pfs, soft_errors,
                          "14s", SSH_FXP_CLOSE, ++pfs->request_id,
                          handle, (size_t)handle_len))
    return rel_table_nil;
  lim_free (0, handle);

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_HANDLE,
                                     soft_errors)))
    return rel_table_nil;

  if (i >= 0)
      lim_free (0, pkt);

  return answer;
}



/* take an sftp attr string and initialize a structure from it */
static int
sftp_import_attrs (struct sftp_attrs *a,
                   char * pkt,
                   unsigned int pkt_len,
                   int soft_errors)
{
  int count, i, j;
  unsigned int len;

  i = 0;

  a->flags = READ_UINT4 (pkt);
  i += 4;

  len = (a->flags & SSH_FILEXFER_ATTR_SIZE ? sizeof (a->size) : 0)
      + (a->flags & SSH_FILEXFER_ATTR_UIDGID ? sizeof (a->uid)*2 : 0)
      + (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS ? sizeof (a->perms) : 0)
      + (a->flags & SSH_FILEXFER_ATTR_ACMODTIME ? sizeof (a->atime)*2 : 0);

  if (len > pkt_len - i)
    {
      if (!soft_errors)
        {
          safe_printfmt (2, "sftp_import_attrs: Packet too short(7): did we get garbage from server?\n");
          exit (2);
        }
      return -1;
    }

  if (a->flags & SSH_FILEXFER_ATTR_SIZE)
    {
      a->size = READ_UINT8 (pkt + i);
      i += 8;
    }

  if (a->flags & SSH_FILEXFER_ATTR_UIDGID)
    {
      a->uid = READ_UINT4 (pkt + i);
      i += 4;
      a->gid = READ_UINT4 (pkt + i);
      i += 4;
    }

  if (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
    {
      a->perms = READ_UINT4 (pkt + i);
      i += 4;
    }

  if (a->flags & SSH_FILEXFER_ATTR_ACMODTIME)
    {
      a->atime = READ_UINT4 (pkt + i);
      i += 4;
      a->mtime = READ_UINT4 (pkt + i);
      i += 4;
    }

  if (a->flags & SSH_FILEXFER_ATTR_EXTENDED)
    {
      count = READ_UINT4 (pkt + i);
      for (j = 0; j < count; j++)
        {
          i += READ_UINT4 (pkt + i);
          i += READ_UINT4 (pkt + i);
        }
    }

  return i;
}

static int
pfs_put_file (struct arch_pfs_session *p,
              const t_uchar * path,
              mode_t perms,
              int data_fd,
              int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  struct sftp_attrs attrs;
  unsigned int pkt_len;
  char *pkt, *file;
  int ret, handle_len, i, errn;
  unsigned long long total_written;
  char *handle;
  char buf[SFTP_RWSIZE];
  int len, eof;

  file = sftp_abs_path (pfs->cwd, path);

  attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
  attrs.perms = perms;

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14s4a", SSH_FXP_OPEN, ++pfs->request_id, file,
                        str_length (file),
                        (t_uint32)(SSH_FXF_WRITE | SSH_FXF_CREAT | 
				   SSH_FXF_TRUNC),
			&attrs);
  lim_free (0, file);

  if (0 > ret)
      return -1;

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_HANDLE,
                                     soft_errors)))
      return i;

  handle_len = READ_UINT4 (pkt + i);
  i += 4;
  if (handle_len > pkt_len - i)
    {
      if (!soft_errors)
        {
          safe_printfmt (2, "sftp_client_put: Packet to short(8): did we get garbage from server?\n");
          exit (2);
        }
      lim_free (0, pkt);
      return -1;
    }
  handle = lim_malloc (0, handle_len);
  mem_move (handle, pkt + i, handle_len);

  lim_free (0, pkt);

  total_written = 0;
  eof = 0;
  while (1)
    {
      for (; ! eof && pfs->plreqs <= MAX_PL_REQS; pfs->plreqs++)
	{
	  if (0 > (len = vu_read_retry (&errn, data_fd, buf, sizeof (buf))))
	    {
	      if (!soft_errors)
		{
		  safe_printfmt (2, "Failed to read file to put()!\n");
		  exit (2);
		}
	      lim_free (0, handle);
	      return -1;
	    }

	  ret = sftp_write_pkt (pfs, soft_errors, "14s8s", SSH_FXP_WRITE,
				++pfs->request_id, handle, (size_t)handle_len,
				total_written + pfs->plreqs * sizeof (buf),
				buf, (size_t)len);
	  if (0 > ret)
	    {
	      lim_free (0, handle);
	      return -1;
	    }
	  
	  if (len < sizeof (buf))
	    eof = 1;
	}
      
      if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS,
                                         soft_errors)))
        {
          lim_free (0, handle);
          return i;
        }

      i = sftp_decode_status (pkt, pkt_len, i, soft_errors);
      lim_free (0, pkt);
      if (i == -1)
        {
          lim_free (0, handle);
          return -1;
        }

      total_written += len;

      if (! --pfs->plreqs)
	break;
    }

  if (pfs->plreqs)
    {
      safe_printfmt (2, "pfs_put_file: Requests still in pipe-line!\n");
      exit (2);
    }

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14s", SSH_FXP_CLOSE, ++pfs->request_id, handle,
                        (size_t)handle_len);
  lim_free (0, handle);

  if (0 > ret)
    return -1;

  i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS, soft_errors);

  if (i >= 0)
    {
      lim_free (0, pkt);
      i = 0;
    }
  else
    i = -1;

  return i;
}

static int
pfs_mkdir (struct arch_pfs_session * p,
           const t_uchar * path,
           mode_t mode,
           int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  struct sftp_attrs attrs;
  t_uchar * dir = 0;
  int answer = 0;
  int ret, i;
  unsigned int pkt_len;
  char *pkt;

  attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
  attrs.perms = mode;

  dir = sftp_abs_path (pfs->cwd, path);
  
  if (pfs_file_exists (p, dir))
    {
      lim_free (0, dir);

      if (soft_errors)
        {
          return -1;
        }
      else
        {
          safe_printfmt(2, "Error creating directory %s in path %s: File exists\n", path, pfs->cwd);
          exit(2);
        }
    }

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14sa", SSH_FXP_MKDIR, ++pfs->request_id,
                        dir, str_length (dir), &attrs);
  if (0 > ret)
    {
      lim_free (0, dir);
      return -1;
    }

  lim_free (0, dir);

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS,
                                     soft_errors)))
    return -1;

  if (0 > sftp_decode_status (pkt, pkt_len, i, 1))
    {
      if (soft_errors)
        {
          return -1;
        }
      else
        {
          safe_printfmt(2, "Error creating directory %s in path %s\n", path, pfs->cwd);
          safe_printfmt(2, "Server: ");
          sftp_decode_status(pkt, pkt_len, i, 0);
        }
    }

  lim_free (0, pkt);

  return answer;
}


static int
pfs_file_exists (struct arch_pfs_session * p,
                 const t_uchar * path)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  struct sftp_attrs attrs;
  t_uchar * path_dir = 0;
  char *pkt;
  unsigned int pkt_len;
  int i;

  path_dir = sftp_abs_path(pfs->cwd, path);

  if (0 > sftp_write_pkt(pfs, 1, "14s",
                         SSH_FXP_STAT, ++pfs->request_id, path_dir,
                         str_length (path_dir)))
    return 0;

  lim_free (0, path_dir);

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_ATTRS,
                                     1)))
    return 0;

  if (0 > (i = sftp_import_attrs (&attrs, pkt + i, pkt_len - i, 1)))
    return 0;

  lim_free (0, pkt);

  return 1;
}


static int
pfs_is_dir (struct arch_pfs_session * p,
            const t_uchar * path)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  int ret, i;
  unsigned int pkt_len;
  char *pkt, *dir;
  struct sftp_attrs attrs;

  dir = sftp_abs_path (pfs->cwd, path);

  if (0 > sftp_write_pkt (pfs, 1,
                          "14s", SSH_FXP_STAT, ++pfs->request_id, dir,
                          str_length (dir)))
    {
      lim_free (0, dir);
      return -1;
    }

  lim_free (0, dir);

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_ATTRS, 1)))
    return -1;

  if (0 > (i = sftp_import_attrs (&attrs, pkt + i, pkt_len - i, 1)))
    {
      lim_free (0, pkt);
      return -1;
    }

  ret = 0;
  if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS && attrs.perms & S_IFDIR)
     ret = 1;

  lim_free (0, pkt);

  return ret;
}


static int
pfs_rename (struct arch_pfs_session * p,
            t_uchar ** errstr,
            const t_uchar * from,
            const t_uchar * to,
            int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  int ret, i;
  unsigned int pkt_len;
  char *pkt, *from2, *to2, *file, *b;

  from2 = sftp_abs_path (pfs->cwd, from);
  to2 = sftp_abs_path (pfs->cwd, to);

  if (0 < pfs_is_dir (p, to2))
    {
      b = file = str_save (0, from);

    filename:
      file = str_chr_rindex (file, '/');
      if (!file)
          file = b;
      else if (!file[1])
        {
          file[0] = 0;
          goto filename;
               }
      else
          file++;

      to2 = str_realloc_cat_many (0, to2, "/", file, str_end);
      lim_free (0, b);
    }

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14ss", SSH_FXP_RENAME, ++pfs->request_id, from2,
                        str_length (from2), to2, str_length (to2));

  lim_free (0, from2);
  lim_free (0, to2);

  if (0 > ret)
    return -1;

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS,
                                     soft_errors)))
      return -1;

  if (0 > (i = sftp_decode_status (pkt, pkt_len, i, soft_errors)))
    {
      lim_free (0, pkt);
      return -1;
    }

  lim_free (0, pkt);

  return i;
}


static int
pfs_rmdir (struct arch_pfs_session * p,
           const t_uchar * path,
           int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;

  int ret, i;
  unsigned int pkt_len;
  char *pkt, *dir;

  dir = sftp_abs_path (pfs->cwd, path);

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14s", SSH_FXP_RMDIR, ++pfs->request_id, dir,
                        str_length (dir));

  lim_free (0, dir);

  if (0 > ret)
      return -1;

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS,
                                     soft_errors)))
    return -1;

  if (0 > sftp_decode_status (pkt, pkt_len, i, 1))
    {
      if (soft_errors)
        {
          return -1;
        }
      else
        {
          safe_printfmt(2, "Error creating directory %s in path %s\n", path, pfs->cwd);
          safe_printfmt(2, "Server: ");
          sftp_decode_status(pkt, pkt_len, i, 0);
        }
    }

  lim_free (0, pkt);

  return i;
}

static int
pfs_rm (struct arch_pfs_session * p,
        const t_uchar * path,
        int soft_errors)
{
  struct arch_pfs_sftp_session * pfs = (struct arch_pfs_sftp_session *)p;
  int ret, i;
  unsigned int pkt_len;
  char *pkt, *file;

  file = sftp_abs_path (pfs->cwd, path);

  ret = sftp_write_pkt (pfs, soft_errors,
                        "14s", SSH_FXP_REMOVE, ++pfs->request_id, file,
                        str_length (file));

  lim_free (0, file);

  if (0 > ret)
    return -1;

  if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_STATUS,
                                     soft_errors)))
    return -1;

  i = sftp_decode_status (pkt, pkt_len, i, soft_errors);

  lim_free (0, pkt);

  return i;
}


static t_uchar *
dirfold (t_uchar *dir)
{
  t_uchar * buf;
  t_uchar * this;
  t_uchar * next;
  int dir_i = 0;

  this = next = buf = str_save (0, dir);
  while ((this = str_separate (&next, "/")) != NULL)
    {
      if (str_length (this) == 0 || (str_length (this) == 1 && this[0] == '.'))
        continue;
      else if (str_length (this) == 2 && *this == '.' && this[1] == '.')
        {
          if (dir_i > 0)
            dir_i = (int)((char *)strrchr (dir, '/') - (char *)dir);
          dir[dir_i] = 0;
        }
      else
        {
          dir[dir_i++] = '/';
          strcpy (dir + dir_i, this);
          dir_i += str_length (this);
        }
    }
  lim_free (0, buf);

  if (!str_length (dir))
      str_cpy (dir, "/");

  return dir;
}


int
arch_pfs_sftp_parse_uri (t_uchar ** user,
                         t_uchar ** hostname,
                         t_uchar ** port,
                         char ** path,
                         const t_uchar * uri)
{
  const t_uchar * start;
  const t_uchar * stop;
  t_uchar * host_part = 0;
  const t_uchar * at;
  const t_uchar * h0;
  const t_uchar * h1;
  const t_uchar * colon;

  /* [user@]hostname[:port]/path
   * ^    ^ ^       ^      ^
   * |    | |       |      |
   * |   at?|       |      stop[*]
   * |      |       |	     0-byte of host_part
   * |     h0       |
   * |              |
   * |              |
   * |              |
   * start[*]     colon?
   * host_part    h1
   * 
   * 
   * start and stop point to one copy of the string
   * (the original uri which includes the /path).
   * 
   * host_part points to another copy of the string 
   * (local to this block) which all the other pointers
   * point into.   
   * 
   * at? and colon? are 0 if @ and : are not present.
   * 
   * The reason to make host_part is so that str_chr_index 
   * searches are confined to just the host_part.
   */


  start = uri + sizeof ("sftp://") - 1;
  stop = str_chr_index (start, '/');
  if (!stop)
    return -1;
  *path = str_save (0, stop);

  host_part = str_save_n (0, start, stop - start);

  at = str_chr_index (host_part, '@');
  if (at)
    h0 = at + 1;
  else
    h0 = host_part;

  colon = str_chr_index (h0, ':');
  if (colon)
    h1 = colon;
  else
    h1 = h0 + str_length (h0);

  if (at)
    *user = str_save_n (0, host_part, at - host_part);

  if (colon)
    *port = str_save (0, colon + 1);

  *hostname = str_save_n (0, h0, h1 - h0);

  lim_free (0, host_part);
  return 0;
}




/* tag: Tom Lord Thu Jun  5 15:23:06 2003 (pfs-sftp.c)
 */
