sftp-server.c revision 204917
1204917Sdes/* $OpenBSD: sftp-server.c,v 1.91 2010/01/13 01:40:16 djm 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"
49162852Sdes#include "uidswap.h"
5065668Skris
5176259Sgreen#include "sftp.h"
5276259Sgreen#include "sftp-common.h"
5365668Skris
5465668Skris/* helper */
5576259Sgreen#define get_int64()			buffer_get_int64(&iqueue);
5665668Skris#define get_int()			buffer_get_int(&iqueue);
5765668Skris#define get_string(lenp)		buffer_get_string(&iqueue, lenp);
5865668Skris
59162852Sdes/* Our verbosity */
60162852SdesLogLevel log_level = SYSLOG_LEVEL_ERROR;
6198937Sdes
62162852Sdes/* Our client */
63162852Sdesstruct passwd *pw = NULL;
64162852Sdeschar *client_addr = NULL;
65162852Sdes
6665668Skris/* input and output queue */
6765668SkrisBuffer iqueue;
6865668SkrisBuffer oqueue;
6965668Skris
7076259Sgreen/* Version of client */
7176259Sgreenint version;
7276259Sgreen
73204917Sdes/* Disable writes */
74204917Sdesint readonly;
75204917Sdes
76124208Sdes/* portable attributes, etc. */
7765668Skris
7865668Skristypedef struct Stat Stat;
7965668Skris
8076259Sgreenstruct Stat {
8165668Skris	char *name;
8265668Skris	char *long_name;
8365668Skris	Attrib attrib;
8465668Skris};
8565668Skris
8692555Sdesstatic int
8765668Skriserrno_to_portable(int unixerrno)
8865668Skris{
8965668Skris	int ret = 0;
9076259Sgreen
9165668Skris	switch (unixerrno) {
9265668Skris	case 0:
9376259Sgreen		ret = SSH2_FX_OK;
9465668Skris		break;
9565668Skris	case ENOENT:
9665668Skris	case ENOTDIR:
9765668Skris	case EBADF:
9865668Skris	case ELOOP:
9976259Sgreen		ret = SSH2_FX_NO_SUCH_FILE;
10065668Skris		break;
10165668Skris	case EPERM:
10265668Skris	case EACCES:
10365668Skris	case EFAULT:
10476259Sgreen		ret = SSH2_FX_PERMISSION_DENIED;
10565668Skris		break;
10665668Skris	case ENAMETOOLONG:
10765668Skris	case EINVAL:
10876259Sgreen		ret = SSH2_FX_BAD_MESSAGE;
10965668Skris		break;
110181111Sdes	case ENOSYS:
111181111Sdes		ret = SSH2_FX_OP_UNSUPPORTED;
112181111Sdes		break;
11365668Skris	default:
11476259Sgreen		ret = SSH2_FX_FAILURE;
11565668Skris		break;
11665668Skris	}
11765668Skris	return ret;
11865668Skris}
11965668Skris
12092555Sdesstatic int
12165668Skrisflags_from_portable(int pflags)
12265668Skris{
12365668Skris	int flags = 0;
12476259Sgreen
12576259Sgreen	if ((pflags & SSH2_FXF_READ) &&
12676259Sgreen	    (pflags & SSH2_FXF_WRITE)) {
12765668Skris		flags = O_RDWR;
12876259Sgreen	} else if (pflags & SSH2_FXF_READ) {
12965668Skris		flags = O_RDONLY;
13076259Sgreen	} else if (pflags & SSH2_FXF_WRITE) {
13165668Skris		flags = O_WRONLY;
13265668Skris	}
13376259Sgreen	if (pflags & SSH2_FXF_CREAT)
13465668Skris		flags |= O_CREAT;
13576259Sgreen	if (pflags & SSH2_FXF_TRUNC)
13665668Skris		flags |= O_TRUNC;
13776259Sgreen	if (pflags & SSH2_FXF_EXCL)
13865668Skris		flags |= O_EXCL;
13965668Skris	return flags;
14065668Skris}
14165668Skris
142162852Sdesstatic const char *
143162852Sdesstring_from_portable(int pflags)
144162852Sdes{
145162852Sdes	static char ret[128];
146162852Sdes
147162852Sdes	*ret = '\0';
148162852Sdes
149162852Sdes#define PAPPEND(str)	{				\
150162852Sdes		if (*ret != '\0')			\
151162852Sdes			strlcat(ret, ",", sizeof(ret));	\
152162852Sdes		strlcat(ret, str, sizeof(ret));		\
153162852Sdes	}
154162852Sdes
155162852Sdes	if (pflags & SSH2_FXF_READ)
156162852Sdes		PAPPEND("READ")
157162852Sdes	if (pflags & SSH2_FXF_WRITE)
158162852Sdes		PAPPEND("WRITE")
159162852Sdes	if (pflags & SSH2_FXF_CREAT)
160162852Sdes		PAPPEND("CREATE")
161162852Sdes	if (pflags & SSH2_FXF_TRUNC)
162162852Sdes		PAPPEND("TRUNCATE")
163162852Sdes	if (pflags & SSH2_FXF_EXCL)
164162852Sdes		PAPPEND("EXCL")
165162852Sdes
166162852Sdes	return ret;
167162852Sdes}
168162852Sdes
16992555Sdesstatic Attrib *
17065668Skrisget_attrib(void)
17165668Skris{
17265668Skris	return decode_attrib(&iqueue);
17365668Skris}
17465668Skris
17565668Skris/* handle handles */
17665668Skris
17765668Skristypedef struct Handle Handle;
17865668Skrisstruct Handle {
17965668Skris	int use;
18065668Skris	DIR *dirp;
18165668Skris	int fd;
18265668Skris	char *name;
183162852Sdes	u_int64_t bytes_read, bytes_write;
184181111Sdes	int next_unused;
18565668Skris};
18676259Sgreen
18765668Skrisenum {
18865668Skris	HANDLE_UNUSED,
18965668Skris	HANDLE_DIR,
19065668Skris	HANDLE_FILE
19165668Skris};
19276259Sgreen
193181111SdesHandle *handles = NULL;
194181111Sdesu_int num_handles = 0;
195181111Sdesint first_unused_handle = -1;
19665668Skris
197181111Sdesstatic void handle_unused(int i)
19865668Skris{
199181111Sdes	handles[i].use = HANDLE_UNUSED;
200181111Sdes	handles[i].next_unused = first_unused_handle;
201181111Sdes	first_unused_handle = i;
20265668Skris}
20365668Skris
20492555Sdesstatic int
205126274Sdeshandle_new(int use, const char *name, int fd, DIR *dirp)
20665668Skris{
207181111Sdes	int i;
20876259Sgreen
209181111Sdes	if (first_unused_handle == -1) {
210181111Sdes		if (num_handles + 1 <= num_handles)
211181111Sdes			return -1;
212181111Sdes		num_handles++;
213181111Sdes		handles = xrealloc(handles, num_handles, sizeof(Handle));
214181111Sdes		handle_unused(num_handles - 1);
21565668Skris	}
216181111Sdes
217181111Sdes	i = first_unused_handle;
218181111Sdes	first_unused_handle = handles[i].next_unused;
219181111Sdes
220181111Sdes	handles[i].use = use;
221181111Sdes	handles[i].dirp = dirp;
222181111Sdes	handles[i].fd = fd;
223181111Sdes	handles[i].name = xstrdup(name);
224181111Sdes	handles[i].bytes_read = handles[i].bytes_write = 0;
225181111Sdes
226181111Sdes	return i;
22765668Skris}
22865668Skris
22992555Sdesstatic int
23065668Skrishandle_is_ok(int i, int type)
23165668Skris{
232181111Sdes	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
23365668Skris}
23465668Skris
23592555Sdesstatic int
23665668Skrishandle_to_string(int handle, char **stringp, int *hlenp)
23765668Skris{
23865668Skris	if (stringp == NULL || hlenp == NULL)
23965668Skris		return -1;
24076259Sgreen	*stringp = xmalloc(sizeof(int32_t));
241162852Sdes	put_u32(*stringp, handle);
24276259Sgreen	*hlenp = sizeof(int32_t);
24365668Skris	return 0;
24465668Skris}
24565668Skris
24692555Sdesstatic int
247126274Sdeshandle_from_string(const char *handle, u_int hlen)
24865668Skris{
24976259Sgreen	int val;
25076259Sgreen
25176259Sgreen	if (hlen != sizeof(int32_t))
25265668Skris		return -1;
253162852Sdes	val = get_u32(handle);
25465668Skris	if (handle_is_ok(val, HANDLE_FILE) ||
25565668Skris	    handle_is_ok(val, HANDLE_DIR))
25665668Skris		return val;
25765668Skris	return -1;
25865668Skris}
25965668Skris
26092555Sdesstatic char *
26165668Skrishandle_to_name(int handle)
26265668Skris{
26365668Skris	if (handle_is_ok(handle, HANDLE_DIR)||
26465668Skris	    handle_is_ok(handle, HANDLE_FILE))
26565668Skris		return handles[handle].name;
26665668Skris	return NULL;
26765668Skris}
26865668Skris
26992555Sdesstatic DIR *
27065668Skrishandle_to_dir(int handle)
27165668Skris{
27265668Skris	if (handle_is_ok(handle, HANDLE_DIR))
27365668Skris		return handles[handle].dirp;
27465668Skris	return NULL;
27565668Skris}
27665668Skris
27792555Sdesstatic int
27865668Skrishandle_to_fd(int handle)
27965668Skris{
28076259Sgreen	if (handle_is_ok(handle, HANDLE_FILE))
28165668Skris		return handles[handle].fd;
28265668Skris	return -1;
28365668Skris}
28465668Skris
285162852Sdesstatic void
286162852Sdeshandle_update_read(int handle, ssize_t bytes)
287162852Sdes{
288162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
289162852Sdes		handles[handle].bytes_read += bytes;
290162852Sdes}
291162852Sdes
292162852Sdesstatic void
293162852Sdeshandle_update_write(int handle, ssize_t bytes)
294162852Sdes{
295162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
296162852Sdes		handles[handle].bytes_write += bytes;
297162852Sdes}
298162852Sdes
299162852Sdesstatic u_int64_t
300162852Sdeshandle_bytes_read(int handle)
301162852Sdes{
302162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
303162852Sdes		return (handles[handle].bytes_read);
304162852Sdes	return 0;
305162852Sdes}
306162852Sdes
307162852Sdesstatic u_int64_t
308162852Sdeshandle_bytes_write(int handle)
309162852Sdes{
310162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
311162852Sdes		return (handles[handle].bytes_write);
312162852Sdes	return 0;
313162852Sdes}
314162852Sdes
31592555Sdesstatic int
31665668Skrishandle_close(int handle)
31765668Skris{
31865668Skris	int ret = -1;
31976259Sgreen
32065668Skris	if (handle_is_ok(handle, HANDLE_FILE)) {
32165668Skris		ret = close(handles[handle].fd);
322113908Sdes		xfree(handles[handle].name);
323181111Sdes		handle_unused(handle);
32465668Skris	} else if (handle_is_ok(handle, HANDLE_DIR)) {
32565668Skris		ret = closedir(handles[handle].dirp);
326113908Sdes		xfree(handles[handle].name);
327181111Sdes		handle_unused(handle);
32865668Skris	} else {
32965668Skris		errno = ENOENT;
33065668Skris	}
33165668Skris	return ret;
33265668Skris}
33365668Skris
334162852Sdesstatic void
335162852Sdeshandle_log_close(int handle, char *emsg)
336162852Sdes{
337162852Sdes	if (handle_is_ok(handle, HANDLE_FILE)) {
338162852Sdes		logit("%s%sclose \"%s\" bytes read %llu written %llu",
339162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
340162852Sdes		    handle_to_name(handle),
341181111Sdes		    (unsigned long long)handle_bytes_read(handle),
342181111Sdes		    (unsigned long long)handle_bytes_write(handle));
343162852Sdes	} else {
344162852Sdes		logit("%s%sclosedir \"%s\"",
345162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
346162852Sdes		    handle_to_name(handle));
347162852Sdes	}
348162852Sdes}
349162852Sdes
350162852Sdesstatic void
351162852Sdeshandle_log_exit(void)
352162852Sdes{
353162852Sdes	u_int i;
354162852Sdes
355181111Sdes	for (i = 0; i < num_handles; i++)
356162852Sdes		if (handles[i].use != HANDLE_UNUSED)
357162852Sdes			handle_log_close(i, "forced");
358162852Sdes}
359162852Sdes
36092555Sdesstatic int
36165668Skrisget_handle(void)
36265668Skris{
36365668Skris	char *handle;
36476259Sgreen	int val = -1;
36565668Skris	u_int hlen;
36676259Sgreen
36765668Skris	handle = get_string(&hlen);
36876259Sgreen	if (hlen < 256)
36976259Sgreen		val = handle_from_string(handle, hlen);
37065668Skris	xfree(handle);
37165668Skris	return val;
37265668Skris}
37365668Skris
37465668Skris/* send replies */
37565668Skris
37692555Sdesstatic void
37765668Skrissend_msg(Buffer *m)
37865668Skris{
37965668Skris	int mlen = buffer_len(m);
38076259Sgreen
38165668Skris	buffer_put_int(&oqueue, mlen);
38265668Skris	buffer_append(&oqueue, buffer_ptr(m), mlen);
38365668Skris	buffer_consume(m, mlen);
38465668Skris}
38565668Skris
386162852Sdesstatic const char *
387162852Sdesstatus_to_message(u_int32_t status)
38865668Skris{
38976259Sgreen	const char *status_messages[] = {
39076259Sgreen		"Success",			/* SSH_FX_OK */
39176259Sgreen		"End of file",			/* SSH_FX_EOF */
39276259Sgreen		"No such file",			/* SSH_FX_NO_SUCH_FILE */
39376259Sgreen		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
39476259Sgreen		"Failure",			/* SSH_FX_FAILURE */
39576259Sgreen		"Bad message",			/* SSH_FX_BAD_MESSAGE */
39676259Sgreen		"No connection",		/* SSH_FX_NO_CONNECTION */
39776259Sgreen		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
39876259Sgreen		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
39976259Sgreen		"Unknown error"			/* Others */
40076259Sgreen	};
401162852Sdes	return (status_messages[MIN(status,SSH2_FX_MAX)]);
402162852Sdes}
40376259Sgreen
404162852Sdesstatic void
405162852Sdessend_status(u_int32_t id, u_int32_t status)
406162852Sdes{
407162852Sdes	Buffer msg;
408162852Sdes
409162852Sdes	debug3("request %u: sent status %u", id, status);
410162852Sdes	if (log_level > SYSLOG_LEVEL_VERBOSE ||
411162852Sdes	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
412162852Sdes		logit("sent status %s", status_to_message(status));
41365668Skris	buffer_init(&msg);
41476259Sgreen	buffer_put_char(&msg, SSH2_FXP_STATUS);
41565668Skris	buffer_put_int(&msg, id);
416137015Sdes	buffer_put_int(&msg, status);
41776259Sgreen	if (version >= 3) {
418162852Sdes		buffer_put_cstring(&msg, status_to_message(status));
41976259Sgreen		buffer_put_cstring(&msg, "");
42076259Sgreen	}
42165668Skris	send_msg(&msg);
42265668Skris	buffer_free(&msg);
42365668Skris}
42492555Sdesstatic void
425126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
42665668Skris{
42765668Skris	Buffer msg;
42876259Sgreen
42965668Skris	buffer_init(&msg);
43065668Skris	buffer_put_char(&msg, type);
43165668Skris	buffer_put_int(&msg, id);
43265668Skris	buffer_put_string(&msg, data, dlen);
43365668Skris	send_msg(&msg);
43465668Skris	buffer_free(&msg);
43565668Skris}
43665668Skris
43792555Sdesstatic void
438126274Sdessend_data(u_int32_t id, const char *data, int dlen)
43965668Skris{
440162852Sdes	debug("request %u: sent data len %d", id, dlen);
44176259Sgreen	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
44265668Skris}
44365668Skris
44492555Sdesstatic void
44565668Skrissend_handle(u_int32_t id, int handle)
44665668Skris{
44765668Skris	char *string;
44865668Skris	int hlen;
44976259Sgreen
45065668Skris	handle_to_string(handle, &string, &hlen);
451162852Sdes	debug("request %u: sent handle handle %d", id, handle);
45276259Sgreen	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
45365668Skris	xfree(string);
45465668Skris}
45565668Skris
45692555Sdesstatic void
457126274Sdessend_names(u_int32_t id, int count, const Stat *stats)
45865668Skris{
45965668Skris	Buffer msg;
46065668Skris	int i;
46176259Sgreen
46265668Skris	buffer_init(&msg);
46376259Sgreen	buffer_put_char(&msg, SSH2_FXP_NAME);
46465668Skris	buffer_put_int(&msg, id);
46565668Skris	buffer_put_int(&msg, count);
466162852Sdes	debug("request %u: sent names count %d", id, count);
46765668Skris	for (i = 0; i < count; i++) {
46865668Skris		buffer_put_cstring(&msg, stats[i].name);
46965668Skris		buffer_put_cstring(&msg, stats[i].long_name);
47065668Skris		encode_attrib(&msg, &stats[i].attrib);
47165668Skris	}
47265668Skris	send_msg(&msg);
47365668Skris	buffer_free(&msg);
47465668Skris}
47565668Skris
47692555Sdesstatic void
477126274Sdessend_attrib(u_int32_t id, const Attrib *a)
47865668Skris{
47965668Skris	Buffer msg;
48076259Sgreen
481162852Sdes	debug("request %u: sent attrib have 0x%x", id, a->flags);
48265668Skris	buffer_init(&msg);
48376259Sgreen	buffer_put_char(&msg, SSH2_FXP_ATTRS);
48465668Skris	buffer_put_int(&msg, id);
48565668Skris	encode_attrib(&msg, a);
48665668Skris	send_msg(&msg);
48765668Skris	buffer_free(&msg);
48865668Skris}
48965668Skris
490181111Sdesstatic void
491181111Sdessend_statvfs(u_int32_t id, struct statvfs *st)
492181111Sdes{
493181111Sdes	Buffer msg;
494181111Sdes	u_int64_t flag;
495181111Sdes
496181111Sdes	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
497181111Sdes	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
498181111Sdes
499181111Sdes	buffer_init(&msg);
500181111Sdes	buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY);
501181111Sdes	buffer_put_int(&msg, id);
502181111Sdes	buffer_put_int64(&msg, st->f_bsize);
503181111Sdes	buffer_put_int64(&msg, st->f_frsize);
504181111Sdes	buffer_put_int64(&msg, st->f_blocks);
505181111Sdes	buffer_put_int64(&msg, st->f_bfree);
506181111Sdes	buffer_put_int64(&msg, st->f_bavail);
507181111Sdes	buffer_put_int64(&msg, st->f_files);
508181111Sdes	buffer_put_int64(&msg, st->f_ffree);
509181111Sdes	buffer_put_int64(&msg, st->f_favail);
510181111Sdes	buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid));
511181111Sdes	buffer_put_int64(&msg, flag);
512181111Sdes	buffer_put_int64(&msg, st->f_namemax);
513181111Sdes	send_msg(&msg);
514181111Sdes	buffer_free(&msg);
515181111Sdes}
516181111Sdes
51765668Skris/* parse incoming */
51865668Skris
51992555Sdesstatic void
52065668Skrisprocess_init(void)
52165668Skris{
52265668Skris	Buffer msg;
52365668Skris
52498675Sdes	version = get_int();
525162852Sdes	verbose("received client version %d", version);
52665668Skris	buffer_init(&msg);
52776259Sgreen	buffer_put_char(&msg, SSH2_FXP_VERSION);
52876259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
529181111Sdes	/* POSIX rename extension */
530181111Sdes	buffer_put_cstring(&msg, "posix-rename@openssh.com");
531181111Sdes	buffer_put_cstring(&msg, "1"); /* version */
532181111Sdes	/* statvfs extension */
533181111Sdes	buffer_put_cstring(&msg, "statvfs@openssh.com");
534181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
535181111Sdes	/* fstatvfs extension */
536181111Sdes	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
537181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
53865668Skris	send_msg(&msg);
53965668Skris	buffer_free(&msg);
54065668Skris}
54165668Skris
54292555Sdesstatic void
54365668Skrisprocess_open(void)
54465668Skris{
54565668Skris	u_int32_t id, pflags;
54665668Skris	Attrib *a;
54765668Skris	char *name;
54876259Sgreen	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
54965668Skris
55065668Skris	id = get_int();
55165668Skris	name = get_string(NULL);
55276259Sgreen	pflags = get_int();		/* portable flags */
553162852Sdes	debug3("request %u: open flags %d", id, pflags);
55465668Skris	a = get_attrib();
55565668Skris	flags = flags_from_portable(pflags);
55676259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
557162852Sdes	logit("open \"%s\" flags %s mode 0%o",
558162852Sdes	    name, string_from_portable(pflags), mode);
559204917Sdes	if (readonly &&
560204917Sdes	    ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR))
561204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
562204917Sdes	else {
563204917Sdes		fd = open(name, flags, mode);
564204917Sdes		if (fd < 0) {
565204917Sdes			status = errno_to_portable(errno);
56665668Skris		} else {
567204917Sdes			handle = handle_new(HANDLE_FILE, name, fd, NULL);
568204917Sdes			if (handle < 0) {
569204917Sdes				close(fd);
570204917Sdes			} else {
571204917Sdes				send_handle(id, handle);
572204917Sdes				status = SSH2_FX_OK;
573204917Sdes			}
57465668Skris		}
57565668Skris	}
57676259Sgreen	if (status != SSH2_FX_OK)
57765668Skris		send_status(id, status);
57865668Skris	xfree(name);
57965668Skris}
58065668Skris
58192555Sdesstatic void
58265668Skrisprocess_close(void)
58365668Skris{
58465668Skris	u_int32_t id;
58576259Sgreen	int handle, ret, status = SSH2_FX_FAILURE;
58665668Skris
58765668Skris	id = get_int();
58865668Skris	handle = get_handle();
589162852Sdes	debug3("request %u: close handle %u", id, handle);
590162852Sdes	handle_log_close(handle, NULL);
59165668Skris	ret = handle_close(handle);
59276259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
59365668Skris	send_status(id, status);
59465668Skris}
59565668Skris
59692555Sdesstatic void
59765668Skrisprocess_read(void)
59865668Skris{
59965668Skris	char buf[64*1024];
60076259Sgreen	u_int32_t id, len;
60176259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
60265668Skris	u_int64_t off;
60365668Skris
60465668Skris	id = get_int();
60565668Skris	handle = get_handle();
60676259Sgreen	off = get_int64();
60765668Skris	len = get_int();
60865668Skris
609162852Sdes	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
610162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
61165668Skris	if (len > sizeof buf) {
61265668Skris		len = sizeof buf;
613162852Sdes		debug2("read change len %d", len);
61465668Skris	}
61565668Skris	fd = handle_to_fd(handle);
61665668Skris	if (fd >= 0) {
61765668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
61865668Skris			error("process_read: seek failed");
61965668Skris			status = errno_to_portable(errno);
62065668Skris		} else {
62165668Skris			ret = read(fd, buf, len);
62265668Skris			if (ret < 0) {
62365668Skris				status = errno_to_portable(errno);
62465668Skris			} else if (ret == 0) {
62576259Sgreen				status = SSH2_FX_EOF;
62665668Skris			} else {
62765668Skris				send_data(id, buf, ret);
62876259Sgreen				status = SSH2_FX_OK;
629162852Sdes				handle_update_read(handle, ret);
63065668Skris			}
63165668Skris		}
63265668Skris	}
63376259Sgreen	if (status != SSH2_FX_OK)
63465668Skris		send_status(id, status);
63565668Skris}
63665668Skris
63792555Sdesstatic void
63865668Skrisprocess_write(void)
63965668Skris{
64076259Sgreen	u_int32_t id;
64165668Skris	u_int64_t off;
64265668Skris	u_int len;
643204917Sdes	int handle, fd, ret, status;
64465668Skris	char *data;
64565668Skris
64665668Skris	id = get_int();
64765668Skris	handle = get_handle();
64876259Sgreen	off = get_int64();
64965668Skris	data = get_string(&len);
65065668Skris
651162852Sdes	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
652162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
65365668Skris	fd = handle_to_fd(handle);
654204917Sdes
655204917Sdes	if (fd < 0)
656204917Sdes		status = SSH2_FX_FAILURE;
657204917Sdes	else if (readonly)
658204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
659204917Sdes	else {
66065668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
66165668Skris			status = errno_to_portable(errno);
66265668Skris			error("process_write: seek failed");
66365668Skris		} else {
66465668Skris/* XXX ATOMICIO ? */
66565668Skris			ret = write(fd, data, len);
666149749Sdes			if (ret < 0) {
66765668Skris				error("process_write: write failed");
66865668Skris				status = errno_to_portable(errno);
669149749Sdes			} else if ((size_t)ret == len) {
67076259Sgreen				status = SSH2_FX_OK;
671162852Sdes				handle_update_write(handle, ret);
67265668Skris			} else {
673162852Sdes				debug2("nothing at all written");
674204917Sdes				status = SSH2_FX_FAILURE;
67565668Skris			}
67665668Skris		}
67765668Skris	}
67865668Skris	send_status(id, status);
67965668Skris	xfree(data);
68065668Skris}
68165668Skris
68292555Sdesstatic void
68365668Skrisprocess_do_stat(int do_lstat)
68465668Skris{
68576259Sgreen	Attrib a;
68665668Skris	struct stat st;
68765668Skris	u_int32_t id;
68865668Skris	char *name;
68976259Sgreen	int ret, status = SSH2_FX_FAILURE;
69065668Skris
69165668Skris	id = get_int();
69265668Skris	name = get_string(NULL);
693162852Sdes	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
694162852Sdes	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
69565668Skris	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
69665668Skris	if (ret < 0) {
69765668Skris		status = errno_to_portable(errno);
69865668Skris	} else {
69976259Sgreen		stat_to_attrib(&st, &a);
70076259Sgreen		send_attrib(id, &a);
70176259Sgreen		status = SSH2_FX_OK;
70265668Skris	}
70376259Sgreen	if (status != SSH2_FX_OK)
70465668Skris		send_status(id, status);
70565668Skris	xfree(name);
70665668Skris}
70765668Skris
70892555Sdesstatic void
70965668Skrisprocess_stat(void)
71065668Skris{
71165668Skris	process_do_stat(0);
71265668Skris}
71365668Skris
71492555Sdesstatic void
71565668Skrisprocess_lstat(void)
71665668Skris{
71765668Skris	process_do_stat(1);
71865668Skris}
71965668Skris
72092555Sdesstatic void
72165668Skrisprocess_fstat(void)
72265668Skris{
72376259Sgreen	Attrib a;
72465668Skris	struct stat st;
72565668Skris	u_int32_t id;
72676259Sgreen	int fd, ret, handle, status = SSH2_FX_FAILURE;
72765668Skris
72865668Skris	id = get_int();
72965668Skris	handle = get_handle();
730162852Sdes	debug("request %u: fstat \"%s\" (handle %u)",
731162852Sdes	    id, handle_to_name(handle), handle);
73265668Skris	fd = handle_to_fd(handle);
733181111Sdes	if (fd >= 0) {
73465668Skris		ret = fstat(fd, &st);
73565668Skris		if (ret < 0) {
73665668Skris			status = errno_to_portable(errno);
73765668Skris		} else {
73876259Sgreen			stat_to_attrib(&st, &a);
73976259Sgreen			send_attrib(id, &a);
74076259Sgreen			status = SSH2_FX_OK;
74165668Skris		}
74265668Skris	}
74376259Sgreen	if (status != SSH2_FX_OK)
74465668Skris		send_status(id, status);
74565668Skris}
74665668Skris
74792555Sdesstatic struct timeval *
748126274Sdesattrib_to_tv(const Attrib *a)
74965668Skris{
75065668Skris	static struct timeval tv[2];
75176259Sgreen
75265668Skris	tv[0].tv_sec = a->atime;
75365668Skris	tv[0].tv_usec = 0;
75465668Skris	tv[1].tv_sec = a->mtime;
75565668Skris	tv[1].tv_usec = 0;
75665668Skris	return tv;
75765668Skris}
75865668Skris
75992555Sdesstatic void
76065668Skrisprocess_setstat(void)
76165668Skris{
76265668Skris	Attrib *a;
76365668Skris	u_int32_t id;
76465668Skris	char *name;
76599060Sdes	int status = SSH2_FX_OK, ret;
76665668Skris
76765668Skris	id = get_int();
76865668Skris	name = get_string(NULL);
76965668Skris	a = get_attrib();
770162852Sdes	debug("request %u: setstat name \"%s\"", id, name);
771204917Sdes	if (readonly) {
772204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
773204917Sdes		a->flags = 0;
774204917Sdes	}
77592555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
776181111Sdes		logit("set \"%s\" size %llu",
777181111Sdes		    name, (unsigned long long)a->size);
77892555Sdes		ret = truncate(name, a->size);
77992555Sdes		if (ret == -1)
78092555Sdes			status = errno_to_portable(errno);
78192555Sdes	}
78276259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
783162852Sdes		logit("set \"%s\" mode %04o", name, a->perm);
784181111Sdes		ret = chmod(name, a->perm & 07777);
78565668Skris		if (ret == -1)
78665668Skris			status = errno_to_portable(errno);
78765668Skris	}
78876259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
789162852Sdes		char buf[64];
790162852Sdes		time_t t = a->mtime;
791162852Sdes
792162852Sdes		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
793162852Sdes		    localtime(&t));
794162852Sdes		logit("set \"%s\" modtime %s", name, buf);
79565668Skris		ret = utimes(name, attrib_to_tv(a));
79665668Skris		if (ret == -1)
79765668Skris			status = errno_to_portable(errno);
79865668Skris	}
79976259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
800162852Sdes		logit("set \"%s\" owner %lu group %lu", name,
801162852Sdes		    (u_long)a->uid, (u_long)a->gid);
80276259Sgreen		ret = chown(name, a->uid, a->gid);
80376259Sgreen		if (ret == -1)
80476259Sgreen			status = errno_to_portable(errno);
80576259Sgreen	}
80665668Skris	send_status(id, status);
80765668Skris	xfree(name);
80865668Skris}
80965668Skris
81092555Sdesstatic void
81165668Skrisprocess_fsetstat(void)
81265668Skris{
81365668Skris	Attrib *a;
81465668Skris	u_int32_t id;
81565668Skris	int handle, fd, ret;
81676259Sgreen	int status = SSH2_FX_OK;
81765668Skris
81865668Skris	id = get_int();
81965668Skris	handle = get_handle();
82065668Skris	a = get_attrib();
821162852Sdes	debug("request %u: fsetstat handle %d", id, handle);
82265668Skris	fd = handle_to_fd(handle);
823204917Sdes	if (fd < 0)
82476259Sgreen		status = SSH2_FX_FAILURE;
825204917Sdes	else if (readonly)
826204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
827204917Sdes	else {
828162852Sdes		char *name = handle_to_name(handle);
829162852Sdes
83092555Sdes		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
831181111Sdes			logit("set \"%s\" size %llu",
832181111Sdes			    name, (unsigned long long)a->size);
83392555Sdes			ret = ftruncate(fd, a->size);
83492555Sdes			if (ret == -1)
83592555Sdes				status = errno_to_portable(errno);
83692555Sdes		}
83776259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
838162852Sdes			logit("set \"%s\" mode %04o", name, a->perm);
83998937Sdes#ifdef HAVE_FCHMOD
840181111Sdes			ret = fchmod(fd, a->perm & 07777);
84198937Sdes#else
842181111Sdes			ret = chmod(name, a->perm & 07777);
84398937Sdes#endif
84465668Skris			if (ret == -1)
84565668Skris				status = errno_to_portable(errno);
84665668Skris		}
84776259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
848162852Sdes			char buf[64];
849162852Sdes			time_t t = a->mtime;
850162852Sdes
851162852Sdes			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
852162852Sdes			    localtime(&t));
853162852Sdes			logit("set \"%s\" modtime %s", name, buf);
85498937Sdes#ifdef HAVE_FUTIMES
85565668Skris			ret = futimes(fd, attrib_to_tv(a));
85698937Sdes#else
85798937Sdes			ret = utimes(name, attrib_to_tv(a));
85898937Sdes#endif
85965668Skris			if (ret == -1)
86065668Skris				status = errno_to_portable(errno);
86165668Skris		}
86276259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
863162852Sdes			logit("set \"%s\" owner %lu group %lu", name,
864162852Sdes			    (u_long)a->uid, (u_long)a->gid);
86598937Sdes#ifdef HAVE_FCHOWN
86676259Sgreen			ret = fchown(fd, a->uid, a->gid);
86798937Sdes#else
86898937Sdes			ret = chown(name, a->uid, a->gid);
86998937Sdes#endif
87076259Sgreen			if (ret == -1)
87176259Sgreen				status = errno_to_portable(errno);
87276259Sgreen		}
87365668Skris	}
87465668Skris	send_status(id, status);
87565668Skris}
87665668Skris
87792555Sdesstatic void
87865668Skrisprocess_opendir(void)
87965668Skris{
88065668Skris	DIR *dirp = NULL;
88165668Skris	char *path;
88276259Sgreen	int handle, status = SSH2_FX_FAILURE;
88365668Skris	u_int32_t id;
88465668Skris
88565668Skris	id = get_int();
88665668Skris	path = get_string(NULL);
887162852Sdes	debug3("request %u: opendir", id);
888162852Sdes	logit("opendir \"%s\"", path);
88976259Sgreen	dirp = opendir(path);
89065668Skris	if (dirp == NULL) {
89165668Skris		status = errno_to_portable(errno);
89265668Skris	} else {
893113908Sdes		handle = handle_new(HANDLE_DIR, path, 0, dirp);
89465668Skris		if (handle < 0) {
89565668Skris			closedir(dirp);
89665668Skris		} else {
89765668Skris			send_handle(id, handle);
89876259Sgreen			status = SSH2_FX_OK;
89965668Skris		}
90076259Sgreen
90165668Skris	}
90276259Sgreen	if (status != SSH2_FX_OK)
90365668Skris		send_status(id, status);
90465668Skris	xfree(path);
90565668Skris}
90665668Skris
90792555Sdesstatic void
90865668Skrisprocess_readdir(void)
90965668Skris{
91065668Skris	DIR *dirp;
91165668Skris	struct dirent *dp;
91265668Skris	char *path;
91365668Skris	int handle;
91465668Skris	u_int32_t id;
91565668Skris
91665668Skris	id = get_int();
91765668Skris	handle = get_handle();
918162852Sdes	debug("request %u: readdir \"%s\" (handle %d)", id,
919162852Sdes	    handle_to_name(handle), handle);
92065668Skris	dirp = handle_to_dir(handle);
92165668Skris	path = handle_to_name(handle);
92265668Skris	if (dirp == NULL || path == NULL) {
92376259Sgreen		send_status(id, SSH2_FX_FAILURE);
92465668Skris	} else {
92565668Skris		struct stat st;
926162852Sdes		char pathname[MAXPATHLEN];
92765668Skris		Stat *stats;
92865668Skris		int nstats = 10, count = 0, i;
92999060Sdes
930162852Sdes		stats = xcalloc(nstats, sizeof(Stat));
93165668Skris		while ((dp = readdir(dirp)) != NULL) {
93265668Skris			if (count >= nstats) {
93365668Skris				nstats *= 2;
934162852Sdes				stats = xrealloc(stats, nstats, sizeof(Stat));
93565668Skris			}
93665668Skris/* XXX OVERFLOW ? */
93792555Sdes			snprintf(pathname, sizeof pathname, "%s%s%s", path,
93892555Sdes			    strcmp(path, "/") ? "/" : "", dp->d_name);
93965668Skris			if (lstat(pathname, &st) < 0)
94065668Skris				continue;
94176259Sgreen			stat_to_attrib(&st, &(stats[count].attrib));
94265668Skris			stats[count].name = xstrdup(dp->d_name);
943204917Sdes			stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
94465668Skris			count++;
94565668Skris			/* send up to 100 entries in one message */
94676259Sgreen			/* XXX check packet size instead */
94765668Skris			if (count == 100)
94865668Skris				break;
94965668Skris		}
95076259Sgreen		if (count > 0) {
95176259Sgreen			send_names(id, count, stats);
95292555Sdes			for (i = 0; i < count; i++) {
95376259Sgreen				xfree(stats[i].name);
95476259Sgreen				xfree(stats[i].long_name);
95576259Sgreen			}
95676259Sgreen		} else {
95776259Sgreen			send_status(id, SSH2_FX_EOF);
95865668Skris		}
95965668Skris		xfree(stats);
96065668Skris	}
96165668Skris}
96265668Skris
96392555Sdesstatic void
96465668Skrisprocess_remove(void)
96565668Skris{
96665668Skris	char *name;
96765668Skris	u_int32_t id;
96876259Sgreen	int status = SSH2_FX_FAILURE;
96965668Skris	int ret;
97065668Skris
97165668Skris	id = get_int();
97265668Skris	name = get_string(NULL);
973162852Sdes	debug3("request %u: remove", id);
974162852Sdes	logit("remove name \"%s\"", name);
975204917Sdes	if (readonly)
976204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
977204917Sdes	else {
978204917Sdes		ret = unlink(name);
979204917Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
980204917Sdes	}
98165668Skris	send_status(id, status);
98265668Skris	xfree(name);
98365668Skris}
98465668Skris
98592555Sdesstatic void
98665668Skrisprocess_mkdir(void)
98765668Skris{
98865668Skris	Attrib *a;
98965668Skris	u_int32_t id;
99065668Skris	char *name;
99176259Sgreen	int ret, mode, status = SSH2_FX_FAILURE;
99265668Skris
99365668Skris	id = get_int();
99465668Skris	name = get_string(NULL);
99565668Skris	a = get_attrib();
99676259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
997181111Sdes	    a->perm & 07777 : 0777;
998162852Sdes	debug3("request %u: mkdir", id);
999162852Sdes	logit("mkdir name \"%s\" mode 0%o", name, mode);
1000204917Sdes	if (readonly)
1001204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
1002204917Sdes	else {
1003204917Sdes		ret = mkdir(name, mode);
1004204917Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1005204917Sdes	}
100665668Skris	send_status(id, status);
100765668Skris	xfree(name);
100865668Skris}
100965668Skris
101092555Sdesstatic void
101165668Skrisprocess_rmdir(void)
101265668Skris{
101365668Skris	u_int32_t id;
101465668Skris	char *name;
101565668Skris	int ret, status;
101665668Skris
101765668Skris	id = get_int();
101865668Skris	name = get_string(NULL);
1019162852Sdes	debug3("request %u: rmdir", id);
1020162852Sdes	logit("rmdir name \"%s\"", name);
1021204917Sdes	if (readonly)
1022204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
1023204917Sdes	else {
1024204917Sdes		ret = rmdir(name);
1025204917Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1026204917Sdes	}
102765668Skris	send_status(id, status);
102865668Skris	xfree(name);
102965668Skris}
103065668Skris
103192555Sdesstatic void
103265668Skrisprocess_realpath(void)
103365668Skris{
103465668Skris	char resolvedname[MAXPATHLEN];
103565668Skris	u_int32_t id;
103665668Skris	char *path;
103765668Skris
103865668Skris	id = get_int();
103965668Skris	path = get_string(NULL);
104076259Sgreen	if (path[0] == '\0') {
104176259Sgreen		xfree(path);
104276259Sgreen		path = xstrdup(".");
104376259Sgreen	}
1044162852Sdes	debug3("request %u: realpath", id);
1045162852Sdes	verbose("realpath \"%s\"", path);
104665668Skris	if (realpath(path, resolvedname) == NULL) {
104765668Skris		send_status(id, errno_to_portable(errno));
104865668Skris	} else {
104965668Skris		Stat s;
105065668Skris		attrib_clear(&s.attrib);
105165668Skris		s.name = s.long_name = resolvedname;
105265668Skris		send_names(id, 1, &s);
105365668Skris	}
105465668Skris	xfree(path);
105565668Skris}
105665668Skris
105792555Sdesstatic void
105865668Skrisprocess_rename(void)
105965668Skris{
106065668Skris	u_int32_t id;
106165668Skris	char *oldpath, *newpath;
1062113908Sdes	int status;
1063113908Sdes	struct stat sb;
106465668Skris
106565668Skris	id = get_int();
106665668Skris	oldpath = get_string(NULL);
106765668Skris	newpath = get_string(NULL);
1068162852Sdes	debug3("request %u: rename", id);
1069162852Sdes	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1070113908Sdes	status = SSH2_FX_FAILURE;
1071204917Sdes	if (readonly)
1072204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
1073204917Sdes	else if (lstat(oldpath, &sb) == -1)
1074113908Sdes		status = errno_to_portable(errno);
1075113908Sdes	else if (S_ISREG(sb.st_mode)) {
1076113908Sdes		/* Race-free rename of regular files */
1077137015Sdes		if (link(oldpath, newpath) == -1) {
1078197679Sdes			if (errno == EOPNOTSUPP || errno == ENOSYS
1079181111Sdes#ifdef EXDEV
1080181111Sdes			    || errno == EXDEV
1081181111Sdes#endif
1082137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO
1083137015Sdes			    || errno == LINK_OPNOTSUPP_ERRNO
1084137015Sdes#endif
1085137015Sdes			    ) {
1086137015Sdes				struct stat st;
1087137015Sdes
1088137015Sdes				/*
1089137015Sdes				 * fs doesn't support links, so fall back to
1090137015Sdes				 * stat+rename.  This is racy.
1091137015Sdes				 */
1092137015Sdes				if (stat(newpath, &st) == -1) {
1093137015Sdes					if (rename(oldpath, newpath) == -1)
1094137015Sdes						status =
1095137015Sdes						    errno_to_portable(errno);
1096137015Sdes					else
1097137015Sdes						status = SSH2_FX_OK;
1098137015Sdes				}
1099137015Sdes			} else {
1100137015Sdes				status = errno_to_portable(errno);
1101137015Sdes			}
1102137015Sdes		} else if (unlink(oldpath) == -1) {
1103113908Sdes			status = errno_to_portable(errno);
1104113908Sdes			/* clean spare link */
1105113908Sdes			unlink(newpath);
1106113908Sdes		} else
1107113908Sdes			status = SSH2_FX_OK;
1108113908Sdes	} else if (stat(newpath, &sb) == -1) {
1109113908Sdes		if (rename(oldpath, newpath) == -1)
1110113908Sdes			status = errno_to_portable(errno);
1111113908Sdes		else
1112113908Sdes			status = SSH2_FX_OK;
111376259Sgreen	}
111465668Skris	send_status(id, status);
111565668Skris	xfree(oldpath);
111665668Skris	xfree(newpath);
111765668Skris}
111865668Skris
111992555Sdesstatic void
112076259Sgreenprocess_readlink(void)
112176259Sgreen{
112276259Sgreen	u_int32_t id;
112392555Sdes	int len;
1124137015Sdes	char buf[MAXPATHLEN];
112576259Sgreen	char *path;
112665668Skris
112776259Sgreen	id = get_int();
112876259Sgreen	path = get_string(NULL);
1129162852Sdes	debug3("request %u: readlink", id);
1130162852Sdes	verbose("readlink \"%s\"", path);
1131137015Sdes	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
113276259Sgreen		send_status(id, errno_to_portable(errno));
113376259Sgreen	else {
113476259Sgreen		Stat s;
113592555Sdes
1136137015Sdes		buf[len] = '\0';
113776259Sgreen		attrib_clear(&s.attrib);
1138137015Sdes		s.name = s.long_name = buf;
113976259Sgreen		send_names(id, 1, &s);
114076259Sgreen	}
114176259Sgreen	xfree(path);
114276259Sgreen}
114376259Sgreen
114492555Sdesstatic void
114576259Sgreenprocess_symlink(void)
114676259Sgreen{
114776259Sgreen	u_int32_t id;
114876259Sgreen	char *oldpath, *newpath;
1149113908Sdes	int ret, status;
115076259Sgreen
115176259Sgreen	id = get_int();
115276259Sgreen	oldpath = get_string(NULL);
115376259Sgreen	newpath = get_string(NULL);
1154162852Sdes	debug3("request %u: symlink", id);
1155162852Sdes	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1156113908Sdes	/* this will fail if 'newpath' exists */
1157204917Sdes	if (readonly)
1158204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
1159204917Sdes	else {
1160204917Sdes		ret = symlink(oldpath, newpath);
1161204917Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1162204917Sdes	}
116376259Sgreen	send_status(id, status);
116476259Sgreen	xfree(oldpath);
116576259Sgreen	xfree(newpath);
116676259Sgreen}
116776259Sgreen
116892555Sdesstatic void
1169181111Sdesprocess_extended_posix_rename(u_int32_t id)
1170181111Sdes{
1171181111Sdes	char *oldpath, *newpath;
1172204917Sdes	int ret, status;
1173181111Sdes
1174181111Sdes	oldpath = get_string(NULL);
1175181111Sdes	newpath = get_string(NULL);
1176181111Sdes	debug3("request %u: posix-rename", id);
1177181111Sdes	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1178204917Sdes	if (readonly)
1179204917Sdes		status = SSH2_FX_PERMISSION_DENIED;
1180204917Sdes	else {
1181204917Sdes		ret = rename(oldpath, newpath);
1182204917Sdes		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1183204917Sdes	}
1184204917Sdes	send_status(id, status);
1185181111Sdes	xfree(oldpath);
1186181111Sdes	xfree(newpath);
1187181111Sdes}
1188181111Sdes
1189181111Sdesstatic void
1190181111Sdesprocess_extended_statvfs(u_int32_t id)
1191181111Sdes{
1192181111Sdes	char *path;
1193181111Sdes	struct statvfs st;
1194181111Sdes
1195181111Sdes	path = get_string(NULL);
1196181111Sdes	debug3("request %u: statfs", id);
1197181111Sdes	logit("statfs \"%s\"", path);
1198181111Sdes
1199181111Sdes	if (statvfs(path, &st) != 0)
1200181111Sdes		send_status(id, errno_to_portable(errno));
1201181111Sdes	else
1202181111Sdes		send_statvfs(id, &st);
1203181111Sdes        xfree(path);
1204181111Sdes}
1205181111Sdes
1206181111Sdesstatic void
1207181111Sdesprocess_extended_fstatvfs(u_int32_t id)
1208181111Sdes{
1209181111Sdes	int handle, fd;
1210181111Sdes	struct statvfs st;
1211181111Sdes
1212181111Sdes	handle = get_handle();
1213181111Sdes	debug("request %u: fstatvfs \"%s\" (handle %u)",
1214181111Sdes	    id, handle_to_name(handle), handle);
1215181111Sdes	if ((fd = handle_to_fd(handle)) < 0) {
1216181111Sdes		send_status(id, SSH2_FX_FAILURE);
1217181111Sdes		return;
1218181111Sdes	}
1219181111Sdes	if (fstatvfs(fd, &st) != 0)
1220181111Sdes		send_status(id, errno_to_portable(errno));
1221181111Sdes	else
1222181111Sdes		send_statvfs(id, &st);
1223181111Sdes}
1224181111Sdes
1225181111Sdesstatic void
122676259Sgreenprocess_extended(void)
122776259Sgreen{
122876259Sgreen	u_int32_t id;
122976259Sgreen	char *request;
123076259Sgreen
123176259Sgreen	id = get_int();
123276259Sgreen	request = get_string(NULL);
1233181111Sdes	if (strcmp(request, "posix-rename@openssh.com") == 0)
1234181111Sdes		process_extended_posix_rename(id);
1235181111Sdes	else if (strcmp(request, "statvfs@openssh.com") == 0)
1236181111Sdes		process_extended_statvfs(id);
1237181111Sdes	else if (strcmp(request, "fstatvfs@openssh.com") == 0)
1238181111Sdes		process_extended_fstatvfs(id);
1239181111Sdes	else
1240181111Sdes		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
124176259Sgreen	xfree(request);
124276259Sgreen}
124376259Sgreen
124465668Skris/* stolen from ssh-agent */
124565668Skris
124692555Sdesstatic void
124765668Skrisprocess(void)
124865668Skris{
124976259Sgreen	u_int msg_len;
125098675Sdes	u_int buf_len;
125198675Sdes	u_int consumed;
125276259Sgreen	u_int type;
125376259Sgreen	u_char *cp;
125465668Skris
125598675Sdes	buf_len = buffer_len(&iqueue);
125698675Sdes	if (buf_len < 5)
125765668Skris		return;		/* Incomplete message. */
125892555Sdes	cp = buffer_ptr(&iqueue);
1259162852Sdes	msg_len = get_u32(cp);
1260157016Sdes	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1261162852Sdes		error("bad message from %s local user %s",
1262162852Sdes		    client_addr, pw->pw_name);
1263181111Sdes		sftp_server_cleanup_exit(11);
126465668Skris	}
126598675Sdes	if (buf_len < msg_len + 4)
126665668Skris		return;
126765668Skris	buffer_consume(&iqueue, 4);
126898675Sdes	buf_len -= 4;
126965668Skris	type = buffer_get_char(&iqueue);
127065668Skris	switch (type) {
127176259Sgreen	case SSH2_FXP_INIT:
127265668Skris		process_init();
127365668Skris		break;
127476259Sgreen	case SSH2_FXP_OPEN:
127565668Skris		process_open();
127665668Skris		break;
127776259Sgreen	case SSH2_FXP_CLOSE:
127865668Skris		process_close();
127965668Skris		break;
128076259Sgreen	case SSH2_FXP_READ:
128165668Skris		process_read();
128265668Skris		break;
128376259Sgreen	case SSH2_FXP_WRITE:
128465668Skris		process_write();
128565668Skris		break;
128676259Sgreen	case SSH2_FXP_LSTAT:
128765668Skris		process_lstat();
128865668Skris		break;
128976259Sgreen	case SSH2_FXP_FSTAT:
129065668Skris		process_fstat();
129165668Skris		break;
129276259Sgreen	case SSH2_FXP_SETSTAT:
129365668Skris		process_setstat();
129465668Skris		break;
129576259Sgreen	case SSH2_FXP_FSETSTAT:
129665668Skris		process_fsetstat();
129765668Skris		break;
129876259Sgreen	case SSH2_FXP_OPENDIR:
129965668Skris		process_opendir();
130065668Skris		break;
130176259Sgreen	case SSH2_FXP_READDIR:
130265668Skris		process_readdir();
130365668Skris		break;
130476259Sgreen	case SSH2_FXP_REMOVE:
130565668Skris		process_remove();
130665668Skris		break;
130776259Sgreen	case SSH2_FXP_MKDIR:
130865668Skris		process_mkdir();
130965668Skris		break;
131076259Sgreen	case SSH2_FXP_RMDIR:
131165668Skris		process_rmdir();
131265668Skris		break;
131376259Sgreen	case SSH2_FXP_REALPATH:
131465668Skris		process_realpath();
131565668Skris		break;
131676259Sgreen	case SSH2_FXP_STAT:
131765668Skris		process_stat();
131865668Skris		break;
131976259Sgreen	case SSH2_FXP_RENAME:
132065668Skris		process_rename();
132165668Skris		break;
132276259Sgreen	case SSH2_FXP_READLINK:
132376259Sgreen		process_readlink();
132476259Sgreen		break;
132576259Sgreen	case SSH2_FXP_SYMLINK:
132676259Sgreen		process_symlink();
132776259Sgreen		break;
132876259Sgreen	case SSH2_FXP_EXTENDED:
132976259Sgreen		process_extended();
133076259Sgreen		break;
133165668Skris	default:
133265668Skris		error("Unknown message %d", type);
133365668Skris		break;
133465668Skris	}
133598675Sdes	/* discard the remaining bytes from the current packet */
1336181111Sdes	if (buf_len < buffer_len(&iqueue)) {
1337181111Sdes		error("iqueue grew unexpectedly");
1338181111Sdes		sftp_server_cleanup_exit(255);
1339181111Sdes	}
134098675Sdes	consumed = buf_len - buffer_len(&iqueue);
1341181111Sdes	if (msg_len < consumed) {
1342181111Sdes		error("msg_len %d < consumed %d", msg_len, consumed);
1343181111Sdes		sftp_server_cleanup_exit(255);
1344181111Sdes	}
134598675Sdes	if (msg_len > consumed)
134698675Sdes		buffer_consume(&iqueue, msg_len - consumed);
134765668Skris}
134865668Skris
1349162852Sdes/* Cleanup handler that logs active handles upon normal exit */
1350162852Sdesvoid
1351181111Sdessftp_server_cleanup_exit(int i)
1352162852Sdes{
1353162852Sdes	if (pw != NULL && client_addr != NULL) {
1354162852Sdes		handle_log_exit();
1355162852Sdes		logit("session closed for local user %s from [%s]",
1356162852Sdes		    pw->pw_name, client_addr);
1357162852Sdes	}
1358162852Sdes	_exit(i);
1359162852Sdes}
1360162852Sdes
1361162852Sdesstatic void
1362181111Sdessftp_server_usage(void)
1363162852Sdes{
1364162852Sdes	extern char *__progname;
1365162852Sdes
1366162852Sdes	fprintf(stderr,
1367204917Sdes	    "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n",
1368204917Sdes	    __progname);
1369162852Sdes	exit(1);
1370162852Sdes}
1371162852Sdes
137265668Skrisint
1373181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw)
137465668Skris{
137576259Sgreen	fd_set *rset, *wset;
1376162852Sdes	int in, out, max, ch, skipargs = 0, log_stderr = 0;
137776259Sgreen	ssize_t len, olen, set_size;
1378162852Sdes	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1379181111Sdes	char *cp, buf[4*4096];
1380204917Sdes	const char *errmsg;
1381204917Sdes	mode_t mask;
138265668Skris
1383162852Sdes	extern char *optarg;
1384162852Sdes	extern char *__progname;
1385162852Sdes
1386162852Sdes	__progname = ssh_get_progname(argv[0]);
1387162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
138876259Sgreen
1389204917Sdes	while (!skipargs && (ch = getopt(argc, argv, "f:l:u:cehR")) != -1) {
1390162852Sdes		switch (ch) {
1391204917Sdes		case 'R':
1392204917Sdes			readonly = 1;
1393204917Sdes			break;
1394162852Sdes		case 'c':
1395162852Sdes			/*
1396162852Sdes			 * Ignore all arguments if we are invoked as a
1397162852Sdes			 * shell using "sftp-server -c command"
1398162852Sdes			 */
1399162852Sdes			skipargs = 1;
1400162852Sdes			break;
1401162852Sdes		case 'e':
1402162852Sdes			log_stderr = 1;
1403162852Sdes			break;
1404162852Sdes		case 'l':
1405162852Sdes			log_level = log_level_number(optarg);
1406162852Sdes			if (log_level == SYSLOG_LEVEL_NOT_SET)
1407162852Sdes				error("Invalid log level \"%s\"", optarg);
1408162852Sdes			break;
1409162852Sdes		case 'f':
1410162852Sdes			log_facility = log_facility_number(optarg);
1411181111Sdes			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1412162852Sdes				error("Invalid log facility \"%s\"", optarg);
1413162852Sdes			break;
1414204917Sdes		case 'u':
1415204917Sdes			mask = (mode_t)strtonum(optarg, 0, 0777, &errmsg);
1416204917Sdes			if (errmsg != NULL)
1417204917Sdes				fatal("Invalid umask \"%s\": %s",
1418204917Sdes				    optarg, errmsg);
1419204917Sdes			(void)umask(mask);
1420204917Sdes			break;
1421162852Sdes		case 'h':
1422162852Sdes		default:
1423181111Sdes			sftp_server_usage();
1424162852Sdes		}
1425162852Sdes	}
1426162852Sdes
1427162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
1428162852Sdes
1429162852Sdes	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1430162852Sdes		client_addr = xstrdup(cp);
1431181111Sdes		if ((cp = strchr(client_addr, ' ')) == NULL) {
1432181111Sdes			error("Malformed SSH_CONNECTION variable: \"%s\"",
1433162852Sdes			    getenv("SSH_CONNECTION"));
1434181111Sdes			sftp_server_cleanup_exit(255);
1435181111Sdes		}
1436162852Sdes		*cp = '\0';
1437162852Sdes	} else
1438162852Sdes		client_addr = xstrdup("UNKNOWN");
1439162852Sdes
1440181111Sdes	pw = pwcopy(user_pw);
1441162852Sdes
1442162852Sdes	logit("session opened for local user %s from [%s]",
1443162852Sdes	    pw->pw_name, client_addr);
1444162852Sdes
1445204917Sdes	in = STDIN_FILENO;
1446204917Sdes	out = STDOUT_FILENO;
144765668Skris
144898937Sdes#ifdef HAVE_CYGWIN
144998937Sdes	setmode(in, O_BINARY);
145098937Sdes	setmode(out, O_BINARY);
145198937Sdes#endif
145298937Sdes
145365668Skris	max = 0;
145465668Skris	if (in > max)
145565668Skris		max = in;
145665668Skris	if (out > max)
145765668Skris		max = out;
145865668Skris
145965668Skris	buffer_init(&iqueue);
146065668Skris	buffer_init(&oqueue);
146165668Skris
146276259Sgreen	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
146376259Sgreen	rset = (fd_set *)xmalloc(set_size);
146476259Sgreen	wset = (fd_set *)xmalloc(set_size);
146576259Sgreen
146665668Skris	for (;;) {
146776259Sgreen		memset(rset, 0, set_size);
146876259Sgreen		memset(wset, 0, set_size);
146965668Skris
1470181111Sdes		/*
1471181111Sdes		 * Ensure that we can read a full buffer and handle
1472181111Sdes		 * the worst-case length packet it can generate,
1473181111Sdes		 * otherwise apply backpressure by stopping reads.
1474181111Sdes		 */
1475181111Sdes		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
1476181111Sdes		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1477181111Sdes			FD_SET(in, rset);
1478181111Sdes
147965668Skris		olen = buffer_len(&oqueue);
148065668Skris		if (olen > 0)
148176259Sgreen			FD_SET(out, wset);
148265668Skris
148376259Sgreen		if (select(max+1, rset, wset, NULL, NULL) < 0) {
148465668Skris			if (errno == EINTR)
148565668Skris				continue;
1486162852Sdes			error("select: %s", strerror(errno));
1487181111Sdes			sftp_server_cleanup_exit(2);
148865668Skris		}
148965668Skris
149065668Skris		/* copy stdin to iqueue */
149176259Sgreen		if (FD_ISSET(in, rset)) {
149265668Skris			len = read(in, buf, sizeof buf);
149365668Skris			if (len == 0) {
149465668Skris				debug("read eof");
1495181111Sdes				sftp_server_cleanup_exit(0);
149665668Skris			} else if (len < 0) {
1497162852Sdes				error("read: %s", strerror(errno));
1498181111Sdes				sftp_server_cleanup_exit(1);
149965668Skris			} else {
150065668Skris				buffer_append(&iqueue, buf, len);
150165668Skris			}
150265668Skris		}
150365668Skris		/* send oqueue to stdout */
150476259Sgreen		if (FD_ISSET(out, wset)) {
150565668Skris			len = write(out, buffer_ptr(&oqueue), olen);
150665668Skris			if (len < 0) {
1507162852Sdes				error("write: %s", strerror(errno));
1508181111Sdes				sftp_server_cleanup_exit(1);
150965668Skris			} else {
151065668Skris				buffer_consume(&oqueue, len);
151165668Skris			}
151265668Skris		}
1513181111Sdes
1514181111Sdes		/*
1515181111Sdes		 * Process requests from client if we can fit the results
1516181111Sdes		 * into the output buffer, otherwise stop processing input
1517181111Sdes		 * and let the output queue drain.
1518181111Sdes		 */
1519181111Sdes		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1520181111Sdes			process();
152165668Skris	}
152265668Skris}
1523