X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/52e3daedec4da996f923934417982ace52d0b981..HEAD:/sftp-server.c diff --git a/sftp-server.c b/sftp-server.c index a6add52a..a98ac2b6 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1,3 +1,4 @@ +/* $OpenBSD: sftp-server.c,v 1.91 2010/01/13 01:40:16 djm Exp $ */ /* * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. * @@ -13,19 +14,39 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + #include "includes.h" #include +#include #include +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_SYS_STATVFS_H +#include +#endif #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xmalloc.h" #include "buffer.h" -#include "bufaux.h" -#include "getput.h" #include "log.h" -#include "xmalloc.h" #include "misc.h" +#include "uidswap.h" #include "sftp.h" #include "sftp-common.h" @@ -34,9 +55,13 @@ #define get_int64() buffer_get_int64(&iqueue); #define get_int() buffer_get_int(&iqueue); #define get_string(lenp) buffer_get_string(&iqueue, lenp); -#define TRACE debug -extern char *__progname; +/* Our verbosity */ +LogLevel log_level = SYSLOG_LEVEL_ERROR; + +/* Our client */ +struct passwd *pw = NULL; +char *client_addr = NULL; /* input and output queue */ Buffer iqueue; @@ -45,6 +70,9 @@ Buffer oqueue; /* Version of client */ int version; +/* Disable writes */ +int readonly; + /* portable attributes, etc. */ typedef struct Stat Stat; @@ -79,6 +107,9 @@ errno_to_portable(int unixerrno) case EINVAL: ret = SSH2_FX_BAD_MESSAGE; break; + case ENOSYS: + ret = SSH2_FX_OP_UNSUPPORTED; + break; default: ret = SSH2_FX_FAILURE; break; @@ -108,6 +139,33 @@ flags_from_portable(int pflags) return flags; } +static const char * +string_from_portable(int pflags) +{ + static char ret[128]; + + *ret = '\0'; + +#define PAPPEND(str) { \ + if (*ret != '\0') \ + strlcat(ret, ",", sizeof(ret)); \ + strlcat(ret, str, sizeof(ret)); \ + } + + if (pflags & SSH2_FXF_READ) + PAPPEND("READ") + if (pflags & SSH2_FXF_WRITE) + PAPPEND("WRITE") + if (pflags & SSH2_FXF_CREAT) + PAPPEND("CREATE") + if (pflags & SSH2_FXF_TRUNC) + PAPPEND("TRUNCATE") + if (pflags & SSH2_FXF_EXCL) + PAPPEND("EXCL") + + return ret; +} + static Attrib * get_attrib(void) { @@ -122,6 +180,8 @@ struct Handle { DIR *dirp; int fd; char *name; + u_int64_t bytes_read, bytes_write; + int next_unused; }; enum { @@ -130,39 +190,46 @@ enum { HANDLE_FILE }; -Handle handles[100]; +Handle *handles = NULL; +u_int num_handles = 0; +int first_unused_handle = -1; -static void -handle_init(void) +static void handle_unused(int i) { - u_int i; - - for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) - handles[i].use = HANDLE_UNUSED; + handles[i].use = HANDLE_UNUSED; + handles[i].next_unused = first_unused_handle; + first_unused_handle = i; } static int handle_new(int use, const char *name, int fd, DIR *dirp) { - u_int i; + int i; - for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) { - if (handles[i].use == HANDLE_UNUSED) { - handles[i].use = use; - handles[i].dirp = dirp; - handles[i].fd = fd; - handles[i].name = xstrdup(name); - return i; - } + if (first_unused_handle == -1) { + if (num_handles + 1 <= num_handles) + return -1; + num_handles++; + handles = xrealloc(handles, num_handles, sizeof(Handle)); + handle_unused(num_handles - 1); } - return -1; + + i = first_unused_handle; + first_unused_handle = handles[i].next_unused; + + handles[i].use = use; + handles[i].dirp = dirp; + handles[i].fd = fd; + handles[i].name = xstrdup(name); + handles[i].bytes_read = handles[i].bytes_write = 0; + + return i; } static int handle_is_ok(int i, int type) { - return i >= 0 && (u_int)i < sizeof(handles)/sizeof(Handle) && - handles[i].use == type; + return i >= 0 && (u_int)i < num_handles && handles[i].use == type; } static int @@ -171,7 +238,7 @@ handle_to_string(int handle, char **stringp, int *hlenp) if (stringp == NULL || hlenp == NULL) return -1; *stringp = xmalloc(sizeof(int32_t)); - PUT_32BIT(*stringp, handle); + put_u32(*stringp, handle); *hlenp = sizeof(int32_t); return 0; } @@ -183,7 +250,7 @@ handle_from_string(const char *handle, u_int hlen) if (hlen != sizeof(int32_t)) return -1; - val = GET_32BIT(handle); + val = get_u32(handle); if (handle_is_ok(val, HANDLE_FILE) || handle_is_ok(val, HANDLE_DIR)) return val; @@ -215,6 +282,36 @@ handle_to_fd(int handle) return -1; } +static void +handle_update_read(int handle, ssize_t bytes) +{ + if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) + handles[handle].bytes_read += bytes; +} + +static void +handle_update_write(int handle, ssize_t bytes) +{ + if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) + handles[handle].bytes_write += bytes; +} + +static u_int64_t +handle_bytes_read(int handle) +{ + if (handle_is_ok(handle, HANDLE_FILE)) + return (handles[handle].bytes_read); + return 0; +} + +static u_int64_t +handle_bytes_write(int handle) +{ + if (handle_is_ok(handle, HANDLE_FILE)) + return (handles[handle].bytes_write); + return 0; +} + static int handle_close(int handle) { @@ -222,18 +319,44 @@ handle_close(int handle) if (handle_is_ok(handle, HANDLE_FILE)) { ret = close(handles[handle].fd); - handles[handle].use = HANDLE_UNUSED; xfree(handles[handle].name); + handle_unused(handle); } else if (handle_is_ok(handle, HANDLE_DIR)) { ret = closedir(handles[handle].dirp); - handles[handle].use = HANDLE_UNUSED; xfree(handles[handle].name); + handle_unused(handle); } else { errno = ENOENT; } return ret; } +static void +handle_log_close(int handle, char *emsg) +{ + if (handle_is_ok(handle, HANDLE_FILE)) { + logit("%s%sclose \"%s\" bytes read %llu written %llu", + emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", + handle_to_name(handle), + (unsigned long long)handle_bytes_read(handle), + (unsigned long long)handle_bytes_write(handle)); + } else { + logit("%s%sclosedir \"%s\"", + emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", + handle_to_name(handle)); + } +} + +static void +handle_log_exit(void) +{ + u_int i; + + for (i = 0; i < num_handles; i++) + if (handles[i].use != HANDLE_UNUSED) + handle_log_close(i, "forced"); +} + static int get_handle(void) { @@ -260,10 +383,9 @@ send_msg(Buffer *m) buffer_consume(m, mlen); } -static void -send_status(u_int32_t id, u_int32_t status) +static const char * +status_to_message(u_int32_t status) { - Buffer msg; const char *status_messages[] = { "Success", /* SSH_FX_OK */ "End of file", /* SSH_FX_EOF */ @@ -276,15 +398,24 @@ send_status(u_int32_t id, u_int32_t status) "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ "Unknown error" /* Others */ }; + return (status_messages[MIN(status,SSH2_FX_MAX)]); +} - TRACE("sent status id %u error %u", id, status); +static void +send_status(u_int32_t id, u_int32_t status) +{ + Buffer msg; + + debug3("request %u: sent status %u", id, status); + if (log_level > SYSLOG_LEVEL_VERBOSE || + (status != SSH2_FX_OK && status != SSH2_FX_EOF)) + logit("sent status %s", status_to_message(status)); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_STATUS); buffer_put_int(&msg, id); buffer_put_int(&msg, status); if (version >= 3) { - buffer_put_cstring(&msg, - status_messages[MIN(status,SSH2_FX_MAX)]); + buffer_put_cstring(&msg, status_to_message(status)); buffer_put_cstring(&msg, ""); } send_msg(&msg); @@ -306,7 +437,7 @@ send_data_or_handle(char type, u_int32_t id, const char *data, int dlen) static void send_data(u_int32_t id, const char *data, int dlen) { - TRACE("sent data id %u len %d", id, dlen); + debug("request %u: sent data len %d", id, dlen); send_data_or_handle(SSH2_FXP_DATA, id, data, dlen); } @@ -317,7 +448,7 @@ send_handle(u_int32_t id, int handle) int hlen; handle_to_string(handle, &string, &hlen); - TRACE("sent handle id %u handle %d", id, handle); + debug("request %u: sent handle handle %d", id, handle); send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); xfree(string); } @@ -332,7 +463,7 @@ send_names(u_int32_t id, int count, const Stat *stats) buffer_put_char(&msg, SSH2_FXP_NAME); buffer_put_int(&msg, id); buffer_put_int(&msg, count); - TRACE("sent names id %u count %d", id, count); + debug("request %u: sent names count %d", id, count); for (i = 0; i < count; i++) { buffer_put_cstring(&msg, stats[i].name); buffer_put_cstring(&msg, stats[i].long_name); @@ -347,7 +478,7 @@ send_attrib(u_int32_t id, const Attrib *a) { Buffer msg; - TRACE("sent attrib id %u have 0x%x", id, a->flags); + debug("request %u: sent attrib have 0x%x", id, a->flags); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_ATTRS); buffer_put_int(&msg, id); @@ -356,6 +487,33 @@ send_attrib(u_int32_t id, const Attrib *a) buffer_free(&msg); } +static void +send_statvfs(u_int32_t id, struct statvfs *st) +{ + Buffer msg; + u_int64_t flag; + + flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; + + buffer_init(&msg); + buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY); + buffer_put_int(&msg, id); + buffer_put_int64(&msg, st->f_bsize); + buffer_put_int64(&msg, st->f_frsize); + buffer_put_int64(&msg, st->f_blocks); + buffer_put_int64(&msg, st->f_bfree); + buffer_put_int64(&msg, st->f_bavail); + buffer_put_int64(&msg, st->f_files); + buffer_put_int64(&msg, st->f_ffree); + buffer_put_int64(&msg, st->f_favail); + buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid)); + buffer_put_int64(&msg, flag); + buffer_put_int64(&msg, st->f_namemax); + send_msg(&msg); + buffer_free(&msg); +} + /* parse incoming */ static void @@ -364,10 +522,19 @@ process_init(void) Buffer msg; version = get_int(); - TRACE("client version %d", version); + verbose("received client version %d", version); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_VERSION); buffer_put_int(&msg, SSH2_FILEXFER_VERSION); + /* POSIX rename extension */ + buffer_put_cstring(&msg, "posix-rename@openssh.com"); + buffer_put_cstring(&msg, "1"); /* version */ + /* statvfs extension */ + buffer_put_cstring(&msg, "statvfs@openssh.com"); + buffer_put_cstring(&msg, "2"); /* version */ + /* fstatvfs extension */ + buffer_put_cstring(&msg, "fstatvfs@openssh.com"); + buffer_put_cstring(&msg, "2"); /* version */ send_msg(&msg); buffer_free(&msg); } @@ -383,20 +550,27 @@ process_open(void) id = get_int(); name = get_string(NULL); pflags = get_int(); /* portable flags */ + debug3("request %u: open flags %d", id, pflags); a = get_attrib(); flags = flags_from_portable(pflags); mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666; - TRACE("open id %u name %s flags %d mode 0%o", id, name, pflags, mode); - fd = open(name, flags, mode); - if (fd < 0) { - status = errno_to_portable(errno); - } else { - handle = handle_new(HANDLE_FILE, name, fd, NULL); - if (handle < 0) { - close(fd); + logit("open \"%s\" flags %s mode 0%o", + name, string_from_portable(pflags), mode); + if (readonly && + ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR)) + status = SSH2_FX_PERMISSION_DENIED; + else { + fd = open(name, flags, mode); + if (fd < 0) { + status = errno_to_portable(errno); } else { - send_handle(id, handle); - status = SSH2_FX_OK; + handle = handle_new(HANDLE_FILE, name, fd, NULL); + if (handle < 0) { + close(fd); + } else { + send_handle(id, handle); + status = SSH2_FX_OK; + } } } if (status != SSH2_FX_OK) @@ -412,7 +586,8 @@ process_close(void) id = get_int(); handle = get_handle(); - TRACE("close id %u handle %d", id, handle); + debug3("request %u: close handle %u", id, handle); + handle_log_close(handle, NULL); ret = handle_close(handle); status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; send_status(id, status); @@ -431,11 +606,11 @@ process_read(void) off = get_int64(); len = get_int(); - TRACE("read id %u handle %d off %llu len %d", id, handle, - (unsigned long long)off, len); + debug("request %u: read \"%s\" (handle %d) off %llu len %d", + id, handle_to_name(handle), handle, (unsigned long long)off, len); if (len > sizeof buf) { len = sizeof buf; - logit("read change len %d", len); + debug2("read change len %d", len); } fd = handle_to_fd(handle); if (fd >= 0) { @@ -451,6 +626,7 @@ process_read(void) } else { send_data(id, buf, ret); status = SSH2_FX_OK; + handle_update_read(handle, ret); } } } @@ -464,7 +640,7 @@ process_write(void) u_int32_t id; u_int64_t off; u_int len; - int handle, fd, ret, status = SSH2_FX_FAILURE; + int handle, fd, ret, status; char *data; id = get_int(); @@ -472,10 +648,15 @@ process_write(void) off = get_int64(); data = get_string(&len); - TRACE("write id %u handle %d off %llu len %d", id, handle, - (unsigned long long)off, len); + debug("request %u: write \"%s\" (handle %d) off %llu len %d", + id, handle_to_name(handle), handle, (unsigned long long)off, len); fd = handle_to_fd(handle); - if (fd >= 0) { + + if (fd < 0) + status = SSH2_FX_FAILURE; + else if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { if (lseek(fd, off, SEEK_SET) < 0) { status = errno_to_portable(errno); error("process_write: seek failed"); @@ -487,8 +668,10 @@ process_write(void) status = errno_to_portable(errno); } else if ((size_t)ret == len) { status = SSH2_FX_OK; + handle_update_write(handle, ret); } else { - logit("nothing at all written"); + debug2("nothing at all written"); + status = SSH2_FX_FAILURE; } } } @@ -507,7 +690,8 @@ process_do_stat(int do_lstat) id = get_int(); name = get_string(NULL); - TRACE("%sstat id %u name %s", do_lstat ? "l" : "", id, name); + debug3("request %u: %sstat", id, do_lstat ? "l" : ""); + verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name); ret = do_lstat ? lstat(name, &st) : stat(name, &st); if (ret < 0) { status = errno_to_portable(errno); @@ -543,9 +727,10 @@ process_fstat(void) id = get_int(); handle = get_handle(); - TRACE("fstat id %u handle %d", id, handle); + debug("request %u: fstat \"%s\" (handle %u)", + id, handle_to_name(handle), handle); fd = handle_to_fd(handle); - if (fd >= 0) { + if (fd >= 0) { ret = fstat(fd, &st); if (ret < 0) { status = errno_to_portable(errno); @@ -582,23 +767,38 @@ process_setstat(void) id = get_int(); name = get_string(NULL); a = get_attrib(); - TRACE("setstat id %u name %s", id, name); + debug("request %u: setstat name \"%s\"", id, name); + if (readonly) { + status = SSH2_FX_PERMISSION_DENIED; + a->flags = 0; + } if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { + logit("set \"%s\" size %llu", + name, (unsigned long long)a->size); ret = truncate(name, a->size); if (ret == -1) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { - ret = chmod(name, a->perm & 0777); + logit("set \"%s\" mode %04o", name, a->perm); + ret = chmod(name, a->perm & 07777); if (ret == -1) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + char buf[64]; + time_t t = a->mtime; + + strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", + localtime(&t)); + logit("set \"%s\" modtime %s", name, buf); ret = utimes(name, attrib_to_tv(a)); if (ret == -1) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + logit("set \"%s\" owner %lu group %lu", name, + (u_long)a->uid, (u_long)a->gid); ret = chown(name, a->uid, a->gid); if (ret == -1) status = errno_to_portable(errno); @@ -614,32 +814,43 @@ process_fsetstat(void) u_int32_t id; int handle, fd, ret; int status = SSH2_FX_OK; - char *name; id = get_int(); handle = get_handle(); a = get_attrib(); - TRACE("fsetstat id %u handle %d", id, handle); + debug("request %u: fsetstat handle %d", id, handle); fd = handle_to_fd(handle); - name = handle_to_name(handle); - if (fd < 0 || name == NULL) { + if (fd < 0) status = SSH2_FX_FAILURE; - } else { + else if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + char *name = handle_to_name(handle); + if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { + logit("set \"%s\" size %llu", + name, (unsigned long long)a->size); ret = ftruncate(fd, a->size); if (ret == -1) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { + logit("set \"%s\" mode %04o", name, a->perm); #ifdef HAVE_FCHMOD - ret = fchmod(fd, a->perm & 0777); + ret = fchmod(fd, a->perm & 07777); #else - ret = chmod(name, a->perm & 0777); + ret = chmod(name, a->perm & 07777); #endif if (ret == -1) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + char buf[64]; + time_t t = a->mtime; + + strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", + localtime(&t)); + logit("set \"%s\" modtime %s", name, buf); #ifdef HAVE_FUTIMES ret = futimes(fd, attrib_to_tv(a)); #else @@ -649,6 +860,8 @@ process_fsetstat(void) status = errno_to_portable(errno); } if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + logit("set \"%s\" owner %lu group %lu", name, + (u_long)a->uid, (u_long)a->gid); #ifdef HAVE_FCHOWN ret = fchown(fd, a->uid, a->gid); #else @@ -671,7 +884,8 @@ process_opendir(void) id = get_int(); path = get_string(NULL); - TRACE("opendir id %u path %s", id, path); + debug3("request %u: opendir", id); + logit("opendir \"%s\"", path); dirp = opendir(path); if (dirp == NULL) { status = errno_to_portable(errno); @@ -701,14 +915,15 @@ process_readdir(void) id = get_int(); handle = get_handle(); - TRACE("readdir id %u handle %d", id, handle); + debug("request %u: readdir \"%s\" (handle %d)", id, + handle_to_name(handle), handle); dirp = handle_to_dir(handle); path = handle_to_name(handle); if (dirp == NULL || path == NULL) { send_status(id, SSH2_FX_FAILURE); } else { struct stat st; - char pathname[1024]; + char pathname[MAXPATHLEN]; Stat *stats; int nstats = 10, count = 0, i; @@ -716,7 +931,7 @@ process_readdir(void) while ((dp = readdir(dirp)) != NULL) { if (count >= nstats) { nstats *= 2; - stats = xrealloc(stats, nstats * sizeof(Stat)); + stats = xrealloc(stats, nstats, sizeof(Stat)); } /* XXX OVERFLOW ? */ snprintf(pathname, sizeof pathname, "%s%s%s", path, @@ -725,7 +940,7 @@ process_readdir(void) continue; stat_to_attrib(&st, &(stats[count].attrib)); stats[count].name = xstrdup(dp->d_name); - stats[count].long_name = ls_file(dp->d_name, &st, 0); + stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); count++; /* send up to 100 entries in one message */ /* XXX check packet size instead */ @@ -755,9 +970,14 @@ process_remove(void) id = get_int(); name = get_string(NULL); - TRACE("remove id %u name %s", id, name); - ret = unlink(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + debug3("request %u: remove", id); + logit("remove name \"%s\"", name); + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + ret = unlink(name); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } send_status(id, status); xfree(name); } @@ -774,10 +994,15 @@ process_mkdir(void) name = get_string(NULL); a = get_attrib(); mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? - a->perm & 0777 : 0777; - TRACE("mkdir id %u name %s mode 0%o", id, name, mode); - ret = mkdir(name, mode); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + a->perm & 07777 : 0777; + debug3("request %u: mkdir", id); + logit("mkdir name \"%s\" mode 0%o", name, mode); + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + ret = mkdir(name, mode); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } send_status(id, status); xfree(name); } @@ -791,9 +1016,14 @@ process_rmdir(void) id = get_int(); name = get_string(NULL); - TRACE("rmdir id %u name %s", id, name); - ret = rmdir(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + debug3("request %u: rmdir", id); + logit("rmdir name \"%s\"", name); + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + ret = rmdir(name); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } send_status(id, status); xfree(name); } @@ -811,7 +1041,8 @@ process_realpath(void) xfree(path); path = xstrdup("."); } - TRACE("realpath id %u path %s", id, path); + debug3("request %u: realpath", id); + verbose("realpath \"%s\"", path); if (realpath(path, resolvedname) == NULL) { send_status(id, errno_to_portable(errno)); } else { @@ -834,14 +1065,20 @@ process_rename(void) id = get_int(); oldpath = get_string(NULL); newpath = get_string(NULL); - TRACE("rename id %u old %s new %s", id, oldpath, newpath); + debug3("request %u: rename", id); + logit("rename old \"%s\" new \"%s\"", oldpath, newpath); status = SSH2_FX_FAILURE; - if (lstat(oldpath, &sb) == -1) + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else if (lstat(oldpath, &sb) == -1) status = errno_to_portable(errno); else if (S_ISREG(sb.st_mode)) { /* Race-free rename of regular files */ if (link(oldpath, newpath) == -1) { - if (errno == EOPNOTSUPP + if (errno == EOPNOTSUPP || errno == ENOSYS +#ifdef EXDEV + || errno == EXDEV +#endif #ifdef LINK_OPNOTSUPP_ERRNO || errno == LINK_OPNOTSUPP_ERRNO #endif @@ -889,7 +1126,8 @@ process_readlink(void) id = get_int(); path = get_string(NULL); - TRACE("readlink id %u path %s", id, path); + debug3("request %u: readlink", id); + verbose("readlink \"%s\"", path); if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) send_status(id, errno_to_portable(errno)); else { @@ -913,15 +1151,77 @@ process_symlink(void) id = get_int(); oldpath = get_string(NULL); newpath = get_string(NULL); - TRACE("symlink id %u old %s new %s", id, oldpath, newpath); + debug3("request %u: symlink", id); + logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); /* this will fail if 'newpath' exists */ - ret = symlink(oldpath, newpath); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + ret = symlink(oldpath, newpath); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } send_status(id, status); xfree(oldpath); xfree(newpath); } +static void +process_extended_posix_rename(u_int32_t id) +{ + char *oldpath, *newpath; + int ret, status; + + oldpath = get_string(NULL); + newpath = get_string(NULL); + debug3("request %u: posix-rename", id); + logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath); + if (readonly) + status = SSH2_FX_PERMISSION_DENIED; + else { + ret = rename(oldpath, newpath); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } + send_status(id, status); + xfree(oldpath); + xfree(newpath); +} + +static void +process_extended_statvfs(u_int32_t id) +{ + char *path; + struct statvfs st; + + path = get_string(NULL); + debug3("request %u: statfs", id); + logit("statfs \"%s\"", path); + + if (statvfs(path, &st) != 0) + send_status(id, errno_to_portable(errno)); + else + send_statvfs(id, &st); + xfree(path); +} + +static void +process_extended_fstatvfs(u_int32_t id) +{ + int handle, fd; + struct statvfs st; + + handle = get_handle(); + debug("request %u: fstatvfs \"%s\" (handle %u)", + id, handle_to_name(handle), handle); + if ((fd = handle_to_fd(handle)) < 0) { + send_status(id, SSH2_FX_FAILURE); + return; + } + if (fstatvfs(fd, &st) != 0) + send_status(id, errno_to_portable(errno)); + else + send_statvfs(id, &st); +} + static void process_extended(void) { @@ -930,7 +1230,14 @@ process_extended(void) id = get_int(); request = get_string(NULL); - send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ + if (strcmp(request, "posix-rename@openssh.com") == 0) + process_extended_posix_rename(id); + else if (strcmp(request, "statvfs@openssh.com") == 0) + process_extended_statvfs(id); + else if (strcmp(request, "fstatvfs@openssh.com") == 0) + process_extended_fstatvfs(id); + else + send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ xfree(request); } @@ -949,10 +1256,11 @@ process(void) if (buf_len < 5) return; /* Incomplete message. */ cp = buffer_ptr(&iqueue); - msg_len = GET_32BIT(cp); + msg_len = get_u32(cp); if (msg_len > SFTP_MAX_MSG_LENGTH) { - error("bad message "); - exit(11); + error("bad message from %s local user %s", + client_addr, pw->pw_name); + sftp_server_cleanup_exit(11); } if (buf_len < msg_len + 4) return; @@ -1025,36 +1333,117 @@ process(void) break; } /* discard the remaining bytes from the current packet */ - if (buf_len < buffer_len(&iqueue)) - fatal("iqueue grows"); + if (buf_len < buffer_len(&iqueue)) { + error("iqueue grew unexpectedly"); + sftp_server_cleanup_exit(255); + } consumed = buf_len - buffer_len(&iqueue); - if (msg_len < consumed) - fatal("msg_len %d < consumed %d", msg_len, consumed); + if (msg_len < consumed) { + error("msg_len %d < consumed %d", msg_len, consumed); + sftp_server_cleanup_exit(255); + } if (msg_len > consumed) buffer_consume(&iqueue, msg_len - consumed); } +/* Cleanup handler that logs active handles upon normal exit */ +void +sftp_server_cleanup_exit(int i) +{ + if (pw != NULL && client_addr != NULL) { + handle_log_exit(); + logit("session closed for local user %s from [%s]", + pw->pw_name, client_addr); + } + _exit(i); +} + +static void +sftp_server_usage(void) +{ + extern char *__progname; + + fprintf(stderr, + "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n", + __progname); + exit(1); +} + int -main(int ac, char **av) +sftp_server_main(int argc, char **argv, struct passwd *user_pw) { fd_set *rset, *wset; - int in, out, max; + int in, out, max, ch, skipargs = 0, log_stderr = 0; ssize_t len, olen, set_size; + SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; + char *cp, buf[4*4096]; + const char *errmsg; + mode_t mask; + + extern char *optarg; + extern char *__progname; + + __progname = ssh_get_progname(argv[0]); + log_init(__progname, log_level, log_facility, log_stderr); + + while (!skipargs && (ch = getopt(argc, argv, "f:l:u:cehR")) != -1) { + switch (ch) { + case 'R': + readonly = 1; + break; + case 'c': + /* + * Ignore all arguments if we are invoked as a + * shell using "sftp-server -c command" + */ + skipargs = 1; + break; + case 'e': + log_stderr = 1; + break; + case 'l': + log_level = log_level_number(optarg); + if (log_level == SYSLOG_LEVEL_NOT_SET) + error("Invalid log level \"%s\"", optarg); + break; + case 'f': + log_facility = log_facility_number(optarg); + if (log_facility == SYSLOG_FACILITY_NOT_SET) + error("Invalid log facility \"%s\"", optarg); + break; + case 'u': + mask = (mode_t)strtonum(optarg, 0, 0777, &errmsg); + if (errmsg != NULL) + fatal("Invalid umask \"%s\": %s", + optarg, errmsg); + (void)umask(mask); + break; + case 'h': + default: + sftp_server_usage(); + } + } - /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ - sanitise_stdfd(); + log_init(__progname, log_level, log_facility, log_stderr); - /* XXX should use getopt */ + if ((cp = getenv("SSH_CONNECTION")) != NULL) { + client_addr = xstrdup(cp); + if ((cp = strchr(client_addr, ' ')) == NULL) { + error("Malformed SSH_CONNECTION variable: \"%s\"", + getenv("SSH_CONNECTION")); + sftp_server_cleanup_exit(255); + } + *cp = '\0'; + } else + client_addr = xstrdup("UNKNOWN"); - __progname = ssh_get_progname(av[0]); - handle_init(); + pw = pwcopy(user_pw); -#ifdef DEBUG_SFTP_SERVER - log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0); -#endif + logit("session opened for local user %s from [%s]", + pw->pw_name, client_addr); - in = dup(STDIN_FILENO); - out = dup(STDOUT_FILENO); + in = STDIN_FILENO; + out = STDOUT_FILENO; #ifdef HAVE_CYGWIN setmode(in, O_BINARY); @@ -1078,7 +1467,15 @@ main(int ac, char **av) memset(rset, 0, set_size); memset(wset, 0, set_size); - FD_SET(in, rset); + /* + * Ensure that we can read a full buffer and handle + * the worst-case length packet it can generate, + * otherwise apply backpressure by stopping reads. + */ + if (buffer_check_alloc(&iqueue, sizeof(buf)) && + buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) + FD_SET(in, rset); + olen = buffer_len(&oqueue); if (olen > 0) FD_SET(out, wset); @@ -1086,19 +1483,19 @@ main(int ac, char **av) if (select(max+1, rset, wset, NULL, NULL) < 0) { if (errno == EINTR) continue; - exit(2); + error("select: %s", strerror(errno)); + sftp_server_cleanup_exit(2); } /* copy stdin to iqueue */ if (FD_ISSET(in, rset)) { - char buf[4*4096]; len = read(in, buf, sizeof buf); if (len == 0) { debug("read eof"); - exit(0); + sftp_server_cleanup_exit(0); } else if (len < 0) { - error("read error"); - exit(1); + error("read: %s", strerror(errno)); + sftp_server_cleanup_exit(1); } else { buffer_append(&iqueue, buf, len); } @@ -1107,13 +1504,19 @@ main(int ac, char **av) if (FD_ISSET(out, wset)) { len = write(out, buffer_ptr(&oqueue), olen); if (len < 0) { - error("write error"); - exit(1); + error("write: %s", strerror(errno)); + sftp_server_cleanup_exit(1); } else { buffer_consume(&oqueue, len); } } - /* process requests from client */ - process(); + + /* + * Process requests from client if we can fit the results + * into the output buffer, otherwise stop processing input + * and let the output queue drain. + */ + if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) + process(); } }