xref: /freebsd/crypto/openssh/sftp-server.c (revision bc5531debefeb54993d01d4f3c8b33ccbe0b4d95)
1*bc5531deSDag-Erling Smørgrav /* $OpenBSD: sftp-server.c,v 1.105 2015/01/20 23:14:00 deraadt 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 
20*bc5531deSDag-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 <pwd.h>
44761efaa7SDag-Erling Smørgrav #include <time.h>
45761efaa7SDag-Erling Smørgrav #include <unistd.h>
46761efaa7SDag-Erling Smørgrav #include <stdarg.h>
47761efaa7SDag-Erling Smørgrav 
48b66f2d16SKris Kennaway #include "xmalloc.h"
49*bc5531deSDag-Erling Smørgrav #include "sshbuf.h"
50*bc5531deSDag-Erling Smørgrav #include "ssherr.h"
51761efaa7SDag-Erling Smørgrav #include "log.h"
52021d409fSDag-Erling Smørgrav #include "misc.h"
53f7167e0eSDag-Erling Smørgrav #include "match.h"
54761efaa7SDag-Erling Smørgrav #include "uidswap.h"
55b66f2d16SKris Kennaway 
561e8db6e2SBrian Feldman #include "sftp.h"
571e8db6e2SBrian Feldman #include "sftp-common.h"
58b66f2d16SKris Kennaway 
59761efaa7SDag-Erling Smørgrav /* Our verbosity */
60f7167e0eSDag-Erling Smørgrav static LogLevel log_level = SYSLOG_LEVEL_ERROR;
61761efaa7SDag-Erling Smørgrav 
62761efaa7SDag-Erling Smørgrav /* Our client */
63f7167e0eSDag-Erling Smørgrav static struct passwd *pw = NULL;
64f7167e0eSDag-Erling Smørgrav static char *client_addr = NULL;
6583d2307dSDag-Erling Smørgrav 
66b66f2d16SKris Kennaway /* input and output queue */
67*bc5531deSDag-Erling Smørgrav struct sshbuf *iqueue;
68*bc5531deSDag-Erling Smørgrav struct sshbuf *oqueue;
69b66f2d16SKris Kennaway 
701e8db6e2SBrian Feldman /* Version of client */
71f7167e0eSDag-Erling Smørgrav static u_int version;
72f7167e0eSDag-Erling Smørgrav 
73f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_INIT received */
74f7167e0eSDag-Erling Smørgrav static int init_done;
751e8db6e2SBrian Feldman 
76b15c8340SDag-Erling Smørgrav /* Disable writes */
77f7167e0eSDag-Erling Smørgrav static int readonly;
78f7167e0eSDag-Erling Smørgrav 
79f7167e0eSDag-Erling Smørgrav /* Requests that are allowed/denied */
80f7167e0eSDag-Erling Smørgrav static char *request_whitelist, *request_blacklist;
81b15c8340SDag-Erling Smørgrav 
82d95e11bfSDag-Erling Smørgrav /* portable attributes, etc. */
83b66f2d16SKris Kennaway typedef struct Stat Stat;
84b66f2d16SKris Kennaway 
851e8db6e2SBrian Feldman struct Stat {
86b66f2d16SKris Kennaway 	char *name;
87b66f2d16SKris Kennaway 	char *long_name;
88b66f2d16SKris Kennaway 	Attrib attrib;
89b66f2d16SKris Kennaway };
90b66f2d16SKris Kennaway 
91f7167e0eSDag-Erling Smørgrav /* Packet handlers */
92f7167e0eSDag-Erling Smørgrav static void process_open(u_int32_t id);
93f7167e0eSDag-Erling Smørgrav static void process_close(u_int32_t id);
94f7167e0eSDag-Erling Smørgrav static void process_read(u_int32_t id);
95f7167e0eSDag-Erling Smørgrav static void process_write(u_int32_t id);
96f7167e0eSDag-Erling Smørgrav static void process_stat(u_int32_t id);
97f7167e0eSDag-Erling Smørgrav static void process_lstat(u_int32_t id);
98f7167e0eSDag-Erling Smørgrav static void process_fstat(u_int32_t id);
99f7167e0eSDag-Erling Smørgrav static void process_setstat(u_int32_t id);
100f7167e0eSDag-Erling Smørgrav static void process_fsetstat(u_int32_t id);
101f7167e0eSDag-Erling Smørgrav static void process_opendir(u_int32_t id);
102f7167e0eSDag-Erling Smørgrav static void process_readdir(u_int32_t id);
103f7167e0eSDag-Erling Smørgrav static void process_remove(u_int32_t id);
104f7167e0eSDag-Erling Smørgrav static void process_mkdir(u_int32_t id);
105f7167e0eSDag-Erling Smørgrav static void process_rmdir(u_int32_t id);
106f7167e0eSDag-Erling Smørgrav static void process_realpath(u_int32_t id);
107f7167e0eSDag-Erling Smørgrav static void process_rename(u_int32_t id);
108f7167e0eSDag-Erling Smørgrav static void process_readlink(u_int32_t id);
109f7167e0eSDag-Erling Smørgrav static void process_symlink(u_int32_t id);
110f7167e0eSDag-Erling Smørgrav static void process_extended_posix_rename(u_int32_t id);
111f7167e0eSDag-Erling Smørgrav static void process_extended_statvfs(u_int32_t id);
112f7167e0eSDag-Erling Smørgrav static void process_extended_fstatvfs(u_int32_t id);
113f7167e0eSDag-Erling Smørgrav static void process_extended_hardlink(u_int32_t id);
114f7167e0eSDag-Erling Smørgrav static void process_extended_fsync(u_int32_t id);
115f7167e0eSDag-Erling Smørgrav static void process_extended(u_int32_t id);
116f7167e0eSDag-Erling Smørgrav 
117f7167e0eSDag-Erling Smørgrav struct sftp_handler {
118f7167e0eSDag-Erling Smørgrav 	const char *name;	/* user-visible name for fine-grained perms */
119f7167e0eSDag-Erling Smørgrav 	const char *ext_name;	/* extended request name */
120f7167e0eSDag-Erling Smørgrav 	u_int type;		/* packet type, for non extended packets */
121f7167e0eSDag-Erling Smørgrav 	void (*handler)(u_int32_t);
122f7167e0eSDag-Erling Smørgrav 	int does_write;		/* if nonzero, banned for readonly mode */
123f7167e0eSDag-Erling Smørgrav };
124f7167e0eSDag-Erling Smørgrav 
125f7167e0eSDag-Erling Smørgrav struct sftp_handler handlers[] = {
126f7167e0eSDag-Erling Smørgrav 	/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
127f7167e0eSDag-Erling Smørgrav 	{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
128f7167e0eSDag-Erling Smørgrav 	{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
129f7167e0eSDag-Erling Smørgrav 	{ "read", NULL, SSH2_FXP_READ, process_read, 0 },
130f7167e0eSDag-Erling Smørgrav 	{ "write", NULL, SSH2_FXP_WRITE, process_write, 1 },
131f7167e0eSDag-Erling Smørgrav 	{ "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 },
132f7167e0eSDag-Erling Smørgrav 	{ "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 },
133f7167e0eSDag-Erling Smørgrav 	{ "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 },
134f7167e0eSDag-Erling Smørgrav 	{ "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 },
135f7167e0eSDag-Erling Smørgrav 	{ "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 },
136f7167e0eSDag-Erling Smørgrav 	{ "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 },
137f7167e0eSDag-Erling Smørgrav 	{ "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 },
138f7167e0eSDag-Erling Smørgrav 	{ "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 },
139f7167e0eSDag-Erling Smørgrav 	{ "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 },
140f7167e0eSDag-Erling Smørgrav 	{ "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 },
141f7167e0eSDag-Erling Smørgrav 	{ "stat", NULL, SSH2_FXP_STAT, process_stat, 0 },
142f7167e0eSDag-Erling Smørgrav 	{ "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 },
143f7167e0eSDag-Erling Smørgrav 	{ "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 },
144f7167e0eSDag-Erling Smørgrav 	{ "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 },
145f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
146f7167e0eSDag-Erling Smørgrav };
147f7167e0eSDag-Erling Smørgrav 
148f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_EXTENDED submessages */
149f7167e0eSDag-Erling Smørgrav struct sftp_handler extended_handlers[] = {
150f7167e0eSDag-Erling Smørgrav 	{ "posix-rename", "posix-rename@openssh.com", 0,
151f7167e0eSDag-Erling Smørgrav 	   process_extended_posix_rename, 1 },
152f7167e0eSDag-Erling Smørgrav 	{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
153f7167e0eSDag-Erling Smørgrav 	{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
154f7167e0eSDag-Erling Smørgrav 	{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
155f7167e0eSDag-Erling Smørgrav 	{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
156f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
157f7167e0eSDag-Erling Smørgrav };
158f7167e0eSDag-Erling Smørgrav 
159f7167e0eSDag-Erling Smørgrav static int
160f7167e0eSDag-Erling Smørgrav request_permitted(struct sftp_handler *h)
161f7167e0eSDag-Erling Smørgrav {
162f7167e0eSDag-Erling Smørgrav 	char *result;
163f7167e0eSDag-Erling Smørgrav 
164f7167e0eSDag-Erling Smørgrav 	if (readonly && h->does_write) {
165f7167e0eSDag-Erling Smørgrav 		verbose("Refusing %s request in read-only mode", h->name);
166f7167e0eSDag-Erling Smørgrav 		return 0;
167f7167e0eSDag-Erling Smørgrav 	}
168f7167e0eSDag-Erling Smørgrav 	if (request_blacklist != NULL &&
169f7167e0eSDag-Erling Smørgrav 	    ((result = match_list(h->name, request_blacklist, NULL))) != NULL) {
170f7167e0eSDag-Erling Smørgrav 		free(result);
171f7167e0eSDag-Erling Smørgrav 		verbose("Refusing blacklisted %s request", h->name);
172f7167e0eSDag-Erling Smørgrav 		return 0;
173f7167e0eSDag-Erling Smørgrav 	}
174f7167e0eSDag-Erling Smørgrav 	if (request_whitelist != NULL &&
175f7167e0eSDag-Erling Smørgrav 	    ((result = match_list(h->name, request_whitelist, NULL))) != NULL) {
176f7167e0eSDag-Erling Smørgrav 		free(result);
177f7167e0eSDag-Erling Smørgrav 		debug2("Permitting whitelisted %s request", h->name);
178f7167e0eSDag-Erling Smørgrav 		return 1;
179f7167e0eSDag-Erling Smørgrav 	}
180f7167e0eSDag-Erling Smørgrav 	if (request_whitelist != NULL) {
181f7167e0eSDag-Erling Smørgrav 		verbose("Refusing non-whitelisted %s request", h->name);
182f7167e0eSDag-Erling Smørgrav 		return 0;
183f7167e0eSDag-Erling Smørgrav 	}
184f7167e0eSDag-Erling Smørgrav 	return 1;
185f7167e0eSDag-Erling Smørgrav }
186f7167e0eSDag-Erling Smørgrav 
187ae1f160dSDag-Erling Smørgrav static int
188b66f2d16SKris Kennaway errno_to_portable(int unixerrno)
189b66f2d16SKris Kennaway {
190b66f2d16SKris Kennaway 	int ret = 0;
1911e8db6e2SBrian Feldman 
192b66f2d16SKris Kennaway 	switch (unixerrno) {
193b66f2d16SKris Kennaway 	case 0:
1941e8db6e2SBrian Feldman 		ret = SSH2_FX_OK;
195b66f2d16SKris Kennaway 		break;
196b66f2d16SKris Kennaway 	case ENOENT:
197b66f2d16SKris Kennaway 	case ENOTDIR:
198b66f2d16SKris Kennaway 	case EBADF:
199b66f2d16SKris Kennaway 	case ELOOP:
2001e8db6e2SBrian Feldman 		ret = SSH2_FX_NO_SUCH_FILE;
201b66f2d16SKris Kennaway 		break;
202b66f2d16SKris Kennaway 	case EPERM:
203b66f2d16SKris Kennaway 	case EACCES:
204b66f2d16SKris Kennaway 	case EFAULT:
2051e8db6e2SBrian Feldman 		ret = SSH2_FX_PERMISSION_DENIED;
206b66f2d16SKris Kennaway 		break;
207b66f2d16SKris Kennaway 	case ENAMETOOLONG:
208b66f2d16SKris Kennaway 	case EINVAL:
2091e8db6e2SBrian Feldman 		ret = SSH2_FX_BAD_MESSAGE;
210b66f2d16SKris Kennaway 		break;
211d4af9e69SDag-Erling Smørgrav 	case ENOSYS:
212d4af9e69SDag-Erling Smørgrav 		ret = SSH2_FX_OP_UNSUPPORTED;
213d4af9e69SDag-Erling Smørgrav 		break;
214b66f2d16SKris Kennaway 	default:
2151e8db6e2SBrian Feldman 		ret = SSH2_FX_FAILURE;
216b66f2d16SKris Kennaway 		break;
217b66f2d16SKris Kennaway 	}
218b66f2d16SKris Kennaway 	return ret;
219b66f2d16SKris Kennaway }
220b66f2d16SKris Kennaway 
221ae1f160dSDag-Erling Smørgrav static int
222b66f2d16SKris Kennaway flags_from_portable(int pflags)
223b66f2d16SKris Kennaway {
224b66f2d16SKris Kennaway 	int flags = 0;
2251e8db6e2SBrian Feldman 
2261e8db6e2SBrian Feldman 	if ((pflags & SSH2_FXF_READ) &&
2271e8db6e2SBrian Feldman 	    (pflags & SSH2_FXF_WRITE)) {
228b66f2d16SKris Kennaway 		flags = O_RDWR;
2291e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_READ) {
230b66f2d16SKris Kennaway 		flags = O_RDONLY;
2311e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_WRITE) {
232b66f2d16SKris Kennaway 		flags = O_WRONLY;
233b66f2d16SKris Kennaway 	}
234f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
235f7167e0eSDag-Erling Smørgrav 		flags |= O_APPEND;
2361e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_CREAT)
237b66f2d16SKris Kennaway 		flags |= O_CREAT;
2381e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_TRUNC)
239b66f2d16SKris Kennaway 		flags |= O_TRUNC;
2401e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_EXCL)
241b66f2d16SKris Kennaway 		flags |= O_EXCL;
242b66f2d16SKris Kennaway 	return flags;
243b66f2d16SKris Kennaway }
244b66f2d16SKris Kennaway 
245761efaa7SDag-Erling Smørgrav static const char *
246761efaa7SDag-Erling Smørgrav string_from_portable(int pflags)
247761efaa7SDag-Erling Smørgrav {
248761efaa7SDag-Erling Smørgrav 	static char ret[128];
249761efaa7SDag-Erling Smørgrav 
250761efaa7SDag-Erling Smørgrav 	*ret = '\0';
251761efaa7SDag-Erling Smørgrav 
252761efaa7SDag-Erling Smørgrav #define PAPPEND(str)	{				\
253761efaa7SDag-Erling Smørgrav 		if (*ret != '\0')			\
254761efaa7SDag-Erling Smørgrav 			strlcat(ret, ",", sizeof(ret));	\
255761efaa7SDag-Erling Smørgrav 		strlcat(ret, str, sizeof(ret));		\
256761efaa7SDag-Erling Smørgrav 	}
257761efaa7SDag-Erling Smørgrav 
258761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_READ)
259761efaa7SDag-Erling Smørgrav 		PAPPEND("READ")
260761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_WRITE)
261761efaa7SDag-Erling Smørgrav 		PAPPEND("WRITE")
262f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
263f7167e0eSDag-Erling Smørgrav 		PAPPEND("APPEND")
264761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_CREAT)
265761efaa7SDag-Erling Smørgrav 		PAPPEND("CREATE")
266761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_TRUNC)
267761efaa7SDag-Erling Smørgrav 		PAPPEND("TRUNCATE")
268761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_EXCL)
269761efaa7SDag-Erling Smørgrav 		PAPPEND("EXCL")
270761efaa7SDag-Erling Smørgrav 
271761efaa7SDag-Erling Smørgrav 	return ret;
272761efaa7SDag-Erling Smørgrav }
273761efaa7SDag-Erling Smørgrav 
274b66f2d16SKris Kennaway /* handle handles */
275b66f2d16SKris Kennaway 
276b66f2d16SKris Kennaway typedef struct Handle Handle;
277b66f2d16SKris Kennaway struct Handle {
278b66f2d16SKris Kennaway 	int use;
279b66f2d16SKris Kennaway 	DIR *dirp;
280b66f2d16SKris Kennaway 	int fd;
281f7167e0eSDag-Erling Smørgrav 	int flags;
282b66f2d16SKris Kennaway 	char *name;
283761efaa7SDag-Erling Smørgrav 	u_int64_t bytes_read, bytes_write;
284d4af9e69SDag-Erling Smørgrav 	int next_unused;
285b66f2d16SKris Kennaway };
2861e8db6e2SBrian Feldman 
287b66f2d16SKris Kennaway enum {
288b66f2d16SKris Kennaway 	HANDLE_UNUSED,
289b66f2d16SKris Kennaway 	HANDLE_DIR,
290b66f2d16SKris Kennaway 	HANDLE_FILE
291b66f2d16SKris Kennaway };
2921e8db6e2SBrian Feldman 
293d4af9e69SDag-Erling Smørgrav Handle *handles = NULL;
294d4af9e69SDag-Erling Smørgrav u_int num_handles = 0;
295d4af9e69SDag-Erling Smørgrav int first_unused_handle = -1;
296b66f2d16SKris Kennaway 
297d4af9e69SDag-Erling Smørgrav static void handle_unused(int i)
298b66f2d16SKris Kennaway {
299b66f2d16SKris Kennaway 	handles[i].use = HANDLE_UNUSED;
300d4af9e69SDag-Erling Smørgrav 	handles[i].next_unused = first_unused_handle;
301d4af9e69SDag-Erling Smørgrav 	first_unused_handle = i;
302b66f2d16SKris Kennaway }
303b66f2d16SKris Kennaway 
304ae1f160dSDag-Erling Smørgrav static int
305f7167e0eSDag-Erling Smørgrav handle_new(int use, const char *name, int fd, int flags, DIR *dirp)
306b66f2d16SKris Kennaway {
307d4af9e69SDag-Erling Smørgrav 	int i;
3081e8db6e2SBrian Feldman 
309d4af9e69SDag-Erling Smørgrav 	if (first_unused_handle == -1) {
310d4af9e69SDag-Erling Smørgrav 		if (num_handles + 1 <= num_handles)
311d4af9e69SDag-Erling Smørgrav 			return -1;
312d4af9e69SDag-Erling Smørgrav 		num_handles++;
313d4af9e69SDag-Erling Smørgrav 		handles = xrealloc(handles, num_handles, sizeof(Handle));
314d4af9e69SDag-Erling Smørgrav 		handle_unused(num_handles - 1);
315d4af9e69SDag-Erling Smørgrav 	}
316d4af9e69SDag-Erling Smørgrav 
317d4af9e69SDag-Erling Smørgrav 	i = first_unused_handle;
318d4af9e69SDag-Erling Smørgrav 	first_unused_handle = handles[i].next_unused;
319d4af9e69SDag-Erling Smørgrav 
320b66f2d16SKris Kennaway 	handles[i].use = use;
321b66f2d16SKris Kennaway 	handles[i].dirp = dirp;
322b66f2d16SKris Kennaway 	handles[i].fd = fd;
323f7167e0eSDag-Erling Smørgrav 	handles[i].flags = flags;
324d0c8c0bcSDag-Erling Smørgrav 	handles[i].name = xstrdup(name);
325761efaa7SDag-Erling Smørgrav 	handles[i].bytes_read = handles[i].bytes_write = 0;
326d4af9e69SDag-Erling Smørgrav 
327b66f2d16SKris Kennaway 	return i;
328b66f2d16SKris Kennaway }
329b66f2d16SKris Kennaway 
330ae1f160dSDag-Erling Smørgrav static int
331b66f2d16SKris Kennaway handle_is_ok(int i, int type)
332b66f2d16SKris Kennaway {
333d4af9e69SDag-Erling Smørgrav 	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
334b66f2d16SKris Kennaway }
335b66f2d16SKris Kennaway 
336ae1f160dSDag-Erling Smørgrav static int
337*bc5531deSDag-Erling Smørgrav handle_to_string(int handle, u_char **stringp, int *hlenp)
338b66f2d16SKris Kennaway {
339b66f2d16SKris Kennaway 	if (stringp == NULL || hlenp == NULL)
340b66f2d16SKris Kennaway 		return -1;
3411e8db6e2SBrian Feldman 	*stringp = xmalloc(sizeof(int32_t));
342761efaa7SDag-Erling Smørgrav 	put_u32(*stringp, handle);
3431e8db6e2SBrian Feldman 	*hlenp = sizeof(int32_t);
344b66f2d16SKris Kennaway 	return 0;
345b66f2d16SKris Kennaway }
346b66f2d16SKris Kennaway 
347ae1f160dSDag-Erling Smørgrav static int
348*bc5531deSDag-Erling Smørgrav handle_from_string(const u_char *handle, u_int hlen)
349b66f2d16SKris Kennaway {
3501e8db6e2SBrian Feldman 	int val;
3511e8db6e2SBrian Feldman 
3521e8db6e2SBrian Feldman 	if (hlen != sizeof(int32_t))
353b66f2d16SKris Kennaway 		return -1;
354761efaa7SDag-Erling Smørgrav 	val = get_u32(handle);
355b66f2d16SKris Kennaway 	if (handle_is_ok(val, HANDLE_FILE) ||
356b66f2d16SKris Kennaway 	    handle_is_ok(val, HANDLE_DIR))
357b66f2d16SKris Kennaway 		return val;
358b66f2d16SKris Kennaway 	return -1;
359b66f2d16SKris Kennaway }
360b66f2d16SKris Kennaway 
361ae1f160dSDag-Erling Smørgrav static char *
362b66f2d16SKris Kennaway handle_to_name(int handle)
363b66f2d16SKris Kennaway {
364b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR)||
365b66f2d16SKris Kennaway 	    handle_is_ok(handle, HANDLE_FILE))
366b66f2d16SKris Kennaway 		return handles[handle].name;
367b66f2d16SKris Kennaway 	return NULL;
368b66f2d16SKris Kennaway }
369b66f2d16SKris Kennaway 
370ae1f160dSDag-Erling Smørgrav static DIR *
371b66f2d16SKris Kennaway handle_to_dir(int handle)
372b66f2d16SKris Kennaway {
373b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR))
374b66f2d16SKris Kennaway 		return handles[handle].dirp;
375b66f2d16SKris Kennaway 	return NULL;
376b66f2d16SKris Kennaway }
377b66f2d16SKris Kennaway 
378ae1f160dSDag-Erling Smørgrav static int
379b66f2d16SKris Kennaway handle_to_fd(int handle)
380b66f2d16SKris Kennaway {
381b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE))
382b66f2d16SKris Kennaway 		return handles[handle].fd;
383b66f2d16SKris Kennaway 	return -1;
384b66f2d16SKris Kennaway }
385b66f2d16SKris Kennaway 
386f7167e0eSDag-Erling Smørgrav static int
387f7167e0eSDag-Erling Smørgrav handle_to_flags(int handle)
388f7167e0eSDag-Erling Smørgrav {
389f7167e0eSDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
390f7167e0eSDag-Erling Smørgrav 		return handles[handle].flags;
391f7167e0eSDag-Erling Smørgrav 	return 0;
392f7167e0eSDag-Erling Smørgrav }
393f7167e0eSDag-Erling Smørgrav 
394761efaa7SDag-Erling Smørgrav static void
395761efaa7SDag-Erling Smørgrav handle_update_read(int handle, ssize_t bytes)
396761efaa7SDag-Erling Smørgrav {
397761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
398761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_read += bytes;
399761efaa7SDag-Erling Smørgrav }
400761efaa7SDag-Erling Smørgrav 
401761efaa7SDag-Erling Smørgrav static void
402761efaa7SDag-Erling Smørgrav handle_update_write(int handle, ssize_t bytes)
403761efaa7SDag-Erling Smørgrav {
404761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
405761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_write += bytes;
406761efaa7SDag-Erling Smørgrav }
407761efaa7SDag-Erling Smørgrav 
408761efaa7SDag-Erling Smørgrav static u_int64_t
409761efaa7SDag-Erling Smørgrav handle_bytes_read(int handle)
410761efaa7SDag-Erling Smørgrav {
411761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
412761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_read);
413761efaa7SDag-Erling Smørgrav 	return 0;
414761efaa7SDag-Erling Smørgrav }
415761efaa7SDag-Erling Smørgrav 
416761efaa7SDag-Erling Smørgrav static u_int64_t
417761efaa7SDag-Erling Smørgrav handle_bytes_write(int handle)
418761efaa7SDag-Erling Smørgrav {
419761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
420761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_write);
421761efaa7SDag-Erling Smørgrav 	return 0;
422761efaa7SDag-Erling Smørgrav }
423761efaa7SDag-Erling Smørgrav 
424ae1f160dSDag-Erling Smørgrav static int
425b66f2d16SKris Kennaway handle_close(int handle)
426b66f2d16SKris Kennaway {
427b66f2d16SKris Kennaway 	int ret = -1;
4281e8db6e2SBrian Feldman 
429b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE)) {
430b66f2d16SKris Kennaway 		ret = close(handles[handle].fd);
431e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
432d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
433b66f2d16SKris Kennaway 	} else if (handle_is_ok(handle, HANDLE_DIR)) {
434b66f2d16SKris Kennaway 		ret = closedir(handles[handle].dirp);
435e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
436d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
437b66f2d16SKris Kennaway 	} else {
438b66f2d16SKris Kennaway 		errno = ENOENT;
439b66f2d16SKris Kennaway 	}
440b66f2d16SKris Kennaway 	return ret;
441b66f2d16SKris Kennaway }
442b66f2d16SKris Kennaway 
443761efaa7SDag-Erling Smørgrav static void
444761efaa7SDag-Erling Smørgrav handle_log_close(int handle, char *emsg)
445761efaa7SDag-Erling Smørgrav {
446761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE)) {
447761efaa7SDag-Erling Smørgrav 		logit("%s%sclose \"%s\" bytes read %llu written %llu",
448761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
449761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle),
450d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_read(handle),
451d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_write(handle));
452761efaa7SDag-Erling Smørgrav 	} else {
453761efaa7SDag-Erling Smørgrav 		logit("%s%sclosedir \"%s\"",
454761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
455761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle));
456761efaa7SDag-Erling Smørgrav 	}
457761efaa7SDag-Erling Smørgrav }
458761efaa7SDag-Erling Smørgrav 
459761efaa7SDag-Erling Smørgrav static void
460761efaa7SDag-Erling Smørgrav handle_log_exit(void)
461761efaa7SDag-Erling Smørgrav {
462761efaa7SDag-Erling Smørgrav 	u_int i;
463761efaa7SDag-Erling Smørgrav 
464d4af9e69SDag-Erling Smørgrav 	for (i = 0; i < num_handles; i++)
465761efaa7SDag-Erling Smørgrav 		if (handles[i].use != HANDLE_UNUSED)
466761efaa7SDag-Erling Smørgrav 			handle_log_close(i, "forced");
467761efaa7SDag-Erling Smørgrav }
468761efaa7SDag-Erling Smørgrav 
469ae1f160dSDag-Erling Smørgrav static int
470*bc5531deSDag-Erling Smørgrav get_handle(struct sshbuf *queue, int *hp)
471b66f2d16SKris Kennaway {
472*bc5531deSDag-Erling Smørgrav 	u_char *handle;
473*bc5531deSDag-Erling Smørgrav 	int r;
474*bc5531deSDag-Erling Smørgrav 	size_t hlen;
4751e8db6e2SBrian Feldman 
476*bc5531deSDag-Erling Smørgrav 	*hp = -1;
477*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_string(queue, &handle, &hlen)) != 0)
478*bc5531deSDag-Erling Smørgrav 		return r;
4791e8db6e2SBrian Feldman 	if (hlen < 256)
480*bc5531deSDag-Erling Smørgrav 		*hp = handle_from_string(handle, hlen);
481e4a9863fSDag-Erling Smørgrav 	free(handle);
482*bc5531deSDag-Erling Smørgrav 	return 0;
483b66f2d16SKris Kennaway }
484b66f2d16SKris Kennaway 
485b66f2d16SKris Kennaway /* send replies */
486b66f2d16SKris Kennaway 
487ae1f160dSDag-Erling Smørgrav static void
488*bc5531deSDag-Erling Smørgrav send_msg(struct sshbuf *m)
489b66f2d16SKris Kennaway {
490*bc5531deSDag-Erling Smørgrav 	int r;
4911e8db6e2SBrian Feldman 
492*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_stringb(oqueue, m)) != 0)
493*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
494*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(m);
495b66f2d16SKris Kennaway }
496b66f2d16SKris Kennaway 
497761efaa7SDag-Erling Smørgrav static const char *
498761efaa7SDag-Erling Smørgrav status_to_message(u_int32_t status)
499b66f2d16SKris Kennaway {
5001e8db6e2SBrian Feldman 	const char *status_messages[] = {
5011e8db6e2SBrian Feldman 		"Success",			/* SSH_FX_OK */
5021e8db6e2SBrian Feldman 		"End of file",			/* SSH_FX_EOF */
5031e8db6e2SBrian Feldman 		"No such file",			/* SSH_FX_NO_SUCH_FILE */
5041e8db6e2SBrian Feldman 		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
5051e8db6e2SBrian Feldman 		"Failure",			/* SSH_FX_FAILURE */
5061e8db6e2SBrian Feldman 		"Bad message",			/* SSH_FX_BAD_MESSAGE */
5071e8db6e2SBrian Feldman 		"No connection",		/* SSH_FX_NO_CONNECTION */
5081e8db6e2SBrian Feldman 		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
5091e8db6e2SBrian Feldman 		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
5101e8db6e2SBrian Feldman 		"Unknown error"			/* Others */
5111e8db6e2SBrian Feldman 	};
512761efaa7SDag-Erling Smørgrav 	return (status_messages[MIN(status,SSH2_FX_MAX)]);
513761efaa7SDag-Erling Smørgrav }
5141e8db6e2SBrian Feldman 
515761efaa7SDag-Erling Smørgrav static void
516761efaa7SDag-Erling Smørgrav send_status(u_int32_t id, u_int32_t status)
517761efaa7SDag-Erling Smørgrav {
518*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
519*bc5531deSDag-Erling Smørgrav 	int r;
520761efaa7SDag-Erling Smørgrav 
521761efaa7SDag-Erling Smørgrav 	debug3("request %u: sent status %u", id, status);
522761efaa7SDag-Erling Smørgrav 	if (log_level > SYSLOG_LEVEL_VERBOSE ||
523761efaa7SDag-Erling Smørgrav 	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
524761efaa7SDag-Erling Smørgrav 		logit("sent status %s", status_to_message(status));
525*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
526*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
527*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_STATUS)) != 0 ||
528*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
529*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, status)) != 0)
530*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
5311e8db6e2SBrian Feldman 	if (version >= 3) {
532*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_cstring(msg,
533*bc5531deSDag-Erling Smørgrav 		    status_to_message(status))) != 0 ||
534*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, "")) != 0)
535*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5361e8db6e2SBrian Feldman 	}
537*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
538*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
539b66f2d16SKris Kennaway }
540ae1f160dSDag-Erling Smørgrav static void
541*bc5531deSDag-Erling Smørgrav send_data_or_handle(char type, u_int32_t id, const u_char *data, int dlen)
542b66f2d16SKris Kennaway {
543*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
544*bc5531deSDag-Erling Smørgrav 	int r;
5451e8db6e2SBrian Feldman 
546*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
547*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
548*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, type)) != 0 ||
549*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
550*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, data, dlen)) != 0)
551*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
552*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
553*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
554b66f2d16SKris Kennaway }
555b66f2d16SKris Kennaway 
556ae1f160dSDag-Erling Smørgrav static void
557*bc5531deSDag-Erling Smørgrav send_data(u_int32_t id, const u_char *data, int dlen)
558b66f2d16SKris Kennaway {
559761efaa7SDag-Erling Smørgrav 	debug("request %u: sent data len %d", id, dlen);
5601e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
561b66f2d16SKris Kennaway }
562b66f2d16SKris Kennaway 
563ae1f160dSDag-Erling Smørgrav static void
564b66f2d16SKris Kennaway send_handle(u_int32_t id, int handle)
565b66f2d16SKris Kennaway {
566*bc5531deSDag-Erling Smørgrav 	u_char *string;
567b66f2d16SKris Kennaway 	int hlen;
5681e8db6e2SBrian Feldman 
569b66f2d16SKris Kennaway 	handle_to_string(handle, &string, &hlen);
570761efaa7SDag-Erling Smørgrav 	debug("request %u: sent handle handle %d", id, handle);
5711e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
572e4a9863fSDag-Erling Smørgrav 	free(string);
573b66f2d16SKris Kennaway }
574b66f2d16SKris Kennaway 
575ae1f160dSDag-Erling Smørgrav static void
576efcad6b7SDag-Erling Smørgrav send_names(u_int32_t id, int count, const Stat *stats)
577b66f2d16SKris Kennaway {
578*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
579*bc5531deSDag-Erling Smørgrav 	int i, r;
5801e8db6e2SBrian Feldman 
581*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
582*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
583*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_NAME)) != 0 ||
584*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
585*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, count)) != 0)
586*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
587761efaa7SDag-Erling Smørgrav 	debug("request %u: sent names count %d", id, count);
588b66f2d16SKris Kennaway 	for (i = 0; i < count; i++) {
589*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 ||
590*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 ||
591*bc5531deSDag-Erling Smørgrav 		    (r = encode_attrib(msg, &stats[i].attrib)) != 0)
592*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
593b66f2d16SKris Kennaway 	}
594*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
595*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
596b66f2d16SKris Kennaway }
597b66f2d16SKris Kennaway 
598ae1f160dSDag-Erling Smørgrav static void
599efcad6b7SDag-Erling Smørgrav send_attrib(u_int32_t id, const Attrib *a)
600b66f2d16SKris Kennaway {
601*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
602*bc5531deSDag-Erling Smørgrav 	int r;
6031e8db6e2SBrian Feldman 
604761efaa7SDag-Erling Smørgrav 	debug("request %u: sent attrib have 0x%x", id, a->flags);
605*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
606*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
607*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_ATTRS)) != 0 ||
608*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
609*bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, a)) != 0)
610*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
611*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
612*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
613b66f2d16SKris Kennaway }
614b66f2d16SKris Kennaway 
615d4af9e69SDag-Erling Smørgrav static void
616d4af9e69SDag-Erling Smørgrav send_statvfs(u_int32_t id, struct statvfs *st)
617d4af9e69SDag-Erling Smørgrav {
618*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
619d4af9e69SDag-Erling Smørgrav 	u_int64_t flag;
620*bc5531deSDag-Erling Smørgrav 	int r;
621d4af9e69SDag-Erling Smørgrav 
622d4af9e69SDag-Erling Smørgrav 	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
623d4af9e69SDag-Erling Smørgrav 	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
624d4af9e69SDag-Erling Smørgrav 
625*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
626*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
627*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
628*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
629*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bsize)) != 0 ||
630*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_frsize)) != 0 ||
631*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_blocks)) != 0 ||
632*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bfree)) != 0 ||
633*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bavail)) != 0 ||
634*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_files)) != 0 ||
635*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_ffree)) != 0 ||
636*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_favail)) != 0 ||
637*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, FSID_TO_ULONG(st->f_fsid))) != 0 ||
638*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, flag)) != 0 ||
639*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_namemax)) != 0)
640*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
641*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
642*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
643d4af9e69SDag-Erling Smørgrav }
644d4af9e69SDag-Erling Smørgrav 
645b66f2d16SKris Kennaway /* parse incoming */
646b66f2d16SKris Kennaway 
647ae1f160dSDag-Erling Smørgrav static void
648b66f2d16SKris Kennaway process_init(void)
649b66f2d16SKris Kennaway {
650*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
651*bc5531deSDag-Erling Smørgrav 	int r;
652b66f2d16SKris Kennaway 
653*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(iqueue, &version)) != 0)
654*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
655e146993eSDag-Erling Smørgrav 	verbose("received client version %u", version);
656*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
657*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
658*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 ||
659*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0 ||
660d4af9e69SDag-Erling Smørgrav 	    /* POSIX rename extension */
661*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "posix-rename@openssh.com")) != 0 ||
662*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
663d4af9e69SDag-Erling Smørgrav 	    /* statvfs extension */
664*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
665*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
666d4af9e69SDag-Erling Smørgrav 	    /* fstatvfs extension */
667*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
668*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
6694a421b63SDag-Erling Smørgrav 	    /* hardlink extension */
670*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
671*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
672f7167e0eSDag-Erling Smørgrav 	    /* fsync extension */
673*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
674*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
675*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
676*bc5531deSDag-Erling Smørgrav 	send_msg(msg);
677*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
678b66f2d16SKris Kennaway }
679b66f2d16SKris Kennaway 
680ae1f160dSDag-Erling Smørgrav static void
681f7167e0eSDag-Erling Smørgrav process_open(u_int32_t id)
682b66f2d16SKris Kennaway {
683f7167e0eSDag-Erling Smørgrav 	u_int32_t pflags;
684*bc5531deSDag-Erling Smørgrav 	Attrib a;
685b66f2d16SKris Kennaway 	char *name;
686*bc5531deSDag-Erling Smørgrav 	int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE;
687b66f2d16SKris Kennaway 
688*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
689*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */
690*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
691*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
692*bc5531deSDag-Erling Smørgrav 
693761efaa7SDag-Erling Smørgrav 	debug3("request %u: open flags %d", id, pflags);
694b66f2d16SKris Kennaway 	flags = flags_from_portable(pflags);
695*bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666;
696761efaa7SDag-Erling Smørgrav 	logit("open \"%s\" flags %s mode 0%o",
697761efaa7SDag-Erling Smørgrav 	    name, string_from_portable(pflags), mode);
698b15c8340SDag-Erling Smørgrav 	if (readonly &&
699f7167e0eSDag-Erling Smørgrav 	    ((flags & O_ACCMODE) == O_WRONLY ||
700f7167e0eSDag-Erling Smørgrav 	    (flags & O_ACCMODE) == O_RDWR)) {
701f7167e0eSDag-Erling Smørgrav 		verbose("Refusing open request in read-only mode");
702b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_PERMISSION_DENIED;
703f7167e0eSDag-Erling Smørgrav 	} else {
704b66f2d16SKris Kennaway 		fd = open(name, flags, mode);
705b66f2d16SKris Kennaway 		if (fd < 0) {
706b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
707b66f2d16SKris Kennaway 		} else {
708f7167e0eSDag-Erling Smørgrav 			handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
709b66f2d16SKris Kennaway 			if (handle < 0) {
710b66f2d16SKris Kennaway 				close(fd);
711b66f2d16SKris Kennaway 			} else {
712b66f2d16SKris Kennaway 				send_handle(id, handle);
7131e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
714b66f2d16SKris Kennaway 			}
715b66f2d16SKris Kennaway 		}
716b15c8340SDag-Erling Smørgrav 	}
7171e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
718b66f2d16SKris Kennaway 		send_status(id, status);
719e4a9863fSDag-Erling Smørgrav 	free(name);
720b66f2d16SKris Kennaway }
721b66f2d16SKris Kennaway 
722ae1f160dSDag-Erling Smørgrav static void
723f7167e0eSDag-Erling Smørgrav process_close(u_int32_t id)
724b66f2d16SKris Kennaway {
725*bc5531deSDag-Erling Smørgrav 	int r, handle, ret, status = SSH2_FX_FAILURE;
726b66f2d16SKris Kennaway 
727*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
728*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
729*bc5531deSDag-Erling Smørgrav 
730761efaa7SDag-Erling Smørgrav 	debug3("request %u: close handle %u", id, handle);
731761efaa7SDag-Erling Smørgrav 	handle_log_close(handle, NULL);
732b66f2d16SKris Kennaway 	ret = handle_close(handle);
7331e8db6e2SBrian Feldman 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
734b66f2d16SKris Kennaway 	send_status(id, status);
735b66f2d16SKris Kennaway }
736b66f2d16SKris Kennaway 
737ae1f160dSDag-Erling Smørgrav static void
738f7167e0eSDag-Erling Smørgrav process_read(u_int32_t id)
739b66f2d16SKris Kennaway {
740*bc5531deSDag-Erling Smørgrav 	u_char buf[64*1024];
741f7167e0eSDag-Erling Smørgrav 	u_int32_t len;
742*bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status = SSH2_FX_FAILURE;
743b66f2d16SKris Kennaway 	u_int64_t off;
744b66f2d16SKris Kennaway 
745*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
746*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
747*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &len)) != 0)
748*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
749b66f2d16SKris Kennaway 
750761efaa7SDag-Erling Smørgrav 	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
751761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
752b66f2d16SKris Kennaway 	if (len > sizeof buf) {
753b66f2d16SKris Kennaway 		len = sizeof buf;
754761efaa7SDag-Erling Smørgrav 		debug2("read change len %d", len);
755b66f2d16SKris Kennaway 	}
756b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
757b66f2d16SKris Kennaway 	if (fd >= 0) {
758b66f2d16SKris Kennaway 		if (lseek(fd, off, SEEK_SET) < 0) {
759b66f2d16SKris Kennaway 			error("process_read: seek failed");
760b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
761b66f2d16SKris Kennaway 		} else {
762b66f2d16SKris Kennaway 			ret = read(fd, buf, len);
763b66f2d16SKris Kennaway 			if (ret < 0) {
764b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
765b66f2d16SKris Kennaway 			} else if (ret == 0) {
7661e8db6e2SBrian Feldman 				status = SSH2_FX_EOF;
767b66f2d16SKris Kennaway 			} else {
768b66f2d16SKris Kennaway 				send_data(id, buf, ret);
7691e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
770761efaa7SDag-Erling Smørgrav 				handle_update_read(handle, ret);
771b66f2d16SKris Kennaway 			}
772b66f2d16SKris Kennaway 		}
773b66f2d16SKris Kennaway 	}
7741e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
775b66f2d16SKris Kennaway 		send_status(id, status);
776b66f2d16SKris Kennaway }
777b66f2d16SKris Kennaway 
778ae1f160dSDag-Erling Smørgrav static void
779f7167e0eSDag-Erling Smørgrav process_write(u_int32_t id)
780b66f2d16SKris Kennaway {
781b66f2d16SKris Kennaway 	u_int64_t off;
782*bc5531deSDag-Erling Smørgrav 	size_t len;
783*bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status;
784*bc5531deSDag-Erling Smørgrav 	u_char *data;
785b66f2d16SKris Kennaway 
786*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
787*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
788*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_string(iqueue, &data, &len)) != 0)
789*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
790b66f2d16SKris Kennaway 
791*bc5531deSDag-Erling Smørgrav 	debug("request %u: write \"%s\" (handle %d) off %llu len %zu",
792761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
793b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
794b15c8340SDag-Erling Smørgrav 
795b15c8340SDag-Erling Smørgrav 	if (fd < 0)
796b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
797b15c8340SDag-Erling Smørgrav 	else {
798f7167e0eSDag-Erling Smørgrav 		if (!(handle_to_flags(handle) & O_APPEND) &&
799f7167e0eSDag-Erling Smørgrav 				lseek(fd, off, SEEK_SET) < 0) {
800b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
801b66f2d16SKris Kennaway 			error("process_write: seek failed");
802b66f2d16SKris Kennaway 		} else {
803b66f2d16SKris Kennaway /* XXX ATOMICIO ? */
804b66f2d16SKris Kennaway 			ret = write(fd, data, len);
805043840dfSDag-Erling Smørgrav 			if (ret < 0) {
806b66f2d16SKris Kennaway 				error("process_write: write failed");
807b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
808043840dfSDag-Erling Smørgrav 			} else if ((size_t)ret == len) {
8091e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
810761efaa7SDag-Erling Smørgrav 				handle_update_write(handle, ret);
811b66f2d16SKris Kennaway 			} else {
812761efaa7SDag-Erling Smørgrav 				debug2("nothing at all written");
813b15c8340SDag-Erling Smørgrav 				status = SSH2_FX_FAILURE;
814b66f2d16SKris Kennaway 			}
815b66f2d16SKris Kennaway 		}
816b66f2d16SKris Kennaway 	}
817b66f2d16SKris Kennaway 	send_status(id, status);
818e4a9863fSDag-Erling Smørgrav 	free(data);
819b66f2d16SKris Kennaway }
820b66f2d16SKris Kennaway 
821ae1f160dSDag-Erling Smørgrav static void
822f7167e0eSDag-Erling Smørgrav process_do_stat(u_int32_t id, int do_lstat)
823b66f2d16SKris Kennaway {
8241e8db6e2SBrian Feldman 	Attrib a;
825b66f2d16SKris Kennaway 	struct stat st;
826b66f2d16SKris Kennaway 	char *name;
827*bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
828b66f2d16SKris Kennaway 
829*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
830*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
831*bc5531deSDag-Erling Smørgrav 
832761efaa7SDag-Erling Smørgrav 	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
833761efaa7SDag-Erling Smørgrav 	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
834*bc5531deSDag-Erling Smørgrav 	r = do_lstat ? lstat(name, &st) : stat(name, &st);
835*bc5531deSDag-Erling Smørgrav 	if (r < 0) {
836b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
837b66f2d16SKris Kennaway 	} else {
8381e8db6e2SBrian Feldman 		stat_to_attrib(&st, &a);
8391e8db6e2SBrian Feldman 		send_attrib(id, &a);
8401e8db6e2SBrian Feldman 		status = SSH2_FX_OK;
841b66f2d16SKris Kennaway 	}
8421e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
843b66f2d16SKris Kennaway 		send_status(id, status);
844e4a9863fSDag-Erling Smørgrav 	free(name);
845b66f2d16SKris Kennaway }
846b66f2d16SKris Kennaway 
847ae1f160dSDag-Erling Smørgrav static void
848f7167e0eSDag-Erling Smørgrav process_stat(u_int32_t id)
849b66f2d16SKris Kennaway {
850f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 0);
851b66f2d16SKris Kennaway }
852b66f2d16SKris Kennaway 
853ae1f160dSDag-Erling Smørgrav static void
854f7167e0eSDag-Erling Smørgrav process_lstat(u_int32_t id)
855b66f2d16SKris Kennaway {
856f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 1);
857b66f2d16SKris Kennaway }
858b66f2d16SKris Kennaway 
859ae1f160dSDag-Erling Smørgrav static void
860f7167e0eSDag-Erling Smørgrav process_fstat(u_int32_t id)
861b66f2d16SKris Kennaway {
8621e8db6e2SBrian Feldman 	Attrib a;
863b66f2d16SKris Kennaway 	struct stat st;
864*bc5531deSDag-Erling Smørgrav 	int fd, r, handle, status = SSH2_FX_FAILURE;
865b66f2d16SKris Kennaway 
866*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
867*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
868761efaa7SDag-Erling Smørgrav 	debug("request %u: fstat \"%s\" (handle %u)",
869761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
870b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
871b66f2d16SKris Kennaway 	if (fd >= 0) {
872*bc5531deSDag-Erling Smørgrav 		r = fstat(fd, &st);
873*bc5531deSDag-Erling Smørgrav 		if (r < 0) {
874b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
875b66f2d16SKris Kennaway 		} else {
8761e8db6e2SBrian Feldman 			stat_to_attrib(&st, &a);
8771e8db6e2SBrian Feldman 			send_attrib(id, &a);
8781e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
879b66f2d16SKris Kennaway 		}
880b66f2d16SKris Kennaway 	}
8811e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
882b66f2d16SKris Kennaway 		send_status(id, status);
883b66f2d16SKris Kennaway }
884b66f2d16SKris Kennaway 
885ae1f160dSDag-Erling Smørgrav static struct timeval *
886efcad6b7SDag-Erling Smørgrav attrib_to_tv(const Attrib *a)
887b66f2d16SKris Kennaway {
888b66f2d16SKris Kennaway 	static struct timeval tv[2];
8891e8db6e2SBrian Feldman 
890b66f2d16SKris Kennaway 	tv[0].tv_sec = a->atime;
891b66f2d16SKris Kennaway 	tv[0].tv_usec = 0;
892b66f2d16SKris Kennaway 	tv[1].tv_sec = a->mtime;
893b66f2d16SKris Kennaway 	tv[1].tv_usec = 0;
894b66f2d16SKris Kennaway 	return tv;
895b66f2d16SKris Kennaway }
896b66f2d16SKris Kennaway 
897ae1f160dSDag-Erling Smørgrav static void
898f7167e0eSDag-Erling Smørgrav process_setstat(u_int32_t id)
899b66f2d16SKris Kennaway {
900*bc5531deSDag-Erling Smørgrav 	Attrib a;
901b66f2d16SKris Kennaway 	char *name;
902*bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_OK;
903b66f2d16SKris Kennaway 
904*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
905*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
906*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
907*bc5531deSDag-Erling Smørgrav 
908761efaa7SDag-Erling Smørgrav 	debug("request %u: setstat name \"%s\"", id, name);
909*bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
910d4af9e69SDag-Erling Smørgrav 		logit("set \"%s\" size %llu",
911*bc5531deSDag-Erling Smørgrav 		    name, (unsigned long long)a.size);
912*bc5531deSDag-Erling Smørgrav 		r = truncate(name, a.size);
913*bc5531deSDag-Erling Smørgrav 		if (r == -1)
914ae1f160dSDag-Erling Smørgrav 			status = errno_to_portable(errno);
915ae1f160dSDag-Erling Smørgrav 	}
916*bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
917*bc5531deSDag-Erling Smørgrav 		logit("set \"%s\" mode %04o", name, a.perm);
918*bc5531deSDag-Erling Smørgrav 		r = chmod(name, a.perm & 07777);
919*bc5531deSDag-Erling Smørgrav 		if (r == -1)
920b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
921b66f2d16SKris Kennaway 	}
922*bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
923761efaa7SDag-Erling Smørgrav 		char buf[64];
924*bc5531deSDag-Erling Smørgrav 		time_t t = a.mtime;
925761efaa7SDag-Erling Smørgrav 
926761efaa7SDag-Erling Smørgrav 		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
927761efaa7SDag-Erling Smørgrav 		    localtime(&t));
928761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" modtime %s", name, buf);
929*bc5531deSDag-Erling Smørgrav 		r = utimes(name, attrib_to_tv(&a));
930*bc5531deSDag-Erling Smørgrav 		if (r == -1)
931b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
932b66f2d16SKris Kennaway 	}
933*bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
934761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" owner %lu group %lu", name,
935*bc5531deSDag-Erling Smørgrav 		    (u_long)a.uid, (u_long)a.gid);
936*bc5531deSDag-Erling Smørgrav 		r = chown(name, a.uid, a.gid);
937*bc5531deSDag-Erling Smørgrav 		if (r == -1)
9381e8db6e2SBrian Feldman 			status = errno_to_portable(errno);
9391e8db6e2SBrian Feldman 	}
940b66f2d16SKris Kennaway 	send_status(id, status);
941e4a9863fSDag-Erling Smørgrav 	free(name);
942b66f2d16SKris Kennaway }
943b66f2d16SKris Kennaway 
944ae1f160dSDag-Erling Smørgrav static void
945f7167e0eSDag-Erling Smørgrav process_fsetstat(u_int32_t id)
946b66f2d16SKris Kennaway {
947*bc5531deSDag-Erling Smørgrav 	Attrib a;
948*bc5531deSDag-Erling Smørgrav 	int handle, fd, r;
9491e8db6e2SBrian Feldman 	int status = SSH2_FX_OK;
950b66f2d16SKris Kennaway 
951*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
952*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
953*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
954*bc5531deSDag-Erling Smørgrav 
955761efaa7SDag-Erling Smørgrav 	debug("request %u: fsetstat handle %d", id, handle);
956b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
957b15c8340SDag-Erling Smørgrav 	if (fd < 0)
9581e8db6e2SBrian Feldman 		status = SSH2_FX_FAILURE;
959b15c8340SDag-Erling Smørgrav 	else {
960761efaa7SDag-Erling Smørgrav 		char *name = handle_to_name(handle);
961761efaa7SDag-Erling Smørgrav 
962*bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
963d4af9e69SDag-Erling Smørgrav 			logit("set \"%s\" size %llu",
964*bc5531deSDag-Erling Smørgrav 			    name, (unsigned long long)a.size);
965*bc5531deSDag-Erling Smørgrav 			r = ftruncate(fd, a.size);
966*bc5531deSDag-Erling Smørgrav 			if (r == -1)
967ae1f160dSDag-Erling Smørgrav 				status = errno_to_portable(errno);
968ae1f160dSDag-Erling Smørgrav 		}
969*bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
970*bc5531deSDag-Erling Smørgrav 			logit("set \"%s\" mode %04o", name, a.perm);
97183d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
972*bc5531deSDag-Erling Smørgrav 			r = fchmod(fd, a.perm & 07777);
97383d2307dSDag-Erling Smørgrav #else
974*bc5531deSDag-Erling Smørgrav 			r = chmod(name, a.perm & 07777);
97583d2307dSDag-Erling Smørgrav #endif
976*bc5531deSDag-Erling Smørgrav 			if (r == -1)
977b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
978b66f2d16SKris Kennaway 		}
979*bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
980761efaa7SDag-Erling Smørgrav 			char buf[64];
981*bc5531deSDag-Erling Smørgrav 			time_t t = a.mtime;
982761efaa7SDag-Erling Smørgrav 
983761efaa7SDag-Erling Smørgrav 			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
984761efaa7SDag-Erling Smørgrav 			    localtime(&t));
985761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" modtime %s", name, buf);
98683d2307dSDag-Erling Smørgrav #ifdef HAVE_FUTIMES
987*bc5531deSDag-Erling Smørgrav 			r = futimes(fd, attrib_to_tv(&a));
98883d2307dSDag-Erling Smørgrav #else
989*bc5531deSDag-Erling Smørgrav 			r = utimes(name, attrib_to_tv(&a));
99083d2307dSDag-Erling Smørgrav #endif
991*bc5531deSDag-Erling Smørgrav 			if (r == -1)
992b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
993b66f2d16SKris Kennaway 		}
994*bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
995761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" owner %lu group %lu", name,
996*bc5531deSDag-Erling Smørgrav 			    (u_long)a.uid, (u_long)a.gid);
99783d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHOWN
998*bc5531deSDag-Erling Smørgrav 			r = fchown(fd, a.uid, a.gid);
99983d2307dSDag-Erling Smørgrav #else
1000*bc5531deSDag-Erling Smørgrav 			r = chown(name, a.uid, a.gid);
100183d2307dSDag-Erling Smørgrav #endif
1002*bc5531deSDag-Erling Smørgrav 			if (r == -1)
10031e8db6e2SBrian Feldman 				status = errno_to_portable(errno);
10041e8db6e2SBrian Feldman 		}
1005b66f2d16SKris Kennaway 	}
1006b66f2d16SKris Kennaway 	send_status(id, status);
1007b66f2d16SKris Kennaway }
1008b66f2d16SKris Kennaway 
1009ae1f160dSDag-Erling Smørgrav static void
1010f7167e0eSDag-Erling Smørgrav process_opendir(u_int32_t id)
1011b66f2d16SKris Kennaway {
1012b66f2d16SKris Kennaway 	DIR *dirp = NULL;
1013b66f2d16SKris Kennaway 	char *path;
1014*bc5531deSDag-Erling Smørgrav 	int r, handle, status = SSH2_FX_FAILURE;
1015b66f2d16SKris Kennaway 
1016*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1017*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1018*bc5531deSDag-Erling Smørgrav 
1019761efaa7SDag-Erling Smørgrav 	debug3("request %u: opendir", id);
1020761efaa7SDag-Erling Smørgrav 	logit("opendir \"%s\"", path);
1021b66f2d16SKris Kennaway 	dirp = opendir(path);
1022b66f2d16SKris Kennaway 	if (dirp == NULL) {
1023b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
1024b66f2d16SKris Kennaway 	} else {
1025f7167e0eSDag-Erling Smørgrav 		handle = handle_new(HANDLE_DIR, path, 0, 0, dirp);
1026b66f2d16SKris Kennaway 		if (handle < 0) {
1027b66f2d16SKris Kennaway 			closedir(dirp);
1028b66f2d16SKris Kennaway 		} else {
1029b66f2d16SKris Kennaway 			send_handle(id, handle);
10301e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
1031b66f2d16SKris Kennaway 		}
1032b66f2d16SKris Kennaway 
1033b66f2d16SKris Kennaway 	}
10341e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
1035b66f2d16SKris Kennaway 		send_status(id, status);
1036e4a9863fSDag-Erling Smørgrav 	free(path);
1037b66f2d16SKris Kennaway }
1038b66f2d16SKris Kennaway 
1039ae1f160dSDag-Erling Smørgrav static void
1040f7167e0eSDag-Erling Smørgrav process_readdir(u_int32_t id)
1041b66f2d16SKris Kennaway {
1042b66f2d16SKris Kennaway 	DIR *dirp;
1043b66f2d16SKris Kennaway 	struct dirent *dp;
1044b66f2d16SKris Kennaway 	char *path;
1045*bc5531deSDag-Erling Smørgrav 	int r, handle;
1046b66f2d16SKris Kennaway 
1047*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1048*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1049*bc5531deSDag-Erling Smørgrav 
1050761efaa7SDag-Erling Smørgrav 	debug("request %u: readdir \"%s\" (handle %d)", id,
1051761efaa7SDag-Erling Smørgrav 	    handle_to_name(handle), handle);
1052b66f2d16SKris Kennaway 	dirp = handle_to_dir(handle);
1053b66f2d16SKris Kennaway 	path = handle_to_name(handle);
1054b66f2d16SKris Kennaway 	if (dirp == NULL || path == NULL) {
10551e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_FAILURE);
1056b66f2d16SKris Kennaway 	} else {
1057b66f2d16SKris Kennaway 		struct stat st;
1058*bc5531deSDag-Erling Smørgrav 		char pathname[PATH_MAX];
1059b66f2d16SKris Kennaway 		Stat *stats;
1060b66f2d16SKris Kennaway 		int nstats = 10, count = 0, i;
1061ee21a45fSDag-Erling Smørgrav 
1062761efaa7SDag-Erling Smørgrav 		stats = xcalloc(nstats, sizeof(Stat));
1063b66f2d16SKris Kennaway 		while ((dp = readdir(dirp)) != NULL) {
1064b66f2d16SKris Kennaway 			if (count >= nstats) {
1065b66f2d16SKris Kennaway 				nstats *= 2;
1066761efaa7SDag-Erling Smørgrav 				stats = xrealloc(stats, nstats, sizeof(Stat));
1067b66f2d16SKris Kennaway 			}
1068b66f2d16SKris Kennaway /* XXX OVERFLOW ? */
1069ae1f160dSDag-Erling Smørgrav 			snprintf(pathname, sizeof pathname, "%s%s%s", path,
1070ae1f160dSDag-Erling Smørgrav 			    strcmp(path, "/") ? "/" : "", dp->d_name);
1071b66f2d16SKris Kennaway 			if (lstat(pathname, &st) < 0)
1072b66f2d16SKris Kennaway 				continue;
10731e8db6e2SBrian Feldman 			stat_to_attrib(&st, &(stats[count].attrib));
1074b66f2d16SKris Kennaway 			stats[count].name = xstrdup(dp->d_name);
1075b15c8340SDag-Erling Smørgrav 			stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
1076b66f2d16SKris Kennaway 			count++;
1077b66f2d16SKris Kennaway 			/* send up to 100 entries in one message */
10781e8db6e2SBrian Feldman 			/* XXX check packet size instead */
1079b66f2d16SKris Kennaway 			if (count == 100)
1080b66f2d16SKris Kennaway 				break;
1081b66f2d16SKris Kennaway 		}
10821e8db6e2SBrian Feldman 		if (count > 0) {
1083b66f2d16SKris Kennaway 			send_names(id, count, stats);
1084b66f2d16SKris Kennaway 			for (i = 0; i < count; i++) {
1085e4a9863fSDag-Erling Smørgrav 				free(stats[i].name);
1086e4a9863fSDag-Erling Smørgrav 				free(stats[i].long_name);
1087b66f2d16SKris Kennaway 			}
10881e8db6e2SBrian Feldman 		} else {
10891e8db6e2SBrian Feldman 			send_status(id, SSH2_FX_EOF);
10901e8db6e2SBrian Feldman 		}
1091e4a9863fSDag-Erling Smørgrav 		free(stats);
1092b66f2d16SKris Kennaway 	}
1093b66f2d16SKris Kennaway }
1094b66f2d16SKris Kennaway 
1095ae1f160dSDag-Erling Smørgrav static void
1096f7167e0eSDag-Erling Smørgrav process_remove(u_int32_t id)
1097b66f2d16SKris Kennaway {
1098b66f2d16SKris Kennaway 	char *name;
1099*bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
1100b66f2d16SKris Kennaway 
1101*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
1102*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1103*bc5531deSDag-Erling Smørgrav 
1104761efaa7SDag-Erling Smørgrav 	debug3("request %u: remove", id);
1105761efaa7SDag-Erling Smørgrav 	logit("remove name \"%s\"", name);
1106*bc5531deSDag-Erling Smørgrav 	r = unlink(name);
1107*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1108b66f2d16SKris Kennaway 	send_status(id, status);
1109e4a9863fSDag-Erling Smørgrav 	free(name);
1110b66f2d16SKris Kennaway }
1111b66f2d16SKris Kennaway 
1112ae1f160dSDag-Erling Smørgrav static void
1113f7167e0eSDag-Erling Smørgrav process_mkdir(u_int32_t id)
1114b66f2d16SKris Kennaway {
1115*bc5531deSDag-Erling Smørgrav 	Attrib a;
1116b66f2d16SKris Kennaway 	char *name;
1117*bc5531deSDag-Erling Smørgrav 	int r, mode, status = SSH2_FX_FAILURE;
1118b66f2d16SKris Kennaway 
1119*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
1120*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
1121*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1122*bc5531deSDag-Erling Smørgrav 
1123*bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
1124*bc5531deSDag-Erling Smørgrav 	    a.perm & 07777 : 0777;
1125761efaa7SDag-Erling Smørgrav 	debug3("request %u: mkdir", id);
1126761efaa7SDag-Erling Smørgrav 	logit("mkdir name \"%s\" mode 0%o", name, mode);
1127*bc5531deSDag-Erling Smørgrav 	r = mkdir(name, mode);
1128*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1129b66f2d16SKris Kennaway 	send_status(id, status);
1130e4a9863fSDag-Erling Smørgrav 	free(name);
1131b66f2d16SKris Kennaway }
1132b66f2d16SKris Kennaway 
1133ae1f160dSDag-Erling Smørgrav static void
1134f7167e0eSDag-Erling Smørgrav process_rmdir(u_int32_t id)
1135b66f2d16SKris Kennaway {
1136b66f2d16SKris Kennaway 	char *name;
1137*bc5531deSDag-Erling Smørgrav 	int r, status;
1138b66f2d16SKris Kennaway 
1139*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
1140*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1141*bc5531deSDag-Erling Smørgrav 
1142761efaa7SDag-Erling Smørgrav 	debug3("request %u: rmdir", id);
1143761efaa7SDag-Erling Smørgrav 	logit("rmdir name \"%s\"", name);
1144*bc5531deSDag-Erling Smørgrav 	r = rmdir(name);
1145*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1146b66f2d16SKris Kennaway 	send_status(id, status);
1147e4a9863fSDag-Erling Smørgrav 	free(name);
1148b66f2d16SKris Kennaway }
1149b66f2d16SKris Kennaway 
1150ae1f160dSDag-Erling Smørgrav static void
1151f7167e0eSDag-Erling Smørgrav process_realpath(u_int32_t id)
1152b66f2d16SKris Kennaway {
1153*bc5531deSDag-Erling Smørgrav 	char resolvedname[PATH_MAX];
1154b66f2d16SKris Kennaway 	char *path;
1155*bc5531deSDag-Erling Smørgrav 	int r;
1156b66f2d16SKris Kennaway 
1157*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1158*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1159*bc5531deSDag-Erling Smørgrav 
11601e8db6e2SBrian Feldman 	if (path[0] == '\0') {
1161e4a9863fSDag-Erling Smørgrav 		free(path);
11621e8db6e2SBrian Feldman 		path = xstrdup(".");
11631e8db6e2SBrian Feldman 	}
1164761efaa7SDag-Erling Smørgrav 	debug3("request %u: realpath", id);
1165761efaa7SDag-Erling Smørgrav 	verbose("realpath \"%s\"", path);
1166b66f2d16SKris Kennaway 	if (realpath(path, resolvedname) == NULL) {
1167b66f2d16SKris Kennaway 		send_status(id, errno_to_portable(errno));
1168b66f2d16SKris Kennaway 	} else {
1169b66f2d16SKris Kennaway 		Stat s;
1170b66f2d16SKris Kennaway 		attrib_clear(&s.attrib);
1171b66f2d16SKris Kennaway 		s.name = s.long_name = resolvedname;
1172b66f2d16SKris Kennaway 		send_names(id, 1, &s);
1173b66f2d16SKris Kennaway 	}
1174e4a9863fSDag-Erling Smørgrav 	free(path);
1175b66f2d16SKris Kennaway }
1176b66f2d16SKris Kennaway 
1177ae1f160dSDag-Erling Smørgrav static void
1178f7167e0eSDag-Erling Smørgrav process_rename(u_int32_t id)
1179b66f2d16SKris Kennaway {
1180b66f2d16SKris Kennaway 	char *oldpath, *newpath;
1181*bc5531deSDag-Erling Smørgrav 	int r, status;
1182d0c8c0bcSDag-Erling Smørgrav 	struct stat sb;
1183b66f2d16SKris Kennaway 
1184*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1185*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1186*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1187*bc5531deSDag-Erling Smørgrav 
1188761efaa7SDag-Erling Smørgrav 	debug3("request %u: rename", id);
1189761efaa7SDag-Erling Smørgrav 	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1190d0c8c0bcSDag-Erling Smørgrav 	status = SSH2_FX_FAILURE;
1191f7167e0eSDag-Erling Smørgrav 	if (lstat(oldpath, &sb) == -1)
1192d0c8c0bcSDag-Erling Smørgrav 		status = errno_to_portable(errno);
1193d0c8c0bcSDag-Erling Smørgrav 	else if (S_ISREG(sb.st_mode)) {
1194d0c8c0bcSDag-Erling Smørgrav 		/* Race-free rename of regular files */
1195d74d50a8SDag-Erling Smørgrav 		if (link(oldpath, newpath) == -1) {
11967aee6ffeSDag-Erling Smørgrav 			if (errno == EOPNOTSUPP || errno == ENOSYS
1197d4af9e69SDag-Erling Smørgrav #ifdef EXDEV
1198d4af9e69SDag-Erling Smørgrav 			    || errno == EXDEV
1199d4af9e69SDag-Erling Smørgrav #endif
1200d74d50a8SDag-Erling Smørgrav #ifdef LINK_OPNOTSUPP_ERRNO
1201d74d50a8SDag-Erling Smørgrav 			    || errno == LINK_OPNOTSUPP_ERRNO
1202d74d50a8SDag-Erling Smørgrav #endif
1203d74d50a8SDag-Erling Smørgrav 			    ) {
1204d74d50a8SDag-Erling Smørgrav 				struct stat st;
1205d74d50a8SDag-Erling Smørgrav 
1206d74d50a8SDag-Erling Smørgrav 				/*
1207d74d50a8SDag-Erling Smørgrav 				 * fs doesn't support links, so fall back to
1208d74d50a8SDag-Erling Smørgrav 				 * stat+rename.  This is racy.
1209d74d50a8SDag-Erling Smørgrav 				 */
1210d74d50a8SDag-Erling Smørgrav 				if (stat(newpath, &st) == -1) {
1211d74d50a8SDag-Erling Smørgrav 					if (rename(oldpath, newpath) == -1)
1212d74d50a8SDag-Erling Smørgrav 						status =
1213d74d50a8SDag-Erling Smørgrav 						    errno_to_portable(errno);
1214d74d50a8SDag-Erling Smørgrav 					else
1215d74d50a8SDag-Erling Smørgrav 						status = SSH2_FX_OK;
1216d74d50a8SDag-Erling Smørgrav 				}
1217d74d50a8SDag-Erling Smørgrav 			} else {
1218d0c8c0bcSDag-Erling Smørgrav 				status = errno_to_portable(errno);
1219d74d50a8SDag-Erling Smørgrav 			}
1220d74d50a8SDag-Erling Smørgrav 		} else if (unlink(oldpath) == -1) {
1221d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1222d0c8c0bcSDag-Erling Smørgrav 			/* clean spare link */
1223d0c8c0bcSDag-Erling Smørgrav 			unlink(newpath);
1224d0c8c0bcSDag-Erling Smørgrav 		} else
1225d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
1226d0c8c0bcSDag-Erling Smørgrav 	} else if (stat(newpath, &sb) == -1) {
1227d0c8c0bcSDag-Erling Smørgrav 		if (rename(oldpath, newpath) == -1)
1228d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1229d0c8c0bcSDag-Erling Smørgrav 		else
1230d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
12311e8db6e2SBrian Feldman 	}
1232b66f2d16SKris Kennaway 	send_status(id, status);
1233e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1234e4a9863fSDag-Erling Smørgrav 	free(newpath);
1235b66f2d16SKris Kennaway }
1236b66f2d16SKris Kennaway 
1237ae1f160dSDag-Erling Smørgrav static void
1238f7167e0eSDag-Erling Smørgrav process_readlink(u_int32_t id)
12391e8db6e2SBrian Feldman {
1240*bc5531deSDag-Erling Smørgrav 	int r, len;
1241*bc5531deSDag-Erling Smørgrav 	char buf[PATH_MAX];
12421e8db6e2SBrian Feldman 	char *path;
12431e8db6e2SBrian Feldman 
1244*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1245*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1246*bc5531deSDag-Erling Smørgrav 
1247761efaa7SDag-Erling Smørgrav 	debug3("request %u: readlink", id);
1248761efaa7SDag-Erling Smørgrav 	verbose("readlink \"%s\"", path);
1249d74d50a8SDag-Erling Smørgrav 	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
12501e8db6e2SBrian Feldman 		send_status(id, errno_to_portable(errno));
12511e8db6e2SBrian Feldman 	else {
12521e8db6e2SBrian Feldman 		Stat s;
12531e8db6e2SBrian Feldman 
1254d74d50a8SDag-Erling Smørgrav 		buf[len] = '\0';
12551e8db6e2SBrian Feldman 		attrib_clear(&s.attrib);
1256d74d50a8SDag-Erling Smørgrav 		s.name = s.long_name = buf;
12571e8db6e2SBrian Feldman 		send_names(id, 1, &s);
12581e8db6e2SBrian Feldman 	}
1259e4a9863fSDag-Erling Smørgrav 	free(path);
12601e8db6e2SBrian Feldman }
12611e8db6e2SBrian Feldman 
1262ae1f160dSDag-Erling Smørgrav static void
1263f7167e0eSDag-Erling Smørgrav process_symlink(u_int32_t id)
12641e8db6e2SBrian Feldman {
12651e8db6e2SBrian Feldman 	char *oldpath, *newpath;
1266*bc5531deSDag-Erling Smørgrav 	int r, status;
12671e8db6e2SBrian Feldman 
1268*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1269*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1270*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1271*bc5531deSDag-Erling Smørgrav 
1272761efaa7SDag-Erling Smørgrav 	debug3("request %u: symlink", id);
1273761efaa7SDag-Erling Smørgrav 	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1274d0c8c0bcSDag-Erling Smørgrav 	/* this will fail if 'newpath' exists */
1275*bc5531deSDag-Erling Smørgrav 	r = symlink(oldpath, newpath);
1276*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
12771e8db6e2SBrian Feldman 	send_status(id, status);
1278e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1279e4a9863fSDag-Erling Smørgrav 	free(newpath);
12801e8db6e2SBrian Feldman }
12811e8db6e2SBrian Feldman 
1282ae1f160dSDag-Erling Smørgrav static void
1283d4af9e69SDag-Erling Smørgrav process_extended_posix_rename(u_int32_t id)
1284d4af9e69SDag-Erling Smørgrav {
1285d4af9e69SDag-Erling Smørgrav 	char *oldpath, *newpath;
1286*bc5531deSDag-Erling Smørgrav 	int r, status;
1287d4af9e69SDag-Erling Smørgrav 
1288*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1289*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1290*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1291*bc5531deSDag-Erling Smørgrav 
1292d4af9e69SDag-Erling Smørgrav 	debug3("request %u: posix-rename", id);
1293d4af9e69SDag-Erling Smørgrav 	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1294*bc5531deSDag-Erling Smørgrav 	r = rename(oldpath, newpath);
1295*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1296b15c8340SDag-Erling Smørgrav 	send_status(id, status);
1297e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1298e4a9863fSDag-Erling Smørgrav 	free(newpath);
1299d4af9e69SDag-Erling Smørgrav }
1300d4af9e69SDag-Erling Smørgrav 
1301d4af9e69SDag-Erling Smørgrav static void
1302d4af9e69SDag-Erling Smørgrav process_extended_statvfs(u_int32_t id)
1303d4af9e69SDag-Erling Smørgrav {
1304d4af9e69SDag-Erling Smørgrav 	char *path;
1305d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1306*bc5531deSDag-Erling Smørgrav 	int r;
1307d4af9e69SDag-Erling Smørgrav 
1308*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
1309*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1310f7167e0eSDag-Erling Smørgrav 	debug3("request %u: statvfs", id);
1311f7167e0eSDag-Erling Smørgrav 	logit("statvfs \"%s\"", path);
1312d4af9e69SDag-Erling Smørgrav 
1313d4af9e69SDag-Erling Smørgrav 	if (statvfs(path, &st) != 0)
1314d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1315d4af9e69SDag-Erling Smørgrav 	else
1316d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1317e4a9863fSDag-Erling Smørgrav         free(path);
1318d4af9e69SDag-Erling Smørgrav }
1319d4af9e69SDag-Erling Smørgrav 
1320d4af9e69SDag-Erling Smørgrav static void
1321d4af9e69SDag-Erling Smørgrav process_extended_fstatvfs(u_int32_t id)
1322d4af9e69SDag-Erling Smørgrav {
1323*bc5531deSDag-Erling Smørgrav 	int r, handle, fd;
1324d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1325d4af9e69SDag-Erling Smørgrav 
1326*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1327*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1328d4af9e69SDag-Erling Smørgrav 	debug("request %u: fstatvfs \"%s\" (handle %u)",
1329d4af9e69SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
1330d4af9e69SDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0) {
1331d4af9e69SDag-Erling Smørgrav 		send_status(id, SSH2_FX_FAILURE);
1332d4af9e69SDag-Erling Smørgrav 		return;
1333d4af9e69SDag-Erling Smørgrav 	}
1334d4af9e69SDag-Erling Smørgrav 	if (fstatvfs(fd, &st) != 0)
1335d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1336d4af9e69SDag-Erling Smørgrav 	else
1337d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1338d4af9e69SDag-Erling Smørgrav }
1339d4af9e69SDag-Erling Smørgrav 
1340d4af9e69SDag-Erling Smørgrav static void
13414a421b63SDag-Erling Smørgrav process_extended_hardlink(u_int32_t id)
13424a421b63SDag-Erling Smørgrav {
13434a421b63SDag-Erling Smørgrav 	char *oldpath, *newpath;
1344*bc5531deSDag-Erling Smørgrav 	int r, status;
13454a421b63SDag-Erling Smørgrav 
1346*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1347*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
1348*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1349*bc5531deSDag-Erling Smørgrav 
13504a421b63SDag-Erling Smørgrav 	debug3("request %u: hardlink", id);
13514a421b63SDag-Erling Smørgrav 	logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
1352*bc5531deSDag-Erling Smørgrav 	r = link(oldpath, newpath);
1353*bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
13544a421b63SDag-Erling Smørgrav 	send_status(id, status);
1355e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1356e4a9863fSDag-Erling Smørgrav 	free(newpath);
13574a421b63SDag-Erling Smørgrav }
13584a421b63SDag-Erling Smørgrav 
13594a421b63SDag-Erling Smørgrav static void
1360f7167e0eSDag-Erling Smørgrav process_extended_fsync(u_int32_t id)
13611e8db6e2SBrian Feldman {
1362*bc5531deSDag-Erling Smørgrav 	int handle, fd, r, status = SSH2_FX_OP_UNSUPPORTED;
13631e8db6e2SBrian Feldman 
1364*bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
1365*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1366f7167e0eSDag-Erling Smørgrav 	debug3("request %u: fsync (handle %u)", id, handle);
1367f7167e0eSDag-Erling Smørgrav 	verbose("fsync \"%s\"", handle_to_name(handle));
1368f7167e0eSDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0)
1369f7167e0eSDag-Erling Smørgrav 		status = SSH2_FX_NO_SUCH_FILE;
1370f7167e0eSDag-Erling Smørgrav 	else if (handle_is_ok(handle, HANDLE_FILE)) {
1371*bc5531deSDag-Erling Smørgrav 		r = fsync(fd);
1372*bc5531deSDag-Erling Smørgrav 		status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1373f7167e0eSDag-Erling Smørgrav 	}
1374f7167e0eSDag-Erling Smørgrav 	send_status(id, status);
1375f7167e0eSDag-Erling Smørgrav }
1376f7167e0eSDag-Erling Smørgrav 
1377f7167e0eSDag-Erling Smørgrav static void
1378f7167e0eSDag-Erling Smørgrav process_extended(u_int32_t id)
1379f7167e0eSDag-Erling Smørgrav {
1380f7167e0eSDag-Erling Smørgrav 	char *request;
1381*bc5531deSDag-Erling Smørgrav 	int i, r;
1382f7167e0eSDag-Erling Smørgrav 
1383*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &request, NULL)) != 0)
1384*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1385f7167e0eSDag-Erling Smørgrav 	for (i = 0; extended_handlers[i].handler != NULL; i++) {
1386f7167e0eSDag-Erling Smørgrav 		if (strcmp(request, extended_handlers[i].ext_name) == 0) {
1387f7167e0eSDag-Erling Smørgrav 			if (!request_permitted(&extended_handlers[i]))
1388f7167e0eSDag-Erling Smørgrav 				send_status(id, SSH2_FX_PERMISSION_DENIED);
1389d4af9e69SDag-Erling Smørgrav 			else
1390f7167e0eSDag-Erling Smørgrav 				extended_handlers[i].handler(id);
1391f7167e0eSDag-Erling Smørgrav 			break;
1392f7167e0eSDag-Erling Smørgrav 		}
1393f7167e0eSDag-Erling Smørgrav 	}
1394f7167e0eSDag-Erling Smørgrav 	if (extended_handlers[i].handler == NULL) {
1395f7167e0eSDag-Erling Smørgrav 		error("Unknown extended request \"%.100s\"", request);
13961e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
1397f7167e0eSDag-Erling Smørgrav 	}
1398e4a9863fSDag-Erling Smørgrav 	free(request);
13991e8db6e2SBrian Feldman }
1400b66f2d16SKris Kennaway 
1401b66f2d16SKris Kennaway /* stolen from ssh-agent */
1402b66f2d16SKris Kennaway 
1403ae1f160dSDag-Erling Smørgrav static void
1404b66f2d16SKris Kennaway process(void)
1405b66f2d16SKris Kennaway {
1406*bc5531deSDag-Erling Smørgrav 	u_int msg_len;
1407*bc5531deSDag-Erling Smørgrav 	u_int buf_len;
1408*bc5531deSDag-Erling Smørgrav 	u_int consumed;
1409*bc5531deSDag-Erling Smørgrav 	u_char type;
1410*bc5531deSDag-Erling Smørgrav 	const u_char *cp;
1411*bc5531deSDag-Erling Smørgrav 	int i, r;
1412f7167e0eSDag-Erling Smørgrav 	u_int32_t id;
1413b66f2d16SKris Kennaway 
1414*bc5531deSDag-Erling Smørgrav 	buf_len = sshbuf_len(iqueue);
1415545d5ecaSDag-Erling Smørgrav 	if (buf_len < 5)
1416b66f2d16SKris Kennaway 		return;		/* Incomplete message. */
1417*bc5531deSDag-Erling Smørgrav 	cp = sshbuf_ptr(iqueue);
1418761efaa7SDag-Erling Smørgrav 	msg_len = get_u32(cp);
1419021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1420761efaa7SDag-Erling Smørgrav 		error("bad message from %s local user %s",
1421761efaa7SDag-Erling Smørgrav 		    client_addr, pw->pw_name);
1422d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(11);
1423b66f2d16SKris Kennaway 	}
1424545d5ecaSDag-Erling Smørgrav 	if (buf_len < msg_len + 4)
1425b66f2d16SKris Kennaway 		return;
1426*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_consume(iqueue, 4)) != 0)
1427*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1428545d5ecaSDag-Erling Smørgrav 	buf_len -= 4;
1429*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(iqueue, &type)) != 0)
1430*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1431f7167e0eSDag-Erling Smørgrav 
1432b66f2d16SKris Kennaway 	switch (type) {
14331e8db6e2SBrian Feldman 	case SSH2_FXP_INIT:
1434b66f2d16SKris Kennaway 		process_init();
1435f7167e0eSDag-Erling Smørgrav 		init_done = 1;
14361e8db6e2SBrian Feldman 		break;
14371e8db6e2SBrian Feldman 	case SSH2_FXP_EXTENDED:
1438f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1439f7167e0eSDag-Erling Smørgrav 			fatal("Received extended request before init");
1440*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
1441*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
1442f7167e0eSDag-Erling Smørgrav 		process_extended(id);
14431e8db6e2SBrian Feldman 		break;
1444b66f2d16SKris Kennaway 	default:
1445f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1446f7167e0eSDag-Erling Smørgrav 			fatal("Received %u request before init", type);
1447*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
1448*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
1449f7167e0eSDag-Erling Smørgrav 		for (i = 0; handlers[i].handler != NULL; i++) {
1450f7167e0eSDag-Erling Smørgrav 			if (type == handlers[i].type) {
1451f7167e0eSDag-Erling Smørgrav 				if (!request_permitted(&handlers[i])) {
1452f7167e0eSDag-Erling Smørgrav 					send_status(id,
1453f7167e0eSDag-Erling Smørgrav 					    SSH2_FX_PERMISSION_DENIED);
1454f7167e0eSDag-Erling Smørgrav 				} else {
1455f7167e0eSDag-Erling Smørgrav 					handlers[i].handler(id);
1456f7167e0eSDag-Erling Smørgrav 				}
1457b66f2d16SKris Kennaway 				break;
1458b66f2d16SKris Kennaway 			}
1459f7167e0eSDag-Erling Smørgrav 		}
1460f7167e0eSDag-Erling Smørgrav 		if (handlers[i].handler == NULL)
1461f7167e0eSDag-Erling Smørgrav 			error("Unknown message %u", type);
1462f7167e0eSDag-Erling Smørgrav 	}
1463545d5ecaSDag-Erling Smørgrav 	/* discard the remaining bytes from the current packet */
1464*bc5531deSDag-Erling Smørgrav 	if (buf_len < sshbuf_len(iqueue)) {
1465d4af9e69SDag-Erling Smørgrav 		error("iqueue grew unexpectedly");
1466d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1467d4af9e69SDag-Erling Smørgrav 	}
1468*bc5531deSDag-Erling Smørgrav 	consumed = buf_len - sshbuf_len(iqueue);
1469d4af9e69SDag-Erling Smørgrav 	if (msg_len < consumed) {
1470f7167e0eSDag-Erling Smørgrav 		error("msg_len %u < consumed %u", msg_len, consumed);
1471d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1472d4af9e69SDag-Erling Smørgrav 	}
1473*bc5531deSDag-Erling Smørgrav 	if (msg_len > consumed &&
1474*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_consume(iqueue, msg_len - consumed)) != 0)
1475*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1476b66f2d16SKris Kennaway }
1477b66f2d16SKris Kennaway 
1478761efaa7SDag-Erling Smørgrav /* Cleanup handler that logs active handles upon normal exit */
1479761efaa7SDag-Erling Smørgrav void
1480d4af9e69SDag-Erling Smørgrav sftp_server_cleanup_exit(int i)
1481761efaa7SDag-Erling Smørgrav {
1482761efaa7SDag-Erling Smørgrav 	if (pw != NULL && client_addr != NULL) {
1483761efaa7SDag-Erling Smørgrav 		handle_log_exit();
1484761efaa7SDag-Erling Smørgrav 		logit("session closed for local user %s from [%s]",
1485761efaa7SDag-Erling Smørgrav 		    pw->pw_name, client_addr);
1486761efaa7SDag-Erling Smørgrav 	}
1487761efaa7SDag-Erling Smørgrav 	_exit(i);
1488761efaa7SDag-Erling Smørgrav }
1489761efaa7SDag-Erling Smørgrav 
1490761efaa7SDag-Erling Smørgrav static void
1491d4af9e69SDag-Erling Smørgrav sftp_server_usage(void)
1492761efaa7SDag-Erling Smørgrav {
1493761efaa7SDag-Erling Smørgrav 	extern char *__progname;
1494761efaa7SDag-Erling Smørgrav 
1495761efaa7SDag-Erling Smørgrav 	fprintf(stderr,
14966888a9beSDag-Erling Smørgrav 	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
1497f7167e0eSDag-Erling Smørgrav 	    "[-l log_level]\n\t[-P blacklisted_requests] "
1498f7167e0eSDag-Erling Smørgrav 	    "[-p whitelisted_requests] [-u umask]\n"
1499f7167e0eSDag-Erling Smørgrav 	    "       %s -Q protocol_feature\n",
1500f7167e0eSDag-Erling Smørgrav 	    __progname, __progname);
1501761efaa7SDag-Erling Smørgrav 	exit(1);
1502761efaa7SDag-Erling Smørgrav }
1503761efaa7SDag-Erling Smørgrav 
1504b66f2d16SKris Kennaway int
1505d4af9e69SDag-Erling Smørgrav sftp_server_main(int argc, char **argv, struct passwd *user_pw)
1506b66f2d16SKris Kennaway {
15071e8db6e2SBrian Feldman 	fd_set *rset, *wset;
1508*bc5531deSDag-Erling Smørgrav 	int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0;
15091e8db6e2SBrian Feldman 	ssize_t len, olen, set_size;
1510761efaa7SDag-Erling Smørgrav 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
15116888a9beSDag-Erling Smørgrav 	char *cp, *homedir = NULL, buf[4*4096];
15124a421b63SDag-Erling Smørgrav 	long mask;
1513761efaa7SDag-Erling Smørgrav 
1514761efaa7SDag-Erling Smørgrav 	extern char *optarg;
1515761efaa7SDag-Erling Smørgrav 	extern char *__progname;
15161e8db6e2SBrian Feldman 
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 
1602761efaa7SDag-Erling Smørgrav 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1603761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup(cp);
1604d4af9e69SDag-Erling Smørgrav 		if ((cp = strchr(client_addr, ' ')) == NULL) {
1605d4af9e69SDag-Erling Smørgrav 			error("Malformed SSH_CONNECTION variable: \"%s\"",
1606761efaa7SDag-Erling Smørgrav 			    getenv("SSH_CONNECTION"));
1607d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(255);
1608d4af9e69SDag-Erling Smørgrav 		}
1609761efaa7SDag-Erling Smørgrav 		*cp = '\0';
1610761efaa7SDag-Erling Smørgrav 	} else
1611761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup("UNKNOWN");
1612761efaa7SDag-Erling Smørgrav 
1613761efaa7SDag-Erling Smørgrav 	logit("session opened for local user %s from [%s]",
1614761efaa7SDag-Erling Smørgrav 	    pw->pw_name, client_addr);
1615761efaa7SDag-Erling Smørgrav 
1616b15c8340SDag-Erling Smørgrav 	in = STDIN_FILENO;
1617b15c8340SDag-Erling Smørgrav 	out = STDOUT_FILENO;
1618b66f2d16SKris Kennaway 
161983d2307dSDag-Erling Smørgrav #ifdef HAVE_CYGWIN
162083d2307dSDag-Erling Smørgrav 	setmode(in, O_BINARY);
162183d2307dSDag-Erling Smørgrav 	setmode(out, O_BINARY);
162283d2307dSDag-Erling Smørgrav #endif
162383d2307dSDag-Erling Smørgrav 
1624b66f2d16SKris Kennaway 	max = 0;
1625b66f2d16SKris Kennaway 	if (in > max)
1626b66f2d16SKris Kennaway 		max = in;
1627b66f2d16SKris Kennaway 	if (out > max)
1628b66f2d16SKris Kennaway 		max = out;
1629b66f2d16SKris Kennaway 
1630*bc5531deSDag-Erling Smørgrav 	if ((iqueue = sshbuf_new()) == NULL)
1631*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1632*bc5531deSDag-Erling Smørgrav 	if ((oqueue = sshbuf_new()) == NULL)
1633*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1634b66f2d16SKris Kennaway 
16351e8db6e2SBrian Feldman 	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
16361e8db6e2SBrian Feldman 	rset = (fd_set *)xmalloc(set_size);
16371e8db6e2SBrian Feldman 	wset = (fd_set *)xmalloc(set_size);
1638b66f2d16SKris Kennaway 
16396888a9beSDag-Erling Smørgrav 	if (homedir != NULL) {
16406888a9beSDag-Erling Smørgrav 		if (chdir(homedir) != 0) {
16416888a9beSDag-Erling Smørgrav 			error("chdir to \"%s\" failed: %s", homedir,
16426888a9beSDag-Erling Smørgrav 			    strerror(errno));
16436888a9beSDag-Erling Smørgrav 		}
16446888a9beSDag-Erling Smørgrav 	}
16456888a9beSDag-Erling Smørgrav 
16461e8db6e2SBrian Feldman 	for (;;) {
16471e8db6e2SBrian Feldman 		memset(rset, 0, set_size);
16481e8db6e2SBrian Feldman 		memset(wset, 0, set_size);
16491e8db6e2SBrian Feldman 
1650d4af9e69SDag-Erling Smørgrav 		/*
1651d4af9e69SDag-Erling Smørgrav 		 * Ensure that we can read a full buffer and handle
1652d4af9e69SDag-Erling Smørgrav 		 * the worst-case length packet it can generate,
1653d4af9e69SDag-Erling Smørgrav 		 * otherwise apply backpressure by stopping reads.
1654d4af9e69SDag-Erling Smørgrav 		 */
1655*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 &&
1656*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_check_reserve(oqueue,
1657*bc5531deSDag-Erling Smørgrav 		    SFTP_MAX_MSG_LENGTH)) == 0)
16581e8db6e2SBrian Feldman 			FD_SET(in, rset);
1659*bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
1660*bc5531deSDag-Erling Smørgrav 			fatal("%s: sshbuf_check_reserve failed: %s",
1661*bc5531deSDag-Erling Smørgrav 			    __func__, ssh_err(r));
1662d4af9e69SDag-Erling Smørgrav 
1663*bc5531deSDag-Erling Smørgrav 		olen = sshbuf_len(oqueue);
1664b66f2d16SKris Kennaway 		if (olen > 0)
16651e8db6e2SBrian Feldman 			FD_SET(out, wset);
1666b66f2d16SKris Kennaway 
16671e8db6e2SBrian Feldman 		if (select(max+1, rset, wset, NULL, NULL) < 0) {
1668b66f2d16SKris Kennaway 			if (errno == EINTR)
1669b66f2d16SKris Kennaway 				continue;
1670761efaa7SDag-Erling Smørgrav 			error("select: %s", strerror(errno));
1671d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(2);
1672b66f2d16SKris Kennaway 		}
1673b66f2d16SKris Kennaway 
1674b66f2d16SKris Kennaway 		/* copy stdin to iqueue */
16751e8db6e2SBrian Feldman 		if (FD_ISSET(in, rset)) {
1676b66f2d16SKris Kennaway 			len = read(in, buf, sizeof buf);
1677b66f2d16SKris Kennaway 			if (len == 0) {
1678b66f2d16SKris Kennaway 				debug("read eof");
1679d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(0);
1680b66f2d16SKris Kennaway 			} else if (len < 0) {
1681761efaa7SDag-Erling Smørgrav 				error("read: %s", strerror(errno));
1682d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(1);
1683*bc5531deSDag-Erling Smørgrav 			} else if ((r = sshbuf_put(iqueue, buf, len)) != 0) {
1684*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1685*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1686b66f2d16SKris Kennaway 			}
1687b66f2d16SKris Kennaway 		}
1688b66f2d16SKris Kennaway 		/* send oqueue to stdout */
16891e8db6e2SBrian Feldman 		if (FD_ISSET(out, wset)) {
1690*bc5531deSDag-Erling Smørgrav 			len = write(out, sshbuf_ptr(oqueue), olen);
1691b66f2d16SKris Kennaway 			if (len < 0) {
1692761efaa7SDag-Erling Smørgrav 				error("write: %s", strerror(errno));
1693d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(1);
1694*bc5531deSDag-Erling Smørgrav 			} else if ((r = sshbuf_consume(oqueue, len)) != 0) {
1695*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1696*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1697b66f2d16SKris Kennaway 			}
1698b66f2d16SKris Kennaway 		}
1699d4af9e69SDag-Erling Smørgrav 
1700d4af9e69SDag-Erling Smørgrav 		/*
1701d4af9e69SDag-Erling Smørgrav 		 * Process requests from client if we can fit the results
1702d4af9e69SDag-Erling Smørgrav 		 * into the output buffer, otherwise stop processing input
1703d4af9e69SDag-Erling Smørgrav 		 * and let the output queue drain.
1704d4af9e69SDag-Erling Smørgrav 		 */
1705*bc5531deSDag-Erling Smørgrav 		r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH);
1706*bc5531deSDag-Erling Smørgrav 		if (r == 0)
1707b66f2d16SKris Kennaway 			process();
1708*bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
1709*bc5531deSDag-Erling Smørgrav 			fatal("%s: sshbuf_check_reserve: %s",
1710*bc5531deSDag-Erling Smørgrav 			    __func__, ssh_err(r));
1711b66f2d16SKris Kennaway 	}
1712b66f2d16SKris Kennaway }
1713