X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/f546c7809836bd6d7fce818781a88166fe50cded..3a7fe5ba48d97d03ebb4350c1951ae2b477ee237:/sftp-server.c diff --git a/sftp-server.c b/sftp-server.c index 40a53531..17808457 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -22,15 +22,16 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" -RCSID("$OpenBSD: sftp-server.c,v 1.10 2001/01/10 22:56:22 markus Exp $"); +RCSID("$OpenBSD: sftp-server.c,v 1.23 2001/03/07 10:11:23 djm Exp $"); -#include "ssh.h" #include "buffer.h" #include "bufaux.h" #include "getput.h" +#include "log.h" #include "xmalloc.h" #include "sftp.h" +#include "sftp-common.h" /* helper */ #define get_int64() buffer_get_int64(&iqueue); @@ -48,24 +49,14 @@ char *__progname; Buffer iqueue; Buffer oqueue; +/* Version of client */ +int version; + /* portable attibutes, etc. */ -typedef struct Attrib Attrib; typedef struct Stat Stat; -struct Attrib -{ - u_int32_t flags; - u_int64_t size; - u_int32_t uid; - u_int32_t gid; - u_int32_t perm; - u_int32_t atime; - u_int32_t mtime; -}; - -struct Stat -{ +struct Stat { char *name; char *long_name; Attrib attrib; @@ -75,6 +66,7 @@ int errno_to_portable(int unixerrno) { int ret = 0; + switch (unixerrno) { case 0: ret = SSH2_FX_OK; @@ -105,8 +97,9 @@ int flags_from_portable(int pflags) { int flags = 0; - if (pflags & SSH2_FXF_READ && - pflags & SSH2_FXF_WRITE) { + + if ((pflags & SSH2_FXF_READ) && + (pflags & SSH2_FXF_WRITE)) { flags = O_RDWR; } else if (pflags & SSH2_FXF_READ) { flags = O_RDONLY; @@ -122,92 +115,6 @@ flags_from_portable(int pflags) return flags; } -void -attrib_clear(Attrib *a) -{ - a->flags = 0; - a->size = 0; - a->uid = 0; - a->gid = 0; - a->perm = 0; - a->atime = 0; - a->mtime = 0; -} - -Attrib * -decode_attrib(Buffer *b) -{ - static Attrib a; - attrib_clear(&a); - a.flags = buffer_get_int(b); - if (a.flags & SSH2_FILEXFER_ATTR_SIZE) { - a.size = buffer_get_int64(b); - } - if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { - a.uid = buffer_get_int(b); - a.gid = buffer_get_int(b); - } - if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { - a.perm = buffer_get_int(b); - } - if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { - a.atime = buffer_get_int(b); - a.mtime = buffer_get_int(b); - } - /* vendor-specific extensions */ - if (a.flags & SSH2_FILEXFER_ATTR_EXTENDED) { - char *type, *data; - int i, count; - count = buffer_get_int(b); - for (i = 0; i < count; i++) { - type = buffer_get_string(b, NULL); - data = buffer_get_string(b, NULL); - xfree(type); - xfree(data); - } - } - return &a; -} - -void -encode_attrib(Buffer *b, Attrib *a) -{ - buffer_put_int(b, a->flags); - if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { - buffer_put_int64(b, a->size); - } - if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { - buffer_put_int(b, a->uid); - buffer_put_int(b, a->gid); - } - if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { - buffer_put_int(b, a->perm); - } - if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { - buffer_put_int(b, a->atime); - buffer_put_int(b, a->mtime); - } -} - -Attrib * -stat_to_attrib(struct stat *st) -{ - static Attrib a; - attrib_clear(&a); - a.flags = 0; - a.flags |= SSH2_FILEXFER_ATTR_SIZE; - a.size = st->st_size; - a.flags |= SSH2_FILEXFER_ATTR_UIDGID; - a.uid = st->st_uid; - a.gid = st->st_gid; - a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; - a.perm = st->st_mode; - a.flags |= SSH2_FILEXFER_ATTR_ACMODTIME; - a.atime = st->st_atime; - a.mtime = st->st_mtime; - return &a; -} - Attrib * get_attrib(void) { @@ -223,17 +130,20 @@ struct Handle { int fd; char *name; }; + enum { HANDLE_UNUSED, HANDLE_DIR, HANDLE_FILE }; + Handle handles[100]; void handle_init(void) { int i; + for(i = 0; i < sizeof(handles)/sizeof(Handle); i++) handles[i].use = HANDLE_UNUSED; } @@ -242,6 +152,7 @@ int handle_new(int use, char *name, int fd, DIR *dirp) { int i; + for(i = 0; i < sizeof(handles)/sizeof(Handle); i++) { if (handles[i].use == HANDLE_UNUSED) { handles[i].use = use; @@ -264,24 +175,22 @@ handle_is_ok(int i, int type) int handle_to_string(int handle, char **stringp, int *hlenp) { - char buf[1024]; if (stringp == NULL || hlenp == NULL) return -1; - snprintf(buf, sizeof buf, "%d", handle); - *stringp = xstrdup(buf); - *hlenp = strlen(*stringp); + *stringp = xmalloc(sizeof(int32_t)); + PUT_32BIT(*stringp, handle); + *hlenp = sizeof(int32_t); return 0; } int handle_from_string(char *handle, u_int hlen) { -/* XXX OVERFLOW ? */ - char *ep; - long lval = strtol(handle, &ep, 10); - int val = lval; - if (*ep != '\0') + int val; + + if (hlen != sizeof(int32_t)) return -1; + val = GET_32BIT(handle); if (handle_is_ok(val, HANDLE_FILE) || handle_is_ok(val, HANDLE_DIR)) return val; @@ -308,7 +217,7 @@ handle_to_dir(int handle) int handle_to_fd(int handle) { - if (handle_is_ok(handle, HANDLE_FILE)) + if (handle_is_ok(handle, HANDLE_FILE)) return handles[handle].fd; return -1; } @@ -317,6 +226,7 @@ int handle_close(int handle) { int ret = -1; + if (handle_is_ok(handle, HANDLE_FILE)) { ret = close(handles[handle].fd); handles[handle].use = HANDLE_UNUSED; @@ -335,6 +245,7 @@ get_handle(void) char *handle; int val = -1; u_int hlen; + handle = get_string(&hlen); if (hlen < 256) val = handle_from_string(handle, hlen); @@ -348,6 +259,7 @@ void send_msg(Buffer *m) { int mlen = buffer_len(m); + buffer_put_int(&oqueue, mlen); buffer_append(&oqueue, buffer_ptr(m), mlen); buffer_consume(m, mlen); @@ -357,11 +269,29 @@ void send_status(u_int32_t id, u_int32_t error) { Buffer msg; + 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 */ + }; + TRACE("sent status id %d error %d", id, error); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_STATUS); buffer_put_int(&msg, id); buffer_put_int(&msg, error); + if (version >= 3) { + buffer_put_cstring(&msg, + status_messages[MIN(error,SSH2_FX_MAX)]); + buffer_put_cstring(&msg, ""); + } send_msg(&msg); buffer_free(&msg); } @@ -369,6 +299,7 @@ void send_data_or_handle(char type, u_int32_t id, char *data, int dlen) { Buffer msg; + buffer_init(&msg); buffer_put_char(&msg, type); buffer_put_int(&msg, id); @@ -389,6 +320,7 @@ send_handle(u_int32_t id, int handle) { char *string; int hlen; + handle_to_string(handle, &string, &hlen); TRACE("sent handle id %d handle %d", id, handle); send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); @@ -400,6 +332,7 @@ send_names(u_int32_t id, int count, Stat *stats) { Buffer msg; int i; + buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_NAME); buffer_put_int(&msg, id); @@ -418,6 +351,7 @@ void send_attrib(u_int32_t id, Attrib *a) { Buffer msg; + TRACE("sent attrib id %d have 0x%x", id, a->flags); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_ATTRS); @@ -433,8 +367,8 @@ void process_init(void) { Buffer msg; - int version = buffer_get_int(&iqueue); + version = buffer_get_int(&iqueue); TRACE("client version %d", version); buffer_init(&msg); buffer_put_char(&msg, SSH2_FXP_VERSION); @@ -502,7 +436,8 @@ process_read(void) off = get_int64(); len = get_int(); - TRACE("read id %d handle %d off %lld len %d", id, handle, off, len); + TRACE("read id %d handle %d off %llu len %d", id, handle, + (u_int64_t)off, len); if (len > sizeof buf) { len = sizeof buf; log("read change len %d", len); @@ -542,7 +477,8 @@ process_write(void) off = get_int64(); data = get_string(&len); - TRACE("write id %d handle %d off %lld len %d", id, handle, off, len); + TRACE("write id %d handle %d off %llu len %d", id, handle, + (u_int64_t)off, len); fd = handle_to_fd(handle); if (fd >= 0) { if (lseek(fd, off, SEEK_SET) < 0) { @@ -568,7 +504,7 @@ process_write(void) void process_do_stat(int do_lstat) { - Attrib *a; + Attrib a; struct stat st; u_int32_t id; char *name; @@ -581,8 +517,8 @@ process_do_stat(int do_lstat) if (ret < 0) { status = errno_to_portable(errno); } else { - a = stat_to_attrib(&st); - send_attrib(id, a); + stat_to_attrib(&st, &a); + send_attrib(id, &a); status = SSH2_FX_OK; } if (status != SSH2_FX_OK) @@ -605,7 +541,7 @@ process_lstat(void) void process_fstat(void) { - Attrib *a; + Attrib a; struct stat st; u_int32_t id; int fd, ret, handle, status = SSH2_FX_FAILURE; @@ -619,8 +555,8 @@ process_fstat(void) if (ret < 0) { status = errno_to_portable(errno); } else { - a = stat_to_attrib(&st); - send_attrib(id, a); + stat_to_attrib(&st, &a); + send_attrib(id, &a); status = SSH2_FX_OK; } } @@ -632,6 +568,7 @@ struct timeval * attrib_to_tv(Attrib *a) { static struct timeval tv[2]; + tv[0].tv_sec = a->atime; tv[0].tv_usec = 0; tv[1].tv_sec = a->mtime; @@ -662,6 +599,11 @@ process_setstat(void) if (ret == -1) status = errno_to_portable(errno); } + if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + ret = chown(name, a->uid, a->gid); + if (ret == -1) + status = errno_to_portable(errno); + } send_status(id, status); xfree(name); } @@ -674,18 +616,22 @@ process_fsetstat(void) int handle, fd, ret; int status = SSH2_FX_OK; char *name; - + id = get_int(); handle = get_handle(); a = get_attrib(); TRACE("fsetstat id %d handle %d", id, handle); fd = handle_to_fd(handle); name = handle_to_name(handle); - if ((fd < 0) || (name == NULL)) { + if (fd < 0 || name == NULL) { status = SSH2_FX_FAILURE; } else { if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { +#ifdef HAVE_FCHMOD ret = fchmod(fd, a->perm & 0777); +#else + ret = chmod(name, a->perm & 0777); +#endif if (ret == -1) status = errno_to_portable(errno); } @@ -694,6 +640,15 @@ process_fsetstat(void) ret = futimes(fd, attrib_to_tv(a)); #else ret = utimes(name, attrib_to_tv(a)); +#endif + if (ret == -1) + status = errno_to_portable(errno); + } + if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { +#ifdef HAVE_FCHOWN + ret = fchown(fd, a->uid, a->gid); +#else + ret = chown(name, a->uid, a->gid); #endif if (ret == -1) status = errno_to_portable(errno); @@ -713,7 +668,7 @@ process_opendir(void) id = get_int(); path = get_string(NULL); TRACE("opendir id %d path %s", id, path); - dirp = opendir(path); + dirp = opendir(path); if (dirp == NULL) { status = errno_to_portable(errno); } else { @@ -724,7 +679,7 @@ process_opendir(void) send_handle(id, handle); status = SSH2_FX_OK; } - + } if (status != SSH2_FX_OK) send_status(id, status); @@ -732,18 +687,41 @@ process_opendir(void) } /* - * XXX, draft-ietf-secsh-filexfer-00.txt says: - * The recommended format for the longname field is as follows: - * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer - * 1234567890 123 12345678 12345678 12345678 123456789012 + * drwxr-xr-x 5 markus markus 1024 Jan 13 18:39 .ssh */ char * ls_file(char *name, struct stat *st) { - char buf[1024]; - snprintf(buf, sizeof buf, "0%o %d %d %lld %d %s", - st->st_mode, st->st_uid, st->st_gid, (long long)st->st_size, - (int)st->st_mtime, name); + int sz = 0; + struct passwd *pw; + struct group *gr; + struct tm *ltime = localtime(&st->st_mtime); + char *user, *group; + char buf[1024], mode[11+1], tbuf[12+1], ubuf[11+1], gbuf[11+1]; + + strmode(st->st_mode, mode); + if ((pw = getpwuid(st->st_uid)) != NULL) { + user = pw->pw_name; + } else { + snprintf(ubuf, sizeof ubuf, "%d", st->st_uid); + user = ubuf; + } + if ((gr = getgrgid(st->st_gid)) != NULL) { + group = gr->gr_name; + } else { + snprintf(gbuf, sizeof gbuf, "%d", st->st_gid); + group = gbuf; + } + if (ltime != NULL) { + if (time(NULL) - st->st_mtime < (365*24*60*60)/2) + sz = strftime(tbuf, sizeof tbuf, "%b %e %H:%M", ltime); + else + sz = strftime(tbuf, sizeof tbuf, "%b %e %Y", ltime); + } + if (sz == 0) + tbuf[0] = '\0'; + snprintf(buf, sizeof buf, "%s %3d %-8.8s %-8.8s %8llu %s %s", mode, + st->st_nlink, user, group, (u_int64_t)st->st_size, tbuf, name); return xstrdup(buf); } @@ -764,7 +742,6 @@ process_readdir(void) if (dirp == NULL || path == NULL) { send_status(id, SSH2_FX_FAILURE); } else { - Attrib *a; struct stat st; char pathname[1024]; Stat *stats; @@ -780,12 +757,12 @@ process_readdir(void) "%s/%s", path, dp->d_name); if (lstat(pathname, &st) < 0) continue; - a = stat_to_attrib(&st); - stats[count].attrib = *a; + stat_to_attrib(&st, &(stats[count].attrib)); stats[count].name = xstrdup(dp->d_name); stats[count].long_name = ls_file(dp->d_name, &st); count++; /* send up to 100 entries in one message */ + /* XXX check packet size instead */ if (count == 100) break; } @@ -884,15 +861,64 @@ void process_rename(void) { u_int32_t id; + struct stat st; char *oldpath, *newpath; - int ret, status; + int ret, status = SSH2_FX_FAILURE; id = get_int(); oldpath = get_string(NULL); newpath = get_string(NULL); TRACE("rename id %d old %s new %s", id, oldpath, newpath); - ret = rename(oldpath, newpath); - status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + /* fail if 'newpath' exists */ + if (stat(newpath, &st) == -1) { + ret = rename(oldpath, newpath); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } + send_status(id, status); + xfree(oldpath); + xfree(newpath); +} + +void +process_readlink(void) +{ + u_int32_t id; + char link[MAXPATHLEN]; + char *path; + + id = get_int(); + path = get_string(NULL); + TRACE("readlink id %d path %s", id, path); + if (readlink(path, link, sizeof(link) - 1) == -1) + send_status(id, errno_to_portable(errno)); + else { + Stat s; + + link[sizeof(link) - 1] = '\0'; + attrib_clear(&s.attrib); + s.name = s.long_name = link; + send_names(id, 1, &s); + } + xfree(path); +} + +void +process_symlink(void) +{ + u_int32_t id; + struct stat st; + char *oldpath, *newpath; + int ret, status = SSH2_FX_FAILURE; + + id = get_int(); + oldpath = get_string(NULL); + newpath = get_string(NULL); + TRACE("symlink id %d old %s new %s", id, oldpath, newpath); + /* fail if 'newpath' exists */ + if (stat(newpath, &st) == -1) { + ret = symlink(oldpath, newpath); + status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; + } send_status(id, status); xfree(oldpath); xfree(newpath); @@ -983,6 +1009,12 @@ process(void) case SSH2_FXP_RENAME: process_rename(); break; + case SSH2_FXP_READLINK: + process_readlink(); + break; + case SSH2_FXP_SYMLINK: + process_symlink(); + break; case SSH2_FXP_EXTENDED: process_extended(); break; @@ -995,14 +1027,16 @@ process(void) int main(int ac, char **av) { - fd_set rset, wset; + fd_set *rset, *wset; int in, out, max; - ssize_t len, olen; + ssize_t len, olen, set_size; __progname = get_progname(av[0]); handle_init(); - log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0); +#ifdef DEBUG_SFTP_SERVER + log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0); +#endif in = dup(STDIN_FILENO); out = dup(STDOUT_FILENO); @@ -1016,23 +1050,27 @@ main(int ac, char **av) buffer_init(&iqueue); buffer_init(&oqueue); + set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); + rset = (fd_set *)xmalloc(set_size); + wset = (fd_set *)xmalloc(set_size); + for (;;) { - FD_ZERO(&rset); - FD_ZERO(&wset); + memset(rset, 0, set_size); + memset(wset, 0, set_size); - FD_SET(in, &rset); + FD_SET(in, rset); olen = buffer_len(&oqueue); if (olen > 0) - FD_SET(out, &wset); + FD_SET(out, wset); - if (select(max+1, &rset, &wset, NULL, NULL) < 0) { + if (select(max+1, rset, wset, NULL, NULL) < 0) { if (errno == EINTR) continue; exit(2); } /* copy stdin to iqueue */ - if (FD_ISSET(in, &rset)) { + if (FD_ISSET(in, rset)) { char buf[4*4096]; len = read(in, buf, sizeof buf); if (len == 0) { @@ -1046,7 +1084,7 @@ main(int ac, char **av) } } /* send oqueue to stdout */ - if (FD_ISSET(out, &wset)) { + if (FD_ISSET(out, wset)) { len = write(out, buffer_ptr(&oqueue), olen); if (len < 0) { error("write error");