// Names extracts the repeated name data from the Data buffer. This message // structure is not supported by Unmarshal, so we take advantage of the "rest" // field. func (r *fxpNameResp) names() ([]nameData, error) { if r.Count == 0 { return nil, nil } data := r.Data r.Data = nil names := make([]nameData, 0, r.Count) for len(data) > 0 { name := fxpNameData{} if err := ssh.Unmarshal(data, &name); err != nil { return nil, err } data = name.Data var attr *FileAttributes var err error attr, data, err = newFileAttributes(data) if err != nil { return nil, err } names = append(names, nameData{ filename: name.Filename, longname: name.Longname, attr: attr, }) } if len(data) != 0 { return nil, fmt.Errorf("Expected to have 0 bytes of data left, have %d", len(data)) } return names, nil }
// decodeClient decodes a response packet's raw data into its corresponding // message structure. func decodeClient(packet []byte) (interface{}, error) { var msg interface{} switch packet[0] { case fxpPacketVersion: msg = new(fxpVersionMsg) case fxpPacketStatus: msg = new(fxpStatusResp) case fxpPacketHandle: msg = new(fxpHandleResp) case fxpPacketData: msg = new(fxpDataResp) case fxpPacketName: msg = new(fxpNameResp) case fxpPacketAttrs: msg = new(fxpAttrsResp) case fxpPacketExtendedReply: msg = new(fxpExtendedResp) default: return nil, UnexpectedMessageError{0, packet[0]} } if err := ssh.Unmarshal(packet, msg); err != nil { return nil, err } return msg, nil }
// init starts the SFTP protocol by negotiating the protocol version to use and // starts the response handler in a goroutine. func (s *Client) init() error { msg := fxpInitMsg{ Version: 3, } if err := s.writePacket(ssh.Marshal(msg)); err != nil { return err } packet, err := s.readOnePacket() if err != nil { return err } resp, err := decodeClient(packet) if err != nil { return err } switch resp := resp.(type) { case *fxpVersionMsg: if resp.Version != 3 { return errors.New("only version 3 of Client protocol supported") } default: return errors.New("invalid packet received during initialization") } vers := resp.(*fxpVersionMsg) s.exts = make(map[string]extension) if len(vers.Ext) > 0 { exts := vers.Ext for len(exts) > 0 { ew := extensionWire{} if err := ssh.Unmarshal(exts, &ew); err != nil { return err } if len(exts) < 2 { break } exts = ew.Rest e := extension{ Name: ew.Name, Data: ew.Data, } // OpenSSH's sftp-server implementation specifies that // the data portion of an extension is an ASCII-encoded // version number. This is not part of the SFTP // specification, however. if n, err := strconv.Atoi(ew.Data); err == nil { e.version = n } s.exts[e.Name] = e } } go s.mainLoop() return nil }
// newFileAttributes returns a new FileAttributes that represents the passed // data. func newFileAttributes(data []byte) (_ *FileAttributes, out []byte, err error) { // TODO(ekg): add error len(data) doesn't have enough bytes. f := &FileAttributes{ flags: binary.BigEndian.Uint32(data[0:4]), } data = data[4:] if f.flags&fxpAttrSize > 0 { f.size = binary.BigEndian.Uint64(data[0:8]) data = data[8:] } if f.flags&fxpAttrUIDGID > 0 { f.uid = binary.BigEndian.Uint32(data[0:4]) f.gid = binary.BigEndian.Uint32(data[4:8]) data = data[8:] } if f.flags&fxpAttrPermissions > 0 { f.permission = binary.BigEndian.Uint32(data[0:4]) data = data[4:] } if f.flags&fxpAttrACModTime > 0 { f.aTime = binary.BigEndian.Uint32(data[0:4]) f.mTime = binary.BigEndian.Uint32(data[4:8]) data = data[8:] } if f.flags&fxpAttrExtended > 0 { c := binary.BigEndian.Uint32(data[0:4]) data = data[4:] for i := 0; i < int(c); i++ { e := extInfo{} if err = ssh.Unmarshal(data, &e); err != nil { return } data = e.Rest e.Rest = nil f.ext = append(f.ext, e) } } return f, data, nil }