add Default() and start of a validate() function
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -245,3 +246,17 @@ func TestMatches(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchUnsupported(t *testing.T) {
|
||||||
|
us := &UserSettings{
|
||||||
|
userConfigFinder: testConfigFinder("testdata/match-directive"),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := us.GetStrict("test.test", "Port")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected Match directive to error, didn't")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "ssh_config: Match directive parsing is unsupported") {
|
||||||
|
t.Errorf("wrong error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
11
parser.go
11
parser.go
@@ -21,6 +21,7 @@ type sshParserStateFn func() sshParserStateFn
|
|||||||
|
|
||||||
// Formats and panics an error message based on a token
|
// Formats and panics an error message based on a token
|
||||||
func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
|
func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
|
||||||
|
// TODO this format is ugly
|
||||||
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
|
panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ func (p *sshParser) raiseError(tok *token, err error) {
|
|||||||
if err == ErrDepthExceeded {
|
if err == ErrDepthExceeded {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
// TODO this format is ugly
|
||||||
panic(tok.Position.String() + ": " + err.Error())
|
panic(tok.Position.String() + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +100,12 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
|||||||
tok = p.getToken()
|
tok = p.getToken()
|
||||||
comment = tok.val
|
comment = tok.val
|
||||||
}
|
}
|
||||||
if key.val == "Host" {
|
if strings.ToLower(key.val) == "match" {
|
||||||
|
// https://github.com/kevinburke/ssh_config/issues/6
|
||||||
|
p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.ToLower(key.val) == "host" {
|
||||||
strPatterns := strings.Split(val.val, " ")
|
strPatterns := strings.Split(val.val, " ")
|
||||||
for i := range strPatterns {
|
for i := range strPatterns {
|
||||||
if strPatterns[i] == "" {
|
if strPatterns[i] == "" {
|
||||||
@@ -123,7 +130,7 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
|||||||
return p.parseStart
|
return p.parseStart
|
||||||
}
|
}
|
||||||
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
|
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
|
||||||
if key.val == "Include" {
|
if strings.ToLower(key.val) == "include" {
|
||||||
inc, err := NewInclude(strings.Split(val.val, " "), comment, p.system, p.depth+1)
|
inc, err := NewInclude(strings.Split(val.val, " "), comment, p.system, p.depth+1)
|
||||||
if err == ErrDepthExceeded {
|
if err == ErrDepthExceeded {
|
||||||
p.raiseError(val, err)
|
p.raiseError(val, err)
|
||||||
|
|||||||
2
testdata/match-directive
vendored
Normal file
2
testdata/match-directive
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Match all
|
||||||
|
Port 4567
|
||||||
160
validators.go
Normal file
160
validators.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package ssh_config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arguments where the value must be "yes" or "no" and *only* yes or no.
|
||||||
|
var yesnos = map[string]bool{
|
||||||
|
strings.ToLower("BatchMode"): true,
|
||||||
|
strings.ToLower("CanonicalizeFallbackLocal"): true,
|
||||||
|
strings.ToLower("ChallengeResponseAuthentication"): true,
|
||||||
|
strings.ToLower("CheckHostIP"): true,
|
||||||
|
strings.ToLower("ClearAllForwardings"): true,
|
||||||
|
strings.ToLower("Compression"): true,
|
||||||
|
strings.ToLower("EnableSSHKeysign"): true,
|
||||||
|
strings.ToLower("ExitOnForwardFailure"): true,
|
||||||
|
strings.ToLower("ForwardAgent"): true,
|
||||||
|
strings.ToLower("ForwardX11"): true,
|
||||||
|
strings.ToLower("ForwardX11Trusted"): true,
|
||||||
|
strings.ToLower("GatewayPorts"): true,
|
||||||
|
strings.ToLower("GSSAPIAuthentication"): true,
|
||||||
|
strings.ToLower("GSSAPIDelegateCredentials"): true,
|
||||||
|
strings.ToLower("HostbasedAuthentication"): true,
|
||||||
|
strings.ToLower("IdentitiesOnly"): true,
|
||||||
|
strings.ToLower("KbdInteractiveAuthentication"): true,
|
||||||
|
strings.ToLower("NoHostAuthenticationForLocalhost"): true,
|
||||||
|
strings.ToLower("PasswordAuthentication"): true,
|
||||||
|
strings.ToLower("PermitLocalCommand"): true,
|
||||||
|
strings.ToLower("PubkeyAuthentication"): true,
|
||||||
|
strings.ToLower("RhostsRSAAuthentication"): true,
|
||||||
|
strings.ToLower("RSAAuthentication"): true,
|
||||||
|
strings.ToLower("StreamLocalBindUnlink"): true,
|
||||||
|
strings.ToLower("TCPKeepAlive"): true,
|
||||||
|
strings.ToLower("UseKeychain"): true,
|
||||||
|
strings.ToLower("UsePrivilegedPort"): true,
|
||||||
|
strings.ToLower("VisualHostKey"): true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var uints = map[string]bool{
|
||||||
|
strings.ToLower("CanonicalizeMaxDots"): true,
|
||||||
|
strings.ToLower("CompressionLevel"): true, // 1 to 9
|
||||||
|
strings.ToLower("ConnectionAttempts"): true,
|
||||||
|
strings.ToLower("ConnectTimeout"): true,
|
||||||
|
strings.ToLower("NumberOfPasswordPrompts"): true,
|
||||||
|
strings.ToLower("Port"): true,
|
||||||
|
strings.ToLower("ServerAliveCountMax"): true,
|
||||||
|
strings.ToLower("ServerAliveInterval"): true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustBeYesOrNo(lkey string) bool {
|
||||||
|
return yesnos[lkey]
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustBeUint(lkey string) bool {
|
||||||
|
return uints[lkey]
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(key, val string) error {
|
||||||
|
lkey := strings.ToLower(key)
|
||||||
|
if mustBeYesOrNo(lkey) && (val != "yes" && val != "no") {
|
||||||
|
return fmt.Errorf("ssh_config: value for key %q must be 'yes' or 'no', got %q", key, val)
|
||||||
|
}
|
||||||
|
if mustBeUint(lkey) {
|
||||||
|
_, err := strconv.ParseUint(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ssh_config: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = map[string]string{
|
||||||
|
strings.ToLower("AddKeysToAgent"): "no",
|
||||||
|
strings.ToLower("AddressFamily"): "any",
|
||||||
|
strings.ToLower("BatchMode"): "no",
|
||||||
|
strings.ToLower("CanonicalizeFallbackLocal"): "yes",
|
||||||
|
strings.ToLower("CanonicalizeHostname"): "no",
|
||||||
|
strings.ToLower("CanonicalizeMaxDots"): "1",
|
||||||
|
strings.ToLower("ChallengeResponseAuthentication"): "yes",
|
||||||
|
strings.ToLower("CheckHostIP"): "yes",
|
||||||
|
// TODO is this still the correct cipher
|
||||||
|
strings.ToLower("Cipher"): "3des",
|
||||||
|
strings.ToLower("Ciphers"): "chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-cbc,aes192-cbc,aes256-cbc",
|
||||||
|
strings.ToLower("ClearAllForwardings"): "no",
|
||||||
|
strings.ToLower("Compression"): "no",
|
||||||
|
strings.ToLower("CompressionLevel"): "6",
|
||||||
|
strings.ToLower("ConnectionAttempts"): "1",
|
||||||
|
strings.ToLower("ControlMaster"): "no",
|
||||||
|
strings.ToLower("EnableSSHKeysign"): "no",
|
||||||
|
strings.ToLower("EscapeChar"): "~",
|
||||||
|
strings.ToLower("ExitOnForwardFailure"): "no",
|
||||||
|
strings.ToLower("FingerprintHash"): "sha256",
|
||||||
|
strings.ToLower("ForwardAgent"): "no",
|
||||||
|
strings.ToLower("ForwardX11"): "no",
|
||||||
|
strings.ToLower("ForwardX11Timeout"): "20m",
|
||||||
|
strings.ToLower("ForwardX11Trusted"): "no",
|
||||||
|
strings.ToLower("GatewayPorts"): "no",
|
||||||
|
strings.ToLower("GlobalKnownHostsFile"): "/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2",
|
||||||
|
strings.ToLower("GSSAPIAuthentication"): "no",
|
||||||
|
strings.ToLower("GSSAPIDelegateCredentials"): "no",
|
||||||
|
strings.ToLower("HashKnownHosts"): "no",
|
||||||
|
strings.ToLower("HostbasedAuthentication"): "no",
|
||||||
|
|
||||||
|
strings.ToLower("HostbasedKeyTypes"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa",
|
||||||
|
strings.ToLower("HostKeyAlgorithms"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa",
|
||||||
|
// HostName has a dynamic default (the value passed at the command line).
|
||||||
|
|
||||||
|
strings.ToLower("IdentitiesOnly"): "no",
|
||||||
|
strings.ToLower("IdentityFile"): "~/.ssh/identity",
|
||||||
|
|
||||||
|
// IPQoS has a dynamic default based on interactive or non-interactive
|
||||||
|
// sessions.
|
||||||
|
|
||||||
|
strings.ToLower("KbdInteractiveAuthentication"): "yes",
|
||||||
|
|
||||||
|
strings.ToLower("KexAlgorithms"): "curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1",
|
||||||
|
strings.ToLower("LogLevel"): "INFO",
|
||||||
|
strings.ToLower("MACs"): "umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1",
|
||||||
|
|
||||||
|
strings.ToLower("NoHostAuthenticationForLocalhost"): "no",
|
||||||
|
strings.ToLower("NumberOfPasswordPrompts"): "3",
|
||||||
|
strings.ToLower("PasswordAuthentication"): "yes",
|
||||||
|
strings.ToLower("PermitLocalCommand"): "no",
|
||||||
|
strings.ToLower("Port"): "22",
|
||||||
|
|
||||||
|
strings.ToLower("PreferredAuthentications"): "gssapi-with-mic,hostbased,publickey,keyboard-interactive,password",
|
||||||
|
strings.ToLower("Protocol"): "2",
|
||||||
|
strings.ToLower("ProxyUseFdpass"): "no",
|
||||||
|
strings.ToLower("PubkeyAcceptedKeyTypes"): "ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa",
|
||||||
|
strings.ToLower("PubkeyAuthentication"): "yes",
|
||||||
|
strings.ToLower("RekeyLimit"): "default none",
|
||||||
|
strings.ToLower("RhostsRSAAuthentication"): "no",
|
||||||
|
strings.ToLower("RSAAuthentication"): "yes",
|
||||||
|
|
||||||
|
strings.ToLower("ServerAliveCountMax"): "3",
|
||||||
|
strings.ToLower("ServerAliveInterval"): "0",
|
||||||
|
strings.ToLower("StreamLocalBindMask"): "0177",
|
||||||
|
strings.ToLower("StreamLocalBindUnlink"): "no",
|
||||||
|
strings.ToLower("StrictHostKeyChecking"): "ask",
|
||||||
|
strings.ToLower("TCPKeepAlive"): "yes",
|
||||||
|
strings.ToLower("Tunnel"): "no",
|
||||||
|
strings.ToLower("TunnelDevice"): "any:any",
|
||||||
|
strings.ToLower("UpdateHostKeys"): "no",
|
||||||
|
strings.ToLower("UseKeychain"): "no",
|
||||||
|
strings.ToLower("UsePrivilegedPort"): "no",
|
||||||
|
|
||||||
|
strings.ToLower("UserKnownHostsFile"): "~/.ssh/known_hosts ~/.ssh/known_hosts2",
|
||||||
|
strings.ToLower("VerifyHostKeyDNS"): "no",
|
||||||
|
strings.ToLower("VisualHostKey"): "no",
|
||||||
|
strings.ToLower("XAuthLocation"): "/usr/X11R6/bin/xauth",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default returns the default value for the given keyword. Default returns the
|
||||||
|
// empty string if the keyword has no default, or if the keyword is unknown.
|
||||||
|
// Keyword matching is case-insensitive.
|
||||||
|
func Default(keyword string) string {
|
||||||
|
return defaults[strings.ToLower(keyword)]
|
||||||
|
}
|
||||||
44
validators_test.go
Normal file
44
validators_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package ssh_config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validateTests = []struct {
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"IdentitiesOnly", "yes", ""},
|
||||||
|
{"IdentitiesOnly", "Yes", `ssh_config: value for key "IdentitiesOnly" must be 'yes' or 'no', got "Yes"`},
|
||||||
|
{"Port", "22", ``},
|
||||||
|
{"Port", "yes", `ssh_config: strconv.ParseUint: parsing "yes": invalid syntax`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
for _, tt := range validateTests {
|
||||||
|
err := validate(tt.key, tt.val)
|
||||||
|
if tt.err == "" && err != nil {
|
||||||
|
t.Errorf("validate(%q, %q): got %v, want nil", tt.key, tt.val, err)
|
||||||
|
}
|
||||||
|
if tt.err != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("validate(%q, %q): got nil error, want %v", tt.key, tt.val, tt.err)
|
||||||
|
} else if err.Error() != tt.err {
|
||||||
|
t.Errorf("validate(%q, %q): got err %v, want %v", tt.key, tt.val, err, tt.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefault(t *testing.T) {
|
||||||
|
if v := Default("VisualHostKey"); v != "no" {
|
||||||
|
t.Errorf("Default(%q): got %v, want 'no'", "VisualHostKey", v)
|
||||||
|
}
|
||||||
|
if v := Default("visualhostkey"); v != "no" {
|
||||||
|
t.Errorf("Default(%q): got %v, want 'no'", "visualhostkey", v)
|
||||||
|
}
|
||||||
|
if v := Default("notfound"); v != "" {
|
||||||
|
t.Errorf("Default(%q): got %v, want ''", "notfound", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user