sftp-server.c revision 221420
1327952Sdim/* $OpenBSD: sftp-server.c,v 1.93 2010/12/04 00:18:01 djm Exp $ */ 2286425Sdim/* 3286425Sdim * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. 4286425Sdim * 5286425Sdim * Permission to use, copy, modify, and distribute this software for any 6286425Sdim * purpose with or without fee is hereby granted, provided that the above 7286425Sdim * copyright notice and this permission notice appear in all copies. 8286425Sdim * 9286425Sdim * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10286425Sdim * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11286425Sdim * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12314564Sdim * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13286425Sdim * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14327952Sdim * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15286425Sdim * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16314564Sdim */ 17286425Sdim 18286425Sdim#include "includes.h" 19341825Sdim 20314564Sdim#include <sys/types.h> 21314564Sdim#include <sys/param.h> 22286425Sdim#include <sys/stat.h> 23314564Sdim#ifdef HAVE_SYS_TIME_H 24286425Sdim# include <sys/time.h> 25286425Sdim#endif 26314564Sdim#ifdef HAVE_SYS_MOUNT_H 27286425Sdim#include <sys/mount.h> 28314564Sdim#endif 29314564Sdim#ifdef HAVE_SYS_STATVFS_H 30314564Sdim#include <sys/statvfs.h> 31314564Sdim#endif 32286425Sdim 33314564Sdim#include <dirent.h> 34286425Sdim#include <errno.h> 35314564Sdim#include <fcntl.h> 36286425Sdim#include <pwd.h> 37314564Sdim#include <stdlib.h> 38286425Sdim#include <stdio.h> 39286425Sdim#include <string.h> 40314564Sdim#include <pwd.h> 41314564Sdim#include <time.h> 42314564Sdim#include <unistd.h> 43314564Sdim#include <stdarg.h> 44314564Sdim 45286425Sdim#include "xmalloc.h" 46286425Sdim#include "buffer.h" 47314564Sdim#include "log.h" 48286425Sdim#include "misc.h" 49286425Sdim#include "uidswap.h" 50286425Sdim 51286425Sdim#include "sftp.h" 52286425Sdim#include "sftp-common.h" 53286425Sdim 54286425Sdim/* helper */ 55286425Sdim#define get_int64() buffer_get_int64(&iqueue); 56286425Sdim#define get_int() buffer_get_int(&iqueue); 57286425Sdim#define get_string(lenp) buffer_get_string(&iqueue, lenp); 58286425Sdim 59286425Sdim/* Our verbosity */ 60286425SdimLogLevel log_level = SYSLOG_LEVEL_ERROR; 61286425Sdim 62314564Sdim/* Our client */ 63286425Sdimstruct passwd *pw = NULL; 64286425Sdimchar *client_addr = NULL; 65314564Sdim 66314564Sdim/* input and output queue */ 67286425SdimBuffer iqueue; 68314564SdimBuffer oqueue; 69286425Sdim 70327952Sdim/* Version of client */ 71327952Sdimint version; 72327952Sdim 73327952Sdim/* Disable writes */ 74327952Sdimint readonly; 75327952Sdim 76286425Sdim/* portable attributes, etc. */ 77286425Sdim 78286425Sdimtypedef struct Stat Stat; 79296417Sdim 80314564Sdimstruct Stat { 81286425Sdim char *name; 82296417Sdim char *long_name; 83296417Sdim Attrib attrib; 84296417Sdim}; 85296417Sdim 86296417Sdimstatic int 87296417Sdimerrno_to_portable(int unixerrno) 88286425Sdim{ 89286425Sdim int ret = 0; 90296417Sdim 91286425Sdim switch (unixerrno) { 92296417Sdim case 0: 93314564Sdim ret = SSH2_FX_OK; 94286425Sdim break; 95286425Sdim case ENOENT: 96286425Sdim case ENOTDIR: 97286425Sdim case EBADF: 98286425Sdim case ELOOP: 99314564Sdim ret = SSH2_FX_NO_SUCH_FILE; 100286425Sdim break; 101286425Sdim case EPERM: 102286425Sdim case EACCES: 103286425Sdim case EFAULT: 104314564Sdim ret = SSH2_FX_PERMISSION_DENIED; 105314564Sdim break; 106314564Sdim case ENAMETOOLONG: 107314564Sdim case EINVAL: 108286425Sdim ret = SSH2_FX_BAD_MESSAGE; 109286425Sdim break; 110309124Sdim case ENOSYS: 111309124Sdim ret = SSH2_FX_OP_UNSUPPORTED; 112286425Sdim break; 113286425Sdim default: 114286425Sdim ret = SSH2_FX_FAILURE; 115286425Sdim break; 116286425Sdim } 117286425Sdim return ret; 118327952Sdim} 119327952Sdim 120327952Sdimstatic int 121286425Sdimflags_from_portable(int pflags) 122286425Sdim{ 123286425Sdim int flags = 0; 124286425Sdim 125286425Sdim if ((pflags & SSH2_FXF_READ) && 126286425Sdim (pflags & SSH2_FXF_WRITE)) { 127286425Sdim flags = O_RDWR; 128286425Sdim } else if (pflags & SSH2_FXF_READ) { 129286425Sdim flags = O_RDONLY; 130286425Sdim } else if (pflags & SSH2_FXF_WRITE) { 131286425Sdim flags = O_WRONLY; 132286425Sdim } 133286425Sdim if (pflags & SSH2_FXF_CREAT) 134286425Sdim flags |= O_CREAT; 135286425Sdim if (pflags & SSH2_FXF_TRUNC) 136286425Sdim flags |= O_TRUNC; 137286425Sdim if (pflags & SSH2_FXF_EXCL) 138286425Sdim flags |= O_EXCL; 139286425Sdim return flags; 140286425Sdim} 141286425Sdim 142286425Sdimstatic const char * 143286425Sdimstring_from_portable(int pflags) 144286425Sdim{ 145286425Sdim static char ret[128]; 146286425Sdim 147286425Sdim *ret = '\0'; 148286425Sdim 149286425Sdim#define PAPPEND(str) { \ 150286425Sdim if (*ret != '\0') \ 151286425Sdim strlcat(ret, ",", sizeof(ret)); \ 152286425Sdim strlcat(ret, str, sizeof(ret)); \ 153286425Sdim } 154286425Sdim 155286425Sdim if (pflags & SSH2_FXF_READ) 156286425Sdim PAPPEND("READ") 157286425Sdim if (pflags & SSH2_FXF_WRITE) 158286425Sdim PAPPEND("WRITE") 159286425Sdim if (pflags & SSH2_FXF_CREAT) 160286425Sdim PAPPEND("CREATE") 161314564Sdim if (pflags & SSH2_FXF_TRUNC) 162286425Sdim PAPPEND("TRUNCATE") 163286425Sdim if (pflags & SSH2_FXF_EXCL) 164327952Sdim PAPPEND("EXCL") 165286425Sdim 166286425Sdim return ret; 167286425Sdim} 168309124Sdim 169286425Sdimstatic Attrib * 170286425Sdimget_attrib(void) 171286425Sdim{ 172286425Sdim return decode_attrib(&iqueue); 173286425Sdim} 174314564Sdim 175286425Sdim/* handle handles */ 176286425Sdim 177286425Sdimtypedef struct Handle Handle; 178286425Sdimstruct Handle { 179286425Sdim int use; 180321369Sdim DIR *dirp; 181321369Sdim int fd; 182286425Sdim char *name; 183286425Sdim u_int64_t bytes_read, bytes_write; 184327952Sdim int next_unused; 185286425Sdim}; 186286425Sdim 187286425Sdimenum { 188286425Sdim HANDLE_UNUSED, 189327952Sdim HANDLE_DIR, 190327952Sdim HANDLE_FILE 191286425Sdim}; 192327952Sdim 193286425SdimHandle *handles = NULL; 194286425Sdimu_int num_handles = 0; 195286425Sdimint first_unused_handle = -1; 196286425Sdim 197286425Sdimstatic void handle_unused(int i) 198286425Sdim{ 199314564Sdim handles[i].use = HANDLE_UNUSED; 200286425Sdim handles[i].next_unused = first_unused_handle; 201286425Sdim first_unused_handle = i; 202286425Sdim} 203286425Sdim 204314564Sdimstatic int 205314564Sdimhandle_new(int use, const char *name, int fd, DIR *dirp) 206286425Sdim{ 207286425Sdim int i; 208286425Sdim 209286425Sdim if (first_unused_handle == -1) { 210286425Sdim if (num_handles + 1 <= num_handles) 211286425Sdim return -1; 212286425Sdim num_handles++; 213286425Sdim handles = xrealloc(handles, num_handles, sizeof(Handle)); 214286425Sdim handle_unused(num_handles - 1); 215286425Sdim } 216286425Sdim 217286425Sdim i = first_unused_handle; 218286425Sdim first_unused_handle = handles[i].next_unused; 219286425Sdim 220286425Sdim handles[i].use = use; 221286425Sdim handles[i].dirp = dirp; 222286425Sdim handles[i].fd = fd; 223286425Sdim handles[i].name = xstrdup(name); 224286425Sdim handles[i].bytes_read = handles[i].bytes_write = 0; 225286425Sdim 226286425Sdim return i; 227286425Sdim} 228286425Sdim 229286425Sdimstatic int 230286425Sdimhandle_is_ok(int i, int type) 231286425Sdim{ 232286425Sdim return i >= 0 && (u_int)i < num_handles && handles[i].use == type; 233286425Sdim} 234286425Sdim 235286425Sdimstatic int 236286425Sdimhandle_to_string(int handle, char **stringp, int *hlenp) 237321369Sdim{ 238321369Sdim if (stringp == NULL || hlenp == NULL) 239321369Sdim return -1; 240321369Sdim *stringp = xmalloc(sizeof(int32_t)); 241321369Sdim put_u32(*stringp, handle); 242286425Sdim *hlenp = sizeof(int32_t); 243286425Sdim return 0; 244286425Sdim} 245286425Sdim 246286425Sdimstatic int 247286425Sdimhandle_from_string(const char *handle, u_int hlen) 248286425Sdim{ 249286425Sdim int val; 250286425Sdim 251286425Sdim if (hlen != sizeof(int32_t)) 252286425Sdim return -1; 253286425Sdim val = get_u32(handle); 254286425Sdim if (handle_is_ok(val, HANDLE_FILE) || 255286425Sdim handle_is_ok(val, HANDLE_DIR)) 256286425Sdim return val; 257286425Sdim return -1; 258286425Sdim} 259286425Sdim 260286425Sdimstatic char * 261286425Sdimhandle_to_name(int handle) 262286425Sdim{ 263286425Sdim if (handle_is_ok(handle, HANDLE_DIR)|| 264286425Sdim handle_is_ok(handle, HANDLE_FILE)) 265286425Sdim return handles[handle].name; 266286425Sdim return NULL; 267286425Sdim} 268286425Sdim 269286425Sdimstatic DIR * 270286425Sdimhandle_to_dir(int handle) 271286425Sdim{ 272327952Sdim if (handle_is_ok(handle, HANDLE_DIR)) 273327952Sdim return handles[handle].dirp; 274286425Sdim return NULL; 275286425Sdim} 276286425Sdim 277286425Sdimstatic int 278286425Sdimhandle_to_fd(int handle) 279286425Sdim{ 280286425Sdim if (handle_is_ok(handle, HANDLE_FILE)) 281286425Sdim return handles[handle].fd; 282286425Sdim return -1; 283286425Sdim} 284286425Sdim 285286425Sdimstatic void 286286425Sdimhandle_update_read(int handle, ssize_t bytes) 287286425Sdim{ 288327952Sdim if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 289327952Sdim handles[handle].bytes_read += bytes; 290286425Sdim} 291286425Sdim 292286425Sdimstatic void 293286425Sdimhandle_update_write(int handle, ssize_t bytes) 294286425Sdim{ 295286425Sdim if (handle_is_ok(handle, HANDLE_FILE) && bytes > 0) 296286425Sdim handles[handle].bytes_write += bytes; 297286425Sdim} 298286425Sdim 299286425Sdimstatic u_int64_t 300286425Sdimhandle_bytes_read(int handle) 301286425Sdim{ 302286425Sdim if (handle_is_ok(handle, HANDLE_FILE)) 303286425Sdim return (handles[handle].bytes_read); 304286425Sdim return 0; 305286425Sdim} 306286425Sdim 307327952Sdimstatic u_int64_t 308286425Sdimhandle_bytes_write(int handle) 309286425Sdim{ 310286425Sdim if (handle_is_ok(handle, HANDLE_FILE)) 311314564Sdim return (handles[handle].bytes_write); 312286425Sdim return 0; 313286425Sdim} 314286425Sdim 315286425Sdimstatic int 316314564Sdimhandle_close(int handle) 317286425Sdim{ 318286425Sdim int ret = -1; 319286425Sdim 320286425Sdim if (handle_is_ok(handle, HANDLE_FILE)) { 321286425Sdim ret = close(handles[handle].fd); 322286425Sdim xfree(handles[handle].name); 323286425Sdim handle_unused(handle); 324286425Sdim } else if (handle_is_ok(handle, HANDLE_DIR)) { 325286425Sdim ret = closedir(handles[handle].dirp); 326286425Sdim xfree(handles[handle].name); 327286425Sdim handle_unused(handle); 328286425Sdim } else { 329321369Sdim errno = ENOENT; 330321369Sdim } 331286425Sdim return ret; 332286425Sdim} 333286425Sdim 334286425Sdimstatic void 335286425Sdimhandle_log_close(int handle, char *emsg) 336286425Sdim{ 337286425Sdim if (handle_is_ok(handle, HANDLE_FILE)) { 338286425Sdim logit("%s%sclose \"%s\" bytes read %llu written %llu", 339286425Sdim emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 340286425Sdim handle_to_name(handle), 341286425Sdim (unsigned long long)handle_bytes_read(handle), 342286425Sdim (unsigned long long)handle_bytes_write(handle)); 343286425Sdim } else { 344286425Sdim logit("%s%sclosedir \"%s\"", 345341825Sdim emsg == NULL ? "" : emsg, emsg == NULL ? "" : " ", 346286425Sdim handle_to_name(handle)); 347286425Sdim } 348321369Sdim} 349286425Sdim 350286425Sdimstatic void 351286425Sdimhandle_log_exit(void) 352321369Sdim{ 353286425Sdim u_int i; 354286425Sdim 355286425Sdim for (i = 0; i < num_handles; i++) 356286425Sdim if (handles[i].use != HANDLE_UNUSED) 357286425Sdim handle_log_close(i, "forced"); 358286425Sdim} 359286425Sdim 360286425Sdimstatic int 361286425Sdimget_handle(void) 362286425Sdim{ 363286425Sdim char *handle; 364286425Sdim int val = -1; 365286425Sdim u_int hlen; 366286425Sdim 367286425Sdim handle = get_string(&hlen); 368286425Sdim if (hlen < 256) 369286425Sdim val = handle_from_string(handle, hlen); 370286425Sdim xfree(handle); 371286425Sdim return val; 372286425Sdim} 373286425Sdim 374286425Sdim/* send replies */ 375286425Sdim 376286425Sdimstatic void 377286425Sdimsend_msg(Buffer *m) 378286425Sdim{ 379286425Sdim int mlen = buffer_len(m); 380286425Sdim 381286425Sdim buffer_put_int(&oqueue, mlen); 382286425Sdim buffer_append(&oqueue, buffer_ptr(m), mlen); 383286425Sdim buffer_consume(m, mlen); 384286425Sdim} 385286425Sdim 386286425Sdimstatic const char * 387286425Sdimstatus_to_message(u_int32_t status) 388321369Sdim{ 389286425Sdim const char *status_messages[] = { 390286425Sdim "Success", /* SSH_FX_OK */ 391286425Sdim "End of file", /* SSH_FX_EOF */ 392286425Sdim "No such file", /* SSH_FX_NO_SUCH_FILE */ 393286425Sdim "Permission denied", /* SSH_FX_PERMISSION_DENIED */ 394286425Sdim "Failure", /* SSH_FX_FAILURE */ 395286425Sdim "Bad message", /* SSH_FX_BAD_MESSAGE */ 396286425Sdim "No connection", /* SSH_FX_NO_CONNECTION */ 397286425Sdim "Connection lost", /* SSH_FX_CONNECTION_LOST */ 398286425Sdim "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ 399286425Sdim "Unknown error" /* Others */ 400286425Sdim }; 401286425Sdim return (status_messages[MIN(status,SSH2_FX_MAX)]); 402286425Sdim} 403286425Sdim 404286425Sdimstatic void 405286425Sdimsend_status(u_int32_t id, u_int32_t status) 406286425Sdim{ 407286425Sdim Buffer msg; 408286425Sdim 409286425Sdim debug3("request %u: sent status %u", id, status); 410286425Sdim if (log_level > SYSLOG_LEVEL_VERBOSE || 411286425Sdim (status != SSH2_FX_OK && status != SSH2_FX_EOF)) 412296417Sdim logit("sent status %s", status_to_message(status)); 413286425Sdim buffer_init(&msg); 414286425Sdim buffer_put_char(&msg, SSH2_FXP_STATUS); 415286425Sdim buffer_put_int(&msg, id); 416286425Sdim buffer_put_int(&msg, status); 417286425Sdim if (version >= 3) { 418286425Sdim buffer_put_cstring(&msg, status_to_message(status)); 419286425Sdim buffer_put_cstring(&msg, ""); 420286425Sdim } 421286425Sdim send_msg(&msg); 422286425Sdim buffer_free(&msg); 423286425Sdim} 424286425Sdimstatic void 425286425Sdimsend_data_or_handle(char type, u_int32_t id, const char *data, int dlen) 426286425Sdim{ 427286425Sdim Buffer msg; 428286425Sdim 429341825Sdim buffer_init(&msg); 430286425Sdim buffer_put_char(&msg, type); 431286425Sdim buffer_put_int(&msg, id); 432314564Sdim buffer_put_string(&msg, data, dlen); 433314564Sdim send_msg(&msg); 434327952Sdim buffer_free(&msg); 435327952Sdim} 436286425Sdim 437286425Sdimstatic void 438286425Sdimsend_data(u_int32_t id, const char *data, int dlen) 439286425Sdim{ 440286425Sdim debug("request %u: sent data len %d", id, dlen); 441286425Sdim send_data_or_handle(SSH2_FXP_DATA, id, data, dlen); 442286425Sdim} 443286425Sdim 444286425Sdimstatic void 445314564Sdimsend_handle(u_int32_t id, int handle) 446286425Sdim{ 447314564Sdim char *string; 448314564Sdim int hlen; 449286425Sdim 450286425Sdim handle_to_string(handle, &string, &hlen); 451286425Sdim debug("request %u: sent handle handle %d", id, handle); 452286425Sdim send_data_or_handle(SSH2_FXP_HANDLE, id, string, hlen); 453286425Sdim xfree(string); 454286425Sdim} 455286425Sdim 456286425Sdimstatic void 457286425Sdimsend_names(u_int32_t id, int count, const Stat *stats) 458286425Sdim{ 459286425Sdim Buffer msg; 460286425Sdim int i; 461286425Sdim 462286425Sdim buffer_init(&msg); 463286425Sdim buffer_put_char(&msg, SSH2_FXP_NAME); 464286425Sdim buffer_put_int(&msg, id); 465314564Sdim buffer_put_int(&msg, count); 466286425Sdim debug("request %u: sent names count %d", id, count); 467327952Sdim for (i = 0; i < count; i++) { 468327952Sdim buffer_put_cstring(&msg, stats[i].name); 469327952Sdim buffer_put_cstring(&msg, stats[i].long_name); 470286425Sdim encode_attrib(&msg, &stats[i].attrib); 471314564Sdim } 472314564Sdim send_msg(&msg); 473314564Sdim buffer_free(&msg); 474286425Sdim} 475286425Sdim 476286425Sdimstatic void 477314564Sdimsend_attrib(u_int32_t id, const Attrib *a) 478314564Sdim{ 479286425Sdim Buffer msg; 480286425Sdim 481286425Sdim debug("request %u: sent attrib have 0x%x", id, a->flags); 482286425Sdim buffer_init(&msg); 483314564Sdim buffer_put_char(&msg, SSH2_FXP_ATTRS); 484286425Sdim buffer_put_int(&msg, id); 485286425Sdim encode_attrib(&msg, a); 486286425Sdim send_msg(&msg); 487286425Sdim buffer_free(&msg); 488314564Sdim} 489286425Sdim 490314564Sdimstatic void 491286425Sdimsend_statvfs(u_int32_t id, struct statvfs *st) 492286425Sdim{ 493286425Sdim Buffer msg; 494286425Sdim u_int64_t flag; 495286425Sdim 496314564Sdim flag = (st->f_flag & ST_RDONLY) ? SSH2_FXE_STATVFS_ST_RDONLY : 0; 497286425Sdim flag |= (st->f_flag & ST_NOSUID) ? SSH2_FXE_STATVFS_ST_NOSUID : 0; 498314564Sdim 499314564Sdim buffer_init(&msg); 500286425Sdim buffer_put_char(&msg, SSH2_FXP_EXTENDED_REPLY); 501286425Sdim buffer_put_int(&msg, id); 502286425Sdim buffer_put_int64(&msg, st->f_bsize); 503286425Sdim buffer_put_int64(&msg, st->f_frsize); 504286425Sdim buffer_put_int64(&msg, st->f_blocks); 505286425Sdim buffer_put_int64(&msg, st->f_bfree); 506286425Sdim buffer_put_int64(&msg, st->f_bavail); 507286425Sdim buffer_put_int64(&msg, st->f_files); 508286425Sdim buffer_put_int64(&msg, st->f_ffree); 509286425Sdim buffer_put_int64(&msg, st->f_favail); 510286425Sdim buffer_put_int64(&msg, FSID_TO_ULONG(st->f_fsid)); 511286425Sdim buffer_put_int64(&msg, flag); 512286425Sdim buffer_put_int64(&msg, st->f_namemax); 513286425Sdim send_msg(&msg); 514286425Sdim buffer_free(&msg); 515286425Sdim} 516286425Sdim 517286425Sdim/* parse incoming */ 518286425Sdim 519286425Sdimstatic void 520286425Sdimprocess_init(void) 521286425Sdim{ 522286425Sdim Buffer msg; 523286425Sdim 524286425Sdim version = get_int(); 525286425Sdim verbose("received client version %d", version); 526286425Sdim buffer_init(&msg); 527286425Sdim buffer_put_char(&msg, SSH2_FXP_VERSION); 528286425Sdim buffer_put_int(&msg, SSH2_FILEXFER_VERSION); 529286425Sdim /* POSIX rename extension */ 530286425Sdim buffer_put_cstring(&msg, "posix-rename@openssh.com"); 531286425Sdim buffer_put_cstring(&msg, "1"); /* version */ 532286425Sdim /* statvfs extension */ 533286425Sdim buffer_put_cstring(&msg, "statvfs@openssh.com"); 534286425Sdim buffer_put_cstring(&msg, "2"); /* version */ 535286425Sdim /* fstatvfs extension */ 536286425Sdim buffer_put_cstring(&msg, "fstatvfs@openssh.com"); 537286425Sdim buffer_put_cstring(&msg, "2"); /* version */ 538327952Sdim /* hardlink extension */ 539286425Sdim buffer_put_cstring(&msg, "hardlink@openssh.com"); 540286425Sdim buffer_put_cstring(&msg, "1"); /* version */ 541286425Sdim send_msg(&msg); 542286425Sdim buffer_free(&msg); 543286425Sdim} 544286425Sdim 545286425Sdimstatic void 546286425Sdimprocess_open(void) 547286425Sdim{ 548286425Sdim u_int32_t id, pflags; 549286425Sdim Attrib *a; 550286425Sdim char *name; 551286425Sdim int handle, fd, flags, mode, status = SSH2_FX_FAILURE; 552286425Sdim 553286425Sdim id = get_int(); 554286425Sdim name = get_string(NULL); 555286425Sdim pflags = get_int(); /* portable flags */ 556286425Sdim debug3("request %u: open flags %d", id, pflags); 557286425Sdim a = get_attrib(); 558286425Sdim flags = flags_from_portable(pflags); 559286425Sdim mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a->perm : 0666; 560286425Sdim logit("open \"%s\" flags %s mode 0%o", 561286425Sdim name, string_from_portable(pflags), mode); 562286425Sdim if (readonly && 563286425Sdim ((flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR)) 564286425Sdim status = SSH2_FX_PERMISSION_DENIED; 565286425Sdim else { 566286425Sdim fd = open(name, flags, mode); 567286425Sdim if (fd < 0) { 568286425Sdim status = errno_to_portable(errno); 569286425Sdim } else { 570286425Sdim handle = handle_new(HANDLE_FILE, name, fd, NULL); 571286425Sdim if (handle < 0) { 572286425Sdim close(fd); 573286425Sdim } else { 574286425Sdim send_handle(id, handle); 575286425Sdim status = SSH2_FX_OK; 576286425Sdim } 577286425Sdim } 578341825Sdim } 579286425Sdim if (status != SSH2_FX_OK) 580286425Sdim send_status(id, status); 581286425Sdim xfree(name); 582286425Sdim} 583286425Sdim 584286425Sdimstatic void 585286425Sdimprocess_close(void) 586286425Sdim{ 587286425Sdim u_int32_t id; 588286425Sdim int handle, ret, status = SSH2_FX_FAILURE; 589286425Sdim 590286425Sdim id = get_int(); 591286425Sdim handle = get_handle(); 592286425Sdim debug3("request %u: close handle %u", id, handle); 593286425Sdim handle_log_close(handle, NULL); 594286425Sdim ret = handle_close(handle); 595286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 596286425Sdim send_status(id, status); 597327952Sdim} 598286425Sdim 599286425Sdimstatic void 600286425Sdimprocess_read(void) 601286425Sdim{ 602286425Sdim char buf[64*1024]; 603286425Sdim u_int32_t id, len; 604286425Sdim int handle, fd, ret, status = SSH2_FX_FAILURE; 605286425Sdim u_int64_t off; 606286425Sdim 607286425Sdim id = get_int(); 608286425Sdim handle = get_handle(); 609286425Sdim off = get_int64(); 610286425Sdim len = get_int(); 611286425Sdim 612286425Sdim debug("request %u: read \"%s\" (handle %d) off %llu len %d", 613286425Sdim id, handle_to_name(handle), handle, (unsigned long long)off, len); 614286425Sdim if (len > sizeof buf) { 615286425Sdim len = sizeof buf; 616286425Sdim debug2("read change len %d", len); 617286425Sdim } 618286425Sdim fd = handle_to_fd(handle); 619286425Sdim if (fd >= 0) { 620286425Sdim if (lseek(fd, off, SEEK_SET) < 0) { 621286425Sdim error("process_read: seek failed"); 622286425Sdim status = errno_to_portable(errno); 623286425Sdim } else { 624286425Sdim ret = read(fd, buf, len); 625286425Sdim if (ret < 0) { 626286425Sdim status = errno_to_portable(errno); 627286425Sdim } else if (ret == 0) { 628286425Sdim status = SSH2_FX_EOF; 629286425Sdim } else { 630286425Sdim send_data(id, buf, ret); 631286425Sdim status = SSH2_FX_OK; 632286425Sdim handle_update_read(handle, ret); 633286425Sdim } 634286425Sdim } 635286425Sdim } 636286425Sdim if (status != SSH2_FX_OK) 637286425Sdim send_status(id, status); 638286425Sdim} 639286425Sdim 640286425Sdimstatic void 641286425Sdimprocess_write(void) 642286425Sdim{ 643286425Sdim u_int32_t id; 644286425Sdim u_int64_t off; 645341825Sdim u_int len; 646286425Sdim int handle, fd, ret, status; 647286425Sdim char *data; 648286425Sdim 649286425Sdim id = get_int(); 650286425Sdim handle = get_handle(); 651286425Sdim off = get_int64(); 652286425Sdim data = get_string(&len); 653286425Sdim 654286425Sdim debug("request %u: write \"%s\" (handle %d) off %llu len %d", 655286425Sdim id, handle_to_name(handle), handle, (unsigned long long)off, len); 656286425Sdim fd = handle_to_fd(handle); 657286425Sdim 658286425Sdim if (fd < 0) 659286425Sdim status = SSH2_FX_FAILURE; 660286425Sdim else if (readonly) 661286425Sdim status = SSH2_FX_PERMISSION_DENIED; 662314564Sdim else { 663286425Sdim if (lseek(fd, off, SEEK_SET) < 0) { 664286425Sdim status = errno_to_portable(errno); 665341825Sdim error("process_write: seek failed"); 666286425Sdim } else { 667286425Sdim/* XXX ATOMICIO ? */ 668314564Sdim ret = write(fd, data, len); 669314564Sdim if (ret < 0) { 670341825Sdim error("process_write: write failed"); 671341825Sdim status = errno_to_portable(errno); 672341825Sdim } else if ((size_t)ret == len) { 673341825Sdim status = SSH2_FX_OK; 674341825Sdim handle_update_write(handle, ret); 675341825Sdim } else { 676341825Sdim debug2("nothing at all written"); 677341825Sdim status = SSH2_FX_FAILURE; 678341825Sdim } 679341825Sdim } 680341825Sdim } 681286425Sdim send_status(id, status); 682341825Sdim xfree(data); 683341825Sdim} 684341825Sdim 685341825Sdimstatic void 686341825Sdimprocess_do_stat(int do_lstat) 687341825Sdim{ 688341825Sdim Attrib a; 689341825Sdim struct stat st; 690341825Sdim u_int32_t id; 691314564Sdim char *name; 692286425Sdim int ret, status = SSH2_FX_FAILURE; 693341825Sdim 694286425Sdim id = get_int(); 695314564Sdim name = get_string(NULL); 696286425Sdim debug3("request %u: %sstat", id, do_lstat ? "l" : ""); 697314564Sdim verbose("%sstat name \"%s\"", do_lstat ? "l" : "", name); 698314564Sdim ret = do_lstat ? lstat(name, &st) : stat(name, &st); 699286425Sdim if (ret < 0) { 700286425Sdim status = errno_to_portable(errno); 701286425Sdim } else { 702286425Sdim stat_to_attrib(&st, &a); 703286425Sdim send_attrib(id, &a); 704286425Sdim status = SSH2_FX_OK; 705286425Sdim } 706286425Sdim if (status != SSH2_FX_OK) 707286425Sdim send_status(id, status); 708286425Sdim xfree(name); 709286425Sdim} 710286425Sdim 711286425Sdimstatic void 712286425Sdimprocess_stat(void) 713286425Sdim{ 714286425Sdim process_do_stat(0); 715314564Sdim} 716286425Sdim 717286425Sdimstatic void 718286425Sdimprocess_lstat(void) 719314564Sdim{ 720286425Sdim process_do_stat(1); 721314564Sdim} 722314564Sdim 723314564Sdimstatic void 724314564Sdimprocess_fstat(void) 725286425Sdim{ 726327952Sdim Attrib a; 727327952Sdim struct stat st; 728327952Sdim u_int32_t id; 729286425Sdim int fd, ret, handle, status = SSH2_FX_FAILURE; 730286425Sdim 731286425Sdim id = get_int(); 732286425Sdim handle = get_handle(); 733286425Sdim debug("request %u: fstat \"%s\" (handle %u)", 734286425Sdim id, handle_to_name(handle), handle); 735286425Sdim fd = handle_to_fd(handle); 736286425Sdim if (fd >= 0) { 737286425Sdim ret = fstat(fd, &st); 738286425Sdim if (ret < 0) { 739286425Sdim status = errno_to_portable(errno); 740286425Sdim } else { 741286425Sdim stat_to_attrib(&st, &a); 742286425Sdim send_attrib(id, &a); 743296417Sdim status = SSH2_FX_OK; 744286425Sdim } 745286425Sdim } 746286425Sdim if (status != SSH2_FX_OK) 747286425Sdim send_status(id, status); 748314564Sdim} 749286425Sdim 750314564Sdimstatic struct timeval * 751286425Sdimattrib_to_tv(const Attrib *a) 752286425Sdim{ 753286425Sdim static struct timeval tv[2]; 754286425Sdim 755286425Sdim tv[0].tv_sec = a->atime; 756341825Sdim tv[0].tv_usec = 0; 757286425Sdim tv[1].tv_sec = a->mtime; 758286425Sdim tv[1].tv_usec = 0; 759314564Sdim return tv; 760286425Sdim} 761286425Sdim 762286425Sdimstatic void 763286425Sdimprocess_setstat(void) 764286425Sdim{ 765286425Sdim Attrib *a; 766286425Sdim u_int32_t id; 767286425Sdim char *name; 768286425Sdim int status = SSH2_FX_OK, ret; 769286425Sdim 770286425Sdim id = get_int(); 771286425Sdim name = get_string(NULL); 772286425Sdim a = get_attrib(); 773286425Sdim debug("request %u: setstat name \"%s\"", id, name); 774286425Sdim if (readonly) { 775286425Sdim status = SSH2_FX_PERMISSION_DENIED; 776286425Sdim a->flags = 0; 777286425Sdim } 778286425Sdim if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 779286425Sdim logit("set \"%s\" size %llu", 780286425Sdim name, (unsigned long long)a->size); 781286425Sdim ret = truncate(name, a->size); 782286425Sdim if (ret == -1) 783286425Sdim status = errno_to_portable(errno); 784286425Sdim } 785286425Sdim if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 786286425Sdim logit("set \"%s\" mode %04o", name, a->perm); 787286425Sdim ret = chmod(name, a->perm & 07777); 788286425Sdim if (ret == -1) 789286425Sdim status = errno_to_portable(errno); 790286425Sdim } 791286425Sdim if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 792286425Sdim char buf[64]; 793286425Sdim time_t t = a->mtime; 794286425Sdim 795286425Sdim strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 796286425Sdim localtime(&t)); 797286425Sdim logit("set \"%s\" modtime %s", name, buf); 798286425Sdim ret = utimes(name, attrib_to_tv(a)); 799286425Sdim if (ret == -1) 800286425Sdim status = errno_to_portable(errno); 801286425Sdim } 802314564Sdim if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 803286425Sdim logit("set \"%s\" owner %lu group %lu", name, 804286425Sdim (u_long)a->uid, (u_long)a->gid); 805286425Sdim ret = chown(name, a->uid, a->gid); 806314564Sdim if (ret == -1) 807286425Sdim status = errno_to_portable(errno); 808286425Sdim } 809286425Sdim send_status(id, status); 810286425Sdim xfree(name); 811286425Sdim} 812286425Sdim 813286425Sdimstatic void 814286425Sdimprocess_fsetstat(void) 815286425Sdim{ 816286425Sdim Attrib *a; 817286425Sdim u_int32_t id; 818286425Sdim int handle, fd, ret; 819286425Sdim int status = SSH2_FX_OK; 820286425Sdim 821286425Sdim id = get_int(); 822286425Sdim handle = get_handle(); 823341825Sdim a = get_attrib(); 824286425Sdim debug("request %u: fsetstat handle %d", id, handle); 825286425Sdim fd = handle_to_fd(handle); 826286425Sdim if (fd < 0) 827286425Sdim status = SSH2_FX_FAILURE; 828286425Sdim else if (readonly) 829286425Sdim status = SSH2_FX_PERMISSION_DENIED; 830286425Sdim else { 831286425Sdim char *name = handle_to_name(handle); 832286425Sdim 833341825Sdim if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { 834286425Sdim logit("set \"%s\" size %llu", 835286425Sdim name, (unsigned long long)a->size); 836286425Sdim ret = ftruncate(fd, a->size); 837286425Sdim if (ret == -1) 838286425Sdim status = errno_to_portable(errno); 839286425Sdim } 840286425Sdim if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { 841286425Sdim logit("set \"%s\" mode %04o", name, a->perm); 842286425Sdim#ifdef HAVE_FCHMOD 843286425Sdim ret = fchmod(fd, a->perm & 07777); 844286425Sdim#else 845286425Sdim ret = chmod(name, a->perm & 07777); 846286425Sdim#endif 847286425Sdim if (ret == -1) 848286425Sdim status = errno_to_portable(errno); 849286425Sdim } 850286425Sdim if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 851286425Sdim char buf[64]; 852286425Sdim time_t t = a->mtime; 853286425Sdim 854286425Sdim strftime(buf, sizeof(buf), "%Y%m%d-%H:%M:%S", 855286425Sdim localtime(&t)); 856286425Sdim logit("set \"%s\" modtime %s", name, buf); 857286425Sdim#ifdef HAVE_FUTIMES 858286425Sdim ret = futimes(fd, attrib_to_tv(a)); 859286425Sdim#else 860286425Sdim ret = utimes(name, attrib_to_tv(a)); 861286425Sdim#endif 862286425Sdim if (ret == -1) 863286425Sdim status = errno_to_portable(errno); 864286425Sdim } 865314564Sdim if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { 866314564Sdim logit("set \"%s\" owner %lu group %lu", name, 867314564Sdim (u_long)a->uid, (u_long)a->gid); 868314564Sdim#ifdef HAVE_FCHOWN 869314564Sdim ret = fchown(fd, a->uid, a->gid); 870314564Sdim#else 871314564Sdim ret = chown(name, a->uid, a->gid); 872314564Sdim#endif 873314564Sdim if (ret == -1) 874286425Sdim status = errno_to_portable(errno); 875286425Sdim } 876286425Sdim } 877286425Sdim send_status(id, status); 878286425Sdim} 879286425Sdim 880286425Sdimstatic void 881286425Sdimprocess_opendir(void) 882286425Sdim{ 883286425Sdim DIR *dirp = NULL; 884286425Sdim char *path; 885286425Sdim int handle, status = SSH2_FX_FAILURE; 886286425Sdim u_int32_t id; 887286425Sdim 888286425Sdim id = get_int(); 889286425Sdim path = get_string(NULL); 890286425Sdim debug3("request %u: opendir", id); 891286425Sdim logit("opendir \"%s\"", path); 892286425Sdim dirp = opendir(path); 893286425Sdim if (dirp == NULL) { 894286425Sdim status = errno_to_portable(errno); 895286425Sdim } else { 896286425Sdim handle = handle_new(HANDLE_DIR, path, 0, dirp); 897286425Sdim if (handle < 0) { 898286425Sdim closedir(dirp); 899286425Sdim } else { 900286425Sdim send_handle(id, handle); 901286425Sdim status = SSH2_FX_OK; 902286425Sdim } 903286425Sdim 904286425Sdim } 905286425Sdim if (status != SSH2_FX_OK) 906286425Sdim send_status(id, status); 907286425Sdim xfree(path); 908286425Sdim} 909286425Sdim 910286425Sdimstatic void 911286425Sdimprocess_readdir(void) 912286425Sdim{ 913286425Sdim DIR *dirp; 914286425Sdim struct dirent *dp; 915286425Sdim char *path; 916286425Sdim int handle; 917286425Sdim u_int32_t id; 918286425Sdim 919286425Sdim id = get_int(); 920286425Sdim handle = get_handle(); 921286425Sdim debug("request %u: readdir \"%s\" (handle %d)", id, 922286425Sdim handle_to_name(handle), handle); 923286425Sdim dirp = handle_to_dir(handle); 924286425Sdim path = handle_to_name(handle); 925314564Sdim if (dirp == NULL || path == NULL) { 926286425Sdim send_status(id, SSH2_FX_FAILURE); 927286425Sdim } else { 928286425Sdim struct stat st; 929314564Sdim char pathname[MAXPATHLEN]; 930286425Sdim Stat *stats; 931286425Sdim int nstats = 10, count = 0, i; 932286425Sdim 933286425Sdim stats = xcalloc(nstats, sizeof(Stat)); 934286425Sdim while ((dp = readdir(dirp)) != NULL) { 935286425Sdim if (count >= nstats) { 936286425Sdim nstats *= 2; 937286425Sdim stats = xrealloc(stats, nstats, sizeof(Stat)); 938286425Sdim } 939286425Sdim/* XXX OVERFLOW ? */ 940286425Sdim snprintf(pathname, sizeof pathname, "%s%s%s", path, 941286425Sdim strcmp(path, "/") ? "/" : "", dp->d_name); 942286425Sdim if (lstat(pathname, &st) < 0) 943286425Sdim continue; 944286425Sdim stat_to_attrib(&st, &(stats[count].attrib)); 945286425Sdim stats[count].name = xstrdup(dp->d_name); 946286425Sdim stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); 947286425Sdim count++; 948286425Sdim /* send up to 100 entries in one message */ 949286425Sdim /* XXX check packet size instead */ 950314564Sdim if (count == 100) 951286425Sdim break; 952286425Sdim } 953286425Sdim if (count > 0) { 954286425Sdim send_names(id, count, stats); 955341825Sdim for (i = 0; i < count; i++) { 956341825Sdim xfree(stats[i].name); 957286425Sdim xfree(stats[i].long_name); 958286425Sdim } 959286425Sdim } else { 960314564Sdim send_status(id, SSH2_FX_EOF); 961286425Sdim } 962286425Sdim xfree(stats); 963286425Sdim } 964286425Sdim} 965286425Sdim 966286425Sdimstatic void 967286425Sdimprocess_remove(void) 968286425Sdim{ 969286425Sdim char *name; 970286425Sdim u_int32_t id; 971286425Sdim int status = SSH2_FX_FAILURE; 972286425Sdim int ret; 973286425Sdim 974286425Sdim id = get_int(); 975286425Sdim name = get_string(NULL); 976286425Sdim debug3("request %u: remove", id); 977286425Sdim logit("remove name \"%s\"", name); 978286425Sdim if (readonly) 979286425Sdim status = SSH2_FX_PERMISSION_DENIED; 980286425Sdim else { 981286425Sdim ret = unlink(name); 982286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 983286425Sdim } 984286425Sdim send_status(id, status); 985286425Sdim xfree(name); 986286425Sdim} 987286425Sdim 988286425Sdimstatic void 989286425Sdimprocess_mkdir(void) 990286425Sdim{ 991286425Sdim Attrib *a; 992286425Sdim u_int32_t id; 993286425Sdim char *name; 994286425Sdim int ret, mode, status = SSH2_FX_FAILURE; 995286425Sdim 996286425Sdim id = get_int(); 997286425Sdim name = get_string(NULL); 998286425Sdim a = get_attrib(); 999341825Sdim mode = (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? 1000286425Sdim a->perm & 07777 : 0777; 1001286425Sdim debug3("request %u: mkdir", id); 1002286425Sdim logit("mkdir name \"%s\" mode 0%o", name, mode); 1003286425Sdim if (readonly) 1004286425Sdim status = SSH2_FX_PERMISSION_DENIED; 1005286425Sdim else { 1006286425Sdim ret = mkdir(name, mode); 1007286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1008286425Sdim } 1009286425Sdim send_status(id, status); 1010341825Sdim xfree(name); 1011286425Sdim} 1012286425Sdim 1013286425Sdimstatic void 1014286425Sdimprocess_rmdir(void) 1015286425Sdim{ 1016286425Sdim u_int32_t id; 1017286425Sdim char *name; 1018286425Sdim int ret, status; 1019286425Sdim 1020286425Sdim id = get_int(); 1021286425Sdim name = get_string(NULL); 1022286425Sdim debug3("request %u: rmdir", id); 1023286425Sdim logit("rmdir name \"%s\"", name); 1024286425Sdim if (readonly) 1025286425Sdim status = SSH2_FX_PERMISSION_DENIED; 1026286425Sdim else { 1027286425Sdim ret = rmdir(name); 1028286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1029286425Sdim } 1030286425Sdim send_status(id, status); 1031286425Sdim xfree(name); 1032286425Sdim} 1033286425Sdim 1034286425Sdimstatic void 1035286425Sdimprocess_realpath(void) 1036286425Sdim{ 1037286425Sdim char resolvedname[MAXPATHLEN]; 1038286425Sdim u_int32_t id; 1039286425Sdim char *path; 1040286425Sdim 1041286425Sdim id = get_int(); 1042286425Sdim path = get_string(NULL); 1043286425Sdim if (path[0] == '\0') { 1044286425Sdim xfree(path); 1045286425Sdim path = xstrdup("."); 1046286425Sdim } 1047341825Sdim debug3("request %u: realpath", id); 1048286425Sdim verbose("realpath \"%s\"", path); 1049286425Sdim if (realpath(path, resolvedname) == NULL) { 1050286425Sdim send_status(id, errno_to_portable(errno)); 1051286425Sdim } else { 1052286425Sdim Stat s; 1053286425Sdim attrib_clear(&s.attrib); 1054286425Sdim s.name = s.long_name = resolvedname; 1055286425Sdim send_names(id, 1, &s); 1056286425Sdim } 1057286425Sdim xfree(path); 1058286425Sdim} 1059286425Sdim 1060286425Sdimstatic void 1061286425Sdimprocess_rename(void) 1062286425Sdim{ 1063286425Sdim u_int32_t id; 1064286425Sdim char *oldpath, *newpath; 1065286425Sdim int status; 1066286425Sdim struct stat sb; 1067286425Sdim 1068286425Sdim id = get_int(); 1069341825Sdim oldpath = get_string(NULL); 1070286425Sdim newpath = get_string(NULL); 1071286425Sdim debug3("request %u: rename", id); 1072286425Sdim logit("rename old \"%s\" new \"%s\"", oldpath, newpath); 1073286425Sdim status = SSH2_FX_FAILURE; 1074286425Sdim if (readonly) 1075341825Sdim status = SSH2_FX_PERMISSION_DENIED; 1076341825Sdim else if (lstat(oldpath, &sb) == -1) 1077286425Sdim status = errno_to_portable(errno); 1078286425Sdim else if (S_ISREG(sb.st_mode)) { 1079286425Sdim /* Race-free rename of regular files */ 1080286425Sdim if (link(oldpath, newpath) == -1) { 1081286425Sdim if (errno == EOPNOTSUPP || errno == ENOSYS 1082341825Sdim#ifdef EXDEV 1083286425Sdim || errno == EXDEV 1084286425Sdim#endif 1085286425Sdim#ifdef LINK_OPNOTSUPP_ERRNO 1086286425Sdim || errno == LINK_OPNOTSUPP_ERRNO 1087286425Sdim#endif 1088341825Sdim ) { 1089286425Sdim struct stat st; 1090286425Sdim 1091286425Sdim /* 1092286425Sdim * fs doesn't support links, so fall back to 1093341825Sdim * stat+rename. This is racy. 1094341825Sdim */ 1095341825Sdim if (stat(newpath, &st) == -1) { 1096286425Sdim if (rename(oldpath, newpath) == -1) 1097286425Sdim status = 1098286425Sdim errno_to_portable(errno); 1099286425Sdim else 1100321369Sdim status = SSH2_FX_OK; 1101286425Sdim } 1102286425Sdim } else { 1103286425Sdim status = errno_to_portable(errno); 1104286425Sdim } 1105286425Sdim } else if (unlink(oldpath) == -1) { 1106286425Sdim status = errno_to_portable(errno); 1107286425Sdim /* clean spare link */ 1108286425Sdim unlink(newpath); 1109286425Sdim } else 1110286425Sdim status = SSH2_FX_OK; 1111286425Sdim } else if (stat(newpath, &sb) == -1) { 1112286425Sdim if (rename(oldpath, newpath) == -1) 1113286425Sdim status = errno_to_portable(errno); 1114286425Sdim else 1115286425Sdim status = SSH2_FX_OK; 1116286425Sdim } 1117286425Sdim send_status(id, status); 1118286425Sdim xfree(oldpath); 1119286425Sdim xfree(newpath); 1120286425Sdim} 1121286425Sdim 1122286425Sdimstatic void 1123286425Sdimprocess_readlink(void) 1124286425Sdim{ 1125286425Sdim u_int32_t id; 1126286425Sdim int len; 1127286425Sdim char buf[MAXPATHLEN]; 1128286425Sdim char *path; 1129286425Sdim 1130296417Sdim id = get_int(); 1131321369Sdim path = get_string(NULL); 1132341825Sdim debug3("request %u: readlink", id); 1133286425Sdim verbose("readlink \"%s\"", path); 1134286425Sdim if ((len = readlink(path, buf, sizeof(buf) - 1)) == -1) 1135286425Sdim send_status(id, errno_to_portable(errno)); 1136286425Sdim else { 1137286425Sdim Stat s; 1138286425Sdim 1139286425Sdim buf[len] = '\0'; 1140286425Sdim attrib_clear(&s.attrib); 1141286425Sdim s.name = s.long_name = buf; 1142286425Sdim send_names(id, 1, &s); 1143286425Sdim } 1144286425Sdim xfree(path); 1145286425Sdim} 1146286425Sdim 1147286425Sdimstatic void 1148286425Sdimprocess_symlink(void) 1149286425Sdim{ 1150286425Sdim u_int32_t id; 1151286425Sdim char *oldpath, *newpath; 1152286425Sdim int ret, status; 1153286425Sdim 1154286425Sdim id = get_int(); 1155286425Sdim oldpath = get_string(NULL); 1156286425Sdim newpath = get_string(NULL); 1157286425Sdim debug3("request %u: symlink", id); 1158286425Sdim logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); 1159286425Sdim /* this will fail if 'newpath' exists */ 1160286425Sdim if (readonly) 1161286425Sdim status = SSH2_FX_PERMISSION_DENIED; 1162286425Sdim else { 1163286425Sdim ret = symlink(oldpath, newpath); 1164286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1165341825Sdim } 1166286425Sdim send_status(id, status); 1167286425Sdim xfree(oldpath); 1168286425Sdim xfree(newpath); 1169286425Sdim} 1170286425Sdim 1171286425Sdimstatic void 1172286425Sdimprocess_extended_posix_rename(u_int32_t id) 1173286425Sdim{ 1174286425Sdim char *oldpath, *newpath; 1175286425Sdim int ret, status; 1176286425Sdim 1177286425Sdim oldpath = get_string(NULL); 1178286425Sdim newpath = get_string(NULL); 1179286425Sdim debug3("request %u: posix-rename", id); 1180286425Sdim logit("posix-rename old \"%s\" new \"%s\"", oldpath, newpath); 1181286425Sdim if (readonly) 1182286425Sdim status = SSH2_FX_PERMISSION_DENIED; 1183286425Sdim else { 1184286425Sdim ret = rename(oldpath, newpath); 1185286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1186286425Sdim } 1187286425Sdim send_status(id, status); 1188286425Sdim xfree(oldpath); 1189286425Sdim xfree(newpath); 1190286425Sdim} 1191286425Sdim 1192286425Sdimstatic void 1193286425Sdimprocess_extended_statvfs(u_int32_t id) 1194286425Sdim{ 1195286425Sdim char *path; 1196286425Sdim struct statvfs st; 1197286425Sdim 1198286425Sdim path = get_string(NULL); 1199286425Sdim debug3("request %u: statfs", id); 1200286425Sdim logit("statfs \"%s\"", path); 1201286425Sdim 1202314564Sdim if (statvfs(path, &st) != 0) 1203286425Sdim send_status(id, errno_to_portable(errno)); 1204286425Sdim else 1205286425Sdim send_statvfs(id, &st); 1206286425Sdim xfree(path); 1207296417Sdim} 1208286425Sdim 1209286425Sdimstatic void 1210286425Sdimprocess_extended_fstatvfs(u_int32_t id) 1211286425Sdim{ 1212286425Sdim int handle, fd; 1213286425Sdim struct statvfs st; 1214286425Sdim 1215286425Sdim handle = get_handle(); 1216286425Sdim debug("request %u: fstatvfs \"%s\" (handle %u)", 1217286425Sdim id, handle_to_name(handle), handle); 1218286425Sdim if ((fd = handle_to_fd(handle)) < 0) { 1219286425Sdim send_status(id, SSH2_FX_FAILURE); 1220286425Sdim return; 1221286425Sdim } 1222286425Sdim if (fstatvfs(fd, &st) != 0) 1223286425Sdim send_status(id, errno_to_portable(errno)); 1224286425Sdim else 1225286425Sdim send_statvfs(id, &st); 1226286425Sdim} 1227286425Sdim 1228286425Sdimstatic void 1229286425Sdimprocess_extended_hardlink(u_int32_t id) 1230286425Sdim{ 1231286425Sdim char *oldpath, *newpath; 1232286425Sdim int ret, status; 1233286425Sdim 1234286425Sdim oldpath = get_string(NULL); 1235286425Sdim newpath = get_string(NULL); 1236286425Sdim debug3("request %u: hardlink", id); 1237286425Sdim logit("hardlink old \"%s\" new \"%s\"", oldpath, newpath); 1238286425Sdim if (readonly) 1239286425Sdim status = SSH2_FX_PERMISSION_DENIED; 1240286425Sdim else { 1241286425Sdim ret = link(oldpath, newpath); 1242286425Sdim status = (ret == -1) ? errno_to_portable(errno) : SSH2_FX_OK; 1243286425Sdim } 1244286425Sdim send_status(id, status); 1245286425Sdim xfree(oldpath); 1246286425Sdim xfree(newpath); 1247286425Sdim} 1248286425Sdim 1249286425Sdimstatic void 1250286425Sdimprocess_extended(void) 1251286425Sdim{ 1252321369Sdim u_int32_t id; 1253321369Sdim char *request; 1254286425Sdim 1255286425Sdim id = get_int(); 1256286425Sdim request = get_string(NULL); 1257286425Sdim if (strcmp(request, "posix-rename@openssh.com") == 0) 1258286425Sdim process_extended_posix_rename(id); 1259327952Sdim else if (strcmp(request, "statvfs@openssh.com") == 0) 1260327952Sdim process_extended_statvfs(id); 1261327952Sdim else if (strcmp(request, "fstatvfs@openssh.com") == 0) 1262286425Sdim process_extended_fstatvfs(id); 1263286425Sdim else if (strcmp(request, "hardlink@openssh.com") == 0) 1264286425Sdim process_extended_hardlink(id); 1265286425Sdim else 1266286425Sdim send_status(id, SSH2_FX_OP_UNSUPPORTED); /* MUST */ 1267286425Sdim xfree(request); 1268286425Sdim} 1269286425Sdim 1270286425Sdim/* stolen from ssh-agent */ 1271286425Sdim 1272286425Sdimstatic void 1273286425Sdimprocess(void) 1274309124Sdim{ 1275309124Sdim u_int msg_len; 1276309124Sdim u_int buf_len; 1277286425Sdim u_int consumed; 1278286425Sdim u_int type; 1279286425Sdim u_char *cp; 1280286425Sdim 1281286425Sdim buf_len = buffer_len(&iqueue); 1282286425Sdim if (buf_len < 5) 1283286425Sdim return; /* Incomplete message. */ 1284286425Sdim cp = buffer_ptr(&iqueue); 1285309124Sdim msg_len = get_u32(cp); 1286286425Sdim if (msg_len > SFTP_MAX_MSG_LENGTH) { 1287286425Sdim error("bad message from %s local user %s", 1288286425Sdim client_addr, pw->pw_name); 1289286425Sdim sftp_server_cleanup_exit(11); 1290286425Sdim } 1291286425Sdim if (buf_len < msg_len + 4) 1292286425Sdim return; 1293286425Sdim buffer_consume(&iqueue, 4); 1294286425Sdim buf_len -= 4; 1295286425Sdim type = buffer_get_char(&iqueue); 1296286425Sdim switch (type) { 1297286425Sdim case SSH2_FXP_INIT: 1298286425Sdim process_init(); 1299286425Sdim break; 1300286425Sdim case SSH2_FXP_OPEN: 1301286425Sdim process_open(); 1302286425Sdim break; 1303286425Sdim case SSH2_FXP_CLOSE: 1304309124Sdim process_close(); 1305286425Sdim break; 1306286425Sdim case SSH2_FXP_READ: 1307286425Sdim process_read(); 1308286425Sdim break; 1309286425Sdim case SSH2_FXP_WRITE: 1310286425Sdim process_write(); 1311314564Sdim break; 1312286425Sdim case SSH2_FXP_LSTAT: 1313286425Sdim process_lstat(); 1314286425Sdim break; 1315286425Sdim case SSH2_FXP_FSTAT: 1316314564Sdim process_fstat(); 1317314564Sdim break; 1318 case SSH2_FXP_SETSTAT: 1319 process_setstat(); 1320 break; 1321 case SSH2_FXP_FSETSTAT: 1322 process_fsetstat(); 1323 break; 1324 case SSH2_FXP_OPENDIR: 1325 process_opendir(); 1326 break; 1327 case SSH2_FXP_READDIR: 1328 process_readdir(); 1329 break; 1330 case SSH2_FXP_REMOVE: 1331 process_remove(); 1332 break; 1333 case SSH2_FXP_MKDIR: 1334 process_mkdir(); 1335 break; 1336 case SSH2_FXP_RMDIR: 1337 process_rmdir(); 1338 break; 1339 case SSH2_FXP_REALPATH: 1340 process_realpath(); 1341 break; 1342 case SSH2_FXP_STAT: 1343 process_stat(); 1344 break; 1345 case SSH2_FXP_RENAME: 1346 process_rename(); 1347 break; 1348 case SSH2_FXP_READLINK: 1349 process_readlink(); 1350 break; 1351 case SSH2_FXP_SYMLINK: 1352 process_symlink(); 1353 break; 1354 case SSH2_FXP_EXTENDED: 1355 process_extended(); 1356 break; 1357 default: 1358 error("Unknown message %d", type); 1359 break; 1360 } 1361 /* discard the remaining bytes from the current packet */ 1362 if (buf_len < buffer_len(&iqueue)) { 1363 error("iqueue grew unexpectedly"); 1364 sftp_server_cleanup_exit(255); 1365 } 1366 consumed = buf_len - buffer_len(&iqueue); 1367 if (msg_len < consumed) { 1368 error("msg_len %d < consumed %d", msg_len, consumed); 1369 sftp_server_cleanup_exit(255); 1370 } 1371 if (msg_len > consumed) 1372 buffer_consume(&iqueue, msg_len - consumed); 1373} 1374 1375/* Cleanup handler that logs active handles upon normal exit */ 1376void 1377sftp_server_cleanup_exit(int i) 1378{ 1379 if (pw != NULL && client_addr != NULL) { 1380 handle_log_exit(); 1381 logit("session closed for local user %s from [%s]", 1382 pw->pw_name, client_addr); 1383 } 1384 _exit(i); 1385} 1386 1387static void 1388sftp_server_usage(void) 1389{ 1390 extern char *__progname; 1391 1392 fprintf(stderr, 1393 "usage: %s [-ehR] [-f log_facility] [-l log_level] [-u umask]\n", 1394 __progname); 1395 exit(1); 1396} 1397 1398int 1399sftp_server_main(int argc, char **argv, struct passwd *user_pw) 1400{ 1401 fd_set *rset, *wset; 1402 int in, out, max, ch, skipargs = 0, log_stderr = 0; 1403 ssize_t len, olen, set_size; 1404 SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 1405 char *cp, buf[4*4096]; 1406 long mask; 1407 1408 extern char *optarg; 1409 extern char *__progname; 1410 1411 __progname = ssh_get_progname(argv[0]); 1412 log_init(__progname, log_level, log_facility, log_stderr); 1413 1414 while (!skipargs && (ch = getopt(argc, argv, "f:l:u:cehR")) != -1) { 1415 switch (ch) { 1416 case 'R': 1417 readonly = 1; 1418 break; 1419 case 'c': 1420 /* 1421 * Ignore all arguments if we are invoked as a 1422 * shell using "sftp-server -c command" 1423 */ 1424 skipargs = 1; 1425 break; 1426 case 'e': 1427 log_stderr = 1; 1428 break; 1429 case 'l': 1430 log_level = log_level_number(optarg); 1431 if (log_level == SYSLOG_LEVEL_NOT_SET) 1432 error("Invalid log level \"%s\"", optarg); 1433 break; 1434 case 'f': 1435 log_facility = log_facility_number(optarg); 1436 if (log_facility == SYSLOG_FACILITY_NOT_SET) 1437 error("Invalid log facility \"%s\"", optarg); 1438 break; 1439 case 'u': 1440 errno = 0; 1441 mask = strtol(optarg, &cp, 8); 1442 if (mask < 0 || mask > 0777 || *cp != '\0' || 1443 cp == optarg || (mask == 0 && errno != 0)) 1444 fatal("Invalid umask \"%s\"", optarg); 1445 (void)umask((mode_t)mask); 1446 break; 1447 case 'h': 1448 default: 1449 sftp_server_usage(); 1450 } 1451 } 1452 1453 log_init(__progname, log_level, log_facility, log_stderr); 1454 1455 if ((cp = getenv("SSH_CONNECTION")) != NULL) { 1456 client_addr = xstrdup(cp); 1457 if ((cp = strchr(client_addr, ' ')) == NULL) { 1458 error("Malformed SSH_CONNECTION variable: \"%s\"", 1459 getenv("SSH_CONNECTION")); 1460 sftp_server_cleanup_exit(255); 1461 } 1462 *cp = '\0'; 1463 } else 1464 client_addr = xstrdup("UNKNOWN"); 1465 1466 pw = pwcopy(user_pw); 1467 1468 logit("session opened for local user %s from [%s]", 1469 pw->pw_name, client_addr); 1470 1471 in = STDIN_FILENO; 1472 out = STDOUT_FILENO; 1473 1474#ifdef HAVE_CYGWIN 1475 setmode(in, O_BINARY); 1476 setmode(out, O_BINARY); 1477#endif 1478 1479 max = 0; 1480 if (in > max) 1481 max = in; 1482 if (out > max) 1483 max = out; 1484 1485 buffer_init(&iqueue); 1486 buffer_init(&oqueue); 1487 1488 set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); 1489 rset = (fd_set *)xmalloc(set_size); 1490 wset = (fd_set *)xmalloc(set_size); 1491 1492 for (;;) { 1493 memset(rset, 0, set_size); 1494 memset(wset, 0, set_size); 1495 1496 /* 1497 * Ensure that we can read a full buffer and handle 1498 * the worst-case length packet it can generate, 1499 * otherwise apply backpressure by stopping reads. 1500 */ 1501 if (buffer_check_alloc(&iqueue, sizeof(buf)) && 1502 buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1503 FD_SET(in, rset); 1504 1505 olen = buffer_len(&oqueue); 1506 if (olen > 0) 1507 FD_SET(out, wset); 1508 1509 if (select(max+1, rset, wset, NULL, NULL) < 0) { 1510 if (errno == EINTR) 1511 continue; 1512 error("select: %s", strerror(errno)); 1513 sftp_server_cleanup_exit(2); 1514 } 1515 1516 /* copy stdin to iqueue */ 1517 if (FD_ISSET(in, rset)) { 1518 len = read(in, buf, sizeof buf); 1519 if (len == 0) { 1520 debug("read eof"); 1521 sftp_server_cleanup_exit(0); 1522 } else if (len < 0) { 1523 error("read: %s", strerror(errno)); 1524 sftp_server_cleanup_exit(1); 1525 } else { 1526 buffer_append(&iqueue, buf, len); 1527 } 1528 } 1529 /* send oqueue to stdout */ 1530 if (FD_ISSET(out, wset)) { 1531 len = write(out, buffer_ptr(&oqueue), olen); 1532 if (len < 0) { 1533 error("write: %s", strerror(errno)); 1534 sftp_server_cleanup_exit(1); 1535 } else { 1536 buffer_consume(&oqueue, len); 1537 } 1538 } 1539 1540 /* 1541 * Process requests from client if we can fit the results 1542 * into the output buffer, otherwise stop processing input 1543 * and let the output queue drain. 1544 */ 1545 if (buffer_check_alloc(&oqueue, SFTP_MAX_MSG_LENGTH)) 1546 process(); 1547 } 1548} 1549