From db7bf82544800c21a471a89781466bbf4ac08731 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Sat, 9 Jan 2010 22:24:33 +1100 Subject: [PATCH] - djm@cvs.openbsd.org 2010/01/09 00:20:26 [sftp-server.c sftp-server.8] add a 'read-only' mode to sftp-server(8) that disables open in write mode and all other fs-modifying protocol methods. bz#430 ok dtucker@ --- ChangeLog | 5 +++ sftp-server.8 | 12 ++++-- sftp-server.c | 103 ++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/ChangeLog b/ChangeLog index 31f205f6a..647d71def 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,11 @@ - (dtucker) [roaming_client.c] Wrap inttypes.h in an ifdef. - (dtucker) [loginrec.c] Use the SUSv3 specified name for the user name when using utmpx. Patch from Ed Schouten. + - (dtucker) OpenBSD CVS Sync + - djm@cvs.openbsd.org 2010/01/09 00:20:26 + [sftp-server.c sftp-server.8] + add a 'read-only' mode to sftp-server(8) that disables open in write mode + and all other fs-modifying protocol methods. bz#430 ok dtucker@ 20091208 - (dtucker) OpenBSD CVS Sync diff --git a/sftp-server.8 b/sftp-server.8 index ee73c345b..84036922c 100644 --- a/sftp-server.8 +++ b/sftp-server.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp-server.8,v 1.17 2009/08/31 21:01:29 djm Exp $ +.\" $OpenBSD: sftp-server.8,v 1.18 2010/01/09 00:20:26 djm Exp $ .\" .\" Copyright (c) 2000 Markus Friedl. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: August 31 2009 $ +.Dd $Mdocdate: January 9 2010 $ .Dt SFTP-SERVER 8 .Os .Sh NAME @@ -30,7 +30,7 @@ .Nd SFTP server subsystem .Sh SYNOPSIS .Nm sftp-server -.Op Fl eh +.Op Fl ehR .Op Fl f Ar log_facility .Op Fl l Ar log_level .Op Fl u Ar umask @@ -81,6 +81,12 @@ performs on behalf of the client. DEBUG and DEBUG1 are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. The default is ERROR. +.It Fl R +Places this instance of +.Nm +into a read-only mode. +Attempts to open files for writing, as well as other operations that change +the state of the filesystem will be denied. .It Fl u Ar umask Sets an explicit .Xr umask 2 diff --git a/sftp-server.c b/sftp-server.c index cf4e273f8..ab9391cfd 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-server.c,v 1.89 2010/01/04 02:25:15 djm 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. * @@ -70,6 +70,9 @@ Buffer oqueue; /* Version of client */ int version; +/* Disable writes */ +int readonly; + /* portable attributes, etc. */ typedef struct Stat Stat; @@ -553,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) @@ -632,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(); @@ -643,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"); @@ -658,6 +671,7 @@ process_write(void) handle_update_write(handle, ret); } else { debug2("nothing at all written"); + status = SSH2_FX_FAILURE; } } } @@ -754,6 +768,10 @@ 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, (unsigned long long)a->size); @@ -802,9 +820,11 @@ 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) { @@ -952,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); } @@ -973,8 +997,12 @@ process_mkdir(void) 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); } @@ -990,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); } @@ -1036,7 +1068,9 @@ 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 */ @@ -1120,8 +1154,12 @@ 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); @@ -1131,15 +1169,19 @@ 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 (rename(oldpath, newpath) == -1) - send_status(id, errno_to_portable(errno)); - else - send_status(id, SSH2_FX_OK); + 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); } @@ -1322,7 +1364,7 @@ sftp_server_usage(void) extern char *__progname; fprintf(stderr, - "usage: %s [-eh] [-f log_facility] [-l log_level] [-u umask]\n", + "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n", __progname); exit(1); } @@ -1344,8 +1386,11 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw) __progname = ssh_get_progname(argv[0]); log_init(__progname, log_level, log_facility, log_stderr); - while (!skipargs && (ch = getopt(argc, argv, "f:l:u: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