xref: /titanic_44/usr/src/cmd/ssh/sftp-server/sftp-server.c (revision 6f8d59d8fcaf391990ca04c7bdcf65ab23320fe0)
17c478bd9Sstevel@tonic-gate /*
290685d2cSjp161948  * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
390685d2cSjp161948  *
490685d2cSjp161948  * Permission to use, copy, modify, and distribute this software for any
590685d2cSjp161948  * purpose with or without fee is hereby granted, provided that the above
690685d2cSjp161948  * copyright notice and this permission notice appear in all copies.
790685d2cSjp161948  *
890685d2cSjp161948  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
990685d2cSjp161948  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1090685d2cSjp161948  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1190685d2cSjp161948  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1290685d2cSjp161948  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1390685d2cSjp161948  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1490685d2cSjp161948  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
157c478bd9Sstevel@tonic-gate  */
167c478bd9Sstevel@tonic-gate 
1790685d2cSjp161948 /* $OpenBSD: sftp-server.c,v 1.71 2007/01/03 07:22:36 stevesk Exp $ */
187c478bd9Sstevel@tonic-gate 
1990685d2cSjp161948 #include "includes.h"
2090685d2cSjp161948 
2190685d2cSjp161948 #include <sys/types.h>
2290685d2cSjp161948 #include <sys/param.h>
2390685d2cSjp161948 #include <sys/stat.h>
2490685d2cSjp161948 #ifdef HAVE_SYS_TIME_H
2590685d2cSjp161948 # include <sys/time.h>
2690685d2cSjp161948 #endif
2790685d2cSjp161948 
2890685d2cSjp161948 #include <dirent.h>
2990685d2cSjp161948 #include <errno.h>
3090685d2cSjp161948 #include <fcntl.h>
3190685d2cSjp161948 #include <pwd.h>
3290685d2cSjp161948 #include <stdlib.h>
3390685d2cSjp161948 #include <stdio.h>
3490685d2cSjp161948 #include <string.h>
3590685d2cSjp161948 #include <pwd.h>
3690685d2cSjp161948 #include <time.h>
3790685d2cSjp161948 #include <unistd.h>
3890685d2cSjp161948 #include <stdarg.h>
3990685d2cSjp161948 
4090685d2cSjp161948 #include "xmalloc.h"
417c478bd9Sstevel@tonic-gate #include "buffer.h"
427c478bd9Sstevel@tonic-gate #include "bufaux.h"
437c478bd9Sstevel@tonic-gate #include "log.h"
4490685d2cSjp161948 #include "misc.h"
4590685d2cSjp161948 #include "uidswap.h"
467c478bd9Sstevel@tonic-gate 
477c478bd9Sstevel@tonic-gate #include "sftp.h"
487c478bd9Sstevel@tonic-gate #include "sftp-common.h"
497c478bd9Sstevel@tonic-gate 
507c478bd9Sstevel@tonic-gate #ifdef HAVE___PROGNAME
517c478bd9Sstevel@tonic-gate extern char *__progname;
527c478bd9Sstevel@tonic-gate #else
537c478bd9Sstevel@tonic-gate char *__progname;
547c478bd9Sstevel@tonic-gate #endif
557c478bd9Sstevel@tonic-gate 
5690685d2cSjp161948 /* helper */
5790685d2cSjp161948 #define get_int64()			buffer_get_int64(&iqueue);
5890685d2cSjp161948 #define get_int()			buffer_get_int(&iqueue);
5990685d2cSjp161948 #define get_string(lenp)		buffer_get_string(&iqueue, lenp);
6090685d2cSjp161948 
61*6f8d59d8SJan Pechanec void cleanup_exit(int i);
6290685d2cSjp161948 
6390685d2cSjp161948 /* Our verbosity */
6490685d2cSjp161948 LogLevel log_level = SYSLOG_LEVEL_ERROR;
6590685d2cSjp161948 
6690685d2cSjp161948 /* Our client */
6790685d2cSjp161948 struct passwd *pw = NULL;
6890685d2cSjp161948 char *client_addr = NULL;
6990685d2cSjp161948 
707c478bd9Sstevel@tonic-gate /* input and output queue */
717c478bd9Sstevel@tonic-gate Buffer iqueue;
727c478bd9Sstevel@tonic-gate Buffer oqueue;
737c478bd9Sstevel@tonic-gate 
747c478bd9Sstevel@tonic-gate /* Version of client */
757c478bd9Sstevel@tonic-gate int version;
767c478bd9Sstevel@tonic-gate 
7790685d2cSjp161948 /* portable attributes, etc. */
787c478bd9Sstevel@tonic-gate 
797c478bd9Sstevel@tonic-gate typedef struct Stat Stat;
807c478bd9Sstevel@tonic-gate 
817c478bd9Sstevel@tonic-gate struct Stat {
827c478bd9Sstevel@tonic-gate 	char *name;
837c478bd9Sstevel@tonic-gate 	char *long_name;
847c478bd9Sstevel@tonic-gate 	Attrib attrib;
857c478bd9Sstevel@tonic-gate };
867c478bd9Sstevel@tonic-gate 
877c478bd9Sstevel@tonic-gate static int
errno_to_portable(int unixerrno)887c478bd9Sstevel@tonic-gate errno_to_portable(int unixerrno)
897c478bd9Sstevel@tonic-gate {
907c478bd9Sstevel@tonic-gate 	int ret = 0;
917c478bd9Sstevel@tonic-gate 
927c478bd9Sstevel@tonic-gate 	switch (unixerrno) {
937c478bd9Sstevel@tonic-gate 	case 0:
947c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_OK;
957c478bd9Sstevel@tonic-gate 		break;
967c478bd9Sstevel@tonic-gate 	case ENOENT:
977c478bd9Sstevel@tonic-gate 	case ENOTDIR:
987c478bd9Sstevel@tonic-gate 	case EBADF:
997c478bd9Sstevel@tonic-gate 	case ELOOP:
1007c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_NO_SUCH_FILE;
1017c478bd9Sstevel@tonic-gate 		break;
1027c478bd9Sstevel@tonic-gate 	case EPERM:
1037c478bd9Sstevel@tonic-gate 	case EACCES:
1047c478bd9Sstevel@tonic-gate 	case EFAULT:
1057c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_PERMISSION_DENIED;
1067c478bd9Sstevel@tonic-gate 		break;
1077c478bd9Sstevel@tonic-gate 	case ENAMETOOLONG:
1087c478bd9Sstevel@tonic-gate 	case EINVAL:
1097c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_BAD_MESSAGE;
1107c478bd9Sstevel@tonic-gate 		break;
1117c478bd9Sstevel@tonic-gate 	default:
1127c478bd9Sstevel@tonic-gate 		ret = SSH2_FX_FAILURE;
1137c478bd9Sstevel@tonic-gate 		break;
1147c478bd9Sstevel@tonic-gate 	}
1157c478bd9Sstevel@tonic-gate 	return ret;
1167c478bd9Sstevel@tonic-gate }
1177c478bd9Sstevel@tonic-gate 
1187c478bd9Sstevel@tonic-gate static int
flags_from_portable(int pflags)1197c478bd9Sstevel@tonic-gate flags_from_portable(int pflags)
1207c478bd9Sstevel@tonic-gate {
1217c478bd9Sstevel@tonic-gate 	int flags = 0;
1227c478bd9Sstevel@tonic-gate 
1237c478bd9Sstevel@tonic-gate 	if ((pflags & SSH2_FXF_READ) &&
1247c478bd9Sstevel@tonic-gate 	    (pflags & SSH2_FXF_WRITE)) {
1257c478bd9Sstevel@tonic-gate 		flags = O_RDWR;
1267c478bd9Sstevel@tonic-gate 	} else if (pflags & SSH2_FXF_READ) {
1277c478bd9Sstevel@tonic-gate 		flags = O_RDONLY;
1287c478bd9Sstevel@tonic-gate 	} else if (pflags & SSH2_FXF_WRITE) {
1297c478bd9Sstevel@tonic-gate 		flags = O_WRONLY;
1307c478bd9Sstevel@tonic-gate 	}
1317c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_CREAT)
1327c478bd9Sstevel@tonic-gate 		flags |= O_CREAT;
1337c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_TRUNC)
1347c478bd9Sstevel@tonic-gate 		flags |= O_TRUNC;
1357c478bd9Sstevel@tonic-gate 	if (pflags & SSH2_FXF_EXCL)
1367c478bd9Sstevel@tonic-gate 		flags |= O_EXCL;
1377c478bd9Sstevel@tonic-gate 	return flags;
1387c478bd9Sstevel@tonic-gate }
1397c478bd9Sstevel@tonic-gate 
14090685d2cSjp161948 static const char *
string_from_portable(int pflags)14190685d2cSjp161948 string_from_portable(int pflags)
14290685d2cSjp161948 {
14390685d2cSjp161948 	static char ret[128];
14490685d2cSjp161948 
14590685d2cSjp161948 	*ret = '\0';
14690685d2cSjp161948 
14790685d2cSjp161948 #define PAPPEND(str)	{				\
14890685d2cSjp161948 		if (*ret != '\0')			\
14990685d2cSjp161948 			strlcat(ret, ",", sizeof(ret));	\
15090685d2cSjp161948 		strlcat(ret, str, sizeof(ret));		\
15190685d2cSjp161948 	}
15290685d2cSjp161948 
15390685d2cSjp161948 	if (pflags & SSH2_FXF_READ)
15490685d2cSjp161948 		PAPPEND("READ")
15590685d2cSjp161948 	if (pflags & SSH2_FXF_WRITE)
15690685d2cSjp161948 		PAPPEND("WRITE")
15790685d2cSjp161948 	if (pflags & SSH2_FXF_CREAT)
15890685d2cSjp161948 		PAPPEND("CREATE")
15990685d2cSjp161948 	if (pflags & SSH2_FXF_TRUNC)
16090685d2cSjp161948 		PAPPEND("TRUNCATE")
16190685d2cSjp161948 	if (pflags & SSH2_FXF_EXCL)
16290685d2cSjp161948 		PAPPEND("EXCL")
16390685d2cSjp161948 
16490685d2cSjp161948 	return ret;
16590685d2cSjp161948 }
16690685d2cSjp161948 
1677c478bd9Sstevel@tonic-gate static Attrib *
get_attrib(void)1687c478bd9Sstevel@tonic-gate get_attrib(void)
1697c478bd9Sstevel@tonic-gate {
1707c478bd9Sstevel@tonic-gate 	return decode_attrib(&iqueue);
1717c478bd9Sstevel@tonic-gate }
1727c478bd9Sstevel@tonic-gate 
1737c478bd9Sstevel@tonic-gate /* handle handles */
1747c478bd9Sstevel@tonic-gate 
1757c478bd9Sstevel@tonic-gate typedef struct Handle Handle;
1767c478bd9Sstevel@tonic-gate struct Handle {
1777c478bd9Sstevel@tonic-gate 	int use;
1787c478bd9Sstevel@tonic-gate 	DIR *dirp;
1797c478bd9Sstevel@tonic-gate 	int fd;
1807c478bd9Sstevel@tonic-gate 	char *name;
18190685d2cSjp161948 	u_int64_t bytes_read, bytes_write;
1827c478bd9Sstevel@tonic-gate };
1837c478bd9Sstevel@tonic-gate 
1847c478bd9Sstevel@tonic-gate enum {
1857c478bd9Sstevel@tonic-gate 	HANDLE_UNUSED,
1867c478bd9Sstevel@tonic-gate 	HANDLE_DIR,
1877c478bd9Sstevel@tonic-gate 	HANDLE_FILE
1887c478bd9Sstevel@tonic-gate };
1897c478bd9Sstevel@tonic-gate 
1907c478bd9Sstevel@tonic-gate Handle	handles[100];
1917c478bd9Sstevel@tonic-gate 
1927c478bd9Sstevel@tonic-gate static void
handle_init(void)1937c478bd9Sstevel@tonic-gate handle_init(void)
1947c478bd9Sstevel@tonic-gate {
19590685d2cSjp161948 	u_int i;
1967c478bd9Sstevel@tonic-gate 
1977c478bd9Sstevel@tonic-gate 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
1987c478bd9Sstevel@tonic-gate 		handles[i].use = HANDLE_UNUSED;
1997c478bd9Sstevel@tonic-gate }
2007c478bd9Sstevel@tonic-gate 
2017c478bd9Sstevel@tonic-gate static int
handle_new(int use,const char * name,int fd,DIR * dirp)20290685d2cSjp161948 handle_new(int use, const char *name, int fd, DIR *dirp)
2037c478bd9Sstevel@tonic-gate {
20490685d2cSjp161948 	u_int i;
2057c478bd9Sstevel@tonic-gate 
2067c478bd9Sstevel@tonic-gate 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++) {
2077c478bd9Sstevel@tonic-gate 		if (handles[i].use == HANDLE_UNUSED) {
2087c478bd9Sstevel@tonic-gate 			handles[i].use = use;
2097c478bd9Sstevel@tonic-gate 			handles[i].dirp = dirp;
2107c478bd9Sstevel@tonic-gate 			handles[i].fd = fd;
21190685d2cSjp161948 			handles[i].name = xstrdup(name);
21290685d2cSjp161948 			handles[i].bytes_read = handles[i].bytes_write = 0;
2137c478bd9Sstevel@tonic-gate 			return i;
2147c478bd9Sstevel@tonic-gate 		}
2157c478bd9Sstevel@tonic-gate 	}
2167c478bd9Sstevel@tonic-gate 	return -1;
2177c478bd9Sstevel@tonic-gate }
2187c478bd9Sstevel@tonic-gate 
2197c478bd9Sstevel@tonic-gate static int
handle_is_ok(int i,int type)2207c478bd9Sstevel@tonic-gate handle_is_ok(int i, int type)
2217c478bd9Sstevel@tonic-gate {
22290685d2cSjp161948 	return i >= 0 && (u_int)i < sizeof(handles)/sizeof(Handle) &&
2237c478bd9Sstevel@tonic-gate 	    handles[i].use == type;
2247c478bd9Sstevel@tonic-gate }
2257c478bd9Sstevel@tonic-gate 
2267c478bd9Sstevel@tonic-gate static int
handle_to_string(int handle,char ** stringp,int * hlenp)2277c478bd9Sstevel@tonic-gate handle_to_string(int handle, char **stringp, int *hlenp)
2287c478bd9Sstevel@tonic-gate {
2297c478bd9Sstevel@tonic-gate 	if (stringp == NULL || hlenp == NULL)
2307c478bd9Sstevel@tonic-gate 		return -1;
2317c478bd9Sstevel@tonic-gate 	*stringp = xmalloc(sizeof(int32_t));
23290685d2cSjp161948 	put_u32(*stringp, handle);
2337c478bd9Sstevel@tonic-gate 	*hlenp = sizeof(int32_t);
2347c478bd9Sstevel@tonic-gate 	return 0;
2357c478bd9Sstevel@tonic-gate }
2367c478bd9Sstevel@tonic-gate 
2377c478bd9Sstevel@tonic-gate static int
handle_from_string(const char * handle,u_int hlen)23890685d2cSjp161948 handle_from_string(const char *handle, u_int hlen)
2397c478bd9Sstevel@tonic-gate {
2407c478bd9Sstevel@tonic-gate 	int val;
2417c478bd9Sstevel@tonic-gate 
2427c478bd9Sstevel@tonic-gate 	if (hlen != sizeof(int32_t))
2437c478bd9Sstevel@tonic-gate 		return -1;
24490685d2cSjp161948 	val = get_u32(handle);
2457c478bd9Sstevel@tonic-gate 	if (handle_is_ok(val, HANDLE_FILE) ||
2467c478bd9Sstevel@tonic-gate 	    handle_is_ok(val, HANDLE_DIR))
2477c478bd9Sstevel@tonic-gate 		return val;
2487c478bd9Sstevel@tonic-gate 	return -1;
2497c478bd9Sstevel@tonic-gate }
2507c478bd9Sstevel@tonic-gate 
2517c478bd9Sstevel@tonic-gate static char *
handle_to_name(int handle)2527c478bd9Sstevel@tonic-gate handle_to_name(int handle)
2537c478bd9Sstevel@tonic-gate {
2547c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_DIR)||
2557c478bd9Sstevel@tonic-gate 	    handle_is_ok(handle, HANDLE_FILE))
2567c478bd9Sstevel@tonic-gate 		return handles[handle].name;
2577c478bd9Sstevel@tonic-gate 	return NULL;
2587c478bd9Sstevel@tonic-gate }
2597c478bd9Sstevel@tonic-gate 
2607c478bd9Sstevel@tonic-gate static DIR *
handle_to_dir(int handle)2617c478bd9Sstevel@tonic-gate handle_to_dir(int handle)
2627c478bd9Sstevel@tonic-gate {
2637c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_DIR))
2647c478bd9Sstevel@tonic-gate 		return handles[handle].dirp;
2657c478bd9Sstevel@tonic-gate 	return NULL;
2667c478bd9Sstevel@tonic-gate }
2677c478bd9Sstevel@tonic-gate 
2687c478bd9Sstevel@tonic-gate static int
handle_to_fd(int handle)2697c478bd9Sstevel@tonic-gate handle_to_fd(int handle)
2707c478bd9Sstevel@tonic-gate {
2717c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_FILE))
2727c478bd9Sstevel@tonic-gate 		return handles[handle].fd;
2737c478bd9Sstevel@tonic-gate 	return -1;
2747c478bd9Sstevel@tonic-gate }
2757c478bd9Sstevel@tonic-gate 
27690685d2cSjp161948 static void
handle_update_read(int handle,ssize_t bytes)27790685d2cSjp161948 handle_update_read(int handle, ssize_t bytes)
27890685d2cSjp161948 {
27990685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
28090685d2cSjp161948 		handles[handle].bytes_read += bytes;
28190685d2cSjp161948 }
28290685d2cSjp161948 
28390685d2cSjp161948 static void
handle_update_write(int handle,ssize_t bytes)28490685d2cSjp161948 handle_update_write(int handle, ssize_t bytes)
28590685d2cSjp161948 {
28690685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
28790685d2cSjp161948 		handles[handle].bytes_write += bytes;
28890685d2cSjp161948 }
28990685d2cSjp161948 
29090685d2cSjp161948 static u_int64_t
handle_bytes_read(int handle)29190685d2cSjp161948 handle_bytes_read(int handle)
29290685d2cSjp161948 {
29390685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE))
29490685d2cSjp161948 		return (handles[handle].bytes_read);
29590685d2cSjp161948 	return 0;
29690685d2cSjp161948 }
29790685d2cSjp161948 
29890685d2cSjp161948 static u_int64_t
handle_bytes_write(int handle)29990685d2cSjp161948 handle_bytes_write(int handle)
30090685d2cSjp161948 {
30190685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE))
30290685d2cSjp161948 		return (handles[handle].bytes_write);
30390685d2cSjp161948 	return 0;
30490685d2cSjp161948 }
30590685d2cSjp161948 
3067c478bd9Sstevel@tonic-gate static int
handle_close(int handle)3077c478bd9Sstevel@tonic-gate handle_close(int handle)
3087c478bd9Sstevel@tonic-gate {
3097c478bd9Sstevel@tonic-gate 	int ret = -1;
3107c478bd9Sstevel@tonic-gate 
3117c478bd9Sstevel@tonic-gate 	if (handle_is_ok(handle, HANDLE_FILE)) {
3127c478bd9Sstevel@tonic-gate 		ret = close(handles[handle].fd);
3137c478bd9Sstevel@tonic-gate 		handles[handle].use = HANDLE_UNUSED;
31490685d2cSjp161948 		xfree(handles[handle].name);
3157c478bd9Sstevel@tonic-gate 	} else if (handle_is_ok(handle, HANDLE_DIR)) {
3167c478bd9Sstevel@tonic-gate 		ret = closedir(handles[handle].dirp);
3177c478bd9Sstevel@tonic-gate 		handles[handle].use = HANDLE_UNUSED;
31890685d2cSjp161948 		xfree(handles[handle].name);
3197c478bd9Sstevel@tonic-gate 	} else {
3207c478bd9Sstevel@tonic-gate 		errno = ENOENT;
3217c478bd9Sstevel@tonic-gate 	}
3227c478bd9Sstevel@tonic-gate 	return ret;
3237c478bd9Sstevel@tonic-gate }
3247c478bd9Sstevel@tonic-gate 
32590685d2cSjp161948 static void
handle_log_close(int handle,char * emsg)32690685d2cSjp161948 handle_log_close(int handle, char *emsg)
32790685d2cSjp161948 {
32890685d2cSjp161948 	if (handle_is_ok(handle, HANDLE_FILE)) {
32990685d2cSjp161948 		log("%s%sclose \"%s\" bytes read %llu written %llu",
33090685d2cSjp161948 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
33190685d2cSjp161948 		    handle_to_name(handle),
33290685d2cSjp161948 		    (unsigned long long)handle_bytes_read(handle),
33390685d2cSjp161948 		    (unsigned long long)handle_bytes_write(handle));
33490685d2cSjp161948 	} else {
33590685d2cSjp161948 		log("%s%sclosedir \"%s\"",
33690685d2cSjp161948 		    emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
33790685d2cSjp161948 		    handle_to_name(handle));
33890685d2cSjp161948 	}
33990685d2cSjp161948 }
34090685d2cSjp161948 
34190685d2cSjp161948 static void
handle_log_exit(void)34290685d2cSjp161948 handle_log_exit(void)
34390685d2cSjp161948 {
34490685d2cSjp161948 	u_int i;
34590685d2cSjp161948 
34690685d2cSjp161948 	for (i = 0; i < sizeof(handles)/sizeof(Handle); i++)
34790685d2cSjp161948 		if (handles[i].use != HANDLE_UNUSED)
34890685d2cSjp161948 			handle_log_close(i, "forced");
34990685d2cSjp161948 }
35090685d2cSjp161948 
3517c478bd9Sstevel@tonic-gate static int
get_handle(void)3527c478bd9Sstevel@tonic-gate get_handle(void)
3537c478bd9Sstevel@tonic-gate {
3547c478bd9Sstevel@tonic-gate 	char *handle;
3557c478bd9Sstevel@tonic-gate 	int val = -1;
3567c478bd9Sstevel@tonic-gate 	u_int hlen;
3577c478bd9Sstevel@tonic-gate 
3587c478bd9Sstevel@tonic-gate 	handle = get_string(&hlen);
3597c478bd9Sstevel@tonic-gate 	if (hlen < 256)
3607c478bd9Sstevel@tonic-gate 		val = handle_from_string(handle, hlen);
3617c478bd9Sstevel@tonic-gate 	xfree(handle);
3627c478bd9Sstevel@tonic-gate 	return val;
3637c478bd9Sstevel@tonic-gate }
3647c478bd9Sstevel@tonic-gate 
3657c478bd9Sstevel@tonic-gate /* send replies */
3667c478bd9Sstevel@tonic-gate 
3677c478bd9Sstevel@tonic-gate static void
send_msg(Buffer * m)3687c478bd9Sstevel@tonic-gate send_msg(Buffer *m)
3697c478bd9Sstevel@tonic-gate {
3707c478bd9Sstevel@tonic-gate 	int mlen = buffer_len(m);
3717c478bd9Sstevel@tonic-gate 
3727c478bd9Sstevel@tonic-gate 	buffer_put_int(&oqueue, mlen);
3737c478bd9Sstevel@tonic-gate 	buffer_append(&oqueue, buffer_ptr(m), mlen);
3747c478bd9Sstevel@tonic-gate 	buffer_consume(m, mlen);
3757c478bd9Sstevel@tonic-gate }
3767c478bd9Sstevel@tonic-gate 
37790685d2cSjp161948 static const char *
status_to_message(u_int32_t status)37890685d2cSjp161948 status_to_message(u_int32_t status)
3797c478bd9Sstevel@tonic-gate {
3807c478bd9Sstevel@tonic-gate 	const char *status_messages[] = {
3817c478bd9Sstevel@tonic-gate 		"Success",			/* SSH_FX_OK */
3827c478bd9Sstevel@tonic-gate 		"End of file",			/* SSH_FX_EOF */
3837c478bd9Sstevel@tonic-gate 		"No such file",			/* SSH_FX_NO_SUCH_FILE */
3847c478bd9Sstevel@tonic-gate 		"Permission denied",		/* SSH_FX_PERMISSION_DENIED */
3857c478bd9Sstevel@tonic-gate 		"Failure",			/* SSH_FX_FAILURE */
3867c478bd9Sstevel@tonic-gate 		"Bad message",			/* SSH_FX_BAD_MESSAGE */
3877c478bd9Sstevel@tonic-gate 		"No connection",		/* SSH_FX_NO_CONNECTION */
3887c478bd9Sstevel@tonic-gate 		"Connection lost",		/* SSH_FX_CONNECTION_LOST */
3897c478bd9Sstevel@tonic-gate 		"Operation unsupported",	/* SSH_FX_OP_UNSUPPORTED */
3907c478bd9Sstevel@tonic-gate 		"Unknown error"			/* Others */
3917c478bd9Sstevel@tonic-gate 	};
39290685d2cSjp161948 	return (status_messages[MIN(status,SSH2_FX_MAX)]);
39390685d2cSjp161948 }
3947c478bd9Sstevel@tonic-gate 
39590685d2cSjp161948 static void
send_status(u_int32_t id,u_int32_t status)39690685d2cSjp161948 send_status(u_int32_t id, u_int32_t status)
39790685d2cSjp161948 {
39890685d2cSjp161948 	Buffer msg;
39990685d2cSjp161948 
40090685d2cSjp161948 	debug3("request %u: sent status %u", id, status);
40190685d2cSjp161948 	if (log_level > SYSLOG_LEVEL_VERBOSE ||
40290685d2cSjp161948 	    (status != SSH2_FX_OK && status != SSH2_FX_EOF))
40390685d2cSjp161948 		log("sent status %s", status_to_message(status));
4047c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4057c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_STATUS);
4067c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
40790685d2cSjp161948 	buffer_put_int(&msg, status);
4087c478bd9Sstevel@tonic-gate 	if (version >= 3) {
40990685d2cSjp161948 		buffer_put_cstring(&msg, status_to_message(status));
4107c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, "");
4117c478bd9Sstevel@tonic-gate 	}
4127c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4137c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4147c478bd9Sstevel@tonic-gate }
4157c478bd9Sstevel@tonic-gate static void
send_data_or_handle(char type,u_int32_t id,const char * data,int dlen)41690685d2cSjp161948 send_data_or_handle(char type, u_int32_t id, const char *data, int dlen)
4177c478bd9Sstevel@tonic-gate {
4187c478bd9Sstevel@tonic-gate 	Buffer msg;
4197c478bd9Sstevel@tonic-gate 
4207c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4217c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, type);
4227c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4237c478bd9Sstevel@tonic-gate 	buffer_put_string(&msg, data, dlen);
4247c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4257c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4267c478bd9Sstevel@tonic-gate }
4277c478bd9Sstevel@tonic-gate 
4287c478bd9Sstevel@tonic-gate static void
send_data(u_int32_t id,const char * data,int dlen)42990685d2cSjp161948 send_data(u_int32_t id, const char *data, int dlen)
4307c478bd9Sstevel@tonic-gate {
43190685d2cSjp161948 	debug("request %u: sent data len %d", id, dlen);
4327c478bd9Sstevel@tonic-gate 	send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
4337c478bd9Sstevel@tonic-gate }
4347c478bd9Sstevel@tonic-gate 
4357c478bd9Sstevel@tonic-gate static void
send_handle(u_int32_t id,int handle)4367c478bd9Sstevel@tonic-gate send_handle(u_int32_t id, int handle)
4377c478bd9Sstevel@tonic-gate {
4387c478bd9Sstevel@tonic-gate 	char *string;
4397c478bd9Sstevel@tonic-gate 	int hlen;
4407c478bd9Sstevel@tonic-gate 
4417c478bd9Sstevel@tonic-gate 	handle_to_string(handle, &string, &hlen);
44290685d2cSjp161948 	debug("request %u: sent handle handle %d", id, handle);
4437c478bd9Sstevel@tonic-gate 	send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
4447c478bd9Sstevel@tonic-gate 	xfree(string);
4457c478bd9Sstevel@tonic-gate }
4467c478bd9Sstevel@tonic-gate 
4477c478bd9Sstevel@tonic-gate static void
send_names(u_int32_t id,int count,const Stat * stats)44890685d2cSjp161948 send_names(u_int32_t id, int count, const Stat *stats)
4497c478bd9Sstevel@tonic-gate {
4507c478bd9Sstevel@tonic-gate 	Buffer msg;
4517c478bd9Sstevel@tonic-gate 	int i;
4527c478bd9Sstevel@tonic-gate 
4537c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4547c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_NAME);
4557c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4567c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, count);
45790685d2cSjp161948 	debug("request %u: sent names count %d", id, count);
4587c478bd9Sstevel@tonic-gate 	for (i = 0; i < count; i++) {
4597c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, stats[i].name);
4607c478bd9Sstevel@tonic-gate 		buffer_put_cstring(&msg, stats[i].long_name);
4617c478bd9Sstevel@tonic-gate 		encode_attrib(&msg, &stats[i].attrib);
4627c478bd9Sstevel@tonic-gate 	}
4637c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4647c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4657c478bd9Sstevel@tonic-gate }
4667c478bd9Sstevel@tonic-gate 
4677c478bd9Sstevel@tonic-gate static void
send_attrib(u_int32_t id,const Attrib * a)46890685d2cSjp161948 send_attrib(u_int32_t id, const Attrib *a)
4697c478bd9Sstevel@tonic-gate {
4707c478bd9Sstevel@tonic-gate 	Buffer msg;
4717c478bd9Sstevel@tonic-gate 
47290685d2cSjp161948 	debug("request %u: sent attrib have 0x%x", id, a->flags);
4737c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4747c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_ATTRS);
4757c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, id);
4767c478bd9Sstevel@tonic-gate 	encode_attrib(&msg, a);
4777c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4787c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4797c478bd9Sstevel@tonic-gate }
4807c478bd9Sstevel@tonic-gate 
4817c478bd9Sstevel@tonic-gate /* parse incoming */
4827c478bd9Sstevel@tonic-gate 
4837c478bd9Sstevel@tonic-gate static void
process_init(void)4847c478bd9Sstevel@tonic-gate process_init(void)
4857c478bd9Sstevel@tonic-gate {
4867c478bd9Sstevel@tonic-gate 	Buffer msg;
4877c478bd9Sstevel@tonic-gate 
4887c478bd9Sstevel@tonic-gate 	version = get_int();
48990685d2cSjp161948 	verbose("received client version %d", version);
4907c478bd9Sstevel@tonic-gate 	buffer_init(&msg);
4917c478bd9Sstevel@tonic-gate 	buffer_put_char(&msg, SSH2_FXP_VERSION);
4927c478bd9Sstevel@tonic-gate 	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
4937c478bd9Sstevel@tonic-gate 	send_msg(&msg);
4947c478bd9Sstevel@tonic-gate 	buffer_free(&msg);
4957c478bd9Sstevel@tonic-gate }
4967c478bd9Sstevel@tonic-gate 
4977c478bd9Sstevel@tonic-gate static void
process_open(void)4987c478bd9Sstevel@tonic-gate process_open(void)
4997c478bd9Sstevel@tonic-gate {
5007c478bd9Sstevel@tonic-gate 	u_int32_t id, pflags;
5017c478bd9Sstevel@tonic-gate 	Attrib *a;
5027c478bd9Sstevel@tonic-gate 	char *name;
5037c478bd9Sstevel@tonic-gate 	int handle, fd, flags, mode, status = SSH2_FX_FAILURE;
5047c478bd9Sstevel@tonic-gate 
5057c478bd9Sstevel@tonic-gate 	id = get_int();
5067c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
5077c478bd9Sstevel@tonic-gate 	pflags = get_int();		/* portable flags */
50890685d2cSjp161948 	debug3("request %u: open flags %d", id, pflags);
5097c478bd9Sstevel@tonic-gate 	a = get_attrib();
5107c478bd9Sstevel@tonic-gate 	flags = flags_from_portable(pflags);
5117c478bd9Sstevel@tonic-gate 	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666;
51290685d2cSjp161948 	log("open \"%s\" flags %s mode 0%o",
51390685d2cSjp161948 	    name, string_from_portable(pflags), mode);
5147c478bd9Sstevel@tonic-gate 	fd = open(name, flags, mode);
5157c478bd9Sstevel@tonic-gate 	if (fd < 0) {
5167c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
5177c478bd9Sstevel@tonic-gate 	} else {
51890685d2cSjp161948 		handle = handle_new(HANDLE_FILE, name, fd, NULL);
5197c478bd9Sstevel@tonic-gate 		if (handle < 0) {
5207c478bd9Sstevel@tonic-gate 			close(fd);
5217c478bd9Sstevel@tonic-gate 		} else {
5227c478bd9Sstevel@tonic-gate 			send_handle(id, handle);
5237c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
5247c478bd9Sstevel@tonic-gate 		}
5257c478bd9Sstevel@tonic-gate 	}
5267c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
5277c478bd9Sstevel@tonic-gate 		send_status(id, status);
5287c478bd9Sstevel@tonic-gate 	xfree(name);
5297c478bd9Sstevel@tonic-gate }
5307c478bd9Sstevel@tonic-gate 
5317c478bd9Sstevel@tonic-gate static void
process_close(void)5327c478bd9Sstevel@tonic-gate process_close(void)
5337c478bd9Sstevel@tonic-gate {
5347c478bd9Sstevel@tonic-gate 	u_int32_t id;
5357c478bd9Sstevel@tonic-gate 	int handle, ret, status = SSH2_FX_FAILURE;
5367c478bd9Sstevel@tonic-gate 
5377c478bd9Sstevel@tonic-gate 	id = get_int();
5387c478bd9Sstevel@tonic-gate 	handle = get_handle();
53990685d2cSjp161948 	debug3("request %u: close handle %u", id, handle);
54090685d2cSjp161948 	handle_log_close(handle, NULL);
5417c478bd9Sstevel@tonic-gate 	ret = handle_close(handle);
5427c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
5437c478bd9Sstevel@tonic-gate 	send_status(id, status);
5447c478bd9Sstevel@tonic-gate }
5457c478bd9Sstevel@tonic-gate 
5467c478bd9Sstevel@tonic-gate static void
process_read(void)5477c478bd9Sstevel@tonic-gate process_read(void)
5487c478bd9Sstevel@tonic-gate {
5497c478bd9Sstevel@tonic-gate 	char buf[64*1024];
5507c478bd9Sstevel@tonic-gate 	u_int32_t id, len;
5517c478bd9Sstevel@tonic-gate 	int handle, fd, ret, status = SSH2_FX_FAILURE;
5527c478bd9Sstevel@tonic-gate 	u_int64_t off;
5537c478bd9Sstevel@tonic-gate 
5547c478bd9Sstevel@tonic-gate 	id = get_int();
5557c478bd9Sstevel@tonic-gate 	handle = get_handle();
5567c478bd9Sstevel@tonic-gate 	off = get_int64();
5577c478bd9Sstevel@tonic-gate 	len = get_int();
5587c478bd9Sstevel@tonic-gate 
55990685d2cSjp161948 	debug("request %u: read \"%s\" (handle %d) off %llu len %d",
56090685d2cSjp161948 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
5617c478bd9Sstevel@tonic-gate 	if (len > sizeof buf) {
5627c478bd9Sstevel@tonic-gate 		len = sizeof buf;
56390685d2cSjp161948 		debug2("read change len %d", len);
5647c478bd9Sstevel@tonic-gate 	}
5657c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
5667c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
5677c478bd9Sstevel@tonic-gate 		if (lseek(fd, off, SEEK_SET) < 0) {
5687c478bd9Sstevel@tonic-gate 			error("process_read: seek failed");
5697c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
5707c478bd9Sstevel@tonic-gate 		} else {
5717c478bd9Sstevel@tonic-gate 			ret = read(fd, buf, len);
5727c478bd9Sstevel@tonic-gate 			if (ret < 0) {
5737c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
5747c478bd9Sstevel@tonic-gate 			} else if (ret == 0) {
5757c478bd9Sstevel@tonic-gate 				status = SSH2_FX_EOF;
5767c478bd9Sstevel@tonic-gate 			} else {
5777c478bd9Sstevel@tonic-gate 				send_data(id, buf, ret);
5787c478bd9Sstevel@tonic-gate 				status = SSH2_FX_OK;
57990685d2cSjp161948 				handle_update_read(handle, ret);
5807c478bd9Sstevel@tonic-gate 			}
5817c478bd9Sstevel@tonic-gate 		}
5827c478bd9Sstevel@tonic-gate 	}
5837c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
5847c478bd9Sstevel@tonic-gate 		send_status(id, status);
5857c478bd9Sstevel@tonic-gate }
5867c478bd9Sstevel@tonic-gate 
5877c478bd9Sstevel@tonic-gate static void
process_write(void)5887c478bd9Sstevel@tonic-gate process_write(void)
5897c478bd9Sstevel@tonic-gate {
5907c478bd9Sstevel@tonic-gate 	u_int32_t id;
5917c478bd9Sstevel@tonic-gate 	u_int64_t off;
5927c478bd9Sstevel@tonic-gate 	u_int len;
5937c478bd9Sstevel@tonic-gate 	int handle, fd, ret, status = SSH2_FX_FAILURE;
5947c478bd9Sstevel@tonic-gate 	char *data;
5957c478bd9Sstevel@tonic-gate 
5967c478bd9Sstevel@tonic-gate 	id = get_int();
5977c478bd9Sstevel@tonic-gate 	handle = get_handle();
5987c478bd9Sstevel@tonic-gate 	off = get_int64();
5997c478bd9Sstevel@tonic-gate 	data = get_string(&len);
6007c478bd9Sstevel@tonic-gate 
60190685d2cSjp161948 	debug("request %u: write \"%s\" (handle %d) off %llu len %d",
60290685d2cSjp161948 	    id, handle_to_name(handle), handle, (unsigned long long)off, len);
6037c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
6047c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
6057c478bd9Sstevel@tonic-gate 		if (lseek(fd, off, SEEK_SET) < 0) {
6067c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
6077c478bd9Sstevel@tonic-gate 			error("process_write: seek failed");
6087c478bd9Sstevel@tonic-gate 		} else {
6097c478bd9Sstevel@tonic-gate /* XXX ATOMICIO ? */
6107c478bd9Sstevel@tonic-gate 			ret = write(fd, data, len);
61190685d2cSjp161948 			if (ret < 0) {
6127c478bd9Sstevel@tonic-gate 				error("process_write: write failed");
6137c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
61490685d2cSjp161948 			} else if ((size_t)ret == len) {
6157c478bd9Sstevel@tonic-gate 				status = SSH2_FX_OK;
61690685d2cSjp161948 				handle_update_write(handle, ret);
6177c478bd9Sstevel@tonic-gate 			} else {
61890685d2cSjp161948 				debug2("nothing at all written");
6197c478bd9Sstevel@tonic-gate 			}
6207c478bd9Sstevel@tonic-gate 		}
6217c478bd9Sstevel@tonic-gate 	}
6227c478bd9Sstevel@tonic-gate 	send_status(id, status);
6237c478bd9Sstevel@tonic-gate 	xfree(data);
6247c478bd9Sstevel@tonic-gate }
6257c478bd9Sstevel@tonic-gate 
6267c478bd9Sstevel@tonic-gate static void
process_do_stat(int do_lstat)6277c478bd9Sstevel@tonic-gate process_do_stat(int do_lstat)
6287c478bd9Sstevel@tonic-gate {
6297c478bd9Sstevel@tonic-gate 	Attrib a;
6307c478bd9Sstevel@tonic-gate 	struct stat st;
6317c478bd9Sstevel@tonic-gate 	u_int32_t id;
6327c478bd9Sstevel@tonic-gate 	char *name;
6337c478bd9Sstevel@tonic-gate 	int ret, status = SSH2_FX_FAILURE;
6347c478bd9Sstevel@tonic-gate 
6357c478bd9Sstevel@tonic-gate 	id = get_int();
6367c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
63790685d2cSjp161948 	debug3("request %u: %sstat", id, do_lstat ? "l" : "");
63890685d2cSjp161948 	verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
6397c478bd9Sstevel@tonic-gate 	ret = do_lstat ? lstat(name, &st) : stat(name, &st);
6407c478bd9Sstevel@tonic-gate 	if (ret < 0) {
6417c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
6427c478bd9Sstevel@tonic-gate 	} else {
6437c478bd9Sstevel@tonic-gate 		stat_to_attrib(&st, &a);
6447c478bd9Sstevel@tonic-gate 		send_attrib(id, &a);
6457c478bd9Sstevel@tonic-gate 		status = SSH2_FX_OK;
6467c478bd9Sstevel@tonic-gate 	}
6477c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
6487c478bd9Sstevel@tonic-gate 		send_status(id, status);
6497c478bd9Sstevel@tonic-gate 	xfree(name);
6507c478bd9Sstevel@tonic-gate }
6517c478bd9Sstevel@tonic-gate 
6527c478bd9Sstevel@tonic-gate static void
process_stat(void)6537c478bd9Sstevel@tonic-gate process_stat(void)
6547c478bd9Sstevel@tonic-gate {
6557c478bd9Sstevel@tonic-gate 	process_do_stat(0);
6567c478bd9Sstevel@tonic-gate }
6577c478bd9Sstevel@tonic-gate 
6587c478bd9Sstevel@tonic-gate static void
process_lstat(void)6597c478bd9Sstevel@tonic-gate process_lstat(void)
6607c478bd9Sstevel@tonic-gate {
6617c478bd9Sstevel@tonic-gate 	process_do_stat(1);
6627c478bd9Sstevel@tonic-gate }
6637c478bd9Sstevel@tonic-gate 
6647c478bd9Sstevel@tonic-gate static void
process_fstat(void)6657c478bd9Sstevel@tonic-gate process_fstat(void)
6667c478bd9Sstevel@tonic-gate {
6677c478bd9Sstevel@tonic-gate 	Attrib a;
6687c478bd9Sstevel@tonic-gate 	struct stat st;
6697c478bd9Sstevel@tonic-gate 	u_int32_t id;
6707c478bd9Sstevel@tonic-gate 	int fd, ret, handle, status = SSH2_FX_FAILURE;
6717c478bd9Sstevel@tonic-gate 
6727c478bd9Sstevel@tonic-gate 	id = get_int();
6737c478bd9Sstevel@tonic-gate 	handle = get_handle();
67490685d2cSjp161948 	debug("request %u: fstat \"%s\" (handle %u)",
67590685d2cSjp161948 	    id, handle_to_name(handle), handle);
6767c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
6777c478bd9Sstevel@tonic-gate 	if (fd >= 0) {
6787c478bd9Sstevel@tonic-gate 		ret = fstat(fd, &st);
6797c478bd9Sstevel@tonic-gate 		if (ret < 0) {
6807c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
6817c478bd9Sstevel@tonic-gate 		} else {
6827c478bd9Sstevel@tonic-gate 			stat_to_attrib(&st, &a);
6837c478bd9Sstevel@tonic-gate 			send_attrib(id, &a);
6847c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
6857c478bd9Sstevel@tonic-gate 		}
6867c478bd9Sstevel@tonic-gate 	}
6877c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
6887c478bd9Sstevel@tonic-gate 		send_status(id, status);
6897c478bd9Sstevel@tonic-gate }
6907c478bd9Sstevel@tonic-gate 
6917c478bd9Sstevel@tonic-gate static struct timeval *
attrib_to_tv(const Attrib * a)69290685d2cSjp161948 attrib_to_tv(const Attrib *a)
6937c478bd9Sstevel@tonic-gate {
6947c478bd9Sstevel@tonic-gate 	static struct timeval tv[2];
6957c478bd9Sstevel@tonic-gate 
6967c478bd9Sstevel@tonic-gate 	tv[0].tv_sec = a->atime;
6977c478bd9Sstevel@tonic-gate 	tv[0].tv_usec = 0;
6987c478bd9Sstevel@tonic-gate 	tv[1].tv_sec = a->mtime;
6997c478bd9Sstevel@tonic-gate 	tv[1].tv_usec = 0;
7007c478bd9Sstevel@tonic-gate 	return tv;
7017c478bd9Sstevel@tonic-gate }
7027c478bd9Sstevel@tonic-gate 
7037c478bd9Sstevel@tonic-gate static void
process_setstat(void)7047c478bd9Sstevel@tonic-gate process_setstat(void)
7057c478bd9Sstevel@tonic-gate {
7067c478bd9Sstevel@tonic-gate 	Attrib *a;
7077c478bd9Sstevel@tonic-gate 	u_int32_t id;
7087c478bd9Sstevel@tonic-gate 	char *name;
7097c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_OK, ret;
7107c478bd9Sstevel@tonic-gate 
7117c478bd9Sstevel@tonic-gate 	id = get_int();
7127c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
7137c478bd9Sstevel@tonic-gate 	a = get_attrib();
71490685d2cSjp161948 	debug("request %u: setstat name \"%s\"", id, name);
7157c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
71690685d2cSjp161948 		log("set \"%s\" size %llu",
71790685d2cSjp161948 		    name, (unsigned long long)a->size);
7187c478bd9Sstevel@tonic-gate 		ret = truncate(name, a->size);
7197c478bd9Sstevel@tonic-gate 		if (ret == -1)
7207c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7217c478bd9Sstevel@tonic-gate 	}
7227c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
72390685d2cSjp161948 		log("set \"%s\" mode %04o", name, a->perm);
7249c68f910SVladimir Kotal 		ret = chmod(name, a->perm & 07777);
7257c478bd9Sstevel@tonic-gate 		if (ret == -1)
7267c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7277c478bd9Sstevel@tonic-gate 	}
7287c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
72990685d2cSjp161948 		char buf[64];
73090685d2cSjp161948 		time_t t = a->mtime;
73190685d2cSjp161948 
73290685d2cSjp161948 		strftime(buf, sizeof(buf), "%Y" "%m%d-%H:%M:%S",
73390685d2cSjp161948 		    localtime(&t));
73490685d2cSjp161948 		log("set \"%s\" modtime %s", name, buf);
7357c478bd9Sstevel@tonic-gate 		ret = utimes(name, attrib_to_tv(a));
7367c478bd9Sstevel@tonic-gate 		if (ret == -1)
7377c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7387c478bd9Sstevel@tonic-gate 	}
7397c478bd9Sstevel@tonic-gate 	if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
74090685d2cSjp161948 		log("set \"%s\" owner %lu group %lu", name,
74190685d2cSjp161948 		    (u_long)a->uid, (u_long)a->gid);
7427c478bd9Sstevel@tonic-gate 		ret = chown(name, a->uid, a->gid);
7437c478bd9Sstevel@tonic-gate 		if (ret == -1)
7447c478bd9Sstevel@tonic-gate 			status = errno_to_portable(errno);
7457c478bd9Sstevel@tonic-gate 	}
7467c478bd9Sstevel@tonic-gate 	send_status(id, status);
7477c478bd9Sstevel@tonic-gate 	xfree(name);
7487c478bd9Sstevel@tonic-gate }
7497c478bd9Sstevel@tonic-gate 
7507c478bd9Sstevel@tonic-gate static void
process_fsetstat(void)7517c478bd9Sstevel@tonic-gate process_fsetstat(void)
7527c478bd9Sstevel@tonic-gate {
7537c478bd9Sstevel@tonic-gate 	Attrib *a;
7547c478bd9Sstevel@tonic-gate 	u_int32_t id;
7557c478bd9Sstevel@tonic-gate 	int handle, fd, ret;
7567c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_OK;
7577c478bd9Sstevel@tonic-gate 
7587c478bd9Sstevel@tonic-gate 	id = get_int();
7597c478bd9Sstevel@tonic-gate 	handle = get_handle();
7607c478bd9Sstevel@tonic-gate 	a = get_attrib();
76190685d2cSjp161948 	debug("request %u: fsetstat handle %d", id, handle);
7627c478bd9Sstevel@tonic-gate 	fd = handle_to_fd(handle);
76390685d2cSjp161948 	if (fd < 0) {
7647c478bd9Sstevel@tonic-gate 		status = SSH2_FX_FAILURE;
7657c478bd9Sstevel@tonic-gate 	} else {
76690685d2cSjp161948 		char *name = handle_to_name(handle);
76790685d2cSjp161948 
7687c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
76990685d2cSjp161948 			log("set \"%s\" size %llu",
77090685d2cSjp161948 			    name, (unsigned long long)a->size);
7717c478bd9Sstevel@tonic-gate 			ret = ftruncate(fd, a->size);
7727c478bd9Sstevel@tonic-gate 			if (ret == -1)
7737c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
7747c478bd9Sstevel@tonic-gate 		}
7757c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
77690685d2cSjp161948 			log("set \"%s\" mode %04o", name, a->perm);
7777c478bd9Sstevel@tonic-gate #ifdef HAVE_FCHMOD
7789c68f910SVladimir Kotal 			ret = fchmod(fd, a->perm & 07777);
7797c478bd9Sstevel@tonic-gate #else
7809c68f910SVladimir Kotal 			ret = chmod(name, a->perm & 07777);
7817c478bd9Sstevel@tonic-gate #endif
7827c478bd9Sstevel@tonic-gate 			if (ret == -1)
7837c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
7847c478bd9Sstevel@tonic-gate 		}
7857c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
78690685d2cSjp161948 			char buf[64];
78790685d2cSjp161948 			time_t t = a->mtime;
78890685d2cSjp161948 
78990685d2cSjp161948 			strftime(buf, sizeof(buf), "%Y" "%m%d-%H:%M:%S",
79090685d2cSjp161948 			    localtime(&t));
79190685d2cSjp161948 			log("set \"%s\" modtime %s", name, buf);
7927c478bd9Sstevel@tonic-gate #ifdef HAVE_FUTIMES
7937c478bd9Sstevel@tonic-gate 			ret = futimes(fd, attrib_to_tv(a));
7947c478bd9Sstevel@tonic-gate #else
7957c478bd9Sstevel@tonic-gate 			ret = utimes(name, attrib_to_tv(a));
7967c478bd9Sstevel@tonic-gate #endif
7977c478bd9Sstevel@tonic-gate 			if (ret == -1)
7987c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
7997c478bd9Sstevel@tonic-gate 		}
8007c478bd9Sstevel@tonic-gate 		if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
80190685d2cSjp161948 			log("set \"%s\" owner %lu group %lu", name,
80290685d2cSjp161948 			    (u_long)a->uid, (u_long)a->gid);
8037c478bd9Sstevel@tonic-gate #ifdef HAVE_FCHOWN
8047c478bd9Sstevel@tonic-gate 			ret = fchown(fd, a->uid, a->gid);
8057c478bd9Sstevel@tonic-gate #else
8067c478bd9Sstevel@tonic-gate 			ret = chown(name, a->uid, a->gid);
8077c478bd9Sstevel@tonic-gate #endif
8087c478bd9Sstevel@tonic-gate 			if (ret == -1)
8097c478bd9Sstevel@tonic-gate 				status = errno_to_portable(errno);
8107c478bd9Sstevel@tonic-gate 		}
8117c478bd9Sstevel@tonic-gate 	}
8127c478bd9Sstevel@tonic-gate 	send_status(id, status);
8137c478bd9Sstevel@tonic-gate }
8147c478bd9Sstevel@tonic-gate 
8157c478bd9Sstevel@tonic-gate static void
process_opendir(void)8167c478bd9Sstevel@tonic-gate process_opendir(void)
8177c478bd9Sstevel@tonic-gate {
8187c478bd9Sstevel@tonic-gate 	DIR *dirp = NULL;
8197c478bd9Sstevel@tonic-gate 	char *path;
8207c478bd9Sstevel@tonic-gate 	int handle, status = SSH2_FX_FAILURE;
8217c478bd9Sstevel@tonic-gate 	u_int32_t id;
8227c478bd9Sstevel@tonic-gate 
8237c478bd9Sstevel@tonic-gate 	id = get_int();
8247c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
82590685d2cSjp161948 	debug3("request %u: opendir", id);
82690685d2cSjp161948 	log("opendir \"%s\"", path);
8277c478bd9Sstevel@tonic-gate 	dirp = opendir(path);
8287c478bd9Sstevel@tonic-gate 	if (dirp == NULL) {
8297c478bd9Sstevel@tonic-gate 		status = errno_to_portable(errno);
8307c478bd9Sstevel@tonic-gate 	} else {
83190685d2cSjp161948 		handle = handle_new(HANDLE_DIR, path, 0, dirp);
8327c478bd9Sstevel@tonic-gate 		if (handle < 0) {
8337c478bd9Sstevel@tonic-gate 			closedir(dirp);
8347c478bd9Sstevel@tonic-gate 		} else {
8357c478bd9Sstevel@tonic-gate 			send_handle(id, handle);
8367c478bd9Sstevel@tonic-gate 			status = SSH2_FX_OK;
8377c478bd9Sstevel@tonic-gate 		}
8387c478bd9Sstevel@tonic-gate 
8397c478bd9Sstevel@tonic-gate 	}
8407c478bd9Sstevel@tonic-gate 	if (status != SSH2_FX_OK)
8417c478bd9Sstevel@tonic-gate 		send_status(id, status);
8427c478bd9Sstevel@tonic-gate 	xfree(path);
8437c478bd9Sstevel@tonic-gate }
8447c478bd9Sstevel@tonic-gate 
8457c478bd9Sstevel@tonic-gate static void
process_readdir(void)8467c478bd9Sstevel@tonic-gate process_readdir(void)
8477c478bd9Sstevel@tonic-gate {
8487c478bd9Sstevel@tonic-gate 	DIR *dirp;
8497c478bd9Sstevel@tonic-gate 	struct dirent *dp;
8507c478bd9Sstevel@tonic-gate 	char *path;
8517c478bd9Sstevel@tonic-gate 	int handle;
8527c478bd9Sstevel@tonic-gate 	u_int32_t id;
8537c478bd9Sstevel@tonic-gate 
8547c478bd9Sstevel@tonic-gate 	id = get_int();
8557c478bd9Sstevel@tonic-gate 	handle = get_handle();
85690685d2cSjp161948 	debug("request %u: readdir \"%s\" (handle %d)", id,
85790685d2cSjp161948 	    handle_to_name(handle), handle);
8587c478bd9Sstevel@tonic-gate 	dirp = handle_to_dir(handle);
8597c478bd9Sstevel@tonic-gate 	path = handle_to_name(handle);
8607c478bd9Sstevel@tonic-gate 	if (dirp == NULL || path == NULL) {
8617c478bd9Sstevel@tonic-gate 		send_status(id, SSH2_FX_FAILURE);
8627c478bd9Sstevel@tonic-gate 	} else {
8637c478bd9Sstevel@tonic-gate 		struct stat st;
86490685d2cSjp161948 		char pathname[MAXPATHLEN];
8657c478bd9Sstevel@tonic-gate 		Stat *stats;
8667c478bd9Sstevel@tonic-gate 		int nstats = 10, count = 0, i;
8677c478bd9Sstevel@tonic-gate 
86890685d2cSjp161948 		stats = xcalloc(nstats, sizeof(Stat));
8697c478bd9Sstevel@tonic-gate 		while ((dp = readdir(dirp)) != NULL) {
8707c478bd9Sstevel@tonic-gate 			if (count >= nstats) {
8717c478bd9Sstevel@tonic-gate 				nstats *= 2;
8727c478bd9Sstevel@tonic-gate 				stats = xrealloc(stats, nstats * sizeof(Stat));
8737c478bd9Sstevel@tonic-gate 			}
8747c478bd9Sstevel@tonic-gate /* XXX OVERFLOW ? */
8757c478bd9Sstevel@tonic-gate 			snprintf(pathname, sizeof pathname, "%s%s%s", path,
8767c478bd9Sstevel@tonic-gate 			    strcmp(path, "/") ? "/" : "", dp->d_name);
8777c478bd9Sstevel@tonic-gate 			if (lstat(pathname, &st) < 0)
8787c478bd9Sstevel@tonic-gate 				continue;
8797c478bd9Sstevel@tonic-gate 			stat_to_attrib(&st, &(stats[count].attrib));
8807c478bd9Sstevel@tonic-gate 			stats[count].name = xstrdup(dp->d_name);
8817c478bd9Sstevel@tonic-gate 			stats[count].long_name = ls_file(dp->d_name, &st, 0);
8827c478bd9Sstevel@tonic-gate 			count++;
8837c478bd9Sstevel@tonic-gate 			/* send up to 100 entries in one message */
8847c478bd9Sstevel@tonic-gate 			/* XXX check packet size instead */
8857c478bd9Sstevel@tonic-gate 			if (count == 100)
8867c478bd9Sstevel@tonic-gate 				break;
8877c478bd9Sstevel@tonic-gate 		}
8887c478bd9Sstevel@tonic-gate 		if (count > 0) {
8897c478bd9Sstevel@tonic-gate 			send_names(id, count, stats);
8907c478bd9Sstevel@tonic-gate 			for (i = 0; i < count; i++) {
8917c478bd9Sstevel@tonic-gate 				xfree(stats[i].name);
8927c478bd9Sstevel@tonic-gate 				xfree(stats[i].long_name);
8937c478bd9Sstevel@tonic-gate 			}
8947c478bd9Sstevel@tonic-gate 		} else {
8957c478bd9Sstevel@tonic-gate 			send_status(id, SSH2_FX_EOF);
8967c478bd9Sstevel@tonic-gate 		}
8977c478bd9Sstevel@tonic-gate 		xfree(stats);
8987c478bd9Sstevel@tonic-gate 	}
8997c478bd9Sstevel@tonic-gate }
9007c478bd9Sstevel@tonic-gate 
9017c478bd9Sstevel@tonic-gate static void
process_remove(void)9027c478bd9Sstevel@tonic-gate process_remove(void)
9037c478bd9Sstevel@tonic-gate {
9047c478bd9Sstevel@tonic-gate 	char *name;
9057c478bd9Sstevel@tonic-gate 	u_int32_t id;
9067c478bd9Sstevel@tonic-gate 	int status = SSH2_FX_FAILURE;
9077c478bd9Sstevel@tonic-gate 	int ret;
9087c478bd9Sstevel@tonic-gate 
9097c478bd9Sstevel@tonic-gate 	id = get_int();
9107c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
91190685d2cSjp161948 	debug3("request %u: remove", id);
91290685d2cSjp161948 	log("remove name \"%s\"", name);
9137c478bd9Sstevel@tonic-gate 	ret = unlink(name);
9147c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9157c478bd9Sstevel@tonic-gate 	send_status(id, status);
9167c478bd9Sstevel@tonic-gate 	xfree(name);
9177c478bd9Sstevel@tonic-gate }
9187c478bd9Sstevel@tonic-gate 
9197c478bd9Sstevel@tonic-gate static void
process_mkdir(void)9207c478bd9Sstevel@tonic-gate process_mkdir(void)
9217c478bd9Sstevel@tonic-gate {
9227c478bd9Sstevel@tonic-gate 	Attrib *a;
9237c478bd9Sstevel@tonic-gate 	u_int32_t id;
9247c478bd9Sstevel@tonic-gate 	char *name;
9257c478bd9Sstevel@tonic-gate 	int ret, mode, status = SSH2_FX_FAILURE;
9267c478bd9Sstevel@tonic-gate 
9277c478bd9Sstevel@tonic-gate 	id = get_int();
9287c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
9297c478bd9Sstevel@tonic-gate 	a = get_attrib();
9307c478bd9Sstevel@tonic-gate 	mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
9317c478bd9Sstevel@tonic-gate 	    a->perm & 0777 : 0777;
93290685d2cSjp161948 	debug3("request %u: mkdir", id);
93390685d2cSjp161948 	log("mkdir name \"%s\" mode 0%o", name, mode);
9347c478bd9Sstevel@tonic-gate 	ret = mkdir(name, mode);
9357c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9367c478bd9Sstevel@tonic-gate 	send_status(id, status);
9377c478bd9Sstevel@tonic-gate 	xfree(name);
9387c478bd9Sstevel@tonic-gate }
9397c478bd9Sstevel@tonic-gate 
9407c478bd9Sstevel@tonic-gate static void
process_rmdir(void)9417c478bd9Sstevel@tonic-gate process_rmdir(void)
9427c478bd9Sstevel@tonic-gate {
9437c478bd9Sstevel@tonic-gate 	u_int32_t id;
9447c478bd9Sstevel@tonic-gate 	char *name;
9457c478bd9Sstevel@tonic-gate 	int ret, status;
9467c478bd9Sstevel@tonic-gate 
9477c478bd9Sstevel@tonic-gate 	id = get_int();
9487c478bd9Sstevel@tonic-gate 	name = get_string(NULL);
94990685d2cSjp161948 	debug3("request %u: rmdir", id);
95090685d2cSjp161948 	log("rmdir name \"%s\"", name);
9517c478bd9Sstevel@tonic-gate 	ret = rmdir(name);
9527c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
9537c478bd9Sstevel@tonic-gate 	send_status(id, status);
9547c478bd9Sstevel@tonic-gate 	xfree(name);
9557c478bd9Sstevel@tonic-gate }
9567c478bd9Sstevel@tonic-gate 
9577c478bd9Sstevel@tonic-gate static void
process_realpath(void)9587c478bd9Sstevel@tonic-gate process_realpath(void)
9597c478bd9Sstevel@tonic-gate {
9607c478bd9Sstevel@tonic-gate 	char resolvedname[MAXPATHLEN];
9617c478bd9Sstevel@tonic-gate 	u_int32_t id;
9627c478bd9Sstevel@tonic-gate 	char *path;
9637c478bd9Sstevel@tonic-gate 
9647c478bd9Sstevel@tonic-gate 	id = get_int();
9657c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
9667c478bd9Sstevel@tonic-gate 	if (path[0] == '\0') {
9677c478bd9Sstevel@tonic-gate 		xfree(path);
9687c478bd9Sstevel@tonic-gate 		path = xstrdup(".");
9697c478bd9Sstevel@tonic-gate 	}
97090685d2cSjp161948 	debug3("request %u: realpath", id);
97190685d2cSjp161948 	verbose("realpath \"%s\"", path);
9727c478bd9Sstevel@tonic-gate 	if (realpath(path, resolvedname) == NULL) {
9737c478bd9Sstevel@tonic-gate 		send_status(id, errno_to_portable(errno));
9747c478bd9Sstevel@tonic-gate 	} else {
9757c478bd9Sstevel@tonic-gate 		Stat s;
9767c478bd9Sstevel@tonic-gate 		attrib_clear(&s.attrib);
9777c478bd9Sstevel@tonic-gate 		s.name = s.long_name = resolvedname;
9787c478bd9Sstevel@tonic-gate 		send_names(id, 1, &s);
9797c478bd9Sstevel@tonic-gate 	}
9807c478bd9Sstevel@tonic-gate 	xfree(path);
9817c478bd9Sstevel@tonic-gate }
9827c478bd9Sstevel@tonic-gate 
9837c478bd9Sstevel@tonic-gate static void
process_rename(void)9847c478bd9Sstevel@tonic-gate process_rename(void)
9857c478bd9Sstevel@tonic-gate {
9867c478bd9Sstevel@tonic-gate 	u_int32_t id;
9877c478bd9Sstevel@tonic-gate 	char *oldpath, *newpath;
98890685d2cSjp161948 	int status;
98990685d2cSjp161948 	struct stat sb;
9907c478bd9Sstevel@tonic-gate 
9917c478bd9Sstevel@tonic-gate 	id = get_int();
9927c478bd9Sstevel@tonic-gate 	oldpath = get_string(NULL);
9937c478bd9Sstevel@tonic-gate 	newpath = get_string(NULL);
99490685d2cSjp161948 	debug3("request %u: rename", id);
99590685d2cSjp161948 	log("rename old \"%s\" new \"%s\"", oldpath, newpath);
99690685d2cSjp161948 	status = SSH2_FX_FAILURE;
99790685d2cSjp161948 	if (lstat(oldpath, &sb) == -1)
99890685d2cSjp161948 		status = errno_to_portable(errno);
99990685d2cSjp161948 	else if (S_ISREG(sb.st_mode)) {
100090685d2cSjp161948 		/* Race-free rename of regular files */
100190685d2cSjp161948 		if (link(oldpath, newpath) == -1) {
100290685d2cSjp161948 			if (errno == EOPNOTSUPP
100390685d2cSjp161948 #ifdef LINK_OPNOTSUPP_ERRNO
100490685d2cSjp161948 			    || errno == LINK_OPNOTSUPP_ERRNO
100590685d2cSjp161948 #endif
100690685d2cSjp161948 			    ) {
100790685d2cSjp161948 				struct stat st;
100890685d2cSjp161948 
100990685d2cSjp161948 				/*
101090685d2cSjp161948 				 * fs doesn't support links, so fall back to
101190685d2cSjp161948 				 * stat+rename.  This is racy.
101290685d2cSjp161948 				 */
10137c478bd9Sstevel@tonic-gate 				if (stat(newpath, &st) == -1) {
101490685d2cSjp161948 					if (rename(oldpath, newpath) == -1)
101590685d2cSjp161948 						status =
101690685d2cSjp161948 						    errno_to_portable(errno);
101790685d2cSjp161948 					else
101890685d2cSjp161948 						status = SSH2_FX_OK;
101990685d2cSjp161948 				}
102090685d2cSjp161948 			} else {
102190685d2cSjp161948 				status = errno_to_portable(errno);
102290685d2cSjp161948 			}
102390685d2cSjp161948 		} else if (unlink(oldpath) == -1) {
102490685d2cSjp161948 			status = errno_to_portable(errno);
102590685d2cSjp161948 			/* clean spare link */
102690685d2cSjp161948 			unlink(newpath);
102790685d2cSjp161948 		} else
102890685d2cSjp161948 			status = SSH2_FX_OK;
102990685d2cSjp161948 	} else if (stat(newpath, &sb) == -1) {
103090685d2cSjp161948 		if (rename(oldpath, newpath) == -1)
103190685d2cSjp161948 			status = errno_to_portable(errno);
103290685d2cSjp161948 		else
103390685d2cSjp161948 			status = SSH2_FX_OK;
10347c478bd9Sstevel@tonic-gate 	}
10357c478bd9Sstevel@tonic-gate 	send_status(id, status);
10367c478bd9Sstevel@tonic-gate 	xfree(oldpath);
10377c478bd9Sstevel@tonic-gate 	xfree(newpath);
10387c478bd9Sstevel@tonic-gate }
10397c478bd9Sstevel@tonic-gate 
10407c478bd9Sstevel@tonic-gate static void
process_readlink(void)10417c478bd9Sstevel@tonic-gate process_readlink(void)
10427c478bd9Sstevel@tonic-gate {
10437c478bd9Sstevel@tonic-gate 	u_int32_t id;
10447c478bd9Sstevel@tonic-gate 	int len;
104590685d2cSjp161948 	char buf[MAXPATHLEN];
10467c478bd9Sstevel@tonic-gate 	char *path;
10477c478bd9Sstevel@tonic-gate 
10487c478bd9Sstevel@tonic-gate 	id = get_int();
10497c478bd9Sstevel@tonic-gate 	path = get_string(NULL);
105090685d2cSjp161948 	debug3("request %u: readlink", id);
105190685d2cSjp161948 	verbose("readlink \"%s\"", path);
105290685d2cSjp161948 	if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
10537c478bd9Sstevel@tonic-gate 		send_status(id, errno_to_portable(errno));
10547c478bd9Sstevel@tonic-gate 	else {
10557c478bd9Sstevel@tonic-gate 		Stat s;
10567c478bd9Sstevel@tonic-gate 
105790685d2cSjp161948 		buf[len] = '\0';
10587c478bd9Sstevel@tonic-gate 		attrib_clear(&s.attrib);
105990685d2cSjp161948 		s.name = s.long_name = buf;
10607c478bd9Sstevel@tonic-gate 		send_names(id, 1, &s);
10617c478bd9Sstevel@tonic-gate 	}
10627c478bd9Sstevel@tonic-gate 	xfree(path);
10637c478bd9Sstevel@tonic-gate }
10647c478bd9Sstevel@tonic-gate 
10657c478bd9Sstevel@tonic-gate static void
process_symlink(void)10667c478bd9Sstevel@tonic-gate process_symlink(void)
10677c478bd9Sstevel@tonic-gate {
10687c478bd9Sstevel@tonic-gate 	u_int32_t id;
10697c478bd9Sstevel@tonic-gate 	char *oldpath, *newpath;
107090685d2cSjp161948 	int ret, status;
10717c478bd9Sstevel@tonic-gate 
10727c478bd9Sstevel@tonic-gate 	id = get_int();
10737c478bd9Sstevel@tonic-gate 	oldpath = get_string(NULL);
10747c478bd9Sstevel@tonic-gate 	newpath = get_string(NULL);
107590685d2cSjp161948 	debug3("request %u: symlink", id);
107690685d2cSjp161948 	log("symlink old \"%s\" new \"%s\"", oldpath, newpath);
107790685d2cSjp161948 	/* this will fail if 'newpath' exists */
10787c478bd9Sstevel@tonic-gate 	ret = symlink(oldpath, newpath);
10797c478bd9Sstevel@tonic-gate 	status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
10807c478bd9Sstevel@tonic-gate 	send_status(id, status);
10817c478bd9Sstevel@tonic-gate 	xfree(oldpath);
10827c478bd9Sstevel@tonic-gate 	xfree(newpath);
10837c478bd9Sstevel@tonic-gate }
10847c478bd9Sstevel@tonic-gate 
10857c478bd9Sstevel@tonic-gate static void
process_extended(void)10867c478bd9Sstevel@tonic-gate process_extended(void)
10877c478bd9Sstevel@tonic-gate {
10887c478bd9Sstevel@tonic-gate 	u_int32_t id;
10897c478bd9Sstevel@tonic-gate 	char *request;
10907c478bd9Sstevel@tonic-gate 
10917c478bd9Sstevel@tonic-gate 	id = get_int();
10927c478bd9Sstevel@tonic-gate 	request = get_string(NULL);
10937c478bd9Sstevel@tonic-gate 	send_status(id, SSH2_FX_OP_UNSUPPORTED);		/* MUST */
10947c478bd9Sstevel@tonic-gate 	xfree(request);
10957c478bd9Sstevel@tonic-gate }
10967c478bd9Sstevel@tonic-gate 
10977c478bd9Sstevel@tonic-gate /* stolen from ssh-agent */
10987c478bd9Sstevel@tonic-gate 
10997c478bd9Sstevel@tonic-gate static void
process(void)11007c478bd9Sstevel@tonic-gate process(void)
11017c478bd9Sstevel@tonic-gate {
11027c478bd9Sstevel@tonic-gate 	u_int msg_len;
11037c478bd9Sstevel@tonic-gate 	u_int buf_len;
11047c478bd9Sstevel@tonic-gate 	u_int consumed;
11057c478bd9Sstevel@tonic-gate 	u_int type;
11067c478bd9Sstevel@tonic-gate 	u_char *cp;
11077c478bd9Sstevel@tonic-gate 
11087c478bd9Sstevel@tonic-gate 	buf_len = buffer_len(&iqueue);
11097c478bd9Sstevel@tonic-gate 	if (buf_len < 5)
11107c478bd9Sstevel@tonic-gate 		return;		/* Incomplete message. */
11117c478bd9Sstevel@tonic-gate 	cp = buffer_ptr(&iqueue);
111290685d2cSjp161948 	msg_len = get_u32(cp);
111390685d2cSjp161948 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
111490685d2cSjp161948 		error("bad message from %s local user %s",
111590685d2cSjp161948 		    client_addr, pw->pw_name);
111690685d2cSjp161948 		cleanup_exit(11);
11177c478bd9Sstevel@tonic-gate 	}
11187c478bd9Sstevel@tonic-gate 	if (buf_len < msg_len + 4)
11197c478bd9Sstevel@tonic-gate 		return;
11207c478bd9Sstevel@tonic-gate 	buffer_consume(&iqueue, 4);
11217c478bd9Sstevel@tonic-gate 	buf_len -= 4;
11227c478bd9Sstevel@tonic-gate 	type = buffer_get_char(&iqueue);
11237c478bd9Sstevel@tonic-gate 	switch (type) {
11247c478bd9Sstevel@tonic-gate 	case SSH2_FXP_INIT:
11257c478bd9Sstevel@tonic-gate 		process_init();
11267c478bd9Sstevel@tonic-gate 		break;
11277c478bd9Sstevel@tonic-gate 	case SSH2_FXP_OPEN:
11287c478bd9Sstevel@tonic-gate 		process_open();
11297c478bd9Sstevel@tonic-gate 		break;
11307c478bd9Sstevel@tonic-gate 	case SSH2_FXP_CLOSE:
11317c478bd9Sstevel@tonic-gate 		process_close();
11327c478bd9Sstevel@tonic-gate 		break;
11337c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READ:
11347c478bd9Sstevel@tonic-gate 		process_read();
11357c478bd9Sstevel@tonic-gate 		break;
11367c478bd9Sstevel@tonic-gate 	case SSH2_FXP_WRITE:
11377c478bd9Sstevel@tonic-gate 		process_write();
11387c478bd9Sstevel@tonic-gate 		break;
11397c478bd9Sstevel@tonic-gate 	case SSH2_FXP_LSTAT:
11407c478bd9Sstevel@tonic-gate 		process_lstat();
11417c478bd9Sstevel@tonic-gate 		break;
11427c478bd9Sstevel@tonic-gate 	case SSH2_FXP_FSTAT:
11437c478bd9Sstevel@tonic-gate 		process_fstat();
11447c478bd9Sstevel@tonic-gate 		break;
11457c478bd9Sstevel@tonic-gate 	case SSH2_FXP_SETSTAT:
11467c478bd9Sstevel@tonic-gate 		process_setstat();
11477c478bd9Sstevel@tonic-gate 		break;
11487c478bd9Sstevel@tonic-gate 	case SSH2_FXP_FSETSTAT:
11497c478bd9Sstevel@tonic-gate 		process_fsetstat();
11507c478bd9Sstevel@tonic-gate 		break;
11517c478bd9Sstevel@tonic-gate 	case SSH2_FXP_OPENDIR:
11527c478bd9Sstevel@tonic-gate 		process_opendir();
11537c478bd9Sstevel@tonic-gate 		break;
11547c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READDIR:
11557c478bd9Sstevel@tonic-gate 		process_readdir();
11567c478bd9Sstevel@tonic-gate 		break;
11577c478bd9Sstevel@tonic-gate 	case SSH2_FXP_REMOVE:
11587c478bd9Sstevel@tonic-gate 		process_remove();
11597c478bd9Sstevel@tonic-gate 		break;
11607c478bd9Sstevel@tonic-gate 	case SSH2_FXP_MKDIR:
11617c478bd9Sstevel@tonic-gate 		process_mkdir();
11627c478bd9Sstevel@tonic-gate 		break;
11637c478bd9Sstevel@tonic-gate 	case SSH2_FXP_RMDIR:
11647c478bd9Sstevel@tonic-gate 		process_rmdir();
11657c478bd9Sstevel@tonic-gate 		break;
11667c478bd9Sstevel@tonic-gate 	case SSH2_FXP_REALPATH:
11677c478bd9Sstevel@tonic-gate 		process_realpath();
11687c478bd9Sstevel@tonic-gate 		break;
11697c478bd9Sstevel@tonic-gate 	case SSH2_FXP_STAT:
11707c478bd9Sstevel@tonic-gate 		process_stat();
11717c478bd9Sstevel@tonic-gate 		break;
11727c478bd9Sstevel@tonic-gate 	case SSH2_FXP_RENAME:
11737c478bd9Sstevel@tonic-gate 		process_rename();
11747c478bd9Sstevel@tonic-gate 		break;
11757c478bd9Sstevel@tonic-gate 	case SSH2_FXP_READLINK:
11767c478bd9Sstevel@tonic-gate 		process_readlink();
11777c478bd9Sstevel@tonic-gate 		break;
11787c478bd9Sstevel@tonic-gate 	case SSH2_FXP_SYMLINK:
11797c478bd9Sstevel@tonic-gate 		process_symlink();
11807c478bd9Sstevel@tonic-gate 		break;
11817c478bd9Sstevel@tonic-gate 	case SSH2_FXP_EXTENDED:
11827c478bd9Sstevel@tonic-gate 		process_extended();
11837c478bd9Sstevel@tonic-gate 		break;
11847c478bd9Sstevel@tonic-gate 	default:
11857c478bd9Sstevel@tonic-gate 		error("Unknown message %d", type);
11867c478bd9Sstevel@tonic-gate 		break;
11877c478bd9Sstevel@tonic-gate 	}
11887c478bd9Sstevel@tonic-gate 	/* discard the remaining bytes from the current packet */
11897c478bd9Sstevel@tonic-gate 	if (buf_len < buffer_len(&iqueue))
119090685d2cSjp161948 		fatal("iqueue grew unexpectedly");
11917c478bd9Sstevel@tonic-gate 	consumed = buf_len - buffer_len(&iqueue);
11927c478bd9Sstevel@tonic-gate 	if (msg_len < consumed)
11937c478bd9Sstevel@tonic-gate 		fatal("msg_len %d < consumed %d", msg_len, consumed);
11947c478bd9Sstevel@tonic-gate 	if (msg_len > consumed)
11957c478bd9Sstevel@tonic-gate 		buffer_consume(&iqueue, msg_len - consumed);
11967c478bd9Sstevel@tonic-gate }
11977c478bd9Sstevel@tonic-gate 
1198*6f8d59d8SJan Pechanec /*
1199*6f8d59d8SJan Pechanec  * Cleanup handler that logs active handles upon normal exit. Not static since
1200*6f8d59d8SJan Pechanec  * sftp-server-main.c file needs that as well.
1201*6f8d59d8SJan Pechanec  */
1202*6f8d59d8SJan Pechanec void
cleanup_exit(int i)120390685d2cSjp161948 cleanup_exit(int i)
120490685d2cSjp161948 {
120590685d2cSjp161948 	if (pw != NULL && client_addr != NULL) {
120690685d2cSjp161948 		handle_log_exit();
120790685d2cSjp161948 		log("session closed for local user %s from [%s]",
120890685d2cSjp161948 		    pw->pw_name, client_addr);
120990685d2cSjp161948 	}
121090685d2cSjp161948 	_exit(i);
121190685d2cSjp161948 }
121290685d2cSjp161948 
121390685d2cSjp161948 static void
usage(void)121490685d2cSjp161948 usage(void)
121590685d2cSjp161948 {
121690685d2cSjp161948 	fprintf(stderr,
121790685d2cSjp161948 	    "Usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname);
121890685d2cSjp161948 	exit(1);
121990685d2cSjp161948 }
122090685d2cSjp161948 
12217c478bd9Sstevel@tonic-gate int
sftp_server_main(int argc,char ** argv,struct passwd * user_pw)1222*6f8d59d8SJan Pechanec sftp_server_main(int argc, char **argv, struct passwd *user_pw)
12237c478bd9Sstevel@tonic-gate {
12247c478bd9Sstevel@tonic-gate 	fd_set *rset, *wset;
122590685d2cSjp161948 	int in, out, max, ch, skipargs = 0, log_stderr = 0;
12267c478bd9Sstevel@tonic-gate 	ssize_t len, olen, set_size;
122790685d2cSjp161948 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
122890685d2cSjp161948 	char *cp, buf[4*4096];
12297c478bd9Sstevel@tonic-gate 
123090685d2cSjp161948 	extern char *optarg;
12317c478bd9Sstevel@tonic-gate 
123290685d2cSjp161948 	__progname = get_progname(argv[0]);
12337c478bd9Sstevel@tonic-gate 
12347c478bd9Sstevel@tonic-gate 	(void) g11n_setlocale(LC_ALL, "");
12357c478bd9Sstevel@tonic-gate 
123690685d2cSjp161948 	log_init(__progname, log_level, log_facility, log_stderr);
12377c478bd9Sstevel@tonic-gate 
123890685d2cSjp161948 	while (!skipargs && (ch = getopt(argc, argv, "C:f:l:che")) != -1) {
123990685d2cSjp161948 		switch (ch) {
124090685d2cSjp161948 		case 'c':
124190685d2cSjp161948 			/*
124290685d2cSjp161948 			 * Ignore all arguments if we are invoked as a
124390685d2cSjp161948 			 * shell using "sftp-server -c command"
124490685d2cSjp161948 			 */
124590685d2cSjp161948 			skipargs = 1;
124690685d2cSjp161948 			break;
124790685d2cSjp161948 		case 'e':
124890685d2cSjp161948 			log_stderr = 1;
124990685d2cSjp161948 			break;
125090685d2cSjp161948 		case 'l':
125190685d2cSjp161948 			log_level = log_level_number(optarg);
125290685d2cSjp161948 			if (log_level == SYSLOG_LEVEL_NOT_SET)
125390685d2cSjp161948 				error("Invalid log level \"%s\"", optarg);
125490685d2cSjp161948 			break;
125590685d2cSjp161948 		case 'f':
125690685d2cSjp161948 			log_facility = log_facility_number(optarg);
125790685d2cSjp161948 			if (log_facility == SYSLOG_FACILITY_NOT_SET)
125890685d2cSjp161948 				error("Invalid log facility \"%s\"", optarg);
125990685d2cSjp161948 			break;
126090685d2cSjp161948 		case 'h':
126190685d2cSjp161948 		default:
126290685d2cSjp161948 			usage();
126390685d2cSjp161948 		}
126490685d2cSjp161948 	}
126590685d2cSjp161948 
126690685d2cSjp161948 	log_init(__progname, log_level, log_facility, log_stderr);
126790685d2cSjp161948 
126890685d2cSjp161948 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
126990685d2cSjp161948 		client_addr = xstrdup(cp);
127090685d2cSjp161948 		if ((cp = strchr(client_addr, ' ')) == NULL)
127190685d2cSjp161948 			fatal("Malformed SSH_CONNECTION variable: \"%s\"",
127290685d2cSjp161948 			    getenv("SSH_CONNECTION"));
127390685d2cSjp161948 		*cp = '\0';
127490685d2cSjp161948 	} else
127590685d2cSjp161948 		client_addr = xstrdup("UNKNOWN");
127690685d2cSjp161948 
1277*6f8d59d8SJan Pechanec 	pw = pwcopy(user_pw);
127890685d2cSjp161948 
127990685d2cSjp161948 	log("session opened for local user %s from [%s]",
128090685d2cSjp161948 	    pw->pw_name, client_addr);
128190685d2cSjp161948 
128290685d2cSjp161948 	handle_init();
12837c478bd9Sstevel@tonic-gate 
12847c478bd9Sstevel@tonic-gate 	in = dup(STDIN_FILENO);
12857c478bd9Sstevel@tonic-gate 	out = dup(STDOUT_FILENO);
12867c478bd9Sstevel@tonic-gate 
12877c478bd9Sstevel@tonic-gate #ifdef HAVE_CYGWIN
12887c478bd9Sstevel@tonic-gate 	setmode(in, O_BINARY);
12897c478bd9Sstevel@tonic-gate 	setmode(out, O_BINARY);
12907c478bd9Sstevel@tonic-gate #endif
12917c478bd9Sstevel@tonic-gate 
12927c478bd9Sstevel@tonic-gate 	max = 0;
12937c478bd9Sstevel@tonic-gate 	if (in > max)
12947c478bd9Sstevel@tonic-gate 		max = in;
12957c478bd9Sstevel@tonic-gate 	if (out > max)
12967c478bd9Sstevel@tonic-gate 		max = out;
12977c478bd9Sstevel@tonic-gate 
12987c478bd9Sstevel@tonic-gate 	buffer_init(&iqueue);
12997c478bd9Sstevel@tonic-gate 	buffer_init(&oqueue);
13007c478bd9Sstevel@tonic-gate 
13017c478bd9Sstevel@tonic-gate 	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
13027c478bd9Sstevel@tonic-gate 	rset = (fd_set *)xmalloc(set_size);
13037c478bd9Sstevel@tonic-gate 	wset = (fd_set *)xmalloc(set_size);
13047c478bd9Sstevel@tonic-gate 
13057c478bd9Sstevel@tonic-gate 	for (;;) {
13067c478bd9Sstevel@tonic-gate 		memset(rset, 0, set_size);
13077c478bd9Sstevel@tonic-gate 		memset(wset, 0, set_size);
13087c478bd9Sstevel@tonic-gate 
130990685d2cSjp161948 		/*
131090685d2cSjp161948 		 * Ensure that we can read a full buffer and handle
131190685d2cSjp161948 		 * the worst-case length packet it can generate,
131290685d2cSjp161948 		 * otherwise apply backpressure by stopping reads.
131390685d2cSjp161948 		 */
131490685d2cSjp161948 		if (buffer_check_alloc(&iqueue, sizeof(buf)) &&
131590685d2cSjp161948 		    buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
13167c478bd9Sstevel@tonic-gate 			FD_SET(in, rset);
131790685d2cSjp161948 
13187c478bd9Sstevel@tonic-gate 		olen = buffer_len(&oqueue);
13197c478bd9Sstevel@tonic-gate 		if (olen > 0)
13207c478bd9Sstevel@tonic-gate 			FD_SET(out, wset);
13217c478bd9Sstevel@tonic-gate 
13227c478bd9Sstevel@tonic-gate 		if (select(max+1, rset, wset, NULL, NULL) < 0) {
13237c478bd9Sstevel@tonic-gate 			if (errno == EINTR)
13247c478bd9Sstevel@tonic-gate 				continue;
132590685d2cSjp161948 			error("select: %s", strerror(errno));
132690685d2cSjp161948 			cleanup_exit(2);
13277c478bd9Sstevel@tonic-gate 		}
13287c478bd9Sstevel@tonic-gate 
13297c478bd9Sstevel@tonic-gate 		/* copy stdin to iqueue */
13307c478bd9Sstevel@tonic-gate 		if (FD_ISSET(in, rset)) {
13317c478bd9Sstevel@tonic-gate 			len = read(in, buf, sizeof buf);
13327c478bd9Sstevel@tonic-gate 			if (len == 0) {
13337c478bd9Sstevel@tonic-gate 				debug("read eof");
133490685d2cSjp161948 				cleanup_exit(0);
13357c478bd9Sstevel@tonic-gate 			} else if (len < 0) {
133690685d2cSjp161948 				error("read: %s", strerror(errno));
133790685d2cSjp161948 				cleanup_exit(1);
13387c478bd9Sstevel@tonic-gate 			} else {
13397c478bd9Sstevel@tonic-gate 				buffer_append(&iqueue, buf, len);
13407c478bd9Sstevel@tonic-gate 			}
13417c478bd9Sstevel@tonic-gate 		}
13427c478bd9Sstevel@tonic-gate 		/* send oqueue to stdout */
13437c478bd9Sstevel@tonic-gate 		if (FD_ISSET(out, wset)) {
13447c478bd9Sstevel@tonic-gate 			len = write(out, buffer_ptr(&oqueue), olen);
13457c478bd9Sstevel@tonic-gate 			if (len < 0) {
134690685d2cSjp161948 				error("write: %s", strerror(errno));
134790685d2cSjp161948 				cleanup_exit(1);
13487c478bd9Sstevel@tonic-gate 			} else {
13497c478bd9Sstevel@tonic-gate 				buffer_consume(&oqueue, len);
13507c478bd9Sstevel@tonic-gate 			}
13517c478bd9Sstevel@tonic-gate 		}
135290685d2cSjp161948 
135390685d2cSjp161948 		/*
135490685d2cSjp161948 		 * Process requests from client if we can fit the results
135590685d2cSjp161948 		 * into the output buffer, otherwise stop processing input
135690685d2cSjp161948 		 * and let the output queue drain.
135790685d2cSjp161948 		 */
135890685d2cSjp161948 		if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH))
13597c478bd9Sstevel@tonic-gate 			process();
13607c478bd9Sstevel@tonic-gate 	}
136190685d2cSjp161948 
136290685d2cSjp161948 	/* NOTREACHED */
136390685d2cSjp161948 	return (0);
13647c478bd9Sstevel@tonic-gate }
1365