summaryrefslogtreecommitdiff
path: root/sftp-server.c
diff options
context:
space:
mode:
Diffstat (limited to 'sftp-server.c')
-rw-r--r--sftp-server.c142
1 files changed, 103 insertions, 39 deletions
diff --git a/sftp-server.c b/sftp-server.c
index 4f735cd9..359204fa 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-server.c,v 1.105 2015/01/20 23:14:00 deraadt Exp $ */
+/* $OpenBSD: sftp-server.c,v 1.117 2019/07/05 04:55:40 djm Exp $ */
/*
* Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
*
@@ -17,7 +17,6 @@
#include "includes.h"
-#include <sys/param.h> /* MIN */
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_TIME_H
@@ -29,9 +28,6 @@
#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif
-#ifdef HAVE_SYS_PRCTL_H
-#include <sys/prctl.h>
-#endif
#include <dirent.h>
#include <errno.h>
@@ -40,7 +36,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <pwd.h>
#include <time.h>
#include <unistd.h>
#include <stdarg.h>
@@ -56,6 +51,8 @@
#include "sftp.h"
#include "sftp-common.h"
+char *sftp_realpath(const char *, char *); /* sftp-realpath.c */
+
/* Our verbosity */
static LogLevel log_level = SYSLOG_LEVEL_ERROR;
@@ -112,6 +109,7 @@ static void process_extended_statvfs(u_int32_t id);
static void process_extended_fstatvfs(u_int32_t id);
static void process_extended_hardlink(u_int32_t id);
static void process_extended_fsync(u_int32_t id);
+static void process_extended_lsetstat(u_int32_t id);
static void process_extended(u_int32_t id);
struct sftp_handler {
@@ -122,7 +120,7 @@ struct sftp_handler {
int does_write; /* if nonzero, banned for readonly mode */
};
-struct sftp_handler handlers[] = {
+static const struct sftp_handler handlers[] = {
/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
@@ -146,18 +144,19 @@ struct sftp_handler handlers[] = {
};
/* SSH2_FXP_EXTENDED submessages */
-struct sftp_handler extended_handlers[] = {
+static const struct sftp_handler extended_handlers[] = {
{ "posix-rename", "posix-rename@openssh.com", 0,
process_extended_posix_rename, 1 },
{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
+ { "lsetstat", "lsetstat@openssh.com", 0, process_extended_lsetstat, 1 },
{ NULL, NULL, 0, NULL, 0 }
};
static int
-request_permitted(struct sftp_handler *h)
+request_permitted(const struct sftp_handler *h)
{
char *result;
@@ -290,9 +289,9 @@ enum {
HANDLE_FILE
};
-Handle *handles = NULL;
-u_int num_handles = 0;
-int first_unused_handle = -1;
+static Handle *handles = NULL;
+static u_int num_handles = 0;
+static int first_unused_handle = -1;
static void handle_unused(int i)
{
@@ -310,7 +309,7 @@ handle_new(int use, const char *name, int fd, int flags, DIR *dirp)
if (num_handles + 1 <= num_handles)
return -1;
num_handles++;
- handles = xrealloc(handles, num_handles, sizeof(Handle));
+ handles = xreallocarray(handles, num_handles, sizeof(Handle));
handle_unused(num_handles - 1);
}
@@ -509,7 +508,7 @@ status_to_message(u_int32_t status)
"Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */
"Unknown error" /* Others */
};
- return (status_messages[MIN(status,SSH2_FX_MAX)]);
+ return (status_messages[MINIMUM(status,SSH2_FX_MAX)]);
}
static void
@@ -671,6 +670,8 @@ process_init(void)
(r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
/* fsync extension */
(r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
+ (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
+ (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
(r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
fatal("%s: buffer error: %s", __func__, ssh_err(r));
send_msg(msg);
@@ -696,13 +697,13 @@ process_open(u_int32_t id)
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)) {
+ ((flags & O_ACCMODE) != O_RDONLY ||
+ (flags & (O_CREAT|O_TRUNC)) != 0)) {
verbose("Refusing open request in read-only mode");
status = SSH2_FX_PERMISSION_DENIED;
} else {
fd = open(name, flags, mode);
- if (fd < 0) {
+ if (fd == -1) {
status = errno_to_portable(errno);
} else {
handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
@@ -755,12 +756,12 @@ process_read(u_int32_t id)
}
fd = handle_to_fd(handle);
if (fd >= 0) {
- if (lseek(fd, off, SEEK_SET) < 0) {
+ if (lseek(fd, off, SEEK_SET) == -1) {
error("process_read: seek failed");
status = errno_to_portable(errno);
} else {
ret = read(fd, buf, len);
- if (ret < 0) {
+ if (ret == -1) {
status = errno_to_portable(errno);
} else if (ret == 0) {
status = SSH2_FX_EOF;
@@ -796,13 +797,13 @@ process_write(u_int32_t id)
status = SSH2_FX_FAILURE;
else {
if (!(handle_to_flags(handle) & O_APPEND) &&
- lseek(fd, off, SEEK_SET) < 0) {
+ lseek(fd, off, SEEK_SET) == -1) {
status = errno_to_portable(errno);
error("process_write: seek failed");
} else {
/* XXX ATOMICIO ? */
ret = write(fd, data, len);
- if (ret < 0) {
+ if (ret == -1) {
error("process_write: write failed");
status = errno_to_portable(errno);
} else if ((size_t)ret == len) {
@@ -832,7 +833,7 @@ process_do_stat(u_int32_t id, int do_lstat)
debug3("request %u: %sstat", id, do_lstat ? "l" : "");
verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
r = do_lstat ? lstat(name, &st) : stat(name, &st);
- if (r < 0) {
+ if (r == -1) {
status = errno_to_portable(errno);
} else {
stat_to_attrib(&st, &a);
@@ -870,7 +871,7 @@ process_fstat(u_int32_t id)
fd = handle_to_fd(handle);
if (fd >= 0) {
r = fstat(fd, &st);
- if (r < 0) {
+ if (r == -1) {
status = errno_to_portable(errno);
} else {
stat_to_attrib(&st, &a);
@@ -894,6 +895,18 @@ attrib_to_tv(const Attrib *a)
return tv;
}
+static struct timespec *
+attrib_to_ts(const Attrib *a)
+{
+ static struct timespec ts[2];
+
+ ts[0].tv_sec = a->atime;
+ ts[0].tv_nsec = 0;
+ ts[1].tv_sec = a->mtime;
+ ts[1].tv_nsec = 0;
+ return ts;
+}
+
static void
process_setstat(u_int32_t id)
{
@@ -1063,12 +1076,12 @@ process_readdir(u_int32_t id)
while ((dp = readdir(dirp)) != NULL) {
if (count >= nstats) {
nstats *= 2;
- stats = xrealloc(stats, nstats, sizeof(Stat));
+ stats = xreallocarray(stats, nstats, sizeof(Stat));
}
/* XXX OVERFLOW ? */
snprintf(pathname, sizeof pathname, "%s%s%s", path,
strcmp(path, "/") ? "/" : "", dp->d_name);
- if (lstat(pathname, &st) < 0)
+ if (lstat(pathname, &st) == -1)
continue;
stat_to_attrib(&st, &(stats[count].attrib));
stats[count].name = xstrdup(dp->d_name);
@@ -1163,7 +1176,7 @@ process_realpath(u_int32_t id)
}
debug3("request %u: realpath", id);
verbose("realpath \"%s\"", path);
- if (realpath(path, resolvedname) == NULL) {
+ if (sftp_realpath(path, resolvedname) == NULL) {
send_status(id, errno_to_portable(errno));
} else {
Stat s;
@@ -1375,6 +1388,55 @@ process_extended_fsync(u_int32_t id)
}
static void
+process_extended_lsetstat(u_int32_t id)
+{
+ Attrib a;
+ char *name;
+ int r, status = SSH2_FX_OK;
+
+ if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
+ (r = decode_attrib(iqueue, &a)) != 0)
+ fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+ debug("request %u: lsetstat name \"%s\"", id, name);
+ if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
+ /* nonsensical for links */
+ status = SSH2_FX_BAD_MESSAGE;
+ goto out;
+ }
+ if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
+ logit("set \"%s\" mode %04o", name, a.perm);
+ r = fchmodat(AT_FDCWD, name,
+ a.perm & 07777, AT_SYMLINK_NOFOLLOW);
+ if (r == -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);
+ r = utimensat(AT_FDCWD, name,
+ attrib_to_ts(&a), AT_SYMLINK_NOFOLLOW);
+ if (r == -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);
+ r = fchownat(AT_FDCWD, name, a.uid, a.gid,
+ AT_SYMLINK_NOFOLLOW);
+ if (r == -1)
+ status = errno_to_portable(errno);
+ }
+ out:
+ send_status(id, status);
+ free(name);
+}
+
+static void
process_extended(u_int32_t id)
{
char *request;
@@ -1508,7 +1570,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0;
ssize_t len, olen, set_size;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
- char *cp, *homedir = NULL, buf[4*4096];
+ char *cp, *homedir = NULL, uidstr[32], buf[4*4096];
long mask;
extern char *optarg;
@@ -1558,8 +1620,10 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
break;
case 'd':
cp = tilde_expand_filename(optarg, user_pw->pw_uid);
+ snprintf(uidstr, sizeof(uidstr), "%llu",
+ (unsigned long long)pw->pw_uid);
homedir = percent_expand(cp, "d", user_pw->pw_dir,
- "u", user_pw->pw_name, (char *)NULL);
+ "u", user_pw->pw_name, "U", uidstr, (char *)NULL);
free(cp);
break;
case 'p':
@@ -1588,16 +1652,16 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
log_init(__progname, log_level, log_facility, log_stderr);
-#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
/*
- * On Linux, we should try to avoid making /proc/self/{mem,maps}
+ * On platforms where we can, avoid making /proc/self/{mem,maps}
* available to the user so that sftp access doesn't automatically
* imply arbitrary code execution access that will break
* restricted configurations.
*/
- if (prctl(PR_SET_DUMPABLE, 0) != 0)
- fatal("unable to make the process undumpable");
-#endif /* defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) */
+ platform_disable_tracing(1); /* strict */
+
+ /* Drop any fine-grained privileges we don't need */
+ platform_pledge_sftp_server();
if ((cp = getenv("SSH_CONNECTION")) != NULL) {
client_addr = xstrdup(cp);
@@ -1632,9 +1696,8 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
if ((oqueue = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new failed", __func__);
- set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
- rset = (fd_set *)xmalloc(set_size);
- wset = (fd_set *)xmalloc(set_size);
+ rset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask));
+ wset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask));
if (homedir != NULL) {
if (chdir(homedir) != 0) {
@@ -1643,6 +1706,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
}
}
+ set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
for (;;) {
memset(rset, 0, set_size);
memset(wset, 0, set_size);
@@ -1664,7 +1728,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
if (olen > 0)
FD_SET(out, wset);
- if (select(max+1, rset, wset, NULL, NULL) < 0) {
+ if (select(max+1, rset, wset, NULL, NULL) == -1) {
if (errno == EINTR)
continue;
error("select: %s", strerror(errno));
@@ -1677,7 +1741,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
if (len == 0) {
debug("read eof");
sftp_server_cleanup_exit(0);
- } else if (len < 0) {
+ } else if (len == -1) {
error("read: %s", strerror(errno));
sftp_server_cleanup_exit(1);
} else if ((r = sshbuf_put(iqueue, buf, len)) != 0) {
@@ -1688,7 +1752,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
/* send oqueue to stdout */
if (FD_ISSET(out, wset)) {
len = write(out, sshbuf_ptr(oqueue), olen);
- if (len < 0) {
+ if (len == -1) {
error("write: %s", strerror(errno));
sftp_server_cleanup_exit(1);
} else if ((r = sshbuf_consume(oqueue, len)) != 0) {