| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ssh |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "io" |
| "math/big" |
| "reflect" |
| "strconv" |
| "strings" |
| ) |
| |
| // These are SSH message type numbers. They are scattered around several |
| // documents but many were taken from [SSH-PARAMETERS]. |
| const ( |
| msgIgnore = 2 |
| msgUnimplemented = 3 |
| msgDebug = 4 |
| msgNewKeys = 21 |
| |
| // Standard authentication messages |
| msgUserAuthSuccess = 52 |
| msgUserAuthBanner = 53 |
| ) |
| |
| // SSH messages: |
| // |
| // These structures mirror the wire format of the corresponding SSH messages. |
| // They are marshaled using reflection with the marshal and unmarshal functions |
| // in this file. The only wrinkle is that a final member of type []byte with a |
| // ssh tag of "rest" receives the remainder of a packet when unmarshaling. |
| |
| // See RFC 4253, section 11.1. |
| const msgDisconnect = 1 |
| |
| // disconnectMsg is the message that signals a disconnect. It is also |
| // the error type returned from mux.Wait() |
| type disconnectMsg struct { |
| Reason uint32 `sshtype:"1"` |
| Message string |
| Language string |
| } |
| |
| func (d *disconnectMsg) Error() string { |
| return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message) |
| } |
| |
| // See RFC 4253, section 7.1. |
| const msgKexInit = 20 |
| |
| type kexInitMsg struct { |
| Cookie [16]byte `sshtype:"20"` |
| KexAlgos []string |
| ServerHostKeyAlgos []string |
| CiphersClientServer []string |
| CiphersServerClient []string |
| MACsClientServer []string |
| MACsServerClient []string |
| CompressionClientServer []string |
| CompressionServerClient []string |
| LanguagesClientServer []string |
| LanguagesServerClient []string |
| FirstKexFollows bool |
| Reserved uint32 |
| } |
| |
| // See RFC 4253, section 8. |
| |
| // Diffie-Helman |
| const msgKexDHInit = 30 |
| |
| type kexDHInitMsg struct { |
| X *big.Int `sshtype:"30"` |
| } |
| |
| const msgKexECDHInit = 30 |
| |
| type kexECDHInitMsg struct { |
| ClientPubKey []byte `sshtype:"30"` |
| } |
| |
| const msgKexECDHReply = 31 |
| |
| type kexECDHReplyMsg struct { |
| HostKey []byte `sshtype:"31"` |
| EphemeralPubKey []byte |
| Signature []byte |
| } |
| |
| const msgKexDHReply = 31 |
| |
| type kexDHReplyMsg struct { |
| HostKey []byte `sshtype:"31"` |
| Y *big.Int |
| Signature []byte |
| } |
| |
| // See RFC 4253, section 10. |
| const msgServiceRequest = 5 |
| |
| type serviceRequestMsg struct { |
| Service string `sshtype:"5"` |
| } |
| |
| // See RFC 4253, section 10. |
| const msgServiceAccept = 6 |
| |
| type serviceAcceptMsg struct { |
| Service string `sshtype:"6"` |
| } |
| |
| // See RFC 4252, section 5. |
| const msgUserAuthRequest = 50 |
| |
| type userAuthRequestMsg struct { |
| User string `sshtype:"50"` |
| Service string |
| Method string |
| Payload []byte `ssh:"rest"` |
| } |
| |
| // Used for debug printouts of packets. |
| type userAuthSuccessMsg struct { |
| } |
| |
| // See RFC 4252, section 5.1 |
| const msgUserAuthFailure = 51 |
| |
| type userAuthFailureMsg struct { |
| Methods []string `sshtype:"51"` |
| PartialSuccess bool |
| } |
| |
| // See RFC 4256, section 3.2 |
| const msgUserAuthInfoRequest = 60 |
| const msgUserAuthInfoResponse = 61 |
| |
| type userAuthInfoRequestMsg struct { |
| User string `sshtype:"60"` |
| Instruction string |
| DeprecatedLanguage string |
| NumPrompts uint32 |
| Prompts []byte `ssh:"rest"` |
| } |
| |
| // See RFC 4254, section 5.1. |
| const msgChannelOpen = 90 |
| |
| type channelOpenMsg struct { |
| ChanType string `sshtype:"90"` |
| PeersId uint32 |
| PeersWindow uint32 |
| MaxPacketSize uint32 |
| TypeSpecificData []byte `ssh:"rest"` |
| } |
| |
| const msgChannelExtendedData = 95 |
| const msgChannelData = 94 |
| |
| // Used for debug print outs of packets. |
| type channelDataMsg struct { |
| PeersId uint32 `sshtype:"94"` |
| Length uint32 |
| Rest []byte `ssh:"rest"` |
| } |
| |
| // See RFC 4254, section 5.1. |
| const msgChannelOpenConfirm = 91 |
| |
| type channelOpenConfirmMsg struct { |
| PeersId uint32 `sshtype:"91"` |
| MyId uint32 |
| MyWindow uint32 |
| MaxPacketSize uint32 |
| TypeSpecificData []byte `ssh:"rest"` |
| } |
| |
| // See RFC 4254, section 5.1. |
| const msgChannelOpenFailure = 92 |
| |
| type channelOpenFailureMsg struct { |
| PeersId uint32 `sshtype:"92"` |
| Reason RejectionReason |
| Message string |
| Language string |
| } |
| |
| const msgChannelRequest = 98 |
| |
| type channelRequestMsg struct { |
| PeersId uint32 `sshtype:"98"` |
| Request string |
| WantReply bool |
| RequestSpecificData []byte `ssh:"rest"` |
| } |
| |
| // See RFC 4254, section 5.4. |
| const msgChannelSuccess = 99 |
| |
| type channelRequestSuccessMsg struct { |
| PeersId uint32 `sshtype:"99"` |
| } |
| |
| // See RFC 4254, section 5.4. |
| const msgChannelFailure = 100 |
| |
| type channelRequestFailureMsg struct { |
| PeersId uint32 `sshtype:"100"` |
| } |
| |
| // See RFC 4254, section 5.3 |
| const msgChannelClose = 97 |
| |
| type channelCloseMsg struct { |
| PeersId uint32 `sshtype:"97"` |
| } |
| |
| // See RFC 4254, section 5.3 |
| const msgChannelEOF = 96 |
| |
| type channelEOFMsg struct { |
| PeersId uint32 `sshtype:"96"` |
| } |
| |
| // See RFC 4254, section 4 |
| const msgGlobalRequest = 80 |
| |
| type globalRequestMsg struct { |
| Type string `sshtype:"80"` |
| WantReply bool |
| Data []byte `ssh:"rest"` |
| } |
| |
| // See RFC 4254, section 4 |
| const msgRequestSuccess = 81 |
| |
| type globalRequestSuccessMsg struct { |
| Data []byte `ssh:"rest" sshtype:"81"` |
| } |
| |
| // See RFC 4254, section 4 |
| const msgRequestFailure = 82 |
| |
| type globalRequestFailureMsg struct { |
| Data []byte `ssh:"rest" sshtype:"82"` |
| } |
| |
| // See RFC 4254, section 5.2 |
| const msgChannelWindowAdjust = 93 |
| |
| type windowAdjustMsg struct { |
| PeersId uint32 `sshtype:"93"` |
| AdditionalBytes uint32 |
| } |
| |
| // See RFC 4252, section 7 |
| const msgUserAuthPubKeyOk = 60 |
| |
| type userAuthPubKeyOkMsg struct { |
| Algo string `sshtype:"60"` |
| PubKey []byte |
| } |
| |
| // typeTags returns the possible type bytes for the given reflect.Type, which |
| // should be a struct. The possible values are separated by a '|' character. |
| func typeTags(structType reflect.Type) (tags []byte) { |
| tagStr := structType.Field(0).Tag.Get("sshtype") |
| |
| for _, tag := range strings.Split(tagStr, "|") { |
| i, err := strconv.Atoi(tag) |
| if err == nil { |
| tags = append(tags, byte(i)) |
| } |
| } |
| |
| return tags |
| } |
| |
| func fieldError(t reflect.Type, field int, problem string) error { |
| if problem != "" { |
| problem = ": " + problem |
| } |
| return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem) |
| } |
| |
| var errShortRead = errors.New("ssh: short read") |
| |
| // Unmarshal parses data in SSH wire format into a structure. The out |
| // argument should be a pointer to struct. If the first member of the |
| // struct has the "sshtype" tag set to a '|'-separated set of numbers |
| // in decimal, the packet must start with one of those numbers. In |
| // case of error, Unmarshal returns a ParseError or |
| // UnexpectedMessageError. |
| func Unmarshal(data []byte, out interface{}) error { |
| v := reflect.ValueOf(out).Elem() |
| structType := v.Type() |
| expectedTypes := typeTags(structType) |
| |
| var expectedType byte |
| if len(expectedTypes) > 0 { |
| expectedType = expectedTypes[0] |
| } |
| |
| if len(data) == 0 { |
| return parseError(expectedType) |
| } |
| |
| if len(expectedTypes) > 0 { |
| goodType := false |
| for _, e := range expectedTypes { |
| if e > 0 && data[0] == e { |
| goodType = true |
| break |
| } |
| } |
| if !goodType { |
| return fmt.Errorf("ssh: unexpected message type %d (expected one of %v)", data[0], expectedTypes) |
| } |
| data = data[1:] |
| } |
| |
| var ok bool |
| for i := 0; i < v.NumField(); i++ { |
| field := v.Field(i) |
| t := field.Type() |
| switch t.Kind() { |
| case reflect.Bool: |
| if len(data) < 1 { |
| return errShortRead |
| } |
| field.SetBool(data[0] != 0) |
| data = data[1:] |
| case reflect.Array: |
| if t.Elem().Kind() != reflect.Uint8 { |
| return fieldError(structType, i, "array of unsupported type") |
| } |
| if len(data) < t.Len() { |
| return errShortRead |
| } |
| for j, n := 0, t.Len(); j < n; j++ { |
| field.Index(j).Set(reflect.ValueOf(data[j])) |
| } |
| data = data[t.Len():] |
| case reflect.Uint64: |
| var u64 uint64 |
| if u64, data, ok = parseUint64(data); !ok { |
| return errShortRead |
| } |
| field.SetUint(u64) |
| case reflect.Uint32: |
| var u32 uint32 |
| if u32, data, ok = parseUint32(data); !ok { |
| return errShortRead |
| } |
| field.SetUint(uint64(u32)) |
| case reflect.Uint8: |
| if len(data) < 1 { |
| return errShortRead |
| } |
| field.SetUint(uint64(data[0])) |
| data = data[1:] |
| case reflect.String: |
| var s []byte |
| if s, data, ok = parseString(data); !ok { |
| return fieldError(structType, i, "") |
| } |
| field.SetString(string(s)) |
| case reflect.Slice: |
| switch t.Elem().Kind() { |
| case reflect.Uint8: |
| if structType.Field(i).Tag.Get("ssh") == "rest" { |
| field.Set(reflect.ValueOf(data)) |
| data = nil |
| } else { |
| var s []byte |
| if s, data, ok = parseString(data); !ok { |
| return errShortRead |
| } |
| field.Set(reflect.ValueOf(s)) |
| } |
| case reflect.String: |
| var nl []string |
| if nl, data, ok = parseNameList(data); !ok { |
| return errShortRead |
| } |
| field.Set(reflect.ValueOf(nl)) |
| default: |
| return fieldError(structType, i, "slice of unsupported type") |
| } |
| case reflect.Ptr: |
| if t == bigIntType { |
| var n *big.Int |
| if n, data, ok = parseInt(data); !ok { |
| return errShortRead |
| } |
| field.Set(reflect.ValueOf(n)) |
| } else { |
| return fieldError(structType, i, "pointer to unsupported type") |
| } |
| default: |
| return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t)) |
| } |
| } |
| |
| if len(data) != 0 { |
| return parseError(expectedType) |
| } |
| |
| return nil |
| } |
| |
| // Marshal serializes the message in msg to SSH wire format. The msg |
| // argument should be a struct or pointer to struct. If the first |
| // member has the "sshtype" tag set to a number in decimal, that |
| // number is prepended to the result. If the last of member has the |
| // "ssh" tag set to "rest", its contents are appended to the output. |
| func Marshal(msg interface{}) []byte { |
| out := make([]byte, 0, 64) |
| return marshalStruct(out, msg) |
| } |
| |
| func marshalStruct(out []byte, msg interface{}) []byte { |
| v := reflect.Indirect(reflect.ValueOf(msg)) |
| msgTypes := typeTags(v.Type()) |
| if len(msgTypes) > 0 { |
| out = append(out, msgTypes[0]) |
| } |
| |
| for i, n := 0, v.NumField(); i < n; i++ { |
| field := v.Field(i) |
| switch t := field.Type(); t.Kind() { |
| case reflect.Bool: |
| var v uint8 |
| if field.Bool() { |
| v = 1 |
| } |
| out = append(out, v) |
| case reflect.Array: |
| if t.Elem().Kind() != reflect.Uint8 { |
| panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface())) |
| } |
| for j, l := 0, t.Len(); j < l; j++ { |
| out = append(out, uint8(field.Index(j).Uint())) |
| } |
| case reflect.Uint32: |
| out = appendU32(out, uint32(field.Uint())) |
| case reflect.Uint64: |
| out = appendU64(out, uint64(field.Uint())) |
| case reflect.Uint8: |
| out = append(out, uint8(field.Uint())) |
| case reflect.String: |
| s := field.String() |
| out = appendInt(out, len(s)) |
| out = append(out, s...) |
| case reflect.Slice: |
| switch t.Elem().Kind() { |
| case reflect.Uint8: |
| if v.Type().Field(i).Tag.Get("ssh") != "rest" { |
| out = appendInt(out, field.Len()) |
| } |
| out = append(out, field.Bytes()...) |
| case reflect.String: |
| offset := len(out) |
| out = appendU32(out, 0) |
| if n := field.Len(); n > 0 { |
| for j := 0; j < n; j++ { |
| f := field.Index(j) |
| if j != 0 { |
| out = append(out, ',') |
| } |
| out = append(out, f.String()...) |
| } |
| // overwrite length value |
| binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4)) |
| } |
| default: |
| panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface())) |
| } |
| case reflect.Ptr: |
| if t == bigIntType { |
| var n *big.Int |
| nValue := reflect.ValueOf(&n) |
| nValue.Elem().Set(field) |
| needed := intLength(n) |
| oldLength := len(out) |
| |
| if cap(out)-len(out) < needed { |
| newOut := make([]byte, len(out), 2*(len(out)+needed)) |
| copy(newOut, out) |
| out = newOut |
| } |
| out = out[:oldLength+needed] |
| marshalInt(out[oldLength:], n) |
| } else { |
| panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface())) |
| } |
| } |
| } |
| |
| return out |
| } |
| |
| var bigOne = big.NewInt(1) |
| |
| func parseString(in []byte) (out, rest []byte, ok bool) { |
| if len(in) < 4 { |
| return |
| } |
| length := binary.BigEndian.Uint32(in) |
| in = in[4:] |
| if uint32(len(in)) < length { |
| return |
| } |
| out = in[:length] |
| rest = in[length:] |
| ok = true |
| return |
| } |
| |
| var ( |
| comma = []byte{','} |
| emptyNameList = []string{} |
| ) |
| |
| func parseNameList(in []byte) (out []string, rest []byte, ok bool) { |
| contents, rest, ok := parseString(in) |
| if !ok { |
| return |
| } |
| if len(contents) == 0 { |
| out = emptyNameList |
| return |
| } |
| parts := bytes.Split(contents, comma) |
| out = make([]string, len(parts)) |
| for i, part := range parts { |
| out[i] = string(part) |
| } |
| return |
| } |
| |
| func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) { |
| contents, rest, ok := parseString(in) |
| if !ok { |
| return |
| } |
| out = new(big.Int) |
| |
| if len(contents) > 0 && contents[0]&0x80 == 0x80 { |
| // This is a negative number |
| notBytes := make([]byte, len(contents)) |
| for i := range notBytes { |
| notBytes[i] = ^contents[i] |
| } |
| out.SetBytes(notBytes) |
| out.Add(out, bigOne) |
| out.Neg(out) |
| } else { |
| // Positive number |
| out.SetBytes(contents) |
| } |
| ok = true |
| return |
| } |
| |
| func parseUint32(in []byte) (uint32, []byte, bool) { |
| if len(in) < 4 { |
| return 0, nil, false |
| } |
| return binary.BigEndian.Uint32(in), in[4:], true |
| } |
| |
| func parseUint64(in []byte) (uint64, []byte, bool) { |
| if len(in) < 8 { |
| return 0, nil, false |
| } |
| return binary.BigEndian.Uint64(in), in[8:], true |
| } |
| |
| func intLength(n *big.Int) int { |
| length := 4 /* length bytes */ |
| if n.Sign() < 0 { |
| nMinus1 := new(big.Int).Neg(n) |
| nMinus1.Sub(nMinus1, bigOne) |
| bitLen := nMinus1.BitLen() |
| if bitLen%8 == 0 { |
| // The number will need 0xff padding |
| length++ |
| } |
| length += (bitLen + 7) / 8 |
| } else if n.Sign() == 0 { |
| // A zero is the zero length string |
| } else { |
| bitLen := n.BitLen() |
| if bitLen%8 == 0 { |
| // The number will need 0x00 padding |
| length++ |
| } |
| length += (bitLen + 7) / 8 |
| } |
| |
| return length |
| } |
| |
| func marshalUint32(to []byte, n uint32) []byte { |
| binary.BigEndian.PutUint32(to, n) |
| return to[4:] |
| } |
| |
| func marshalUint64(to []byte, n uint64) []byte { |
| binary.BigEndian.PutUint64(to, n) |
| return to[8:] |
| } |
| |
| func marshalInt(to []byte, n *big.Int) []byte { |
| lengthBytes := to |
| to = to[4:] |
| length := 0 |
| |
| if n.Sign() < 0 { |
| // A negative number has to be converted to two's-complement |
| // form. So we'll subtract 1 and invert. If the |
| // most-significant-bit isn't set then we'll need to pad the |
| // beginning with 0xff in order to keep the number negative. |
| nMinus1 := new(big.Int).Neg(n) |
| nMinus1.Sub(nMinus1, bigOne) |
| bytes := nMinus1.Bytes() |
| for i := range bytes { |
| bytes[i] ^= 0xff |
| } |
| if len(bytes) == 0 || bytes[0]&0x80 == 0 { |
| to[0] = 0xff |
| to = to[1:] |
| length++ |
| } |
| nBytes := copy(to, bytes) |
| to = to[nBytes:] |
| length += nBytes |
| } else if n.Sign() == 0 { |
| // A zero is the zero length string |
| } else { |
| bytes := n.Bytes() |
| if len(bytes) > 0 && bytes[0]&0x80 != 0 { |
| // We'll have to pad this with a 0x00 in order to |
| // stop it looking like a negative number. |
| to[0] = 0 |
| to = to[1:] |
| length++ |
| } |
| nBytes := copy(to, bytes) |
| to = to[nBytes:] |
| length += nBytes |
| } |
| |
| lengthBytes[0] = byte(length >> 24) |
| lengthBytes[1] = byte(length >> 16) |
| lengthBytes[2] = byte(length >> 8) |
| lengthBytes[3] = byte(length) |
| return to |
| } |
| |
| func writeInt(w io.Writer, n *big.Int) { |
| length := intLength(n) |
| buf := make([]byte, length) |
| marshalInt(buf, n) |
| w.Write(buf) |
| } |
| |
| func writeString(w io.Writer, s []byte) { |
| var lengthBytes [4]byte |
| lengthBytes[0] = byte(len(s) >> 24) |
| lengthBytes[1] = byte(len(s) >> 16) |
| lengthBytes[2] = byte(len(s) >> 8) |
| lengthBytes[3] = byte(len(s)) |
| w.Write(lengthBytes[:]) |
| w.Write(s) |
| } |
| |
| func stringLength(n int) int { |
| return 4 + n |
| } |
| |
| func marshalString(to []byte, s []byte) []byte { |
| to[0] = byte(len(s) >> 24) |
| to[1] = byte(len(s) >> 16) |
| to[2] = byte(len(s) >> 8) |
| to[3] = byte(len(s)) |
| to = to[4:] |
| copy(to, s) |
| return to[len(s):] |
| } |
| |
| var bigIntType = reflect.TypeOf((*big.Int)(nil)) |
| |
| // Decode a packet into its corresponding message. |
| func decode(packet []byte) (interface{}, error) { |
| var msg interface{} |
| switch packet[0] { |
| case msgDisconnect: |
| msg = new(disconnectMsg) |
| case msgServiceRequest: |
| msg = new(serviceRequestMsg) |
| case msgServiceAccept: |
| msg = new(serviceAcceptMsg) |
| case msgKexInit: |
| msg = new(kexInitMsg) |
| case msgKexDHInit: |
| msg = new(kexDHInitMsg) |
| case msgKexDHReply: |
| msg = new(kexDHReplyMsg) |
| case msgUserAuthRequest: |
| msg = new(userAuthRequestMsg) |
| case msgUserAuthSuccess: |
| return new(userAuthSuccessMsg), nil |
| case msgUserAuthFailure: |
| msg = new(userAuthFailureMsg) |
| case msgUserAuthPubKeyOk: |
| msg = new(userAuthPubKeyOkMsg) |
| case msgGlobalRequest: |
| msg = new(globalRequestMsg) |
| case msgRequestSuccess: |
| msg = new(globalRequestSuccessMsg) |
| case msgRequestFailure: |
| msg = new(globalRequestFailureMsg) |
| case msgChannelOpen: |
| msg = new(channelOpenMsg) |
| case msgChannelData: |
| msg = new(channelDataMsg) |
| case msgChannelOpenConfirm: |
| msg = new(channelOpenConfirmMsg) |
| case msgChannelOpenFailure: |
| msg = new(channelOpenFailureMsg) |
| case msgChannelWindowAdjust: |
| msg = new(windowAdjustMsg) |
| case msgChannelEOF: |
| msg = new(channelEOFMsg) |
| case msgChannelClose: |
| msg = new(channelCloseMsg) |
| case msgChannelRequest: |
| msg = new(channelRequestMsg) |
| case msgChannelSuccess: |
| msg = new(channelRequestSuccessMsg) |
| case msgChannelFailure: |
| msg = new(channelRequestFailureMsg) |
| default: |
| return nil, unexpectedMessageError(0, packet[0]) |
| } |
| if err := Unmarshal(packet, msg); err != nil { |
| return nil, err |
| } |
| return msg, nil |
| } |