mirror of
https://github.com/wavetermdev/ssh_config.git
synced 2026-08-05 13:43:40 -07:00
merge branch 'ignore-match-directive'
This commit is contained in:
@@ -59,15 +59,16 @@ var _ config = &Config{}
|
||||
// UserSettings checks ~/.ssh and /etc/ssh for configuration files. The config
|
||||
// files are parsed and cached the first time Get() or GetStrict() is called.
|
||||
type UserSettings struct {
|
||||
IgnoreErrors bool
|
||||
customConfig *Config
|
||||
customConfigFinder configFinder
|
||||
systemConfig *Config
|
||||
systemConfigFinder configFinder
|
||||
userConfig *Config
|
||||
userConfigFinder configFinder
|
||||
loadConfigs *sync.Once
|
||||
onceErr error
|
||||
IgnoreErrors bool
|
||||
IgnoreMatchDirective bool
|
||||
customConfig *Config
|
||||
customConfigFinder configFinder
|
||||
systemConfig *Config
|
||||
systemConfigFinder configFinder
|
||||
userConfig *Config
|
||||
userConfigFinder configFinder
|
||||
loadConfigs *sync.Once
|
||||
onceErr error
|
||||
}
|
||||
|
||||
func homedir() string {
|
||||
@@ -87,9 +88,10 @@ func userConfigFinder() string {
|
||||
// GetStrict. It checks both $HOME/.ssh/config and /etc/ssh/ssh_config for keys,
|
||||
// and it will return parse errors (if any) instead of swallowing them.
|
||||
var DefaultUserSettings = &UserSettings{
|
||||
IgnoreErrors: false,
|
||||
systemConfigFinder: systemConfigFinder,
|
||||
userConfigFinder: userConfigFinder,
|
||||
IgnoreErrors: false,
|
||||
IgnoreMatchDirective: false,
|
||||
systemConfigFinder: systemConfigFinder,
|
||||
userConfigFinder: userConfigFinder,
|
||||
}
|
||||
|
||||
func systemConfigFinder() string {
|
||||
@@ -295,10 +297,11 @@ func (u *UserSettings) doLoadConfigs() {
|
||||
var err error
|
||||
if u.customConfigFinder != nil {
|
||||
filename = u.customConfigFinder()
|
||||
u.customConfig, err = parseFile(filename)
|
||||
u.customConfig, err = parseFile(filename, u.IgnoreMatchDirective)
|
||||
// IsNotExist should be returned because a user specified this
|
||||
// function - not existing likely means they made an error
|
||||
if err != nil {
|
||||
// We should also respect the ignore flag
|
||||
if err != nil && !u.IgnoreErrors {
|
||||
u.onceErr = err
|
||||
}
|
||||
return
|
||||
@@ -308,7 +311,7 @@ func (u *UserSettings) doLoadConfigs() {
|
||||
} else {
|
||||
filename = u.userConfigFinder()
|
||||
}
|
||||
u.userConfig, err = parseFile(filename)
|
||||
u.userConfig, err = parseFile(filename, u.IgnoreMatchDirective)
|
||||
//lint:ignore S1002 I prefer it this way
|
||||
if err != nil && os.IsNotExist(err) == false {
|
||||
u.onceErr = err
|
||||
@@ -319,7 +322,7 @@ func (u *UserSettings) doLoadConfigs() {
|
||||
} else {
|
||||
filename = u.systemConfigFinder()
|
||||
}
|
||||
u.systemConfig, err = parseFile(filename)
|
||||
u.systemConfig, err = parseFile(filename, u.IgnoreMatchDirective)
|
||||
//lint:ignore S1002 I prefer it this way
|
||||
if err != nil && os.IsNotExist(err) == false {
|
||||
u.onceErr = err
|
||||
@@ -335,16 +338,23 @@ func (u *UserSettings) ReloadConfigs() {
|
||||
u.doLoadConfigs()
|
||||
}
|
||||
|
||||
func parseFile(filename string) (*Config, error) {
|
||||
return parseWithDepth(filename, 0)
|
||||
// ReloadConfigs clears the cached config data and freshly loads the config
|
||||
// files again.
|
||||
func (u *UserSettings) ReloadConfigs() {
|
||||
u.loadConfigs = new(sync.Once)
|
||||
u.doLoadConfigs()
|
||||
}
|
||||
|
||||
func parseWithDepth(filename string, depth uint8) (*Config, error) {
|
||||
func parseFile(filename string, ignoreMatchDirective bool) (*Config, error) {
|
||||
return parseWithDepth(filename, ignoreMatchDirective, 0)
|
||||
}
|
||||
|
||||
func parseWithDepth(filename string, ignoreMatchDirective bool, depth uint8) (*Config, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeBytes(b, isSystem(filename), depth)
|
||||
return decodeBytes(b, isSystem(filename), ignoreMatchDirective, depth)
|
||||
}
|
||||
|
||||
func isSystem(filename string) bool {
|
||||
@@ -354,21 +364,21 @@ func isSystem(filename string) bool {
|
||||
|
||||
// Decode reads r into a Config, or returns an error if r could not be parsed as
|
||||
// an SSH config file.
|
||||
func Decode(r io.Reader) (*Config, error) {
|
||||
func Decode(r io.Reader, ignoreMatchDirective bool) (*Config, error) {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeBytes(b, false, 0)
|
||||
return decodeBytes(b, false, ignoreMatchDirective, 0)
|
||||
}
|
||||
|
||||
// DecodeBytes reads b into a Config, or returns an error if r could not be
|
||||
// parsed as an SSH config file.
|
||||
func DecodeBytes(b []byte) (*Config, error) {
|
||||
return decodeBytes(b, false, 0)
|
||||
func DecodeBytes(b []byte, ignoreMatchDirective bool) (*Config, error) {
|
||||
return decodeBytes(b, false, ignoreMatchDirective, 0)
|
||||
}
|
||||
|
||||
func decodeBytes(b []byte, system bool, depth uint8) (c *Config, err error) {
|
||||
func decodeBytes(b []byte, system, ignoreMatchDirective bool, depth uint8) (c *Config, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if _, ok := r.(runtime.Error); ok {
|
||||
@@ -382,7 +392,7 @@ func decodeBytes(b []byte, system bool, depth uint8) (c *Config, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
c = parseSSH(lexSSH(b), system, depth)
|
||||
c = parseSSH(lexSSH(b), system, ignoreMatchDirective, depth)
|
||||
return c, err
|
||||
}
|
||||
|
||||
@@ -390,9 +400,10 @@ func decodeBytes(b []byte, system bool, depth uint8) (c *Config, err error) {
|
||||
type Config struct {
|
||||
// A list of hosts to match against. The file begins with an implicit
|
||||
// "Host *" declaration matching all hosts.
|
||||
Hosts []*Host
|
||||
depth uint8
|
||||
position Position
|
||||
Hosts []*Host
|
||||
depth uint8
|
||||
position Position
|
||||
ignoreMatchDirective bool
|
||||
}
|
||||
|
||||
// Get finds the first value in the configuration that matches the alias and
|
||||
@@ -413,7 +424,7 @@ func (c *Config) Get(alias, key string) (string, error) {
|
||||
case *KV:
|
||||
// "keys are case insensitive" per the spec
|
||||
lkey := strings.ToLower(t.Key)
|
||||
if lkey == "match" {
|
||||
if lkey == "match" && !c.ignoreMatchDirective {
|
||||
panic("can't handle Match directives")
|
||||
}
|
||||
if lkey == lowerKey {
|
||||
@@ -741,7 +752,8 @@ func removeDups(arr []string) []string {
|
||||
// Configuration files are parsed greedily (e.g. as soon as this function runs).
|
||||
// Any error encountered while parsing nested configuration files will be
|
||||
// returned.
|
||||
func NewInclude(directives []string, hasEquals bool, pos Position, comment string, system bool, depth uint8) (*Include, error) {
|
||||
func NewInclude(directives []string, hasEquals bool, pos Position, comment string, system, ignoreMatchDirective bool, depth uint8,
|
||||
) (*Include, error) {
|
||||
if depth > maxRecurseDepth {
|
||||
return nil, ErrDepthExceeded
|
||||
}
|
||||
@@ -774,7 +786,7 @@ func NewInclude(directives []string, hasEquals bool, pos Position, comment strin
|
||||
matches = removeDups(matches)
|
||||
inc.matches = matches
|
||||
for i := range matches {
|
||||
config, err := parseWithDepth(matches[i], depth)
|
||||
config, err := parseWithDepth(matches[i], ignoreMatchDirective, depth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+28
-2
@@ -27,7 +27,7 @@ var files = []string{
|
||||
func TestDecode(t *testing.T) {
|
||||
for _, filename := range files {
|
||||
data := loadFile(t, filename)
|
||||
cfg, err := Decode(bytes.NewReader(data))
|
||||
cfg, err := Decode(bytes.NewReader(data), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -404,7 +404,7 @@ func TestIncludeString(t *testing.T) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
c, err := Decode(bytes.NewReader(data))
|
||||
c, err := Decode(bytes.NewReader(data), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -532,3 +532,29 @@ func TestCustomFinder(t *testing.T) {
|
||||
t.Errorf("expected to find User root, got %q", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomFinderWhenIgnoringMatchDirective(t *testing.T) {
|
||||
us := &UserSettings{
|
||||
IgnoreMatchDirective: true,
|
||||
}
|
||||
us.ConfigFinder(func() string {
|
||||
return "testdata/config1-with-match-directive"
|
||||
})
|
||||
|
||||
val := us.Get("git.yahoo.com", "HostName")
|
||||
if val != "git.proxy.com" {
|
||||
t.Errorf("expected to find Hostname git.proxy.com, got %q", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomFinderWhenNotIgnoringMatchDirective(t *testing.T) {
|
||||
us := &UserSettings{}
|
||||
us.ConfigFinder(func() string {
|
||||
return "testdata/config1-with-match-directive"
|
||||
})
|
||||
|
||||
val := us.Get("git.yahoo.com", "HostName")
|
||||
if val != "" {
|
||||
t.Errorf("expected to find Hostname empty %q", val)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ Host *.example.com
|
||||
Compression yes
|
||||
`
|
||||
|
||||
cfg, _ := ssh_config.Decode(strings.NewReader(config))
|
||||
cfg, _ := ssh_config.Decode(strings.NewReader(config), false)
|
||||
val, _ := cfg.Get("test.example.com", "Compression")
|
||||
fmt.Println(val)
|
||||
// Output: yes
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
)
|
||||
|
||||
type sshParser struct {
|
||||
flow chan token
|
||||
config *Config
|
||||
tokensBuffer []token
|
||||
currentTable []string
|
||||
seenTableKeys []string
|
||||
ignoreMatchDirective bool
|
||||
flow chan token
|
||||
config *Config
|
||||
tokensBuffer []token
|
||||
currentTable []string
|
||||
seenTableKeys []string
|
||||
// /etc/ssh parser or local parser - used to find the default for relative
|
||||
// filepaths in the Include directive
|
||||
system bool
|
||||
@@ -104,7 +105,7 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
||||
tok = p.getToken()
|
||||
comment = tok.val
|
||||
}
|
||||
if strings.ToLower(key.val) == "match" {
|
||||
if strings.ToLower(key.val) == "match" && !p.ignoreMatchDirective {
|
||||
// https://github.com/kevinburke/ssh_config/issues/6
|
||||
p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
|
||||
return nil
|
||||
@@ -127,18 +128,20 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
||||
hostval := strings.TrimRightFunc(val.val, unicode.IsSpace)
|
||||
spaceBeforeComment := val.val[len(hostval):]
|
||||
val.val = hostval
|
||||
p.config.ignoreMatchDirective = p.ignoreMatchDirective
|
||||
p.config.Hosts = append(p.config.Hosts, &Host{
|
||||
Patterns: patterns,
|
||||
Nodes: make([]Node, 0),
|
||||
EOLComment: comment,
|
||||
spaceBeforeComment: spaceBeforeComment,
|
||||
hasEquals: hasEquals,
|
||||
})
|
||||
},
|
||||
)
|
||||
return p.parseStart
|
||||
}
|
||||
lastHost := p.config.Hosts[len(p.config.Hosts)-1]
|
||||
if strings.ToLower(key.val) == "include" {
|
||||
inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1)
|
||||
inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.ignoreMatchDirective, p.depth+1)
|
||||
if err == ErrDepthExceeded {
|
||||
p.raiseError(val, err)
|
||||
return nil
|
||||
@@ -177,7 +180,7 @@ func (p *sshParser) parseComment() sshParserStateFn {
|
||||
return p.parseStart
|
||||
}
|
||||
|
||||
func parseSSH(flow chan token, system bool, depth uint8) *Config {
|
||||
func parseSSH(flow chan token, system, ignoreMatchDirective bool, depth uint8) *Config {
|
||||
// Ensure we consume tokens to completion even if parser exits early
|
||||
defer func() {
|
||||
for range flow {
|
||||
@@ -187,13 +190,14 @@ func parseSSH(flow chan token, system bool, depth uint8) *Config {
|
||||
result := newConfig()
|
||||
result.position = Position{1, 1}
|
||||
parser := &sshParser{
|
||||
flow: flow,
|
||||
config: result,
|
||||
tokensBuffer: make([]token, 0),
|
||||
currentTable: make([]string, 0),
|
||||
seenTableKeys: make([]string, 0),
|
||||
system: system,
|
||||
depth: depth,
|
||||
ignoreMatchDirective: ignoreMatchDirective,
|
||||
flow: flow,
|
||||
config: result,
|
||||
tokensBuffer: make([]token, 0),
|
||||
currentTable: make([]string, 0),
|
||||
seenTableKeys: make([]string, 0),
|
||||
system: system,
|
||||
depth: depth,
|
||||
}
|
||||
parser.run()
|
||||
return result
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ func (b *errReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
func TestIOError(t *testing.T) {
|
||||
buf := &errReader{}
|
||||
_, err := Decode(buf)
|
||||
_, err := Decode(buf, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected non-nil err, got nil")
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
Match all
|
||||
Include ~/.ssh
|
||||
Host *
|
||||
User usr
|
||||
Host git.yahoo.com
|
||||
HostName git.proxy.com
|
||||
Reference in New Issue
Block a user