sftp-server.c revision 6f8d59d8fcaf391990ca04c7bdcf65ab23320fe0
/*
* 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.
*/
/* $OpenBSD: sftp-server.c,v 1.71 2007/01/03 07:22:36 stevesk Exp $ */
#include "includes.h"
#ifdef HAVE_SYS_TIME_H
#endif
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <time.h>
#include <unistd.h>
#include <stdarg.h>
#include "xmalloc.h"
#include "buffer.h"
#include "bufaux.h"
#include "log.h"
#include "misc.h"
#include "uidswap.h"
#include "sftp.h"
#include "sftp-common.h"
#ifdef HAVE___PROGNAME
extern char *__progname;
#else
char *__progname;
#endif
/* helper */
void cleanup_exit(int i);
/* Our verbosity */
/* Our client */
char *client_addr = NULL;
/* input and output queue */
/* Version of client */
int version;
/* portable attributes, etc. */
struct Stat {
char *name;
char *long_name;
};
static int
{
int ret = 0;
switch (unixerrno) {
case 0:
ret = SSH2_FX_OK;
break;
case ENOENT:
case ENOTDIR:
case EBADF:
case ELOOP:
break;
case EPERM:
case EACCES:
case EFAULT:
break;
case ENAMETOOLONG:
case EINVAL:
break;
default:
break;
}
return ret;
}
static int
{
int flags = 0;
if ((pflags & SSH2_FXF_READ) &&
(pflags & SSH2_FXF_WRITE)) {
} else if (pflags & SSH2_FXF_READ) {
} else if (pflags & SSH2_FXF_WRITE) {
}
if (pflags & SSH2_FXF_CREAT)
if (pflags & SSH2_FXF_TRUNC)
if (pflags & SSH2_FXF_EXCL)
return flags;
}
static const char *
{
static char ret[128];
*ret = '\0';
if (*ret != '\0') \
}
if (pflags & SSH2_FXF_READ)
PAPPEND("READ")
if (pflags & SSH2_FXF_WRITE)
PAPPEND("WRITE")
if (pflags & SSH2_FXF_CREAT)
PAPPEND("CREATE")
if (pflags & SSH2_FXF_TRUNC)
PAPPEND("TRUNCATE")
if (pflags & SSH2_FXF_EXCL)
PAPPEND("EXCL")
return ret;
}
static Attrib *
get_attrib(void)
{
return decode_attrib(&iqueue);
}
/* handle handles */
struct Handle {
int use;
int fd;
char *name;
};
enum {
};
static void
handle_init(void)
{
u_int i;
}
static int
{
u_int i;
return i;
}
}
return -1;
}
static int
handle_is_ok(int i, int type)
{
}
static int
{
return -1;
return 0;
}
static int
{
int val;
return -1;
return val;
return -1;
}
static char *
handle_to_name(int handle)
{
return NULL;
}
static DIR *
handle_to_dir(int handle)
{
return NULL;
}
static int
handle_to_fd(int handle)
{
return -1;
}
static void
{
}
static void
{
}
static u_int64_t
handle_bytes_read(int handle)
{
return 0;
}
static u_int64_t
handle_bytes_write(int handle)
{
return 0;
}
static int
handle_close(int handle)
{
int ret = -1;
} else {
}
return ret;
}
static void
{
log("%s%sclose \"%s\" bytes read %llu written %llu",
(unsigned long long)handle_bytes_read(handle),
(unsigned long long)handle_bytes_write(handle));
} else {
log("%s%sclosedir \"%s\"",
}
}
static void
handle_log_exit(void)
{
u_int i;
handle_log_close(i, "forced");
}
static int
get_handle(void)
{
char *handle;
int val = -1;
if (hlen < 256)
return val;
}
/* send replies */
static void
{
int mlen = buffer_len(m);
buffer_consume(m, mlen);
}
static const char *
{
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 */
};
}
static void
{
if (log_level > SYSLOG_LEVEL_VERBOSE ||
buffer_init(&msg);
if (version >= 3) {
}
buffer_free(&msg);
}
static void
{
buffer_init(&msg);
buffer_free(&msg);
}
static void
{
}
static void
{
char *string;
int hlen;
}
static void
{
int i;
buffer_init(&msg);
for (i = 0; i < count; i++) {
}
buffer_free(&msg);
}
static void
{
buffer_init(&msg);
encode_attrib(&msg, a);
buffer_free(&msg);
}
/* parse incoming */
static void
process_init(void)
{
buffer_init(&msg);
buffer_free(&msg);
}
static void
process_open(void)
{
Attrib *a;
char *name;
a = get_attrib();
log("open \"%s\" flags %s mode 0%o",
if (fd < 0) {
} else {
if (handle < 0) {
} else {
status = SSH2_FX_OK;
}
}
if (status != SSH2_FX_OK)
}
static void
process_close(void)
{
handle = get_handle();
}
static void
process_read(void)
{
handle = get_handle();
debug("request %u: read \"%s\" (handle %d) off %llu len %d",
}
if (fd >= 0) {
error("process_read: seek failed");
} else {
if (ret < 0) {
} else if (ret == 0) {
} else {
status = SSH2_FX_OK;
}
}
}
if (status != SSH2_FX_OK)
}
static void
process_write(void)
{
char *data;
handle = get_handle();
debug("request %u: write \"%s\" (handle %d) off %llu len %d",
if (fd >= 0) {
error("process_write: seek failed");
} else {
/* XXX ATOMICIO ? */
if (ret < 0) {
error("process_write: write failed");
status = SSH2_FX_OK;
} else {
debug2("nothing at all written");
}
}
}
}
static void
process_do_stat(int do_lstat)
{
Attrib a;
char *name;
if (ret < 0) {
} else {
stat_to_attrib(&st, &a);
send_attrib(id, &a);
status = SSH2_FX_OK;
}
if (status != SSH2_FX_OK)
}
static void
process_stat(void)
{
process_do_stat(0);
}
static void
process_lstat(void)
{
process_do_stat(1);
}
static void
process_fstat(void)
{
Attrib a;
handle = get_handle();
debug("request %u: fstat \"%s\" (handle %u)",
if (fd >= 0) {
if (ret < 0) {
} else {
stat_to_attrib(&st, &a);
send_attrib(id, &a);
status = SSH2_FX_OK;
}
}
if (status != SSH2_FX_OK)
}
static struct timeval *
attrib_to_tv(const Attrib *a)
{
return tv;
}
static void
process_setstat(void)
{
Attrib *a;
char *name;
a = get_attrib();
if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
log("set \"%s\" size %llu",
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
char buf[64];
localtime(&t));
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
if (ret == -1)
}
}
static void
process_fsetstat(void)
{
Attrib *a;
int status = SSH2_FX_OK;
handle = get_handle();
a = get_attrib();
if (fd < 0) {
} else {
if (a->flags & SSH2_FILEXFER_ATTR_SIZE) {
log("set \"%s\" size %llu",
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
#ifdef HAVE_FCHMOD
#else
#endif
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
char buf[64];
localtime(&t));
#ifdef HAVE_FUTIMES
#else
#endif
if (ret == -1)
}
if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) {
#ifdef HAVE_FCHOWN
#else
#endif
if (ret == -1)
}
}
}
static void
process_opendir(void)
{
char *path;
} else {
if (handle < 0) {
} else {
status = SSH2_FX_OK;
}
}
if (status != SSH2_FX_OK)
}
static void
process_readdir(void)
{
char *path;
int handle;
handle = get_handle();
} else {
char pathname[MAXPATHLEN];
nstats *= 2;
}
/* XXX OVERFLOW ? */
continue;
count++;
/* send up to 100 entries in one message */
/* XXX check packet size instead */
if (count == 100)
break;
}
if (count > 0) {
for (i = 0; i < count; i++) {
}
} else {
}
}
}
static void
process_remove(void)
{
char *name;
int status = SSH2_FX_FAILURE;
int ret;
}
static void
process_mkdir(void)
{
Attrib *a;
char *name;
a = get_attrib();
}
static void
process_rmdir(void)
{
char *name;
}
static void
process_realpath(void)
{
char resolvedname[MAXPATHLEN];
char *path;
if (path[0] == '\0') {
}
} else {
Stat s;
attrib_clear(&s.attrib);
}
}
static void
process_rename(void)
{
int status;
/* Race-free rename of regular files */
if (errno == EOPNOTSUPP
#ifdef LINK_OPNOTSUPP_ERRNO
|| errno == LINK_OPNOTSUPP_ERRNO
#endif
) {
/*
* fs doesn't support links, so fall back to
* stat+rename. This is racy.
*/
status =
else
status = SSH2_FX_OK;
}
} else {
}
/* clean spare link */
} else
status = SSH2_FX_OK;
else
status = SSH2_FX_OK;
}
}
static void
process_readlink(void)
{
int len;
char buf[MAXPATHLEN];
char *path;
else {
Stat s;
attrib_clear(&s.attrib);
}
}
static void
process_symlink(void)
{
/* this will fail if 'newpath' exists */
}
static void
process_extended(void)
{
char *request;
}
/* stolen from ssh-agent */
static void
process(void)
{
if (buf_len < 5)
return; /* Incomplete message. */
if (msg_len > SFTP_MAX_MSG_LENGTH) {
error("bad message from %s local user %s",
cleanup_exit(11);
}
return;
buf_len -= 4;
switch (type) {
case SSH2_FXP_INIT:
process_init();
break;
case SSH2_FXP_OPEN:
process_open();
break;
case SSH2_FXP_CLOSE:
break;
case SSH2_FXP_READ:
process_read();
break;
case SSH2_FXP_WRITE:
break;
case SSH2_FXP_LSTAT:
break;
case SSH2_FXP_FSTAT:
break;
case SSH2_FXP_SETSTAT:
break;
case SSH2_FXP_FSETSTAT:
break;
case SSH2_FXP_OPENDIR:
break;
case SSH2_FXP_READDIR:
break;
case SSH2_FXP_REMOVE:
break;
case SSH2_FXP_MKDIR:
break;
case SSH2_FXP_RMDIR:
break;
case SSH2_FXP_REALPATH:
break;
case SSH2_FXP_STAT:
process_stat();
break;
case SSH2_FXP_RENAME:
break;
case SSH2_FXP_READLINK:
break;
case SSH2_FXP_SYMLINK:
break;
case SSH2_FXP_EXTENDED:
break;
default:
break;
}
/* discard the remaining bytes from the current packet */
fatal("iqueue grew unexpectedly");
}
/*
* Cleanup handler that logs active handles upon normal exit. Not static since
* sftp-server-main.c file needs that as well.
*/
void
cleanup_exit(int i)
{
log("session closed for local user %s from [%s]",
}
_exit(i);
}
static void
usage(void)
{
"Usage: %s [-he] [-l log_level] [-f log_facility]\n", __progname);
exit(1);
}
int
{
extern char *optarg;
switch (ch) {
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':
if (log_level == SYSLOG_LEVEL_NOT_SET)
break;
case 'f':
if (log_facility == SYSLOG_FACILITY_NOT_SET)
break;
case 'h':
default:
usage();
}
}
fatal("Malformed SSH_CONNECTION variable: \"%s\"",
getenv("SSH_CONNECTION"));
*cp = '\0';
} else
log("session opened for local user %s from [%s]",
handle_init();
#ifdef HAVE_CYGWIN
#endif
max = 0;
for (;;) {
/*
* 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 (olen > 0)
continue;
cleanup_exit(2);
}
/* copy stdin to iqueue */
if (len == 0) {
debug("read eof");
cleanup_exit(0);
} else if (len < 0) {
cleanup_exit(1);
} else {
}
}
/* send oqueue to stdout */
if (len < 0) {
cleanup_exit(1);
} else {
}
}
/*
* Process requests from client if we can fit the results
* into the output buffer, otherwise stop processing input
* and let the output queue drain.
*/
process();
}
/* NOTREACHED */
return (0);
}