package filexfer import ( "errors" "fmt" "io" ) // smallBufferSize is an initial allocation minimal capacity. const smallBufferSize = 64 func newPacketFromType(typ PacketType) (Packet, error) { switch typ { case PacketTypeOpen: return new(OpenPacket), nil case PacketTypeClose: return new(ClosePacket), nil case PacketTypeRead: return new(ReadPacket), nil case PacketTypeWrite: return new(WritePacket), nil case PacketTypeLStat: return new(LStatPacket), nil case PacketTypeFStat: return new(FStatPacket), nil case PacketTypeSetstat: return new(SetstatPacket), nil case PacketTypeFSetstat: return new(FSetstatPacket), nil case PacketTypeOpenDir: return new(OpenDirPacket), nil case PacketTypeReadDir: return new(ReadDirPacket), nil case PacketTypeRemove: return new(RemovePacket), nil case PacketTypeMkdir: return new(MkdirPacket), nil case PacketTypeRmdir: return new(RmdirPacket), nil case PacketTypeRealPath: return new(RealPathPacket), nil case PacketTypeStat: return new(StatPacket), nil case PacketTypeRename: return new(RenamePacket), nil case PacketTypeReadLink: return new(ReadLinkPacket), nil case PacketTypeSymlink: return new(SymlinkPacket), nil case PacketTypeExtended: return new(ExtendedPacket), nil default: return nil, fmt.Errorf("unexpected request packet type: %v", typ) } } // RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02 // // RawPacket is intended for use in clients receiving responses, // where a response will be expected to be of a limited number of types, // and unmarshaling unknown/unexpected response packets is unnecessary. // // For servers expecting to receive arbitrary request packet types, // use RequestPacket. // // Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 type RawPacket struct { PacketType PacketType RequestID uint32 Data Buffer } // Type returns the Type field defining the SSH_FXP_xy type for this packet. func (p *RawPacket) Type() PacketType { return p.PacketType } // Reset clears the pointers and reference-semantic variables of RawPacket, // releasing underlying resources, and making them and the RawPacket suitable to be reused, // so long as no other references have been kept. func (p *RawPacket) Reset() { p.Data = Buffer{} } // MarshalPacket returns p as a two-part binary encoding of p. // // The internal p.RequestID is overridden by the reqid argument. func (p *RawPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) { buf := NewBuffer(b) if buf.Cap() < 9 { buf = NewMarshalBuffer(0) } buf.StartPacket(p.PacketType, reqid) return buf.Packet(p.Data.Bytes()) } // MarshalBinary returns p as the binary encoding of p. // // This is a convenience implementation primarily intended for tests, // because it is inefficient with allocations. func (p *RawPacket) MarshalBinary() ([]byte, error) { return ComposePacket(p.MarshalPacket(p.RequestID, nil)) } // UnmarshalFrom decodes a RawPacket from the given Buffer into p. // // The Data field will alias the passed in Buffer, // so the buffer passed in should not be reused before RawPacket.Reset(). func (p *RawPacket) UnmarshalFrom(buf *Buffer) error { typ, err := buf.ConsumeUint8() if err != nil { return err } p.PacketType = PacketType(typ) if p.RequestID, err = buf.ConsumeUint32(); err != nil { return err } p.Data = *buf return nil } // UnmarshalBinary decodes a full raw packet out of the given data. // It is assumed that the uint32(length) has already been consumed to receive the data. // // This is a convenience implementation primarily intended for tests, // because this must clone the given data byte slice, // as Data is not allowed to alias any part of the data byte slice. func (p *RawPacket) UnmarshalBinary(data []byte) error { clone := make([]byte, len(data)) n := copy(clone, data) return p.UnmarshalFrom(NewBuffer(clone[:n])) } // readPacket reads a uint32 length-prefixed binary data packet from r. // using the given byte slice as a backing array. // // If the packet length read from r is bigger than maxPacketLength, // or greater than math.MaxInt32 on a 32-bit implementation, // then a `ErrLongPacket` error will be returned. // // If the given byte slice is insufficient to hold the packet, // then it will be extended to fill the packet size. func readPacket(r io.Reader, b []byte, maxPacketLength uint32) ([]byte, error) { if cap(b) < 4 { // We will need allocate our own buffer just for reading the packet length. // However, we don’t really want to allocate an extremely narrow buffer (4-bytes), // and cause unnecessary allocation churn from both length reads and small packet reads, // so we use smallBufferSize from the bytes package as a reasonable guess. // But if callers really do want to force narrow throw-away allocation of every packet body, // they can do so with a buffer of capacity 4. b = make([]byte, smallBufferSize) } if _, err := io.ReadFull(r, b[:4]); err != nil { return nil, err } length := unmarshalUint32(b) if int(length) < 5 { // Must have at least uint8(type) and uint32(request-id) if int(length) < 0 { // Only possible when strconv.IntSize == 32, // the packet length is longer than math.MaxInt32, // and thus longer than any possible slice. return nil, ErrLongPacket } return nil, ErrShortPacket } if length > maxPacketLength { return nil, ErrLongPacket } if int(length) > cap(b) { // We know int(length) must be positive, because of tests above. b = make([]byte, length) } n, err := io.ReadFull(r, b[:length]) return b[:n], err } // ReadFrom provides a simple functional packet reader, // using the given byte slice as a backing array. // // To protect against potential denial of service attacks, // if the read packet length is longer than maxPacketLength, // then no packet data will be read, and ErrLongPacket will be returned. // (On 32-bit int architectures, all packets >= 2^31 in length // will return ErrLongPacket regardless of maxPacketLength.) // // If the read packet length is longer than cap(b), // then a throw-away slice will allocated to meet the exact packet length. // This can be used to limit the length of reused buffers, // while still allowing reception of occasional large packets. // // The Data field may alias the passed in byte slice, // so the byte slice passed in should not be reused before RawPacket.Reset(). func (p *RawPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) error { b, err := readPacket(r, b, maxPacketLength) if err != nil { return err } return p.UnmarshalFrom(NewBuffer(b)) } // RequestPacket implements the general packet format from draft-ietf-secsh-filexfer-02 // but also automatically decode/encodes valid request packets (2 < type < 100 || type == 200). // // RequestPacket is intended for use in servers receiving requests, // where any arbitrary request may be received, and so decoding them automatically // is useful. // // For clients expecting to receive specific response packet types, // where automatic unmarshaling of the packet body does not make sense, // use RawPacket. // // Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 type RequestPacket struct { RequestID uint32 Request Packet } // Type returns the SSH_FXP_xy value associated with the underlying packet. func (p *RequestPacket) Type() PacketType { return p.Request.Type() } // Reset clears the pointers and reference-semantic variables in RequestPacket, // releasing underlying resources, and making them and the RequestPacket suitable to be reused, // so long as no other references have been kept. func (p *RequestPacket) Reset() { p.Request = nil } // MarshalPacket returns p as a two-part binary encoding of p. // // The internal p.RequestID is overridden by the reqid argument. func (p *RequestPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) { if p.Request == nil { return nil, nil, errors.New("empty request packet") } return p.Request.MarshalPacket(reqid, b) } // MarshalBinary returns p as the binary encoding of p. // // This is a convenience implementation primarily intended for tests, // because it is inefficient with allocations. func (p *RequestPacket) MarshalBinary() ([]byte, error) { return ComposePacket(p.MarshalPacket(p.RequestID, nil)) } // UnmarshalFrom decodes a RequestPacket from the given Buffer into p. // // The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE), // so the buffer passed in should not be reused before RequestPacket.Reset(). func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error { typ, err := buf.ConsumeUint8() if err != nil { return err } p.Request, err = newPacketFromType(PacketType(typ)) if err != nil { return err } if p.RequestID, err = buf.ConsumeUint32(); err != nil { return err } return p.Request.UnmarshalPacketBody(buf) } // UnmarshalBinary decodes a full request packet out of the given data. // It is assumed that the uint32(length) has already been consumed to receive the data. // // This is a convenience implementation primarily intended for tests, // because this must clone the given data byte slice, // as Request is not allowed to alias any part of the data byte slice. func (p *RequestPacket) UnmarshalBinary(data []byte) error { clone := make([]byte, len(data)) n := copy(clone, data) return p.UnmarshalFrom(NewBuffer(clone[:n])) } // ReadFrom provides a simple functional packet reader, // using the given byte slice as a backing array. // // To protect against potential denial of service attacks, // if the read packet length is longer than maxPacketLength, // then no packet data will be read, and ErrLongPacket will be returned. // (On 32-bit int architectures, all packets >= 2^31 in length // will return ErrLongPacket regardless of maxPacketLength.) // // If the read packet length is longer than cap(b), // then a throw-away slice will allocated to meet the exact packet length. // This can be used to limit the length of reused buffers, // while still allowing reception of occasional large packets. // // The Request field may alias the passed in byte slice, // so the byte slice passed in should not be reused before RawPacket.Reset(). func (p *RequestPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) error { b, err := readPacket(r, b, maxPacketLength) if err != nil { return err } return p.UnmarshalFrom(NewBuffer(b)) }