X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/3165286982a9ab28fd80125b2fb2608716fb37db..420c55a191b06378fb33977410797c0afe91fb2c:/sftp-server.c diff --git a/sftp-server.c b/sftp-server.c index c57958b0..ab9391cf 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-server.c,v 1.70 2006/08/03 03:34:42 deraadt Exp $ */ +/* $OpenBSD: sftp-server.c,v 1.90 2010/01/09 00:20:26 djm Exp $ */ /* * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. * @@ -23,6 +23,12 @@ #ifdef HAVE_SYS_TIME_H # include #endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_SYS_STATVFS_H +#include +#endif #include #include @@ -64,6 +70,9 @@ Buffer oqueue; /* Version of client */ int version; +/* Disable writes */ +int readonly; + /* portable attributes, etc. */ typedef struct Stat Stat; @@ -98,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; @@ -169,6 +181,7 @@ struct Handle { int fd; char *name; u_int64_t bytes_read, bytes_write; + int next_unused; }; enum { @@ -177,40 +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); - handles[i].bytes_read = handles[i].bytes_write = 0; - 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 @@ -300,12 +319,12 @@ 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; } @@ -319,7 +338,8 @@ handle_log_close(int handle, char *emsg) logit("%s%sclose \"%s\" bytes read %llu written %llu", emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", handle_to_name(handle), - handle_bytes_read(handle), handle_bytes_write(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 ? "" : " ", @@ -332,7 +352,7 @@ handle_log_exit(void) { u_int i; - for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) + for (i = 0; i < num_handles; i++) if (handles[i].use != HANDLE_UNUSED) handle_log_close(i, "forced"); } @@ -467,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 @@ -479,6 +526,15 @@ process_init(void) 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); } @@ -500,16 +556,21 @@ process_open(void) mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666; 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); - } else { - handle = handle_new(HANDLE_FILE, name, fd, NULL); - if (handle < 0) { - close(fd); + 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) @@ -579,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(); @@ -590,7 +651,12 @@ process_write(void) 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"); @@ -605,6 +671,7 @@ process_write(void) handle_update_write(handle, ret); } else { debug2("nothing at all written"); + status = SSH2_FX_FAILURE; } } } @@ -663,7 +730,7 @@ process_fstat(void) 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); @@ -701,15 +768,20 @@ process_setstat(void) name = get_string(NULL); a = get_attrib(); 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, a->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); + ret = chmod(name, a->perm & 07777); if (ret == -1) status = errno_to_portable(errno); } @@ -748,13 +820,16 @@ process_fsetstat(void) a = get_attrib(); debug("request %u: fsetstat handle %d", id, handle); fd = handle_to_fd(handle); - if (fd < 0) { + 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, a->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); @@ -762,9 +837,9 @@ process_fsetstat(void) 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); @@ -897,8 +972,12 @@ process_remove(void) name = get_string(NULL); debug3("request %u: remove", id); logit("remove name \"%s\"", name); - ret = unlink(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + 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); } @@ -915,11 +994,15 @@ process_mkdir(void) name = get_string(NULL); a = get_attrib(); mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? - a->perm & 0777 : 0777; + a->perm & 07777 : 0777; 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; + 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); } @@ -935,8 +1018,12 @@ process_rmdir(void) name = get_string(NULL); debug3("request %u: rmdir", id); logit("rmdir name \"%s\"", name); - ret = rmdir(name); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + 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); } @@ -981,12 +1068,17 @@ process_rename(void) 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 @@ -1062,13 +1154,74 @@ process_symlink(void) 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) { @@ -1077,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); } @@ -1100,7 +1260,7 @@ process(void) if (msg_len > SFTP_MAX_MSG_LENGTH) { error("bad message from %s local user %s", client_addr, pw->pw_name); - cleanup_exit(11); + sftp_server_cleanup_exit(11); } if (buf_len < msg_len + 4) return; @@ -1173,18 +1333,22 @@ process(void) break; } /* discard the remaining bytes from the current packet */ - if (buf_len < buffer_len(&iqueue)) - fatal("iqueue grew unexpectedly"); + 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 -cleanup_exit(int i) +sftp_server_cleanup_exit(int i) { if (pw != NULL && client_addr != NULL) { handle_log_exit(); @@ -1195,35 +1359,38 @@ cleanup_exit(int i) } static void -usage(void) +sftp_server_usage(void) { extern char *__progname; fprintf(stderr, - "usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname); + "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n", + __progname); exit(1); } int -main(int argc, char **argv) +sftp_server_main(int argc, char **argv, struct passwd *user_pw) { fd_set *rset, *wset; int in, out, max, ch, skipargs = 0, log_stderr = 0; ssize_t len, olen, set_size; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; - char *cp; + char *cp, buf[4*4096]; + const char *errmsg; + mode_t mask; extern char *optarg; extern char *__progname; - /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ - sanitise_stdfd(); - __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) { + 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 @@ -1241,12 +1408,19 @@ main(int argc, char **argv) break; case 'f': log_facility = log_facility_number(optarg); - if (log_level == SYSLOG_FACILITY_NOT_SET) + 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: - usage(); + sftp_server_usage(); } } @@ -1254,24 +1428,22 @@ main(int argc, char **argv) if ((cp = getenv("SSH_CONNECTION")) != NULL) { client_addr = xstrdup(cp); - if ((cp = strchr(client_addr, ' ')) == NULL) - fatal("Malformed SSH_CONNECTION variable: \"%s\"", + 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"); - if ((pw = getpwuid(getuid())) == NULL) - fatal("No user found for uid %lu", (u_long)getuid()); - pw = pwcopy(pw); + pw = pwcopy(user_pw); logit("session opened for local user %s from [%s]", pw->pw_name, client_addr); - handle_init(); - - in = dup(STDIN_FILENO); - out = dup(STDOUT_FILENO); + in = STDIN_FILENO; + out = STDOUT_FILENO; #ifdef HAVE_CYGWIN setmode(in, O_BINARY); @@ -1295,7 +1467,15 @@ main(int argc, char **argv) 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); @@ -1304,19 +1484,18 @@ main(int argc, char **argv) if (errno == EINTR) continue; error("select: %s", strerror(errno)); - cleanup_exit(2); + 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"); - cleanup_exit(0); + sftp_server_cleanup_exit(0); } else if (len < 0) { error("read: %s", strerror(errno)); - cleanup_exit(1); + sftp_server_cleanup_exit(1); } else { buffer_append(&iqueue, buf, len); } @@ -1326,12 +1505,18 @@ main(int argc, char **argv) len = write(out, buffer_ptr(&oqueue), olen); if (len < 0) { error("write: %s", strerror(errno)); - cleanup_exit(1); + 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(); } }