mirror of
https://github.com/PowerShell/Win32-OpenSSH.git
synced 2025-04-08 19:35:37 +02:00
Fix the reported problem #7. Remote directory set to remote user's home directory like its is done on openssh on all platforms. Users can then cd to "Documents" directory. Actual fix was to expand "." to user's home directory so that pwd showed it correctly rather than just showing a dot.
2218 lines
51 KiB
C
2218 lines
51 KiB
C
/* $OpenBSD: sftp-server.c,v 1.107 2015/08/20 22:32:42 deraadt Exp $ */
|
|
/*
|
|
* Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
/*
|
|
* We support only client side kerberos on Windows.
|
|
*/
|
|
|
|
#ifdef WIN32_FIXME
|
|
#undef GSSAPI
|
|
#undef KRB5
|
|
#endif
|
|
|
|
#include <sys/param.h> /* MIN */
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_MOUNT_H
|
|
#include <sys/mount.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STATVFS_H
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_PRCTL_H
|
|
#include <sys/prctl.h>
|
|
#endif
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "sshbuf.h"
|
|
#include "ssherr.h"
|
|
#include "log.h"
|
|
#include "misc.h"
|
|
#include "match.h"
|
|
#include "uidswap.h"
|
|
|
|
#include "sftp.h"
|
|
#include "sftp-common.h"
|
|
|
|
#ifdef WIN32_FIXME
|
|
|
|
#undef select
|
|
#undef send
|
|
#undef recv
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#define isatty(a) WSHELPisatty(a)
|
|
|
|
#define mkdir(a, b) _mkdir(a)
|
|
|
|
#define open(NAME, FLAGS, MODE) _open(NAME, FLAGS | O_BINARY, MODE)
|
|
|
|
#define close(FD) _close(FD)
|
|
|
|
#define write(FD, BUF, LEN) _write(FD, BUF, LEN)
|
|
|
|
#define read(FD, BUF, LEN) _read(FD, BUF, LEN)
|
|
|
|
#define lstat(PATH, BUF) _stat(PATH, BUF)
|
|
|
|
#define stat(PATH, BUF) _stat(PATH, BUF)
|
|
|
|
/*
|
|
* Function to cut last slash (windows
|
|
* stat requires paths
|
|
* without slash at the end).
|
|
*/
|
|
|
|
#define realpath realpathWin32
|
|
|
|
/*
|
|
* Don't use fstat() function redefined
|
|
* in socket.h ported header. It is wrong
|
|
* in this context.
|
|
*/
|
|
|
|
#ifdef fstat
|
|
#undef fstat
|
|
#endif
|
|
|
|
/*
|
|
* Handle to log file.
|
|
*/
|
|
|
|
extern int logfd;
|
|
extern int sfd_start;
|
|
|
|
#endif /* WIN32_FIXME */
|
|
|
|
/* Our verbosity */
|
|
static LogLevel log_level = SYSLOG_LEVEL_ERROR;
|
|
|
|
/* Our client */
|
|
static struct passwd *pw = NULL;
|
|
static char *client_addr = NULL;
|
|
|
|
/* input and output queue */
|
|
struct sshbuf *iqueue;
|
|
struct sshbuf *oqueue;
|
|
|
|
/* Version of client */
|
|
static u_int version;
|
|
|
|
/* SSH2_FXP_INIT received */
|
|
static int init_done;
|
|
|
|
/* Disable writes */
|
|
static int readonly = 0;
|
|
|
|
/* Requests that are allowed/denied */
|
|
static char *request_whitelist, *request_blacklist;
|
|
|
|
/* portable attributes, etc. */
|
|
typedef struct Stat Stat;
|
|
|
|
struct Stat {
|
|
char *name;
|
|
char *long_name;
|
|
Attrib attrib;
|
|
};
|
|
|
|
/* Packet handlers */
|
|
static void process_open(u_int32_t id);
|
|
static void process_close(u_int32_t id);
|
|
static void process_read(u_int32_t id);
|
|
static void process_write(u_int32_t id);
|
|
static void process_stat(u_int32_t id);
|
|
static void process_lstat(u_int32_t id);
|
|
static void process_fstat(u_int32_t id);
|
|
static void process_setstat(u_int32_t id);
|
|
static void process_fsetstat(u_int32_t id);
|
|
static void process_opendir(u_int32_t id);
|
|
static void process_readdir(u_int32_t id);
|
|
static void process_remove(u_int32_t id);
|
|
static void process_mkdir(u_int32_t id);
|
|
static void process_rmdir(u_int32_t id);
|
|
static void process_realpath(u_int32_t id);
|
|
static void process_rename(u_int32_t id);
|
|
static void process_readlink(u_int32_t id);
|
|
static void process_symlink(u_int32_t id);
|
|
static void process_extended_posix_rename(u_int32_t id);
|
|
static void process_extended_statvfs(u_int32_t id);
|
|
static void process_extended_fstatvfs(u_int32_t id);
|
|
static void process_extended_hardlink(u_int32_t id);
|
|
static void process_extended_fsync(u_int32_t id);
|
|
static void process_extended(u_int32_t id);
|
|
|
|
struct sftp_handler {
|
|
const char *name; /* user-visible name for fine-grained perms */
|
|
const char *ext_name; /* extended request name */
|
|
u_int type; /* packet type, for non extended packets */
|
|
void (*handler)(u_int32_t);
|
|
int does_write; /* if nonzero, banned for readonly mode */
|
|
};
|
|
|
|
struct sftp_handler handlers[] = {
|
|
/* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */
|
|
{ "open", NULL, SSH2_FXP_OPEN, process_open, 0 },
|
|
{ "close", NULL, SSH2_FXP_CLOSE, process_close, 0 },
|
|
{ "read", NULL, SSH2_FXP_READ, process_read, 0 },
|
|
{ "write", NULL, SSH2_FXP_WRITE, process_write, 1 },
|
|
{ "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 },
|
|
{ "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 },
|
|
{ "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 },
|
|
{ "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 },
|
|
{ "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 },
|
|
{ "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 },
|
|
{ "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 },
|
|
{ "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 },
|
|
{ "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 },
|
|
{ "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 },
|
|
{ "stat", NULL, SSH2_FXP_STAT, process_stat, 0 },
|
|
{ "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 },
|
|
{ "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 },
|
|
{ "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 },
|
|
{ NULL, NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
/* SSH2_FXP_EXTENDED submessages */
|
|
struct sftp_handler extended_handlers[] = {
|
|
{ "posix-rename", "posix-rename@openssh.com", 0,
|
|
process_extended_posix_rename, 1 },
|
|
{ "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 },
|
|
{ "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 },
|
|
{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
|
|
{ "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 },
|
|
{ NULL, NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
static int
|
|
request_permitted(struct sftp_handler *h)
|
|
{
|
|
char *result;
|
|
|
|
if (readonly && h->does_write) {
|
|
verbose("Refusing %s request in read-only mode", h->name);
|
|
return 0;
|
|
}
|
|
if (request_blacklist != NULL &&
|
|
((result = match_list(h->name, request_blacklist, NULL))) != NULL) {
|
|
free(result);
|
|
verbose("Refusing blacklisted %s request", h->name);
|
|
return 0;
|
|
}
|
|
if (request_whitelist != NULL &&
|
|
((result = match_list(h->name, request_whitelist, NULL))) != NULL) {
|
|
free(result);
|
|
debug2("Permitting whitelisted %s request", h->name);
|
|
return 1;
|
|
}
|
|
if (request_whitelist != NULL) {
|
|
verbose("Refusing non-whitelisted %s request", h->name);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
errno_to_portable(int unixerrno)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (unixerrno) {
|
|
case 0:
|
|
ret = SSH2_FX_OK;
|
|
break;
|
|
case ENOENT:
|
|
case ENOTDIR:
|
|
case EBADF:
|
|
#ifndef WIN32_FIXME
|
|
case ELOOP:
|
|
#endif
|
|
ret = SSH2_FX_NO_SUCH_FILE;
|
|
break;
|
|
case EPERM:
|
|
case EACCES:
|
|
case EFAULT:
|
|
ret = SSH2_FX_PERMISSION_DENIED;
|
|
break;
|
|
case ENAMETOOLONG:
|
|
case EINVAL:
|
|
ret = SSH2_FX_BAD_MESSAGE;
|
|
break;
|
|
case ENOSYS:
|
|
ret = SSH2_FX_OP_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
ret = SSH2_FX_FAILURE;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
flags_from_portable(int pflags)
|
|
{
|
|
int flags = 0;
|
|
|
|
if ((pflags & SSH2_FXF_READ) &&
|
|
(pflags & SSH2_FXF_WRITE)) {
|
|
flags = O_RDWR;
|
|
} else if (pflags & SSH2_FXF_READ) {
|
|
flags = O_RDONLY;
|
|
} else if (pflags & SSH2_FXF_WRITE) {
|
|
flags = O_WRONLY;
|
|
}
|
|
if (pflags & SSH2_FXF_APPEND)
|
|
flags |= O_APPEND;
|
|
if (pflags & SSH2_FXF_CREAT)
|
|
flags |= O_CREAT;
|
|
if (pflags & SSH2_FXF_TRUNC)
|
|
flags |= O_TRUNC;
|
|
if (pflags & SSH2_FXF_EXCL)
|
|
flags |= O_EXCL;
|
|
return flags;
|
|
}
|
|
|
|
static const char *
|
|
string_from_portable(int pflags)
|
|
{
|
|
static char ret[128];
|
|
|
|
*ret = '\0';
|
|
|
|
#define PAPPEND(str) { \
|
|
if (*ret != '\0') \
|
|
strlcat(ret, ",", sizeof(ret)); \
|
|
strlcat(ret, str, sizeof(ret)); \
|
|
}
|
|
|
|
if (pflags & SSH2_FXF_READ)
|
|
PAPPEND("READ")
|
|
if (pflags & SSH2_FXF_WRITE)
|
|
PAPPEND("WRITE")
|
|
if (pflags & SSH2_FXF_APPEND)
|
|
PAPPEND("APPEND")
|
|
if (pflags & SSH2_FXF_CREAT)
|
|
PAPPEND("CREATE")
|
|
if (pflags & SSH2_FXF_TRUNC)
|
|
PAPPEND("TRUNCATE")
|
|
if (pflags & SSH2_FXF_EXCL)
|
|
PAPPEND("EXCL")
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* handle handles */
|
|
|
|
typedef struct Handle Handle;
|
|
struct Handle {
|
|
int use;
|
|
DIR *dirp;
|
|
int fd;
|
|
int flags;
|
|
char *name;
|
|
u_int64_t bytes_read, bytes_write;
|
|
int next_unused;
|
|
};
|
|
|
|
enum {
|
|
HANDLE_UNUSED,
|
|
HANDLE_DIR,
|
|
HANDLE_FILE
|
|
};
|
|
|
|
Handle *handles = NULL;
|
|
u_int num_handles = 0;
|
|
int first_unused_handle = -1;
|
|
|
|
static void handle_unused(int i)
|
|
{
|
|
handles[i].use = HANDLE_UNUSED;
|
|
handles[i].next_unused = first_unused_handle;
|
|
first_unused_handle = i;
|
|
}
|
|
|
|
static int
|
|
handle_new(int use, const char *name, int fd, int flags, DIR *dirp)
|
|
{
|
|
int i;
|
|
|
|
if (first_unused_handle == -1) {
|
|
if (num_handles + 1 <= num_handles)
|
|
return -1;
|
|
num_handles++;
|
|
handles = xreallocarray(handles, num_handles, sizeof(Handle));
|
|
handle_unused(num_handles - 1);
|
|
}
|
|
|
|
i = first_unused_handle;
|
|
first_unused_handle = handles[i].next_unused;
|
|
|
|
handles[i].use = use;
|
|
handles[i].dirp = dirp;
|
|
handles[i].fd = fd;
|
|
handles[i].flags = flags;
|
|
handles[i].name = xstrdup(name);
|
|
handles[i].bytes_read = handles[i].bytes_write = 0;
|
|
|
|
return i;
|
|
}
|
|
|
|
static int
|
|
handle_is_ok(int i, int type)
|
|
{
|
|
return i >= 0 && (u_int)i < num_handles && handles[i].use == type;
|
|
}
|
|
|
|
static int
|
|
handle_to_string(int handle, u_char **stringp, int *hlenp)
|
|
{
|
|
if (stringp == NULL || hlenp == NULL)
|
|
return -1;
|
|
*stringp = xmalloc(sizeof(int32_t));
|
|
put_u32(*stringp, handle);
|
|
*hlenp = sizeof(int32_t);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
handle_from_string(const u_char *handle, u_int hlen)
|
|
{
|
|
int val;
|
|
|
|
if (hlen != sizeof(int32_t))
|
|
return -1;
|
|
val = get_u32(handle);
|
|
if (handle_is_ok(val, HANDLE_FILE) ||
|
|
handle_is_ok(val, HANDLE_DIR))
|
|
return val;
|
|
return -1;
|
|
}
|
|
|
|
static char *
|
|
handle_to_name(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_DIR)||
|
|
handle_is_ok(handle, HANDLE_FILE))
|
|
return handles[handle].name;
|
|
return NULL;
|
|
}
|
|
|
|
static DIR *
|
|
handle_to_dir(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_DIR))
|
|
return handles[handle].dirp;
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
handle_to_fd(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE))
|
|
return handles[handle].fd;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
handle_to_flags(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE))
|
|
return handles[handle].flags;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
handle_update_read(int handle, ssize_t bytes)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
|
|
handles[handle].bytes_read += bytes;
|
|
}
|
|
|
|
static void
|
|
handle_update_write(int handle, ssize_t bytes)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0)
|
|
handles[handle].bytes_write += bytes;
|
|
}
|
|
|
|
static u_int64_t
|
|
handle_bytes_read(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE))
|
|
return (handles[handle].bytes_read);
|
|
return 0;
|
|
}
|
|
|
|
static u_int64_t
|
|
handle_bytes_write(int handle)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE))
|
|
return (handles[handle].bytes_write);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
handle_close(int handle)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (handle_is_ok(handle, HANDLE_FILE)) {
|
|
ret = close(handles[handle].fd);
|
|
free(handles[handle].name);
|
|
handle_unused(handle);
|
|
} else if (handle_is_ok(handle, HANDLE_DIR)) {
|
|
ret = closedir(handles[handle].dirp);
|
|
free(handles[handle].name);
|
|
handle_unused(handle);
|
|
} else {
|
|
errno = ENOENT;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
handle_log_close(int handle, char *emsg)
|
|
{
|
|
if (handle_is_ok(handle, HANDLE_FILE)) {
|
|
logit("%s%sclose \"%s\" bytes read %llu written %llu",
|
|
emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
|
|
handle_to_name(handle),
|
|
(unsigned long long)handle_bytes_read(handle),
|
|
(unsigned long long)handle_bytes_write(handle));
|
|
} else {
|
|
logit("%s%sclosedir \"%s\"",
|
|
emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ",
|
|
handle_to_name(handle));
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_log_exit(void)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; i < num_handles; i++)
|
|
if (handles[i].use != HANDLE_UNUSED)
|
|
handle_log_close(i, "forced");
|
|
}
|
|
|
|
static int
|
|
get_handle(struct sshbuf *queue, int *hp)
|
|
{
|
|
u_char *handle;
|
|
int r;
|
|
size_t hlen;
|
|
|
|
*hp = -1;
|
|
if ((r = sshbuf_get_string(queue, &handle, &hlen)) != 0)
|
|
return r;
|
|
if (hlen < 256)
|
|
*hp = handle_from_string(handle, hlen);
|
|
free(handle);
|
|
return 0;
|
|
}
|
|
|
|
/* send replies */
|
|
|
|
static void
|
|
send_msg(struct sshbuf *m)
|
|
{
|
|
int r;
|
|
|
|
if ((r = sshbuf_put_stringb(oqueue, m)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
sshbuf_reset(m);
|
|
}
|
|
|
|
static const char *
|
|
status_to_message(u_int32_t status)
|
|
{
|
|
const char *status_messages[] = {
|
|
"Success", /* SSH_FX_OK */
|
|
"End of file", /* SSH_FX_EOF */
|
|
"No such file", /* SSH_FX_NO_SUCH_FILE */
|
|
"Permission denied", /* SSH_FX_PERMISSION_DENIED */
|
|
"Failure", /* SSH_FX_FAILURE */
|
|
"Bad message", /* SSH_FX_BAD_MESSAGE */
|
|
"No connection", /* SSH_FX_NO_CONNECTION */
|
|
"Connection lost", /* SSH_FX_CONNECTION_LOST */
|
|
"Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */
|
|
"Unknown error" /* Others */
|
|
};
|
|
return (status_messages[MIN(status,SSH2_FX_MAX)]);
|
|
}
|
|
|
|
static void
|
|
send_status(u_int32_t id, u_int32_t status)
|
|
{
|
|
struct sshbuf *msg;
|
|
int r;
|
|
|
|
debug3("request %u: sent status %u", id, status);
|
|
if (log_level > SYSLOG_LEVEL_VERBOSE ||
|
|
(status != SSH2_FX_OK && status != SSH2_FX_EOF))
|
|
logit("sent status %s", status_to_message(status));
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_STATUS)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, status)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
if (version >= 3) {
|
|
if ((r = sshbuf_put_cstring(msg,
|
|
status_to_message(status))) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "")) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
}
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
static void
|
|
send_data_or_handle(char type, u_int32_t id, const u_char *data, int dlen)
|
|
{
|
|
struct sshbuf *msg;
|
|
int r;
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, type)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
(r = sshbuf_put_string(msg, data, dlen)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
|
|
static void
|
|
send_data(u_int32_t id, const u_char *data, int dlen)
|
|
{
|
|
debug("request %u: sent data len %d", id, dlen);
|
|
send_data_or_handle(SSH2_FXP_DATA, id, data, dlen);
|
|
}
|
|
|
|
static void
|
|
send_handle(u_int32_t id, int handle)
|
|
{
|
|
u_char *string;
|
|
int hlen;
|
|
|
|
handle_to_string(handle, &string, &hlen);
|
|
debug("request %u: sent handle handle %d", id, handle);
|
|
send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen);
|
|
free(string);
|
|
}
|
|
|
|
static void
|
|
send_names(u_int32_t id, int count, const Stat *stats)
|
|
{
|
|
struct sshbuf *msg;
|
|
int i, r;
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_NAME)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, count)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
debug("request %u: sent names count %d", id, count);
|
|
for (i = 0; i < count; i++) {
|
|
if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 ||
|
|
(r = encode_attrib(msg, &stats[i].attrib)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
}
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
|
|
static void
|
|
send_attrib(u_int32_t id, const Attrib *a)
|
|
{
|
|
struct sshbuf *msg;
|
|
int r;
|
|
|
|
debug("request %u: sent attrib have 0x%x", id, a->flags);
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_ATTRS)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
(r = encode_attrib(msg, a)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
|
|
static void
|
|
send_statvfs(u_int32_t id, struct statvfs *st)
|
|
{
|
|
struct sshbuf *msg;
|
|
u_int64_t flag;
|
|
int r;
|
|
|
|
flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0;
|
|
flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0;
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_bsize)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_frsize)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_blocks)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_bfree)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_bavail)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_files)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_ffree)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_favail)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, FSID_TO_ULONG(st->f_fsid))) != 0 ||
|
|
(r = sshbuf_put_u64(msg, flag)) != 0 ||
|
|
(r = sshbuf_put_u64(msg, st->f_namemax)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
|
|
/* parse incoming */
|
|
|
|
static void
|
|
process_init(void)
|
|
{
|
|
struct sshbuf *msg;
|
|
int r;
|
|
|
|
if ((r = sshbuf_get_u32(iqueue, &version)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
verbose("received client version %u", version);
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 ||
|
|
(r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0 ||
|
|
/* POSIX rename extension */
|
|
(r = sshbuf_put_cstring(msg, "posix-rename@openssh.com")) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
|
|
/* statvfs extension */
|
|
(r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
|
|
/* fstatvfs extension */
|
|
(r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */
|
|
/* hardlink extension */
|
|
(r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
|
|
/* fsync extension */
|
|
(r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
|
|
(r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
send_msg(msg);
|
|
sshbuf_free(msg);
|
|
}
|
|
|
|
static void
|
|
process_open(u_int32_t id)
|
|
{
|
|
u_int32_t pflags;
|
|
Attrib a;
|
|
char *name;
|
|
int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE;
|
|
|
|
// #ifdef WIN32_FIXME
|
|
// name = buffer_get_string_local8_from_utf8(&iqueue, NULL);
|
|
//#else
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 )
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
//#endif /* WIN32_FIXME */
|
|
|
|
if ((r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */
|
|
(r = decode_attrib(iqueue, &a)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: open flags %d", id, pflags);
|
|
flags = flags_from_portable(pflags);
|
|
mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666;
|
|
logit("open \"%s\" flags %s mode 0%o",
|
|
name, string_from_portable(pflags), mode);
|
|
if (readonly &&
|
|
((flags & O_ACCMODE) == O_WRONLY ||
|
|
(flags & O_ACCMODE) == O_RDWR)) {
|
|
verbose("Refusing open request in read-only mode");
|
|
status = SSH2_FX_PERMISSION_DENIED;
|
|
} else {
|
|
fd = open(name, flags, mode);
|
|
if (fd < 0) {
|
|
status = errno_to_portable(errno);
|
|
} else {
|
|
handle = handle_new(HANDLE_FILE, name, fd, flags, NULL);
|
|
if (handle < 0) {
|
|
close(fd);
|
|
} else {
|
|
send_handle(id, handle);
|
|
status = SSH2_FX_OK;
|
|
}
|
|
}
|
|
}
|
|
if (status != SSH2_FX_OK)
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_close(u_int32_t id)
|
|
{
|
|
int r, handle, ret, status = SSH2_FX_FAILURE;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: close handle %u", id, handle);
|
|
handle_log_close(handle, NULL);
|
|
ret = handle_close(handle);
|
|
status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
}
|
|
|
|
static void
|
|
process_read(u_int32_t id)
|
|
{
|
|
u_char buf[64*1024];
|
|
u_int32_t len;
|
|
int r, handle, fd, ret, status = SSH2_FX_FAILURE;
|
|
u_int64_t off;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0 ||
|
|
(r = sshbuf_get_u64(iqueue, &off)) != 0 ||
|
|
(r = sshbuf_get_u32(iqueue, &len)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug("request %u: read \"%s\" (handle %d) off %llu len %d",
|
|
id, handle_to_name(handle), handle, (unsigned long long)off, len);
|
|
if (len > sizeof buf) {
|
|
len = sizeof buf;
|
|
debug2("read change len %d", len);
|
|
}
|
|
fd = handle_to_fd(handle);
|
|
if (fd >= 0) {
|
|
if (lseek(fd, off, SEEK_SET) < 0) {
|
|
error("process_read: seek failed");
|
|
status = errno_to_portable(errno);
|
|
} else {
|
|
ret = read(fd, buf, len);
|
|
if (ret < 0) {
|
|
status = errno_to_portable(errno);
|
|
} else if (ret == 0) {
|
|
status = SSH2_FX_EOF;
|
|
} else {
|
|
send_data(id, buf, ret);
|
|
status = SSH2_FX_OK;
|
|
handle_update_read(handle, ret);
|
|
}
|
|
}
|
|
}
|
|
if (status != SSH2_FX_OK)
|
|
send_status(id, status);
|
|
}
|
|
|
|
static void
|
|
process_write(u_int32_t id)
|
|
{
|
|
u_int64_t off;
|
|
size_t len;
|
|
int r, handle, fd, ret, status;
|
|
u_char *data;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0 ||
|
|
(r = sshbuf_get_u64(iqueue, &off)) != 0 ||
|
|
(r = sshbuf_get_string(iqueue, &data, &len)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug("request %u: write \"%s\" (handle %d) off %llu len %zu",
|
|
id, handle_to_name(handle), handle, (unsigned long long)off, len);
|
|
fd = handle_to_fd(handle);
|
|
|
|
if (fd < 0)
|
|
status = SSH2_FX_FAILURE;
|
|
else {
|
|
if (!(handle_to_flags(handle) & O_APPEND) &&
|
|
lseek(fd, off, SEEK_SET) < 0) {
|
|
status = errno_to_portable(errno);
|
|
error("process_write: seek failed");
|
|
} else {
|
|
/* XXX ATOMICIO ? */
|
|
ret = write(fd, data, len);
|
|
if (ret < 0) {
|
|
error("process_write: write failed");
|
|
status = errno_to_portable(errno);
|
|
} else if ((size_t)ret == len) {
|
|
status = SSH2_FX_OK;
|
|
handle_update_write(handle, ret);
|
|
} else {
|
|
debug2("nothing at all written");
|
|
status = SSH2_FX_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
send_status(id, status);
|
|
free(data);
|
|
}
|
|
|
|
static void
|
|
process_do_stat(u_int32_t id, int do_lstat)
|
|
{
|
|
Attrib a;
|
|
struct stat st;
|
|
char *name;
|
|
int r, status = SSH2_FX_FAILURE;
|
|
|
|
#ifdef WIN32_FIXME
|
|
char resolvedname[MAXPATHLEN];
|
|
#endif
|
|
|
|
|
|
#ifdef WIN32_FIXME
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
if (realpathWin32(name, resolvedname))
|
|
{
|
|
free(name);
|
|
|
|
name = strdup(resolvedname);
|
|
}
|
|
|
|
debug3("request %u: %sstat", id, do_lstat ? "l" : "");
|
|
verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
|
|
r = stat(name, &st);
|
|
|
|
#else
|
|
|
|
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: %sstat", id, do_lstat ? "l" : "");
|
|
verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name);
|
|
r = do_lstat ? lstat(name, &st) : stat(name, &st);
|
|
#endif /* WIN32_FIXME */
|
|
if (r < 0) {
|
|
status = errno_to_portable(errno);
|
|
} else {
|
|
stat_to_attrib(&st, &a);
|
|
send_attrib(id, &a);
|
|
status = SSH2_FX_OK;
|
|
}
|
|
if (status != SSH2_FX_OK)
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_stat(u_int32_t id)
|
|
{
|
|
process_do_stat(id, 0);
|
|
}
|
|
|
|
static void
|
|
process_lstat(u_int32_t id)
|
|
{
|
|
process_do_stat(id, 1);
|
|
}
|
|
|
|
static void
|
|
process_fstat(u_int32_t id)
|
|
{
|
|
Attrib a;
|
|
struct stat st;
|
|
int fd, r, handle, status = SSH2_FX_FAILURE;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
debug("request %u: fstat \"%s\" (handle %u)",
|
|
id, handle_to_name(handle), handle);
|
|
fd = handle_to_fd(handle);
|
|
if (fd >= 0) {
|
|
r = fstat(fd, &st);
|
|
if (r < 0) {
|
|
status = errno_to_portable(errno);
|
|
} else {
|
|
stat_to_attrib(&st, &a);
|
|
send_attrib(id, &a);
|
|
status = SSH2_FX_OK;
|
|
}
|
|
}
|
|
if (status != SSH2_FX_OK)
|
|
send_status(id, status);
|
|
}
|
|
|
|
static struct timeval *
|
|
attrib_to_tv(const Attrib *a)
|
|
{
|
|
static struct timeval tv[2];
|
|
|
|
tv[0].tv_sec = a->atime;
|
|
tv[0].tv_usec = 0;
|
|
tv[1].tv_sec = a->mtime;
|
|
tv[1].tv_usec = 0;
|
|
return tv;
|
|
}
|
|
|
|
static void
|
|
process_setstat(u_int32_t id)
|
|
{
|
|
Attrib a;
|
|
char *name;
|
|
int r, status = SSH2_FX_OK;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 )
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
if ((r = decode_attrib(iqueue, &a)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug("request %u: setstat name \"%s\"", id, name);
|
|
if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
|
|
logit("set \"%s\" size %llu",
|
|
name, (unsigned long long)a.size);
|
|
#ifndef WIN32_FIXME
|
|
r = truncate(name, a.size);
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
#endif
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
|
|
logit("set \"%s\" mode %04o", name, a.perm);
|
|
r = chmod(name, a.perm & 07777);
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
|
|
char buf[64];
|
|
time_t t = a.mtime;
|
|
|
|
strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
|
|
localtime(&t));
|
|
logit("set \"%s\" modtime %s", name, buf);
|
|
r = utimes(name, attrib_to_tv(&a));
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
|
|
logit("set \"%s\" owner %lu group %lu", name,
|
|
(u_long)a.uid, (u_long)a.gid);
|
|
#ifndef WIN32_FIXME
|
|
r = chown(name, a.uid, a.gid);
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
#endif
|
|
}
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_fsetstat(u_int32_t id)
|
|
{
|
|
Attrib a;
|
|
int handle, fd, r;
|
|
int status = SSH2_FX_OK;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0 ||
|
|
(r = decode_attrib(iqueue, &a)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug("request %u: fsetstat handle %d", id, handle);
|
|
fd = handle_to_fd(handle);
|
|
if (fd < 0)
|
|
status = SSH2_FX_FAILURE;
|
|
else {
|
|
char *name = handle_to_name(handle);
|
|
|
|
if (a.flags & SSH2_FILEXFER_ATTR_SIZE) {
|
|
logit("set \"%s\" size %llu",
|
|
name, (unsigned long long)a.size);
|
|
#ifndef WIN32_FIXME
|
|
r = ftruncate(fd, a.size);
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
#endif
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
|
|
logit("set \"%s\" mode %04o", name, a.perm);
|
|
#ifdef HAVE_FCHMOD
|
|
r = fchmod(fd, a.perm & 07777);
|
|
#else
|
|
r = chmod(name, a.perm & 07777);
|
|
#endif
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
|
|
char buf[64];
|
|
time_t t = a.mtime;
|
|
|
|
strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S",
|
|
localtime(&t));
|
|
logit("set \"%s\" modtime %s", name, buf);
|
|
#ifdef HAVE_FUTIMES
|
|
r = futimes(fd, attrib_to_tv(&a));
|
|
#else
|
|
r = utimes(name, attrib_to_tv(&a));
|
|
#endif
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
}
|
|
if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) {
|
|
logit("set \"%s\" owner %lu group %lu", name,
|
|
(u_long)a.uid, (u_long)a.gid);
|
|
#ifndef WIN32_FIXME
|
|
#ifdef HAVE_FCHOWN
|
|
r = fchown(fd, a.uid, a.gid);
|
|
#else
|
|
r = chown(name, a.uid, a.gid);
|
|
#endif
|
|
if (r == -1)
|
|
status = errno_to_portable(errno);
|
|
#endif
|
|
}
|
|
}
|
|
send_status(id, status);
|
|
}
|
|
|
|
static void
|
|
process_opendir(u_int32_t id)
|
|
{
|
|
DIR *dirp = NULL;
|
|
char *path;
|
|
int r, handle, status = SSH2_FX_FAILURE;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
|
|
debug3("request %u: opendir", id);
|
|
logit("opendir \"%s\"", path);
|
|
dirp = opendir(path);
|
|
if (dirp == NULL) {
|
|
status = errno_to_portable(errno);
|
|
} else {
|
|
handle = handle_new(HANDLE_DIR, path, 0, 0, dirp);
|
|
if (handle < 0) {
|
|
closedir(dirp);
|
|
} else {
|
|
send_handle(id, handle);
|
|
status = SSH2_FX_OK;
|
|
}
|
|
|
|
}
|
|
if (status != SSH2_FX_OK)
|
|
send_status(id, status);
|
|
free(path);
|
|
}
|
|
|
|
static void
|
|
process_readdir(u_int32_t id)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *dp;
|
|
char *path;
|
|
int r, handle;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug("request %u: readdir \"%s\" (handle %d)", id,
|
|
handle_to_name(handle), handle);
|
|
dirp = handle_to_dir(handle);
|
|
path = handle_to_name(handle);
|
|
if (dirp == NULL || path == NULL) {
|
|
send_status(id, SSH2_FX_FAILURE);
|
|
} else {
|
|
struct stat st;
|
|
char pathname[PATH_MAX];
|
|
Stat *stats;
|
|
int nstats = 10, count = 0, i;
|
|
|
|
stats = xcalloc(nstats, sizeof(Stat));
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
if (count >= nstats) {
|
|
nstats *= 2;
|
|
stats = xreallocarray(stats, nstats, sizeof(Stat));
|
|
}
|
|
/* XXX OVERFLOW ? */
|
|
snprintf(pathname, sizeof pathname, "%s%s%s", path,
|
|
strcmp(path, "/") ? "/" : "", dp->d_name);
|
|
if (lstat(pathname, &st) < 0)
|
|
continue;
|
|
stat_to_attrib(&st, &(stats[count].attrib));
|
|
#ifdef WIN32_FIXME
|
|
{
|
|
/*
|
|
* Convert names to UTF8 before send to network.
|
|
*/
|
|
|
|
stats[count].name = ConvertLocal8ToUtf8(dp -> d_name, -1, NULL);
|
|
stats[count].long_name = ls_file(dp -> d_name, &st, 0, 0);
|
|
|
|
/*
|
|
debug3("putting name [%s]...\n", stats[count].name);
|
|
debug3("putting long name [%s]...\n", stats[count].long_name);
|
|
*/
|
|
}
|
|
#else
|
|
stats[count].name = xstrdup(dp->d_name);
|
|
stats[count].long_name = ls_file(dp->d_name, &st, 0, 0);
|
|
#endif
|
|
count++;
|
|
/* send up to 100 entries in one message */
|
|
/* XXX check packet size instead */
|
|
if (count == 100)
|
|
break;
|
|
}
|
|
if (count > 0) {
|
|
send_names(id, count, stats);
|
|
for (i = 0; i < count; i++) {
|
|
free(stats[i].name);
|
|
free(stats[i].long_name);
|
|
}
|
|
} else {
|
|
send_status(id, SSH2_FX_EOF);
|
|
}
|
|
free(stats);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_remove(u_int32_t id)
|
|
{
|
|
char *name;
|
|
int r, status = SSH2_FX_FAILURE;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: remove", id);
|
|
logit("remove name \"%s\"", name);
|
|
r = unlink(name);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_mkdir(u_int32_t id)
|
|
{
|
|
Attrib a;
|
|
char *name;
|
|
int r, mode, status = SSH2_FX_FAILURE;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 )
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
if ((r = decode_attrib(iqueue, &a)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ?
|
|
a.perm & 07777 : 0777;
|
|
debug3("request %u: mkdir", id);
|
|
logit("mkdir name \"%s\" mode 0%o", name, mode);
|
|
r = mkdir(name, mode);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_rmdir(u_int32_t id)
|
|
{
|
|
char *name;
|
|
int r, status;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: rmdir", id);
|
|
logit("rmdir name \"%s\"", name);
|
|
r = rmdir(name);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
free(name);
|
|
}
|
|
|
|
static void
|
|
process_realpath(u_int32_t id)
|
|
{
|
|
char resolvedname[PATH_MAX];
|
|
char *path;
|
|
int r;
|
|
//#ifdef WIN32_FIXME
|
|
//path = buffer_get_string_local8_from_utf8(&iqueue, NULL);
|
|
//#else
|
|
if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
//#endif
|
|
|
|
|
|
#ifndef WIN32_FIXME
|
|
if (path[0] == '\0') {
|
|
free(path);
|
|
path = xstrdup(".");
|
|
}
|
|
#else
|
|
if ( (path[0] == '\0') || ( strcmp(path, ".")== 0 ) ) {
|
|
free(path);
|
|
_getcwd(resolvedname, sizeof(resolvedname));
|
|
// convert back slashes to forward slashes to be compatibale with unix naming
|
|
char *cptr = resolvedname;
|
|
while (*cptr) {
|
|
if (*cptr == '\\')
|
|
*cptr = '/' ;
|
|
cptr++;
|
|
}
|
|
path = xstrdup(resolvedname);
|
|
}
|
|
#endif
|
|
|
|
debug3("request %u: realpath", id);
|
|
verbose("realpath \"%s\"", path);
|
|
if (realpath(path, resolvedname) == NULL) {
|
|
send_status(id, errno_to_portable(errno));
|
|
} else {
|
|
Stat s;
|
|
attrib_clear(&s.attrib);
|
|
s.name = s.long_name = resolvedname;
|
|
send_names(id, 1, &s);
|
|
}
|
|
free(path);
|
|
}
|
|
|
|
static void
|
|
process_rename(u_int32_t id)
|
|
{
|
|
char *oldpath, *newpath;
|
|
int r, status;
|
|
struct stat sb;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
|
|
(r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
|
|
debug3("request %u: rename", id);
|
|
logit("rename old \"%s\" new \"%s\"", oldpath, newpath);
|
|
status = SSH2_FX_FAILURE;
|
|
if (lstat(oldpath, &sb) == -1)
|
|
status = errno_to_portable(errno);
|
|
#ifndef WIN32_FIXME
|
|
else if (S_ISREG(sb.st_mode)) {
|
|
/* Race-free rename of regular files */
|
|
if (link(oldpath, newpath) == -1) {
|
|
if (errno == EOPNOTSUPP || errno == ENOSYS
|
|
#ifdef EXDEV
|
|
|| errno == EXDEV
|
|
#endif
|
|
#ifdef LINK_OPNOTSUPP_ERRNO
|
|
|| errno == LINK_OPNOTSUPP_ERRNO
|
|
#endif
|
|
) {
|
|
struct stat st;
|
|
|
|
/*
|
|
* fs doesn't support links, so fall back to
|
|
* stat+rename. This is racy.
|
|
*/
|
|
if (stat(newpath, &st) == -1) {
|
|
if (rename(oldpath, newpath) == -1)
|
|
status =
|
|
errno_to_portable(errno);
|
|
else
|
|
status = SSH2_FX_OK;
|
|
}
|
|
} else {
|
|
status = errno_to_portable(errno);
|
|
}
|
|
} else if (unlink(oldpath) == -1) {
|
|
status = errno_to_portable(errno);
|
|
/* clean spare link */
|
|
unlink(newpath);
|
|
} else
|
|
status = SSH2_FX_OK;
|
|
}
|
|
#endif /* !WIN32_FIXME */
|
|
else if (stat(newpath, &sb) == -1) {
|
|
if (rename(oldpath, newpath) == -1)
|
|
status = errno_to_portable(errno);
|
|
else
|
|
status = SSH2_FX_OK;
|
|
}
|
|
send_status(id, status);
|
|
free(oldpath);
|
|
free(newpath);
|
|
}
|
|
|
|
static void
|
|
process_readlink(u_int32_t id)
|
|
{
|
|
#ifdef WIN32_FIXME
|
|
|
|
/*
|
|
* Win32 code.
|
|
*/
|
|
|
|
//u_int32_t id;
|
|
|
|
char *request;
|
|
|
|
//id = get_int();
|
|
#ifndef WIN32_FIXME
|
|
// PRAGMA:TODO
|
|
request = get_string(NULL);
|
|
|
|
send_status(id, SSH2_FX_OP_UNSUPPORTED);
|
|
|
|
free(request);
|
|
#endif
|
|
|
|
#else
|
|
|
|
/*
|
|
* Original OpenSSH code.
|
|
*/
|
|
int r, len;
|
|
char buf[PATH_MAX];
|
|
char *path;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: readlink", id);
|
|
verbose("readlink \"%s\"", path);
|
|
if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1)
|
|
send_status(id, errno_to_portable(errno));
|
|
else {
|
|
Stat s;
|
|
|
|
buf[len] = '\0';
|
|
attrib_clear(&s.attrib);
|
|
s.name = s.long_name = buf;
|
|
send_names(id, 1, &s);
|
|
}
|
|
free(path);
|
|
#endif /* WIN32_FIXME */
|
|
}
|
|
|
|
static void
|
|
process_symlink(u_int32_t id)
|
|
{
|
|
#ifdef WIN32_FIXME
|
|
|
|
/*
|
|
* Win32 code.
|
|
*/
|
|
|
|
//u_int32_t id;
|
|
|
|
char *request;
|
|
|
|
//id = get_int();
|
|
#ifndef WIN32_FIXME
|
|
// PRAGMA:TODO
|
|
request = get_string(NULL);
|
|
|
|
send_status(id, SSH2_FX_OP_UNSUPPORTED);
|
|
|
|
free(request);
|
|
#endif
|
|
|
|
#else
|
|
|
|
/*
|
|
* Original OpenSSH code.
|
|
*/
|
|
char *oldpath, *newpath;
|
|
int r, status;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
|
|
(r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: symlink", id);
|
|
logit("symlink old \"%s\" new \"%s\"", oldpath, newpath);
|
|
/* this will fail if 'newpath' exists */
|
|
r = symlink(oldpath, newpath);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
free(oldpath);
|
|
free(newpath);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
process_extended_posix_rename(u_int32_t id)
|
|
{
|
|
char *oldpath, *newpath;
|
|
int r, status;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
|
|
(r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
|
|
debug3("request %u: posix-rename", id);
|
|
logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath);
|
|
r = rename(oldpath, newpath);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
send_status(id, status);
|
|
free(oldpath);
|
|
free(newpath);
|
|
}
|
|
|
|
/*
|
|
* Remove whole directory tree.
|
|
*
|
|
* path - target dir, non-empty dir or file to remove (IN).
|
|
*
|
|
* RETURNS: 0 if OK.
|
|
*/
|
|
|
|
int RemoveTree(const char *path)
|
|
{
|
|
DIR *d = opendir(path);
|
|
|
|
size_t path_len = strlen(path);
|
|
|
|
int r = -1;
|
|
|
|
if (d)
|
|
{
|
|
struct dirent *p;
|
|
|
|
r = 0;
|
|
|
|
while (!r && (p=readdir(d)))
|
|
{
|
|
int r2 = -1;
|
|
|
|
char *buf;
|
|
|
|
size_t len;
|
|
|
|
/*
|
|
* Skip the names "." and ".." as we don't want to recurse on them.
|
|
*/
|
|
|
|
if (!strcmp(p -> d_name, ".") || !strcmp(p -> d_name, ".."))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
len = path_len + strlen(p -> d_name) + 2;
|
|
|
|
buf = (char *) malloc(len);
|
|
|
|
if (buf)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
snprintf(buf, len, "%s/%s", path, p -> d_name);
|
|
|
|
if (!stat(buf, &statbuf))
|
|
{
|
|
if (S_ISDIR(statbuf.st_mode))
|
|
{
|
|
r2 = RemoveTree(buf);
|
|
}
|
|
else
|
|
{
|
|
r2 = unlink(buf);
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
r = r2;
|
|
}
|
|
|
|
closedir(d);
|
|
|
|
if (r == 0)
|
|
{
|
|
r = rmdir(path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
r = unlink(path);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Close all handles to given target path.
|
|
*
|
|
* path - target path to close (IN).
|
|
* recursive - cloese subpaths too if set to 1 (IN).
|
|
*/
|
|
|
|
static void HandlesCloseByPath(char *path, int recursive)
|
|
{
|
|
int len = strlen(path);
|
|
int i = 0;
|
|
|
|
for (i = 0; i < num_handles; i++)
|
|
{
|
|
int closeNeeded = 0;
|
|
|
|
if (recursive)
|
|
{
|
|
if (strncmp(handles[i].name, path, len) == 0)
|
|
{
|
|
closeNeeded = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strcmp(handles[i].name, path) == 0)
|
|
{
|
|
closeNeeded = 1;
|
|
}
|
|
}
|
|
|
|
if (closeNeeded &&
|
|
(handles[i].use == HANDLE_FILE ||
|
|
handles[i].use == HANDLE_DIR))
|
|
{
|
|
debug3("Closing handle [%d] to [%s]...", i, handles[i].name);
|
|
|
|
handle_close(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_extended_statvfs(u_int32_t id)
|
|
{
|
|
char *path;
|
|
|
|
#ifdef WIN32_FIXME
|
|
struct stat st;
|
|
#else
|
|
struct statvfs st;
|
|
#endif
|
|
|
|
int r;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
debug3("request %u: statvfs", id);
|
|
logit("statvfs \"%s\"", path);
|
|
|
|
if (statvfs(path, &st) != 0)
|
|
send_status(id, errno_to_portable(errno));
|
|
else
|
|
send_statvfs(id, &st);
|
|
free(path);
|
|
}
|
|
|
|
static void
|
|
process_extended_fstatvfs(u_int32_t id)
|
|
{
|
|
int r, handle, fd;
|
|
#ifdef WIN32_FIXME
|
|
struct stat st;
|
|
#else
|
|
struct statvfs st;
|
|
#endif
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
debug("request %u: fstatvfs \"%s\" (handle %u)",
|
|
id, handle_to_name(handle), handle);
|
|
if ((fd = handle_to_fd(handle)) < 0) {
|
|
send_status(id, SSH2_FX_FAILURE);
|
|
return;
|
|
}
|
|
#ifdef WIN32_FIXME
|
|
if (statvfs(handle_to_name(handle), &st) != 0)
|
|
#else
|
|
if (fstatvfs(fd, &st) != 0)
|
|
#endif
|
|
|
|
send_status(id, errno_to_portable(errno));
|
|
else
|
|
send_statvfs(id, &st);
|
|
}
|
|
|
|
static void
|
|
process_extended_hardlink(u_int32_t id)
|
|
{
|
|
char *oldpath, *newpath;
|
|
int r, status;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 ||
|
|
(r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
|
|
debug3("request %u: hardlink", id);
|
|
logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath);
|
|
#ifndef WIN32_FIXME
|
|
r = link(oldpath, newpath);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
#endif
|
|
send_status(id, status);
|
|
free(oldpath);
|
|
free(newpath);
|
|
}
|
|
|
|
static void
|
|
process_extended_fsync(u_int32_t id)
|
|
{
|
|
int handle, fd, r, status = SSH2_FX_OP_UNSUPPORTED;
|
|
|
|
if ((r = get_handle(iqueue, &handle)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
debug3("request %u: fsync (handle %u)", id, handle);
|
|
verbose("fsync \"%s\"", handle_to_name(handle));
|
|
if ((fd = handle_to_fd(handle)) < 0)
|
|
status = SSH2_FX_NO_SUCH_FILE;
|
|
#ifndef WIN32_FIXME
|
|
// PRAGMA:TODO
|
|
else if (handle_is_ok(handle, HANDLE_FILE)) {
|
|
r = fsync(fd);
|
|
status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK;
|
|
}
|
|
#endif
|
|
send_status(id, status);
|
|
}
|
|
|
|
static void
|
|
process_extended(u_int32_t id)
|
|
{
|
|
char *request;
|
|
int i, r;
|
|
|
|
if ((r = sshbuf_get_cstring(iqueue, &request, NULL)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
for (i = 0; extended_handlers[i].handler != NULL; i++) {
|
|
if (strcmp(request, extended_handlers[i].ext_name) == 0) {
|
|
if (!request_permitted(&extended_handlers[i]))
|
|
send_status(id, SSH2_FX_PERMISSION_DENIED);
|
|
else
|
|
extended_handlers[i].handler(id);
|
|
break;
|
|
}
|
|
}
|
|
if (extended_handlers[i].handler == NULL) {
|
|
error("Unknown extended request \"%.100s\"", request);
|
|
send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */
|
|
}
|
|
free(request);
|
|
}
|
|
|
|
/* stolen from ssh-agent */
|
|
|
|
#ifdef WIN32_FIXME
|
|
int readsomemore=0;
|
|
#endif
|
|
|
|
static void
|
|
process(void)
|
|
{
|
|
u_int msg_len;
|
|
u_int buf_len;
|
|
u_int consumed;
|
|
u_char type;
|
|
const u_char *cp;
|
|
int i, r;
|
|
u_int32_t id;
|
|
|
|
#ifdef WIN32_FIXME
|
|
// we use to tell our caller to read more data if a message is not complete
|
|
readsomemore=0;
|
|
#endif
|
|
|
|
buf_len = sshbuf_len(iqueue);
|
|
if (buf_len < 5) {
|
|
#ifdef WIN32_FIXME
|
|
readsomemore =1;
|
|
#endif
|
|
return; /* Incomplete message. */
|
|
}
|
|
cp = sshbuf_ptr(iqueue);
|
|
msg_len = get_u32(cp);
|
|
if (msg_len > SFTP_MAX_MSG_LENGTH) {
|
|
error("bad message from %s local user %s",
|
|
client_addr, pw->pw_name);
|
|
sftp_server_cleanup_exit(11);
|
|
}
|
|
if (buf_len < msg_len + 4) {
|
|
#ifdef WIN32_FIXME
|
|
readsomemore =1;
|
|
#endif
|
|
return;
|
|
}
|
|
if ((r = sshbuf_consume(iqueue, 4)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
buf_len -= 4;
|
|
if ((r = sshbuf_get_u8(iqueue, &type)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
|
|
switch (type) {
|
|
case SSH2_FXP_INIT:
|
|
process_init();
|
|
init_done = 1;
|
|
break;
|
|
case SSH2_FXP_EXTENDED:
|
|
if (!init_done)
|
|
fatal("Received extended request before init");
|
|
if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
process_extended(id);
|
|
break;
|
|
default:
|
|
if (!init_done)
|
|
fatal("Received %u request before init", type);
|
|
if ((r = sshbuf_get_u32(iqueue, &id)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
for (i = 0; handlers[i].handler != NULL; i++) {
|
|
if (type == handlers[i].type) {
|
|
if (!request_permitted(&handlers[i])) {
|
|
send_status(id,
|
|
SSH2_FX_PERMISSION_DENIED);
|
|
} else {
|
|
handlers[i].handler(id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (handlers[i].handler == NULL)
|
|
error("Unknown message %u", type);
|
|
}
|
|
/* discard the remaining bytes from the current packet */
|
|
if (buf_len < sshbuf_len(iqueue)) {
|
|
error("iqueue grew unexpectedly");
|
|
sftp_server_cleanup_exit(255);
|
|
}
|
|
consumed = buf_len - sshbuf_len(iqueue);
|
|
if (msg_len < consumed) {
|
|
error("msg_len %u < consumed %u", msg_len, consumed);
|
|
sftp_server_cleanup_exit(255);
|
|
}
|
|
if (msg_len > consumed &&
|
|
(r = sshbuf_consume(iqueue, msg_len - consumed)) != 0)
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r));
|
|
}
|
|
|
|
/* Cleanup handler that logs active handles upon normal exit */
|
|
void
|
|
sftp_server_cleanup_exit(int i)
|
|
{
|
|
if (pw != NULL && client_addr != NULL) {
|
|
handle_log_exit();
|
|
logit("session closed for local user %s from [%s]",
|
|
pw->pw_name, client_addr);
|
|
}
|
|
_exit(i);
|
|
}
|
|
|
|
static void
|
|
sftp_server_usage(void)
|
|
{
|
|
extern char *__progname;
|
|
|
|
fprintf(stderr,
|
|
"usage: %s [-ehR] [-d start_directory] [-f log_facility] "
|
|
"[-l log_level]\n\t[-P blacklisted_requests] "
|
|
"[-p whitelisted_requests] [-u umask]\n"
|
|
" %s -Q protocol_feature\n",
|
|
__progname, __progname);
|
|
exit(1);
|
|
}
|
|
|
|
#ifdef WIN32_FIXME
|
|
DWORD select_in_handle( HANDLE in_handle)
|
|
{
|
|
//DWORD rc = WaitForSingleObject (in_handle, 0);
|
|
//if (rc == WAIT_OBJECT_0)
|
|
// return 1;
|
|
//else
|
|
// return 0;
|
|
|
|
DWORD bytesavail = 0 ;
|
|
PeekNamedPipe(in_handle, NULL,0, NULL, &bytesavail, NULL );
|
|
return bytesavail;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
sftp_server_main(int argc, char **argv, struct passwd *user_pw)
|
|
{
|
|
fd_set *rset, *wset;
|
|
int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0;
|
|
ssize_t len, olen, set_size;
|
|
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
|
|
|
|
#ifdef WIN32_FIXME
|
|
char *cp, *homedir = NULL, buf[32768];
|
|
#else
|
|
char *cp, *homedir = NULL, buf[4*4096];
|
|
#endif
|
|
|
|
long mask;
|
|
|
|
extern char *optarg;
|
|
extern char *__progname;
|
|
#ifdef WIN32_FIXME
|
|
|
|
/*
|
|
* Initialize Win32 log.
|
|
*/
|
|
|
|
logfd = _open("sftp-server.log", O_WRONLY | O_CREAT | O_APPEND , S_IREAD | S_IWRITE);
|
|
|
|
log_level = SYSLOG_LEVEL_DEBUG3;
|
|
|
|
__progname = ssh_get_progname(argv[0]);
|
|
|
|
log_init(__progname, log_level, log_facility, log_stderr);
|
|
|
|
/*
|
|
* Initialize Win32 I/O wrapper.
|
|
*/
|
|
|
|
//WSHELPinitialize();
|
|
|
|
{
|
|
WSADATA wsaData;
|
|
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
|
|
{
|
|
fatal("ERROR: Cannot initialize WinSock DLL.");
|
|
}
|
|
}
|
|
|
|
// allocate_standard_descriptor(STDIN_FILENO);
|
|
// allocate_standard_descriptor(STDOUT_FILENO);
|
|
// allocate_standard_descriptor(STDERR_FILENO);
|
|
|
|
// sfd_start = 3;
|
|
|
|
#else
|
|
|
|
__progname = ssh_get_progname(argv[0]);
|
|
log_init(__progname, log_level, log_facility, log_stderr);
|
|
|
|
#endif
|
|
pw = pwcopy(user_pw);
|
|
|
|
|
|
while (!skipargs && (ch = getopt(argc, argv,
|
|
"d:f:l:P:p:Q:u:cehR")) != -1) {
|
|
switch (ch) {
|
|
case 'Q':
|
|
if (strcasecmp(optarg, "requests") != 0) {
|
|
fprintf(stderr, "Invalid query type\n");
|
|
exit(1);
|
|
}
|
|
for (i = 0; handlers[i].handler != NULL; i++)
|
|
printf("%s\n", handlers[i].name);
|
|
for (i = 0; extended_handlers[i].handler != NULL; i++)
|
|
printf("%s\n", extended_handlers[i].name);
|
|
exit(0);
|
|
break;
|
|
case 'R':
|
|
readonly = 1;
|
|
break;
|
|
case 'c':
|
|
/*
|
|
* Ignore all arguments if we are invoked as a
|
|
* shell using "sftp-server -c command"
|
|
*/
|
|
skipargs = 1;
|
|
break;
|
|
case 'e':
|
|
log_stderr = 1;
|
|
break;
|
|
case 'l':
|
|
log_level = log_level_number(optarg);
|
|
if (log_level == SYSLOG_LEVEL_NOT_SET)
|
|
error("Invalid log level \"%s\"", optarg);
|
|
break;
|
|
case 'f':
|
|
log_facility = log_facility_number(optarg);
|
|
if (log_facility == SYSLOG_FACILITY_NOT_SET)
|
|
error("Invalid log facility \"%s\"", optarg);
|
|
break;
|
|
case 'd':
|
|
cp = tilde_expand_filename(optarg, user_pw->pw_uid);
|
|
homedir = percent_expand(cp, "d", user_pw->pw_dir,
|
|
"u", user_pw->pw_name, (char *)NULL);
|
|
free(cp);
|
|
break;
|
|
case 'p':
|
|
if (request_whitelist != NULL)
|
|
fatal("Permitted requests already set");
|
|
request_whitelist = xstrdup(optarg);
|
|
break;
|
|
case 'P':
|
|
if (request_blacklist != NULL)
|
|
fatal("Refused requests already set");
|
|
request_blacklist = xstrdup(optarg);
|
|
break;
|
|
case 'u':
|
|
errno = 0;
|
|
mask = strtol(optarg, &cp, 8);
|
|
if (mask < 0 || mask > 0777 || *cp != '\0' ||
|
|
cp == optarg || (mask == 0 && errno != 0))
|
|
fatal("Invalid umask \"%s\"", optarg);
|
|
(void)umask((mode_t)mask);
|
|
break;
|
|
case 'h':
|
|
default:
|
|
sftp_server_usage();
|
|
}
|
|
}
|
|
|
|
#ifndef WIN32_FIXME
|
|
log_init(__progname, log_level, log_facility, log_stderr);
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
|
|
/*
|
|
* On Linux, we should try to avoid making /proc/self/{mem,maps}
|
|
* available to the user so that sftp access doesn't automatically
|
|
* imply arbitrary code execution access that will break
|
|
* restricted configurations.
|
|
*/
|
|
if (prctl(PR_SET_DUMPABLE, 0) != 0)
|
|
fatal("unable to make the process undumpable");
|
|
#endif /* defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) */
|
|
|
|
if ((cp = getenv("SSH_CONNECTION")) != NULL) {
|
|
client_addr = xstrdup(cp);
|
|
if ((cp = strchr(client_addr, ' ')) == NULL) {
|
|
error("Malformed SSH_CONNECTION variable: \"%s\"",
|
|
getenv("SSH_CONNECTION"));
|
|
sftp_server_cleanup_exit(255);
|
|
}
|
|
*cp = '\0';
|
|
} else
|
|
client_addr = xstrdup("UNKNOWN");
|
|
|
|
logit("session opened for local user %s from [%s]",
|
|
pw->pw_name, client_addr);
|
|
|
|
in = STDIN_FILENO;
|
|
out = STDOUT_FILENO;
|
|
|
|
#ifdef WIN32_FIXME
|
|
//in = GetStdHandle(STD_INPUT_HANDLE);
|
|
//out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
//setmode(in, O_BINARY);
|
|
//setmode(out, O_BINARY);
|
|
in = STDIN_FILENO;
|
|
out = STDOUT_FILENO;
|
|
_setmode(in,O_BINARY); // avoid CrLf translations of text mode
|
|
_setmode(out,O_BINARY); // avoid CrLf translation
|
|
|
|
HANDLE in_handle = (HANDLE) _get_osfhandle (in);
|
|
#else
|
|
#ifdef HAVE_CYGWIN
|
|
setmode(in, O_BINARY);
|
|
setmode(out, O_BINARY);
|
|
#endif
|
|
#endif
|
|
|
|
max = 0;
|
|
if (in > max)
|
|
max = in;
|
|
if (out > max)
|
|
max = out;
|
|
|
|
if ((iqueue = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
if ((oqueue = sshbuf_new()) == NULL)
|
|
fatal("%s: sshbuf_new failed", __func__);
|
|
#ifdef WIN32_FIXME
|
|
//rset = (fd_set *)xmalloc(sizeof(fd_set));
|
|
//wset = (fd_set *)xmalloc(sizeof(fd_set));
|
|
#else
|
|
set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
|
|
rset = xmalloc(set_size);
|
|
wset = xmalloc(set_size);
|
|
#endif
|
|
|
|
|
|
if (homedir != NULL) {
|
|
if (chdir(homedir) != 0) {
|
|
error("chdir to \"%s\" failed: %s", homedir,
|
|
strerror(errno));
|
|
}
|
|
}
|
|
|
|
for (;;) {
|
|
#ifndef WIN32_FIXME
|
|
|
|
memset(rset, 0, set_size);
|
|
memset(wset, 0, set_size);
|
|
|
|
/*
|
|
* Ensure that we can read a full buffer and handle
|
|
* the worst-case length packet it can generate,
|
|
* otherwise apply backpressure by stopping reads.
|
|
*/
|
|
if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 &&
|
|
(r = sshbuf_check_reserve(oqueue,
|
|
SFTP_MAX_MSG_LENGTH)) == 0)
|
|
FD_SET(in, rset);
|
|
else if (r != SSH_ERR_NO_BUFFER_SPACE)
|
|
fatal("%s: sshbuf_check_reserve failed: %s",
|
|
__func__, ssh_err(r));
|
|
#endif
|
|
|
|
#ifdef WIN32_FIXME
|
|
|
|
int select_read = 0;
|
|
if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 &&
|
|
(r = sshbuf_check_reserve(oqueue,
|
|
SFTP_MAX_MSG_LENGTH)) == 0)
|
|
select_read = 1;
|
|
else if (r != SSH_ERR_NO_BUFFER_SPACE)
|
|
fatal("%s: sshbuf_check_reserve failed: %s",
|
|
__func__, ssh_err(r));
|
|
|
|
|
|
/* send oqueue to stdout */
|
|
olen = sshbuf_len(oqueue);
|
|
|
|
if (olen > 0) {
|
|
len = _write(out, sshbuf_ptr(oqueue), olen);
|
|
if (len < 0) {
|
|
error("write: %s", strerror(errno));
|
|
sftp_server_cleanup_exit(1);
|
|
} else if ((r = sshbuf_consume(oqueue, len)) != 0) {
|
|
fatal("%s: buffer error: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
}
|
|
|
|
/* copy stdin to iqueue */
|
|
if ( select_read ) {
|
|
if ( readsomemore || ( sshbuf_len(iqueue) <= 0)) {
|
|
len = _read(in, buf, sizeof buf);
|
|
if (len == 0) {
|
|
debug("read eof");
|
|
sftp_server_cleanup_exit(0);
|
|
} else if (len < 0) {
|
|
error("read: %s", strerror(errno));
|
|
sftp_server_cleanup_exit(1);
|
|
} else if ((r = sshbuf_put(iqueue, buf, len)) != 0) {
|
|
fatal("%s: buffer error: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Process requests from client if we can fit the results
|
|
* into the output buffer, otherwise stop processing input
|
|
* and let the output queue drain.
|
|
*/
|
|
r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH);
|
|
if (r == 0)
|
|
process();
|
|
else if (r != SSH_ERR_NO_BUFFER_SPACE)
|
|
fatal("%s: sshbuf_check_reserve: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
#else /* WIN32_FIXME */
|
|
|
|
olen = sshbuf_len(oqueue);
|
|
if (olen > 0)
|
|
FD_SET(out, wset);
|
|
|
|
if (select(max+1, rset, wset, NULL, NULL) < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
error("select: %s", strerror(errno));
|
|
sftp_server_cleanup_exit(2);
|
|
}
|
|
|
|
/* copy stdin to iqueue */
|
|
if (FD_ISSET(in, rset)) {
|
|
len = read(in, buf, sizeof buf);
|
|
if (len == 0) {
|
|
debug("read eof");
|
|
sftp_server_cleanup_exit(0);
|
|
} else if (len < 0) {
|
|
error("read: %s", strerror(errno));
|
|
sftp_server_cleanup_exit(1);
|
|
} else if ((r = sshbuf_put(iqueue, buf, len)) != 0) {
|
|
fatal("%s: buffer error: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
}
|
|
/* send oqueue to stdout */
|
|
if (FD_ISSET(out, wset)) {
|
|
len = write(out, sshbuf_ptr(oqueue), olen);
|
|
if (len < 0) {
|
|
error("write: %s", strerror(errno));
|
|
sftp_server_cleanup_exit(1);
|
|
} else if ((r = sshbuf_consume(oqueue, len)) != 0) {
|
|
fatal("%s: buffer error: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Process requests from client if we can fit the results
|
|
* into the output buffer, otherwise stop processing input
|
|
* and let the output queue drain.
|
|
*/
|
|
r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH);
|
|
if (r == 0)
|
|
process();
|
|
else if (r != SSH_ERR_NO_BUFFER_SPACE)
|
|
fatal("%s: sshbuf_check_reserve: %s",
|
|
__func__, ssh_err(r));
|
|
}
|
|
#endif /* else WIN32 */
|
|
}
|