1262566Sdes/* $OpenBSD: sftp-server.c,v 1.103 2014/01/17 06:23:24 dtucker Exp $ */
265668Skris/*
3126274Sdes * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
465668Skris *
5126274Sdes * Permission to use, copy, modify, and distribute this software for any
6126274Sdes * purpose with or without fee is hereby granted, provided that the above
7126274Sdes * copyright notice and this permission notice appear in all copies.
865668Skris *
9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1665668Skris */
17162852Sdes
1865668Skris#include "includes.h"
1965668Skris
20162852Sdes#include <sys/types.h>
21162852Sdes#include <sys/param.h>
22162852Sdes#include <sys/stat.h>
23162852Sdes#ifdef HAVE_SYS_TIME_H
24162852Sdes# include <sys/time.h>
25162852Sdes#endif
26181111Sdes#ifdef HAVE_SYS_MOUNT_H
27181111Sdes#include <sys/mount.h>
28181111Sdes#endif
29181111Sdes#ifdef HAVE_SYS_STATVFS_H
30181111Sdes#include <sys/statvfs.h>
31181111Sdes#endif
32162852Sdes
33162852Sdes#include <dirent.h>
34162852Sdes#include <errno.h>
35162852Sdes#include <fcntl.h>
36162852Sdes#include <pwd.h>
37162852Sdes#include <stdlib.h>
38162852Sdes#include <stdio.h>
39162852Sdes#include <string.h>
40162852Sdes#include <pwd.h>
41162852Sdes#include <time.h>
42162852Sdes#include <unistd.h>
43162852Sdes#include <stdarg.h>
44162852Sdes
45162852Sdes#include "xmalloc.h"
4665668Skris#include "buffer.h"
4776259Sgreen#include "log.h"
48157016Sdes#include "misc.h"
49262566Sdes#include "match.h"
50162852Sdes#include "uidswap.h"
5165668Skris
5276259Sgreen#include "sftp.h"
5376259Sgreen#include "sftp-common.h"
5465668Skris
5565668Skris/* helper */
5676259Sgreen#define get_int64()			buffer_get_int64(&iqueue);
5765668Skris#define get_int()			buffer_get_int(&iqueue);
5865668Skris#define get_string(lenp)		buffer_get_string(&iqueue, lenp);
5965668Skris
60162852Sdes/* Our verbosity */
61262566Sdesstatic LogLevel log_level = SYSLOG_LEVEL_ERROR;
6298937Sdes
63162852Sdes/* Our client */
64262566Sdesstatic struct passwd *pw = NULL;
65262566Sdesstatic char *client_addr = NULL;
66162852Sdes
6765668Skris/* input and output queue */
68262566Sdesstatic Buffer iqueue;
69262566Sdesstatic Buffer oqueue;
7065668Skris
7176259Sgreen/* Version of client */
72262566Sdesstatic u_int version;
7376259Sgreen
74262566Sdes/* SSH2_FXP_INIT received */
75262566Sdesstatic int init_done;
76262566Sdes
77204917Sdes/* Disable writes */
78262566Sdesstatic int readonly;
79204917Sdes
80262566Sdes/* Requests that are allowed/denied */
81262566Sdesstatic char *request_whitelist, *request_blacklist;
82262566Sdes
83124208Sdes/* portable attributes, etc. */
8465668Skristypedef struct Stat Stat;
8565668Skris
8676259Sgreenstruct Stat {
8765668Skris	char *name;
8865668Skris	char *long_name;
8965668Skris	Attrib attrib;
9065668Skris};
9165668Skris
92262566Sdes/* Packet handlers */
93262566Sdesstatic void process_open(u_int32_t id);
94262566Sdesstatic void process_close(u_int32_t id);
95262566Sdesstatic void process_read(u_int32_t id);
96262566Sdesstatic void process_write(u_int32_t id);
97262566Sdesstatic void process_stat(u_int32_t id);
98262566Sdesstatic void process_lstat(u_int32_t id);
99262566Sdesstatic void process_fstat(u_int32_t id);
100262566Sdesstatic void process_setstat(u_int32_t id);
101262566Sdesstatic void process_fsetstat(u_int32_t id);
102262566Sdesstatic void process_opendir(u_int32_t id);
103262566Sdesstatic void process_readdir(u_int32_t id);
104262566Sdesstatic void process_remove(u_int32_t id);
105262566Sdesstatic void process_mkdir(u_int32_t id);
106262566Sdesstatic void process_rmdir(u_int32_t id);
107262566Sdesstatic void process_realpath(u_int32_t id);
108262566Sdesstatic void process_rename(u_int32_t id);
109262566Sdesstatic void process_readlink(u_int32_t id);
110262566Sdesstatic void process_symlink(u_int32_t id);
111262566Sdesstatic void process_extended_posix_rename(u_int32_t id);
112262566Sdesstatic void process_extended_statvfs(u_int32_t id);
113262566Sdesstatic void process_extended_fstatvfs(u_int32_t id);
114262566Sdesstatic void process_extended_hardlink(u_int32_t id);
115262566Sdesstatic void process_extended_fsync(u_int32_t id);
116262566Sdesstatic void process_extended(u_int32_t id);
117262566Sdes
118262566Sdesstruct sftp_handler {
119262566Sdes	const char *name;	/* user-visible name for fine-grained perms */
120262566Sdes	const char *ext_name;	/* extended request name */
121262566Sdes	u_int type;		/* packet type, for non extended packets */
122262566Sdes	void (*handler)(u_int32_t);
123262566Sdes	int does_write;		/* if nonzero, banned for readonly mode */
124262566Sdes};
125262566Sdes
126262566Sdesstruct sftp_handler handlers[] = {
127262566Sdes	/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
128262566Sdes	{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
129262566Sdes	{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
130262566Sdes	{ "read", NULL, SSH2_FXP_READ, process_read, 0 },
131262566Sdes	{ "write", NULL, SSH2_FXP_WRITE, process_write, 1 },
132262566Sdes	{ "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 },
133262566Sdes	{ "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 },
134262566Sdes	{ "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 },
135262566Sdes	{ "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 },
136262566Sdes	{ "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 },
137262566Sdes	{ "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 },
138262566Sdes	{ "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 },
139262566Sdes	{ "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 },
140262566Sdes	{ "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 },
141262566Sdes	{ "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 },
142262566Sdes	{ "stat", NULL, SSH2_FXP_STAT, process_stat, 0 },
143262566Sdes	{ "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 },
144262566Sdes	{ "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 },
145262566Sdes	{ "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 },
146262566Sdes	{ NULL, NULL, 0, NULL, 0 }
147262566Sdes};
148262566Sdes
149262566Sdes/* SSH2_FXP_EXTENDED submessages */
150262566Sdesstruct sftp_handler extended_handlers[] = {
151262566Sdes	{ "posix-rename", "posix-rename@openssh.com", 0,
152262566Sdes	   process_extended_posix_rename, 1 },
153262566Sdes	{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
154262566Sdes	{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
155262566Sdes	{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
156262566Sdes	{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
157262566Sdes	{ NULL, NULL, 0, NULL, 0 }
158262566Sdes};
159262566Sdes
16092555Sdesstatic int
161262566Sdesrequest_permitted(struct sftp_handler *h)
162262566Sdes{
163262566Sdes	char *result;
164262566Sdes
165262566Sdes	if (readonly && h->does_write) {
166262566Sdes		verbose("Refusing %s request in read-only mode", h->name);
167262566Sdes		return 0;
168262566Sdes	}
169262566Sdes	if (request_blacklist != NULL &&
170262566Sdes	    ((result = match_list(h->name, request_blacklist, NULL))) != NULL) {
171262566Sdes		free(result);
172262566Sdes		verbose("Refusing blacklisted %s request", h->name);
173262566Sdes		return 0;
174262566Sdes	}
175262566Sdes	if (request_whitelist != NULL &&
176262566Sdes	    ((result = match_list(h->name, request_whitelist, NULL))) != NULL) {
177262566Sdes		free(result);
178262566Sdes		debug2("Permitting whitelisted %s request", h->name);
179262566Sdes		return 1;
180262566Sdes	}
181262566Sdes	if (request_whitelist != NULL) {
182262566Sdes		verbose("Refusing non-whitelisted %s request", h->name);
183262566Sdes		return 0;
184262566Sdes	}
185262566Sdes	return 1;
186262566Sdes}
187262566Sdes
188262566Sdesstatic int
18965668Skriserrno_to_portable(int unixerrno)
19065668Skris{
19165668Skris	int ret = 0;
19276259Sgreen
19365668Skris	switch (unixerrno) {
19465668Skris	case 0:
19576259Sgreen		ret = SSH2_FX_OK;
19665668Skris		break;
19765668Skris	case ENOENT:
19865668Skris	case ENOTDIR:
19965668Skris	case EBADF:
20065668Skris	case ELOOP:
20176259Sgreen		ret = SSH2_FX_NO_SUCH_FILE;
20265668Skris		break;
20365668Skris	case EPERM:
20465668Skris	case EACCES:
20565668Skris	case EFAULT:
20676259Sgreen		ret = SSH2_FX_PERMISSION_DENIED;
20765668Skris		break;
20865668Skris	case ENAMETOOLONG:
20965668Skris	case EINVAL:
21076259Sgreen		ret = SSH2_FX_BAD_MESSAGE;
21165668Skris		break;
212181111Sdes	case ENOSYS:
213181111Sdes		ret = SSH2_FX_OP_UNSUPPORTED;
214181111Sdes		break;
21565668Skris	default:
21676259Sgreen		ret = SSH2_FX_FAILURE;
21765668Skris		break;
21865668Skris	}
21965668Skris	return ret;
22065668Skris}
22165668Skris
22292555Sdesstatic int
22365668Skrisflags_from_portable(int pflags)
22465668Skris{
22565668Skris	int flags = 0;
22676259Sgreen
22776259Sgreen	if ((pflags & SSH2_FXF_READ) &&
22876259Sgreen	    (pflags & SSH2_FXF_WRITE)) {
22965668Skris		flags = O_RDWR;
23076259Sgreen	} else if (pflags & SSH2_FXF_READ) {
23165668Skris		flags = O_RDONLY;
23276259Sgreen	} else if (pflags & SSH2_FXF_WRITE) {
23365668Skris		flags = O_WRONLY;
23465668Skris	}
235262566Sdes	if (pflags & SSH2_FXF_APPEND)
236262566Sdes		flags |= O_APPEND;
23776259Sgreen	if (pflags & SSH2_FXF_CREAT)
23865668Skris		flags |= O_CREAT;
23976259Sgreen	if (pflags & SSH2_FXF_TRUNC)
24065668Skris		flags |= O_TRUNC;
24176259Sgreen	if (pflags & SSH2_FXF_EXCL)
24265668Skris		flags |= O_EXCL;
24365668Skris	return flags;
24465668Skris}
24565668Skris
246162852Sdesstatic const char *
247162852Sdesstring_from_portable(int pflags)
248162852Sdes{
249162852Sdes	static char ret[128];
250162852Sdes
251162852Sdes	*ret = '\0';
252162852Sdes
253162852Sdes#define PAPPEND(str)	{				\
254162852Sdes		if (*ret != '\0')			\
255162852Sdes			strlcat(ret, ",", sizeof(ret));	\
256162852Sdes		strlcat(ret, str, sizeof(ret));		\
257162852Sdes	}
258162852Sdes
259162852Sdes	if (pflags & SSH2_FXF_READ)
260162852Sdes		PAPPEND("READ")
261162852Sdes	if (pflags & SSH2_FXF_WRITE)
262162852Sdes		PAPPEND("WRITE")
263262566Sdes	if (pflags & SSH2_FXF_APPEND)
264262566Sdes		PAPPEND("APPEND")
265162852Sdes	if (pflags & SSH2_FXF_CREAT)
266162852Sdes		PAPPEND("CREATE")
267162852Sdes	if (pflags & SSH2_FXF_TRUNC)
268162852Sdes		PAPPEND("TRUNCATE")
269162852Sdes	if (pflags & SSH2_FXF_EXCL)
270162852Sdes		PAPPEND("EXCL")
271162852Sdes
272162852Sdes	return ret;
273162852Sdes}
274162852Sdes
27592555Sdesstatic Attrib *
27665668Skrisget_attrib(void)
27765668Skris{
27865668Skris	return decode_attrib(&iqueue);
27965668Skris}
28065668Skris
28165668Skris/* handle handles */
28265668Skris
28365668Skristypedef struct Handle Handle;
28465668Skrisstruct Handle {
28565668Skris	int use;
28665668Skris	DIR *dirp;
28765668Skris	int fd;
288262566Sdes	int flags;
28965668Skris	char *name;
290162852Sdes	u_int64_t bytes_read, bytes_write;
291181111Sdes	int next_unused;
29265668Skris};
29376259Sgreen
29465668Skrisenum {
29565668Skris	HANDLE_UNUSED,
29665668Skris	HANDLE_DIR,
29765668Skris	HANDLE_FILE
29865668Skris};
29976259Sgreen
300181111SdesHandle *handles = NULL;
301181111Sdesu_int num_handles = 0;
302181111Sdesint first_unused_handle = -1;
30365668Skris
304181111Sdesstatic void handle_unused(int i)
30565668Skris{
306181111Sdes	handles[i].use = HANDLE_UNUSED;
307181111Sdes	handles[i].next_unused = first_unused_handle;
308181111Sdes	first_unused_handle = i;
30965668Skris}
31065668Skris
31192555Sdesstatic int
312262566Sdeshandle_new(int use, const char *name, int fd, int flags, DIR *dirp)
31365668Skris{
314181111Sdes	int i;
31576259Sgreen
316181111Sdes	if (first_unused_handle == -1) {
317181111Sdes		if (num_handles + 1 <= num_handles)
318181111Sdes			return -1;
319181111Sdes		num_handles++;
320181111Sdes		handles = xrealloc(handles, num_handles, sizeof(Handle));
321181111Sdes		handle_unused(num_handles - 1);
32265668Skris	}
323181111Sdes
324181111Sdes	i = first_unused_handle;
325181111Sdes	first_unused_handle = handles[i].next_unused;
326181111Sdes
327181111Sdes	handles[i].use = use;
328181111Sdes	handles[i].dirp = dirp;
329181111Sdes	handles[i].fd = fd;
330262566Sdes	handles[i].flags = flags;
331181111Sdes	handles[i].name = xstrdup(name);
332181111Sdes	handles[i].bytes_read = handles[i].bytes_write = 0;
333181111Sdes
334181111Sdes	return i;
33565668Skris}
33665668Skris
33792555Sdesstatic int
33865668Skrishandle_is_ok(int i, int type)
33965668Skris{
340181111Sdes	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
34165668Skris}
34265668Skris
34392555Sdesstatic int
34465668Skrishandle_to_string(int handle, char **stringp, int *hlenp)
34565668Skris{
34665668Skris	if (stringp == NULL || hlenp == NULL)
34765668Skris		return -1;
34876259Sgreen	*stringp = xmalloc(sizeof(int32_t));
349162852Sdes	put_u32(*stringp, handle);
35076259Sgreen	*hlenp = sizeof(int32_t);
35165668Skris	return 0;
35265668Skris}
35365668Skris
35492555Sdesstatic int
355126274Sdeshandle_from_string(const char *handle, u_int hlen)
35665668Skris{
35776259Sgreen	int val;
35876259Sgreen
35976259Sgreen	if (hlen != sizeof(int32_t))
36065668Skris		return -1;
361162852Sdes	val = get_u32(handle);
36265668Skris	if (handle_is_ok(val, HANDLE_FILE) ||
36365668Skris	    handle_is_ok(val, HANDLE_DIR))
36465668Skris		return val;
36565668Skris	return -1;
36665668Skris}
36765668Skris
36892555Sdesstatic char *
36965668Skrishandle_to_name(int handle)
37065668Skris{
37165668Skris	if (handle_is_ok(handle, HANDLE_DIR)||
37265668Skris	    handle_is_ok(handle, HANDLE_FILE))
37365668Skris		return handles[handle].name;
37465668Skris	return NULL;
37565668Skris}
37665668Skris
37792555Sdesstatic DIR *
37865668Skrishandle_to_dir(int handle)
37965668Skris{
38065668Skris	if (handle_is_ok(handle, HANDLE_DIR))
38165668Skris		return handles[handle].dirp;
38265668Skris	return NULL;
38365668Skris}
38465668Skris
38592555Sdesstatic int
38665668Skrishandle_to_fd(int handle)
38765668Skris{
38876259Sgreen	if (handle_is_ok(handle, HANDLE_FILE))
38965668Skris		return handles[handle].fd;
39065668Skris	return -1;
39165668Skris}
39265668Skris
393262566Sdesstatic int
394262566Sdeshandle_to_flags(int handle)
395262566Sdes{
396262566Sdes	if (handle_is_ok(handle, HANDLE_FILE))
397262566Sdes		return handles[handle].flags;
398262566Sdes	return 0;
399262566Sdes}
400262566Sdes
401162852Sdesstatic void
402162852Sdeshandle_update_read(int handle, ssize_t bytes)
403162852Sdes{
404162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
405162852Sdes		handles[handle].bytes_read += bytes;
406162852Sdes}
407162852Sdes
408162852Sdesstatic void
409162852Sdeshandle_update_write(int handle, ssize_t bytes)
410162852Sdes{
411162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
412162852Sdes		handles[handle].bytes_write += bytes;
413162852Sdes}
414162852Sdes
415162852Sdesstatic u_int64_t
416162852Sdeshandle_bytes_read(int handle)
417162852Sdes{
418162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
419162852Sdes		return (handles[handle].bytes_read);
420162852Sdes	return 0;
421162852Sdes}
422162852Sdes
423162852Sdesstatic u_int64_t
424162852Sdeshandle_bytes_write(int handle)
425162852Sdes{
426162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
427162852Sdes		return (handles[handle].bytes_write);
428162852Sdes	return 0;
429162852Sdes}
430162852Sdes
43192555Sdesstatic int
43265668Skrishandle_close(int handle)
43365668Skris{
43465668Skris	int ret = -1;
43576259Sgreen
43665668Skris	if (handle_is_ok(handle, HANDLE_FILE)) {
43765668Skris		ret = close(handles[handle].fd);
438255767Sdes		free(handles[handle].name);
439181111Sdes		handle_unused(handle);
44065668Skris	} else if (handle_is_ok(handle, HANDLE_DIR)) {
44165668Skris		ret = closedir(handles[handle].dirp);
442255767Sdes		free(handles[handle].name);
443181111Sdes		handle_unused(handle);
44465668Skris	} else {
44565668Skris		errno = ENOENT;
44665668Skris	}
44765668Skris	return ret;
44865668Skris}
44965668Skris
450162852Sdesstatic void
451162852Sdeshandle_log_close(int handle, char *emsg)
452162852Sdes{
453162852Sdes	if (handle_is_ok(handle, HANDLE_FILE)) {
454162852Sdes		logit("%s%sclose \"%s\" bytes read %llu written %llu",
455162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
456162852Sdes		    handle_to_name(handle),
457181111Sdes		    (unsigned long long)handle_bytes_read(handle),
458181111Sdes		    (unsigned long long)handle_bytes_write(handle));
459162852Sdes	} else {
460162852Sdes		logit("%s%sclosedir \"%s\"",
461162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
462162852Sdes		    handle_to_name(handle));
463162852Sdes	}
464162852Sdes}
465162852Sdes
466162852Sdesstatic void
467162852Sdeshandle_log_exit(void)
468162852Sdes{
469162852Sdes	u_int i;
470162852Sdes
471181111Sdes	for (i = 0; i < num_handles; i++)
472162852Sdes		if (handles[i].use != HANDLE_UNUSED)
473162852Sdes			handle_log_close(i, "forced");
474162852Sdes}
475162852Sdes
47692555Sdesstatic int
47765668Skrisget_handle(void)
47865668Skris{
47965668Skris	char *handle;
48076259Sgreen	int val = -1;
48165668Skris	u_int hlen;
48276259Sgreen
48365668Skris	handle = get_string(&hlen);
48476259Sgreen	if (hlen < 256)
48576259Sgreen		val = handle_from_string(handle, hlen);
486255767Sdes	free(handle);
48765668Skris	return val;
48865668Skris}
48965668Skris
49065668Skris/* send replies */
49165668Skris
49292555Sdesstatic void
49365668Skrissend_msg(Buffer *m)
49465668Skris{
49565668Skris	int mlen = buffer_len(m);
49676259Sgreen
49765668Skris	buffer_put_int(&oqueue, mlen);
49865668Skris	buffer_append(&oqueue, buffer_ptr(m), mlen);
49965668Skris	buffer_consume(m, mlen);
50065668Skris}
50165668Skris
502162852Sdesstatic const char *
503162852Sdesstatus_to_message(u_int32_t status)
50465668Skris{
50576259Sgreen	const char *status_messages[] = {
50676259Sgreen		"Success",			/* SSH_FX_OK */
50776259Sgreen		"End of file",			/* SSH_FX_EOF */
50876259Sgreen		"No such file",			/* SSH_FX_NO_SUCH_FILE */
50976259Sgreen		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
51076259Sgreen		"Failure",			/* SSH_FX_FAILURE */
51176259Sgreen		"Bad message",			/* SSH_FX_BAD_MESSAGE */
51276259Sgreen		"No connection",		/* SSH_FX_NO_CONNECTION */
51376259Sgreen		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
51476259Sgreen		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
51576259Sgreen		"Unknown error"			/* Others */
51676259Sgreen	};
517162852Sdes	return (status_messages[MIN(status,SSH2_FX_MAX)]);
518162852Sdes}
51976259Sgreen
520162852Sdesstatic void
521162852Sdessend_status(u_int32_t id, u_int32_t status)
522162852Sdes{
523162852Sdes	Buffer msg;
524162852Sdes
525162852Sdes	debug3("request %u: sent status %u", id, status);
526162852Sdes	if (log_level > SYSLOG_LEVEL_VERBOSE ||
527162852Sdes	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
528162852Sdes		logit("sent status %s", status_to_message(status));
52965668Skris	buffer_init(&msg);
53076259Sgreen	buffer_put_char(&msg, SSH2_FXP_STATUS);
53165668Skris	buffer_put_int(&msg, id);
532137015Sdes	buffer_put_int(&msg, status);
53376259Sgreen	if (version >= 3) {
534162852Sdes		buffer_put_cstring(&msg, status_to_message(status));
53576259Sgreen		buffer_put_cstring(&msg, "");
53676259Sgreen	}
53765668Skris	send_msg(&msg);
53865668Skris	buffer_free(&msg);
53965668Skris}
54092555Sdesstatic void
541126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
54265668Skris{
54365668Skris	Buffer msg;
54476259Sgreen
54565668Skris	buffer_init(&msg);
54665668Skris	buffer_put_char(&msg, type);
54765668Skris	buffer_put_int(&msg, id);
54865668Skris	buffer_put_string(&msg, data, dlen);
54965668Skris	send_msg(&msg);
55065668Skris	buffer_free(&msg);
55165668Skris}
55265668Skris
55392555Sdesstatic void
554126274Sdessend_data(u_int32_t id, const char *data, int dlen)
55565668Skris{
556162852Sdes	debug("request %u: sent data len %d", id, dlen);
55776259Sgreen	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
55865668Skris}
55965668Skris
56092555Sdesstatic void
56165668Skrissend_handle(u_int32_t id, int handle)
56265668Skris{
56365668Skris	char *string;
56465668Skris	int hlen;
56576259Sgreen
56665668Skris	handle_to_string(handle, &string, &hlen);
567162852Sdes	debug("request %u: sent handle handle %d", id, handle);
56876259Sgreen	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
569255767Sdes	free(string);
57065668Skris}
57165668Skris
57292555Sdesstatic void
573126274Sdessend_names(u_int32_t id, int count, const Stat *stats)
57465668Skris{
57565668Skris	Buffer msg;
57665668Skris	int i;
57776259Sgreen
57865668Skris	buffer_init(&msg);
57976259Sgreen	buffer_put_char(&msg, SSH2_FXP_NAME);
58065668Skris	buffer_put_int(&msg, id);
58165668Skris	buffer_put_int(&msg, count);
582162852Sdes	debug("request %u: sent names count %d", id, count);
58365668Skris	for (i = 0; i < count; i++) {
58465668Skris		buffer_put_cstring(&msg, stats[i].name);
58565668Skris		buffer_put_cstring(&msg, stats[i].long_name);
58665668Skris		encode_attrib(&msg, &stats[i].attrib);
58765668Skris	}
58865668Skris	send_msg(&msg);
58965668Skris	buffer_free(&msg);
59065668Skris}
59165668Skris
59292555Sdesstatic void
593126274Sdessend_attrib(u_int32_t id, const Attrib *a)
59465668Skris{
59565668Skris	Buffer msg;
59676259Sgreen
597162852Sdes	debug("request %u: sent attrib have 0x%x", id, a->flags);
59865668Skris	buffer_init(&msg);
59976259Sgreen	buffer_put_char(&msg, SSH2_FXP_ATTRS);
60065668Skris	buffer_put_int(&msg, id);
60165668Skris	encode_attrib(&msg, a);
60265668Skris	send_msg(&msg);
60365668Skris	buffer_free(&msg);
60465668Skris}
60565668Skris
606181111Sdesstatic void
607181111Sdessend_statvfs(u_int32_t id, struct statvfs *st)
608181111Sdes{
609181111Sdes	Buffer msg;
610181111Sdes	u_int64_t flag;
611181111Sdes
612181111Sdes	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
613181111Sdes	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
614181111Sdes
615181111Sdes	buffer_init(&msg);
616181111Sdes	buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY);
617181111Sdes	buffer_put_int(&msg, id);
618181111Sdes	buffer_put_int64(&msg, st->f_bsize);
619181111Sdes	buffer_put_int64(&msg, st->f_frsize);
620181111Sdes	buffer_put_int64(&msg, st->f_blocks);
621181111Sdes	buffer_put_int64(&msg, st->f_bfree);
622181111Sdes	buffer_put_int64(&msg, st->f_bavail);
623181111Sdes	buffer_put_int64(&msg, st->f_files);
624181111Sdes	buffer_put_int64(&msg, st->f_ffree);
625181111Sdes	buffer_put_int64(&msg, st->f_favail);
626181111Sdes	buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid));
627181111Sdes	buffer_put_int64(&msg, flag);
628181111Sdes	buffer_put_int64(&msg, st->f_namemax);
629181111Sdes	send_msg(&msg);
630181111Sdes	buffer_free(&msg);
631181111Sdes}
632181111Sdes
63365668Skris/* parse incoming */
63465668Skris
63592555Sdesstatic void
63665668Skrisprocess_init(void)
63765668Skris{
63865668Skris	Buffer msg;
63965668Skris
64098675Sdes	version = get_int();
641226046Sdes	verbose("received client version %u", version);
64265668Skris	buffer_init(&msg);
64376259Sgreen	buffer_put_char(&msg, SSH2_FXP_VERSION);
64476259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
645181111Sdes	/* POSIX rename extension */
646181111Sdes	buffer_put_cstring(&msg, "posix-rename@openssh.com");
647181111Sdes	buffer_put_cstring(&msg, "1"); /* version */
648181111Sdes	/* statvfs extension */
649181111Sdes	buffer_put_cstring(&msg, "statvfs@openssh.com");
650181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
651181111Sdes	/* fstatvfs extension */
652181111Sdes	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
653181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
654221420Sdes	/* hardlink extension */
655221420Sdes	buffer_put_cstring(&msg, "hardlink@openssh.com");
656221420Sdes	buffer_put_cstring(&msg, "1"); /* version */
657262566Sdes	/* fsync extension */
658262566Sdes	buffer_put_cstring(&msg, "fsync@openssh.com");
659262566Sdes	buffer_put_cstring(&msg, "1"); /* version */
66065668Skris	send_msg(&msg);
66165668Skris	buffer_free(&msg);
66265668Skris}
66365668Skris
66492555Sdesstatic void
665262566Sdesprocess_open(u_int32_t id)
66665668Skris{
667262566Sdes	u_int32_t pflags;
66865668Skris	Attrib *a;
66965668Skris	char *name;
67076259Sgreen	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
67165668Skris
67265668Skris	name = get_string(NULL);
67376259Sgreen	pflags = get_int();		/* portable flags */
674162852Sdes	debug3("request %u: open flags %d", id, pflags);
67565668Skris	a = get_attrib();
67665668Skris	flags = flags_from_portable(pflags);
67776259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
678162852Sdes	logit("open \"%s\" flags %s mode 0%o",
679162852Sdes	    name, string_from_portable(pflags), mode);
680204917Sdes	if (readonly &&
681262566Sdes	    ((flags & O_ACCMODE) == O_WRONLY ||
682262566Sdes	    (flags & O_ACCMODE) == O_RDWR)) {
683262566Sdes		verbose("Refusing open request in read-only mode");
684262566Sdes	  	status = SSH2_FX_PERMISSION_DENIED;
685262566Sdes	} else {
686204917Sdes		fd = open(name, flags, mode);
687204917Sdes		if (fd < 0) {
688204917Sdes			status = errno_to_portable(errno);
68965668Skris		} else {
690262566Sdes			handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
691204917Sdes			if (handle < 0) {
692204917Sdes				close(fd);
693204917Sdes			} else {
694204917Sdes				send_handle(id, handle);
695204917Sdes				status = SSH2_FX_OK;
696204917Sdes			}
69765668Skris		}
69865668Skris	}
69976259Sgreen	if (status != SSH2_FX_OK)
70065668Skris		send_status(id, status);
701255767Sdes	free(name);
70265668Skris}
70365668Skris
70492555Sdesstatic void
705262566Sdesprocess_close(u_int32_t id)
70665668Skris{
70776259Sgreen	int handle, ret, status = SSH2_FX_FAILURE;
70865668Skris
70965668Skris	handle = get_handle();
710162852Sdes	debug3("request %u: close handle %u", id, handle);
711162852Sdes	handle_log_close(handle, NULL);
71265668Skris	ret = handle_close(handle);
71376259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
71465668Skris	send_status(id, status);
71565668Skris}
71665668Skris
71792555Sdesstatic void
718262566Sdesprocess_read(u_int32_t id)
71965668Skris{
72065668Skris	char buf[64*1024];
721262566Sdes	u_int32_t len;
72276259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
72365668Skris	u_int64_t off;
72465668Skris
72565668Skris	handle = get_handle();
72676259Sgreen	off = get_int64();
72765668Skris	len = get_int();
72865668Skris
729162852Sdes	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
730162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
73165668Skris	if (len > sizeof buf) {
73265668Skris		len = sizeof buf;
733162852Sdes		debug2("read change len %d", len);
73465668Skris	}
73565668Skris	fd = handle_to_fd(handle);
73665668Skris	if (fd >= 0) {
73765668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
73865668Skris			error("process_read: seek failed");
73965668Skris			status = errno_to_portable(errno);
74065668Skris		} else {
74165668Skris			ret = read(fd, buf, len);
74265668Skris			if (ret < 0) {
74365668Skris				status = errno_to_portable(errno);
74465668Skris			} else if (ret == 0) {
74576259Sgreen				status = SSH2_FX_EOF;
74665668Skris			} else {
74765668Skris				send_data(id, buf, ret);
74876259Sgreen				status = SSH2_FX_OK;
749162852Sdes				handle_update_read(handle, ret);
75065668Skris			}
75165668Skris		}
75265668Skris	}
75376259Sgreen	if (status != SSH2_FX_OK)
75465668Skris		send_status(id, status);
75565668Skris}
75665668Skris
75792555Sdesstatic void
758262566Sdesprocess_write(u_int32_t id)
75965668Skris{
76065668Skris	u_int64_t off;
76165668Skris	u_int len;
762204917Sdes	int handle, fd, ret, status;
76365668Skris	char *data;
76465668Skris
76565668Skris	handle = get_handle();
76676259Sgreen	off = get_int64();
76765668Skris	data = get_string(&len);
76865668Skris
769162852Sdes	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
770162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
77165668Skris	fd = handle_to_fd(handle);
772204917Sdes
773204917Sdes	if (fd < 0)
774204917Sdes		status = SSH2_FX_FAILURE;
775204917Sdes	else {
776262566Sdes		if (!(handle_to_flags(handle) & O_APPEND) &&
777262566Sdes				lseek(fd, off, SEEK_SET) < 0) {
77865668Skris			status = errno_to_portable(errno);
77965668Skris			error("process_write: seek failed");
78065668Skris		} else {
78165668Skris/* XXX ATOMICIO ? */
78265668Skris			ret = write(fd, data, len);
783149749Sdes			if (ret < 0) {
78465668Skris				error("process_write: write failed");
78565668Skris				status = errno_to_portable(errno);
786149749Sdes			} else if ((size_t)ret == len) {
78776259Sgreen				status = SSH2_FX_OK;
788162852Sdes				handle_update_write(handle, ret);
78965668Skris			} else {
790162852Sdes				debug2("nothing at all written");
791204917Sdes				status = SSH2_FX_FAILURE;
79265668Skris			}
79365668Skris		}
79465668Skris	}
79565668Skris	send_status(id, status);
796255767Sdes	free(data);
79765668Skris}
79865668Skris
79992555Sdesstatic void
800262566Sdesprocess_do_stat(u_int32_t id, int do_lstat)
80165668Skris{
80276259Sgreen	Attrib a;
80365668Skris	struct stat st;
80465668Skris	char *name;
80576259Sgreen	int ret, status = SSH2_FX_FAILURE;
80665668Skris
80765668Skris	name = get_string(NULL);
808162852Sdes	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
809162852Sdes	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
81065668Skris	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
81165668Skris	if (ret < 0) {
81265668Skris		status = errno_to_portable(errno);
81365668Skris	} else {
81476259Sgreen		stat_to_attrib(&st, &a);
81576259Sgreen		send_attrib(id, &a);
81676259Sgreen		status = SSH2_FX_OK;
81765668Skris	}
81876259Sgreen	if (status != SSH2_FX_OK)
81965668Skris		send_status(id, status);
820255767Sdes	free(name);
82165668Skris}
82265668Skris
82392555Sdesstatic void
824262566Sdesprocess_stat(u_int32_t id)
82565668Skris{
826262566Sdes	process_do_stat(id, 0);
82765668Skris}
82865668Skris
82992555Sdesstatic void
830262566Sdesprocess_lstat(u_int32_t id)
83165668Skris{
832262566Sdes	process_do_stat(id, 1);
83365668Skris}
83465668Skris
83592555Sdesstatic void
836262566Sdesprocess_fstat(u_int32_t id)
83765668Skris{
83876259Sgreen	Attrib a;
83965668Skris	struct stat st;
84076259Sgreen	int fd, ret, handle, status = SSH2_FX_FAILURE;
84165668Skris
84265668Skris	handle = get_handle();
843162852Sdes	debug("request %u: fstat \"%s\" (handle %u)",
844162852Sdes	    id, handle_to_name(handle), handle);
84565668Skris	fd = handle_to_fd(handle);
846181111Sdes	if (fd >= 0) {
84765668Skris		ret = fstat(fd, &st);
84865668Skris		if (ret < 0) {
84965668Skris			status = errno_to_portable(errno);
85065668Skris		} else {
85176259Sgreen			stat_to_attrib(&st, &a);
85276259Sgreen			send_attrib(id, &a);
85376259Sgreen			status = SSH2_FX_OK;
85465668Skris		}
85565668Skris	}
85676259Sgreen	if (status != SSH2_FX_OK)
85765668Skris		send_status(id, status);
85865668Skris}
85965668Skris
86092555Sdesstatic struct timeval *
861126274Sdesattrib_to_tv(const Attrib *a)
86265668Skris{
86365668Skris	static struct timeval tv[2];
86476259Sgreen
86565668Skris	tv[0].tv_sec = a->atime;
86665668Skris	tv[0].tv_usec = 0;
86765668Skris	tv[1].tv_sec = a->mtime;
86865668Skris	tv[1].tv_usec = 0;
86965668Skris	return tv;
87065668Skris}
87165668Skris
87292555Sdesstatic void
873262566Sdesprocess_setstat(u_int32_t id)
87465668Skris{
87565668Skris	Attrib *a;
87665668Skris	char *name;
87799060Sdes	int status = SSH2_FX_OK, ret;
87865668Skris
87965668Skris	name = get_string(NULL);
88065668Skris	a = get_attrib();
881162852Sdes	debug("request %u: setstat name \"%s\"", id, name);
88292555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
883181111Sdes		logit("set \"%s\" size %llu",
884181111Sdes		    name, (unsigned long long)a->size);
88592555Sdes		ret = truncate(name, a->size);
88692555Sdes		if (ret == -1)
88792555Sdes			status = errno_to_portable(errno);
88892555Sdes	}
88976259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
890162852Sdes		logit("set \"%s\" mode %04o", name, a->perm);
891181111Sdes		ret = chmod(name, a->perm & 07777);
89265668Skris		if (ret == -1)
89365668Skris			status = errno_to_portable(errno);
89465668Skris	}
89576259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
896162852Sdes		char buf[64];
897162852Sdes		time_t t = a->mtime;
898162852Sdes
899162852Sdes		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
900162852Sdes		    localtime(&t));
901162852Sdes		logit("set \"%s\" modtime %s", name, buf);
90265668Skris		ret = utimes(name, attrib_to_tv(a));
90365668Skris		if (ret == -1)
90465668Skris			status = errno_to_portable(errno);
90565668Skris	}
90676259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
907162852Sdes		logit("set \"%s\" owner %lu group %lu", name,
908162852Sdes		    (u_long)a->uid, (u_long)a->gid);
90976259Sgreen		ret = chown(name, a->uid, a->gid);
91076259Sgreen		if (ret == -1)
91176259Sgreen			status = errno_to_portable(errno);
91276259Sgreen	}
91365668Skris	send_status(id, status);
914255767Sdes	free(name);
91565668Skris}
91665668Skris
91792555Sdesstatic void
918262566Sdesprocess_fsetstat(u_int32_t id)
91965668Skris{
92065668Skris	Attrib *a;
92165668Skris	int handle, fd, ret;
92276259Sgreen	int status = SSH2_FX_OK;
92365668Skris
92465668Skris	handle = get_handle();
92565668Skris	a = get_attrib();
926162852Sdes	debug("request %u: fsetstat handle %d", id, handle);
92765668Skris	fd = handle_to_fd(handle);
928204917Sdes	if (fd < 0)
92976259Sgreen		status = SSH2_FX_FAILURE;
930204917Sdes	else {
931162852Sdes		char *name = handle_to_name(handle);
932162852Sdes
93392555Sdes		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
934181111Sdes			logit("set \"%s\" size %llu",
935181111Sdes			    name, (unsigned long long)a->size);
93692555Sdes			ret = ftruncate(fd, a->size);
93792555Sdes			if (ret == -1)
93892555Sdes				status = errno_to_portable(errno);
93992555Sdes		}
94076259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
941162852Sdes			logit("set \"%s\" mode %04o", name, a->perm);
94298937Sdes#ifdef HAVE_FCHMOD
943181111Sdes			ret = fchmod(fd, a->perm & 07777);
94498937Sdes#else
945181111Sdes			ret = chmod(name, a->perm & 07777);
94698937Sdes#endif
94765668Skris			if (ret == -1)
94865668Skris				status = errno_to_portable(errno);
94965668Skris		}
95076259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
951162852Sdes			char buf[64];
952162852Sdes			time_t t = a->mtime;
953162852Sdes
954162852Sdes			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
955162852Sdes			    localtime(&t));
956162852Sdes			logit("set \"%s\" modtime %s", name, buf);
95798937Sdes#ifdef HAVE_FUTIMES
95865668Skris			ret = futimes(fd, attrib_to_tv(a));
95998937Sdes#else
96098937Sdes			ret = utimes(name, attrib_to_tv(a));
96198937Sdes#endif
96265668Skris			if (ret == -1)
96365668Skris				status = errno_to_portable(errno);
96465668Skris		}
96576259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
966162852Sdes			logit("set \"%s\" owner %lu group %lu", name,
967162852Sdes			    (u_long)a->uid, (u_long)a->gid);
96898937Sdes#ifdef HAVE_FCHOWN
96976259Sgreen			ret = fchown(fd, a->uid, a->gid);
97098937Sdes#else
97198937Sdes			ret = chown(name, a->uid, a->gid);
97298937Sdes#endif
97376259Sgreen			if (ret == -1)
97476259Sgreen				status = errno_to_portable(errno);
97576259Sgreen		}
97665668Skris	}
97765668Skris	send_status(id, status);
97865668Skris}
97965668Skris
98092555Sdesstatic void
981262566Sdesprocess_opendir(u_int32_t id)
98265668Skris{
98365668Skris	DIR *dirp = NULL;
98465668Skris	char *path;
98576259Sgreen	int handle, status = SSH2_FX_FAILURE;
98665668Skris
98765668Skris	path = get_string(NULL);
988162852Sdes	debug3("request %u: opendir", id);
989162852Sdes	logit("opendir \"%s\"", path);
99076259Sgreen	dirp = opendir(path);
99165668Skris	if (dirp == NULL) {
99265668Skris		status = errno_to_portable(errno);
99365668Skris	} else {
994262566Sdes		handle = handle_new(HANDLE_DIR, path, 0, 0, dirp);
99565668Skris		if (handle < 0) {
99665668Skris			closedir(dirp);
99765668Skris		} else {
99865668Skris			send_handle(id, handle);
99976259Sgreen			status = SSH2_FX_OK;
100065668Skris		}
100176259Sgreen
100265668Skris	}
100376259Sgreen	if (status != SSH2_FX_OK)
100465668Skris		send_status(id, status);
1005255767Sdes	free(path);
100665668Skris}
100765668Skris
100892555Sdesstatic void
1009262566Sdesprocess_readdir(u_int32_t id)
101065668Skris{
101165668Skris	DIR *dirp;
101265668Skris	struct dirent *dp;
101365668Skris	char *path;
101465668Skris	int handle;
101565668Skris
101665668Skris	handle = get_handle();
1017162852Sdes	debug("request %u: readdir \"%s\" (handle %d)", id,
1018162852Sdes	    handle_to_name(handle), handle);
101965668Skris	dirp = handle_to_dir(handle);
102065668Skris	path = handle_to_name(handle);
102165668Skris	if (dirp == NULL || path == NULL) {
102276259Sgreen		send_status(id, SSH2_FX_FAILURE);
102365668Skris	} else {
102465668Skris		struct stat st;
1025162852Sdes		char pathname[MAXPATHLEN];
102665668Skris		Stat *stats;
102765668Skris		int nstats = 10, count = 0, i;
102899060Sdes
1029162852Sdes		stats = xcalloc(nstats, sizeof(Stat));
103065668Skris		while ((dp = readdir(dirp)) != NULL) {
103165668Skris			if (count >= nstats) {
103265668Skris				nstats *= 2;
1033162852Sdes				stats = xrealloc(stats, nstats, sizeof(Stat));
103465668Skris			}
103565668Skris/* XXX OVERFLOW ? */
103692555Sdes			snprintf(pathname, sizeof pathname, "%s%s%s", path,
103792555Sdes			    strcmp(path, "/") ? "/" : "", dp->d_name);
103865668Skris			if (lstat(pathname, &st) < 0)
103965668Skris				continue;
104076259Sgreen			stat_to_attrib(&st, &(stats[count].attrib));
104165668Skris			stats[count].name = xstrdup(dp->d_name);
1042204917Sdes			stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
104365668Skris			count++;
104465668Skris			/* send up to 100 entries in one message */
104576259Sgreen			/* XXX check packet size instead */
104665668Skris			if (count == 100)
104765668Skris				break;
104865668Skris		}
104976259Sgreen		if (count > 0) {
105076259Sgreen			send_names(id, count, stats);
105192555Sdes			for (i = 0; i < count; i++) {
1052255767Sdes				free(stats[i].name);
1053255767Sdes				free(stats[i].long_name);
105476259Sgreen			}
105576259Sgreen		} else {
105676259Sgreen			send_status(id, SSH2_FX_EOF);
105765668Skris		}
1058255767Sdes		free(stats);
105965668Skris	}
106065668Skris}
106165668Skris
106292555Sdesstatic void
1063262566Sdesprocess_remove(u_int32_t id)
106465668Skris{
106565668Skris	char *name;
106676259Sgreen	int status = SSH2_FX_FAILURE;
106765668Skris	int ret;
106865668Skris
106965668Skris	name = get_string(NULL);
1070162852Sdes	debug3("request %u: remove", id);
1071162852Sdes	logit("remove name \"%s\"", name);
1072262566Sdes	ret = unlink(name);
1073262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
107465668Skris	send_status(id, status);
1075255767Sdes	free(name);
107665668Skris}
107765668Skris
107892555Sdesstatic void
1079262566Sdesprocess_mkdir(u_int32_t id)
108065668Skris{
108165668Skris	Attrib *a;
108265668Skris	char *name;
108376259Sgreen	int ret, mode, status = SSH2_FX_FAILURE;
108465668Skris
108565668Skris	name = get_string(NULL);
108665668Skris	a = get_attrib();
108776259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
1088181111Sdes	    a->perm & 07777 : 0777;
1089162852Sdes	debug3("request %u: mkdir", id);
1090162852Sdes	logit("mkdir name \"%s\" mode 0%o", name, mode);
1091262566Sdes	ret = mkdir(name, mode);
1092262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
109365668Skris	send_status(id, status);
1094255767Sdes	free(name);
109565668Skris}
109665668Skris
109792555Sdesstatic void
1098262566Sdesprocess_rmdir(u_int32_t id)
109965668Skris{
110065668Skris	char *name;
110165668Skris	int ret, status;
110265668Skris
110365668Skris	name = get_string(NULL);
1104162852Sdes	debug3("request %u: rmdir", id);
1105162852Sdes	logit("rmdir name \"%s\"", name);
1106262566Sdes	ret = rmdir(name);
1107262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
110865668Skris	send_status(id, status);
1109255767Sdes	free(name);
111065668Skris}
111165668Skris
111292555Sdesstatic void
1113262566Sdesprocess_realpath(u_int32_t id)
111465668Skris{
111565668Skris	char resolvedname[MAXPATHLEN];
111665668Skris	char *path;
111765668Skris
111865668Skris	path = get_string(NULL);
111976259Sgreen	if (path[0] == '\0') {
1120255767Sdes		free(path);
112176259Sgreen		path = xstrdup(".");
112276259Sgreen	}
1123162852Sdes	debug3("request %u: realpath", id);
1124162852Sdes	verbose("realpath \"%s\"", path);
112565668Skris	if (realpath(path, resolvedname) == NULL) {
112665668Skris		send_status(id, errno_to_portable(errno));
112765668Skris	} else {
112865668Skris		Stat s;
112965668Skris		attrib_clear(&s.attrib);
113065668Skris		s.name = s.long_name = resolvedname;
113165668Skris		send_names(id, 1, &s);
113265668Skris	}
1133255767Sdes	free(path);
113465668Skris}
113565668Skris
113692555Sdesstatic void
1137262566Sdesprocess_rename(u_int32_t id)
113865668Skris{
113965668Skris	char *oldpath, *newpath;
1140113908Sdes	int status;
1141113908Sdes	struct stat sb;
114265668Skris
114365668Skris	oldpath = get_string(NULL);
114465668Skris	newpath = get_string(NULL);
1145162852Sdes	debug3("request %u: rename", id);
1146162852Sdes	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1147113908Sdes	status = SSH2_FX_FAILURE;
1148262566Sdes	if (lstat(oldpath, &sb) == -1)
1149113908Sdes		status = errno_to_portable(errno);
1150113908Sdes	else if (S_ISREG(sb.st_mode)) {
1151113908Sdes		/* Race-free rename of regular files */
1152137015Sdes		if (link(oldpath, newpath) == -1) {
1153197679Sdes			if (errno == EOPNOTSUPP || errno == ENOSYS
1154181111Sdes#ifdef EXDEV
1155181111Sdes			    || errno == EXDEV
1156181111Sdes#endif
1157137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO
1158137015Sdes			    || errno == LINK_OPNOTSUPP_ERRNO
1159137015Sdes#endif
1160137015Sdes			    ) {
1161137015Sdes				struct stat st;
1162137015Sdes
1163137015Sdes				/*
1164137015Sdes				 * fs doesn't support links, so fall back to
1165137015Sdes				 * stat+rename.  This is racy.
1166137015Sdes				 */
1167137015Sdes				if (stat(newpath, &st) == -1) {
1168137015Sdes					if (rename(oldpath, newpath) == -1)
1169137015Sdes						status =
1170137015Sdes						    errno_to_portable(errno);
1171137015Sdes					else
1172137015Sdes						status = SSH2_FX_OK;
1173137015Sdes				}
1174137015Sdes			} else {
1175137015Sdes				status = errno_to_portable(errno);
1176137015Sdes			}
1177137015Sdes		} else if (unlink(oldpath) == -1) {
1178113908Sdes			status = errno_to_portable(errno);
1179113908Sdes			/* clean spare link */
1180113908Sdes			unlink(newpath);
1181113908Sdes		} else
1182113908Sdes			status = SSH2_FX_OK;
1183113908Sdes	} else if (stat(newpath, &sb) == -1) {
1184113908Sdes		if (rename(oldpath, newpath) == -1)
1185113908Sdes			status = errno_to_portable(errno);
1186113908Sdes		else
1187113908Sdes			status = SSH2_FX_OK;
118876259Sgreen	}
118965668Skris	send_status(id, status);
1190255767Sdes	free(oldpath);
1191255767Sdes	free(newpath);
119265668Skris}
119365668Skris
119492555Sdesstatic void
1195262566Sdesprocess_readlink(u_int32_t id)
119676259Sgreen{
119792555Sdes	int len;
1198137015Sdes	char buf[MAXPATHLEN];
119976259Sgreen	char *path;
120065668Skris
120176259Sgreen	path = get_string(NULL);
1202162852Sdes	debug3("request %u: readlink", id);
1203162852Sdes	verbose("readlink \"%s\"", path);
1204137015Sdes	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
120576259Sgreen		send_status(id, errno_to_portable(errno));
120676259Sgreen	else {
120776259Sgreen		Stat s;
120892555Sdes
1209137015Sdes		buf[len] = '\0';
121076259Sgreen		attrib_clear(&s.attrib);
1211137015Sdes		s.name = s.long_name = buf;
121276259Sgreen		send_names(id, 1, &s);
121376259Sgreen	}
1214255767Sdes	free(path);
121576259Sgreen}
121676259Sgreen
121792555Sdesstatic void
1218262566Sdesprocess_symlink(u_int32_t id)
121976259Sgreen{
122076259Sgreen	char *oldpath, *newpath;
1221113908Sdes	int ret, status;
122276259Sgreen
122376259Sgreen	oldpath = get_string(NULL);
122476259Sgreen	newpath = get_string(NULL);
1225162852Sdes	debug3("request %u: symlink", id);
1226162852Sdes	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1227113908Sdes	/* this will fail if 'newpath' exists */
1228262566Sdes	ret = symlink(oldpath, newpath);
1229262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
123076259Sgreen	send_status(id, status);
1231255767Sdes	free(oldpath);
1232255767Sdes	free(newpath);
123376259Sgreen}
123476259Sgreen
123592555Sdesstatic void
1236181111Sdesprocess_extended_posix_rename(u_int32_t id)
1237181111Sdes{
1238181111Sdes	char *oldpath, *newpath;
1239204917Sdes	int ret, status;
1240181111Sdes
1241181111Sdes	oldpath = get_string(NULL);
1242181111Sdes	newpath = get_string(NULL);
1243181111Sdes	debug3("request %u: posix-rename", id);
1244181111Sdes	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1245262566Sdes	ret = rename(oldpath, newpath);
1246262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1247204917Sdes	send_status(id, status);
1248255767Sdes	free(oldpath);
1249255767Sdes	free(newpath);
1250181111Sdes}
1251181111Sdes
1252181111Sdesstatic void
1253181111Sdesprocess_extended_statvfs(u_int32_t id)
1254181111Sdes{
1255181111Sdes	char *path;
1256181111Sdes	struct statvfs st;
1257181111Sdes
1258181111Sdes	path = get_string(NULL);
1259262566Sdes	debug3("request %u: statvfs", id);
1260262566Sdes	logit("statvfs \"%s\"", path);
1261181111Sdes
1262181111Sdes	if (statvfs(path, &st) != 0)
1263181111Sdes		send_status(id, errno_to_portable(errno));
1264181111Sdes	else
1265181111Sdes		send_statvfs(id, &st);
1266255767Sdes        free(path);
1267181111Sdes}
1268181111Sdes
1269181111Sdesstatic void
1270181111Sdesprocess_extended_fstatvfs(u_int32_t id)
1271181111Sdes{
1272181111Sdes	int handle, fd;
1273181111Sdes	struct statvfs st;
1274181111Sdes
1275181111Sdes	handle = get_handle();
1276181111Sdes	debug("request %u: fstatvfs \"%s\" (handle %u)",
1277181111Sdes	    id, handle_to_name(handle), handle);
1278181111Sdes	if ((fd = handle_to_fd(handle)) < 0) {
1279181111Sdes		send_status(id, SSH2_FX_FAILURE);
1280181111Sdes		return;
1281181111Sdes	}
1282181111Sdes	if (fstatvfs(fd, &st) != 0)
1283181111Sdes		send_status(id, errno_to_portable(errno));
1284181111Sdes	else
1285181111Sdes		send_statvfs(id, &st);
1286181111Sdes}
1287181111Sdes
1288181111Sdesstatic void
1289221420Sdesprocess_extended_hardlink(u_int32_t id)
1290221420Sdes{
1291221420Sdes	char *oldpath, *newpath;
1292221420Sdes	int ret, status;
1293221420Sdes
1294221420Sdes	oldpath = get_string(NULL);
1295221420Sdes	newpath = get_string(NULL);
1296221420Sdes	debug3("request %u: hardlink", id);
1297221420Sdes	logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
1298262566Sdes	ret = link(oldpath, newpath);
1299262566Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1300221420Sdes	send_status(id, status);
1301255767Sdes	free(oldpath);
1302255767Sdes	free(newpath);
1303221420Sdes}
1304221420Sdes
1305221420Sdesstatic void
1306262566Sdesprocess_extended_fsync(u_int32_t id)
130776259Sgreen{
1308262566Sdes	int handle, fd, ret, status = SSH2_FX_OP_UNSUPPORTED;
1309262566Sdes
1310262566Sdes	handle = get_handle();
1311262566Sdes	debug3("request %u: fsync (handle %u)", id, handle);
1312262566Sdes	verbose("fsync \"%s\"", handle_to_name(handle));
1313262566Sdes	if ((fd = handle_to_fd(handle)) < 0)
1314262566Sdes		status = SSH2_FX_NO_SUCH_FILE;
1315262566Sdes	else if (handle_is_ok(handle, HANDLE_FILE)) {
1316262566Sdes		ret = fsync(fd);
1317262566Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1318262566Sdes	}
1319262566Sdes	send_status(id, status);
1320262566Sdes}
1321262566Sdes
1322262566Sdesstatic void
1323262566Sdesprocess_extended(u_int32_t id)
1324262566Sdes{
132576259Sgreen	char *request;
1326262566Sdes	u_int i;
132776259Sgreen
132876259Sgreen	request = get_string(NULL);
1329262566Sdes	for (i = 0; extended_handlers[i].handler != NULL; i++) {
1330262566Sdes		if (strcmp(request, extended_handlers[i].ext_name) == 0) {
1331262566Sdes			if (!request_permitted(&extended_handlers[i]))
1332262566Sdes				send_status(id, SSH2_FX_PERMISSION_DENIED);
1333262566Sdes			else
1334262566Sdes				extended_handlers[i].handler(id);
1335262566Sdes			break;
1336262566Sdes		}
1337262566Sdes	}
1338262566Sdes	if (extended_handlers[i].handler == NULL) {
1339262566Sdes		error("Unknown extended request \"%.100s\"", request);
1340181111Sdes		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
1341262566Sdes	}
1342255767Sdes	free(request);
134376259Sgreen}
134476259Sgreen
134565668Skris/* stolen from ssh-agent */
134665668Skris
134792555Sdesstatic void
134865668Skrisprocess(void)
134965668Skris{
1350262566Sdes	u_int msg_len, buf_len, consumed, type, i;
135176259Sgreen	u_char *cp;
1352262566Sdes	u_int32_t id;
135365668Skris
135498675Sdes	buf_len = buffer_len(&iqueue);
135598675Sdes	if (buf_len < 5)
135665668Skris		return;		/* Incomplete message. */
135792555Sdes	cp = buffer_ptr(&iqueue);
1358162852Sdes	msg_len = get_u32(cp);
1359157016Sdes	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1360162852Sdes		error("bad message from %s local user %s",
1361162852Sdes		    client_addr, pw->pw_name);
1362181111Sdes		sftp_server_cleanup_exit(11);
136365668Skris	}
136498675Sdes	if (buf_len < msg_len + 4)
136565668Skris		return;
136665668Skris	buffer_consume(&iqueue, 4);
136798675Sdes	buf_len -= 4;
136865668Skris	type = buffer_get_char(&iqueue);
1369262566Sdes
137065668Skris	switch (type) {
137176259Sgreen	case SSH2_FXP_INIT:
137265668Skris		process_init();
1373262566Sdes		init_done = 1;
137465668Skris		break;
137576259Sgreen	case SSH2_FXP_EXTENDED:
1376262566Sdes		if (!init_done)
1377262566Sdes			fatal("Received extended request before init");
1378262566Sdes		id = get_int();
1379262566Sdes		process_extended(id);
138076259Sgreen		break;
138165668Skris	default:
1382262566Sdes		if (!init_done)
1383262566Sdes			fatal("Received %u request before init", type);
1384262566Sdes		id = get_int();
1385262566Sdes		for (i = 0; handlers[i].handler != NULL; i++) {
1386262566Sdes			if (type == handlers[i].type) {
1387262566Sdes				if (!request_permitted(&handlers[i])) {
1388262566Sdes					send_status(id,
1389262566Sdes					    SSH2_FX_PERMISSION_DENIED);
1390262566Sdes				} else {
1391262566Sdes					handlers[i].handler(id);
1392262566Sdes				}
1393262566Sdes				break;
1394262566Sdes			}
1395262566Sdes		}
1396262566Sdes		if (handlers[i].handler == NULL)
1397262566Sdes			error("Unknown message %u", type);
139865668Skris	}
139998675Sdes	/* discard the remaining bytes from the current packet */
1400181111Sdes	if (buf_len < buffer_len(&iqueue)) {
1401181111Sdes		error("iqueue grew unexpectedly");
1402181111Sdes		sftp_server_cleanup_exit(255);
1403181111Sdes	}
140498675Sdes	consumed = buf_len - buffer_len(&iqueue);
1405181111Sdes	if (msg_len < consumed) {
1406262566Sdes		error("msg_len %u < consumed %u", msg_len, consumed);
1407181111Sdes		sftp_server_cleanup_exit(255);
1408181111Sdes	}
140998675Sdes	if (msg_len > consumed)
141098675Sdes		buffer_consume(&iqueue, msg_len - consumed);
141165668Skris}
141265668Skris
1413162852Sdes/* Cleanup handler that logs active handles upon normal exit */
1414162852Sdesvoid
1415181111Sdessftp_server_cleanup_exit(int i)
1416162852Sdes{
1417162852Sdes	if (pw != NULL && client_addr != NULL) {
1418162852Sdes		handle_log_exit();
1419162852Sdes		logit("session closed for local user %s from [%s]",
1420162852Sdes		    pw->pw_name, client_addr);
1421162852Sdes	}
1422162852Sdes	_exit(i);
1423162852Sdes}
1424162852Sdes
1425162852Sdesstatic void
1426181111Sdessftp_server_usage(void)
1427162852Sdes{
1428162852Sdes	extern char *__progname;
1429162852Sdes
1430162852Sdes	fprintf(stderr,
1431248619Sdes	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
1432262566Sdes	    "[-l log_level]\n\t[-P blacklisted_requests] "
1433262566Sdes	    "[-p whitelisted_requests] [-u umask]\n"
1434262566Sdes	    "       %s -Q protocol_feature\n",
1435262566Sdes	    __progname, __progname);
1436162852Sdes	exit(1);
1437162852Sdes}
1438162852Sdes
143965668Skrisint
1440181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw)
144165668Skris{
144276259Sgreen	fd_set *rset, *wset;
1443262566Sdes	int i, in, out, max, ch, skipargs = 0, log_stderr = 0;
144476259Sgreen	ssize_t len, olen, set_size;
1445162852Sdes	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1446248619Sdes	char *cp, *homedir = NULL, buf[4*4096];
1447221420Sdes	long mask;
144865668Skris
1449162852Sdes	extern char *optarg;
1450162852Sdes	extern char *__progname;
1451162852Sdes
1452162852Sdes	__progname = ssh_get_progname(argv[0]);
1453162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
145476259Sgreen
1455248619Sdes	pw = pwcopy(user_pw);
1456248619Sdes
1457262566Sdes	while (!skipargs && (ch = getopt(argc, argv,
1458262566Sdes	    "d:f:l:P:p:Q:u:cehR")) != -1) {
1459162852Sdes		switch (ch) {
1460262566Sdes		case 'Q':
1461262566Sdes			if (strcasecmp(optarg, "requests") != 0) {
1462262566Sdes				fprintf(stderr, "Invalid query type\n");
1463262566Sdes				exit(1);
1464262566Sdes			}
1465262566Sdes			for (i = 0; handlers[i].handler != NULL; i++)
1466262566Sdes				printf("%s\n", handlers[i].name);
1467262566Sdes			for (i = 0; extended_handlers[i].handler != NULL; i++)
1468262566Sdes				printf("%s\n", extended_handlers[i].name);
1469262566Sdes			exit(0);
1470262566Sdes			break;
1471204917Sdes		case 'R':
1472204917Sdes			readonly = 1;
1473204917Sdes			break;
1474162852Sdes		case 'c':
1475162852Sdes			/*
1476162852Sdes			 * Ignore all arguments if we are invoked as a
1477162852Sdes			 * shell using "sftp-server -c command"
1478162852Sdes			 */
1479162852Sdes			skipargs = 1;
1480162852Sdes			break;
1481162852Sdes		case 'e':
1482162852Sdes			log_stderr = 1;
1483162852Sdes			break;
1484162852Sdes		case 'l':
1485162852Sdes			log_level = log_level_number(optarg);
1486162852Sdes			if (log_level == SYSLOG_LEVEL_NOT_SET)
1487162852Sdes				error("Invalid log level \"%s\"", optarg);
1488162852Sdes			break;
1489162852Sdes		case 'f':
1490162852Sdes			log_facility = log_facility_number(optarg);
1491181111Sdes			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1492162852Sdes				error("Invalid log facility \"%s\"", optarg);
1493162852Sdes			break;
1494248619Sdes		case 'd':
1495248619Sdes			cp = tilde_expand_filename(optarg, user_pw->pw_uid);
1496248619Sdes			homedir = percent_expand(cp, "d", user_pw->pw_dir,
1497248619Sdes			    "u", user_pw->pw_name, (char *)NULL);
1498248619Sdes			free(cp);
1499248619Sdes			break;
1500262566Sdes		case 'p':
1501262566Sdes			if (request_whitelist != NULL)
1502262566Sdes				fatal("Permitted requests already set");
1503262566Sdes			request_whitelist = xstrdup(optarg);
1504262566Sdes			break;
1505262566Sdes		case 'P':
1506262566Sdes			if (request_blacklist != NULL)
1507262566Sdes				fatal("Refused requests already set");
1508262566Sdes			request_blacklist = xstrdup(optarg);
1509262566Sdes			break;
1510204917Sdes		case 'u':
1511221420Sdes			errno = 0;
1512221420Sdes			mask = strtol(optarg, &cp, 8);
1513221420Sdes			if (mask < 0 || mask > 0777 || *cp != '\0' ||
1514221420Sdes			    cp == optarg || (mask == 0 && errno != 0))
1515221420Sdes				fatal("Invalid umask \"%s\"", optarg);
1516221420Sdes			(void)umask((mode_t)mask);
1517204917Sdes			break;
1518162852Sdes		case 'h':
1519162852Sdes		default:
1520181111Sdes			sftp_server_usage();
1521162852Sdes		}
1522162852Sdes	}
1523162852Sdes
1524162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
1525162852Sdes
1526162852Sdes	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1527162852Sdes		client_addr = xstrdup(cp);
1528181111Sdes		if ((cp = strchr(client_addr, ' ')) == NULL) {
1529181111Sdes			error("Malformed SSH_CONNECTION variable: \"%s\"",
1530162852Sdes			    getenv("SSH_CONNECTION"));
1531181111Sdes			sftp_server_cleanup_exit(255);
1532181111Sdes		}
1533162852Sdes		*cp = '\0';
1534162852Sdes	} else
1535162852Sdes		client_addr = xstrdup("UNKNOWN");
1536162852Sdes
1537162852Sdes	logit("session opened for local user %s from [%s]",
1538162852Sdes	    pw->pw_name, client_addr);
1539162852Sdes
1540204917Sdes	in = STDIN_FILENO;
1541204917Sdes	out = STDOUT_FILENO;
154265668Skris
154398937Sdes#ifdef HAVE_CYGWIN
154498937Sdes	setmode(in, O_BINARY);
154598937Sdes	setmode(out, O_BINARY);
154698937Sdes#endif
154798937Sdes
154865668Skris	max = 0;
154965668Skris	if (in > max)
155065668Skris		max = in;
155165668Skris	if (out > max)
155265668Skris		max = out;
155365668Skris
155465668Skris	buffer_init(&iqueue);
155565668Skris	buffer_init(&oqueue);
155665668Skris
155776259Sgreen	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
155876259Sgreen	rset = (fd_set *)xmalloc(set_size);
155976259Sgreen	wset = (fd_set *)xmalloc(set_size);
156076259Sgreen
1561248619Sdes	if (homedir != NULL) {
1562248619Sdes		if (chdir(homedir) != 0) {
1563248619Sdes			error("chdir to \"%s\" failed: %s", homedir,
1564248619Sdes			    strerror(errno));
1565248619Sdes		}
1566248619Sdes	}
1567248619Sdes
156865668Skris	for (;;) {
156976259Sgreen		memset(rset, 0, set_size);
157076259Sgreen		memset(wset, 0, set_size);
157165668Skris
1572181111Sdes		/*
1573181111Sdes		 * Ensure that we can read a full buffer and handle
1574181111Sdes		 * the worst-case length packet it can generate,
1575181111Sdes		 * otherwise apply backpressure by stopping reads.
1576181111Sdes		 */
1577181111Sdes		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
1578181111Sdes		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1579181111Sdes			FD_SET(in, rset);
1580181111Sdes
158165668Skris		olen = buffer_len(&oqueue);
158265668Skris		if (olen > 0)
158376259Sgreen			FD_SET(out, wset);
158465668Skris
158576259Sgreen		if (select(max+1, rset, wset, NULL, NULL) < 0) {
158665668Skris			if (errno == EINTR)
158765668Skris				continue;
1588162852Sdes			error("select: %s", strerror(errno));
1589181111Sdes			sftp_server_cleanup_exit(2);
159065668Skris		}
159165668Skris
159265668Skris		/* copy stdin to iqueue */
159376259Sgreen		if (FD_ISSET(in, rset)) {
159465668Skris			len = read(in, buf, sizeof buf);
159565668Skris			if (len == 0) {
159665668Skris				debug("read eof");
1597181111Sdes				sftp_server_cleanup_exit(0);
159865668Skris			} else if (len < 0) {
1599162852Sdes				error("read: %s", strerror(errno));
1600181111Sdes				sftp_server_cleanup_exit(1);
160165668Skris			} else {
160265668Skris				buffer_append(&iqueue, buf, len);
160365668Skris			}
160465668Skris		}
160565668Skris		/* send oqueue to stdout */
160676259Sgreen		if (FD_ISSET(out, wset)) {
160765668Skris			len = write(out, buffer_ptr(&oqueue), olen);
160865668Skris			if (len < 0) {
1609162852Sdes				error("write: %s", strerror(errno));
1610181111Sdes				sftp_server_cleanup_exit(1);
161165668Skris			} else {
161265668Skris				buffer_consume(&oqueue, len);
161365668Skris			}
161465668Skris		}
1615181111Sdes
1616181111Sdes		/*
1617181111Sdes		 * Process requests from client if we can fit the results
1618181111Sdes		 * into the output buffer, otherwise stop processing input
1619181111Sdes		 * and let the output queue drain.
1620181111Sdes		 */
1621181111Sdes		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1622181111Sdes			process();
162365668Skris	}
162465668Skris}
1625