Commit 2226927f authored by Pontus Sköld's avatar Pontus Sköld Committed by Niels Möller
Browse files

* src/sftp/sftp-server.c: Merged code from Pontus Sköld.

(sftp_put_longname_mode): New function.
(sftp_put_longname): New function.
(sftp_put_filename): New function.
(getuser): New dummy function.
(getgroup): New dummy function.

Rev: src/sftp/sftp-server.c:1.3
parent 611a27a9
/* sftp-server.c
*
*/
* $Id$
*
* The server side of the sftp subsystem. */
/* lsh, an implementation of the ssh protocol
*
* Copyright (C) 2001 Niels Mller, Pontus Skld
*
* Also includes parts from GNU fileutils, Copyright by Free Software
* Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include "buffer.h"
#include "sftp.h"
#include "filemode.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
......@@ -15,145 +40,147 @@
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#define SFTP_VERSION 3
#define FATAL(x) do { fputs("sftp-server: " x "\n", stderr); exit(EXIT_FAILURE); } while (0)
struct sftp_attrib
void sftp_attrib_from_stat( struct stat *st, struct sftp_attrib* a)
{
UINT32 flags;
UINT64 size;
UINT32 uid;
UINT32 gid;
UINT32 permissions;
/* NOTE: The representations of times is about to change. */
UINT32 atime;
UINT32 mtime;
};
a->permissions=st->st_mode;
a->uid=st->st_uid;
a->gid=st->st_gid;
a->atime=st->st_atime;
a->mtime=st->st_mtime;
a->size=st->st_size;
a->flags= ( SSH_FILEXFER_ATTR_SIZE ||
SSH_FILEXFER_ATTR_UIDGID ||
SSH_FILEXFER_ATTR_PERMISSIONS ||
SSH_FILEXFER_ATTR_ACMODTIME
);
}
static void
sftp_clear_attrib(struct sftp_attrib *a)
{
a->flags = 0;
a->size = 0;
a->uid = 0;
a->gid = 0;
a->permissions = 0;
a->atime = 0;
a->mtime = 0;
};
int
sftp_skip_extension(struct sftp_input *i)
sftp_put_longname_mode(struct sftp_output *o, struct stat *st)
{
UINT32 length;
UINT8 *data;
unsigned j;
/* A 10 character modestring and a space */
UINT8 modes[MODE_STRING_LENGTH];
/* Skip name and value*/
for (j = 0; j<2; j++)
{
if (!(data = sftp_get_string(i, &length)))
return 0;
filemodestring(st, modes);
sftp_free_string(data);
}
return 1;
sftp_put_data(o, sizeof(modes), modes);
}
int
sftp_get_attrib(struct sftp_input *i, struct sftp_attrib *a)
{
sftp_clear_attrib(a);
/* FIXME: Replace these dummy functions with the functions from
* fileutil's lib/idcache.c. */
static const char *
getuser(uid_t uid)
{ return NULL; }
if (!sftp_get_uint32(i, &a->flags))
return 0;
static const char *
getgroup(gid_t gid)
{ return NULL; }
if (a->flags & SSH_FILEXFER_ATTR_SIZE)
{
if (!sftp_get_uint64(i, &a->size))
return 0;
}
static void
sftp_put_longname(struct sftp_output *o,
struct stat *st, UINT8* fname)
{
/* NOTE: The current spec doesn't mandate utf8. */
/* Where to store the length. */
UINT32 length_index = sftp_put_reserve_length(o);
const char *user_name;
const char *group_name;
time_t now, when;
struct tm *when_local;
const char *time_format;
sftp_put_longname_mode(o, st);
sftp_put_printf(o, " %3u ", (unsigned) st->st_nlink);
user_name = getuser(st->st_uid);
if (user_name)
sftp_put_printf(o, "%-8.8s ", user_name);
else
sftp_put_printf(o, "%-8u ", (unsigned int) st->st_uid);
if (a->flags & SSH_FILEXFER_ATTR_UIDGID)
{
if (!sftp_get_uint32(i, &a->uid))
return 0;
group_name = getgroup(st->st_gid);
if (group_name)
sftp_put_printf(o, "%-8.8s ", group_name);
else
sftp_put_printf(o, "%-8u ", (unsigned) st->st_gid);
if (!sftp_get_uint32(i, &a->gid))
return 0;
}
/* FIXME: How to deal with long long sizes? */
sftp_put_printf(o, "%8lu ", (unsigned long) st->st_size);
if (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
{
if (!sftp_get_uint32(i, &a->permissions))
return 0;
}
now = time(NULL);
when = st->st_mtime;
if (a->flags & SSH_FILEXFER_ATTR_ACMODTIME)
{
if (!sftp_get_uint32(i, &a->atime))
return 0;
when_local = localtime( &st->st_mtime );
if (!sftp_get_uint32(i, &a->mtime))
return 0;
}
if ( (now > when + 6L * 30L * 24L * 60L * 60L) /* Old. */
|| (now < when - 60L * 60L) ) /* In the future. */
/* The file is fairly old or in the future.
POSIX says the cutoff is 6 months old;
approximate this by 6*30 days.
Allow a 1 hour slop factor for what is considered "the future",
to allow for NFS server/client clock disagreement.
Show the year instead of the time of day. */
time_format = "%b %e %Y";
else
time_format = "%b %e %H:%M";
if (a->flags & SSH_FILEXFER_ATTR_EXTENDED)
{
UINT32 count;
UINT32 n;
sftp_put_strftime(o, 12, time_format, when_local);
if (!sftp_get_uint32(i, &count))
return 0;
sftp_put_printf(o, " %s", fname);
/* Just skip the extensions */
for (n = 0; n < count; n++)
if (!sftp_skip_extension(i))
return 0;
}
return 1;
sftp_put_final_length(o, length_index);
}
void
sftp_put_attrib(struct sftp_output *o, const struct sftp_attrib *a)
static void
sftp_put_filename(struct sftp_output *o,
struct stat *st,
const char *name)
{
assert(!a->flags & SSH_FILEXFER_ATTR_EXTENDED);
sftp_put_uint32(o, a->flags);
struct sftp_attrib a;
if (a->flags & SSH_FILEXFER_ATTR_SIZE)
sftp_put_uint64(o, a->size);
sftp_attrib_from_stat( st, &a);
sftp_put_string(o, strlen(name), name);
sftp_put_longname(o, st, name);
sftp_put_attrib(o, &a);
}
if (a->flags & SSH_FILEXFER_ATTR_UIDGID)
{
sftp_put_uint32(o, a->uid);
sftp_put_uint32(o, a->gid);
}
#define SFTP_MAX_HANDLES 200
if (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
sftp_put_uint32(o, a->permissions);
struct sftp_handle
{
enum sftp_handle_type
{ HANDLE_UNUSED = 0, HANDLE_FILE, HANDLE_DIR } type;
if (a->flags & SSH_FILEXFER_ATTR_ACMODTIME)
union
{
sftp_put_uint32(o, a->atime);
sftp_put_uint32(o, a->mtime);
}
}
#define SFTP_MAX_FDS 200
int fd;
DIR *dir;
} u;
};
#if 0
/* A handle is simply an fd */
#define handle_t UINT32
#else
#define handle_t struct sftp_handle *
#endif
struct sftp_ctx
{
struct sftp_input *i;
struct sftp_output *o;
enum sftp_handle_type
{ HANDLE_UNUSED = 0, HANDLE_FILE, HANDLE_DIR } handles[SFTP_MAX_FDS];
struct sftp_handle handles[SFTP_MAX_HANDLES];
};
void
......@@ -172,15 +199,15 @@ sftp_init(struct sftp_ctx *ctx, FILE *in, FILE *out)
ctx->i = input;
ctx->o = output;
for (i = 0; i < SFTP_MAX_FDS; i++)
ctx->handles[i] = HANDLE_UNUSED;
for (i = 0; i < SFTP_MAX_HANDLES; i++)
ctx->handles[i].type = HANDLE_UNUSED;
}
int
sftp_handle_used(struct sftp_ctx *ctx, handle_t handle)
{
return (handle < SFTP_MAX_FDS)
&& (ctx->handles[handle] != HANDLE_UNUSED);
return (handle < SFTP_MAX_HANDLES)
&& (ctx->handles[handle].type != HANDLE_UNUSED);
}
void
......@@ -188,8 +215,8 @@ sftp_register_handle(struct sftp_ctx *ctx,
UINT32 handle,
enum sftp_handle_type type)
{
assert(handle < SFTP_MAX_FDS);
assert(ctx->handles[handle] == HANDLE_UNUSED);
assert(handle < SFTP_MAX_HANDLES);
assert(ctx->handles[handle].type == HANDLE_UNUSED);
assert(type != HANDLE_UNUSED);
ctx->handles[handle] = type;
......@@ -271,6 +298,397 @@ sftp_check_filename(UINT32 length, const UINT8 *data)
return !memchr(data, 0, length);
}
static int
sftp_process_opendir(struct sftp_ctx *ctx)
{
UINT32 length;
UINT8 *name;
DIR* dirhandle;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name)))
{
sftp_free_string(name);
return sftp_send_status(ctx, SSH_FX_FAILURE);
}
/* Fixme; Perhaps we should have a sftp_mangle_fname? If not, we
have to handle an empty filename here */
dirhandle=opendir(length ? name : ".");
sftp_free_string(name);
if ( !dirhandle )
return sftp_send_errno(ctx);
/* Open successful */
/* Fixme; we need to redo how handles work, perhaps a struct
consisting of the type, an int (for an fd) and a DIR* (for a
directory). I will look into this later (or we could skip using
opendir/readdir/closedir, but I think that's not better */
sftp_register_handle(ctx, dirhandle, HANDLE_DIR);
sftp_put_handle(ctx, dirhandle);
}
static int
sftp_process_readdir(struct sftp_ctx *ctx)
{
handle_t handle;
struct dirent* direntry;
struct stat st;
if ( !sftp_get_handle(ctx->i, &handle) ||
ctx->handles[handle] != HANDLE_DIR
)
return sftp_bad_message(ctx);
direntry=readdir(handle);
/* Fixme; we need to redo how handles work, perhaps a struct
consisting of the type, an int (for an fd) and a DIR* (for a
directory). I will look into this later (or we could skip using
opendir/readdir/closedir, but I think that's not better */
if ( !direntry )
return (errno ? sftp_send_errno(ctx)
: sftp_send_status(ctx, SSH_FX_EOF));
/* Fixme; concat name */
if( lstat(direntry->d_name, &st ) )
return sftp_send_errno(ctx);
/* Fixme; we don't have to, but maybe we should be nice and pass
several at once? It might improve performance quite a lot (or it
might not)
*/
/* Use count == 1 for now. */
sftp_put_uint32(ctx->o, 1);
sftp_put_filename(ctx->o, &st, direntry->d_name);
sftp_set_msg(ctx->o, SSH_FXP_NAME );
return 1;
}
static int
sftp_process_stat(struct sftp_ctx *ctx)
{
struct stat st;
struct sftp_attrib a;
UINT32 length;
UINT8 *name;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname ? */
/* Fixme; concat name */
if ( stat(name, &st ) )
return sftp_send_errno(ctx);
sftp_attrib_from_stat( &st, &a);
sftp_set_msg( ctx->o, SSH_FXP_ATTRS );
sftp_put_attrib( ctx->o, &a);
return 1;
}
static int
sftp_process_lstat(struct sftp_ctx *ctx)
{
struct stat st;
struct sftp_attrib a;
UINT32 length;
UINT8 *name;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname ? */
/* Fixme; concat name */
if ( lstat(name, &st ) )
return sftp_send_errno(ctx);
sftp_attrib_from_stat( &st, &a );
sftp_set_msg( ctx->o, SSH_FXP_ATTRS );
sftp_put_attrib( ctx->o, &a);
return 1;
}
static int
sftp_process_fstat(struct sftp_ctx *ctx)
{
struct stat st;
struct sftp_attrib a;
handle_t handle;
if ( sftp_get_handle(ctx->i, &handle) )
if ( ctx->handles[handle] == HANDLE_FILE )
{
if ( fstat(handle, &st ) )
return sftp_send_errno(ctx);
sftp_attrib_from_stat(&st,&a);
sftp_set_msg( ctx->o, SSH_FXP_ATTRS );
sftp_put_attrib( ctx->o, &a);
return 1;
}
return sftp_bad_message(ctx);
}
static int
sftp_process_fsetstat(struct sftp_ctx *ctx)
{
struct sftp_attrib a;
handle_t handle;
if ( sftp_get_handle(ctx->i, &handle) &&
sftp_get_attrib(ctx->i, &a)
)
if ( ! (ctx->handles[handle] == HANDLE_FILE ))
return sftp_bad_message(ctx);
/* Fixme; set stat */
if ( a.flags & SSH_FILEXFER_ATTR_UIDGID )
if ( fchown( handle, a.uid, a.gid ) )
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_SIZE )
if( ftruncate( handle, a.size ) )
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_PERMISSIONS )
if( fchmod( handle, a.permissions ) )
/* Fixme; Perhaps we should mask it */
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_EXTENDED ||
a.flags & SSH_FILEXFER_ATTR_ACMODTIME ) /* Fixme; how do we? */
return sftp_send_status(ctx, SSH_FX_OP_UNSUPPORTED );
return sftp_send_status(ctx, SSH_FX_OK);
}
static int
sftp_process_setstat(struct sftp_ctx *ctx)
{
struct sftp_attrib a;
UINT32 length;
UINT8 *name;
if ( (! (name = sftp_get_string(ctx->i, &length))) ||
(!sftp_get_attrib(ctx->i, &a))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname ? */
if ( a.flags & SSH_FILEXFER_ATTR_UIDGID )
if ( chown( name, a.uid, a.gid ) )
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_SIZE )
if( truncate( name, a.size ) )
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_PERMISSIONS )
if( chmod( name, a.permissions ) ) /* Fixme; Perhaps we should mask it */
return sftp_send_errno(ctx);
if ( a.flags & SSH_FILEXFER_ATTR_EXTENDED ||
a.flags & SSH_FILEXFER_ATTR_ACMODTIME ) /* Fixme; how do we? */
return sftp_send_status(ctx, SSH_FX_OP_UNSUPPORTED );
return sftp_send_status(ctx, SSH_FX_OK);
}
static int
sftp_process_remove(struct sftp_ctx *ctx)
{
UINT32 length;
UINT8 *name;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname ? */
if( ! unlink(name) )
return sftp_send_errno(ctx);
else
return sftp_send_status(ctx, SSH_FX_OK);
}
static int
sftp_process_mkdir(struct sftp_ctx *ctx)
{
UINT32 length;
UINT8 *name;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname? If not, we
have to handle an empty filename here */
if( ! mkdir(name, 0755) ) /* Fixme; default permissions ? */
return sftp_send_errno(ctx);
else
return sftp_send_status(ctx, SSH_FX_OK);
}
static int
sftp_process_rmdir(struct sftp_ctx *ctx)
{
UINT32 length;
UINT8 *name;
if ( ! (name = sftp_get_string(ctx->i, &length))
)
return sftp_bad_message(ctx);
if( (! sftp_check_filename(length, name))
)
return sftp_send_status(ctx, SSH_FX_FAILURE);
/* Fixme; Perhaps we should have a sftp_mangle_fname? If not, we
need to handle an empty filename here */
if( ! rmdir(name) )
return sftp_send_errno(ctx);
else
return sftp_send_status(ctx, SSH_FX_OK);
}
static int
sftp_process_realpath(struct sftp_ctx *ctx)
{