/* 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 #include #include #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: ) */ 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) */