mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-18 08:26:45 +02:00
660 lines
14 KiB
Go
660 lines
14 KiB
Go
// 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"
|
|
"io"
|
|
"math/big"
|
|
"reflect"
|
|
)
|
|
|
|
// These are SSH message type numbers. They are scattered around several
|
|
// documents but many were taken from [SSH-PARAMETERS].
|
|
const (
|
|
msgDisconnect = 1
|
|
msgIgnore = 2
|
|
msgUnimplemented = 3
|
|
msgDebug = 4
|
|
msgServiceRequest = 5
|
|
msgServiceAccept = 6
|
|
|
|
msgKexInit = 20
|
|
msgNewKeys = 21
|
|
|
|
// Diffie-Helman
|
|
msgKexDHInit = 30
|
|
msgKexDHReply = 31
|
|
|
|
msgKexECDHInit = 30
|
|
msgKexECDHReply = 31
|
|
|
|
// Standard authentication messages
|
|
msgUserAuthRequest = 50
|
|
msgUserAuthFailure = 51
|
|
msgUserAuthSuccess = 52
|
|
msgUserAuthBanner = 53
|
|
msgUserAuthPubKeyOk = 60
|
|
|
|
// Method specific messages
|
|
msgUserAuthInfoRequest = 60
|
|
msgUserAuthInfoResponse = 61
|
|
|
|
msgGlobalRequest = 80
|
|
msgRequestSuccess = 81
|
|
msgRequestFailure = 82
|
|
|
|
// Channel manipulation
|
|
msgChannelOpen = 90
|
|
msgChannelOpenConfirm = 91
|
|
msgChannelOpenFailure = 92
|
|
msgChannelWindowAdjust = 93
|
|
msgChannelData = 94
|
|
msgChannelExtendedData = 95
|
|
msgChannelEOF = 96
|
|
msgChannelClose = 97
|
|
msgChannelRequest = 98
|
|
msgChannelSuccess = 99
|
|
msgChannelFailure = 100
|
|
)
|
|
|
|
// 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.
|
|
type disconnectMsg struct {
|
|
Reason uint32
|
|
Message string
|
|
Language string
|
|
}
|
|
|
|
// See RFC 4253, section 7.1.
|
|
type kexInitMsg struct {
|
|
Cookie [16]byte
|
|
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.
|
|
type kexDHInitMsg struct {
|
|
X *big.Int
|
|
}
|
|
|
|
type kexECDHInitMsg struct {
|
|
ClientPubKey []byte
|
|
}
|
|
|
|
type kexECDHReplyMsg struct {
|
|
HostKey []byte
|
|
EphemeralPubKey []byte
|
|
Signature []byte
|
|
}
|
|
|
|
type kexDHReplyMsg struct {
|
|
HostKey []byte
|
|
Y *big.Int
|
|
Signature []byte
|
|
}
|
|
|
|
// See RFC 4253, section 10.
|
|
type serviceRequestMsg struct {
|
|
Service string
|
|
}
|
|
|
|
// See RFC 4253, section 10.
|
|
type serviceAcceptMsg struct {
|
|
Service string
|
|
}
|
|
|
|
// See RFC 4252, section 5.
|
|
type userAuthRequestMsg struct {
|
|
User string
|
|
Service string
|
|
Method string
|
|
Payload []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4252, section 5.1
|
|
type userAuthFailureMsg struct {
|
|
Methods []string
|
|
PartialSuccess bool
|
|
}
|
|
|
|
// See RFC 4256, section 3.2
|
|
type userAuthInfoRequestMsg struct {
|
|
User string
|
|
Instruction string
|
|
DeprecatedLanguage string
|
|
NumPrompts uint32
|
|
Prompts []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 5.1.
|
|
type channelOpenMsg struct {
|
|
ChanType string
|
|
PeersId uint32
|
|
PeersWindow uint32
|
|
MaxPacketSize uint32
|
|
TypeSpecificData []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 5.1.
|
|
type channelOpenConfirmMsg struct {
|
|
PeersId uint32
|
|
MyId uint32
|
|
MyWindow uint32
|
|
MaxPacketSize uint32
|
|
TypeSpecificData []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 5.1.
|
|
type channelOpenFailureMsg struct {
|
|
PeersId uint32
|
|
Reason RejectionReason
|
|
Message string
|
|
Language string
|
|
}
|
|
|
|
type channelRequestMsg struct {
|
|
PeersId uint32
|
|
Request string
|
|
WantReply bool
|
|
RequestSpecificData []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 5.4.
|
|
type channelRequestSuccessMsg struct {
|
|
PeersId uint32
|
|
}
|
|
|
|
// See RFC 4254, section 5.4.
|
|
type channelRequestFailureMsg struct {
|
|
PeersId uint32
|
|
}
|
|
|
|
// See RFC 4254, section 5.3
|
|
type channelCloseMsg struct {
|
|
PeersId uint32
|
|
}
|
|
|
|
// See RFC 4254, section 5.3
|
|
type channelEOFMsg struct {
|
|
PeersId uint32
|
|
}
|
|
|
|
// See RFC 4254, section 4
|
|
type globalRequestMsg struct {
|
|
Type string
|
|
WantReply bool
|
|
}
|
|
|
|
// See RFC 4254, section 4
|
|
type globalRequestSuccessMsg struct {
|
|
Data []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 4
|
|
type globalRequestFailureMsg struct {
|
|
Data []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See RFC 4254, section 5.2
|
|
type windowAdjustMsg struct {
|
|
PeersId uint32
|
|
AdditionalBytes uint32
|
|
}
|
|
|
|
// See RFC 4252, section 7
|
|
type userAuthPubKeyOkMsg struct {
|
|
Algo string
|
|
PubKey string
|
|
}
|
|
|
|
// unmarshal parses the SSH wire data in packet into out using
|
|
// reflection. expectedType, if non-zero, is the SSH message type that
|
|
// the packet is expected to start with. unmarshal either returns nil
|
|
// on success, or a ParseError or UnexpectedMessageError on error.
|
|
func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
|
|
if len(packet) == 0 {
|
|
return ParseError{expectedType}
|
|
}
|
|
if expectedType > 0 {
|
|
if packet[0] != expectedType {
|
|
return UnexpectedMessageError{expectedType, packet[0]}
|
|
}
|
|
packet = packet[1:]
|
|
}
|
|
|
|
v := reflect.ValueOf(out).Elem()
|
|
structType := v.Type()
|
|
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(packet) < 1 {
|
|
return ParseError{expectedType}
|
|
}
|
|
field.SetBool(packet[0] != 0)
|
|
packet = packet[1:]
|
|
case reflect.Array:
|
|
if t.Elem().Kind() != reflect.Uint8 {
|
|
panic("array of non-uint8")
|
|
}
|
|
if len(packet) < t.Len() {
|
|
return ParseError{expectedType}
|
|
}
|
|
for j, n := 0, t.Len(); j < n; j++ {
|
|
field.Index(j).Set(reflect.ValueOf(packet[j]))
|
|
}
|
|
packet = packet[t.Len():]
|
|
case reflect.Uint32:
|
|
var u32 uint32
|
|
if u32, packet, ok = parseUint32(packet); !ok {
|
|
return ParseError{expectedType}
|
|
}
|
|
field.SetUint(uint64(u32))
|
|
case reflect.String:
|
|
var s []byte
|
|
if s, packet, ok = parseString(packet); !ok {
|
|
return ParseError{expectedType}
|
|
}
|
|
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(packet))
|
|
packet = nil
|
|
} else {
|
|
var s []byte
|
|
if s, packet, ok = parseString(packet); !ok {
|
|
return ParseError{expectedType}
|
|
}
|
|
field.Set(reflect.ValueOf(s))
|
|
}
|
|
case reflect.String:
|
|
var nl []string
|
|
if nl, packet, ok = parseNameList(packet); !ok {
|
|
return ParseError{expectedType}
|
|
}
|
|
field.Set(reflect.ValueOf(nl))
|
|
default:
|
|
panic("slice of unknown type")
|
|
}
|
|
case reflect.Ptr:
|
|
if t == bigIntType {
|
|
var n *big.Int
|
|
if n, packet, ok = parseInt(packet); !ok {
|
|
return ParseError{expectedType}
|
|
}
|
|
field.Set(reflect.ValueOf(n))
|
|
} else {
|
|
panic("pointer to unknown type")
|
|
}
|
|
default:
|
|
panic("unknown type")
|
|
}
|
|
}
|
|
|
|
if len(packet) != 0 {
|
|
return ParseError{expectedType}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// marshal serializes the message in msg. The given message type is
|
|
// prepended if it is non-zero.
|
|
func marshal(msgType uint8, msg interface{}) []byte {
|
|
out := make([]byte, 0, 64)
|
|
if msgType > 0 {
|
|
out = append(out, msgType)
|
|
}
|
|
|
|
v := reflect.ValueOf(msg)
|
|
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("array of non-uint8")
|
|
}
|
|
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.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("slice of unknown type")
|
|
}
|
|
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("pointer to unknown type")
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
if uint32(len(in)) < 4+length {
|
|
return
|
|
}
|
|
out = in[4 : 4+length]
|
|
rest = in[4+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 nameListLength(namelist []string) int {
|
|
length := 4 /* uint32 length prefix */
|
|
for i, name := range namelist {
|
|
if i != 0 {
|
|
length++ /* comma */
|
|
}
|
|
length += len(name)
|
|
}
|
|
return length
|
|
}
|
|
|
|
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(msg, packet, packet[0]); err != nil {
|
|
return nil, err
|
|
}
|
|
return msg, nil
|
|
}
|