1262566Sdes/* $OpenBSD: sftp-server.c,v 1.103 2014/01/17 06:23:24 dtucker 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" 49262566Sdes#include "match.h" 50162852Sdes#include "uidswap.h" 5165668Skris 5276259Sgreen#include "sftp.h" 5376259Sgreen#include "sftp-common.h" 5465668Skris 5565668Skris/* helper */ 5676259Sgreen#define get_int64() buffer_get_int64(&iqueue); 5765668Skris#define get_int() buffer_get_int(&iqueue); 5865668Skris#define get_string(lenp) buffer_get_string(&iqueue, lenp); 5965668Skris 60162852Sdes/* Our verbosity */ 61262566Sdesstatic LogLevel log_level = SYSLOG_LEVEL_ERROR; 6298937Sdes 63162852Sdes/* Our client */ 64262566Sdesstatic struct passwd *pw = NULL; 65262566Sdesstatic char *client_addr = NULL; 66162852Sdes 6765668Skris/* input and output queue */ 68262566Sdesstatic Buffer iqueue; 69262566Sdesstatic Buffer oqueue; 7065668Skris 7176259Sgreen/* Version of client */ 72262566Sdesstatic u_int version; 7376259Sgreen 74262566Sdes/* SSH2_FXP_INIT received */ 75262566Sdesstatic int init_done; 76262566Sdes 77204917Sdes/* Disable writes */ 78262566Sdesstatic int readonly; 79204917Sdes 80262566Sdes/* Requests that are allowed/denied */ 81262566Sdesstatic char *request_whitelist, *request_blacklist; 82262566Sdes 83124208Sdes/* portable attributes, etc. */ 8465668Skristypedef struct Stat Stat; 8565668Skris 8676259Sgreenstruct Stat { 8765668Skris char *name; 8865668Skris char *long_name; 8965668Skris Attrib attrib; 9065668Skris}; 9165668Skris 92262566Sdes/* Packet handlers */ 93262566Sdesstatic void process_open(u_int32_t id); 94262566Sdesstatic void process_close(u_int32_t id); 95262566Sdesstatic void process_read(u_int32_t id); 96262566Sdesstatic void process_write(u_int32_t id); 97262566Sdesstatic void process_stat(u_int32_t id); 98262566Sdesstatic void process_lstat(u_int32_t id); 99262566Sdesstatic void process_fstat(u_int32_t id); 100262566Sdesstatic void process_setstat(u_int32_t id); 101262566Sdesstatic void process_fsetstat(u_int32_t id); 102262566Sdesstatic void process_opendir(u_int32_t id); 103262566Sdesstatic void process_readdir(u_int32_t id); 104262566Sdesstatic void process_remove(u_int32_t id); 105262566Sdesstatic void process_mkdir(u_int32_t id); 106262566Sdesstatic void process_rmdir(u_int32_t id); 107262566Sdesstatic void process_realpath(u_int32_t id); 108262566Sdesstatic void process_rename(u_int32_t id); 109262566Sdesstatic void process_readlink(u_int32_t id); 110262566Sdesstatic void process_symlink(u_int32_t id); 111262566Sdesstatic void process_extended_posix_rename(u_int32_t id); 112262566Sdesstatic void process_extended_statvfs(u_int32_t id); 113262566Sdesstatic void process_extended_fstatvfs(u_int32_t id); 114262566Sdesstatic void process_extended_hardlink(u_int32_t id); 115262566Sdesstatic void process_extended_fsync(u_int32_t id); 116262566Sdesstatic void process_extended(u_int32_t id); 117262566Sdes 118262566Sdesstruct sftp_handler { 119262566Sdes const char *name; /* user-visible name for fine-grained perms */ 120262566Sdes const char *ext_name; /* extended request name */ 121262566Sdes u_int type; /* packet type, for non extended packets */ 122262566Sdes void (*handler)(u_int32_t); 123262566Sdes int does_write; /* if nonzero, banned for readonly mode */ 124262566Sdes}; 125262566Sdes 126262566Sdesstruct sftp_handler handlers[] = { 127262566Sdes /* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */ 128262566Sdes { "open", NULL, SSH2_FXP_OPEN, process_open, 0 }, 129262566Sdes { "close", NULL, SSH2_FXP_CLOSE, process_close, 0 }, 130262566Sdes { "read", NULL, SSH2_FXP_READ, process_read, 0 }, 131262566Sdes { "write", NULL, SSH2_FXP_WRITE, process_write, 1 }, 132262566Sdes { "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 }, 133262566Sdes { "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 }, 134262566Sdes { "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 }, 135262566Sdes { "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 }, 136262566Sdes { "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 }, 137262566Sdes { "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 }, 138262566Sdes { "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 }, 139262566Sdes { "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 }, 140262566Sdes { "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 }, 141262566Sdes { "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 }, 142262566Sdes { "stat", NULL, SSH2_FXP_STAT, process_stat, 0 }, 143262566Sdes { "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 }, 144262566Sdes { "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 }, 145262566Sdes { "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 }, 146262566Sdes { NULL, NULL, 0, NULL, 0 } 147262566Sdes}; 148262566Sdes 149262566Sdes/* SSH2_FXP_EXTENDED submessages */ 150262566Sdesstruct sftp_handler extended_handlers[] = { 151262566Sdes { "posix-rename", "posix-rename@openssh.com", 0, 152262566Sdes process_extended_posix_rename, 1 }, 153262566Sdes { "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 }, 154262566Sdes { "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 }, 155262566Sdes { "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 }, 156262566Sdes { "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 }, 157262566Sdes { NULL, NULL, 0, NULL, 0 } 158262566Sdes}; 159262566Sdes 16092555Sdesstatic int 161262566Sdesrequest_permitted(struct sftp_handler *h) 162262566Sdes{ 163262566Sdes char *result; 164262566Sdes 165262566Sdes if (readonly && h->does_write) { 166262566Sdes verbose("Refusing %s request in read-only mode", h->name); 167262566Sdes return 0; 168262566Sdes } 169262566Sdes if (request_blacklist != NULL && 170262566Sdes ((result = match_list(h->name, request_blacklist, NULL))) != NULL) { 171262566Sdes free(result); 172262566Sdes verbose("Refusing blacklisted %s request", h->name); 173262566Sdes return 0; 174262566Sdes } 175262566Sdes if (request_whitelist != NULL && 176262566Sdes ((result = match_list(h->name, request_whitelist, NULL))) != NULL) { 177262566Sdes free(result); 178262566Sdes debug2("Permitting whitelisted %s request", h->name); 179262566Sdes return 1; 180262566Sdes } 181262566Sdes if (request_whitelist != NULL) { 182262566Sdes verbose("Refusing non-whitelisted %s request", h->name); 183262566Sdes return 0; 184262566Sdes } 185262566Sdes return 1; 186262566Sdes} 187262566Sdes 188262566Sdesstatic int 18965668Skriserrno_to_portable(int unixerrno) 19065668Skris{ 19165668Skris int ret = 0; 19276259Sgreen 19365668Skris switch (unixerrno) { 19465668Skris case 0: 19576259Sgreen ret = SSH2_FX_OK; 19665668Skris break; 19765668Skris case ENOENT: 19865668Skris case ENOTDIR: 19965668Skris case EBADF: 20065668Skris case ELOOP: 20176259Sgreen ret = SSH2_FX_NO_SUCH_FILE; 20265668Skris break; 20365668Skris case EPERM: 20465668Skris case EACCES: 20565668Skris case EFAULT: 20676259Sgreen ret = SSH2_FX_PERMISSION_DENIED; 20765668Skris break; 20865668Skris case ENAMETOOLONG: 20965668Skris case EINVAL: 21076259Sgreen ret = SSH2_FX_BAD_MESSAGE; 21165668Skris break; 212181111Sdes case ENOSYS: 213181111Sdes ret = SSH2_FX_OP_UNSUPPORTED; 214181111Sdes break; 21565668Skris default: 21676259Sgreen ret = SSH2_FX_FAILURE; 21765668Skris break; 21865668Skris } 21965668Skris return ret; 22065668Skris} 22165668Skris 22292555Sdesstatic int 22365668Skrisflags_from_portable(int pflags) 22465668Skris{ 22565668Skris int flags = 0; 22676259Sgreen 22776259Sgreen if ((pflags & SSH2_FXF_READ) && 22876259Sgreen (pflags & SSH2_FXF_WRITE)) { 22965668Skris flags = O_RDWR; 23076259Sgreen } else if (pflags & SSH2_FXF_READ) { 23165668Skris flags = O_RDONLY; 23276259Sgreen } else if (pflags & SSH2_FXF_WRITE) { 23365668Skris flags = O_WRONLY; 23465668Skris } 235262566Sdes if (pflags & SSH2_FXF_APPEND) 236262566Sdes flags |= O_APPEND; 23776259Sgreen if (pflags & SSH2_FXF_CREAT) 23865668Skris flags |= O_CREAT; 23976259Sgreen if (pflags & SSH2_FXF_TRUNC) 24065668Skris flags |= O_TRUNC; 24176259Sgreen if (pflags & SSH2_FXF_EXCL) 24265668Skris flags |= O_EXCL; 24365668Skris return flags; 24465668Skris} 24565668Skris 246162852Sdesstatic const char * 247162852Sdesstring_from_portable(int pflags) 248162852Sdes{ 249162852Sdes static char ret[128]; 250162852Sdes 251162852Sdes *ret = '\0'; 252162852Sdes 253162852Sdes#define PAPPEND(str) { \ 254162852Sdes if (*ret != '\0') \ 255162852Sdes strlcat(ret, ",", sizeof(ret)); \ 256162852Sdes strlcat(ret, str, sizeof(ret)); \ 257162852Sdes } 258162852Sdes 259162852Sdes if (pflags & SSH2_FXF_READ) 260162852Sdes PAPPEND("READ") 261162852Sdes if (pflags & SSH2_FXF_WRITE) 262162852Sdes PAPPEND("WRITE") 263262566Sdes if (pflags & SSH2_FXF_APPEND) 264262566Sdes PAPPEND("APPEND") 265162852Sdes if (pflags & SSH2_FXF_CREAT) 266162852Sdes PAPPEND("CREATE") 267162852Sdes if (pflags & SSH2_FXF_TRUNC) 268162852Sdes PAPPEND("TRUNCATE") 269162852Sdes if (pflags & SSH2_FXF_EXCL) 270162852Sdes PAPPEND("EXCL") 271162852Sdes 272162852Sdes return ret; 273162852Sdes} 274162852Sdes 27592555Sdesstatic Attrib * 27665668Skrisget_attrib(void) 27765668Skris{ 27865668Skris return decode_attrib(&iqueue); 27965668Skris} 28065668Skris 28165668Skris/* handle handles */ 28265668Skris 28365668Skristypedef struct Handle Handle; 28465668Skrisstruct Handle { 28565668Skris int use; 28665668Skris DIR *dirp; 28765668Skris int fd; 288262566Sdes int flags; 28965668Skris char *name; 290162852Sdes u_int64_t bytes_read, bytes_write; 291181111Sdes int next_unused; 29265668Skris}; 29376259Sgreen 29465668Skrisenum { 29565668Skris HANDLE_UNUSED, 29665668Skris HANDLE_DIR, 29765668Skris HANDLE_FILE 29865668Skris}; 29976259Sgreen 300181111SdesHandle *handles = NULL; 301181111Sdesu_int num_handles = 0; 302181111Sdesint first_unused_handle = -1; 30365668Skris 304181111Sdesstatic void handle_unused(int i) 30565668Skris{ 306181111Sdes handles[i].use = HANDLE_UNUSED; 307181111Sdes handles[i].next_unused = first_unused_handle; 308181111Sdes first_unused_handle = i; 30965668Skris} 31065668Skris 31192555Sdesstatic int 312262566Sdeshandle_new(int use, const char *name, int fd, int flags, DIR *dirp) 31365668Skris{ 314181111Sdes int i; 31576259Sgreen 316181111Sdes if (first_unused_handle == -1) { 317181111Sdes if (num_handles + 1 <= num_handles) 318181111Sdes return -1; 319181111Sdes num_handles++; 320181111Sdes handles = xrealloc(handles, num_handles, sizeof(Handle)); 321181111Sdes handle_unused(num_handles - 1); 32265668Skris } 323181111Sdes 324181111Sdes i = first_unused_handle; 325181111Sdes first_unused_handle = handles[i].next_unused; 326181111Sdes 327181111Sdes handles[i].use = use; 328181111Sdes handles[i].dirp = dirp; 329181111Sdes handles[i].fd = fd; 330262566Sdes handles[i].flags = flags; 331181111Sdes handles[i].name = xstrdup(name); 332181111Sdes handles[i].bytes_read = handles[i].bytes_write = 0; 333181111Sdes 334181111Sdes return i; 33565668Skris} 33665668Skris 33792555Sdesstatic int 33865668Skrishandle_is_ok(int i, int type) 33965668Skris{ 340181111Sdes return i >= 0 && (u_int)i < num_handles && handles[i].use == type; 34165668Skris} 34265668Skris 34392555Sdesstatic int 34465668Skrishandle_to_string(int handle, char **stringp, int *hlenp) 34565668Skris{ 34665668Skris if (stringp == NULL || hlenp == NULL) 34765668Skris return -1; 34876259Sgreen *stringp = xmalloc(sizeof(int32_t)); 349162852Sdes put_u32(*stringp, handle); 35076259Sgreen *hlenp = sizeof(int32_t); 35165668Skris return 0; 35265668Skris} 35365668Skris 35492555Sdesstatic int 355126274Sdeshandle_from_string(const char *handle, u_int hlen) 35665668Skris{ 35776259Sgreen int val; 35876259Sgreen 35976259Sgreen if (hlen != sizeof(int32_t)) 36065668Skris return -1; 361162852Sdes val = get_u32(handle); 36265668Skris if (handle_is_ok(val, HANDLE_FILE) || 36365668Skris handle_is_ok(val, HANDLE_DIR)) 36465668Skris return val; 36565668Skris return -1; 36665668Skris} 36765668Skris 36892555Sdesstatic char * 36965668Skrishandle_to_name(int handle) 37065668Skris{ 37165668Skris if (handle_is_ok(handle, HANDLE_DIR)|| 37265668Skris handle_is_ok(handle, HANDLE_FILE)) 37365668Skris return handles[handle].name; 37465668Skris return NULL; 37565668Skris} 37665668Skris 37792555Sdesstatic DIR * 37865668Skrishandle_to_dir(int handle) 37965668Skris{ 38065668Skris if (handle_is_ok(handle, HANDLE_DIR)) 38165668Skris return handles[handle].dirp; 38265668Skris return NULL; 38365668Skris} 38465668Skris 38592555Sdesstatic int 38665668Skrishandle_to_fd(int handle) 38765668Skris{ 38876259Sgreen if (handle_is_ok(handle, HANDLE_FILE)) 38965668Skris return handles[handle].fd; 39065668Skris return -1; 39165668Skris} 39265668Skris 393262566Sdesstatic int 394262566Sdeshandle_to_flags(int handle) 395262566Sdes{ 396262566Sdes if (handle_is_ok(handle, HANDLE_FILE)) 397262566Sdes return handles[handle].flags; 398262566Sdes return 0; 399262566Sdes} 400262566Sdes 401162852Sdesstatic void 402162852Sdeshandle_update_read(int handle, ssize_t bytes) 403162852Sdes{ 404162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 405162852Sdes handles[handle].bytes_read += bytes; 406162852Sdes} 407162852Sdes 408162852Sdesstatic void 409162852Sdeshandle_update_write(int handle, ssize_t bytes) 410162852Sdes{ 411162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 412162852Sdes handles[handle].bytes_write += bytes; 413162852Sdes} 414162852Sdes 415162852Sdesstatic u_int64_t 416162852Sdeshandle_bytes_read(int handle) 417162852Sdes{ 418162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 419162852Sdes return (handles[handle].bytes_read); 420162852Sdes return 0; 421162852Sdes} 422162852Sdes 423162852Sdesstatic u_int64_t 424162852Sdeshandle_bytes_write(int handle) 425162852Sdes{ 426162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 427162852Sdes return (handles[handle].bytes_write); 428162852Sdes return 0; 429162852Sdes} 430162852Sdes 43192555Sdesstatic int 43265668Skrishandle_close(int handle) 43365668Skris{ 43465668Skris int ret = -1; 43576259Sgreen 43665668Skris if (handle_is_ok(handle, HANDLE_FILE)) { 43765668Skris ret = close(handles[handle].fd); 438255767Sdes free(handles[handle].name); 439181111Sdes handle_unused(handle); 44065668Skris } else if (handle_is_ok(handle, HANDLE_DIR)) { 44165668Skris ret = closedir(handles[handle].dirp); 442255767Sdes free(handles[handle].name); 443181111Sdes handle_unused(handle); 44465668Skris } else { 44565668Skris errno = ENOENT; 44665668Skris } 44765668Skris return ret; 44865668Skris} 44965668Skris 450162852Sdesstatic void 451162852Sdeshandle_log_close(int handle, char *emsg) 452162852Sdes{ 453162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) { 454162852Sdes logit("%s%sclose \"%s\" bytes read %llu written %llu", 455162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 456162852Sdes handle_to_name(handle), 457181111Sdes (unsigned long long)handle_bytes_read(handle), 458181111Sdes (unsigned long long)handle_bytes_write(handle)); 459162852Sdes } else { 460162852Sdes logit("%s%sclosedir \"%s\"", 461162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 462162852Sdes handle_to_name(handle)); 463162852Sdes } 464162852Sdes} 465162852Sdes 466162852Sdesstatic void 467162852Sdeshandle_log_exit(void) 468162852Sdes{ 469162852Sdes u_int i; 470162852Sdes 471181111Sdes for (i = 0; i < num_handles; i++) 472162852Sdes if (handles[i].use != HANDLE_UNUSED) 473162852Sdes handle_log_close(i, "forced"); 474162852Sdes} 475162852Sdes 47692555Sdesstatic int 47765668Skrisget_handle(void) 47865668Skris{ 47965668Skris char *handle; 48076259Sgreen int val = -1; 48165668Skris u_int hlen; 48276259Sgreen 48365668Skris handle = get_string(&hlen); 48476259Sgreen if (hlen < 256) 48576259Sgreen val = handle_from_string(handle, hlen); 486255767Sdes free(handle); 48765668Skris return val; 48865668Skris} 48965668Skris 49065668Skris/* send replies */ 49165668Skris 49292555Sdesstatic void 49365668Skrissend_msg(Buffer *m) 49465668Skris{ 49565668Skris int mlen = buffer_len(m); 49676259Sgreen 49765668Skris buffer_put_int(&oqueue, mlen); 49865668Skris buffer_append(&oqueue, buffer_ptr(m), mlen); 49965668Skris buffer_consume(m, mlen); 50065668Skris} 50165668Skris 502162852Sdesstatic const char * 503162852Sdesstatus_to_message(u_int32_t status) 50465668Skris{ 50576259Sgreen const char *status_messages[] = { 50676259Sgreen "Success", /* SSH_FX_OK */ 50776259Sgreen "End of file", /* SSH_FX_EOF */ 50876259Sgreen "No such file", /* SSH_FX_NO_SUCH_FILE */ 50976259Sgreen "Permission denied", /* SSH_FX_PERMISSION_DENIED */ 51076259Sgreen "Failure", /* SSH_FX_FAILURE */ 51176259Sgreen "Bad message", /* SSH_FX_BAD_MESSAGE */ 51276259Sgreen "No connection", /* SSH_FX_NO_CONNECTION */ 51376259Sgreen "Connection lost", /* SSH_FX_CONNECTION_LOST */ 51476259Sgreen "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ 51576259Sgreen "Unknown error" /* Others */ 51676259Sgreen }; 517162852Sdes return (status_messages[MIN(status,SSH2_FX_MAX)]); 518162852Sdes} 51976259Sgreen 520162852Sdesstatic void 521162852Sdessend_status(u_int32_t id, u_int32_t status) 522162852Sdes{ 523162852Sdes Buffer msg; 524162852Sdes 525162852Sdes debug3("request %u: sent status %u", id, status); 526162852Sdes if (log_level > SYSLOG_LEVEL_VERBOSE || 527162852Sdes (status != SSH2_FX_OK && status != SSH2_FX_EOF)) 528162852Sdes logit("sent status %s", status_to_message(status)); 52965668Skris buffer_init(&msg); 53076259Sgreen buffer_put_char(&msg, SSH2_FXP_STATUS); 53165668Skris buffer_put_int(&msg, id); 532137015Sdes buffer_put_int(&msg, status); 53376259Sgreen if (version >= 3) { 534162852Sdes buffer_put_cstring(&msg, status_to_message(status)); 53576259Sgreen buffer_put_cstring(&msg, ""); 53676259Sgreen } 53765668Skris send_msg(&msg); 53865668Skris buffer_free(&msg); 53965668Skris} 54092555Sdesstatic void 541126274Sdessend_data_or_handle(char type, u_int32_t id, const char *data, int dlen) 54265668Skris{ 54365668Skris Buffer msg; 54476259Sgreen 54565668Skris buffer_init(&msg); 54665668Skris buffer_put_char(&msg, type); 54765668Skris buffer_put_int(&msg, id); 54865668Skris buffer_put_string(&msg, data, dlen); 54965668Skris send_msg(&msg); 55065668Skris buffer_free(&msg); 55165668Skris} 55265668Skris 55392555Sdesstatic void 554126274Sdessend_data(u_int32_t id, const char *data, int dlen) 55565668Skris{ 556162852Sdes debug("request %u: sent data len %d", id, dlen); 55776259Sgreen send_data_or_handle(SSH2_FXP_DATA, id, data, dlen); 55865668Skris} 55965668Skris 56092555Sdesstatic void 56165668Skrissend_handle(u_int32_t id, int handle) 56265668Skris{ 56365668Skris char *string; 56465668Skris int hlen; 56576259Sgreen 56665668Skris handle_to_string(handle, &string, &hlen); 567162852Sdes debug("request %u: sent handle handle %d", id, handle); 56876259Sgreen send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); 569255767Sdes free(string); 57065668Skris} 57165668Skris 57292555Sdesstatic void 573126274Sdessend_names(u_int32_t id, int count, const Stat *stats) 57465668Skris{ 57565668Skris Buffer msg; 57665668Skris int i; 57776259Sgreen 57865668Skris buffer_init(&msg); 57976259Sgreen buffer_put_char(&msg, SSH2_FXP_NAME); 58065668Skris buffer_put_int(&msg, id); 58165668Skris buffer_put_int(&msg, count); 582162852Sdes debug("request %u: sent names count %d", id, count); 58365668Skris for (i = 0; i < count; i++) { 58465668Skris buffer_put_cstring(&msg, stats[i].name); 58565668Skris buffer_put_cstring(&msg, stats[i].long_name); 58665668Skris encode_attrib(&msg, &stats[i].attrib); 58765668Skris } 58865668Skris send_msg(&msg); 58965668Skris buffer_free(&msg); 59065668Skris} 59165668Skris 59292555Sdesstatic void 593126274Sdessend_attrib(u_int32_t id, const Attrib *a) 59465668Skris{ 59565668Skris Buffer msg; 59676259Sgreen 597162852Sdes debug("request %u: sent attrib have 0x%x", id, a->flags); 59865668Skris buffer_init(&msg); 59976259Sgreen buffer_put_char(&msg, SSH2_FXP_ATTRS); 60065668Skris buffer_put_int(&msg, id); 60165668Skris encode_attrib(&msg, a); 60265668Skris send_msg(&msg); 60365668Skris buffer_free(&msg); 60465668Skris} 60565668Skris 606181111Sdesstatic void 607181111Sdessend_statvfs(u_int32_t id, struct statvfs *st) 608181111Sdes{ 609181111Sdes Buffer msg; 610181111Sdes u_int64_t flag; 611181111Sdes 612181111Sdes flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; 613181111Sdes flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; 614181111Sdes 615181111Sdes buffer_init(&msg); 616181111Sdes buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY); 617181111Sdes buffer_put_int(&msg, id); 618181111Sdes buffer_put_int64(&msg, st->f_bsize); 619181111Sdes buffer_put_int64(&msg, st->f_frsize); 620181111Sdes buffer_put_int64(&msg, st->f_blocks); 621181111Sdes buffer_put_int64(&msg, st->f_bfree); 622181111Sdes buffer_put_int64(&msg, st->f_bavail); 623181111Sdes buffer_put_int64(&msg, st->f_files); 624181111Sdes buffer_put_int64(&msg, st->f_ffree); 625181111Sdes buffer_put_int64(&msg, st->f_favail); 626181111Sdes buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid)); 627181111Sdes buffer_put_int64(&msg, flag); 628181111Sdes buffer_put_int64(&msg, st->f_namemax); 629181111Sdes send_msg(&msg); 630181111Sdes buffer_free(&msg); 631181111Sdes} 632181111Sdes 63365668Skris/* parse incoming */ 63465668Skris 63592555Sdesstatic void 63665668Skrisprocess_init(void) 63765668Skris{ 63865668Skris Buffer msg; 63965668Skris 64098675Sdes version = get_int(); 641226046Sdes verbose("received client version %u", version); 64265668Skris buffer_init(&msg); 64376259Sgreen buffer_put_char(&msg, SSH2_FXP_VERSION); 64476259Sgreen buffer_put_int(&msg, SSH2_FILEXFER_VERSION); 645181111Sdes /* POSIX rename extension */ 646181111Sdes buffer_put_cstring(&msg, "posix-rename@openssh.com"); 647181111Sdes buffer_put_cstring(&msg, "1"); /* version */ 648181111Sdes /* statvfs extension */ 649181111Sdes buffer_put_cstring(&msg, "statvfs@openssh.com"); 650181111Sdes buffer_put_cstring(&msg, "2"); /* version */ 651181111Sdes /* fstatvfs extension */ 652181111Sdes buffer_put_cstring(&msg, "fstatvfs@openssh.com"); 653181111Sdes buffer_put_cstring(&msg, "2"); /* version */ 654221420Sdes /* hardlink extension */ 655221420Sdes buffer_put_cstring(&msg, "hardlink@openssh.com"); 656221420Sdes buffer_put_cstring(&msg, "1"); /* version */ 657262566Sdes /* fsync extension */ 658262566Sdes buffer_put_cstring(&msg, "fsync@openssh.com"); 659262566Sdes buffer_put_cstring(&msg, "1"); /* version */ 66065668Skris send_msg(&msg); 66165668Skris buffer_free(&msg); 66265668Skris} 66365668Skris 66492555Sdesstatic void 665262566Sdesprocess_open(u_int32_t id) 66665668Skris{ 667262566Sdes u_int32_t pflags; 66865668Skris Attrib *a; 66965668Skris char *name; 67076259Sgreen int handle, fd, flags, mode, status = SSH2_FX_FAILURE; 67165668Skris 67265668Skris name = get_string(NULL); 67376259Sgreen pflags = get_int(); /* portable flags */ 674162852Sdes debug3("request %u: open flags %d", id, pflags); 67565668Skris a = get_attrib(); 67665668Skris flags = flags_from_portable(pflags); 67776259Sgreen mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666; 678162852Sdes logit("open \"%s\" flags %s mode 0%o", 679162852Sdes name, string_from_portable(pflags), mode); 680204917Sdes if (readonly && 681262566Sdes ((flags & O_ACCMODE) == O_WRONLY || 682262566Sdes (flags & O_ACCMODE) == O_RDWR)) { 683262566Sdes verbose("Refusing open request in read-only mode"); 684262566Sdes status = SSH2_FX_PERMISSION_DENIED; 685262566Sdes } else { 686204917Sdes fd = open(name, flags, mode); 687204917Sdes if (fd < 0) { 688204917Sdes status = errno_to_portable(errno); 68965668Skris } else { 690262566Sdes handle = handle_new(HANDLE_FILE, name, fd, flags, NULL); 691204917Sdes if (handle < 0) { 692204917Sdes close(fd); 693204917Sdes } else { 694204917Sdes send_handle(id, handle); 695204917Sdes status = SSH2_FX_OK; 696204917Sdes } 69765668Skris } 69865668Skris } 69976259Sgreen if (status != SSH2_FX_OK) 70065668Skris send_status(id, status); 701255767Sdes free(name); 70265668Skris} 70365668Skris 70492555Sdesstatic void 705262566Sdesprocess_close(u_int32_t id) 70665668Skris{ 70776259Sgreen int handle, ret, status = SSH2_FX_FAILURE; 70865668Skris 70965668Skris handle = get_handle(); 710162852Sdes debug3("request %u: close handle %u", id, handle); 711162852Sdes handle_log_close(handle, NULL); 71265668Skris ret = handle_close(handle); 71376259Sgreen status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 71465668Skris send_status(id, status); 71565668Skris} 71665668Skris 71792555Sdesstatic void 718262566Sdesprocess_read(u_int32_t id) 71965668Skris{ 72065668Skris char buf[64*1024]; 721262566Sdes u_int32_t len; 72276259Sgreen int handle, fd, ret, status = SSH2_FX_FAILURE; 72365668Skris u_int64_t off; 72465668Skris 72565668Skris handle = get_handle(); 72676259Sgreen off = get_int64(); 72765668Skris len = get_int(); 72865668Skris 729162852Sdes debug("request %u: read \"%s\" (handle %d) off %llu len %d", 730162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 73165668Skris if (len > sizeof buf) { 73265668Skris len = sizeof buf; 733162852Sdes debug2("read change len %d", len); 73465668Skris } 73565668Skris fd = handle_to_fd(handle); 73665668Skris if (fd >= 0) { 73765668Skris if (lseek(fd, off, SEEK_SET) < 0) { 73865668Skris error("process_read: seek failed"); 73965668Skris status = errno_to_portable(errno); 74065668Skris } else { 74165668Skris ret = read(fd, buf, len); 74265668Skris if (ret < 0) { 74365668Skris status = errno_to_portable(errno); 74465668Skris } else if (ret == 0) { 74576259Sgreen status = SSH2_FX_EOF; 74665668Skris } else { 74765668Skris send_data(id, buf, ret); 74876259Sgreen status = SSH2_FX_OK; 749162852Sdes handle_update_read(handle, ret); 75065668Skris } 75165668Skris } 75265668Skris } 75376259Sgreen if (status != SSH2_FX_OK) 75465668Skris send_status(id, status); 75565668Skris} 75665668Skris 75792555Sdesstatic void 758262566Sdesprocess_write(u_int32_t id) 75965668Skris{ 76065668Skris u_int64_t off; 76165668Skris u_int len; 762204917Sdes int handle, fd, ret, status; 76365668Skris char *data; 76465668Skris 76565668Skris handle = get_handle(); 76676259Sgreen off = get_int64(); 76765668Skris data = get_string(&len); 76865668Skris 769162852Sdes debug("request %u: write \"%s\" (handle %d) off %llu len %d", 770162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 77165668Skris fd = handle_to_fd(handle); 772204917Sdes 773204917Sdes if (fd < 0) 774204917Sdes status = SSH2_FX_FAILURE; 775204917Sdes else { 776262566Sdes if (!(handle_to_flags(handle) & O_APPEND) && 777262566Sdes lseek(fd, off, SEEK_SET) < 0) { 77865668Skris status = errno_to_portable(errno); 77965668Skris error("process_write: seek failed"); 78065668Skris } else { 78165668Skris/* XXX ATOMICIO ? */ 78265668Skris ret = write(fd, data, len); 783149749Sdes if (ret < 0) { 78465668Skris error("process_write: write failed"); 78565668Skris status = errno_to_portable(errno); 786149749Sdes } else if ((size_t)ret == len) { 78776259Sgreen status = SSH2_FX_OK; 788162852Sdes handle_update_write(handle, ret); 78965668Skris } else { 790162852Sdes debug2("nothing at all written"); 791204917Sdes status = SSH2_FX_FAILURE; 79265668Skris } 79365668Skris } 79465668Skris } 79565668Skris send_status(id, status); 796255767Sdes free(data); 79765668Skris} 79865668Skris 79992555Sdesstatic void 800262566Sdesprocess_do_stat(u_int32_t id, int do_lstat) 80165668Skris{ 80276259Sgreen Attrib a; 80365668Skris struct stat st; 80465668Skris char *name; 80576259Sgreen int ret, status = SSH2_FX_FAILURE; 80665668Skris 80765668Skris name = get_string(NULL); 808162852Sdes debug3("request %u: %sstat", id, do_lstat ? "l" : ""); 809162852Sdes verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name); 81065668Skris ret = do_lstat ? lstat(name, &st) : stat(name, &st); 81165668Skris if (ret < 0) { 81265668Skris status = errno_to_portable(errno); 81365668Skris } else { 81476259Sgreen stat_to_attrib(&st, &a); 81576259Sgreen send_attrib(id, &a); 81676259Sgreen status = SSH2_FX_OK; 81765668Skris } 81876259Sgreen if (status != SSH2_FX_OK) 81965668Skris send_status(id, status); 820255767Sdes free(name); 82165668Skris} 82265668Skris 82392555Sdesstatic void 824262566Sdesprocess_stat(u_int32_t id) 82565668Skris{ 826262566Sdes process_do_stat(id, 0); 82765668Skris} 82865668Skris 82992555Sdesstatic void 830262566Sdesprocess_lstat(u_int32_t id) 83165668Skris{ 832262566Sdes process_do_stat(id, 1); 83365668Skris} 83465668Skris 83592555Sdesstatic void 836262566Sdesprocess_fstat(u_int32_t id) 83765668Skris{ 83876259Sgreen Attrib a; 83965668Skris struct stat st; 84076259Sgreen int fd, ret, handle, status = SSH2_FX_FAILURE; 84165668Skris 84265668Skris handle = get_handle(); 843162852Sdes debug("request %u: fstat \"%s\" (handle %u)", 844162852Sdes id, handle_to_name(handle), handle); 84565668Skris fd = handle_to_fd(handle); 846181111Sdes if (fd >= 0) { 84765668Skris ret = fstat(fd, &st); 84865668Skris if (ret < 0) { 84965668Skris status = errno_to_portable(errno); 85065668Skris } else { 85176259Sgreen stat_to_attrib(&st, &a); 85276259Sgreen send_attrib(id, &a); 85376259Sgreen status = SSH2_FX_OK; 85465668Skris } 85565668Skris } 85676259Sgreen if (status != SSH2_FX_OK) 85765668Skris send_status(id, status); 85865668Skris} 85965668Skris 86092555Sdesstatic struct timeval * 861126274Sdesattrib_to_tv(const Attrib *a) 86265668Skris{ 86365668Skris static struct timeval tv[2]; 86476259Sgreen 86565668Skris tv[0].tv_sec = a->atime; 86665668Skris tv[0].tv_usec = 0; 86765668Skris tv[1].tv_sec = a->mtime; 86865668Skris tv[1].tv_usec = 0; 86965668Skris return tv; 87065668Skris} 87165668Skris 87292555Sdesstatic void 873262566Sdesprocess_setstat(u_int32_t id) 87465668Skris{ 87565668Skris Attrib *a; 87665668Skris char *name; 87799060Sdes int status = SSH2_FX_OK, ret; 87865668Skris 87965668Skris name = get_string(NULL); 88065668Skris a = get_attrib(); 881162852Sdes debug("request %u: setstat name \"%s\"", id, name); 88292555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 883181111Sdes logit("set \"%s\" size %llu", 884181111Sdes name, (unsigned long long)a->size); 88592555Sdes ret = truncate(name, a->size); 88692555Sdes if (ret == -1) 88792555Sdes status = errno_to_portable(errno); 88892555Sdes } 88976259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 890162852Sdes logit("set \"%s\" mode %04o", name, a->perm); 891181111Sdes ret = chmod(name, a->perm & 07777); 89265668Skris if (ret == -1) 89365668Skris status = errno_to_portable(errno); 89465668Skris } 89576259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 896162852Sdes char buf[64]; 897162852Sdes time_t t = a->mtime; 898162852Sdes 899162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 900162852Sdes localtime(&t)); 901162852Sdes logit("set \"%s\" modtime %s", name, buf); 90265668Skris ret = utimes(name, attrib_to_tv(a)); 90365668Skris if (ret == -1) 90465668Skris status = errno_to_portable(errno); 90565668Skris } 90676259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 907162852Sdes logit("set \"%s\" owner %lu group %lu", name, 908162852Sdes (u_long)a->uid, (u_long)a->gid); 90976259Sgreen ret = chown(name, a->uid, a->gid); 91076259Sgreen if (ret == -1) 91176259Sgreen status = errno_to_portable(errno); 91276259Sgreen } 91365668Skris send_status(id, status); 914255767Sdes free(name); 91565668Skris} 91665668Skris 91792555Sdesstatic void 918262566Sdesprocess_fsetstat(u_int32_t id) 91965668Skris{ 92065668Skris Attrib *a; 92165668Skris int handle, fd, ret; 92276259Sgreen int status = SSH2_FX_OK; 92365668Skris 92465668Skris handle = get_handle(); 92565668Skris a = get_attrib(); 926162852Sdes debug("request %u: fsetstat handle %d", id, handle); 92765668Skris fd = handle_to_fd(handle); 928204917Sdes if (fd < 0) 92976259Sgreen status = SSH2_FX_FAILURE; 930204917Sdes else { 931162852Sdes char *name = handle_to_name(handle); 932162852Sdes 93392555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 934181111Sdes logit("set \"%s\" size %llu", 935181111Sdes name, (unsigned long long)a->size); 93692555Sdes ret = ftruncate(fd, a->size); 93792555Sdes if (ret == -1) 93892555Sdes status = errno_to_portable(errno); 93992555Sdes } 94076259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 941162852Sdes logit("set \"%s\" mode %04o", name, a->perm); 94298937Sdes#ifdef HAVE_FCHMOD 943181111Sdes ret = fchmod(fd, a->perm & 07777); 94498937Sdes#else 945181111Sdes ret = chmod(name, a->perm & 07777); 94698937Sdes#endif 94765668Skris if (ret == -1) 94865668Skris status = errno_to_portable(errno); 94965668Skris } 95076259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 951162852Sdes char buf[64]; 952162852Sdes time_t t = a->mtime; 953162852Sdes 954162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 955162852Sdes localtime(&t)); 956162852Sdes logit("set \"%s\" modtime %s", name, buf); 95798937Sdes#ifdef HAVE_FUTIMES 95865668Skris ret = futimes(fd, attrib_to_tv(a)); 95998937Sdes#else 96098937Sdes ret = utimes(name, attrib_to_tv(a)); 96198937Sdes#endif 96265668Skris if (ret == -1) 96365668Skris status = errno_to_portable(errno); 96465668Skris } 96576259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 966162852Sdes logit("set \"%s\" owner %lu group %lu", name, 967162852Sdes (u_long)a->uid, (u_long)a->gid); 96898937Sdes#ifdef HAVE_FCHOWN 96976259Sgreen ret = fchown(fd, a->uid, a->gid); 97098937Sdes#else 97198937Sdes ret = chown(name, a->uid, a->gid); 97298937Sdes#endif 97376259Sgreen if (ret == -1) 97476259Sgreen status = errno_to_portable(errno); 97576259Sgreen } 97665668Skris } 97765668Skris send_status(id, status); 97865668Skris} 97965668Skris 98092555Sdesstatic void 981262566Sdesprocess_opendir(u_int32_t id) 98265668Skris{ 98365668Skris DIR *dirp = NULL; 98465668Skris char *path; 98576259Sgreen int handle, status = SSH2_FX_FAILURE; 98665668Skris 98765668Skris path = get_string(NULL); 988162852Sdes debug3("request %u: opendir", id); 989162852Sdes logit("opendir \"%s\"", path); 99076259Sgreen dirp = opendir(path); 99165668Skris if (dirp == NULL) { 99265668Skris status = errno_to_portable(errno); 99365668Skris } else { 994262566Sdes handle = handle_new(HANDLE_DIR, path, 0, 0, dirp); 99565668Skris if (handle < 0) { 99665668Skris closedir(dirp); 99765668Skris } else { 99865668Skris send_handle(id, handle); 99976259Sgreen status = SSH2_FX_OK; 100065668Skris } 100176259Sgreen 100265668Skris } 100376259Sgreen if (status != SSH2_FX_OK) 100465668Skris send_status(id, status); 1005255767Sdes free(path); 100665668Skris} 100765668Skris 100892555Sdesstatic void 1009262566Sdesprocess_readdir(u_int32_t id) 101065668Skris{ 101165668Skris DIR *dirp; 101265668Skris struct dirent *dp; 101365668Skris char *path; 101465668Skris int handle; 101565668Skris 101665668Skris handle = get_handle(); 1017162852Sdes debug("request %u: readdir \"%s\" (handle %d)", id, 1018162852Sdes handle_to_name(handle), handle); 101965668Skris dirp = handle_to_dir(handle); 102065668Skris path = handle_to_name(handle); 102165668Skris if (dirp == NULL || path == NULL) { 102276259Sgreen send_status(id, SSH2_FX_FAILURE); 102365668Skris } else { 102465668Skris struct stat st; 1025162852Sdes char pathname[MAXPATHLEN]; 102665668Skris Stat *stats; 102765668Skris int nstats = 10, count = 0, i; 102899060Sdes 1029162852Sdes stats = xcalloc(nstats, sizeof(Stat)); 103065668Skris while ((dp = readdir(dirp)) != NULL) { 103165668Skris if (count >= nstats) { 103265668Skris nstats *= 2; 1033162852Sdes stats = xrealloc(stats, nstats, sizeof(Stat)); 103465668Skris } 103565668Skris/* XXX OVERFLOW ? */ 103692555Sdes snprintf(pathname, sizeof pathname, "%s%s%s", path, 103792555Sdes strcmp(path, "/") ? "/" : "", dp->d_name); 103865668Skris if (lstat(pathname, &st) < 0) 103965668Skris continue; 104076259Sgreen stat_to_attrib(&st, &(stats[count].attrib)); 104165668Skris stats[count].name = xstrdup(dp->d_name); 1042204917Sdes stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); 104365668Skris count++; 104465668Skris /* send up to 100 entries in one message */ 104576259Sgreen /* XXX check packet size instead */ 104665668Skris if (count == 100) 104765668Skris break; 104865668Skris } 104976259Sgreen if (count > 0) { 105076259Sgreen send_names(id, count, stats); 105192555Sdes for (i = 0; i < count; i++) { 1052255767Sdes free(stats[i].name); 1053255767Sdes free(stats[i].long_name); 105476259Sgreen } 105576259Sgreen } else { 105676259Sgreen send_status(id, SSH2_FX_EOF); 105765668Skris } 1058255767Sdes free(stats); 105965668Skris } 106065668Skris} 106165668Skris 106292555Sdesstatic void 1063262566Sdesprocess_remove(u_int32_t id) 106465668Skris{ 106565668Skris char *name; 106676259Sgreen int status = SSH2_FX_FAILURE; 106765668Skris int ret; 106865668Skris 106965668Skris name = get_string(NULL); 1070162852Sdes debug3("request %u: remove", id); 1071162852Sdes logit("remove name \"%s\"", name); 1072262566Sdes ret = unlink(name); 1073262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 107465668Skris send_status(id, status); 1075255767Sdes free(name); 107665668Skris} 107765668Skris 107892555Sdesstatic void 1079262566Sdesprocess_mkdir(u_int32_t id) 108065668Skris{ 108165668Skris Attrib *a; 108265668Skris char *name; 108376259Sgreen int ret, mode, status = SSH2_FX_FAILURE; 108465668Skris 108565668Skris name = get_string(NULL); 108665668Skris a = get_attrib(); 108776259Sgreen mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? 1088181111Sdes a->perm & 07777 : 0777; 1089162852Sdes debug3("request %u: mkdir", id); 1090162852Sdes logit("mkdir name \"%s\" mode 0%o", name, mode); 1091262566Sdes ret = mkdir(name, mode); 1092262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 109365668Skris send_status(id, status); 1094255767Sdes free(name); 109565668Skris} 109665668Skris 109792555Sdesstatic void 1098262566Sdesprocess_rmdir(u_int32_t id) 109965668Skris{ 110065668Skris char *name; 110165668Skris int ret, status; 110265668Skris 110365668Skris name = get_string(NULL); 1104162852Sdes debug3("request %u: rmdir", id); 1105162852Sdes logit("rmdir name \"%s\"", name); 1106262566Sdes ret = rmdir(name); 1107262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 110865668Skris send_status(id, status); 1109255767Sdes free(name); 111065668Skris} 111165668Skris 111292555Sdesstatic void 1113262566Sdesprocess_realpath(u_int32_t id) 111465668Skris{ 111565668Skris char resolvedname[MAXPATHLEN]; 111665668Skris char *path; 111765668Skris 111865668Skris path = get_string(NULL); 111976259Sgreen if (path[0] == '\0') { 1120255767Sdes free(path); 112176259Sgreen path = xstrdup("."); 112276259Sgreen } 1123162852Sdes debug3("request %u: realpath", id); 1124162852Sdes verbose("realpath \"%s\"", path); 112565668Skris if (realpath(path, resolvedname) == NULL) { 112665668Skris send_status(id, errno_to_portable(errno)); 112765668Skris } else { 112865668Skris Stat s; 112965668Skris attrib_clear(&s.attrib); 113065668Skris s.name = s.long_name = resolvedname; 113165668Skris send_names(id, 1, &s); 113265668Skris } 1133255767Sdes free(path); 113465668Skris} 113565668Skris 113692555Sdesstatic void 1137262566Sdesprocess_rename(u_int32_t id) 113865668Skris{ 113965668Skris char *oldpath, *newpath; 1140113908Sdes int status; 1141113908Sdes struct stat sb; 114265668Skris 114365668Skris oldpath = get_string(NULL); 114465668Skris newpath = get_string(NULL); 1145162852Sdes debug3("request %u: rename", id); 1146162852Sdes logit("rename old \"%s\" new \"%s\"", oldpath, newpath); 1147113908Sdes status = SSH2_FX_FAILURE; 1148262566Sdes if (lstat(oldpath, &sb) == -1) 1149113908Sdes status = errno_to_portable(errno); 1150113908Sdes else if (S_ISREG(sb.st_mode)) { 1151113908Sdes /* Race-free rename of regular files */ 1152137015Sdes if (link(oldpath, newpath) == -1) { 1153197679Sdes if (errno == EOPNOTSUPP || errno == ENOSYS 1154181111Sdes#ifdef EXDEV 1155181111Sdes || errno == EXDEV 1156181111Sdes#endif 1157137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO 1158137015Sdes || errno == LINK_OPNOTSUPP_ERRNO 1159137015Sdes#endif 1160137015Sdes ) { 1161137015Sdes struct stat st; 1162137015Sdes 1163137015Sdes /* 1164137015Sdes * fs doesn't support links, so fall back to 1165137015Sdes * stat+rename. This is racy. 1166137015Sdes */ 1167137015Sdes if (stat(newpath, &st) == -1) { 1168137015Sdes if (rename(oldpath, newpath) == -1) 1169137015Sdes status = 1170137015Sdes errno_to_portable(errno); 1171137015Sdes else 1172137015Sdes status = SSH2_FX_OK; 1173137015Sdes } 1174137015Sdes } else { 1175137015Sdes status = errno_to_portable(errno); 1176137015Sdes } 1177137015Sdes } else if (unlink(oldpath) == -1) { 1178113908Sdes status = errno_to_portable(errno); 1179113908Sdes /* clean spare link */ 1180113908Sdes unlink(newpath); 1181113908Sdes } else 1182113908Sdes status = SSH2_FX_OK; 1183113908Sdes } else if (stat(newpath, &sb) == -1) { 1184113908Sdes if (rename(oldpath, newpath) == -1) 1185113908Sdes status = errno_to_portable(errno); 1186113908Sdes else 1187113908Sdes status = SSH2_FX_OK; 118876259Sgreen } 118965668Skris send_status(id, status); 1190255767Sdes free(oldpath); 1191255767Sdes free(newpath); 119265668Skris} 119365668Skris 119492555Sdesstatic void 1195262566Sdesprocess_readlink(u_int32_t id) 119676259Sgreen{ 119792555Sdes int len; 1198137015Sdes char buf[MAXPATHLEN]; 119976259Sgreen char *path; 120065668Skris 120176259Sgreen path = get_string(NULL); 1202162852Sdes debug3("request %u: readlink", id); 1203162852Sdes verbose("readlink \"%s\"", path); 1204137015Sdes if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) 120576259Sgreen send_status(id, errno_to_portable(errno)); 120676259Sgreen else { 120776259Sgreen Stat s; 120892555Sdes 1209137015Sdes buf[len] = '\0'; 121076259Sgreen attrib_clear(&s.attrib); 1211137015Sdes s.name = s.long_name = buf; 121276259Sgreen send_names(id, 1, &s); 121376259Sgreen } 1214255767Sdes free(path); 121576259Sgreen} 121676259Sgreen 121792555Sdesstatic void 1218262566Sdesprocess_symlink(u_int32_t id) 121976259Sgreen{ 122076259Sgreen char *oldpath, *newpath; 1221113908Sdes int ret, status; 122276259Sgreen 122376259Sgreen oldpath = get_string(NULL); 122476259Sgreen newpath = get_string(NULL); 1225162852Sdes debug3("request %u: symlink", id); 1226162852Sdes logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); 1227113908Sdes /* this will fail if 'newpath' exists */ 1228262566Sdes ret = symlink(oldpath, newpath); 1229262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 123076259Sgreen send_status(id, status); 1231255767Sdes free(oldpath); 1232255767Sdes free(newpath); 123376259Sgreen} 123476259Sgreen 123592555Sdesstatic void 1236181111Sdesprocess_extended_posix_rename(u_int32_t id) 1237181111Sdes{ 1238181111Sdes char *oldpath, *newpath; 1239204917Sdes int ret, status; 1240181111Sdes 1241181111Sdes oldpath = get_string(NULL); 1242181111Sdes newpath = get_string(NULL); 1243181111Sdes debug3("request %u: posix-rename", id); 1244181111Sdes logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath); 1245262566Sdes ret = rename(oldpath, newpath); 1246262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1247204917Sdes send_status(id, status); 1248255767Sdes free(oldpath); 1249255767Sdes free(newpath); 1250181111Sdes} 1251181111Sdes 1252181111Sdesstatic void 1253181111Sdesprocess_extended_statvfs(u_int32_t id) 1254181111Sdes{ 1255181111Sdes char *path; 1256181111Sdes struct statvfs st; 1257181111Sdes 1258181111Sdes path = get_string(NULL); 1259262566Sdes debug3("request %u: statvfs", id); 1260262566Sdes logit("statvfs \"%s\"", path); 1261181111Sdes 1262181111Sdes if (statvfs(path, &st) != 0) 1263181111Sdes send_status(id, errno_to_portable(errno)); 1264181111Sdes else 1265181111Sdes send_statvfs(id, &st); 1266255767Sdes free(path); 1267181111Sdes} 1268181111Sdes 1269181111Sdesstatic void 1270181111Sdesprocess_extended_fstatvfs(u_int32_t id) 1271181111Sdes{ 1272181111Sdes int handle, fd; 1273181111Sdes struct statvfs st; 1274181111Sdes 1275181111Sdes handle = get_handle(); 1276181111Sdes debug("request %u: fstatvfs \"%s\" (handle %u)", 1277181111Sdes id, handle_to_name(handle), handle); 1278181111Sdes if ((fd = handle_to_fd(handle)) < 0) { 1279181111Sdes send_status(id, SSH2_FX_FAILURE); 1280181111Sdes return; 1281181111Sdes } 1282181111Sdes if (fstatvfs(fd, &st) != 0) 1283181111Sdes send_status(id, errno_to_portable(errno)); 1284181111Sdes else 1285181111Sdes send_statvfs(id, &st); 1286181111Sdes} 1287181111Sdes 1288181111Sdesstatic void 1289221420Sdesprocess_extended_hardlink(u_int32_t id) 1290221420Sdes{ 1291221420Sdes char *oldpath, *newpath; 1292221420Sdes int ret, status; 1293221420Sdes 1294221420Sdes oldpath = get_string(NULL); 1295221420Sdes newpath = get_string(NULL); 1296221420Sdes debug3("request %u: hardlink", id); 1297221420Sdes logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath); 1298262566Sdes ret = link(oldpath, newpath); 1299262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1300221420Sdes send_status(id, status); 1301255767Sdes free(oldpath); 1302255767Sdes free(newpath); 1303221420Sdes} 1304221420Sdes 1305221420Sdesstatic void 1306262566Sdesprocess_extended_fsync(u_int32_t id) 130776259Sgreen{ 1308262566Sdes int handle, fd, ret, status = SSH2_FX_OP_UNSUPPORTED; 1309262566Sdes 1310262566Sdes handle = get_handle(); 1311262566Sdes debug3("request %u: fsync (handle %u)", id, handle); 1312262566Sdes verbose("fsync \"%s\"", handle_to_name(handle)); 1313262566Sdes if ((fd = handle_to_fd(handle)) < 0) 1314262566Sdes status = SSH2_FX_NO_SUCH_FILE; 1315262566Sdes else if (handle_is_ok(handle, HANDLE_FILE)) { 1316262566Sdes ret = fsync(fd); 1317262566Sdes status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1318262566Sdes } 1319262566Sdes send_status(id, status); 1320262566Sdes} 1321262566Sdes 1322262566Sdesstatic void 1323262566Sdesprocess_extended(u_int32_t id) 1324262566Sdes{ 132576259Sgreen char *request; 1326262566Sdes u_int i; 132776259Sgreen 132876259Sgreen request = get_string(NULL); 1329262566Sdes for (i = 0; extended_handlers[i].handler != NULL; i++) { 1330262566Sdes if (strcmp(request, extended_handlers[i].ext_name) == 0) { 1331262566Sdes if (!request_permitted(&extended_handlers[i])) 1332262566Sdes send_status(id, SSH2_FX_PERMISSION_DENIED); 1333262566Sdes else 1334262566Sdes extended_handlers[i].handler(id); 1335262566Sdes break; 1336262566Sdes } 1337262566Sdes } 1338262566Sdes if (extended_handlers[i].handler == NULL) { 1339262566Sdes error("Unknown extended request \"%.100s\"", request); 1340181111Sdes send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ 1341262566Sdes } 1342255767Sdes free(request); 134376259Sgreen} 134476259Sgreen 134565668Skris/* stolen from ssh-agent */ 134665668Skris 134792555Sdesstatic void 134865668Skrisprocess(void) 134965668Skris{ 1350262566Sdes u_int msg_len, buf_len, consumed, type, i; 135176259Sgreen u_char *cp; 1352262566Sdes u_int32_t id; 135365668Skris 135498675Sdes buf_len = buffer_len(&iqueue); 135598675Sdes if (buf_len < 5) 135665668Skris return; /* Incomplete message. */ 135792555Sdes cp = buffer_ptr(&iqueue); 1358162852Sdes msg_len = get_u32(cp); 1359157016Sdes if (msg_len > SFTP_MAX_MSG_LENGTH) { 1360162852Sdes error("bad message from %s local user %s", 1361162852Sdes client_addr, pw->pw_name); 1362181111Sdes sftp_server_cleanup_exit(11); 136365668Skris } 136498675Sdes if (buf_len < msg_len + 4) 136565668Skris return; 136665668Skris buffer_consume(&iqueue, 4); 136798675Sdes buf_len -= 4; 136865668Skris type = buffer_get_char(&iqueue); 1369262566Sdes 137065668Skris switch (type) { 137176259Sgreen case SSH2_FXP_INIT: 137265668Skris process_init(); 1373262566Sdes init_done = 1; 137465668Skris break; 137576259Sgreen case SSH2_FXP_EXTENDED: 1376262566Sdes if (!init_done) 1377262566Sdes fatal("Received extended request before init"); 1378262566Sdes id = get_int(); 1379262566Sdes process_extended(id); 138076259Sgreen break; 138165668Skris default: 1382262566Sdes if (!init_done) 1383262566Sdes fatal("Received %u request before init", type); 1384262566Sdes id = get_int(); 1385262566Sdes for (i = 0; handlers[i].handler != NULL; i++) { 1386262566Sdes if (type == handlers[i].type) { 1387262566Sdes if (!request_permitted(&handlers[i])) { 1388262566Sdes send_status(id, 1389262566Sdes SSH2_FX_PERMISSION_DENIED); 1390262566Sdes } else { 1391262566Sdes handlers[i].handler(id); 1392262566Sdes } 1393262566Sdes break; 1394262566Sdes } 1395262566Sdes } 1396262566Sdes if (handlers[i].handler == NULL) 1397262566Sdes error("Unknown message %u", type); 139865668Skris } 139998675Sdes /* discard the remaining bytes from the current packet */ 1400181111Sdes if (buf_len < buffer_len(&iqueue)) { 1401181111Sdes error("iqueue grew unexpectedly"); 1402181111Sdes sftp_server_cleanup_exit(255); 1403181111Sdes } 140498675Sdes consumed = buf_len - buffer_len(&iqueue); 1405181111Sdes if (msg_len < consumed) { 1406262566Sdes error("msg_len %u < consumed %u", msg_len, consumed); 1407181111Sdes sftp_server_cleanup_exit(255); 1408181111Sdes } 140998675Sdes if (msg_len > consumed) 141098675Sdes buffer_consume(&iqueue, msg_len - consumed); 141165668Skris} 141265668Skris 1413162852Sdes/* Cleanup handler that logs active handles upon normal exit */ 1414162852Sdesvoid 1415181111Sdessftp_server_cleanup_exit(int i) 1416162852Sdes{ 1417162852Sdes if (pw != NULL && client_addr != NULL) { 1418162852Sdes handle_log_exit(); 1419162852Sdes logit("session closed for local user %s from [%s]", 1420162852Sdes pw->pw_name, client_addr); 1421162852Sdes } 1422162852Sdes _exit(i); 1423162852Sdes} 1424162852Sdes 1425162852Sdesstatic void 1426181111Sdessftp_server_usage(void) 1427162852Sdes{ 1428162852Sdes extern char *__progname; 1429162852Sdes 1430162852Sdes fprintf(stderr, 1431248619Sdes "usage: %s [-ehR] [-d start_directory] [-f log_facility] " 1432262566Sdes "[-l log_level]\n\t[-P blacklisted_requests] " 1433262566Sdes "[-p whitelisted_requests] [-u umask]\n" 1434262566Sdes " %s -Q protocol_feature\n", 1435262566Sdes __progname, __progname); 1436162852Sdes exit(1); 1437162852Sdes} 1438162852Sdes 143965668Skrisint 1440181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw) 144165668Skris{ 144276259Sgreen fd_set *rset, *wset; 1443262566Sdes int i, in, out, max, ch, skipargs = 0, log_stderr = 0; 144476259Sgreen ssize_t len, olen, set_size; 1445162852Sdes SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 1446248619Sdes char *cp, *homedir = NULL, buf[4*4096]; 1447221420Sdes long mask; 144865668Skris 1449162852Sdes extern char *optarg; 1450162852Sdes extern char *__progname; 1451162852Sdes 1452162852Sdes __progname = ssh_get_progname(argv[0]); 1453162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 145476259Sgreen 1455248619Sdes pw = pwcopy(user_pw); 1456248619Sdes 1457262566Sdes while (!skipargs && (ch = getopt(argc, argv, 1458262566Sdes "d:f:l:P:p:Q:u:cehR")) != -1) { 1459162852Sdes switch (ch) { 1460262566Sdes case 'Q': 1461262566Sdes if (strcasecmp(optarg, "requests") != 0) { 1462262566Sdes fprintf(stderr, "Invalid query type\n"); 1463262566Sdes exit(1); 1464262566Sdes } 1465262566Sdes for (i = 0; handlers[i].handler != NULL; i++) 1466262566Sdes printf("%s\n", handlers[i].name); 1467262566Sdes for (i = 0; extended_handlers[i].handler != NULL; i++) 1468262566Sdes printf("%s\n", extended_handlers[i].name); 1469262566Sdes exit(0); 1470262566Sdes break; 1471204917Sdes case 'R': 1472204917Sdes readonly = 1; 1473204917Sdes break; 1474162852Sdes case 'c': 1475162852Sdes /* 1476162852Sdes * Ignore all arguments if we are invoked as a 1477162852Sdes * shell using "sftp-server -c command" 1478162852Sdes */ 1479162852Sdes skipargs = 1; 1480162852Sdes break; 1481162852Sdes case 'e': 1482162852Sdes log_stderr = 1; 1483162852Sdes break; 1484162852Sdes case 'l': 1485162852Sdes log_level = log_level_number(optarg); 1486162852Sdes if (log_level == SYSLOG_LEVEL_NOT_SET) 1487162852Sdes error("Invalid log level \"%s\"", optarg); 1488162852Sdes break; 1489162852Sdes case 'f': 1490162852Sdes log_facility = log_facility_number(optarg); 1491181111Sdes if (log_facility == SYSLOG_FACILITY_NOT_SET) 1492162852Sdes error("Invalid log facility \"%s\"", optarg); 1493162852Sdes break; 1494248619Sdes case 'd': 1495248619Sdes cp = tilde_expand_filename(optarg, user_pw->pw_uid); 1496248619Sdes homedir = percent_expand(cp, "d", user_pw->pw_dir, 1497248619Sdes "u", user_pw->pw_name, (char *)NULL); 1498248619Sdes free(cp); 1499248619Sdes break; 1500262566Sdes case 'p': 1501262566Sdes if (request_whitelist != NULL) 1502262566Sdes fatal("Permitted requests already set"); 1503262566Sdes request_whitelist = xstrdup(optarg); 1504262566Sdes break; 1505262566Sdes case 'P': 1506262566Sdes if (request_blacklist != NULL) 1507262566Sdes fatal("Refused requests already set"); 1508262566Sdes request_blacklist = xstrdup(optarg); 1509262566Sdes break; 1510204917Sdes case 'u': 1511221420Sdes errno = 0; 1512221420Sdes mask = strtol(optarg, &cp, 8); 1513221420Sdes if (mask < 0 || mask > 0777 || *cp != '\0' || 1514221420Sdes cp == optarg || (mask == 0 && errno != 0)) 1515221420Sdes fatal("Invalid umask \"%s\"", optarg); 1516221420Sdes (void)umask((mode_t)mask); 1517204917Sdes break; 1518162852Sdes case 'h': 1519162852Sdes default: 1520181111Sdes sftp_server_usage(); 1521162852Sdes } 1522162852Sdes } 1523162852Sdes 1524162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 1525162852Sdes 1526162852Sdes if ((cp = getenv("SSH_CONNECTION")) != NULL) { 1527162852Sdes client_addr = xstrdup(cp); 1528181111Sdes if ((cp = strchr(client_addr, ' ')) == NULL) { 1529181111Sdes error("Malformed SSH_CONNECTION variable: \"%s\"", 1530162852Sdes getenv("SSH_CONNECTION")); 1531181111Sdes sftp_server_cleanup_exit(255); 1532181111Sdes } 1533162852Sdes *cp = '\0'; 1534162852Sdes } else 1535162852Sdes client_addr = xstrdup("UNKNOWN"); 1536162852Sdes 1537162852Sdes logit("session opened for local user %s from [%s]", 1538162852Sdes pw->pw_name, client_addr); 1539162852Sdes 1540204917Sdes in = STDIN_FILENO; 1541204917Sdes out = STDOUT_FILENO; 154265668Skris 154398937Sdes#ifdef HAVE_CYGWIN 154498937Sdes setmode(in, O_BINARY); 154598937Sdes setmode(out, O_BINARY); 154698937Sdes#endif 154798937Sdes 154865668Skris max = 0; 154965668Skris if (in > max) 155065668Skris max = in; 155165668Skris if (out > max) 155265668Skris max = out; 155365668Skris 155465668Skris buffer_init(&iqueue); 155565668Skris buffer_init(&oqueue); 155665668Skris 155776259Sgreen set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); 155876259Sgreen rset = (fd_set *)xmalloc(set_size); 155976259Sgreen wset = (fd_set *)xmalloc(set_size); 156076259Sgreen 1561248619Sdes if (homedir != NULL) { 1562248619Sdes if (chdir(homedir) != 0) { 1563248619Sdes error("chdir to \"%s\" failed: %s", homedir, 1564248619Sdes strerror(errno)); 1565248619Sdes } 1566248619Sdes } 1567248619Sdes 156865668Skris for (;;) { 156976259Sgreen memset(rset, 0, set_size); 157076259Sgreen memset(wset, 0, set_size); 157165668Skris 1572181111Sdes /* 1573181111Sdes * Ensure that we can read a full buffer and handle 1574181111Sdes * the worst-case length packet it can generate, 1575181111Sdes * otherwise apply backpressure by stopping reads. 1576181111Sdes */ 1577181111Sdes if (buffer_check_alloc(&iqueue, sizeof(buf)) && 1578181111Sdes buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1579181111Sdes FD_SET(in, rset); 1580181111Sdes 158165668Skris olen = buffer_len(&oqueue); 158265668Skris if (olen > 0) 158376259Sgreen FD_SET(out, wset); 158465668Skris 158576259Sgreen if (select(max+1, rset, wset, NULL, NULL) < 0) { 158665668Skris if (errno == EINTR) 158765668Skris continue; 1588162852Sdes error("select: %s", strerror(errno)); 1589181111Sdes sftp_server_cleanup_exit(2); 159065668Skris } 159165668Skris 159265668Skris /* copy stdin to iqueue */ 159376259Sgreen if (FD_ISSET(in, rset)) { 159465668Skris len = read(in, buf, sizeof buf); 159565668Skris if (len == 0) { 159665668Skris debug("read eof"); 1597181111Sdes sftp_server_cleanup_exit(0); 159865668Skris } else if (len < 0) { 1599162852Sdes error("read: %s", strerror(errno)); 1600181111Sdes sftp_server_cleanup_exit(1); 160165668Skris } else { 160265668Skris buffer_append(&iqueue, buf, len); 160365668Skris } 160465668Skris } 160565668Skris /* send oqueue to stdout */ 160676259Sgreen if (FD_ISSET(out, wset)) { 160765668Skris len = write(out, buffer_ptr(&oqueue), olen); 160865668Skris if (len < 0) { 1609162852Sdes error("write: %s", strerror(errno)); 1610181111Sdes sftp_server_cleanup_exit(1); 161165668Skris } else { 161265668Skris buffer_consume(&oqueue, len); 161365668Skris } 161465668Skris } 1615181111Sdes 1616181111Sdes /* 1617181111Sdes * Process requests from client if we can fit the results 1618181111Sdes * into the output buffer, otherwise stop processing input 1619181111Sdes * and let the output queue drain. 1620181111Sdes */ 1621181111Sdes if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1622181111Sdes process(); 162365668Skris } 162465668Skris} 1625