diff --git a/config.go b/config.go index 834abf6..47602b8 100644 --- a/config.go +++ b/config.go @@ -247,6 +247,7 @@ func (c *Config) String() string { type Pattern struct { str string // Its appearance in the file, not the value that gets compiled. regex *regexp.Regexp + not bool // True if this is a negated match } // String prints the string representation of the pattern. @@ -275,6 +276,14 @@ func NewPattern(s string) (*Pattern, error) { // The following pattern would match any host in the 192.168.0.[0-9] network range: // // Host 192.168.0.? + if s == "" { + return nil, errors.New("ssh_config: empty pattern") + } + negated := false + if s[0] == '!' { + negated = true + s = s[1:] + } var buf bytes.Buffer buf.WriteByte('^') for i := 0; i < len(s); i++ { @@ -297,7 +306,7 @@ func NewPattern(s string) (*Pattern, error) { if err != nil { return nil, err } - return &Pattern{str: s, regex: r}, nil + return &Pattern{str: s, regex: r, not: negated}, nil } type Host struct { @@ -317,12 +326,22 @@ type Host struct { // a description of the rules that provide a match, see the manpage for // ssh_config. func (h *Host) Matches(alias string) bool { + found := false for i := range h.Patterns { if h.Patterns[i].regex.MatchString(alias) { - return true + if h.Patterns[i].not == true { + // Negated match. "A pattern entry may be negated by prefixing + // it with an exclamation mark (`!'). If a negated entry is + // matched, then the Host entry is ignored, regardless of + // whether any other patterns on the line match. Negated matches + // are therefore useful to provide exceptions for wildcard + // matches." + return false + } + found = true } } - return false + return found } // String prints h as it would appear in a config file. Minor tweaks may be diff --git a/config_test.go b/config_test.go index 5213d4a..9f2db4a 100644 --- a/config_test.go +++ b/config_test.go @@ -159,6 +159,8 @@ var matchTests = []struct { {[]string{"*.co.uk"}, "subdomain.bbc.co.uk", true}, {[]string{"*.*.co.uk"}, "bbc.co.uk", false}, {[]string{"*.*.co.uk"}, "subdomain.bbc.co.uk", true}, + {[]string{"*.example.com", "!*.dialup.example.com", "foo.dialup.example.com"}, "foo.dialup.example.com", false}, + {[]string{"test.*", "!test.host"}, "test.host", false}, } func TestMatches(t *testing.T) { diff --git a/testdata/negated b/testdata/negated new file mode 100644 index 0000000..82df3c8 --- /dev/null +++ b/testdata/negated @@ -0,0 +1,5 @@ +Host *.example.com !*.dialup.example.com + Port 1234 + +Host * + Port 5678