1296781Sdes/* $OpenBSD: sftp-server.c,v 1.109 2016/02/15 09:47:49 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 20295367Sdes#include <sys/param.h> /* MIN */ 21162852Sdes#include <sys/types.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 <time.h> 41162852Sdes#include <unistd.h> 42162852Sdes#include <stdarg.h> 43162852Sdes 44162852Sdes#include "xmalloc.h" 45295367Sdes#include "sshbuf.h" 46295367Sdes#include "ssherr.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 55162852Sdes/* Our verbosity */ 56262566Sdesstatic LogLevel log_level = SYSLOG_LEVEL_ERROR; 5798937Sdes 58162852Sdes/* Our client */ 59262566Sdesstatic struct passwd *pw = NULL; 60262566Sdesstatic char *client_addr = NULL; 61162852Sdes 6265668Skris/* input and output queue */ 63295367Sdesstruct sshbuf *iqueue; 64295367Sdesstruct sshbuf *oqueue; 6565668Skris 6676259Sgreen/* Version of client */ 67262566Sdesstatic u_int version; 6876259Sgreen 69262566Sdes/* SSH2_FXP_INIT received */ 70262566Sdesstatic int init_done; 71262566Sdes 72204917Sdes/* Disable writes */ 73262566Sdesstatic int readonly; 74204917Sdes 75262566Sdes/* Requests that are allowed/denied */ 76262566Sdesstatic char *request_whitelist, *request_blacklist; 77262566Sdes 78124208Sdes/* portable attributes, etc. */ 7965668Skristypedef struct Stat Stat; 8065668Skris 8176259Sgreenstruct Stat { 8265668Skris char *name; 8365668Skris char *long_name; 8465668Skris Attrib attrib; 8565668Skris}; 8665668Skris 87262566Sdes/* Packet handlers */ 88262566Sdesstatic void process_open(u_int32_t id); 89262566Sdesstatic void process_close(u_int32_t id); 90262566Sdesstatic void process_read(u_int32_t id); 91262566Sdesstatic void process_write(u_int32_t id); 92262566Sdesstatic void process_stat(u_int32_t id); 93262566Sdesstatic void process_lstat(u_int32_t id); 94262566Sdesstatic void process_fstat(u_int32_t id); 95262566Sdesstatic void process_setstat(u_int32_t id); 96262566Sdesstatic void process_fsetstat(u_int32_t id); 97262566Sdesstatic void process_opendir(u_int32_t id); 98262566Sdesstatic void process_readdir(u_int32_t id); 99262566Sdesstatic void process_remove(u_int32_t id); 100262566Sdesstatic void process_mkdir(u_int32_t id); 101262566Sdesstatic void process_rmdir(u_int32_t id); 102262566Sdesstatic void process_realpath(u_int32_t id); 103262566Sdesstatic void process_rename(u_int32_t id); 104262566Sdesstatic void process_readlink(u_int32_t id); 105262566Sdesstatic void process_symlink(u_int32_t id); 106262566Sdesstatic void process_extended_posix_rename(u_int32_t id); 107262566Sdesstatic void process_extended_statvfs(u_int32_t id); 108262566Sdesstatic void process_extended_fstatvfs(u_int32_t id); 109262566Sdesstatic void process_extended_hardlink(u_int32_t id); 110262566Sdesstatic void process_extended_fsync(u_int32_t id); 111262566Sdesstatic void process_extended(u_int32_t id); 112262566Sdes 113262566Sdesstruct sftp_handler { 114262566Sdes const char *name; /* user-visible name for fine-grained perms */ 115262566Sdes const char *ext_name; /* extended request name */ 116262566Sdes u_int type; /* packet type, for non extended packets */ 117262566Sdes void (*handler)(u_int32_t); 118262566Sdes int does_write; /* if nonzero, banned for readonly mode */ 119262566Sdes}; 120262566Sdes 121262566Sdesstruct sftp_handler handlers[] = { 122262566Sdes /* NB. SSH2_FXP_OPEN does the readonly check in the handler itself */ 123262566Sdes { "open", NULL, SSH2_FXP_OPEN, process_open, 0 }, 124262566Sdes { "close", NULL, SSH2_FXP_CLOSE, process_close, 0 }, 125262566Sdes { "read", NULL, SSH2_FXP_READ, process_read, 0 }, 126262566Sdes { "write", NULL, SSH2_FXP_WRITE, process_write, 1 }, 127262566Sdes { "lstat", NULL, SSH2_FXP_LSTAT, process_lstat, 0 }, 128262566Sdes { "fstat", NULL, SSH2_FXP_FSTAT, process_fstat, 0 }, 129262566Sdes { "setstat", NULL, SSH2_FXP_SETSTAT, process_setstat, 1 }, 130262566Sdes { "fsetstat", NULL, SSH2_FXP_FSETSTAT, process_fsetstat, 1 }, 131262566Sdes { "opendir", NULL, SSH2_FXP_OPENDIR, process_opendir, 0 }, 132262566Sdes { "readdir", NULL, SSH2_FXP_READDIR, process_readdir, 0 }, 133262566Sdes { "remove", NULL, SSH2_FXP_REMOVE, process_remove, 1 }, 134262566Sdes { "mkdir", NULL, SSH2_FXP_MKDIR, process_mkdir, 1 }, 135262566Sdes { "rmdir", NULL, SSH2_FXP_RMDIR, process_rmdir, 1 }, 136262566Sdes { "realpath", NULL, SSH2_FXP_REALPATH, process_realpath, 0 }, 137262566Sdes { "stat", NULL, SSH2_FXP_STAT, process_stat, 0 }, 138262566Sdes { "rename", NULL, SSH2_FXP_RENAME, process_rename, 1 }, 139262566Sdes { "readlink", NULL, SSH2_FXP_READLINK, process_readlink, 0 }, 140262566Sdes { "symlink", NULL, SSH2_FXP_SYMLINK, process_symlink, 1 }, 141262566Sdes { NULL, NULL, 0, NULL, 0 } 142262566Sdes}; 143262566Sdes 144262566Sdes/* SSH2_FXP_EXTENDED submessages */ 145262566Sdesstruct sftp_handler extended_handlers[] = { 146262566Sdes { "posix-rename", "posix-rename@openssh.com", 0, 147262566Sdes process_extended_posix_rename, 1 }, 148262566Sdes { "statvfs", "statvfs@openssh.com", 0, process_extended_statvfs, 0 }, 149262566Sdes { "fstatvfs", "fstatvfs@openssh.com", 0, process_extended_fstatvfs, 0 }, 150262566Sdes { "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 }, 151262566Sdes { "fsync", "fsync@openssh.com", 0, process_extended_fsync, 1 }, 152262566Sdes { NULL, NULL, 0, NULL, 0 } 153262566Sdes}; 154262566Sdes 15592555Sdesstatic int 156262566Sdesrequest_permitted(struct sftp_handler *h) 157262566Sdes{ 158262566Sdes char *result; 159262566Sdes 160262566Sdes if (readonly && h->does_write) { 161262566Sdes verbose("Refusing %s request in read-only mode", h->name); 162262566Sdes return 0; 163262566Sdes } 164262566Sdes if (request_blacklist != NULL && 165262566Sdes ((result = match_list(h->name, request_blacklist, NULL))) != NULL) { 166262566Sdes free(result); 167262566Sdes verbose("Refusing blacklisted %s request", h->name); 168262566Sdes return 0; 169262566Sdes } 170262566Sdes if (request_whitelist != NULL && 171262566Sdes ((result = match_list(h->name, request_whitelist, NULL))) != NULL) { 172262566Sdes free(result); 173262566Sdes debug2("Permitting whitelisted %s request", h->name); 174262566Sdes return 1; 175262566Sdes } 176262566Sdes if (request_whitelist != NULL) { 177262566Sdes verbose("Refusing non-whitelisted %s request", h->name); 178262566Sdes return 0; 179262566Sdes } 180262566Sdes return 1; 181262566Sdes} 182262566Sdes 183262566Sdesstatic int 18465668Skriserrno_to_portable(int unixerrno) 18565668Skris{ 18665668Skris int ret = 0; 18776259Sgreen 18865668Skris switch (unixerrno) { 18965668Skris case 0: 19076259Sgreen ret = SSH2_FX_OK; 19165668Skris break; 19265668Skris case ENOENT: 19365668Skris case ENOTDIR: 19465668Skris case EBADF: 19565668Skris case ELOOP: 19676259Sgreen ret = SSH2_FX_NO_SUCH_FILE; 19765668Skris break; 19865668Skris case EPERM: 19965668Skris case EACCES: 20065668Skris case EFAULT: 20176259Sgreen ret = SSH2_FX_PERMISSION_DENIED; 20265668Skris break; 20365668Skris case ENAMETOOLONG: 20465668Skris case EINVAL: 20576259Sgreen ret = SSH2_FX_BAD_MESSAGE; 20665668Skris break; 207181111Sdes case ENOSYS: 208181111Sdes ret = SSH2_FX_OP_UNSUPPORTED; 209181111Sdes break; 21065668Skris default: 21176259Sgreen ret = SSH2_FX_FAILURE; 21265668Skris break; 21365668Skris } 21465668Skris return ret; 21565668Skris} 21665668Skris 21792555Sdesstatic int 21865668Skrisflags_from_portable(int pflags) 21965668Skris{ 22065668Skris int flags = 0; 22176259Sgreen 22276259Sgreen if ((pflags & SSH2_FXF_READ) && 22376259Sgreen (pflags & SSH2_FXF_WRITE)) { 22465668Skris flags = O_RDWR; 22576259Sgreen } else if (pflags & SSH2_FXF_READ) { 22665668Skris flags = O_RDONLY; 22776259Sgreen } else if (pflags & SSH2_FXF_WRITE) { 22865668Skris flags = O_WRONLY; 22965668Skris } 230262566Sdes if (pflags & SSH2_FXF_APPEND) 231262566Sdes flags |= O_APPEND; 23276259Sgreen if (pflags & SSH2_FXF_CREAT) 23365668Skris flags |= O_CREAT; 23476259Sgreen if (pflags & SSH2_FXF_TRUNC) 23565668Skris flags |= O_TRUNC; 23676259Sgreen if (pflags & SSH2_FXF_EXCL) 23765668Skris flags |= O_EXCL; 23865668Skris return flags; 23965668Skris} 24065668Skris 241162852Sdesstatic const char * 242162852Sdesstring_from_portable(int pflags) 243162852Sdes{ 244162852Sdes static char ret[128]; 245162852Sdes 246162852Sdes *ret = '\0'; 247162852Sdes 248162852Sdes#define PAPPEND(str) { \ 249162852Sdes if (*ret != '\0') \ 250162852Sdes strlcat(ret, ",", sizeof(ret)); \ 251162852Sdes strlcat(ret, str, sizeof(ret)); \ 252162852Sdes } 253162852Sdes 254162852Sdes if (pflags & SSH2_FXF_READ) 255162852Sdes PAPPEND("READ") 256162852Sdes if (pflags & SSH2_FXF_WRITE) 257162852Sdes PAPPEND("WRITE") 258262566Sdes if (pflags & SSH2_FXF_APPEND) 259262566Sdes PAPPEND("APPEND") 260162852Sdes if (pflags & SSH2_FXF_CREAT) 261162852Sdes PAPPEND("CREATE") 262162852Sdes if (pflags & SSH2_FXF_TRUNC) 263162852Sdes PAPPEND("TRUNCATE") 264162852Sdes if (pflags & SSH2_FXF_EXCL) 265162852Sdes PAPPEND("EXCL") 266162852Sdes 267162852Sdes return ret; 268162852Sdes} 269162852Sdes 27065668Skris/* handle handles */ 27165668Skris 27265668Skristypedef struct Handle Handle; 27365668Skrisstruct Handle { 27465668Skris int use; 27565668Skris DIR *dirp; 27665668Skris int fd; 277262566Sdes int flags; 27865668Skris char *name; 279162852Sdes u_int64_t bytes_read, bytes_write; 280181111Sdes int next_unused; 28165668Skris}; 28276259Sgreen 28365668Skrisenum { 28465668Skris HANDLE_UNUSED, 28565668Skris HANDLE_DIR, 28665668Skris HANDLE_FILE 28765668Skris}; 28876259Sgreen 289181111SdesHandle *handles = NULL; 290181111Sdesu_int num_handles = 0; 291181111Sdesint first_unused_handle = -1; 29265668Skris 293181111Sdesstatic void handle_unused(int i) 29465668Skris{ 295181111Sdes handles[i].use = HANDLE_UNUSED; 296181111Sdes handles[i].next_unused = first_unused_handle; 297181111Sdes first_unused_handle = i; 29865668Skris} 29965668Skris 30092555Sdesstatic int 301262566Sdeshandle_new(int use, const char *name, int fd, int flags, DIR *dirp) 30265668Skris{ 303181111Sdes int i; 30476259Sgreen 305181111Sdes if (first_unused_handle == -1) { 306181111Sdes if (num_handles + 1 <= num_handles) 307181111Sdes return -1; 308181111Sdes num_handles++; 309295367Sdes handles = xreallocarray(handles, num_handles, sizeof(Handle)); 310181111Sdes handle_unused(num_handles - 1); 31165668Skris } 312181111Sdes 313181111Sdes i = first_unused_handle; 314181111Sdes first_unused_handle = handles[i].next_unused; 315181111Sdes 316181111Sdes handles[i].use = use; 317181111Sdes handles[i].dirp = dirp; 318181111Sdes handles[i].fd = fd; 319262566Sdes handles[i].flags = flags; 320181111Sdes handles[i].name = xstrdup(name); 321181111Sdes handles[i].bytes_read = handles[i].bytes_write = 0; 322181111Sdes 323181111Sdes return i; 32465668Skris} 32565668Skris 32692555Sdesstatic int 32765668Skrishandle_is_ok(int i, int type) 32865668Skris{ 329181111Sdes return i >= 0 && (u_int)i < num_handles && handles[i].use == type; 33065668Skris} 33165668Skris 33292555Sdesstatic int 333295367Sdeshandle_to_string(int handle, u_char **stringp, int *hlenp) 33465668Skris{ 33565668Skris if (stringp == NULL || hlenp == NULL) 33665668Skris return -1; 33776259Sgreen *stringp = xmalloc(sizeof(int32_t)); 338162852Sdes put_u32(*stringp, handle); 33976259Sgreen *hlenp = sizeof(int32_t); 34065668Skris return 0; 34165668Skris} 34265668Skris 34392555Sdesstatic int 344295367Sdeshandle_from_string(const u_char *handle, u_int hlen) 34565668Skris{ 34676259Sgreen int val; 34776259Sgreen 34876259Sgreen if (hlen != sizeof(int32_t)) 34965668Skris return -1; 350162852Sdes val = get_u32(handle); 35165668Skris if (handle_is_ok(val, HANDLE_FILE) || 35265668Skris handle_is_ok(val, HANDLE_DIR)) 35365668Skris return val; 35465668Skris return -1; 35565668Skris} 35665668Skris 35792555Sdesstatic char * 35865668Skrishandle_to_name(int handle) 35965668Skris{ 36065668Skris if (handle_is_ok(handle, HANDLE_DIR)|| 36165668Skris handle_is_ok(handle, HANDLE_FILE)) 36265668Skris return handles[handle].name; 36365668Skris return NULL; 36465668Skris} 36565668Skris 36692555Sdesstatic DIR * 36765668Skrishandle_to_dir(int handle) 36865668Skris{ 36965668Skris if (handle_is_ok(handle, HANDLE_DIR)) 37065668Skris return handles[handle].dirp; 37165668Skris return NULL; 37265668Skris} 37365668Skris 37492555Sdesstatic int 37565668Skrishandle_to_fd(int handle) 37665668Skris{ 37776259Sgreen if (handle_is_ok(handle, HANDLE_FILE)) 37865668Skris return handles[handle].fd; 37965668Skris return -1; 38065668Skris} 38165668Skris 382262566Sdesstatic int 383262566Sdeshandle_to_flags(int handle) 384262566Sdes{ 385262566Sdes if (handle_is_ok(handle, HANDLE_FILE)) 386262566Sdes return handles[handle].flags; 387262566Sdes return 0; 388262566Sdes} 389262566Sdes 390162852Sdesstatic void 391162852Sdeshandle_update_read(int handle, ssize_t bytes) 392162852Sdes{ 393162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 394162852Sdes handles[handle].bytes_read += bytes; 395162852Sdes} 396162852Sdes 397162852Sdesstatic void 398162852Sdeshandle_update_write(int handle, ssize_t bytes) 399162852Sdes{ 400162852Sdes if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 401162852Sdes handles[handle].bytes_write += bytes; 402162852Sdes} 403162852Sdes 404162852Sdesstatic u_int64_t 405162852Sdeshandle_bytes_read(int handle) 406162852Sdes{ 407162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 408162852Sdes return (handles[handle].bytes_read); 409162852Sdes return 0; 410162852Sdes} 411162852Sdes 412162852Sdesstatic u_int64_t 413162852Sdeshandle_bytes_write(int handle) 414162852Sdes{ 415162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) 416162852Sdes return (handles[handle].bytes_write); 417162852Sdes return 0; 418162852Sdes} 419162852Sdes 42092555Sdesstatic int 42165668Skrishandle_close(int handle) 42265668Skris{ 42365668Skris int ret = -1; 42476259Sgreen 42565668Skris if (handle_is_ok(handle, HANDLE_FILE)) { 42665668Skris ret = close(handles[handle].fd); 427255767Sdes free(handles[handle].name); 428181111Sdes handle_unused(handle); 42965668Skris } else if (handle_is_ok(handle, HANDLE_DIR)) { 43065668Skris ret = closedir(handles[handle].dirp); 431255767Sdes free(handles[handle].name); 432181111Sdes handle_unused(handle); 43365668Skris } else { 43465668Skris errno = ENOENT; 43565668Skris } 43665668Skris return ret; 43765668Skris} 43865668Skris 439162852Sdesstatic void 440162852Sdeshandle_log_close(int handle, char *emsg) 441162852Sdes{ 442162852Sdes if (handle_is_ok(handle, HANDLE_FILE)) { 443162852Sdes logit("%s%sclose \"%s\" bytes read %llu written %llu", 444162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 445162852Sdes handle_to_name(handle), 446181111Sdes (unsigned long long)handle_bytes_read(handle), 447181111Sdes (unsigned long long)handle_bytes_write(handle)); 448162852Sdes } else { 449162852Sdes logit("%s%sclosedir \"%s\"", 450162852Sdes emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 451162852Sdes handle_to_name(handle)); 452162852Sdes } 453162852Sdes} 454162852Sdes 455162852Sdesstatic void 456162852Sdeshandle_log_exit(void) 457162852Sdes{ 458162852Sdes u_int i; 459162852Sdes 460181111Sdes for (i = 0; i < num_handles; i++) 461162852Sdes if (handles[i].use != HANDLE_UNUSED) 462162852Sdes handle_log_close(i, "forced"); 463162852Sdes} 464162852Sdes 46592555Sdesstatic int 466295367Sdesget_handle(struct sshbuf *queue, int *hp) 46765668Skris{ 468295367Sdes u_char *handle; 469295367Sdes int r; 470295367Sdes size_t hlen; 47176259Sgreen 472295367Sdes *hp = -1; 473295367Sdes if ((r = sshbuf_get_string(queue, &handle, &hlen)) != 0) 474295367Sdes return r; 47576259Sgreen if (hlen < 256) 476295367Sdes *hp = handle_from_string(handle, hlen); 477255767Sdes free(handle); 478295367Sdes return 0; 47965668Skris} 48065668Skris 48165668Skris/* send replies */ 48265668Skris 48392555Sdesstatic void 484295367Sdessend_msg(struct sshbuf *m) 48565668Skris{ 486295367Sdes int r; 48776259Sgreen 488295367Sdes if ((r = sshbuf_put_stringb(oqueue, m)) != 0) 489295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 490295367Sdes sshbuf_reset(m); 49165668Skris} 49265668Skris 493162852Sdesstatic const char * 494162852Sdesstatus_to_message(u_int32_t status) 49565668Skris{ 49676259Sgreen const char *status_messages[] = { 49776259Sgreen "Success", /* SSH_FX_OK */ 49876259Sgreen "End of file", /* SSH_FX_EOF */ 49976259Sgreen "No such file", /* SSH_FX_NO_SUCH_FILE */ 50076259Sgreen "Permission denied", /* SSH_FX_PERMISSION_DENIED */ 50176259Sgreen "Failure", /* SSH_FX_FAILURE */ 50276259Sgreen "Bad message", /* SSH_FX_BAD_MESSAGE */ 50376259Sgreen "No connection", /* SSH_FX_NO_CONNECTION */ 50476259Sgreen "Connection lost", /* SSH_FX_CONNECTION_LOST */ 50576259Sgreen "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ 50676259Sgreen "Unknown error" /* Others */ 50776259Sgreen }; 508162852Sdes return (status_messages[MIN(status,SSH2_FX_MAX)]); 509162852Sdes} 51076259Sgreen 511162852Sdesstatic void 512162852Sdessend_status(u_int32_t id, u_int32_t status) 513162852Sdes{ 514295367Sdes struct sshbuf *msg; 515295367Sdes int r; 516162852Sdes 517162852Sdes debug3("request %u: sent status %u", id, status); 518162852Sdes if (log_level > SYSLOG_LEVEL_VERBOSE || 519162852Sdes (status != SSH2_FX_OK && status != SSH2_FX_EOF)) 520162852Sdes logit("sent status %s", status_to_message(status)); 521295367Sdes if ((msg = sshbuf_new()) == NULL) 522295367Sdes fatal("%s: sshbuf_new failed", __func__); 523295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_STATUS)) != 0 || 524295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 525295367Sdes (r = sshbuf_put_u32(msg, status)) != 0) 526295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 52776259Sgreen if (version >= 3) { 528295367Sdes if ((r = sshbuf_put_cstring(msg, 529295367Sdes status_to_message(status))) != 0 || 530295367Sdes (r = sshbuf_put_cstring(msg, "")) != 0) 531295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 53276259Sgreen } 533295367Sdes send_msg(msg); 534295367Sdes sshbuf_free(msg); 53565668Skris} 53692555Sdesstatic void 537295367Sdessend_data_or_handle(char type, u_int32_t id, const u_char *data, int dlen) 53865668Skris{ 539295367Sdes struct sshbuf *msg; 540295367Sdes int r; 54176259Sgreen 542295367Sdes if ((msg = sshbuf_new()) == NULL) 543295367Sdes fatal("%s: sshbuf_new failed", __func__); 544295367Sdes if ((r = sshbuf_put_u8(msg, type)) != 0 || 545295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 546295367Sdes (r = sshbuf_put_string(msg, data, dlen)) != 0) 547295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 548295367Sdes send_msg(msg); 549295367Sdes sshbuf_free(msg); 55065668Skris} 55165668Skris 55292555Sdesstatic void 553295367Sdessend_data(u_int32_t id, const u_char *data, int dlen) 55465668Skris{ 555162852Sdes debug("request %u: sent data len %d", id, dlen); 55676259Sgreen send_data_or_handle(SSH2_FXP_DATA, id, data, dlen); 55765668Skris} 55865668Skris 55992555Sdesstatic void 56065668Skrissend_handle(u_int32_t id, int handle) 56165668Skris{ 562295367Sdes u_char *string; 56365668Skris int hlen; 56476259Sgreen 56565668Skris handle_to_string(handle, &string, &hlen); 566162852Sdes debug("request %u: sent handle handle %d", id, handle); 56776259Sgreen send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); 568255767Sdes free(string); 56965668Skris} 57065668Skris 57192555Sdesstatic void 572126274Sdessend_names(u_int32_t id, int count, const Stat *stats) 57365668Skris{ 574295367Sdes struct sshbuf *msg; 575295367Sdes int i, r; 57676259Sgreen 577295367Sdes if ((msg = sshbuf_new()) == NULL) 578295367Sdes fatal("%s: sshbuf_new failed", __func__); 579295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_NAME)) != 0 || 580295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 581295367Sdes (r = sshbuf_put_u32(msg, count)) != 0) 582295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 583162852Sdes debug("request %u: sent names count %d", id, count); 58465668Skris for (i = 0; i < count; i++) { 585295367Sdes if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 || 586295367Sdes (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 || 587295367Sdes (r = encode_attrib(msg, &stats[i].attrib)) != 0) 588295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 58965668Skris } 590295367Sdes send_msg(msg); 591295367Sdes sshbuf_free(msg); 59265668Skris} 59365668Skris 59492555Sdesstatic void 595126274Sdessend_attrib(u_int32_t id, const Attrib *a) 59665668Skris{ 597295367Sdes struct sshbuf *msg; 598295367Sdes int r; 59976259Sgreen 600162852Sdes debug("request %u: sent attrib have 0x%x", id, a->flags); 601295367Sdes if ((msg = sshbuf_new()) == NULL) 602295367Sdes fatal("%s: sshbuf_new failed", __func__); 603295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_ATTRS)) != 0 || 604295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 605295367Sdes (r = encode_attrib(msg, a)) != 0) 606295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 607295367Sdes send_msg(msg); 608295367Sdes sshbuf_free(msg); 60965668Skris} 61065668Skris 611181111Sdesstatic void 612181111Sdessend_statvfs(u_int32_t id, struct statvfs *st) 613181111Sdes{ 614295367Sdes struct sshbuf *msg; 615181111Sdes u_int64_t flag; 616295367Sdes int r; 617181111Sdes 618181111Sdes flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; 619181111Sdes flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; 620181111Sdes 621295367Sdes if ((msg = sshbuf_new()) == NULL) 622295367Sdes fatal("%s: sshbuf_new failed", __func__); 623295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED_REPLY)) != 0 || 624295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 625295367Sdes (r = sshbuf_put_u64(msg, st->f_bsize)) != 0 || 626295367Sdes (r = sshbuf_put_u64(msg, st->f_frsize)) != 0 || 627295367Sdes (r = sshbuf_put_u64(msg, st->f_blocks)) != 0 || 628295367Sdes (r = sshbuf_put_u64(msg, st->f_bfree)) != 0 || 629295367Sdes (r = sshbuf_put_u64(msg, st->f_bavail)) != 0 || 630295367Sdes (r = sshbuf_put_u64(msg, st->f_files)) != 0 || 631295367Sdes (r = sshbuf_put_u64(msg, st->f_ffree)) != 0 || 632295367Sdes (r = sshbuf_put_u64(msg, st->f_favail)) != 0 || 633295367Sdes (r = sshbuf_put_u64(msg, FSID_TO_ULONG(st->f_fsid))) != 0 || 634295367Sdes (r = sshbuf_put_u64(msg, flag)) != 0 || 635295367Sdes (r = sshbuf_put_u64(msg, st->f_namemax)) != 0) 636295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 637295367Sdes send_msg(msg); 638295367Sdes sshbuf_free(msg); 639181111Sdes} 640181111Sdes 64165668Skris/* parse incoming */ 64265668Skris 64392555Sdesstatic void 64465668Skrisprocess_init(void) 64565668Skris{ 646295367Sdes struct sshbuf *msg; 647295367Sdes int r; 64865668Skris 649295367Sdes if ((r = sshbuf_get_u32(iqueue, &version)) != 0) 650295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 651226046Sdes verbose("received client version %u", version); 652295367Sdes if ((msg = sshbuf_new()) == NULL) 653295367Sdes fatal("%s: sshbuf_new failed", __func__); 654295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 || 655295367Sdes (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0 || 656295367Sdes /* POSIX rename extension */ 657295367Sdes (r = sshbuf_put_cstring(msg, "posix-rename@openssh.com")) != 0 || 658295367Sdes (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */ 659295367Sdes /* statvfs extension */ 660295367Sdes (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || 661295367Sdes (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */ 662295367Sdes /* fstatvfs extension */ 663295367Sdes (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || 664295367Sdes (r = sshbuf_put_cstring(msg, "2")) != 0 || /* version */ 665295367Sdes /* hardlink extension */ 666295367Sdes (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || 667295367Sdes (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */ 668295367Sdes /* fsync extension */ 669295367Sdes (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || 670295367Sdes (r = sshbuf_put_cstring(msg, "1")) != 0) /* version */ 671295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 672295367Sdes send_msg(msg); 673295367Sdes sshbuf_free(msg); 67465668Skris} 67565668Skris 67692555Sdesstatic void 677262566Sdesprocess_open(u_int32_t id) 67865668Skris{ 679262566Sdes u_int32_t pflags; 680295367Sdes Attrib a; 68165668Skris char *name; 682295367Sdes int r, handle, fd, flags, mode, status = SSH2_FX_FAILURE; 68365668Skris 684295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 || 685295367Sdes (r = sshbuf_get_u32(iqueue, &pflags)) != 0 || /* portable flags */ 686295367Sdes (r = decode_attrib(iqueue, &a)) != 0) 687295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 688295367Sdes 689162852Sdes debug3("request %u: open flags %d", id, pflags); 69065668Skris flags = flags_from_portable(pflags); 691295367Sdes mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666; 692162852Sdes logit("open \"%s\" flags %s mode 0%o", 693162852Sdes name, string_from_portable(pflags), mode); 694204917Sdes if (readonly && 695262566Sdes ((flags & O_ACCMODE) == O_WRONLY || 696262566Sdes (flags & O_ACCMODE) == O_RDWR)) { 697262566Sdes verbose("Refusing open request in read-only mode"); 698295367Sdes status = SSH2_FX_PERMISSION_DENIED; 699262566Sdes } else { 700204917Sdes fd = open(name, flags, mode); 701204917Sdes if (fd < 0) { 702204917Sdes status = errno_to_portable(errno); 70365668Skris } else { 704262566Sdes handle = handle_new(HANDLE_FILE, name, fd, flags, NULL); 705204917Sdes if (handle < 0) { 706204917Sdes close(fd); 707204917Sdes } else { 708204917Sdes send_handle(id, handle); 709204917Sdes status = SSH2_FX_OK; 710204917Sdes } 71165668Skris } 71265668Skris } 71376259Sgreen if (status != SSH2_FX_OK) 71465668Skris send_status(id, status); 715255767Sdes free(name); 71665668Skris} 71765668Skris 71892555Sdesstatic void 719262566Sdesprocess_close(u_int32_t id) 72065668Skris{ 721295367Sdes int r, handle, ret, status = SSH2_FX_FAILURE; 72265668Skris 723295367Sdes if ((r = get_handle(iqueue, &handle)) != 0) 724295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 725295367Sdes 726162852Sdes debug3("request %u: close handle %u", id, handle); 727162852Sdes handle_log_close(handle, NULL); 72865668Skris ret = handle_close(handle); 72976259Sgreen status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 73065668Skris send_status(id, status); 73165668Skris} 73265668Skris 73392555Sdesstatic void 734262566Sdesprocess_read(u_int32_t id) 73565668Skris{ 736295367Sdes u_char buf[64*1024]; 737262566Sdes u_int32_t len; 738295367Sdes int r, handle, fd, ret, status = SSH2_FX_FAILURE; 73965668Skris u_int64_t off; 74065668Skris 741295367Sdes if ((r = get_handle(iqueue, &handle)) != 0 || 742295367Sdes (r = sshbuf_get_u64(iqueue, &off)) != 0 || 743295367Sdes (r = sshbuf_get_u32(iqueue, &len)) != 0) 744295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 74565668Skris 746162852Sdes debug("request %u: read \"%s\" (handle %d) off %llu len %d", 747162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 74865668Skris if (len > sizeof buf) { 74965668Skris len = sizeof buf; 750162852Sdes debug2("read change len %d", len); 75165668Skris } 75265668Skris fd = handle_to_fd(handle); 75365668Skris if (fd >= 0) { 75465668Skris if (lseek(fd, off, SEEK_SET) < 0) { 75565668Skris error("process_read: seek failed"); 75665668Skris status = errno_to_portable(errno); 75765668Skris } else { 75865668Skris ret = read(fd, buf, len); 75965668Skris if (ret < 0) { 76065668Skris status = errno_to_portable(errno); 76165668Skris } else if (ret == 0) { 76276259Sgreen status = SSH2_FX_EOF; 76365668Skris } else { 76465668Skris send_data(id, buf, ret); 76576259Sgreen status = SSH2_FX_OK; 766162852Sdes handle_update_read(handle, ret); 76765668Skris } 76865668Skris } 76965668Skris } 77076259Sgreen if (status != SSH2_FX_OK) 77165668Skris send_status(id, status); 77265668Skris} 77365668Skris 77492555Sdesstatic void 775262566Sdesprocess_write(u_int32_t id) 77665668Skris{ 77765668Skris u_int64_t off; 778295367Sdes size_t len; 779295367Sdes int r, handle, fd, ret, status; 780295367Sdes u_char *data; 78165668Skris 782295367Sdes if ((r = get_handle(iqueue, &handle)) != 0 || 783295367Sdes (r = sshbuf_get_u64(iqueue, &off)) != 0 || 784295367Sdes (r = sshbuf_get_string(iqueue, &data, &len)) != 0) 785295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 78665668Skris 787295367Sdes debug("request %u: write \"%s\" (handle %d) off %llu len %zu", 788162852Sdes id, handle_to_name(handle), handle, (unsigned long long)off, len); 78965668Skris fd = handle_to_fd(handle); 790295367Sdes 791204917Sdes if (fd < 0) 792204917Sdes status = SSH2_FX_FAILURE; 793204917Sdes else { 794262566Sdes if (!(handle_to_flags(handle) & O_APPEND) && 795262566Sdes lseek(fd, off, SEEK_SET) < 0) { 79665668Skris status = errno_to_portable(errno); 79765668Skris error("process_write: seek failed"); 79865668Skris } else { 79965668Skris/* XXX ATOMICIO ? */ 80065668Skris ret = write(fd, data, len); 801149749Sdes if (ret < 0) { 80265668Skris error("process_write: write failed"); 80365668Skris status = errno_to_portable(errno); 804149749Sdes } else if ((size_t)ret == len) { 80576259Sgreen status = SSH2_FX_OK; 806162852Sdes handle_update_write(handle, ret); 80765668Skris } else { 808162852Sdes debug2("nothing at all written"); 809204917Sdes status = SSH2_FX_FAILURE; 81065668Skris } 81165668Skris } 81265668Skris } 81365668Skris send_status(id, status); 814255767Sdes free(data); 81565668Skris} 81665668Skris 81792555Sdesstatic void 818262566Sdesprocess_do_stat(u_int32_t id, int do_lstat) 81965668Skris{ 82076259Sgreen Attrib a; 82165668Skris struct stat st; 82265668Skris char *name; 823295367Sdes int r, status = SSH2_FX_FAILURE; 82465668Skris 825295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0) 826295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 827295367Sdes 828162852Sdes debug3("request %u: %sstat", id, do_lstat ? "l" : ""); 829162852Sdes verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name); 830295367Sdes r = do_lstat ? lstat(name, &st) : stat(name, &st); 831295367Sdes if (r < 0) { 83265668Skris status = errno_to_portable(errno); 83365668Skris } else { 83476259Sgreen stat_to_attrib(&st, &a); 83576259Sgreen send_attrib(id, &a); 83676259Sgreen status = SSH2_FX_OK; 83765668Skris } 83876259Sgreen if (status != SSH2_FX_OK) 83965668Skris send_status(id, status); 840255767Sdes free(name); 84165668Skris} 84265668Skris 84392555Sdesstatic void 844262566Sdesprocess_stat(u_int32_t id) 84565668Skris{ 846262566Sdes process_do_stat(id, 0); 84765668Skris} 84865668Skris 84992555Sdesstatic void 850262566Sdesprocess_lstat(u_int32_t id) 85165668Skris{ 852262566Sdes process_do_stat(id, 1); 85365668Skris} 85465668Skris 85592555Sdesstatic void 856262566Sdesprocess_fstat(u_int32_t id) 85765668Skris{ 85876259Sgreen Attrib a; 85965668Skris struct stat st; 860295367Sdes int fd, r, handle, status = SSH2_FX_FAILURE; 86165668Skris 862295367Sdes if ((r = get_handle(iqueue, &handle)) != 0) 863295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 864162852Sdes debug("request %u: fstat \"%s\" (handle %u)", 865162852Sdes id, handle_to_name(handle), handle); 86665668Skris fd = handle_to_fd(handle); 867181111Sdes if (fd >= 0) { 868295367Sdes r = fstat(fd, &st); 869295367Sdes if (r < 0) { 87065668Skris status = errno_to_portable(errno); 87165668Skris } else { 87276259Sgreen stat_to_attrib(&st, &a); 87376259Sgreen send_attrib(id, &a); 87476259Sgreen status = SSH2_FX_OK; 87565668Skris } 87665668Skris } 87776259Sgreen if (status != SSH2_FX_OK) 87865668Skris send_status(id, status); 87965668Skris} 88065668Skris 88192555Sdesstatic struct timeval * 882126274Sdesattrib_to_tv(const Attrib *a) 88365668Skris{ 88465668Skris static struct timeval tv[2]; 88576259Sgreen 88665668Skris tv[0].tv_sec = a->atime; 88765668Skris tv[0].tv_usec = 0; 88865668Skris tv[1].tv_sec = a->mtime; 88965668Skris tv[1].tv_usec = 0; 89065668Skris return tv; 89165668Skris} 89265668Skris 89392555Sdesstatic void 894262566Sdesprocess_setstat(u_int32_t id) 89565668Skris{ 896295367Sdes Attrib a; 89765668Skris char *name; 898295367Sdes int r, status = SSH2_FX_OK; 89965668Skris 900295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 || 901295367Sdes (r = decode_attrib(iqueue, &a)) != 0) 902295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 903295367Sdes 904162852Sdes debug("request %u: setstat name \"%s\"", id, name); 905295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_SIZE) { 906181111Sdes logit("set \"%s\" size %llu", 907295367Sdes name, (unsigned long long)a.size); 908295367Sdes r = truncate(name, a.size); 909295367Sdes if (r == -1) 91092555Sdes status = errno_to_portable(errno); 91192555Sdes } 912295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 913295367Sdes logit("set \"%s\" mode %04o", name, a.perm); 914295367Sdes r = chmod(name, a.perm & 07777); 915295367Sdes if (r == -1) 91665668Skris status = errno_to_portable(errno); 91765668Skris } 918295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 919162852Sdes char buf[64]; 920295367Sdes time_t t = a.mtime; 921162852Sdes 922162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 923162852Sdes localtime(&t)); 924162852Sdes logit("set \"%s\" modtime %s", name, buf); 925295367Sdes r = utimes(name, attrib_to_tv(&a)); 926295367Sdes if (r == -1) 92765668Skris status = errno_to_portable(errno); 92865668Skris } 929295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { 930162852Sdes logit("set \"%s\" owner %lu group %lu", name, 931295367Sdes (u_long)a.uid, (u_long)a.gid); 932295367Sdes r = chown(name, a.uid, a.gid); 933295367Sdes if (r == -1) 93476259Sgreen status = errno_to_portable(errno); 93576259Sgreen } 93665668Skris send_status(id, status); 937255767Sdes free(name); 93865668Skris} 93965668Skris 94092555Sdesstatic void 941262566Sdesprocess_fsetstat(u_int32_t id) 94265668Skris{ 943295367Sdes Attrib a; 944295367Sdes int handle, fd, r; 94576259Sgreen int status = SSH2_FX_OK; 94665668Skris 947295367Sdes if ((r = get_handle(iqueue, &handle)) != 0 || 948295367Sdes (r = decode_attrib(iqueue, &a)) != 0) 949295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 950295367Sdes 951162852Sdes debug("request %u: fsetstat handle %d", id, handle); 95265668Skris fd = handle_to_fd(handle); 953204917Sdes if (fd < 0) 95476259Sgreen status = SSH2_FX_FAILURE; 955204917Sdes else { 956162852Sdes char *name = handle_to_name(handle); 957162852Sdes 958295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_SIZE) { 959181111Sdes logit("set \"%s\" size %llu", 960295367Sdes name, (unsigned long long)a.size); 961295367Sdes r = ftruncate(fd, a.size); 962295367Sdes if (r == -1) 96392555Sdes status = errno_to_portable(errno); 96492555Sdes } 965295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 966295367Sdes logit("set \"%s\" mode %04o", name, a.perm); 96798937Sdes#ifdef HAVE_FCHMOD 968295367Sdes r = fchmod(fd, a.perm & 07777); 96998937Sdes#else 970295367Sdes r = chmod(name, a.perm & 07777); 97198937Sdes#endif 972295367Sdes if (r == -1) 97365668Skris status = errno_to_portable(errno); 97465668Skris } 975295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 976162852Sdes char buf[64]; 977295367Sdes time_t t = a.mtime; 978162852Sdes 979162852Sdes strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 980162852Sdes localtime(&t)); 981162852Sdes logit("set \"%s\" modtime %s", name, buf); 98298937Sdes#ifdef HAVE_FUTIMES 983295367Sdes r = futimes(fd, attrib_to_tv(&a)); 98498937Sdes#else 985295367Sdes r = utimes(name, attrib_to_tv(&a)); 98698937Sdes#endif 987295367Sdes if (r == -1) 98865668Skris status = errno_to_portable(errno); 98965668Skris } 990295367Sdes if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { 991162852Sdes logit("set \"%s\" owner %lu group %lu", name, 992295367Sdes (u_long)a.uid, (u_long)a.gid); 99398937Sdes#ifdef HAVE_FCHOWN 994295367Sdes r = fchown(fd, a.uid, a.gid); 99598937Sdes#else 996295367Sdes r = chown(name, a.uid, a.gid); 99798937Sdes#endif 998295367Sdes if (r == -1) 99976259Sgreen status = errno_to_portable(errno); 100076259Sgreen } 100165668Skris } 100265668Skris send_status(id, status); 100365668Skris} 100465668Skris 100592555Sdesstatic void 1006262566Sdesprocess_opendir(u_int32_t id) 100765668Skris{ 100865668Skris DIR *dirp = NULL; 100965668Skris char *path; 1010295367Sdes int r, handle, status = SSH2_FX_FAILURE; 101165668Skris 1012295367Sdes if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0) 1013295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1014295367Sdes 1015162852Sdes debug3("request %u: opendir", id); 1016162852Sdes logit("opendir \"%s\"", path); 101776259Sgreen dirp = opendir(path); 101865668Skris if (dirp == NULL) { 101965668Skris status = errno_to_portable(errno); 102065668Skris } else { 1021262566Sdes handle = handle_new(HANDLE_DIR, path, 0, 0, dirp); 102265668Skris if (handle < 0) { 102365668Skris closedir(dirp); 102465668Skris } else { 102565668Skris send_handle(id, handle); 102676259Sgreen status = SSH2_FX_OK; 102765668Skris } 102876259Sgreen 102965668Skris } 103076259Sgreen if (status != SSH2_FX_OK) 103165668Skris send_status(id, status); 1032255767Sdes free(path); 103365668Skris} 103465668Skris 103592555Sdesstatic void 1036262566Sdesprocess_readdir(u_int32_t id) 103765668Skris{ 103865668Skris DIR *dirp; 103965668Skris struct dirent *dp; 104065668Skris char *path; 1041295367Sdes int r, handle; 104265668Skris 1043295367Sdes if ((r = get_handle(iqueue, &handle)) != 0) 1044295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1045295367Sdes 1046162852Sdes debug("request %u: readdir \"%s\" (handle %d)", id, 1047162852Sdes handle_to_name(handle), handle); 104865668Skris dirp = handle_to_dir(handle); 104965668Skris path = handle_to_name(handle); 105065668Skris if (dirp == NULL || path == NULL) { 105176259Sgreen send_status(id, SSH2_FX_FAILURE); 105265668Skris } else { 105365668Skris struct stat st; 1054295367Sdes char pathname[PATH_MAX]; 105565668Skris Stat *stats; 105665668Skris int nstats = 10, count = 0, i; 105799060Sdes 1058162852Sdes stats = xcalloc(nstats, sizeof(Stat)); 105965668Skris while ((dp = readdir(dirp)) != NULL) { 106065668Skris if (count >= nstats) { 106165668Skris nstats *= 2; 1062295367Sdes stats = xreallocarray(stats, nstats, sizeof(Stat)); 106365668Skris } 106465668Skris/* XXX OVERFLOW ? */ 106592555Sdes snprintf(pathname, sizeof pathname, "%s%s%s", path, 106692555Sdes strcmp(path, "/") ? "/" : "", dp->d_name); 106765668Skris if (lstat(pathname, &st) < 0) 106865668Skris continue; 106976259Sgreen stat_to_attrib(&st, &(stats[count].attrib)); 107065668Skris stats[count].name = xstrdup(dp->d_name); 1071204917Sdes stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); 107265668Skris count++; 107365668Skris /* send up to 100 entries in one message */ 107476259Sgreen /* XXX check packet size instead */ 107565668Skris if (count == 100) 107665668Skris break; 107765668Skris } 107876259Sgreen if (count > 0) { 107976259Sgreen send_names(id, count, stats); 108092555Sdes for (i = 0; i < count; i++) { 1081255767Sdes free(stats[i].name); 1082255767Sdes free(stats[i].long_name); 108376259Sgreen } 108476259Sgreen } else { 108576259Sgreen send_status(id, SSH2_FX_EOF); 108665668Skris } 1087255767Sdes free(stats); 108865668Skris } 108965668Skris} 109065668Skris 109192555Sdesstatic void 1092262566Sdesprocess_remove(u_int32_t id) 109365668Skris{ 109465668Skris char *name; 1095295367Sdes int r, status = SSH2_FX_FAILURE; 109665668Skris 1097295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0) 1098295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1099295367Sdes 1100162852Sdes debug3("request %u: remove", id); 1101162852Sdes logit("remove name \"%s\"", name); 1102295367Sdes r = unlink(name); 1103295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 110465668Skris send_status(id, status); 1105255767Sdes free(name); 110665668Skris} 110765668Skris 110892555Sdesstatic void 1109262566Sdesprocess_mkdir(u_int32_t id) 111065668Skris{ 1111295367Sdes Attrib a; 111265668Skris char *name; 1113295367Sdes int r, mode, status = SSH2_FX_FAILURE; 111465668Skris 1115295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0 || 1116295367Sdes (r = decode_attrib(iqueue, &a)) != 0) 1117295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1118295367Sdes 1119295367Sdes mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? 1120295367Sdes a.perm & 07777 : 0777; 1121162852Sdes debug3("request %u: mkdir", id); 1122162852Sdes logit("mkdir name \"%s\" mode 0%o", name, mode); 1123295367Sdes r = mkdir(name, mode); 1124295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 112565668Skris send_status(id, status); 1126255767Sdes free(name); 112765668Skris} 112865668Skris 112992555Sdesstatic void 1130262566Sdesprocess_rmdir(u_int32_t id) 113165668Skris{ 113265668Skris char *name; 1133295367Sdes int r, status; 113465668Skris 1135295367Sdes if ((r = sshbuf_get_cstring(iqueue, &name, NULL)) != 0) 1136295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1137295367Sdes 1138162852Sdes debug3("request %u: rmdir", id); 1139162852Sdes logit("rmdir name \"%s\"", name); 1140295367Sdes r = rmdir(name); 1141295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 114265668Skris send_status(id, status); 1143255767Sdes free(name); 114465668Skris} 114565668Skris 114692555Sdesstatic void 1147262566Sdesprocess_realpath(u_int32_t id) 114865668Skris{ 1149295367Sdes char resolvedname[PATH_MAX]; 115065668Skris char *path; 1151295367Sdes int r; 115265668Skris 1153295367Sdes if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0) 1154295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1155295367Sdes 115676259Sgreen if (path[0] == '\0') { 1157255767Sdes free(path); 115876259Sgreen path = xstrdup("."); 115976259Sgreen } 1160162852Sdes debug3("request %u: realpath", id); 1161162852Sdes verbose("realpath \"%s\"", path); 116265668Skris if (realpath(path, resolvedname) == NULL) { 116365668Skris send_status(id, errno_to_portable(errno)); 116465668Skris } else { 116565668Skris Stat s; 116665668Skris attrib_clear(&s.attrib); 116765668Skris s.name = s.long_name = resolvedname; 116865668Skris send_names(id, 1, &s); 116965668Skris } 1170255767Sdes free(path); 117165668Skris} 117265668Skris 117392555Sdesstatic void 1174262566Sdesprocess_rename(u_int32_t id) 117565668Skris{ 117665668Skris char *oldpath, *newpath; 1177295367Sdes int r, status; 1178113908Sdes struct stat sb; 117965668Skris 1180295367Sdes if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || 1181295367Sdes (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) 1182295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1183295367Sdes 1184162852Sdes debug3("request %u: rename", id); 1185162852Sdes logit("rename old \"%s\" new \"%s\"", oldpath, newpath); 1186113908Sdes status = SSH2_FX_FAILURE; 1187262566Sdes if (lstat(oldpath, &sb) == -1) 1188113908Sdes status = errno_to_portable(errno); 1189113908Sdes else if (S_ISREG(sb.st_mode)) { 1190113908Sdes /* Race-free rename of regular files */ 1191137015Sdes if (link(oldpath, newpath) == -1) { 1192197679Sdes if (errno == EOPNOTSUPP || errno == ENOSYS 1193181111Sdes#ifdef EXDEV 1194181111Sdes || errno == EXDEV 1195181111Sdes#endif 1196137015Sdes#ifdef LINK_OPNOTSUPP_ERRNO 1197137015Sdes || errno == LINK_OPNOTSUPP_ERRNO 1198137015Sdes#endif 1199137015Sdes ) { 1200137015Sdes struct stat st; 1201137015Sdes 1202137015Sdes /* 1203137015Sdes * fs doesn't support links, so fall back to 1204137015Sdes * stat+rename. This is racy. 1205137015Sdes */ 1206137015Sdes if (stat(newpath, &st) == -1) { 1207137015Sdes if (rename(oldpath, newpath) == -1) 1208137015Sdes status = 1209137015Sdes errno_to_portable(errno); 1210137015Sdes else 1211137015Sdes status = SSH2_FX_OK; 1212137015Sdes } 1213137015Sdes } else { 1214137015Sdes status = errno_to_portable(errno); 1215137015Sdes } 1216137015Sdes } else if (unlink(oldpath) == -1) { 1217113908Sdes status = errno_to_portable(errno); 1218113908Sdes /* clean spare link */ 1219113908Sdes unlink(newpath); 1220113908Sdes } else 1221113908Sdes status = SSH2_FX_OK; 1222113908Sdes } else if (stat(newpath, &sb) == -1) { 1223113908Sdes if (rename(oldpath, newpath) == -1) 1224113908Sdes status = errno_to_portable(errno); 1225113908Sdes else 1226113908Sdes status = SSH2_FX_OK; 122776259Sgreen } 122865668Skris send_status(id, status); 1229255767Sdes free(oldpath); 1230255767Sdes free(newpath); 123165668Skris} 123265668Skris 123392555Sdesstatic void 1234262566Sdesprocess_readlink(u_int32_t id) 123576259Sgreen{ 1236295367Sdes int r, len; 1237295367Sdes char buf[PATH_MAX]; 123876259Sgreen char *path; 123965668Skris 1240295367Sdes if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0) 1241295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1242295367Sdes 1243162852Sdes debug3("request %u: readlink", id); 1244162852Sdes verbose("readlink \"%s\"", path); 1245137015Sdes if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) 124676259Sgreen send_status(id, errno_to_portable(errno)); 124776259Sgreen else { 124876259Sgreen Stat s; 124992555Sdes 1250137015Sdes buf[len] = '\0'; 125176259Sgreen attrib_clear(&s.attrib); 1252137015Sdes s.name = s.long_name = buf; 125376259Sgreen send_names(id, 1, &s); 125476259Sgreen } 1255255767Sdes free(path); 125676259Sgreen} 125776259Sgreen 125892555Sdesstatic void 1259262566Sdesprocess_symlink(u_int32_t id) 126076259Sgreen{ 126176259Sgreen char *oldpath, *newpath; 1262295367Sdes int r, status; 126376259Sgreen 1264295367Sdes if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || 1265295367Sdes (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) 1266295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1267295367Sdes 1268162852Sdes debug3("request %u: symlink", id); 1269162852Sdes logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); 1270113908Sdes /* this will fail if 'newpath' exists */ 1271295367Sdes r = symlink(oldpath, newpath); 1272295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 127376259Sgreen send_status(id, status); 1274255767Sdes free(oldpath); 1275255767Sdes free(newpath); 127676259Sgreen} 127776259Sgreen 127892555Sdesstatic void 1279181111Sdesprocess_extended_posix_rename(u_int32_t id) 1280181111Sdes{ 1281181111Sdes char *oldpath, *newpath; 1282295367Sdes int r, status; 1283181111Sdes 1284295367Sdes if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || 1285295367Sdes (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) 1286295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1287295367Sdes 1288181111Sdes debug3("request %u: posix-rename", id); 1289181111Sdes logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath); 1290295367Sdes r = rename(oldpath, newpath); 1291295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1292204917Sdes send_status(id, status); 1293255767Sdes free(oldpath); 1294255767Sdes free(newpath); 1295181111Sdes} 1296181111Sdes 1297181111Sdesstatic void 1298181111Sdesprocess_extended_statvfs(u_int32_t id) 1299181111Sdes{ 1300181111Sdes char *path; 1301181111Sdes struct statvfs st; 1302295367Sdes int r; 1303181111Sdes 1304295367Sdes if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0) 1305295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1306262566Sdes debug3("request %u: statvfs", id); 1307262566Sdes logit("statvfs \"%s\"", path); 1308181111Sdes 1309181111Sdes if (statvfs(path, &st) != 0) 1310181111Sdes send_status(id, errno_to_portable(errno)); 1311181111Sdes else 1312181111Sdes send_statvfs(id, &st); 1313255767Sdes free(path); 1314181111Sdes} 1315181111Sdes 1316181111Sdesstatic void 1317181111Sdesprocess_extended_fstatvfs(u_int32_t id) 1318181111Sdes{ 1319295367Sdes int r, handle, fd; 1320181111Sdes struct statvfs st; 1321181111Sdes 1322295367Sdes if ((r = get_handle(iqueue, &handle)) != 0) 1323295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1324181111Sdes debug("request %u: fstatvfs \"%s\" (handle %u)", 1325181111Sdes id, handle_to_name(handle), handle); 1326181111Sdes if ((fd = handle_to_fd(handle)) < 0) { 1327181111Sdes send_status(id, SSH2_FX_FAILURE); 1328181111Sdes return; 1329181111Sdes } 1330181111Sdes if (fstatvfs(fd, &st) != 0) 1331181111Sdes send_status(id, errno_to_portable(errno)); 1332181111Sdes else 1333181111Sdes send_statvfs(id, &st); 1334181111Sdes} 1335181111Sdes 1336181111Sdesstatic void 1337221420Sdesprocess_extended_hardlink(u_int32_t id) 1338221420Sdes{ 1339221420Sdes char *oldpath, *newpath; 1340295367Sdes int r, status; 1341221420Sdes 1342295367Sdes if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || 1343295367Sdes (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) 1344295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1345295367Sdes 1346221420Sdes debug3("request %u: hardlink", id); 1347221420Sdes logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath); 1348295367Sdes r = link(oldpath, newpath); 1349295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1350221420Sdes send_status(id, status); 1351255767Sdes free(oldpath); 1352255767Sdes free(newpath); 1353221420Sdes} 1354221420Sdes 1355221420Sdesstatic void 1356262566Sdesprocess_extended_fsync(u_int32_t id) 135776259Sgreen{ 1358295367Sdes int handle, fd, r, status = SSH2_FX_OP_UNSUPPORTED; 1359262566Sdes 1360295367Sdes if ((r = get_handle(iqueue, &handle)) != 0) 1361295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1362262566Sdes debug3("request %u: fsync (handle %u)", id, handle); 1363262566Sdes verbose("fsync \"%s\"", handle_to_name(handle)); 1364262566Sdes if ((fd = handle_to_fd(handle)) < 0) 1365262566Sdes status = SSH2_FX_NO_SUCH_FILE; 1366262566Sdes else if (handle_is_ok(handle, HANDLE_FILE)) { 1367295367Sdes r = fsync(fd); 1368295367Sdes status = (r == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1369262566Sdes } 1370262566Sdes send_status(id, status); 1371262566Sdes} 1372262566Sdes 1373262566Sdesstatic void 1374262566Sdesprocess_extended(u_int32_t id) 1375262566Sdes{ 137676259Sgreen char *request; 1377295367Sdes int i, r; 137876259Sgreen 1379295367Sdes if ((r = sshbuf_get_cstring(iqueue, &request, NULL)) != 0) 1380295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1381262566Sdes for (i = 0; extended_handlers[i].handler != NULL; i++) { 1382262566Sdes if (strcmp(request, extended_handlers[i].ext_name) == 0) { 1383262566Sdes if (!request_permitted(&extended_handlers[i])) 1384262566Sdes send_status(id, SSH2_FX_PERMISSION_DENIED); 1385262566Sdes else 1386262566Sdes extended_handlers[i].handler(id); 1387262566Sdes break; 1388262566Sdes } 1389262566Sdes } 1390262566Sdes if (extended_handlers[i].handler == NULL) { 1391262566Sdes error("Unknown extended request \"%.100s\"", request); 1392181111Sdes send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ 1393262566Sdes } 1394255767Sdes free(request); 139576259Sgreen} 139676259Sgreen 139765668Skris/* stolen from ssh-agent */ 139865668Skris 139992555Sdesstatic void 140065668Skrisprocess(void) 140165668Skris{ 1402295367Sdes u_int msg_len; 1403295367Sdes u_int buf_len; 1404295367Sdes u_int consumed; 1405295367Sdes u_char type; 1406295367Sdes const u_char *cp; 1407295367Sdes int i, r; 1408262566Sdes u_int32_t id; 140965668Skris 1410295367Sdes buf_len = sshbuf_len(iqueue); 141198675Sdes if (buf_len < 5) 141265668Skris return; /* Incomplete message. */ 1413295367Sdes cp = sshbuf_ptr(iqueue); 1414162852Sdes msg_len = get_u32(cp); 1415157016Sdes if (msg_len > SFTP_MAX_MSG_LENGTH) { 1416162852Sdes error("bad message from %s local user %s", 1417162852Sdes client_addr, pw->pw_name); 1418181111Sdes sftp_server_cleanup_exit(11); 141965668Skris } 142098675Sdes if (buf_len < msg_len + 4) 142165668Skris return; 1422295367Sdes if ((r = sshbuf_consume(iqueue, 4)) != 0) 1423295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 142498675Sdes buf_len -= 4; 1425295367Sdes if ((r = sshbuf_get_u8(iqueue, &type)) != 0) 1426295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1427262566Sdes 142865668Skris switch (type) { 142976259Sgreen case SSH2_FXP_INIT: 143065668Skris process_init(); 1431262566Sdes init_done = 1; 143265668Skris break; 143376259Sgreen case SSH2_FXP_EXTENDED: 1434262566Sdes if (!init_done) 1435262566Sdes fatal("Received extended request before init"); 1436295367Sdes if ((r = sshbuf_get_u32(iqueue, &id)) != 0) 1437295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1438262566Sdes process_extended(id); 143976259Sgreen break; 144065668Skris default: 1441262566Sdes if (!init_done) 1442262566Sdes fatal("Received %u request before init", type); 1443295367Sdes if ((r = sshbuf_get_u32(iqueue, &id)) != 0) 1444295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1445262566Sdes for (i = 0; handlers[i].handler != NULL; i++) { 1446262566Sdes if (type == handlers[i].type) { 1447262566Sdes if (!request_permitted(&handlers[i])) { 1448262566Sdes send_status(id, 1449262566Sdes SSH2_FX_PERMISSION_DENIED); 1450262566Sdes } else { 1451262566Sdes handlers[i].handler(id); 1452262566Sdes } 1453262566Sdes break; 1454262566Sdes } 1455262566Sdes } 1456262566Sdes if (handlers[i].handler == NULL) 1457262566Sdes error("Unknown message %u", type); 145865668Skris } 145998675Sdes /* discard the remaining bytes from the current packet */ 1460295367Sdes if (buf_len < sshbuf_len(iqueue)) { 1461181111Sdes error("iqueue grew unexpectedly"); 1462181111Sdes sftp_server_cleanup_exit(255); 1463181111Sdes } 1464295367Sdes consumed = buf_len - sshbuf_len(iqueue); 1465181111Sdes if (msg_len < consumed) { 1466262566Sdes error("msg_len %u < consumed %u", msg_len, consumed); 1467181111Sdes sftp_server_cleanup_exit(255); 1468181111Sdes } 1469295367Sdes if (msg_len > consumed && 1470295367Sdes (r = sshbuf_consume(iqueue, msg_len - consumed)) != 0) 1471295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 147265668Skris} 147365668Skris 1474162852Sdes/* Cleanup handler that logs active handles upon normal exit */ 1475162852Sdesvoid 1476181111Sdessftp_server_cleanup_exit(int i) 1477162852Sdes{ 1478162852Sdes if (pw != NULL && client_addr != NULL) { 1479162852Sdes handle_log_exit(); 1480162852Sdes logit("session closed for local user %s from [%s]", 1481162852Sdes pw->pw_name, client_addr); 1482162852Sdes } 1483162852Sdes _exit(i); 1484162852Sdes} 1485162852Sdes 1486162852Sdesstatic void 1487181111Sdessftp_server_usage(void) 1488162852Sdes{ 1489162852Sdes extern char *__progname; 1490162852Sdes 1491162852Sdes fprintf(stderr, 1492248619Sdes "usage: %s [-ehR] [-d start_directory] [-f log_facility] " 1493262566Sdes "[-l log_level]\n\t[-P blacklisted_requests] " 1494262566Sdes "[-p whitelisted_requests] [-u umask]\n" 1495262566Sdes " %s -Q protocol_feature\n", 1496262566Sdes __progname, __progname); 1497162852Sdes exit(1); 1498162852Sdes} 1499162852Sdes 150065668Skrisint 1501181111Sdessftp_server_main(int argc, char **argv, struct passwd *user_pw) 150265668Skris{ 150376259Sgreen fd_set *rset, *wset; 1504295367Sdes int i, r, in, out, max, ch, skipargs = 0, log_stderr = 0; 150576259Sgreen ssize_t len, olen, set_size; 1506162852Sdes SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 1507248619Sdes char *cp, *homedir = NULL, buf[4*4096]; 1508221420Sdes long mask; 150965668Skris 1510162852Sdes extern char *optarg; 1511162852Sdes extern char *__progname; 1512162852Sdes 1513296781Sdes ssh_malloc_init(); /* must be called before any mallocs */ 1514162852Sdes __progname = ssh_get_progname(argv[0]); 1515162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 151676259Sgreen 1517248619Sdes pw = pwcopy(user_pw); 1518248619Sdes 1519262566Sdes while (!skipargs && (ch = getopt(argc, argv, 1520262566Sdes "d:f:l:P:p:Q:u:cehR")) != -1) { 1521162852Sdes switch (ch) { 1522262566Sdes case 'Q': 1523262566Sdes if (strcasecmp(optarg, "requests") != 0) { 1524262566Sdes fprintf(stderr, "Invalid query type\n"); 1525262566Sdes exit(1); 1526262566Sdes } 1527262566Sdes for (i = 0; handlers[i].handler != NULL; i++) 1528262566Sdes printf("%s\n", handlers[i].name); 1529262566Sdes for (i = 0; extended_handlers[i].handler != NULL; i++) 1530262566Sdes printf("%s\n", extended_handlers[i].name); 1531262566Sdes exit(0); 1532262566Sdes break; 1533204917Sdes case 'R': 1534204917Sdes readonly = 1; 1535204917Sdes break; 1536162852Sdes case 'c': 1537162852Sdes /* 1538162852Sdes * Ignore all arguments if we are invoked as a 1539162852Sdes * shell using "sftp-server -c command" 1540162852Sdes */ 1541162852Sdes skipargs = 1; 1542162852Sdes break; 1543162852Sdes case 'e': 1544162852Sdes log_stderr = 1; 1545162852Sdes break; 1546162852Sdes case 'l': 1547162852Sdes log_level = log_level_number(optarg); 1548162852Sdes if (log_level == SYSLOG_LEVEL_NOT_SET) 1549162852Sdes error("Invalid log level \"%s\"", optarg); 1550162852Sdes break; 1551162852Sdes case 'f': 1552162852Sdes log_facility = log_facility_number(optarg); 1553181111Sdes if (log_facility == SYSLOG_FACILITY_NOT_SET) 1554162852Sdes error("Invalid log facility \"%s\"", optarg); 1555162852Sdes break; 1556248619Sdes case 'd': 1557248619Sdes cp = tilde_expand_filename(optarg, user_pw->pw_uid); 1558248619Sdes homedir = percent_expand(cp, "d", user_pw->pw_dir, 1559248619Sdes "u", user_pw->pw_name, (char *)NULL); 1560248619Sdes free(cp); 1561248619Sdes break; 1562262566Sdes case 'p': 1563262566Sdes if (request_whitelist != NULL) 1564262566Sdes fatal("Permitted requests already set"); 1565262566Sdes request_whitelist = xstrdup(optarg); 1566262566Sdes break; 1567262566Sdes case 'P': 1568262566Sdes if (request_blacklist != NULL) 1569262566Sdes fatal("Refused requests already set"); 1570262566Sdes request_blacklist = xstrdup(optarg); 1571262566Sdes break; 1572204917Sdes case 'u': 1573221420Sdes errno = 0; 1574221420Sdes mask = strtol(optarg, &cp, 8); 1575221420Sdes if (mask < 0 || mask > 0777 || *cp != '\0' || 1576221420Sdes cp == optarg || (mask == 0 && errno != 0)) 1577221420Sdes fatal("Invalid umask \"%s\"", optarg); 1578221420Sdes (void)umask((mode_t)mask); 1579204917Sdes break; 1580162852Sdes case 'h': 1581162852Sdes default: 1582181111Sdes sftp_server_usage(); 1583162852Sdes } 1584162852Sdes } 1585162852Sdes 1586162852Sdes log_init(__progname, log_level, log_facility, log_stderr); 1587162852Sdes 1588295367Sdes /* 1589323124Sdes * On platforms where we can, avoid making /proc/self/{mem,maps} 1590295367Sdes * available to the user so that sftp access doesn't automatically 1591295367Sdes * imply arbitrary code execution access that will break 1592295367Sdes * restricted configurations. 1593295367Sdes */ 1594323124Sdes platform_disable_tracing(1); /* strict */ 1595295367Sdes 1596296781Sdes /* Drop any fine-grained privileges we don't need */ 1597296781Sdes platform_pledge_sftp_server(); 1598296781Sdes 1599162852Sdes if ((cp = getenv("SSH_CONNECTION")) != NULL) { 1600162852Sdes client_addr = xstrdup(cp); 1601181111Sdes if ((cp = strchr(client_addr, ' ')) == NULL) { 1602181111Sdes error("Malformed SSH_CONNECTION variable: \"%s\"", 1603162852Sdes getenv("SSH_CONNECTION")); 1604181111Sdes sftp_server_cleanup_exit(255); 1605181111Sdes } 1606162852Sdes *cp = '\0'; 1607162852Sdes } else 1608162852Sdes client_addr = xstrdup("UNKNOWN"); 1609162852Sdes 1610162852Sdes logit("session opened for local user %s from [%s]", 1611162852Sdes pw->pw_name, client_addr); 1612162852Sdes 1613204917Sdes in = STDIN_FILENO; 1614204917Sdes out = STDOUT_FILENO; 161565668Skris 161698937Sdes#ifdef HAVE_CYGWIN 161798937Sdes setmode(in, O_BINARY); 161898937Sdes setmode(out, O_BINARY); 161998937Sdes#endif 162098937Sdes 162165668Skris max = 0; 162265668Skris if (in > max) 162365668Skris max = in; 162465668Skris if (out > max) 162565668Skris max = out; 162665668Skris 1627295367Sdes if ((iqueue = sshbuf_new()) == NULL) 1628295367Sdes fatal("%s: sshbuf_new failed", __func__); 1629295367Sdes if ((oqueue = sshbuf_new()) == NULL) 1630295367Sdes fatal("%s: sshbuf_new failed", __func__); 163165668Skris 1632296781Sdes rset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask)); 1633296781Sdes wset = xcalloc(howmany(max + 1, NFDBITS), sizeof(fd_mask)); 163476259Sgreen 1635248619Sdes if (homedir != NULL) { 1636248619Sdes if (chdir(homedir) != 0) { 1637248619Sdes error("chdir to \"%s\" failed: %s", homedir, 1638248619Sdes strerror(errno)); 1639248619Sdes } 1640248619Sdes } 1641248619Sdes 1642296781Sdes set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); 164365668Skris for (;;) { 164476259Sgreen memset(rset, 0, set_size); 164576259Sgreen memset(wset, 0, set_size); 164665668Skris 1647181111Sdes /* 1648181111Sdes * Ensure that we can read a full buffer and handle 1649181111Sdes * the worst-case length packet it can generate, 1650181111Sdes * otherwise apply backpressure by stopping reads. 1651181111Sdes */ 1652295367Sdes if ((r = sshbuf_check_reserve(iqueue, sizeof(buf))) == 0 && 1653295367Sdes (r = sshbuf_check_reserve(oqueue, 1654295367Sdes SFTP_MAX_MSG_LENGTH)) == 0) 1655181111Sdes FD_SET(in, rset); 1656295367Sdes else if (r != SSH_ERR_NO_BUFFER_SPACE) 1657295367Sdes fatal("%s: sshbuf_check_reserve failed: %s", 1658295367Sdes __func__, ssh_err(r)); 1659181111Sdes 1660295367Sdes olen = sshbuf_len(oqueue); 166165668Skris if (olen > 0) 166276259Sgreen FD_SET(out, wset); 166365668Skris 166476259Sgreen if (select(max+1, rset, wset, NULL, NULL) < 0) { 166565668Skris if (errno == EINTR) 166665668Skris continue; 1667162852Sdes error("select: %s", strerror(errno)); 1668181111Sdes sftp_server_cleanup_exit(2); 166965668Skris } 167065668Skris 167165668Skris /* copy stdin to iqueue */ 167276259Sgreen if (FD_ISSET(in, rset)) { 167365668Skris len = read(in, buf, sizeof buf); 167465668Skris if (len == 0) { 167565668Skris debug("read eof"); 1676181111Sdes sftp_server_cleanup_exit(0); 167765668Skris } else if (len < 0) { 1678162852Sdes error("read: %s", strerror(errno)); 1679181111Sdes sftp_server_cleanup_exit(1); 1680295367Sdes } else if ((r = sshbuf_put(iqueue, buf, len)) != 0) { 1681295367Sdes fatal("%s: buffer error: %s", 1682295367Sdes __func__, ssh_err(r)); 168365668Skris } 168465668Skris } 168565668Skris /* send oqueue to stdout */ 168676259Sgreen if (FD_ISSET(out, wset)) { 1687295367Sdes len = write(out, sshbuf_ptr(oqueue), olen); 168865668Skris if (len < 0) { 1689162852Sdes error("write: %s", strerror(errno)); 1690181111Sdes sftp_server_cleanup_exit(1); 1691295367Sdes } else if ((r = sshbuf_consume(oqueue, len)) != 0) { 1692295367Sdes fatal("%s: buffer error: %s", 1693295367Sdes __func__, ssh_err(r)); 169465668Skris } 169565668Skris } 1696181111Sdes 1697181111Sdes /* 1698181111Sdes * Process requests from client if we can fit the results 1699181111Sdes * into the output buffer, otherwise stop processing input 1700181111Sdes * and let the output queue drain. 1701181111Sdes */ 1702295367Sdes r = sshbuf_check_reserve(oqueue, SFTP_MAX_MSG_LENGTH); 1703295367Sdes if (r == 0) 1704181111Sdes process(); 1705295367Sdes else if (r != SSH_ERR_NO_BUFFER_SPACE) 1706295367Sdes fatal("%s: sshbuf_check_reserve: %s", 1707295367Sdes __func__, ssh_err(r)); 170865668Skris } 170965668Skris} 1710