// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file. package ssh import ( "bytes" "errors" "fmt" "io" "net") // The Permissions type holds fine-grained permissions that are// specific to a user or a specific authentication method for a// user. Permissions, except for "source-address", must be enforced in// the server application layer, after successful authentication. The// Permissions are passed on in ServerConn so a server implementation// can honor them.struct Permissions { // Critical options restrict default permissions. Common // restrictions are "source-address" and "force-command". If // the server cannot enforce the restriction, or does not // recognize it, the user should not authenticate. CriticalOptions map[string]string // Extensions are extra functionality that the server may // offer on authenticated connections. Common extensions are // "permit-agent-forwarding", "permit-X11-forwarding". Lack of // support for an extension does not preclude authenticating a // user. Extensions map[string]string} // ServerConfig holds server specific configuration data.struct ServerConfig { // Config contains configuration shared between client and server. Config hostKeys []Signer // NoClientAuth is true if clients are allowed to connect without // authenticating. NoClientAuth bool // PasswordCallback, if non-nil, is called when a user // attempts to authenticate using a password. PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) // PublicKeyCallback, if non-nil, is called when a client attempts public // key authentication. It must return true if the given public key is // valid for the given user. For example, see CertChecker.Authenticate. PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) // KeyboardInteractiveCallback, if non-nil, is called when // keyboard-interactive authentication is selected (RFC // 4256). The client object's Challenge function should be // used to query the user. The callback may offer multiple // Challenge rounds. To avoid information leaks, the client // should be presented a challenge even if the user is // unknown. KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) // AuthLogCallback, if non-nil, is called to log all authentication // attempts. AuthLogCallback func(conn ConnMetadata, method string, err error)} // AddHostKey adds a private key as a host key. If an existing host// key exists with the same algorithm, it is overwritten. Each server// config must have at least one host key.func (s *ServerConfig) AddHostKey(key Signer) { for i, k := range s.hostKeys { if k.PublicKey().Type() == key.PublicKey().Type() { s.hostKeys[i] = key return } } s.hostKeys = append(s.hostKeys, key)} // cachedPubKey contains the results of querying whether a public key is// acceptable for a user.struct cachedPubKey { user string pubKeyData []byte result error perms *Permissions} const maxCachedPubKeys = 16 // pubKeyCache caches tests for public keys. Since SSH clients// will query whether a public key is acceptable before attempting to// authenticate with it, we end up with duplicate queries for public// key validity. The cache only applies to a single ServerConn.struct pubKeyCache { keys []cachedPubKey} // get returns the result for a given user/algo/key tuple.func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { for _, k := range c.keys { if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { return k, true } } return cachedPubKey{}, false} // add adds the given tuple to the cache.func (c *pubKeyCache) add(candidate cachedPubKey) { if len(c.keys) < maxCachedPubKeys { c.keys = append(c.keys, candidate) }} // ServerConn is an authenticated SSH connection, as seen from the// serverstruct ServerConn { Conn // If the succeeding authentication callback returned a // non-nil Permissions pointer, it is stored here. Permissions *Permissions} // NewServerConn starts a new SSH server with c as the underlying// transport. It starts with a handshake and, if the handshake is// unsuccessful, it closes the connection and returns an error. The// Request and NewChannel channels must be serviced, or the connection// will hang.func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { fullConf := *config fullConf.SetDefaults() s := &connection{ sshConn: sshConn{conn: c}, } perms, err := s.serverHandshake(&fullConf) if err != nil { c.Close() return nil, nil, nil, err } return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil} // signAndMarshal signs the data with the appropriate algorithm,// and serializes the result in SSH wire format.func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { sig, err := k.Sign(rand, data) if err != nil { return nil, err } return Marshal(sig), nil} // handshake performs key exchange and user authentication.func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { if len(config.hostKeys) == 0 { return nil, errors.New("ssh: server has no host keys") } var err error s.serverVersion = []byte(packageVersion) s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) if err != nil { return nil, err } tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) if err := s.transport.requestKeyChange(); err != nil { return nil, err } if packet, err := s.transport.readPacket(); err != nil { return nil, err } else if packet[0] != msgNewKeys { return nil, unexpectedMessageError(msgNewKeys, packet[0]) } var packet []byte if packet, err = s.transport.readPacket(); err != nil { return nil, err } var serviceRequest serviceRequestMsg if err = Unmarshal(packet, &serviceRequest); err != nil { return nil, err } if serviceRequest.Service != serviceUserAuth { return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") } serviceAccept := serviceAcceptMsg{ Service: serviceUserAuth, } if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { return nil, err } perms, err := s.serverAuthenticate(config) if err != nil { return nil, err } s.mux = newMux(s.transport) return perms, err} func isAcceptableAlgo(algo string) bool { switch algo { case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: return true } return false} func checkSourceAddress(addr net.Addr, sourceAddr string) error { if addr == nil { return errors.New("ssh: no address known for client, but source-address match required") } tcpAddr, ok := addr.(*net.TCPAddr) if !ok { return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) } if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { if bytes.Equal(allowedIP, tcpAddr.IP) { return nil } } else { _, ipNet, err := net.ParseCIDR(sourceAddr) if err != nil { return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) } if ipNet.Contains(tcpAddr.IP) { return nil } } return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)} func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { var err error var cache pubKeyCache var perms *Permissions userAuthLoop: for { var userAuthReq userAuthRequestMsg if packet, err := s.transport.readPacket(); err != nil { return nil, err } else if err = Unmarshal(packet, &userAuthReq); err != nil { return nil, err } if userAuthReq.Service != serviceSSH { return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) } s.user = userAuthReq.User perms = nil authErr := errors.New("no auth passed yet") switch userAuthReq.Method { case "none": if config.NoClientAuth { s.user = "" authErr = nil } case "password": if config.PasswordCallback == nil { authErr = errors.New("ssh: password auth not configured") break } payload := userAuthReq.Payload if len(payload) < 1 || payload[0] != 0 { return nil, parseError(msgUserAuthRequest) } payload = payload[1:] password, payload, ok := parseString(payload) if !ok || len(payload) > 0 { return nil, parseError(msgUserAuthRequest) } perms, authErr = config.PasswordCallback(s, password) case "keyboard-interactive": if config.KeyboardInteractiveCallback == nil { authErr = errors.New("ssh: keyboard-interactive auth not configubred") break } prompter := &sshClientKeyboardInteractive{s} perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) case "publickey": if config.PublicKeyCallback == nil { authErr = errors.New("ssh: publickey auth not configured") break } payload := userAuthReq.Payload if len(payload) < 1 { return nil, parseError(msgUserAuthRequest) } isQuery := payload[0] == 0 payload = payload[1:] algoBytes, payload, ok := parseString(payload) if !ok { return nil, parseError(msgUserAuthRequest) } algo := string(algoBytes) if !isAcceptableAlgo(algo) { authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) break } pubKeyData, payload, ok := parseString(payload) if !ok { return nil, parseError(msgUserAuthRequest) } pubKey, err := ParsePublicKey(pubKeyData) if err != nil { return nil, err } candidate, ok := cache.get(s.user, pubKeyData) if !ok { candidate.user = s.user candidate.pubKeyData = pubKeyData candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { candidate.result = checkSourceAddress( s.RemoteAddr(), candidate.perms.CriticalOptions[sourceAddressCriticalOption]) } cache.add(candidate) } if isQuery { // The client can query if the given public key // would be okay. if len(payload) > 0 { return nil, parseError(msgUserAuthRequest) } if candidate.result == nil { okMsg := userAuthPubKeyOkMsg{ Algo: algo, PubKey: pubKeyData, } if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { return nil, err } continue userAuthLoop } authErr = candidate.result } else { sig, payload, ok := parseSignature(payload) if !ok || len(payload) > 0 { return nil, parseError(msgUserAuthRequest) } // Ensure the public key algo and signature algo // are supported. Compare the private key // algorithm name that corresponds to algo with // sig.Format. This is usually the same, but // for certs, the names differ. if !isAcceptableAlgo(sig.Format) { break } signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData) if err := pubKey.Verify(signedData, sig); err != nil { return nil, err } authErr = candidate.result perms = candidate.perms } default: authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) } if config.AuthLogCallback != nil { config.AuthLogCallback(s, userAuthReq.Method, authErr) } if authErr == nil { break userAuthLoop } var failureMsg userAuthFailureMsg if config.PasswordCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "password") } if config.PublicKeyCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "publickey") } if config.KeyboardInteractiveCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") } if len(failureMsg.Methods) == 0 { return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") } if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil { return nil, err } } if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { return nil, err } return perms, nil} // sshClientKeyboardInteractive implements a ClientKeyboardInteractive by// asking the client on the other side of a ServerConn.struct sshClientKeyboardInteractive { *connection} func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { if len(questions) != len(echos) { return nil, errors.New("ssh: echos and questions must have equal length") } var prompts []byte for i := range questions { prompts = appendString(prompts, questions[i]) prompts = appendBool(prompts, echos[i]) } if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ Instruction: instruction, NumPrompts: uint32(len(questions)), Prompts: prompts, })); err != nil { return nil, err } packet, err := c.transport.readPacket() if err != nil { return nil, err } if packet[0] != msgUserAuthInfoResponse { return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) } packet = packet[1:] n, packet, ok := parseUint32(packet) if !ok || int(n) != len(questions) { return nil, parseError(msgUserAuthInfoResponse) } for i := uint32(0); i < n; i++ { ans, rest, ok := parseString(packet) if !ok { return nil, parseError(msgUserAuthInfoResponse) } answers = append(answers, string(ans)) packet = rest } if len(packet) != 0 { return nil, errors.New("ssh: junk at end of message") } return answers, nil}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssh
import (
"bytes"
"errors"
"fmt"
"io"
"net"
)
// The Permissions type holds fine-grained permissions that are
// specific to a user or a specific authentication method for a
// user. Permissions, except for "source-address", must be enforced in
// the server application layer, after successful authentication. The
// Permissions are passed on in ServerConn so a server implementation
// can honor them.
struct Permissions {
// Critical options restrict default permissions. Common
// restrictions are "source-address" and "force-command". If
// the server cannot enforce the restriction, or does not
// recognize it, the user should not authenticate.
CriticalOptions map[string]string
// Extensions are extra functionality that the server may
// offer on authenticated connections. Common extensions are
// "permit-agent-forwarding", "permit-X11-forwarding". Lack of
// support for an extension does not preclude authenticating a
// user.
Extensions map[string]string
}
// ServerConfig holds server specific configuration data.
struct ServerConfig {
// Config contains configuration shared between client and server.
Config
hostKeys []Signer
// NoClientAuth is true if clients are allowed to connect without
// authenticating.
NoClientAuth bool
// PasswordCallback, if non-nil, is called when a user
// attempts to authenticate using a password.
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
// PublicKeyCallback, if non-nil, is called when a client attempts public
// key authentication. It must return true if the given public key is
// valid for the given user. For example, see CertChecker.Authenticate.
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
// KeyboardInteractiveCallback, if non-nil, is called when
// keyboard-interactive authentication is selected (RFC
// 4256). The client object's Challenge function should be
// used to query the user. The callback may offer multiple
// Challenge rounds. To avoid information leaks, the client
// should be presented a challenge even if the user is
// unknown.
KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
// AuthLogCallback, if non-nil, is called to log all authentication
// attempts.
AuthLogCallback func(conn ConnMetadata, method string, err error)
}
// AddHostKey adds a private key as a host key. If an existing host
// key exists with the same algorithm, it is overwritten. Each server
// config must have at least one host key.
func (s *ServerConfig) AddHostKey(key Signer) {
for i, k := range s.hostKeys {
if k.PublicKey().Type() == key.PublicKey().Type() {
s.hostKeys[i] = key
return
}
}
s.hostKeys = append(s.hostKeys, key)
}
// cachedPubKey contains the results of querying whether a public key is
// acceptable for a user.
struct cachedPubKey {
user string
pubKeyData []byte
result error
perms *Permissions
}
const maxCachedPubKeys = 16
// pubKeyCache caches tests for public keys. Since SSH clients
// will query whether a public key is acceptable before attempting to
// authenticate with it, we end up with duplicate queries for public
// key validity. The cache only applies to a single ServerConn.
struct pubKeyCache {
keys []cachedPubKey
}
// get returns the result for a given user/algo/key tuple.
func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
for _, k := range c.keys {
if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) {
return k, true
}
}
return cachedPubKey{}, false
}
// add adds the given tuple to the cache.
func (c *pubKeyCache) add(candidate cachedPubKey) {
if len(c.keys) < maxCachedPubKeys {
c.keys = append(c.keys, candidate)
}
}
// ServerConn is an authenticated SSH connection, as seen from the
// server
struct ServerConn {
Conn
// If the succeeding authentication callback returned a
// non-nil Permissions pointer, it is stored here.
Permissions *Permissions
}
// NewServerConn starts a new SSH server with c as the underlying
// transport. It starts with a handshake and, if the handshake is
// unsuccessful, it closes the connection and returns an error. The
// Request and NewChannel channels must be serviced, or the connection
// will hang.
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config
fullConf.SetDefaults()
s := &connection{
sshConn: sshConn{conn: c},
}
perms, err := s.serverHandshake(&fullConf)
if err != nil {
c.Close()
return nil, nil, nil, err
}
return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil
}
// signAndMarshal signs the data with the appropriate algorithm,
// and serializes the result in SSH wire format.
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
sig, err := k.Sign(rand, data)
if err != nil {
return nil, err
}
return Marshal(sig), nil
}
// handshake performs key exchange and user authentication.
func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) {
if len(config.hostKeys) == 0 {
return nil, errors.New("ssh: server has no host keys")
}
var err error
s.serverVersion = []byte(packageVersion)
s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion)
if err != nil {
return nil, err
}
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
if err := s.transport.requestKeyChange(); err != nil {
return nil, err
}
if packet, err := s.transport.readPacket(); err != nil {
return nil, err
} else if packet[0] != msgNewKeys {
return nil, unexpectedMessageError(msgNewKeys, packet[0])
}
var packet []byte
if packet, err = s.transport.readPacket(); err != nil {
return nil, err
}
var serviceRequest serviceRequestMsg
if err = Unmarshal(packet, &serviceRequest); err != nil {
return nil, err
}
if serviceRequest.Service != serviceUserAuth {
return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
}
serviceAccept := serviceAcceptMsg{
Service: serviceUserAuth,
}
if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil {
return nil, err
}
perms, err := s.serverAuthenticate(config)
if err != nil {
return nil, err
}
s.mux = newMux(s.transport)
return perms, err
}
func isAcceptableAlgo(algo string) bool {
switch algo {
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
return true
}
return false
}
func checkSourceAddress(addr net.Addr, sourceAddr string) error {
if addr == nil {
return errors.New("ssh: no address known for client, but source-address match required")
}
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
}
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
if bytes.Equal(allowedIP, tcpAddr.IP) {
return nil
}
} else {
_, ipNet, err := net.ParseCIDR(sourceAddr)
if err != nil {
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
}
if ipNet.Contains(tcpAddr.IP) {
return nil
}
}
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
}
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
var err error
var cache pubKeyCache
var perms *Permissions
userAuthLoop:
for {
var userAuthReq userAuthRequestMsg
if packet, err := s.transport.readPacket(); err != nil {
return nil, err
} else if err = Unmarshal(packet, &userAuthReq); err != nil {
return nil, err
}
if userAuthReq.Service != serviceSSH {
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
}
s.user = userAuthReq.User
perms = nil
authErr := errors.New("no auth passed yet")
switch userAuthReq.Method {
case "none":
if config.NoClientAuth {
s.user = ""
authErr = nil
}
case "password":
if config.PasswordCallback == nil {
authErr = errors.New("ssh: password auth not configured")
break
}
payload := userAuthReq.Payload
if len(payload) < 1 || payload[0] != 0 {
return nil, parseError(msgUserAuthRequest)
}
payload = payload[1:]
password, payload, ok := parseString(payload)
if !ok || len(payload) > 0 {
return nil, parseError(msgUserAuthRequest)
}
perms, authErr = config.PasswordCallback(s, password)
case "keyboard-interactive":
if config.KeyboardInteractiveCallback == nil {
authErr = errors.New("ssh: keyboard-interactive auth not configubred")
break
}
prompter := &sshClientKeyboardInteractive{s}
perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
case "publickey":
if config.PublicKeyCallback == nil {
authErr = errors.New("ssh: publickey auth not configured")
break
}
payload := userAuthReq.Payload
if len(payload) < 1 {
return nil, parseError(msgUserAuthRequest)
}
isQuery := payload[0] == 0
payload = payload[1:]
algoBytes, payload, ok := parseString(payload)
if !ok {
return nil, parseError(msgUserAuthRequest)
}
algo := string(algoBytes)
if !isAcceptableAlgo(algo) {
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
break
}
pubKeyData, payload, ok := parseString(payload)
if !ok {
return nil, parseError(msgUserAuthRequest)
}
pubKey, err := ParsePublicKey(pubKeyData)
if err != nil {
return nil, err
}
candidate, ok := cache.get(s.user, pubKeyData)
if !ok {
candidate.user = s.user
candidate.pubKeyData = pubKeyData
candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
candidate.result = checkSourceAddress(
s.RemoteAddr(),
candidate.perms.CriticalOptions[sourceAddressCriticalOption])
}
cache.add(candidate)
}
if isQuery {
// The client can query if the given public key
// would be okay.
if len(payload) > 0 {
return nil, parseError(msgUserAuthRequest)
}
if candidate.result == nil {
okMsg := userAuthPubKeyOkMsg{
Algo: algo,
PubKey: pubKeyData,
}
if err = s.transport.writePacket(Marshal(&okMsg)); err != nil {
return nil, err
}
continue userAuthLoop
}
authErr = candidate.result
} else {
sig, payload, ok := parseSignature(payload)
if !ok || len(payload) > 0 {
return nil, parseError(msgUserAuthRequest)
}
// Ensure the public key algo and signature algo
// are supported. Compare the private key
// algorithm name that corresponds to algo with
// sig.Format. This is usually the same, but
// for certs, the names differ.
if !isAcceptableAlgo(sig.Format) {
break
}
signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData)
if err := pubKey.Verify(signedData, sig); err != nil {
return nil, err
}
authErr = candidate.result
perms = candidate.perms
}
default:
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
}
if config.AuthLogCallback != nil {
config.AuthLogCallback(s, userAuthReq.Method, authErr)
}
if authErr == nil {
break userAuthLoop
}
var failureMsg userAuthFailureMsg
if config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password")
}
if config.PublicKeyCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "publickey")
}
if config.KeyboardInteractiveCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
}
if len(failureMsg.Methods) == 0 {
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
}
if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil {
return nil, err
}
}
if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
return nil, err
}
return perms, nil
}
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
// asking the client on the other side of a ServerConn.
struct sshClientKeyboardInteractive {
*connection
}
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
if len(questions) != len(echos) {
return nil, errors.New("ssh: echos and questions must have equal length")
}
var prompts []byte
for i := range questions {
prompts = appendString(prompts, questions[i])
prompts = appendBool(prompts, echos[i])
}
if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
Instruction: instruction,
NumPrompts: uint32(len(questions)),
Prompts: prompts,
})); err != nil {
return nil, err
}
packet, err := c.transport.readPacket()
if err != nil {
return nil, err
}
if packet[0] != msgUserAuthInfoResponse {
return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0])
}
packet = packet[1:]
n, packet, ok := parseUint32(packet)
if !ok || int(n) != len(questions) {
return nil, parseError(msgUserAuthInfoResponse)
}
for i := uint32(0); i < n; i++ {
ans, rest, ok := parseString(packet)
if !ok {
return nil, parseError(msgUserAuthInfoResponse)
}
answers = append(answers, string(ans))
packet = rest
}
if len(packet) != 0 {
return nil, errors.New("ssh: junk at end of message")
}
return answers, nil
}