sftp-server.c revision 221420
1327952Sdim/* $OpenBSD: sftp-server.c,v 1.93 2010/12/04 00:18:01 djm Exp $ */
2286425Sdim/*
3286425Sdim * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
4286425Sdim *
5286425Sdim * Permission to use, copy, modify, and distribute this software for any
6286425Sdim * purpose with or without fee is hereby granted, provided that the above
7286425Sdim * copyright notice and this permission notice appear in all copies.
8286425Sdim *
9286425Sdim * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10286425Sdim * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11286425Sdim * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12314564Sdim * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13286425Sdim * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14327952Sdim * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15286425Sdim * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16314564Sdim */
17286425Sdim
18286425Sdim#include "includes.h"
19341825Sdim
20314564Sdim#include <sys/types.h>
21314564Sdim#include <sys/param.h>
22286425Sdim#include <sys/stat.h>
23314564Sdim#ifdef HAVE_SYS_TIME_H
24286425Sdim# include <sys/time.h>
25286425Sdim#endif
26314564Sdim#ifdef HAVE_SYS_MOUNT_H
27286425Sdim#include <sys/mount.h>
28314564Sdim#endif
29314564Sdim#ifdef HAVE_SYS_STATVFS_H
30314564Sdim#include <sys/statvfs.h>
31314564Sdim#endif
32286425Sdim
33314564Sdim#include <dirent.h>
34286425Sdim#include <errno.h>
35314564Sdim#include <fcntl.h>
36286425Sdim#include <pwd.h>
37314564Sdim#include <stdlib.h>
38286425Sdim#include <stdio.h>
39286425Sdim#include <string.h>
40314564Sdim#include <pwd.h>
41314564Sdim#include <time.h>
42314564Sdim#include <unistd.h>
43314564Sdim#include <stdarg.h>
44314564Sdim
45286425Sdim#include "xmalloc.h"
46286425Sdim#include "buffer.h"
47314564Sdim#include "log.h"
48286425Sdim#include "misc.h"
49286425Sdim#include "uidswap.h"
50286425Sdim
51286425Sdim#include "sftp.h"
52286425Sdim#include "sftp-common.h"
53286425Sdim
54286425Sdim/* helper */
55286425Sdim#define get_int64()			buffer_get_int64(&iqueue);
56286425Sdim#define get_int()			buffer_get_int(&iqueue);
57286425Sdim#define get_string(lenp)		buffer_get_string(&iqueue, lenp);
58286425Sdim
59286425Sdim/* Our verbosity */
60286425SdimLogLevel log_level = SYSLOG_LEVEL_ERROR;
61286425Sdim
62314564Sdim/* Our client */
63286425Sdimstruct passwd *pw = NULL;
64286425Sdimchar *client_addr = NULL;
65314564Sdim
66314564Sdim/* input and output queue */
67286425SdimBuffer iqueue;
68314564SdimBuffer oqueue;
69286425Sdim
70327952Sdim/* Version of client */
71327952Sdimint version;
72327952Sdim
73327952Sdim/* Disable writes */
74327952Sdimint readonly;
75327952Sdim
76286425Sdim/* portable attributes, etc. */
77286425Sdim
78286425Sdimtypedef struct Stat Stat;
79296417Sdim
80314564Sdimstruct Stat {
81286425Sdim	char *name;
82296417Sdim	char *long_name;
83296417Sdim	Attrib attrib;
84296417Sdim};
85296417Sdim
86296417Sdimstatic int
87296417Sdimerrno_to_portable(int unixerrno)
88286425Sdim{
89286425Sdim	int ret = 0;
90296417Sdim
91286425Sdim	switch (unixerrno) {
92296417Sdim	case 0:
93314564Sdim		ret = SSH2_FX_OK;
94286425Sdim		break;
95286425Sdim	case ENOENT:
96286425Sdim	case ENOTDIR:
97286425Sdim	case EBADF:
98286425Sdim	case ELOOP:
99314564Sdim		ret = SSH2_FX_NO_SUCH_FILE;
100286425Sdim		break;
101286425Sdim	case EPERM:
102286425Sdim	case EACCES:
103286425Sdim	case EFAULT:
104314564Sdim		ret = SSH2_FX_PERMISSION_DENIED;
105314564Sdim		break;
106314564Sdim	case ENAMETOOLONG:
107314564Sdim	case EINVAL:
108286425Sdim		ret = SSH2_FX_BAD_MESSAGE;
109286425Sdim		break;
110309124Sdim	case ENOSYS:
111309124Sdim		ret = SSH2_FX_OP_UNSUPPORTED;
112286425Sdim		break;
113286425Sdim	default:
114286425Sdim		ret = SSH2_FX_FAILURE;
115286425Sdim		break;
116286425Sdim	}
117286425Sdim	return ret;
118327952Sdim}
119327952Sdim
120327952Sdimstatic int
121286425Sdimflags_from_portable(int pflags)
122286425Sdim{
123286425Sdim	int flags = 0;
124286425Sdim
125286425Sdim	if ((pflags & SSH2_FXF_READ) &&
126286425Sdim	    (pflags & SSH2_FXF_WRITE)) {
127286425Sdim		flags = O_RDWR;
128286425Sdim	} else if (pflags & SSH2_FXF_READ) {
129286425Sdim		flags = O_RDONLY;
130286425Sdim	} else if (pflags & SSH2_FXF_WRITE) {
131286425Sdim		flags = O_WRONLY;
132286425Sdim	}
133286425Sdim	if (pflags & SSH2_FXF_CREAT)
134286425Sdim		flags |= O_CREAT;
135286425Sdim	if (pflags & SSH2_FXF_TRUNC)
136286425Sdim		flags |= O_TRUNC;
137286425Sdim	if (pflags & SSH2_FXF_EXCL)
138286425Sdim		flags |= O_EXCL;
139286425Sdim	return flags;
140286425Sdim}
141286425Sdim
142286425Sdimstatic const char *
143286425Sdimstring_from_portable(int pflags)
144286425Sdim{
145286425Sdim	static char ret[128];
146286425Sdim
147286425Sdim	*ret = '\0';
148286425Sdim
149286425Sdim#define PAPPEND(str)	{				\
150286425Sdim		if (*ret != '\0')			\
151286425Sdim			strlcat(ret, ",", sizeof(ret));	\
152286425Sdim		strlcat(ret, str, sizeof(ret));		\
153286425Sdim	}
154286425Sdim
155286425Sdim	if (pflags & SSH2_FXF_READ)
156286425Sdim		PAPPEND("READ")
157286425Sdim	if (pflags & SSH2_FXF_WRITE)
158286425Sdim		PAPPEND("WRITE")
159286425Sdim	if (pflags & SSH2_FXF_CREAT)
160286425Sdim		PAPPEND("CREATE")
161314564Sdim	if (pflags & SSH2_FXF_TRUNC)
162286425Sdim		PAPPEND("TRUNCATE")
163286425Sdim	if (pflags & SSH2_FXF_EXCL)
164327952Sdim		PAPPEND("EXCL")
165286425Sdim
166286425Sdim	return ret;
167286425Sdim}
168309124Sdim
169286425Sdimstatic Attrib *
170286425Sdimget_attrib(void)
171286425Sdim{
172286425Sdim	return decode_attrib(&iqueue);
173286425Sdim}
174314564Sdim
175286425Sdim/* handle handles */
176286425Sdim
177286425Sdimtypedef struct Handle Handle;
178286425Sdimstruct Handle {
179286425Sdim	int use;
180321369Sdim	DIR *dirp;
181321369Sdim	int fd;
182286425Sdim	char *name;
183286425Sdim	u_int64_t bytes_read, bytes_write;
184327952Sdim	int next_unused;
185286425Sdim};
186286425Sdim
187286425Sdimenum {
188286425Sdim	HANDLE_UNUSED,
189327952Sdim	HANDLE_DIR,
190327952Sdim	HANDLE_FILE
191286425Sdim};
192327952Sdim
193286425SdimHandle *handles = NULL;
194286425Sdimu_int num_handles = 0;
195286425Sdimint first_unused_handle = -1;
196286425Sdim
197286425Sdimstatic void handle_unused(int i)
198286425Sdim{
199314564Sdim	handles[i].use = HANDLE_UNUSED;
200286425Sdim	handles[i].next_unused = first_unused_handle;
201286425Sdim	first_unused_handle = i;
202286425Sdim}
203286425Sdim
204314564Sdimstatic int
205314564Sdimhandle_new(int use, const char *name, int fd, DIR *dirp)
206286425Sdim{
207286425Sdim	int i;
208286425Sdim
209286425Sdim	if (first_unused_handle == -1) {
210286425Sdim		if (num_handles + 1 <= num_handles)
211286425Sdim			return -1;
212286425Sdim		num_handles++;
213286425Sdim		handles = xrealloc(handles, num_handles, sizeof(Handle));
214286425Sdim		handle_unused(num_handles - 1);
215286425Sdim	}
216286425Sdim
217286425Sdim	i = first_unused_handle;
218286425Sdim	first_unused_handle = handles[i].next_unused;
219286425Sdim
220286425Sdim	handles[i].use = use;
221286425Sdim	handles[i].dirp = dirp;
222286425Sdim	handles[i].fd = fd;
223286425Sdim	handles[i].name = xstrdup(name);
224286425Sdim	handles[i].bytes_read = handles[i].bytes_write = 0;
225286425Sdim
226286425Sdim	return i;
227286425Sdim}
228286425Sdim
229286425Sdimstatic int
230286425Sdimhandle_is_ok(int i, int type)
231286425Sdim{
232286425Sdim	return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
233286425Sdim}
234286425Sdim
235286425Sdimstatic int
236286425Sdimhandle_to_string(int handle, char **stringp, int *hlenp)
237321369Sdim{
238321369Sdim	if (stringp == NULL || hlenp == NULL)
239321369Sdim		return -1;
240321369Sdim	*stringp = xmalloc(sizeof(int32_t));
241321369Sdim	put_u32(*stringp, handle);
242286425Sdim	*hlenp = sizeof(int32_t);
243286425Sdim	return 0;
244286425Sdim}
245286425Sdim
246286425Sdimstatic int
247286425Sdimhandle_from_string(const char *handle, u_int hlen)
248286425Sdim{
249286425Sdim	int val;
250286425Sdim
251286425Sdim	if (hlen != sizeof(int32_t))
252286425Sdim		return -1;
253286425Sdim	val = get_u32(handle);
254286425Sdim	if (handle_is_ok(val, HANDLE_FILE) ||
255286425Sdim	    handle_is_ok(val, HANDLE_DIR))
256286425Sdim		return val;
257286425Sdim	return -1;
258286425Sdim}
259286425Sdim
260286425Sdimstatic char *
261286425Sdimhandle_to_name(int handle)
262286425Sdim{
263286425Sdim	if (handle_is_ok(handle, HANDLE_DIR)||
264286425Sdim	    handle_is_ok(handle, HANDLE_FILE))
265286425Sdim		return handles[handle].name;
266286425Sdim	return NULL;
267286425Sdim}
268286425Sdim
269286425Sdimstatic DIR *
270286425Sdimhandle_to_dir(int handle)
271286425Sdim{
272327952Sdim	if (handle_is_ok(handle, HANDLE_DIR))
273327952Sdim		return handles[handle].dirp;
274286425Sdim	return NULL;
275286425Sdim}
276286425Sdim
277286425Sdimstatic int
278286425Sdimhandle_to_fd(int handle)
279286425Sdim{
280286425Sdim	if (handle_is_ok(handle, HANDLE_FILE))
281286425Sdim		return handles[handle].fd;
282286425Sdim	return -1;
283286425Sdim}
284286425Sdim
285286425Sdimstatic void
286286425Sdimhandle_update_read(int handle, ssize_t bytes)
287286425Sdim{
288327952Sdim	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
289327952Sdim		handles[handle].bytes_read += bytes;
290286425Sdim}
291286425Sdim
292286425Sdimstatic void
293286425Sdimhandle_update_write(int handle, ssize_t bytes)
294286425Sdim{
295286425Sdim	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
296286425Sdim		handles[handle].bytes_write += bytes;
297286425Sdim}
298286425Sdim
299286425Sdimstatic u_int64_t
300286425Sdimhandle_bytes_read(int handle)
301286425Sdim{
302286425Sdim	if (handle_is_ok(handle, HANDLE_FILE))
303286425Sdim		return (handles[handle].bytes_read);
304286425Sdim	return 0;
305286425Sdim}
306286425Sdim
307327952Sdimstatic u_int64_t
308286425Sdimhandle_bytes_write(int handle)
309286425Sdim{
310286425Sdim	if (handle_is_ok(handle, HANDLE_FILE))
311314564Sdim		return (handles[handle].bytes_write);
312286425Sdim	return 0;
313286425Sdim}
314286425Sdim
315286425Sdimstatic int
316314564Sdimhandle_close(int handle)
317286425Sdim{
318286425Sdim	int ret = -1;
319286425Sdim
320286425Sdim	if (handle_is_ok(handle, HANDLE_FILE)) {
321286425Sdim		ret = close(handles[handle].fd);
322286425Sdim		xfree(handles[handle].name);
323286425Sdim		handle_unused(handle);
324286425Sdim	} else if (handle_is_ok(handle, HANDLE_DIR)) {
325286425Sdim		ret = closedir(handles[handle].dirp);
326286425Sdim		xfree(handles[handle].name);
327286425Sdim		handle_unused(handle);
328286425Sdim	} else {
329321369Sdim		errno = ENOENT;
330321369Sdim	}
331286425Sdim	return ret;
332286425Sdim}
333286425Sdim
334286425Sdimstatic void
335286425Sdimhandle_log_close(int handle, char *emsg)
336286425Sdim{
337286425Sdim	if (handle_is_ok(handle, HANDLE_FILE)) {
338286425Sdim		logit("%s%sclose \"%s\" bytes read %llu written %llu",
339286425Sdim		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
340286425Sdim		    handle_to_name(handle),
341286425Sdim		    (unsigned long long)handle_bytes_read(handle),
342286425Sdim		    (unsigned long long)handle_bytes_write(handle));
343286425Sdim	} else {
344286425Sdim		logit("%s%sclosedir \"%s\"",
345341825Sdim		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
346286425Sdim		    handle_to_name(handle));
347286425Sdim	}
348321369Sdim}
349286425Sdim
350286425Sdimstatic void
351286425Sdimhandle_log_exit(void)
352321369Sdim{
353286425Sdim	u_int i;
354286425Sdim
355286425Sdim	for (i = 0; i < num_handles; i++)
356286425Sdim		if (handles[i].use != HANDLE_UNUSED)
357286425Sdim			handle_log_close(i, "forced");
358286425Sdim}
359286425Sdim
360286425Sdimstatic int
361286425Sdimget_handle(void)
362286425Sdim{
363286425Sdim	char *handle;
364286425Sdim	int val = -1;
365286425Sdim	u_int hlen;
366286425Sdim
367286425Sdim	handle = get_string(&hlen);
368286425Sdim	if (hlen < 256)
369286425Sdim		val = handle_from_string(handle, hlen);
370286425Sdim	xfree(handle);
371286425Sdim	return val;
372286425Sdim}
373286425Sdim
374286425Sdim/* send replies */
375286425Sdim
376286425Sdimstatic void
377286425Sdimsend_msg(Buffer *m)
378286425Sdim{
379286425Sdim	int mlen = buffer_len(m);
380286425Sdim
381286425Sdim	buffer_put_int(&oqueue, mlen);
382286425Sdim	buffer_append(&oqueue, buffer_ptr(m), mlen);
383286425Sdim	buffer_consume(m, mlen);
384286425Sdim}
385286425Sdim
386286425Sdimstatic const char *
387286425Sdimstatus_to_message(u_int32_t status)
388321369Sdim{
389286425Sdim	const char *status_messages[] = {
390286425Sdim		"Success",			/* SSH_FX_OK */
391286425Sdim		"End of file",			/* SSH_FX_EOF */
392286425Sdim		"No such file",			/* SSH_FX_NO_SUCH_FILE */
393286425Sdim		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
394286425Sdim		"Failure",			/* SSH_FX_FAILURE */
395286425Sdim		"Bad message",			/* SSH_FX_BAD_MESSAGE */
396286425Sdim		"No connection",		/* SSH_FX_NO_CONNECTION */
397286425Sdim		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
398286425Sdim		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
399286425Sdim		"Unknown error"			/* Others */
400286425Sdim	};
401286425Sdim	return (status_messages[MIN(status,SSH2_FX_MAX)]);
402286425Sdim}
403286425Sdim
404286425Sdimstatic void
405286425Sdimsend_status(u_int32_t id, u_int32_t status)
406286425Sdim{
407286425Sdim	Buffer msg;
408286425Sdim
409286425Sdim	debug3("request %u: sent status %u", id, status);
410286425Sdim	if (log_level > SYSLOG_LEVEL_VERBOSE ||
411286425Sdim	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
412296417Sdim		logit("sent status %s", status_to_message(status));
413286425Sdim	buffer_init(&msg);
414286425Sdim	buffer_put_char(&msg, SSH2_FXP_STATUS);
415286425Sdim	buffer_put_int(&msg, id);
416286425Sdim	buffer_put_int(&msg, status);
417286425Sdim	if (version >= 3) {
418286425Sdim		buffer_put_cstring(&msg, status_to_message(status));
419286425Sdim		buffer_put_cstring(&msg, "");
420286425Sdim	}
421286425Sdim	send_msg(&msg);
422286425Sdim	buffer_free(&msg);
423286425Sdim}
424286425Sdimstatic void
425286425Sdimsend_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
426286425Sdim{
427286425Sdim	Buffer msg;
428286425Sdim
429341825Sdim	buffer_init(&msg);
430286425Sdim	buffer_put_char(&msg, type);
431286425Sdim	buffer_put_int(&msg, id);
432314564Sdim	buffer_put_string(&msg, data, dlen);
433314564Sdim	send_msg(&msg);
434327952Sdim	buffer_free(&msg);
435327952Sdim}
436286425Sdim
437286425Sdimstatic void
438286425Sdimsend_data(u_int32_t id, const char *data, int dlen)
439286425Sdim{
440286425Sdim	debug("request %u: sent data len %d", id, dlen);
441286425Sdim	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
442286425Sdim}
443286425Sdim
444286425Sdimstatic void
445314564Sdimsend_handle(u_int32_t id, int handle)
446286425Sdim{
447314564Sdim	char *string;
448314564Sdim	int hlen;
449286425Sdim
450286425Sdim	handle_to_string(handle, &string, &hlen);
451286425Sdim	debug("request %u: sent handle handle %d", id, handle);
452286425Sdim	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
453286425Sdim	xfree(string);
454286425Sdim}
455286425Sdim
456286425Sdimstatic void
457286425Sdimsend_names(u_int32_t id, int count, const Stat *stats)
458286425Sdim{
459286425Sdim	Buffer msg;
460286425Sdim	int i;
461286425Sdim
462286425Sdim	buffer_init(&msg);
463286425Sdim	buffer_put_char(&msg, SSH2_FXP_NAME);
464286425Sdim	buffer_put_int(&msg, id);
465314564Sdim	buffer_put_int(&msg, count);
466286425Sdim	debug("request %u: sent names count %d", id, count);
467327952Sdim	for (i = 0; i < count; i++) {
468327952Sdim		buffer_put_cstring(&msg, stats[i].name);
469327952Sdim		buffer_put_cstring(&msg, stats[i].long_name);
470286425Sdim		encode_attrib(&msg, &stats[i].attrib);
471314564Sdim	}
472314564Sdim	send_msg(&msg);
473314564Sdim	buffer_free(&msg);
474286425Sdim}
475286425Sdim
476286425Sdimstatic void
477314564Sdimsend_attrib(u_int32_t id, const Attrib *a)
478314564Sdim{
479286425Sdim	Buffer msg;
480286425Sdim
481286425Sdim	debug("request %u: sent attrib have 0x%x", id, a->flags);
482286425Sdim	buffer_init(&msg);
483314564Sdim	buffer_put_char(&msg, SSH2_FXP_ATTRS);
484286425Sdim	buffer_put_int(&msg, id);
485286425Sdim	encode_attrib(&msg, a);
486286425Sdim	send_msg(&msg);
487286425Sdim	buffer_free(&msg);
488314564Sdim}
489286425Sdim
490314564Sdimstatic void
491286425Sdimsend_statvfs(u_int32_t id, struct statvfs *st)
492286425Sdim{
493286425Sdim	Buffer msg;
494286425Sdim	u_int64_t flag;
495286425Sdim
496314564Sdim	flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
497286425Sdim	flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
498314564Sdim
499314564Sdim	buffer_init(&msg);
500286425Sdim	buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY);
501286425Sdim	buffer_put_int(&msg, id);
502286425Sdim	buffer_put_int64(&msg, st->f_bsize);
503286425Sdim	buffer_put_int64(&msg, st->f_frsize);
504286425Sdim	buffer_put_int64(&msg, st->f_blocks);
505286425Sdim	buffer_put_int64(&msg, st->f_bfree);
506286425Sdim	buffer_put_int64(&msg, st->f_bavail);
507286425Sdim	buffer_put_int64(&msg, st->f_files);
508286425Sdim	buffer_put_int64(&msg, st->f_ffree);
509286425Sdim	buffer_put_int64(&msg, st->f_favail);
510286425Sdim	buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid));
511286425Sdim	buffer_put_int64(&msg, flag);
512286425Sdim	buffer_put_int64(&msg, st->f_namemax);
513286425Sdim	send_msg(&msg);
514286425Sdim	buffer_free(&msg);
515286425Sdim}
516286425Sdim
517286425Sdim/* parse incoming */
518286425Sdim
519286425Sdimstatic void
520286425Sdimprocess_init(void)
521286425Sdim{
522286425Sdim	Buffer msg;
523286425Sdim
524286425Sdim	version = get_int();
525286425Sdim	verbose("received client version %d", version);
526286425Sdim	buffer_init(&msg);
527286425Sdim	buffer_put_char(&msg, SSH2_FXP_VERSION);
528286425Sdim	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
529286425Sdim	/* POSIX rename extension */
530286425Sdim	buffer_put_cstring(&msg, "posix-rename@openssh.com");
531286425Sdim	buffer_put_cstring(&msg, "1"); /* version */
532286425Sdim	/* statvfs extension */
533286425Sdim	buffer_put_cstring(&msg, "statvfs@openssh.com");
534286425Sdim	buffer_put_cstring(&msg, "2"); /* version */
535286425Sdim	/* fstatvfs extension */
536286425Sdim	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
537286425Sdim	buffer_put_cstring(&msg, "2"); /* version */
538327952Sdim	/* hardlink extension */
539286425Sdim	buffer_put_cstring(&msg, "hardlink@openssh.com");
540286425Sdim	buffer_put_cstring(&msg, "1"); /* version */
541286425Sdim	send_msg(&msg);
542286425Sdim	buffer_free(&msg);
543286425Sdim}
544286425Sdim
545286425Sdimstatic void
546286425Sdimprocess_open(void)
547286425Sdim{
548286425Sdim	u_int32_t id, pflags;
549286425Sdim	Attrib *a;
550286425Sdim	char *name;
551286425Sdim	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
552286425Sdim
553286425Sdim	id = get_int();
554286425Sdim	name = get_string(NULL);
555286425Sdim	pflags = get_int();		/* portable flags */
556286425Sdim	debug3("request %u: open flags %d", id, pflags);
557286425Sdim	a = get_attrib();
558286425Sdim	flags = flags_from_portable(pflags);
559286425Sdim	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
560286425Sdim	logit("open \"%s\" flags %s mode 0%o",
561286425Sdim	    name, string_from_portable(pflags), mode);
562286425Sdim	if (readonly &&
563286425Sdim	    ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR))
564286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
565286425Sdim	else {
566286425Sdim		fd = open(name, flags, mode);
567286425Sdim		if (fd < 0) {
568286425Sdim			status = errno_to_portable(errno);
569286425Sdim		} else {
570286425Sdim			handle = handle_new(HANDLE_FILE, name, fd, NULL);
571286425Sdim			if (handle < 0) {
572286425Sdim				close(fd);
573286425Sdim			} else {
574286425Sdim				send_handle(id, handle);
575286425Sdim				status = SSH2_FX_OK;
576286425Sdim			}
577286425Sdim		}
578341825Sdim	}
579286425Sdim	if (status != SSH2_FX_OK)
580286425Sdim		send_status(id, status);
581286425Sdim	xfree(name);
582286425Sdim}
583286425Sdim
584286425Sdimstatic void
585286425Sdimprocess_close(void)
586286425Sdim{
587286425Sdim	u_int32_t id;
588286425Sdim	int handle, ret, status = SSH2_FX_FAILURE;
589286425Sdim
590286425Sdim	id = get_int();
591286425Sdim	handle = get_handle();
592286425Sdim	debug3("request %u: close handle %u", id, handle);
593286425Sdim	handle_log_close(handle, NULL);
594286425Sdim	ret = handle_close(handle);
595286425Sdim	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
596286425Sdim	send_status(id, status);
597327952Sdim}
598286425Sdim
599286425Sdimstatic void
600286425Sdimprocess_read(void)
601286425Sdim{
602286425Sdim	char buf[64*1024];
603286425Sdim	u_int32_t id, len;
604286425Sdim	int handle, fd, ret, status = SSH2_FX_FAILURE;
605286425Sdim	u_int64_t off;
606286425Sdim
607286425Sdim	id = get_int();
608286425Sdim	handle = get_handle();
609286425Sdim	off = get_int64();
610286425Sdim	len = get_int();
611286425Sdim
612286425Sdim	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
613286425Sdim	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
614286425Sdim	if (len > sizeof buf) {
615286425Sdim		len = sizeof buf;
616286425Sdim		debug2("read change len %d", len);
617286425Sdim	}
618286425Sdim	fd = handle_to_fd(handle);
619286425Sdim	if (fd >= 0) {
620286425Sdim		if (lseek(fd, off, SEEK_SET) < 0) {
621286425Sdim			error("process_read: seek failed");
622286425Sdim			status = errno_to_portable(errno);
623286425Sdim		} else {
624286425Sdim			ret = read(fd, buf, len);
625286425Sdim			if (ret < 0) {
626286425Sdim				status = errno_to_portable(errno);
627286425Sdim			} else if (ret == 0) {
628286425Sdim				status = SSH2_FX_EOF;
629286425Sdim			} else {
630286425Sdim				send_data(id, buf, ret);
631286425Sdim				status = SSH2_FX_OK;
632286425Sdim				handle_update_read(handle, ret);
633286425Sdim			}
634286425Sdim		}
635286425Sdim	}
636286425Sdim	if (status != SSH2_FX_OK)
637286425Sdim		send_status(id, status);
638286425Sdim}
639286425Sdim
640286425Sdimstatic void
641286425Sdimprocess_write(void)
642286425Sdim{
643286425Sdim	u_int32_t id;
644286425Sdim	u_int64_t off;
645341825Sdim	u_int len;
646286425Sdim	int handle, fd, ret, status;
647286425Sdim	char *data;
648286425Sdim
649286425Sdim	id = get_int();
650286425Sdim	handle = get_handle();
651286425Sdim	off = get_int64();
652286425Sdim	data = get_string(&len);
653286425Sdim
654286425Sdim	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
655286425Sdim	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
656286425Sdim	fd = handle_to_fd(handle);
657286425Sdim
658286425Sdim	if (fd < 0)
659286425Sdim		status = SSH2_FX_FAILURE;
660286425Sdim	else if (readonly)
661286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
662314564Sdim	else {
663286425Sdim		if (lseek(fd, off, SEEK_SET) < 0) {
664286425Sdim			status = errno_to_portable(errno);
665341825Sdim			error("process_write: seek failed");
666286425Sdim		} else {
667286425Sdim/* XXX ATOMICIO ? */
668314564Sdim			ret = write(fd, data, len);
669314564Sdim			if (ret < 0) {
670341825Sdim				error("process_write: write failed");
671341825Sdim				status = errno_to_portable(errno);
672341825Sdim			} else if ((size_t)ret == len) {
673341825Sdim				status = SSH2_FX_OK;
674341825Sdim				handle_update_write(handle, ret);
675341825Sdim			} else {
676341825Sdim				debug2("nothing at all written");
677341825Sdim				status = SSH2_FX_FAILURE;
678341825Sdim			}
679341825Sdim		}
680341825Sdim	}
681286425Sdim	send_status(id, status);
682341825Sdim	xfree(data);
683341825Sdim}
684341825Sdim
685341825Sdimstatic void
686341825Sdimprocess_do_stat(int do_lstat)
687341825Sdim{
688341825Sdim	Attrib a;
689341825Sdim	struct stat st;
690341825Sdim	u_int32_t id;
691314564Sdim	char *name;
692286425Sdim	int ret, status = SSH2_FX_FAILURE;
693341825Sdim
694286425Sdim	id = get_int();
695314564Sdim	name = get_string(NULL);
696286425Sdim	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
697314564Sdim	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
698314564Sdim	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
699286425Sdim	if (ret < 0) {
700286425Sdim		status = errno_to_portable(errno);
701286425Sdim	} else {
702286425Sdim		stat_to_attrib(&st, &a);
703286425Sdim		send_attrib(id, &a);
704286425Sdim		status = SSH2_FX_OK;
705286425Sdim	}
706286425Sdim	if (status != SSH2_FX_OK)
707286425Sdim		send_status(id, status);
708286425Sdim	xfree(name);
709286425Sdim}
710286425Sdim
711286425Sdimstatic void
712286425Sdimprocess_stat(void)
713286425Sdim{
714286425Sdim	process_do_stat(0);
715314564Sdim}
716286425Sdim
717286425Sdimstatic void
718286425Sdimprocess_lstat(void)
719314564Sdim{
720286425Sdim	process_do_stat(1);
721314564Sdim}
722314564Sdim
723314564Sdimstatic void
724314564Sdimprocess_fstat(void)
725286425Sdim{
726327952Sdim	Attrib a;
727327952Sdim	struct stat st;
728327952Sdim	u_int32_t id;
729286425Sdim	int fd, ret, handle, status = SSH2_FX_FAILURE;
730286425Sdim
731286425Sdim	id = get_int();
732286425Sdim	handle = get_handle();
733286425Sdim	debug("request %u: fstat \"%s\" (handle %u)",
734286425Sdim	    id, handle_to_name(handle), handle);
735286425Sdim	fd = handle_to_fd(handle);
736286425Sdim	if (fd >= 0) {
737286425Sdim		ret = fstat(fd, &st);
738286425Sdim		if (ret < 0) {
739286425Sdim			status = errno_to_portable(errno);
740286425Sdim		} else {
741286425Sdim			stat_to_attrib(&st, &a);
742286425Sdim			send_attrib(id, &a);
743296417Sdim			status = SSH2_FX_OK;
744286425Sdim		}
745286425Sdim	}
746286425Sdim	if (status != SSH2_FX_OK)
747286425Sdim		send_status(id, status);
748314564Sdim}
749286425Sdim
750314564Sdimstatic struct timeval *
751286425Sdimattrib_to_tv(const Attrib *a)
752286425Sdim{
753286425Sdim	static struct timeval tv[2];
754286425Sdim
755286425Sdim	tv[0].tv_sec = a->atime;
756341825Sdim	tv[0].tv_usec = 0;
757286425Sdim	tv[1].tv_sec = a->mtime;
758286425Sdim	tv[1].tv_usec = 0;
759314564Sdim	return tv;
760286425Sdim}
761286425Sdim
762286425Sdimstatic void
763286425Sdimprocess_setstat(void)
764286425Sdim{
765286425Sdim	Attrib *a;
766286425Sdim	u_int32_t id;
767286425Sdim	char *name;
768286425Sdim	int status = SSH2_FX_OK, ret;
769286425Sdim
770286425Sdim	id = get_int();
771286425Sdim	name = get_string(NULL);
772286425Sdim	a = get_attrib();
773286425Sdim	debug("request %u: setstat name \"%s\"", id, name);
774286425Sdim	if (readonly) {
775286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
776286425Sdim		a->flags = 0;
777286425Sdim	}
778286425Sdim	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
779286425Sdim		logit("set \"%s\" size %llu",
780286425Sdim		    name, (unsigned long long)a->size);
781286425Sdim		ret = truncate(name, a->size);
782286425Sdim		if (ret == -1)
783286425Sdim			status = errno_to_portable(errno);
784286425Sdim	}
785286425Sdim	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
786286425Sdim		logit("set \"%s\" mode %04o", name, a->perm);
787286425Sdim		ret = chmod(name, a->perm & 07777);
788286425Sdim		if (ret == -1)
789286425Sdim			status = errno_to_portable(errno);
790286425Sdim	}
791286425Sdim	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
792286425Sdim		char buf[64];
793286425Sdim		time_t t = a->mtime;
794286425Sdim
795286425Sdim		strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
796286425Sdim		    localtime(&t));
797286425Sdim		logit("set \"%s\" modtime %s", name, buf);
798286425Sdim		ret = utimes(name, attrib_to_tv(a));
799286425Sdim		if (ret == -1)
800286425Sdim			status = errno_to_portable(errno);
801286425Sdim	}
802314564Sdim	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
803286425Sdim		logit("set \"%s\" owner %lu group %lu", name,
804286425Sdim		    (u_long)a->uid, (u_long)a->gid);
805286425Sdim		ret = chown(name, a->uid, a->gid);
806314564Sdim		if (ret == -1)
807286425Sdim			status = errno_to_portable(errno);
808286425Sdim	}
809286425Sdim	send_status(id, status);
810286425Sdim	xfree(name);
811286425Sdim}
812286425Sdim
813286425Sdimstatic void
814286425Sdimprocess_fsetstat(void)
815286425Sdim{
816286425Sdim	Attrib *a;
817286425Sdim	u_int32_t id;
818286425Sdim	int handle, fd, ret;
819286425Sdim	int status = SSH2_FX_OK;
820286425Sdim
821286425Sdim	id = get_int();
822286425Sdim	handle = get_handle();
823341825Sdim	a = get_attrib();
824286425Sdim	debug("request %u: fsetstat handle %d", id, handle);
825286425Sdim	fd = handle_to_fd(handle);
826286425Sdim	if (fd < 0)
827286425Sdim		status = SSH2_FX_FAILURE;
828286425Sdim	else if (readonly)
829286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
830286425Sdim	else {
831286425Sdim		char *name = handle_to_name(handle);
832286425Sdim
833341825Sdim		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
834286425Sdim			logit("set \"%s\" size %llu",
835286425Sdim			    name, (unsigned long long)a->size);
836286425Sdim			ret = ftruncate(fd, a->size);
837286425Sdim			if (ret == -1)
838286425Sdim				status = errno_to_portable(errno);
839286425Sdim		}
840286425Sdim		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
841286425Sdim			logit("set \"%s\" mode %04o", name, a->perm);
842286425Sdim#ifdef HAVE_FCHMOD
843286425Sdim			ret = fchmod(fd, a->perm & 07777);
844286425Sdim#else
845286425Sdim			ret = chmod(name, a->perm & 07777);
846286425Sdim#endif
847286425Sdim			if (ret == -1)
848286425Sdim				status = errno_to_portable(errno);
849286425Sdim		}
850286425Sdim		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
851286425Sdim			char buf[64];
852286425Sdim			time_t t = a->mtime;
853286425Sdim
854286425Sdim			strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
855286425Sdim			    localtime(&t));
856286425Sdim			logit("set \"%s\" modtime %s", name, buf);
857286425Sdim#ifdef HAVE_FUTIMES
858286425Sdim			ret = futimes(fd, attrib_to_tv(a));
859286425Sdim#else
860286425Sdim			ret = utimes(name, attrib_to_tv(a));
861286425Sdim#endif
862286425Sdim			if (ret == -1)
863286425Sdim				status = errno_to_portable(errno);
864286425Sdim		}
865314564Sdim		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
866314564Sdim			logit("set \"%s\" owner %lu group %lu", name,
867314564Sdim			    (u_long)a->uid, (u_long)a->gid);
868314564Sdim#ifdef HAVE_FCHOWN
869314564Sdim			ret = fchown(fd, a->uid, a->gid);
870314564Sdim#else
871314564Sdim			ret = chown(name, a->uid, a->gid);
872314564Sdim#endif
873314564Sdim			if (ret == -1)
874286425Sdim				status = errno_to_portable(errno);
875286425Sdim		}
876286425Sdim	}
877286425Sdim	send_status(id, status);
878286425Sdim}
879286425Sdim
880286425Sdimstatic void
881286425Sdimprocess_opendir(void)
882286425Sdim{
883286425Sdim	DIR *dirp = NULL;
884286425Sdim	char *path;
885286425Sdim	int handle, status = SSH2_FX_FAILURE;
886286425Sdim	u_int32_t id;
887286425Sdim
888286425Sdim	id = get_int();
889286425Sdim	path = get_string(NULL);
890286425Sdim	debug3("request %u: opendir", id);
891286425Sdim	logit("opendir \"%s\"", path);
892286425Sdim	dirp = opendir(path);
893286425Sdim	if (dirp == NULL) {
894286425Sdim		status = errno_to_portable(errno);
895286425Sdim	} else {
896286425Sdim		handle = handle_new(HANDLE_DIR, path, 0, dirp);
897286425Sdim		if (handle < 0) {
898286425Sdim			closedir(dirp);
899286425Sdim		} else {
900286425Sdim			send_handle(id, handle);
901286425Sdim			status = SSH2_FX_OK;
902286425Sdim		}
903286425Sdim
904286425Sdim	}
905286425Sdim	if (status != SSH2_FX_OK)
906286425Sdim		send_status(id, status);
907286425Sdim	xfree(path);
908286425Sdim}
909286425Sdim
910286425Sdimstatic void
911286425Sdimprocess_readdir(void)
912286425Sdim{
913286425Sdim	DIR *dirp;
914286425Sdim	struct dirent *dp;
915286425Sdim	char *path;
916286425Sdim	int handle;
917286425Sdim	u_int32_t id;
918286425Sdim
919286425Sdim	id = get_int();
920286425Sdim	handle = get_handle();
921286425Sdim	debug("request %u: readdir \"%s\" (handle %d)", id,
922286425Sdim	    handle_to_name(handle), handle);
923286425Sdim	dirp = handle_to_dir(handle);
924286425Sdim	path = handle_to_name(handle);
925314564Sdim	if (dirp == NULL || path == NULL) {
926286425Sdim		send_status(id, SSH2_FX_FAILURE);
927286425Sdim	} else {
928286425Sdim		struct stat st;
929314564Sdim		char pathname[MAXPATHLEN];
930286425Sdim		Stat *stats;
931286425Sdim		int nstats = 10, count = 0, i;
932286425Sdim
933286425Sdim		stats = xcalloc(nstats, sizeof(Stat));
934286425Sdim		while ((dp = readdir(dirp)) != NULL) {
935286425Sdim			if (count >= nstats) {
936286425Sdim				nstats *= 2;
937286425Sdim				stats = xrealloc(stats, nstats, sizeof(Stat));
938286425Sdim			}
939286425Sdim/* XXX OVERFLOW ? */
940286425Sdim			snprintf(pathname, sizeof pathname, "%s%s%s", path,
941286425Sdim			    strcmp(path, "/") ? "/" : "", dp->d_name);
942286425Sdim			if (lstat(pathname, &st) < 0)
943286425Sdim				continue;
944286425Sdim			stat_to_attrib(&st, &(stats[count].attrib));
945286425Sdim			stats[count].name = xstrdup(dp->d_name);
946286425Sdim			stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
947286425Sdim			count++;
948286425Sdim			/* send up to 100 entries in one message */
949286425Sdim			/* XXX check packet size instead */
950314564Sdim			if (count == 100)
951286425Sdim				break;
952286425Sdim		}
953286425Sdim		if (count > 0) {
954286425Sdim			send_names(id, count, stats);
955341825Sdim			for (i = 0; i < count; i++) {
956341825Sdim				xfree(stats[i].name);
957286425Sdim				xfree(stats[i].long_name);
958286425Sdim			}
959286425Sdim		} else {
960314564Sdim			send_status(id, SSH2_FX_EOF);
961286425Sdim		}
962286425Sdim		xfree(stats);
963286425Sdim	}
964286425Sdim}
965286425Sdim
966286425Sdimstatic void
967286425Sdimprocess_remove(void)
968286425Sdim{
969286425Sdim	char *name;
970286425Sdim	u_int32_t id;
971286425Sdim	int status = SSH2_FX_FAILURE;
972286425Sdim	int ret;
973286425Sdim
974286425Sdim	id = get_int();
975286425Sdim	name = get_string(NULL);
976286425Sdim	debug3("request %u: remove", id);
977286425Sdim	logit("remove name \"%s\"", name);
978286425Sdim	if (readonly)
979286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
980286425Sdim	else {
981286425Sdim		ret = unlink(name);
982286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
983286425Sdim	}
984286425Sdim	send_status(id, status);
985286425Sdim	xfree(name);
986286425Sdim}
987286425Sdim
988286425Sdimstatic void
989286425Sdimprocess_mkdir(void)
990286425Sdim{
991286425Sdim	Attrib *a;
992286425Sdim	u_int32_t id;
993286425Sdim	char *name;
994286425Sdim	int ret, mode, status = SSH2_FX_FAILURE;
995286425Sdim
996286425Sdim	id = get_int();
997286425Sdim	name = get_string(NULL);
998286425Sdim	a = get_attrib();
999341825Sdim	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
1000286425Sdim	    a->perm & 07777 : 0777;
1001286425Sdim	debug3("request %u: mkdir", id);
1002286425Sdim	logit("mkdir name \"%s\" mode 0%o", name, mode);
1003286425Sdim	if (readonly)
1004286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
1005286425Sdim	else {
1006286425Sdim		ret = mkdir(name, mode);
1007286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1008286425Sdim	}
1009286425Sdim	send_status(id, status);
1010341825Sdim	xfree(name);
1011286425Sdim}
1012286425Sdim
1013286425Sdimstatic void
1014286425Sdimprocess_rmdir(void)
1015286425Sdim{
1016286425Sdim	u_int32_t id;
1017286425Sdim	char *name;
1018286425Sdim	int ret, status;
1019286425Sdim
1020286425Sdim	id = get_int();
1021286425Sdim	name = get_string(NULL);
1022286425Sdim	debug3("request %u: rmdir", id);
1023286425Sdim	logit("rmdir name \"%s\"", name);
1024286425Sdim	if (readonly)
1025286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
1026286425Sdim	else {
1027286425Sdim		ret = rmdir(name);
1028286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1029286425Sdim	}
1030286425Sdim	send_status(id, status);
1031286425Sdim	xfree(name);
1032286425Sdim}
1033286425Sdim
1034286425Sdimstatic void
1035286425Sdimprocess_realpath(void)
1036286425Sdim{
1037286425Sdim	char resolvedname[MAXPATHLEN];
1038286425Sdim	u_int32_t id;
1039286425Sdim	char *path;
1040286425Sdim
1041286425Sdim	id = get_int();
1042286425Sdim	path = get_string(NULL);
1043286425Sdim	if (path[0] == '\0') {
1044286425Sdim		xfree(path);
1045286425Sdim		path = xstrdup(".");
1046286425Sdim	}
1047341825Sdim	debug3("request %u: realpath", id);
1048286425Sdim	verbose("realpath \"%s\"", path);
1049286425Sdim	if (realpath(path, resolvedname) == NULL) {
1050286425Sdim		send_status(id, errno_to_portable(errno));
1051286425Sdim	} else {
1052286425Sdim		Stat s;
1053286425Sdim		attrib_clear(&s.attrib);
1054286425Sdim		s.name = s.long_name = resolvedname;
1055286425Sdim		send_names(id, 1, &s);
1056286425Sdim	}
1057286425Sdim	xfree(path);
1058286425Sdim}
1059286425Sdim
1060286425Sdimstatic void
1061286425Sdimprocess_rename(void)
1062286425Sdim{
1063286425Sdim	u_int32_t id;
1064286425Sdim	char *oldpath, *newpath;
1065286425Sdim	int status;
1066286425Sdim	struct stat sb;
1067286425Sdim
1068286425Sdim	id = get_int();
1069341825Sdim	oldpath = get_string(NULL);
1070286425Sdim	newpath = get_string(NULL);
1071286425Sdim	debug3("request %u: rename", id);
1072286425Sdim	logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
1073286425Sdim	status = SSH2_FX_FAILURE;
1074286425Sdim	if (readonly)
1075341825Sdim		status = SSH2_FX_PERMISSION_DENIED;
1076341825Sdim	else if (lstat(oldpath, &sb) == -1)
1077286425Sdim		status = errno_to_portable(errno);
1078286425Sdim	else if (S_ISREG(sb.st_mode)) {
1079286425Sdim		/* Race-free rename of regular files */
1080286425Sdim		if (link(oldpath, newpath) == -1) {
1081286425Sdim			if (errno == EOPNOTSUPP || errno == ENOSYS
1082341825Sdim#ifdef EXDEV
1083286425Sdim			    || errno == EXDEV
1084286425Sdim#endif
1085286425Sdim#ifdef LINK_OPNOTSUPP_ERRNO
1086286425Sdim			    || errno == LINK_OPNOTSUPP_ERRNO
1087286425Sdim#endif
1088341825Sdim			    ) {
1089286425Sdim				struct stat st;
1090286425Sdim
1091286425Sdim				/*
1092286425Sdim				 * fs doesn't support links, so fall back to
1093341825Sdim				 * stat+rename.  This is racy.
1094341825Sdim				 */
1095341825Sdim				if (stat(newpath, &st) == -1) {
1096286425Sdim					if (rename(oldpath, newpath) == -1)
1097286425Sdim						status =
1098286425Sdim						    errno_to_portable(errno);
1099286425Sdim					else
1100321369Sdim						status = SSH2_FX_OK;
1101286425Sdim				}
1102286425Sdim			} else {
1103286425Sdim				status = errno_to_portable(errno);
1104286425Sdim			}
1105286425Sdim		} else if (unlink(oldpath) == -1) {
1106286425Sdim			status = errno_to_portable(errno);
1107286425Sdim			/* clean spare link */
1108286425Sdim			unlink(newpath);
1109286425Sdim		} else
1110286425Sdim			status = SSH2_FX_OK;
1111286425Sdim	} else if (stat(newpath, &sb) == -1) {
1112286425Sdim		if (rename(oldpath, newpath) == -1)
1113286425Sdim			status = errno_to_portable(errno);
1114286425Sdim		else
1115286425Sdim			status = SSH2_FX_OK;
1116286425Sdim	}
1117286425Sdim	send_status(id, status);
1118286425Sdim	xfree(oldpath);
1119286425Sdim	xfree(newpath);
1120286425Sdim}
1121286425Sdim
1122286425Sdimstatic void
1123286425Sdimprocess_readlink(void)
1124286425Sdim{
1125286425Sdim	u_int32_t id;
1126286425Sdim	int len;
1127286425Sdim	char buf[MAXPATHLEN];
1128286425Sdim	char *path;
1129286425Sdim
1130296417Sdim	id = get_int();
1131321369Sdim	path = get_string(NULL);
1132341825Sdim	debug3("request %u: readlink", id);
1133286425Sdim	verbose("readlink \"%s\"", path);
1134286425Sdim	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
1135286425Sdim		send_status(id, errno_to_portable(errno));
1136286425Sdim	else {
1137286425Sdim		Stat s;
1138286425Sdim
1139286425Sdim		buf[len] = '\0';
1140286425Sdim		attrib_clear(&s.attrib);
1141286425Sdim		s.name = s.long_name = buf;
1142286425Sdim		send_names(id, 1, &s);
1143286425Sdim	}
1144286425Sdim	xfree(path);
1145286425Sdim}
1146286425Sdim
1147286425Sdimstatic void
1148286425Sdimprocess_symlink(void)
1149286425Sdim{
1150286425Sdim	u_int32_t id;
1151286425Sdim	char *oldpath, *newpath;
1152286425Sdim	int ret, status;
1153286425Sdim
1154286425Sdim	id = get_int();
1155286425Sdim	oldpath = get_string(NULL);
1156286425Sdim	newpath = get_string(NULL);
1157286425Sdim	debug3("request %u: symlink", id);
1158286425Sdim	logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1159286425Sdim	/* this will fail if 'newpath' exists */
1160286425Sdim	if (readonly)
1161286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
1162286425Sdim	else {
1163286425Sdim		ret = symlink(oldpath, newpath);
1164286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1165341825Sdim	}
1166286425Sdim	send_status(id, status);
1167286425Sdim	xfree(oldpath);
1168286425Sdim	xfree(newpath);
1169286425Sdim}
1170286425Sdim
1171286425Sdimstatic void
1172286425Sdimprocess_extended_posix_rename(u_int32_t id)
1173286425Sdim{
1174286425Sdim	char *oldpath, *newpath;
1175286425Sdim	int ret, status;
1176286425Sdim
1177286425Sdim	oldpath = get_string(NULL);
1178286425Sdim	newpath = get_string(NULL);
1179286425Sdim	debug3("request %u: posix-rename", id);
1180286425Sdim	logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
1181286425Sdim	if (readonly)
1182286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
1183286425Sdim	else {
1184286425Sdim		ret = rename(oldpath, newpath);
1185286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1186286425Sdim	}
1187286425Sdim	send_status(id, status);
1188286425Sdim	xfree(oldpath);
1189286425Sdim	xfree(newpath);
1190286425Sdim}
1191286425Sdim
1192286425Sdimstatic void
1193286425Sdimprocess_extended_statvfs(u_int32_t id)
1194286425Sdim{
1195286425Sdim	char *path;
1196286425Sdim	struct statvfs st;
1197286425Sdim
1198286425Sdim	path = get_string(NULL);
1199286425Sdim	debug3("request %u: statfs", id);
1200286425Sdim	logit("statfs \"%s\"", path);
1201286425Sdim
1202314564Sdim	if (statvfs(path, &st) != 0)
1203286425Sdim		send_status(id, errno_to_portable(errno));
1204286425Sdim	else
1205286425Sdim		send_statvfs(id, &st);
1206286425Sdim        xfree(path);
1207296417Sdim}
1208286425Sdim
1209286425Sdimstatic void
1210286425Sdimprocess_extended_fstatvfs(u_int32_t id)
1211286425Sdim{
1212286425Sdim	int handle, fd;
1213286425Sdim	struct statvfs st;
1214286425Sdim
1215286425Sdim	handle = get_handle();
1216286425Sdim	debug("request %u: fstatvfs \"%s\" (handle %u)",
1217286425Sdim	    id, handle_to_name(handle), handle);
1218286425Sdim	if ((fd = handle_to_fd(handle)) < 0) {
1219286425Sdim		send_status(id, SSH2_FX_FAILURE);
1220286425Sdim		return;
1221286425Sdim	}
1222286425Sdim	if (fstatvfs(fd, &st) != 0)
1223286425Sdim		send_status(id, errno_to_portable(errno));
1224286425Sdim	else
1225286425Sdim		send_statvfs(id, &st);
1226286425Sdim}
1227286425Sdim
1228286425Sdimstatic void
1229286425Sdimprocess_extended_hardlink(u_int32_t id)
1230286425Sdim{
1231286425Sdim	char *oldpath, *newpath;
1232286425Sdim	int ret, status;
1233286425Sdim
1234286425Sdim	oldpath = get_string(NULL);
1235286425Sdim	newpath = get_string(NULL);
1236286425Sdim	debug3("request %u: hardlink", id);
1237286425Sdim	logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
1238286425Sdim	if (readonly)
1239286425Sdim		status = SSH2_FX_PERMISSION_DENIED;
1240286425Sdim	else {
1241286425Sdim		ret = link(oldpath, newpath);
1242286425Sdim		status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
1243286425Sdim	}
1244286425Sdim	send_status(id, status);
1245286425Sdim	xfree(oldpath);
1246286425Sdim	xfree(newpath);
1247286425Sdim}
1248286425Sdim
1249286425Sdimstatic void
1250286425Sdimprocess_extended(void)
1251286425Sdim{
1252321369Sdim	u_int32_t id;
1253321369Sdim	char *request;
1254286425Sdim
1255286425Sdim	id = get_int();
1256286425Sdim	request = get_string(NULL);
1257286425Sdim	if (strcmp(request, "posix-rename@openssh.com") == 0)
1258286425Sdim		process_extended_posix_rename(id);
1259327952Sdim	else if (strcmp(request, "statvfs@openssh.com") == 0)
1260327952Sdim		process_extended_statvfs(id);
1261327952Sdim	else if (strcmp(request, "fstatvfs@openssh.com") == 0)
1262286425Sdim		process_extended_fstatvfs(id);
1263286425Sdim	else if (strcmp(request, "hardlink@openssh.com") == 0)
1264286425Sdim		process_extended_hardlink(id);
1265286425Sdim	else
1266286425Sdim		send_status(id, SSH2_FX_OP_UNSUPPORTED);	/* MUST */
1267286425Sdim	xfree(request);
1268286425Sdim}
1269286425Sdim
1270286425Sdim/* stolen from ssh-agent */
1271286425Sdim
1272286425Sdimstatic void
1273286425Sdimprocess(void)
1274309124Sdim{
1275309124Sdim	u_int msg_len;
1276309124Sdim	u_int buf_len;
1277286425Sdim	u_int consumed;
1278286425Sdim	u_int type;
1279286425Sdim	u_char *cp;
1280286425Sdim
1281286425Sdim	buf_len = buffer_len(&iqueue);
1282286425Sdim	if (buf_len < 5)
1283286425Sdim		return;		/* Incomplete message. */
1284286425Sdim	cp = buffer_ptr(&iqueue);
1285309124Sdim	msg_len = get_u32(cp);
1286286425Sdim	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1287286425Sdim		error("bad message from %s local user %s",
1288286425Sdim		    client_addr, pw->pw_name);
1289286425Sdim		sftp_server_cleanup_exit(11);
1290286425Sdim	}
1291286425Sdim	if (buf_len < msg_len + 4)
1292286425Sdim		return;
1293286425Sdim	buffer_consume(&iqueue, 4);
1294286425Sdim	buf_len -= 4;
1295286425Sdim	type = buffer_get_char(&iqueue);
1296286425Sdim	switch (type) {
1297286425Sdim	case SSH2_FXP_INIT:
1298286425Sdim		process_init();
1299286425Sdim		break;
1300286425Sdim	case SSH2_FXP_OPEN:
1301286425Sdim		process_open();
1302286425Sdim		break;
1303286425Sdim	case SSH2_FXP_CLOSE:
1304309124Sdim		process_close();
1305286425Sdim		break;
1306286425Sdim	case SSH2_FXP_READ:
1307286425Sdim		process_read();
1308286425Sdim		break;
1309286425Sdim	case SSH2_FXP_WRITE:
1310286425Sdim		process_write();
1311314564Sdim		break;
1312286425Sdim	case SSH2_FXP_LSTAT:
1313286425Sdim		process_lstat();
1314286425Sdim		break;
1315286425Sdim	case SSH2_FXP_FSTAT:
1316314564Sdim		process_fstat();
1317314564Sdim		break;
1318	case SSH2_FXP_SETSTAT:
1319		process_setstat();
1320		break;
1321	case SSH2_FXP_FSETSTAT:
1322		process_fsetstat();
1323		break;
1324	case SSH2_FXP_OPENDIR:
1325		process_opendir();
1326		break;
1327	case SSH2_FXP_READDIR:
1328		process_readdir();
1329		break;
1330	case SSH2_FXP_REMOVE:
1331		process_remove();
1332		break;
1333	case SSH2_FXP_MKDIR:
1334		process_mkdir();
1335		break;
1336	case SSH2_FXP_RMDIR:
1337		process_rmdir();
1338		break;
1339	case SSH2_FXP_REALPATH:
1340		process_realpath();
1341		break;
1342	case SSH2_FXP_STAT:
1343		process_stat();
1344		break;
1345	case SSH2_FXP_RENAME:
1346		process_rename();
1347		break;
1348	case SSH2_FXP_READLINK:
1349		process_readlink();
1350		break;
1351	case SSH2_FXP_SYMLINK:
1352		process_symlink();
1353		break;
1354	case SSH2_FXP_EXTENDED:
1355		process_extended();
1356		break;
1357	default:
1358		error("Unknown message %d", type);
1359		break;
1360	}
1361	/* discard the remaining bytes from the current packet */
1362	if (buf_len < buffer_len(&iqueue)) {
1363		error("iqueue grew unexpectedly");
1364		sftp_server_cleanup_exit(255);
1365	}
1366	consumed = buf_len - buffer_len(&iqueue);
1367	if (msg_len < consumed) {
1368		error("msg_len %d < consumed %d", msg_len, consumed);
1369		sftp_server_cleanup_exit(255);
1370	}
1371	if (msg_len > consumed)
1372		buffer_consume(&iqueue, msg_len - consumed);
1373}
1374
1375/* Cleanup handler that logs active handles upon normal exit */
1376void
1377sftp_server_cleanup_exit(int i)
1378{
1379	if (pw != NULL && client_addr != NULL) {
1380		handle_log_exit();
1381		logit("session closed for local user %s from [%s]",
1382		    pw->pw_name, client_addr);
1383	}
1384	_exit(i);
1385}
1386
1387static void
1388sftp_server_usage(void)
1389{
1390	extern char *__progname;
1391
1392	fprintf(stderr,
1393	    "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n",
1394	    __progname);
1395	exit(1);
1396}
1397
1398int
1399sftp_server_main(int argc, char **argv, struct passwd *user_pw)
1400{
1401	fd_set *rset, *wset;
1402	int in, out, max, ch, skipargs = 0, log_stderr = 0;
1403	ssize_t len, olen, set_size;
1404	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1405	char *cp, buf[4*4096];
1406	long mask;
1407
1408	extern char *optarg;
1409	extern char *__progname;
1410
1411	__progname = ssh_get_progname(argv[0]);
1412	log_init(__progname, log_level, log_facility, log_stderr);
1413
1414	while (!skipargs && (ch = getopt(argc, argv, "f:l:u:cehR")) != -1) {
1415		switch (ch) {
1416		case 'R':
1417			readonly = 1;
1418			break;
1419		case 'c':
1420			/*
1421			 * Ignore all arguments if we are invoked as a
1422			 * shell using "sftp-server -c command"
1423			 */
1424			skipargs = 1;
1425			break;
1426		case 'e':
1427			log_stderr = 1;
1428			break;
1429		case 'l':
1430			log_level = log_level_number(optarg);
1431			if (log_level == SYSLOG_LEVEL_NOT_SET)
1432				error("Invalid log level \"%s\"", optarg);
1433			break;
1434		case 'f':
1435			log_facility = log_facility_number(optarg);
1436			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1437				error("Invalid log facility \"%s\"", optarg);
1438			break;
1439		case 'u':
1440			errno = 0;
1441			mask = strtol(optarg, &cp, 8);
1442			if (mask < 0 || mask > 0777 || *cp != '\0' ||
1443			    cp == optarg || (mask == 0 && errno != 0))
1444				fatal("Invalid umask \"%s\"", optarg);
1445			(void)umask((mode_t)mask);
1446			break;
1447		case 'h':
1448		default:
1449			sftp_server_usage();
1450		}
1451	}
1452
1453	log_init(__progname, log_level, log_facility, log_stderr);
1454
1455	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1456		client_addr = xstrdup(cp);
1457		if ((cp = strchr(client_addr, ' ')) == NULL) {
1458			error("Malformed SSH_CONNECTION variable: \"%s\"",
1459			    getenv("SSH_CONNECTION"));
1460			sftp_server_cleanup_exit(255);
1461		}
1462		*cp = '\0';
1463	} else
1464		client_addr = xstrdup("UNKNOWN");
1465
1466	pw = pwcopy(user_pw);
1467
1468	logit("session opened for local user %s from [%s]",
1469	    pw->pw_name, client_addr);
1470
1471	in = STDIN_FILENO;
1472	out = STDOUT_FILENO;
1473
1474#ifdef HAVE_CYGWIN
1475	setmode(in, O_BINARY);
1476	setmode(out, O_BINARY);
1477#endif
1478
1479	max = 0;
1480	if (in > max)
1481		max = in;
1482	if (out > max)
1483		max = out;
1484
1485	buffer_init(&iqueue);
1486	buffer_init(&oqueue);
1487
1488	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
1489	rset = (fd_set *)xmalloc(set_size);
1490	wset = (fd_set *)xmalloc(set_size);
1491
1492	for (;;) {
1493		memset(rset, 0, set_size);
1494		memset(wset, 0, set_size);
1495
1496		/*
1497		 * Ensure that we can read a full buffer and handle
1498		 * the worst-case length packet it can generate,
1499		 * otherwise apply backpressure by stopping reads.
1500		 */
1501		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
1502		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1503			FD_SET(in, rset);
1504
1505		olen = buffer_len(&oqueue);
1506		if (olen > 0)
1507			FD_SET(out, wset);
1508
1509		if (select(max+1, rset, wset, NULL, NULL) < 0) {
1510			if (errno == EINTR)
1511				continue;
1512			error("select: %s", strerror(errno));
1513			sftp_server_cleanup_exit(2);
1514		}
1515
1516		/* copy stdin to iqueue */
1517		if (FD_ISSET(in, rset)) {
1518			len = read(in, buf, sizeof buf);
1519			if (len == 0) {
1520				debug("read eof");
1521				sftp_server_cleanup_exit(0);
1522			} else if (len < 0) {
1523				error("read: %s", strerror(errno));
1524				sftp_server_cleanup_exit(1);
1525			} else {
1526				buffer_append(&iqueue, buf, len);
1527			}
1528		}
1529		/* send oqueue to stdout */
1530		if (FD_ISSET(out, wset)) {
1531			len = write(out, buffer_ptr(&oqueue), olen);
1532			if (len < 0) {
1533				error("write: %s", strerror(errno));
1534				sftp_server_cleanup_exit(1);
1535			} else {
1536				buffer_consume(&oqueue, len);
1537			}
1538		}
1539
1540		/*
1541		 * Process requests from client if we can fit the results
1542		 * into the output buffer, otherwise stop processing input
1543		 * and let the output queue drain.
1544		 */
1545		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
1546			process();
1547	}
1548}
1549