cmd/puppeth, vendor: update ssh, manage server keys (#14398)

This commit is contained in:
Péter Szilágyi 2017-05-03 10:09:34 +03:00 committed by Felix Lange
parent 59966255ad
commit cf4faa491a
19 changed files with 704 additions and 288 deletions

View File

@ -17,6 +17,8 @@
package main package main
import ( import (
"bufio"
"bytes"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -37,18 +39,26 @@ import (
type sshClient struct { type sshClient struct {
server string // Server name or IP without port number server string // Server name or IP without port number
address string // IP address of the remote server address string // IP address of the remote server
pubkey []byte // RSA public key to authenticate the server
client *ssh.Client client *ssh.Client
logger log.Logger logger log.Logger
} }
// dial establishes an SSH connection to a remote node using the current user and // dial establishes an SSH connection to a remote node using the current user and
// the user's configured private RSA key. // the user's configured private RSA key. If that fails, password authentication
func dial(server string) (*sshClient, error) { // is fallen back to. The caller may override the login user via user@server:port.
func dial(server string, pubkey []byte) (*sshClient, error) {
// Figure out a label for the server and a logger // Figure out a label for the server and a logger
label := server label := server
if strings.Contains(label, ":") { if strings.Contains(label, ":") {
label = label[:strings.Index(label, ":")] label = label[:strings.Index(label, ":")]
} }
login := ""
if strings.Contains(server, "@") {
login = label[:strings.Index(label, "@")]
label = label[strings.Index(label, "@")+1:]
server = server[strings.Index(server, "@")+1:]
}
logger := log.New("server", label) logger := log.New("server", label)
logger.Debug("Attempting to establish SSH connection") logger.Debug("Attempting to establish SSH connection")
@ -56,6 +66,9 @@ func dial(server string) (*sshClient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if login == "" {
login = user.Username
}
// Configure the supported authentication methods (private key and password) // Configure the supported authentication methods (private key and password)
var auths []ssh.AuthMethod var auths []ssh.AuthMethod
@ -71,7 +84,7 @@ func dial(server string) (*sshClient, error) {
} }
} }
auths = append(auths, ssh.PasswordCallback(func() (string, error) { auths = append(auths, ssh.PasswordCallback(func() (string, error) {
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", user.Username, server) fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
blob, err := terminal.ReadPassword(int(syscall.Stdin)) blob, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println() fmt.Println()
@ -86,11 +99,36 @@ func dial(server string) (*sshClient, error) {
return nil, errors.New("no IPs associated with domain") return nil, errors.New("no IPs associated with domain")
} }
// Try to dial in to the remote server // Try to dial in to the remote server
logger.Trace("Dialing remote SSH server", "user", user.Username, "key", path) logger.Trace("Dialing remote SSH server", "user", login)
if !strings.Contains(server, ":") { if !strings.Contains(server, ":") {
server += ":22" server += ":22"
} }
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: user.Username, Auth: auths}) keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// If no public key is known for SSH, ask the user to confirm
if pubkey == nil {
fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
switch {
case err != nil:
return err
case strings.TrimSpace(text) == "yes":
pubkey = key.Marshal()
return nil
default:
return fmt.Errorf("unknown auth choice: %v", text)
}
}
// If a public key exists for this SSH server, check that it matches
if bytes.Compare(pubkey, key.Marshal()) == 0 {
return nil
}
// We have a mismatch, forbid connecting
return errors.New("ssh key mismatch, readd the machine to update")
}
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,6 +136,7 @@ func dial(server string) (*sshClient, error) {
c := &sshClient{ c := &sshClient{
server: label, server: label,
address: addr[0], address: addr[0],
pubkey: pubkey,
client: client, client: client,
logger: logger, logger: logger,
} }

View File

@ -44,14 +44,24 @@ type config struct {
bootLight []string // Bootnodes to always connect to by light nodes bootLight []string // Bootnodes to always connect to by light nodes
ethstats string // Ethstats settings to cache for node deploys ethstats string // Ethstats settings to cache for node deploys
Servers []string `json:"servers,omitempty"` Servers map[string][]byte `json:"servers,omitempty"`
}
// servers retrieves an alphabetically sorted list of servers.
func (c config) servers() []string {
servers := make([]string, 0, len(c.Servers))
for server := range c.Servers {
servers = append(servers, server)
}
sort.Strings(servers)
return servers
} }
// flush dumps the contents of config to disk. // flush dumps the contents of config to disk.
func (c config) flush() { func (c config) flush() {
os.MkdirAll(filepath.Dir(c.path), 0755) os.MkdirAll(filepath.Dir(c.path), 0755)
sort.Strings(c.Servers)
out, _ := json.MarshalIndent(c, "", " ") out, _ := json.MarshalIndent(c, "", " ")
if err := ioutil.WriteFile(c.path, out, 0644); err != nil { if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)

View File

@ -32,6 +32,9 @@ import (
func makeWizard(network string) *wizard { func makeWizard(network string) *wizard {
return &wizard{ return &wizard{
network: network, network: network,
conf: config{
Servers: make(map[string][]byte),
},
servers: make(map[string]*sshClient), servers: make(map[string]*sshClient),
services: make(map[string][]string), services: make(map[string][]string),
in: bufio.NewReader(os.Stdin), in: bufio.NewReader(os.Stdin),
@ -77,9 +80,9 @@ func (w *wizard) run() {
} else if err := json.Unmarshal(blob, &w.conf); err != nil { } else if err := json.Unmarshal(blob, &w.conf); err != nil {
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err) log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
} else { } else {
for _, server := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
log.Info("Dialing previously configured server", "server", server) log.Info("Dialing previously configured server", "server", server)
client, err := dial(server) client, err := dial(server, pubkey)
if err != nil { if err != nil {
log.Error("Previous server unreachable", "server", server, "err", err) log.Error("Previous server unreachable", "server", server, "err", err)
} }

View File

@ -41,14 +41,14 @@ func (w *wizard) networkStats(tips bool) {
stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"}) stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"})
stats.SetColWidth(128) stats.SetColWidth(128)
for _, server := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
client := w.servers[server] client := w.servers[server]
logger := log.New("server", server) logger := log.New("server", server)
logger.Info("Starting remote server health-check") logger.Info("Starting remote server health-check")
// If the server is not connected, try to connect again // If the server is not connected, try to connect again
if client == nil { if client == nil {
conn, err := dial(server) conn, err := dial(server, pubkey)
if err != nil { if err != nil {
logger.Error("Failed to establish remote connection", "err", err) logger.Error("Failed to establish remote connection", "err", err)
stats.Append([]string{server, "", err.Error(), "", ""}) stats.Append([]string{server, "", err.Error(), "", ""})

View File

@ -28,7 +28,9 @@ import (
func (w *wizard) manageServers() { func (w *wizard) manageServers() {
// List all the servers we can disconnect, along with an entry to connect a new one // List all the servers we can disconnect, along with an entry to connect a new one
fmt.Println() fmt.Println()
for i, server := range w.conf.Servers {
servers := w.conf.servers()
for i, server := range servers {
fmt.Printf(" %d. Disconnect %s\n", i+1, server) fmt.Printf(" %d. Disconnect %s\n", i+1, server)
} }
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@ -40,14 +42,14 @@ func (w *wizard) manageServers() {
} }
// If the user selected an existing server, drop it // If the user selected an existing server, drop it
if choice <= len(w.conf.Servers) { if choice <= len(w.conf.Servers) {
server := w.conf.Servers[choice-1] server := servers[choice-1]
client := w.servers[server] client := w.servers[server]
delete(w.servers, server) delete(w.servers, server)
if client != nil { if client != nil {
client.Close() client.Close()
} }
w.conf.Servers = append(w.conf.Servers[:choice-1], w.conf.Servers[choice:]...) delete(w.conf.Servers, server)
w.conf.flush() w.conf.flush()
log.Info("Disconnected existing server", "server", server) log.Info("Disconnected existing server", "server", server)
@ -73,14 +75,14 @@ func (w *wizard) makeServer() string {
// Read and fial the server to ensure docker is present // Read and fial the server to ensure docker is present
input := w.readString() input := w.readString()
client, err := dial(input) client, err := dial(input, nil)
if err != nil { if err != nil {
log.Error("Server not ready for puppeth", "err", err) log.Error("Server not ready for puppeth", "err", err)
return "" return ""
} }
// All checks passed, start tracking the server // All checks passed, start tracking the server
w.servers[input] = client w.servers[input] = client
w.conf.Servers = append(w.conf.Servers, input) w.conf.Servers[input] = client.pubkey
w.conf.flush() w.conf.flush()
return input return input
@ -93,7 +95,9 @@ func (w *wizard) selectServer() string {
// List the available server to the user and wait for a choice // List the available server to the user and wait for a choice
fmt.Println() fmt.Println()
fmt.Println("Which server do you want to interact with?") fmt.Println("Which server do you want to interact with?")
for i, server := range w.conf.Servers {
servers := w.conf.servers()
for i, server := range servers {
fmt.Printf(" %d. %s\n", i+1, server) fmt.Printf(" %d. %s\n", i+1, server)
} }
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@ -105,7 +109,7 @@ func (w *wizard) selectServer() string {
} }
// If the user requested connecting to a new server, go for it // If the user requested connecting to a new server, go for it
if choice <= len(w.conf.Servers) { if choice <= len(w.conf.Servers) {
return w.conf.Servers[choice-1] return servers[choice-1]
} }
return w.makeServer() return w.makeServer()
} }

View File

@ -2,87 +2,64 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine // +build amd64,!gccgo,!appengine
// func cswap(inout *[5]uint64, v uint64) // func cswap(inout *[4][5]uint64, v uint64)
TEXT ·cswap(SB),7,$0 TEXT ·cswap(SB),7,$0
MOVQ inout+0(FP),DI MOVQ inout+0(FP),DI
MOVQ v+8(FP),SI MOVQ v+8(FP),SI
CMPQ SI,$1 SUBQ $1, SI
MOVQ 0(DI),SI NOTQ SI
MOVQ 80(DI),DX MOVQ SI, X15
MOVQ 8(DI),CX PSHUFD $0x44, X15, X15
MOVQ 88(DI),R8
MOVQ SI,R9 MOVOU 0(DI), X0
CMOVQEQ DX,SI MOVOU 16(DI), X2
CMOVQEQ R9,DX MOVOU 32(DI), X4
MOVQ CX,R9 MOVOU 48(DI), X6
CMOVQEQ R8,CX MOVOU 64(DI), X8
CMOVQEQ R9,R8 MOVOU 80(DI), X1
MOVQ SI,0(DI) MOVOU 96(DI), X3
MOVQ DX,80(DI) MOVOU 112(DI), X5
MOVQ CX,8(DI) MOVOU 128(DI), X7
MOVQ R8,88(DI) MOVOU 144(DI), X9
MOVQ 16(DI),SI
MOVQ 96(DI),DX MOVO X1, X10
MOVQ 24(DI),CX MOVO X3, X11
MOVQ 104(DI),R8 MOVO X5, X12
MOVQ SI,R9 MOVO X7, X13
CMOVQEQ DX,SI MOVO X9, X14
CMOVQEQ R9,DX
MOVQ CX,R9 PXOR X0, X10
CMOVQEQ R8,CX PXOR X2, X11
CMOVQEQ R9,R8 PXOR X4, X12
MOVQ SI,16(DI) PXOR X6, X13
MOVQ DX,96(DI) PXOR X8, X14
MOVQ CX,24(DI) PAND X15, X10
MOVQ R8,104(DI) PAND X15, X11
MOVQ 32(DI),SI PAND X15, X12
MOVQ 112(DI),DX PAND X15, X13
MOVQ 40(DI),CX PAND X15, X14
MOVQ 120(DI),R8 PXOR X10, X0
MOVQ SI,R9 PXOR X10, X1
CMOVQEQ DX,SI PXOR X11, X2
CMOVQEQ R9,DX PXOR X11, X3
MOVQ CX,R9 PXOR X12, X4
CMOVQEQ R8,CX PXOR X12, X5
CMOVQEQ R9,R8 PXOR X13, X6
MOVQ SI,32(DI) PXOR X13, X7
MOVQ DX,112(DI) PXOR X14, X8
MOVQ CX,40(DI) PXOR X14, X9
MOVQ R8,120(DI)
MOVQ 48(DI),SI MOVOU X0, 0(DI)
MOVQ 128(DI),DX MOVOU X2, 16(DI)
MOVQ 56(DI),CX MOVOU X4, 32(DI)
MOVQ 136(DI),R8 MOVOU X6, 48(DI)
MOVQ SI,R9 MOVOU X8, 64(DI)
CMOVQEQ DX,SI MOVOU X1, 80(DI)
CMOVQEQ R9,DX MOVOU X3, 96(DI)
MOVQ CX,R9 MOVOU X5, 112(DI)
CMOVQEQ R8,CX MOVOU X7, 128(DI)
CMOVQEQ R9,R8 MOVOU X9, 144(DI)
MOVQ SI,48(DI)
MOVQ DX,128(DI)
MOVQ CX,56(DI)
MOVQ R8,136(DI)
MOVQ 64(DI),SI
MOVQ 144(DI),DX
MOVQ 72(DI),CX
MOVQ 152(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,64(DI)
MOVQ DX,144(DI)
MOVQ CX,72(DI)
MOVQ R8,152(DI)
MOVQ DI,AX
MOVQ SI,DX
RET RET

View File

@ -8,6 +8,10 @@
package curve25519 package curve25519
import (
"encoding/binary"
)
// This code is a port of the public domain, "ref10" implementation of // This code is a port of the public domain, "ref10" implementation of
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein. // curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
@ -50,17 +54,11 @@ func feCopy(dst, src *fieldElement) {
// //
// Preconditions: b in {0,1}. // Preconditions: b in {0,1}.
func feCSwap(f, g *fieldElement, b int32) { func feCSwap(f, g *fieldElement, b int32) {
var x fieldElement
b = -b b = -b
for i := range x {
x[i] = b & (f[i] ^ g[i])
}
for i := range f { for i := range f {
f[i] ^= x[i] t := b & (f[i] ^ g[i])
} f[i] ^= t
for i := range g { g[i] ^= t
g[i] ^= x[i]
} }
} }
@ -75,12 +73,7 @@ func load3(in []byte) int64 {
// load4 reads a 32-bit, little-endian value from in. // load4 reads a 32-bit, little-endian value from in.
func load4(in []byte) int64 { func load4(in []byte) int64 {
var r int64 return int64(binary.LittleEndian.Uint32(in))
r = int64(in[0])
r |= int64(in[1]) << 8
r |= int64(in[2]) << 16
r |= int64(in[3]) << 24
return r
} }
func feFromBytes(dst *fieldElement, src *[32]byte) { func feFromBytes(dst *fieldElement, src *[32]byte) {

View File

@ -268,7 +268,7 @@ type CertChecker struct {
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a // HostKeyFallback is called when CertChecker.CheckHostKey encounters a
// public key that is not a certificate. It must implement host key // public key that is not a certificate. It must implement host key
// validation or else, if nil, all such keys are rejected. // validation or else, if nil, all such keys are rejected.
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error HostKeyFallback HostKeyCallback
// IsRevoked is called for each certificate so that revocation checking // IsRevoked is called for each certificate so that revocation checking
// can be implemented. It should return true if the given certificate // can be implemented. It should return true if the given certificate

View File

@ -5,6 +5,7 @@
package ssh package ssh
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -13,7 +14,7 @@ import (
) )
// Client implements a traditional SSH client that supports shells, // Client implements a traditional SSH client that supports shells,
// subprocesses, port forwarding and tunneled dialing. // subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
type Client struct { type Client struct {
Conn Conn
@ -59,6 +60,7 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn.forwards.closeAll() conn.forwards.closeAll()
}() }()
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip")) go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
return conn return conn
} }
@ -68,6 +70,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config fullConf := *config
fullConf.SetDefaults() fullConf.SetDefaults()
if fullConf.HostKeyCallback == nil {
c.Close()
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
}
conn := &connection{ conn := &connection{
sshConn: sshConn{conn: c}, sshConn: sshConn{conn: c},
} }
@ -173,6 +180,13 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
return NewClient(c, chans, reqs), nil return NewClient(c, chans, reqs), nil
} }
// HostKeyCallback is the function type used for verifying server
// keys. A HostKeyCallback must return nil if the host key is OK, or
// an error to reject it. It receives the hostname as passed to Dial
// or NewClientConn. The remote address is the RemoteAddr of the
// net.Conn underlying the the SSH connection.
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
// A ClientConfig structure is used to configure a Client. It must not be // A ClientConfig structure is used to configure a Client. It must not be
// modified after having been passed to an SSH function. // modified after having been passed to an SSH function.
type ClientConfig struct { type ClientConfig struct {
@ -188,10 +202,12 @@ type ClientConfig struct {
// be used during authentication. // be used during authentication.
Auth []AuthMethod Auth []AuthMethod
// HostKeyCallback, if not nil, is called during the cryptographic // HostKeyCallback is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyCallback // handshake to validate the server's host key. The client
// implies that all host keys are accepted. // configuration must supply this callback for the connection
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error // to succeed. The functions InsecureIgnoreHostKey or
// FixedHostKey can be used for simplistic host key checks.
HostKeyCallback HostKeyCallback
// ClientVersion contains the version identification string that will // ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used. // be used for the connection. If empty, a reasonable default is used.
@ -209,3 +225,33 @@ type ClientConfig struct {
// A Timeout of zero means no timeout. // A Timeout of zero means no timeout.
Timeout time.Duration Timeout time.Duration
} }
// InsecureIgnoreHostKey returns a function that can be used for
// ClientConfig.HostKeyCallback to accept any host key. It should
// not be used for production code.
func InsecureIgnoreHostKey() HostKeyCallback {
return func(hostname string, remote net.Addr, key PublicKey) error {
return nil
}
}
type fixedHostKey struct {
key PublicKey
}
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
if f.key == nil {
return fmt.Errorf("ssh: required host key was nil")
}
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
return fmt.Errorf("ssh: host key mismatch")
}
return nil
}
// FixedHostKey returns a function for use in
// ClientConfig.HostKeyCallback to accept only a specific host key.
func FixedHostKey(key PublicKey) HostKeyCallback {
hk := &fixedHostKey{key}
return hk.check
}

View File

@ -179,31 +179,26 @@ func (cb publicKeyCallback) method() string {
} }
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an // Authentication is performed by sending an enquiry to test if a key is
// enquiry to test if each key is acceptable to the remote. The second // acceptable to the remote. If the key is acceptable, the client will
// stage attempts to authenticate with the valid keys obtained in the // attempt to authenticate with the valid key. If not the client will repeat
// first stage. // the process with the remaining keys.
signers, err := cb() signers, err := cb()
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
var validKeys []Signer var methods []string
for _, signer := range signers { for _, signer := range signers {
if ok, err := validateKey(signer.PublicKey(), user, c); ok { ok, err := validateKey(signer.PublicKey(), user, c)
validKeys = append(validKeys, signer)
} else {
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
} if !ok {
continue
} }
// methods that may continue if this auth is not successful.
var methods []string
for _, signer := range validKeys {
pub := signer.PublicKey() pub := signer.PublicKey()
pubKey := pub.Marshal() pubKey := pub.Marshal()
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
User: user, User: user,
@ -236,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
if success {
// If authentication succeeds or the list of available methods does not
// contain the "publickey" method, do not attempt to authenticate with any
// other keys. According to RFC 4252 Section 7, the latter can occur when
// additional authentication methods are required.
if success || !containsMethod(methods, cb.method()) {
return success, methods, err return success, methods, err
} }
} }
return false, methods, nil return false, methods, nil
} }
func containsMethod(methods []string, method string) bool {
for _, m := range methods {
if m == method {
return true
}
}
return false
}
// validateKey validates the key provided is acceptable to the server. // validateKey validates the key provided is acceptable to the server.
func validateKey(key PublicKey, user string, c packetConn) (bool, error) { func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
pubKey := key.Marshal() pubKey := key.Marshal()

View File

@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io" "io"
"math"
"sync" "sync"
_ "crypto/sha1" _ "crypto/sha1"
@ -40,7 +41,7 @@ var supportedKexAlgos = []string{
kexAlgoDH14SHA1, kexAlgoDH1SHA1, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
} }
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods // supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
// of authenticating servers) in preference order. // of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{ var supportedHostKeyAlgos = []string{
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
@ -186,7 +187,7 @@ type Config struct {
// The maximum number of bytes sent or received after which a // The maximum number of bytes sent or received after which a
// new key is negotiated. It must be at least 256. If // new key is negotiated. It must be at least 256. If
// unspecified, 1 gigabyte is used. // unspecified, a size suitable for the chosen cipher is used.
RekeyThreshold uint64 RekeyThreshold uint64
// The allowed key exchanges algorithms. If unspecified then a // The allowed key exchanges algorithms. If unspecified then a
@ -230,11 +231,12 @@ func (c *Config) SetDefaults() {
} }
if c.RekeyThreshold == 0 { if c.RekeyThreshold == 0 {
// RFC 4253, section 9 suggests rekeying after 1G. // cipher specific default
c.RekeyThreshold = 1 << 30 } else if c.RekeyThreshold < minRekeyThreshold {
}
if c.RekeyThreshold < minRekeyThreshold {
c.RekeyThreshold = minRekeyThreshold c.RekeyThreshold = minRekeyThreshold
} else if c.RekeyThreshold >= math.MaxInt64 {
// Avoid weirdness if somebody uses -1 as a threshold.
c.RekeyThreshold = math.MaxInt64
} }
} }

View File

@ -14,5 +14,8 @@ others.
References: References:
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
This package does not fall under the stability promise of the Go language itself,
so its API may be changed when pressing needs arise.
*/ */
package ssh // import "golang.org/x/crypto/ssh" package ssh // import "golang.org/x/crypto/ssh"

View File

@ -74,7 +74,7 @@ type handshakeTransport struct {
startKex chan *pendingKex startKex chan *pendingKex
// data for host key checking // data for host key checking
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error hostKeyCallback HostKeyCallback
dialAddress string dialAddress string
remoteAddr net.Addr remoteAddr net.Addr
@ -107,6 +107,8 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
config: config, config: config,
} }
t.resetReadThresholds()
t.resetWriteThresholds()
// We always start with a mandatory key exchange. // We always start with a mandatory key exchange.
t.requestKex <- struct{}{} t.requestKex <- struct{}{}
@ -237,6 +239,17 @@ func (t *handshakeTransport) requestKeyExchange() {
} }
} }
func (t *handshakeTransport) resetWriteThresholds() {
t.writePacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
} else {
t.writeBytesLeft = 1 << 30
}
}
func (t *handshakeTransport) kexLoop() { func (t *handshakeTransport) kexLoop() {
write: write:
@ -285,12 +298,8 @@ write:
t.writeError = err t.writeError = err
t.sentInitPacket = nil t.sentInitPacket = nil
t.sentInitMsg = nil t.sentInitMsg = nil
t.writePacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 { t.resetWriteThresholds()
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
}
// we have completed the key exchange. Since the // we have completed the key exchange. Since the
// reader is still blocked, it is safe to clear out // reader is still blocked, it is safe to clear out
@ -344,6 +353,17 @@ write:
// key exchange itself. // key exchange itself.
const packetRekeyThreshold = (1 << 31) const packetRekeyThreshold = (1 << 31)
func (t *handshakeTransport) resetReadThresholds() {
t.readPacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
} else {
t.readBytesLeft = 1 << 30
}
}
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
p, err := t.conn.readPacket() p, err := t.conn.readPacket()
if err != nil { if err != nil {
@ -391,12 +411,7 @@ func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
return nil, err return nil, err
} }
t.readPacketsLeft = packetRekeyThreshold t.resetReadThresholds()
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
}
// By default, a key exchange is hidden from higher layers by // By default, a key exchange is hidden from higher layers by
// translating it into msgIgnore. // translating it into msgIgnore.
@ -574,7 +589,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
} }
result.SessionID = t.sessionID result.SessionID = t.sessionID
t.conn.prepareKeyChange(t.algorithms, result) if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
return err
}
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
return err return err
} }
@ -614,12 +631,10 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
return nil, err return nil, err
} }
if t.hostKeyCallback != nil {
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
return result, nil return result, nil
} }

View File

@ -824,7 +824,7 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
// Implemented based on the documentation at // Implemented based on the documentation at
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
magic := append([]byte("openssh-key-v1"), 0) magic := append([]byte("openssh-key-v1"), 0)
if !bytes.Equal(magic, key[0:len(magic)]) { if !bytes.Equal(magic, key[0:len(magic)]) {
return nil, errors.New("ssh: invalid openssh private key format") return nil, errors.New("ssh: invalid openssh private key format")
@ -844,14 +844,15 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
return nil, err return nil, err
} }
if w.KdfName != "none" || w.CipherName != "none" {
return nil, errors.New("ssh: cannot decode encrypted private keys")
}
pk1 := struct { pk1 := struct {
Check1 uint32 Check1 uint32
Check2 uint32 Check2 uint32
Keytype string Keytype string
Pub []byte Rest []byte `ssh:"rest"`
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{} }{}
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil { if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
@ -862,24 +863,75 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
return nil, errors.New("ssh: checkint mismatch") return nil, errors.New("ssh: checkint mismatch")
} }
// we only handle ed25519 keys currently // we only handle ed25519 and rsa keys currently
if pk1.Keytype != KeyAlgoED25519 { switch pk1.Keytype {
return nil, errors.New("ssh: unhandled key type") case KeyAlgoRSA:
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
key := struct {
N *big.Int
E *big.Int
D *big.Int
Iqmp *big.Int
P *big.Int
Q *big.Int
Comment string
Pad []byte `ssh:"rest"`
}{}
if err := Unmarshal(pk1.Rest, &key); err != nil {
return nil, err
} }
for i, b := range pk1.Pad { for i, b := range key.Pad {
if int(b) != i+1 { if int(b) != i+1 {
return nil, errors.New("ssh: padding not as expected") return nil, errors.New("ssh: padding not as expected")
} }
} }
if len(pk1.Priv) != ed25519.PrivateKeySize { pk := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: key.N,
E: int(key.E.Int64()),
},
D: key.D,
Primes: []*big.Int{key.P, key.Q},
}
if err := pk.Validate(); err != nil {
return nil, err
}
pk.Precompute()
return pk, nil
case KeyAlgoED25519:
key := struct {
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{}
if err := Unmarshal(pk1.Rest, &key); err != nil {
return nil, err
}
if len(key.Priv) != ed25519.PrivateKeySize {
return nil, errors.New("ssh: private key unexpected length") return nil, errors.New("ssh: private key unexpected length")
} }
for i, b := range key.Pad {
if int(b) != i+1 {
return nil, errors.New("ssh: padding not as expected")
}
}
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
copy(pk, pk1.Priv) copy(pk, key.Priv)
return &pk, nil return &pk, nil
default:
return nil, errors.New("ssh: unhandled key type")
}
} }
// FingerprintLegacyMD5 returns the user presentation of the key's // FingerprintLegacyMD5 returns the user presentation of the key's

View File

@ -45,6 +45,12 @@ type ServerConfig struct {
// authenticating. // authenticating.
NoClientAuth bool NoClientAuth bool
// MaxAuthTries specifies the maximum number of authentication attempts
// permitted per connection. If set to a negative number, the number of
// attempts are unlimited. If set to zero, the number of attempts are limited
// to 6.
MaxAuthTries int
// PasswordCallback, if non-nil, is called when a user // PasswordCallback, if non-nil, is called when a user
// attempts to authenticate using a password. // attempts to authenticate using a password.
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
@ -141,6 +147,10 @@ type ServerConn struct {
// Request and NewChannel channels must be serviced, or the connection // Request and NewChannel channels must be serviced, or the connection
// will hang. // will hang.
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
if config.MaxAuthTries == 0 {
config.MaxAuthTries = 6
}
fullConf := *config fullConf := *config
fullConf.SetDefaults() fullConf.SetDefaults()
s := &connection{ s := &connection{
@ -267,8 +277,23 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
var cache pubKeyCache var cache pubKeyCache
var perms *Permissions var perms *Permissions
authFailures := 0
userAuthLoop: userAuthLoop:
for { for {
if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 {
discMsg := &disconnectMsg{
Reason: 2,
Message: "too many authentication failures",
}
if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
return nil, err
}
return nil, discMsg
}
var userAuthReq userAuthRequestMsg var userAuthReq userAuthRequestMsg
if packet, err := s.transport.readPacket(); err != nil { if packet, err := s.transport.readPacket(); err != nil {
return nil, err return nil, err
@ -289,6 +314,11 @@ userAuthLoop:
if config.NoClientAuth { if config.NoClientAuth {
authErr = nil authErr = nil
} }
// allow initial attempt of 'none' without penalty
if authFailures == 0 {
authFailures--
}
case "password": case "password":
if config.PasswordCallback == nil { if config.PasswordCallback == nil {
authErr = errors.New("ssh: password auth not configured") authErr = errors.New("ssh: password auth not configured")
@ -360,6 +390,7 @@ userAuthLoop:
if isQuery { if isQuery {
// The client can query if the given public key // The client can query if the given public key
// would be okay. // would be okay.
if len(payload) > 0 { if len(payload) > 0 {
return nil, parseError(msgUserAuthRequest) return nil, parseError(msgUserAuthRequest)
} }
@ -409,6 +440,8 @@ userAuthLoop:
break userAuthLoop break userAuthLoop
} }
authFailures++
var failureMsg userAuthFailureMsg var failureMsg userAuthFailureMsg
if config.PasswordCallback != nil { if config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password") failureMsg.Methods = append(failureMsg.Methods, "password")

115
vendor/golang.org/x/crypto/ssh/streamlocal.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
package ssh
import (
"errors"
"io"
"net"
)
// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
// with "direct-streamlocal@openssh.com" string.
//
// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
type streamLocalChannelOpenDirectMsg struct {
socketPath string
reserved0 string
reserved1 uint32
}
// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
// with "forwarded-streamlocal@openssh.com" string.
type forwardedStreamLocalPayload struct {
SocketPath string
Reserved0 string
}
// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
type streamLocalChannelForwardMsg struct {
socketPath string
}
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
m := streamLocalChannelForwardMsg{
socketPath,
}
// send message
ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
}
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
return &unixListener{socketPath, c, ch}, nil
}
func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
msg := streamLocalChannelOpenDirectMsg{
socketPath: socketPath,
}
ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
if err != nil {
return nil, err
}
go DiscardRequests(in)
return ch, err
}
type unixListener struct {
socketPath string
conn *Client
in <-chan forward
}
// Accept waits for and returns the next connection to the listener.
func (l *unixListener) Accept() (net.Conn, error) {
s, ok := <-l.in
if !ok {
return nil, io.EOF
}
ch, incoming, err := s.newCh.Accept()
if err != nil {
return nil, err
}
go DiscardRequests(incoming)
return &chanConn{
Channel: ch,
laddr: &net.UnixAddr{
Name: l.socketPath,
Net: "unix",
},
raddr: &net.UnixAddr{
Name: "@",
Net: "unix",
},
}, nil
}
// Close closes the listener.
func (l *unixListener) Close() error {
// this also closes the listener.
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
m := streamLocalChannelForwardMsg{
l.socketPath,
}
ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
if err == nil && !ok {
err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
}
return err
}
// Addr returns the listener's network address.
func (l *unixListener) Addr() net.Addr {
return &net.UnixAddr{
Name: l.socketPath,
Net: "unix",
}
}

View File

@ -20,12 +20,20 @@ import (
// addr. Incoming connections will be available by calling Accept on // addr. Incoming connections will be available by calling Accept on
// the returned net.Listener. The listener must be serviced, or the // the returned net.Listener. The listener must be serviced, or the
// SSH connection may hang. // SSH connection may hang.
// N must be "tcp", "tcp4", "tcp6", or "unix".
func (c *Client) Listen(n, addr string) (net.Listener, error) { func (c *Client) Listen(n, addr string) (net.Listener, error) {
switch n {
case "tcp", "tcp4", "tcp6":
laddr, err := net.ResolveTCPAddr(n, addr) laddr, err := net.ResolveTCPAddr(n, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.ListenTCP(laddr) return c.ListenTCP(laddr)
case "unix":
return c.ListenUnix(addr)
default:
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
}
} }
// Automatic port allocation is broken with OpenSSH before 6.0. See // Automatic port allocation is broken with OpenSSH before 6.0. See
@ -116,7 +124,7 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
} }
// Register this forward, using the port number we obtained. // Register this forward, using the port number we obtained.
ch := c.forwards.add(*laddr) ch := c.forwards.add(laddr)
return &tcpListener{laddr, c, ch}, nil return &tcpListener{laddr, c, ch}, nil
} }
@ -131,7 +139,7 @@ type forwardList struct {
// forwardEntry represents an established mapping of a laddr on a // forwardEntry represents an established mapping of a laddr on a
// remote ssh server to a channel connected to a tcpListener. // remote ssh server to a channel connected to a tcpListener.
type forwardEntry struct { type forwardEntry struct {
laddr net.TCPAddr laddr net.Addr
c chan forward c chan forward
} }
@ -140,15 +148,15 @@ type forwardEntry struct {
// the original forward-request. // the original forward-request.
type forward struct { type forward struct {
newCh NewChannel // the ssh client channel underlying this forward newCh NewChannel // the ssh client channel underlying this forward
raddr *net.TCPAddr // the raddr of the incoming connection raddr net.Addr // the raddr of the incoming connection
} }
func (l *forwardList) add(addr net.TCPAddr) chan forward { func (l *forwardList) add(addr net.Addr) chan forward {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
f := forwardEntry{ f := forwardEntry{
addr, laddr: addr,
make(chan forward, 1), c: make(chan forward, 1),
} }
l.entries = append(l.entries, f) l.entries = append(l.entries, f)
return f.c return f.c
@ -176,8 +184,15 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
func (l *forwardList) handleChannels(in <-chan NewChannel) { func (l *forwardList) handleChannels(in <-chan NewChannel) {
for ch := range in { for ch := range in {
var (
laddr net.Addr
raddr net.Addr
err error
)
switch channelType := ch.ChannelType(); channelType {
case "forwarded-tcpip":
var payload forwardedTCPPayload var payload forwardedTCPPayload
if err := Unmarshal(ch.ExtraData(), &payload); err != nil { if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
continue continue
} }
@ -187,33 +202,51 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
// format. It is implied that this should be an IP // format. It is implied that this should be an IP
// address, as it would be impossible to connect to it // address, as it would be impossible to connect to it
// otherwise. // otherwise.
laddr, err := parseTCPAddr(payload.Addr, payload.Port) laddr, err = parseTCPAddr(payload.Addr, payload.Port)
if err != nil { if err != nil {
ch.Reject(ConnectionFailed, err.Error()) ch.Reject(ConnectionFailed, err.Error())
continue continue
} }
raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort) raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
if err != nil { if err != nil {
ch.Reject(ConnectionFailed, err.Error()) ch.Reject(ConnectionFailed, err.Error())
continue continue
} }
if ok := l.forward(*laddr, *raddr, ch); !ok { case "forwarded-streamlocal@openssh.com":
var payload forwardedStreamLocalPayload
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
continue
}
laddr = &net.UnixAddr{
Name: payload.SocketPath,
Net: "unix",
}
raddr = &net.UnixAddr{
Name: "@",
Net: "unix",
}
default:
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
}
if ok := l.forward(laddr, raddr, ch); !ok {
// Section 7.2, implementations MUST reject spurious incoming // Section 7.2, implementations MUST reject spurious incoming
// connections. // connections.
ch.Reject(Prohibited, "no forward for address") ch.Reject(Prohibited, "no forward for address")
continue continue
} }
} }
} }
// remove removes the forward entry, and the channel feeding its // remove removes the forward entry, and the channel feeding its
// listener. // listener.
func (l *forwardList) remove(addr net.TCPAddr) { func (l *forwardList) remove(addr net.Addr) {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
for i, f := range l.entries { for i, f := range l.entries {
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
l.entries = append(l.entries[:i], l.entries[i+1:]...) l.entries = append(l.entries[:i], l.entries[i+1:]...)
close(f.c) close(f.c)
return return
@ -231,12 +264,12 @@ func (l *forwardList) closeAll() {
l.entries = nil l.entries = nil
} }
func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool { func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
for _, f := range l.entries { for _, f := range l.entries {
if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port { if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
f.c <- forward{ch, &raddr} f.c <- forward{newCh: ch, raddr: raddr}
return true return true
} }
} }
@ -262,7 +295,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
} }
go DiscardRequests(incoming) go DiscardRequests(incoming)
return &tcpChanConn{ return &chanConn{
Channel: ch, Channel: ch,
laddr: l.laddr, laddr: l.laddr,
raddr: s.raddr, raddr: s.raddr,
@ -277,7 +310,7 @@ func (l *tcpListener) Close() error {
} }
// this also closes the listener. // this also closes the listener.
l.conn.forwards.remove(*l.laddr) l.conn.forwards.remove(l.laddr)
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
if err == nil && !ok { if err == nil && !ok {
err = errors.New("ssh: cancel-tcpip-forward failed") err = errors.New("ssh: cancel-tcpip-forward failed")
@ -293,6 +326,9 @@ func (l *tcpListener) Addr() net.Addr {
// Dial initiates a connection to the addr from the remote host. // Dial initiates a connection to the addr from the remote host.
// The resulting connection has a zero LocalAddr() and RemoteAddr(). // The resulting connection has a zero LocalAddr() and RemoteAddr().
func (c *Client) Dial(n, addr string) (net.Conn, error) { func (c *Client) Dial(n, addr string) (net.Conn, error) {
var ch Channel
switch n {
case "tcp", "tcp4", "tcp6":
// Parse the address into host and numeric port. // Parse the address into host and numeric port.
host, portString, err := net.SplitHostPort(addr) host, portString, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
@ -302,20 +338,40 @@ func (c *Client) Dial(n, addr string) (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port))
if err != nil {
return nil, err
}
// Use a zero address for local and remote address. // Use a zero address for local and remote address.
zeroAddr := &net.TCPAddr{ zeroAddr := &net.TCPAddr{
IP: net.IPv4zero, IP: net.IPv4zero,
Port: 0, Port: 0,
} }
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port)) return &chanConn{
if err != nil {
return nil, err
}
return &tcpChanConn{
Channel: ch, Channel: ch,
laddr: zeroAddr, laddr: zeroAddr,
raddr: zeroAddr, raddr: zeroAddr,
}, nil }, nil
case "unix":
var err error
ch, err = c.dialStreamLocal(addr)
if err != nil {
return nil, err
}
return &chanConn{
Channel: ch,
laddr: &net.UnixAddr{
Name: "@",
Net: "unix",
},
raddr: &net.UnixAddr{
Name: addr,
Net: "unix",
},
}, nil
default:
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
}
} }
// DialTCP connects to the remote address raddr on the network net, // DialTCP connects to the remote address raddr on the network net,
@ -332,7 +388,7 @@ func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &tcpChanConn{ return &chanConn{
Channel: ch, Channel: ch,
laddr: laddr, laddr: laddr,
raddr: raddr, raddr: raddr,
@ -366,26 +422,26 @@ type tcpChan struct {
Channel // the backing channel Channel // the backing channel
} }
// tcpChanConn fulfills the net.Conn interface without // chanConn fulfills the net.Conn interface without
// the tcpChan having to hold laddr or raddr directly. // the tcpChan having to hold laddr or raddr directly.
type tcpChanConn struct { type chanConn struct {
Channel Channel
laddr, raddr net.Addr laddr, raddr net.Addr
} }
// LocalAddr returns the local network address. // LocalAddr returns the local network address.
func (t *tcpChanConn) LocalAddr() net.Addr { func (t *chanConn) LocalAddr() net.Addr {
return t.laddr return t.laddr
} }
// RemoteAddr returns the remote network address. // RemoteAddr returns the remote network address.
func (t *tcpChanConn) RemoteAddr() net.Addr { func (t *chanConn) RemoteAddr() net.Addr {
return t.raddr return t.raddr
} }
// SetDeadline sets the read and write deadlines associated // SetDeadline sets the read and write deadlines associated
// with the connection. // with the connection.
func (t *tcpChanConn) SetDeadline(deadline time.Time) error { func (t *chanConn) SetDeadline(deadline time.Time) error {
if err := t.SetReadDeadline(deadline); err != nil { if err := t.SetReadDeadline(deadline); err != nil {
return err return err
} }
@ -396,12 +452,14 @@ func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
// A zero value for t means Read will not time out. // A zero value for t means Read will not time out.
// After the deadline, the error from Read will implement net.Error // After the deadline, the error from Read will implement net.Error
// with Timeout() == true. // with Timeout() == true.
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error { func (t *chanConn) SetReadDeadline(deadline time.Time) error {
// for compatibility with previous version,
// the error message contains "tcpChan"
return errors.New("ssh: tcpChan: deadline not supported") return errors.New("ssh: tcpChan: deadline not supported")
} }
// SetWriteDeadline exists to satisfy the net.Conn interface // SetWriteDeadline exists to satisfy the net.Conn interface
// but is not implemented by this type. It always returns an error. // but is not implemented by this type. It always returns an error.
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error { func (t *chanConn) SetWriteDeadline(deadline time.Time) error {
return errors.New("ssh: tcpChan: deadline not supported") return errors.New("ssh: tcpChan: deadline not supported")
} }

View File

@ -14,14 +14,12 @@ import (
// State contains the state of a terminal. // State contains the state of a terminal.
type State struct { type State struct {
termios syscall.Termios state *unix.Termios
} }
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool { func IsTerminal(fd int) bool {
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c _, err := unix.IoctlGetTermio(fd, unix.TCGETA)
var termio unix.Termio
err := unix.IoctlSetTermio(fd, unix.TCGETA, &termio)
return err == nil return err == nil
} }
@ -71,3 +69,60 @@ func ReadPassword(fd int) ([]byte, error) {
return ret, nil return ret, nil
} }
// MakeRaw puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
// see http://cr.illumos.org/~webrev/andy_js/1060/
func MakeRaw(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
oldTermios := *oldTermiosPtr
newTermios := oldTermios
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
newTermios.Oflag &^= syscall.OPOST
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
newTermios.Cflag |= syscall.CS8
newTermios.Cc[unix.VMIN] = 1
newTermios.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, oldState *State) error {
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (width, height int, err error) {
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}

66
vendor/vendor.json vendored
View File

@ -384,92 +384,92 @@
{ {
"checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=",
"path": "golang.org/x/crypto/cast5", "path": "golang.org/x/crypto/cast5",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "C1KKOxFoW7/W/NFNpiXK+boguNo=", "checksumSHA1": "nAu0XmCeC6WnUySyI8R7w4cxAqU=",
"path": "golang.org/x/crypto/curve25519", "path": "golang.org/x/crypto/curve25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=",
"path": "golang.org/x/crypto/ed25519", "path": "golang.org/x/crypto/ed25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
"path": "golang.org/x/crypto/ed25519/internal/edwards25519", "path": "golang.org/x/crypto/ed25519/internal/edwards25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "IIhFTrLlmlc6lEFSitqi4aw2lw0=", "checksumSHA1": "IIhFTrLlmlc6lEFSitqi4aw2lw0=",
"path": "golang.org/x/crypto/openpgp", "path": "golang.org/x/crypto/openpgp",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "olOKkhrdkYQHZ0lf1orrFQPQrv4=", "checksumSHA1": "olOKkhrdkYQHZ0lf1orrFQPQrv4=",
"path": "golang.org/x/crypto/openpgp/armor", "path": "golang.org/x/crypto/openpgp/armor",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "eo/KtdjieJQXH7Qy+faXFcF70ME=", "checksumSHA1": "eo/KtdjieJQXH7Qy+faXFcF70ME=",
"path": "golang.org/x/crypto/openpgp/elgamal", "path": "golang.org/x/crypto/openpgp/elgamal",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "rlxVSaGgqdAgwblsErxTxIfuGfg=", "checksumSHA1": "rlxVSaGgqdAgwblsErxTxIfuGfg=",
"path": "golang.org/x/crypto/openpgp/errors", "path": "golang.org/x/crypto/openpgp/errors",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "LWdaR8Q9yn6eBCcnGl0HvJRDUBE=", "checksumSHA1": "LWdaR8Q9yn6eBCcnGl0HvJRDUBE=",
"path": "golang.org/x/crypto/openpgp/packet", "path": "golang.org/x/crypto/openpgp/packet",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "s2qT4UwvzBSkzXuiuMkowif1Olw=", "checksumSHA1": "s2qT4UwvzBSkzXuiuMkowif1Olw=",
"path": "golang.org/x/crypto/openpgp/s2k", "path": "golang.org/x/crypto/openpgp/s2k",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=", "checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=",
"path": "golang.org/x/crypto/pbkdf2", "path": "golang.org/x/crypto/pbkdf2",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "y/oIaxq2d3WPizRZfVjo8RCRYTU=", "checksumSHA1": "y/oIaxq2d3WPizRZfVjo8RCRYTU=",
"path": "golang.org/x/crypto/ripemd160", "path": "golang.org/x/crypto/ripemd160",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "E8pDMGySfy5Mw+jzXOkOxo35bww=", "checksumSHA1": "E8pDMGySfy5Mw+jzXOkOxo35bww=",
"path": "golang.org/x/crypto/scrypt", "path": "golang.org/x/crypto/scrypt",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=", "checksumSHA1": "8sVsMTphul+B0sI0qAv4TE1ZxUk=",
"path": "golang.org/x/crypto/ssh", "path": "golang.org/x/crypto/ssh",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "xiderUuvye8Kpn7yX3niiJg32bE=", "checksumSHA1": "ZaU56svwLgiJD0y8JOB3+/mpYBA=",
"path": "golang.org/x/crypto/ssh/terminal", "path": "golang.org/x/crypto/ssh/terminal",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=",