xref: /freebsd/crypto/openssh/sftp-server.c (revision acc1a9ef8333c798c210fa94be6af4d5fe2dd794)
1*acc1a9efSDag-Erling Smørgrav /* $OpenBSD: sftp-server.c,v 1.109 2016/02/15 09:47:49 dtucker Exp $ */
2b66f2d16SKris Kennaway /*
3efcad6b7SDag-Erling Smørgrav  * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
4b66f2d16SKris Kennaway  *
5efcad6b7SDag-Erling Smørgrav  * Permission to use, copy, modify, and distribute this software for any
6efcad6b7SDag-Erling Smørgrav  * purpose with or without fee is hereby granted, provided that the above
7efcad6b7SDag-Erling Smørgrav  * copyright notice and this permission notice appear in all copies.
8b66f2d16SKris Kennaway  *
9efcad6b7SDag-Erling Smørgrav  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10efcad6b7SDag-Erling Smørgrav  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11efcad6b7SDag-Erling Smørgrav  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12efcad6b7SDag-Erling Smørgrav  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13efcad6b7SDag-Erling Smørgrav  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14efcad6b7SDag-Erling Smørgrav  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15efcad6b7SDag-Erling Smørgrav  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16b66f2d16SKris Kennaway  */
17b66f2d16SKris Kennaway 
18761efaa7SDag-Erling Smørgrav #include "includes.h"
19761efaa7SDag-Erling Smørgrav 
20bc5531deSDag-Erling Smørgrav #include <sys/param.h>	/* MIN */
21761efaa7SDag-Erling Smørgrav #include <sys/types.h>
22761efaa7SDag-Erling Smørgrav #include <sys/stat.h>
23761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H
24761efaa7SDag-Erling Smørgrav # include <sys/time.h>
25761efaa7SDag-Erling Smørgrav #endif
26d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_MOUNT_H
27d4af9e69SDag-Erling Smørgrav #include <sys/mount.h>
28d4af9e69SDag-Erling Smørgrav #endif
29d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_STATVFS_H
30d4af9e69SDag-Erling Smørgrav #include <sys/statvfs.h>
31d4af9e69SDag-Erling Smørgrav #endif
32a0ee8cc6SDag-Erling Smørgrav #ifdef HAVE_SYS_PRCTL_H
33a0ee8cc6SDag-Erling Smørgrav #include <sys/prctl.h>
34a0ee8cc6SDag-Erling Smørgrav #endif
35761efaa7SDag-Erling Smørgrav 
36761efaa7SDag-Erling Smørgrav #include <dirent.h>
37761efaa7SDag-Erling Smørgrav #include <errno.h>
38761efaa7SDag-Erling Smørgrav #include <fcntl.h>
39761efaa7SDag-Erling Smørgrav #include <pwd.h>
40761efaa7SDag-Erling Smørgrav #include <stdlib.h>
41761efaa7SDag-Erling Smørgrav #include <stdio.h>
42761efaa7SDag-Erling Smørgrav #include <string.h>
43761efaa7SDag-Erling Smørgrav #include <time.h>
44761efaa7SDag-Erling Smørgrav #include <unistd.h>
45761efaa7SDag-Erling Smørgrav #include <stdarg.h>
46761efaa7SDag-Erling Smørgrav 
47b66f2d16SKris Kennaway #include "xmalloc.h"
48bc5531deSDag-Erling Smørgrav #include "sshbuf.h"
49bc5531deSDag-Erling Smørgrav #include "ssherr.h"
50761efaa7SDag-Erling Smørgrav #include "log.h"
51021d409fSDag-Erling Smørgrav #include "misc.h"
52f7167e0eSDag-Erling Smørgrav #include "match.h"
53761efaa7SDag-Erling Smørgrav #include "uidswap.h"
54b66f2d16SKris Kennaway 
551e8db6e2SBrian Feldman #include "sftp.h"
561e8db6e2SBrian Feldman #include "sftp-common.h"
57b66f2d16SKris Kennaway 
58761efaa7SDag-Erling Smørgrav /* Our verbosity */
59f7167e0eSDag-Erling Smørgrav static LogLevel log_level = SYSLOG_LEVEL_ERROR;
60761efaa7SDag-Erling Smørgrav 
61761efaa7SDag-Erling Smørgrav /* Our client */
62f7167e0eSDag-Erling Smørgrav static struct passwd *pw = NULL;
63f7167e0eSDag-Erling Smørgrav static char *client_addr = NULL;
6483d2307dSDag-Erling Smørgrav 
65b66f2d16SKris Kennaway /* input and output queue */
66bc5531deSDag-Erling Smørgrav struct sshbuf *iqueue;
67bc5531deSDag-Erling Smørgrav struct sshbuf *oqueue;
68b66f2d16SKris Kennaway 
691e8db6e2SBrian Feldman /* Version of client */
70f7167e0eSDag-Erling Smørgrav static u_int version;
71f7167e0eSDag-Erling Smørgrav 
72f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_INIT received */
73f7167e0eSDag-Erling Smørgrav static int init_done;
741e8db6e2SBrian Feldman 
75b15c8340SDag-Erling Smørgrav /* Disable writes */
76f7167e0eSDag-Erling Smørgrav static int readonly;
77f7167e0eSDag-Erling Smørgrav 
78f7167e0eSDag-Erling Smørgrav /* Requests that are allowed/denied */
79f7167e0eSDag-Erling Smørgrav static char *request_whitelist, *request_blacklist;
80b15c8340SDag-Erling Smørgrav 
81d95e11bfSDag-Erling Smørgrav /* portable attributes, etc. */
82b66f2d16SKris Kennaway typedef struct Stat Stat;
83b66f2d16SKris Kennaway 
841e8db6e2SBrian Feldman struct Stat {
85b66f2d16SKris Kennaway 	char *name;
86b66f2d16SKris Kennaway 	char *long_name;
87b66f2d16SKris Kennaway 	Attrib attrib;
88b66f2d16SKris Kennaway };
89b66f2d16SKris Kennaway 
90f7167e0eSDag-Erling Smørgrav /* Packet handlers */
91f7167e0eSDag-Erling Smørgrav static void process_open(u_int32_t id);
92f7167e0eSDag-Erling Smørgrav static void process_close(u_int32_t id);
93f7167e0eSDag-Erling Smørgrav static void process_read(u_int32_t id);
94f7167e0eSDag-Erling Smørgrav static void process_write(u_int32_t id);
95f7167e0eSDag-Erling Smørgrav static void process_stat(u_int32_t id);
96f7167e0eSDag-Erling Smørgrav static void process_lstat(u_int32_t id);
97f7167e0eSDag-Erling Smørgrav static void process_fstat(u_int32_t id);
98f7167e0eSDag-Erling Smørgrav static void process_setstat(u_int32_t id);
99f7167e0eSDag-Erling Smørgrav static void process_fsetstat(u_int32_t id);
100f7167e0eSDag-Erling Smørgrav static void process_opendir(u_int32_t id);
101f7167e0eSDag-Erling Smørgrav static void process_readdir(u_int32_t id);
102f7167e0eSDag-Erling Smørgrav static void process_remove(u_int32_t id);
103f7167e0eSDag-Erling Smørgrav static void process_mkdir(u_int32_t id);
104f7167e0eSDag-Erling Smørgrav static void process_rmdir(u_int32_t id);
105f7167e0eSDag-Erling Smørgrav static void process_realpath(u_int32_t id);
106f7167e0eSDag-Erling Smørgrav static void process_rename(u_int32_t id);
107f7167e0eSDag-Erling Smørgrav static void process_readlink(u_int32_t id);
108f7167e0eSDag-Erling Smørgrav static void process_symlink(u_int32_t id);
109f7167e0eSDag-Erling Smørgrav static void process_extended_posix_rename(u_int32_t id);
110f7167e0eSDag-Erling Smørgrav static void process_extended_statvfs(u_int32_t id);
111f7167e0eSDag-Erling Smørgrav static void process_extended_fstatvfs(u_int32_t id);
112f7167e0eSDag-Erling Smørgrav static void process_extended_hardlink(u_int32_t id);
113f7167e0eSDag-Erling Smørgrav static void process_extended_fsync(u_int32_t id);
114f7167e0eSDag-Erling Smørgrav static void process_extended(u_int32_t id);
115f7167e0eSDag-Erling Smørgrav 
116f7167e0eSDag-Erling Smørgrav struct sftp_handler {
117f7167e0eSDag-Erling Smørgrav 	const char *name;	/* user-visible name for fine-grained perms */
118f7167e0eSDag-Erling Smørgrav 	const char *ext_name;	/* extended request name */
119f7167e0eSDag-Erling Smørgrav 	u_int type;		/* packet type, for non extended packets */
120f7167e0eSDag-Erling Smørgrav 	void (*handler)(u_int32_t);
121f7167e0eSDag-Erling Smørgrav 	int does_write;		/* if nonzero, banned for readonly mode */
122f7167e0eSDag-Erling Smørgrav };
123f7167e0eSDag-Erling Smørgrav 
124f7167e0eSDag-Erling Smørgrav struct sftp_handler handlers[] = {
125f7167e0eSDag-Erling Smørgrav 	/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
126f7167e0eSDag-Erling Smørgrav 	{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
127f7167e0eSDag-Erling Smørgrav 	{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
128f7167e0eSDag-Erling Smørgrav 	{ "read", NULL, SSH2_FXP_READ, process_read, 0 },
129f7167e0eSDag-Erling Smørgrav 	{ "write", NULL, SSH2_FXP_WRITE, process_write, 1 },
130f7167e0eSDag-Erling Smørgrav 	{ "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 },
131f7167e0eSDag-Erling Smørgrav 	{ "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 },
132f7167e0eSDag-Erling Smørgrav 	{ "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 },
133f7167e0eSDag-Erling Smørgrav 	{ "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 },
134f7167e0eSDag-Erling Smørgrav 	{ "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 },
135f7167e0eSDag-Erling Smørgrav 	{ "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 },
136f7167e0eSDag-Erling Smørgrav 	{ "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 },
137f7167e0eSDag-Erling Smørgrav 	{ "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 },
138f7167e0eSDag-Erling Smørgrav 	{ "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 },
139f7167e0eSDag-Erling Smørgrav 	{ "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 },
140f7167e0eSDag-Erling Smørgrav 	{ "stat", NULL, SSH2_FXP_STAT, process_stat, 0 },
141f7167e0eSDag-Erling Smørgrav 	{ "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 },
142f7167e0eSDag-Erling Smørgrav 	{ "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 },
143f7167e0eSDag-Erling Smørgrav 	{ "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 },
144f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
145f7167e0eSDag-Erling Smørgrav };
146f7167e0eSDag-Erling Smørgrav 
147f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_EXTENDED submessages */
148f7167e0eSDag-Erling Smørgrav struct sftp_handler extended_handlers[] = {
149f7167e0eSDag-Erling Smørgrav 	{ "posix-rename", "posix-rename@openssh.com", 0,
150f7167e0eSDag-Erling Smørgrav 	   process_extended_posix_rename, 1 },
151f7167e0eSDag-Erling Smørgrav 	{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
152f7167e0eSDag-Erling Smørgrav 	{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
153f7167e0eSDag-Erling Smørgrav 	{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
154f7167e0eSDag-Erling Smørgrav 	{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
155f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
156f7167e0eSDag-Erling Smørgrav };
157f7167e0eSDag-Erling Smørgrav 
158f7167e0eSDag-Erling Smørgrav static int
159f7167e0eSDag-Erling Smørgrav request_permitted(struct sftp_handler *h)
160f7167e0eSDag-Erling Smørgrav {
161f7167e0eSDag-Erling Smørgrav 	char *result;
162f7167e0eSDag-Erling Smørgrav 
163f7167e0eSDag-Erling Smørgrav 	if (readonly && h->does_write) {
164f7167e0eSDag-Erling Smørgrav 		verbose("Refusing %s request in read-only mode", h->name);
165f7167e0eSDag-Erling Smørgrav 		return 0;
166f7167e0eSDag-Erling Smørgrav 	}
167f7167e0eSDag-Erling Smørgrav 	if (request_blacklist != NULL &&
168f7167e0eSDag-Erling Smørgrav 	    ((result = match_list(h->name, request_blacklist, NULL))) != NULL) {
169f7167e0eSDag-Erling Smørgrav 		free(result);
170f7167e0eSDag-Erling Smørgrav 		verbose("Refusing blacklisted %s request", h->name);
171f7167e0eSDag-Erling Smørgrav 		return 0;
172f7167e0eSDag-Erling Smørgrav 	}
173f7167e0eSDag-Erling Smørgrav 	if (request_whitelist != NULL &&
174f7167e0eSDag-Erling Smørgrav 	    ((result = match_list(h->name, request_whitelist, NULL))) != NULL) {
175f7167e0eSDag-Erling Smørgrav 		free(result);
176f7167e0eSDag-Erling Smørgrav 		debug2("Permitting whitelisted %s request", h->name);
177f7167e0eSDag-Erling Smørgrav 		return 1;
178f7167e0eSDag-Erling Smørgrav 	}
179f7167e0eSDag-Erling Smørgrav 	if (request_whitelist != NULL) {
180f7167e0eSDag-Erling Smørgrav 		verbose("Refusing non-whitelisted %s request", h->name);
181f7167e0eSDag-Erling Smørgrav 		return 0;
182f7167e0eSDag-Erling Smørgrav 	}
183f7167e0eSDag-Erling Smørgrav 	return 1;
184f7167e0eSDag-Erling Smørgrav }
185f7167e0eSDag-Erling Smørgrav 
186ae1f160dSDag-Erling Smørgrav static int
187b66f2d16SKris Kennaway errno_to_portable(int unixerrno)
188b66f2d16SKris Kennaway {
189b66f2d16SKris Kennaway 	int ret = 0;
1901e8db6e2SBrian Feldman 
191b66f2d16SKris Kennaway 	switch (unixerrno) {
192b66f2d16SKris Kennaway 	case 0:
1931e8db6e2SBrian Feldman 		ret = SSH2_FX_OK;
194b66f2d16SKris Kennaway 		break;
195b66f2d16SKris Kennaway 	case ENOENT:
196b66f2d16SKris Kennaway 	case ENOTDIR:
197b66f2d16SKris Kennaway 	case EBADF:
198b66f2d16SKris Kennaway 	case ELOOP:
1991e8db6e2SBrian Feldman 		ret = SSH2_FX_NO_SUCH_FILE;
200b66f2d16SKris Kennaway 		break;
201b66f2d16SKris Kennaway 	case EPERM:
202b66f2d16SKris Kennaway 	case EACCES:
203b66f2d16SKris Kennaway 	case EFAULT:
2041e8db6e2SBrian Feldman 		ret = SSH2_FX_PERMISSION_DENIED;
205b66f2d16SKris Kennaway 		break;
206b66f2d16SKris Kennaway 	case ENAMETOOLONG:
207b66f2d16SKris Kennaway 	case EINVAL:
2081e8db6e2SBrian Feldman 		ret = SSH2_FX_BAD_MESSAGE;
209b66f2d16SKris Kennaway 		break;
210d4af9e69SDag-Erling Smørgrav 	case ENOSYS:
211d4af9e69SDag-Erling Smørgrav 		ret = SSH2_FX_OP_UNSUPPORTED;
212d4af9e69SDag-Erling Smørgrav 		break;
213b66f2d16SKris Kennaway 	default:
2141e8db6e2SBrian Feldman 		ret = SSH2_FX_FAILURE;
215b66f2d16SKris Kennaway 		break;
216b66f2d16SKris Kennaway 	}
217b66f2d16SKris Kennaway 	return ret;
218b66f2d16SKris Kennaway }
219b66f2d16SKris Kennaway 
220ae1f160dSDag-Erling Smørgrav static int
221b66f2d16SKris Kennaway flags_from_portable(int pflags)
222b66f2d16SKris Kennaway {
223b66f2d16SKris Kennaway 	int flags = 0;
2241e8db6e2SBrian Feldman 
2251e8db6e2SBrian Feldman 	if ((pflags & SSH2_FXF_READ) &&
2261e8db6e2SBrian Feldman 	    (pflags & SSH2_FXF_WRITE)) {
227b66f2d16SKris Kennaway 		flags = O_RDWR;
2281e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_READ) {
229b66f2d16SKris Kennaway 		flags = O_RDONLY;
2301e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_WRITE) {
231b66f2d16SKris Kennaway 		flags = O_WRONLY;
232b66f2d16SKris Kennaway 	}
233f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
234f7167e0eSDag-Erling Smørgrav 		flags |= O_APPEND;
2351e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_CREAT)
236b66f2d16SKris Kennaway 		flags |= O_CREAT;
2371e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_TRUNC)
238b66f2d16SKris Kennaway 		flags |= O_TRUNC;
2391e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_EXCL)
240b66f2d16SKris Kennaway 		flags |= O_EXCL;
241b66f2d16SKris Kennaway 	return flags;
242b66f2d16SKris Kennaway }
243b66f2d16SKris Kennaway 
244761efaa7SDag-Erling Smørgrav static const char *
245761efaa7SDag-Erling Smørgrav string_from_portable(int pflags)
246761efaa7SDag-Erling Smørgrav {
247761efaa7SDag-Erling Smørgrav 	static char ret[128];
248761efaa7SDag-Erling Smørgrav 
249761efaa7SDag-Erling Smørgrav 	*ret = '\0';
250761efaa7SDag-Erling Smørgrav 
251761efaa7SDag-Erling Smørgrav #define PAPPEND(str)	{				\
252761efaa7SDag-Erling Smørgrav 		if (*ret != '\0')			\
253761efaa7SDag-Erling Smørgrav 			strlcat(ret, ",", sizeof(ret));	\
254761efaa7SDag-Erling Smørgrav 		strlcat(ret, str, sizeof(ret));		\
255761efaa7SDag-Erling Smørgrav 	}
256761efaa7SDag-Erling Smørgrav 
257761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_READ)
258761efaa7SDag-Erling Smørgrav 		PAPPEND("READ")
259761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_WRITE)
260761efaa7SDag-Erling Smørgrav 		PAPPEND("WRITE")
261f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
262f7167e0eSDag-Erling Smørgrav 		PAPPEND("APPEND")
263761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_CREAT)
264761efaa7SDag-Erling Smørgrav 		PAPPEND("CREATE")
265761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_TRUNC)
266761efaa7SDag-Erling Smørgrav 		PAPPEND("TRUNCATE")
267761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_EXCL)
268761efaa7SDag-Erling Smørgrav 		PAPPEND("EXCL")
269761efaa7SDag-Erling Smørgrav 
270761efaa7SDag-Erling Smørgrav 	return ret;
271761efaa7SDag-Erling Smørgrav }
272761efaa7SDag-Erling Smørgrav 
273b66f2d16SKris Kennaway /* handle handles */
274b66f2d16SKris Kennaway 
275b66f2d16SKris Kennaway typedef struct Handle Handle;
276b66f2d16SKris Kennaway struct Handle {
277b66f2d16SKris Kennaway 	int use;
278b66f2d16SKris Kennaway 	DIR *dirp;
279b66f2d16SKris Kennaway 	int fd;
280f7167e0eSDag-Erling Smørgrav 	int flags;
281b66f2d16SKris Kennaway 	char *name;
282761efaa7SDag-Erling Smørgrav 	u_int64_t bytes_read, bytes_write;
283d4af9e69SDag-Erling Smørgrav 	int next_unused;
284b66f2d16SKris Kennaway };
2851e8db6e2SBrian Feldman 
286b66f2d16SKris Kennaway enum {
287b66f2d16SKris Kennaway 	HANDLE_UNUSED,
288b66f2d16SKris Kennaway 	HANDLE_DIR,
289b66f2d16SKris Kennaway 	HANDLE_FILE
290b66f2d16SKris Kennaway };
2911e8db6e2SBrian Feldman 
292d4af9e69SDag-Erling Smørgrav Handle *handles = NULL;
293d4af9e69SDag-Erling Smørgrav u_int num_handles = 0;
294d4af9e69SDag-Erling Smørgrav int first_unused_handle = -1;
295b66f2d16SKris Kennaway 
296d4af9e69SDag-Erling Smørgrav static void handle_unused(int i)
297b66f2d16SKris Kennaway {
298b66f2d16SKris Kennaway 	handles[i].use = HANDLE_UNUSED;
299d4af9e69SDag-Erling Smørgrav 	handles[i].next_unused = first_unused_handle;
300d4af9e69SDag-Erling Smørgrav 	first_unused_handle = i;
301b66f2d16SKris Kennaway }
302b66f2d16SKris Kennaway 
303ae1f160dSDag-Erling Smørgrav static int
304f7167e0eSDag-Erling Smørgrav handle_new(int use, const char *name, int fd, int flags, DIR *dirp)
305b66f2d16SKris Kennaway {
306d4af9e69SDag-Erling Smørgrav 	int i;
3071e8db6e2SBrian Feldman 
308d4af9e69SDag-Erling Smørgrav 	if (first_unused_handle == -1) {
309d4af9e69SDag-Erling Smørgrav 		if (num_handles + 1 <= num_handles)
310d4af9e69SDag-Erling Smørgrav 			return -1;
311d4af9e69SDag-Erling Smørgrav 		num_handles++;
312557f75e5SDag-Erling Smørgrav 		handles = xreallocarray(handles, num_handles, sizeof(Handle));
313d4af9e69SDag-Erling Smørgrav 		handle_unused(num_handles - 1);
314d4af9e69SDag-Erling Smørgrav 	}
315d4af9e69SDag-Erling Smørgrav 
316d4af9e69SDag-Erling Smørgrav 	i = first_unused_handle;
317d4af9e69SDag-Erling Smørgrav 	first_unused_handle = handles[i].next_unused;
318d4af9e69SDag-Erling Smørgrav 
319b66f2d16SKris Kennaway 	handles[i].use = use;
320b66f2d16SKris Kennaway 	handles[i].dirp = dirp;
321b66f2d16SKris Kennaway 	handles[i].fd = fd;
322f7167e0eSDag-Erling Smørgrav 	handles[i].flags = flags;
323d0c8c0bcSDag-Erling Smørgrav 	handles[i].name = xstrdup(name);
324761efaa7SDag-Erling Smørgrav 	handles[i].bytes_read = handles[i].bytes_write = 0;
325d4af9e69SDag-Erling Smørgrav 
326b66f2d16SKris Kennaway 	return i;
327b66f2d16SKris Kennaway }
328b66f2d16SKris Kennaway 
329ae1f160dSDag-Erling Smørgrav static int
330b66f2d16SKris Kennaway handle_is_ok(int i, int type)
331b66f2d16SKris Kennaway {
332d4af9e69SDag-Erling Smørgrav 	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
333b66f2d16SKris Kennaway }
334b66f2d16SKris Kennaway 
335ae1f160dSDag-Erling Smørgrav static int
336bc5531deSDag-Erling Smørgrav handle_to_string(int handle, u_char **stringp, int *hlenp)
337b66f2d16SKris Kennaway {
338b66f2d16SKris Kennaway 	if (stringp == NULL || hlenp == NULL)
339b66f2d16SKris Kennaway 		return -1;
3401e8db6e2SBrian Feldman 	*stringp = xmalloc(sizeof(int32_t));
341761efaa7SDag-Erling Smørgrav 	put_u32(*stringp, handle);
3421e8db6e2SBrian Feldman 	*hlenp = sizeof(int32_t);
343b66f2d16SKris Kennaway 	return 0;
344b66f2d16SKris Kennaway }
345b66f2d16SKris Kennaway 
346ae1f160dSDag-Erling Smørgrav static int
347bc5531deSDag-Erling Smørgrav handle_from_string(const u_char *handle, u_int hlen)
348b66f2d16SKris Kennaway {
3491e8db6e2SBrian Feldman 	int val;
3501e8db6e2SBrian Feldman 
3511e8db6e2SBrian Feldman 	if (hlen != sizeof(int32_t))
352b66f2d16SKris Kennaway 		return -1;
353761efaa7SDag-Erling Smørgrav 	val = get_u32(handle);
354b66f2d16SKris Kennaway 	if (handle_is_ok(val, HANDLE_FILE) ||
355b66f2d16SKris Kennaway 	    handle_is_ok(val, HANDLE_DIR))
356b66f2d16SKris Kennaway 		return val;
357b66f2d16SKris Kennaway 	return -1;
358b66f2d16SKris Kennaway }
359b66f2d16SKris Kennaway 
360ae1f160dSDag-Erling Smørgrav static char *
361b66f2d16SKris Kennaway handle_to_name(int handle)
362b66f2d16SKris Kennaway {
363b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR)||
364b66f2d16SKris Kennaway 	    handle_is_ok(handle, HANDLE_FILE))
365b66f2d16SKris Kennaway 		return handles[handle].name;
366b66f2d16SKris Kennaway 	return NULL;
367b66f2d16SKris Kennaway }
368b66f2d16SKris Kennaway 
369ae1f160dSDag-Erling Smørgrav static DIR *
370b66f2d16SKris Kennaway handle_to_dir(int handle)
371b66f2d16SKris Kennaway {
372b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR))
373b66f2d16SKris Kennaway 		return handles[handle].dirp;
374b66f2d16SKris Kennaway 	return NULL;
375b66f2d16SKris Kennaway }
376b66f2d16SKris Kennaway 
377ae1f160dSDag-Erling Smørgrav static int
378b66f2d16SKris Kennaway handle_to_fd(int handle)
379b66f2d16SKris Kennaway {
380b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE))
381b66f2d16SKris Kennaway 		return handles[handle].fd;
382b66f2d16SKris Kennaway 	return -1;
383b66f2d16SKris Kennaway }
384b66f2d16SKris Kennaway 
385f7167e0eSDag-Erling Smørgrav static int
386f7167e0eSDag-Erling Smørgrav handle_to_flags(int handle)
387f7167e0eSDag-Erling Smørgrav {
388f7167e0eSDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
389f7167e0eSDag-Erling Smørgrav 		return handles[handle].flags;
390f7167e0eSDag-Erling Smørgrav 	return 0;
391f7167e0eSDag-Erling Smørgrav }
392f7167e0eSDag-Erling Smørgrav 
393761efaa7SDag-Erling Smørgrav static void
394761efaa7SDag-Erling Smørgrav handle_update_read(int handle, ssize_t bytes)
395761efaa7SDag-Erling Smørgrav {
396761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
397761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_read += bytes;
398761efaa7SDag-Erling Smørgrav }
399761efaa7SDag-Erling Smørgrav 
400761efaa7SDag-Erling Smørgrav static void
401761efaa7SDag-Erling Smørgrav handle_update_write(int handle, ssize_t bytes)
402761efaa7SDag-Erling Smørgrav {
403761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
404761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_write += bytes;
405761efaa7SDag-Erling Smørgrav }
406761efaa7SDag-Erling Smørgrav 
407761efaa7SDag-Erling Smørgrav static u_int64_t
408761efaa7SDag-Erling Smørgrav handle_bytes_read(int handle)
409761efaa7SDag-Erling Smørgrav {
410761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
411761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_read);
412761efaa7SDag-Erling Smørgrav 	return 0;
413761efaa7SDag-Erling Smørgrav }
414761efaa7SDag-Erling Smørgrav 
415761efaa7SDag-Erling Smørgrav static u_int64_t
416761efaa7SDag-Erling Smørgrav handle_bytes_write(int handle)
417761efaa7SDag-Erling Smørgrav {
418761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
419761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_write);
420761efaa7SDag-Erling Smørgrav 	return 0;
421761efaa7SDag-Erling Smørgrav }
422761efaa7SDag-Erling Smørgrav 
423ae1f160dSDag-Erling Smørgrav static int
424b66f2d16SKris Kennaway handle_close(int handle)
425b66f2d16SKris Kennaway {
426b66f2d16SKris Kennaway 	int ret = -1;
4271e8db6e2SBrian Feldman 
428b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE)) {
429b66f2d16SKris Kennaway 		ret = close(handles[handle].fd);
430e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
431d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
432b66f2d16SKris Kennaway 	} else if (handle_is_ok(handle, HANDLE_DIR)) {
433b66f2d16SKris Kennaway 		ret = closedir(handles[handle].dirp);
434e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
435d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
436b66f2d16SKris Kennaway 	} else {
437b66f2d16SKris Kennaway 		errno = ENOENT;
438b66f2d16SKris Kennaway 	}
439b66f2d16SKris Kennaway 	return ret;
440b66f2d16SKris Kennaway }
441b66f2d16SKris Kennaway 
442761efaa7SDag-Erling Smørgrav static void
443761efaa7SDag-Erling Smørgrav handle_log_close(int handle, char *emsg)
444761efaa7SDag-Erling Smørgrav {
445761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE)) {
446761efaa7SDag-Erling Smørgrav 		logit("%s%sclose \"%s\" bytes read %llu written %llu",
447761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
448761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle),
449d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_read(handle),
450d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_write(handle));
451761efaa7SDag-Erling Smørgrav 	} else {
452761efaa7SDag-Erling Smørgrav 		logit("%s%sclosedir \"%s\"",
453761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
454761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle));
455761efaa7SDag-Erling Smørgrav 	}
456761efaa7SDag-Erling Smørgrav }
457761efaa7SDag-Erling Smørgrav 
458761efaa7SDag-Erling Smørgrav static void
459761efaa7SDag-Erling Smørgrav handle_log_exit(void)
460761efaa7SDag-Erling Smørgrav {
461761efaa7SDag-Erling Smørgrav 	u_int i;
462761efaa7SDag-Erling Smørgrav 
463d4af9e69SDag-Erling Smørgrav 	for (i = 0; i < num_handles; i++)
464761efaa7SDag-Erling Smørgrav 		if (handles[i].use != HANDLE_UNUSED)
465761efaa7SDag-Erling Smørgrav 			handle_log_close(i, "forced");
466761efaa7SDag-Erling Smørgrav }
467761efaa7SDag-Erling Smørgrav 
468ae1f160dSDag-Erling Smørgrav static int
469bc5531deSDag-Erling Smørgrav get_handle(struct sshbuf *queue, int *hp)
470b66f2d16SKris Kennaway {
471bc5531deSDag-Erling Smørgrav 	u_char *handle;
472bc5531deSDag-Erling Smørgrav 	int r;
473bc5531deSDag-Erling Smørgrav 	size_t hlen;
4741e8db6e2SBrian Feldman 
475bc5531deSDag-Erling Smørgrav 	*hp = -1;
476bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_string(queue, &handle, &hlen)) != 0)
477bc5531deSDag-Erling Smørgrav 		return r;
4781e8db6e2SBrian Feldman 	if (hlen < 256)
479bc5531deSDag-Erling Smørgrav 		*hp = handle_from_string(handle, hlen);
480e4a9863fSDag-Erling Smørgrav 	free(handle);
481bc5531deSDag-Erling Smørgrav 	return 0;
482b66f2d16SKris Kennaway }
483b66f2d16SKris Kennaway 
484b66f2d16SKris Kennaway /* send replies */
485b66f2d16SKris Kennaway 
486ae1f160dSDag-Erling Smørgrav static void
487bc5531deSDag-Erling Smørgrav send_msg(struct sshbuf *m)
488b66f2d16SKris Kennaway {
489bc5531deSDag-Erling Smørgrav 	int r;
4901e8db6e2SBrian Feldman 
491bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_stringb(oqueue, m)) != 0)
492bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
493bc5531deSDag-Erling Smørgrav 	sshbuf_reset(m);
494b66f2d16SKris Kennaway }
495b66f2d16SKris Kennaway 
496761efaa7SDag-Erling Smørgrav static const char *
497761efaa7SDag-Erling Smørgrav status_to_message(u_int32_t status)
498b66f2d16SKris Kennaway {
4991e8db6e2SBrian Feldman 	const char *status_messages[] = {
5001e8db6e2SBrian Feldman 		"Success",			/* SSH_FX_OK */
5011e8db6e2SBrian Feldman 		"End of file",			/* SSH_FX_EOF */
5021e8db6e2SBrian Feldman 		"No such file",			/* SSH_FX_NO_SUCH_FILE */
5031e8db6e2SBrian Feldman 		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
5041e8db6e2SBrian Feldman 		"Failure",			/* SSH_FX_FAILURE */
5051e8db6e2SBrian Feldman 		"Bad message",			/* SSH_FX_BAD_MESSAGE */
5061e8db6e2SBrian Feldman 		"No connection",		/* SSH_FX_NO_CONNECTION */
5071e8db6e2SBrian Feldman 		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
5081e8db6e2SBrian Feldman 		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
5091e8db6e2SBrian Feldman 		"Unknown error"			/* Others */
5101e8db6e2SBrian Feldman 	};
511761efaa7SDag-Erling Smørgrav 	return (status_messages[MIN(status,SSH2_FX_MAX)]);
512761efaa7SDag-Erling Smørgrav }
5131e8db6e2SBrian Feldman 
514761efaa7SDag-Erling Smørgrav static void
515761efaa7SDag-Erling Smørgrav send_status(u_int32_t id, u_int32_t status)
516761efaa7SDag-Erling Smørgrav {
517bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
518bc5531deSDag-Erling Smørgrav 	int r;
519761efaa7SDag-Erling Smørgrav 
520761efaa7SDag-Erling Smørgrav 	debug3("request %u: sent status %u", id, status);
521761efaa7SDag-Erling Smørgrav 	if (log_level > SYSLOG_LEVEL_VERBOSE ||
522761efaa7SDag-Erling Smørgrav 	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
523761efaa7SDag-Erling Smørgrav 		logit("sent status %s", status_to_message(status));
524bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
525bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
526bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_STATUS)) != 0 ||
527bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
528bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, status)) != 0)
529bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
5301e8db6e2SBrian Feldman 	if (version >= 3) {
531bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_cstring(msg,
532bc5531deSDag-Erling Smørgrav 		    status_to_message(status))) != 0 ||
533bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, "")) != 0)
534bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5351e8db6e2SBrian Feldman 	}
536bc5531deSDag-Erling Smørgrav 	send_msg(msg);
537bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
538b66f2d16SKris Kennaway }
539ae1f160dSDag-Erling Smørgrav static void
540bc5531deSDag-Erling Smørgrav send_data_or_handle(char type, u_int32_t id, const u_char *data, int dlen)
541b66f2d16SKris Kennaway {
542bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
543bc5531deSDag-Erling Smørgrav 	int r;
5441e8db6e2SBrian Feldman 
545bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
546bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
547bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, type)) != 0 ||
548bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
549bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, data, dlen)) != 0)
550bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
551bc5531deSDag-Erling Smørgrav 	send_msg(msg);
552bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
553b66f2d16SKris Kennaway }
554b66f2d16SKris Kennaway 
555ae1f160dSDag-Erling Smørgrav static void
556bc5531deSDag-Erling Smørgrav send_data(u_int32_t id, const u_char *data, int dlen)
557b66f2d16SKris Kennaway {
558761efaa7SDag-Erling Smørgrav 	debug("request %u: sent data len %d", id, dlen);
5591e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
560b66f2d16SKris Kennaway }
561b66f2d16SKris Kennaway 
562ae1f160dSDag-Erling Smørgrav static void
563b66f2d16SKris Kennaway send_handle(u_int32_t id, int handle)
564b66f2d16SKris Kennaway {
565bc5531deSDag-Erling Smørgrav 	u_char *string;
566b66f2d16SKris Kennaway 	int hlen;
5671e8db6e2SBrian Feldman 
568b66f2d16SKris Kennaway 	handle_to_string(handle, &string, &hlen);
569761efaa7SDag-Erling Smørgrav 	debug("request %u: sent handle handle %d", id, handle);
5701e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
571e4a9863fSDag-Erling Smørgrav 	free(string);
572b66f2d16SKris Kennaway }
573b66f2d16SKris Kennaway 
574ae1f160dSDag-Erling Smørgrav static void
575efcad6b7SDag-Erling Smørgrav send_names(u_int32_t id, int count, const Stat *stats)
576b66f2d16SKris Kennaway {
577bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
578bc5531deSDag-Erling Smørgrav 	int i, r;
5791e8db6e2SBrian Feldman 
580bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
581bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
582bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_NAME)) != 0 ||
583bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
584bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, count)) != 0)
585bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
586761efaa7SDag-Erling Smørgrav 	debug("request %u: sent names count %d", id, count);
587b66f2d16SKris Kennaway 	for (i = 0; i < count; i++) {
588bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 ||
589bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 ||
590bc5531deSDag-Erling Smørgrav 		    (r = encode_attrib(msg, &stats[i].attrib)) != 0)
591bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
592b66f2d16SKris Kennaway 	}
593bc5531deSDag-Erling Smørgrav 	send_msg(msg);
594bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
595b66f2d16SKris Kennaway }
596b66f2d16SKris Kennaway 
597ae1f160dSDag-Erling Smørgrav static void
598efcad6b7SDag-Erling Smørgrav send_attrib(u_int32_t id, const Attrib *a)
599b66f2d16SKris Kennaway {
600bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
601bc5531deSDag-Erling Smørgrav 	int r;
6021e8db6e2SBrian Feldman 
603761efaa7SDag-Erling Smørgrav 	debug("request %u: sent attrib have 0x%x", id, a->flags);
604bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
605bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
606bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_ATTRS)) != 0 ||
607bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
608bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, a)) != 0)
609bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
610bc5531deSDag-Erling Smørgrav 	send_msg(msg);
611bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
612b66f2d16SKris Kennaway }
613b66f2d16SKris Kennaway 
614d4af9e69SDag-Erling Smørgrav static void
615d4af9e69SDag-Erling Smørgrav send_statvfs(u_int32_t id, struct statvfs *st)
616d4af9e69SDag-Erling Smørgrav {
617bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
618d4af9e69SDag-Erling Smørgrav 	u_int64_t flag;
619bc5531deSDag-Erling Smørgrav 	int r;
620d4af9e69SDag-Erling Smørgrav 
621d4af9e69SDag-Erling Smørgrav 	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
622d4af9e69SDag-Erling Smørgrav 	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
623d4af9e69SDag-Erling Smørgrav 
624bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
625bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
626bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
627bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
628bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bsize)) != 0 ||
629bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_frsize)) != 0 ||
630bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_blocks)) != 0 ||
631bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bfree)) != 0 ||
632bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bavail)) != 0 ||
633bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_files)) != 0 ||
634bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_ffree)) != 0 ||
635bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_favail)) != 0 ||
636bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, FSID_TO_ULONG(st->f_fsid))) != 0 ||
637bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, flag)) != 0 ||
638bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_namemax)) != 0)
639bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
640bc5531deSDag-Erling Smørgrav 	send_msg(msg);
641bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
642d4af9e69SDag-Erling Smørgrav }
643d4af9e69SDag-Erling Smørgrav 
644b66f2d16SKris Kennaway /* parse incoming */
645b66f2d16SKris Kennaway 
646ae1f160dSDag-Erling Smørgrav static void
647b66f2d16SKris Kennaway process_init(void)
648b66f2d16SKris Kennaway {
649bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
650bc5531deSDag-Erling Smørgrav 	int r;
651b66f2d16SKris Kennaway 
652bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(iqueue, &version)) != 0)
653bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
654e146993eSDag-Erling Smørgrav 	verbose("received client version %u", version);
655bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
656bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
657bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 ||
658bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0 ||
659d4af9e69SDag-Erling Smørgrav 	    /* POSIX rename extension */
660bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "posix-rename@openssh.com")) != 0 ||
661bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
662d4af9e69SDag-Erling Smørgrav 	    /* statvfs extension */
663bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
664bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
665d4af9e69SDag-Erling Smørgrav 	    /* fstatvfs extension */
666bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
667bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
6684a421b63SDag-Erling Smørgrav 	    /* hardlink extension */
669bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
670bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
671f7167e0eSDag-Erling Smørgrav 	    /* fsync extension */
672bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
673bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
674bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
675bc5531deSDag-Erling Smørgrav 	send_msg(msg);
676bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
677b66f2d16SKris Kennaway }
678b66f2d16SKris Kennaway 
679ae1f160dSDag-Erling Smørgrav static void
680f7167e0eSDag-Erling Smørgrav process_open(u_int32_t id)
681b66f2d16SKris Kennaway {
682f7167e0eSDag-Erling Smørgrav 	u_int32_t pflags;
683bc5531deSDag-Erling Smørgrav 	Attrib a;
684b66f2d16SKris Kennaway 	char *name;
685bc5531deSDag-Erling Smørgrav 	int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE;
686b66f2d16SKris Kennaway 
687bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
688bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */
689bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
690bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
691bc5531deSDag-Erling Smørgrav 
692761efaa7SDag-Erling Smørgrav 	debug3("request %u: open flags %d", id, pflags);
693b66f2d16SKris Kennaway 	flags = flags_from_portable(pflags);
694bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666;
695761efaa7SDag-Erling Smørgrav 	logit("open \"%s\" flags %s mode 0%o",
696761efaa7SDag-Erling Smørgrav 	    name, string_from_portable(pflags), mode);
697b15c8340SDag-Erling Smørgrav 	if (readonly &&
698f7167e0eSDag-Erling Smørgrav 	    ((flags & O_ACCMODE) == O_WRONLY ||
699f7167e0eSDag-Erling Smørgrav 	    (flags & O_ACCMODE) == O_RDWR)) {
700f7167e0eSDag-Erling Smørgrav 		verbose("Refusing open request in read-only mode");
701b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_PERMISSION_DENIED;
702f7167e0eSDag-Erling Smørgrav 	} else {
703b66f2d16SKris Kennaway 		fd = open(name, flags, mode);
704b66f2d16SKris Kennaway 		if (fd < 0) {
705b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
706b66f2d16SKris Kennaway 		} else {
707f7167e0eSDag-Erling Smørgrav 			handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
708b66f2d16SKris Kennaway 			if (handle < 0) {
709b66f2d16SKris Kennaway 				close(fd);
710b66f2d16SKris Kennaway 			} else {
711b66f2d16SKris Kennaway 				send_handle(id, handle);
7121e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
713b66f2d16SKris Kennaway 			}
714b66f2d16SKris Kennaway 		}
715b15c8340SDag-Erling Smørgrav 	}
7161e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
717b66f2d16SKris Kennaway 		send_status(id, status);
718e4a9863fSDag-Erling Smørgrav 	free(name);
719b66f2d16SKris Kennaway }
720b66f2d16SKris Kennaway 
721ae1f160dSDag-Erling Smørgrav static void
722f7167e0eSDag-Erling Smørgrav process_close(u_int32_t id)
723b66f2d16SKris Kennaway {
724bc5531deSDag-Erling Smørgrav 	int r, handle, ret, status = SSH2_FX_FAILURE;
725b66f2d16SKris Kennaway 
726bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
727bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
728bc5531deSDag-Erling Smørgrav 
729761efaa7SDag-Erling Smørgrav 	debug3("request %u: close handle %u", id, handle);
730761efaa7SDag-Erling Smørgrav 	handle_log_close(handle, NULL);
731b66f2d16SKris Kennaway 	ret = handle_close(handle);
7321e8db6e2SBrian Feldman 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
733b66f2d16SKris Kennaway 	send_status(id, status);
734b66f2d16SKris Kennaway }
735b66f2d16SKris Kennaway 
736ae1f160dSDag-Erling Smørgrav static void
737f7167e0eSDag-Erling Smørgrav process_read(u_int32_t id)
738b66f2d16SKris Kennaway {
739bc5531deSDag-Erling Smørgrav 	u_char buf[64*1024];
740f7167e0eSDag-Erling Smørgrav 	u_int32_t len;
741bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status = SSH2_FX_FAILURE;
742b66f2d16SKris Kennaway 	u_int64_t off;
743b66f2d16SKris Kennaway 
744bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
745bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
746bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &len)) != 0)
747bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
748b66f2d16SKris Kennaway 
749761efaa7SDag-Erling Smørgrav 	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
750761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
751b66f2d16SKris Kennaway 	if (len > sizeof buf) {
752b66f2d16SKris Kennaway 		len = sizeof buf;
753761efaa7SDag-Erling Smørgrav 		debug2("read change len %d", len);
754b66f2d16SKris Kennaway 	}
755b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
756b66f2d16SKris Kennaway 	if (fd >= 0) {
757b66f2d16SKris Kennaway 		if (lseek(fd, off, SEEK_SET) < 0) {
758b66f2d16SKris Kennaway 			error("process_read: seek failed");
759b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
760b66f2d16SKris Kennaway 		} else {
761b66f2d16SKris Kennaway 			ret = read(fd, buf, len);
762b66f2d16SKris Kennaway 			if (ret < 0) {
763b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
764b66f2d16SKris Kennaway 			} else if (ret == 0) {
7651e8db6e2SBrian Feldman 				status = SSH2_FX_EOF;
766b66f2d16SKris Kennaway 			} else {
767b66f2d16SKris Kennaway 				send_data(id, buf, ret);
7681e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
769761efaa7SDag-Erling Smørgrav 				handle_update_read(handle, ret);
770b66f2d16SKris Kennaway 			}
771b66f2d16SKris Kennaway 		}
772b66f2d16SKris Kennaway 	}
7731e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
774b66f2d16SKris Kennaway 		send_status(id, status);
775b66f2d16SKris Kennaway }
776b66f2d16SKris Kennaway 
777ae1f160dSDag-Erling Smørgrav static void
778f7167e0eSDag-Erling Smørgrav process_write(u_int32_t id)
779b66f2d16SKris Kennaway {
780b66f2d16SKris Kennaway 	u_int64_t off;
781bc5531deSDag-Erling Smørgrav 	size_t len;
782bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status;
783bc5531deSDag-Erling Smørgrav 	u_char *data;
784b66f2d16SKris Kennaway 
785bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
786bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
787bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_string(iqueue, &data, &len)) != 0)
788bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
789b66f2d16SKris Kennaway 
790bc5531deSDag-Erling Smørgrav 	debug("request %u: write \"%s\" (handle %d) off %llu len %zu",
791761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
792b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
793b15c8340SDag-Erling Smørgrav 
794b15c8340SDag-Erling Smørgrav 	if (fd < 0)
795b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
796b15c8340SDag-Erling Smørgrav 	else {
797f7167e0eSDag-Erling Smørgrav 		if (!(handle_to_flags(handle) & O_APPEND) &&
798f7167e0eSDag-Erling Smørgrav 				lseek(fd, off, SEEK_SET) < 0) {
799b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
800b66f2d16SKris Kennaway 			error("process_write: seek failed");
801b66f2d16SKris Kennaway 		} else {
802b66f2d16SKris Kennaway /* XXX ATOMICIO ? */
803b66f2d16SKris Kennaway 			ret = write(fd, data, len);
804043840dfSDag-Erling Smørgrav 			if (ret < 0) {
805b66f2d16SKris Kennaway 				error("process_write: write failed");
806b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
807043840dfSDag-Erling Smørgrav 			} else if ((size_t)ret == len) {
8081e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
809761efaa7SDag-Erling Smørgrav 				handle_update_write(handle, ret);
810b66f2d16SKris Kennaway 			} else {
811761efaa7SDag-Erling Smørgrav 				debug2("nothing at all written");
812b15c8340SDag-Erling Smørgrav 				status = SSH2_FX_FAILURE;
813b66f2d16SKris Kennaway 			}
814b66f2d16SKris Kennaway 		}
815b66f2d16SKris Kennaway 	}
816b66f2d16SKris Kennaway 	send_status(id, status);
817e4a9863fSDag-Erling Smørgrav 	free(data);
818b66f2d16SKris Kennaway }
819b66f2d16SKris Kennaway 
820ae1f160dSDag-Erling Smørgrav static void
821f7167e0eSDag-Erling Smørgrav process_do_stat(u_int32_t id, int do_lstat)
822b66f2d16SKris Kennaway {
8231e8db6e2SBrian Feldman 	Attrib a;
824b66f2d16SKris Kennaway 	struct stat st;
825b66f2d16SKris Kennaway 	char *name;
826bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
827b66f2d16SKris Kennaway 
828bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
829bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
830bc5531deSDag-Erling Smørgrav 
831761efaa7SDag-Erling Smørgrav 	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
832761efaa7SDag-Erling Smørgrav 	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
833bc5531deSDag-Erling Smørgrav 	r = do_lstat ? lstat(name, &st) : stat(name, &st);
834bc5531deSDag-Erling Smørgrav 	if (r < 0) {
835b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
836b66f2d16SKris Kennaway 	} else {
8371e8db6e2SBrian Feldman 		stat_to_attrib(&st, &a);
8381e8db6e2SBrian Feldman 		send_attrib(id, &a);
8391e8db6e2SBrian Feldman 		status = SSH2_FX_OK;
840b66f2d16SKris Kennaway 	}
8411e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
842b66f2d16SKris Kennaway 		send_status(id, status);
843e4a9863fSDag-Erling Smørgrav 	free(name);
844b66f2d16SKris Kennaway }
845b66f2d16SKris Kennaway 
846ae1f160dSDag-Erling Smørgrav static void
847f7167e0eSDag-Erling Smørgrav process_stat(u_int32_t id)
848b66f2d16SKris Kennaway {
849f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 0);
850b66f2d16SKris Kennaway }
851b66f2d16SKris Kennaway 
852ae1f160dSDag-Erling Smørgrav static void
853f7167e0eSDag-Erling Smørgrav process_lstat(u_int32_t id)
854b66f2d16SKris Kennaway {
855f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 1);
856b66f2d16SKris Kennaway }
857b66f2d16SKris Kennaway 
858ae1f160dSDag-Erling Smørgrav static void
859f7167e0eSDag-Erling Smørgrav process_fstat(u_int32_t id)
860b66f2d16SKris Kennaway {
8611e8db6e2SBrian Feldman 	Attrib a;
862b66f2d16SKris Kennaway 	struct stat st;
863bc5531deSDag-Erling Smørgrav 	int fd, r, handle, status = SSH2_FX_FAILURE;
864b66f2d16SKris Kennaway 
865bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
866bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
867761efaa7SDag-Erling Smørgrav 	debug("request %u: fstat \"%s\" (handle %u)",
868761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
869b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
870b66f2d16SKris Kennaway 	if (fd >= 0) {
871bc5531deSDag-Erling Smørgrav 		r = fstat(fd, &st);
872bc5531deSDag-Erling Smørgrav 		if (r < 0) {
873b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
874b66f2d16SKris Kennaway 		} else {
8751e8db6e2SBrian Feldman 			stat_to_attrib(&st, &a);
8761e8db6e2SBrian Feldman 			send_attrib(id, &a);
8771e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
878b66f2d16SKris Kennaway 		}
879b66f2d16SKris Kennaway 	}
8801e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
881b66f2d16SKris Kennaway 		send_status(id, status);
882b66f2d16SKris Kennaway }
883b66f2d16SKris Kennaway 
884ae1f160dSDag-Erling Smørgrav static struct timeval *
885efcad6b7SDag-Erling Smørgrav attrib_to_tv(const Attrib *a)
886b66f2d16SKris Kennaway {
887b66f2d16SKris Kennaway 	static struct timeval tv[2];
8881e8db6e2SBrian Feldman 
889b66f2d16SKris Kennaway 	tv[0].tv_sec = a->atime;
890b66f2d16SKris Kennaway 	tv[0].tv_usec = 0;
891b66f2d16SKris Kennaway 	tv[1].tv_sec = a->mtime;
892b66f2d16SKris Kennaway 	tv[1].tv_usec = 0;
893b66f2d16SKris Kennaway 	return tv;
894b66f2d16SKris Kennaway }
895b66f2d16SKris Kennaway 
896ae1f160dSDag-Erling Smørgrav static void
897f7167e0eSDag-Erling Smørgrav process_setstat(u_int32_t id)
898b66f2d16SKris Kennaway {
899bc5531deSDag-Erling Smørgrav 	Attrib a;
900b66f2d16SKris Kennaway 	char *name;
901bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_OK;
902b66f2d16SKris Kennaway 
903bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
904bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
905bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
906bc5531deSDag-Erling Smørgrav 
907761efaa7SDag-Erling Smørgrav 	debug("request %u: setstat name \"%s\"", id, name);
908bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
909d4af9e69SDag-Erling Smørgrav 		logit("set \"%s\" size %llu",
910bc5531deSDag-Erling Smørgrav 		    name, (unsigned long long)a.size);
911bc5531deSDag-Erling Smørgrav 		r = truncate(name, a.size);
912bc5531deSDag-Erling Smørgrav 		if (r == -1)
913ae1f160dSDag-Erling Smørgrav 			status = errno_to_portable(errno);
914ae1f160dSDag-Erling Smørgrav 	}
915bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
916bc5531deSDag-Erling Smørgrav 		logit("set \"%s\" mode %04o", name, a.perm);
917bc5531deSDag-Erling Smørgrav 		r = chmod(name, a.perm & 07777);
918bc5531deSDag-Erling Smørgrav 		if (r == -1)
919b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
920b66f2d16SKris Kennaway 	}
921bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
922761efaa7SDag-Erling Smørgrav 		char buf[64];
923bc5531deSDag-Erling Smørgrav 		time_t t = a.mtime;
924761efaa7SDag-Erling Smørgrav 
925761efaa7SDag-Erling Smørgrav 		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
926761efaa7SDag-Erling Smørgrav 		    localtime(&t));
927761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" modtime %s", name, buf);
928bc5531deSDag-Erling Smørgrav 		r = utimes(name, attrib_to_tv(&a));
929bc5531deSDag-Erling Smørgrav 		if (r == -1)
930b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
931b66f2d16SKris Kennaway 	}
932bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
933761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" owner %lu group %lu", name,
934bc5531deSDag-Erling Smørgrav 		    (u_long)a.uid, (u_long)a.gid);
935bc5531deSDag-Erling Smørgrav 		r = chown(name, a.uid, a.gid);
936bc5531deSDag-Erling Smørgrav 		if (r == -1)
9371e8db6e2SBrian Feldman 			status = errno_to_portable(errno);
9381e8db6e2SBrian Feldman 	}
939b66f2d16SKris Kennaway 	send_status(id, status);
940e4a9863fSDag-Erling Smørgrav 	free(name);
941b66f2d16SKris Kennaway }
942b66f2d16SKris Kennaway 
943ae1f160dSDag-Erling Smørgrav static void
944f7167e0eSDag-Erling Smørgrav process_fsetstat(u_int32_t id)
945b66f2d16SKris Kennaway {
946bc5531deSDag-Erling Smørgrav 	Attrib a;
947bc5531deSDag-Erling Smørgrav 	int handle, fd, r;
9481e8db6e2SBrian Feldman 	int status = SSH2_FX_OK;
949b66f2d16SKris Kennaway 
950bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
951bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
952bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
953bc5531deSDag-Erling Smørgrav 
954761efaa7SDag-Erling Smørgrav 	debug("request %u: fsetstat handle %d", id, handle);
955b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
956b15c8340SDag-Erling Smørgrav 	if (fd < 0)
9571e8db6e2SBrian Feldman 		status = SSH2_FX_FAILURE;
958b15c8340SDag-Erling Smørgrav 	else {
959761efaa7SDag-Erling Smørgrav 		char *name = handle_to_name(handle);
960761efaa7SDag-Erling Smørgrav 
961bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
962d4af9e69SDag-Erling Smørgrav 			logit("set \"%s\" size %llu",
963bc5531deSDag-Erling Smørgrav 			    name, (unsigned long long)a.size);
964bc5531deSDag-Erling Smørgrav 			r = ftruncate(fd, a.size);
965bc5531deSDag-Erling Smørgrav 			if (r == -1)
966ae1f160dSDag-Erling Smørgrav 				status = errno_to_portable(errno);
967ae1f160dSDag-Erling Smørgrav 		}
968bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
969bc5531deSDag-Erling Smørgrav 			logit("set \"%s\" mode %04o", name, a.perm);
97083d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
971bc5531deSDag-Erling Smørgrav 			r = fchmod(fd, a.perm & 07777);
97283d2307dSDag-Erling Smørgrav #else
973bc5531deSDag-Erling Smørgrav 			r = chmod(name, a.perm & 07777);
97483d2307dSDag-Erling Smørgrav #endif
975bc5531deSDag-Erling Smørgrav 			if (r == -1)
976b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
977b66f2d16SKris Kennaway 		}
978bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
979761efaa7SDag-Erling Smørgrav 			char buf[64];
980bc5531deSDag-Erling Smørgrav 			time_t t = a.mtime;
981761efaa7SDag-Erling Smørgrav 
982761efaa7SDag-Erling Smørgrav 			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
983761efaa7SDag-Erling Smørgrav 			    localtime(&t));
984761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" modtime %s", name, buf);
98583d2307dSDag-Erling Smørgrav #ifdef HAVE_FUTIMES
986bc5531deSDag-Erling Smørgrav 			r = futimes(fd, attrib_to_tv(&a));
98783d2307dSDag-Erling Smørgrav #else
988bc5531deSDag-Erling Smørgrav 			r = utimes(name, attrib_to_tv(&a));
98983d2307dSDag-Erling Smørgrav #endif
990bc5531deSDag-Erling Smørgrav 			if (r == -1)
991b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
992b66f2d16SKris Kennaway 		}
993bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
994761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" owner %lu group %lu", name,
995bc5531deSDag-Erling Smørgrav 			    (u_long)a.uid, (u_long)a.gid);
99683d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHOWN
997bc5531deSDag-Erling Smørgrav 			r = fchown(fd, a.uid, a.gid);
99883d2307dSDag-Erling Smørgrav #else
999bc5531deSDag-Erling Smørgrav 			r = chown(name, a.uid, a.gid);
100083d2307dSDag-Erling Smørgrav #endif
1001bc5531deSDag-Erling Smørgrav 			if (r == -1)
10021e8db6e2SBrian Feldman 				status = errno_to_portable(errno);
10031e8db6e2SBrian Feldman 		}
1004b66f2d16SKris Kennaway 	}
1005b66f2d16SKris Kennaway 	send_status(id, status);
1006b66f2d16SKris Kennaway }
1007b66f2d16SKris Kennaway 
1008ae1f160dSDag-Erling Smørgrav static void
1009f7167e0eSDag-Erling Smørgrav process_opendir(u_int32_t id)
1010b66f2d16SKris Kennaway {
1011b66f2d16SKris Kennaway 	DIR *dirp = NULL;
1012b66f2d16SKris Kennaway 	char *path;
1013bc5531deSDag-Erling Smørgrav 	int r, handle, status = SSH2_FX_FAILURE;
1014b66f2d16SKris Kennaway 
1015bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1016bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1017bc5531deSDag-Erling Smørgrav 
1018761efaa7SDag-Erling Smørgrav 	debug3("request %u: opendir", id);
1019761efaa7SDag-Erling Smørgrav 	logit("opendir \"%s\"", path);
1020b66f2d16SKris Kennaway 	dirp = opendir(path);
1021b66f2d16SKris Kennaway 	if (dirp == NULL) {
1022b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
1023b66f2d16SKris Kennaway 	} else {
1024f7167e0eSDag-Erling Smørgrav 		handle = handle_new(HANDLE_DIR, path, 0, 0, dirp);
1025b66f2d16SKris Kennaway 		if (handle < 0) {
1026b66f2d16SKris Kennaway 			closedir(dirp);
1027b66f2d16SKris Kennaway 		} else {
1028b66f2d16SKris Kennaway 			send_handle(id, handle);
10291e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
1030b66f2d16SKris Kennaway 		}
1031b66f2d16SKris Kennaway 
1032b66f2d16SKris Kennaway 	}
10331e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
1034b66f2d16SKris Kennaway 		send_status(id, status);
1035e4a9863fSDag-Erling Smørgrav 	free(path);
1036b66f2d16SKris Kennaway }
1037b66f2d16SKris Kennaway 
1038ae1f160dSDag-Erling Smørgrav static void
1039f7167e0eSDag-Erling Smørgrav process_readdir(u_int32_t id)
1040b66f2d16SKris Kennaway {
1041b66f2d16SKris Kennaway 	DIR *dirp;
1042b66f2d16SKris Kennaway 	struct dirent *dp;
1043b66f2d16SKris Kennaway 	char *path;
1044bc5531deSDag-Erling Smørgrav 	int r, handle;
1045b66f2d16SKris Kennaway 
1046bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1047bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1048bc5531deSDag-Erling Smørgrav 
1049761efaa7SDag-Erling Smørgrav 	debug("request %u: readdir \"%s\" (handle %d)", id,
1050761efaa7SDag-Erling Smørgrav 	    handle_to_name(handle), handle);
1051b66f2d16SKris Kennaway 	dirp = handle_to_dir(handle);
1052b66f2d16SKris Kennaway 	path = handle_to_name(handle);
1053b66f2d16SKris Kennaway 	if (dirp == NULL || path == NULL) {
10541e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_FAILURE);
1055b66f2d16SKris Kennaway 	} else {
1056b66f2d16SKris Kennaway 		struct stat st;
1057bc5531deSDag-Erling Smørgrav 		char pathname[PATH_MAX];
1058b66f2d16SKris Kennaway 		Stat *stats;
1059b66f2d16SKris Kennaway 		int nstats = 10, count = 0, i;
1060ee21a45fSDag-Erling Smørgrav 
1061761efaa7SDag-Erling Smørgrav 		stats = xcalloc(nstats, sizeof(Stat));
1062b66f2d16SKris Kennaway 		while ((dp = readdir(dirp)) != NULL) {
1063b66f2d16SKris Kennaway 			if (count >= nstats) {
1064b66f2d16SKris Kennaway 				nstats *= 2;
1065557f75e5SDag-Erling Smørgrav 				stats = xreallocarray(stats, nstats, sizeof(Stat));
1066b66f2d16SKris Kennaway 			}
1067b66f2d16SKris Kennaway /* XXX OVERFLOW ? */
1068ae1f160dSDag-Erling Smørgrav 			snprintf(pathname, sizeof pathname, "%s%s%s", path,
1069ae1f160dSDag-Erling Smørgrav 			    strcmp(path, "/") ? "/" : "", dp->d_name);
1070b66f2d16SKris Kennaway 			if (lstat(pathname, &st) < 0)
1071b66f2d16SKris Kennaway 				continue;
10721e8db6e2SBrian Feldman 			stat_to_attrib(&st, &(stats[count].attrib));
1073b66f2d16SKris Kennaway 			stats[count].name = xstrdup(dp->d_name);
1074b15c8340SDag-Erling Smørgrav 			stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
1075b66f2d16SKris Kennaway 			count++;
1076b66f2d16SKris Kennaway 			/* send up to 100 entries in one message */
10771e8db6e2SBrian Feldman 			/* XXX check packet size instead */
1078b66f2d16SKris Kennaway 			if (count == 100)
1079b66f2d16SKris Kennaway 				break;
1080b66f2d16SKris Kennaway 		}
10811e8db6e2SBrian Feldman 		if (count > 0) {
1082b66f2d16SKris Kennaway 			send_names(id, count, stats);
1083b66f2d16SKris Kennaway 			for (i = 0; i < count; i++) {
1084e4a9863fSDag-Erling Smørgrav 				free(stats[i].name);
1085e4a9863fSDag-Erling Smørgrav 				free(stats[i].long_name);
1086b66f2d16SKris Kennaway 			}
10871e8db6e2SBrian Feldman 		} else {
10881e8db6e2SBrian Feldman 			send_status(id, SSH2_FX_EOF);
10891e8db6e2SBrian Feldman 		}
1090e4a9863fSDag-Erling Smørgrav 		free(stats);
1091b66f2d16SKris Kennaway 	}
1092b66f2d16SKris Kennaway }
1093b66f2d16SKris Kennaway 
1094ae1f160dSDag-Erling Smørgrav static void
1095f7167e0eSDag-Erling Smørgrav process_remove(u_int32_t id)
1096b66f2d16SKris Kennaway {
1097b66f2d16SKris Kennaway 	char *name;
1098bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
1099b66f2d16SKris Kennaway 
1100bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
1101bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1102bc5531deSDag-Erling Smørgrav 
1103761efaa7SDag-Erling Smørgrav 	debug3("request %u: remove", id);
1104761efaa7SDag-Erling Smørgrav 	logit("remove name \"%s\"", name);
1105bc5531deSDag-Erling Smørgrav 	r = unlink(name);
1106bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1107b66f2d16SKris Kennaway 	send_status(id, status);
1108e4a9863fSDag-Erling Smørgrav 	free(name);
1109b66f2d16SKris Kennaway }
1110b66f2d16SKris Kennaway 
1111ae1f160dSDag-Erling Smørgrav static void
1112f7167e0eSDag-Erling Smørgrav process_mkdir(u_int32_t id)
1113b66f2d16SKris Kennaway {
1114bc5531deSDag-Erling Smørgrav 	Attrib a;
1115b66f2d16SKris Kennaway 	char *name;
1116bc5531deSDag-Erling Smørgrav 	int r, mode, status = SSH2_FX_FAILURE;
1117b66f2d16SKris Kennaway 
1118bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
1119bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
1120bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1121bc5531deSDag-Erling Smørgrav 
1122bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
1123bc5531deSDag-Erling Smørgrav 	    a.perm & 07777 : 0777;
1124761efaa7SDag-Erling Smørgrav 	debug3("request %u: mkdir", id);
1125761efaa7SDag-Erling Smørgrav 	logit("mkdir name \"%s\" mode 0%o", name, mode);
1126bc5531deSDag-Erling Smørgrav 	r = mkdir(name, mode);
1127bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1128b66f2d16SKris Kennaway 	send_status(id, status);
1129e4a9863fSDag-Erling Smørgrav 	free(name);
1130b66f2d16SKris Kennaway }
1131b66f2d16SKris Kennaway 
1132ae1f160dSDag-Erling Smørgrav static void
1133f7167e0eSDag-Erling Smørgrav process_rmdir(u_int32_t id)
1134b66f2d16SKris Kennaway {
1135b66f2d16SKris Kennaway 	char *name;
1136bc5531deSDag-Erling Smørgrav 	int r, status;
1137b66f2d16SKris Kennaway 
1138bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
1139bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1140bc5531deSDag-Erling Smørgrav 
1141761efaa7SDag-Erling Smørgrav 	debug3("request %u: rmdir", id);
1142761efaa7SDag-Erling Smørgrav 	logit("rmdir name \"%s\"", name);
1143bc5531deSDag-Erling Smørgrav 	r = rmdir(name);
1144bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1145b66f2d16SKris Kennaway 	send_status(id, status);
1146e4a9863fSDag-Erling Smørgrav 	free(name);
1147b66f2d16SKris Kennaway }
1148b66f2d16SKris Kennaway 
1149ae1f160dSDag-Erling Smørgrav static void
1150f7167e0eSDag-Erling Smørgrav process_realpath(u_int32_t id)
1151b66f2d16SKris Kennaway {
1152bc5531deSDag-Erling Smørgrav 	char resolvedname[PATH_MAX];
1153b66f2d16SKris Kennaway 	char *path;
1154bc5531deSDag-Erling Smørgrav 	int r;
1155b66f2d16SKris Kennaway 
1156bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1157bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1158bc5531deSDag-Erling Smørgrav 
11591e8db6e2SBrian Feldman 	if (path[0] == '\0') {
1160e4a9863fSDag-Erling Smørgrav 		free(path);
11611e8db6e2SBrian Feldman 		path = xstrdup(".");
11621e8db6e2SBrian Feldman 	}
1163761efaa7SDag-Erling Smørgrav 	debug3("request %u: realpath", id);
1164761efaa7SDag-Erling Smørgrav 	verbose("realpath \"%s\"", path);
1165b66f2d16SKris Kennaway 	if (realpath(path, resolvedname) == NULL) {
1166b66f2d16SKris Kennaway 		send_status(id, errno_to_portable(errno));
1167b66f2d16SKris Kennaway 	} else {
1168b66f2d16SKris Kennaway 		Stat s;
1169b66f2d16SKris Kennaway 		attrib_clear(&s.attrib);
1170b66f2d16SKris Kennaway 		s.name = s.long_name = resolvedname;
1171b66f2d16SKris Kennaway 		send_names(id, 1, &s);
1172b66f2d16SKris Kennaway 	}
1173e4a9863fSDag-Erling Smørgrav 	free(path);
1174b66f2d16SKris Kennaway }
1175b66f2d16SKris Kennaway 
1176ae1f160dSDag-Erling Smørgrav static void
1177f7167e0eSDag-Erling Smørgrav process_rename(u_int32_t id)
1178b66f2d16SKris Kennaway {
1179b66f2d16SKris Kennaway 	char *oldpath, *newpath;
1180bc5531deSDag-Erling Smørgrav 	int r, status;
1181d0c8c0bcSDag-Erling Smørgrav 	struct stat sb;
1182b66f2d16SKris Kennaway 
1183bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1184bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1185bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1186bc5531deSDag-Erling Smørgrav 
1187761efaa7SDag-Erling Smørgrav 	debug3("request %u: rename", id);
1188761efaa7SDag-Erling Smørgrav 	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1189d0c8c0bcSDag-Erling Smørgrav 	status = SSH2_FX_FAILURE;
1190f7167e0eSDag-Erling Smørgrav 	if (lstat(oldpath, &sb) == -1)
1191d0c8c0bcSDag-Erling Smørgrav 		status = errno_to_portable(errno);
1192d0c8c0bcSDag-Erling Smørgrav 	else if (S_ISREG(sb.st_mode)) {
1193d0c8c0bcSDag-Erling Smørgrav 		/* Race-free rename of regular files */
1194d74d50a8SDag-Erling Smørgrav 		if (link(oldpath, newpath) == -1) {
11957aee6ffeSDag-Erling Smørgrav 			if (errno == EOPNOTSUPP || errno == ENOSYS
1196d4af9e69SDag-Erling Smørgrav #ifdef EXDEV
1197d4af9e69SDag-Erling Smørgrav 			    || errno == EXDEV
1198d4af9e69SDag-Erling Smørgrav #endif
1199d74d50a8SDag-Erling Smørgrav #ifdef LINK_OPNOTSUPP_ERRNO
1200d74d50a8SDag-Erling Smørgrav 			    || errno == LINK_OPNOTSUPP_ERRNO
1201d74d50a8SDag-Erling Smørgrav #endif
1202d74d50a8SDag-Erling Smørgrav 			    ) {
1203d74d50a8SDag-Erling Smørgrav 				struct stat st;
1204d74d50a8SDag-Erling Smørgrav 
1205d74d50a8SDag-Erling Smørgrav 				/*
1206d74d50a8SDag-Erling Smørgrav 				 * fs doesn't support links, so fall back to
1207d74d50a8SDag-Erling Smørgrav 				 * stat+rename.  This is racy.
1208d74d50a8SDag-Erling Smørgrav 				 */
1209d74d50a8SDag-Erling Smørgrav 				if (stat(newpath, &st) == -1) {
1210d74d50a8SDag-Erling Smørgrav 					if (rename(oldpath, newpath) == -1)
1211d74d50a8SDag-Erling Smørgrav 						status =
1212d74d50a8SDag-Erling Smørgrav 						    errno_to_portable(errno);
1213d74d50a8SDag-Erling Smørgrav 					else
1214d74d50a8SDag-Erling Smørgrav 						status = SSH2_FX_OK;
1215d74d50a8SDag-Erling Smørgrav 				}
1216d74d50a8SDag-Erling Smørgrav 			} else {
1217d0c8c0bcSDag-Erling Smørgrav 				status = errno_to_portable(errno);
1218d74d50a8SDag-Erling Smørgrav 			}
1219d74d50a8SDag-Erling Smørgrav 		} else if (unlink(oldpath) == -1) {
1220d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1221d0c8c0bcSDag-Erling Smørgrav 			/* clean spare link */
1222d0c8c0bcSDag-Erling Smørgrav 			unlink(newpath);
1223d0c8c0bcSDag-Erling Smørgrav 		} else
1224d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
1225d0c8c0bcSDag-Erling Smørgrav 	} else if (stat(newpath, &sb) == -1) {
1226d0c8c0bcSDag-Erling Smørgrav 		if (rename(oldpath, newpath) == -1)
1227d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1228d0c8c0bcSDag-Erling Smørgrav 		else
1229d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
12301e8db6e2SBrian Feldman 	}
1231b66f2d16SKris Kennaway 	send_status(id, status);
1232e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1233e4a9863fSDag-Erling Smørgrav 	free(newpath);
1234b66f2d16SKris Kennaway }
1235b66f2d16SKris Kennaway 
1236ae1f160dSDag-Erling Smørgrav static void
1237f7167e0eSDag-Erling Smørgrav process_readlink(u_int32_t id)
12381e8db6e2SBrian Feldman {
1239bc5531deSDag-Erling Smørgrav 	int r, len;
1240bc5531deSDag-Erling Smørgrav 	char buf[PATH_MAX];
12411e8db6e2SBrian Feldman 	char *path;
12421e8db6e2SBrian Feldman 
1243bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1244bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1245bc5531deSDag-Erling Smørgrav 
1246761efaa7SDag-Erling Smørgrav 	debug3("request %u: readlink", id);
1247761efaa7SDag-Erling Smørgrav 	verbose("readlink \"%s\"", path);
1248d74d50a8SDag-Erling Smørgrav 	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
12491e8db6e2SBrian Feldman 		send_status(id, errno_to_portable(errno));
12501e8db6e2SBrian Feldman 	else {
12511e8db6e2SBrian Feldman 		Stat s;
12521e8db6e2SBrian Feldman 
1253d74d50a8SDag-Erling Smørgrav 		buf[len] = '\0';
12541e8db6e2SBrian Feldman 		attrib_clear(&s.attrib);
1255d74d50a8SDag-Erling Smørgrav 		s.name = s.long_name = buf;
12561e8db6e2SBrian Feldman 		send_names(id, 1, &s);
12571e8db6e2SBrian Feldman 	}
1258e4a9863fSDag-Erling Smørgrav 	free(path);
12591e8db6e2SBrian Feldman }
12601e8db6e2SBrian Feldman 
1261ae1f160dSDag-Erling Smørgrav static void
1262f7167e0eSDag-Erling Smørgrav process_symlink(u_int32_t id)
12631e8db6e2SBrian Feldman {
12641e8db6e2SBrian Feldman 	char *oldpath, *newpath;
1265bc5531deSDag-Erling Smørgrav 	int r, status;
12661e8db6e2SBrian Feldman 
1267bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1268bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1269bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1270bc5531deSDag-Erling Smørgrav 
1271761efaa7SDag-Erling Smørgrav 	debug3("request %u: symlink", id);
1272761efaa7SDag-Erling Smørgrav 	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1273d0c8c0bcSDag-Erling Smørgrav 	/* this will fail if 'newpath' exists */
1274bc5531deSDag-Erling Smørgrav 	r = symlink(oldpath, newpath);
1275bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
12761e8db6e2SBrian Feldman 	send_status(id, status);
1277e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1278e4a9863fSDag-Erling Smørgrav 	free(newpath);
12791e8db6e2SBrian Feldman }
12801e8db6e2SBrian Feldman 
1281ae1f160dSDag-Erling Smørgrav static void
1282d4af9e69SDag-Erling Smørgrav process_extended_posix_rename(u_int32_t id)
1283d4af9e69SDag-Erling Smørgrav {
1284d4af9e69SDag-Erling Smørgrav 	char *oldpath, *newpath;
1285bc5531deSDag-Erling Smørgrav 	int r, status;
1286d4af9e69SDag-Erling Smørgrav 
1287bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1288bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1289bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1290bc5531deSDag-Erling Smørgrav 
1291d4af9e69SDag-Erling Smørgrav 	debug3("request %u: posix-rename", id);
1292d4af9e69SDag-Erling Smørgrav 	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1293bc5531deSDag-Erling Smørgrav 	r = rename(oldpath, newpath);
1294bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1295b15c8340SDag-Erling Smørgrav 	send_status(id, status);
1296e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1297e4a9863fSDag-Erling Smørgrav 	free(newpath);
1298d4af9e69SDag-Erling Smørgrav }
1299d4af9e69SDag-Erling Smørgrav 
1300d4af9e69SDag-Erling Smørgrav static void
1301d4af9e69SDag-Erling Smørgrav process_extended_statvfs(u_int32_t id)
1302d4af9e69SDag-Erling Smørgrav {
1303d4af9e69SDag-Erling Smørgrav 	char *path;
1304d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1305bc5531deSDag-Erling Smørgrav 	int r;
1306d4af9e69SDag-Erling Smørgrav 
1307bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1308bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1309f7167e0eSDag-Erling Smørgrav 	debug3("request %u: statvfs", id);
1310f7167e0eSDag-Erling Smørgrav 	logit("statvfs \"%s\"", path);
1311d4af9e69SDag-Erling Smørgrav 
1312d4af9e69SDag-Erling Smørgrav 	if (statvfs(path, &st) != 0)
1313d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1314d4af9e69SDag-Erling Smørgrav 	else
1315d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1316e4a9863fSDag-Erling Smørgrav         free(path);
1317d4af9e69SDag-Erling Smørgrav }
1318d4af9e69SDag-Erling Smørgrav 
1319d4af9e69SDag-Erling Smørgrav static void
1320d4af9e69SDag-Erling Smørgrav process_extended_fstatvfs(u_int32_t id)
1321d4af9e69SDag-Erling Smørgrav {
1322bc5531deSDag-Erling Smørgrav 	int r, handle, fd;
1323d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1324d4af9e69SDag-Erling Smørgrav 
1325bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1326bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1327d4af9e69SDag-Erling Smørgrav 	debug("request %u: fstatvfs \"%s\" (handle %u)",
1328d4af9e69SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
1329d4af9e69SDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0) {
1330d4af9e69SDag-Erling Smørgrav 		send_status(id, SSH2_FX_FAILURE);
1331d4af9e69SDag-Erling Smørgrav 		return;
1332d4af9e69SDag-Erling Smørgrav 	}
1333d4af9e69SDag-Erling Smørgrav 	if (fstatvfs(fd, &st) != 0)
1334d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1335d4af9e69SDag-Erling Smørgrav 	else
1336d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1337d4af9e69SDag-Erling Smørgrav }
1338d4af9e69SDag-Erling Smørgrav 
1339d4af9e69SDag-Erling Smørgrav static void
13404a421b63SDag-Erling Smørgrav process_extended_hardlink(u_int32_t id)
13414a421b63SDag-Erling Smørgrav {
13424a421b63SDag-Erling Smørgrav 	char *oldpath, *newpath;
1343bc5531deSDag-Erling Smørgrav 	int r, status;
13444a421b63SDag-Erling Smørgrav 
1345bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1346bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1347bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1348bc5531deSDag-Erling Smørgrav 
13494a421b63SDag-Erling Smørgrav 	debug3("request %u: hardlink", id);
13504a421b63SDag-Erling Smørgrav 	logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
1351bc5531deSDag-Erling Smørgrav 	r = link(oldpath, newpath);
1352bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
13534a421b63SDag-Erling Smørgrav 	send_status(id, status);
1354e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1355e4a9863fSDag-Erling Smørgrav 	free(newpath);
13564a421b63SDag-Erling Smørgrav }
13574a421b63SDag-Erling Smørgrav 
13584a421b63SDag-Erling Smørgrav static void
1359f7167e0eSDag-Erling Smørgrav process_extended_fsync(u_int32_t id)
13601e8db6e2SBrian Feldman {
1361bc5531deSDag-Erling Smørgrav 	int handle, fd, r, status = SSH2_FX_OP_UNSUPPORTED;
13621e8db6e2SBrian Feldman 
1363bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1364bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1365f7167e0eSDag-Erling Smørgrav 	debug3("request %u: fsync (handle %u)", id, handle);
1366f7167e0eSDag-Erling Smørgrav 	verbose("fsync \"%s\"", handle_to_name(handle));
1367f7167e0eSDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0)
1368f7167e0eSDag-Erling Smørgrav 		status = SSH2_FX_NO_SUCH_FILE;
1369f7167e0eSDag-Erling Smørgrav 	else if (handle_is_ok(handle, HANDLE_FILE)) {
1370bc5531deSDag-Erling Smørgrav 		r = fsync(fd);
1371bc5531deSDag-Erling Smørgrav 		status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1372f7167e0eSDag-Erling Smørgrav 	}
1373f7167e0eSDag-Erling Smørgrav 	send_status(id, status);
1374f7167e0eSDag-Erling Smørgrav }
1375f7167e0eSDag-Erling Smørgrav 
1376f7167e0eSDag-Erling Smørgrav static void
1377f7167e0eSDag-Erling Smørgrav process_extended(u_int32_t id)
1378f7167e0eSDag-Erling Smørgrav {
1379f7167e0eSDag-Erling Smørgrav 	char *request;
1380bc5531deSDag-Erling Smørgrav 	int i, r;
1381f7167e0eSDag-Erling Smørgrav 
1382bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &request, NULL)) != 0)
1383bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1384f7167e0eSDag-Erling Smørgrav 	for (i = 0; extended_handlers[i].handler != NULL; i++) {
1385f7167e0eSDag-Erling Smørgrav 		if (strcmp(request, extended_handlers[i].ext_name) == 0) {
1386f7167e0eSDag-Erling Smørgrav 			if (!request_permitted(&extended_handlers[i]))
1387f7167e0eSDag-Erling Smørgrav 				send_status(id, SSH2_FX_PERMISSION_DENIED);
1388d4af9e69SDag-Erling Smørgrav 			else
1389f7167e0eSDag-Erling Smørgrav 				extended_handlers[i].handler(id);
1390f7167e0eSDag-Erling Smørgrav 			break;
1391f7167e0eSDag-Erling Smørgrav 		}
1392f7167e0eSDag-Erling Smørgrav 	}
1393f7167e0eSDag-Erling Smørgrav 	if (extended_handlers[i].handler == NULL) {
1394f7167e0eSDag-Erling Smørgrav 		error("Unknown extended request \"%.100s\"", request);
13951e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
1396f7167e0eSDag-Erling Smørgrav 	}
1397e4a9863fSDag-Erling Smørgrav 	free(request);
13981e8db6e2SBrian Feldman }
1399b66f2d16SKris Kennaway 
1400b66f2d16SKris Kennaway /* stolen from ssh-agent */
1401b66f2d16SKris Kennaway 
1402ae1f160dSDag-Erling Smørgrav static void
1403b66f2d16SKris Kennaway process(void)
1404b66f2d16SKris Kennaway {
1405bc5531deSDag-Erling Smørgrav 	u_int msg_len;
1406bc5531deSDag-Erling Smørgrav 	u_int buf_len;
1407bc5531deSDag-Erling Smørgrav 	u_int consumed;
1408bc5531deSDag-Erling Smørgrav 	u_char type;
1409bc5531deSDag-Erling Smørgrav 	const u_char *cp;
1410bc5531deSDag-Erling Smørgrav 	int i, r;
1411f7167e0eSDag-Erling Smørgrav 	u_int32_t id;
1412b66f2d16SKris Kennaway 
1413bc5531deSDag-Erling Smørgrav 	buf_len = sshbuf_len(iqueue);
1414545d5ecaSDag-Erling Smørgrav 	if (buf_len < 5)
1415b66f2d16SKris Kennaway 		return;		/* Incomplete message. */
1416bc5531deSDag-Erling Smørgrav 	cp = sshbuf_ptr(iqueue);
1417761efaa7SDag-Erling Smørgrav 	msg_len = get_u32(cp);
1418021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1419761efaa7SDag-Erling Smørgrav 		error("bad message from %s local user %s",
1420761efaa7SDag-Erling Smørgrav 		    client_addr, pw->pw_name);
1421d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(11);
1422b66f2d16SKris Kennaway 	}
1423545d5ecaSDag-Erling Smørgrav 	if (buf_len < msg_len + 4)
1424b66f2d16SKris Kennaway 		return;
1425bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_consume(iqueue, 4)) != 0)
1426bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1427545d5ecaSDag-Erling Smørgrav 	buf_len -= 4;
1428bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(iqueue, &type)) != 0)
1429bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1430f7167e0eSDag-Erling Smørgrav 
1431b66f2d16SKris Kennaway 	switch (type) {
14321e8db6e2SBrian Feldman 	case SSH2_FXP_INIT:
1433b66f2d16SKris Kennaway 		process_init();
1434f7167e0eSDag-Erling Smørgrav 		init_done = 1;
14351e8db6e2SBrian Feldman 		break;
14361e8db6e2SBrian Feldman 	case SSH2_FXP_EXTENDED:
1437f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1438f7167e0eSDag-Erling Smørgrav 			fatal("Received extended request before init");
1439bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
1440bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
1441f7167e0eSDag-Erling Smørgrav 		process_extended(id);
14421e8db6e2SBrian Feldman 		break;
1443b66f2d16SKris Kennaway 	default:
1444f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1445f7167e0eSDag-Erling Smørgrav 			fatal("Received %u request before init", type);
1446bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
1447bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
1448f7167e0eSDag-Erling Smørgrav 		for (i = 0; handlers[i].handler != NULL; i++) {
1449f7167e0eSDag-Erling Smørgrav 			if (type == handlers[i].type) {
1450f7167e0eSDag-Erling Smørgrav 				if (!request_permitted(&handlers[i])) {
1451f7167e0eSDag-Erling Smørgrav 					send_status(id,
1452f7167e0eSDag-Erling Smørgrav 					    SSH2_FX_PERMISSION_DENIED);
1453f7167e0eSDag-Erling Smørgrav 				} else {
1454f7167e0eSDag-Erling Smørgrav 					handlers[i].handler(id);
1455f7167e0eSDag-Erling Smørgrav 				}
1456b66f2d16SKris Kennaway 				break;
1457b66f2d16SKris Kennaway 			}
1458f7167e0eSDag-Erling Smørgrav 		}
1459f7167e0eSDag-Erling Smørgrav 		if (handlers[i].handler == NULL)
1460f7167e0eSDag-Erling Smørgrav 			error("Unknown message %u", type);
1461f7167e0eSDag-Erling Smørgrav 	}
1462545d5ecaSDag-Erling Smørgrav 	/* discard the remaining bytes from the current packet */
1463bc5531deSDag-Erling Smørgrav 	if (buf_len < sshbuf_len(iqueue)) {
1464d4af9e69SDag-Erling Smørgrav 		error("iqueue grew unexpectedly");
1465d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1466d4af9e69SDag-Erling Smørgrav 	}
1467bc5531deSDag-Erling Smørgrav 	consumed = buf_len - sshbuf_len(iqueue);
1468d4af9e69SDag-Erling Smørgrav 	if (msg_len < consumed) {
1469f7167e0eSDag-Erling Smørgrav 		error("msg_len %u < consumed %u", msg_len, consumed);
1470d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1471d4af9e69SDag-Erling Smørgrav 	}
1472bc5531deSDag-Erling Smørgrav 	if (msg_len > consumed &&
1473bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_consume(iqueue, msg_len - consumed)) != 0)
1474bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1475b66f2d16SKris Kennaway }
1476b66f2d16SKris Kennaway 
1477761efaa7SDag-Erling Smørgrav /* Cleanup handler that logs active handles upon normal exit */
1478761efaa7SDag-Erling Smørgrav void
1479d4af9e69SDag-Erling Smørgrav sftp_server_cleanup_exit(int i)
1480761efaa7SDag-Erling Smørgrav {
1481761efaa7SDag-Erling Smørgrav 	if (pw != NULL && client_addr != NULL) {
1482761efaa7SDag-Erling Smørgrav 		handle_log_exit();
1483761efaa7SDag-Erling Smørgrav 		logit("session closed for local user %s from [%s]",
1484761efaa7SDag-Erling Smørgrav 		    pw->pw_name, client_addr);
1485761efaa7SDag-Erling Smørgrav 	}
1486761efaa7SDag-Erling Smørgrav 	_exit(i);
1487761efaa7SDag-Erling Smørgrav }
1488761efaa7SDag-Erling Smørgrav 
1489761efaa7SDag-Erling Smørgrav static void
1490d4af9e69SDag-Erling Smørgrav sftp_server_usage(void)
1491761efaa7SDag-Erling Smørgrav {
1492761efaa7SDag-Erling Smørgrav 	extern char *__progname;
1493761efaa7SDag-Erling Smørgrav 
1494761efaa7SDag-Erling Smørgrav 	fprintf(stderr,
14956888a9beSDag-Erling Smørgrav 	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
1496f7167e0eSDag-Erling Smørgrav 	    "[-l log_level]\n\t[-P blacklisted_requests] "
1497f7167e0eSDag-Erling Smørgrav 	    "[-p whitelisted_requests] [-u umask]\n"
1498f7167e0eSDag-Erling Smørgrav 	    "       %s -Q protocol_feature\n",
1499f7167e0eSDag-Erling Smørgrav 	    __progname, __progname);
1500761efaa7SDag-Erling Smørgrav 	exit(1);
1501761efaa7SDag-Erling Smørgrav }
1502761efaa7SDag-Erling Smørgrav 
1503b66f2d16SKris Kennaway int
1504d4af9e69SDag-Erling Smørgrav sftp_server_main(int argc, char **argv, struct passwd *user_pw)
1505b66f2d16SKris Kennaway {
15061e8db6e2SBrian Feldman 	fd_set *rset, *wset;
1507bc5531deSDag-Erling Smørgrav 	int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0;
15081e8db6e2SBrian Feldman 	ssize_t len, olen, set_size;
1509761efaa7SDag-Erling Smørgrav 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
15106888a9beSDag-Erling Smørgrav 	char *cp, *homedir = NULL, buf[4*4096];
15114a421b63SDag-Erling Smørgrav 	long mask;
1512761efaa7SDag-Erling Smørgrav 
1513761efaa7SDag-Erling Smørgrav 	extern char *optarg;
1514761efaa7SDag-Erling Smørgrav 	extern char *__progname;
15151e8db6e2SBrian Feldman 
1516*acc1a9efSDag-Erling Smørgrav 	ssh_malloc_init();	/* must be called before any mallocs */
1517761efaa7SDag-Erling Smørgrav 	__progname = ssh_get_progname(argv[0]);
1518761efaa7SDag-Erling Smørgrav 	log_init(__progname, log_level, log_facility, log_stderr);
1519b66f2d16SKris Kennaway 
15206888a9beSDag-Erling Smørgrav 	pw = pwcopy(user_pw);
15216888a9beSDag-Erling Smørgrav 
1522f7167e0eSDag-Erling Smørgrav 	while (!skipargs && (ch = getopt(argc, argv,
1523f7167e0eSDag-Erling Smørgrav 	    "d:f:l:P:p:Q:u:cehR")) != -1) {
1524761efaa7SDag-Erling Smørgrav 		switch (ch) {
1525f7167e0eSDag-Erling Smørgrav 		case 'Q':
1526f7167e0eSDag-Erling Smørgrav 			if (strcasecmp(optarg, "requests") != 0) {
1527f7167e0eSDag-Erling Smørgrav 				fprintf(stderr, "Invalid query type\n");
1528f7167e0eSDag-Erling Smørgrav 				exit(1);
1529f7167e0eSDag-Erling Smørgrav 			}
1530f7167e0eSDag-Erling Smørgrav 			for (i = 0; handlers[i].handler != NULL; i++)
1531f7167e0eSDag-Erling Smørgrav 				printf("%s\n", handlers[i].name);
1532f7167e0eSDag-Erling Smørgrav 			for (i = 0; extended_handlers[i].handler != NULL; i++)
1533f7167e0eSDag-Erling Smørgrav 				printf("%s\n", extended_handlers[i].name);
1534f7167e0eSDag-Erling Smørgrav 			exit(0);
1535f7167e0eSDag-Erling Smørgrav 			break;
1536b15c8340SDag-Erling Smørgrav 		case 'R':
1537b15c8340SDag-Erling Smørgrav 			readonly = 1;
1538b15c8340SDag-Erling Smørgrav 			break;
1539761efaa7SDag-Erling Smørgrav 		case 'c':
1540761efaa7SDag-Erling Smørgrav 			/*
1541761efaa7SDag-Erling Smørgrav 			 * Ignore all arguments if we are invoked as a
1542761efaa7SDag-Erling Smørgrav 			 * shell using "sftp-server -c command"
1543761efaa7SDag-Erling Smørgrav 			 */
1544761efaa7SDag-Erling Smørgrav 			skipargs = 1;
1545761efaa7SDag-Erling Smørgrav 			break;
1546761efaa7SDag-Erling Smørgrav 		case 'e':
1547761efaa7SDag-Erling Smørgrav 			log_stderr = 1;
1548761efaa7SDag-Erling Smørgrav 			break;
1549761efaa7SDag-Erling Smørgrav 		case 'l':
1550761efaa7SDag-Erling Smørgrav 			log_level = log_level_number(optarg);
1551761efaa7SDag-Erling Smørgrav 			if (log_level == SYSLOG_LEVEL_NOT_SET)
1552761efaa7SDag-Erling Smørgrav 				error("Invalid log level \"%s\"", optarg);
1553761efaa7SDag-Erling Smørgrav 			break;
1554761efaa7SDag-Erling Smørgrav 		case 'f':
1555761efaa7SDag-Erling Smørgrav 			log_facility = log_facility_number(optarg);
1556d4af9e69SDag-Erling Smørgrav 			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1557761efaa7SDag-Erling Smørgrav 				error("Invalid log facility \"%s\"", optarg);
1558761efaa7SDag-Erling Smørgrav 			break;
15596888a9beSDag-Erling Smørgrav 		case 'd':
15606888a9beSDag-Erling Smørgrav 			cp = tilde_expand_filename(optarg, user_pw->pw_uid);
15616888a9beSDag-Erling Smørgrav 			homedir = percent_expand(cp, "d", user_pw->pw_dir,
15626888a9beSDag-Erling Smørgrav 			    "u", user_pw->pw_name, (char *)NULL);
15636888a9beSDag-Erling Smørgrav 			free(cp);
15646888a9beSDag-Erling Smørgrav 			break;
1565f7167e0eSDag-Erling Smørgrav 		case 'p':
1566f7167e0eSDag-Erling Smørgrav 			if (request_whitelist != NULL)
1567f7167e0eSDag-Erling Smørgrav 				fatal("Permitted requests already set");
1568f7167e0eSDag-Erling Smørgrav 			request_whitelist = xstrdup(optarg);
1569f7167e0eSDag-Erling Smørgrav 			break;
1570f7167e0eSDag-Erling Smørgrav 		case 'P':
1571f7167e0eSDag-Erling Smørgrav 			if (request_blacklist != NULL)
1572f7167e0eSDag-Erling Smørgrav 				fatal("Refused requests already set");
1573f7167e0eSDag-Erling Smørgrav 			request_blacklist = xstrdup(optarg);
1574f7167e0eSDag-Erling Smørgrav 			break;
1575b15c8340SDag-Erling Smørgrav 		case 'u':
15764a421b63SDag-Erling Smørgrav 			errno = 0;
15774a421b63SDag-Erling Smørgrav 			mask = strtol(optarg, &cp, 8);
15784a421b63SDag-Erling Smørgrav 			if (mask < 0 || mask > 0777 || *cp != '\0' ||
15794a421b63SDag-Erling Smørgrav 			    cp == optarg || (mask == 0 && errno != 0))
15804a421b63SDag-Erling Smørgrav 				fatal("Invalid umask \"%s\"", optarg);
15814a421b63SDag-Erling Smørgrav 			(void)umask((mode_t)mask);
1582b15c8340SDag-Erling Smørgrav 			break;
1583761efaa7SDag-Erling Smørgrav 		case 'h':
1584761efaa7SDag-Erling Smørgrav 		default:
1585d4af9e69SDag-Erling Smørgrav 			sftp_server_usage();
1586761efaa7SDag-Erling Smørgrav 		}
1587761efaa7SDag-Erling Smørgrav 	}
1588761efaa7SDag-Erling Smørgrav 
1589761efaa7SDag-Erling Smørgrav 	log_init(__progname, log_level, log_facility, log_stderr);
1590761efaa7SDag-Erling Smørgrav 
1591a0ee8cc6SDag-Erling Smørgrav #if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
1592a0ee8cc6SDag-Erling Smørgrav 	/*
1593a0ee8cc6SDag-Erling Smørgrav 	 * On Linux, we should try to avoid making /proc/self/{mem,maps}
1594a0ee8cc6SDag-Erling Smørgrav 	 * available to the user so that sftp access doesn't automatically
1595a0ee8cc6SDag-Erling Smørgrav 	 * imply arbitrary code execution access that will break
1596a0ee8cc6SDag-Erling Smørgrav 	 * restricted configurations.
1597a0ee8cc6SDag-Erling Smørgrav 	 */
1598a0ee8cc6SDag-Erling Smørgrav 	if (prctl(PR_SET_DUMPABLE, 0) != 0)
1599a0ee8cc6SDag-Erling Smørgrav 		fatal("unable to make the process undumpable");
1600a0ee8cc6SDag-Erling Smørgrav #endif /* defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) */
1601a0ee8cc6SDag-Erling Smørgrav 
1602*acc1a9efSDag-Erling Smørgrav 	/* Drop any fine-grained privileges we don't need */
1603*acc1a9efSDag-Erling Smørgrav 	platform_pledge_sftp_server();
1604*acc1a9efSDag-Erling Smørgrav 
1605761efaa7SDag-Erling Smørgrav 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1606761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup(cp);
1607d4af9e69SDag-Erling Smørgrav 		if ((cp = strchr(client_addr, ' ')) == NULL) {
1608d4af9e69SDag-Erling Smørgrav 			error("Malformed SSH_CONNECTION variable: \"%s\"",
1609761efaa7SDag-Erling Smørgrav 			    getenv("SSH_CONNECTION"));
1610d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(255);
1611d4af9e69SDag-Erling Smørgrav 		}
1612761efaa7SDag-Erling Smørgrav 		*cp = '\0';
1613761efaa7SDag-Erling Smørgrav 	} else
1614761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup("UNKNOWN");
1615761efaa7SDag-Erling Smørgrav 
1616761efaa7SDag-Erling Smørgrav 	logit("session opened for local user %s from [%s]",
1617761efaa7SDag-Erling Smørgrav 	    pw->pw_name, client_addr);
1618761efaa7SDag-Erling Smørgrav 
1619b15c8340SDag-Erling Smørgrav 	in = STDIN_FILENO;
1620b15c8340SDag-Erling Smørgrav 	out = STDOUT_FILENO;
1621b66f2d16SKris Kennaway 
162283d2307dSDag-Erling Smørgrav #ifdef HAVE_CYGWIN
162383d2307dSDag-Erling Smørgrav 	setmode(in, O_BINARY);
162483d2307dSDag-Erling Smørgrav 	setmode(out, O_BINARY);
162583d2307dSDag-Erling Smørgrav #endif
162683d2307dSDag-Erling Smørgrav 
1627b66f2d16SKris Kennaway 	max = 0;
1628b66f2d16SKris Kennaway 	if (in > max)
1629b66f2d16SKris Kennaway 		max = in;
1630b66f2d16SKris Kennaway 	if (out > max)
1631b66f2d16SKris Kennaway 		max = out;
1632b66f2d16SKris Kennaway 
1633bc5531deSDag-Erling Smørgrav 	if ((iqueue = sshbuf_new()) == NULL)
1634bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1635bc5531deSDag-Erling Smørgrav 	if ((oqueue = sshbuf_new()) == NULL)
1636bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1637b66f2d16SKris Kennaway 
1638*acc1a9efSDag-Erling Smørgrav 	rset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask));
1639*acc1a9efSDag-Erling Smørgrav 	wset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask));
1640b66f2d16SKris Kennaway 
16416888a9beSDag-Erling Smørgrav 	if (homedir != NULL) {
16426888a9beSDag-Erling Smørgrav 		if (chdir(homedir) != 0) {
16436888a9beSDag-Erling Smørgrav 			error("chdir to \"%s\" failed: %s", homedir,
16446888a9beSDag-Erling Smørgrav 			    strerror(errno));
16456888a9beSDag-Erling Smørgrav 		}
16466888a9beSDag-Erling Smørgrav 	}
16476888a9beSDag-Erling Smørgrav 
1648*acc1a9efSDag-Erling Smørgrav 	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
16491e8db6e2SBrian Feldman 	for (;;) {
16501e8db6e2SBrian Feldman 		memset(rset, 0, set_size);
16511e8db6e2SBrian Feldman 		memset(wset, 0, set_size);
16521e8db6e2SBrian Feldman 
1653d4af9e69SDag-Erling Smørgrav 		/*
1654d4af9e69SDag-Erling Smørgrav 		 * Ensure that we can read a full buffer and handle
1655d4af9e69SDag-Erling Smørgrav 		 * the worst-case length packet it can generate,
1656d4af9e69SDag-Erling Smørgrav 		 * otherwise apply backpressure by stopping reads.
1657d4af9e69SDag-Erling Smørgrav 		 */
1658bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 &&
1659bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_check_reserve(oqueue,
1660bc5531deSDag-Erling Smørgrav 		    SFTP_MAX_MSG_LENGTH)) == 0)
16611e8db6e2SBrian Feldman 			FD_SET(in, rset);
1662bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
1663bc5531deSDag-Erling Smørgrav 			fatal("%s: sshbuf_check_reserve failed: %s",
1664bc5531deSDag-Erling Smørgrav 			    __func__, ssh_err(r));
1665d4af9e69SDag-Erling Smørgrav 
1666bc5531deSDag-Erling Smørgrav 		olen = sshbuf_len(oqueue);
1667b66f2d16SKris Kennaway 		if (olen > 0)
16681e8db6e2SBrian Feldman 			FD_SET(out, wset);
1669b66f2d16SKris Kennaway 
16701e8db6e2SBrian Feldman 		if (select(max+1, rset, wset, NULL, NULL) < 0) {
1671b66f2d16SKris Kennaway 			if (errno == EINTR)
1672b66f2d16SKris Kennaway 				continue;
1673761efaa7SDag-Erling Smørgrav 			error("select: %s", strerror(errno));
1674d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(2);
1675b66f2d16SKris Kennaway 		}
1676b66f2d16SKris Kennaway 
1677b66f2d16SKris Kennaway 		/* copy stdin to iqueue */
16781e8db6e2SBrian Feldman 		if (FD_ISSET(in, rset)) {
1679b66f2d16SKris Kennaway 			len = read(in, buf, sizeof buf);
1680b66f2d16SKris Kennaway 			if (len == 0) {
1681b66f2d16SKris Kennaway 				debug("read eof");
1682d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(0);
1683b66f2d16SKris Kennaway 			} else if (len < 0) {
1684761efaa7SDag-Erling Smørgrav 				error("read: %s", strerror(errno));
1685d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(1);
1686bc5531deSDag-Erling Smørgrav 			} else if ((r = sshbuf_put(iqueue, buf, len)) != 0) {
1687bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1688bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1689b66f2d16SKris Kennaway 			}
1690b66f2d16SKris Kennaway 		}
1691b66f2d16SKris Kennaway 		/* send oqueue to stdout */
16921e8db6e2SBrian Feldman 		if (FD_ISSET(out, wset)) {
1693bc5531deSDag-Erling Smørgrav 			len = write(out, sshbuf_ptr(oqueue), olen);
1694b66f2d16SKris Kennaway 			if (len < 0) {
1695761efaa7SDag-Erling Smørgrav 				error("write: %s", strerror(errno));
1696d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(1);
1697bc5531deSDag-Erling Smørgrav 			} else if ((r = sshbuf_consume(oqueue, len)) != 0) {
1698bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1699bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1700b66f2d16SKris Kennaway 			}
1701b66f2d16SKris Kennaway 		}
1702d4af9e69SDag-Erling Smørgrav 
1703d4af9e69SDag-Erling Smørgrav 		/*
1704d4af9e69SDag-Erling Smørgrav 		 * Process requests from client if we can fit the results
1705d4af9e69SDag-Erling Smørgrav 		 * into the output buffer, otherwise stop processing input
1706d4af9e69SDag-Erling Smørgrav 		 * and let the output queue drain.
1707d4af9e69SDag-Erling Smørgrav 		 */
1708bc5531deSDag-Erling Smørgrav 		r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH);
1709bc5531deSDag-Erling Smørgrav 		if (r == 0)
1710b66f2d16SKris Kennaway 			process();
1711bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
1712bc5531deSDag-Erling Smørgrav 			fatal("%s: sshbuf_check_reserve: %s",
1713bc5531deSDag-Erling Smørgrav 			    __func__, ssh_err(r));
1714b66f2d16SKris Kennaway 	}
1715b66f2d16SKris Kennaway }
1716