github.com/pkg/sftp@v1.13.6/server.go (about) 1 package sftp 2 3 // sftp server counterpart 4 5 import ( 6 "encoding" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strconv" 14 "sync" 15 "syscall" 16 "time" 17 ) 18 19 const ( 20 // SftpServerWorkerCount defines the number of workers for the SFTP server 21 SftpServerWorkerCount = 8 22 ) 23 24 // Server is an SSH File Transfer Protocol (sftp) server. 25 // This is intended to provide the sftp subsystem to an ssh server daemon. 26 // This implementation currently supports most of sftp server protocol version 3, 27 // as specified at https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt 28 type Server struct { 29 *serverConn 30 debugStream io.Writer 31 readOnly bool 32 pktMgr *packetManager 33 openFiles map[string]*os.File 34 openFilesLock sync.RWMutex 35 handleCount int 36 workDir string 37 } 38 39 func (svr *Server) nextHandle(f *os.File) string { 40 svr.openFilesLock.Lock() 41 defer svr.openFilesLock.Unlock() 42 svr.handleCount++ 43 handle := strconv.Itoa(svr.handleCount) 44 svr.openFiles[handle] = f 45 return handle 46 } 47 48 func (svr *Server) closeHandle(handle string) error { 49 svr.openFilesLock.Lock() 50 defer svr.openFilesLock.Unlock() 51 if f, ok := svr.openFiles[handle]; ok { 52 delete(svr.openFiles, handle) 53 return f.Close() 54 } 55 56 return EBADF 57 } 58 59 func (svr *Server) getHandle(handle string) (*os.File, bool) { 60 svr.openFilesLock.RLock() 61 defer svr.openFilesLock.RUnlock() 62 f, ok := svr.openFiles[handle] 63 return f, ok 64 } 65 66 type serverRespondablePacket interface { 67 encoding.BinaryUnmarshaler 68 id() uint32 69 respond(svr *Server) responsePacket 70 } 71 72 // NewServer creates a new Server instance around the provided streams, serving 73 // content from the root of the filesystem. Optionally, ServerOption 74 // functions may be specified to further configure the Server. 75 // 76 // A subsequent call to Serve() is required to begin serving files over SFTP. 77 func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) { 78 svrConn := &serverConn{ 79 conn: conn{ 80 Reader: rwc, 81 WriteCloser: rwc, 82 }, 83 } 84 s := &Server{ 85 serverConn: svrConn, 86 debugStream: ioutil.Discard, 87 pktMgr: newPktMgr(svrConn), 88 openFiles: make(map[string]*os.File), 89 } 90 91 for _, o := range options { 92 if err := o(s); err != nil { 93 return nil, err 94 } 95 } 96 97 return s, nil 98 } 99 100 // A ServerOption is a function which applies configuration to a Server. 101 type ServerOption func(*Server) error 102 103 // WithDebug enables Server debugging output to the supplied io.Writer. 104 func WithDebug(w io.Writer) ServerOption { 105 return func(s *Server) error { 106 s.debugStream = w 107 return nil 108 } 109 } 110 111 // ReadOnly configures a Server to serve files in read-only mode. 112 func ReadOnly() ServerOption { 113 return func(s *Server) error { 114 s.readOnly = true 115 return nil 116 } 117 } 118 119 // WithAllocator enable the allocator. 120 // After processing a packet we keep in memory the allocated slices 121 // and we reuse them for new packets. 122 // The allocator is experimental 123 func WithAllocator() ServerOption { 124 return func(s *Server) error { 125 alloc := newAllocator() 126 s.pktMgr.alloc = alloc 127 s.conn.alloc = alloc 128 return nil 129 } 130 } 131 132 // WithServerWorkingDirectory sets a working directory to use as base 133 // for relative paths. 134 // If unset the default is current working directory (os.Getwd). 135 func WithServerWorkingDirectory(workDir string) ServerOption { 136 return func(s *Server) error { 137 s.workDir = cleanPath(workDir) 138 return nil 139 } 140 } 141 142 type rxPacket struct { 143 pktType fxp 144 pktBytes []byte 145 } 146 147 // Up to N parallel servers 148 func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error { 149 for pkt := range pktChan { 150 // readonly checks 151 readonly := true 152 switch pkt := pkt.requestPacket.(type) { 153 case notReadOnly: 154 readonly = false 155 case *sshFxpOpenPacket: 156 readonly = pkt.readonly() 157 case *sshFxpExtendedPacket: 158 readonly = pkt.readonly() 159 } 160 161 // If server is operating read-only and a write operation is requested, 162 // return permission denied 163 if !readonly && svr.readOnly { 164 svr.pktMgr.readyPacket( 165 svr.pktMgr.newOrderedResponse(statusFromError(pkt.id(), syscall.EPERM), pkt.orderID()), 166 ) 167 continue 168 } 169 170 if err := handlePacket(svr, pkt); err != nil { 171 return err 172 } 173 } 174 return nil 175 } 176 177 func handlePacket(s *Server, p orderedRequest) error { 178 var rpkt responsePacket 179 orderID := p.orderID() 180 switch p := p.requestPacket.(type) { 181 case *sshFxInitPacket: 182 rpkt = &sshFxVersionPacket{ 183 Version: sftpProtocolVersion, 184 Extensions: sftpExtensions, 185 } 186 case *sshFxpStatPacket: 187 // stat the requested file 188 info, err := os.Stat(s.toLocalPath(p.Path)) 189 rpkt = &sshFxpStatResponse{ 190 ID: p.ID, 191 info: info, 192 } 193 if err != nil { 194 rpkt = statusFromError(p.ID, err) 195 } 196 case *sshFxpLstatPacket: 197 // stat the requested file 198 info, err := os.Lstat(s.toLocalPath(p.Path)) 199 rpkt = &sshFxpStatResponse{ 200 ID: p.ID, 201 info: info, 202 } 203 if err != nil { 204 rpkt = statusFromError(p.ID, err) 205 } 206 case *sshFxpFstatPacket: 207 f, ok := s.getHandle(p.Handle) 208 var err error = EBADF 209 var info os.FileInfo 210 if ok { 211 info, err = f.Stat() 212 rpkt = &sshFxpStatResponse{ 213 ID: p.ID, 214 info: info, 215 } 216 } 217 if err != nil { 218 rpkt = statusFromError(p.ID, err) 219 } 220 case *sshFxpMkdirPacket: 221 // TODO FIXME: ignore flags field 222 err := os.Mkdir(s.toLocalPath(p.Path), 0o755) 223 rpkt = statusFromError(p.ID, err) 224 case *sshFxpRmdirPacket: 225 err := os.Remove(s.toLocalPath(p.Path)) 226 rpkt = statusFromError(p.ID, err) 227 case *sshFxpRemovePacket: 228 err := os.Remove(s.toLocalPath(p.Filename)) 229 rpkt = statusFromError(p.ID, err) 230 case *sshFxpRenamePacket: 231 err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath)) 232 rpkt = statusFromError(p.ID, err) 233 case *sshFxpSymlinkPacket: 234 err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath)) 235 rpkt = statusFromError(p.ID, err) 236 case *sshFxpClosePacket: 237 rpkt = statusFromError(p.ID, s.closeHandle(p.Handle)) 238 case *sshFxpReadlinkPacket: 239 f, err := os.Readlink(s.toLocalPath(p.Path)) 240 rpkt = &sshFxpNamePacket{ 241 ID: p.ID, 242 NameAttrs: []*sshFxpNameAttr{ 243 { 244 Name: f, 245 LongName: f, 246 Attrs: emptyFileStat, 247 }, 248 }, 249 } 250 if err != nil { 251 rpkt = statusFromError(p.ID, err) 252 } 253 case *sshFxpRealpathPacket: 254 f, err := filepath.Abs(s.toLocalPath(p.Path)) 255 f = cleanPath(f) 256 rpkt = &sshFxpNamePacket{ 257 ID: p.ID, 258 NameAttrs: []*sshFxpNameAttr{ 259 { 260 Name: f, 261 LongName: f, 262 Attrs: emptyFileStat, 263 }, 264 }, 265 } 266 if err != nil { 267 rpkt = statusFromError(p.ID, err) 268 } 269 case *sshFxpOpendirPacket: 270 lp := s.toLocalPath(p.Path) 271 272 if stat, err := os.Stat(lp); err != nil { 273 rpkt = statusFromError(p.ID, err) 274 } else if !stat.IsDir() { 275 rpkt = statusFromError(p.ID, &os.PathError{ 276 Path: lp, Err: syscall.ENOTDIR, 277 }) 278 } else { 279 rpkt = (&sshFxpOpenPacket{ 280 ID: p.ID, 281 Path: p.Path, 282 Pflags: sshFxfRead, 283 }).respond(s) 284 } 285 case *sshFxpReadPacket: 286 var err error = EBADF 287 f, ok := s.getHandle(p.Handle) 288 if ok { 289 err = nil 290 data := p.getDataSlice(s.pktMgr.alloc, orderID) 291 n, _err := f.ReadAt(data, int64(p.Offset)) 292 if _err != nil && (_err != io.EOF || n == 0) { 293 err = _err 294 } 295 rpkt = &sshFxpDataPacket{ 296 ID: p.ID, 297 Length: uint32(n), 298 Data: data[:n], 299 // do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations 300 } 301 } 302 if err != nil { 303 rpkt = statusFromError(p.ID, err) 304 } 305 306 case *sshFxpWritePacket: 307 f, ok := s.getHandle(p.Handle) 308 var err error = EBADF 309 if ok { 310 _, err = f.WriteAt(p.Data, int64(p.Offset)) 311 } 312 rpkt = statusFromError(p.ID, err) 313 case *sshFxpExtendedPacket: 314 if p.SpecificPacket == nil { 315 rpkt = statusFromError(p.ID, ErrSSHFxOpUnsupported) 316 } else { 317 rpkt = p.respond(s) 318 } 319 case serverRespondablePacket: 320 rpkt = p.respond(s) 321 default: 322 return fmt.Errorf("unexpected packet type %T", p) 323 } 324 325 s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID)) 326 return nil 327 } 328 329 // Serve serves SFTP connections until the streams stop or the SFTP subsystem 330 // is stopped. It returns nil if the server exits cleanly. 331 func (svr *Server) Serve() error { 332 defer func() { 333 if svr.pktMgr.alloc != nil { 334 svr.pktMgr.alloc.Free() 335 } 336 }() 337 var wg sync.WaitGroup 338 runWorker := func(ch chan orderedRequest) { 339 wg.Add(1) 340 go func() { 341 defer wg.Done() 342 if err := svr.sftpServerWorker(ch); err != nil { 343 svr.conn.Close() // shuts down recvPacket 344 } 345 }() 346 } 347 pktChan := svr.pktMgr.workerChan(runWorker) 348 349 var err error 350 var pkt requestPacket 351 var pktType uint8 352 var pktBytes []byte 353 for { 354 pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID()) 355 if err != nil { 356 // Check whether the connection terminated cleanly in-between packets. 357 if err == io.EOF { 358 err = nil 359 } 360 // we don't care about releasing allocated pages here, the server will quit and the allocator freed 361 break 362 } 363 364 pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes}) 365 if err != nil { 366 switch { 367 case errors.Is(err, errUnknownExtendedPacket): 368 //if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil { 369 // debug("failed to send err packet: %v", err) 370 // svr.conn.Close() // shuts down recvPacket 371 // break 372 //} 373 default: 374 debug("makePacket err: %v", err) 375 svr.conn.Close() // shuts down recvPacket 376 break 377 } 378 } 379 380 pktChan <- svr.pktMgr.newOrderedRequest(pkt) 381 } 382 383 close(pktChan) // shuts down sftpServerWorkers 384 wg.Wait() // wait for all workers to exit 385 386 // close any still-open files 387 for handle, file := range svr.openFiles { 388 fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name()) 389 file.Close() 390 } 391 return err // error from recvPacket 392 } 393 394 type ider interface { 395 id() uint32 396 } 397 398 // The init packet has no ID, so we just return a zero-value ID 399 func (p *sshFxInitPacket) id() uint32 { return 0 } 400 401 type sshFxpStatResponse struct { 402 ID uint32 403 info os.FileInfo 404 } 405 406 func (p *sshFxpStatResponse) marshalPacket() ([]byte, []byte, error) { 407 l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id) 408 409 b := make([]byte, 4, l) 410 b = append(b, sshFxpAttrs) 411 b = marshalUint32(b, p.ID) 412 413 var payload []byte 414 payload = marshalFileInfo(payload, p.info) 415 416 return b, payload, nil 417 } 418 419 func (p *sshFxpStatResponse) MarshalBinary() ([]byte, error) { 420 header, payload, err := p.marshalPacket() 421 return append(header, payload...), err 422 } 423 424 var emptyFileStat = []interface{}{uint32(0)} 425 426 func (p *sshFxpOpenPacket) readonly() bool { 427 return !p.hasPflags(sshFxfWrite) 428 } 429 430 func (p *sshFxpOpenPacket) hasPflags(flags ...uint32) bool { 431 for _, f := range flags { 432 if p.Pflags&f == 0 { 433 return false 434 } 435 } 436 return true 437 } 438 439 func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { 440 var osFlags int 441 if p.hasPflags(sshFxfRead, sshFxfWrite) { 442 osFlags |= os.O_RDWR 443 } else if p.hasPflags(sshFxfWrite) { 444 osFlags |= os.O_WRONLY 445 } else if p.hasPflags(sshFxfRead) { 446 osFlags |= os.O_RDONLY 447 } else { 448 // how are they opening? 449 return statusFromError(p.ID, syscall.EINVAL) 450 } 451 452 // Don't use O_APPEND flag as it conflicts with WriteAt. 453 // The sshFxfAppend flag is a no-op here as the client sends the offsets. 454 455 if p.hasPflags(sshFxfCreat) { 456 osFlags |= os.O_CREATE 457 } 458 if p.hasPflags(sshFxfTrunc) { 459 osFlags |= os.O_TRUNC 460 } 461 if p.hasPflags(sshFxfExcl) { 462 osFlags |= os.O_EXCL 463 } 464 465 f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644) 466 if err != nil { 467 return statusFromError(p.ID, err) 468 } 469 470 handle := svr.nextHandle(f) 471 return &sshFxpHandlePacket{ID: p.ID, Handle: handle} 472 } 473 474 func (p *sshFxpReaddirPacket) respond(svr *Server) responsePacket { 475 f, ok := svr.getHandle(p.Handle) 476 if !ok { 477 return statusFromError(p.ID, EBADF) 478 } 479 480 dirents, err := f.Readdir(128) 481 if err != nil { 482 return statusFromError(p.ID, err) 483 } 484 485 idLookup := osIDLookup{} 486 487 ret := &sshFxpNamePacket{ID: p.ID} 488 for _, dirent := range dirents { 489 ret.NameAttrs = append(ret.NameAttrs, &sshFxpNameAttr{ 490 Name: dirent.Name(), 491 LongName: runLs(idLookup, dirent), 492 Attrs: []interface{}{dirent}, 493 }) 494 } 495 return ret 496 } 497 498 func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket { 499 // additional unmarshalling is required for each possibility here 500 b := p.Attrs.([]byte) 501 var err error 502 503 p.Path = svr.toLocalPath(p.Path) 504 505 debug("setstat name \"%s\"", p.Path) 506 if (p.Flags & sshFileXferAttrSize) != 0 { 507 var size uint64 508 if size, b, err = unmarshalUint64Safe(b); err == nil { 509 err = os.Truncate(p.Path, int64(size)) 510 } 511 } 512 if (p.Flags & sshFileXferAttrPermissions) != 0 { 513 var mode uint32 514 if mode, b, err = unmarshalUint32Safe(b); err == nil { 515 err = os.Chmod(p.Path, os.FileMode(mode)) 516 } 517 } 518 if (p.Flags & sshFileXferAttrACmodTime) != 0 { 519 var atime uint32 520 var mtime uint32 521 if atime, b, err = unmarshalUint32Safe(b); err != nil { 522 } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { 523 } else { 524 atimeT := time.Unix(int64(atime), 0) 525 mtimeT := time.Unix(int64(mtime), 0) 526 err = os.Chtimes(p.Path, atimeT, mtimeT) 527 } 528 } 529 if (p.Flags & sshFileXferAttrUIDGID) != 0 { 530 var uid uint32 531 var gid uint32 532 if uid, b, err = unmarshalUint32Safe(b); err != nil { 533 } else if gid, _, err = unmarshalUint32Safe(b); err != nil { 534 } else { 535 err = os.Chown(p.Path, int(uid), int(gid)) 536 } 537 } 538 539 return statusFromError(p.ID, err) 540 } 541 542 func (p *sshFxpFsetstatPacket) respond(svr *Server) responsePacket { 543 f, ok := svr.getHandle(p.Handle) 544 if !ok { 545 return statusFromError(p.ID, EBADF) 546 } 547 548 // additional unmarshalling is required for each possibility here 549 b := p.Attrs.([]byte) 550 var err error 551 552 debug("fsetstat name \"%s\"", f.Name()) 553 if (p.Flags & sshFileXferAttrSize) != 0 { 554 var size uint64 555 if size, b, err = unmarshalUint64Safe(b); err == nil { 556 err = f.Truncate(int64(size)) 557 } 558 } 559 if (p.Flags & sshFileXferAttrPermissions) != 0 { 560 var mode uint32 561 if mode, b, err = unmarshalUint32Safe(b); err == nil { 562 err = f.Chmod(os.FileMode(mode)) 563 } 564 } 565 if (p.Flags & sshFileXferAttrACmodTime) != 0 { 566 var atime uint32 567 var mtime uint32 568 if atime, b, err = unmarshalUint32Safe(b); err != nil { 569 } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { 570 } else { 571 atimeT := time.Unix(int64(atime), 0) 572 mtimeT := time.Unix(int64(mtime), 0) 573 err = os.Chtimes(f.Name(), atimeT, mtimeT) 574 } 575 } 576 if (p.Flags & sshFileXferAttrUIDGID) != 0 { 577 var uid uint32 578 var gid uint32 579 if uid, b, err = unmarshalUint32Safe(b); err != nil { 580 } else if gid, _, err = unmarshalUint32Safe(b); err != nil { 581 } else { 582 err = f.Chown(int(uid), int(gid)) 583 } 584 } 585 586 return statusFromError(p.ID, err) 587 } 588 589 func statusFromError(id uint32, err error) *sshFxpStatusPacket { 590 ret := &sshFxpStatusPacket{ 591 ID: id, 592 StatusError: StatusError{ 593 // sshFXOk = 0 594 // sshFXEOF = 1 595 // sshFXNoSuchFile = 2 ENOENT 596 // sshFXPermissionDenied = 3 597 // sshFXFailure = 4 598 // sshFXBadMessage = 5 599 // sshFXNoConnection = 6 600 // sshFXConnectionLost = 7 601 // sshFXOPUnsupported = 8 602 Code: sshFxOk, 603 }, 604 } 605 if err == nil { 606 return ret 607 } 608 609 debug("statusFromError: error is %T %#v", err, err) 610 ret.StatusError.Code = sshFxFailure 611 ret.StatusError.msg = err.Error() 612 613 if os.IsNotExist(err) { 614 ret.StatusError.Code = sshFxNoSuchFile 615 return ret 616 } 617 if code, ok := translateSyscallError(err); ok { 618 ret.StatusError.Code = code 619 return ret 620 } 621 622 if errors.Is(err, io.EOF) { 623 ret.StatusError.Code = sshFxEOF 624 return ret 625 } 626 627 var e fxerr 628 if errors.As(err, &e) { 629 ret.StatusError.Code = uint32(e) 630 return ret 631 } 632 633 return ret 634 }