xref: /titanic_50/usr/src/cmd/ssh/sftp-server/sftp-server.c (revision 90685d2c52744c6540828f16cdd2db815d467e37)
17c478bd9Sstevel@tonic-gate /*
2*90685d2cSjp161948  * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
3*90685d2cSjp161948  *
4*90685d2cSjp161948  * Permission to use, copy, modify, and distribute this software for any
5*90685d2cSjp161948  * purpose with or without fee is hereby granted, provided that the above
6*90685d2cSjp161948  * copyright notice and this permission notice appear in all copies.
7*90685d2cSjp161948  *
8*90685d2cSjp161948  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9*90685d2cSjp161948  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10*90685d2cSjp161948  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11*90685d2cSjp161948  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12*90685d2cSjp161948  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13*90685d2cSjp161948  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14*90685d2cSjp161948  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
157c478bd9Sstevel@tonic-gate  */
167c478bd9Sstevel@tonic-gate 
17*90685d2cSjp161948 /* $OpenBSD: sftp-server.c,v 1.71 2007/01/03 07:22:36 stevesk Exp $ */
187c478bd9Sstevel@tonic-gate 
197c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
207c478bd9Sstevel@tonic-gate 
21*90685d2cSjp161948 #include "includes.h"
22*90685d2cSjp161948 
23*90685d2cSjp161948 #include <sys/types.h>
24*90685d2cSjp161948 #include <sys/param.h>
25*90685d2cSjp161948 #include <sys/stat.h>
26*90685d2cSjp161948 #ifdef HAVE_SYS_TIME_H
27*90685d2cSjp161948 # include <sys/time.h>
28*90685d2cSjp161948 #endif
29*90685d2cSjp161948 
30*90685d2cSjp161948 #include <dirent.h>
31*90685d2cSjp161948 #include <errno.h>
32*90685d2cSjp161948 #include <fcntl.h>
33*90685d2cSjp161948 #include <pwd.h>
34*90685d2cSjp161948 #include <stdlib.h>
35*90685d2cSjp161948 #include <stdio.h>
36*90685d2cSjp161948 #include <string.h>
37*90685d2cSjp161948 #include <pwd.h>
38*90685d2cSjp161948 #include <time.h>
39*90685d2cSjp161948 #include <unistd.h>
40*90685d2cSjp161948 #include <stdarg.h>
41*90685d2cSjp161948 
42*90685d2cSjp161948 #include "xmalloc.h"
437c478bd9Sstevel@tonic-gate #include "buffer.h"
447c478bd9Sstevel@tonic-gate #include "bufaux.h"
457c478bd9Sstevel@tonic-gate #include "log.h"
46*90685d2cSjp161948 #include "misc.h"
47*90685d2cSjp161948 #include "uidswap.h"
487c478bd9Sstevel@tonic-gate 
497c478bd9Sstevel@tonic-gate #include "sftp.h"
507c478bd9Sstevel@tonic-gate #include "sftp-common.h"
517c478bd9Sstevel@tonic-gate 
527c478bd9Sstevel@tonic-gate #ifdef HAVE___PROGNAME
537c478bd9Sstevel@tonic-gate extern char *__progname;
547c478bd9Sstevel@tonic-gate #else
557c478bd9Sstevel@tonic-gate char *__progname;
567c478bd9Sstevel@tonic-gate #endif
577c478bd9Sstevel@tonic-gate 
58*90685d2cSjp161948 /* helper */
59*90685d2cSjp161948 #define get_int64()			buffer_get_int64(&iqueue);
60*90685d2cSjp161948 #define get_int()			buffer_get_int(&iqueue);
61*90685d2cSjp161948 #define get_string(lenp)		buffer_get_string(&iqueue, lenp);
62*90685d2cSjp161948 
63*90685d2cSjp161948 static void cleanup_exit(int i);
64*90685d2cSjp161948 
65*90685d2cSjp161948 /* Our verbosity */
66*90685d2cSjp161948 LogLevel log_level = SYSLOG_LEVEL_ERROR;
67*90685d2cSjp161948 
68*90685d2cSjp161948 /* Our client */
69*90685d2cSjp161948 struct passwd *pw = NULL;
70*90685d2cSjp161948 char *client_addr = NULL;
71*90685d2cSjp161948 
727c478bd9Sstevel@tonic-gate /* input and output queue */
737c478bd9Sstevel@tonic-gate Buffer iqueue;
747c478bd9Sstevel@tonic-gate Buffer oqueue;
757c478bd9Sstevel@tonic-gate 
767c478bd9Sstevel@tonic-gate /* Version of client */
777c478bd9Sstevel@tonic-gate int version;
787c478bd9Sstevel@tonic-gate 
79*90685d2cSjp161948 /* portable attributes, etc. */
807c478bd9Sstevel@tonic-gate 
817c478bd9Sstevel@tonic-gate typedef struct Stat Stat;
827c478bd9Sstevel@tonic-gate 
837c478bd9Sstevel@tonic-gate struct Stat {
847c478bd9Sstevel@tonic-gate 	char *name;
857c478bd9Sstevel@tonic-gate 	char *long_name;
867c478bd9Sstevel@tonic-gate 	Attrib attrib;
877c478bd9Sstevel@tonic-gate };
887c478bd9Sstevel@tonic-gate 
897c478bd9Sstevel@tonic-gate static int
907c478bd9Sstevel@tonic-gate errno_to_portable(int unixerrno)
917c478bd9Sstevel@tonic-gate {
927c478bd9Sstevel@tonic-gate 	int ret = 0;
937c478bd9Sstevel@tonic-gate 
947c478bd9Sstevel@tonic-gate 	switch (unixerrno) {
957c478bd9Sstevel@tonic-gate 	case 0:
967c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_OK;
977c478bd9Sstevel@tonic-gate 		break;
987c478bd9Sstevel@tonic-gate 	case ENOENT:
997c478bd9Sstevel@tonic-gate 	case ENOTDIR:
1007c478bd9Sstevel@tonic-gate 	case EBADF:
1017c478bd9Sstevel@tonic-gate 	case ELOOP:
1027c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_NO_SUCH_FILE;
1037c478bd9Sstevel@tonic-gate 		break;
1047c478bd9Sstevel@tonic-gate 	case EPERM:
1057c478bd9Sstevel@tonic-gate 	case EACCES:
1067c478bd9Sstevel@tonic-gate 	case EFAULT:
1077c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_PERMISSION_DENIED;
1087c478bd9Sstevel@tonic-gate 		break;
1097c478bd9Sstevel@tonic-gate 	case ENAMETOOLONG:
1107c478bd9Sstevel@tonic-gate 	case EINVAL:
1117c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_BAD_MESSAGE;
1127c478bd9Sstevel@tonic-gate 		break;
1137c478bd9Sstevel@tonic-gate 	default:
1147c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_FAILURE;
1157c478bd9Sstevel@tonic-gate 		break;
1167c478bd9Sstevel@tonic-gate 	}
1177c478bd9Sstevel@tonic-gate 	return ret;
1187c478bd9Sstevel@tonic-gate }
1197c478bd9Sstevel@tonic-gate 
1207c478bd9Sstevel@tonic-gate static int
1217c478bd9Sstevel@tonic-gate flags_from_portable(int pflags)
1227c478bd9Sstevel@tonic-gate {
1237c478bd9Sstevel@tonic-gate 	int flags = 0;
1247c478bd9Sstevel@tonic-gate 
1257c478bd9Sstevel@tonic-gate 	if ((pflags & SSH2_FXF_READ) &&
1267c478bd9Sstevel@tonic-gate 	    (pflags & SSH2_FXF_WRITE)) {
1277c478bd9Sstevel@tonic-gate 		flags = O_RDWR;
1287c478bd9Sstevel@tonic-gate 	} else if (pflags & SSH2_FXF_READ) {
1297c478bd9Sstevel@tonic-gate 		flags = O_RDONLY;
1307c478bd9Sstevel@tonic-gate 	} else if (pflags & SSH2_FXF_WRITE) {
1317c478bd9Sstevel@tonic-gate 		flags = O_WRONLY;
1327c478bd9Sstevel@tonic-gate 	}
1337c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_CREAT)
1347c478bd9Sstevel@tonic-gate 		flags |= O_CREAT;
1357c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_TRUNC)
1367c478bd9Sstevel@tonic-gate 		flags |= O_TRUNC;
1377c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_EXCL)
1387c478bd9Sstevel@tonic-gate 		flags |= O_EXCL;
1397c478bd9Sstevel@tonic-gate 	return flags;
1407c478bd9Sstevel@tonic-gate }
1417c478bd9Sstevel@tonic-gate 
142*90685d2cSjp161948 static const char *
143*90685d2cSjp161948 string_from_portable(int pflags)
144*90685d2cSjp161948 {
145*90685d2cSjp161948 	static char ret[128];
146*90685d2cSjp161948 
147*90685d2cSjp161948 	*ret = '\0';
148*90685d2cSjp161948 
149*90685d2cSjp161948 #define PAPPEND(str)	{				\
150*90685d2cSjp161948 		if (*ret != '\0')			\
151*90685d2cSjp161948 			strlcat(ret, ",", sizeof(ret));	\
152*90685d2cSjp161948 		strlcat(ret, str, sizeof(ret));		\
153*90685d2cSjp161948 	}
154*90685d2cSjp161948 
155*90685d2cSjp161948 	if (pflags & SSH2_FXF_READ)
156*90685d2cSjp161948 		PAPPEND("READ")
157*90685d2cSjp161948 	if (pflags & SSH2_FXF_WRITE)
158*90685d2cSjp161948 		PAPPEND("WRITE")
159*90685d2cSjp161948 	if (pflags & SSH2_FXF_CREAT)
160*90685d2cSjp161948 		PAPPEND("CREATE")
161*90685d2cSjp161948 	if (pflags & SSH2_FXF_TRUNC)
162*90685d2cSjp161948 		PAPPEND("TRUNCATE")
163*90685d2cSjp161948 	if (pflags & SSH2_FXF_EXCL)
164*90685d2cSjp161948 		PAPPEND("EXCL")
165*90685d2cSjp161948 
166*90685d2cSjp161948 	return ret;
167*90685d2cSjp161948 }
168*90685d2cSjp161948 
1697c478bd9Sstevel@tonic-gate static Attrib *
1707c478bd9Sstevel@tonic-gate get_attrib(void)
1717c478bd9Sstevel@tonic-gate {
1727c478bd9Sstevel@tonic-gate 	return decode_attrib(&iqueue);
1737c478bd9Sstevel@tonic-gate }
1747c478bd9Sstevel@tonic-gate 
1757c478bd9Sstevel@tonic-gate /* handle handles */
1767c478bd9Sstevel@tonic-gate 
1777c478bd9Sstevel@tonic-gate typedef struct Handle Handle;
1787c478bd9Sstevel@tonic-gate struct Handle {
1797c478bd9Sstevel@tonic-gate 	int use;
1807c478bd9Sstevel@tonic-gate 	DIR *dirp;
1817c478bd9Sstevel@tonic-gate 	int fd;
1827c478bd9Sstevel@tonic-gate 	char *name;
183*90685d2cSjp161948 	u_int64_t bytes_read, bytes_write;
1847c478bd9Sstevel@tonic-gate };
1857c478bd9Sstevel@tonic-gate 
1867c478bd9Sstevel@tonic-gate enum {
1877c478bd9Sstevel@tonic-gate 	HANDLE_UNUSED,
1887c478bd9Sstevel@tonic-gate 	HANDLE_DIR,
1897c478bd9Sstevel@tonic-gate 	HANDLE_FILE
1907c478bd9Sstevel@tonic-gate };
1917c478bd9Sstevel@tonic-gate 
1927c478bd9Sstevel@tonic-gate Handle	handles[100];
1937c478bd9Sstevel@tonic-gate 
1947c478bd9Sstevel@tonic-gate static void
1957c478bd9Sstevel@tonic-gate handle_init(void)
1967c478bd9Sstevel@tonic-gate {
197*90685d2cSjp161948 	u_int i;
1987c478bd9Sstevel@tonic-gate 
1997c478bd9Sstevel@tonic-gate 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
2007c478bd9Sstevel@tonic-gate 		handles[i].use = HANDLE_UNUSED;
2017c478bd9Sstevel@tonic-gate }
2027c478bd9Sstevel@tonic-gate 
2037c478bd9Sstevel@tonic-gate static int
204*90685d2cSjp161948 handle_new(int use, const char *name, int fd, DIR *dirp)
2057c478bd9Sstevel@tonic-gate {
206*90685d2cSjp161948 	u_int i;
2077c478bd9Sstevel@tonic-gate 
2087c478bd9Sstevel@tonic-gate 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) {
2097c478bd9Sstevel@tonic-gate 		if (handles[i].use == HANDLE_UNUSED) {
2107c478bd9Sstevel@tonic-gate 			handles[i].use = use;
2117c478bd9Sstevel@tonic-gate 			handles[i].dirp = dirp;
2127c478bd9Sstevel@tonic-gate 			handles[i].fd = fd;
213*90685d2cSjp161948 			handles[i].name = xstrdup(name);
214*90685d2cSjp161948 			handles[i].bytes_read = handles[i].bytes_write = 0;
2157c478bd9Sstevel@tonic-gate 			return i;
2167c478bd9Sstevel@tonic-gate 		}
2177c478bd9Sstevel@tonic-gate 	}
2187c478bd9Sstevel@tonic-gate 	return -1;
2197c478bd9Sstevel@tonic-gate }
2207c478bd9Sstevel@tonic-gate 
2217c478bd9Sstevel@tonic-gate static int
2227c478bd9Sstevel@tonic-gate handle_is_ok(int i, int type)
2237c478bd9Sstevel@tonic-gate {
224*90685d2cSjp161948 	return i >= 0 && (u_int)i < sizeof(handles)/sizeof(Handle) &&
2257c478bd9Sstevel@tonic-gate 	    handles[i].use == type;
2267c478bd9Sstevel@tonic-gate }
2277c478bd9Sstevel@tonic-gate 
2287c478bd9Sstevel@tonic-gate static int
2297c478bd9Sstevel@tonic-gate handle_to_string(int handle, char **stringp, int *hlenp)
2307c478bd9Sstevel@tonic-gate {
2317c478bd9Sstevel@tonic-gate 	if (stringp == NULL || hlenp == NULL)
2327c478bd9Sstevel@tonic-gate 		return -1;
2337c478bd9Sstevel@tonic-gate 	*stringp = xmalloc(sizeof(int32_t));
234*90685d2cSjp161948 	put_u32(*stringp, handle);
2357c478bd9Sstevel@tonic-gate 	*hlenp = sizeof(int32_t);
2367c478bd9Sstevel@tonic-gate 	return 0;
2377c478bd9Sstevel@tonic-gate }
2387c478bd9Sstevel@tonic-gate 
2397c478bd9Sstevel@tonic-gate static int
240*90685d2cSjp161948 handle_from_string(const char *handle, u_int hlen)
2417c478bd9Sstevel@tonic-gate {
2427c478bd9Sstevel@tonic-gate 	int val;
2437c478bd9Sstevel@tonic-gate 
2447c478bd9Sstevel@tonic-gate 	if (hlen != sizeof(int32_t))
2457c478bd9Sstevel@tonic-gate 		return -1;
246*90685d2cSjp161948 	val = get_u32(handle);
2477c478bd9Sstevel@tonic-gate 	if (handle_is_ok(val, HANDLE_FILE) ||
2487c478bd9Sstevel@tonic-gate 	    handle_is_ok(val, HANDLE_DIR))
2497c478bd9Sstevel@tonic-gate 		return val;
2507c478bd9Sstevel@tonic-gate 	return -1;
2517c478bd9Sstevel@tonic-gate }
2527c478bd9Sstevel@tonic-gate 
2537c478bd9Sstevel@tonic-gate static char *
2547c478bd9Sstevel@tonic-gate handle_to_name(int handle)
2557c478bd9Sstevel@tonic-gate {
2567c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_DIR)||
2577c478bd9Sstevel@tonic-gate 	    handle_is_ok(handle, HANDLE_FILE))
2587c478bd9Sstevel@tonic-gate 		return handles[handle].name;
2597c478bd9Sstevel@tonic-gate 	return NULL;
2607c478bd9Sstevel@tonic-gate }
2617c478bd9Sstevel@tonic-gate 
2627c478bd9Sstevel@tonic-gate static DIR *
2637c478bd9Sstevel@tonic-gate handle_to_dir(int handle)
2647c478bd9Sstevel@tonic-gate {
2657c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_DIR))
2667c478bd9Sstevel@tonic-gate 		return handles[handle].dirp;
2677c478bd9Sstevel@tonic-gate 	return NULL;
2687c478bd9Sstevel@tonic-gate }
2697c478bd9Sstevel@tonic-gate 
2707c478bd9Sstevel@tonic-gate static int
2717c478bd9Sstevel@tonic-gate handle_to_fd(int handle)
2727c478bd9Sstevel@tonic-gate {
2737c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_FILE))
2747c478bd9Sstevel@tonic-gate 		return handles[handle].fd;
2757c478bd9Sstevel@tonic-gate 	return -1;
2767c478bd9Sstevel@tonic-gate }
2777c478bd9Sstevel@tonic-gate 
278*90685d2cSjp161948 static void
279*90685d2cSjp161948 handle_update_read(int handle, ssize_t bytes)
280*90685d2cSjp161948 {
281*90685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
282*90685d2cSjp161948 		handles[handle].bytes_read += bytes;
283*90685d2cSjp161948 }
284*90685d2cSjp161948 
285*90685d2cSjp161948 static void
286*90685d2cSjp161948 handle_update_write(int handle, ssize_t bytes)
287*90685d2cSjp161948 {
288*90685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
289*90685d2cSjp161948 		handles[handle].bytes_write += bytes;
290*90685d2cSjp161948 }
291*90685d2cSjp161948 
292*90685d2cSjp161948 static u_int64_t
293*90685d2cSjp161948 handle_bytes_read(int handle)
294*90685d2cSjp161948 {
295*90685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE))
296*90685d2cSjp161948 		return (handles[handle].bytes_read);
297*90685d2cSjp161948 	return 0;
298*90685d2cSjp161948 }
299*90685d2cSjp161948 
300*90685d2cSjp161948 static u_int64_t
301*90685d2cSjp161948 handle_bytes_write(int handle)
302*90685d2cSjp161948 {
303*90685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE))
304*90685d2cSjp161948 		return (handles[handle].bytes_write);
305*90685d2cSjp161948 	return 0;
306*90685d2cSjp161948 }
307*90685d2cSjp161948 
3087c478bd9Sstevel@tonic-gate static int
3097c478bd9Sstevel@tonic-gate handle_close(int handle)
3107c478bd9Sstevel@tonic-gate {
3117c478bd9Sstevel@tonic-gate 	int ret = -1;
3127c478bd9Sstevel@tonic-gate 
3137c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_FILE)) {
3147c478bd9Sstevel@tonic-gate 		ret = close(handles[handle].fd);
3157c478bd9Sstevel@tonic-gate 		handles[handle].use = HANDLE_UNUSED;
316*90685d2cSjp161948 		xfree(handles[handle].name);
3177c478bd9Sstevel@tonic-gate 	} else if (handle_is_ok(handle, HANDLE_DIR)) {
3187c478bd9Sstevel@tonic-gate 		ret = closedir(handles[handle].dirp);
3197c478bd9Sstevel@tonic-gate 		handles[handle].use = HANDLE_UNUSED;
320*90685d2cSjp161948 		xfree(handles[handle].name);
3217c478bd9Sstevel@tonic-gate 	} else {
3227c478bd9Sstevel@tonic-gate 		errno = ENOENT;
3237c478bd9Sstevel@tonic-gate 	}
3247c478bd9Sstevel@tonic-gate 	return ret;
3257c478bd9Sstevel@tonic-gate }
3267c478bd9Sstevel@tonic-gate 
327*90685d2cSjp161948 static void
328*90685d2cSjp161948 handle_log_close(int handle, char *emsg)
329*90685d2cSjp161948 {
330*90685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE)) {
331*90685d2cSjp161948 		log("%s%sclose \"%s\" bytes read %llu written %llu",
332*90685d2cSjp161948 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
333*90685d2cSjp161948 		    handle_to_name(handle),
334*90685d2cSjp161948 		    (unsigned long long)handle_bytes_read(handle),
335*90685d2cSjp161948 		    (unsigned long long)handle_bytes_write(handle));
336*90685d2cSjp161948 	} else {
337*90685d2cSjp161948 		log("%s%sclosedir \"%s\"",
338*90685d2cSjp161948 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
339*90685d2cSjp161948 		    handle_to_name(handle));
340*90685d2cSjp161948 	}
341*90685d2cSjp161948 }
342*90685d2cSjp161948 
343*90685d2cSjp161948 static void
344*90685d2cSjp161948 handle_log_exit(void)
345*90685d2cSjp161948 {
346*90685d2cSjp161948 	u_int i;
347*90685d2cSjp161948 
348*90685d2cSjp161948 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
349*90685d2cSjp161948 		if (handles[i].use != HANDLE_UNUSED)
350*90685d2cSjp161948 			handle_log_close(i, "forced");
351*90685d2cSjp161948 }
352*90685d2cSjp161948 
3537c478bd9Sstevel@tonic-gate static int
3547c478bd9Sstevel@tonic-gate get_handle(void)
3557c478bd9Sstevel@tonic-gate {
3567c478bd9Sstevel@tonic-gate 	char *handle;
3577c478bd9Sstevel@tonic-gate 	int val = -1;
3587c478bd9Sstevel@tonic-gate 	u_int hlen;
3597c478bd9Sstevel@tonic-gate 
3607c478bd9Sstevel@tonic-gate 	handle = get_string(&hlen);
3617c478bd9Sstevel@tonic-gate 	if (hlen < 256)
3627c478bd9Sstevel@tonic-gate 		val = handle_from_string(handle, hlen);
3637c478bd9Sstevel@tonic-gate 	xfree(handle);
3647c478bd9Sstevel@tonic-gate 	return val;
3657c478bd9Sstevel@tonic-gate }
3667c478bd9Sstevel@tonic-gate 
3677c478bd9Sstevel@tonic-gate /* send replies */
3687c478bd9Sstevel@tonic-gate 
3697c478bd9Sstevel@tonic-gate static void
3707c478bd9Sstevel@tonic-gate send_msg(Buffer *m)
3717c478bd9Sstevel@tonic-gate {
3727c478bd9Sstevel@tonic-gate 	int mlen = buffer_len(m);
3737c478bd9Sstevel@tonic-gate 
3747c478bd9Sstevel@tonic-gate 	buffer_put_int(&oqueue, mlen);
3757c478bd9Sstevel@tonic-gate 	buffer_append(&oqueue, buffer_ptr(m), mlen);
3767c478bd9Sstevel@tonic-gate 	buffer_consume(m, mlen);
3777c478bd9Sstevel@tonic-gate }
3787c478bd9Sstevel@tonic-gate 
379*90685d2cSjp161948 static const char *
380*90685d2cSjp161948 status_to_message(u_int32_t status)
3817c478bd9Sstevel@tonic-gate {
3827c478bd9Sstevel@tonic-gate 	const char *status_messages[] = {
3837c478bd9Sstevel@tonic-gate 		"Success",			/* SSH_FX_OK */
3847c478bd9Sstevel@tonic-gate 		"End of file",			/* SSH_FX_EOF */
3857c478bd9Sstevel@tonic-gate 		"No such file",			/* SSH_FX_NO_SUCH_FILE */
3867c478bd9Sstevel@tonic-gate 		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
3877c478bd9Sstevel@tonic-gate 		"Failure",			/* SSH_FX_FAILURE */
3887c478bd9Sstevel@tonic-gate 		"Bad message",			/* SSH_FX_BAD_MESSAGE */
3897c478bd9Sstevel@tonic-gate 		"No connection",		/* SSH_FX_NO_CONNECTION */
3907c478bd9Sstevel@tonic-gate 		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
3917c478bd9Sstevel@tonic-gate 		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
3927c478bd9Sstevel@tonic-gate 		"Unknown error"			/* Others */
3937c478bd9Sstevel@tonic-gate 	};
394*90685d2cSjp161948 	return (status_messages[MIN(status,SSH2_FX_MAX)]);
395*90685d2cSjp161948 }
3967c478bd9Sstevel@tonic-gate 
397*90685d2cSjp161948 static void
398*90685d2cSjp161948 send_status(u_int32_t id, u_int32_t status)
399*90685d2cSjp161948 {
400*90685d2cSjp161948 	Buffer msg;
401*90685d2cSjp161948 
402*90685d2cSjp161948 	debug3("request %u: sent status %u", id, status);
403*90685d2cSjp161948 	if (log_level > SYSLOG_LEVEL_VERBOSE ||
404*90685d2cSjp161948 	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
405*90685d2cSjp161948 		log("sent status %s", status_to_message(status));
4067c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4077c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_STATUS);
4087c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
409*90685d2cSjp161948 	buffer_put_int(&msg, status);
4107c478bd9Sstevel@tonic-gate 	if (version >= 3) {
411*90685d2cSjp161948 		buffer_put_cstring(&msg, status_to_message(status));
4127c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, "");
4137c478bd9Sstevel@tonic-gate 	}
4147c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4157c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4167c478bd9Sstevel@tonic-gate }
4177c478bd9Sstevel@tonic-gate static void
418*90685d2cSjp161948 send_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
4197c478bd9Sstevel@tonic-gate {
4207c478bd9Sstevel@tonic-gate 	Buffer msg;
4217c478bd9Sstevel@tonic-gate 
4227c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4237c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, type);
4247c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4257c478bd9Sstevel@tonic-gate 	buffer_put_string(&msg, data, dlen);
4267c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4277c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4287c478bd9Sstevel@tonic-gate }
4297c478bd9Sstevel@tonic-gate 
4307c478bd9Sstevel@tonic-gate static void
431*90685d2cSjp161948 send_data(u_int32_t id, const char *data, int dlen)
4327c478bd9Sstevel@tonic-gate {
433*90685d2cSjp161948 	debug("request %u: sent data len %d", id, dlen);
4347c478bd9Sstevel@tonic-gate 	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
4357c478bd9Sstevel@tonic-gate }
4367c478bd9Sstevel@tonic-gate 
4377c478bd9Sstevel@tonic-gate static void
4387c478bd9Sstevel@tonic-gate send_handle(u_int32_t id, int handle)
4397c478bd9Sstevel@tonic-gate {
4407c478bd9Sstevel@tonic-gate 	char *string;
4417c478bd9Sstevel@tonic-gate 	int hlen;
4427c478bd9Sstevel@tonic-gate 
4437c478bd9Sstevel@tonic-gate 	handle_to_string(handle, &string, &hlen);
444*90685d2cSjp161948 	debug("request %u: sent handle handle %d", id, handle);
4457c478bd9Sstevel@tonic-gate 	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
4467c478bd9Sstevel@tonic-gate 	xfree(string);
4477c478bd9Sstevel@tonic-gate }
4487c478bd9Sstevel@tonic-gate 
4497c478bd9Sstevel@tonic-gate static void
450*90685d2cSjp161948 send_names(u_int32_t id, int count, const Stat *stats)
4517c478bd9Sstevel@tonic-gate {
4527c478bd9Sstevel@tonic-gate 	Buffer msg;
4537c478bd9Sstevel@tonic-gate 	int i;
4547c478bd9Sstevel@tonic-gate 
4557c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4567c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_NAME);
4577c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4587c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, count);
459*90685d2cSjp161948 	debug("request %u: sent names count %d", id, count);
4607c478bd9Sstevel@tonic-gate 	for (i = 0; i < count; i++) {
4617c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, stats[i].name);
4627c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, stats[i].long_name);
4637c478bd9Sstevel@tonic-gate 		encode_attrib(&msg, &stats[i].attrib);
4647c478bd9Sstevel@tonic-gate 	}
4657c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4667c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4677c478bd9Sstevel@tonic-gate }
4687c478bd9Sstevel@tonic-gate 
4697c478bd9Sstevel@tonic-gate static void
470*90685d2cSjp161948 send_attrib(u_int32_t id, const Attrib *a)
4717c478bd9Sstevel@tonic-gate {
4727c478bd9Sstevel@tonic-gate 	Buffer msg;
4737c478bd9Sstevel@tonic-gate 
474*90685d2cSjp161948 	debug("request %u: sent attrib have 0x%x", id, a->flags);
4757c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4767c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_ATTRS);
4777c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4787c478bd9Sstevel@tonic-gate 	encode_attrib(&msg, a);
4797c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4807c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4817c478bd9Sstevel@tonic-gate }
4827c478bd9Sstevel@tonic-gate 
4837c478bd9Sstevel@tonic-gate /* parse incoming */
4847c478bd9Sstevel@tonic-gate 
4857c478bd9Sstevel@tonic-gate static void
4867c478bd9Sstevel@tonic-gate process_init(void)
4877c478bd9Sstevel@tonic-gate {
4887c478bd9Sstevel@tonic-gate 	Buffer msg;
4897c478bd9Sstevel@tonic-gate 
4907c478bd9Sstevel@tonic-gate 	version = get_int();
491*90685d2cSjp161948 	verbose("received client version %d", version);
4927c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4937c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_VERSION);
4947c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
4957c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4967c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4977c478bd9Sstevel@tonic-gate }
4987c478bd9Sstevel@tonic-gate 
4997c478bd9Sstevel@tonic-gate static void
5007c478bd9Sstevel@tonic-gate process_open(void)
5017c478bd9Sstevel@tonic-gate {
5027c478bd9Sstevel@tonic-gate 	u_int32_t id, pflags;
5037c478bd9Sstevel@tonic-gate 	Attrib *a;
5047c478bd9Sstevel@tonic-gate 	char *name;
5057c478bd9Sstevel@tonic-gate 	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
5067c478bd9Sstevel@tonic-gate 
5077c478bd9Sstevel@tonic-gate 	id = get_int();
5087c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
5097c478bd9Sstevel@tonic-gate 	pflags = get_int();		/* portable flags */
510*90685d2cSjp161948 	debug3("request %u: open flags %d", id, pflags);
5117c478bd9Sstevel@tonic-gate 	a = get_attrib();
5127c478bd9Sstevel@tonic-gate 	flags = flags_from_portable(pflags);
5137c478bd9Sstevel@tonic-gate 	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
514*90685d2cSjp161948 	log("open \"%s\" flags %s mode 0%o",
515*90685d2cSjp161948 	    name, string_from_portable(pflags), mode);
5167c478bd9Sstevel@tonic-gate 	fd = open(name, flags, mode);
5177c478bd9Sstevel@tonic-gate 	if (fd < 0) {
5187c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
5197c478bd9Sstevel@tonic-gate 	} else {
520*90685d2cSjp161948 		handle = handle_new(HANDLE_FILE, name, fd, NULL);
5217c478bd9Sstevel@tonic-gate 		if (handle < 0) {
5227c478bd9Sstevel@tonic-gate 			close(fd);
5237c478bd9Sstevel@tonic-gate 		} else {
5247c478bd9Sstevel@tonic-gate 			send_handle(id, handle);
5257c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
5267c478bd9Sstevel@tonic-gate 		}
5277c478bd9Sstevel@tonic-gate 	}
5287c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
5297c478bd9Sstevel@tonic-gate 		send_status(id, status);
5307c478bd9Sstevel@tonic-gate 	xfree(name);
5317c478bd9Sstevel@tonic-gate }
5327c478bd9Sstevel@tonic-gate 
5337c478bd9Sstevel@tonic-gate static void
5347c478bd9Sstevel@tonic-gate process_close(void)
5357c478bd9Sstevel@tonic-gate {
5367c478bd9Sstevel@tonic-gate 	u_int32_t id;
5377c478bd9Sstevel@tonic-gate 	int handle, ret, status = SSH2_FX_FAILURE;
5387c478bd9Sstevel@tonic-gate 
5397c478bd9Sstevel@tonic-gate 	id = get_int();
5407c478bd9Sstevel@tonic-gate 	handle = get_handle();
541*90685d2cSjp161948 	debug3("request %u: close handle %u", id, handle);
542*90685d2cSjp161948 	handle_log_close(handle, NULL);
5437c478bd9Sstevel@tonic-gate 	ret = handle_close(handle);
5447c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
5457c478bd9Sstevel@tonic-gate 	send_status(id, status);
5467c478bd9Sstevel@tonic-gate }
5477c478bd9Sstevel@tonic-gate 
5487c478bd9Sstevel@tonic-gate static void
5497c478bd9Sstevel@tonic-gate process_read(void)
5507c478bd9Sstevel@tonic-gate {
5517c478bd9Sstevel@tonic-gate 	char buf[64*1024];
5527c478bd9Sstevel@tonic-gate 	u_int32_t id, len;
5537c478bd9Sstevel@tonic-gate 	int handle, fd, ret, status = SSH2_FX_FAILURE;
5547c478bd9Sstevel@tonic-gate 	u_int64_t off;
5557c478bd9Sstevel@tonic-gate 
5567c478bd9Sstevel@tonic-gate 	id = get_int();
5577c478bd9Sstevel@tonic-gate 	handle = get_handle();
5587c478bd9Sstevel@tonic-gate 	off = get_int64();
5597c478bd9Sstevel@tonic-gate 	len = get_int();
5607c478bd9Sstevel@tonic-gate 
561*90685d2cSjp161948 	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
562*90685d2cSjp161948 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
5637c478bd9Sstevel@tonic-gate 	if (len > sizeof buf) {
5647c478bd9Sstevel@tonic-gate 		len = sizeof buf;
565*90685d2cSjp161948 		debug2("read change len %d", len);
5667c478bd9Sstevel@tonic-gate 	}
5677c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
5687c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
5697c478bd9Sstevel@tonic-gate 		if (lseek(fd, off, SEEK_SET) < 0) {
5707c478bd9Sstevel@tonic-gate 			error("process_read: seek failed");
5717c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
5727c478bd9Sstevel@tonic-gate 		} else {
5737c478bd9Sstevel@tonic-gate 			ret = read(fd, buf, len);
5747c478bd9Sstevel@tonic-gate 			if (ret < 0) {
5757c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
5767c478bd9Sstevel@tonic-gate 			} else if (ret == 0) {
5777c478bd9Sstevel@tonic-gate 				status = SSH2_FX_EOF;
5787c478bd9Sstevel@tonic-gate 			} else {
5797c478bd9Sstevel@tonic-gate 				send_data(id, buf, ret);
5807c478bd9Sstevel@tonic-gate 				status = SSH2_FX_OK;
581*90685d2cSjp161948 				handle_update_read(handle, ret);
5827c478bd9Sstevel@tonic-gate 			}
5837c478bd9Sstevel@tonic-gate 		}
5847c478bd9Sstevel@tonic-gate 	}
5857c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
5867c478bd9Sstevel@tonic-gate 		send_status(id, status);
5877c478bd9Sstevel@tonic-gate }
5887c478bd9Sstevel@tonic-gate 
5897c478bd9Sstevel@tonic-gate static void
5907c478bd9Sstevel@tonic-gate process_write(void)
5917c478bd9Sstevel@tonic-gate {
5927c478bd9Sstevel@tonic-gate 	u_int32_t id;
5937c478bd9Sstevel@tonic-gate 	u_int64_t off;
5947c478bd9Sstevel@tonic-gate 	u_int len;
5957c478bd9Sstevel@tonic-gate 	int handle, fd, ret, status = SSH2_FX_FAILURE;
5967c478bd9Sstevel@tonic-gate 	char *data;
5977c478bd9Sstevel@tonic-gate 
5987c478bd9Sstevel@tonic-gate 	id = get_int();
5997c478bd9Sstevel@tonic-gate 	handle = get_handle();
6007c478bd9Sstevel@tonic-gate 	off = get_int64();
6017c478bd9Sstevel@tonic-gate 	data = get_string(&len);
6027c478bd9Sstevel@tonic-gate 
603*90685d2cSjp161948 	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
604*90685d2cSjp161948 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
6057c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
6067c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
6077c478bd9Sstevel@tonic-gate 		if (lseek(fd, off, SEEK_SET) < 0) {
6087c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
6097c478bd9Sstevel@tonic-gate 			error("process_write: seek failed");
6107c478bd9Sstevel@tonic-gate 		} else {
6117c478bd9Sstevel@tonic-gate /* XXX ATOMICIO ? */
6127c478bd9Sstevel@tonic-gate 			ret = write(fd, data, len);
613*90685d2cSjp161948 			if (ret < 0) {
6147c478bd9Sstevel@tonic-gate 				error("process_write: write failed");
6157c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
616*90685d2cSjp161948 			} else if ((size_t)ret == len) {
6177c478bd9Sstevel@tonic-gate 				status = SSH2_FX_OK;
618*90685d2cSjp161948 				handle_update_write(handle, ret);
6197c478bd9Sstevel@tonic-gate 			} else {
620*90685d2cSjp161948 				debug2("nothing at all written");
6217c478bd9Sstevel@tonic-gate 			}
6227c478bd9Sstevel@tonic-gate 		}
6237c478bd9Sstevel@tonic-gate 	}
6247c478bd9Sstevel@tonic-gate 	send_status(id, status);
6257c478bd9Sstevel@tonic-gate 	xfree(data);
6267c478bd9Sstevel@tonic-gate }
6277c478bd9Sstevel@tonic-gate 
6287c478bd9Sstevel@tonic-gate static void
6297c478bd9Sstevel@tonic-gate process_do_stat(int do_lstat)
6307c478bd9Sstevel@tonic-gate {
6317c478bd9Sstevel@tonic-gate 	Attrib a;
6327c478bd9Sstevel@tonic-gate 	struct stat st;
6337c478bd9Sstevel@tonic-gate 	u_int32_t id;
6347c478bd9Sstevel@tonic-gate 	char *name;
6357c478bd9Sstevel@tonic-gate 	int ret, status = SSH2_FX_FAILURE;
6367c478bd9Sstevel@tonic-gate 
6377c478bd9Sstevel@tonic-gate 	id = get_int();
6387c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
639*90685d2cSjp161948 	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
640*90685d2cSjp161948 	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
6417c478bd9Sstevel@tonic-gate 	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
6427c478bd9Sstevel@tonic-gate 	if (ret < 0) {
6437c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
6447c478bd9Sstevel@tonic-gate 	} else {
6457c478bd9Sstevel@tonic-gate 		stat_to_attrib(&st, &a);
6467c478bd9Sstevel@tonic-gate 		send_attrib(id, &a);
6477c478bd9Sstevel@tonic-gate 		status = SSH2_FX_OK;
6487c478bd9Sstevel@tonic-gate 	}
6497c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
6507c478bd9Sstevel@tonic-gate 		send_status(id, status);
6517c478bd9Sstevel@tonic-gate 	xfree(name);
6527c478bd9Sstevel@tonic-gate }
6537c478bd9Sstevel@tonic-gate 
6547c478bd9Sstevel@tonic-gate static void
6557c478bd9Sstevel@tonic-gate process_stat(void)
6567c478bd9Sstevel@tonic-gate {
6577c478bd9Sstevel@tonic-gate 	process_do_stat(0);
6587c478bd9Sstevel@tonic-gate }
6597c478bd9Sstevel@tonic-gate 
6607c478bd9Sstevel@tonic-gate static void
6617c478bd9Sstevel@tonic-gate process_lstat(void)
6627c478bd9Sstevel@tonic-gate {
6637c478bd9Sstevel@tonic-gate 	process_do_stat(1);
6647c478bd9Sstevel@tonic-gate }
6657c478bd9Sstevel@tonic-gate 
6667c478bd9Sstevel@tonic-gate static void
6677c478bd9Sstevel@tonic-gate process_fstat(void)
6687c478bd9Sstevel@tonic-gate {
6697c478bd9Sstevel@tonic-gate 	Attrib a;
6707c478bd9Sstevel@tonic-gate 	struct stat st;
6717c478bd9Sstevel@tonic-gate 	u_int32_t id;
6727c478bd9Sstevel@tonic-gate 	int fd, ret, handle, status = SSH2_FX_FAILURE;
6737c478bd9Sstevel@tonic-gate 
6747c478bd9Sstevel@tonic-gate 	id = get_int();
6757c478bd9Sstevel@tonic-gate 	handle = get_handle();
676*90685d2cSjp161948 	debug("request %u: fstat \"%s\" (handle %u)",
677*90685d2cSjp161948 	    id, handle_to_name(handle), handle);
6787c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
6797c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
6807c478bd9Sstevel@tonic-gate 		ret = fstat(fd, &st);
6817c478bd9Sstevel@tonic-gate 		if (ret < 0) {
6827c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
6837c478bd9Sstevel@tonic-gate 		} else {
6847c478bd9Sstevel@tonic-gate 			stat_to_attrib(&st, &a);
6857c478bd9Sstevel@tonic-gate 			send_attrib(id, &a);
6867c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
6877c478bd9Sstevel@tonic-gate 		}
6887c478bd9Sstevel@tonic-gate 	}
6897c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
6907c478bd9Sstevel@tonic-gate 		send_status(id, status);
6917c478bd9Sstevel@tonic-gate }
6927c478bd9Sstevel@tonic-gate 
6937c478bd9Sstevel@tonic-gate static struct timeval *
694*90685d2cSjp161948 attrib_to_tv(const Attrib *a)
6957c478bd9Sstevel@tonic-gate {
6967c478bd9Sstevel@tonic-gate 	static struct timeval tv[2];
6977c478bd9Sstevel@tonic-gate 
6987c478bd9Sstevel@tonic-gate 	tv[0].tv_sec = a->atime;
6997c478bd9Sstevel@tonic-gate 	tv[0].tv_usec = 0;
7007c478bd9Sstevel@tonic-gate 	tv[1].tv_sec = a->mtime;
7017c478bd9Sstevel@tonic-gate 	tv[1].tv_usec = 0;
7027c478bd9Sstevel@tonic-gate 	return tv;
7037c478bd9Sstevel@tonic-gate }
7047c478bd9Sstevel@tonic-gate 
7057c478bd9Sstevel@tonic-gate static void
7067c478bd9Sstevel@tonic-gate process_setstat(void)
7077c478bd9Sstevel@tonic-gate {
7087c478bd9Sstevel@tonic-gate 	Attrib *a;
7097c478bd9Sstevel@tonic-gate 	u_int32_t id;
7107c478bd9Sstevel@tonic-gate 	char *name;
7117c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_OK, ret;
7127c478bd9Sstevel@tonic-gate 
7137c478bd9Sstevel@tonic-gate 	id = get_int();
7147c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
7157c478bd9Sstevel@tonic-gate 	a = get_attrib();
716*90685d2cSjp161948 	debug("request %u: setstat name \"%s\"", id, name);
7177c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
718*90685d2cSjp161948 		log("set \"%s\" size %llu",
719*90685d2cSjp161948 		    name, (unsigned long long)a->size);
7207c478bd9Sstevel@tonic-gate 		ret = truncate(name, a->size);
7217c478bd9Sstevel@tonic-gate 		if (ret == -1)
7227c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7237c478bd9Sstevel@tonic-gate 	}
7247c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
725*90685d2cSjp161948 		log("set \"%s\" mode %04o", name, a->perm);
7267c478bd9Sstevel@tonic-gate 		ret = chmod(name, a->perm & 0777);
7277c478bd9Sstevel@tonic-gate 		if (ret == -1)
7287c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7297c478bd9Sstevel@tonic-gate 	}
7307c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
731*90685d2cSjp161948 		char buf[64];
732*90685d2cSjp161948 		time_t t = a->mtime;
733*90685d2cSjp161948 
734*90685d2cSjp161948 		strftime(buf, sizeof(buf), "%Y" "%m%d-%H:%M:%S",
735*90685d2cSjp161948 		    localtime(&t));
736*90685d2cSjp161948 		log("set \"%s\" modtime %s", name, buf);
7377c478bd9Sstevel@tonic-gate 		ret = utimes(name, attrib_to_tv(a));
7387c478bd9Sstevel@tonic-gate 		if (ret == -1)
7397c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7407c478bd9Sstevel@tonic-gate 	}
7417c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
742*90685d2cSjp161948 		log("set \"%s\" owner %lu group %lu", name,
743*90685d2cSjp161948 		    (u_long)a->uid, (u_long)a->gid);
7447c478bd9Sstevel@tonic-gate 		ret = chown(name, a->uid, a->gid);
7457c478bd9Sstevel@tonic-gate 		if (ret == -1)
7467c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7477c478bd9Sstevel@tonic-gate 	}
7487c478bd9Sstevel@tonic-gate 	send_status(id, status);
7497c478bd9Sstevel@tonic-gate 	xfree(name);
7507c478bd9Sstevel@tonic-gate }
7517c478bd9Sstevel@tonic-gate 
7527c478bd9Sstevel@tonic-gate static void
7537c478bd9Sstevel@tonic-gate process_fsetstat(void)
7547c478bd9Sstevel@tonic-gate {
7557c478bd9Sstevel@tonic-gate 	Attrib *a;
7567c478bd9Sstevel@tonic-gate 	u_int32_t id;
7577c478bd9Sstevel@tonic-gate 	int handle, fd, ret;
7587c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_OK;
7597c478bd9Sstevel@tonic-gate 
7607c478bd9Sstevel@tonic-gate 	id = get_int();
7617c478bd9Sstevel@tonic-gate 	handle = get_handle();
7627c478bd9Sstevel@tonic-gate 	a = get_attrib();
763*90685d2cSjp161948 	debug("request %u: fsetstat handle %d", id, handle);
7647c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
765*90685d2cSjp161948 	if (fd < 0) {
7667c478bd9Sstevel@tonic-gate 		status = SSH2_FX_FAILURE;
7677c478bd9Sstevel@tonic-gate 	} else {
768*90685d2cSjp161948 		char *name = handle_to_name(handle);
769*90685d2cSjp161948 
7707c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
771*90685d2cSjp161948 			log("set \"%s\" size %llu",
772*90685d2cSjp161948 			    name, (unsigned long long)a->size);
7737c478bd9Sstevel@tonic-gate 			ret = ftruncate(fd, a->size);
7747c478bd9Sstevel@tonic-gate 			if (ret == -1)
7757c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
7767c478bd9Sstevel@tonic-gate 		}
7777c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
778*90685d2cSjp161948 			log("set \"%s\" mode %04o", name, a->perm);
7797c478bd9Sstevel@tonic-gate #ifdef HAVE_FCHMOD
7807c478bd9Sstevel@tonic-gate 			ret = fchmod(fd, a->perm & 0777);
7817c478bd9Sstevel@tonic-gate #else
7827c478bd9Sstevel@tonic-gate 			ret = chmod(name, a->perm & 0777);
7837c478bd9Sstevel@tonic-gate #endif
7847c478bd9Sstevel@tonic-gate 			if (ret == -1)
7857c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
7867c478bd9Sstevel@tonic-gate 		}
7877c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
788*90685d2cSjp161948 			char buf[64];
789*90685d2cSjp161948 			time_t t = a->mtime;
790*90685d2cSjp161948 
791*90685d2cSjp161948 			strftime(buf, sizeof(buf), "%Y" "%m%d-%H:%M:%S",
792*90685d2cSjp161948 			    localtime(&t));
793*90685d2cSjp161948 			log("set \"%s\" modtime %s", name, buf);
7947c478bd9Sstevel@tonic-gate #ifdef HAVE_FUTIMES
7957c478bd9Sstevel@tonic-gate 			ret = futimes(fd, attrib_to_tv(a));
7967c478bd9Sstevel@tonic-gate #else
7977c478bd9Sstevel@tonic-gate 			ret = utimes(name, attrib_to_tv(a));
7987c478bd9Sstevel@tonic-gate #endif
7997c478bd9Sstevel@tonic-gate 			if (ret == -1)
8007c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
8017c478bd9Sstevel@tonic-gate 		}
8027c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
803*90685d2cSjp161948 			log("set \"%s\" owner %lu group %lu", name,
804*90685d2cSjp161948 			    (u_long)a->uid, (u_long)a->gid);
8057c478bd9Sstevel@tonic-gate #ifdef HAVE_FCHOWN
8067c478bd9Sstevel@tonic-gate 			ret = fchown(fd, a->uid, a->gid);
8077c478bd9Sstevel@tonic-gate #else
8087c478bd9Sstevel@tonic-gate 			ret = chown(name, a->uid, a->gid);
8097c478bd9Sstevel@tonic-gate #endif
8107c478bd9Sstevel@tonic-gate 			if (ret == -1)
8117c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
8127c478bd9Sstevel@tonic-gate 		}
8137c478bd9Sstevel@tonic-gate 	}
8147c478bd9Sstevel@tonic-gate 	send_status(id, status);
8157c478bd9Sstevel@tonic-gate }
8167c478bd9Sstevel@tonic-gate 
8177c478bd9Sstevel@tonic-gate static void
8187c478bd9Sstevel@tonic-gate process_opendir(void)
8197c478bd9Sstevel@tonic-gate {
8207c478bd9Sstevel@tonic-gate 	DIR *dirp = NULL;
8217c478bd9Sstevel@tonic-gate 	char *path;
8227c478bd9Sstevel@tonic-gate 	int handle, status = SSH2_FX_FAILURE;
8237c478bd9Sstevel@tonic-gate 	u_int32_t id;
8247c478bd9Sstevel@tonic-gate 
8257c478bd9Sstevel@tonic-gate 	id = get_int();
8267c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
827*90685d2cSjp161948 	debug3("request %u: opendir", id);
828*90685d2cSjp161948 	log("opendir \"%s\"", path);
8297c478bd9Sstevel@tonic-gate 	dirp = opendir(path);
8307c478bd9Sstevel@tonic-gate 	if (dirp == NULL) {
8317c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
8327c478bd9Sstevel@tonic-gate 	} else {
833*90685d2cSjp161948 		handle = handle_new(HANDLE_DIR, path, 0, dirp);
8347c478bd9Sstevel@tonic-gate 		if (handle < 0) {
8357c478bd9Sstevel@tonic-gate 			closedir(dirp);
8367c478bd9Sstevel@tonic-gate 		} else {
8377c478bd9Sstevel@tonic-gate 			send_handle(id, handle);
8387c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
8397c478bd9Sstevel@tonic-gate 		}
8407c478bd9Sstevel@tonic-gate 
8417c478bd9Sstevel@tonic-gate 	}
8427c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
8437c478bd9Sstevel@tonic-gate 		send_status(id, status);
8447c478bd9Sstevel@tonic-gate 	xfree(path);
8457c478bd9Sstevel@tonic-gate }
8467c478bd9Sstevel@tonic-gate 
8477c478bd9Sstevel@tonic-gate static void
8487c478bd9Sstevel@tonic-gate process_readdir(void)
8497c478bd9Sstevel@tonic-gate {
8507c478bd9Sstevel@tonic-gate 	DIR *dirp;
8517c478bd9Sstevel@tonic-gate 	struct dirent *dp;
8527c478bd9Sstevel@tonic-gate 	char *path;
8537c478bd9Sstevel@tonic-gate 	int handle;
8547c478bd9Sstevel@tonic-gate 	u_int32_t id;
8557c478bd9Sstevel@tonic-gate 
8567c478bd9Sstevel@tonic-gate 	id = get_int();
8577c478bd9Sstevel@tonic-gate 	handle = get_handle();
858*90685d2cSjp161948 	debug("request %u: readdir \"%s\" (handle %d)", id,
859*90685d2cSjp161948 	    handle_to_name(handle), handle);
8607c478bd9Sstevel@tonic-gate 	dirp = handle_to_dir(handle);
8617c478bd9Sstevel@tonic-gate 	path = handle_to_name(handle);
8627c478bd9Sstevel@tonic-gate 	if (dirp == NULL || path == NULL) {
8637c478bd9Sstevel@tonic-gate 		send_status(id, SSH2_FX_FAILURE);
8647c478bd9Sstevel@tonic-gate 	} else {
8657c478bd9Sstevel@tonic-gate 		struct stat st;
866*90685d2cSjp161948 		char pathname[MAXPATHLEN];
8677c478bd9Sstevel@tonic-gate 		Stat *stats;
8687c478bd9Sstevel@tonic-gate 		int nstats = 10, count = 0, i;
8697c478bd9Sstevel@tonic-gate 
870*90685d2cSjp161948 		stats = xcalloc(nstats, sizeof(Stat));
8717c478bd9Sstevel@tonic-gate 		while ((dp = readdir(dirp)) != NULL) {
8727c478bd9Sstevel@tonic-gate 			if (count >= nstats) {
8737c478bd9Sstevel@tonic-gate 				nstats *= 2;
8747c478bd9Sstevel@tonic-gate 				stats = xrealloc(stats, nstats * sizeof(Stat));
8757c478bd9Sstevel@tonic-gate 			}
8767c478bd9Sstevel@tonic-gate /* XXX OVERFLOW ? */
8777c478bd9Sstevel@tonic-gate 			snprintf(pathname, sizeof pathname, "%s%s%s", path,
8787c478bd9Sstevel@tonic-gate 			    strcmp(path, "/") ? "/" : "", dp->d_name);
8797c478bd9Sstevel@tonic-gate 			if (lstat(pathname, &st) < 0)
8807c478bd9Sstevel@tonic-gate 				continue;
8817c478bd9Sstevel@tonic-gate 			stat_to_attrib(&st, &(stats[count].attrib));
8827c478bd9Sstevel@tonic-gate 			stats[count].name = xstrdup(dp->d_name);
8837c478bd9Sstevel@tonic-gate 			stats[count].long_name = ls_file(dp->d_name, &st, 0);
8847c478bd9Sstevel@tonic-gate 			count++;
8857c478bd9Sstevel@tonic-gate 			/* send up to 100 entries in one message */
8867c478bd9Sstevel@tonic-gate 			/* XXX check packet size instead */
8877c478bd9Sstevel@tonic-gate 			if (count == 100)
8887c478bd9Sstevel@tonic-gate 				break;
8897c478bd9Sstevel@tonic-gate 		}
8907c478bd9Sstevel@tonic-gate 		if (count > 0) {
8917c478bd9Sstevel@tonic-gate 			send_names(id, count, stats);
8927c478bd9Sstevel@tonic-gate 			for (i = 0; i < count; i++) {
8937c478bd9Sstevel@tonic-gate 				xfree(stats[i].name);
8947c478bd9Sstevel@tonic-gate 				xfree(stats[i].long_name);
8957c478bd9Sstevel@tonic-gate 			}
8967c478bd9Sstevel@tonic-gate 		} else {
8977c478bd9Sstevel@tonic-gate 			send_status(id, SSH2_FX_EOF);
8987c478bd9Sstevel@tonic-gate 		}
8997c478bd9Sstevel@tonic-gate 		xfree(stats);
9007c478bd9Sstevel@tonic-gate 	}
9017c478bd9Sstevel@tonic-gate }
9027c478bd9Sstevel@tonic-gate 
9037c478bd9Sstevel@tonic-gate static void
9047c478bd9Sstevel@tonic-gate process_remove(void)
9057c478bd9Sstevel@tonic-gate {
9067c478bd9Sstevel@tonic-gate 	char *name;
9077c478bd9Sstevel@tonic-gate 	u_int32_t id;
9087c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_FAILURE;
9097c478bd9Sstevel@tonic-gate 	int ret;
9107c478bd9Sstevel@tonic-gate 
9117c478bd9Sstevel@tonic-gate 	id = get_int();
9127c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
913*90685d2cSjp161948 	debug3("request %u: remove", id);
914*90685d2cSjp161948 	log("remove name \"%s\"", name);
9157c478bd9Sstevel@tonic-gate 	ret = unlink(name);
9167c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9177c478bd9Sstevel@tonic-gate 	send_status(id, status);
9187c478bd9Sstevel@tonic-gate 	xfree(name);
9197c478bd9Sstevel@tonic-gate }
9207c478bd9Sstevel@tonic-gate 
9217c478bd9Sstevel@tonic-gate static void
9227c478bd9Sstevel@tonic-gate process_mkdir(void)
9237c478bd9Sstevel@tonic-gate {
9247c478bd9Sstevel@tonic-gate 	Attrib *a;
9257c478bd9Sstevel@tonic-gate 	u_int32_t id;
9267c478bd9Sstevel@tonic-gate 	char *name;
9277c478bd9Sstevel@tonic-gate 	int ret, mode, status = SSH2_FX_FAILURE;
9287c478bd9Sstevel@tonic-gate 
9297c478bd9Sstevel@tonic-gate 	id = get_int();
9307c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
9317c478bd9Sstevel@tonic-gate 	a = get_attrib();
9327c478bd9Sstevel@tonic-gate 	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
9337c478bd9Sstevel@tonic-gate 	    a->perm & 0777 : 0777;
934*90685d2cSjp161948 	debug3("request %u: mkdir", id);
935*90685d2cSjp161948 	log("mkdir name \"%s\" mode 0%o", name, mode);
9367c478bd9Sstevel@tonic-gate 	ret = mkdir(name, mode);
9377c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9387c478bd9Sstevel@tonic-gate 	send_status(id, status);
9397c478bd9Sstevel@tonic-gate 	xfree(name);
9407c478bd9Sstevel@tonic-gate }
9417c478bd9Sstevel@tonic-gate 
9427c478bd9Sstevel@tonic-gate static void
9437c478bd9Sstevel@tonic-gate process_rmdir(void)
9447c478bd9Sstevel@tonic-gate {
9457c478bd9Sstevel@tonic-gate 	u_int32_t id;
9467c478bd9Sstevel@tonic-gate 	char *name;
9477c478bd9Sstevel@tonic-gate 	int ret, status;
9487c478bd9Sstevel@tonic-gate 
9497c478bd9Sstevel@tonic-gate 	id = get_int();
9507c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
951*90685d2cSjp161948 	debug3("request %u: rmdir", id);
952*90685d2cSjp161948 	log("rmdir name \"%s\"", name);
9537c478bd9Sstevel@tonic-gate 	ret = rmdir(name);
9547c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9557c478bd9Sstevel@tonic-gate 	send_status(id, status);
9567c478bd9Sstevel@tonic-gate 	xfree(name);
9577c478bd9Sstevel@tonic-gate }
9587c478bd9Sstevel@tonic-gate 
9597c478bd9Sstevel@tonic-gate static void
9607c478bd9Sstevel@tonic-gate process_realpath(void)
9617c478bd9Sstevel@tonic-gate {
9627c478bd9Sstevel@tonic-gate 	char resolvedname[MAXPATHLEN];
9637c478bd9Sstevel@tonic-gate 	u_int32_t id;
9647c478bd9Sstevel@tonic-gate 	char *path;
9657c478bd9Sstevel@tonic-gate 
9667c478bd9Sstevel@tonic-gate 	id = get_int();
9677c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
9687c478bd9Sstevel@tonic-gate 	if (path[0] == '\0') {
9697c478bd9Sstevel@tonic-gate 		xfree(path);
9707c478bd9Sstevel@tonic-gate 		path = xstrdup(".");
9717c478bd9Sstevel@tonic-gate 	}
972*90685d2cSjp161948 	debug3("request %u: realpath", id);
973*90685d2cSjp161948 	verbose("realpath \"%s\"", path);
9747c478bd9Sstevel@tonic-gate 	if (realpath(path, resolvedname) == NULL) {
9757c478bd9Sstevel@tonic-gate 		send_status(id, errno_to_portable(errno));
9767c478bd9Sstevel@tonic-gate 	} else {
9777c478bd9Sstevel@tonic-gate 		Stat s;
9787c478bd9Sstevel@tonic-gate 		attrib_clear(&s.attrib);
9797c478bd9Sstevel@tonic-gate 		s.name = s.long_name = resolvedname;
9807c478bd9Sstevel@tonic-gate 		send_names(id, 1, &s);
9817c478bd9Sstevel@tonic-gate 	}
9827c478bd9Sstevel@tonic-gate 	xfree(path);
9837c478bd9Sstevel@tonic-gate }
9847c478bd9Sstevel@tonic-gate 
9857c478bd9Sstevel@tonic-gate static void
9867c478bd9Sstevel@tonic-gate process_rename(void)
9877c478bd9Sstevel@tonic-gate {
9887c478bd9Sstevel@tonic-gate 	u_int32_t id;
9897c478bd9Sstevel@tonic-gate 	char *oldpath, *newpath;
990*90685d2cSjp161948 	int status;
991*90685d2cSjp161948 	struct stat sb;
9927c478bd9Sstevel@tonic-gate 
9937c478bd9Sstevel@tonic-gate 	id = get_int();
9947c478bd9Sstevel@tonic-gate 	oldpath = get_string(NULL);
9957c478bd9Sstevel@tonic-gate 	newpath = get_string(NULL);
996*90685d2cSjp161948 	debug3("request %u: rename", id);
997*90685d2cSjp161948 	log("rename old \"%s\" new \"%s\"", oldpath, newpath);
998*90685d2cSjp161948 	status = SSH2_FX_FAILURE;
999*90685d2cSjp161948 	if (lstat(oldpath, &sb) == -1)
1000*90685d2cSjp161948 		status = errno_to_portable(errno);
1001*90685d2cSjp161948 	else if (S_ISREG(sb.st_mode)) {
1002*90685d2cSjp161948 		/* Race-free rename of regular files */
1003*90685d2cSjp161948 		if (link(oldpath, newpath) == -1) {
1004*90685d2cSjp161948 			if (errno == EOPNOTSUPP
1005*90685d2cSjp161948 #ifdef LINK_OPNOTSUPP_ERRNO
1006*90685d2cSjp161948 			    || errno == LINK_OPNOTSUPP_ERRNO
1007*90685d2cSjp161948 #endif
1008*90685d2cSjp161948 			    ) {
1009*90685d2cSjp161948 				struct stat st;
1010*90685d2cSjp161948 
1011*90685d2cSjp161948 				/*
1012*90685d2cSjp161948 				 * fs doesn't support links, so fall back to
1013*90685d2cSjp161948 				 * stat+rename.  This is racy.
1014*90685d2cSjp161948 				 */
10157c478bd9Sstevel@tonic-gate 				if (stat(newpath, &st) == -1) {
1016*90685d2cSjp161948 					if (rename(oldpath, newpath) == -1)
1017*90685d2cSjp161948 						status =
1018*90685d2cSjp161948 						    errno_to_portable(errno);
1019*90685d2cSjp161948 					else
1020*90685d2cSjp161948 						status = SSH2_FX_OK;
1021*90685d2cSjp161948 				}
1022*90685d2cSjp161948 			} else {
1023*90685d2cSjp161948 				status = errno_to_portable(errno);
1024*90685d2cSjp161948 			}
1025*90685d2cSjp161948 		} else if (unlink(oldpath) == -1) {
1026*90685d2cSjp161948 			status = errno_to_portable(errno);
1027*90685d2cSjp161948 			/* clean spare link */
1028*90685d2cSjp161948 			unlink(newpath);
1029*90685d2cSjp161948 		} else
1030*90685d2cSjp161948 			status = SSH2_FX_OK;
1031*90685d2cSjp161948 	} else if (stat(newpath, &sb) == -1) {
1032*90685d2cSjp161948 		if (rename(oldpath, newpath) == -1)
1033*90685d2cSjp161948 			status = errno_to_portable(errno);
1034*90685d2cSjp161948 		else
1035*90685d2cSjp161948 			status = SSH2_FX_OK;
10367c478bd9Sstevel@tonic-gate 	}
10377c478bd9Sstevel@tonic-gate 	send_status(id, status);
10387c478bd9Sstevel@tonic-gate 	xfree(oldpath);
10397c478bd9Sstevel@tonic-gate 	xfree(newpath);
10407c478bd9Sstevel@tonic-gate }
10417c478bd9Sstevel@tonic-gate 
10427c478bd9Sstevel@tonic-gate static void
10437c478bd9Sstevel@tonic-gate process_readlink(void)
10447c478bd9Sstevel@tonic-gate {
10457c478bd9Sstevel@tonic-gate 	u_int32_t id;
10467c478bd9Sstevel@tonic-gate 	int len;
1047*90685d2cSjp161948 	char buf[MAXPATHLEN];
10487c478bd9Sstevel@tonic-gate 	char *path;
10497c478bd9Sstevel@tonic-gate 
10507c478bd9Sstevel@tonic-gate 	id = get_int();
10517c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
1052*90685d2cSjp161948 	debug3("request %u: readlink", id);
1053*90685d2cSjp161948 	verbose("readlink \"%s\"", path);
1054*90685d2cSjp161948 	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
10557c478bd9Sstevel@tonic-gate 		send_status(id, errno_to_portable(errno));
10567c478bd9Sstevel@tonic-gate 	else {
10577c478bd9Sstevel@tonic-gate 		Stat s;
10587c478bd9Sstevel@tonic-gate 
1059*90685d2cSjp161948 		buf[len] = '\0';
10607c478bd9Sstevel@tonic-gate 		attrib_clear(&s.attrib);
1061*90685d2cSjp161948 		s.name = s.long_name = buf;
10627c478bd9Sstevel@tonic-gate 		send_names(id, 1, &s);
10637c478bd9Sstevel@tonic-gate 	}
10647c478bd9Sstevel@tonic-gate 	xfree(path);
10657c478bd9Sstevel@tonic-gate }
10667c478bd9Sstevel@tonic-gate 
10677c478bd9Sstevel@tonic-gate static void
10687c478bd9Sstevel@tonic-gate process_symlink(void)
10697c478bd9Sstevel@tonic-gate {
10707c478bd9Sstevel@tonic-gate 	u_int32_t id;
10717c478bd9Sstevel@tonic-gate 	char *oldpath, *newpath;
1072*90685d2cSjp161948 	int ret, status;
10737c478bd9Sstevel@tonic-gate 
10747c478bd9Sstevel@tonic-gate 	id = get_int();
10757c478bd9Sstevel@tonic-gate 	oldpath = get_string(NULL);
10767c478bd9Sstevel@tonic-gate 	newpath = get_string(NULL);
1077*90685d2cSjp161948 	debug3("request %u: symlink", id);
1078*90685d2cSjp161948 	log("symlink old \"%s\" new \"%s\"", oldpath, newpath);
1079*90685d2cSjp161948 	/* this will fail if 'newpath' exists */
10807c478bd9Sstevel@tonic-gate 	ret = symlink(oldpath, newpath);
10817c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
10827c478bd9Sstevel@tonic-gate 	send_status(id, status);
10837c478bd9Sstevel@tonic-gate 	xfree(oldpath);
10847c478bd9Sstevel@tonic-gate 	xfree(newpath);
10857c478bd9Sstevel@tonic-gate }
10867c478bd9Sstevel@tonic-gate 
10877c478bd9Sstevel@tonic-gate static void
10887c478bd9Sstevel@tonic-gate process_extended(void)
10897c478bd9Sstevel@tonic-gate {
10907c478bd9Sstevel@tonic-gate 	u_int32_t id;
10917c478bd9Sstevel@tonic-gate 	char *request;
10927c478bd9Sstevel@tonic-gate 
10937c478bd9Sstevel@tonic-gate 	id = get_int();
10947c478bd9Sstevel@tonic-gate 	request = get_string(NULL);
10957c478bd9Sstevel@tonic-gate 	send_status(id, SSH2_FX_OP_UNSUPPORTED);		/* MUST */
10967c478bd9Sstevel@tonic-gate 	xfree(request);
10977c478bd9Sstevel@tonic-gate }
10987c478bd9Sstevel@tonic-gate 
10997c478bd9Sstevel@tonic-gate /* stolen from ssh-agent */
11007c478bd9Sstevel@tonic-gate 
11017c478bd9Sstevel@tonic-gate static void
11027c478bd9Sstevel@tonic-gate process(void)
11037c478bd9Sstevel@tonic-gate {
11047c478bd9Sstevel@tonic-gate 	u_int msg_len;
11057c478bd9Sstevel@tonic-gate 	u_int buf_len;
11067c478bd9Sstevel@tonic-gate 	u_int consumed;
11077c478bd9Sstevel@tonic-gate 	u_int type;
11087c478bd9Sstevel@tonic-gate 	u_char *cp;
11097c478bd9Sstevel@tonic-gate 
11107c478bd9Sstevel@tonic-gate 	buf_len = buffer_len(&iqueue);
11117c478bd9Sstevel@tonic-gate 	if (buf_len < 5)
11127c478bd9Sstevel@tonic-gate 		return;		/* Incomplete message. */
11137c478bd9Sstevel@tonic-gate 	cp = buffer_ptr(&iqueue);
1114*90685d2cSjp161948 	msg_len = get_u32(cp);
1115*90685d2cSjp161948 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
1116*90685d2cSjp161948 		error("bad message from %s local user %s",
1117*90685d2cSjp161948 		    client_addr, pw->pw_name);
1118*90685d2cSjp161948 		cleanup_exit(11);
11197c478bd9Sstevel@tonic-gate 	}
11207c478bd9Sstevel@tonic-gate 	if (buf_len < msg_len + 4)
11217c478bd9Sstevel@tonic-gate 		return;
11227c478bd9Sstevel@tonic-gate 	buffer_consume(&iqueue, 4);
11237c478bd9Sstevel@tonic-gate 	buf_len -= 4;
11247c478bd9Sstevel@tonic-gate 	type = buffer_get_char(&iqueue);
11257c478bd9Sstevel@tonic-gate 	switch (type) {
11267c478bd9Sstevel@tonic-gate 	case SSH2_FXP_INIT:
11277c478bd9Sstevel@tonic-gate 		process_init();
11287c478bd9Sstevel@tonic-gate 		break;
11297c478bd9Sstevel@tonic-gate 	case SSH2_FXP_OPEN:
11307c478bd9Sstevel@tonic-gate 		process_open();
11317c478bd9Sstevel@tonic-gate 		break;
11327c478bd9Sstevel@tonic-gate 	case SSH2_FXP_CLOSE:
11337c478bd9Sstevel@tonic-gate 		process_close();
11347c478bd9Sstevel@tonic-gate 		break;
11357c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READ:
11367c478bd9Sstevel@tonic-gate 		process_read();
11377c478bd9Sstevel@tonic-gate 		break;
11387c478bd9Sstevel@tonic-gate 	case SSH2_FXP_WRITE:
11397c478bd9Sstevel@tonic-gate 		process_write();
11407c478bd9Sstevel@tonic-gate 		break;
11417c478bd9Sstevel@tonic-gate 	case SSH2_FXP_LSTAT:
11427c478bd9Sstevel@tonic-gate 		process_lstat();
11437c478bd9Sstevel@tonic-gate 		break;
11447c478bd9Sstevel@tonic-gate 	case SSH2_FXP_FSTAT:
11457c478bd9Sstevel@tonic-gate 		process_fstat();
11467c478bd9Sstevel@tonic-gate 		break;
11477c478bd9Sstevel@tonic-gate 	case SSH2_FXP_SETSTAT:
11487c478bd9Sstevel@tonic-gate 		process_setstat();
11497c478bd9Sstevel@tonic-gate 		break;
11507c478bd9Sstevel@tonic-gate 	case SSH2_FXP_FSETSTAT:
11517c478bd9Sstevel@tonic-gate 		process_fsetstat();
11527c478bd9Sstevel@tonic-gate 		break;
11537c478bd9Sstevel@tonic-gate 	case SSH2_FXP_OPENDIR:
11547c478bd9Sstevel@tonic-gate 		process_opendir();
11557c478bd9Sstevel@tonic-gate 		break;
11567c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READDIR:
11577c478bd9Sstevel@tonic-gate 		process_readdir();
11587c478bd9Sstevel@tonic-gate 		break;
11597c478bd9Sstevel@tonic-gate 	case SSH2_FXP_REMOVE:
11607c478bd9Sstevel@tonic-gate 		process_remove();
11617c478bd9Sstevel@tonic-gate 		break;
11627c478bd9Sstevel@tonic-gate 	case SSH2_FXP_MKDIR:
11637c478bd9Sstevel@tonic-gate 		process_mkdir();
11647c478bd9Sstevel@tonic-gate 		break;
11657c478bd9Sstevel@tonic-gate 	case SSH2_FXP_RMDIR:
11667c478bd9Sstevel@tonic-gate 		process_rmdir();
11677c478bd9Sstevel@tonic-gate 		break;
11687c478bd9Sstevel@tonic-gate 	case SSH2_FXP_REALPATH:
11697c478bd9Sstevel@tonic-gate 		process_realpath();
11707c478bd9Sstevel@tonic-gate 		break;
11717c478bd9Sstevel@tonic-gate 	case SSH2_FXP_STAT:
11727c478bd9Sstevel@tonic-gate 		process_stat();
11737c478bd9Sstevel@tonic-gate 		break;
11747c478bd9Sstevel@tonic-gate 	case SSH2_FXP_RENAME:
11757c478bd9Sstevel@tonic-gate 		process_rename();
11767c478bd9Sstevel@tonic-gate 		break;
11777c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READLINK:
11787c478bd9Sstevel@tonic-gate 		process_readlink();
11797c478bd9Sstevel@tonic-gate 		break;
11807c478bd9Sstevel@tonic-gate 	case SSH2_FXP_SYMLINK:
11817c478bd9Sstevel@tonic-gate 		process_symlink();
11827c478bd9Sstevel@tonic-gate 		break;
11837c478bd9Sstevel@tonic-gate 	case SSH2_FXP_EXTENDED:
11847c478bd9Sstevel@tonic-gate 		process_extended();
11857c478bd9Sstevel@tonic-gate 		break;
11867c478bd9Sstevel@tonic-gate 	default:
11877c478bd9Sstevel@tonic-gate 		error("Unknown message %d", type);
11887c478bd9Sstevel@tonic-gate 		break;
11897c478bd9Sstevel@tonic-gate 	}
11907c478bd9Sstevel@tonic-gate 	/* discard the remaining bytes from the current packet */
11917c478bd9Sstevel@tonic-gate 	if (buf_len < buffer_len(&iqueue))
1192*90685d2cSjp161948 		fatal("iqueue grew unexpectedly");
11937c478bd9Sstevel@tonic-gate 	consumed = buf_len - buffer_len(&iqueue);
11947c478bd9Sstevel@tonic-gate 	if (msg_len < consumed)
11957c478bd9Sstevel@tonic-gate 		fatal("msg_len %d < consumed %d", msg_len, consumed);
11967c478bd9Sstevel@tonic-gate 	if (msg_len > consumed)
11977c478bd9Sstevel@tonic-gate 		buffer_consume(&iqueue, msg_len - consumed);
11987c478bd9Sstevel@tonic-gate }
11997c478bd9Sstevel@tonic-gate 
1200*90685d2cSjp161948 /* Cleanup handler that logs active handles upon normal exit */
1201*90685d2cSjp161948 static void
1202*90685d2cSjp161948 cleanup_exit(int i)
1203*90685d2cSjp161948 {
1204*90685d2cSjp161948 	if (pw != NULL && client_addr != NULL) {
1205*90685d2cSjp161948 		handle_log_exit();
1206*90685d2cSjp161948 		log("session closed for local user %s from [%s]",
1207*90685d2cSjp161948 		    pw->pw_name, client_addr);
1208*90685d2cSjp161948 	}
1209*90685d2cSjp161948 	_exit(i);
1210*90685d2cSjp161948 }
1211*90685d2cSjp161948 
1212*90685d2cSjp161948 static void
1213*90685d2cSjp161948 usage(void)
1214*90685d2cSjp161948 {
1215*90685d2cSjp161948 	fprintf(stderr,
1216*90685d2cSjp161948 	    "Usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname);
1217*90685d2cSjp161948 	exit(1);
1218*90685d2cSjp161948 }
1219*90685d2cSjp161948 
12207c478bd9Sstevel@tonic-gate int
1221*90685d2cSjp161948 main(int argc, char **argv)
12227c478bd9Sstevel@tonic-gate {
12237c478bd9Sstevel@tonic-gate 	fd_set *rset, *wset;
1224*90685d2cSjp161948 	int in, out, max, ch, skipargs = 0, log_stderr = 0;
12257c478bd9Sstevel@tonic-gate 	ssize_t len, olen, set_size;
1226*90685d2cSjp161948 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
1227*90685d2cSjp161948 	char *cp, buf[4*4096];
12287c478bd9Sstevel@tonic-gate 
1229*90685d2cSjp161948 	extern char *optarg;
12307c478bd9Sstevel@tonic-gate 
1231*90685d2cSjp161948 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1232*90685d2cSjp161948 	sanitise_stdfd();
1233*90685d2cSjp161948 
1234*90685d2cSjp161948 	__progname = get_progname(argv[0]);
12357c478bd9Sstevel@tonic-gate 
12367c478bd9Sstevel@tonic-gate 	(void) g11n_setlocale(LC_ALL, "");
12377c478bd9Sstevel@tonic-gate 
1238*90685d2cSjp161948 	log_init(__progname, log_level, log_facility, log_stderr);
12397c478bd9Sstevel@tonic-gate 
1240*90685d2cSjp161948 	while (!skipargs && (ch = getopt(argc, argv, "C:f:l:che")) != -1) {
1241*90685d2cSjp161948 		switch (ch) {
1242*90685d2cSjp161948 		case 'c':
1243*90685d2cSjp161948 			/*
1244*90685d2cSjp161948 			 * Ignore all arguments if we are invoked as a
1245*90685d2cSjp161948 			 * shell using "sftp-server -c command"
1246*90685d2cSjp161948 			 */
1247*90685d2cSjp161948 			skipargs = 1;
1248*90685d2cSjp161948 			break;
1249*90685d2cSjp161948 		case 'e':
1250*90685d2cSjp161948 			log_stderr = 1;
1251*90685d2cSjp161948 			break;
1252*90685d2cSjp161948 		case 'l':
1253*90685d2cSjp161948 			log_level = log_level_number(optarg);
1254*90685d2cSjp161948 			if (log_level == SYSLOG_LEVEL_NOT_SET)
1255*90685d2cSjp161948 				error("Invalid log level \"%s\"", optarg);
1256*90685d2cSjp161948 			break;
1257*90685d2cSjp161948 		case 'f':
1258*90685d2cSjp161948 			log_facility = log_facility_number(optarg);
1259*90685d2cSjp161948 			if (log_facility == SYSLOG_FACILITY_NOT_SET)
1260*90685d2cSjp161948 				error("Invalid log facility \"%s\"", optarg);
1261*90685d2cSjp161948 			break;
1262*90685d2cSjp161948 		case 'h':
1263*90685d2cSjp161948 		default:
1264*90685d2cSjp161948 			usage();
1265*90685d2cSjp161948 		}
1266*90685d2cSjp161948 	}
1267*90685d2cSjp161948 
1268*90685d2cSjp161948 	log_init(__progname, log_level, log_facility, log_stderr);
1269*90685d2cSjp161948 
1270*90685d2cSjp161948 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
1271*90685d2cSjp161948 		client_addr = xstrdup(cp);
1272*90685d2cSjp161948 		if ((cp = strchr(client_addr, ' ')) == NULL)
1273*90685d2cSjp161948 			fatal("Malformed SSH_CONNECTION variable: \"%s\"",
1274*90685d2cSjp161948 			    getenv("SSH_CONNECTION"));
1275*90685d2cSjp161948 		*cp = '\0';
1276*90685d2cSjp161948 	} else
1277*90685d2cSjp161948 		client_addr = xstrdup("UNKNOWN");
1278*90685d2cSjp161948 
1279*90685d2cSjp161948 	if ((pw = getpwuid(getuid())) == NULL)
1280*90685d2cSjp161948 		fatal("No user found for uid %lu", (u_long)getuid());
1281*90685d2cSjp161948 	pw = pwcopy(pw);
1282*90685d2cSjp161948 
1283*90685d2cSjp161948 	log("session opened for local user %s from [%s]",
1284*90685d2cSjp161948 	    pw->pw_name, client_addr);
1285*90685d2cSjp161948 
1286*90685d2cSjp161948 	handle_init();
12877c478bd9Sstevel@tonic-gate 
12887c478bd9Sstevel@tonic-gate 	in = dup(STDIN_FILENO);
12897c478bd9Sstevel@tonic-gate 	out = dup(STDOUT_FILENO);
12907c478bd9Sstevel@tonic-gate 
12917c478bd9Sstevel@tonic-gate #ifdef HAVE_CYGWIN
12927c478bd9Sstevel@tonic-gate 	setmode(in, O_BINARY);
12937c478bd9Sstevel@tonic-gate 	setmode(out, O_BINARY);
12947c478bd9Sstevel@tonic-gate #endif
12957c478bd9Sstevel@tonic-gate 
12967c478bd9Sstevel@tonic-gate 	max = 0;
12977c478bd9Sstevel@tonic-gate 	if (in > max)
12987c478bd9Sstevel@tonic-gate 		max = in;
12997c478bd9Sstevel@tonic-gate 	if (out > max)
13007c478bd9Sstevel@tonic-gate 		max = out;
13017c478bd9Sstevel@tonic-gate 
13027c478bd9Sstevel@tonic-gate 	buffer_init(&iqueue);
13037c478bd9Sstevel@tonic-gate 	buffer_init(&oqueue);
13047c478bd9Sstevel@tonic-gate 
13057c478bd9Sstevel@tonic-gate 	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
13067c478bd9Sstevel@tonic-gate 	rset = (fd_set *)xmalloc(set_size);
13077c478bd9Sstevel@tonic-gate 	wset = (fd_set *)xmalloc(set_size);
13087c478bd9Sstevel@tonic-gate 
13097c478bd9Sstevel@tonic-gate 	for (;;) {
13107c478bd9Sstevel@tonic-gate 		memset(rset, 0, set_size);
13117c478bd9Sstevel@tonic-gate 		memset(wset, 0, set_size);
13127c478bd9Sstevel@tonic-gate 
1313*90685d2cSjp161948 		/*
1314*90685d2cSjp161948 		 * Ensure that we can read a full buffer and handle
1315*90685d2cSjp161948 		 * the worst-case length packet it can generate,
1316*90685d2cSjp161948 		 * otherwise apply backpressure by stopping reads.
1317*90685d2cSjp161948 		 */
1318*90685d2cSjp161948 		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
1319*90685d2cSjp161948 		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
13207c478bd9Sstevel@tonic-gate 			FD_SET(in, rset);
1321*90685d2cSjp161948 
13227c478bd9Sstevel@tonic-gate 		olen = buffer_len(&oqueue);
13237c478bd9Sstevel@tonic-gate 		if (olen > 0)
13247c478bd9Sstevel@tonic-gate 			FD_SET(out, wset);
13257c478bd9Sstevel@tonic-gate 
13267c478bd9Sstevel@tonic-gate 		if (select(max+1, rset, wset, NULL, NULL) < 0) {
13277c478bd9Sstevel@tonic-gate 			if (errno == EINTR)
13287c478bd9Sstevel@tonic-gate 				continue;
1329*90685d2cSjp161948 			error("select: %s", strerror(errno));
1330*90685d2cSjp161948 			cleanup_exit(2);
13317c478bd9Sstevel@tonic-gate 		}
13327c478bd9Sstevel@tonic-gate 
13337c478bd9Sstevel@tonic-gate 		/* copy stdin to iqueue */
13347c478bd9Sstevel@tonic-gate 		if (FD_ISSET(in, rset)) {
13357c478bd9Sstevel@tonic-gate 			len = read(in, buf, sizeof buf);
13367c478bd9Sstevel@tonic-gate 			if (len == 0) {
13377c478bd9Sstevel@tonic-gate 				debug("read eof");
1338*90685d2cSjp161948 				cleanup_exit(0);
13397c478bd9Sstevel@tonic-gate 			} else if (len < 0) {
1340*90685d2cSjp161948 				error("read: %s", strerror(errno));
1341*90685d2cSjp161948 				cleanup_exit(1);
13427c478bd9Sstevel@tonic-gate 			} else {
13437c478bd9Sstevel@tonic-gate 				buffer_append(&iqueue, buf, len);
13447c478bd9Sstevel@tonic-gate 			}
13457c478bd9Sstevel@tonic-gate 		}
13467c478bd9Sstevel@tonic-gate 		/* send oqueue to stdout */
13477c478bd9Sstevel@tonic-gate 		if (FD_ISSET(out, wset)) {
13487c478bd9Sstevel@tonic-gate 			len = write(out, buffer_ptr(&oqueue), olen);
13497c478bd9Sstevel@tonic-gate 			if (len < 0) {
1350*90685d2cSjp161948 				error("write: %s", strerror(errno));
1351*90685d2cSjp161948 				cleanup_exit(1);
13527c478bd9Sstevel@tonic-gate 			} else {
13537c478bd9Sstevel@tonic-gate 				buffer_consume(&oqueue, len);
13547c478bd9Sstevel@tonic-gate 			}
13557c478bd9Sstevel@tonic-gate 		}
1356*90685d2cSjp161948 
1357*90685d2cSjp161948 		/*
1358*90685d2cSjp161948 		 * Process requests from client if we can fit the results
1359*90685d2cSjp161948 		 * into the output buffer, otherwise stop processing input
1360*90685d2cSjp161948 		 * and let the output queue drain.
1361*90685d2cSjp161948 		 */
1362*90685d2cSjp161948 		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
13637c478bd9Sstevel@tonic-gate 			process();
13647c478bd9Sstevel@tonic-gate 	}
1365*90685d2cSjp161948 
1366*90685d2cSjp161948 	/* NOTREACHED */
1367*90685d2cSjp161948 	return (0);
13687c478bd9Sstevel@tonic-gate }
1369