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