Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'unix/sftpserver.c')
-rw-r--r--unix/sftpserver.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/unix/sftpserver.c b/unix/sftpserver.c
new file mode 100644
index 00000000..453a3ee0
--- /dev/null
+++ b/unix/sftpserver.c
@@ -0,0 +1,703 @@
+/*
+ * Implement the SftpServer abstraction, in the 'live' form (i.e.
+ * really operating on the Unix filesystem).
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <utime.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ssh/server.h"
+#include "ssh/sftp.h"
+#include "tree234.h"
+
+typedef struct UnixSftpServer UnixSftpServer;
+
+struct UnixSftpServer {
+ unsigned *fdseqs;
+ bool *fdsopen;
+ size_t fdsize;
+
+ tree234 *dirhandles;
+ int last_dirhandle_index;
+
+ char handlekey[8];
+
+ SftpServer srv;
+};
+
+struct uss_dirhandle {
+ int index;
+ DIR *dp;
+};
+
+#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
+
+static int uss_dirhandle_cmp(void *av, void *bv)
+{
+ struct uss_dirhandle *a = (struct uss_dirhandle *)av;
+ struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
+ if (a->index < b->index)
+ return -1;
+ if (a->index > b->index)
+ return +1;
+ return 0;
+}
+
+static SftpServer *uss_new(const SftpServerVtable *vt)
+{
+ UnixSftpServer *uss = snew(UnixSftpServer);
+
+ memset(uss, 0, sizeof(UnixSftpServer));
+
+ uss->dirhandles = newtree234(uss_dirhandle_cmp);
+ uss->srv.vt = vt;
+
+ make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey));
+
+ return &uss->srv;
+}
+
+static void uss_free(SftpServer *srv)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct uss_dirhandle *udh;
+
+ for (size_t i = 0; i < uss->fdsize; i++)
+ if (uss->fdsopen[i])
+ close(i);
+ sfree(uss->fdseqs);
+
+ while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
+ closedir(udh->dp);
+ sfree(udh);
+ }
+
+ sfree(uss);
+}
+
+static void uss_return_handle_raw(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
+{
+ unsigned char handlebuf[8];
+ PUT_32BIT_MSB_FIRST(handlebuf, index);
+ PUT_32BIT_MSB_FIRST(handlebuf + 4, seq);
+ des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
+ fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
+}
+
+static bool uss_decode_handle(
+ UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
+{
+ unsigned char handlebuf[8];
+
+ if (handle.len != 8)
+ return false;
+ memcpy(handlebuf, handle.ptr, 8);
+ des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
+ *index = toint(GET_32BIT_MSB_FIRST(handlebuf));
+ *seq = GET_32BIT_MSB_FIRST(handlebuf + 4);
+ return true;
+}
+
+static void uss_return_new_handle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
+{
+ assert(fd >= 0);
+ if (fd >= uss->fdsize) {
+ size_t old_size = uss->fdsize;
+ sgrowarray(uss->fdseqs, uss->fdsize, fd);
+ uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool);
+ while (old_size < uss->fdsize) {
+ uss->fdseqs[old_size] = 0;
+ uss->fdsopen[old_size] = false;
+ old_size++;
+ }
+ }
+ assert(!uss->fdsopen[fd]);
+ uss->fdsopen[fd] = true;
+ if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
+ uss->fdseqs[fd] = 0;
+ uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
+}
+
+static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
+{
+ int fd;
+ unsigned seq;
+ if (!uss_decode_handle(uss, handle, &fd, &seq) ||
+ fd < 0 || fd >= uss->fdsize ||
+ !uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
+ return -1;
+
+ return fd;
+}
+
+static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ int fd = uss_try_lookup_fd(uss, handle);
+ if (fd < 0)
+ fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
+ return fd;
+}
+
+static void uss_return_new_dirhandle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
+{
+ struct uss_dirhandle *udh = snew(struct uss_dirhandle);
+ udh->index = uss->last_dirhandle_index++;
+ udh->dp = dp;
+ struct uss_dirhandle *added = add234(uss->dirhandles, udh);
+ assert(added == udh);
+ uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
+}
+
+static struct uss_dirhandle *uss_try_lookup_dirhandle(
+ UnixSftpServer *uss, ptrlen handle)
+{
+ struct uss_dirhandle key, *udh;
+ unsigned seq;
+
+ if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
+ seq != USS_DIRHANDLE_SEQ ||
+ (udh = find234(uss->dirhandles, &key, NULL)) == NULL)
+ return NULL;
+
+ return udh;
+}
+
+static struct uss_dirhandle *uss_lookup_dirhandle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
+{
+ struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
+ if (!udh)
+ fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
+ return udh;
+}
+
+static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
+{
+ unsigned code = SSH_FX_FAILURE;
+ switch (errno) {
+ case ENOENT:
+ code = SSH_FX_NO_SUCH_FILE;
+ break;
+ case EPERM:
+ code = SSH_FX_PERMISSION_DENIED;
+ break;
+ }
+ fxp_reply_error(reply, code, strerror(errno));
+}
+
+static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *inpath = mkstr(path);
+ char *outpath = realpath(inpath, NULL);
+ free(inpath);
+
+ if (!outpath) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
+ free(outpath);
+ }
+}
+
+static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, unsigned flags, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ int openflags = 0;
+ if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
+ openflags |= O_RDWR;
+ else if (flags & SSH_FXF_WRITE)
+ openflags |= O_WRONLY;
+ else if (flags & SSH_FXF_READ)
+ openflags |= O_RDONLY;
+ if (flags & SSH_FXF_APPEND)
+ openflags |= O_APPEND;
+ if (flags & SSH_FXF_CREAT)
+ openflags |= O_CREAT;
+ if (flags & SSH_FXF_TRUNC)
+ openflags |= O_TRUNC;
+ if (flags & SSH_FXF_EXCL)
+ openflags |= O_EXCL;
+
+ char *pathstr = mkstr(path);
+ int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
+ free(pathstr);
+
+ if (fd < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_return_new_handle(uss, reply, fd);
+ }
+}
+
+static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ DIR *dp = opendir(pathstr);
+ free(pathstr);
+
+ if (!dp) {
+ uss_error(uss, reply);
+ } else {
+ uss_return_new_dirhandle(uss, reply, dp);
+ }
+}
+
+static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+ struct uss_dirhandle *udh;
+
+ if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
+ closedir(udh->dp);
+ del234(uss->dirhandles, udh);
+ sfree(udh);
+ fxp_reply_ok(reply);
+ } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
+ close(fd);
+ assert(0 <= fd && fd <= uss->fdsize);
+ uss->fdsopen[fd] = false;
+ fxp_reply_ok(reply);
+ }
+ /* if both failed, uss_lookup_fd will have filled in an error response */
+}
+
+static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = rmdir(pathstr);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = unlink(pathstr);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen srcpath, ptrlen dstpath)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
+ int status = rename(srcstr, dststr);
+ free(srcstr);
+ free(dststr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
+{
+ struct fxp_attrs attrs;
+
+ attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
+ SSH_FILEXFER_ATTR_PERMISSIONS |
+ SSH_FILEXFER_ATTR_UIDGID |
+ SSH_FILEXFER_ATTR_ACMODTIME);
+
+ attrs.size = st->st_size;
+ attrs.permissions = st->st_mode;
+ attrs.uid = st->st_uid;
+ attrs.gid = st->st_gid;
+ attrs.atime = st->st_atime;
+ attrs.mtime = st->st_mtime;
+
+ return attrs;
+}
+
+static void uss_reply_struct_stat(SftpReplyBuilder *reply,
+ const struct stat *st)
+{
+ fxp_reply_attrs(reply, uss_translate_struct_stat(st));
+}
+
+static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, bool follow_symlinks)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct stat st;
+
+ char *pathstr = mkstr(path);
+ int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_reply_struct_stat(reply, &st);
+ }
+}
+
+static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct stat st;
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+ int status = fstat(fd, &st);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_reply_struct_stat(reply, &st);
+ }
+}
+
+#if !HAVE_FUTIMES
+static inline int futimes(int fd, const struct timeval tv[2])
+{
+ /* If the OS doesn't support futimes(3) then we have to pretend it
+ * always returns failure */
+ errno = EINVAL;
+ return -1;
+}
+#endif
+
+/*
+ * The guts of setstat and fsetstat, macroised so that they can call
+ * fchown(fd,...) or chown(path,...) depending on parameters.
+ */
+#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
+ { \
+ if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
+ if (api_prefix(truncate)(api_arg, attrs.size) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
+ if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
+ if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
+ struct timeval tv[2]; \
+ tv[0].tv_sec = attrs.atime; \
+ tv[1].tv_sec = attrs.mtime; \
+ tv[0].tv_usec = tv[1].tv_usec = 0; \
+ if (api_prefix(utimes)(api_arg, tv) < 0) \
+ success = false; \
+ } \
+ } while (0)
+
+#define PATH_PREFIX(func) func
+#define FD_PREFIX(func) f ## func
+
+static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ bool success = true;
+ SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
+ free(pathstr);
+
+ if (!success) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ bool success = true;
+ SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
+
+ if (!success) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, unsigned length)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+ char *buf;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ if ((buf = malloc(length)) == NULL) {
+ /* A rare case in which I bother to check malloc failure,
+ * because in this case we can localise the problem easily by
+ * turning it into a failure response from this one sftp
+ * request */
+ fxp_reply_error(reply, SSH_FX_FAILURE,
+ "Out of memory for read buffer");
+ return;
+ }
+
+ char *p = buf;
+
+ int status = lseek(fd, offset, SEEK_SET);
+ if (status >= 0 || errno == ESPIPE) {
+ bool seekable = (status >= 0);
+ while (length > 0) {
+ status = read(fd, p, length);
+ if (status <= 0)
+ break;
+
+ unsigned bytes_read = status;
+ assert(bytes_read <= length);
+ length -= bytes_read;
+ p += bytes_read;
+
+ if (!seekable) {
+ /*
+ * If the seek failed because the file is fundamentally
+ * not a seekable kind of thing, abandon this loop after
+ * one attempt, i.e. we just read whatever we could get
+ * and we don't mind returning a short buffer.
+ */
+ }
+ }
+ }
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else if (p == buf) {
+ fxp_reply_error(reply, SSH_FX_EOF, "End of file");
+ } else {
+ fxp_reply_data(reply, make_ptrlen(buf, p - buf));
+ }
+
+ free(buf);
+}
+
+static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, ptrlen data)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ const char *p = data.ptr;
+ unsigned length = data.len;
+
+ int status = lseek(fd, offset, SEEK_SET);
+ if (status >= 0 || errno == ESPIPE) {
+
+ while (length > 0) {
+ status = write(fd, p, length);
+ assert(status != 0);
+ if (status < 0)
+ break;
+
+ unsigned bytes_written = status;
+ assert(bytes_written <= length);
+ length -= bytes_written;
+ p += bytes_written;
+ }
+ }
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, int max_entries, bool omit_longname)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct dirent *de;
+ struct uss_dirhandle *udh;
+
+ if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
+ return;
+
+ errno = 0;
+ de = readdir(udh->dp);
+ if (!de) {
+ if (errno == 0) {
+ fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
+ } else {
+ uss_error(uss, reply);
+ }
+ } else {
+ ptrlen longname = PTRLEN_LITERAL("");
+ char *longnamebuf = NULL;
+ struct fxp_attrs attrs = no_attrs;
+
+#if defined HAVE_FSTATAT && defined HAVE_DIRFD
+ struct stat st;
+ if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
+ char perms[11], *uidbuf = NULL, *gidbuf = NULL;
+ struct passwd *pwd;
+ struct group *grp;
+ const char *user, *group;
+ struct tm tm;
+
+ attrs = uss_translate_struct_stat(&st);
+
+ if (!omit_longname) {
+
+ strcpy(perms, "----------");
+ switch (st.st_mode & S_IFMT) {
+ case S_IFBLK: perms[0] = 'b'; break;
+ case S_IFCHR: perms[0] = 'c'; break;
+ case S_IFDIR: perms[0] = 'd'; break;
+ case S_IFIFO: perms[0] = 'p'; break;
+ case S_IFLNK: perms[0] = 'l'; break;
+ case S_IFSOCK: perms[0] = 's'; break;
+ }
+ if (st.st_mode & S_IRUSR)
+ perms[1] = 'r';
+ if (st.st_mode & S_IWUSR)
+ perms[2] = 'w';
+ if (st.st_mode & S_IXUSR)
+ perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
+ else
+ perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
+ if (st.st_mode & S_IRGRP)
+ perms[4] = 'r';
+ if (st.st_mode & S_IWGRP)
+ perms[5] = 'w';
+ if (st.st_mode & S_IXGRP)
+ perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
+ else
+ perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
+ if (st.st_mode & S_IROTH)
+ perms[7] = 'r';
+ if (st.st_mode & S_IWOTH)
+ perms[8] = 'w';
+ if (st.st_mode & S_IXOTH)
+ perms[9] = 'x';
+
+ if ((pwd = getpwuid(st.st_uid)) != NULL)
+ user = pwd->pw_name;
+ else
+ user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
+ if ((grp = getgrgid(st.st_gid)) != NULL)
+ group = grp->gr_name;
+ else
+ group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
+
+ tm = *localtime(&st.st_mtime);
+
+ longnamebuf = dupprintf(
+ "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s",
+ perms, (unsigned)st.st_nlink, user, group,
+ (uintmax_t)st.st_size,
+ (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]),
+ tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
+ longname = ptrlen_from_asciz(longnamebuf);
+
+ sfree(uidbuf);
+ sfree(gidbuf);
+ }
+ }
+#endif
+
+ /* FIXME: be able to return more than one, in which case we
+ * must also check max_entries */
+ fxp_reply_name_count(reply, 1);
+ fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
+ longname, attrs);
+
+ sfree(longnamebuf);
+ }
+}
+
+const SftpServerVtable unix_live_sftpserver_vt = {
+ .new = uss_new,
+ .free = uss_free,
+ .realpath = uss_realpath,
+ .open = uss_open,
+ .opendir = uss_opendir,
+ .close = uss_close,
+ .mkdir = uss_mkdir,
+ .rmdir = uss_rmdir,
+ .remove = uss_remove,
+ .rename = uss_rename,
+ .stat = uss_stat,
+ .fstat = uss_fstat,
+ .setstat = uss_setstat,
+ .fsetstat = uss_fsetstat,
+ .read = uss_read,
+ .write = uss_write,
+ .readdir = uss_readdir,
+};