func writeAttr(c ssh.Channel, id uint32, a *Attr, e error) error { if e != nil { return writeErr(c, id, e) } var l binp.Len o := binp.Out().LenB32(&l).LenStart(&l).Byte(ssh_FXP_ATTRS).B32(id).B32(a.Flags) if a.Flags&ssh_FILEXFER_ATTR_SIZE != 0 { o = o.B64(a.Size) } if a.Flags&ssh_FILEXFER_ATTR_UIDGID != 0 { o = o.B32(a.Uid).B32(a.Gid) } if a.Flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 { o = o.B32(a.Mode) } if a.Flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 { o = o.B32(a.ATime).B32(a.MTime) } if a.Flags&ssh_FILEXFER_ATTR_EXTENDED != 0 { count := uint32(len(a.Extended) / 2) o = o.B32(count) for _, s := range a.Extended { o = o.B32String(s) } } o.LenDone(&l) return wrc(c, o.Out()) }
func writeNameOnly(c ssh.Channel, id uint32, path string, e error) error { if e != nil { return writeErr(c, id, e) } var l binp.Len o := binp.Out().LenB32(&l).LenStart(&l).Byte(ssh_FXP_NAME).B32(id).B32(1) o.B32String(path).B32String(path).B32(0) o.LenDone(&l) return wrc(c, o.Out()) }
// ServeChannel serves a ssh.Channel with the given FileSystem. func ServeChannel(c ssh.Channel, fs FileSystem) error { defer c.Close() var h handles h.init() defer h.closeAll() brd := bufio.NewReader(c) var e error var plen int var op byte var bs []byte var id uint32 for { if e != nil { debug("Sending errror", e) e = writeErr(c, id, e) if e != nil { return e } } discard(brd, plen) plen, op, e = readPacketHeader(brd) if e != nil { return e } plen-- debugf("CR op=%v data len=%d\n", ssh_fxp(op), plen) if plen < 2 { return errors.New("Packet too short") } // Feeding too large values to peek is ok, it just errors. bs, e = brd.Peek(plen) if e != nil { return e } debugf("Data %X\n", bs) p := binp.NewParser(bs) switch op { case ssh_FXP_INIT: e = wrc(c, initReply) case ssh_FXP_OPEN: var path string var flags uint32 var a Attr e = parseAttr(p.B32(&id).B32String(&path).B32(&flags), &a).End() if e != nil { return e } if h.nfiles() >= maxFiles { e = etoomany continue } var f File f, e = fs.OpenFile(path, flags, &a) if e != nil { continue } e = writeHandle(c, id, h.newFile(f)) case ssh_FXP_CLOSE: var handle string e = p.B32(&id).B32String(&handle).End() if e != nil { return e } h.closeHandle(handle) e = writeErr(c, id, nil) case ssh_FXP_READ: var handle string var offset uint64 var length uint32 var n int e = p.B32(&id).B32String(&handle).B64(&offset).B32(&length).End() if e != nil { return e } f := h.getFile(handle) if f == nil { return einvhandle } if length > 64*1024 { length = 64 * 1024 } bs := bytepool.Alloc(int(length)) n, e = f.ReadAt(bs, int64(offset)) // Handle go readers that return io.EOF and bytes at the same time. if e == io.EOF && n > 0 { e = nil } if e != nil { bytepool.Free(bs) continue } bs = bs[0:n] e = wrc(c, binp.Out().B32(1+4+4+uint32(len(bs))).Byte(ssh_FXP_DATA).B32(id).B32(uint32(len(bs))).Out()) if e == nil { e = wrc(c, bs) } bytepool.Free(bs) case ssh_FXP_WRITE: var handle string var offset uint64 var length uint32 p.B32(&id).B32String(&handle).B64(&offset).B32(&length) f := h.getFile(handle) if f == nil { return einvhandle } var bs []byte e = p.NBytesPeek(int(length), &bs).End() if e != nil { return e } _, e = f.WriteAt(bs, int64(offset)) e = writeErr(c, id, e) case ssh_FXP_LSTAT, ssh_FXP_STAT: var path string var a *Attr e = p.B32(&id).B32String(&path).End() if e != nil { return e } a, e = fs.Stat(path, op == ssh_FXP_LSTAT) debug("stat/lstat", path, "=>", a, e) e = writeAttr(c, id, a, e) case ssh_FXP_FSTAT: var handle string var a *Attr e = p.B32(&id).B32String(&handle).End() if e != nil { return e } f := h.getFile(handle) if f == nil { return einvhandle } a, e = f.FStat() e = writeAttr(c, id, a, e) case ssh_FXP_SETSTAT: var path string var a Attr e = parseAttr(p.B32(&id).B32String(&path), &a).End() if e != nil { return e } e = writeErr(c, id, fs.SetStat(path, &a)) case ssh_FXP_FSETSTAT: var handle string var a Attr e = parseAttr(p.B32(&id).B32String(&handle), &a).End() if e != nil { return e } f := h.getFile(handle) if f == nil { return einvhandle } e = writeErr(c, id, f.FSetStat(&a)) case ssh_FXP_OPENDIR: var path string var dh Dir e = p.B32(&id).B32String(&path).End() if e != nil { return e } dh, e = fs.OpenDir(path) debug("opendir", id, path, "=>", dh, e) if e != nil { continue } e = writeHandle(c, id, h.newDir(dh)) case ssh_FXP_READDIR: var handle string e = p.B32(&id).B32String(&handle).End() if e != nil { return e } f := h.getDir(handle) if f == nil { return einvhandle } var fis []NamedAttr fis, e = f.Readdir(1024) debug("readdir", id, handle, fis, e) if e != nil { continue } var l binp.Len o := binp.Out().LenB32(&l).LenStart(&l).Byte(ssh_FXP_NAME).B32(id).B32(uint32(len(fis))) for _, fi := range fis { // FIXME should we do special handling for long names? n := fi.Name o.B32String(n).B32String(n).B32(fi.Flags) if fi.Flags&ATTR_SIZE != 0 { o.B64(uint64(fi.Size)) } if fi.Flags&ATTR_UIDGID != 0 { o.B32(fi.Uid).B32(fi.Gid) } if fi.Flags&ATTR_MODE != 0 { o.B32(fi.Mode) } if fi.Flags&ATTR_TIME != 0 { o.B32(fi.ATime).B32(fi.MTime) } } o.LenDone(&l) e = wrc(c, o.Out()) case ssh_FXP_REMOVE: var path string e = p.B32(&id).B32String(&path).End() if e != nil { return e } e = writeErr(c, id, fs.Remove(path)) case ssh_FXP_MKDIR: var path string var a Attr p = p.B32(&id).B32String(&path) e = parseAttr(p, &a).End() if e != nil { return e } e = writeErr(c, id, fs.Mkdir(path, &a)) case ssh_FXP_RMDIR: var path string e = p.B32(&id).B32String(&path).End() if e != nil { return e } e = writeErr(c, id, fs.Rmdir(path)) case ssh_FXP_REALPATH: var path, newpath string p.B32(&id).B32String(&path).End() newpath, e = fs.RealPath(path) debug("realpath: mapping", path, "=>", newpath, e) e = writeNameOnly(c, id, newpath, e) case ssh_FXP_RENAME: debug("FIXME RENAME NOT SUPPORTED") e = writeFail(c, id) // FIXME case ssh_FXP_READLINK: var path string e = p.B32(&id).B32String(&path).End() path, e = fs.ReadLink(path) e = writeNameOnly(c, id, path, e) case ssh_FXP_SYMLINK: debug("FIXME SYMLINK NOT SUPPORTED") e = writeFail(c, id) // FIXME } if e != nil { return e } } }