Compare commits
2 Commits
fa9b7b1838
...
725f650193
Author | SHA1 | Date | |
---|---|---|---|
725f650193 | |||
0f27400a48 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,6 +21,8 @@ build/
|
||||
*.pb.go
|
||||
|
||||
# Go workspace file
|
||||
cryptolib
|
||||
go.work
|
||||
.idea
|
||||
config.yml
|
||||
!examples/config.yml
|
||||
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM golang:1.21-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY ./ ./
|
||||
RUN set -eux; \
|
||||
apk add --no-cache bash make git protoc protobuf-dev dumb-init mailcap ca-certificates tzdata; \
|
||||
update-ca-certificates; \
|
||||
make install_protobuf build
|
||||
|
||||
FROM scratch
|
||||
WORKDIR /
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /etc/mime.types /etc/mime.types
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
COPY --from=builder /usr/bin/dumb-init /bin/dumb-init
|
||||
COPY --from=builder /app/build/sshpoke /sshpoke
|
||||
EXPOSE 25680
|
||||
EXPOSE 25681
|
||||
ENTRYPOINT ["/bin/dumb-init", "--"]
|
||||
CMD ["/sshpoke"]
|
26
Makefile
26
Makefile
@ -8,7 +8,7 @@ GO_VERSION=$(shell go version | sed -e 's/go version //')
|
||||
VERSION=$(shell git describe --tags 2>/dev/null || git log --format="v0.0.0-%h" -n 1 || echo "v0.0.0-unknown")
|
||||
LDFLAGS="-X 'github.com/Neur0toxine/sshpoke/cmd.Version=${VERSION}'"
|
||||
|
||||
.PHONY: run clean_backend
|
||||
.PHONY: run clean_backend clone_sshlib
|
||||
|
||||
build: generate deps fmt
|
||||
@echo " ► Building with ${GO_VERSION}"
|
||||
@ -42,17 +42,23 @@ install_protobuf:
|
||||
@go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
|
||||
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
||||
|
||||
update_sshlib:
|
||||
clone_cryptolib:
|
||||
@rm -rf cryptolib && \
|
||||
git clone https://go.googlesource.com/crypto cryptolib && \
|
||||
rm -rf pkg/proto/ssh && \
|
||||
mv cryptolib/internal/poly1305 cryptolib/ssh/internal/ && \
|
||||
find cryptolib/ssh -type f -name '*.go' -exec sed -i 's?golang.org/x/crypto/ssh?github.com/Neur0toxine/sshpoke/pkg/proto/ssh?g' {} \; && \
|
||||
find cryptolib/ssh -type f -name '*.go' -exec sed -i 's?golang.org/x/crypto/internal/poly1305?github.com/Neur0toxine/sshpoke/pkg/proto/ssh/internal/poly1305?g' {} \; && \
|
||||
find cryptolib/ssh -type f -name '*_test.go' -delete && \
|
||||
rm -rf cryptolib/ssh/test && \
|
||||
rm -rf cryptolib/ssh/testdata
|
||||
|
||||
update_sshlib_patch:
|
||||
@diff -Naru cryptolib/ssh pkg/proto/ssh > patch/ssh_proto_patches.patch
|
||||
|
||||
update_sshlib: clone_cryptolib
|
||||
@mv pkg/proto/ssh pkg/proto/ssh.bak && \
|
||||
mv cryptolib/ssh pkg/proto/ && \
|
||||
mv cryptolib/internal/poly1305 pkg/proto/ssh/internal/ && \
|
||||
find pkg/proto/ssh -type f -name '*.go' -exec sed -i 's?golang.org/x/crypto/ssh?github.com/Neur0toxine/sshpoke/pkg/proto/ssh?g' {} \; && \
|
||||
find pkg/proto/ssh -type f -name '*.go' -exec sed -i 's?golang.org/x/crypto/internal/poly1305?github.com/Neur0toxine/sshpoke/pkg/proto/ssh/internal/poly1305?g' {} \; && \
|
||||
find pkg/proto/ssh -type f -name '*_test.go' -delete && \
|
||||
patch -p0 < patch/ssh_fakehost.patch && \
|
||||
rm -rf pkg/proto/ssh/test && \
|
||||
rm -rf pkg/proto/ssh/testdata && \
|
||||
patch -p0 < patch/ssh_proto_patches.patch && \
|
||||
rm -rf pkg/proto/ssh.bak && \
|
||||
rm -rf cryptolib
|
||||
|
||||
|
@ -1,18 +1,21 @@
|
||||
# Enable or disable debug logging.
|
||||
# Enable or disable debug logging. Default: false
|
||||
debug: true
|
||||
# API settings.
|
||||
api:
|
||||
# Local port for Web API. Will be bound to localhost.
|
||||
web_port: 25680
|
||||
# Local port for plugin API. Will listen on all interfaces because it has auth.
|
||||
plugin_port: 25681
|
||||
web:
|
||||
# Local port for Web API. Will be bound to localhost.
|
||||
port: 25680
|
||||
token: "VbNG6T6wYmj9YHM6etuZgN35"
|
||||
plugin:
|
||||
# Local port for plugin API. Will listen on all interfaces because it has auth.
|
||||
port: 25681
|
||||
# Docker client preferences.
|
||||
docker:
|
||||
# Extract client params from the environment.
|
||||
# Extract client params from the environment. Default: true
|
||||
from_env: true
|
||||
# Cert path for the Docker client.
|
||||
cert_path: ~
|
||||
# Set it to false to disable TLS cert verification.
|
||||
# Set it to false to disable TLS cert verification. Default: true
|
||||
tls_verify: true
|
||||
# Docker host. Can be useful for running containers alongside remote plugin (although it sounds weird to do so).
|
||||
host: ~
|
||||
@ -34,14 +37,19 @@ servers:
|
||||
# This disables remote host resolution and forcibly uses server IP for remote host.
|
||||
# It's the same as this syntax for sish: `ssh -R addr:80:localhost:80 your.sish.server`
|
||||
# Set this to true if you're using sish, otherwise you'll get weird domains with IP's in them.
|
||||
# Default: false
|
||||
fake_remote_host: true
|
||||
# Disables PTY request for this server.
|
||||
# Disables PTY request for this server. Default: false
|
||||
nopty: true
|
||||
# Requests interactive shell for SSH sessions. Should be `true` for the `commands`.
|
||||
# You can also pass a string with shell binary, for example, "/bin/sh".
|
||||
# Note: commands will be executed using provided shell binary.
|
||||
# Note (2): some servers won't send you any data until you request a shell even without a PTY.
|
||||
# You can use a combination of `nopty: true` & `shell: true`. Also, even with PTY you may need `shell` to be `true`.
|
||||
# Default: false
|
||||
shell: false
|
||||
# Spoof client version with provided (value below is taken directly from OpenSSH). This value must be compliant with RFC-4253.
|
||||
# Default: SSH-2.0-Go
|
||||
client_version: "SSH-2.0-OpenSSH_9.5"
|
||||
# Authentication data.
|
||||
auth:
|
||||
@ -57,9 +65,9 @@ servers:
|
||||
mode: multi
|
||||
# Keep-alive settings. Remove to disable keep-alive completely.
|
||||
keepalive:
|
||||
# Interval for keep-alive requests in seconds.
|
||||
# Interval for keep-alive requests in seconds. Default: 0 (disabled).
|
||||
interval: 1
|
||||
# How many attempts should fail to forcibly restart the connection.
|
||||
# How many attempts should fail to forcibly restart the connection. Default: 0 (disabled).
|
||||
max_attempts: 2
|
||||
# Regular expression that will be used to extract domain from stdout & stderr. Useful for services like sish or
|
||||
# localhost.run. `commands` output will also be parsed by this regex.
|
||||
@ -67,6 +75,7 @@ servers:
|
||||
# - !webUrl - any HTTP or HTTPS URL.
|
||||
# - !httpUrl - any HTTP URL.
|
||||
# - !httpsUrl - any HTTPS URL.
|
||||
# Default: "" (disabled).
|
||||
domain_extract_regex: "!httpsUrl"
|
||||
# Host keys to prevent MITM. You can obtain those via `ssh-keyscan <address>` (specify `-p` for non-standard port).
|
||||
# Always use '|' YAML syntax here (not '>') or sshpoke won't be able to parse keys.
|
||||
@ -95,6 +104,13 @@ servers:
|
||||
interval: 1
|
||||
max_attempts: 2
|
||||
domain_extract_regex: "!webUrl"
|
||||
# Read output data from raw SSH packets. This can help if domain_extract_regex couldn't catch the domain.
|
||||
# You can also enable debug logging - it should contain outputs from ssh server.
|
||||
# Default: false
|
||||
read_raw_packets: true
|
||||
# Enable or disable sessions output. Disabling this will stop domain_extract_regex from reading commands output.
|
||||
# Default: true
|
||||
read_sessions_output: false
|
||||
- name: ssh-demo-commands
|
||||
driver: ssh
|
||||
params:
|
10
examples/docker-compose.yml
Normal file
10
examples/docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
sshpoke:
|
||||
image: "neur0toxine/sshpoke:latest"
|
||||
container_name: "sshpoke"
|
||||
network_mode: host
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./config.yml:/config.yml
|
@ -61,7 +61,7 @@ func (p *pluginAPI) receiverForContext(ctx context.Context) plugin.Plugin {
|
||||
}
|
||||
|
||||
func StartPluginAPI() {
|
||||
port := config.Default.API.PluginPort
|
||||
port := config.Default.API.Plugin.Port
|
||||
if port == 0 {
|
||||
port = plugin2.DefaultPort
|
||||
}
|
||||
|
@ -19,8 +19,17 @@ type Config struct {
|
||||
}
|
||||
|
||||
type API struct {
|
||||
WebPort int `mapstructure:"web_port" validate:"gte=0,lte=65535"`
|
||||
PluginPort int `mapstructure:"plugin_port" validate:"gte=0,lte=65535"`
|
||||
Web WebAPI `mapstructure:"web"`
|
||||
Plugin PluginAPI `mapstructure:"plugin"`
|
||||
}
|
||||
|
||||
type WebAPI struct {
|
||||
Port int `mapstructure:"port" validate:"gte=0,lte=65535"`
|
||||
Token string `mapstructure:"token"`
|
||||
}
|
||||
|
||||
type PluginAPI struct {
|
||||
Port int `mapstructure:"port" validate:"gte=0,lte=65535"`
|
||||
}
|
||||
|
||||
type DockerConfig struct {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/Neur0toxine/sshpoke/pkg/dto"
|
||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh/knownhosts"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const KnownHostsFile = "known_hosts"
|
||||
@ -85,17 +86,42 @@ func (d *SSH) forward(val sshtun.Forward, domainMatcher func(string)) conn {
|
||||
d.Log())
|
||||
ctx, cancel := context.WithCancel(d.Context())
|
||||
tunDbgLog := d.Log().With("ssh-output", val.Remote.String())
|
||||
go tun.Connect(ctx,
|
||||
sshtun.BannerDebugLogCallback(tunDbgLog),
|
||||
sshtun.OutputReaderCallback(func(msg string) {
|
||||
d.Log().Debug(msg)
|
||||
var outputReaderCb sshtun.SessionCallback
|
||||
if d.params.ReadSessionsOutput == nil || (*d.params.ReadSessionsOutput) {
|
||||
outputReaderCb = sshtun.OutputReaderCallback(func(msg string) {
|
||||
msg = strings.TrimSpace(msg)
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
tunDbgLog.Debug("session: ", msg)
|
||||
if domainMatcher != nil {
|
||||
domainMatcher(msg)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
go tun.Connect(ctx,
|
||||
d.buildHandshakeLineCallback(domainMatcher, tunDbgLog),
|
||||
sshtun.BannerDebugLogCallback(tunDbgLog),
|
||||
outputReaderCb)
|
||||
return conn{ctx: ctx, cancel: cancel, tun: tun}
|
||||
}
|
||||
|
||||
func (d *SSH) buildHandshakeLineCallback(domainMatcher func(string), tunDbgLog *zap.SugaredLogger) func(string) {
|
||||
if d.params.ReadRawPackets {
|
||||
return func(msg string) {
|
||||
msg = strings.TrimSpace(msg)
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
tunDbgLog.Debugf("ssh: %s", msg)
|
||||
if domainMatcher != nil {
|
||||
domainMatcher(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *SSH) buildHostKeyCallback() ssh.HostKeyCallback {
|
||||
keysCallback := func() ssh.HostKeyCallback {
|
||||
if d.hostKeys == nil || len(d.hostKeys) == 0 {
|
||||
@ -280,6 +306,10 @@ func (d *SSH) remoteEndpoint(remoteHost string) sshtun.Endpoint {
|
||||
if port == 0 {
|
||||
port = 80
|
||||
}
|
||||
if remoteHost == "" && !d.params.FakeRemoteHost {
|
||||
// Listen on all interfaces if no host was provided.
|
||||
remoteHost = "0.0.0.0"
|
||||
}
|
||||
return sshtun.Endpoint{
|
||||
Host: remoteHost,
|
||||
Port: port,
|
||||
|
@ -15,6 +15,8 @@ type Params struct {
|
||||
Auth types.Auth `mapstructure:"auth"`
|
||||
KeepAlive types.KeepAlive `mapstructure:"keepalive"`
|
||||
DomainExtractRegex string `mapstructure:"domain_extract_regex" validate:"validregexp"`
|
||||
ReadRawPackets bool `mapstructure:"read_raw_packets"`
|
||||
ReadSessionsOutput *bool `mapstructure:"read_sessions_output"`
|
||||
Mode types.DomainMode `mapstructure:"mode" validate:"required,oneof=single multi"`
|
||||
FakeRemoteHost bool `mapstructure:"fake_remote_host"`
|
||||
NoPTY bool `mapstructure:"nopty"`
|
||||
|
@ -39,11 +39,11 @@ type TunnelConfig struct {
|
||||
type BoolOrStr string
|
||||
|
||||
func (b BoolOrStr) IsBool() bool {
|
||||
return b == "true" || b == "false"
|
||||
return b == "true" || b == "false" || b == "1" || b == "0" || b == ""
|
||||
}
|
||||
|
||||
func (b BoolOrStr) Falsy() bool {
|
||||
return b == "" || b == "false"
|
||||
return b == "" || b == "0" || b == "false"
|
||||
}
|
||||
|
||||
func (b BoolOrStr) String() string {
|
||||
@ -60,7 +60,8 @@ func New(address, user string, auth []ssh.AuthMethod, sc TunnelConfig, log *zap.
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) Connect(ctx context.Context, bannerCb ssh.BannerCallback, sessionCb SessionCallback) {
|
||||
func (t *Tunnel) Connect(ctx context.Context,
|
||||
hsLineCb func(msg string), bannerCb ssh.BannerCallback, sessionCb SessionCallback) {
|
||||
if t.connected.Load() {
|
||||
return
|
||||
}
|
||||
@ -69,7 +70,7 @@ func (t *Tunnel) Connect(ctx context.Context, bannerCb ssh.BannerCallback, sessi
|
||||
backoffTime := backoff.ExponentialWithCappedMax(100*time.Millisecond, 5*time.Second)
|
||||
for {
|
||||
t.connected.Store(true)
|
||||
err := t.connect(ctx, bannerCb, sessionCb)
|
||||
err := t.connect(ctx, hsLineCb, bannerCb, sessionCb)
|
||||
if err != nil {
|
||||
t.log.Error("connect error: ", err)
|
||||
}
|
||||
@ -86,9 +87,13 @@ func (t *Tunnel) Connect(ctx context.Context, bannerCb ssh.BannerCallback, sessi
|
||||
|
||||
// connect once to the SSH server. if the connection breaks, we return error and the caller
|
||||
// will try to re-connect
|
||||
func (t *Tunnel) connect(ctx context.Context, bannerCb ssh.BannerCallback, sessionCb SessionCallback) error {
|
||||
func (t *Tunnel) connect(ctx context.Context,
|
||||
hsLineCb func(string), bannerCb ssh.BannerCallback, sessionCb SessionCallback) error {
|
||||
t.log.Debug("connecting")
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
Config: ssh.Config{
|
||||
HandshakePacketReader: newHandshakePerLineReader(hsLineCb),
|
||||
},
|
||||
User: t.user,
|
||||
Auth: t.authMethods,
|
||||
HostKeyCallback: t.tunConfig.HostKeyCallback,
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
// assume that the underlying net.Conn abruptly died.
|
||||
func (t *Tunnel) keepAlive(ctx context.Context, client *ssh.Client, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
if t.tunConfig.KeepAliveInterval == 0 || t.tunConfig.KeepAliveMax == 0 {
|
||||
if t.tunConfig.KeepAliveInterval <= 0 || t.tunConfig.KeepAliveMax <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
31
internal/server/driver/ssh/sshtun/packet.go
Normal file
31
internal/server/driver/ssh/sshtun/packet.go
Normal file
@ -0,0 +1,31 @@
|
||||
package sshtun
|
||||
|
||||
type handshakePerLineReader struct {
|
||||
buf []byte
|
||||
off int
|
||||
cb func(line string)
|
||||
}
|
||||
|
||||
func newHandshakePerLineReader(cb func(string)) func([]byte) {
|
||||
if cb == nil {
|
||||
return nil
|
||||
}
|
||||
lr := &handshakePerLineReader{cb: cb, buf: make([]byte, 1024)}
|
||||
return lr.packet
|
||||
}
|
||||
|
||||
func (h *handshakePerLineReader) packet(p []byte) {
|
||||
for _, char := range p {
|
||||
if (char == 10 || char == 13) && len(h.buf) > 0 {
|
||||
h.cb(string(h.buf[:h.off]))
|
||||
h.off = 0
|
||||
}
|
||||
if cap(h.buf) <= (h.off + 1) {
|
||||
newBuf := make([]byte, cap(h.buf)+256)
|
||||
copy(newBuf, h.buf)
|
||||
h.buf = newBuf
|
||||
}
|
||||
h.buf[h.off] = char
|
||||
h.off++
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ func OutputReaderCallback(callback func(string)) SessionCallback {
|
||||
|
||||
func BannerDebugLogCallback(log *zap.SugaredLogger) ssh.BannerCallback {
|
||||
return func(msg string) error {
|
||||
log.Debug(msg)
|
||||
log.Debug("banner: ", msg)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ package types
|
||||
|
||||
type KeepAlive struct {
|
||||
Interval int `mapstructure:"interval" validate:"gte=0"`
|
||||
MaxAttempts int `mapstructure:"max_attempts" validate:"gte=1"`
|
||||
MaxAttempts int `mapstructure:"max_attempts" validate:"gte=0"`
|
||||
}
|
||||
|
@ -1,5 +1,68 @@
|
||||
--- cryptolib/ssh/tcpip.go 2023-11-18 22:20:50.609146922 +0300
|
||||
+++ pkg/proto/ssh/tcpip.go 2023-11-18 22:19:08.891684669 +0300
|
||||
diff -Naru cryptolib/ssh/cipher.go pkg/proto/ssh/cipher.go
|
||||
--- cryptolib/ssh/cipher.go 2023-11-21 18:52:03.117248053 +0300
|
||||
+++ pkg/proto/ssh/cipher.go 2023-11-21 17:19:04.688042738 +0300
|
||||
@@ -16,8 +16,8 @@
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
- "golang.org/x/crypto/chacha20"
|
||||
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh/internal/poly1305"
|
||||
+ "golang.org/x/crypto/chacha20"
|
||||
)
|
||||
|
||||
const (
|
||||
diff -Naru cryptolib/ssh/common.go pkg/proto/ssh/common.go
|
||||
--- cryptolib/ssh/common.go 2023-11-21 18:52:03.217249582 +0300
|
||||
+++ pkg/proto/ssh/common.go 2023-11-21 17:28:23.221203344 +0300
|
||||
@@ -7,14 +7,13 @@
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
+ _ "crypto/sha1"
|
||||
+ _ "crypto/sha256"
|
||||
+ _ "crypto/sha512"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
-
|
||||
- _ "crypto/sha1"
|
||||
- _ "crypto/sha256"
|
||||
- _ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
@@ -279,6 +278,9 @@
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
MACs []string
|
||||
+
|
||||
+ // Called on every incoming handshake packet for client. Only receives data and extended data packets.
|
||||
+ HandshakePacketReader func(p []byte)
|
||||
}
|
||||
|
||||
// SetDefaults sets sensible values for unset fields in config. This is
|
||||
diff -Naru cryptolib/ssh/handshake.go pkg/proto/ssh/handshake.go
|
||||
--- cryptolib/ssh/handshake.go 2023-11-21 18:52:03.209249460 +0300
|
||||
+++ pkg/proto/ssh/handshake.go 2023-11-21 18:51:58.681180283 +0300
|
||||
@@ -401,6 +401,14 @@
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
+ if t.config.HandshakePacketReader != nil && p[0] == msgChannelData {
|
||||
+ data, err := decode(p)
|
||||
+ packetData, ok := data.(*channelDataMsg)
|
||||
+ if err == nil && ok && packetData != nil {
|
||||
+ t.config.HandshakePacketReader(packetData.Rest)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if first && p[0] != msgKexInit {
|
||||
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
|
||||
}
|
||||
diff -Naru cryptolib/ssh/tcpip.go pkg/proto/ssh/tcpip.go
|
||||
--- cryptolib/ssh/tcpip.go 2023-11-21 18:52:03.129248237 +0300
|
||||
+++ pkg/proto/ssh/tcpip.go 2023-11-21 17:19:04.688042738 +0300
|
||||
@@ -101,14 +101,18 @@
|
||||
// ListenTCP requests the remote peer open a listening socket
|
||||
// on laddr. Incoming connections will be available by calling
|
@ -7,14 +7,13 @@ package ssh
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
@ -279,6 +278,9 @@ type Config struct {
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default is
|
||||
// used. Unsupported values are silently ignored.
|
||||
MACs []string
|
||||
|
||||
// Called on every incoming handshake packet for client. Only receives data and extended data packets.
|
||||
HandshakePacketReader func(p []byte)
|
||||
}
|
||||
|
||||
// SetDefaults sets sensible values for unset fields in config. This is
|
||||
|
@ -401,6 +401,14 @@ func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
if t.config.HandshakePacketReader != nil && p[0] == msgChannelData {
|
||||
data, err := decode(p)
|
||||
packetData, ok := data.(*channelDataMsg)
|
||||
if err == nil && ok && packetData != nil {
|
||||
t.config.HandshakePacketReader(packetData.Rest)
|
||||
}
|
||||
}
|
||||
|
||||
if first && p[0] != msgKexInit {
|
||||
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user