xref: /freebsd/crypto/openssh/sftp-server.c (revision 0fdf8fae8b569bf9fff3b5171e669dcd7cf9c79e)
1*0fdf8faeSEd Maste /* $OpenBSD: sftp-server.c,v 1.148 2024/04/30 06:23:51 djm 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 
20761efaa7SDag-Erling Smørgrav #include <sys/types.h>
21761efaa7SDag-Erling Smørgrav #include <sys/stat.h>
2219261079SEd Maste #include <sys/resource.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
32761efaa7SDag-Erling Smørgrav 
33761efaa7SDag-Erling Smørgrav #include <dirent.h>
34761efaa7SDag-Erling Smørgrav #include <errno.h>
35761efaa7SDag-Erling Smørgrav #include <fcntl.h>
361323ec57SEd Maste #ifdef HAVE_POLL_H
371323ec57SEd Maste #include <poll.h>
381323ec57SEd Maste #endif
39761efaa7SDag-Erling Smørgrav #include <pwd.h>
4038a52bd3SEd Maste #include <grp.h>
41761efaa7SDag-Erling Smørgrav #include <stdlib.h>
42761efaa7SDag-Erling Smørgrav #include <stdio.h>
43761efaa7SDag-Erling Smørgrav #include <string.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 
4887c1498dSEd Maste #include "atomicio.h"
49b66f2d16SKris Kennaway #include "xmalloc.h"
50bc5531deSDag-Erling Smørgrav #include "sshbuf.h"
51bc5531deSDag-Erling Smørgrav #include "ssherr.h"
52761efaa7SDag-Erling Smørgrav #include "log.h"
53021d409fSDag-Erling Smørgrav #include "misc.h"
54f7167e0eSDag-Erling Smørgrav #include "match.h"
55761efaa7SDag-Erling Smørgrav #include "uidswap.h"
56b66f2d16SKris Kennaway 
571e8db6e2SBrian Feldman #include "sftp.h"
581e8db6e2SBrian Feldman #include "sftp-common.h"
59b66f2d16SKris Kennaway 
6019261079SEd Maste char *sftp_realpath(const char *, char *); /* sftp-realpath.c */
6119261079SEd Maste 
6219261079SEd Maste /* Maximum data read that we are willing to accept */
6319261079SEd Maste #define SFTP_MAX_READ_LENGTH (SFTP_MAX_MSG_LENGTH - 1024)
6419261079SEd Maste 
65761efaa7SDag-Erling Smørgrav /* Our verbosity */
66f7167e0eSDag-Erling Smørgrav static LogLevel log_level = SYSLOG_LEVEL_ERROR;
67761efaa7SDag-Erling Smørgrav 
68761efaa7SDag-Erling Smørgrav /* Our client */
69f7167e0eSDag-Erling Smørgrav static struct passwd *pw = NULL;
70f7167e0eSDag-Erling Smørgrav static char *client_addr = NULL;
7183d2307dSDag-Erling Smørgrav 
72b66f2d16SKris Kennaway /* input and output queue */
73bc5531deSDag-Erling Smørgrav struct sshbuf *iqueue;
74bc5531deSDag-Erling Smørgrav struct sshbuf *oqueue;
75b66f2d16SKris Kennaway 
761e8db6e2SBrian Feldman /* Version of client */
77f7167e0eSDag-Erling Smørgrav static u_int version;
78f7167e0eSDag-Erling Smørgrav 
79f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_INIT received */
80f7167e0eSDag-Erling Smørgrav static int init_done;
811e8db6e2SBrian Feldman 
82b15c8340SDag-Erling Smørgrav /* Disable writes */
83f7167e0eSDag-Erling Smørgrav static int readonly;
84f7167e0eSDag-Erling Smørgrav 
85f7167e0eSDag-Erling Smørgrav /* Requests that are allowed/denied */
8619261079SEd Maste static char *request_allowlist, *request_denylist;
87b15c8340SDag-Erling Smørgrav 
88d95e11bfSDag-Erling Smørgrav /* portable attributes, etc. */
89b66f2d16SKris Kennaway typedef struct Stat Stat;
90b66f2d16SKris Kennaway 
911e8db6e2SBrian Feldman struct Stat {
92b66f2d16SKris Kennaway 	char *name;
93b66f2d16SKris Kennaway 	char *long_name;
94b66f2d16SKris Kennaway 	Attrib attrib;
95b66f2d16SKris Kennaway };
96b66f2d16SKris Kennaway 
97f7167e0eSDag-Erling Smørgrav /* Packet handlers */
98f7167e0eSDag-Erling Smørgrav static void process_open(u_int32_t id);
99f7167e0eSDag-Erling Smørgrav static void process_close(u_int32_t id);
100f7167e0eSDag-Erling Smørgrav static void process_read(u_int32_t id);
101f7167e0eSDag-Erling Smørgrav static void process_write(u_int32_t id);
102f7167e0eSDag-Erling Smørgrav static void process_stat(u_int32_t id);
103f7167e0eSDag-Erling Smørgrav static void process_lstat(u_int32_t id);
104f7167e0eSDag-Erling Smørgrav static void process_fstat(u_int32_t id);
105f7167e0eSDag-Erling Smørgrav static void process_setstat(u_int32_t id);
106f7167e0eSDag-Erling Smørgrav static void process_fsetstat(u_int32_t id);
107f7167e0eSDag-Erling Smørgrav static void process_opendir(u_int32_t id);
108f7167e0eSDag-Erling Smørgrav static void process_readdir(u_int32_t id);
109f7167e0eSDag-Erling Smørgrav static void process_remove(u_int32_t id);
110f7167e0eSDag-Erling Smørgrav static void process_mkdir(u_int32_t id);
111f7167e0eSDag-Erling Smørgrav static void process_rmdir(u_int32_t id);
112f7167e0eSDag-Erling Smørgrav static void process_realpath(u_int32_t id);
113f7167e0eSDag-Erling Smørgrav static void process_rename(u_int32_t id);
114f7167e0eSDag-Erling Smørgrav static void process_readlink(u_int32_t id);
115f7167e0eSDag-Erling Smørgrav static void process_symlink(u_int32_t id);
116f7167e0eSDag-Erling Smørgrav static void process_extended_posix_rename(u_int32_t id);
117f7167e0eSDag-Erling Smørgrav static void process_extended_statvfs(u_int32_t id);
118f7167e0eSDag-Erling Smørgrav static void process_extended_fstatvfs(u_int32_t id);
119f7167e0eSDag-Erling Smørgrav static void process_extended_hardlink(u_int32_t id);
120f7167e0eSDag-Erling Smørgrav static void process_extended_fsync(u_int32_t id);
12119261079SEd Maste static void process_extended_lsetstat(u_int32_t id);
12219261079SEd Maste static void process_extended_limits(u_int32_t id);
12319261079SEd Maste static void process_extended_expand(u_int32_t id);
12487c1498dSEd Maste static void process_extended_copy_data(u_int32_t id);
12538a52bd3SEd Maste static void process_extended_home_directory(u_int32_t id);
12638a52bd3SEd Maste static void process_extended_get_users_groups_by_id(u_int32_t id);
127f7167e0eSDag-Erling Smørgrav static void process_extended(u_int32_t id);
128f7167e0eSDag-Erling Smørgrav 
129f7167e0eSDag-Erling Smørgrav struct sftp_handler {
130f7167e0eSDag-Erling Smørgrav 	const char *name;	/* user-visible name for fine-grained perms */
131f7167e0eSDag-Erling Smørgrav 	const char *ext_name;	/* extended request name */
132f7167e0eSDag-Erling Smørgrav 	u_int type;		/* packet type, for non extended packets */
133f7167e0eSDag-Erling Smørgrav 	void (*handler)(u_int32_t);
134f7167e0eSDag-Erling Smørgrav 	int does_write;		/* if nonzero, banned for readonly mode */
135f7167e0eSDag-Erling Smørgrav };
136f7167e0eSDag-Erling Smørgrav 
13719261079SEd Maste static const struct sftp_handler handlers[] = {
138f7167e0eSDag-Erling Smørgrav 	/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
139f7167e0eSDag-Erling Smørgrav 	{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
140f7167e0eSDag-Erling Smørgrav 	{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
141f7167e0eSDag-Erling Smørgrav 	{ "read", NULL, SSH2_FXP_READ, process_read, 0 },
142f7167e0eSDag-Erling Smørgrav 	{ "write", NULL, SSH2_FXP_WRITE, process_write, 1 },
143f7167e0eSDag-Erling Smørgrav 	{ "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 },
144f7167e0eSDag-Erling Smørgrav 	{ "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 },
145f7167e0eSDag-Erling Smørgrav 	{ "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 },
146f7167e0eSDag-Erling Smørgrav 	{ "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 },
147f7167e0eSDag-Erling Smørgrav 	{ "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 },
148f7167e0eSDag-Erling Smørgrav 	{ "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 },
149f7167e0eSDag-Erling Smørgrav 	{ "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 },
150f7167e0eSDag-Erling Smørgrav 	{ "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 },
151f7167e0eSDag-Erling Smørgrav 	{ "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 },
152f7167e0eSDag-Erling Smørgrav 	{ "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 },
153f7167e0eSDag-Erling Smørgrav 	{ "stat", NULL, SSH2_FXP_STAT, process_stat, 0 },
154f7167e0eSDag-Erling Smørgrav 	{ "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 },
155f7167e0eSDag-Erling Smørgrav 	{ "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 },
156f7167e0eSDag-Erling Smørgrav 	{ "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 },
157f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
158f7167e0eSDag-Erling Smørgrav };
159f7167e0eSDag-Erling Smørgrav 
160f7167e0eSDag-Erling Smørgrav /* SSH2_FXP_EXTENDED submessages */
16119261079SEd Maste static const struct sftp_handler extended_handlers[] = {
162f7167e0eSDag-Erling Smørgrav 	{ "posix-rename", "posix-rename@openssh.com", 0,
163f7167e0eSDag-Erling Smørgrav 	    process_extended_posix_rename, 1 },
164f7167e0eSDag-Erling Smørgrav 	{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
165f7167e0eSDag-Erling Smørgrav 	{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
166f7167e0eSDag-Erling Smørgrav 	{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
167f7167e0eSDag-Erling Smørgrav 	{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
16819261079SEd Maste 	{ "lsetstat", "lsetstat@openssh.com", 0, process_extended_lsetstat, 1 },
16919261079SEd Maste 	{ "limits", "limits@openssh.com", 0, process_extended_limits, 0 },
17019261079SEd Maste 	{ "expand-path", "expand-path@openssh.com", 0,
17119261079SEd Maste 	    process_extended_expand, 0 },
17287c1498dSEd Maste 	{ "copy-data", "copy-data", 0, process_extended_copy_data, 1 },
17338a52bd3SEd Maste 	{ "home-directory", "home-directory", 0,
17438a52bd3SEd Maste 	    process_extended_home_directory, 0 },
17538a52bd3SEd Maste 	{ "users-groups-by-id", "users-groups-by-id@openssh.com", 0,
17638a52bd3SEd Maste 	    process_extended_get_users_groups_by_id, 0 },
177f7167e0eSDag-Erling Smørgrav 	{ NULL, NULL, 0, NULL, 0 }
178f7167e0eSDag-Erling Smørgrav };
179f7167e0eSDag-Erling Smørgrav 
18019261079SEd Maste static const struct sftp_handler *
extended_handler_byname(const char * name)18119261079SEd Maste extended_handler_byname(const char *name)
18219261079SEd Maste {
18319261079SEd Maste 	int i;
18419261079SEd Maste 
18519261079SEd Maste 	for (i = 0; extended_handlers[i].handler != NULL; i++) {
18619261079SEd Maste 		if (strcmp(name, extended_handlers[i].ext_name) == 0)
18719261079SEd Maste 			return &extended_handlers[i];
18819261079SEd Maste 	}
18919261079SEd Maste 	return NULL;
19019261079SEd Maste }
19119261079SEd Maste 
192f7167e0eSDag-Erling Smørgrav static int
request_permitted(const struct sftp_handler * h)19319261079SEd Maste request_permitted(const struct sftp_handler *h)
194f7167e0eSDag-Erling Smørgrav {
195f7167e0eSDag-Erling Smørgrav 	char *result;
196f7167e0eSDag-Erling Smørgrav 
197f7167e0eSDag-Erling Smørgrav 	if (readonly && h->does_write) {
198f7167e0eSDag-Erling Smørgrav 		verbose("Refusing %s request in read-only mode", h->name);
199f7167e0eSDag-Erling Smørgrav 		return 0;
200f7167e0eSDag-Erling Smørgrav 	}
20119261079SEd Maste 	if (request_denylist != NULL &&
20219261079SEd Maste 	    ((result = match_list(h->name, request_denylist, NULL))) != NULL) {
203f7167e0eSDag-Erling Smørgrav 		free(result);
20419261079SEd Maste 		verbose("Refusing denylisted %s request", h->name);
205f7167e0eSDag-Erling Smørgrav 		return 0;
206f7167e0eSDag-Erling Smørgrav 	}
20719261079SEd Maste 	if (request_allowlist != NULL &&
20819261079SEd Maste 	    ((result = match_list(h->name, request_allowlist, NULL))) != NULL) {
209f7167e0eSDag-Erling Smørgrav 		free(result);
21019261079SEd Maste 		debug2("Permitting allowlisted %s request", h->name);
211f7167e0eSDag-Erling Smørgrav 		return 1;
212f7167e0eSDag-Erling Smørgrav 	}
21319261079SEd Maste 	if (request_allowlist != NULL) {
21419261079SEd Maste 		verbose("Refusing non-allowlisted %s request", h->name);
215f7167e0eSDag-Erling Smørgrav 		return 0;
216f7167e0eSDag-Erling Smørgrav 	}
217f7167e0eSDag-Erling Smørgrav 	return 1;
218f7167e0eSDag-Erling Smørgrav }
219f7167e0eSDag-Erling Smørgrav 
220ae1f160dSDag-Erling Smørgrav static int
errno_to_portable(int unixerrno)221b66f2d16SKris Kennaway errno_to_portable(int unixerrno)
222b66f2d16SKris Kennaway {
223b66f2d16SKris Kennaway 	int ret = 0;
2241e8db6e2SBrian Feldman 
225b66f2d16SKris Kennaway 	switch (unixerrno) {
226b66f2d16SKris Kennaway 	case 0:
2271e8db6e2SBrian Feldman 		ret = SSH2_FX_OK;
228b66f2d16SKris Kennaway 		break;
229b66f2d16SKris Kennaway 	case ENOENT:
230b66f2d16SKris Kennaway 	case ENOTDIR:
231b66f2d16SKris Kennaway 	case EBADF:
232b66f2d16SKris Kennaway 	case ELOOP:
2331e8db6e2SBrian Feldman 		ret = SSH2_FX_NO_SUCH_FILE;
234b66f2d16SKris Kennaway 		break;
235b66f2d16SKris Kennaway 	case EPERM:
236b66f2d16SKris Kennaway 	case EACCES:
237b66f2d16SKris Kennaway 	case EFAULT:
2381e8db6e2SBrian Feldman 		ret = SSH2_FX_PERMISSION_DENIED;
239b66f2d16SKris Kennaway 		break;
240b66f2d16SKris Kennaway 	case ENAMETOOLONG:
241b66f2d16SKris Kennaway 	case EINVAL:
2421e8db6e2SBrian Feldman 		ret = SSH2_FX_BAD_MESSAGE;
243b66f2d16SKris Kennaway 		break;
244d4af9e69SDag-Erling Smørgrav 	case ENOSYS:
245d4af9e69SDag-Erling Smørgrav 		ret = SSH2_FX_OP_UNSUPPORTED;
246d4af9e69SDag-Erling Smørgrav 		break;
247b66f2d16SKris Kennaway 	default:
2481e8db6e2SBrian Feldman 		ret = SSH2_FX_FAILURE;
249b66f2d16SKris Kennaway 		break;
250b66f2d16SKris Kennaway 	}
251b66f2d16SKris Kennaway 	return ret;
252b66f2d16SKris Kennaway }
253b66f2d16SKris Kennaway 
254ae1f160dSDag-Erling Smørgrav static int
flags_from_portable(int pflags)255b66f2d16SKris Kennaway flags_from_portable(int pflags)
256b66f2d16SKris Kennaway {
257b66f2d16SKris Kennaway 	int flags = 0;
2581e8db6e2SBrian Feldman 
2591e8db6e2SBrian Feldman 	if ((pflags & SSH2_FXF_READ) &&
2601e8db6e2SBrian Feldman 	    (pflags & SSH2_FXF_WRITE)) {
261b66f2d16SKris Kennaway 		flags = O_RDWR;
2621e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_READ) {
263b66f2d16SKris Kennaway 		flags = O_RDONLY;
2641e8db6e2SBrian Feldman 	} else if (pflags & SSH2_FXF_WRITE) {
265b66f2d16SKris Kennaway 		flags = O_WRONLY;
266b66f2d16SKris Kennaway 	}
267f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
268f7167e0eSDag-Erling Smørgrav 		flags |= O_APPEND;
2691e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_CREAT)
270b66f2d16SKris Kennaway 		flags |= O_CREAT;
2711e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_TRUNC)
272b66f2d16SKris Kennaway 		flags |= O_TRUNC;
2731e8db6e2SBrian Feldman 	if (pflags & SSH2_FXF_EXCL)
274b66f2d16SKris Kennaway 		flags |= O_EXCL;
275b66f2d16SKris Kennaway 	return flags;
276b66f2d16SKris Kennaway }
277b66f2d16SKris Kennaway 
278761efaa7SDag-Erling Smørgrav static const char *
string_from_portable(int pflags)279761efaa7SDag-Erling Smørgrav string_from_portable(int pflags)
280761efaa7SDag-Erling Smørgrav {
281761efaa7SDag-Erling Smørgrav 	static char ret[128];
282761efaa7SDag-Erling Smørgrav 
283761efaa7SDag-Erling Smørgrav 	*ret = '\0';
284761efaa7SDag-Erling Smørgrav 
285761efaa7SDag-Erling Smørgrav #define PAPPEND(str)	{				\
286761efaa7SDag-Erling Smørgrav 		if (*ret != '\0')			\
287761efaa7SDag-Erling Smørgrav 			strlcat(ret, ",", sizeof(ret));	\
288761efaa7SDag-Erling Smørgrav 		strlcat(ret, str, sizeof(ret));		\
289761efaa7SDag-Erling Smørgrav 	}
290761efaa7SDag-Erling Smørgrav 
291761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_READ)
292761efaa7SDag-Erling Smørgrav 		PAPPEND("READ")
293761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_WRITE)
294761efaa7SDag-Erling Smørgrav 		PAPPEND("WRITE")
295f7167e0eSDag-Erling Smørgrav 	if (pflags & SSH2_FXF_APPEND)
296f7167e0eSDag-Erling Smørgrav 		PAPPEND("APPEND")
297761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_CREAT)
298761efaa7SDag-Erling Smørgrav 		PAPPEND("CREATE")
299761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_TRUNC)
300761efaa7SDag-Erling Smørgrav 		PAPPEND("TRUNCATE")
301761efaa7SDag-Erling Smørgrav 	if (pflags & SSH2_FXF_EXCL)
302761efaa7SDag-Erling Smørgrav 		PAPPEND("EXCL")
303761efaa7SDag-Erling Smørgrav 
304761efaa7SDag-Erling Smørgrav 	return ret;
305761efaa7SDag-Erling Smørgrav }
306761efaa7SDag-Erling Smørgrav 
307b66f2d16SKris Kennaway /* handle handles */
308b66f2d16SKris Kennaway 
309b66f2d16SKris Kennaway typedef struct Handle Handle;
310b66f2d16SKris Kennaway struct Handle {
311b66f2d16SKris Kennaway 	int use;
312b66f2d16SKris Kennaway 	DIR *dirp;
313b66f2d16SKris Kennaway 	int fd;
314f7167e0eSDag-Erling Smørgrav 	int flags;
315b66f2d16SKris Kennaway 	char *name;
316761efaa7SDag-Erling Smørgrav 	u_int64_t bytes_read, bytes_write;
317d4af9e69SDag-Erling Smørgrav 	int next_unused;
318b66f2d16SKris Kennaway };
3191e8db6e2SBrian Feldman 
320b66f2d16SKris Kennaway enum {
321b66f2d16SKris Kennaway 	HANDLE_UNUSED,
322b66f2d16SKris Kennaway 	HANDLE_DIR,
323b66f2d16SKris Kennaway 	HANDLE_FILE
324b66f2d16SKris Kennaway };
3251e8db6e2SBrian Feldman 
32619261079SEd Maste static Handle *handles = NULL;
32719261079SEd Maste static u_int num_handles = 0;
32819261079SEd Maste static int first_unused_handle = -1;
329b66f2d16SKris Kennaway 
handle_unused(int i)330d4af9e69SDag-Erling Smørgrav static void handle_unused(int i)
331b66f2d16SKris Kennaway {
332b66f2d16SKris Kennaway 	handles[i].use = HANDLE_UNUSED;
333d4af9e69SDag-Erling Smørgrav 	handles[i].next_unused = first_unused_handle;
334d4af9e69SDag-Erling Smørgrav 	first_unused_handle = i;
335b66f2d16SKris Kennaway }
336b66f2d16SKris Kennaway 
337ae1f160dSDag-Erling Smørgrav static int
handle_new(int use,const char * name,int fd,int flags,DIR * dirp)338f7167e0eSDag-Erling Smørgrav handle_new(int use, const char *name, int fd, int flags, DIR *dirp)
339b66f2d16SKris Kennaway {
340d4af9e69SDag-Erling Smørgrav 	int i;
3411e8db6e2SBrian Feldman 
342d4af9e69SDag-Erling Smørgrav 	if (first_unused_handle == -1) {
343d4af9e69SDag-Erling Smørgrav 		if (num_handles + 1 <= num_handles)
344d4af9e69SDag-Erling Smørgrav 			return -1;
345d4af9e69SDag-Erling Smørgrav 		num_handles++;
346557f75e5SDag-Erling Smørgrav 		handles = xreallocarray(handles, num_handles, sizeof(Handle));
347d4af9e69SDag-Erling Smørgrav 		handle_unused(num_handles - 1);
348d4af9e69SDag-Erling Smørgrav 	}
349d4af9e69SDag-Erling Smørgrav 
350d4af9e69SDag-Erling Smørgrav 	i = first_unused_handle;
351d4af9e69SDag-Erling Smørgrav 	first_unused_handle = handles[i].next_unused;
352d4af9e69SDag-Erling Smørgrav 
353b66f2d16SKris Kennaway 	handles[i].use = use;
354b66f2d16SKris Kennaway 	handles[i].dirp = dirp;
355b66f2d16SKris Kennaway 	handles[i].fd = fd;
356f7167e0eSDag-Erling Smørgrav 	handles[i].flags = flags;
357d0c8c0bcSDag-Erling Smørgrav 	handles[i].name = xstrdup(name);
358761efaa7SDag-Erling Smørgrav 	handles[i].bytes_read = handles[i].bytes_write = 0;
359d4af9e69SDag-Erling Smørgrav 
360b66f2d16SKris Kennaway 	return i;
361b66f2d16SKris Kennaway }
362b66f2d16SKris Kennaway 
363ae1f160dSDag-Erling Smørgrav static int
handle_is_ok(int i,int type)364b66f2d16SKris Kennaway handle_is_ok(int i, int type)
365b66f2d16SKris Kennaway {
366d4af9e69SDag-Erling Smørgrav 	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
367b66f2d16SKris Kennaway }
368b66f2d16SKris Kennaway 
369ae1f160dSDag-Erling Smørgrav static int
handle_to_string(int handle,u_char ** stringp,int * hlenp)370bc5531deSDag-Erling Smørgrav handle_to_string(int handle, u_char **stringp, int *hlenp)
371b66f2d16SKris Kennaway {
372b66f2d16SKris Kennaway 	if (stringp == NULL || hlenp == NULL)
373b66f2d16SKris Kennaway 		return -1;
3741e8db6e2SBrian Feldman 	*stringp = xmalloc(sizeof(int32_t));
375761efaa7SDag-Erling Smørgrav 	put_u32(*stringp, handle);
3761e8db6e2SBrian Feldman 	*hlenp = sizeof(int32_t);
377b66f2d16SKris Kennaway 	return 0;
378b66f2d16SKris Kennaway }
379b66f2d16SKris Kennaway 
380ae1f160dSDag-Erling Smørgrav static int
handle_from_string(const u_char * handle,u_int hlen)381bc5531deSDag-Erling Smørgrav handle_from_string(const u_char *handle, u_int hlen)
382b66f2d16SKris Kennaway {
3831e8db6e2SBrian Feldman 	int val;
3841e8db6e2SBrian Feldman 
3851e8db6e2SBrian Feldman 	if (hlen != sizeof(int32_t))
386b66f2d16SKris Kennaway 		return -1;
387761efaa7SDag-Erling Smørgrav 	val = get_u32(handle);
388b66f2d16SKris Kennaway 	if (handle_is_ok(val, HANDLE_FILE) ||
389b66f2d16SKris Kennaway 	    handle_is_ok(val, HANDLE_DIR))
390b66f2d16SKris Kennaway 		return val;
391b66f2d16SKris Kennaway 	return -1;
392b66f2d16SKris Kennaway }
393b66f2d16SKris Kennaway 
394ae1f160dSDag-Erling Smørgrav static char *
handle_to_name(int handle)395b66f2d16SKris Kennaway handle_to_name(int handle)
396b66f2d16SKris Kennaway {
397b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR)||
398b66f2d16SKris Kennaway 	    handle_is_ok(handle, HANDLE_FILE))
399b66f2d16SKris Kennaway 		return handles[handle].name;
400b66f2d16SKris Kennaway 	return NULL;
401b66f2d16SKris Kennaway }
402b66f2d16SKris Kennaway 
403ae1f160dSDag-Erling Smørgrav static DIR *
handle_to_dir(int handle)404b66f2d16SKris Kennaway handle_to_dir(int handle)
405b66f2d16SKris Kennaway {
406b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_DIR))
407b66f2d16SKris Kennaway 		return handles[handle].dirp;
408b66f2d16SKris Kennaway 	return NULL;
409b66f2d16SKris Kennaway }
410b66f2d16SKris Kennaway 
411ae1f160dSDag-Erling Smørgrav static int
handle_to_fd(int handle)412b66f2d16SKris Kennaway handle_to_fd(int handle)
413b66f2d16SKris Kennaway {
414b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE))
415b66f2d16SKris Kennaway 		return handles[handle].fd;
416b66f2d16SKris Kennaway 	return -1;
417b66f2d16SKris Kennaway }
418b66f2d16SKris Kennaway 
419f7167e0eSDag-Erling Smørgrav static int
handle_to_flags(int handle)420f7167e0eSDag-Erling Smørgrav handle_to_flags(int handle)
421f7167e0eSDag-Erling Smørgrav {
422f7167e0eSDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
423f7167e0eSDag-Erling Smørgrav 		return handles[handle].flags;
424f7167e0eSDag-Erling Smørgrav 	return 0;
425f7167e0eSDag-Erling Smørgrav }
426f7167e0eSDag-Erling Smørgrav 
427761efaa7SDag-Erling Smørgrav static void
handle_update_read(int handle,ssize_t bytes)428761efaa7SDag-Erling Smørgrav handle_update_read(int handle, ssize_t bytes)
429761efaa7SDag-Erling Smørgrav {
430761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
431761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_read += bytes;
432761efaa7SDag-Erling Smørgrav }
433761efaa7SDag-Erling Smørgrav 
434761efaa7SDag-Erling Smørgrav static void
handle_update_write(int handle,ssize_t bytes)435761efaa7SDag-Erling Smørgrav handle_update_write(int handle, ssize_t bytes)
436761efaa7SDag-Erling Smørgrav {
437761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
438761efaa7SDag-Erling Smørgrav 		handles[handle].bytes_write += bytes;
439761efaa7SDag-Erling Smørgrav }
440761efaa7SDag-Erling Smørgrav 
441761efaa7SDag-Erling Smørgrav static u_int64_t
handle_bytes_read(int handle)442761efaa7SDag-Erling Smørgrav handle_bytes_read(int handle)
443761efaa7SDag-Erling Smørgrav {
444761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
445761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_read);
446761efaa7SDag-Erling Smørgrav 	return 0;
447761efaa7SDag-Erling Smørgrav }
448761efaa7SDag-Erling Smørgrav 
449761efaa7SDag-Erling Smørgrav static u_int64_t
handle_bytes_write(int handle)450761efaa7SDag-Erling Smørgrav handle_bytes_write(int handle)
451761efaa7SDag-Erling Smørgrav {
452761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE))
453761efaa7SDag-Erling Smørgrav 		return (handles[handle].bytes_write);
454761efaa7SDag-Erling Smørgrav 	return 0;
455761efaa7SDag-Erling Smørgrav }
456761efaa7SDag-Erling Smørgrav 
457ae1f160dSDag-Erling Smørgrav static int
handle_close(int handle)458b66f2d16SKris Kennaway handle_close(int handle)
459b66f2d16SKris Kennaway {
460b66f2d16SKris Kennaway 	int ret = -1;
4611e8db6e2SBrian Feldman 
462b66f2d16SKris Kennaway 	if (handle_is_ok(handle, HANDLE_FILE)) {
463b66f2d16SKris Kennaway 		ret = close(handles[handle].fd);
464e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
465d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
466b66f2d16SKris Kennaway 	} else if (handle_is_ok(handle, HANDLE_DIR)) {
467b66f2d16SKris Kennaway 		ret = closedir(handles[handle].dirp);
468e4a9863fSDag-Erling Smørgrav 		free(handles[handle].name);
469d4af9e69SDag-Erling Smørgrav 		handle_unused(handle);
470b66f2d16SKris Kennaway 	} else {
471b66f2d16SKris Kennaway 		errno = ENOENT;
472b66f2d16SKris Kennaway 	}
473b66f2d16SKris Kennaway 	return ret;
474b66f2d16SKris Kennaway }
475b66f2d16SKris Kennaway 
476761efaa7SDag-Erling Smørgrav static void
handle_log_close(int handle,char * emsg)477761efaa7SDag-Erling Smørgrav handle_log_close(int handle, char *emsg)
478761efaa7SDag-Erling Smørgrav {
479761efaa7SDag-Erling Smørgrav 	if (handle_is_ok(handle, HANDLE_FILE)) {
480761efaa7SDag-Erling Smørgrav 		logit("%s%sclose \"%s\" bytes read %llu written %llu",
481761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
482761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle),
483d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_read(handle),
484d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)handle_bytes_write(handle));
485761efaa7SDag-Erling Smørgrav 	} else {
486761efaa7SDag-Erling Smørgrav 		logit("%s%sclosedir \"%s\"",
487761efaa7SDag-Erling Smørgrav 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
488761efaa7SDag-Erling Smørgrav 		    handle_to_name(handle));
489761efaa7SDag-Erling Smørgrav 	}
490761efaa7SDag-Erling Smørgrav }
491761efaa7SDag-Erling Smørgrav 
492761efaa7SDag-Erling Smørgrav static void
handle_log_exit(void)493761efaa7SDag-Erling Smørgrav handle_log_exit(void)
494761efaa7SDag-Erling Smørgrav {
495761efaa7SDag-Erling Smørgrav 	u_int i;
496761efaa7SDag-Erling Smørgrav 
497d4af9e69SDag-Erling Smørgrav 	for (i = 0; i < num_handles; i++)
498761efaa7SDag-Erling Smørgrav 		if (handles[i].use != HANDLE_UNUSED)
499761efaa7SDag-Erling Smørgrav 			handle_log_close(i, "forced");
500761efaa7SDag-Erling Smørgrav }
501761efaa7SDag-Erling Smørgrav 
502ae1f160dSDag-Erling Smørgrav static int
get_handle(struct sshbuf * queue,int * hp)503bc5531deSDag-Erling Smørgrav get_handle(struct sshbuf *queue, int *hp)
504b66f2d16SKris Kennaway {
505bc5531deSDag-Erling Smørgrav 	u_char *handle;
506bc5531deSDag-Erling Smørgrav 	int r;
507bc5531deSDag-Erling Smørgrav 	size_t hlen;
5081e8db6e2SBrian Feldman 
509bc5531deSDag-Erling Smørgrav 	*hp = -1;
510bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_string(queue, &handle, &hlen)) != 0)
511bc5531deSDag-Erling Smørgrav 		return r;
5121e8db6e2SBrian Feldman 	if (hlen < 256)
513bc5531deSDag-Erling Smørgrav 		*hp = handle_from_string(handle, hlen);
514e4a9863fSDag-Erling Smørgrav 	free(handle);
515bc5531deSDag-Erling Smørgrav 	return 0;
516b66f2d16SKris Kennaway }
517b66f2d16SKris Kennaway 
518b66f2d16SKris Kennaway /* send replies */
519b66f2d16SKris Kennaway 
520ae1f160dSDag-Erling Smørgrav static void
send_msg(struct sshbuf * m)521bc5531deSDag-Erling Smørgrav send_msg(struct sshbuf *m)
522b66f2d16SKris Kennaway {
523bc5531deSDag-Erling Smørgrav 	int r;
5241e8db6e2SBrian Feldman 
525bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_stringb(oqueue, m)) != 0)
52619261079SEd Maste 		fatal_fr(r, "enqueue");
527bc5531deSDag-Erling Smørgrav 	sshbuf_reset(m);
528b66f2d16SKris Kennaway }
529b66f2d16SKris Kennaway 
530761efaa7SDag-Erling Smørgrav static const char *
status_to_message(u_int32_t status)531761efaa7SDag-Erling Smørgrav status_to_message(u_int32_t status)
532b66f2d16SKris Kennaway {
5331323ec57SEd Maste 	static const char * const status_messages[] = {
5341e8db6e2SBrian Feldman 		"Success",			/* SSH_FX_OK */
5351e8db6e2SBrian Feldman 		"End of file",			/* SSH_FX_EOF */
5361e8db6e2SBrian Feldman 		"No such file",			/* SSH_FX_NO_SUCH_FILE */
5371e8db6e2SBrian Feldman 		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
5381e8db6e2SBrian Feldman 		"Failure",			/* SSH_FX_FAILURE */
5391e8db6e2SBrian Feldman 		"Bad message",			/* SSH_FX_BAD_MESSAGE */
5401e8db6e2SBrian Feldman 		"No connection",		/* SSH_FX_NO_CONNECTION */
5411e8db6e2SBrian Feldman 		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
5421e8db6e2SBrian Feldman 		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
5431e8db6e2SBrian Feldman 		"Unknown error"			/* Others */
5441e8db6e2SBrian Feldman 	};
545ca86bcf2SDag-Erling Smørgrav 	return (status_messages[MINIMUM(status,SSH2_FX_MAX)]);
546761efaa7SDag-Erling Smørgrav }
5471e8db6e2SBrian Feldman 
548761efaa7SDag-Erling Smørgrav static void
send_status_errmsg(u_int32_t id,u_int32_t status,const char * errmsg)5491323ec57SEd Maste send_status_errmsg(u_int32_t id, u_int32_t status, const char *errmsg)
550761efaa7SDag-Erling Smørgrav {
551bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
552bc5531deSDag-Erling Smørgrav 	int r;
553761efaa7SDag-Erling Smørgrav 
554761efaa7SDag-Erling Smørgrav 	debug3("request %u: sent status %u", id, status);
555761efaa7SDag-Erling Smørgrav 	if (log_level > SYSLOG_LEVEL_VERBOSE ||
556761efaa7SDag-Erling Smørgrav 	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
557761efaa7SDag-Erling Smørgrav 		logit("sent status %s", status_to_message(status));
558bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
55919261079SEd Maste 		fatal_f("sshbuf_new failed");
560bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_STATUS)) != 0 ||
561bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
562bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, status)) != 0)
56319261079SEd Maste 		fatal_fr(r, "compose");
5641e8db6e2SBrian Feldman 	if (version >= 3) {
5651323ec57SEd Maste 		if ((r = sshbuf_put_cstring(msg, errmsg == NULL ?
5661323ec57SEd Maste 		    status_to_message(status) : errmsg)) != 0 ||
567bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, "")) != 0)
56819261079SEd Maste 			fatal_fr(r, "compose message");
5691e8db6e2SBrian Feldman 	}
570bc5531deSDag-Erling Smørgrav 	send_msg(msg);
571bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
572b66f2d16SKris Kennaway }
5731323ec57SEd Maste 
5741323ec57SEd Maste static void
send_status(u_int32_t id,u_int32_t status)5751323ec57SEd Maste send_status(u_int32_t id, u_int32_t status)
5761323ec57SEd Maste {
5771323ec57SEd Maste 	send_status_errmsg(id, status, NULL);
5781323ec57SEd Maste }
5791323ec57SEd Maste 
580ae1f160dSDag-Erling Smørgrav static void
send_data_or_handle(char type,u_int32_t id,const u_char * data,int dlen)581bc5531deSDag-Erling Smørgrav send_data_or_handle(char type, u_int32_t id, const u_char *data, int dlen)
582b66f2d16SKris Kennaway {
583bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
584bc5531deSDag-Erling Smørgrav 	int r;
5851e8db6e2SBrian Feldman 
586bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
58719261079SEd Maste 		fatal_f("sshbuf_new failed");
588bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, type)) != 0 ||
589bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
590bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, data, dlen)) != 0)
59119261079SEd Maste 		fatal_fr(r, "compose");
592bc5531deSDag-Erling Smørgrav 	send_msg(msg);
593bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
594b66f2d16SKris Kennaway }
595b66f2d16SKris Kennaway 
596ae1f160dSDag-Erling Smørgrav static void
send_data(u_int32_t id,const u_char * data,int dlen)597bc5531deSDag-Erling Smørgrav send_data(u_int32_t id, const u_char *data, int dlen)
598b66f2d16SKris Kennaway {
599761efaa7SDag-Erling Smørgrav 	debug("request %u: sent data len %d", id, dlen);
6001e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
601b66f2d16SKris Kennaway }
602b66f2d16SKris Kennaway 
603ae1f160dSDag-Erling Smørgrav static void
send_handle(u_int32_t id,int handle)604b66f2d16SKris Kennaway send_handle(u_int32_t id, int handle)
605b66f2d16SKris Kennaway {
606bc5531deSDag-Erling Smørgrav 	u_char *string;
607b66f2d16SKris Kennaway 	int hlen;
6081e8db6e2SBrian Feldman 
609b66f2d16SKris Kennaway 	handle_to_string(handle, &string, &hlen);
610535af610SEd Maste 	debug("request %u: sent handle %d", id, handle);
6111e8db6e2SBrian Feldman 	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
612e4a9863fSDag-Erling Smørgrav 	free(string);
613b66f2d16SKris Kennaway }
614b66f2d16SKris Kennaway 
615ae1f160dSDag-Erling Smørgrav static void
send_names(u_int32_t id,int count,const Stat * stats)616efcad6b7SDag-Erling Smørgrav send_names(u_int32_t id, int count, const Stat *stats)
617b66f2d16SKris Kennaway {
618bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
619bc5531deSDag-Erling Smørgrav 	int i, r;
6201e8db6e2SBrian Feldman 
621bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
62219261079SEd Maste 		fatal_f("sshbuf_new failed");
623bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_NAME)) != 0 ||
624bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
625bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, count)) != 0)
62619261079SEd Maste 		fatal_fr(r, "compose");
627761efaa7SDag-Erling Smørgrav 	debug("request %u: sent names count %d", id, count);
628b66f2d16SKris Kennaway 	for (i = 0; i < count; i++) {
629bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 ||
630bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 ||
631bc5531deSDag-Erling Smørgrav 		    (r = encode_attrib(msg, &stats[i].attrib)) != 0)
63219261079SEd Maste 			fatal_fr(r, "compose filenames/attrib");
633b66f2d16SKris Kennaway 	}
634bc5531deSDag-Erling Smørgrav 	send_msg(msg);
635bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
636b66f2d16SKris Kennaway }
637b66f2d16SKris Kennaway 
638ae1f160dSDag-Erling Smørgrav static void
send_attrib(u_int32_t id,const Attrib * a)639efcad6b7SDag-Erling Smørgrav send_attrib(u_int32_t id, const Attrib *a)
640b66f2d16SKris Kennaway {
641bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
642bc5531deSDag-Erling Smørgrav 	int r;
6431e8db6e2SBrian Feldman 
644761efaa7SDag-Erling Smørgrav 	debug("request %u: sent attrib have 0x%x", id, a->flags);
645bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
64619261079SEd Maste 		fatal_f("sshbuf_new failed");
647bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_ATTRS)) != 0 ||
648bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
649bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, a)) != 0)
65019261079SEd Maste 		fatal_fr(r, "compose");
651bc5531deSDag-Erling Smørgrav 	send_msg(msg);
652bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
653b66f2d16SKris Kennaway }
654b66f2d16SKris Kennaway 
655d4af9e69SDag-Erling Smørgrav static void
send_statvfs(u_int32_t id,struct statvfs * st)656d4af9e69SDag-Erling Smørgrav send_statvfs(u_int32_t id, struct statvfs *st)
657d4af9e69SDag-Erling Smørgrav {
658bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
659d4af9e69SDag-Erling Smørgrav 	u_int64_t flag;
660bc5531deSDag-Erling Smørgrav 	int r;
661d4af9e69SDag-Erling Smørgrav 
662d4af9e69SDag-Erling Smørgrav 	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
663d4af9e69SDag-Erling Smørgrav 	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
664d4af9e69SDag-Erling Smørgrav 
665bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
66619261079SEd Maste 		fatal_f("sshbuf_new failed");
667bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
668bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
669bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bsize)) != 0 ||
670bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_frsize)) != 0 ||
671bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_blocks)) != 0 ||
672bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bfree)) != 0 ||
673bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_bavail)) != 0 ||
674bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_files)) != 0 ||
675bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_ffree)) != 0 ||
676bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_favail)) != 0 ||
677bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, FSID_TO_ULONG(st->f_fsid))) != 0 ||
678bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, flag)) != 0 ||
679bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, st->f_namemax)) != 0)
68019261079SEd Maste 		fatal_fr(r, "compose");
681bc5531deSDag-Erling Smørgrav 	send_msg(msg);
682bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
683d4af9e69SDag-Erling Smørgrav }
684d4af9e69SDag-Erling Smørgrav 
68519261079SEd Maste /*
68619261079SEd Maste  * Prepare SSH2_FXP_VERSION extension advertisement for a single extension.
6871323ec57SEd Maste  * The extension is checked for permission prior to advertisement.
68819261079SEd Maste  */
68919261079SEd Maste static int
compose_extension(struct sshbuf * msg,const char * name,const char * ver)69019261079SEd Maste compose_extension(struct sshbuf *msg, const char *name, const char *ver)
69119261079SEd Maste {
69219261079SEd Maste 	int r;
69319261079SEd Maste 	const struct sftp_handler *exthnd;
69419261079SEd Maste 
69519261079SEd Maste 	if ((exthnd = extended_handler_byname(name)) == NULL)
69619261079SEd Maste 		fatal_f("internal error: no handler for %s", name);
69719261079SEd Maste 	if (!request_permitted(exthnd)) {
69819261079SEd Maste 		debug2_f("refusing to advertise disallowed extension %s", name);
69919261079SEd Maste 		return 0;
70019261079SEd Maste 	}
70119261079SEd Maste 	if ((r = sshbuf_put_cstring(msg, name)) != 0 ||
70219261079SEd Maste 	    (r = sshbuf_put_cstring(msg, ver)) != 0)
70319261079SEd Maste 		fatal_fr(r, "compose %s", name);
70419261079SEd Maste 	return 0;
70519261079SEd Maste }
70619261079SEd Maste 
707b66f2d16SKris Kennaway /* parse incoming */
708b66f2d16SKris Kennaway 
709ae1f160dSDag-Erling Smørgrav static void
process_init(void)710b66f2d16SKris Kennaway process_init(void)
711b66f2d16SKris Kennaway {
712bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
713bc5531deSDag-Erling Smørgrav 	int r;
714b66f2d16SKris Kennaway 
715bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(iqueue, &version)) != 0)
71619261079SEd Maste 		fatal_fr(r, "parse");
717e146993eSDag-Erling Smørgrav 	verbose("received client version %u", version);
718bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
71919261079SEd Maste 		fatal_f("sshbuf_new failed");
720bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 ||
72119261079SEd Maste 	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
72219261079SEd Maste 		fatal_fr(r, "compose");
72319261079SEd Maste 
7241323ec57SEd Maste 	 /* extension advertisements */
72519261079SEd Maste 	compose_extension(msg, "posix-rename@openssh.com", "1");
72619261079SEd Maste 	compose_extension(msg, "statvfs@openssh.com", "2");
72719261079SEd Maste 	compose_extension(msg, "fstatvfs@openssh.com", "2");
72819261079SEd Maste 	compose_extension(msg, "hardlink@openssh.com", "1");
72919261079SEd Maste 	compose_extension(msg, "fsync@openssh.com", "1");
73019261079SEd Maste 	compose_extension(msg, "lsetstat@openssh.com", "1");
73119261079SEd Maste 	compose_extension(msg, "limits@openssh.com", "1");
73219261079SEd Maste 	compose_extension(msg, "expand-path@openssh.com", "1");
73387c1498dSEd Maste 	compose_extension(msg, "copy-data", "1");
73438a52bd3SEd Maste 	compose_extension(msg, "home-directory", "1");
73538a52bd3SEd Maste 	compose_extension(msg, "users-groups-by-id@openssh.com", "1");
73619261079SEd Maste 
737bc5531deSDag-Erling Smørgrav 	send_msg(msg);
738bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
739b66f2d16SKris Kennaway }
740b66f2d16SKris Kennaway 
741ae1f160dSDag-Erling Smørgrav static void
process_open(u_int32_t id)742f7167e0eSDag-Erling Smørgrav process_open(u_int32_t id)
743b66f2d16SKris Kennaway {
744f7167e0eSDag-Erling Smørgrav 	u_int32_t pflags;
745bc5531deSDag-Erling Smørgrav 	Attrib a;
746b66f2d16SKris Kennaway 	char *name;
747bc5531deSDag-Erling Smørgrav 	int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE;
748b66f2d16SKris Kennaway 
749bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
750bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */
751bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
75219261079SEd Maste 		fatal_fr(r, "parse");
753bc5531deSDag-Erling Smørgrav 
754761efaa7SDag-Erling Smørgrav 	debug3("request %u: open flags %d", id, pflags);
755b66f2d16SKris Kennaway 	flags = flags_from_portable(pflags);
756bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666;
757761efaa7SDag-Erling Smørgrav 	logit("open \"%s\" flags %s mode 0%o",
758761efaa7SDag-Erling Smørgrav 	    name, string_from_portable(pflags), mode);
759b15c8340SDag-Erling Smørgrav 	if (readonly &&
7604f52dfbbSDag-Erling Smørgrav 	    ((flags & O_ACCMODE) != O_RDONLY ||
7614f52dfbbSDag-Erling Smørgrav 	    (flags & (O_CREAT|O_TRUNC)) != 0)) {
762f7167e0eSDag-Erling Smørgrav 		verbose("Refusing open request in read-only mode");
763b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_PERMISSION_DENIED;
764f7167e0eSDag-Erling Smørgrav 	} else {
765b66f2d16SKris Kennaway 		fd = open(name, flags, mode);
76619261079SEd Maste 		if (fd == -1) {
767b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
768b66f2d16SKris Kennaway 		} else {
769f7167e0eSDag-Erling Smørgrav 			handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
770b66f2d16SKris Kennaway 			if (handle < 0) {
771b66f2d16SKris Kennaway 				close(fd);
772b66f2d16SKris Kennaway 			} else {
773b66f2d16SKris Kennaway 				send_handle(id, handle);
7741e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
775b66f2d16SKris Kennaway 			}
776b66f2d16SKris Kennaway 		}
777b15c8340SDag-Erling Smørgrav 	}
7781e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
779b66f2d16SKris Kennaway 		send_status(id, status);
780e4a9863fSDag-Erling Smørgrav 	free(name);
781b66f2d16SKris Kennaway }
782b66f2d16SKris Kennaway 
783ae1f160dSDag-Erling Smørgrav static void
process_close(u_int32_t id)784f7167e0eSDag-Erling Smørgrav process_close(u_int32_t id)
785b66f2d16SKris Kennaway {
786bc5531deSDag-Erling Smørgrav 	int r, handle, ret, status = SSH2_FX_FAILURE;
787b66f2d16SKris Kennaway 
788bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
78919261079SEd Maste 		fatal_fr(r, "parse");
790bc5531deSDag-Erling Smørgrav 
791761efaa7SDag-Erling Smørgrav 	debug3("request %u: close handle %u", id, handle);
792761efaa7SDag-Erling Smørgrav 	handle_log_close(handle, NULL);
793b66f2d16SKris Kennaway 	ret = handle_close(handle);
7941e8db6e2SBrian Feldman 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
795b66f2d16SKris Kennaway 	send_status(id, status);
796b66f2d16SKris Kennaway }
797b66f2d16SKris Kennaway 
798ae1f160dSDag-Erling Smørgrav static void
process_read(u_int32_t id)799f7167e0eSDag-Erling Smørgrav process_read(u_int32_t id)
800b66f2d16SKris Kennaway {
80119261079SEd Maste 	static u_char *buf;
80219261079SEd Maste 	static size_t buflen;
803f7167e0eSDag-Erling Smørgrav 	u_int32_t len;
804bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status = SSH2_FX_FAILURE;
805b66f2d16SKris Kennaway 	u_int64_t off;
806b66f2d16SKris Kennaway 
807bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
808bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
809bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(iqueue, &len)) != 0)
81019261079SEd Maste 		fatal_fr(r, "parse");
811b66f2d16SKris Kennaway 
81219261079SEd Maste 	debug("request %u: read \"%s\" (handle %d) off %llu len %u",
813761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
81419261079SEd Maste 	if ((fd = handle_to_fd(handle)) == -1)
81519261079SEd Maste 		goto out;
81619261079SEd Maste 	if (len > SFTP_MAX_READ_LENGTH) {
81719261079SEd Maste 		debug2("read change len %u to %u", len, SFTP_MAX_READ_LENGTH);
81819261079SEd Maste 		len = SFTP_MAX_READ_LENGTH;
819b66f2d16SKris Kennaway 	}
82019261079SEd Maste 	if (len > buflen) {
82119261079SEd Maste 		debug3_f("allocate %zu => %u", buflen, len);
8224d3fc8b0SEd Maste 		if ((buf = realloc(buf, len)) == NULL)
82319261079SEd Maste 			fatal_f("realloc failed");
82419261079SEd Maste 		buflen = len;
82519261079SEd Maste 	}
82619261079SEd Maste 	if (lseek(fd, off, SEEK_SET) == -1) {
827b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
82819261079SEd Maste 		error_f("seek \"%.100s\": %s", handle_to_name(handle),
82919261079SEd Maste 		    strerror(errno));
83019261079SEd Maste 		goto out;
83119261079SEd Maste 	}
83219261079SEd Maste 	if (len == 0) {
83319261079SEd Maste 		/* weird, but not strictly disallowed */
83419261079SEd Maste 		ret = 0;
83519261079SEd Maste 	} else if ((ret = read(fd, buf, len)) == -1) {
836b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
83719261079SEd Maste 		error_f("read \"%.100s\": %s", handle_to_name(handle),
83819261079SEd Maste 		    strerror(errno));
83919261079SEd Maste 		goto out;
840b66f2d16SKris Kennaway 	} else if (ret == 0) {
8411e8db6e2SBrian Feldman 		status = SSH2_FX_EOF;
84219261079SEd Maste 		goto out;
84319261079SEd Maste 	}
844b66f2d16SKris Kennaway 	send_data(id, buf, ret);
845761efaa7SDag-Erling Smørgrav 	handle_update_read(handle, ret);
84619261079SEd Maste 	/* success */
84719261079SEd Maste 	status = SSH2_FX_OK;
84819261079SEd Maste  out:
8491e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
850b66f2d16SKris Kennaway 		send_status(id, status);
851b66f2d16SKris Kennaway }
852b66f2d16SKris Kennaway 
853ae1f160dSDag-Erling Smørgrav static void
process_write(u_int32_t id)854f7167e0eSDag-Erling Smørgrav process_write(u_int32_t id)
855b66f2d16SKris Kennaway {
856b66f2d16SKris Kennaway 	u_int64_t off;
857bc5531deSDag-Erling Smørgrav 	size_t len;
858bc5531deSDag-Erling Smørgrav 	int r, handle, fd, ret, status;
859bc5531deSDag-Erling Smørgrav 	u_char *data;
860b66f2d16SKris Kennaway 
861bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
862bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(iqueue, &off)) != 0 ||
863bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_string(iqueue, &data, &len)) != 0)
86419261079SEd Maste 		fatal_fr(r, "parse");
865b66f2d16SKris Kennaway 
866bc5531deSDag-Erling Smørgrav 	debug("request %u: write \"%s\" (handle %d) off %llu len %zu",
867761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
868b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
869b15c8340SDag-Erling Smørgrav 
870b15c8340SDag-Erling Smørgrav 	if (fd < 0)
871b15c8340SDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
872b15c8340SDag-Erling Smørgrav 	else {
873f7167e0eSDag-Erling Smørgrav 		if (!(handle_to_flags(handle) & O_APPEND) &&
87419261079SEd Maste 		    lseek(fd, off, SEEK_SET) == -1) {
875b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
87619261079SEd Maste 			error_f("seek \"%.100s\": %s", handle_to_name(handle),
87719261079SEd Maste 			    strerror(errno));
878b66f2d16SKris Kennaway 		} else {
879b66f2d16SKris Kennaway /* XXX ATOMICIO ? */
880b66f2d16SKris Kennaway 			ret = write(fd, data, len);
88119261079SEd Maste 			if (ret == -1) {
882b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
88319261079SEd Maste 				error_f("write \"%.100s\": %s",
88419261079SEd Maste 				    handle_to_name(handle), strerror(errno));
885043840dfSDag-Erling Smørgrav 			} else if ((size_t)ret == len) {
8861e8db6e2SBrian Feldman 				status = SSH2_FX_OK;
887761efaa7SDag-Erling Smørgrav 				handle_update_write(handle, ret);
888b66f2d16SKris Kennaway 			} else {
88919261079SEd Maste 				debug2_f("nothing at all written");
890b15c8340SDag-Erling Smørgrav 				status = SSH2_FX_FAILURE;
891b66f2d16SKris Kennaway 			}
892b66f2d16SKris Kennaway 		}
893b66f2d16SKris Kennaway 	}
894b66f2d16SKris Kennaway 	send_status(id, status);
895e4a9863fSDag-Erling Smørgrav 	free(data);
896b66f2d16SKris Kennaway }
897b66f2d16SKris Kennaway 
898ae1f160dSDag-Erling Smørgrav static void
process_do_stat(u_int32_t id,int do_lstat)899f7167e0eSDag-Erling Smørgrav process_do_stat(u_int32_t id, int do_lstat)
900b66f2d16SKris Kennaway {
9011e8db6e2SBrian Feldman 	Attrib a;
902b66f2d16SKris Kennaway 	struct stat st;
903b66f2d16SKris Kennaway 	char *name;
904bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
905b66f2d16SKris Kennaway 
906bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
90719261079SEd Maste 		fatal_fr(r, "parse");
908bc5531deSDag-Erling Smørgrav 
909761efaa7SDag-Erling Smørgrav 	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
910761efaa7SDag-Erling Smørgrav 	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
911bc5531deSDag-Erling Smørgrav 	r = do_lstat ? lstat(name, &st) : stat(name, &st);
91219261079SEd Maste 	if (r == -1) {
913b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
914b66f2d16SKris Kennaway 	} else {
9151e8db6e2SBrian Feldman 		stat_to_attrib(&st, &a);
9161e8db6e2SBrian Feldman 		send_attrib(id, &a);
9171e8db6e2SBrian Feldman 		status = SSH2_FX_OK;
918b66f2d16SKris Kennaway 	}
9191e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
920b66f2d16SKris Kennaway 		send_status(id, status);
921e4a9863fSDag-Erling Smørgrav 	free(name);
922b66f2d16SKris Kennaway }
923b66f2d16SKris Kennaway 
924ae1f160dSDag-Erling Smørgrav static void
process_stat(u_int32_t id)925f7167e0eSDag-Erling Smørgrav process_stat(u_int32_t id)
926b66f2d16SKris Kennaway {
927f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 0);
928b66f2d16SKris Kennaway }
929b66f2d16SKris Kennaway 
930ae1f160dSDag-Erling Smørgrav static void
process_lstat(u_int32_t id)931f7167e0eSDag-Erling Smørgrav process_lstat(u_int32_t id)
932b66f2d16SKris Kennaway {
933f7167e0eSDag-Erling Smørgrav 	process_do_stat(id, 1);
934b66f2d16SKris Kennaway }
935b66f2d16SKris Kennaway 
936ae1f160dSDag-Erling Smørgrav static void
process_fstat(u_int32_t id)937f7167e0eSDag-Erling Smørgrav process_fstat(u_int32_t id)
938b66f2d16SKris Kennaway {
9391e8db6e2SBrian Feldman 	Attrib a;
940b66f2d16SKris Kennaway 	struct stat st;
941bc5531deSDag-Erling Smørgrav 	int fd, r, handle, status = SSH2_FX_FAILURE;
942b66f2d16SKris Kennaway 
943bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
94419261079SEd Maste 		fatal_fr(r, "parse");
945761efaa7SDag-Erling Smørgrav 	debug("request %u: fstat \"%s\" (handle %u)",
946761efaa7SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
947b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
948b66f2d16SKris Kennaway 	if (fd >= 0) {
949bc5531deSDag-Erling Smørgrav 		r = fstat(fd, &st);
95019261079SEd Maste 		if (r == -1) {
951b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
952b66f2d16SKris Kennaway 		} else {
9531e8db6e2SBrian Feldman 			stat_to_attrib(&st, &a);
9541e8db6e2SBrian Feldman 			send_attrib(id, &a);
9551e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
956b66f2d16SKris Kennaway 		}
957b66f2d16SKris Kennaway 	}
9581e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
959b66f2d16SKris Kennaway 		send_status(id, status);
960b66f2d16SKris Kennaway }
961b66f2d16SKris Kennaway 
962ae1f160dSDag-Erling Smørgrav static struct timeval *
attrib_to_tv(const Attrib * a)963efcad6b7SDag-Erling Smørgrav attrib_to_tv(const Attrib *a)
964b66f2d16SKris Kennaway {
965b66f2d16SKris Kennaway 	static struct timeval tv[2];
9661e8db6e2SBrian Feldman 
967b66f2d16SKris Kennaway 	tv[0].tv_sec = a->atime;
968b66f2d16SKris Kennaway 	tv[0].tv_usec = 0;
969b66f2d16SKris Kennaway 	tv[1].tv_sec = a->mtime;
970b66f2d16SKris Kennaway 	tv[1].tv_usec = 0;
971b66f2d16SKris Kennaway 	return tv;
972b66f2d16SKris Kennaway }
973b66f2d16SKris Kennaway 
97419261079SEd Maste static struct timespec *
attrib_to_ts(const Attrib * a)97519261079SEd Maste attrib_to_ts(const Attrib *a)
97619261079SEd Maste {
97719261079SEd Maste 	static struct timespec ts[2];
97819261079SEd Maste 
97919261079SEd Maste 	ts[0].tv_sec = a->atime;
98019261079SEd Maste 	ts[0].tv_nsec = 0;
98119261079SEd Maste 	ts[1].tv_sec = a->mtime;
98219261079SEd Maste 	ts[1].tv_nsec = 0;
98319261079SEd Maste 	return ts;
98419261079SEd Maste }
98519261079SEd Maste 
986ae1f160dSDag-Erling Smørgrav static void
process_setstat(u_int32_t id)987f7167e0eSDag-Erling Smørgrav process_setstat(u_int32_t id)
988b66f2d16SKris Kennaway {
989bc5531deSDag-Erling Smørgrav 	Attrib a;
990b66f2d16SKris Kennaway 	char *name;
991bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_OK;
992b66f2d16SKris Kennaway 
993bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
994bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
99519261079SEd Maste 		fatal_fr(r, "parse");
996bc5531deSDag-Erling Smørgrav 
997761efaa7SDag-Erling Smørgrav 	debug("request %u: setstat name \"%s\"", id, name);
998bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
999d4af9e69SDag-Erling Smørgrav 		logit("set \"%s\" size %llu",
1000bc5531deSDag-Erling Smørgrav 		    name, (unsigned long long)a.size);
1001bc5531deSDag-Erling Smørgrav 		r = truncate(name, a.size);
1002bc5531deSDag-Erling Smørgrav 		if (r == -1)
1003ae1f160dSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1004ae1f160dSDag-Erling Smørgrav 	}
1005bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1006bc5531deSDag-Erling Smørgrav 		logit("set \"%s\" mode %04o", name, a.perm);
1007bc5531deSDag-Erling Smørgrav 		r = chmod(name, a.perm & 07777);
1008bc5531deSDag-Erling Smørgrav 		if (r == -1)
1009b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
1010b66f2d16SKris Kennaway 	}
1011bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1012761efaa7SDag-Erling Smørgrav 		char buf[64];
1013bc5531deSDag-Erling Smørgrav 		time_t t = a.mtime;
1014761efaa7SDag-Erling Smørgrav 
1015761efaa7SDag-Erling Smørgrav 		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
1016761efaa7SDag-Erling Smørgrav 		    localtime(&t));
1017761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" modtime %s", name, buf);
1018bc5531deSDag-Erling Smørgrav 		r = utimes(name, attrib_to_tv(&a));
1019bc5531deSDag-Erling Smørgrav 		if (r == -1)
1020b66f2d16SKris Kennaway 			status = errno_to_portable(errno);
1021b66f2d16SKris Kennaway 	}
1022bc5531deSDag-Erling Smørgrav 	if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
1023761efaa7SDag-Erling Smørgrav 		logit("set \"%s\" owner %lu group %lu", name,
1024bc5531deSDag-Erling Smørgrav 		    (u_long)a.uid, (u_long)a.gid);
1025bc5531deSDag-Erling Smørgrav 		r = chown(name, a.uid, a.gid);
1026bc5531deSDag-Erling Smørgrav 		if (r == -1)
10271e8db6e2SBrian Feldman 			status = errno_to_portable(errno);
10281e8db6e2SBrian Feldman 	}
1029b66f2d16SKris Kennaway 	send_status(id, status);
1030e4a9863fSDag-Erling Smørgrav 	free(name);
1031b66f2d16SKris Kennaway }
1032b66f2d16SKris Kennaway 
1033ae1f160dSDag-Erling Smørgrav static void
process_fsetstat(u_int32_t id)1034f7167e0eSDag-Erling Smørgrav process_fsetstat(u_int32_t id)
1035b66f2d16SKris Kennaway {
1036bc5531deSDag-Erling Smørgrav 	Attrib a;
1037bc5531deSDag-Erling Smørgrav 	int handle, fd, r;
10381e8db6e2SBrian Feldman 	int status = SSH2_FX_OK;
1039b66f2d16SKris Kennaway 
1040bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0 ||
1041bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
104219261079SEd Maste 		fatal_fr(r, "parse");
1043bc5531deSDag-Erling Smørgrav 
1044761efaa7SDag-Erling Smørgrav 	debug("request %u: fsetstat handle %d", id, handle);
1045b66f2d16SKris Kennaway 	fd = handle_to_fd(handle);
1046b15c8340SDag-Erling Smørgrav 	if (fd < 0)
10471e8db6e2SBrian Feldman 		status = SSH2_FX_FAILURE;
1048b15c8340SDag-Erling Smørgrav 	else {
1049761efaa7SDag-Erling Smørgrav 		char *name = handle_to_name(handle);
1050761efaa7SDag-Erling Smørgrav 
1051bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
1052d4af9e69SDag-Erling Smørgrav 			logit("set \"%s\" size %llu",
1053bc5531deSDag-Erling Smørgrav 			    name, (unsigned long long)a.size);
1054bc5531deSDag-Erling Smørgrav 			r = ftruncate(fd, a.size);
1055bc5531deSDag-Erling Smørgrav 			if (r == -1)
1056ae1f160dSDag-Erling Smørgrav 				status = errno_to_portable(errno);
1057ae1f160dSDag-Erling Smørgrav 		}
1058bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1059bc5531deSDag-Erling Smørgrav 			logit("set \"%s\" mode %04o", name, a.perm);
106083d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
1061bc5531deSDag-Erling Smørgrav 			r = fchmod(fd, a.perm & 07777);
106283d2307dSDag-Erling Smørgrav #else
1063bc5531deSDag-Erling Smørgrav 			r = chmod(name, a.perm & 07777);
106483d2307dSDag-Erling Smørgrav #endif
1065bc5531deSDag-Erling Smørgrav 			if (r == -1)
1066b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
1067b66f2d16SKris Kennaway 		}
1068bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1069761efaa7SDag-Erling Smørgrav 			char buf[64];
1070bc5531deSDag-Erling Smørgrav 			time_t t = a.mtime;
1071761efaa7SDag-Erling Smørgrav 
1072761efaa7SDag-Erling Smørgrav 			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
1073761efaa7SDag-Erling Smørgrav 			    localtime(&t));
1074761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" modtime %s", name, buf);
107583d2307dSDag-Erling Smørgrav #ifdef HAVE_FUTIMES
1076bc5531deSDag-Erling Smørgrav 			r = futimes(fd, attrib_to_tv(&a));
107783d2307dSDag-Erling Smørgrav #else
1078bc5531deSDag-Erling Smørgrav 			r = utimes(name, attrib_to_tv(&a));
107983d2307dSDag-Erling Smørgrav #endif
1080bc5531deSDag-Erling Smørgrav 			if (r == -1)
1081b66f2d16SKris Kennaway 				status = errno_to_portable(errno);
1082b66f2d16SKris Kennaway 		}
1083bc5531deSDag-Erling Smørgrav 		if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
1084761efaa7SDag-Erling Smørgrav 			logit("set \"%s\" owner %lu group %lu", name,
1085bc5531deSDag-Erling Smørgrav 			    (u_long)a.uid, (u_long)a.gid);
108683d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHOWN
1087bc5531deSDag-Erling Smørgrav 			r = fchown(fd, a.uid, a.gid);
108883d2307dSDag-Erling Smørgrav #else
1089bc5531deSDag-Erling Smørgrav 			r = chown(name, a.uid, a.gid);
109083d2307dSDag-Erling Smørgrav #endif
1091bc5531deSDag-Erling Smørgrav 			if (r == -1)
10921e8db6e2SBrian Feldman 				status = errno_to_portable(errno);
10931e8db6e2SBrian Feldman 		}
1094b66f2d16SKris Kennaway 	}
1095b66f2d16SKris Kennaway 	send_status(id, status);
1096b66f2d16SKris Kennaway }
1097b66f2d16SKris Kennaway 
1098ae1f160dSDag-Erling Smørgrav static void
process_opendir(u_int32_t id)1099f7167e0eSDag-Erling Smørgrav process_opendir(u_int32_t id)
1100b66f2d16SKris Kennaway {
1101b66f2d16SKris Kennaway 	DIR *dirp = NULL;
1102b66f2d16SKris Kennaway 	char *path;
1103bc5531deSDag-Erling Smørgrav 	int r, handle, status = SSH2_FX_FAILURE;
1104b66f2d16SKris Kennaway 
1105bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
110619261079SEd Maste 		fatal_fr(r, "parse");
1107bc5531deSDag-Erling Smørgrav 
1108761efaa7SDag-Erling Smørgrav 	debug3("request %u: opendir", id);
1109761efaa7SDag-Erling Smørgrav 	logit("opendir \"%s\"", path);
1110b66f2d16SKris Kennaway 	dirp = opendir(path);
1111b66f2d16SKris Kennaway 	if (dirp == NULL) {
1112b66f2d16SKris Kennaway 		status = errno_to_portable(errno);
1113b66f2d16SKris Kennaway 	} else {
1114f7167e0eSDag-Erling Smørgrav 		handle = handle_new(HANDLE_DIR, path, 0, 0, dirp);
1115b66f2d16SKris Kennaway 		if (handle < 0) {
1116b66f2d16SKris Kennaway 			closedir(dirp);
1117b66f2d16SKris Kennaway 		} else {
1118b66f2d16SKris Kennaway 			send_handle(id, handle);
11191e8db6e2SBrian Feldman 			status = SSH2_FX_OK;
1120b66f2d16SKris Kennaway 		}
1121b66f2d16SKris Kennaway 
1122b66f2d16SKris Kennaway 	}
11231e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
1124b66f2d16SKris Kennaway 		send_status(id, status);
1125e4a9863fSDag-Erling Smørgrav 	free(path);
1126b66f2d16SKris Kennaway }
1127b66f2d16SKris Kennaway 
1128ae1f160dSDag-Erling Smørgrav static void
process_readdir(u_int32_t id)1129f7167e0eSDag-Erling Smørgrav process_readdir(u_int32_t id)
1130b66f2d16SKris Kennaway {
1131b66f2d16SKris Kennaway 	DIR *dirp;
1132b66f2d16SKris Kennaway 	struct dirent *dp;
1133b66f2d16SKris Kennaway 	char *path;
1134bc5531deSDag-Erling Smørgrav 	int r, handle;
1135b66f2d16SKris Kennaway 
1136bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
113719261079SEd Maste 		fatal_fr(r, "parse");
1138bc5531deSDag-Erling Smørgrav 
1139761efaa7SDag-Erling Smørgrav 	debug("request %u: readdir \"%s\" (handle %d)", id,
1140761efaa7SDag-Erling Smørgrav 	    handle_to_name(handle), handle);
1141b66f2d16SKris Kennaway 	dirp = handle_to_dir(handle);
1142b66f2d16SKris Kennaway 	path = handle_to_name(handle);
1143b66f2d16SKris Kennaway 	if (dirp == NULL || path == NULL) {
11441e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_FAILURE);
1145b66f2d16SKris Kennaway 	} else {
1146b66f2d16SKris Kennaway 		struct stat st;
1147bc5531deSDag-Erling Smørgrav 		char pathname[PATH_MAX];
1148b66f2d16SKris Kennaway 		Stat *stats;
1149b66f2d16SKris Kennaway 		int nstats = 10, count = 0, i;
1150ee21a45fSDag-Erling Smørgrav 
1151761efaa7SDag-Erling Smørgrav 		stats = xcalloc(nstats, sizeof(Stat));
1152b66f2d16SKris Kennaway 		while ((dp = readdir(dirp)) != NULL) {
1153b66f2d16SKris Kennaway 			if (count >= nstats) {
1154b66f2d16SKris Kennaway 				nstats *= 2;
1155557f75e5SDag-Erling Smørgrav 				stats = xreallocarray(stats, nstats, sizeof(Stat));
1156b66f2d16SKris Kennaway 			}
1157b66f2d16SKris Kennaway /* XXX OVERFLOW ? */
1158ae1f160dSDag-Erling Smørgrav 			snprintf(pathname, sizeof pathname, "%s%s%s", path,
1159ae1f160dSDag-Erling Smørgrav 			    strcmp(path, "/") ? "/" : "", dp->d_name);
116019261079SEd Maste 			if (lstat(pathname, &st) == -1)
1161b66f2d16SKris Kennaway 				continue;
11621e8db6e2SBrian Feldman 			stat_to_attrib(&st, &(stats[count].attrib));
1163b66f2d16SKris Kennaway 			stats[count].name = xstrdup(dp->d_name);
116438a52bd3SEd Maste 			stats[count].long_name = ls_file(dp->d_name, &st,
116538a52bd3SEd Maste 			    0, 0, NULL, NULL);
1166b66f2d16SKris Kennaway 			count++;
1167b66f2d16SKris Kennaway 			/* send up to 100 entries in one message */
11681e8db6e2SBrian Feldman 			/* XXX check packet size instead */
1169b66f2d16SKris Kennaway 			if (count == 100)
1170b66f2d16SKris Kennaway 				break;
1171b66f2d16SKris Kennaway 		}
11721e8db6e2SBrian Feldman 		if (count > 0) {
1173b66f2d16SKris Kennaway 			send_names(id, count, stats);
1174b66f2d16SKris Kennaway 			for (i = 0; i < count; i++) {
1175e4a9863fSDag-Erling Smørgrav 				free(stats[i].name);
1176e4a9863fSDag-Erling Smørgrav 				free(stats[i].long_name);
1177b66f2d16SKris Kennaway 			}
11781e8db6e2SBrian Feldman 		} else {
11791e8db6e2SBrian Feldman 			send_status(id, SSH2_FX_EOF);
11801e8db6e2SBrian Feldman 		}
1181e4a9863fSDag-Erling Smørgrav 		free(stats);
1182b66f2d16SKris Kennaway 	}
1183b66f2d16SKris Kennaway }
1184b66f2d16SKris Kennaway 
1185ae1f160dSDag-Erling Smørgrav static void
process_remove(u_int32_t id)1186f7167e0eSDag-Erling Smørgrav process_remove(u_int32_t id)
1187b66f2d16SKris Kennaway {
1188b66f2d16SKris Kennaway 	char *name;
1189bc5531deSDag-Erling Smørgrav 	int r, status = SSH2_FX_FAILURE;
1190b66f2d16SKris Kennaway 
1191bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
119219261079SEd Maste 		fatal_fr(r, "parse");
1193bc5531deSDag-Erling Smørgrav 
1194761efaa7SDag-Erling Smørgrav 	debug3("request %u: remove", id);
1195761efaa7SDag-Erling Smørgrav 	logit("remove name \"%s\"", name);
1196bc5531deSDag-Erling Smørgrav 	r = unlink(name);
1197bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1198b66f2d16SKris Kennaway 	send_status(id, status);
1199e4a9863fSDag-Erling Smørgrav 	free(name);
1200b66f2d16SKris Kennaway }
1201b66f2d16SKris Kennaway 
1202ae1f160dSDag-Erling Smørgrav static void
process_mkdir(u_int32_t id)1203f7167e0eSDag-Erling Smørgrav process_mkdir(u_int32_t id)
1204b66f2d16SKris Kennaway {
1205bc5531deSDag-Erling Smørgrav 	Attrib a;
1206b66f2d16SKris Kennaway 	char *name;
1207bc5531deSDag-Erling Smørgrav 	int r, mode, status = SSH2_FX_FAILURE;
1208b66f2d16SKris Kennaway 
1209bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
1210bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(iqueue, &a)) != 0)
121119261079SEd Maste 		fatal_fr(r, "parse");
1212bc5531deSDag-Erling Smørgrav 
1213bc5531deSDag-Erling Smørgrav 	mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
1214bc5531deSDag-Erling Smørgrav 	    a.perm & 07777 : 0777;
1215761efaa7SDag-Erling Smørgrav 	debug3("request %u: mkdir", id);
1216761efaa7SDag-Erling Smørgrav 	logit("mkdir name \"%s\" mode 0%o", name, mode);
1217bc5531deSDag-Erling Smørgrav 	r = mkdir(name, mode);
1218bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1219b66f2d16SKris Kennaway 	send_status(id, status);
1220e4a9863fSDag-Erling Smørgrav 	free(name);
1221b66f2d16SKris Kennaway }
1222b66f2d16SKris Kennaway 
1223ae1f160dSDag-Erling Smørgrav static void
process_rmdir(u_int32_t id)1224f7167e0eSDag-Erling Smørgrav process_rmdir(u_int32_t id)
1225b66f2d16SKris Kennaway {
1226b66f2d16SKris Kennaway 	char *name;
1227bc5531deSDag-Erling Smørgrav 	int r, status;
1228b66f2d16SKris Kennaway 
1229bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
123019261079SEd Maste 		fatal_fr(r, "parse");
1231bc5531deSDag-Erling Smørgrav 
1232761efaa7SDag-Erling Smørgrav 	debug3("request %u: rmdir", id);
1233761efaa7SDag-Erling Smørgrav 	logit("rmdir name \"%s\"", name);
1234bc5531deSDag-Erling Smørgrav 	r = rmdir(name);
1235bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1236b66f2d16SKris Kennaway 	send_status(id, status);
1237e4a9863fSDag-Erling Smørgrav 	free(name);
1238b66f2d16SKris Kennaway }
1239b66f2d16SKris Kennaway 
1240ae1f160dSDag-Erling Smørgrav static void
process_realpath(u_int32_t id)1241f7167e0eSDag-Erling Smørgrav process_realpath(u_int32_t id)
1242b66f2d16SKris Kennaway {
1243bc5531deSDag-Erling Smørgrav 	char resolvedname[PATH_MAX];
1244b66f2d16SKris Kennaway 	char *path;
1245bc5531deSDag-Erling Smørgrav 	int r;
1246b66f2d16SKris Kennaway 
1247bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
124819261079SEd Maste 		fatal_fr(r, "parse");
1249bc5531deSDag-Erling Smørgrav 
12501e8db6e2SBrian Feldman 	if (path[0] == '\0') {
1251e4a9863fSDag-Erling Smørgrav 		free(path);
12521e8db6e2SBrian Feldman 		path = xstrdup(".");
12531e8db6e2SBrian Feldman 	}
1254761efaa7SDag-Erling Smørgrav 	debug3("request %u: realpath", id);
1255761efaa7SDag-Erling Smørgrav 	verbose("realpath \"%s\"", path);
125619261079SEd Maste 	if (sftp_realpath(path, resolvedname) == NULL) {
1257b66f2d16SKris Kennaway 		send_status(id, errno_to_portable(errno));
1258b66f2d16SKris Kennaway 	} else {
1259b66f2d16SKris Kennaway 		Stat s;
1260b66f2d16SKris Kennaway 		attrib_clear(&s.attrib);
1261b66f2d16SKris Kennaway 		s.name = s.long_name = resolvedname;
1262b66f2d16SKris Kennaway 		send_names(id, 1, &s);
1263b66f2d16SKris Kennaway 	}
1264e4a9863fSDag-Erling Smørgrav 	free(path);
1265b66f2d16SKris Kennaway }
1266b66f2d16SKris Kennaway 
1267ae1f160dSDag-Erling Smørgrav static void
process_rename(u_int32_t id)1268f7167e0eSDag-Erling Smørgrav process_rename(u_int32_t id)
1269b66f2d16SKris Kennaway {
1270b66f2d16SKris Kennaway 	char *oldpath, *newpath;
1271bc5531deSDag-Erling Smørgrav 	int r, status;
1272d0c8c0bcSDag-Erling Smørgrav 	struct stat sb;
1273b66f2d16SKris Kennaway 
1274bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1275bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
127619261079SEd Maste 		fatal_fr(r, "parse");
1277bc5531deSDag-Erling Smørgrav 
1278761efaa7SDag-Erling Smørgrav 	debug3("request %u: rename", id);
1279761efaa7SDag-Erling Smørgrav 	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1280d0c8c0bcSDag-Erling Smørgrav 	status = SSH2_FX_FAILURE;
1281f7167e0eSDag-Erling Smørgrav 	if (lstat(oldpath, &sb) == -1)
1282d0c8c0bcSDag-Erling Smørgrav 		status = errno_to_portable(errno);
1283d0c8c0bcSDag-Erling Smørgrav 	else if (S_ISREG(sb.st_mode)) {
1284d0c8c0bcSDag-Erling Smørgrav 		/* Race-free rename of regular files */
1285d74d50a8SDag-Erling Smørgrav 		if (link(oldpath, newpath) == -1) {
12867aee6ffeSDag-Erling Smørgrav 			if (errno == EOPNOTSUPP || errno == ENOSYS
1287d4af9e69SDag-Erling Smørgrav #ifdef EXDEV
1288d4af9e69SDag-Erling Smørgrav 			    || errno == EXDEV
1289d4af9e69SDag-Erling Smørgrav #endif
1290d74d50a8SDag-Erling Smørgrav #ifdef LINK_OPNOTSUPP_ERRNO
1291d74d50a8SDag-Erling Smørgrav 			    || errno == LINK_OPNOTSUPP_ERRNO
1292d74d50a8SDag-Erling Smørgrav #endif
1293d74d50a8SDag-Erling Smørgrav 			    ) {
1294d74d50a8SDag-Erling Smørgrav 				struct stat st;
1295d74d50a8SDag-Erling Smørgrav 
1296d74d50a8SDag-Erling Smørgrav 				/*
1297d74d50a8SDag-Erling Smørgrav 				 * fs doesn't support links, so fall back to
1298d74d50a8SDag-Erling Smørgrav 				 * stat+rename.  This is racy.
1299d74d50a8SDag-Erling Smørgrav 				 */
1300d74d50a8SDag-Erling Smørgrav 				if (stat(newpath, &st) == -1) {
1301d74d50a8SDag-Erling Smørgrav 					if (rename(oldpath, newpath) == -1)
1302d74d50a8SDag-Erling Smørgrav 						status =
1303d74d50a8SDag-Erling Smørgrav 						    errno_to_portable(errno);
1304d74d50a8SDag-Erling Smørgrav 					else
1305d74d50a8SDag-Erling Smørgrav 						status = SSH2_FX_OK;
1306d74d50a8SDag-Erling Smørgrav 				}
1307d74d50a8SDag-Erling Smørgrav 			} else {
1308d0c8c0bcSDag-Erling Smørgrav 				status = errno_to_portable(errno);
1309d74d50a8SDag-Erling Smørgrav 			}
1310d74d50a8SDag-Erling Smørgrav 		} else if (unlink(oldpath) == -1) {
1311d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1312d0c8c0bcSDag-Erling Smørgrav 			/* clean spare link */
1313d0c8c0bcSDag-Erling Smørgrav 			unlink(newpath);
1314d0c8c0bcSDag-Erling Smørgrav 		} else
1315d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
1316d0c8c0bcSDag-Erling Smørgrav 	} else if (stat(newpath, &sb) == -1) {
1317d0c8c0bcSDag-Erling Smørgrav 		if (rename(oldpath, newpath) == -1)
1318d0c8c0bcSDag-Erling Smørgrav 			status = errno_to_portable(errno);
1319d0c8c0bcSDag-Erling Smørgrav 		else
1320d0c8c0bcSDag-Erling Smørgrav 			status = SSH2_FX_OK;
13211e8db6e2SBrian Feldman 	}
1322b66f2d16SKris Kennaway 	send_status(id, status);
1323e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1324e4a9863fSDag-Erling Smørgrav 	free(newpath);
1325b66f2d16SKris Kennaway }
1326b66f2d16SKris Kennaway 
1327ae1f160dSDag-Erling Smørgrav static void
process_readlink(u_int32_t id)1328f7167e0eSDag-Erling Smørgrav process_readlink(u_int32_t id)
13291e8db6e2SBrian Feldman {
1330bc5531deSDag-Erling Smørgrav 	int r, len;
1331bc5531deSDag-Erling Smørgrav 	char buf[PATH_MAX];
13321e8db6e2SBrian Feldman 	char *path;
13331e8db6e2SBrian Feldman 
1334bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
133519261079SEd Maste 		fatal_fr(r, "parse");
1336bc5531deSDag-Erling Smørgrav 
1337761efaa7SDag-Erling Smørgrav 	debug3("request %u: readlink", id);
1338761efaa7SDag-Erling Smørgrav 	verbose("readlink \"%s\"", path);
1339d74d50a8SDag-Erling Smørgrav 	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
13401e8db6e2SBrian Feldman 		send_status(id, errno_to_portable(errno));
13411e8db6e2SBrian Feldman 	else {
13421e8db6e2SBrian Feldman 		Stat s;
13431e8db6e2SBrian Feldman 
1344d74d50a8SDag-Erling Smørgrav 		buf[len] = '\0';
13451e8db6e2SBrian Feldman 		attrib_clear(&s.attrib);
1346d74d50a8SDag-Erling Smørgrav 		s.name = s.long_name = buf;
13471e8db6e2SBrian Feldman 		send_names(id, 1, &s);
13481e8db6e2SBrian Feldman 	}
1349e4a9863fSDag-Erling Smørgrav 	free(path);
13501e8db6e2SBrian Feldman }
13511e8db6e2SBrian Feldman 
1352ae1f160dSDag-Erling Smørgrav static void
process_symlink(u_int32_t id)1353f7167e0eSDag-Erling Smørgrav process_symlink(u_int32_t id)
13541e8db6e2SBrian Feldman {
13551e8db6e2SBrian Feldman 	char *oldpath, *newpath;
1356bc5531deSDag-Erling Smørgrav 	int r, status;
13571e8db6e2SBrian Feldman 
1358bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1359bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
136019261079SEd Maste 		fatal_fr(r, "parse");
1361bc5531deSDag-Erling Smørgrav 
1362761efaa7SDag-Erling Smørgrav 	debug3("request %u: symlink", id);
1363761efaa7SDag-Erling Smørgrav 	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1364d0c8c0bcSDag-Erling Smørgrav 	/* this will fail if 'newpath' exists */
1365bc5531deSDag-Erling Smørgrav 	r = symlink(oldpath, newpath);
1366bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
13671e8db6e2SBrian Feldman 	send_status(id, status);
1368e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1369e4a9863fSDag-Erling Smørgrav 	free(newpath);
13701e8db6e2SBrian Feldman }
13711e8db6e2SBrian Feldman 
1372ae1f160dSDag-Erling Smørgrav static void
process_extended_posix_rename(u_int32_t id)1373d4af9e69SDag-Erling Smørgrav process_extended_posix_rename(u_int32_t id)
1374d4af9e69SDag-Erling Smørgrav {
1375d4af9e69SDag-Erling Smørgrav 	char *oldpath, *newpath;
1376bc5531deSDag-Erling Smørgrav 	int r, status;
1377d4af9e69SDag-Erling Smørgrav 
1378bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1379bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
138019261079SEd Maste 		fatal_fr(r, "parse");
1381bc5531deSDag-Erling Smørgrav 
1382d4af9e69SDag-Erling Smørgrav 	debug3("request %u: posix-rename", id);
1383d4af9e69SDag-Erling Smørgrav 	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1384bc5531deSDag-Erling Smørgrav 	r = rename(oldpath, newpath);
1385bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1386b15c8340SDag-Erling Smørgrav 	send_status(id, status);
1387e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1388e4a9863fSDag-Erling Smørgrav 	free(newpath);
1389d4af9e69SDag-Erling Smørgrav }
1390d4af9e69SDag-Erling Smørgrav 
1391d4af9e69SDag-Erling Smørgrav static void
process_extended_statvfs(u_int32_t id)1392d4af9e69SDag-Erling Smørgrav process_extended_statvfs(u_int32_t id)
1393d4af9e69SDag-Erling Smørgrav {
1394d4af9e69SDag-Erling Smørgrav 	char *path;
1395d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1396bc5531deSDag-Erling Smørgrav 	int r;
1397d4af9e69SDag-Erling Smørgrav 
1398bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
139919261079SEd Maste 		fatal_fr(r, "parse");
1400f7167e0eSDag-Erling Smørgrav 	debug3("request %u: statvfs", id);
1401f7167e0eSDag-Erling Smørgrav 	logit("statvfs \"%s\"", path);
1402d4af9e69SDag-Erling Smørgrav 
1403d4af9e69SDag-Erling Smørgrav 	if (statvfs(path, &st) != 0)
1404d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1405d4af9e69SDag-Erling Smørgrav 	else
1406d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1407e4a9863fSDag-Erling Smørgrav 	free(path);
1408d4af9e69SDag-Erling Smørgrav }
1409d4af9e69SDag-Erling Smørgrav 
1410d4af9e69SDag-Erling Smørgrav static void
process_extended_fstatvfs(u_int32_t id)1411d4af9e69SDag-Erling Smørgrav process_extended_fstatvfs(u_int32_t id)
1412d4af9e69SDag-Erling Smørgrav {
1413bc5531deSDag-Erling Smørgrav 	int r, handle, fd;
1414d4af9e69SDag-Erling Smørgrav 	struct statvfs st;
1415d4af9e69SDag-Erling Smørgrav 
1416bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
141719261079SEd Maste 		fatal_fr(r, "parse");
1418d4af9e69SDag-Erling Smørgrav 	debug("request %u: fstatvfs \"%s\" (handle %u)",
1419d4af9e69SDag-Erling Smørgrav 	    id, handle_to_name(handle), handle);
1420d4af9e69SDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0) {
1421d4af9e69SDag-Erling Smørgrav 		send_status(id, SSH2_FX_FAILURE);
1422d4af9e69SDag-Erling Smørgrav 		return;
1423d4af9e69SDag-Erling Smørgrav 	}
1424d4af9e69SDag-Erling Smørgrav 	if (fstatvfs(fd, &st) != 0)
1425d4af9e69SDag-Erling Smørgrav 		send_status(id, errno_to_portable(errno));
1426d4af9e69SDag-Erling Smørgrav 	else
1427d4af9e69SDag-Erling Smørgrav 		send_statvfs(id, &st);
1428d4af9e69SDag-Erling Smørgrav }
1429d4af9e69SDag-Erling Smørgrav 
1430d4af9e69SDag-Erling Smørgrav static void
process_extended_hardlink(u_int32_t id)14314a421b63SDag-Erling Smørgrav process_extended_hardlink(u_int32_t id)
14324a421b63SDag-Erling Smørgrav {
14334a421b63SDag-Erling Smørgrav 	char *oldpath, *newpath;
1434bc5531deSDag-Erling Smørgrav 	int r, status;
14354a421b63SDag-Erling Smørgrav 
1436bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
1437bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
143819261079SEd Maste 		fatal_fr(r, "parse");
1439bc5531deSDag-Erling Smørgrav 
14404a421b63SDag-Erling Smørgrav 	debug3("request %u: hardlink", id);
14414a421b63SDag-Erling Smørgrav 	logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
1442bc5531deSDag-Erling Smørgrav 	r = link(oldpath, newpath);
1443bc5531deSDag-Erling Smørgrav 	status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
14444a421b63SDag-Erling Smørgrav 	send_status(id, status);
1445e4a9863fSDag-Erling Smørgrav 	free(oldpath);
1446e4a9863fSDag-Erling Smørgrav 	free(newpath);
14474a421b63SDag-Erling Smørgrav }
14484a421b63SDag-Erling Smørgrav 
14494a421b63SDag-Erling Smørgrav static void
process_extended_fsync(u_int32_t id)1450f7167e0eSDag-Erling Smørgrav process_extended_fsync(u_int32_t id)
14511e8db6e2SBrian Feldman {
1452bc5531deSDag-Erling Smørgrav 	int handle, fd, r, status = SSH2_FX_OP_UNSUPPORTED;
14531e8db6e2SBrian Feldman 
1454bc5531deSDag-Erling Smørgrav 	if ((r = get_handle(iqueue, &handle)) != 0)
145519261079SEd Maste 		fatal_fr(r, "parse");
1456f7167e0eSDag-Erling Smørgrav 	debug3("request %u: fsync (handle %u)", id, handle);
1457f7167e0eSDag-Erling Smørgrav 	verbose("fsync \"%s\"", handle_to_name(handle));
1458f7167e0eSDag-Erling Smørgrav 	if ((fd = handle_to_fd(handle)) < 0)
1459f7167e0eSDag-Erling Smørgrav 		status = SSH2_FX_NO_SUCH_FILE;
1460f7167e0eSDag-Erling Smørgrav 	else if (handle_is_ok(handle, HANDLE_FILE)) {
1461bc5531deSDag-Erling Smørgrav 		r = fsync(fd);
1462bc5531deSDag-Erling Smørgrav 		status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1463f7167e0eSDag-Erling Smørgrav 	}
1464f7167e0eSDag-Erling Smørgrav 	send_status(id, status);
1465f7167e0eSDag-Erling Smørgrav }
1466f7167e0eSDag-Erling Smørgrav 
1467f7167e0eSDag-Erling Smørgrav static void
process_extended_lsetstat(u_int32_t id)146819261079SEd Maste process_extended_lsetstat(u_int32_t id)
146919261079SEd Maste {
147019261079SEd Maste 	Attrib a;
147119261079SEd Maste 	char *name;
147219261079SEd Maste 	int r, status = SSH2_FX_OK;
147319261079SEd Maste 
147419261079SEd Maste 	if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 ||
147519261079SEd Maste 	    (r = decode_attrib(iqueue, &a)) != 0)
147619261079SEd Maste 		fatal_fr(r, "parse");
147719261079SEd Maste 
147819261079SEd Maste 	debug("request %u: lsetstat name \"%s\"", id, name);
147919261079SEd Maste 	if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
148019261079SEd Maste 		/* nonsensical for links */
148119261079SEd Maste 		status = SSH2_FX_BAD_MESSAGE;
148219261079SEd Maste 		goto out;
148319261079SEd Maste 	}
148419261079SEd Maste 	if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
148519261079SEd Maste 		logit("set \"%s\" mode %04o", name, a.perm);
148619261079SEd Maste 		r = fchmodat(AT_FDCWD, name,
148719261079SEd Maste 		    a.perm & 07777, AT_SYMLINK_NOFOLLOW);
148819261079SEd Maste 		if (r == -1)
148919261079SEd Maste 			status = errno_to_portable(errno);
149019261079SEd Maste 	}
149119261079SEd Maste 	if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
149219261079SEd Maste 		char buf[64];
149319261079SEd Maste 		time_t t = a.mtime;
149419261079SEd Maste 
149519261079SEd Maste 		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
149619261079SEd Maste 		    localtime(&t));
149719261079SEd Maste 		logit("set \"%s\" modtime %s", name, buf);
149819261079SEd Maste 		r = utimensat(AT_FDCWD, name,
149919261079SEd Maste 		    attrib_to_ts(&a), AT_SYMLINK_NOFOLLOW);
150019261079SEd Maste 		if (r == -1)
150119261079SEd Maste 			status = errno_to_portable(errno);
150219261079SEd Maste 	}
150319261079SEd Maste 	if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
150419261079SEd Maste 		logit("set \"%s\" owner %lu group %lu", name,
150519261079SEd Maste 		    (u_long)a.uid, (u_long)a.gid);
150619261079SEd Maste 		r = fchownat(AT_FDCWD, name, a.uid, a.gid,
150719261079SEd Maste 		    AT_SYMLINK_NOFOLLOW);
150819261079SEd Maste 		if (r == -1)
150919261079SEd Maste 			status = errno_to_portable(errno);
151019261079SEd Maste 	}
151119261079SEd Maste  out:
151219261079SEd Maste 	send_status(id, status);
151319261079SEd Maste 	free(name);
151419261079SEd Maste }
151519261079SEd Maste 
151619261079SEd Maste static void
process_extended_limits(u_int32_t id)151719261079SEd Maste process_extended_limits(u_int32_t id)
151819261079SEd Maste {
151919261079SEd Maste 	struct sshbuf *msg;
152019261079SEd Maste 	int r;
152119261079SEd Maste 	uint64_t nfiles = 0;
152219261079SEd Maste #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
152319261079SEd Maste 	struct rlimit rlim;
152419261079SEd Maste #endif
152519261079SEd Maste 
152619261079SEd Maste 	debug("request %u: limits", id);
152719261079SEd Maste 
152819261079SEd Maste #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
152919261079SEd Maste 	if (getrlimit(RLIMIT_NOFILE, &rlim) != -1 && rlim.rlim_cur > 5)
153019261079SEd Maste 		nfiles = rlim.rlim_cur - 5; /* stdio(3) + syslog + spare */
153119261079SEd Maste #endif
153219261079SEd Maste 
153319261079SEd Maste 	if ((msg = sshbuf_new()) == NULL)
153419261079SEd Maste 		fatal_f("sshbuf_new failed");
153519261079SEd Maste 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
153619261079SEd Maste 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
153719261079SEd Maste 	    /* max-packet-length */
153819261079SEd Maste 	    (r = sshbuf_put_u64(msg, SFTP_MAX_MSG_LENGTH)) != 0 ||
153919261079SEd Maste 	    /* max-read-length */
154019261079SEd Maste 	    (r = sshbuf_put_u64(msg, SFTP_MAX_READ_LENGTH)) != 0 ||
154119261079SEd Maste 	    /* max-write-length */
154219261079SEd Maste 	    (r = sshbuf_put_u64(msg, SFTP_MAX_MSG_LENGTH - 1024)) != 0 ||
154319261079SEd Maste 	    /* max-open-handles */
154419261079SEd Maste 	    (r = sshbuf_put_u64(msg, nfiles)) != 0)
154519261079SEd Maste 		fatal_fr(r, "compose");
154619261079SEd Maste 	send_msg(msg);
154719261079SEd Maste 	sshbuf_free(msg);
154819261079SEd Maste }
154919261079SEd Maste 
155019261079SEd Maste static void
process_extended_expand(u_int32_t id)155119261079SEd Maste process_extended_expand(u_int32_t id)
155219261079SEd Maste {
155319261079SEd Maste 	char cwd[PATH_MAX], resolvedname[PATH_MAX];
155419261079SEd Maste 	char *path, *npath;
155519261079SEd Maste 	int r;
155619261079SEd Maste 	Stat s;
155719261079SEd Maste 
155819261079SEd Maste 	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
155919261079SEd Maste 		fatal_fr(r, "parse");
156019261079SEd Maste 	if (getcwd(cwd, sizeof(cwd)) == NULL) {
156119261079SEd Maste 		send_status(id, errno_to_portable(errno));
156219261079SEd Maste 		goto out;
156319261079SEd Maste 	}
156419261079SEd Maste 
156519261079SEd Maste 	debug3("request %u: expand, original \"%s\"", id, path);
156619261079SEd Maste 	if (path[0] == '\0') {
156719261079SEd Maste 		/* empty path */
156819261079SEd Maste 		free(path);
156919261079SEd Maste 		path = xstrdup(".");
157019261079SEd Maste 	} else if (*path == '~') {
157119261079SEd Maste 		/* ~ expand path */
157219261079SEd Maste 		/* Special-case for "~" and "~/" to respect homedir flag */
157319261079SEd Maste 		if (strcmp(path, "~") == 0) {
157419261079SEd Maste 			free(path);
157519261079SEd Maste 			path = xstrdup(cwd);
157619261079SEd Maste 		} else if (strncmp(path, "~/", 2) == 0) {
157719261079SEd Maste 			npath = xstrdup(path + 2);
157819261079SEd Maste 			free(path);
157919261079SEd Maste 			xasprintf(&path, "%s/%s", cwd, npath);
15801323ec57SEd Maste 			free(npath);
158119261079SEd Maste 		} else {
158219261079SEd Maste 			/* ~user expansions */
158319261079SEd Maste 			if (tilde_expand(path, pw->pw_uid, &npath) != 0) {
15841323ec57SEd Maste 				send_status_errmsg(id,
15851323ec57SEd Maste 				    errno_to_portable(ENOENT), "no such user");
158619261079SEd Maste 				goto out;
158719261079SEd Maste 			}
158819261079SEd Maste 			free(path);
158919261079SEd Maste 			path = npath;
159019261079SEd Maste 		}
159119261079SEd Maste 	} else if (*path != '/') {
159219261079SEd Maste 		/* relative path */
159319261079SEd Maste 		xasprintf(&npath, "%s/%s", cwd, path);
159419261079SEd Maste 		free(path);
159519261079SEd Maste 		path = npath;
159619261079SEd Maste 	}
159719261079SEd Maste 	verbose("expand \"%s\"", path);
159819261079SEd Maste 	if (sftp_realpath(path, resolvedname) == NULL) {
159919261079SEd Maste 		send_status(id, errno_to_portable(errno));
160019261079SEd Maste 		goto out;
160119261079SEd Maste 	}
160219261079SEd Maste 	attrib_clear(&s.attrib);
160319261079SEd Maste 	s.name = s.long_name = resolvedname;
160419261079SEd Maste 	send_names(id, 1, &s);
160519261079SEd Maste  out:
160619261079SEd Maste 	free(path);
160719261079SEd Maste }
160819261079SEd Maste 
160919261079SEd Maste static void
process_extended_copy_data(u_int32_t id)161087c1498dSEd Maste process_extended_copy_data(u_int32_t id)
161187c1498dSEd Maste {
161287c1498dSEd Maste 	u_char buf[64*1024];
161387c1498dSEd Maste 	int read_handle, read_fd, write_handle, write_fd;
161487c1498dSEd Maste 	u_int64_t len, read_off, read_len, write_off;
161587c1498dSEd Maste 	int r, copy_until_eof, status = SSH2_FX_OP_UNSUPPORTED;
161687c1498dSEd Maste 	size_t ret;
161787c1498dSEd Maste 
161887c1498dSEd Maste 	if ((r = get_handle(iqueue, &read_handle)) != 0 ||
161987c1498dSEd Maste 	    (r = sshbuf_get_u64(iqueue, &read_off)) != 0 ||
162087c1498dSEd Maste 	    (r = sshbuf_get_u64(iqueue, &read_len)) != 0 ||
162187c1498dSEd Maste 	    (r = get_handle(iqueue, &write_handle)) != 0 ||
162287c1498dSEd Maste 	    (r = sshbuf_get_u64(iqueue, &write_off)) != 0)
162387c1498dSEd Maste 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
162487c1498dSEd Maste 
162587c1498dSEd Maste 	debug("request %u: copy-data from \"%s\" (handle %d) off %llu len %llu "
162687c1498dSEd Maste 	    "to \"%s\" (handle %d) off %llu",
162787c1498dSEd Maste 	    id, handle_to_name(read_handle), read_handle,
162887c1498dSEd Maste 	    (unsigned long long)read_off, (unsigned long long)read_len,
162987c1498dSEd Maste 	    handle_to_name(write_handle), write_handle,
163087c1498dSEd Maste 	    (unsigned long long)write_off);
163187c1498dSEd Maste 
163287c1498dSEd Maste 	/* For read length of 0, we read until EOF. */
163387c1498dSEd Maste 	if (read_len == 0) {
163487c1498dSEd Maste 		read_len = (u_int64_t)-1 - read_off;
163587c1498dSEd Maste 		copy_until_eof = 1;
163687c1498dSEd Maste 	} else
163787c1498dSEd Maste 		copy_until_eof = 0;
163887c1498dSEd Maste 
163987c1498dSEd Maste 	read_fd = handle_to_fd(read_handle);
164087c1498dSEd Maste 	write_fd = handle_to_fd(write_handle);
164187c1498dSEd Maste 
164287c1498dSEd Maste 	/* Disallow reading & writing to the same handle or same path or dirs */
164387c1498dSEd Maste 	if (read_handle == write_handle || read_fd < 0 || write_fd < 0 ||
164487c1498dSEd Maste 	    !strcmp(handle_to_name(read_handle), handle_to_name(write_handle))) {
164587c1498dSEd Maste 		status = SSH2_FX_FAILURE;
164687c1498dSEd Maste 		goto out;
164787c1498dSEd Maste 	}
164887c1498dSEd Maste 
164987c1498dSEd Maste 	if (lseek(read_fd, read_off, SEEK_SET) < 0) {
165087c1498dSEd Maste 		status = errno_to_portable(errno);
165187c1498dSEd Maste 		error("%s: read_seek failed", __func__);
165287c1498dSEd Maste 		goto out;
165387c1498dSEd Maste 	}
165487c1498dSEd Maste 
165587c1498dSEd Maste 	if ((handle_to_flags(write_handle) & O_APPEND) == 0 &&
165687c1498dSEd Maste 	    lseek(write_fd, write_off, SEEK_SET) < 0) {
165787c1498dSEd Maste 		status = errno_to_portable(errno);
165887c1498dSEd Maste 		error("%s: write_seek failed", __func__);
165987c1498dSEd Maste 		goto out;
166087c1498dSEd Maste 	}
166187c1498dSEd Maste 
166287c1498dSEd Maste 	/* Process the request in chunks. */
166387c1498dSEd Maste 	while (read_len > 0 || copy_until_eof) {
166487c1498dSEd Maste 		len = MINIMUM(sizeof(buf), read_len);
166587c1498dSEd Maste 		read_len -= len;
166687c1498dSEd Maste 
166787c1498dSEd Maste 		ret = atomicio(read, read_fd, buf, len);
166887c1498dSEd Maste 		if (ret == 0 && errno == EPIPE) {
166987c1498dSEd Maste 			status = copy_until_eof ? SSH2_FX_OK : SSH2_FX_EOF;
167087c1498dSEd Maste 			break;
167187c1498dSEd Maste 		} else if (ret == 0) {
167287c1498dSEd Maste 			status = errno_to_portable(errno);
167387c1498dSEd Maste 			error("%s: read failed: %s", __func__, strerror(errno));
167487c1498dSEd Maste 			break;
167587c1498dSEd Maste 		}
167687c1498dSEd Maste 		len = ret;
167787c1498dSEd Maste 		handle_update_read(read_handle, len);
167887c1498dSEd Maste 
167987c1498dSEd Maste 		ret = atomicio(vwrite, write_fd, buf, len);
168087c1498dSEd Maste 		if (ret != len) {
168187c1498dSEd Maste 			status = errno_to_portable(errno);
168287c1498dSEd Maste 			error("%s: write failed: %llu != %llu: %s", __func__,
168387c1498dSEd Maste 			    (unsigned long long)ret, (unsigned long long)len,
168487c1498dSEd Maste 			    strerror(errno));
168587c1498dSEd Maste 			break;
168687c1498dSEd Maste 		}
168787c1498dSEd Maste 		handle_update_write(write_handle, len);
168887c1498dSEd Maste 	}
168987c1498dSEd Maste 
169087c1498dSEd Maste 	if (read_len == 0)
169187c1498dSEd Maste 		status = SSH2_FX_OK;
169287c1498dSEd Maste 
169387c1498dSEd Maste  out:
169487c1498dSEd Maste 	send_status(id, status);
169587c1498dSEd Maste }
169687c1498dSEd Maste 
169787c1498dSEd Maste static void
process_extended_home_directory(u_int32_t id)169838a52bd3SEd Maste process_extended_home_directory(u_int32_t id)
169938a52bd3SEd Maste {
170038a52bd3SEd Maste 	char *username;
170138a52bd3SEd Maste 	struct passwd *user_pw;
170238a52bd3SEd Maste 	int r;
170338a52bd3SEd Maste 	Stat s;
170438a52bd3SEd Maste 
170538a52bd3SEd Maste 	if ((r = sshbuf_get_cstring(iqueue, &username, NULL)) != 0)
170638a52bd3SEd Maste 		fatal_fr(r, "parse");
170738a52bd3SEd Maste 
170838a52bd3SEd Maste 	debug3("request %u: home-directory \"%s\"", id, username);
1709*0fdf8faeSEd Maste 	if (username[0] == '\0') {
1710*0fdf8faeSEd Maste 		user_pw = pw;
1711*0fdf8faeSEd Maste 	} else if ((user_pw = getpwnam(username)) == NULL) {
171238a52bd3SEd Maste 		send_status(id, SSH2_FX_FAILURE);
171338a52bd3SEd Maste 		goto out;
171438a52bd3SEd Maste 	}
171538a52bd3SEd Maste 
1716*0fdf8faeSEd Maste 	verbose("home-directory \"%s\"", user_pw->pw_dir);
171738a52bd3SEd Maste 	attrib_clear(&s.attrib);
1718*0fdf8faeSEd Maste 	s.name = s.long_name = user_pw->pw_dir;
171938a52bd3SEd Maste 	send_names(id, 1, &s);
172038a52bd3SEd Maste  out:
172138a52bd3SEd Maste 	free(username);
172238a52bd3SEd Maste }
172338a52bd3SEd Maste 
172438a52bd3SEd Maste static void
process_extended_get_users_groups_by_id(u_int32_t id)172538a52bd3SEd Maste process_extended_get_users_groups_by_id(u_int32_t id)
172638a52bd3SEd Maste {
172738a52bd3SEd Maste 	struct passwd *user_pw;
172838a52bd3SEd Maste 	struct group *gr;
172938a52bd3SEd Maste 	struct sshbuf *uids, *gids, *usernames, *groupnames, *msg;
173038a52bd3SEd Maste 	int r;
173138a52bd3SEd Maste 	u_int n, nusers = 0, ngroups = 0;
173238a52bd3SEd Maste 	const char *name;
173338a52bd3SEd Maste 
173438a52bd3SEd Maste 	if ((usernames = sshbuf_new()) == NULL ||
173538a52bd3SEd Maste 	    (groupnames = sshbuf_new()) == NULL ||
173638a52bd3SEd Maste 	    (msg = sshbuf_new()) == NULL)
173738a52bd3SEd Maste 		fatal_f("sshbuf_new failed");
173838a52bd3SEd Maste 	if ((r = sshbuf_froms(iqueue, &uids)) != 0 ||
173938a52bd3SEd Maste 	    (r = sshbuf_froms(iqueue, &gids)) != 0)
174038a52bd3SEd Maste 		fatal_fr(r, "parse");
174138a52bd3SEd Maste 	debug_f("uids len = %zu, gids len = %zu",
174238a52bd3SEd Maste 	    sshbuf_len(uids), sshbuf_len(gids));
174338a52bd3SEd Maste 	while (sshbuf_len(uids) != 0) {
174438a52bd3SEd Maste 		if ((r = sshbuf_get_u32(uids, &n)) != 0)
174538a52bd3SEd Maste 			fatal_fr(r, "parse inner uid");
174638a52bd3SEd Maste 		user_pw = getpwuid((uid_t)n);
174738a52bd3SEd Maste 		name = user_pw == NULL ? "" : user_pw->pw_name;
174838a52bd3SEd Maste 		debug3_f("uid %u => \"%s\"", n, name);
174938a52bd3SEd Maste 		if ((r = sshbuf_put_cstring(usernames, name)) != 0)
1750f374ba41SEd Maste 			fatal_fr(r, "assemble uid reply");
175138a52bd3SEd Maste 		nusers++;
175238a52bd3SEd Maste 	}
175338a52bd3SEd Maste 	while (sshbuf_len(gids) != 0) {
175438a52bd3SEd Maste 		if ((r = sshbuf_get_u32(gids, &n)) != 0)
175538a52bd3SEd Maste 			fatal_fr(r, "parse inner gid");
175638a52bd3SEd Maste 		gr = getgrgid((gid_t)n);
175738a52bd3SEd Maste 		name = gr == NULL ? "" : gr->gr_name;
175838a52bd3SEd Maste 		debug3_f("gid %u => \"%s\"", n, name);
175938a52bd3SEd Maste 		if ((r = sshbuf_put_cstring(groupnames, name)) != 0)
176038a52bd3SEd Maste 			fatal_fr(r, "assemble gid reply");
176138a52bd3SEd Maste 		nusers++;
176238a52bd3SEd Maste 	}
176338a52bd3SEd Maste 	verbose("users-groups-by-id: %u users, %u groups", nusers, ngroups);
176438a52bd3SEd Maste 
176538a52bd3SEd Maste 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
176638a52bd3SEd Maste 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
176738a52bd3SEd Maste 	    (r = sshbuf_put_stringb(msg, usernames)) != 0 ||
176838a52bd3SEd Maste 	    (r = sshbuf_put_stringb(msg, groupnames)) != 0)
176938a52bd3SEd Maste 		fatal_fr(r, "compose");
177038a52bd3SEd Maste 	send_msg(msg);
177138a52bd3SEd Maste 
177238a52bd3SEd Maste 	sshbuf_free(uids);
177338a52bd3SEd Maste 	sshbuf_free(gids);
177438a52bd3SEd Maste 	sshbuf_free(usernames);
177538a52bd3SEd Maste 	sshbuf_free(groupnames);
177638a52bd3SEd Maste 	sshbuf_free(msg);
177738a52bd3SEd Maste }
177838a52bd3SEd Maste 
177938a52bd3SEd Maste static void
process_extended(u_int32_t id)1780f7167e0eSDag-Erling Smørgrav process_extended(u_int32_t id)
1781f7167e0eSDag-Erling Smørgrav {
1782f7167e0eSDag-Erling Smørgrav 	char *request;
178319261079SEd Maste 	int r;
178419261079SEd Maste 	const struct sftp_handler *exthand;
1785f7167e0eSDag-Erling Smørgrav 
1786bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(iqueue, &request, NULL)) != 0)
178719261079SEd Maste 		fatal_fr(r, "parse");
178819261079SEd Maste 	if ((exthand = extended_handler_byname(request)) == NULL) {
1789f7167e0eSDag-Erling Smørgrav 		error("Unknown extended request \"%.100s\"", request);
17901e8db6e2SBrian Feldman 		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
179119261079SEd Maste 	} else {
179219261079SEd Maste 		if (!request_permitted(exthand))
179319261079SEd Maste 			send_status(id, SSH2_FX_PERMISSION_DENIED);
179419261079SEd Maste 		else
179519261079SEd Maste 			exthand->handler(id);
1796f7167e0eSDag-Erling Smørgrav 	}
1797e4a9863fSDag-Erling Smørgrav 	free(request);
17981e8db6e2SBrian Feldman }
1799b66f2d16SKris Kennaway 
1800b66f2d16SKris Kennaway /* stolen from ssh-agent */
1801b66f2d16SKris Kennaway 
1802ae1f160dSDag-Erling Smørgrav static void
process(void)1803b66f2d16SKris Kennaway process(void)
1804b66f2d16SKris Kennaway {
1805bc5531deSDag-Erling Smørgrav 	u_int msg_len;
1806bc5531deSDag-Erling Smørgrav 	u_int buf_len;
1807bc5531deSDag-Erling Smørgrav 	u_int consumed;
1808bc5531deSDag-Erling Smørgrav 	u_char type;
1809bc5531deSDag-Erling Smørgrav 	const u_char *cp;
1810bc5531deSDag-Erling Smørgrav 	int i, r;
1811f7167e0eSDag-Erling Smørgrav 	u_int32_t id;
1812b66f2d16SKris Kennaway 
1813bc5531deSDag-Erling Smørgrav 	buf_len = sshbuf_len(iqueue);
1814545d5ecaSDag-Erling Smørgrav 	if (buf_len < 5)
1815b66f2d16SKris Kennaway 		return;		/* Incomplete message. */
1816bc5531deSDag-Erling Smørgrav 	cp = sshbuf_ptr(iqueue);
1817761efaa7SDag-Erling Smørgrav 	msg_len = get_u32(cp);
1818021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1819761efaa7SDag-Erling Smørgrav 		error("bad message from %s local user %s",
1820761efaa7SDag-Erling Smørgrav 		    client_addr, pw->pw_name);
1821d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(11);
1822b66f2d16SKris Kennaway 	}
1823545d5ecaSDag-Erling Smørgrav 	if (buf_len < msg_len + 4)
1824b66f2d16SKris Kennaway 		return;
1825bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_consume(iqueue, 4)) != 0)
182619261079SEd Maste 		fatal_fr(r, "consume");
1827545d5ecaSDag-Erling Smørgrav 	buf_len -= 4;
1828bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(iqueue, &type)) != 0)
182919261079SEd Maste 		fatal_fr(r, "parse type");
1830f7167e0eSDag-Erling Smørgrav 
1831b66f2d16SKris Kennaway 	switch (type) {
18321e8db6e2SBrian Feldman 	case SSH2_FXP_INIT:
1833b66f2d16SKris Kennaway 		process_init();
1834f7167e0eSDag-Erling Smørgrav 		init_done = 1;
18351e8db6e2SBrian Feldman 		break;
18361e8db6e2SBrian Feldman 	case SSH2_FXP_EXTENDED:
1837f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1838f7167e0eSDag-Erling Smørgrav 			fatal("Received extended request before init");
1839bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
184019261079SEd Maste 			fatal_fr(r, "parse extended ID");
1841f7167e0eSDag-Erling Smørgrav 		process_extended(id);
18421e8db6e2SBrian Feldman 		break;
1843b66f2d16SKris Kennaway 	default:
1844f7167e0eSDag-Erling Smørgrav 		if (!init_done)
1845f7167e0eSDag-Erling Smørgrav 			fatal("Received %u request before init", type);
1846bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
184719261079SEd Maste 			fatal_fr(r, "parse ID");
1848f7167e0eSDag-Erling Smørgrav 		for (i = 0; handlers[i].handler != NULL; i++) {
1849f7167e0eSDag-Erling Smørgrav 			if (type == handlers[i].type) {
1850f7167e0eSDag-Erling Smørgrav 				if (!request_permitted(&handlers[i])) {
1851f7167e0eSDag-Erling Smørgrav 					send_status(id,
1852f7167e0eSDag-Erling Smørgrav 					    SSH2_FX_PERMISSION_DENIED);
1853f7167e0eSDag-Erling Smørgrav 				} else {
1854f7167e0eSDag-Erling Smørgrav 					handlers[i].handler(id);
1855f7167e0eSDag-Erling Smørgrav 				}
1856b66f2d16SKris Kennaway 				break;
1857b66f2d16SKris Kennaway 			}
1858f7167e0eSDag-Erling Smørgrav 		}
1859f7167e0eSDag-Erling Smørgrav 		if (handlers[i].handler == NULL)
1860f7167e0eSDag-Erling Smørgrav 			error("Unknown message %u", type);
1861f7167e0eSDag-Erling Smørgrav 	}
1862545d5ecaSDag-Erling Smørgrav 	/* discard the remaining bytes from the current packet */
1863bc5531deSDag-Erling Smørgrav 	if (buf_len < sshbuf_len(iqueue)) {
1864d4af9e69SDag-Erling Smørgrav 		error("iqueue grew unexpectedly");
1865d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1866d4af9e69SDag-Erling Smørgrav 	}
1867bc5531deSDag-Erling Smørgrav 	consumed = buf_len - sshbuf_len(iqueue);
1868d4af9e69SDag-Erling Smørgrav 	if (msg_len < consumed) {
1869f7167e0eSDag-Erling Smørgrav 		error("msg_len %u < consumed %u", msg_len, consumed);
1870d4af9e69SDag-Erling Smørgrav 		sftp_server_cleanup_exit(255);
1871d4af9e69SDag-Erling Smørgrav 	}
1872bc5531deSDag-Erling Smørgrav 	if (msg_len > consumed &&
1873bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_consume(iqueue, msg_len - consumed)) != 0)
187419261079SEd Maste 		fatal_fr(r, "consume");
1875b66f2d16SKris Kennaway }
1876b66f2d16SKris Kennaway 
1877761efaa7SDag-Erling Smørgrav /* Cleanup handler that logs active handles upon normal exit */
1878761efaa7SDag-Erling Smørgrav void
sftp_server_cleanup_exit(int i)1879d4af9e69SDag-Erling Smørgrav sftp_server_cleanup_exit(int i)
1880761efaa7SDag-Erling Smørgrav {
1881761efaa7SDag-Erling Smørgrav 	if (pw != NULL && client_addr != NULL) {
1882761efaa7SDag-Erling Smørgrav 		handle_log_exit();
1883761efaa7SDag-Erling Smørgrav 		logit("session closed for local user %s from [%s]",
1884761efaa7SDag-Erling Smørgrav 		    pw->pw_name, client_addr);
1885761efaa7SDag-Erling Smørgrav 	}
1886761efaa7SDag-Erling Smørgrav 	_exit(i);
1887761efaa7SDag-Erling Smørgrav }
1888761efaa7SDag-Erling Smørgrav 
1889761efaa7SDag-Erling Smørgrav static void
sftp_server_usage(void)1890d4af9e69SDag-Erling Smørgrav sftp_server_usage(void)
1891761efaa7SDag-Erling Smørgrav {
1892761efaa7SDag-Erling Smørgrav 	extern char *__progname;
1893761efaa7SDag-Erling Smørgrav 
1894761efaa7SDag-Erling Smørgrav 	fprintf(stderr,
18956888a9beSDag-Erling Smørgrav 	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
189619261079SEd Maste 	    "[-l log_level]\n\t[-P denied_requests] "
189719261079SEd Maste 	    "[-p allowed_requests] [-u umask]\n"
1898f7167e0eSDag-Erling Smørgrav 	    "       %s -Q protocol_feature\n",
1899f7167e0eSDag-Erling Smørgrav 	    __progname, __progname);
1900761efaa7SDag-Erling Smørgrav 	exit(1);
1901761efaa7SDag-Erling Smørgrav }
1902761efaa7SDag-Erling Smørgrav 
1903b66f2d16SKris Kennaway int
sftp_server_main(int argc,char ** argv,struct passwd * user_pw)1904d4af9e69SDag-Erling Smørgrav sftp_server_main(int argc, char **argv, struct passwd *user_pw)
1905b66f2d16SKris Kennaway {
19061323ec57SEd Maste 	int i, r, in, out, ch, skipargs = 0, log_stderr = 0;
19071323ec57SEd Maste 	ssize_t len, olen;
1908761efaa7SDag-Erling Smørgrav 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1909190cef3dSDag-Erling Smørgrav 	char *cp, *homedir = NULL, uidstr[32], buf[4*4096];
19104a421b63SDag-Erling Smørgrav 	long mask;
1911761efaa7SDag-Erling Smørgrav 
1912761efaa7SDag-Erling Smørgrav 	extern char *optarg;
1913761efaa7SDag-Erling Smørgrav 	extern char *__progname;
19141e8db6e2SBrian Feldman 
1915761efaa7SDag-Erling Smørgrav 	__progname = ssh_get_progname(argv[0]);
1916761efaa7SDag-Erling Smørgrav 	log_init(__progname, log_level, log_facility, log_stderr);
1917b66f2d16SKris Kennaway 
19186888a9beSDag-Erling Smørgrav 	pw = pwcopy(user_pw);
19196888a9beSDag-Erling Smørgrav 
1920f7167e0eSDag-Erling Smørgrav 	while (!skipargs && (ch = getopt(argc, argv,
1921f7167e0eSDag-Erling Smørgrav 	    "d:f:l:P:p:Q:u:cehR")) != -1) {
1922761efaa7SDag-Erling Smørgrav 		switch (ch) {
1923f7167e0eSDag-Erling Smørgrav 		case 'Q':
1924f7167e0eSDag-Erling Smørgrav 			if (strcasecmp(optarg, "requests") != 0) {
1925f7167e0eSDag-Erling Smørgrav 				fprintf(stderr, "Invalid query type\n");
1926f7167e0eSDag-Erling Smørgrav 				exit(1);
1927f7167e0eSDag-Erling Smørgrav 			}
1928f7167e0eSDag-Erling Smørgrav 			for (i = 0; handlers[i].handler != NULL; i++)
1929f7167e0eSDag-Erling Smørgrav 				printf("%s\n", handlers[i].name);
1930f7167e0eSDag-Erling Smørgrav 			for (i = 0; extended_handlers[i].handler != NULL; i++)
1931f7167e0eSDag-Erling Smørgrav 				printf("%s\n", extended_handlers[i].name);
1932f7167e0eSDag-Erling Smørgrav 			exit(0);
1933f7167e0eSDag-Erling Smørgrav 			break;
1934b15c8340SDag-Erling Smørgrav 		case 'R':
1935b15c8340SDag-Erling Smørgrav 			readonly = 1;
1936b15c8340SDag-Erling Smørgrav 			break;
1937761efaa7SDag-Erling Smørgrav 		case 'c':
1938761efaa7SDag-Erling Smørgrav 			/*
1939761efaa7SDag-Erling Smørgrav 			 * Ignore all arguments if we are invoked as a
1940761efaa7SDag-Erling Smørgrav 			 * shell using "sftp-server -c command"
1941761efaa7SDag-Erling Smørgrav 			 */
1942761efaa7SDag-Erling Smørgrav 			skipargs = 1;
1943761efaa7SDag-Erling Smørgrav 			break;
1944761efaa7SDag-Erling Smørgrav 		case 'e':
1945761efaa7SDag-Erling Smørgrav 			log_stderr = 1;
1946761efaa7SDag-Erling Smørgrav 			break;
1947761efaa7SDag-Erling Smørgrav 		case 'l':
1948761efaa7SDag-Erling Smørgrav 			log_level = log_level_number(optarg);
1949761efaa7SDag-Erling Smørgrav 			if (log_level == SYSLOG_LEVEL_NOT_SET)
1950761efaa7SDag-Erling Smørgrav 				error("Invalid log level \"%s\"", optarg);
1951761efaa7SDag-Erling Smørgrav 			break;
1952761efaa7SDag-Erling Smørgrav 		case 'f':
1953761efaa7SDag-Erling Smørgrav 			log_facility = log_facility_number(optarg);
1954d4af9e69SDag-Erling Smørgrav 			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1955761efaa7SDag-Erling Smørgrav 				error("Invalid log facility \"%s\"", optarg);
1956761efaa7SDag-Erling Smørgrav 			break;
19576888a9beSDag-Erling Smørgrav 		case 'd':
19586888a9beSDag-Erling Smørgrav 			cp = tilde_expand_filename(optarg, user_pw->pw_uid);
1959190cef3dSDag-Erling Smørgrav 			snprintf(uidstr, sizeof(uidstr), "%llu",
1960190cef3dSDag-Erling Smørgrav 			    (unsigned long long)pw->pw_uid);
19616888a9beSDag-Erling Smørgrav 			homedir = percent_expand(cp, "d", user_pw->pw_dir,
1962190cef3dSDag-Erling Smørgrav 			    "u", user_pw->pw_name, "U", uidstr, (char *)NULL);
19636888a9beSDag-Erling Smørgrav 			free(cp);
19646888a9beSDag-Erling Smørgrav 			break;
1965f7167e0eSDag-Erling Smørgrav 		case 'p':
196619261079SEd Maste 			if (request_allowlist != NULL)
1967f7167e0eSDag-Erling Smørgrav 				fatal("Permitted requests already set");
196819261079SEd Maste 			request_allowlist = xstrdup(optarg);
1969f7167e0eSDag-Erling Smørgrav 			break;
1970f7167e0eSDag-Erling Smørgrav 		case 'P':
197119261079SEd Maste 			if (request_denylist != NULL)
1972f7167e0eSDag-Erling Smørgrav 				fatal("Refused requests already set");
197319261079SEd Maste 			request_denylist = xstrdup(optarg);
1974f7167e0eSDag-Erling Smørgrav 			break;
1975b15c8340SDag-Erling Smørgrav 		case 'u':
19764a421b63SDag-Erling Smørgrav 			errno = 0;
19774a421b63SDag-Erling Smørgrav 			mask = strtol(optarg, &cp, 8);
19784a421b63SDag-Erling Smørgrav 			if (mask < 0 || mask > 0777 || *cp != '\0' ||
19794a421b63SDag-Erling Smørgrav 			    cp == optarg || (mask == 0 && errno != 0))
19804a421b63SDag-Erling Smørgrav 				fatal("Invalid umask \"%s\"", optarg);
19814a421b63SDag-Erling Smørgrav 			(void)umask((mode_t)mask);
1982b15c8340SDag-Erling Smørgrav 			break;
1983761efaa7SDag-Erling Smørgrav 		case 'h':
1984761efaa7SDag-Erling Smørgrav 		default:
1985d4af9e69SDag-Erling Smørgrav 			sftp_server_usage();
1986761efaa7SDag-Erling Smørgrav 		}
1987761efaa7SDag-Erling Smørgrav 	}
1988761efaa7SDag-Erling Smørgrav 
1989761efaa7SDag-Erling Smørgrav 	log_init(__progname, log_level, log_facility, log_stderr);
1990761efaa7SDag-Erling Smørgrav 
1991a0ee8cc6SDag-Erling Smørgrav 	/*
1992076ad2f8SDag-Erling Smørgrav 	 * On platforms where we can, avoid making /proc/self/{mem,maps}
1993a0ee8cc6SDag-Erling Smørgrav 	 * available to the user so that sftp access doesn't automatically
1994a0ee8cc6SDag-Erling Smørgrav 	 * imply arbitrary code execution access that will break
1995a0ee8cc6SDag-Erling Smørgrav 	 * restricted configurations.
1996a0ee8cc6SDag-Erling Smørgrav 	 */
1997076ad2f8SDag-Erling Smørgrav 	platform_disable_tracing(1);	/* strict */
1998a0ee8cc6SDag-Erling Smørgrav 
1999acc1a9efSDag-Erling Smørgrav 	/* Drop any fine-grained privileges we don't need */
2000acc1a9efSDag-Erling Smørgrav 	platform_pledge_sftp_server();
2001acc1a9efSDag-Erling Smørgrav 
2002761efaa7SDag-Erling Smørgrav 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
2003761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup(cp);
2004d4af9e69SDag-Erling Smørgrav 		if ((cp = strchr(client_addr, ' ')) == NULL) {
2005d4af9e69SDag-Erling Smørgrav 			error("Malformed SSH_CONNECTION variable: \"%s\"",
2006761efaa7SDag-Erling Smørgrav 			    getenv("SSH_CONNECTION"));
2007d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(255);
2008d4af9e69SDag-Erling Smørgrav 		}
2009761efaa7SDag-Erling Smørgrav 		*cp = '\0';
2010761efaa7SDag-Erling Smørgrav 	} else
2011761efaa7SDag-Erling Smørgrav 		client_addr = xstrdup("UNKNOWN");
2012761efaa7SDag-Erling Smørgrav 
2013761efaa7SDag-Erling Smørgrav 	logit("session opened for local user %s from [%s]",
2014761efaa7SDag-Erling Smørgrav 	    pw->pw_name, client_addr);
2015761efaa7SDag-Erling Smørgrav 
2016b15c8340SDag-Erling Smørgrav 	in = STDIN_FILENO;
2017b15c8340SDag-Erling Smørgrav 	out = STDOUT_FILENO;
2018b66f2d16SKris Kennaway 
201983d2307dSDag-Erling Smørgrav #ifdef HAVE_CYGWIN
202083d2307dSDag-Erling Smørgrav 	setmode(in, O_BINARY);
202183d2307dSDag-Erling Smørgrav 	setmode(out, O_BINARY);
202283d2307dSDag-Erling Smørgrav #endif
202383d2307dSDag-Erling Smørgrav 
2024bc5531deSDag-Erling Smørgrav 	if ((iqueue = sshbuf_new()) == NULL)
202519261079SEd Maste 		fatal_f("sshbuf_new failed");
2026bc5531deSDag-Erling Smørgrav 	if ((oqueue = sshbuf_new()) == NULL)
202719261079SEd Maste 		fatal_f("sshbuf_new failed");
2028b66f2d16SKris Kennaway 
20296888a9beSDag-Erling Smørgrav 	if (homedir != NULL) {
20306888a9beSDag-Erling Smørgrav 		if (chdir(homedir) != 0) {
20316888a9beSDag-Erling Smørgrav 			error("chdir to \"%s\" failed: %s", homedir,
20326888a9beSDag-Erling Smørgrav 			    strerror(errno));
20336888a9beSDag-Erling Smørgrav 		}
20346888a9beSDag-Erling Smørgrav 	}
20356888a9beSDag-Erling Smørgrav 
20361e8db6e2SBrian Feldman 	for (;;) {
20371323ec57SEd Maste 		struct pollfd pfd[2];
20381323ec57SEd Maste 
20391323ec57SEd Maste 		memset(pfd, 0, sizeof pfd);
20401323ec57SEd Maste 		pfd[0].fd = pfd[1].fd = -1;
20411e8db6e2SBrian Feldman 
2042d4af9e69SDag-Erling Smørgrav 		/*
2043d4af9e69SDag-Erling Smørgrav 		 * Ensure that we can read a full buffer and handle
2044d4af9e69SDag-Erling Smørgrav 		 * the worst-case length packet it can generate,
2045d4af9e69SDag-Erling Smørgrav 		 * otherwise apply backpressure by stopping reads.
2046d4af9e69SDag-Erling Smørgrav 		 */
2047bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 &&
2048bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_check_reserve(oqueue,
20491323ec57SEd Maste 		    SFTP_MAX_MSG_LENGTH)) == 0) {
20501323ec57SEd Maste 			pfd[0].fd = in;
20511323ec57SEd Maste 			pfd[0].events = POLLIN;
20521323ec57SEd Maste 		}
2053bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
205419261079SEd Maste 			fatal_fr(r, "reserve");
2055d4af9e69SDag-Erling Smørgrav 
2056bc5531deSDag-Erling Smørgrav 		olen = sshbuf_len(oqueue);
20571323ec57SEd Maste 		if (olen > 0) {
20581323ec57SEd Maste 			pfd[1].fd = out;
20591323ec57SEd Maste 			pfd[1].events = POLLOUT;
20601323ec57SEd Maste 		}
2061b66f2d16SKris Kennaway 
20621323ec57SEd Maste 		if (poll(pfd, 2, -1) == -1) {
2063b66f2d16SKris Kennaway 			if (errno == EINTR)
2064b66f2d16SKris Kennaway 				continue;
20651323ec57SEd Maste 			error("poll: %s", strerror(errno));
2066d4af9e69SDag-Erling Smørgrav 			sftp_server_cleanup_exit(2);
2067b66f2d16SKris Kennaway 		}
2068b66f2d16SKris Kennaway 
2069b66f2d16SKris Kennaway 		/* copy stdin to iqueue */
20701323ec57SEd Maste 		if (pfd[0].revents & (POLLIN|POLLHUP)) {
2071b66f2d16SKris Kennaway 			len = read(in, buf, sizeof buf);
2072b66f2d16SKris Kennaway 			if (len == 0) {
2073b66f2d16SKris Kennaway 				debug("read eof");
2074d4af9e69SDag-Erling Smørgrav 				sftp_server_cleanup_exit(0);
207519261079SEd Maste 			} else if (len == -1) {
20761323ec57SEd Maste 				if (errno != EAGAIN && errno != EINTR) {
2077761efaa7SDag-Erling Smørgrav 					error("read: %s", strerror(errno));
2078d4af9e69SDag-Erling Smørgrav 					sftp_server_cleanup_exit(1);
20791323ec57SEd Maste 				}
208019261079SEd Maste 			} else if ((r = sshbuf_put(iqueue, buf, len)) != 0)
208119261079SEd Maste 				fatal_fr(r, "sshbuf_put");
2082b66f2d16SKris Kennaway 		}
2083b66f2d16SKris Kennaway 		/* send oqueue to stdout */
20841323ec57SEd Maste 		if (pfd[1].revents & (POLLOUT|POLLHUP)) {
2085bc5531deSDag-Erling Smørgrav 			len = write(out, sshbuf_ptr(oqueue), olen);
20861323ec57SEd Maste 			if (len == 0 || (len == -1 && errno == EPIPE)) {
20871323ec57SEd Maste 				debug("write eof");
20881323ec57SEd Maste 				sftp_server_cleanup_exit(0);
20891323ec57SEd Maste 			} else if (len == -1) {
20901323ec57SEd Maste 				sftp_server_cleanup_exit(1);
20911323ec57SEd Maste 				if (errno != EAGAIN && errno != EINTR) {
2092761efaa7SDag-Erling Smørgrav 					error("write: %s", strerror(errno));
2093d4af9e69SDag-Erling Smørgrav 					sftp_server_cleanup_exit(1);
20941323ec57SEd Maste 				}
209519261079SEd Maste 			} else if ((r = sshbuf_consume(oqueue, len)) != 0)
209619261079SEd Maste 				fatal_fr(r, "consume");
2097b66f2d16SKris Kennaway 		}
2098d4af9e69SDag-Erling Smørgrav 
2099d4af9e69SDag-Erling Smørgrav 		/*
2100d4af9e69SDag-Erling Smørgrav 		 * Process requests from client if we can fit the results
2101d4af9e69SDag-Erling Smørgrav 		 * into the output buffer, otherwise stop processing input
2102d4af9e69SDag-Erling Smørgrav 		 * and let the output queue drain.
2103d4af9e69SDag-Erling Smørgrav 		 */
2104bc5531deSDag-Erling Smørgrav 		r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH);
2105bc5531deSDag-Erling Smørgrav 		if (r == 0)
2106b66f2d16SKris Kennaway 			process();
2107bc5531deSDag-Erling Smørgrav 		else if (r != SSH_ERR_NO_BUFFER_SPACE)
210819261079SEd Maste 			fatal_fr(r, "reserve");
2109b66f2d16SKris Kennaway 	}
2110b66f2d16SKris Kennaway }
2111