mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
725 lines
16 KiB
725 lines
16 KiB
// 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" |
|
) |
|
|
|
// 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"` |
|
} |
|
|
|
// 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 |
|
|
|
// 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 |
|
} |
|
|
|
// typeTag returns the type byte for the given type. The type should |
|
// be struct. |
|
func typeTag(structType reflect.Type) byte { |
|
var tag byte |
|
var tagStr string |
|
tagStr = structType.Field(0).Tag.Get("sshtype") |
|
i, err := strconv.Atoi(tagStr) |
|
if err == nil { |
|
tag = byte(i) |
|
} |
|
return tag |
|
} |
|
|
|
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 number in decimal, the packet |
|
// must start that number. 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() |
|
expectedType := typeTag(structType) |
|
if len(data) == 0 { |
|
return parseError(expectedType) |
|
} |
|
if expectedType > 0 { |
|
if data[0] != expectedType { |
|
return unexpectedMessageError(expectedType, data[0]) |
|
} |
|
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, "unsupported type") |
|
} |
|
} |
|
|
|
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)) |
|
msgType := typeTag(v.Type()) |
|
if msgType > 0 { |
|
out = append(out, msgType) |
|
} |
|
|
|
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 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 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 |
|
}
|
|
|