/* 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/os/signal.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/file-contents.h"
#include "libarch/archives.h"
#include "libarch/exec.h"
#include "libarch/pfs-sftp.h"
#include "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 void fs_disconnect (struct arch_pfs_session **pfs);
static t_uchar * pfs_file_contents (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static rel_table pfs_directory_files (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_file_exists (struct arch_pfs_session * p, t_uchar * path);
static int pfs_get_file (struct arch_pfs_session * p, int out_fd, t_uchar * path, int soft_errors);
static int pfs_mkdir (struct arch_pfs_session * p, t_uchar * path, mode_t mode, int soft_errors);
static int pfs_rename (struct arch_pfs_session * p, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors);
static int pfs_is_dir (struct arch_pfs_session * p, t_uchar * path);
static int pfs_rmdir (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_rm (struct arch_pfs_session * p, t_uchar * path, int soft_errors);
static int pfs_put_file (struct arch_pfs_session * p, t_uchar * path, mode_t perms, int in_fd, int soft_errors);
static t_uchar * sftp_abs_path (t_uchar * cwd, t_uchar * path);
static int sftp_client_do_init (struct arch_pfs_sftp_session * p, int soft_errors);
static int sftp_write_pkt (struct arch_pfs_sftp_session * p, int soft_errors, char *fmt, ...);
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 int sftp_get (struct arch_pfs_sftp_session * p, int data_fd, 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 sftp_export_attrs (struct sftp_attrs *a, char **pkt, int *len);
static t_uchar * dirfold (t_uchar *dir);
struct arch_pfs_vtable sftp_pfs_fns =
{
fs_disconnect,
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 (t_uchar * uri)
{
if (!str_cmp_prefix ("sftp:", uri))
return 1;
else
return 0;
}
/**
* \brief write a packet
* returns 0 on success
*/
static int
sftp_write_pkt (struct arch_pfs_sftp_session * p, int soft_errors, 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': /* int64 */
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': /* int32 */
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': /* int8 */
c = va_arg (ap, int);
len += sizeof (c);
buf = lim_realloc (0, buf, len);
buf[i] = c;
i += sizeof (c);
break;
case 's': /* string (IE: <int32:length> <string>) */
s = va_arg (ap, char *);
l = va_arg (ap, int);
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': /* attributes */
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));
{
struct sigaction newact;
struct sigaction oldact;
/* 20050302 RBC
* this is not thread-safe. we either need a generic SIGPIPE handler
* or a user lock callback or such. mind you hackerlab isn't threadsafe
* anyway right now.
*/
mem_set0 ((t_uchar *)&newact, sizeof (newact));
newact.sa_handler = SIG_IGN;
if (sigaction (SIGPIPE, &newact, &oldact))
safe_printfmt (2, "failed to set new sigaction for SIGPIPE\n");
ret = vu_write_retry (&errn, p->out_fd, buf, len) < 0;
if (sigaction (SIGPIPE, &oldact, NULL))
safe_printfmt (2, "failed to restore sigaction for SIGPIPE\n");
}
lim_free (0, buf);
va_end (ap);
return ret;
}
/* take the structure and create an sftp attr string, returns size of string */
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 (t_uchar * uri, int soft_errors)
{
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;
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)
{
if (soft_errors)
{
lim_free (0, answer);
/* FIXME free answers resources */
return NULL;
}
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;
ar_push_uchar_star (&ssh_argv, cfg__ssh);
if (port)
{
ar_push_uchar_star (&ssh_argv, "-p");
ar_push_uchar_star (&ssh_argv, port);
}
if (user)
{
ar_push_uchar_star (&ssh_argv, "-l");
ar_push_uchar_star (&ssh_argv, user);
}
#if cfg__ssh_is_lsh
ar_push_uchar_star (&ssh_argv, "--no-x11-forward");
ar_push_uchar_star (&ssh_argv, "--subsystem=sftp");
ar_push_uchar_star (&ssh_argv, hostname);
#elif cfg__ssh_is_openssh
ar_push_uchar_star (&ssh_argv, "-oFallBackToRsh=no");
ar_push_uchar_star (&ssh_argv, "-oForwardX11=no");
ar_push_uchar_star (&ssh_argv, "-oForwardAgent=no");
/* ar_push_uchar_star (&ssh_argv, "-oClearAllForwardings=yes"); */
ar_push_uchar_star (&ssh_argv, "-2");
ar_push_uchar_star (&ssh_argv, "-s");
ar_push_uchar_star (&ssh_argv, hostname);
ar_push_uchar_star (&ssh_argv, "sftp");
ar_push_uchar_star (&ssh_argv, 0);
#else
panic ("No ssh flavor was specified at compile time!");
#endif
if ((pipe (pin) == -1) || (pipe (pout) == -1))
{
if (soft_errors)
{
lim_free (0, answer);
/* FIXME free answers resources */
return NULL;
}
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)
{
if (soft_errors)
{
lim_free (0, answer);
/* FIXME free answers resources */
return NULL;
}
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);
if (sftp_client_do_init (answer, soft_errors))
{
if (soft_errors)
{
lim_free (0, answer);
/* FIXME free answers resources */
return NULL;
}
safe_printfmt (2, "Failed to initialise the sftp client\n");
exit (2);
}
if (!pfs_file_exists((struct arch_pfs_session *)answer, answer->cwd))
{
if (soft_errors)
{
lim_free (0, answer);
/* FIXME free answers resources */
return NULL;
}
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;
}
void
fs_disconnect (struct arch_pfs_session **pfs)
{
/* FIXME: close stuff yadayadayada */
lim_free (0, *pfs);
*pfs = NULL;
}
static int
sftp_client_do_init (struct arch_pfs_sftp_session * p, int soft_errors)
{
int pkt_len;
char *pkt;
int result = 0;
if (sftp_write_pkt (p, soft_errors, "14", SSH_FXP_INIT, SFTP_VERSION, soft_errors) ||
0 > sftp_server_response (p, &pkt, &pkt_len, SSH_FXP_VERSION, soft_errors))
result = -1;
else
lim_free (0, pkt);
return result;
}
static int
sftp_server_response (struct arch_pfs_sftp_session * p, char **pkt,
int *pkt_len, int expected_response, int soft_errors)
{
int pkt_offset, 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;
}
pkt_offset = 0;
response = pl[pkt_offset++];
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 + pkt_offset) != (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;
}
pkt_offset += 4;
}
if (response != expected_response)
{
if (response == SSH_FXP_STATUS)
{
return sftp_decode_status (pl, *pkt_len, pkt_offset, soft_errors);
}
if (!soft_errors)
{
safe_printfmt (2, "Error: server returned wrong response\n");
exit (2);
}
else
lim_free (0, pl);
}
return pkt_offset;
}
static int
sftp_decode_status (char *pkt, int pkt_len, int i, int soft_errors)
{
int code, err_len;
code = READ_UINT4 (pkt + i);
i += 4;
if (code == SSH_FX_OK || code == SSH_FX_EOF)
return 0;
err_len = READ_UINT4 (pkt + i);
i += 4;
if (err_len > pkt_len - i)
{
if (!soft_errors)
{
safe_printfmt (2, "sftp_decode_status: Packet too short(1): possibly garbage from server?\n");
exit (2);
}
else
return -1;
}
if (!soft_errors)
{
safe_printfmt (2, "sftp status: %.*s\n", err_len, pkt + i);
exit (2);
}
else
return -1;
}
static t_uchar *
sftp_abs_path (t_uchar * cwd, 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, 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), SSH_FXF_READ, &attrs);
lim_free (0, file);
if (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, handle_len,
total_read + SFTP_RWSIZE * p->plreqs,
SFTP_RWSIZE);
if (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 (sftp_write_pkt (p, soft_errors,
"14s", SSH_FXP_CLOSE, ++p->request_id,
handle, 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, 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, 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_in_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);
lim_free(0, tmp_path);
return answer;
}
static rel_table
pfs_directory_files (struct arch_pfs_session * p, 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 = 0;
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 (sftp_write_pkt (pfs, soft_errors,
"14s", SSH_FXP_OPENDIR, ++pfs->request_id,
dir, str_length (dir)))
return 0;
lim_free (0, dir);
if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_HANDLE,
soft_errors)))
return 0;
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 0;
}
handle = lim_malloc (0, handle_len);
mem_move (handle, pkt + i, handle_len);
lim_free (0, pkt);
while (1)
{
if (sftp_write_pkt (pfs, soft_errors,
"14s", SSH_FXP_READDIR, ++pfs->request_id,
handle, handle_len))
return 0;
if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_NAME,
soft_errors)))
return 0;
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 0;
}
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_make_record (file, 0), 0);
}
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 0;
}
i += len;
if (0 > (i += sftp_import_attrs (&attrs, pkt + i, pkt_len - i,
soft_errors)))
return 0;
}
lim_free (0, pkt);
}
if (sftp_write_pkt (pfs, soft_errors,
"14s", SSH_FXP_CLOSE, ++pfs->request_id,
handle, handle_len))
return 0;
lim_free (0, handle);
if (0 > (i = sftp_server_response (pfs, &pkt, &pkt_len, SSH_FXP_HANDLE,
soft_errors)))
return 0;
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, 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),
SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, &attrs);
lim_free (0, file);
if (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)
{
len = 0;
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, handle_len,
total_written + pfs->plreqs * sizeof (buf),
buf, len);
if (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,
handle_len);
lim_free (0, handle);
if (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, 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 (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, 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 (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, 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 (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, t_uchar * from,
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 (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, 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 (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, 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 (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;
}
/**
* \brief parse a uri into components
* \return 0 on success
*/
int
arch_pfs_sftp_parse_uri (t_uchar ** user, t_uchar ** hostname, t_uchar ** port, char ** path, t_uchar * uri)
{
ne_uri parsed_uri;
/* [authino@]host[:port]/path */
if (ne_uri_parse (uri, &parsed_uri) != 0)
return -1;
/* can only be called for sftp uris */
invariant (!str_cmp(parsed_uri.scheme, "sftp"));
/* handle non nrmalised uris, with whacky user input.
* RBC 20050304 FIXME: when we have the new registration syste,
* we must move this call to the uri loading code
*/
arch_uri_heuristics (&parsed_uri);
*user = str_save (0, parsed_uri.userinfo);
*hostname = str_save (0, parsed_uri.host);
if (parsed_uri.port)
{
t_uchar buf[64];
cvt_long_to_decimal (buf, parsed_uri.port);
*port = str_save (0, buf);
}
*path = str_save (0, parsed_uri.path);
ne_uri_free(&parsed_uri);
return 0;
}
/* tag: Tom Lord Thu Jun 5 15:23:06 2003 (pfs-sftp.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1