forked from cerc-io/plugeth
cmd/puppeth, vendor: update ssh, manage server keys (#14398)
This commit is contained in:
parent
59966255ad
commit
cf4faa491a
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -31,7 +31,10 @@ import (
|
|||||||
// makeWizard creates and returns a new puppeth wizard.
|
// makeWizard creates and returns a new puppeth wizard.
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -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(), "", ""})
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
131
vendor/golang.org/x/crypto/curve25519/cswap_amd64.s
generated
vendored
131
vendor/golang.org/x/crypto/curve25519/cswap_amd64.s
generated
vendored
@ -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
|
||||||
|
23
vendor/golang.org/x/crypto/curve25519/curve25519.go
generated
vendored
23
vendor/golang.org/x/crypto/curve25519/curve25519.go
generated
vendored
@ -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) {
|
||||||
|
2
vendor/golang.org/x/crypto/ssh/certs.go
generated
vendored
2
vendor/golang.org/x/crypto/ssh/certs.go
generated
vendored
@ -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
|
||||||
|
56
vendor/golang.org/x/crypto/ssh/client.go
generated
vendored
56
vendor/golang.org/x/crypto/ssh/client.go
generated
vendored
@ -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
|
||||||
|
}
|
||||||
|
49
vendor/golang.org/x/crypto/ssh/client_auth.go
generated
vendored
49
vendor/golang.org/x/crypto/ssh/client_auth.go
generated
vendored
@ -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
|
|
||||||
for _, signer := range signers {
|
|
||||||
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
|
|
||||||
validKeys = append(validKeys, signer)
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods that may continue if this auth is not successful.
|
|
||||||
var methods []string
|
var methods []string
|
||||||
for _, signer := range validKeys {
|
for _, signer := range signers {
|
||||||
pub := signer.PublicKey()
|
ok, err := validateKey(signer.PublicKey(), user, c)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
14
vendor/golang.org/x/crypto/ssh/common.go
generated
vendored
14
vendor/golang.org/x/crypto/ssh/common.go
generated
vendored
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
vendor/golang.org/x/crypto/ssh/doc.go
generated
vendored
3
vendor/golang.org/x/crypto/ssh/doc.go
generated
vendored
@ -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"
|
||||||
|
53
vendor/golang.org/x/crypto/ssh/handshake.go
generated
vendored
53
vendor/golang.org/x/crypto/ssh/handshake.go
generated
vendored
@ -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,11 +631,9 @@ 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
|
||||||
|
94
vendor/golang.org/x/crypto/ssh/keys.go
generated
vendored
94
vendor/golang.org/x/crypto/ssh/keys.go
generated
vendored
@ -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 {
|
||||||
|
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 key.Pad {
|
||||||
|
if int(b) != i+1 {
|
||||||
|
return nil, errors.New("ssh: padding not as expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
copy(pk, key.Priv)
|
||||||
|
return &pk, nil
|
||||||
|
default:
|
||||||
return nil, errors.New("ssh: unhandled key type")
|
return nil, errors.New("ssh: unhandled key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, b := range pk1.Pad {
|
|
||||||
if int(b) != i+1 {
|
|
||||||
return nil, errors.New("ssh: padding not as expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pk1.Priv) != ed25519.PrivateKeySize {
|
|
||||||
return nil, errors.New("ssh: private key unexpected length")
|
|
||||||
}
|
|
||||||
|
|
||||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
|
||||||
copy(pk, pk1.Priv)
|
|
||||||
return &pk, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FingerprintLegacyMD5 returns the user presentation of the key's
|
// FingerprintLegacyMD5 returns the user presentation of the key's
|
||||||
|
33
vendor/golang.org/x/crypto/ssh/server.go
generated
vendored
33
vendor/golang.org/x/crypto/ssh/server.go
generated
vendored
@ -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
115
vendor/golang.org/x/crypto/ssh/streamlocal.go
generated
vendored
Normal 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",
|
||||||
|
}
|
||||||
|
}
|
196
vendor/golang.org/x/crypto/ssh/tcpip.go
generated
vendored
196
vendor/golang.org/x/crypto/ssh/tcpip.go
generated
vendored
@ -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) {
|
||||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
switch n {
|
||||||
if err != nil {
|
case "tcp", "tcp4", "tcp6":
|
||||||
return nil, err
|
laddr, err := net.ResolveTCPAddr(n, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.ListenTCP(laddr)
|
||||||
|
case "unix":
|
||||||
|
return c.ListenUnix(addr)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
|
||||||
}
|
}
|
||||||
return c.ListenTCP(laddr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,16 +147,16 @@ type forwardEntry struct {
|
|||||||
// arguments to add/remove/lookup should be address as specified in
|
// arguments to add/remove/lookup should be address as specified in
|
||||||
// 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,44 +184,69 @@ 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 payload forwardedTCPPayload
|
var (
|
||||||
if err := Unmarshal(ch.ExtraData(), &payload); err != nil {
|
laddr net.Addr
|
||||||
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
raddr net.Addr
|
||||||
continue
|
err error
|
||||||
}
|
)
|
||||||
|
switch channelType := ch.ChannelType(); channelType {
|
||||||
|
case "forwarded-tcpip":
|
||||||
|
var payload forwardedTCPPayload
|
||||||
|
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||||
|
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// RFC 4254 section 7.2 specifies that incoming
|
// RFC 4254 section 7.2 specifies that incoming
|
||||||
// addresses should list the address, in string
|
// addresses should list the address, in string
|
||||||
// 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,29 +326,52 @@ 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) {
|
||||||
// Parse the address into host and numeric port.
|
var ch Channel
|
||||||
host, portString, err := net.SplitHostPort(addr)
|
switch n {
|
||||||
if err != nil {
|
case "tcp", "tcp4", "tcp6":
|
||||||
return nil, err
|
// Parse the address into host and numeric port.
|
||||||
|
host, portString, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
port, err := strconv.ParseUint(portString, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
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.
|
||||||
|
zeroAddr := &net.TCPAddr{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Port: 0,
|
||||||
|
}
|
||||||
|
return &chanConn{
|
||||||
|
Channel: ch,
|
||||||
|
laddr: zeroAddr,
|
||||||
|
raddr: zeroAddr,
|
||||||
|
}, 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)
|
||||||
}
|
}
|
||||||
port, err := strconv.ParseUint(portString, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Use a zero address for local and remote address.
|
|
||||||
zeroAddr := &net.TCPAddr{
|
|
||||||
IP: net.IPv4zero,
|
|
||||||
Port: 0,
|
|
||||||
}
|
|
||||||
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &tcpChanConn{
|
|
||||||
Channel: ch,
|
|
||||||
laddr: zeroAddr,
|
|
||||||
raddr: zeroAddr,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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")
|
||||||
}
|
}
|
||||||
|
63
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
63
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
@ -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
66
vendor/vendor.json
vendored
@ -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=",
|
||||||
|
Loading…
Reference in New Issue
Block a user