sftp-server.c revision 124208
165668Skris/*
292555Sdes * Copyright (c) 2000, 2001, 2002 Markus Friedl.  All rights reserved.
365668Skris *
465668Skris * Redistribution and use in source and binary forms, with or without
565668Skris * modification, are permitted provided that the following conditions
665668Skris * are met:
765668Skris * 1. Redistributions of source code must retain the above copyright
865668Skris *    notice, this list of conditions and the following disclaimer.
965668Skris * 2. Redistributions in binary form must reproduce the above copyright
1065668Skris *    notice, this list of conditions and the following disclaimer in the
1165668Skris *    documentation and/or other materials provided with the distribution.
1265668Skris *
1365668Skris * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1465668Skris * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1565668Skris * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1665668Skris * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1765668Skris * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1865668Skris * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1965668Skris * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2065668Skris * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2165668Skris * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2265668Skris * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2365668Skris */
2465668Skris#include "includes.h"
25124208SdesRCSID("$OpenBSD: sftp-server.c,v 1.43 2003/06/25 22:39:36 miod Exp $");
2665668Skris
2765668Skris#include "buffer.h"
2865668Skris#include "bufaux.h"
2965668Skris#include "getput.h"
3076259Sgreen#include "log.h"
3165668Skris#include "xmalloc.h"
3265668Skris
3376259Sgreen#include "sftp.h"
3476259Sgreen#include "sftp-common.h"
3565668Skris
3665668Skris/* helper */
3776259Sgreen#define get_int64()			buffer_get_int64(&iqueue);
3865668Skris#define get_int()			buffer_get_int(&iqueue);
3965668Skris#define get_string(lenp)		buffer_get_string(&iqueue, lenp);
4076259Sgreen#define TRACE				debug
4165668Skris
4298937Sdes#ifdef HAVE___PROGNAME
4398937Sdesextern char *__progname;
4498937Sdes#else
4598937Sdeschar *__progname;
4698937Sdes#endif
4798937Sdes
4865668Skris/* input and output queue */
4965668SkrisBuffer iqueue;
5065668SkrisBuffer oqueue;
5165668Skris
5276259Sgreen/* Version of client */
5376259Sgreenint version;
5476259Sgreen
55124208Sdes/* portable attributes, etc. */
5665668Skris
5765668Skristypedef struct Stat Stat;
5865668Skris
5976259Sgreenstruct Stat {
6065668Skris	char *name;
6165668Skris	char *long_name;
6265668Skris	Attrib attrib;
6365668Skris};
6465668Skris
6592555Sdesstatic int
6665668Skriserrno_to_portable(int unixerrno)
6765668Skris{
6865668Skris	int ret = 0;
6976259Sgreen
7065668Skris	switch (unixerrno) {
7165668Skris	case 0:
7276259Sgreen		ret = SSH2_FX_OK;
7365668Skris		break;
7465668Skris	case ENOENT:
7565668Skris	case ENOTDIR:
7665668Skris	case EBADF:
7765668Skris	case ELOOP:
7876259Sgreen		ret = SSH2_FX_NO_SUCH_FILE;
7965668Skris		break;
8065668Skris	case EPERM:
8165668Skris	case EACCES:
8265668Skris	case EFAULT:
8376259Sgreen		ret = SSH2_FX_PERMISSION_DENIED;
8465668Skris		break;
8565668Skris	case ENAMETOOLONG:
8665668Skris	case EINVAL:
8776259Sgreen		ret = SSH2_FX_BAD_MESSAGE;
8865668Skris		break;
8965668Skris	default:
9076259Sgreen		ret = SSH2_FX_FAILURE;
9165668Skris		break;
9265668Skris	}
9365668Skris	return ret;
9465668Skris}
9565668Skris
9692555Sdesstatic int
9765668Skrisflags_from_portable(int pflags)
9865668Skris{
9965668Skris	int flags = 0;
10076259Sgreen
10176259Sgreen	if ((pflags & SSH2_FXF_READ) &&
10276259Sgreen	    (pflags & SSH2_FXF_WRITE)) {
10365668Skris		flags = O_RDWR;
10476259Sgreen	} else if (pflags & SSH2_FXF_READ) {
10565668Skris		flags = O_RDONLY;
10676259Sgreen	} else if (pflags & SSH2_FXF_WRITE) {
10765668Skris		flags = O_WRONLY;
10865668Skris	}
10976259Sgreen	if (pflags & SSH2_FXF_CREAT)
11065668Skris		flags |= O_CREAT;
11176259Sgreen	if (pflags & SSH2_FXF_TRUNC)
11265668Skris		flags |= O_TRUNC;
11376259Sgreen	if (pflags & SSH2_FXF_EXCL)
11465668Skris		flags |= O_EXCL;
11565668Skris	return flags;
11665668Skris}
11765668Skris
11892555Sdesstatic Attrib *
11965668Skrisget_attrib(void)
12065668Skris{
12165668Skris	return decode_attrib(&iqueue);
12265668Skris}
12365668Skris
12465668Skris/* handle handles */
12565668Skris
12665668Skristypedef struct Handle Handle;
12765668Skrisstruct Handle {
12865668Skris	int use;
12965668Skris	DIR *dirp;
13065668Skris	int fd;
13165668Skris	char *name;
13265668Skris};
13376259Sgreen
13465668Skrisenum {
13565668Skris	HANDLE_UNUSED,
13665668Skris	HANDLE_DIR,
13765668Skris	HANDLE_FILE
13865668Skris};
13976259Sgreen
14065668SkrisHandle	handles[100];
14165668Skris
14292555Sdesstatic void
14365668Skrishandle_init(void)
14465668Skris{
14565668Skris	int i;
14676259Sgreen
14792555Sdes	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
14865668Skris		handles[i].use = HANDLE_UNUSED;
14965668Skris}
15065668Skris
15192555Sdesstatic int
15265668Skrishandle_new(int use, char *name, int fd, DIR *dirp)
15365668Skris{
15465668Skris	int i;
15576259Sgreen
15692555Sdes	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) {
15765668Skris		if (handles[i].use == HANDLE_UNUSED) {
15865668Skris			handles[i].use = use;
15965668Skris			handles[i].dirp = dirp;
16065668Skris			handles[i].fd = fd;
161113908Sdes			handles[i].name = xstrdup(name);
16265668Skris			return i;
16365668Skris		}
16465668Skris	}
16565668Skris	return -1;
16665668Skris}
16765668Skris
16892555Sdesstatic int
16965668Skrishandle_is_ok(int i, int type)
17065668Skris{
17176259Sgreen	return i >= 0 && i < sizeof(handles)/sizeof(Handle) &&
17276259Sgreen	    handles[i].use == type;
17365668Skris}
17465668Skris
17592555Sdesstatic int
17665668Skrishandle_to_string(int handle, char **stringp, int *hlenp)
17765668Skris{
17865668Skris	if (stringp == NULL || hlenp == NULL)
17965668Skris		return -1;
18076259Sgreen	*stringp = xmalloc(sizeof(int32_t));
18176259Sgreen	PUT_32BIT(*stringp, handle);
18276259Sgreen	*hlenp = sizeof(int32_t);
18365668Skris	return 0;
18465668Skris}
18565668Skris
18692555Sdesstatic int
18765668Skrishandle_from_string(char *handle, u_int hlen)
18865668Skris{
18976259Sgreen	int val;
19076259Sgreen
19176259Sgreen	if (hlen != sizeof(int32_t))
19265668Skris		return -1;
19376259Sgreen	val = GET_32BIT(handle);
19465668Skris	if (handle_is_ok(val, HANDLE_FILE) ||
19565668Skris	    handle_is_ok(val, HANDLE_DIR))
19665668Skris		return val;
19765668Skris	return -1;
19865668Skris}
19965668Skris
20092555Sdesstatic char *
20165668Skrishandle_to_name(int handle)
20265668Skris{
20365668Skris	if (handle_is_ok(handle, HANDLE_DIR)||
20465668Skris	    handle_is_ok(handle, HANDLE_FILE))
20565668Skris		return handles[handle].name;
20665668Skris	return NULL;
20765668Skris}
20865668Skris
20992555Sdesstatic DIR *
21065668Skrishandle_to_dir(int handle)
21165668Skris{
21265668Skris	if (handle_is_ok(handle, HANDLE_DIR))
21365668Skris		return handles[handle].dirp;
21465668Skris	return NULL;
21565668Skris}
21665668Skris
21792555Sdesstatic int
21865668Skrishandle_to_fd(int handle)
21965668Skris{
22076259Sgreen	if (handle_is_ok(handle, HANDLE_FILE))
22165668Skris		return handles[handle].fd;
22265668Skris	return -1;
22365668Skris}
22465668Skris
22592555Sdesstatic int
22665668Skrishandle_close(int handle)
22765668Skris{
22865668Skris	int ret = -1;
22976259Sgreen
23065668Skris	if (handle_is_ok(handle, HANDLE_FILE)) {
23165668Skris		ret = close(handles[handle].fd);
23265668Skris		handles[handle].use = HANDLE_UNUSED;
233113908Sdes		xfree(handles[handle].name);
23465668Skris	} else if (handle_is_ok(handle, HANDLE_DIR)) {
23565668Skris		ret = closedir(handles[handle].dirp);
23665668Skris		handles[handle].use = HANDLE_UNUSED;
237113908Sdes		xfree(handles[handle].name);
23865668Skris	} else {
23965668Skris		errno = ENOENT;
24065668Skris	}
24165668Skris	return ret;
24265668Skris}
24365668Skris
24492555Sdesstatic int
24565668Skrisget_handle(void)
24665668Skris{
24765668Skris	char *handle;
24876259Sgreen	int val = -1;
24965668Skris	u_int hlen;
25076259Sgreen
25165668Skris	handle = get_string(&hlen);
25276259Sgreen	if (hlen < 256)
25376259Sgreen		val = handle_from_string(handle, hlen);
25465668Skris	xfree(handle);
25565668Skris	return val;
25665668Skris}
25765668Skris
25865668Skris/* send replies */
25965668Skris
26092555Sdesstatic void
26165668Skrissend_msg(Buffer *m)
26265668Skris{
26365668Skris	int mlen = buffer_len(m);
26476259Sgreen
26565668Skris	buffer_put_int(&oqueue, mlen);
26665668Skris	buffer_append(&oqueue, buffer_ptr(m), mlen);
26765668Skris	buffer_consume(m, mlen);
26865668Skris}
26965668Skris
27092555Sdesstatic void
27165668Skrissend_status(u_int32_t id, u_int32_t error)
27265668Skris{
27365668Skris	Buffer msg;
27476259Sgreen	const char *status_messages[] = {
27576259Sgreen		"Success",			/* SSH_FX_OK */
27676259Sgreen		"End of file",			/* SSH_FX_EOF */
27776259Sgreen		"No such file",			/* SSH_FX_NO_SUCH_FILE */
27876259Sgreen		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
27976259Sgreen		"Failure",			/* SSH_FX_FAILURE */
28076259Sgreen		"Bad message",			/* SSH_FX_BAD_MESSAGE */
28176259Sgreen		"No connection",		/* SSH_FX_NO_CONNECTION */
28276259Sgreen		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
28376259Sgreen		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
28476259Sgreen		"Unknown error"			/* Others */
28576259Sgreen	};
28676259Sgreen
28799060Sdes	TRACE("sent status id %u error %u", id, error);
28865668Skris	buffer_init(&msg);
28976259Sgreen	buffer_put_char(&msg, SSH2_FXP_STATUS);
29065668Skris	buffer_put_int(&msg, id);
29165668Skris	buffer_put_int(&msg, error);
29276259Sgreen	if (version >= 3) {
29376259Sgreen		buffer_put_cstring(&msg,
29476259Sgreen		    status_messages[MIN(error,SSH2_FX_MAX)]);
29576259Sgreen		buffer_put_cstring(&msg, "");
29676259Sgreen	}
29765668Skris	send_msg(&msg);
29865668Skris	buffer_free(&msg);
29965668Skris}
30092555Sdesstatic void
30165668Skrissend_data_or_handle(char type, u_int32_t id, char *data, int dlen)
30265668Skris{
30365668Skris	Buffer msg;
30476259Sgreen
30565668Skris	buffer_init(&msg);
30665668Skris	buffer_put_char(&msg, type);
30765668Skris	buffer_put_int(&msg, id);
30865668Skris	buffer_put_string(&msg, data, dlen);
30965668Skris	send_msg(&msg);
31065668Skris	buffer_free(&msg);
31165668Skris}
31265668Skris
31392555Sdesstatic void
31465668Skrissend_data(u_int32_t id, char *data, int dlen)
31565668Skris{
31699060Sdes	TRACE("sent data id %u len %d", id, dlen);
31776259Sgreen	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
31865668Skris}
31965668Skris
32092555Sdesstatic void
32165668Skrissend_handle(u_int32_t id, int handle)
32265668Skris{
32365668Skris	char *string;
32465668Skris	int hlen;
32576259Sgreen
32665668Skris	handle_to_string(handle, &string, &hlen);
32799060Sdes	TRACE("sent handle id %u handle %d", id, handle);
32876259Sgreen	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
32965668Skris	xfree(string);
33065668Skris}
33165668Skris
33292555Sdesstatic void
33365668Skrissend_names(u_int32_t id, int count, Stat *stats)
33465668Skris{
33565668Skris	Buffer msg;
33665668Skris	int i;
33776259Sgreen
33865668Skris	buffer_init(&msg);
33976259Sgreen	buffer_put_char(&msg, SSH2_FXP_NAME);
34065668Skris	buffer_put_int(&msg, id);
34165668Skris	buffer_put_int(&msg, count);
34299060Sdes	TRACE("sent names id %u count %d", id, count);
34365668Skris	for (i = 0; i < count; i++) {
34465668Skris		buffer_put_cstring(&msg, stats[i].name);
34565668Skris		buffer_put_cstring(&msg, stats[i].long_name);
34665668Skris		encode_attrib(&msg, &stats[i].attrib);
34765668Skris	}
34865668Skris	send_msg(&msg);
34965668Skris	buffer_free(&msg);
35065668Skris}
35165668Skris
35292555Sdesstatic void
35365668Skrissend_attrib(u_int32_t id, Attrib *a)
35465668Skris{
35565668Skris	Buffer msg;
35676259Sgreen
35799060Sdes	TRACE("sent attrib id %u have 0x%x", id, a->flags);
35865668Skris	buffer_init(&msg);
35976259Sgreen	buffer_put_char(&msg, SSH2_FXP_ATTRS);
36065668Skris	buffer_put_int(&msg, id);
36165668Skris	encode_attrib(&msg, a);
36265668Skris	send_msg(&msg);
36365668Skris	buffer_free(&msg);
36465668Skris}
36565668Skris
36665668Skris/* parse incoming */
36765668Skris
36892555Sdesstatic void
36965668Skrisprocess_init(void)
37065668Skris{
37165668Skris	Buffer msg;
37265668Skris
37398675Sdes	version = get_int();
37465668Skris	TRACE("client version %d", version);
37565668Skris	buffer_init(&msg);
37676259Sgreen	buffer_put_char(&msg, SSH2_FXP_VERSION);
37776259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
37865668Skris	send_msg(&msg);
37965668Skris	buffer_free(&msg);
38065668Skris}
38165668Skris
38292555Sdesstatic void
38365668Skrisprocess_open(void)
38465668Skris{
38565668Skris	u_int32_t id, pflags;
38665668Skris	Attrib *a;
38765668Skris	char *name;
38876259Sgreen	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
38965668Skris
39065668Skris	id = get_int();
39165668Skris	name = get_string(NULL);
39276259Sgreen	pflags = get_int();		/* portable flags */
39365668Skris	a = get_attrib();
39465668Skris	flags = flags_from_portable(pflags);
39576259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
39699060Sdes	TRACE("open id %u name %s flags %d mode 0%o", id, name, pflags, mode);
39765668Skris	fd = open(name, flags, mode);
39865668Skris	if (fd < 0) {
39965668Skris		status = errno_to_portable(errno);
40065668Skris	} else {
401113908Sdes		handle = handle_new(HANDLE_FILE, name, fd, NULL);
40265668Skris		if (handle < 0) {
40365668Skris			close(fd);
40465668Skris		} else {
40565668Skris			send_handle(id, handle);
40676259Sgreen			status = SSH2_FX_OK;
40765668Skris		}
40865668Skris	}
40976259Sgreen	if (status != SSH2_FX_OK)
41065668Skris		send_status(id, status);
41165668Skris	xfree(name);
41265668Skris}
41365668Skris
41492555Sdesstatic void
41565668Skrisprocess_close(void)
41665668Skris{
41765668Skris	u_int32_t id;
41876259Sgreen	int handle, ret, status = SSH2_FX_FAILURE;
41965668Skris
42065668Skris	id = get_int();
42165668Skris	handle = get_handle();
42299060Sdes	TRACE("close id %u handle %d", id, handle);
42365668Skris	ret = handle_close(handle);
42476259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
42565668Skris	send_status(id, status);
42665668Skris}
42765668Skris
42892555Sdesstatic void
42965668Skrisprocess_read(void)
43065668Skris{
43165668Skris	char buf[64*1024];
43276259Sgreen	u_int32_t id, len;
43376259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
43465668Skris	u_int64_t off;
43565668Skris
43665668Skris	id = get_int();
43765668Skris	handle = get_handle();
43876259Sgreen	off = get_int64();
43965668Skris	len = get_int();
44065668Skris
44199060Sdes	TRACE("read id %u handle %d off %llu len %d", id, handle,
44298937Sdes	    (u_int64_t)off, len);
44365668Skris	if (len > sizeof buf) {
44465668Skris		len = sizeof buf;
445124208Sdes		logit("read change len %d", len);
44665668Skris	}
44765668Skris	fd = handle_to_fd(handle);
44865668Skris	if (fd >= 0) {
44965668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
45065668Skris			error("process_read: seek failed");
45165668Skris			status = errno_to_portable(errno);
45265668Skris		} else {
45365668Skris			ret = read(fd, buf, len);
45465668Skris			if (ret < 0) {
45565668Skris				status = errno_to_portable(errno);
45665668Skris			} else if (ret == 0) {
45776259Sgreen				status = SSH2_FX_EOF;
45865668Skris			} else {
45965668Skris				send_data(id, buf, ret);
46076259Sgreen				status = SSH2_FX_OK;
46165668Skris			}
46265668Skris		}
46365668Skris	}
46476259Sgreen	if (status != SSH2_FX_OK)
46565668Skris		send_status(id, status);
46665668Skris}
46765668Skris
46892555Sdesstatic void
46965668Skrisprocess_write(void)
47065668Skris{
47176259Sgreen	u_int32_t id;
47265668Skris	u_int64_t off;
47365668Skris	u_int len;
47476259Sgreen	int handle, fd, ret, status = SSH2_FX_FAILURE;
47565668Skris	char *data;
47665668Skris
47765668Skris	id = get_int();
47865668Skris	handle = get_handle();
47976259Sgreen	off = get_int64();
48065668Skris	data = get_string(&len);
48165668Skris
48299060Sdes	TRACE("write id %u handle %d off %llu len %d", id, handle,
48398937Sdes	    (u_int64_t)off, len);
48465668Skris	fd = handle_to_fd(handle);
48565668Skris	if (fd >= 0) {
48665668Skris		if (lseek(fd, off, SEEK_SET) < 0) {
48765668Skris			status = errno_to_portable(errno);
48865668Skris			error("process_write: seek failed");
48965668Skris		} else {
49065668Skris/* XXX ATOMICIO ? */
49165668Skris			ret = write(fd, data, len);
49265668Skris			if (ret == -1) {
49365668Skris				error("process_write: write failed");
49465668Skris				status = errno_to_portable(errno);
49565668Skris			} else if (ret == len) {
49676259Sgreen				status = SSH2_FX_OK;
49765668Skris			} else {
498124208Sdes				logit("nothing at all written");
49965668Skris			}
50065668Skris		}
50165668Skris	}
50265668Skris	send_status(id, status);
50365668Skris	xfree(data);
50465668Skris}
50565668Skris
50692555Sdesstatic void
50765668Skrisprocess_do_stat(int do_lstat)
50865668Skris{
50976259Sgreen	Attrib a;
51065668Skris	struct stat st;
51165668Skris	u_int32_t id;
51265668Skris	char *name;
51376259Sgreen	int ret, status = SSH2_FX_FAILURE;
51465668Skris
51565668Skris	id = get_int();
51665668Skris	name = get_string(NULL);
51799060Sdes	TRACE("%sstat id %u name %s", do_lstat ? "l" : "", id, name);
51865668Skris	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
51965668Skris	if (ret < 0) {
52065668Skris		status = errno_to_portable(errno);
52165668Skris	} else {
52276259Sgreen		stat_to_attrib(&st, &a);
52376259Sgreen		send_attrib(id, &a);
52476259Sgreen		status = SSH2_FX_OK;
52565668Skris	}
52676259Sgreen	if (status != SSH2_FX_OK)
52765668Skris		send_status(id, status);
52865668Skris	xfree(name);
52965668Skris}
53065668Skris
53192555Sdesstatic void
53265668Skrisprocess_stat(void)
53365668Skris{
53465668Skris	process_do_stat(0);
53565668Skris}
53665668Skris
53792555Sdesstatic void
53865668Skrisprocess_lstat(void)
53965668Skris{
54065668Skris	process_do_stat(1);
54165668Skris}
54265668Skris
54392555Sdesstatic void
54465668Skrisprocess_fstat(void)
54565668Skris{
54676259Sgreen	Attrib a;
54765668Skris	struct stat st;
54865668Skris	u_int32_t id;
54976259Sgreen	int fd, ret, handle, status = SSH2_FX_FAILURE;
55065668Skris
55165668Skris	id = get_int();
55265668Skris	handle = get_handle();
55399060Sdes	TRACE("fstat id %u handle %d", id, handle);
55465668Skris	fd = handle_to_fd(handle);
55565668Skris	if (fd  >= 0) {
55665668Skris		ret = fstat(fd, &st);
55765668Skris		if (ret < 0) {
55865668Skris			status = errno_to_portable(errno);
55965668Skris		} else {
56076259Sgreen			stat_to_attrib(&st, &a);
56176259Sgreen			send_attrib(id, &a);
56276259Sgreen			status = SSH2_FX_OK;
56365668Skris		}
56465668Skris	}
56576259Sgreen	if (status != SSH2_FX_OK)
56665668Skris		send_status(id, status);
56765668Skris}
56865668Skris
56992555Sdesstatic struct timeval *
57065668Skrisattrib_to_tv(Attrib *a)
57165668Skris{
57265668Skris	static struct timeval tv[2];
57376259Sgreen
57465668Skris	tv[0].tv_sec = a->atime;
57565668Skris	tv[0].tv_usec = 0;
57665668Skris	tv[1].tv_sec = a->mtime;
57765668Skris	tv[1].tv_usec = 0;
57865668Skris	return tv;
57965668Skris}
58065668Skris
58192555Sdesstatic void
58265668Skrisprocess_setstat(void)
58365668Skris{
58465668Skris	Attrib *a;
58565668Skris	u_int32_t id;
58665668Skris	char *name;
58799060Sdes	int status = SSH2_FX_OK, ret;
58865668Skris
58965668Skris	id = get_int();
59065668Skris	name = get_string(NULL);
59165668Skris	a = get_attrib();
59299060Sdes	TRACE("setstat id %u name %s", id, name);
59392555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
59492555Sdes		ret = truncate(name, a->size);
59592555Sdes		if (ret == -1)
59692555Sdes			status = errno_to_portable(errno);
59792555Sdes	}
59876259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
59965668Skris		ret = chmod(name, a->perm & 0777);
60065668Skris		if (ret == -1)
60165668Skris			status = errno_to_portable(errno);
60265668Skris	}
60376259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
60465668Skris		ret = utimes(name, attrib_to_tv(a));
60565668Skris		if (ret == -1)
60665668Skris			status = errno_to_portable(errno);
60765668Skris	}
60876259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
60976259Sgreen		ret = chown(name, a->uid, a->gid);
61076259Sgreen		if (ret == -1)
61176259Sgreen			status = errno_to_portable(errno);
61276259Sgreen	}
61365668Skris	send_status(id, status);
61465668Skris	xfree(name);
61565668Skris}
61665668Skris
61792555Sdesstatic void
61865668Skrisprocess_fsetstat(void)
61965668Skris{
62065668Skris	Attrib *a;
62165668Skris	u_int32_t id;
62265668Skris	int handle, fd, ret;
62376259Sgreen	int status = SSH2_FX_OK;
62498937Sdes	char *name;
62565668Skris
62665668Skris	id = get_int();
62765668Skris	handle = get_handle();
62865668Skris	a = get_attrib();
62999060Sdes	TRACE("fsetstat id %u handle %d", id, handle);
63065668Skris	fd = handle_to_fd(handle);
63198937Sdes	name = handle_to_name(handle);
63298937Sdes	if (fd < 0 || name == NULL) {
63376259Sgreen		status = SSH2_FX_FAILURE;
63465668Skris	} else {
63592555Sdes		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
63692555Sdes			ret = ftruncate(fd, a->size);
63792555Sdes			if (ret == -1)
63892555Sdes				status = errno_to_portable(errno);
63992555Sdes		}
64076259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
64198937Sdes#ifdef HAVE_FCHMOD
64265668Skris			ret = fchmod(fd, a->perm & 0777);
64398937Sdes#else
64498937Sdes			ret = chmod(name, a->perm & 0777);
64598937Sdes#endif
64665668Skris			if (ret == -1)
64765668Skris				status = errno_to_portable(errno);
64865668Skris		}
64976259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
65098937Sdes#ifdef HAVE_FUTIMES
65165668Skris			ret = futimes(fd, attrib_to_tv(a));
65298937Sdes#else
65398937Sdes			ret = utimes(name, attrib_to_tv(a));
65498937Sdes#endif
65565668Skris			if (ret == -1)
65665668Skris				status = errno_to_portable(errno);
65765668Skris		}
65876259Sgreen		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
65998937Sdes#ifdef HAVE_FCHOWN
66076259Sgreen			ret = fchown(fd, a->uid, a->gid);
66198937Sdes#else
66298937Sdes			ret = chown(name, a->uid, a->gid);
66398937Sdes#endif
66476259Sgreen			if (ret == -1)
66576259Sgreen				status = errno_to_portable(errno);
66676259Sgreen		}
66765668Skris	}
66865668Skris	send_status(id, status);
66965668Skris}
67065668Skris
67192555Sdesstatic void
67265668Skrisprocess_opendir(void)
67365668Skris{
67465668Skris	DIR *dirp = NULL;
67565668Skris	char *path;
67676259Sgreen	int handle, status = SSH2_FX_FAILURE;
67765668Skris	u_int32_t id;
67865668Skris
67965668Skris	id = get_int();
68065668Skris	path = get_string(NULL);
68199060Sdes	TRACE("opendir id %u path %s", id, path);
68276259Sgreen	dirp = opendir(path);
68365668Skris	if (dirp == NULL) {
68465668Skris		status = errno_to_portable(errno);
68565668Skris	} else {
686113908Sdes		handle = handle_new(HANDLE_DIR, path, 0, dirp);
68765668Skris		if (handle < 0) {
68865668Skris			closedir(dirp);
68965668Skris		} else {
69065668Skris			send_handle(id, handle);
69176259Sgreen			status = SSH2_FX_OK;
69265668Skris		}
69376259Sgreen
69465668Skris	}
69576259Sgreen	if (status != SSH2_FX_OK)
69665668Skris		send_status(id, status);
69765668Skris	xfree(path);
69865668Skris}
69965668Skris
70092555Sdesstatic void
70165668Skrisprocess_readdir(void)
70265668Skris{
70365668Skris	DIR *dirp;
70465668Skris	struct dirent *dp;
70565668Skris	char *path;
70665668Skris	int handle;
70765668Skris	u_int32_t id;
70865668Skris
70965668Skris	id = get_int();
71065668Skris	handle = get_handle();
71199060Sdes	TRACE("readdir id %u handle %d", id, handle);
71265668Skris	dirp = handle_to_dir(handle);
71365668Skris	path = handle_to_name(handle);
71465668Skris	if (dirp == NULL || path == NULL) {
71576259Sgreen		send_status(id, SSH2_FX_FAILURE);
71665668Skris	} else {
71765668Skris		struct stat st;
71865668Skris		char pathname[1024];
71965668Skris		Stat *stats;
72065668Skris		int nstats = 10, count = 0, i;
72199060Sdes
72265668Skris		stats = xmalloc(nstats * sizeof(Stat));
72365668Skris		while ((dp = readdir(dirp)) != NULL) {
72465668Skris			if (count >= nstats) {
72565668Skris				nstats *= 2;
72665668Skris				stats = xrealloc(stats, nstats * sizeof(Stat));
72765668Skris			}
72865668Skris/* XXX OVERFLOW ? */
72992555Sdes			snprintf(pathname, sizeof pathname, "%s%s%s", path,
73092555Sdes			    strcmp(path, "/") ? "/" : "", dp->d_name);
73165668Skris			if (lstat(pathname, &st) < 0)
73265668Skris				continue;
73376259Sgreen			stat_to_attrib(&st, &(stats[count].attrib));
73465668Skris			stats[count].name = xstrdup(dp->d_name);
735106121Sdes			stats[count].long_name = ls_file(dp->d_name, &st, 0);
73665668Skris			count++;
73765668Skris			/* send up to 100 entries in one message */
73876259Sgreen			/* XXX check packet size instead */
73965668Skris			if (count == 100)
74065668Skris				break;
74165668Skris		}
74276259Sgreen		if (count > 0) {
74376259Sgreen			send_names(id, count, stats);
74492555Sdes			for (i = 0; i < count; i++) {
74576259Sgreen				xfree(stats[i].name);
74676259Sgreen				xfree(stats[i].long_name);
74776259Sgreen			}
74876259Sgreen		} else {
74976259Sgreen			send_status(id, SSH2_FX_EOF);
75065668Skris		}
75165668Skris		xfree(stats);
75265668Skris	}
75365668Skris}
75465668Skris
75592555Sdesstatic void
75665668Skrisprocess_remove(void)
75765668Skris{
75865668Skris	char *name;
75965668Skris	u_int32_t id;
76076259Sgreen	int status = SSH2_FX_FAILURE;
76165668Skris	int ret;
76265668Skris
76365668Skris	id = get_int();
76465668Skris	name = get_string(NULL);
76599060Sdes	TRACE("remove id %u name %s", id, name);
76676259Sgreen	ret = unlink(name);
76776259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
76865668Skris	send_status(id, status);
76965668Skris	xfree(name);
77065668Skris}
77165668Skris
77292555Sdesstatic void
77365668Skrisprocess_mkdir(void)
77465668Skris{
77565668Skris	Attrib *a;
77665668Skris	u_int32_t id;
77765668Skris	char *name;
77876259Sgreen	int ret, mode, status = SSH2_FX_FAILURE;
77965668Skris
78065668Skris	id = get_int();
78165668Skris	name = get_string(NULL);
78265668Skris	a = get_attrib();
78376259Sgreen	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
78476259Sgreen	    a->perm & 0777 : 0777;
78599060Sdes	TRACE("mkdir id %u name %s mode 0%o", id, name, mode);
78665668Skris	ret = mkdir(name, mode);
78776259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
78865668Skris	send_status(id, status);
78965668Skris	xfree(name);
79065668Skris}
79165668Skris
79292555Sdesstatic void
79365668Skrisprocess_rmdir(void)
79465668Skris{
79565668Skris	u_int32_t id;
79665668Skris	char *name;
79765668Skris	int ret, status;
79865668Skris
79965668Skris	id = get_int();
80065668Skris	name = get_string(NULL);
80199060Sdes	TRACE("rmdir id %u name %s", id, name);
80265668Skris	ret = rmdir(name);
80376259Sgreen	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
80465668Skris	send_status(id, status);
80565668Skris	xfree(name);
80665668Skris}
80765668Skris
80892555Sdesstatic void
80965668Skrisprocess_realpath(void)
81065668Skris{
81165668Skris	char resolvedname[MAXPATHLEN];
81265668Skris	u_int32_t id;
81365668Skris	char *path;
81465668Skris
81565668Skris	id = get_int();
81665668Skris	path = get_string(NULL);
81776259Sgreen	if (path[0] == '\0') {
81876259Sgreen		xfree(path);
81976259Sgreen		path = xstrdup(".");
82076259Sgreen	}
82199060Sdes	TRACE("realpath id %u path %s", id, path);
82265668Skris	if (realpath(path, resolvedname) == NULL) {
82365668Skris		send_status(id, errno_to_portable(errno));
82465668Skris	} else {
82565668Skris		Stat s;
82665668Skris		attrib_clear(&s.attrib);
82765668Skris		s.name = s.long_name = resolvedname;
82865668Skris		send_names(id, 1, &s);
82965668Skris	}
83065668Skris	xfree(path);
83165668Skris}
83265668Skris
83392555Sdesstatic void
83465668Skrisprocess_rename(void)
83565668Skris{
83665668Skris	u_int32_t id;
83765668Skris	char *oldpath, *newpath;
838113908Sdes	int status;
839113908Sdes	struct stat sb;
84065668Skris
84165668Skris	id = get_int();
84265668Skris	oldpath = get_string(NULL);
84365668Skris	newpath = get_string(NULL);
84499060Sdes	TRACE("rename id %u old %s new %s", id, oldpath, newpath);
845113908Sdes	status = SSH2_FX_FAILURE;
846113908Sdes	if (lstat(oldpath, &sb) == -1)
847113908Sdes		status = errno_to_portable(errno);
848113908Sdes	else if (S_ISREG(sb.st_mode)) {
849113908Sdes		/* Race-free rename of regular files */
850113908Sdes		if (link(oldpath, newpath) == -1)
851113908Sdes			status = errno_to_portable(errno);
852113908Sdes		else if (unlink(oldpath) == -1) {
853113908Sdes			status = errno_to_portable(errno);
854113908Sdes			/* clean spare link */
855113908Sdes			unlink(newpath);
856113908Sdes		} else
857113908Sdes			status = SSH2_FX_OK;
858113908Sdes	} else if (stat(newpath, &sb) == -1) {
859113908Sdes		if (rename(oldpath, newpath) == -1)
860113908Sdes			status = errno_to_portable(errno);
861113908Sdes		else
862113908Sdes			status = SSH2_FX_OK;
86376259Sgreen	}
86465668Skris	send_status(id, status);
86565668Skris	xfree(oldpath);
86665668Skris	xfree(newpath);
86765668Skris}
86865668Skris
86992555Sdesstatic void
87076259Sgreenprocess_readlink(void)
87176259Sgreen{
87276259Sgreen	u_int32_t id;
87392555Sdes	int len;
87476259Sgreen	char link[MAXPATHLEN];
87576259Sgreen	char *path;
87665668Skris
87776259Sgreen	id = get_int();
87876259Sgreen	path = get_string(NULL);
87999060Sdes	TRACE("readlink id %u path %s", id, path);
88092555Sdes	if ((len = readlink(path, link, sizeof(link) - 1)) == -1)
88176259Sgreen		send_status(id, errno_to_portable(errno));
88276259Sgreen	else {
88376259Sgreen		Stat s;
88492555Sdes
88592555Sdes		link[len] = '\0';
88676259Sgreen		attrib_clear(&s.attrib);
88776259Sgreen		s.name = s.long_name = link;
88876259Sgreen		send_names(id, 1, &s);
88976259Sgreen	}
89076259Sgreen	xfree(path);
89176259Sgreen}
89276259Sgreen
89392555Sdesstatic void
89476259Sgreenprocess_symlink(void)
89576259Sgreen{
89676259Sgreen	u_int32_t id;
89776259Sgreen	char *oldpath, *newpath;
898113908Sdes	int ret, status;
89976259Sgreen
90076259Sgreen	id = get_int();
90176259Sgreen	oldpath = get_string(NULL);
90276259Sgreen	newpath = get_string(NULL);
90399060Sdes	TRACE("symlink id %u old %s new %s", id, oldpath, newpath);
904113908Sdes	/* this will fail if 'newpath' exists */
905113908Sdes	ret = symlink(oldpath, newpath);
906113908Sdes	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
90776259Sgreen	send_status(id, status);
90876259Sgreen	xfree(oldpath);
90976259Sgreen	xfree(newpath);
91076259Sgreen}
91176259Sgreen
91292555Sdesstatic void
91376259Sgreenprocess_extended(void)
91476259Sgreen{
91576259Sgreen	u_int32_t id;
91676259Sgreen	char *request;
91776259Sgreen
91876259Sgreen	id = get_int();
91976259Sgreen	request = get_string(NULL);
92076259Sgreen	send_status(id, SSH2_FX_OP_UNSUPPORTED);		/* MUST */
92176259Sgreen	xfree(request);
92276259Sgreen}
92376259Sgreen
92465668Skris/* stolen from ssh-agent */
92565668Skris
92692555Sdesstatic void
92765668Skrisprocess(void)
92865668Skris{
92976259Sgreen	u_int msg_len;
93098675Sdes	u_int buf_len;
93198675Sdes	u_int consumed;
93276259Sgreen	u_int type;
93376259Sgreen	u_char *cp;
93465668Skris
93598675Sdes	buf_len = buffer_len(&iqueue);
93698675Sdes	if (buf_len < 5)
93765668Skris		return;		/* Incomplete message. */
93892555Sdes	cp = buffer_ptr(&iqueue);
93965668Skris	msg_len = GET_32BIT(cp);
94065668Skris	if (msg_len > 256 * 1024) {
94165668Skris		error("bad message ");
94265668Skris		exit(11);
94365668Skris	}
94498675Sdes	if (buf_len < msg_len + 4)
94565668Skris		return;
94665668Skris	buffer_consume(&iqueue, 4);
94798675Sdes	buf_len -= 4;
94865668Skris	type = buffer_get_char(&iqueue);
94965668Skris	switch (type) {
95076259Sgreen	case SSH2_FXP_INIT:
95165668Skris		process_init();
95265668Skris		break;
95376259Sgreen	case SSH2_FXP_OPEN:
95465668Skris		process_open();
95565668Skris		break;
95676259Sgreen	case SSH2_FXP_CLOSE:
95765668Skris		process_close();
95865668Skris		break;
95976259Sgreen	case SSH2_FXP_READ:
96065668Skris		process_read();
96165668Skris		break;
96276259Sgreen	case SSH2_FXP_WRITE:
96365668Skris		process_write();
96465668Skris		break;
96576259Sgreen	case SSH2_FXP_LSTAT:
96665668Skris		process_lstat();
96765668Skris		break;
96876259Sgreen	case SSH2_FXP_FSTAT:
96965668Skris		process_fstat();
97065668Skris		break;
97176259Sgreen	case SSH2_FXP_SETSTAT:
97265668Skris		process_setstat();
97365668Skris		break;
97476259Sgreen	case SSH2_FXP_FSETSTAT:
97565668Skris		process_fsetstat();
97665668Skris		break;
97776259Sgreen	case SSH2_FXP_OPENDIR:
97865668Skris		process_opendir();
97965668Skris		break;
98076259Sgreen	case SSH2_FXP_READDIR:
98165668Skris		process_readdir();
98265668Skris		break;
98376259Sgreen	case SSH2_FXP_REMOVE:
98465668Skris		process_remove();
98565668Skris		break;
98676259Sgreen	case SSH2_FXP_MKDIR:
98765668Skris		process_mkdir();
98865668Skris		break;
98976259Sgreen	case SSH2_FXP_RMDIR:
99065668Skris		process_rmdir();
99165668Skris		break;
99276259Sgreen	case SSH2_FXP_REALPATH:
99365668Skris		process_realpath();
99465668Skris		break;
99576259Sgreen	case SSH2_FXP_STAT:
99665668Skris		process_stat();
99765668Skris		break;
99876259Sgreen	case SSH2_FXP_RENAME:
99965668Skris		process_rename();
100065668Skris		break;
100176259Sgreen	case SSH2_FXP_READLINK:
100276259Sgreen		process_readlink();
100376259Sgreen		break;
100476259Sgreen	case SSH2_FXP_SYMLINK:
100576259Sgreen		process_symlink();
100676259Sgreen		break;
100776259Sgreen	case SSH2_FXP_EXTENDED:
100876259Sgreen		process_extended();
100976259Sgreen		break;
101065668Skris	default:
101165668Skris		error("Unknown message %d", type);
101265668Skris		break;
101365668Skris	}
101498675Sdes	/* discard the remaining bytes from the current packet */
101598675Sdes	if (buf_len < buffer_len(&iqueue))
101698675Sdes		fatal("iqueue grows");
101798675Sdes	consumed = buf_len - buffer_len(&iqueue);
101898675Sdes	if (msg_len < consumed)
101998675Sdes		fatal("msg_len %d < consumed %d", msg_len, consumed);
102098675Sdes	if (msg_len > consumed)
102198675Sdes		buffer_consume(&iqueue, msg_len - consumed);
102265668Skris}
102365668Skris
102465668Skrisint
102565668Skrismain(int ac, char **av)
102665668Skris{
102776259Sgreen	fd_set *rset, *wset;
102865668Skris	int in, out, max;
102976259Sgreen	ssize_t len, olen, set_size;
103065668Skris
103176259Sgreen	/* XXX should use getopt */
103276259Sgreen
1033124208Sdes	__progname = ssh_get_progname(av[0]);
103465668Skris	handle_init();
103565668Skris
103676259Sgreen#ifdef DEBUG_SFTP_SERVER
103776259Sgreen	log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0);
103876259Sgreen#endif
103976259Sgreen
104065668Skris	in = dup(STDIN_FILENO);
104165668Skris	out = dup(STDOUT_FILENO);
104265668Skris
104398937Sdes#ifdef HAVE_CYGWIN
104498937Sdes	setmode(in, O_BINARY);
104598937Sdes	setmode(out, O_BINARY);
104698937Sdes#endif
104798937Sdes
104865668Skris	max = 0;
104965668Skris	if (in > max)
105065668Skris		max = in;
105165668Skris	if (out > max)
105265668Skris		max = out;
105365668Skris
105465668Skris	buffer_init(&iqueue);
105565668Skris	buffer_init(&oqueue);
105665668Skris
105776259Sgreen	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
105876259Sgreen	rset = (fd_set *)xmalloc(set_size);
105976259Sgreen	wset = (fd_set *)xmalloc(set_size);
106076259Sgreen
106165668Skris	for (;;) {
106276259Sgreen		memset(rset, 0, set_size);
106376259Sgreen		memset(wset, 0, set_size);
106465668Skris
106576259Sgreen		FD_SET(in, rset);
106665668Skris		olen = buffer_len(&oqueue);
106765668Skris		if (olen > 0)
106876259Sgreen			FD_SET(out, wset);
106965668Skris
107076259Sgreen		if (select(max+1, rset, wset, NULL, NULL) < 0) {
107165668Skris			if (errno == EINTR)
107265668Skris				continue;
107365668Skris			exit(2);
107465668Skris		}
107565668Skris
107665668Skris		/* copy stdin to iqueue */
107776259Sgreen		if (FD_ISSET(in, rset)) {
107865668Skris			char buf[4*4096];
107965668Skris			len = read(in, buf, sizeof buf);
108065668Skris			if (len == 0) {
108165668Skris				debug("read eof");
108265668Skris				exit(0);
108365668Skris			} else if (len < 0) {
108465668Skris				error("read error");
108565668Skris				exit(1);
108665668Skris			} else {
108765668Skris				buffer_append(&iqueue, buf, len);
108865668Skris			}
108965668Skris		}
109065668Skris		/* send oqueue to stdout */
109176259Sgreen		if (FD_ISSET(out, wset)) {
109265668Skris			len = write(out, buffer_ptr(&oqueue), olen);
109365668Skris			if (len < 0) {
109465668Skris				error("write error");
109565668Skris				exit(1);
109665668Skris			} else {
109765668Skris				buffer_consume(&oqueue, len);
109865668Skris			}
109965668Skris		}
110065668Skris		/* process requests from client */
110165668Skris		process();
110265668Skris	}
110365668Skris}
1104