sftp-server.c revision 197679
1197679Sdes/* $OpenBSD: sftp-server.c,v 1.85 2009/04/14 16:33:42 stevesk 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
73124208Sdes/* portable attributes, etc. */
7465668Skris
7565668Skristypedef struct Stat Stat;
7665668Skris
7776259Sgreenstruct Stat {
7865668Skris	char *name;
7965668Skris	char *long_name;
8065668Skris	Attrib attrib;
8165668Skris};
8265668Skris
8392555Sdesstatic int
8465668Skriserrno_to_portable(int unixerrno)
8565668Skris{
8665668Skris	int ret = 0;
8776259Sgreen
8865668Skris	switch (unixerrno) {
8965668Skris	case 0:
9076259Sgreen		ret = SSH2_FX_OK;
9165668Skris		break;
9265668Skris	case ENOENT:
9365668Skris	case ENOTDIR:
9465668Skris	case EBADF:
9565668Skris	case ELOOP:
9676259Sgreen		ret = SSH2_FX_NO_SUCH_FILE;
9765668Skris		break;
9865668Skris	case EPERM:
9965668Skris	case EACCES:
10065668Skris	case EFAULT:
10176259Sgreen		ret = SSH2_FX_PERMISSION_DENIED;
10265668Skris		break;
10365668Skris	case ENAMETOOLONG:
10465668Skris	case EINVAL:
10576259Sgreen		ret = SSH2_FX_BAD_MESSAGE;
10665668Skris		break;
107181111Sdes	case ENOSYS:
108181111Sdes		ret = SSH2_FX_OP_UNSUPPORTED;
109181111Sdes		break;
11065668Skris	default:
11176259Sgreen		ret = SSH2_FX_FAILURE;
11265668Skris		break;
11365668Skris	}
11465668Skris	return ret;
11565668Skris}
11665668Skris
11792555Sdesstatic int
11865668Skrisflags_from_portable(int pflags)
11965668Skris{
12065668Skris	int flags = 0;
12176259Sgreen
12276259Sgreen	if ((pflags & SSH2_FXF_READ) &&
12376259Sgreen	    (pflags & SSH2_FXF_WRITE)) {
12465668Skris		flags = O_RDWR;
12576259Sgreen	} else if (pflags & SSH2_FXF_READ) {
12665668Skris		flags = O_RDONLY;
12776259Sgreen	} else if (pflags & SSH2_FXF_WRITE) {
12865668Skris		flags = O_WRONLY;
12965668Skris	}
13076259Sgreen	if (pflags & SSH2_FXF_CREAT)
13165668Skris		flags |= O_CREAT;
13276259Sgreen	if (pflags & SSH2_FXF_TRUNC)
13365668Skris		flags |= O_TRUNC;
13476259Sgreen	if (pflags & SSH2_FXF_EXCL)
13565668Skris		flags |= O_EXCL;
13665668Skris	return flags;
13765668Skris}
13865668Skris
139162852Sdesstatic const char *
140162852Sdesstring_from_portable(int pflags)
141162852Sdes{
142162852Sdes	static char ret[128];
143162852Sdes
144162852Sdes	*ret = '\0';
145162852Sdes
146162852Sdes#define PAPPEND(str)	{				\
147162852Sdes		if (*ret != '\0')			\
148162852Sdes			strlcat(ret, ",", sizeof(ret));	\
149162852Sdes		strlcat(ret, str, sizeof(ret));		\
150162852Sdes	}
151162852Sdes
152162852Sdes	if (pflags & SSH2_FXF_READ)
153162852Sdes		PAPPEND("READ")
154162852Sdes	if (pflags & SSH2_FXF_WRITE)
155162852Sdes		PAPPEND("WRITE")
156162852Sdes	if (pflags & SSH2_FXF_CREAT)
157162852Sdes		PAPPEND("CREATE")
158162852Sdes	if (pflags & SSH2_FXF_TRUNC)
159162852Sdes		PAPPEND("TRUNCATE")
160162852Sdes	if (pflags & SSH2_FXF_EXCL)
161162852Sdes		PAPPEND("EXCL")
162162852Sdes
163162852Sdes	return ret;
164162852Sdes}
165162852Sdes
16692555Sdesstatic Attrib *
16765668Skrisget_attrib(void)
16865668Skris{
16965668Skris	return decode_attrib(&iqueue);
17065668Skris}
17165668Skris
17265668Skris/* handle handles */
17365668Skris
17465668Skristypedef struct Handle Handle;
17565668Skrisstruct Handle {
17665668Skris	int use;
17765668Skris	DIR *dirp;
17865668Skris	int fd;
17965668Skris	char *name;
180162852Sdes	u_int64_t bytes_read, bytes_write;
181181111Sdes	int next_unused;
18265668Skris};
18376259Sgreen
18465668Skrisenum {
18565668Skris	HANDLE_UNUSED,
18665668Skris	HANDLE_DIR,
18765668Skris	HANDLE_FILE
18865668Skris};
18976259Sgreen
190181111SdesHandle *handles = NULL;
191181111Sdesu_int num_handles = 0;
192181111Sdesint first_unused_handle = -1;
19365668Skris
194181111Sdesstatic void handle_unused(int i)
19565668Skris{
196181111Sdes	handles[i].use = HANDLE_UNUSED;
197181111Sdes	handles[i].next_unused = first_unused_handle;
198181111Sdes	first_unused_handle = i;
19965668Skris}
20065668Skris
20192555Sdesstatic int
202126274Sdeshandle_new(int use, const char *name, int fd, DIR *dirp)
20365668Skris{
204181111Sdes	int i;
20576259Sgreen
206181111Sdes	if (first_unused_handle == -1) {
207181111Sdes		if (num_handles + 1 <= num_handles)
208181111Sdes			return -1;
209181111Sdes		num_handles++;
210181111Sdes		handles = xrealloc(handles, num_handles, sizeof(Handle));
211181111Sdes		handle_unused(num_handles - 1);
21265668Skris	}
213181111Sdes
214181111Sdes	i = first_unused_handle;
215181111Sdes	first_unused_handle = handles[i].next_unused;
216181111Sdes
217181111Sdes	handles[i].use = use;
218181111Sdes	handles[i].dirp = dirp;
219181111Sdes	handles[i].fd = fd;
220181111Sdes	handles[i].name = xstrdup(name);
221181111Sdes	handles[i].bytes_read = handles[i].bytes_write = 0;
222181111Sdes
223181111Sdes	return i;
22465668Skris}
22565668Skris
22692555Sdesstatic int
22765668Skrishandle_is_ok(int i, int type)
22865668Skris{
229181111Sdes	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
23065668Skris}
23165668Skris
23292555Sdesstatic int
23365668Skrishandle_to_string(int handle, char **stringp, int *hlenp)
23465668Skris{
23565668Skris	if (stringp == NULL || hlenp == NULL)
23665668Skris		return -1;
23776259Sgreen	*stringp = xmalloc(sizeof(int32_t));
238162852Sdes	put_u32(*stringp, handle);
23976259Sgreen	*hlenp = sizeof(int32_t);
24065668Skris	return 0;
24165668Skris}
24265668Skris
24392555Sdesstatic int
244126274Sdeshandle_from_string(const char *handle, u_int hlen)
24565668Skris{
24676259Sgreen	int val;
24776259Sgreen
24876259Sgreen	if (hlen != sizeof(int32_t))
24965668Skris		return -1;
250162852Sdes	val = get_u32(handle);
25165668Skris	if (handle_is_ok(val, HANDLE_FILE) ||
25265668Skris	    handle_is_ok(val, HANDLE_DIR))
25365668Skris		return val;
25465668Skris	return -1;
25565668Skris}
25665668Skris
25792555Sdesstatic char *
25865668Skrishandle_to_name(int handle)
25965668Skris{
26065668Skris	if (handle_is_ok(handle, HANDLE_DIR)||
26165668Skris	    handle_is_ok(handle, HANDLE_FILE))
26265668Skris		return handles[handle].name;
26365668Skris	return NULL;
26465668Skris}
26565668Skris
26692555Sdesstatic DIR *
26765668Skrishandle_to_dir(int handle)
26865668Skris{
26965668Skris	if (handle_is_ok(handle, HANDLE_DIR))
27065668Skris		return handles[handle].dirp;
27165668Skris	return NULL;
27265668Skris}
27365668Skris
27492555Sdesstatic int
27565668Skrishandle_to_fd(int handle)
27665668Skris{
27776259Sgreen	if (handle_is_ok(handle, HANDLE_FILE))
27865668Skris		return handles[handle].fd;
27965668Skris	return -1;
28065668Skris}
28165668Skris
282162852Sdesstatic void
283162852Sdeshandle_update_read(int handle, ssize_t bytes)
284162852Sdes{
285162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
286162852Sdes		handles[handle].bytes_read += bytes;
287162852Sdes}
288162852Sdes
289162852Sdesstatic void
290162852Sdeshandle_update_write(int handle, ssize_t bytes)
291162852Sdes{
292162852Sdes	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
293162852Sdes		handles[handle].bytes_write += bytes;
294162852Sdes}
295162852Sdes
296162852Sdesstatic u_int64_t
297162852Sdeshandle_bytes_read(int handle)
298162852Sdes{
299162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
300162852Sdes		return (handles[handle].bytes_read);
301162852Sdes	return 0;
302162852Sdes}
303162852Sdes
304162852Sdesstatic u_int64_t
305162852Sdeshandle_bytes_write(int handle)
306162852Sdes{
307162852Sdes	if (handle_is_ok(handle, HANDLE_FILE))
308162852Sdes		return (handles[handle].bytes_write);
309162852Sdes	return 0;
310162852Sdes}
311162852Sdes
31292555Sdesstatic int
31365668Skrishandle_close(int handle)
31465668Skris{
31565668Skris	int ret = -1;
31676259Sgreen
31765668Skris	if (handle_is_ok(handle, HANDLE_FILE)) {
31865668Skris		ret = close(handles[handle].fd);
319113908Sdes		xfree(handles[handle].name);
320181111Sdes		handle_unused(handle);
32165668Skris	} else if (handle_is_ok(handle, HANDLE_DIR)) {
32265668Skris		ret = closedir(handles[handle].dirp);
323113908Sdes		xfree(handles[handle].name);
324181111Sdes		handle_unused(handle);
32565668Skris	} else {
32665668Skris		errno = ENOENT;
32765668Skris	}
32865668Skris	return ret;
32965668Skris}
33065668Skris
331162852Sdesstatic void
332162852Sdeshandle_log_close(int handle, char *emsg)
333162852Sdes{
334162852Sdes	if (handle_is_ok(handle, HANDLE_FILE)) {
335162852Sdes		logit("%s%sclose \"%s\" bytes read %llu written %llu",
336162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
337162852Sdes		    handle_to_name(handle),
338181111Sdes		    (unsigned long long)handle_bytes_read(handle),
339181111Sdes		    (unsigned long long)handle_bytes_write(handle));
340162852Sdes	} else {
341162852Sdes		logit("%s%sclosedir \"%s\"",
342162852Sdes		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
343162852Sdes		    handle_to_name(handle));
344162852Sdes	}
345162852Sdes}
346162852Sdes
347162852Sdesstatic void
348162852Sdeshandle_log_exit(void)
349162852Sdes{
350162852Sdes	u_int i;
351162852Sdes
352181111Sdes	for (i = 0; i < num_handles; i++)
353162852Sdes		if (handles[i].use != HANDLE_UNUSED)
354162852Sdes			handle_log_close(i, "forced");
355162852Sdes}
356162852Sdes
35792555Sdesstatic int
35865668Skrisget_handle(void)
35965668Skris{
36065668Skris	char *handle;
36176259Sgreen	int val = -1;
36265668Skris	u_int hlen;
36376259Sgreen
36465668Skris	handle = get_string(&hlen);
36576259Sgreen	if (hlen < 256)
36676259Sgreen		val = handle_from_string(handle, hlen);
36765668Skris	xfree(handle);
36865668Skris	return val;
36965668Skris}
37065668Skris
37165668Skris/* send replies */
37265668Skris
37392555Sdesstatic void
37465668Skrissend_msg(Buffer *m)
37565668Skris{
37665668Skris	int mlen = buffer_len(m);
37776259Sgreen
37865668Skris	buffer_put_int(&oqueue, mlen);
37965668Skris	buffer_append(&oqueue, buffer_ptr(m), mlen);
38065668Skris	buffer_consume(m, mlen);
38165668Skris}
38265668Skris
383162852Sdesstatic const char *
384162852Sdesstatus_to_message(u_int32_t status)
38565668Skris{
38676259Sgreen	const char *status_messages[] = {
38776259Sgreen		"Success",			/* SSH_FX_OK */
38876259Sgreen		"End of file",			/* SSH_FX_EOF */
38976259Sgreen		"No such file",			/* SSH_FX_NO_SUCH_FILE */
39076259Sgreen		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
39176259Sgreen		"Failure",			/* SSH_FX_FAILURE */
39276259Sgreen		"Bad message",			/* SSH_FX_BAD_MESSAGE */
39376259Sgreen		"No connection",		/* SSH_FX_NO_CONNECTION */
39476259Sgreen		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
39576259Sgreen		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
39676259Sgreen		"Unknown error"			/* Others */
39776259Sgreen	};
398162852Sdes	return (status_messages[MIN(status,SSH2_FX_MAX)]);
399162852Sdes}
40076259Sgreen
401162852Sdesstatic void
402162852Sdessend_status(u_int32_t id, u_int32_t status)
403162852Sdes{
404162852Sdes	Buffer msg;
405162852Sdes
406162852Sdes	debug3("request %u: sent status %u", id, status);
407162852Sdes	if (log_level > SYSLOG_LEVEL_VERBOSE ||
408162852Sdes	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
409162852Sdes		logit("sent status %s", status_to_message(status));
41065668Skris	buffer_init(&msg);
41176259Sgreen	buffer_put_char(&msg, SSH2_FXP_STATUS);
41265668Skris	buffer_put_int(&msg, id);
413137015Sdes	buffer_put_int(&msg, status);
41476259Sgreen	if (version >= 3) {
415162852Sdes		buffer_put_cstring(&msg, status_to_message(status));
41676259Sgreen		buffer_put_cstring(&msg, "");
41776259Sgreen	}
41865668Skris	send_msg(&msg);
41965668Skris	buffer_free(&msg);
42065668Skris}
42192555Sdesstatic void
422126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
42365668Skris{
42465668Skris	Buffer msg;
42576259Sgreen
42665668Skris	buffer_init(&msg);
42765668Skris	buffer_put_char(&msg, type);
42865668Skris	buffer_put_int(&msg, id);
42965668Skris	buffer_put_string(&msg, data, dlen);
43065668Skris	send_msg(&msg);
43165668Skris	buffer_free(&msg);
43265668Skris}
43365668Skris
43492555Sdesstatic void
435126274Sdessend_data(u_int32_t id, const char *data, int dlen)
43665668Skris{
437162852Sdes	debug("request %u: sent data len %d", id, dlen);
43876259Sgreen	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
43965668Skris}
44065668Skris
44192555Sdesstatic void
44265668Skrissend_handle(u_int32_t id, int handle)
44365668Skris{
44465668Skris	char *string;
44565668Skris	int hlen;
44676259Sgreen
44765668Skris	handle_to_string(handle, &string, &hlen);
448162852Sdes	debug("request %u: sent handle handle %d", id, handle);
44976259Sgreen	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
45065668Skris	xfree(string);
45165668Skris}
45265668Skris
45392555Sdesstatic void
454126274Sdessend_names(u_int32_t id, int count, const Stat *stats)
45565668Skris{
45665668Skris	Buffer msg;
45765668Skris	int i;
45876259Sgreen
45965668Skris	buffer_init(&msg);
46076259Sgreen	buffer_put_char(&msg, SSH2_FXP_NAME);
46165668Skris	buffer_put_int(&msg, id);
46265668Skris	buffer_put_int(&msg, count);
463162852Sdes	debug("request %u: sent names count %d", id, count);
46465668Skris	for (i = 0; i < count; i++) {
46565668Skris		buffer_put_cstring(&msg, stats[i].name);
46665668Skris		buffer_put_cstring(&msg, stats[i].long_name);
46765668Skris		encode_attrib(&msg, &stats[i].attrib);
46865668Skris	}
46965668Skris	send_msg(&msg);
47065668Skris	buffer_free(&msg);
47165668Skris}
47265668Skris
47392555Sdesstatic void
474126274Sdessend_attrib(u_int32_t id, const Attrib *a)
47565668Skris{
47665668Skris	Buffer msg;
47776259Sgreen
478162852Sdes	debug("request %u: sent attrib have 0x%x", id, a->flags);
47965668Skris	buffer_init(&msg);
48076259Sgreen	buffer_put_char(&msg, SSH2_FXP_ATTRS);
48165668Skris	buffer_put_int(&msg, id);
48265668Skris	encode_attrib(&msg, a);
48365668Skris	send_msg(&msg);
48465668Skris	buffer_free(&msg);
48565668Skris}
48665668Skris
487181111Sdesstatic void
488181111Sdessend_statvfs(u_int32_t id, struct statvfs *st)
489181111Sdes{
490181111Sdes	Buffer msg;
491181111Sdes	u_int64_t flag;
492181111Sdes
493181111Sdes	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
494181111Sdes	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
495181111Sdes
496181111Sdes	buffer_init(&msg);
497181111Sdes	buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY);
498181111Sdes	buffer_put_int(&msg, id);
499181111Sdes	buffer_put_int64(&msg, st->f_bsize);
500181111Sdes	buffer_put_int64(&msg, st->f_frsize);
501181111Sdes	buffer_put_int64(&msg, st->f_blocks);
502181111Sdes	buffer_put_int64(&msg, st->f_bfree);
503181111Sdes	buffer_put_int64(&msg, st->f_bavail);
504181111Sdes	buffer_put_int64(&msg, st->f_files);
505181111Sdes	buffer_put_int64(&msg, st->f_ffree);
506181111Sdes	buffer_put_int64(&msg, st->f_favail);
507181111Sdes	buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid));
508181111Sdes	buffer_put_int64(&msg, flag);
509181111Sdes	buffer_put_int64(&msg, st->f_namemax);
510181111Sdes	send_msg(&msg);
511181111Sdes	buffer_free(&msg);
512181111Sdes}
513181111Sdes
51465668Skris/* parse incoming */
51565668Skris
51692555Sdesstatic void
51765668Skrisprocess_init(void)
51865668Skris{
51965668Skris	Buffer msg;
52065668Skris
52198675Sdes	version = get_int();
522162852Sdes	verbose("received client version %d", version);
52365668Skris	buffer_init(&msg);
52476259Sgreen	buffer_put_char(&msg, SSH2_FXP_VERSION);
52576259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
526181111Sdes	/* POSIX rename extension */
527181111Sdes	buffer_put_cstring(&msg, "posix-rename@openssh.com");
528181111Sdes	buffer_put_cstring(&msg, "1"); /* version */
529181111Sdes	/* statvfs extension */
530181111Sdes	buffer_put_cstring(&msg, "statvfs@openssh.com");
531181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
532181111Sdes	/* fstatvfs extension */
533181111Sdes	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
534181111Sdes	buffer_put_cstring(&msg, "2"); /* version */
53565668Skris	send_msg(&msg);
53665668Skris	buffer_free(&msg);
53765668Skris}
53865668Skris
53992555Sdesstatic void
54065668Skrisprocess_open(void)
54165668Skris{
54265668Skris	u_int32_t id, pflags;
54365668Skris	Attrib *a;
54465668Skris	char *name;
54576259Sgreen	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
54665668Skris
54765668Skris	id = get_int();
54865668Skris	name = get_string(NULL);
54976259Sgreen	pflags = get_int();		/* portable flags */
550162852Sdes	debug3("request %u: open flags %d", id, pflags);
55165668Skris	a = get_attrib();
55265668Skris	flags = flags_from_portable(pflags);
55376259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
554162852Sdes	logit("open \"%s\" flags %s mode 0%o",
555162852Sdes	    name, string_from_portable(pflags), mode);
55665668Skris	fd = open(name, flags, mode);
55765668Skris	if (fd < 0) {
55865668Skris		status = errno_to_portable(errno);
55965668Skris	} else {
560113908Sdes		handle = handle_new(HANDLE_FILE, name, fd, NULL);
56165668Skris		if (handle < 0) {
56265668Skris			close(fd);
56365668Skris		} else {
56465668Skris			send_handle(id, handle);
56576259Sgreen			status = SSH2_FX_OK;
56665668Skris		}
56765668Skris	}
56876259Sgreen	if (status != SSH2_FX_OK)
56965668Skris		send_status(id, status);
57065668Skris	xfree(name);
57165668Skris}
57265668Skris
57392555Sdesstatic void
57465668Skrisprocess_close(void)
57565668Skris{
57665668Skris	u_int32_t id;
57776259Sgreen	int handle, ret, status = SSH2_FX_FAILURE;
57865668Skris
57965668Skris	id = get_int();
58065668Skris	handle = get_handle();
581162852Sdes	debug3("request %u: close handle %u", id, handle);
582162852Sdes	handle_log_close(handle, NULL);
58365668Skris	ret = handle_close(handle);
58476259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
58565668Skris	send_status(id, status);
58665668Skris}
58765668Skris
58892555Sdesstatic void
58965668Skrisprocess_read(void)
59065668Skris{
59165668Skris	char buf[64*1024];
59276259Sgreen	u_int32_t id, len;
59376259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
59465668Skris	u_int64_t off;
59565668Skris
59665668Skris	id = get_int();
59765668Skris	handle = get_handle();
59876259Sgreen	off = get_int64();
59965668Skris	len = get_int();
60065668Skris
601162852Sdes	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
602162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
60365668Skris	if (len > sizeof buf) {
60465668Skris		len = sizeof buf;
605162852Sdes		debug2("read change len %d", len);
60665668Skris	}
60765668Skris	fd = handle_to_fd(handle);
60865668Skris	if (fd >= 0) {
60965668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
61065668Skris			error("process_read: seek failed");
61165668Skris			status = errno_to_portable(errno);
61265668Skris		} else {
61365668Skris			ret = read(fd, buf, len);
61465668Skris			if (ret < 0) {
61565668Skris				status = errno_to_portable(errno);
61665668Skris			} else if (ret == 0) {
61776259Sgreen				status = SSH2_FX_EOF;
61865668Skris			} else {
61965668Skris				send_data(id, buf, ret);
62076259Sgreen				status = SSH2_FX_OK;
621162852Sdes				handle_update_read(handle, ret);
62265668Skris			}
62365668Skris		}
62465668Skris	}
62576259Sgreen	if (status != SSH2_FX_OK)
62665668Skris		send_status(id, status);
62765668Skris}
62865668Skris
62992555Sdesstatic void
63065668Skrisprocess_write(void)
63165668Skris{
63276259Sgreen	u_int32_t id;
63365668Skris	u_int64_t off;
63465668Skris	u_int len;
63576259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
63665668Skris	char *data;
63765668Skris
63865668Skris	id = get_int();
63965668Skris	handle = get_handle();
64076259Sgreen	off = get_int64();
64165668Skris	data = get_string(&len);
64265668Skris
643162852Sdes	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
644162852Sdes	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
64565668Skris	fd = handle_to_fd(handle);
64665668Skris	if (fd >= 0) {
64765668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
64865668Skris			status = errno_to_portable(errno);
64965668Skris			error("process_write: seek failed");
65065668Skris		} else {
65165668Skris/* XXX ATOMICIO ? */
65265668Skris			ret = write(fd, data, len);
653149749Sdes			if (ret < 0) {
65465668Skris				error("process_write: write failed");
65565668Skris				status = errno_to_portable(errno);
656149749Sdes			} else if ((size_t)ret == len) {
65776259Sgreen				status = SSH2_FX_OK;
658162852Sdes				handle_update_write(handle, ret);
65965668Skris			} else {
660162852Sdes				debug2("nothing at all written");
66165668Skris			}
66265668Skris		}
66365668Skris	}
66465668Skris	send_status(id, status);
66565668Skris	xfree(data);
66665668Skris}
66765668Skris
66892555Sdesstatic void
66965668Skrisprocess_do_stat(int do_lstat)
67065668Skris{
67176259Sgreen	Attrib a;
67265668Skris	struct stat st;
67365668Skris	u_int32_t id;
67465668Skris	char *name;
67576259Sgreen	int ret, status = SSH2_FX_FAILURE;
67665668Skris
67765668Skris	id = get_int();
67865668Skris	name = get_string(NULL);
679162852Sdes	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
680162852Sdes	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
68165668Skris	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
68265668Skris	if (ret < 0) {
68365668Skris		status = errno_to_portable(errno);
68465668Skris	} else {
68576259Sgreen		stat_to_attrib(&st, &a);
68676259Sgreen		send_attrib(id, &a);
68776259Sgreen		status = SSH2_FX_OK;
68865668Skris	}
68976259Sgreen	if (status != SSH2_FX_OK)
69065668Skris		send_status(id, status);
69165668Skris	xfree(name);
69265668Skris}
69365668Skris
69492555Sdesstatic void
69565668Skrisprocess_stat(void)
69665668Skris{
69765668Skris	process_do_stat(0);
69865668Skris}
69965668Skris
70092555Sdesstatic void
70165668Skrisprocess_lstat(void)
70265668Skris{
70365668Skris	process_do_stat(1);
70465668Skris}
70565668Skris
70692555Sdesstatic void
70765668Skrisprocess_fstat(void)
70865668Skris{
70976259Sgreen	Attrib a;
71065668Skris	struct stat st;
71165668Skris	u_int32_t id;
71276259Sgreen	int fd, ret, handle, status = SSH2_FX_FAILURE;
71365668Skris
71465668Skris	id = get_int();
71565668Skris	handle = get_handle();
716162852Sdes	debug("request %u: fstat \"%s\" (handle %u)",
717162852Sdes	    id, handle_to_name(handle), handle);
71865668Skris	fd = handle_to_fd(handle);
719181111Sdes	if (fd >= 0) {
72065668Skris		ret = fstat(fd, &st);
72165668Skris		if (ret < 0) {
72265668Skris			status = errno_to_portable(errno);
72365668Skris		} else {
72476259Sgreen			stat_to_attrib(&st, &a);
72576259Sgreen			send_attrib(id, &a);
72676259Sgreen			status = SSH2_FX_OK;
72765668Skris		}
72865668Skris	}
72976259Sgreen	if (status != SSH2_FX_OK)
73065668Skris		send_status(id, status);
73165668Skris}
73265668Skris
73392555Sdesstatic struct timeval *
734126274Sdesattrib_to_tv(const Attrib *a)
73565668Skris{
73665668Skris	static struct timeval tv[2];
73776259Sgreen
73865668Skris	tv[0].tv_sec = a->atime;
73965668Skris	tv[0].tv_usec = 0;
74065668Skris	tv[1].tv_sec = a->mtime;
74165668Skris	tv[1].tv_usec = 0;
74265668Skris	return tv;
74365668Skris}
74465668Skris
74592555Sdesstatic void
74665668Skrisprocess_setstat(void)
74765668Skris{
74865668Skris	Attrib *a;
74965668Skris	u_int32_t id;
75065668Skris	char *name;
75199060Sdes	int status = SSH2_FX_OK, ret;
75265668Skris
75365668Skris	id = get_int();
75465668Skris	name = get_string(NULL);
75565668Skris	a = get_attrib();
756162852Sdes	debug("request %u: setstat name \"%s\"", id, name);
75792555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
758181111Sdes		logit("set \"%s\" size %llu",
759181111Sdes		    name, (unsigned long long)a->size);
76092555Sdes		ret = truncate(name, a->size);
76192555Sdes		if (ret == -1)
76292555Sdes			status = errno_to_portable(errno);
76392555Sdes	}
76476259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
765162852Sdes		logit("set \"%s\" mode %04o", name, a->perm);
766181111Sdes		ret = chmod(name, a->perm & 07777);
76765668Skris		if (ret == -1)
76865668Skris			status = errno_to_portable(errno);
76965668Skris	}
77076259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
771162852Sdes		char buf[64];
772162852Sdes		time_t t = a->mtime;
773162852Sdes
774162852Sdes		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
775162852Sdes		    localtime(&t));
776162852Sdes		logit("set \"%s\" modtime %s", name, buf);
77765668Skris		ret = utimes(name, attrib_to_tv(a));
77865668Skris		if (ret == -1)
77965668Skris			status = errno_to_portable(errno);
78065668Skris	}
78176259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
782162852Sdes		logit("set \"%s\" owner %lu group %lu", name,
783162852Sdes		    (u_long)a->uid, (u_long)a->gid);
78476259Sgreen		ret = chown(name, a->uid, a->gid);
78576259Sgreen		if (ret == -1)
78676259Sgreen			status = errno_to_portable(errno);
78776259Sgreen	}
78865668Skris	send_status(id, status);
78965668Skris	xfree(name);
79065668Skris}
79165668Skris
79292555Sdesstatic void
79365668Skrisprocess_fsetstat(void)
79465668Skris{
79565668Skris	Attrib *a;
79665668Skris	u_int32_t id;
79765668Skris	int handle, fd, ret;
79876259Sgreen	int status = SSH2_FX_OK;
79965668Skris
80065668Skris	id = get_int();
80165668Skris	handle = get_handle();
80265668Skris	a = get_attrib();
803162852Sdes	debug("request %u: fsetstat handle %d", id, handle);
80465668Skris	fd = handle_to_fd(handle);
805162852Sdes	if (fd < 0) {
80676259Sgreen		status = SSH2_FX_FAILURE;
80765668Skris	} else {
808162852Sdes		char *name = handle_to_name(handle);
809162852Sdes
81092555Sdes		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
811181111Sdes			logit("set \"%s\" size %llu",
812181111Sdes			    name, (unsigned long long)a->size);
81392555Sdes			ret = ftruncate(fd, a->size);
81492555Sdes			if (ret == -1)
81592555Sdes				status = errno_to_portable(errno);
81692555Sdes		}
81776259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
818162852Sdes			logit("set \"%s\" mode %04o", name, a->perm);
81998937Sdes#ifdef HAVE_FCHMOD
820181111Sdes			ret = fchmod(fd, a->perm & 07777);
82198937Sdes#else
822181111Sdes			ret = chmod(name, a->perm & 07777);
82398937Sdes#endif
82465668Skris			if (ret == -1)
82565668Skris				status = errno_to_portable(errno);
82665668Skris		}
82776259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
828162852Sdes			char buf[64];
829162852Sdes			time_t t = a->mtime;
830162852Sdes
831162852Sdes			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
832162852Sdes			    localtime(&t));
833162852Sdes			logit("set \"%s\" modtime %s", name, buf);
83498937Sdes#ifdef HAVE_FUTIMES
83565668Skris			ret = futimes(fd, attrib_to_tv(a));
83698937Sdes#else
83798937Sdes			ret = utimes(name, attrib_to_tv(a));
83898937Sdes#endif
83965668Skris			if (ret == -1)
84065668Skris				status = errno_to_portable(errno);
84165668Skris		}
84276259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
843162852Sdes			logit("set \"%s\" owner %lu group %lu", name,
844162852Sdes			    (u_long)a->uid, (u_long)a->gid);
84598937Sdes#ifdef HAVE_FCHOWN
84676259Sgreen			ret = fchown(fd, a->uid, a->gid);
84798937Sdes#else
84898937Sdes			ret = chown(name, a->uid, a->gid);
84998937Sdes#endif
85076259Sgreen			if (ret == -1)
85176259Sgreen				status = errno_to_portable(errno);
85276259Sgreen		}
85365668Skris	}
85465668Skris	send_status(id, status);
85565668Skris}
85665668Skris
85792555Sdesstatic void
85865668Skrisprocess_opendir(void)
85965668Skris{
86065668Skris	DIR *dirp = NULL;
86165668Skris	char *path;
86276259Sgreen	int handle, status = SSH2_FX_FAILURE;
86365668Skris	u_int32_t id;
86465668Skris
86565668Skris	id = get_int();
86665668Skris	path = get_string(NULL);
867162852Sdes	debug3("request %u: opendir", id);
868162852Sdes	logit("opendir \"%s\"", path);
86976259Sgreen	dirp = opendir(path);
87065668Skris	if (dirp == NULL) {
87165668Skris		status = errno_to_portable(errno);
87265668Skris	} else {
873113908Sdes		handle = handle_new(HANDLE_DIR, path, 0, dirp);
87465668Skris		if (handle < 0) {
87565668Skris			closedir(dirp);
87665668Skris		} else {
87765668Skris			send_handle(id, handle);
87876259Sgreen			status = SSH2_FX_OK;
87965668Skris		}
88076259Sgreen
88165668Skris	}
88276259Sgreen	if (status != SSH2_FX_OK)
88365668Skris		send_status(id, status);
88465668Skris	xfree(path);
88565668Skris}
88665668Skris
88792555Sdesstatic void
88865668Skrisprocess_readdir(void)
88965668Skris{
89065668Skris	DIR *dirp;
89165668Skris	struct dirent *dp;
89265668Skris	char *path;
89365668Skris	int handle;
89465668Skris	u_int32_t id;
89565668Skris
89665668Skris	id = get_int();
89765668Skris	handle = get_handle();
898162852Sdes	debug("request %u: readdir \"%s\" (handle %d)", id,
899162852Sdes	    handle_to_name(handle), handle);
90065668Skris	dirp = handle_to_dir(handle);
90165668Skris	path = handle_to_name(handle);
90265668Skris	if (dirp == NULL || path == NULL) {
90376259Sgreen		send_status(id, SSH2_FX_FAILURE);
90465668Skris	} else {
90565668Skris		struct stat st;
906162852Sdes		char pathname[MAXPATHLEN];
90765668Skris		Stat *stats;
90865668Skris		int nstats = 10, count = 0, i;
90999060Sdes
910162852Sdes		stats = xcalloc(nstats, sizeof(Stat));
91165668Skris		while ((dp = readdir(dirp)) != NULL) {
91265668Skris			if (count >= nstats) {
91365668Skris				nstats *= 2;
914162852Sdes				stats = xrealloc(stats, nstats, sizeof(Stat));
91565668Skris			}
91665668Skris/* XXX OVERFLOW ? */
91792555Sdes			snprintf(pathname, sizeof pathname, "%s%s%s", path,
91892555Sdes			    strcmp(path, "/") ? "/" : "", dp->d_name);
91965668Skris			if (lstat(pathname, &st) < 0)
92065668Skris				continue;
92176259Sgreen			stat_to_attrib(&st, &(stats[count].attrib));
92265668Skris			stats[count].name = xstrdup(dp->d_name);
923106121Sdes			stats[count].long_name = ls_file(dp->d_name, &st, 0);
92465668Skris			count++;
92565668Skris			/* send up to 100 entries in one message */
92676259Sgreen			/* XXX check packet size instead */
92765668Skris			if (count == 100)
92865668Skris				break;
92965668Skris		}
93076259Sgreen		if (count > 0) {
93176259Sgreen			send_names(id, count, stats);
93292555Sdes			for (i = 0; i < count; i++) {
93376259Sgreen				xfree(stats[i].name);
93476259Sgreen				xfree(stats[i].long_name);
93576259Sgreen			}
93676259Sgreen		} else {
93776259Sgreen			send_status(id, SSH2_FX_EOF);
93865668Skris		}
93965668Skris		xfree(stats);
94065668Skris	}
94165668Skris}
94265668Skris
94392555Sdesstatic void
94465668Skrisprocess_remove(void)
94565668Skris{
94665668Skris	char *name;
94765668Skris	u_int32_t id;
94876259Sgreen	int status = SSH2_FX_FAILURE;
94965668Skris	int ret;
95065668Skris
95165668Skris	id = get_int();
95265668Skris	name = get_string(NULL);
953162852Sdes	debug3("request %u: remove", id);
954162852Sdes	logit("remove name \"%s\"", name);
95576259Sgreen	ret = unlink(name);
95676259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
95765668Skris	send_status(id, status);
95865668Skris	xfree(name);
95965668Skris}
96065668Skris
96192555Sdesstatic void
96265668Skrisprocess_mkdir(void)
96365668Skris{
96465668Skris	Attrib *a;
96565668Skris	u_int32_t id;
96665668Skris	char *name;
96776259Sgreen	int ret, mode, status = SSH2_FX_FAILURE;
96865668Skris
96965668Skris	id = get_int();
97065668Skris	name = get_string(NULL);
97165668Skris	a = get_attrib();
97276259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
973181111Sdes	    a->perm & 07777 : 0777;
974162852Sdes	debug3("request %u: mkdir", id);
975162852Sdes	logit("mkdir name \"%s\" mode 0%o", name, mode);
97665668Skris	ret = mkdir(name, mode);
97776259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
97865668Skris	send_status(id, status);
97965668Skris	xfree(name);
98065668Skris}
98165668Skris
98292555Sdesstatic void
98365668Skrisprocess_rmdir(void)
98465668Skris{
98565668Skris	u_int32_t id;
98665668Skris	char *name;
98765668Skris	int ret, status;
98865668Skris
98965668Skris	id = get_int();
99065668Skris	name = get_string(NULL);
991162852Sdes	debug3("request %u: rmdir", id);
992162852Sdes	logit("rmdir name \"%s\"", name);
99365668Skris	ret = rmdir(name);
99476259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
99565668Skris	send_status(id, status);
99665668Skris	xfree(name);
99765668Skris}
99865668Skris
99992555Sdesstatic void
100065668Skrisprocess_realpath(void)
100165668Skris{
100265668Skris	char resolvedname[MAXPATHLEN];
100365668Skris	u_int32_t id;
100465668Skris	char *path;
100565668Skris
100665668Skris	id = get_int();
100765668Skris	path = get_string(NULL);
100876259Sgreen	if (path[0] == '\0') {
100976259Sgreen		xfree(path);
101076259Sgreen		path = xstrdup(".");
101176259Sgreen	}
1012162852Sdes	debug3("request %u: realpath", id);
1013162852Sdes	verbose("realpath \"%s\"", path);
101465668Skris	if (realpath(path, resolvedname) == NULL) {
101565668Skris		send_status(id, errno_to_portable(errno));
101665668Skris	} else {
101765668Skris		Stat s;
101865668Skris		attrib_clear(&s.attrib);
101965668Skris		s.name = s.long_name = resolvedname;
102065668Skris		send_names(id, 1, &s);
102165668Skris	}
102265668Skris	xfree(path);
102365668Skris}
102465668Skris
102592555Sdesstatic void
102665668Skrisprocess_rename(void)
102765668Skris{
102865668Skris	u_int32_t id;
102965668Skris	char *oldpath, *newpath;
1030113908Sdes	int status;
1031113908Sdes	struct stat sb;
103265668Skris
103365668Skris	id = get_int();
103465668Skris	oldpath = get_string(NULL);
103565668Skris	newpath = get_string(NULL);
1036162852Sdes	debug3("request %u: rename", id);
1037162852Sdes	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1038113908Sdes	status = SSH2_FX_FAILURE;
1039113908Sdes	if (lstat(oldpath, &sb) == -1)
1040113908Sdes		status = errno_to_portable(errno);
1041113908Sdes	else if (S_ISREG(sb.st_mode)) {
1042113908Sdes		/* Race-free rename of regular files */
1043137015Sdes		if (link(oldpath, newpath) == -1) {
1044197679Sdes			if (errno == EOPNOTSUPP || errno == ENOSYS
1045181111Sdes#ifdef EXDEV
1046181111Sdes			    || errno == EXDEV
1047181111Sdes#endif
1048137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO
1049137015Sdes			    || errno == LINK_OPNOTSUPP_ERRNO
1050137015Sdes#endif
1051137015Sdes			    ) {
1052137015Sdes				struct stat st;
1053137015Sdes
1054137015Sdes				/*
1055137015Sdes				 * fs doesn't support links, so fall back to
1056137015Sdes				 * stat+rename.  This is racy.
1057137015Sdes				 */
1058137015Sdes				if (stat(newpath, &st) == -1) {
1059137015Sdes					if (rename(oldpath, newpath) == -1)
1060137015Sdes						status =
1061137015Sdes						    errno_to_portable(errno);
1062137015Sdes					else
1063137015Sdes						status = SSH2_FX_OK;
1064137015Sdes				}
1065137015Sdes			} else {
1066137015Sdes				status = errno_to_portable(errno);
1067137015Sdes			}
1068137015Sdes		} else if (unlink(oldpath) == -1) {
1069113908Sdes			status = errno_to_portable(errno);
1070113908Sdes			/* clean spare link */
1071113908Sdes			unlink(newpath);
1072113908Sdes		} else
1073113908Sdes			status = SSH2_FX_OK;
1074113908Sdes	} else if (stat(newpath, &sb) == -1) {
1075113908Sdes		if (rename(oldpath, newpath) == -1)
1076113908Sdes			status = errno_to_portable(errno);
1077113908Sdes		else
1078113908Sdes			status = SSH2_FX_OK;
107976259Sgreen	}
108065668Skris	send_status(id, status);
108165668Skris	xfree(oldpath);
108265668Skris	xfree(newpath);
108365668Skris}
108465668Skris
108592555Sdesstatic void
108676259Sgreenprocess_readlink(void)
108776259Sgreen{
108876259Sgreen	u_int32_t id;
108992555Sdes	int len;
1090137015Sdes	char buf[MAXPATHLEN];
109176259Sgreen	char *path;
109265668Skris
109376259Sgreen	id = get_int();
109476259Sgreen	path = get_string(NULL);
1095162852Sdes	debug3("request %u: readlink", id);
1096162852Sdes	verbose("readlink \"%s\"", path);
1097137015Sdes	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
109876259Sgreen		send_status(id, errno_to_portable(errno));
109976259Sgreen	else {
110076259Sgreen		Stat s;
110192555Sdes
1102137015Sdes		buf[len] = '\0';
110376259Sgreen		attrib_clear(&s.attrib);
1104137015Sdes		s.name = s.long_name = buf;
110576259Sgreen		send_names(id, 1, &s);
110676259Sgreen	}
110776259Sgreen	xfree(path);
110876259Sgreen}
110976259Sgreen
111092555Sdesstatic void
111176259Sgreenprocess_symlink(void)
111276259Sgreen{
111376259Sgreen	u_int32_t id;
111476259Sgreen	char *oldpath, *newpath;
1115113908Sdes	int ret, status;
111676259Sgreen
111776259Sgreen	id = get_int();
111876259Sgreen	oldpath = get_string(NULL);
111976259Sgreen	newpath = get_string(NULL);
1120162852Sdes	debug3("request %u: symlink", id);
1121162852Sdes	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1122113908Sdes	/* this will fail if 'newpath' exists */
1123113908Sdes	ret = symlink(oldpath, newpath);
1124113908Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
112576259Sgreen	send_status(id, status);
112676259Sgreen	xfree(oldpath);
112776259Sgreen	xfree(newpath);
112876259Sgreen}
112976259Sgreen
113092555Sdesstatic void
1131181111Sdesprocess_extended_posix_rename(u_int32_t id)
1132181111Sdes{
1133181111Sdes	char *oldpath, *newpath;
1134181111Sdes
1135181111Sdes	oldpath = get_string(NULL);
1136181111Sdes	newpath = get_string(NULL);
1137181111Sdes	debug3("request %u: posix-rename", id);
1138181111Sdes	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1139181111Sdes	if (rename(oldpath, newpath) == -1)
1140181111Sdes		send_status(id, errno_to_portable(errno));
1141181111Sdes	else
1142181111Sdes		send_status(id, SSH2_FX_OK);
1143181111Sdes	xfree(oldpath);
1144181111Sdes	xfree(newpath);
1145181111Sdes}
1146181111Sdes
1147181111Sdesstatic void
1148181111Sdesprocess_extended_statvfs(u_int32_t id)
1149181111Sdes{
1150181111Sdes	char *path;
1151181111Sdes	struct statvfs st;
1152181111Sdes
1153181111Sdes	path = get_string(NULL);
1154181111Sdes	debug3("request %u: statfs", id);
1155181111Sdes	logit("statfs \"%s\"", path);
1156181111Sdes
1157181111Sdes	if (statvfs(path, &st) != 0)
1158181111Sdes		send_status(id, errno_to_portable(errno));
1159181111Sdes	else
1160181111Sdes		send_statvfs(id, &st);
1161181111Sdes        xfree(path);
1162181111Sdes}
1163181111Sdes
1164181111Sdesstatic void
1165181111Sdesprocess_extended_fstatvfs(u_int32_t id)
1166181111Sdes{
1167181111Sdes	int handle, fd;
1168181111Sdes	struct statvfs st;
1169181111Sdes
1170181111Sdes	handle = get_handle();
1171181111Sdes	debug("request %u: fstatvfs \"%s\" (handle %u)",
1172181111Sdes	    id, handle_to_name(handle), handle);
1173181111Sdes	if ((fd = handle_to_fd(handle)) < 0) {
1174181111Sdes		send_status(id, SSH2_FX_FAILURE);
1175181111Sdes		return;
1176181111Sdes	}
1177181111Sdes	if (fstatvfs(fd, &st) != 0)
1178181111Sdes		send_status(id, errno_to_portable(errno));
1179181111Sdes	else
1180181111Sdes		send_statvfs(id, &st);
1181181111Sdes}
1182181111Sdes
1183181111Sdesstatic void
118476259Sgreenprocess_extended(void)
118576259Sgreen{
118676259Sgreen	u_int32_t id;
118776259Sgreen	char *request;
118876259Sgreen
118976259Sgreen	id = get_int();
119076259Sgreen	request = get_string(NULL);
1191181111Sdes	if (strcmp(request, "posix-rename@openssh.com") == 0)
1192181111Sdes		process_extended_posix_rename(id);
1193181111Sdes	else if (strcmp(request, "statvfs@openssh.com") == 0)
1194181111Sdes		process_extended_statvfs(id);
1195181111Sdes	else if (strcmp(request, "fstatvfs@openssh.com") == 0)
1196181111Sdes		process_extended_fstatvfs(id);
1197181111Sdes	else
1198181111Sdes		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
119976259Sgreen	xfree(request);
120076259Sgreen}
120176259Sgreen
120265668Skris/* stolen from ssh-agent */
120365668Skris
120492555Sdesstatic void
120565668Skrisprocess(void)
120665668Skris{
120776259Sgreen	u_int msg_len;
120898675Sdes	u_int buf_len;
120998675Sdes	u_int consumed;
121076259Sgreen	u_int type;
121176259Sgreen	u_char *cp;
121265668Skris
121398675Sdes	buf_len = buffer_len(&iqueue);
121498675Sdes	if (buf_len < 5)
121565668Skris		return;		/* Incomplete message. */
121692555Sdes	cp = buffer_ptr(&iqueue);
1217162852Sdes	msg_len = get_u32(cp);
1218157016Sdes	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1219162852Sdes		error("bad message from %s local user %s",
1220162852Sdes		    client_addr, pw->pw_name);
1221181111Sdes		sftp_server_cleanup_exit(11);
122265668Skris	}
122398675Sdes	if (buf_len < msg_len + 4)
122465668Skris		return;
122565668Skris	buffer_consume(&iqueue, 4);
122698675Sdes	buf_len -= 4;
122765668Skris	type = buffer_get_char(&iqueue);
122865668Skris	switch (type) {
122976259Sgreen	case SSH2_FXP_INIT:
123065668Skris		process_init();
123165668Skris		break;
123276259Sgreen	case SSH2_FXP_OPEN:
123365668Skris		process_open();
123465668Skris		break;
123576259Sgreen	case SSH2_FXP_CLOSE:
123665668Skris		process_close();
123765668Skris		break;
123876259Sgreen	case SSH2_FXP_READ:
123965668Skris		process_read();
124065668Skris		break;
124176259Sgreen	case SSH2_FXP_WRITE:
124265668Skris		process_write();
124365668Skris		break;
124476259Sgreen	case SSH2_FXP_LSTAT:
124565668Skris		process_lstat();
124665668Skris		break;
124776259Sgreen	case SSH2_FXP_FSTAT:
124865668Skris		process_fstat();
124965668Skris		break;
125076259Sgreen	case SSH2_FXP_SETSTAT:
125165668Skris		process_setstat();
125265668Skris		break;
125376259Sgreen	case SSH2_FXP_FSETSTAT:
125465668Skris		process_fsetstat();
125565668Skris		break;
125676259Sgreen	case SSH2_FXP_OPENDIR:
125765668Skris		process_opendir();
125865668Skris		break;
125976259Sgreen	case SSH2_FXP_READDIR:
126065668Skris		process_readdir();
126165668Skris		break;
126276259Sgreen	case SSH2_FXP_REMOVE:
126365668Skris		process_remove();
126465668Skris		break;
126576259Sgreen	case SSH2_FXP_MKDIR:
126665668Skris		process_mkdir();
126765668Skris		break;
126876259Sgreen	case SSH2_FXP_RMDIR:
126965668Skris		process_rmdir();
127065668Skris		break;
127176259Sgreen	case SSH2_FXP_REALPATH:
127265668Skris		process_realpath();
127365668Skris		break;
127476259Sgreen	case SSH2_FXP_STAT:
127565668Skris		process_stat();
127665668Skris		break;
127776259Sgreen	case SSH2_FXP_RENAME:
127865668Skris		process_rename();
127965668Skris		break;
128076259Sgreen	case SSH2_FXP_READLINK:
128176259Sgreen		process_readlink();
128276259Sgreen		break;
128376259Sgreen	case SSH2_FXP_SYMLINK:
128476259Sgreen		process_symlink();
128576259Sgreen		break;
128676259Sgreen	case SSH2_FXP_EXTENDED:
128776259Sgreen		process_extended();
128876259Sgreen		break;
128965668Skris	default:
129065668Skris		error("Unknown message %d", type);
129165668Skris		break;
129265668Skris	}
129398675Sdes	/* discard the remaining bytes from the current packet */
1294181111Sdes	if (buf_len < buffer_len(&iqueue)) {
1295181111Sdes		error("iqueue grew unexpectedly");
1296181111Sdes		sftp_server_cleanup_exit(255);
1297181111Sdes	}
129898675Sdes	consumed = buf_len - buffer_len(&iqueue);
1299181111Sdes	if (msg_len < consumed) {
1300181111Sdes		error("msg_len %d < consumed %d", msg_len, consumed);
1301181111Sdes		sftp_server_cleanup_exit(255);
1302181111Sdes	}
130398675Sdes	if (msg_len > consumed)
130498675Sdes		buffer_consume(&iqueue, msg_len - consumed);
130565668Skris}
130665668Skris
1307162852Sdes/* Cleanup handler that logs active handles upon normal exit */
1308162852Sdesvoid
1309181111Sdessftp_server_cleanup_exit(int i)
1310162852Sdes{
1311162852Sdes	if (pw != NULL && client_addr != NULL) {
1312162852Sdes		handle_log_exit();
1313162852Sdes		logit("session closed for local user %s from [%s]",
1314162852Sdes		    pw->pw_name, client_addr);
1315162852Sdes	}
1316162852Sdes	_exit(i);
1317162852Sdes}
1318162852Sdes
1319162852Sdesstatic void
1320181111Sdessftp_server_usage(void)
1321162852Sdes{
1322162852Sdes	extern char *__progname;
1323162852Sdes
1324162852Sdes	fprintf(stderr,
1325162852Sdes	    "usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname);
1326162852Sdes	exit(1);
1327162852Sdes}
1328162852Sdes
132965668Skrisint
1330181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw)
133165668Skris{
133276259Sgreen	fd_set *rset, *wset;
1333162852Sdes	int in, out, max, ch, skipargs = 0, log_stderr = 0;
133476259Sgreen	ssize_t len, olen, set_size;
1335162852Sdes	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1336181111Sdes	char *cp, buf[4*4096];
133765668Skris
1338162852Sdes	extern char *optarg;
1339162852Sdes	extern char *__progname;
1340162852Sdes
1341162852Sdes	__progname = ssh_get_progname(argv[0]);
1342162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
134376259Sgreen
1344197679Sdes	while (!skipargs && (ch = getopt(argc, argv, "f:l:che")) != -1) {
1345162852Sdes		switch (ch) {
1346162852Sdes		case 'c':
1347162852Sdes			/*
1348162852Sdes			 * Ignore all arguments if we are invoked as a
1349162852Sdes			 * shell using "sftp-server -c command"
1350162852Sdes			 */
1351162852Sdes			skipargs = 1;
1352162852Sdes			break;
1353162852Sdes		case 'e':
1354162852Sdes			log_stderr = 1;
1355162852Sdes			break;
1356162852Sdes		case 'l':
1357162852Sdes			log_level = log_level_number(optarg);
1358162852Sdes			if (log_level == SYSLOG_LEVEL_NOT_SET)
1359162852Sdes				error("Invalid log level \"%s\"", optarg);
1360162852Sdes			break;
1361162852Sdes		case 'f':
1362162852Sdes			log_facility = log_facility_number(optarg);
1363181111Sdes			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1364162852Sdes				error("Invalid log facility \"%s\"", optarg);
1365162852Sdes			break;
1366162852Sdes		case 'h':
1367162852Sdes		default:
1368181111Sdes			sftp_server_usage();
1369162852Sdes		}
1370162852Sdes	}
1371162852Sdes
1372162852Sdes	log_init(__progname, log_level, log_facility, log_stderr);
1373162852Sdes
1374162852Sdes	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1375162852Sdes		client_addr = xstrdup(cp);
1376181111Sdes		if ((cp = strchr(client_addr, ' ')) == NULL) {
1377181111Sdes			error("Malformed SSH_CONNECTION variable: \"%s\"",
1378162852Sdes			    getenv("SSH_CONNECTION"));
1379181111Sdes			sftp_server_cleanup_exit(255);
1380181111Sdes		}
1381162852Sdes		*cp = '\0';
1382162852Sdes	} else
1383162852Sdes		client_addr = xstrdup("UNKNOWN");
1384162852Sdes
1385181111Sdes	pw = pwcopy(user_pw);
1386162852Sdes
1387162852Sdes	logit("session opened for local user %s from [%s]",
1388162852Sdes	    pw->pw_name, client_addr);
1389162852Sdes
139065668Skris	in = dup(STDIN_FILENO);
139165668Skris	out = dup(STDOUT_FILENO);
139265668Skris
139398937Sdes#ifdef HAVE_CYGWIN
139498937Sdes	setmode(in, O_BINARY);
139598937Sdes	setmode(out, O_BINARY);
139698937Sdes#endif
139798937Sdes
139865668Skris	max = 0;
139965668Skris	if (in > max)
140065668Skris		max = in;
140165668Skris	if (out > max)
140265668Skris		max = out;
140365668Skris
140465668Skris	buffer_init(&iqueue);
140565668Skris	buffer_init(&oqueue);
140665668Skris
140776259Sgreen	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
140876259Sgreen	rset = (fd_set *)xmalloc(set_size);
140976259Sgreen	wset = (fd_set *)xmalloc(set_size);
141076259Sgreen
141165668Skris	for (;;) {
141276259Sgreen		memset(rset, 0, set_size);
141376259Sgreen		memset(wset, 0, set_size);
141465668Skris
1415181111Sdes		/*
1416181111Sdes		 * Ensure that we can read a full buffer and handle
1417181111Sdes		 * the worst-case length packet it can generate,
1418181111Sdes		 * otherwise apply backpressure by stopping reads.
1419181111Sdes		 */
1420181111Sdes		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
1421181111Sdes		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1422181111Sdes			FD_SET(in, rset);
1423181111Sdes
142465668Skris		olen = buffer_len(&oqueue);
142565668Skris		if (olen > 0)
142676259Sgreen			FD_SET(out, wset);
142765668Skris
142876259Sgreen		if (select(max+1, rset, wset, NULL, NULL) < 0) {
142965668Skris			if (errno == EINTR)
143065668Skris				continue;
1431162852Sdes			error("select: %s", strerror(errno));
1432181111Sdes			sftp_server_cleanup_exit(2);
143365668Skris		}
143465668Skris
143565668Skris		/* copy stdin to iqueue */
143676259Sgreen		if (FD_ISSET(in, rset)) {
143765668Skris			len = read(in, buf, sizeof buf);
143865668Skris			if (len == 0) {
143965668Skris				debug("read eof");
1440181111Sdes				sftp_server_cleanup_exit(0);
144165668Skris			} else if (len < 0) {
1442162852Sdes				error("read: %s", strerror(errno));
1443181111Sdes				sftp_server_cleanup_exit(1);
144465668Skris			} else {
144565668Skris				buffer_append(&iqueue, buf, len);
144665668Skris			}
144765668Skris		}
144865668Skris		/* send oqueue to stdout */
144976259Sgreen		if (FD_ISSET(out, wset)) {
145065668Skris			len = write(out, buffer_ptr(&oqueue), olen);
145165668Skris			if (len < 0) {
1452162852Sdes				error("write: %s", strerror(errno));
1453181111Sdes				sftp_server_cleanup_exit(1);
145465668Skris			} else {
145565668Skris				buffer_consume(&oqueue, len);
145665668Skris			}
145765668Skris		}
1458181111Sdes
1459181111Sdes		/*
1460181111Sdes		 * Process requests from client if we can fit the results
1461181111Sdes		 * into the output buffer, otherwise stop processing input
1462181111Sdes		 * and let the output queue drain.
1463181111Sdes		 */
1464181111Sdes		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1465181111Sdes			process();
146665668Skris	}
146765668Skris}
1468