+/* $OpenBSD: sftp-server.c,v 1.83 2008/06/09 13:02:39 dtucker Exp $ */
/*
* Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
*
* 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"
-RCSID("$OpenBSD: sftp-server.c,v 1.52 2006/02/20 17:19:54 stevesk Exp $");
#include <sys/types.h>
+#include <sys/param.h>
#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif
+#ifdef HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+#endif
#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#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"
#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;
case EINVAL:
ret = SSH2_FX_BAD_MESSAGE;
break;
+ case ENOSYS:
+ ret = SSH2_FX_OP_UNSUPPORTED;
+ break;
default:
ret = SSH2_FX_FAILURE;
break;
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)
{
DIR *dirp;
int fd;
char *name;
+ u_int64_t bytes_read, bytes_write;
+ int next_unused;
};
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
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;
}
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;
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)
{
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)
{
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 */
"Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */
"Unknown error" /* Others */
};
+ return (status_messages[MIN(status,SSH2_FX_MAX)]);
+}
+
+static void
+send_status(u_int32_t id, u_int32_t status)
+{
+ Buffer msg;
- TRACE("sent status id %u error %u", id, status);
+ 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);
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);
}
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);
}
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);
{
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);
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
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);
}
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);
+ logit("open \"%s\" flags %s mode 0%o",
+ name, string_from_portable(pflags), mode);
fd = open(name, flags, mode);
if (fd < 0) {
status = errno_to_portable(errno);
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);
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) {
} else {
send_data(id, buf, ret);
status = SSH2_FX_OK;
+ handle_update_read(handle, ret);
}
}
}
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 (lseek(fd, off, SEEK_SET) < 0) {
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");
}
}
}
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);
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);
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 (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) {
+ logit("set \"%s\" mode %04o", name, a->perm);
ret = chmod(name, a->perm & 0777);
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);
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 {
+ 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);
#else
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
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
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);
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;
- stats = xmalloc(nstats * sizeof(Stat));
+ stats = xcalloc(nstats, sizeof(Stat));
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,
id = get_int();
name = get_string(NULL);
- TRACE("remove id %u name %s", id, name);
+ debug3("request %u: remove", id);
+ logit("remove name \"%s\"", name);
ret = unlink(name);
status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
send_status(id, status);
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);
+ debug3("request %u: mkdir", id);
+ logit("mkdir name \"%s\" mode 0%o", name, mode);
ret = mkdir(name, mode);
status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
send_status(id, status);
id = get_int();
name = get_string(NULL);
- TRACE("rmdir id %u name %s", id, name);
+ debug3("request %u: rmdir", id);
+ logit("rmdir name \"%s\"", name);
ret = rmdir(name);
status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
send_status(id, status);
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 {
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)
status = errno_to_portable(errno);
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 {
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;
xfree(newpath);
}
+static void
+process_extended_posix_rename(u_int32_t id)
+{
+ char *oldpath, *newpath;
+
+ oldpath = get_string(NULL);
+ newpath = get_string(NULL);
+ debug3("request %u: posix-rename", id);
+ logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
+ if (rename(oldpath, newpath) == -1)
+ send_status(id, errno_to_portable(errno));
+ else
+ send_status(id, SSH2_FX_OK);
+ 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)
{
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);
}
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;
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 [-he] [-l log_level] [-f log_facility]\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];
+
+ 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, "C:f:l:che")) != -1) {
+ switch (ch) {
+ 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 '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);
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);
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);
}
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();
}
}