1package sftp 2 3// sftp server counterpart 4 5import ( 6 "encoding" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "strconv" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/pkg/errors" 18) 19 20const ( 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 pktChan chan rxPacket 33 openFiles map[string]*os.File 34 openFilesLock sync.RWMutex 35 handleCount int 36 maxTxPacket uint32 37} 38 39func (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 48func (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 syscall.EBADF 57} 58 59func (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 66type serverRespondablePacket interface { 67 encoding.BinaryUnmarshaler 68 id() uint32 69 respond(svr *Server) error 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. 77func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) { 78 s := &Server{ 79 serverConn: serverConn{ 80 conn: conn{ 81 Reader: rwc, 82 WriteCloser: rwc, 83 }, 84 }, 85 debugStream: ioutil.Discard, 86 pktChan: make(chan rxPacket, sftpServerWorkerCount), 87 openFiles: make(map[string]*os.File), 88 maxTxPacket: 1 << 15, 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. 101type ServerOption func(*Server) error 102 103// WithDebug enables Server debugging output to the supplied io.Writer. 104func 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. 112func ReadOnly() ServerOption { 113 return func(s *Server) error { 114 s.readOnly = true 115 return nil 116 } 117} 118 119type rxPacket struct { 120 pktType fxp 121 pktBytes []byte 122} 123 124// Up to N parallel servers 125func (svr *Server) sftpServerWorker() error { 126 for p := range svr.pktChan { 127 var pkt interface { 128 encoding.BinaryUnmarshaler 129 id() uint32 130 } 131 var readonly = true 132 switch p.pktType { 133 case ssh_FXP_INIT: 134 pkt = &sshFxInitPacket{} 135 case ssh_FXP_LSTAT: 136 pkt = &sshFxpLstatPacket{} 137 case ssh_FXP_OPEN: 138 pkt = &sshFxpOpenPacket{} 139 // readonly handled specially below 140 case ssh_FXP_CLOSE: 141 pkt = &sshFxpClosePacket{} 142 case ssh_FXP_READ: 143 pkt = &sshFxpReadPacket{} 144 case ssh_FXP_WRITE: 145 pkt = &sshFxpWritePacket{} 146 readonly = false 147 case ssh_FXP_FSTAT: 148 pkt = &sshFxpFstatPacket{} 149 case ssh_FXP_SETSTAT: 150 pkt = &sshFxpSetstatPacket{} 151 readonly = false 152 case ssh_FXP_FSETSTAT: 153 pkt = &sshFxpFsetstatPacket{} 154 readonly = false 155 case ssh_FXP_OPENDIR: 156 pkt = &sshFxpOpendirPacket{} 157 case ssh_FXP_READDIR: 158 pkt = &sshFxpReaddirPacket{} 159 case ssh_FXP_REMOVE: 160 pkt = &sshFxpRemovePacket{} 161 readonly = false 162 case ssh_FXP_MKDIR: 163 pkt = &sshFxpMkdirPacket{} 164 readonly = false 165 case ssh_FXP_RMDIR: 166 pkt = &sshFxpRmdirPacket{} 167 readonly = false 168 case ssh_FXP_REALPATH: 169 pkt = &sshFxpRealpathPacket{} 170 case ssh_FXP_STAT: 171 pkt = &sshFxpStatPacket{} 172 case ssh_FXP_RENAME: 173 pkt = &sshFxpRenamePacket{} 174 readonly = false 175 case ssh_FXP_READLINK: 176 pkt = &sshFxpReadlinkPacket{} 177 case ssh_FXP_SYMLINK: 178 pkt = &sshFxpSymlinkPacket{} 179 readonly = false 180 case ssh_FXP_EXTENDED: 181 pkt = &sshFxpExtendedPacket{} 182 default: 183 return errors.Errorf("unhandled packet type: %s", p.pktType) 184 } 185 if err := pkt.UnmarshalBinary(p.pktBytes); err != nil { 186 return err 187 } 188 189 // handle FXP_OPENDIR specially 190 switch pkt := pkt.(type) { 191 case *sshFxpOpenPacket: 192 readonly = pkt.readonly() 193 case *sshFxpExtendedPacket: 194 readonly = pkt.SpecificPacket.readonly() 195 } 196 197 // If server is operating read-only and a write operation is requested, 198 // return permission denied 199 if !readonly && svr.readOnly { 200 if err := svr.sendError(pkt, syscall.EPERM); err != nil { 201 return errors.Wrap(err, "failed to send read only packet response") 202 } 203 continue 204 } 205 206 if err := handlePacket(svr, pkt); err != nil { 207 return err 208 } 209 } 210 return nil 211} 212 213func handlePacket(s *Server, p interface{}) error { 214 switch p := p.(type) { 215 case *sshFxInitPacket: 216 return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil}) 217 case *sshFxpStatPacket: 218 // stat the requested file 219 info, err := os.Stat(p.Path) 220 if err != nil { 221 return s.sendError(p, err) 222 } 223 return s.sendPacket(sshFxpStatResponse{ 224 ID: p.ID, 225 info: info, 226 }) 227 case *sshFxpLstatPacket: 228 // stat the requested file 229 info, err := os.Lstat(p.Path) 230 if err != nil { 231 return s.sendError(p, err) 232 } 233 return s.sendPacket(sshFxpStatResponse{ 234 ID: p.ID, 235 info: info, 236 }) 237 case *sshFxpFstatPacket: 238 f, ok := s.getHandle(p.Handle) 239 if !ok { 240 return s.sendError(p, syscall.EBADF) 241 } 242 243 info, err := f.Stat() 244 if err != nil { 245 return s.sendError(p, err) 246 } 247 248 return s.sendPacket(sshFxpStatResponse{ 249 ID: p.ID, 250 info: info, 251 }) 252 case *sshFxpMkdirPacket: 253 // TODO FIXME: ignore flags field 254 err := os.Mkdir(p.Path, 0755) 255 return s.sendError(p, err) 256 case *sshFxpRmdirPacket: 257 err := os.Remove(p.Path) 258 return s.sendError(p, err) 259 case *sshFxpRemovePacket: 260 err := os.Remove(p.Filename) 261 return s.sendError(p, err) 262 case *sshFxpRenamePacket: 263 err := os.Rename(p.Oldpath, p.Newpath) 264 return s.sendError(p, err) 265 case *sshFxpSymlinkPacket: 266 err := os.Symlink(p.Targetpath, p.Linkpath) 267 return s.sendError(p, err) 268 case *sshFxpClosePacket: 269 return s.sendError(p, s.closeHandle(p.Handle)) 270 case *sshFxpReadlinkPacket: 271 f, err := os.Readlink(p.Path) 272 if err != nil { 273 return s.sendError(p, err) 274 } 275 276 return s.sendPacket(sshFxpNamePacket{ 277 ID: p.ID, 278 NameAttrs: []sshFxpNameAttr{{ 279 Name: f, 280 LongName: f, 281 Attrs: emptyFileStat, 282 }}, 283 }) 284 285 case *sshFxpRealpathPacket: 286 f, err := filepath.Abs(p.Path) 287 if err != nil { 288 return s.sendError(p, err) 289 } 290 f = filepath.Clean(f) 291 return s.sendPacket(sshFxpNamePacket{ 292 ID: p.ID, 293 NameAttrs: []sshFxpNameAttr{{ 294 Name: f, 295 LongName: f, 296 Attrs: emptyFileStat, 297 }}, 298 }) 299 case *sshFxpOpendirPacket: 300 return sshFxpOpenPacket{ 301 ID: p.ID, 302 Path: p.Path, 303 Pflags: ssh_FXF_READ, 304 }.respond(s) 305 case *sshFxpReadPacket: 306 f, ok := s.getHandle(p.Handle) 307 if !ok { 308 return s.sendError(p, syscall.EBADF) 309 } 310 311 data := make([]byte, clamp(p.Len, s.maxTxPacket)) 312 n, err := f.ReadAt(data, int64(p.Offset)) 313 if err != nil && (err != io.EOF || n == 0) { 314 return s.sendError(p, err) 315 } 316 return s.sendPacket(sshFxpDataPacket{ 317 ID: p.ID, 318 Length: uint32(n), 319 Data: data[:n], 320 }) 321 case *sshFxpWritePacket: 322 f, ok := s.getHandle(p.Handle) 323 if !ok { 324 return s.sendError(p, syscall.EBADF) 325 } 326 327 _, err := f.WriteAt(p.Data, int64(p.Offset)) 328 return s.sendError(p, err) 329 case serverRespondablePacket: 330 err := p.respond(s) 331 return errors.Wrap(err, "pkt.respond failed") 332 default: 333 return errors.Errorf("unexpected packet type %T", p) 334 } 335} 336 337// Serve serves SFTP connections until the streams stop or the SFTP subsystem 338// is stopped. 339func (svr *Server) Serve() error { 340 var wg sync.WaitGroup 341 wg.Add(sftpServerWorkerCount) 342 for i := 0; i < sftpServerWorkerCount; i++ { 343 go func() { 344 defer wg.Done() 345 if err := svr.sftpServerWorker(); err != nil { 346 svr.conn.Close() // shuts down recvPacket 347 } 348 }() 349 } 350 351 var err error 352 var pktType uint8 353 var pktBytes []byte 354 for { 355 pktType, pktBytes, err = svr.recvPacket() 356 if err != nil { 357 break 358 } 359 svr.pktChan <- rxPacket{fxp(pktType), pktBytes} 360 } 361 362 close(svr.pktChan) // shuts down sftpServerWorkers 363 wg.Wait() // wait for all workers to exit 364 365 // close any still-open files 366 for handle, file := range svr.openFiles { 367 fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name()) 368 file.Close() 369 } 370 return err // error from recvPacket 371} 372 373type id interface { 374 id() uint32 375} 376 377// The init packet has no ID, so we just return a zero-value ID 378func (p sshFxInitPacket) id() uint32 { return 0 } 379 380type sshFxpStatResponse struct { 381 ID uint32 382 info os.FileInfo 383} 384 385func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) { 386 b := []byte{ssh_FXP_ATTRS} 387 b = marshalUint32(b, p.ID) 388 b = marshalFileInfo(b, p.info) 389 return b, nil 390} 391 392var emptyFileStat = []interface{}{uint32(0)} 393 394func (p sshFxpOpenPacket) readonly() bool { 395 return !p.hasPflags(ssh_FXF_WRITE) 396} 397 398func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool { 399 for _, f := range flags { 400 if p.Pflags&f == 0 { 401 return false 402 } 403 } 404 return true 405} 406 407func (p sshFxpOpenPacket) respond(svr *Server) error { 408 var osFlags int 409 if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) { 410 osFlags |= os.O_RDWR 411 } else if p.hasPflags(ssh_FXF_WRITE) { 412 osFlags |= os.O_WRONLY 413 } else if p.hasPflags(ssh_FXF_READ) { 414 osFlags |= os.O_RDONLY 415 } else { 416 // how are they opening? 417 return svr.sendError(p, syscall.EINVAL) 418 } 419 420 if p.hasPflags(ssh_FXF_APPEND) { 421 osFlags |= os.O_APPEND 422 } 423 if p.hasPflags(ssh_FXF_CREAT) { 424 osFlags |= os.O_CREATE 425 } 426 if p.hasPflags(ssh_FXF_TRUNC) { 427 osFlags |= os.O_TRUNC 428 } 429 if p.hasPflags(ssh_FXF_EXCL) { 430 osFlags |= os.O_EXCL 431 } 432 433 f, err := os.OpenFile(p.Path, osFlags, 0644) 434 if err != nil { 435 return svr.sendError(p, err) 436 } 437 438 handle := svr.nextHandle(f) 439 return svr.sendPacket(sshFxpHandlePacket{p.ID, handle}) 440} 441 442func (p sshFxpReaddirPacket) respond(svr *Server) error { 443 f, ok := svr.getHandle(p.Handle) 444 if !ok { 445 return svr.sendError(p, syscall.EBADF) 446 } 447 448 dirname := f.Name() 449 dirents, err := f.Readdir(128) 450 if err != nil { 451 return svr.sendError(p, err) 452 } 453 454 ret := sshFxpNamePacket{ID: p.ID} 455 for _, dirent := range dirents { 456 ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{ 457 Name: dirent.Name(), 458 LongName: runLs(dirname, dirent), 459 Attrs: []interface{}{dirent}, 460 }) 461 } 462 return svr.sendPacket(ret) 463} 464 465func (p sshFxpSetstatPacket) respond(svr *Server) error { 466 // additional unmarshalling is required for each possibility here 467 b := p.Attrs.([]byte) 468 var err error 469 470 debug("setstat name \"%s\"", p.Path) 471 if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 { 472 var size uint64 473 if size, b, err = unmarshalUint64Safe(b); err == nil { 474 err = os.Truncate(p.Path, int64(size)) 475 } 476 } 477 if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 { 478 var mode uint32 479 if mode, b, err = unmarshalUint32Safe(b); err == nil { 480 err = os.Chmod(p.Path, os.FileMode(mode)) 481 } 482 } 483 if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 { 484 var atime uint32 485 var mtime uint32 486 if atime, b, err = unmarshalUint32Safe(b); err != nil { 487 } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { 488 } else { 489 atimeT := time.Unix(int64(atime), 0) 490 mtimeT := time.Unix(int64(mtime), 0) 491 err = os.Chtimes(p.Path, atimeT, mtimeT) 492 } 493 } 494 if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 { 495 var uid uint32 496 var gid uint32 497 if uid, b, err = unmarshalUint32Safe(b); err != nil { 498 } else if gid, b, err = unmarshalUint32Safe(b); err != nil { 499 } else { 500 err = os.Chown(p.Path, int(uid), int(gid)) 501 } 502 } 503 504 return svr.sendError(p, err) 505} 506 507func (p sshFxpFsetstatPacket) respond(svr *Server) error { 508 f, ok := svr.getHandle(p.Handle) 509 if !ok { 510 return svr.sendError(p, syscall.EBADF) 511 } 512 513 // additional unmarshalling is required for each possibility here 514 b := p.Attrs.([]byte) 515 var err error 516 517 debug("fsetstat name \"%s\"", f.Name()) 518 if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 { 519 var size uint64 520 if size, b, err = unmarshalUint64Safe(b); err == nil { 521 err = f.Truncate(int64(size)) 522 } 523 } 524 if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 { 525 var mode uint32 526 if mode, b, err = unmarshalUint32Safe(b); err == nil { 527 err = f.Chmod(os.FileMode(mode)) 528 } 529 } 530 if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 { 531 var atime uint32 532 var mtime uint32 533 if atime, b, err = unmarshalUint32Safe(b); err != nil { 534 } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { 535 } else { 536 atimeT := time.Unix(int64(atime), 0) 537 mtimeT := time.Unix(int64(mtime), 0) 538 err = os.Chtimes(f.Name(), atimeT, mtimeT) 539 } 540 } 541 if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 { 542 var uid uint32 543 var gid uint32 544 if uid, b, err = unmarshalUint32Safe(b); err != nil { 545 } else if gid, b, err = unmarshalUint32Safe(b); err != nil { 546 } else { 547 err = f.Chown(int(uid), int(gid)) 548 } 549 } 550 551 return svr.sendError(p, err) 552} 553 554// translateErrno translates a syscall error number to a SFTP error code. 555func translateErrno(errno syscall.Errno) uint32 { 556 switch errno { 557 case 0: 558 return ssh_FX_OK 559 case syscall.ENOENT: 560 return ssh_FX_NO_SUCH_FILE 561 case syscall.EPERM: 562 return ssh_FX_PERMISSION_DENIED 563 } 564 565 return ssh_FX_FAILURE 566} 567 568func statusFromError(p id, err error) sshFxpStatusPacket { 569 ret := sshFxpStatusPacket{ 570 ID: p.id(), 571 StatusError: StatusError{ 572 // ssh_FX_OK = 0 573 // ssh_FX_EOF = 1 574 // ssh_FX_NO_SUCH_FILE = 2 ENOENT 575 // ssh_FX_PERMISSION_DENIED = 3 576 // ssh_FX_FAILURE = 4 577 // ssh_FX_BAD_MESSAGE = 5 578 // ssh_FX_NO_CONNECTION = 6 579 // ssh_FX_CONNECTION_LOST = 7 580 // ssh_FX_OP_UNSUPPORTED = 8 581 Code: ssh_FX_OK, 582 }, 583 } 584 if err != nil { 585 debug("statusFromError: error is %T %#v", err, err) 586 ret.StatusError.Code = ssh_FX_FAILURE 587 ret.StatusError.msg = err.Error() 588 if err == io.EOF { 589 ret.StatusError.Code = ssh_FX_EOF 590 } else if errno, ok := err.(syscall.Errno); ok { 591 ret.StatusError.Code = translateErrno(errno) 592 } else if pathError, ok := err.(*os.PathError); ok { 593 debug("statusFromError: error is %T %#v", pathError.Err, pathError.Err) 594 if errno, ok := pathError.Err.(syscall.Errno); ok { 595 ret.StatusError.Code = translateErrno(errno) 596 } 597 } 598 } 599 return ret 600} 601 602func clamp(v, max uint32) uint32 { 603 if v > max { 604 return max 605 } 606 return v 607} 608