sftp-server.c revision 126274
165668Skris/*
2126274Sdes * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
365668Skris *
4126274Sdes * Permission to use, copy, modify, and distribute this software for any
5126274Sdes * purpose with or without fee is hereby granted, provided that the above
6126274Sdes * copyright notice and this permission notice appear in all copies.
765668Skris *
8126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1565668Skris */
1665668Skris#include "includes.h"
17126274SdesRCSID("$OpenBSD: sftp-server.c,v 1.45 2004/02/19 21:15:04 markus Exp $");
1865668Skris
1965668Skris#include "buffer.h"
2065668Skris#include "bufaux.h"
2165668Skris#include "getput.h"
2276259Sgreen#include "log.h"
2365668Skris#include "xmalloc.h"
2465668Skris
2576259Sgreen#include "sftp.h"
2676259Sgreen#include "sftp-common.h"
2765668Skris
2865668Skris/* helper */
2976259Sgreen#define get_int64()			buffer_get_int64(&iqueue);
3065668Skris#define get_int()			buffer_get_int(&iqueue);
3165668Skris#define get_string(lenp)		buffer_get_string(&iqueue, lenp);
3276259Sgreen#define TRACE				debug
3365668Skris
3498937Sdes#ifdef HAVE___PROGNAME
3598937Sdesextern char *__progname;
3698937Sdes#else
3798937Sdeschar *__progname;
3898937Sdes#endif
3998937Sdes
4065668Skris/* input and output queue */
4165668SkrisBuffer iqueue;
4265668SkrisBuffer oqueue;
4365668Skris
4476259Sgreen/* Version of client */
4576259Sgreenint version;
4676259Sgreen
47124208Sdes/* portable attributes, etc. */
4865668Skris
4965668Skristypedef struct Stat Stat;
5065668Skris
5176259Sgreenstruct Stat {
5265668Skris	char *name;
5365668Skris	char *long_name;
5465668Skris	Attrib attrib;
5565668Skris};
5665668Skris
5792555Sdesstatic int
5865668Skriserrno_to_portable(int unixerrno)
5965668Skris{
6065668Skris	int ret = 0;
6176259Sgreen
6265668Skris	switch (unixerrno) {
6365668Skris	case 0:
6476259Sgreen		ret = SSH2_FX_OK;
6565668Skris		break;
6665668Skris	case ENOENT:
6765668Skris	case ENOTDIR:
6865668Skris	case EBADF:
6965668Skris	case ELOOP:
7076259Sgreen		ret = SSH2_FX_NO_SUCH_FILE;
7165668Skris		break;
7265668Skris	case EPERM:
7365668Skris	case EACCES:
7465668Skris	case EFAULT:
7576259Sgreen		ret = SSH2_FX_PERMISSION_DENIED;
7665668Skris		break;
7765668Skris	case ENAMETOOLONG:
7865668Skris	case EINVAL:
7976259Sgreen		ret = SSH2_FX_BAD_MESSAGE;
8065668Skris		break;
8165668Skris	default:
8276259Sgreen		ret = SSH2_FX_FAILURE;
8365668Skris		break;
8465668Skris	}
8565668Skris	return ret;
8665668Skris}
8765668Skris
8892555Sdesstatic int
8965668Skrisflags_from_portable(int pflags)
9065668Skris{
9165668Skris	int flags = 0;
9276259Sgreen
9376259Sgreen	if ((pflags & SSH2_FXF_READ) &&
9476259Sgreen	    (pflags & SSH2_FXF_WRITE)) {
9565668Skris		flags = O_RDWR;
9676259Sgreen	} else if (pflags & SSH2_FXF_READ) {
9765668Skris		flags = O_RDONLY;
9876259Sgreen	} else if (pflags & SSH2_FXF_WRITE) {
9965668Skris		flags = O_WRONLY;
10065668Skris	}
10176259Sgreen	if (pflags & SSH2_FXF_CREAT)
10265668Skris		flags |= O_CREAT;
10376259Sgreen	if (pflags & SSH2_FXF_TRUNC)
10465668Skris		flags |= O_TRUNC;
10576259Sgreen	if (pflags & SSH2_FXF_EXCL)
10665668Skris		flags |= O_EXCL;
10765668Skris	return flags;
10865668Skris}
10965668Skris
11092555Sdesstatic Attrib *
11165668Skrisget_attrib(void)
11265668Skris{
11365668Skris	return decode_attrib(&iqueue);
11465668Skris}
11565668Skris
11665668Skris/* handle handles */
11765668Skris
11865668Skristypedef struct Handle Handle;
11965668Skrisstruct Handle {
12065668Skris	int use;
12165668Skris	DIR *dirp;
12265668Skris	int fd;
12365668Skris	char *name;
12465668Skris};
12576259Sgreen
12665668Skrisenum {
12765668Skris	HANDLE_UNUSED,
12865668Skris	HANDLE_DIR,
12965668Skris	HANDLE_FILE
13065668Skris};
13176259Sgreen
13265668SkrisHandle	handles[100];
13365668Skris
13492555Sdesstatic void
13565668Skrishandle_init(void)
13665668Skris{
13765668Skris	int i;
13876259Sgreen
13992555Sdes	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
14065668Skris		handles[i].use = HANDLE_UNUSED;
14165668Skris}
14265668Skris
14392555Sdesstatic int
144126274Sdeshandle_new(int use, const char *name, int fd, DIR *dirp)
14565668Skris{
14665668Skris	int i;
14776259Sgreen
14892555Sdes	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) {
14965668Skris		if (handles[i].use == HANDLE_UNUSED) {
15065668Skris			handles[i].use = use;
15165668Skris			handles[i].dirp = dirp;
15265668Skris			handles[i].fd = fd;
153113908Sdes			handles[i].name = xstrdup(name);
15465668Skris			return i;
15565668Skris		}
15665668Skris	}
15765668Skris	return -1;
15865668Skris}
15965668Skris
16092555Sdesstatic int
16165668Skrishandle_is_ok(int i, int type)
16265668Skris{
16376259Sgreen	return i >= 0 && i < sizeof(handles)/sizeof(Handle) &&
16476259Sgreen	    handles[i].use == type;
16565668Skris}
16665668Skris
16792555Sdesstatic int
16865668Skrishandle_to_string(int handle, char **stringp, int *hlenp)
16965668Skris{
17065668Skris	if (stringp == NULL || hlenp == NULL)
17165668Skris		return -1;
17276259Sgreen	*stringp = xmalloc(sizeof(int32_t));
17376259Sgreen	PUT_32BIT(*stringp, handle);
17476259Sgreen	*hlenp = sizeof(int32_t);
17565668Skris	return 0;
17665668Skris}
17765668Skris
17892555Sdesstatic int
179126274Sdeshandle_from_string(const char *handle, u_int hlen)
18065668Skris{
18176259Sgreen	int val;
18276259Sgreen
18376259Sgreen	if (hlen != sizeof(int32_t))
18465668Skris		return -1;
18576259Sgreen	val = GET_32BIT(handle);
18665668Skris	if (handle_is_ok(val, HANDLE_FILE) ||
18765668Skris	    handle_is_ok(val, HANDLE_DIR))
18865668Skris		return val;
18965668Skris	return -1;
19065668Skris}
19165668Skris
19292555Sdesstatic char *
19365668Skrishandle_to_name(int handle)
19465668Skris{
19565668Skris	if (handle_is_ok(handle, HANDLE_DIR)||
19665668Skris	    handle_is_ok(handle, HANDLE_FILE))
19765668Skris		return handles[handle].name;
19865668Skris	return NULL;
19965668Skris}
20065668Skris
20192555Sdesstatic DIR *
20265668Skrishandle_to_dir(int handle)
20365668Skris{
20465668Skris	if (handle_is_ok(handle, HANDLE_DIR))
20565668Skris		return handles[handle].dirp;
20665668Skris	return NULL;
20765668Skris}
20865668Skris
20992555Sdesstatic int
21065668Skrishandle_to_fd(int handle)
21165668Skris{
21276259Sgreen	if (handle_is_ok(handle, HANDLE_FILE))
21365668Skris		return handles[handle].fd;
21465668Skris	return -1;
21565668Skris}
21665668Skris
21792555Sdesstatic int
21865668Skrishandle_close(int handle)
21965668Skris{
22065668Skris	int ret = -1;
22176259Sgreen
22265668Skris	if (handle_is_ok(handle, HANDLE_FILE)) {
22365668Skris		ret = close(handles[handle].fd);
22465668Skris		handles[handle].use = HANDLE_UNUSED;
225113908Sdes		xfree(handles[handle].name);
22665668Skris	} else if (handle_is_ok(handle, HANDLE_DIR)) {
22765668Skris		ret = closedir(handles[handle].dirp);
22865668Skris		handles[handle].use = HANDLE_UNUSED;
229113908Sdes		xfree(handles[handle].name);
23065668Skris	} else {
23165668Skris		errno = ENOENT;
23265668Skris	}
23365668Skris	return ret;
23465668Skris}
23565668Skris
23692555Sdesstatic int
23765668Skrisget_handle(void)
23865668Skris{
23965668Skris	char *handle;
24076259Sgreen	int val = -1;
24165668Skris	u_int hlen;
24276259Sgreen
24365668Skris	handle = get_string(&hlen);
24476259Sgreen	if (hlen < 256)
24576259Sgreen		val = handle_from_string(handle, hlen);
24665668Skris	xfree(handle);
24765668Skris	return val;
24865668Skris}
24965668Skris
25065668Skris/* send replies */
25165668Skris
25292555Sdesstatic void
25365668Skrissend_msg(Buffer *m)
25465668Skris{
25565668Skris	int mlen = buffer_len(m);
25676259Sgreen
25765668Skris	buffer_put_int(&oqueue, mlen);
25865668Skris	buffer_append(&oqueue, buffer_ptr(m), mlen);
25965668Skris	buffer_consume(m, mlen);
26065668Skris}
26165668Skris
26292555Sdesstatic void
26365668Skrissend_status(u_int32_t id, u_int32_t error)
26465668Skris{
26565668Skris	Buffer msg;
26676259Sgreen	const char *status_messages[] = {
26776259Sgreen		"Success",			/* SSH_FX_OK */
26876259Sgreen		"End of file",			/* SSH_FX_EOF */
26976259Sgreen		"No such file",			/* SSH_FX_NO_SUCH_FILE */
27076259Sgreen		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
27176259Sgreen		"Failure",			/* SSH_FX_FAILURE */
27276259Sgreen		"Bad message",			/* SSH_FX_BAD_MESSAGE */
27376259Sgreen		"No connection",		/* SSH_FX_NO_CONNECTION */
27476259Sgreen		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
27576259Sgreen		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
27676259Sgreen		"Unknown error"			/* Others */
27776259Sgreen	};
27876259Sgreen
27999060Sdes	TRACE("sent status id %u error %u", id, error);
28065668Skris	buffer_init(&msg);
28176259Sgreen	buffer_put_char(&msg, SSH2_FXP_STATUS);
28265668Skris	buffer_put_int(&msg, id);
28365668Skris	buffer_put_int(&msg, error);
28476259Sgreen	if (version >= 3) {
28576259Sgreen		buffer_put_cstring(&msg,
28676259Sgreen		    status_messages[MIN(error,SSH2_FX_MAX)]);
28776259Sgreen		buffer_put_cstring(&msg, "");
28876259Sgreen	}
28965668Skris	send_msg(&msg);
29065668Skris	buffer_free(&msg);
29165668Skris}
29292555Sdesstatic void
293126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
29465668Skris{
29565668Skris	Buffer msg;
29676259Sgreen
29765668Skris	buffer_init(&msg);
29865668Skris	buffer_put_char(&msg, type);
29965668Skris	buffer_put_int(&msg, id);
30065668Skris	buffer_put_string(&msg, data, dlen);
30165668Skris	send_msg(&msg);
30265668Skris	buffer_free(&msg);
30365668Skris}
30465668Skris
30592555Sdesstatic void
306126274Sdessend_data(u_int32_t id, const char *data, int dlen)
30765668Skris{
30899060Sdes	TRACE("sent data id %u len %d", id, dlen);
30976259Sgreen	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
31065668Skris}
31165668Skris
31292555Sdesstatic void
31365668Skrissend_handle(u_int32_t id, int handle)
31465668Skris{
31565668Skris	char *string;
31665668Skris	int hlen;
31776259Sgreen
31865668Skris	handle_to_string(handle, &string, &hlen);
31999060Sdes	TRACE("sent handle id %u handle %d", id, handle);
32076259Sgreen	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
32165668Skris	xfree(string);
32265668Skris}
32365668Skris
32492555Sdesstatic void
325126274Sdessend_names(u_int32_t id, int count, const Stat *stats)
32665668Skris{
32765668Skris	Buffer msg;
32865668Skris	int i;
32976259Sgreen
33065668Skris	buffer_init(&msg);
33176259Sgreen	buffer_put_char(&msg, SSH2_FXP_NAME);
33265668Skris	buffer_put_int(&msg, id);
33365668Skris	buffer_put_int(&msg, count);
33499060Sdes	TRACE("sent names id %u count %d", id, count);
33565668Skris	for (i = 0; i < count; i++) {
33665668Skris		buffer_put_cstring(&msg, stats[i].name);
33765668Skris		buffer_put_cstring(&msg, stats[i].long_name);
33865668Skris		encode_attrib(&msg, &stats[i].attrib);
33965668Skris	}
34065668Skris	send_msg(&msg);
34165668Skris	buffer_free(&msg);
34265668Skris}
34365668Skris
34492555Sdesstatic void
345126274Sdessend_attrib(u_int32_t id, const Attrib *a)
34665668Skris{
34765668Skris	Buffer msg;
34876259Sgreen
34999060Sdes	TRACE("sent attrib id %u have 0x%x", id, a->flags);
35065668Skris	buffer_init(&msg);
35176259Sgreen	buffer_put_char(&msg, SSH2_FXP_ATTRS);
35265668Skris	buffer_put_int(&msg, id);
35365668Skris	encode_attrib(&msg, a);
35465668Skris	send_msg(&msg);
35565668Skris	buffer_free(&msg);
35665668Skris}
35765668Skris
35865668Skris/* parse incoming */
35965668Skris
36092555Sdesstatic void
36165668Skrisprocess_init(void)
36265668Skris{
36365668Skris	Buffer msg;
36465668Skris
36598675Sdes	version = get_int();
36665668Skris	TRACE("client version %d", version);
36765668Skris	buffer_init(&msg);
36876259Sgreen	buffer_put_char(&msg, SSH2_FXP_VERSION);
36976259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
37065668Skris	send_msg(&msg);
37165668Skris	buffer_free(&msg);
37265668Skris}
37365668Skris
37492555Sdesstatic void
37565668Skrisprocess_open(void)
37665668Skris{
37765668Skris	u_int32_t id, pflags;
37865668Skris	Attrib *a;
37965668Skris	char *name;
38076259Sgreen	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
38165668Skris
38265668Skris	id = get_int();
38365668Skris	name = get_string(NULL);
38476259Sgreen	pflags = get_int();		/* portable flags */
38565668Skris	a = get_attrib();
38665668Skris	flags = flags_from_portable(pflags);
38776259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
38899060Sdes	TRACE("open id %u name %s flags %d mode 0%o", id, name, pflags, mode);
38965668Skris	fd = open(name, flags, mode);
39065668Skris	if (fd < 0) {
39165668Skris		status = errno_to_portable(errno);
39265668Skris	} else {
393113908Sdes		handle = handle_new(HANDLE_FILE, name, fd, NULL);
39465668Skris		if (handle < 0) {
39565668Skris			close(fd);
39665668Skris		} else {
39765668Skris			send_handle(id, handle);
39876259Sgreen			status = SSH2_FX_OK;
39965668Skris		}
40065668Skris	}
40176259Sgreen	if (status != SSH2_FX_OK)
40265668Skris		send_status(id, status);
40365668Skris	xfree(name);
40465668Skris}
40565668Skris
40692555Sdesstatic void
40765668Skrisprocess_close(void)
40865668Skris{
40965668Skris	u_int32_t id;
41076259Sgreen	int handle, ret, status = SSH2_FX_FAILURE;
41165668Skris
41265668Skris	id = get_int();
41365668Skris	handle = get_handle();
41499060Sdes	TRACE("close id %u handle %d", id, handle);
41565668Skris	ret = handle_close(handle);
41676259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
41765668Skris	send_status(id, status);
41865668Skris}
41965668Skris
42092555Sdesstatic void
42165668Skrisprocess_read(void)
42265668Skris{
42365668Skris	char buf[64*1024];
42476259Sgreen	u_int32_t id, len;
42576259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
42665668Skris	u_int64_t off;
42765668Skris
42865668Skris	id = get_int();
42965668Skris	handle = get_handle();
43076259Sgreen	off = get_int64();
43165668Skris	len = get_int();
43265668Skris
43399060Sdes	TRACE("read id %u handle %d off %llu len %d", id, handle,
43498937Sdes	    (u_int64_t)off, len);
43565668Skris	if (len > sizeof buf) {
43665668Skris		len = sizeof buf;
437124208Sdes		logit("read change len %d", len);
43865668Skris	}
43965668Skris	fd = handle_to_fd(handle);
44065668Skris	if (fd >= 0) {
44165668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
44265668Skris			error("process_read: seek failed");
44365668Skris			status = errno_to_portable(errno);
44465668Skris		} else {
44565668Skris			ret = read(fd, buf, len);
44665668Skris			if (ret < 0) {
44765668Skris				status = errno_to_portable(errno);
44865668Skris			} else if (ret == 0) {
44976259Sgreen				status = SSH2_FX_EOF;
45065668Skris			} else {
45165668Skris				send_data(id, buf, ret);
45276259Sgreen				status = SSH2_FX_OK;
45365668Skris			}
45465668Skris		}
45565668Skris	}
45676259Sgreen	if (status != SSH2_FX_OK)
45765668Skris		send_status(id, status);
45865668Skris}
45965668Skris
46092555Sdesstatic void
46165668Skrisprocess_write(void)
46265668Skris{
46376259Sgreen	u_int32_t id;
46465668Skris	u_int64_t off;
46565668Skris	u_int len;
46676259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
46765668Skris	char *data;
46865668Skris
46965668Skris	id = get_int();
47065668Skris	handle = get_handle();
47176259Sgreen	off = get_int64();
47265668Skris	data = get_string(&len);
47365668Skris
47499060Sdes	TRACE("write id %u handle %d off %llu len %d", id, handle,
47598937Sdes	    (u_int64_t)off, len);
47665668Skris	fd = handle_to_fd(handle);
47765668Skris	if (fd >= 0) {
47865668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
47965668Skris			status = errno_to_portable(errno);
48065668Skris			error("process_write: seek failed");
48165668Skris		} else {
48265668Skris/* XXX ATOMICIO ? */
48365668Skris			ret = write(fd, data, len);
48465668Skris			if (ret == -1) {
48565668Skris				error("process_write: write failed");
48665668Skris				status = errno_to_portable(errno);
48765668Skris			} else if (ret == len) {
48876259Sgreen				status = SSH2_FX_OK;
48965668Skris			} else {
490124208Sdes				logit("nothing at all written");
49165668Skris			}
49265668Skris		}
49365668Skris	}
49465668Skris	send_status(id, status);
49565668Skris	xfree(data);
49665668Skris}
49765668Skris
49892555Sdesstatic void
49965668Skrisprocess_do_stat(int do_lstat)
50065668Skris{
50176259Sgreen	Attrib a;
50265668Skris	struct stat st;
50365668Skris	u_int32_t id;
50465668Skris	char *name;
50576259Sgreen	int ret, status = SSH2_FX_FAILURE;
50665668Skris
50765668Skris	id = get_int();
50865668Skris	name = get_string(NULL);
50999060Sdes	TRACE("%sstat id %u name %s", do_lstat ? "l" : "", id, name);
51065668Skris	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
51165668Skris	if (ret < 0) {
51265668Skris		status = errno_to_portable(errno);
51365668Skris	} else {
51476259Sgreen		stat_to_attrib(&st, &a);
51576259Sgreen		send_attrib(id, &a);
51676259Sgreen		status = SSH2_FX_OK;
51765668Skris	}
51876259Sgreen	if (status != SSH2_FX_OK)
51965668Skris		send_status(id, status);
52065668Skris	xfree(name);
52165668Skris}
52265668Skris
52392555Sdesstatic void
52465668Skrisprocess_stat(void)
52565668Skris{
52665668Skris	process_do_stat(0);
52765668Skris}
52865668Skris
52992555Sdesstatic void
53065668Skrisprocess_lstat(void)
53165668Skris{
53265668Skris	process_do_stat(1);
53365668Skris}
53465668Skris
53592555Sdesstatic void
53665668Skrisprocess_fstat(void)
53765668Skris{
53876259Sgreen	Attrib a;
53965668Skris	struct stat st;
54065668Skris	u_int32_t id;
54176259Sgreen	int fd, ret, handle, status = SSH2_FX_FAILURE;
54265668Skris
54365668Skris	id = get_int();
54465668Skris	handle = get_handle();
54599060Sdes	TRACE("fstat id %u handle %d", id, handle);
54665668Skris	fd = handle_to_fd(handle);
54765668Skris	if (fd  >= 0) {
54865668Skris		ret = fstat(fd, &st);
54965668Skris		if (ret < 0) {
55065668Skris			status = errno_to_portable(errno);
55165668Skris		} else {
55276259Sgreen			stat_to_attrib(&st, &a);
55376259Sgreen			send_attrib(id, &a);
55476259Sgreen			status = SSH2_FX_OK;
55565668Skris		}
55665668Skris	}
55776259Sgreen	if (status != SSH2_FX_OK)
55865668Skris		send_status(id, status);
55965668Skris}
56065668Skris
56192555Sdesstatic struct timeval *
562126274Sdesattrib_to_tv(const Attrib *a)
56365668Skris{
56465668Skris	static struct timeval tv[2];
56576259Sgreen
56665668Skris	tv[0].tv_sec = a->atime;
56765668Skris	tv[0].tv_usec = 0;
56865668Skris	tv[1].tv_sec = a->mtime;
56965668Skris	tv[1].tv_usec = 0;
57065668Skris	return tv;
57165668Skris}
57265668Skris
57392555Sdesstatic void
57465668Skrisprocess_setstat(void)
57565668Skris{
57665668Skris	Attrib *a;
57765668Skris	u_int32_t id;
57865668Skris	char *name;
57999060Sdes	int status = SSH2_FX_OK, ret;
58065668Skris
58165668Skris	id = get_int();
58265668Skris	name = get_string(NULL);
58365668Skris	a = get_attrib();
58499060Sdes	TRACE("setstat id %u name %s", id, name);
58592555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
58692555Sdes		ret = truncate(name, a->size);
58792555Sdes		if (ret == -1)
58892555Sdes			status = errno_to_portable(errno);
58992555Sdes	}
59076259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
59165668Skris		ret = chmod(name, a->perm & 0777);
59265668Skris		if (ret == -1)
59365668Skris			status = errno_to_portable(errno);
59465668Skris	}
59576259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
59665668Skris		ret = utimes(name, attrib_to_tv(a));
59765668Skris		if (ret == -1)
59865668Skris			status = errno_to_portable(errno);
59965668Skris	}
60076259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
60176259Sgreen		ret = chown(name, a->uid, a->gid);
60276259Sgreen		if (ret == -1)
60376259Sgreen			status = errno_to_portable(errno);
60476259Sgreen	}
60565668Skris	send_status(id, status);
60665668Skris	xfree(name);
60765668Skris}
60865668Skris
60992555Sdesstatic void
61065668Skrisprocess_fsetstat(void)
61165668Skris{
61265668Skris	Attrib *a;
61365668Skris	u_int32_t id;
61465668Skris	int handle, fd, ret;
61576259Sgreen	int status = SSH2_FX_OK;
61698937Sdes	char *name;
61765668Skris
61865668Skris	id = get_int();
61965668Skris	handle = get_handle();
62065668Skris	a = get_attrib();
62199060Sdes	TRACE("fsetstat id %u handle %d", id, handle);
62265668Skris	fd = handle_to_fd(handle);
62398937Sdes	name = handle_to_name(handle);
62498937Sdes	if (fd < 0 || name == NULL) {
62576259Sgreen		status = SSH2_FX_FAILURE;
62665668Skris	} else {
62792555Sdes		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
62892555Sdes			ret = ftruncate(fd, a->size);
62992555Sdes			if (ret == -1)
63092555Sdes				status = errno_to_portable(errno);
63192555Sdes		}
63276259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
63398937Sdes#ifdef HAVE_FCHMOD
63465668Skris			ret = fchmod(fd, a->perm & 0777);
63598937Sdes#else
63698937Sdes			ret = chmod(name, a->perm & 0777);
63798937Sdes#endif
63865668Skris			if (ret == -1)
63965668Skris				status = errno_to_portable(errno);
64065668Skris		}
64176259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
64298937Sdes#ifdef HAVE_FUTIMES
64365668Skris			ret = futimes(fd, attrib_to_tv(a));
64498937Sdes#else
64598937Sdes			ret = utimes(name, attrib_to_tv(a));
64698937Sdes#endif
64765668Skris			if (ret == -1)
64865668Skris				status = errno_to_portable(errno);
64965668Skris		}
65076259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
65198937Sdes#ifdef HAVE_FCHOWN
65276259Sgreen			ret = fchown(fd, a->uid, a->gid);
65398937Sdes#else
65498937Sdes			ret = chown(name, a->uid, a->gid);
65598937Sdes#endif
65676259Sgreen			if (ret == -1)
65776259Sgreen				status = errno_to_portable(errno);
65876259Sgreen		}
65965668Skris	}
66065668Skris	send_status(id, status);
66165668Skris}
66265668Skris
66392555Sdesstatic void
66465668Skrisprocess_opendir(void)
66565668Skris{
66665668Skris	DIR *dirp = NULL;
66765668Skris	char *path;
66876259Sgreen	int handle, status = SSH2_FX_FAILURE;
66965668Skris	u_int32_t id;
67065668Skris
67165668Skris	id = get_int();
67265668Skris	path = get_string(NULL);
67399060Sdes	TRACE("opendir id %u path %s", id, path);
67476259Sgreen	dirp = opendir(path);
67565668Skris	if (dirp == NULL) {
67665668Skris		status = errno_to_portable(errno);
67765668Skris	} else {
678113908Sdes		handle = handle_new(HANDLE_DIR, path, 0, dirp);
67965668Skris		if (handle < 0) {
68065668Skris			closedir(dirp);
68165668Skris		} else {
68265668Skris			send_handle(id, handle);
68376259Sgreen			status = SSH2_FX_OK;
68465668Skris		}
68576259Sgreen
68665668Skris	}
68776259Sgreen	if (status != SSH2_FX_OK)
68865668Skris		send_status(id, status);
68965668Skris	xfree(path);
69065668Skris}
69165668Skris
69292555Sdesstatic void
69365668Skrisprocess_readdir(void)
69465668Skris{
69565668Skris	DIR *dirp;
69665668Skris	struct dirent *dp;
69765668Skris	char *path;
69865668Skris	int handle;
69965668Skris	u_int32_t id;
70065668Skris
70165668Skris	id = get_int();
70265668Skris	handle = get_handle();
70399060Sdes	TRACE("readdir id %u handle %d", id, handle);
70465668Skris	dirp = handle_to_dir(handle);
70565668Skris	path = handle_to_name(handle);
70665668Skris	if (dirp == NULL || path == NULL) {
70776259Sgreen		send_status(id, SSH2_FX_FAILURE);
70865668Skris	} else {
70965668Skris		struct stat st;
71065668Skris		char pathname[1024];
71165668Skris		Stat *stats;
71265668Skris		int nstats = 10, count = 0, i;
71399060Sdes
71465668Skris		stats = xmalloc(nstats * sizeof(Stat));
71565668Skris		while ((dp = readdir(dirp)) != NULL) {
71665668Skris			if (count >= nstats) {
71765668Skris				nstats *= 2;
71865668Skris				stats = xrealloc(stats, nstats * sizeof(Stat));
71965668Skris			}
72065668Skris/* XXX OVERFLOW ? */
72192555Sdes			snprintf(pathname, sizeof pathname, "%s%s%s", path,
72292555Sdes			    strcmp(path, "/") ? "/" : "", dp->d_name);
72365668Skris			if (lstat(pathname, &st) < 0)
72465668Skris				continue;
72576259Sgreen			stat_to_attrib(&st, &(stats[count].attrib));
72665668Skris			stats[count].name = xstrdup(dp->d_name);
727106121Sdes			stats[count].long_name = ls_file(dp->d_name, &st, 0);
72865668Skris			count++;
72965668Skris			/* send up to 100 entries in one message */
73076259Sgreen			/* XXX check packet size instead */
73165668Skris			if (count == 100)
73265668Skris				break;
73365668Skris		}
73476259Sgreen		if (count > 0) {
73576259Sgreen			send_names(id, count, stats);
73692555Sdes			for (i = 0; i < count; i++) {
73776259Sgreen				xfree(stats[i].name);
73876259Sgreen				xfree(stats[i].long_name);
73976259Sgreen			}
74076259Sgreen		} else {
74176259Sgreen			send_status(id, SSH2_FX_EOF);
74265668Skris		}
74365668Skris		xfree(stats);
74465668Skris	}
74565668Skris}
74665668Skris
74792555Sdesstatic void
74865668Skrisprocess_remove(void)
74965668Skris{
75065668Skris	char *name;
75165668Skris	u_int32_t id;
75276259Sgreen	int status = SSH2_FX_FAILURE;
75365668Skris	int ret;
75465668Skris
75565668Skris	id = get_int();
75665668Skris	name = get_string(NULL);
75799060Sdes	TRACE("remove id %u name %s", id, name);
75876259Sgreen	ret = unlink(name);
75976259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
76065668Skris	send_status(id, status);
76165668Skris	xfree(name);
76265668Skris}
76365668Skris
76492555Sdesstatic void
76565668Skrisprocess_mkdir(void)
76665668Skris{
76765668Skris	Attrib *a;
76865668Skris	u_int32_t id;
76965668Skris	char *name;
77076259Sgreen	int ret, mode, status = SSH2_FX_FAILURE;
77165668Skris
77265668Skris	id = get_int();
77365668Skris	name = get_string(NULL);
77465668Skris	a = get_attrib();
77576259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
77676259Sgreen	    a->perm & 0777 : 0777;
77799060Sdes	TRACE("mkdir id %u name %s mode 0%o", id, name, mode);
77865668Skris	ret = mkdir(name, mode);
77976259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
78065668Skris	send_status(id, status);
78165668Skris	xfree(name);
78265668Skris}
78365668Skris
78492555Sdesstatic void
78565668Skrisprocess_rmdir(void)
78665668Skris{
78765668Skris	u_int32_t id;
78865668Skris	char *name;
78965668Skris	int ret, status;
79065668Skris
79165668Skris	id = get_int();
79265668Skris	name = get_string(NULL);
79399060Sdes	TRACE("rmdir id %u name %s", id, name);
79465668Skris	ret = rmdir(name);
79576259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
79665668Skris	send_status(id, status);
79765668Skris	xfree(name);
79865668Skris}
79965668Skris
80092555Sdesstatic void
80165668Skrisprocess_realpath(void)
80265668Skris{
80365668Skris	char resolvedname[MAXPATHLEN];
80465668Skris	u_int32_t id;
80565668Skris	char *path;
80665668Skris
80765668Skris	id = get_int();
80865668Skris	path = get_string(NULL);
80976259Sgreen	if (path[0] == '\0') {
81076259Sgreen		xfree(path);
81176259Sgreen		path = xstrdup(".");
81276259Sgreen	}
81399060Sdes	TRACE("realpath id %u path %s", id, path);
81465668Skris	if (realpath(path, resolvedname) == NULL) {
81565668Skris		send_status(id, errno_to_portable(errno));
81665668Skris	} else {
81765668Skris		Stat s;
81865668Skris		attrib_clear(&s.attrib);
81965668Skris		s.name = s.long_name = resolvedname;
82065668Skris		send_names(id, 1, &s);
82165668Skris	}
82265668Skris	xfree(path);
82365668Skris}
82465668Skris
82592555Sdesstatic void
82665668Skrisprocess_rename(void)
82765668Skris{
82865668Skris	u_int32_t id;
82965668Skris	char *oldpath, *newpath;
830113908Sdes	int status;
831113908Sdes	struct stat sb;
83265668Skris
83365668Skris	id = get_int();
83465668Skris	oldpath = get_string(NULL);
83565668Skris	newpath = get_string(NULL);
83699060Sdes	TRACE("rename id %u old %s new %s", id, oldpath, newpath);
837113908Sdes	status = SSH2_FX_FAILURE;
838113908Sdes	if (lstat(oldpath, &sb) == -1)
839113908Sdes		status = errno_to_portable(errno);
840113908Sdes	else if (S_ISREG(sb.st_mode)) {
841113908Sdes		/* Race-free rename of regular files */
842113908Sdes		if (link(oldpath, newpath) == -1)
843113908Sdes			status = errno_to_portable(errno);
844113908Sdes		else if (unlink(oldpath) == -1) {
845113908Sdes			status = errno_to_portable(errno);
846113908Sdes			/* clean spare link */
847113908Sdes			unlink(newpath);
848113908Sdes		} else
849113908Sdes			status = SSH2_FX_OK;
850113908Sdes	} else if (stat(newpath, &sb) == -1) {
851113908Sdes		if (rename(oldpath, newpath) == -1)
852113908Sdes			status = errno_to_portable(errno);
853113908Sdes		else
854113908Sdes			status = SSH2_FX_OK;
85576259Sgreen	}
85665668Skris	send_status(id, status);
85765668Skris	xfree(oldpath);
85865668Skris	xfree(newpath);
85965668Skris}
86065668Skris
86192555Sdesstatic void
86276259Sgreenprocess_readlink(void)
86376259Sgreen{
86476259Sgreen	u_int32_t id;
86592555Sdes	int len;
86676259Sgreen	char link[MAXPATHLEN];
86776259Sgreen	char *path;
86865668Skris
86976259Sgreen	id = get_int();
87076259Sgreen	path = get_string(NULL);
87199060Sdes	TRACE("readlink id %u path %s", id, path);
87292555Sdes	if ((len = readlink(path, link, sizeof(link) - 1)) == -1)
87376259Sgreen		send_status(id, errno_to_portable(errno));
87476259Sgreen	else {
87576259Sgreen		Stat s;
87692555Sdes
87792555Sdes		link[len] = '\0';
87876259Sgreen		attrib_clear(&s.attrib);
87976259Sgreen		s.name = s.long_name = link;
88076259Sgreen		send_names(id, 1, &s);
88176259Sgreen	}
88276259Sgreen	xfree(path);
88376259Sgreen}
88476259Sgreen
88592555Sdesstatic void
88676259Sgreenprocess_symlink(void)
88776259Sgreen{
88876259Sgreen	u_int32_t id;
88976259Sgreen	char *oldpath, *newpath;
890113908Sdes	int ret, status;
89176259Sgreen
89276259Sgreen	id = get_int();
89376259Sgreen	oldpath = get_string(NULL);
89476259Sgreen	newpath = get_string(NULL);
89599060Sdes	TRACE("symlink id %u old %s new %s", id, oldpath, newpath);
896113908Sdes	/* this will fail if 'newpath' exists */
897113908Sdes	ret = symlink(oldpath, newpath);
898113908Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
89976259Sgreen	send_status(id, status);
90076259Sgreen	xfree(oldpath);
90176259Sgreen	xfree(newpath);
90276259Sgreen}
90376259Sgreen
90492555Sdesstatic void
90576259Sgreenprocess_extended(void)
90676259Sgreen{
90776259Sgreen	u_int32_t id;
90876259Sgreen	char *request;
90976259Sgreen
91076259Sgreen	id = get_int();
91176259Sgreen	request = get_string(NULL);
91276259Sgreen	send_status(id, SSH2_FX_OP_UNSUPPORTED);		/* MUST */
91376259Sgreen	xfree(request);
91476259Sgreen}
91576259Sgreen
91665668Skris/* stolen from ssh-agent */
91765668Skris
91892555Sdesstatic void
91965668Skrisprocess(void)
92065668Skris{
92176259Sgreen	u_int msg_len;
92298675Sdes	u_int buf_len;
92398675Sdes	u_int consumed;
92476259Sgreen	u_int type;
92576259Sgreen	u_char *cp;
92665668Skris
92798675Sdes	buf_len = buffer_len(&iqueue);
92898675Sdes	if (buf_len < 5)
92965668Skris		return;		/* Incomplete message. */
93092555Sdes	cp = buffer_ptr(&iqueue);
93165668Skris	msg_len = GET_32BIT(cp);
93265668Skris	if (msg_len > 256 * 1024) {
93365668Skris		error("bad message ");
93465668Skris		exit(11);
93565668Skris	}
93698675Sdes	if (buf_len < msg_len + 4)
93765668Skris		return;
93865668Skris	buffer_consume(&iqueue, 4);
93998675Sdes	buf_len -= 4;
94065668Skris	type = buffer_get_char(&iqueue);
94165668Skris	switch (type) {
94276259Sgreen	case SSH2_FXP_INIT:
94365668Skris		process_init();
94465668Skris		break;
94576259Sgreen	case SSH2_FXP_OPEN:
94665668Skris		process_open();
94765668Skris		break;
94876259Sgreen	case SSH2_FXP_CLOSE:
94965668Skris		process_close();
95065668Skris		break;
95176259Sgreen	case SSH2_FXP_READ:
95265668Skris		process_read();
95365668Skris		break;
95476259Sgreen	case SSH2_FXP_WRITE:
95565668Skris		process_write();
95665668Skris		break;
95776259Sgreen	case SSH2_FXP_LSTAT:
95865668Skris		process_lstat();
95965668Skris		break;
96076259Sgreen	case SSH2_FXP_FSTAT:
96165668Skris		process_fstat();
96265668Skris		break;
96376259Sgreen	case SSH2_FXP_SETSTAT:
96465668Skris		process_setstat();
96565668Skris		break;
96676259Sgreen	case SSH2_FXP_FSETSTAT:
96765668Skris		process_fsetstat();
96865668Skris		break;
96976259Sgreen	case SSH2_FXP_OPENDIR:
97065668Skris		process_opendir();
97165668Skris		break;
97276259Sgreen	case SSH2_FXP_READDIR:
97365668Skris		process_readdir();
97465668Skris		break;
97576259Sgreen	case SSH2_FXP_REMOVE:
97665668Skris		process_remove();
97765668Skris		break;
97876259Sgreen	case SSH2_FXP_MKDIR:
97965668Skris		process_mkdir();
98065668Skris		break;
98176259Sgreen	case SSH2_FXP_RMDIR:
98265668Skris		process_rmdir();
98365668Skris		break;
98476259Sgreen	case SSH2_FXP_REALPATH:
98565668Skris		process_realpath();
98665668Skris		break;
98776259Sgreen	case SSH2_FXP_STAT:
98865668Skris		process_stat();
98965668Skris		break;
99076259Sgreen	case SSH2_FXP_RENAME:
99165668Skris		process_rename();
99265668Skris		break;
99376259Sgreen	case SSH2_FXP_READLINK:
99476259Sgreen		process_readlink();
99576259Sgreen		break;
99676259Sgreen	case SSH2_FXP_SYMLINK:
99776259Sgreen		process_symlink();
99876259Sgreen		break;
99976259Sgreen	case SSH2_FXP_EXTENDED:
100076259Sgreen		process_extended();
100176259Sgreen		break;
100265668Skris	default:
100365668Skris		error("Unknown message %d", type);
100465668Skris		break;
100565668Skris	}
100698675Sdes	/* discard the remaining bytes from the current packet */
100798675Sdes	if (buf_len < buffer_len(&iqueue))
100898675Sdes		fatal("iqueue grows");
100998675Sdes	consumed = buf_len - buffer_len(&iqueue);
101098675Sdes	if (msg_len < consumed)
101198675Sdes		fatal("msg_len %d < consumed %d", msg_len, consumed);
101298675Sdes	if (msg_len > consumed)
101398675Sdes		buffer_consume(&iqueue, msg_len - consumed);
101465668Skris}
101565668Skris
101665668Skrisint
101765668Skrismain(int ac, char **av)
101865668Skris{
101976259Sgreen	fd_set *rset, *wset;
102065668Skris	int in, out, max;
102176259Sgreen	ssize_t len, olen, set_size;
102265668Skris
102376259Sgreen	/* XXX should use getopt */
102476259Sgreen
1025124208Sdes	__progname = ssh_get_progname(av[0]);
102665668Skris	handle_init();
102765668Skris
102876259Sgreen#ifdef DEBUG_SFTP_SERVER
102976259Sgreen	log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0);
103076259Sgreen#endif
103176259Sgreen
103265668Skris	in = dup(STDIN_FILENO);
103365668Skris	out = dup(STDOUT_FILENO);
103465668Skris
103598937Sdes#ifdef HAVE_CYGWIN
103698937Sdes	setmode(in, O_BINARY);
103798937Sdes	setmode(out, O_BINARY);
103898937Sdes#endif
103998937Sdes
104065668Skris	max = 0;
104165668Skris	if (in > max)
104265668Skris		max = in;
104365668Skris	if (out > max)
104465668Skris		max = out;
104565668Skris
104665668Skris	buffer_init(&iqueue);
104765668Skris	buffer_init(&oqueue);
104865668Skris
104976259Sgreen	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
105076259Sgreen	rset = (fd_set *)xmalloc(set_size);
105176259Sgreen	wset = (fd_set *)xmalloc(set_size);
105276259Sgreen
105365668Skris	for (;;) {
105476259Sgreen		memset(rset, 0, set_size);
105576259Sgreen		memset(wset, 0, set_size);
105665668Skris
105776259Sgreen		FD_SET(in, rset);
105865668Skris		olen = buffer_len(&oqueue);
105965668Skris		if (olen > 0)
106076259Sgreen			FD_SET(out, wset);
106165668Skris
106276259Sgreen		if (select(max+1, rset, wset, NULL, NULL) < 0) {
106365668Skris			if (errno == EINTR)
106465668Skris				continue;
106565668Skris			exit(2);
106665668Skris		}
106765668Skris
106865668Skris		/* copy stdin to iqueue */
106976259Sgreen		if (FD_ISSET(in, rset)) {
107065668Skris			char buf[4*4096];
107165668Skris			len = read(in, buf, sizeof buf);
107265668Skris			if (len == 0) {
107365668Skris				debug("read eof");
107465668Skris				exit(0);
107565668Skris			} else if (len < 0) {
107665668Skris				error("read error");
107765668Skris				exit(1);
107865668Skris			} else {
107965668Skris				buffer_append(&iqueue, buf, len);
108065668Skris			}
108165668Skris		}
108265668Skris		/* send oqueue to stdout */
108376259Sgreen		if (FD_ISSET(out, wset)) {
108465668Skris			len = write(out, buffer_ptr(&oqueue), olen);
108565668Skris			if (len < 0) {
108665668Skris				error("write error");
108765668Skris				exit(1);
108865668Skris			} else {
108965668Skris				buffer_consume(&oqueue, len);
109065668Skris			}
109165668Skris		}
109265668Skris		/* process requests from client */
109365668Skris		process();
109465668Skris	}
109565668Skris}
1096