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