sftp-server.c revision 204917
1204917Sdes/* $OpenBSD: sftp-server.c,v 1.91 2010/01/13 01:40:16 djm Exp $ */ 265668Skris/* 3126274Sdes * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. 465668Skris * 5126274Sdes * Permission to use, copy, modify, and distribute this software for any 6126274Sdes * purpose with or without fee is hereby granted, provided that the above 7126274Sdes * copyright notice and this permission notice appear in all copies. 865668Skris * 9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1665668Skris */ 17162852Sdes 1865668Skris#include "includes.h" 1965668Skris 20162852Sdes#include <sys/types.h> 21162852Sdes#include <sys/param.h> 22162852Sdes#include <sys/stat.h> 23162852Sdes#ifdef HAVE_SYS_TIME_H 24162852Sdes# include <sys/time.h> 25162852Sdes#endif 26181111Sdes#ifdef HAVE_SYS_MOUNT_H 27181111Sdes#include <sys/mount.h> 28181111Sdes#endif 29181111Sdes#ifdef HAVE_SYS_STATVFS_H 30181111Sdes#include <sys/statvfs.h> 31181111Sdes#endif 32162852Sdes 33162852Sdes#include <dirent.h> 34162852Sdes#include <errno.h> 35162852Sdes#include <fcntl.h> 36162852Sdes#include <pwd.h> 37162852Sdes#include <stdlib.h> 38162852Sdes#include <stdio.h> 39162852Sdes#include <string.h> 40162852Sdes#include <pwd.h> 41162852Sdes#include <time.h> 42162852Sdes#include <unistd.h> 43162852Sdes#include <stdarg.h> 44162852Sdes 45162852Sdes#include "xmalloc.h" 4665668Skris#include "buffer.h" 4776259Sgreen#include "log.h" 48157016Sdes#include "misc.h" 49162852Sdes#include "uidswap.h" 5065668Skris 5176259Sgreen#include "sftp.h" 5276259Sgreen#include "sftp-common.h" 5365668Skris 5465668Skris/* helper */ 5576259Sgreen#define get_int64() buffer_get_int64(&iqueue); 5665668Skris#define get_int() buffer_get_int(&iqueue); 5765668Skris#define get_string(lenp) buffer_get_string(&iqueue, lenp); 5865668Skris 59162852Sdes/* Our verbosity */ 60162852SdesLogLevel log_level = SYSLOG_LEVEL_ERROR; 6198937Sdes 62162852Sdes/* Our client */ 63162852Sdesstruct passwd *pw = NULL; 64162852Sdeschar *client_addr = NULL; 65162852Sdes 6665668Skris/* input and output queue */ 6765668SkrisBuffer iqueue; 6865668SkrisBuffer oqueue; 6965668Skris 7076259Sgreen/* Version of client */ 7176259Sgreenint version; 7276259Sgreen 73204917Sdes/* Disable writes */ 74204917Sdesint readonly; 75204917Sdes 76124208Sdes/* portable attributes, etc. */ 7765668Skris 7865668Skristypedef struct Stat Stat; 7965668Skris 8076259Sgreenstruct Stat { 8165668Skris char *name; 8265668Skris char *long_name; 8365668Skris Attrib attrib; 8465668Skris}; 8565668Skris 8692555Sdesstatic int 8765668Skriserrno_to_portable(int unixerrno) 8865668Skris{ 8965668Skris int ret = 0; 9076259Sgreen 9165668Skris switch (unixerrno) { 9265668Skris case 0: 9376259Sgreen ret = SSH2_FX_OK; 9465668Skris break; 9565668Skris case ENOENT: 9665668Skris case ENOTDIR: 9765668Skris case EBADF: 9865668Skris case ELOOP: 9976259Sgreen ret = SSH2_FX_NO_SUCH_FILE; 10065668Skris break; 10165668Skris case EPERM: 10265668Skris case EACCES: 10365668Skris case EFAULT: 10476259Sgreen ret = SSH2_FX_PERMISSION_DENIED; 10565668Skris break; 10665668Skris case ENAMETOOLONG: 10765668Skris case EINVAL: 10876259Sgreen ret = SSH2_FX_BAD_MESSAGE; 10965668Skris break; 110181111Sdes case ENOSYS: 111181111Sdes ret = SSH2_FX_OP_UNSUPPORTED; 112181111Sdes break; 11365668Skris default: 11476259Sgreen ret = SSH2_FX_FAILURE; 11565668Skris break; 11665668Skris } 11765668Skris return ret; 11865668Skris} 11965668Skris 12092555Sdesstatic int 12165668Skrisflags_from_portable(int pflags) 12265668Skris{ 12365668Skris int flags = 0; 12476259Sgreen 12576259Sgreen if ((pflags & SSH2_FXF_READ) && 12676259Sgreen (pflags & SSH2_FXF_WRITE)) { 12765668Skris flags = O_RDWR; 12876259Sgreen } else if (pflags & SSH2_FXF_READ) { 12965668Skris flags = O_RDONLY; 13076259Sgreen } else if (pflags & SSH2_FXF_WRITE) { 13165668Skris flags = O_WRONLY; 13265668Skris } 13376259Sgreen if (pflags & SSH2_FXF_CREAT) 13465668Skris flags |= O_CREAT; 13576259Sgreen if (pflags & SSH2_FXF_TRUNC) 13665668Skris flags |= O_TRUNC; 13776259Sgreen if (pflags & SSH2_FXF_EXCL) 13865668Skris flags |= O_EXCL; 13965668Skris return flags; 14065668Skris} 14165668Skris 142162852Sdesstatic const char * 143162852Sdesstring_from_portable(int pflags) 144162852Sdes{ 145162852Sdes static char ret[128]; 146162852Sdes 147162852Sdes *ret = '\0'; 148162852Sdes 149162852Sdes#define PAPPEND(str) { \ 150162852Sdes if (*ret != '\0') \ 151162852Sdes strlcat(ret, ",", sizeof(ret)); \ 152162852Sdes strlcat(ret, str, sizeof(ret)); \ 153162852Sdes } 154162852Sdes 155162852Sdes if (pflags & SSH2_FXF_READ) 156162852Sdes PAPPEND("READ") 157162852Sdes if (pflags & SSH2_FXF_WRITE) 158162852Sdes PAPPEND("WRITE") 159162852Sdes if (pflags & SSH2_FXF_CREAT) 160162852Sdes PAPPEND("CREATE") 161162852Sdes if (pflags & SSH2_FXF_TRUNC) 162162852Sdes PAPPEND("TRUNCATE") 163162852Sdes if (pflags & SSH2_FXF_EXCL) 164162852Sdes PAPPEND("EXCL") 165162852Sdes 166162852Sdes return ret; 167162852Sdes} 168162852Sdes 16992555Sdesstatic Attrib * 17065668Skrisget_attrib(void) 17165668Skris{ 17265668Skris return decode_attrib(&iqueue); 17365668Skris} 17465668Skris 17565668Skris/* handle handles */ 17665668Skris 17765668Skristypedef struct Handle Handle; 17865668Skrisstruct Handle { 17965668Skris int use; 18065668Skris DIR *dirp; 18165668Skris int fd; 18265668Skris char *name; 183162852Sdes u_int64_t bytes_read, bytes_write; 184181111Sdes int next_unused; 18565668Skris}; 18676259Sgreen 18765668Skrisenum { 18865668Skris HANDLE_UNUSED, 18965668Skris HANDLE_DIR, 19065668Skris HANDLE_FILE 19165668Skris}; 19276259Sgreen 193181111SdesHandle *handles = NULL; 194181111Sdesu_int num_handles = 0; 195181111Sdesint first_unused_handle = -1; 19665668Skris 197181111Sdesstatic void handle_unused(int i) 19865668Skris{ 199181111Sdes handles[i].use = HANDLE_UNUSED; 200181111Sdes handles[i].next_unused = first_unused_handle; 201181111Sdes first_unused_handle = i; 20265668Skris} 20365668Skris 20492555Sdesstatic int 205126274Sdeshandle_new(int use, const char *name, int fd, DIR *dirp) 20665668Skris{ 207181111Sdes int i; 20876259Sgreen 209181111Sdes if (first_unused_handle == -1) { 210181111Sdes if (num_handles + 1 <= num_handles) 211181111Sdes return -1; 212181111Sdes num_handles++; 213181111Sdes handles = xrealloc(handles, num_handles, sizeof(Handle)); 214181111Sdes handle_unused(num_handles - 1); 21565668Skris } 216181111Sdes 217181111Sdes i = first_unused_handle; 218181111Sdes first_unused_handle = handles[i].next_unused; 219181111Sdes 220181111Sdes handles[i].use = use; 221181111Sdes handles[i].dirp = dirp; 222181111Sdes handles[i].fd = fd; 223181111Sdes handles[i].name = xstrdup(name); 224181111Sdes handles[i].bytes_read = handles[i].bytes_write = 0; 225181111Sdes 226181111Sdes return i; 22765668Skris} 22865668Skris 22992555Sdesstatic int 23065668Skrishandle_is_ok(int i, int type) 23165668Skris{ 232181111Sdes return i >= 0 && (u_int)i < num_handles && handles[i].use == type; 23365668Skris} 23465668Skris 23592555Sdesstatic int 23665668Skrishandle_to_string(int handle, char **stringp, int *hlenp) 23765668Skris{ 23865668Skris if (stringp == NULL || hlenp == NULL) 23965668Skris return -1; 24076259Sgreen *stringp = xmalloc(sizeof(int32_t)); 241162852Sdes put_u32(*stringp, handle); 24276259Sgreen *hlenp = sizeof(int32_t); 24365668Skris return 0; 24465668Skris} 24565668Skris 24692555Sdesstatic int 247126274Sdeshandle_from_string(const char *handle, u_int hlen) 24865668Skris{ 24976259Sgreen int val; 25076259Sgreen 25176259Sgreen if (hlen != sizeof(int32_t)) 25265668Skris return -1; 253162852Sdes val = get_u32(handle); 25465668Skris if (handle_is_ok(val, HANDLE_FILE) || 25565668Skris handle_is_ok(val, HANDLE_DIR)) 25665668Skris return val; 25765668Skris return -1; 25865668Skris} 25965668Skris 26092555Sdesstatic char * 26165668Skrishandle_to_name(int handle) 26265668Skris{ 26365668Skris if (handle_is_ok(handle, HANDLE_DIR)|| 26465668Skris handle_is_ok(handle, HANDLE_FILE)) 26565668Skris return handles[handle].name; 26665668Skris return NULL; 26765668Skris} 26865668Skris 26992555Sdesstatic DIR * 27065668Skrishandle_to_dir(int handle) 27165668Skris{ 27265668Skris if (handle_is_ok(handle, HANDLE_DIR)) 27365668Skris return handles[handle].dirp; 27465668Skris return NULL; 27565668Skris} 27665668Skris 27792555Sdesstatic int 27865668Skrishandle_to_fd(int handle) 27965668Skris{ 28076259Sgreen if (handle_is_ok(handle, HANDLE_FILE)) 28165668Skris return handles[handle].fd; 28265668Skris return -1; 28365668Skris} 28465668Skris 285162852Sdesstatic void 286162852Sdeshandle_update_read(int handle, ssize_t bytes) 287162852Sdes{ 288162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 289162852Sdes handles[handle].bytes_read += bytes; 290162852Sdes} 291162852Sdes 292162852Sdesstatic void 293162852Sdeshandle_update_write(int handle, ssize_t bytes) 294162852Sdes{ 295162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 296162852Sdes handles[handle].bytes_write += bytes; 297162852Sdes} 298162852Sdes 299162852Sdesstatic u_int64_t 300162852Sdeshandle_bytes_read(int handle) 301162852Sdes{ 302162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 303162852Sdes return (handles[handle].bytes_read); 304162852Sdes return 0; 305162852Sdes} 306162852Sdes 307162852Sdesstatic u_int64_t 308162852Sdeshandle_bytes_write(int handle) 309162852Sdes{ 310162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 311162852Sdes return (handles[handle].bytes_write); 312162852Sdes return 0; 313162852Sdes} 314162852Sdes 31592555Sdesstatic int 31665668Skrishandle_close(int handle) 31765668Skris{ 31865668Skris int ret = -1; 31976259Sgreen 32065668Skris if (handle_is_ok(handle, HANDLE_FILE)) { 32165668Skris ret = close(handles[handle].fd); 322113908Sdes xfree(handles[handle].name); 323181111Sdes handle_unused(handle); 32465668Skris } else if (handle_is_ok(handle, HANDLE_DIR)) { 32565668Skris ret = closedir(handles[handle].dirp); 326113908Sdes xfree(handles[handle].name); 327181111Sdes handle_unused(handle); 32865668Skris } else { 32965668Skris errno = ENOENT; 33065668Skris } 33165668Skris return ret; 33265668Skris} 33365668Skris 334162852Sdesstatic void 335162852Sdeshandle_log_close(int handle, char *emsg) 336162852Sdes{ 337162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) { 338162852Sdes logit("%s%sclose \"%s\" bytes read %llu written %llu", 339162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 340162852Sdes handle_to_name(handle), 341181111Sdes (unsigned long long)handle_bytes_read(handle), 342181111Sdes (unsigned long long)handle_bytes_write(handle)); 343162852Sdes } else { 344162852Sdes logit("%s%sclosedir \"%s\"", 345162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 346162852Sdes handle_to_name(handle)); 347162852Sdes } 348162852Sdes} 349162852Sdes 350162852Sdesstatic void 351162852Sdeshandle_log_exit(void) 352162852Sdes{ 353162852Sdes u_int i; 354162852Sdes 355181111Sdes for (i = 0; i < num_handles; i++) 356162852Sdes if (handles[i].use != HANDLE_UNUSED) 357162852Sdes handle_log_close(i, "forced"); 358162852Sdes} 359162852Sdes 36092555Sdesstatic int 36165668Skrisget_handle(void) 36265668Skris{ 36365668Skris char *handle; 36476259Sgreen int val = -1; 36565668Skris u_int hlen; 36676259Sgreen 36765668Skris handle = get_string(&hlen); 36876259Sgreen if (hlen < 256) 36976259Sgreen val = handle_from_string(handle, hlen); 37065668Skris xfree(handle); 37165668Skris return val; 37265668Skris} 37365668Skris 37465668Skris/* send replies */ 37565668Skris 37692555Sdesstatic void 37765668Skrissend_msg(Buffer *m) 37865668Skris{ 37965668Skris int mlen = buffer_len(m); 38076259Sgreen 38165668Skris buffer_put_int(&oqueue, mlen); 38265668Skris buffer_append(&oqueue, buffer_ptr(m), mlen); 38365668Skris buffer_consume(m, mlen); 38465668Skris} 38565668Skris 386162852Sdesstatic const char * 387162852Sdesstatus_to_message(u_int32_t status) 38865668Skris{ 38976259Sgreen const char *status_messages[] = { 39076259Sgreen "Success", /* SSH_FX_OK */ 39176259Sgreen "End of file", /* SSH_FX_EOF */ 39276259Sgreen "No such file", /* SSH_FX_NO_SUCH_FILE */ 39376259Sgreen "Permission denied", /* SSH_FX_PERMISSION_DENIED */ 39476259Sgreen "Failure", /* SSH_FX_FAILURE */ 39576259Sgreen "Bad message", /* SSH_FX_BAD_MESSAGE */ 39676259Sgreen "No connection", /* SSH_FX_NO_CONNECTION */ 39776259Sgreen "Connection lost", /* SSH_FX_CONNECTION_LOST */ 39876259Sgreen "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ 39976259Sgreen "Unknown error" /* Others */ 40076259Sgreen }; 401162852Sdes return (status_messages[MIN(status,SSH2_FX_MAX)]); 402162852Sdes} 40376259Sgreen 404162852Sdesstatic void 405162852Sdessend_status(u_int32_t id, u_int32_t status) 406162852Sdes{ 407162852Sdes Buffer msg; 408162852Sdes 409162852Sdes debug3("request %u: sent status %u", id, status); 410162852Sdes if (log_level > SYSLOG_LEVEL_VERBOSE || 411162852Sdes (status != SSH2_FX_OK && status != SSH2_FX_EOF)) 412162852Sdes logit("sent status %s", status_to_message(status)); 41365668Skris buffer_init(&msg); 41476259Sgreen buffer_put_char(&msg, SSH2_FXP_STATUS); 41565668Skris buffer_put_int(&msg, id); 416137015Sdes buffer_put_int(&msg, status); 41776259Sgreen if (version >= 3) { 418162852Sdes buffer_put_cstring(&msg, status_to_message(status)); 41976259Sgreen buffer_put_cstring(&msg, ""); 42076259Sgreen } 42165668Skris send_msg(&msg); 42265668Skris buffer_free(&msg); 42365668Skris} 42492555Sdesstatic void 425126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen) 42665668Skris{ 42765668Skris Buffer msg; 42876259Sgreen 42965668Skris buffer_init(&msg); 43065668Skris buffer_put_char(&msg, type); 43165668Skris buffer_put_int(&msg, id); 43265668Skris buffer_put_string(&msg, data, dlen); 43365668Skris send_msg(&msg); 43465668Skris buffer_free(&msg); 43565668Skris} 43665668Skris 43792555Sdesstatic void 438126274Sdessend_data(u_int32_t id, const char *data, int dlen) 43965668Skris{ 440162852Sdes debug("request %u: sent data len %d", id, dlen); 44176259Sgreen send_data_or_handle(SSH2_FXP_DATA, id, data, dlen); 44265668Skris} 44365668Skris 44492555Sdesstatic void 44565668Skrissend_handle(u_int32_t id, int handle) 44665668Skris{ 44765668Skris char *string; 44865668Skris int hlen; 44976259Sgreen 45065668Skris handle_to_string(handle, &string, &hlen); 451162852Sdes debug("request %u: sent handle handle %d", id, handle); 45276259Sgreen send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); 45365668Skris xfree(string); 45465668Skris} 45565668Skris 45692555Sdesstatic void 457126274Sdessend_names(u_int32_t id, int count, const Stat *stats) 45865668Skris{ 45965668Skris Buffer msg; 46065668Skris int i; 46176259Sgreen 46265668Skris buffer_init(&msg); 46376259Sgreen buffer_put_char(&msg, SSH2_FXP_NAME); 46465668Skris buffer_put_int(&msg, id); 46565668Skris buffer_put_int(&msg, count); 466162852Sdes debug("request %u: sent names count %d", id, count); 46765668Skris for (i = 0; i < count; i++) { 46865668Skris buffer_put_cstring(&msg, stats[i].name); 46965668Skris buffer_put_cstring(&msg, stats[i].long_name); 47065668Skris encode_attrib(&msg, &stats[i].attrib); 47165668Skris } 47265668Skris send_msg(&msg); 47365668Skris buffer_free(&msg); 47465668Skris} 47565668Skris 47692555Sdesstatic void 477126274Sdessend_attrib(u_int32_t id, const Attrib *a) 47865668Skris{ 47965668Skris Buffer msg; 48076259Sgreen 481162852Sdes debug("request %u: sent attrib have 0x%x", id, a->flags); 48265668Skris buffer_init(&msg); 48376259Sgreen buffer_put_char(&msg, SSH2_FXP_ATTRS); 48465668Skris buffer_put_int(&msg, id); 48565668Skris encode_attrib(&msg, a); 48665668Skris send_msg(&msg); 48765668Skris buffer_free(&msg); 48865668Skris} 48965668Skris 490181111Sdesstatic void 491181111Sdessend_statvfs(u_int32_t id, struct statvfs *st) 492181111Sdes{ 493181111Sdes Buffer msg; 494181111Sdes u_int64_t flag; 495181111Sdes 496181111Sdes flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; 497181111Sdes flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; 498181111Sdes 499181111Sdes buffer_init(&msg); 500181111Sdes buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY); 501181111Sdes buffer_put_int(&msg, id); 502181111Sdes buffer_put_int64(&msg, st->f_bsize); 503181111Sdes buffer_put_int64(&msg, st->f_frsize); 504181111Sdes buffer_put_int64(&msg, st->f_blocks); 505181111Sdes buffer_put_int64(&msg, st->f_bfree); 506181111Sdes buffer_put_int64(&msg, st->f_bavail); 507181111Sdes buffer_put_int64(&msg, st->f_files); 508181111Sdes buffer_put_int64(&msg, st->f_ffree); 509181111Sdes buffer_put_int64(&msg, st->f_favail); 510181111Sdes buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid)); 511181111Sdes buffer_put_int64(&msg, flag); 512181111Sdes buffer_put_int64(&msg, st->f_namemax); 513181111Sdes send_msg(&msg); 514181111Sdes buffer_free(&msg); 515181111Sdes} 516181111Sdes 51765668Skris/* parse incoming */ 51865668Skris 51992555Sdesstatic void 52065668Skrisprocess_init(void) 52165668Skris{ 52265668Skris Buffer msg; 52365668Skris 52498675Sdes version = get_int(); 525162852Sdes verbose("received client version %d", version); 52665668Skris buffer_init(&msg); 52776259Sgreen buffer_put_char(&msg, SSH2_FXP_VERSION); 52876259Sgreen buffer_put_int(&msg, SSH2_FILEXFER_VERSION); 529181111Sdes /* POSIX rename extension */ 530181111Sdes buffer_put_cstring(&msg, "posix-rename@openssh.com"); 531181111Sdes buffer_put_cstring(&msg, "1"); /* version */ 532181111Sdes /* statvfs extension */ 533181111Sdes buffer_put_cstring(&msg, "statvfs@openssh.com"); 534181111Sdes buffer_put_cstring(&msg, "2"); /* version */ 535181111Sdes /* fstatvfs extension */ 536181111Sdes buffer_put_cstring(&msg, "fstatvfs@openssh.com"); 537181111Sdes buffer_put_cstring(&msg, "2"); /* version */ 53865668Skris send_msg(&msg); 53965668Skris buffer_free(&msg); 54065668Skris} 54165668Skris 54292555Sdesstatic void 54365668Skrisprocess_open(void) 54465668Skris{ 54565668Skris u_int32_t id, pflags; 54665668Skris Attrib *a; 54765668Skris char *name; 54876259Sgreen int handle, fd, flags, mode, status = SSH2_FX_FAILURE; 54965668Skris 55065668Skris id = get_int(); 55165668Skris name = get_string(NULL); 55276259Sgreen pflags = get_int(); /* portable flags */ 553162852Sdes debug3("request %u: open flags %d", id, pflags); 55465668Skris a = get_attrib(); 55565668Skris flags = flags_from_portable(pflags); 55676259Sgreen mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666; 557162852Sdes logit("open \"%s\" flags %s mode 0%o", 558162852Sdes name, string_from_portable(pflags), mode); 559204917Sdes if (readonly && 560204917Sdes ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR)) 561204917Sdes status = SSH2_FX_PERMISSION_DENIED; 562204917Sdes else { 563204917Sdes fd = open(name, flags, mode); 564204917Sdes if (fd < 0) { 565204917Sdes status = errno_to_portable(errno); 56665668Skris } else { 567204917Sdes handle = handle_new(HANDLE_FILE, name, fd, NULL); 568204917Sdes if (handle < 0) { 569204917Sdes close(fd); 570204917Sdes } else { 571204917Sdes send_handle(id, handle); 572204917Sdes status = SSH2_FX_OK; 573204917Sdes } 57465668Skris } 57565668Skris } 57676259Sgreen if (status != SSH2_FX_OK) 57765668Skris send_status(id, status); 57865668Skris xfree(name); 57965668Skris} 58065668Skris 58192555Sdesstatic void 58265668Skrisprocess_close(void) 58365668Skris{ 58465668Skris u_int32_t id; 58576259Sgreen int handle, ret, status = SSH2_FX_FAILURE; 58665668Skris 58765668Skris id = get_int(); 58865668Skris handle = get_handle(); 589162852Sdes debug3("request %u: close handle %u", id, handle); 590162852Sdes handle_log_close(handle, NULL); 59165668Skris ret = handle_close(handle); 59276259Sgreen status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 59365668Skris send_status(id, status); 59465668Skris} 59565668Skris 59692555Sdesstatic void 59765668Skrisprocess_read(void) 59865668Skris{ 59965668Skris char buf[64*1024]; 60076259Sgreen u_int32_t id, len; 60176259Sgreen int handle, fd, ret, status = SSH2_FX_FAILURE; 60265668Skris u_int64_t off; 60365668Skris 60465668Skris id = get_int(); 60565668Skris handle = get_handle(); 60676259Sgreen off = get_int64(); 60765668Skris len = get_int(); 60865668Skris 609162852Sdes debug("request %u: read \"%s\" (handle %d) off %llu len %d", 610162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 61165668Skris if (len > sizeof buf) { 61265668Skris len = sizeof buf; 613162852Sdes debug2("read change len %d", len); 61465668Skris } 61565668Skris fd = handle_to_fd(handle); 61665668Skris if (fd >= 0) { 61765668Skris if (lseek(fd, off, SEEK_SET) < 0) { 61865668Skris error("process_read: seek failed"); 61965668Skris status = errno_to_portable(errno); 62065668Skris } else { 62165668Skris ret = read(fd, buf, len); 62265668Skris if (ret < 0) { 62365668Skris status = errno_to_portable(errno); 62465668Skris } else if (ret == 0) { 62576259Sgreen status = SSH2_FX_EOF; 62665668Skris } else { 62765668Skris send_data(id, buf, ret); 62876259Sgreen status = SSH2_FX_OK; 629162852Sdes handle_update_read(handle, ret); 63065668Skris } 63165668Skris } 63265668Skris } 63376259Sgreen if (status != SSH2_FX_OK) 63465668Skris send_status(id, status); 63565668Skris} 63665668Skris 63792555Sdesstatic void 63865668Skrisprocess_write(void) 63965668Skris{ 64076259Sgreen u_int32_t id; 64165668Skris u_int64_t off; 64265668Skris u_int len; 643204917Sdes int handle, fd, ret, status; 64465668Skris char *data; 64565668Skris 64665668Skris id = get_int(); 64765668Skris handle = get_handle(); 64876259Sgreen off = get_int64(); 64965668Skris data = get_string(&len); 65065668Skris 651162852Sdes debug("request %u: write \"%s\" (handle %d) off %llu len %d", 652162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 65365668Skris fd = handle_to_fd(handle); 654204917Sdes 655204917Sdes if (fd < 0) 656204917Sdes status = SSH2_FX_FAILURE; 657204917Sdes else if (readonly) 658204917Sdes status = SSH2_FX_PERMISSION_DENIED; 659204917Sdes else { 66065668Skris if (lseek(fd, off, SEEK_SET) < 0) { 66165668Skris status = errno_to_portable(errno); 66265668Skris error("process_write: seek failed"); 66365668Skris } else { 66465668Skris/* XXX ATOMICIO ? */ 66565668Skris ret = write(fd, data, len); 666149749Sdes if (ret < 0) { 66765668Skris error("process_write: write failed"); 66865668Skris status = errno_to_portable(errno); 669149749Sdes } else if ((size_t)ret == len) { 67076259Sgreen status = SSH2_FX_OK; 671162852Sdes handle_update_write(handle, ret); 67265668Skris } else { 673162852Sdes debug2("nothing at all written"); 674204917Sdes status = SSH2_FX_FAILURE; 67565668Skris } 67665668Skris } 67765668Skris } 67865668Skris send_status(id, status); 67965668Skris xfree(data); 68065668Skris} 68165668Skris 68292555Sdesstatic void 68365668Skrisprocess_do_stat(int do_lstat) 68465668Skris{ 68576259Sgreen Attrib a; 68665668Skris struct stat st; 68765668Skris u_int32_t id; 68865668Skris char *name; 68976259Sgreen int ret, status = SSH2_FX_FAILURE; 69065668Skris 69165668Skris id = get_int(); 69265668Skris name = get_string(NULL); 693162852Sdes debug3("request %u: %sstat", id, do_lstat ? "l" : ""); 694162852Sdes verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name); 69565668Skris ret = do_lstat ? lstat(name, &st) : stat(name, &st); 69665668Skris if (ret < 0) { 69765668Skris status = errno_to_portable(errno); 69865668Skris } else { 69976259Sgreen stat_to_attrib(&st, &a); 70076259Sgreen send_attrib(id, &a); 70176259Sgreen status = SSH2_FX_OK; 70265668Skris } 70376259Sgreen if (status != SSH2_FX_OK) 70465668Skris send_status(id, status); 70565668Skris xfree(name); 70665668Skris} 70765668Skris 70892555Sdesstatic void 70965668Skrisprocess_stat(void) 71065668Skris{ 71165668Skris process_do_stat(0); 71265668Skris} 71365668Skris 71492555Sdesstatic void 71565668Skrisprocess_lstat(void) 71665668Skris{ 71765668Skris process_do_stat(1); 71865668Skris} 71965668Skris 72092555Sdesstatic void 72165668Skrisprocess_fstat(void) 72265668Skris{ 72376259Sgreen Attrib a; 72465668Skris struct stat st; 72565668Skris u_int32_t id; 72676259Sgreen int fd, ret, handle, status = SSH2_FX_FAILURE; 72765668Skris 72865668Skris id = get_int(); 72965668Skris handle = get_handle(); 730162852Sdes debug("request %u: fstat \"%s\" (handle %u)", 731162852Sdes id, handle_to_name(handle), handle); 73265668Skris fd = handle_to_fd(handle); 733181111Sdes if (fd >= 0) { 73465668Skris ret = fstat(fd, &st); 73565668Skris if (ret < 0) { 73665668Skris status = errno_to_portable(errno); 73765668Skris } else { 73876259Sgreen stat_to_attrib(&st, &a); 73976259Sgreen send_attrib(id, &a); 74076259Sgreen status = SSH2_FX_OK; 74165668Skris } 74265668Skris } 74376259Sgreen if (status != SSH2_FX_OK) 74465668Skris send_status(id, status); 74565668Skris} 74665668Skris 74792555Sdesstatic struct timeval * 748126274Sdesattrib_to_tv(const Attrib *a) 74965668Skris{ 75065668Skris static struct timeval tv[2]; 75176259Sgreen 75265668Skris tv[0].tv_sec = a->atime; 75365668Skris tv[0].tv_usec = 0; 75465668Skris tv[1].tv_sec = a->mtime; 75565668Skris tv[1].tv_usec = 0; 75665668Skris return tv; 75765668Skris} 75865668Skris 75992555Sdesstatic void 76065668Skrisprocess_setstat(void) 76165668Skris{ 76265668Skris Attrib *a; 76365668Skris u_int32_t id; 76465668Skris char *name; 76599060Sdes int status = SSH2_FX_OK, ret; 76665668Skris 76765668Skris id = get_int(); 76865668Skris name = get_string(NULL); 76965668Skris a = get_attrib(); 770162852Sdes debug("request %u: setstat name \"%s\"", id, name); 771204917Sdes if (readonly) { 772204917Sdes status = SSH2_FX_PERMISSION_DENIED; 773204917Sdes a->flags = 0; 774204917Sdes } 77592555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 776181111Sdes logit("set \"%s\" size %llu", 777181111Sdes name, (unsigned long long)a->size); 77892555Sdes ret = truncate(name, a->size); 77992555Sdes if (ret == -1) 78092555Sdes status = errno_to_portable(errno); 78192555Sdes } 78276259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 783162852Sdes logit("set \"%s\" mode %04o", name, a->perm); 784181111Sdes ret = chmod(name, a->perm & 07777); 78565668Skris if (ret == -1) 78665668Skris status = errno_to_portable(errno); 78765668Skris } 78876259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 789162852Sdes char buf[64]; 790162852Sdes time_t t = a->mtime; 791162852Sdes 792162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 793162852Sdes localtime(&t)); 794162852Sdes logit("set \"%s\" modtime %s", name, buf); 79565668Skris ret = utimes(name, attrib_to_tv(a)); 79665668Skris if (ret == -1) 79765668Skris status = errno_to_portable(errno); 79865668Skris } 79976259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 800162852Sdes logit("set \"%s\" owner %lu group %lu", name, 801162852Sdes (u_long)a->uid, (u_long)a->gid); 80276259Sgreen ret = chown(name, a->uid, a->gid); 80376259Sgreen if (ret == -1) 80476259Sgreen status = errno_to_portable(errno); 80576259Sgreen } 80665668Skris send_status(id, status); 80765668Skris xfree(name); 80865668Skris} 80965668Skris 81092555Sdesstatic void 81165668Skrisprocess_fsetstat(void) 81265668Skris{ 81365668Skris Attrib *a; 81465668Skris u_int32_t id; 81565668Skris int handle, fd, ret; 81676259Sgreen int status = SSH2_FX_OK; 81765668Skris 81865668Skris id = get_int(); 81965668Skris handle = get_handle(); 82065668Skris a = get_attrib(); 821162852Sdes debug("request %u: fsetstat handle %d", id, handle); 82265668Skris fd = handle_to_fd(handle); 823204917Sdes if (fd < 0) 82476259Sgreen status = SSH2_FX_FAILURE; 825204917Sdes else if (readonly) 826204917Sdes status = SSH2_FX_PERMISSION_DENIED; 827204917Sdes else { 828162852Sdes char *name = handle_to_name(handle); 829162852Sdes 83092555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 831181111Sdes logit("set \"%s\" size %llu", 832181111Sdes name, (unsigned long long)a->size); 83392555Sdes ret = ftruncate(fd, a->size); 83492555Sdes if (ret == -1) 83592555Sdes status = errno_to_portable(errno); 83692555Sdes } 83776259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 838162852Sdes logit("set \"%s\" mode %04o", name, a->perm); 83998937Sdes#ifdef HAVE_FCHMOD 840181111Sdes ret = fchmod(fd, a->perm & 07777); 84198937Sdes#else 842181111Sdes ret = chmod(name, a->perm & 07777); 84398937Sdes#endif 84465668Skris if (ret == -1) 84565668Skris status = errno_to_portable(errno); 84665668Skris } 84776259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 848162852Sdes char buf[64]; 849162852Sdes time_t t = a->mtime; 850162852Sdes 851162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 852162852Sdes localtime(&t)); 853162852Sdes logit("set \"%s\" modtime %s", name, buf); 85498937Sdes#ifdef HAVE_FUTIMES 85565668Skris ret = futimes(fd, attrib_to_tv(a)); 85698937Sdes#else 85798937Sdes ret = utimes(name, attrib_to_tv(a)); 85898937Sdes#endif 85965668Skris if (ret == -1) 86065668Skris status = errno_to_portable(errno); 86165668Skris } 86276259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 863162852Sdes logit("set \"%s\" owner %lu group %lu", name, 864162852Sdes (u_long)a->uid, (u_long)a->gid); 86598937Sdes#ifdef HAVE_FCHOWN 86676259Sgreen ret = fchown(fd, a->uid, a->gid); 86798937Sdes#else 86898937Sdes ret = chown(name, a->uid, a->gid); 86998937Sdes#endif 87076259Sgreen if (ret == -1) 87176259Sgreen status = errno_to_portable(errno); 87276259Sgreen } 87365668Skris } 87465668Skris send_status(id, status); 87565668Skris} 87665668Skris 87792555Sdesstatic void 87865668Skrisprocess_opendir(void) 87965668Skris{ 88065668Skris DIR *dirp = NULL; 88165668Skris char *path; 88276259Sgreen int handle, status = SSH2_FX_FAILURE; 88365668Skris u_int32_t id; 88465668Skris 88565668Skris id = get_int(); 88665668Skris path = get_string(NULL); 887162852Sdes debug3("request %u: opendir", id); 888162852Sdes logit("opendir \"%s\"", path); 88976259Sgreen dirp = opendir(path); 89065668Skris if (dirp == NULL) { 89165668Skris status = errno_to_portable(errno); 89265668Skris } else { 893113908Sdes handle = handle_new(HANDLE_DIR, path, 0, dirp); 89465668Skris if (handle < 0) { 89565668Skris closedir(dirp); 89665668Skris } else { 89765668Skris send_handle(id, handle); 89876259Sgreen status = SSH2_FX_OK; 89965668Skris } 90076259Sgreen 90165668Skris } 90276259Sgreen if (status != SSH2_FX_OK) 90365668Skris send_status(id, status); 90465668Skris xfree(path); 90565668Skris} 90665668Skris 90792555Sdesstatic void 90865668Skrisprocess_readdir(void) 90965668Skris{ 91065668Skris DIR *dirp; 91165668Skris struct dirent *dp; 91265668Skris char *path; 91365668Skris int handle; 91465668Skris u_int32_t id; 91565668Skris 91665668Skris id = get_int(); 91765668Skris handle = get_handle(); 918162852Sdes debug("request %u: readdir \"%s\" (handle %d)", id, 919162852Sdes handle_to_name(handle), handle); 92065668Skris dirp = handle_to_dir(handle); 92165668Skris path = handle_to_name(handle); 92265668Skris if (dirp == NULL || path == NULL) { 92376259Sgreen send_status(id, SSH2_FX_FAILURE); 92465668Skris } else { 92565668Skris struct stat st; 926162852Sdes char pathname[MAXPATHLEN]; 92765668Skris Stat *stats; 92865668Skris int nstats = 10, count = 0, i; 92999060Sdes 930162852Sdes stats = xcalloc(nstats, sizeof(Stat)); 93165668Skris while ((dp = readdir(dirp)) != NULL) { 93265668Skris if (count >= nstats) { 93365668Skris nstats *= 2; 934162852Sdes stats = xrealloc(stats, nstats, sizeof(Stat)); 93565668Skris } 93665668Skris/* XXX OVERFLOW ? */ 93792555Sdes snprintf(pathname, sizeof pathname, "%s%s%s", path, 93892555Sdes strcmp(path, "/") ? "/" : "", dp->d_name); 93965668Skris if (lstat(pathname, &st) < 0) 94065668Skris continue; 94176259Sgreen stat_to_attrib(&st, &(stats[count].attrib)); 94265668Skris stats[count].name = xstrdup(dp->d_name); 943204917Sdes stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); 94465668Skris count++; 94565668Skris /* send up to 100 entries in one message */ 94676259Sgreen /* XXX check packet size instead */ 94765668Skris if (count == 100) 94865668Skris break; 94965668Skris } 95076259Sgreen if (count > 0) { 95176259Sgreen send_names(id, count, stats); 95292555Sdes for (i = 0; i < count; i++) { 95376259Sgreen xfree(stats[i].name); 95476259Sgreen xfree(stats[i].long_name); 95576259Sgreen } 95676259Sgreen } else { 95776259Sgreen send_status(id, SSH2_FX_EOF); 95865668Skris } 95965668Skris xfree(stats); 96065668Skris } 96165668Skris} 96265668Skris 96392555Sdesstatic void 96465668Skrisprocess_remove(void) 96565668Skris{ 96665668Skris char *name; 96765668Skris u_int32_t id; 96876259Sgreen int status = SSH2_FX_FAILURE; 96965668Skris int ret; 97065668Skris 97165668Skris id = get_int(); 97265668Skris name = get_string(NULL); 973162852Sdes debug3("request %u: remove", id); 974162852Sdes logit("remove name \"%s\"", name); 975204917Sdes if (readonly) 976204917Sdes status = SSH2_FX_PERMISSION_DENIED; 977204917Sdes else { 978204917Sdes ret = unlink(name); 979204917Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 980204917Sdes } 98165668Skris send_status(id, status); 98265668Skris xfree(name); 98365668Skris} 98465668Skris 98592555Sdesstatic void 98665668Skrisprocess_mkdir(void) 98765668Skris{ 98865668Skris Attrib *a; 98965668Skris u_int32_t id; 99065668Skris char *name; 99176259Sgreen int ret, mode, status = SSH2_FX_FAILURE; 99265668Skris 99365668Skris id = get_int(); 99465668Skris name = get_string(NULL); 99565668Skris a = get_attrib(); 99676259Sgreen mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? 997181111Sdes a->perm & 07777 : 0777; 998162852Sdes debug3("request %u: mkdir", id); 999162852Sdes logit("mkdir name \"%s\" mode 0%o", name, mode); 1000204917Sdes if (readonly) 1001204917Sdes status = SSH2_FX_PERMISSION_DENIED; 1002204917Sdes else { 1003204917Sdes ret = mkdir(name, mode); 1004204917Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1005204917Sdes } 100665668Skris send_status(id, status); 100765668Skris xfree(name); 100865668Skris} 100965668Skris 101092555Sdesstatic void 101165668Skrisprocess_rmdir(void) 101265668Skris{ 101365668Skris u_int32_t id; 101465668Skris char *name; 101565668Skris int ret, status; 101665668Skris 101765668Skris id = get_int(); 101865668Skris name = get_string(NULL); 1019162852Sdes debug3("request %u: rmdir", id); 1020162852Sdes logit("rmdir name \"%s\"", name); 1021204917Sdes if (readonly) 1022204917Sdes status = SSH2_FX_PERMISSION_DENIED; 1023204917Sdes else { 1024204917Sdes ret = rmdir(name); 1025204917Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1026204917Sdes } 102765668Skris send_status(id, status); 102865668Skris xfree(name); 102965668Skris} 103065668Skris 103192555Sdesstatic void 103265668Skrisprocess_realpath(void) 103365668Skris{ 103465668Skris char resolvedname[MAXPATHLEN]; 103565668Skris u_int32_t id; 103665668Skris char *path; 103765668Skris 103865668Skris id = get_int(); 103965668Skris path = get_string(NULL); 104076259Sgreen if (path[0] == '\0') { 104176259Sgreen xfree(path); 104276259Sgreen path = xstrdup("."); 104376259Sgreen } 1044162852Sdes debug3("request %u: realpath", id); 1045162852Sdes verbose("realpath \"%s\"", path); 104665668Skris if (realpath(path, resolvedname) == NULL) { 104765668Skris send_status(id, errno_to_portable(errno)); 104865668Skris } else { 104965668Skris Stat s; 105065668Skris attrib_clear(&s.attrib); 105165668Skris s.name = s.long_name = resolvedname; 105265668Skris send_names(id, 1, &s); 105365668Skris } 105465668Skris xfree(path); 105565668Skris} 105665668Skris 105792555Sdesstatic void 105865668Skrisprocess_rename(void) 105965668Skris{ 106065668Skris u_int32_t id; 106165668Skris char *oldpath, *newpath; 1062113908Sdes int status; 1063113908Sdes struct stat sb; 106465668Skris 106565668Skris id = get_int(); 106665668Skris oldpath = get_string(NULL); 106765668Skris newpath = get_string(NULL); 1068162852Sdes debug3("request %u: rename", id); 1069162852Sdes logit("rename old \"%s\" new \"%s\"", oldpath, newpath); 1070113908Sdes status = SSH2_FX_FAILURE; 1071204917Sdes if (readonly) 1072204917Sdes status = SSH2_FX_PERMISSION_DENIED; 1073204917Sdes else if (lstat(oldpath, &sb) == -1) 1074113908Sdes status = errno_to_portable(errno); 1075113908Sdes else if (S_ISREG(sb.st_mode)) { 1076113908Sdes /* Race-free rename of regular files */ 1077137015Sdes if (link(oldpath, newpath) == -1) { 1078197679Sdes if (errno == EOPNOTSUPP || errno == ENOSYS 1079181111Sdes#ifdef EXDEV 1080181111Sdes || errno == EXDEV 1081181111Sdes#endif 1082137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO 1083137015Sdes || errno == LINK_OPNOTSUPP_ERRNO 1084137015Sdes#endif 1085137015Sdes ) { 1086137015Sdes struct stat st; 1087137015Sdes 1088137015Sdes /* 1089137015Sdes * fs doesn't support links, so fall back to 1090137015Sdes * stat+rename. This is racy. 1091137015Sdes */ 1092137015Sdes if (stat(newpath, &st) == -1) { 1093137015Sdes if (rename(oldpath, newpath) == -1) 1094137015Sdes status = 1095137015Sdes errno_to_portable(errno); 1096137015Sdes else 1097137015Sdes status = SSH2_FX_OK; 1098137015Sdes } 1099137015Sdes } else { 1100137015Sdes status = errno_to_portable(errno); 1101137015Sdes } 1102137015Sdes } else if (unlink(oldpath) == -1) { 1103113908Sdes status = errno_to_portable(errno); 1104113908Sdes /* clean spare link */ 1105113908Sdes unlink(newpath); 1106113908Sdes } else 1107113908Sdes status = SSH2_FX_OK; 1108113908Sdes } else if (stat(newpath, &sb) == -1) { 1109113908Sdes if (rename(oldpath, newpath) == -1) 1110113908Sdes status = errno_to_portable(errno); 1111113908Sdes else 1112113908Sdes status = SSH2_FX_OK; 111376259Sgreen } 111465668Skris send_status(id, status); 111565668Skris xfree(oldpath); 111665668Skris xfree(newpath); 111765668Skris} 111865668Skris 111992555Sdesstatic void 112076259Sgreenprocess_readlink(void) 112176259Sgreen{ 112276259Sgreen u_int32_t id; 112392555Sdes int len; 1124137015Sdes char buf[MAXPATHLEN]; 112576259Sgreen char *path; 112665668Skris 112776259Sgreen id = get_int(); 112876259Sgreen path = get_string(NULL); 1129162852Sdes debug3("request %u: readlink", id); 1130162852Sdes verbose("readlink \"%s\"", path); 1131137015Sdes if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) 113276259Sgreen send_status(id, errno_to_portable(errno)); 113376259Sgreen else { 113476259Sgreen Stat s; 113592555Sdes 1136137015Sdes buf[len] = '\0'; 113776259Sgreen attrib_clear(&s.attrib); 1138137015Sdes s.name = s.long_name = buf; 113976259Sgreen send_names(id, 1, &s); 114076259Sgreen } 114176259Sgreen xfree(path); 114276259Sgreen} 114376259Sgreen 114492555Sdesstatic void 114576259Sgreenprocess_symlink(void) 114676259Sgreen{ 114776259Sgreen u_int32_t id; 114876259Sgreen char *oldpath, *newpath; 1149113908Sdes int ret, status; 115076259Sgreen 115176259Sgreen id = get_int(); 115276259Sgreen oldpath = get_string(NULL); 115376259Sgreen newpath = get_string(NULL); 1154162852Sdes debug3("request %u: symlink", id); 1155162852Sdes logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); 1156113908Sdes /* this will fail if 'newpath' exists */ 1157204917Sdes if (readonly) 1158204917Sdes status = SSH2_FX_PERMISSION_DENIED; 1159204917Sdes else { 1160204917Sdes ret = symlink(oldpath, newpath); 1161204917Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1162204917Sdes } 116376259Sgreen send_status(id, status); 116476259Sgreen xfree(oldpath); 116576259Sgreen xfree(newpath); 116676259Sgreen} 116776259Sgreen 116892555Sdesstatic void 1169181111Sdesprocess_extended_posix_rename(u_int32_t id) 1170181111Sdes{ 1171181111Sdes char *oldpath, *newpath; 1172204917Sdes int ret, status; 1173181111Sdes 1174181111Sdes oldpath = get_string(NULL); 1175181111Sdes newpath = get_string(NULL); 1176181111Sdes debug3("request %u: posix-rename", id); 1177181111Sdes logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath); 1178204917Sdes if (readonly) 1179204917Sdes status = SSH2_FX_PERMISSION_DENIED; 1180204917Sdes else { 1181204917Sdes ret = rename(oldpath, newpath); 1182204917Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1183204917Sdes } 1184204917Sdes send_status(id, status); 1185181111Sdes xfree(oldpath); 1186181111Sdes xfree(newpath); 1187181111Sdes} 1188181111Sdes 1189181111Sdesstatic void 1190181111Sdesprocess_extended_statvfs(u_int32_t id) 1191181111Sdes{ 1192181111Sdes char *path; 1193181111Sdes struct statvfs st; 1194181111Sdes 1195181111Sdes path = get_string(NULL); 1196181111Sdes debug3("request %u: statfs", id); 1197181111Sdes logit("statfs \"%s\"", path); 1198181111Sdes 1199181111Sdes if (statvfs(path, &st) != 0) 1200181111Sdes send_status(id, errno_to_portable(errno)); 1201181111Sdes else 1202181111Sdes send_statvfs(id, &st); 1203181111Sdes xfree(path); 1204181111Sdes} 1205181111Sdes 1206181111Sdesstatic void 1207181111Sdesprocess_extended_fstatvfs(u_int32_t id) 1208181111Sdes{ 1209181111Sdes int handle, fd; 1210181111Sdes struct statvfs st; 1211181111Sdes 1212181111Sdes handle = get_handle(); 1213181111Sdes debug("request %u: fstatvfs \"%s\" (handle %u)", 1214181111Sdes id, handle_to_name(handle), handle); 1215181111Sdes if ((fd = handle_to_fd(handle)) < 0) { 1216181111Sdes send_status(id, SSH2_FX_FAILURE); 1217181111Sdes return; 1218181111Sdes } 1219181111Sdes if (fstatvfs(fd, &st) != 0) 1220181111Sdes send_status(id, errno_to_portable(errno)); 1221181111Sdes else 1222181111Sdes send_statvfs(id, &st); 1223181111Sdes} 1224181111Sdes 1225181111Sdesstatic void 122676259Sgreenprocess_extended(void) 122776259Sgreen{ 122876259Sgreen u_int32_t id; 122976259Sgreen char *request; 123076259Sgreen 123176259Sgreen id = get_int(); 123276259Sgreen request = get_string(NULL); 1233181111Sdes if (strcmp(request, "posix-rename@openssh.com") == 0) 1234181111Sdes process_extended_posix_rename(id); 1235181111Sdes else if (strcmp(request, "statvfs@openssh.com") == 0) 1236181111Sdes process_extended_statvfs(id); 1237181111Sdes else if (strcmp(request, "fstatvfs@openssh.com") == 0) 1238181111Sdes process_extended_fstatvfs(id); 1239181111Sdes else 1240181111Sdes send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ 124176259Sgreen xfree(request); 124276259Sgreen} 124376259Sgreen 124465668Skris/* stolen from ssh-agent */ 124565668Skris 124692555Sdesstatic void 124765668Skrisprocess(void) 124865668Skris{ 124976259Sgreen u_int msg_len; 125098675Sdes u_int buf_len; 125198675Sdes u_int consumed; 125276259Sgreen u_int type; 125376259Sgreen u_char *cp; 125465668Skris 125598675Sdes buf_len = buffer_len(&iqueue); 125698675Sdes if (buf_len < 5) 125765668Skris return; /* Incomplete message. */ 125892555Sdes cp = buffer_ptr(&iqueue); 1259162852Sdes msg_len = get_u32(cp); 1260157016Sdes if (msg_len > SFTP_MAX_MSG_LENGTH) { 1261162852Sdes error("bad message from %s local user %s", 1262162852Sdes client_addr, pw->pw_name); 1263181111Sdes sftp_server_cleanup_exit(11); 126465668Skris } 126598675Sdes if (buf_len < msg_len + 4) 126665668Skris return; 126765668Skris buffer_consume(&iqueue, 4); 126898675Sdes buf_len -= 4; 126965668Skris type = buffer_get_char(&iqueue); 127065668Skris switch (type) { 127176259Sgreen case SSH2_FXP_INIT: 127265668Skris process_init(); 127365668Skris break; 127476259Sgreen case SSH2_FXP_OPEN: 127565668Skris process_open(); 127665668Skris break; 127776259Sgreen case SSH2_FXP_CLOSE: 127865668Skris process_close(); 127965668Skris break; 128076259Sgreen case SSH2_FXP_READ: 128165668Skris process_read(); 128265668Skris break; 128376259Sgreen case SSH2_FXP_WRITE: 128465668Skris process_write(); 128565668Skris break; 128676259Sgreen case SSH2_FXP_LSTAT: 128765668Skris process_lstat(); 128865668Skris break; 128976259Sgreen case SSH2_FXP_FSTAT: 129065668Skris process_fstat(); 129165668Skris break; 129276259Sgreen case SSH2_FXP_SETSTAT: 129365668Skris process_setstat(); 129465668Skris break; 129576259Sgreen case SSH2_FXP_FSETSTAT: 129665668Skris process_fsetstat(); 129765668Skris break; 129876259Sgreen case SSH2_FXP_OPENDIR: 129965668Skris process_opendir(); 130065668Skris break; 130176259Sgreen case SSH2_FXP_READDIR: 130265668Skris process_readdir(); 130365668Skris break; 130476259Sgreen case SSH2_FXP_REMOVE: 130565668Skris process_remove(); 130665668Skris break; 130776259Sgreen case SSH2_FXP_MKDIR: 130865668Skris process_mkdir(); 130965668Skris break; 131076259Sgreen case SSH2_FXP_RMDIR: 131165668Skris process_rmdir(); 131265668Skris break; 131376259Sgreen case SSH2_FXP_REALPATH: 131465668Skris process_realpath(); 131565668Skris break; 131676259Sgreen case SSH2_FXP_STAT: 131765668Skris process_stat(); 131865668Skris break; 131976259Sgreen case SSH2_FXP_RENAME: 132065668Skris process_rename(); 132165668Skris break; 132276259Sgreen case SSH2_FXP_READLINK: 132376259Sgreen process_readlink(); 132476259Sgreen break; 132576259Sgreen case SSH2_FXP_SYMLINK: 132676259Sgreen process_symlink(); 132776259Sgreen break; 132876259Sgreen case SSH2_FXP_EXTENDED: 132976259Sgreen process_extended(); 133076259Sgreen break; 133165668Skris default: 133265668Skris error("Unknown message %d", type); 133365668Skris break; 133465668Skris } 133598675Sdes /* discard the remaining bytes from the current packet */ 1336181111Sdes if (buf_len < buffer_len(&iqueue)) { 1337181111Sdes error("iqueue grew unexpectedly"); 1338181111Sdes sftp_server_cleanup_exit(255); 1339181111Sdes } 134098675Sdes consumed = buf_len - buffer_len(&iqueue); 1341181111Sdes if (msg_len < consumed) { 1342181111Sdes error("msg_len %d < consumed %d", msg_len, consumed); 1343181111Sdes sftp_server_cleanup_exit(255); 1344181111Sdes } 134598675Sdes if (msg_len > consumed) 134698675Sdes buffer_consume(&iqueue, msg_len - consumed); 134765668Skris} 134865668Skris 1349162852Sdes/* Cleanup handler that logs active handles upon normal exit */ 1350162852Sdesvoid 1351181111Sdessftp_server_cleanup_exit(int i) 1352162852Sdes{ 1353162852Sdes if (pw != NULL && client_addr != NULL) { 1354162852Sdes handle_log_exit(); 1355162852Sdes logit("session closed for local user %s from [%s]", 1356162852Sdes pw->pw_name, client_addr); 1357162852Sdes } 1358162852Sdes _exit(i); 1359162852Sdes} 1360162852Sdes 1361162852Sdesstatic void 1362181111Sdessftp_server_usage(void) 1363162852Sdes{ 1364162852Sdes extern char *__progname; 1365162852Sdes 1366162852Sdes fprintf(stderr, 1367204917Sdes "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n", 1368204917Sdes __progname); 1369162852Sdes exit(1); 1370162852Sdes} 1371162852Sdes 137265668Skrisint 1373181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw) 137465668Skris{ 137576259Sgreen fd_set *rset, *wset; 1376162852Sdes int in, out, max, ch, skipargs = 0, log_stderr = 0; 137776259Sgreen ssize_t len, olen, set_size; 1378162852Sdes SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 1379181111Sdes char *cp, buf[4*4096]; 1380204917Sdes const char *errmsg; 1381204917Sdes mode_t mask; 138265668Skris 1383162852Sdes extern char *optarg; 1384162852Sdes extern char *__progname; 1385162852Sdes 1386162852Sdes __progname = ssh_get_progname(argv[0]); 1387162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 138876259Sgreen 1389204917Sdes while (!skipargs && (ch = getopt(argc, argv, "f:l:u:cehR")) != -1) { 1390162852Sdes switch (ch) { 1391204917Sdes case 'R': 1392204917Sdes readonly = 1; 1393204917Sdes break; 1394162852Sdes case 'c': 1395162852Sdes /* 1396162852Sdes * Ignore all arguments if we are invoked as a 1397162852Sdes * shell using "sftp-server -c command" 1398162852Sdes */ 1399162852Sdes skipargs = 1; 1400162852Sdes break; 1401162852Sdes case 'e': 1402162852Sdes log_stderr = 1; 1403162852Sdes break; 1404162852Sdes case 'l': 1405162852Sdes log_level = log_level_number(optarg); 1406162852Sdes if (log_level == SYSLOG_LEVEL_NOT_SET) 1407162852Sdes error("Invalid log level \"%s\"", optarg); 1408162852Sdes break; 1409162852Sdes case 'f': 1410162852Sdes log_facility = log_facility_number(optarg); 1411181111Sdes if (log_facility == SYSLOG_FACILITY_NOT_SET) 1412162852Sdes error("Invalid log facility \"%s\"", optarg); 1413162852Sdes break; 1414204917Sdes case 'u': 1415204917Sdes mask = (mode_t)strtonum(optarg, 0, 0777, &errmsg); 1416204917Sdes if (errmsg != NULL) 1417204917Sdes fatal("Invalid umask \"%s\": %s", 1418204917Sdes optarg, errmsg); 1419204917Sdes (void)umask(mask); 1420204917Sdes break; 1421162852Sdes case 'h': 1422162852Sdes default: 1423181111Sdes sftp_server_usage(); 1424162852Sdes } 1425162852Sdes } 1426162852Sdes 1427162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 1428162852Sdes 1429162852Sdes if ((cp = getenv("SSH_CONNECTION")) != NULL) { 1430162852Sdes client_addr = xstrdup(cp); 1431181111Sdes if ((cp = strchr(client_addr, ' ')) == NULL) { 1432181111Sdes error("Malformed SSH_CONNECTION variable: \"%s\"", 1433162852Sdes getenv("SSH_CONNECTION")); 1434181111Sdes sftp_server_cleanup_exit(255); 1435181111Sdes } 1436162852Sdes *cp = '\0'; 1437162852Sdes } else 1438162852Sdes client_addr = xstrdup("UNKNOWN"); 1439162852Sdes 1440181111Sdes pw = pwcopy(user_pw); 1441162852Sdes 1442162852Sdes logit("session opened for local user %s from [%s]", 1443162852Sdes pw->pw_name, client_addr); 1444162852Sdes 1445204917Sdes in = STDIN_FILENO; 1446204917Sdes out = STDOUT_FILENO; 144765668Skris 144898937Sdes#ifdef HAVE_CYGWIN 144998937Sdes setmode(in, O_BINARY); 145098937Sdes setmode(out, O_BINARY); 145198937Sdes#endif 145298937Sdes 145365668Skris max = 0; 145465668Skris if (in > max) 145565668Skris max = in; 145665668Skris if (out > max) 145765668Skris max = out; 145865668Skris 145965668Skris buffer_init(&iqueue); 146065668Skris buffer_init(&oqueue); 146165668Skris 146276259Sgreen set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); 146376259Sgreen rset = (fd_set *)xmalloc(set_size); 146476259Sgreen wset = (fd_set *)xmalloc(set_size); 146576259Sgreen 146665668Skris for (;;) { 146776259Sgreen memset(rset, 0, set_size); 146876259Sgreen memset(wset, 0, set_size); 146965668Skris 1470181111Sdes /* 1471181111Sdes * Ensure that we can read a full buffer and handle 1472181111Sdes * the worst-case length packet it can generate, 1473181111Sdes * otherwise apply backpressure by stopping reads. 1474181111Sdes */ 1475181111Sdes if (buffer_check_alloc(&iqueue, sizeof(buf)) && 1476181111Sdes buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1477181111Sdes FD_SET(in, rset); 1478181111Sdes 147965668Skris olen = buffer_len(&oqueue); 148065668Skris if (olen > 0) 148176259Sgreen FD_SET(out, wset); 148265668Skris 148376259Sgreen if (select(max+1, rset, wset, NULL, NULL) < 0) { 148465668Skris if (errno == EINTR) 148565668Skris continue; 1486162852Sdes error("select: %s", strerror(errno)); 1487181111Sdes sftp_server_cleanup_exit(2); 148865668Skris } 148965668Skris 149065668Skris /* copy stdin to iqueue */ 149176259Sgreen if (FD_ISSET(in, rset)) { 149265668Skris len = read(in, buf, sizeof buf); 149365668Skris if (len == 0) { 149465668Skris debug("read eof"); 1495181111Sdes sftp_server_cleanup_exit(0); 149665668Skris } else if (len < 0) { 1497162852Sdes error("read: %s", strerror(errno)); 1498181111Sdes sftp_server_cleanup_exit(1); 149965668Skris } else { 150065668Skris buffer_append(&iqueue, buf, len); 150165668Skris } 150265668Skris } 150365668Skris /* send oqueue to stdout */ 150476259Sgreen if (FD_ISSET(out, wset)) { 150565668Skris len = write(out, buffer_ptr(&oqueue), olen); 150665668Skris if (len < 0) { 1507162852Sdes error("write: %s", strerror(errno)); 1508181111Sdes sftp_server_cleanup_exit(1); 150965668Skris } else { 151065668Skris buffer_consume(&oqueue, len); 151165668Skris } 151265668Skris } 1513181111Sdes 1514181111Sdes /* 1515181111Sdes * Process requests from client if we can fit the results 1516181111Sdes * into the output buffer, otherwise stop processing input 1517181111Sdes * and let the output queue drain. 1518181111Sdes */ 1519181111Sdes if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1520181111Sdes process(); 152165668Skris } 152265668Skris} 1523