Codebase list golang-github-kevinburke-ssh-config / 53e7472
Import upstream version 1.2.0 Debian Janitor 1 year, 11 months ago
17 changed file(s) with 433 addition(s) and 79 deletion(s). Raw diff Collapse all Expand all
0 on: [push, pull_request]
1 name: Test
2 jobs:
3 test:
4 strategy:
5 matrix:
6 go-version: [1.17.x, 1.18.x]
7 platform: [ubuntu-latest]
8 runs-on: ${{ matrix.platform }}
9 steps:
10 - name: Install Go
11 uses: WillAbides/setup-go-faster@main
12 with:
13 go-version: ${{ matrix.go-version }}
14 - uses: actions/checkout@v2
15 with:
16 path: './src/github.com/kevinburke/ssh_config'
17 # staticcheck needs this for GOPATH
18 - run: |
19 echo "GO111MODULE=off" >> $GITHUB_ENV
20 echo "GOPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
21 echo "PATH=$GITHUB_WORKSPACE/bin:$PATH" >> $GITHUB_ENV
22 - name: Run tests
23 run: make lint race-test
24 working-directory: './src/github.com/kevinburke/ssh_config'
0 Kevin Burke <kevin@burke.dev> Kevin Burke <kev@inburke.com>
+0
-14
.travis.yml less more
0 go_import_path: github.com/kevinburke/ssh_config
1
2 language: go
3
4 go:
5 - 1.11.x
6 - 1.12.x
7 - master
8
9 before_script:
10 - go get -u ./...
11
12 script:
13 - make race-test
0 Carlos A Becker <caarlos0@gmail.com>
1 Dustin Spicuzza <dustin@virtualroadside.com>
02 Eugene Terentev <eugene@terentev.net>
1 Kevin Burke <kev@inburke.com>
3 Kevin Burke <kevin@burke.dev>
4 Mark Nevill <nev@improbable.io>
5 Scott Lessans <slessans@gmail.com>
26 Sergey Lukjanov <me@slukjanov.name>
37 Wayne Ashley Berry <wayneashleyberry@gmail.com>
8 santosh653 <70637961+santosh653@users.noreply.github.com>
0 # Changes
1
2 ## Version 1.2
3
4 Previously, if a Host declaration or a value had trailing whitespace, that
5 whitespace would have been included as part of the value. This led to unexpected
6 consequences. For example:
7
8 ```
9 Host example # A comment
10 HostName example.com # Another comment
11 ```
12
13 Prior to version 1.2, the value for Host would have been "example " and the
14 value for HostName would have been "example.com ". Both of these are
15 unintuitive.
16
17 Instead, we strip the trailing whitespace in the configuration, which leads to
18 more intuitive behavior.
1818 $(BUMP_VERSION):
1919 go get -u github.com/kevinburke/bump_version
2020
21 $(WRITE_MAILMAP):
22 go get -u github.com/kevinburke/write_mailmap
23
2124 release: test | $(BUMP_VERSION)
22 $(BUMP_VERSION) minor config.go
25 $(BUMP_VERSION) --tag-prefix=v minor config.go
2326
2427 force: ;
2528
1414
1515 ```go
1616 port := ssh_config.Get("myhost", "Port")
17 ```
18
19 Certain directives can occur multiple times for a host (such as `IdentityFile`),
20 so you should use the `GetAll` or `GetAllStrict` directive to retrieve those
21 instead.
22
23 ```go
24 files := ssh_config.GetAll("myhost", "IdentityFile")
1725 ```
1826
1927 You can also load a config file and read values from it.
7583
7684 ## Donating
7785
78 Donations free up time to make improvements to the library, and respond to
79 bug reports. You can send donations via Paypal's "Send Money" feature to
80 kev@inburke.com. Donations are not tax deductible in the USA.
86 I don't get paid to maintain this project. Donations free up time to make
87 improvements to the library, and respond to bug reports. You can send donations
88 via Paypal's "Send Money" feature to kev@inburke.com. Donations are not tax
89 deductible in the USA.
90
91 You can also reach out about a consulting engagement: https://burke.services
3333 "errors"
3434 "fmt"
3535 "io"
36 "io/ioutil"
3736 "os"
3837 osuser "os/user"
3938 "path/filepath"
4342 "sync"
4443 )
4544
46 const version = "1.0"
45 const version = "1.2"
4746
4847 var _ = version
4948
101100 return val, nil
102101 }
103102
103 func findAll(c *Config, alias, key string) ([]string, error) {
104 if c == nil {
105 return nil, nil
106 }
107 return c.GetAll(alias, key)
108 }
109
104110 // Get finds the first value for key within a declaration that matches the
105111 // alias. Get returns the empty string if no value was found, or if IgnoreErrors
106112 // is false and we could not parse the configuration file. Use GetStrict to
113119 return DefaultUserSettings.Get(alias, key)
114120 }
115121
122 // GetAll retrieves zero or more directives for key for the given alias. GetAll
123 // returns nil if no value was found, or if IgnoreErrors is false and we could
124 // not parse the configuration file. Use GetAllStrict to disambiguate the
125 // latter cases.
126 //
127 // In most cases you want to use Get or GetStrict, which returns a single value.
128 // However, a subset of ssh configuration values (IdentityFile, for example)
129 // allow you to specify multiple directives.
130 //
131 // The match for key is case insensitive.
132 //
133 // GetAll is a wrapper around DefaultUserSettings.GetAll.
134 func GetAll(alias, key string) []string {
135 return DefaultUserSettings.GetAll(alias, key)
136 }
137
116138 // GetStrict finds the first value for key within a declaration that matches the
117139 // alias. If key has a default value and no matching configuration is found, the
118140 // default will be returned. For more information on default values and the way
119141 // patterns are matched, see the manpage for ssh_config.
120142 //
121 // error will be non-nil if and only if a user's configuration file or the
122 // system configuration file could not be parsed, and u.IgnoreErrors is false.
143 // The returned error will be non-nil if and only if a user's configuration file
144 // or the system configuration file could not be parsed, and u.IgnoreErrors is
145 // false.
123146 //
124147 // GetStrict is a wrapper around DefaultUserSettings.GetStrict.
125148 func GetStrict(alias, key string) (string, error) {
126149 return DefaultUserSettings.GetStrict(alias, key)
150 }
151
152 // GetAllStrict retrieves zero or more directives for key for the given alias.
153 //
154 // In most cases you want to use Get or GetStrict, which returns a single value.
155 // However, a subset of ssh configuration values (IdentityFile, for example)
156 // allow you to specify multiple directives.
157 //
158 // The returned error will be non-nil if and only if a user's configuration file
159 // or the system configuration file could not be parsed, and u.IgnoreErrors is
160 // false.
161 //
162 // GetAllStrict is a wrapper around DefaultUserSettings.GetAllStrict.
163 func GetAllStrict(alias, key string) ([]string, error) {
164 return DefaultUserSettings.GetAllStrict(alias, key)
127165 }
128166
129167 // Get finds the first value for key within a declaration that matches the
140178 return val
141179 }
142180
181 // GetAll retrieves zero or more directives for key for the given alias. GetAll
182 // returns nil if no value was found, or if IgnoreErrors is false and we could
183 // not parse the configuration file. Use GetStrict to disambiguate the latter
184 // cases.
185 //
186 // The match for key is case insensitive.
187 func (u *UserSettings) GetAll(alias, key string) []string {
188 val, _ := u.GetAllStrict(alias, key)
189 return val
190 }
191
143192 // GetStrict finds the first value for key within a declaration that matches the
144193 // alias. If key has a default value and no matching configuration is found, the
145194 // default will be returned. For more information on default values and the way
148197 // error will be non-nil if and only if a user's configuration file or the
149198 // system configuration file could not be parsed, and u.IgnoreErrors is false.
150199 func (u *UserSettings) GetStrict(alias, key string) (string, error) {
200 u.doLoadConfigs()
201 //lint:ignore S1002 I prefer it this way
202 if u.onceErr != nil && u.IgnoreErrors == false {
203 return "", u.onceErr
204 }
205 val, err := findVal(u.userConfig, alias, key)
206 if err != nil || val != "" {
207 return val, err
208 }
209 val2, err2 := findVal(u.systemConfig, alias, key)
210 if err2 != nil || val2 != "" {
211 return val2, err2
212 }
213 return Default(key), nil
214 }
215
216 // GetAllStrict retrieves zero or more directives for key for the given alias.
217 // If key has a default value and no matching configuration is found, the
218 // default will be returned. For more information on default values and the way
219 // patterns are matched, see the manpage for ssh_config.
220 //
221 // The returned error will be non-nil if and only if a user's configuration file
222 // or the system configuration file could not be parsed, and u.IgnoreErrors is
223 // false.
224 func (u *UserSettings) GetAllStrict(alias, key string) ([]string, error) {
225 u.doLoadConfigs()
226 //lint:ignore S1002 I prefer it this way
227 if u.onceErr != nil && u.IgnoreErrors == false {
228 return nil, u.onceErr
229 }
230 val, err := findAll(u.userConfig, alias, key)
231 if err != nil || val != nil {
232 return val, err
233 }
234 val2, err2 := findAll(u.systemConfig, alias, key)
235 if err2 != nil || val2 != nil {
236 return val2, err2
237 }
238 // TODO: IdentityFile has multiple default values that we should return.
239 if def := Default(key); def != "" {
240 return []string{def}, nil
241 }
242 return []string{}, nil
243 }
244
245 func (u *UserSettings) doLoadConfigs() {
151246 u.loadConfigs.Do(func() {
152247 // can't parse user file, that's ok.
153248 var filename string
175270 return
176271 }
177272 })
178 //lint:ignore S1002 I prefer it this way
179 if u.onceErr != nil && u.IgnoreErrors == false {
180 return "", u.onceErr
181 }
182 val, err := findVal(u.userConfig, alias, key)
183 if err != nil || val != "" {
184 return val, err
185 }
186 val2, err2 := findVal(u.systemConfig, alias, key)
187 if err2 != nil || val2 != "" {
188 return val2, err2
189 }
190 return Default(key), nil
191273 }
192274
193275 func parseFile(filename string) (*Config, error) {
195277 }
196278
197279 func parseWithDepth(filename string, depth uint8) (*Config, error) {
198 b, err := ioutil.ReadFile(filename)
280 b, err := os.ReadFile(filename)
199281 if err != nil {
200282 return nil, err
201283 }
210292 // Decode reads r into a Config, or returns an error if r could not be parsed as
211293 // an SSH config file.
212294 func Decode(r io.Reader) (*Config, error) {
213 b, err := ioutil.ReadAll(r)
295 b, err := io.ReadAll(r)
214296 if err != nil {
215297 return nil, err
216298 }
299 return decodeBytes(b, false, 0)
300 }
301
302 // DecodeBytes reads b into a Config, or returns an error if r could not be
303 // parsed as an SSH config file.
304 func DecodeBytes(b []byte) (*Config, error) {
217305 return decodeBytes(b, false, 0)
218306 }
219307
281369 return "", nil
282370 }
283371
372 // GetAll returns all values in the configuration that match the alias and
373 // contains key, or nil if none are present.
374 func (c *Config) GetAll(alias, key string) ([]string, error) {
375 lowerKey := strings.ToLower(key)
376 all := []string(nil)
377 for _, host := range c.Hosts {
378 if !host.Matches(alias) {
379 continue
380 }
381 for _, node := range host.Nodes {
382 switch t := node.(type) {
383 case *Empty:
384 continue
385 case *KV:
386 // "keys are case insensitive" per the spec
387 lkey := strings.ToLower(t.Key)
388 if lkey == "match" {
389 panic("can't handle Match directives")
390 }
391 if lkey == lowerKey {
392 all = append(all, t.Value)
393 }
394 case *Include:
395 val, _ := t.GetAll(alias, key)
396 if len(val) > 0 {
397 all = append(all, val...)
398 }
399 default:
400 return nil, fmt.Errorf("unknown Node type %v", t)
401 }
402 }
403 }
404
405 return all, nil
406 }
407
284408 // String returns a string representation of the Config file.
285409 func (c Config) String() string {
286410 return marshal(c).String()
373497 // A Node is either a key/value pair or a comment line.
374498 Nodes []Node
375499 // EOLComment is the comment (if any) terminating the Host line.
376 EOLComment string
500 EOLComment string
501 // Whitespace if any between the Host declaration and a trailing comment.
502 spaceBeforeComment string
503
377504 hasEquals bool
378505 leadingSpace int // TODO: handle spaces vs tabs here.
379506 // The file starts with an implicit "Host *" declaration.
405532 // String prints h as it would appear in a config file. Minor tweaks may be
406533 // present in the whitespace in the printed file.
407534 func (h *Host) String() string {
408 var buf bytes.Buffer
535 var buf strings.Builder
409536 //lint:ignore S1002 I prefer to write it this way
410537 if h.implicit == false {
411538 buf.WriteString(strings.Repeat(" ", int(h.leadingSpace)))
421548 buf.WriteString(" ")
422549 }
423550 }
551 buf.WriteString(h.spaceBeforeComment)
424552 if h.EOLComment != "" {
425 buf.WriteString(" #")
553 buf.WriteByte('#')
426554 buf.WriteString(h.EOLComment)
427555 }
428556 buf.WriteByte('\n')
443571 // KV is a line in the config file that contains a key, a value, and possibly
444572 // a comment.
445573 type KV struct {
446 Key string
447 Value string
448 Comment string
449 hasEquals bool
450 leadingSpace int // Space before the key. TODO handle spaces vs tabs.
451 position Position
574 Key string
575 Value string
576 // Whitespace after the value but before any comment
577 spaceAfterValue string
578 Comment string
579 hasEquals bool
580 leadingSpace int // Space before the key. TODO handle spaces vs tabs.
581 position Position
452582 }
453583
454584 // Pos returns k's Position.
456586 return k.position
457587 }
458588
459 // String prints k as it was parsed in the config file. There may be slight
460 // changes to the whitespace between values.
589 // String prints k as it was parsed in the config file.
461590 func (k *KV) String() string {
462591 if k == nil {
463592 return ""
466595 if k.hasEquals {
467596 equals = " = "
468597 }
469 line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", int(k.leadingSpace)), k.Key, equals, k.Value)
598 line := strings.Repeat(" ", int(k.leadingSpace)) + k.Key + equals + k.Value + k.spaceAfterValue
470599 if k.Comment != "" {
471 line += " #" + k.Comment
600 line += "#" + k.Comment
472601 }
473602 return line
474603 }
610739 return ""
611740 }
612741
742 // GetAll finds all values in the Include statement matching the alias and the
743 // given key.
744 func (inc *Include) GetAll(alias, key string) ([]string, error) {
745 inc.mu.Lock()
746 defer inc.mu.Unlock()
747 var vals []string
748
749 // TODO: we search files in any order which is not correct
750 for i := range inc.matches {
751 cfg := inc.files[inc.matches[i]]
752 if cfg == nil {
753 panic("nil cfg")
754 }
755 val, err := cfg.GetAll(alias, key)
756 if err == nil && len(val) != 0 {
757 // In theory if SupportsMultiple was false for this key we could
758 // stop looking here. But the caller has asked us to find all
759 // instances of the keyword (and could use Get() if they wanted) so
760 // let's keep looking.
761 vals = append(vals, val...)
762 }
763 }
764 return vals, nil
765 }
766
613767 // String prints out a string representation of this Include directive. Note
614768 // included Config files are not printed as part of this representation.
615769 func (inc *Include) String() string {
11
22 import (
33 "bytes"
4 "io/ioutil"
54 "log"
65 "os"
76 "path/filepath"
109 )
1110
1211 func loadFile(t *testing.T, filename string) []byte {
13 data, err := ioutil.ReadFile(filename)
12 t.Helper()
13 data, err := os.ReadFile(filename)
1414 if err != nil {
1515 t.Fatal(err)
1616 }
1717 return data
1818 }
1919
20 var files = []string{"testdata/config1", "testdata/config2"}
20 var files = []string{
21 "testdata/config1",
22 "testdata/config2",
23 "testdata/eol-comments",
24 }
2125
2226 func TestDecode(t *testing.T) {
2327 for _, filename := range files {
2832 }
2933 out := cfg.String()
3034 if out != string(data) {
31 t.Errorf("out != data: out: %q\ndata: %q", out, string(data))
35 t.Errorf("%s out != data: got:\n%s\nwant:\n%s\n", filename, out, string(data))
3236 }
3337 }
3438 }
6670 }
6771 }
6872
73 func TestGetAllWithDefault(t *testing.T) {
74 us := &UserSettings{
75 userConfigFinder: testConfigFinder("testdata/config1"),
76 }
77
78 val, err := us.GetAllStrict("wap", "PasswordAuthentication")
79 if err != nil {
80 t.Fatalf("expected nil err, got %v", err)
81 }
82 if len(val) != 1 || val[0] != "yes" {
83 t.Errorf("expected to get PasswordAuthentication yes, got %q", val)
84 }
85 }
86
87 func TestGetIdentities(t *testing.T) {
88 us := &UserSettings{
89 userConfigFinder: testConfigFinder("testdata/identities"),
90 }
91
92 val, err := us.GetAllStrict("hasidentity", "IdentityFile")
93 if err != nil {
94 t.Errorf("expected nil err, got %v", err)
95 }
96 if len(val) != 1 || val[0] != "file1" {
97 t.Errorf(`expected ["file1"], got %v`, val)
98 }
99
100 val, err = us.GetAllStrict("has2identity", "IdentityFile")
101 if err != nil {
102 t.Errorf("expected nil err, got %v", err)
103 }
104 if len(val) != 2 || val[0] != "f1" || val[1] != "f2" {
105 t.Errorf(`expected [\"f1\", \"f2\"], got %v`, val)
106 }
107
108 val, err = us.GetAllStrict("randomhost", "IdentityFile")
109 if err != nil {
110 t.Errorf("expected nil err, got %v", err)
111 }
112 if len(val) != len(defaultProtocol2Identities) {
113 // TODO: return the right values here.
114 log.Printf("expected defaults, got %v", val)
115 } else {
116 for i, v := range defaultProtocol2Identities {
117 if val[i] != v {
118 t.Errorf("invalid %d in val, expected %s got %s", i, v, val[i])
119 }
120 }
121 }
122
123 val, err = us.GetAllStrict("protocol1", "IdentityFile")
124 if err != nil {
125 t.Errorf("expected nil err, got %v", err)
126 }
127 if len(val) != 1 || val[0] != "~/.ssh/identity" {
128 t.Errorf("expected [\"~/.ssh/identity\"], got %v", val)
129 }
130 }
131
69132 func TestGetInvalidPort(t *testing.T) {
70133 us := &UserSettings{
71134 userConfigFinder: testConfigFinder("testdata/invalid-port"),
93156 t.Fatalf("expected nil err, got %v", err)
94157 }
95158 if val != "" {
159 t.Errorf("expected to get CanonicalDomains '', got %q", val)
160 }
161 }
162
163 func TestGetAllNotFoundNoDefault(t *testing.T) {
164 us := &UserSettings{
165 userConfigFinder: testConfigFinder("testdata/config1"),
166 }
167
168 val, err := us.GetAllStrict("wap", "CanonicalDomains")
169 if err != nil {
170 t.Fatalf("expected nil err, got %v", err)
171 }
172 if len(val) != 0 {
96173 t.Errorf("expected to get CanonicalDomains '', got %q", val)
97174 }
98175 }
193270 t.Skip("skipping fs write in short mode")
194271 }
195272 testPath := filepath.Join(homedir(), ".ssh", "kevinburke-ssh-config-test-file")
196 err := ioutil.WriteFile(testPath, includeFile, 0644)
273 err := os.WriteFile(testPath, includeFile, 0644)
197274 if err != nil {
198275 t.Skipf("couldn't write SSH config file: %v", err.Error())
199276 }
212289 t.Skip("skipping fs write in short mode")
213290 }
214291 testPath := filepath.Join("/", "etc", "ssh", "kevinburke-ssh-config-test-file")
215 err := ioutil.WriteFile(testPath, includeFile, 0644)
292 err := os.WriteFile(testPath, includeFile, 0644)
216293 if err != nil {
217294 t.Skipf("couldn't write SSH config file: %v", err.Error())
218295 }
236313 t.Skip("skipping fs write in short mode")
237314 }
238315 testPath := filepath.Join(homedir(), ".ssh", "kevinburke-ssh-config-recursive-include")
239 err := ioutil.WriteFile(testPath, recursiveIncludeFile, 0644)
316 err := os.WriteFile(testPath, recursiveIncludeFile, 0644)
240317 if err != nil {
241318 t.Skipf("couldn't write SSH config file: %v", err.Error())
242319 }
257334 if testing.Short() {
258335 t.Skip("skipping fs write in short mode")
259336 }
260 data, err := ioutil.ReadFile("testdata/include")
337 data, err := os.ReadFile("testdata/include")
261338 if err != nil {
262339 log.Fatal(err)
263340 }
0 //go:build 1.18
1 // +build 1.18
2
3 package ssh_config
4
5 import (
6 "bytes"
7 "testing"
8 )
9
10 func FuzzDecode(f *testing.F) {
11 f.Fuzz(func(t *testing.T, in []byte) {
12 _, err := Decode(bytes.NewReader(in))
13 if err != nil {
14 t.Fatalf("decode %q: %v", string(in), err)
15 }
16 })
17 }
22 import (
33 "fmt"
44 "strings"
5 "unicode"
56 )
67
78 type sshParser struct {
121122 }
122123 patterns = append(patterns, pat)
123124 }
125 // val.val at this point could be e.g. "example.com "
126 hostval := strings.TrimRightFunc(val.val, unicode.IsSpace)
127 spaceBeforeComment := val.val[len(hostval):]
128 val.val = hostval
124129 p.config.Hosts = append(p.config.Hosts, &Host{
125 Patterns: patterns,
126 Nodes: make([]Node, 0),
127 EOLComment: comment,
128 hasEquals: hasEquals,
130 Patterns: patterns,
131 Nodes: make([]Node, 0),
132 EOLComment: comment,
133 spaceBeforeComment: spaceBeforeComment,
134 hasEquals: hasEquals,
129135 })
130136 return p.parseStart
131137 }
143149 lastHost.Nodes = append(lastHost.Nodes, inc)
144150 return p.parseStart
145151 }
152 shortval := strings.TrimRightFunc(val.val, unicode.IsSpace)
153 spaceAfterValue := val.val[len(shortval):]
146154 kv := &KV{
147 Key: key.val,
148 Value: val.val,
149 Comment: comment,
150 hasEquals: hasEquals,
151 leadingSpace: key.Position.Col - 1,
152 position: key.Position,
155 Key: key.val,
156 Value: shortval,
157 spaceAfterValue: spaceAfterValue,
158 Comment: comment,
159 hasEquals: hasEquals,
160 leadingSpace: key.Position.Col - 1,
161 position: key.Position,
153162 }
154163 lastHost.Nodes = append(lastHost.Nodes, kv)
155164 return p.parseStart
0 # Config file with dos line endings
1 Host wap
2 HostName wap.example.org
3 Port 22
4 User root
5 KexAlgorithms diffie-hellman-group1-sha1
6
7 Host wap2
8 HostName 8.8.8.8
9 User google
0 # Config file with dos line endings
1 Host wap
2 HostName wap.example.org
3 Port 22
4 User root
5 KexAlgorithms diffie-hellman-group1-sha1
6
7 Host wap2
8 HostName 8.8.8.8
9 User google
0 Host example # this comment terminates a Host line
1 HostName example.com # aligned eol comment 1
2 ForwardX11Timeout 52w # aligned eol comment 2
3 # This comment takes up a whole line
4 # This comment is offset and takes up a whole line
5 AddressFamily inet # aligned eol comment 3
6 Port 4242 #compact comment
0 go test fuzz v1
1 []byte("#\t$OpenBSD: ssh_config,v 1.30 2016/02/20 23:06:23 sobrado Exp $\n\n# This is the ssh client system-wide configuration file. See\n# ssh_config(5) for more information. This file provides defaults for\n# users, and the values can be changed in per-user configuration files\n# or on the command line.\n\n# Configuration data is parsed as follows:\n# 1. command line options\n# 2. user-specific file\n# 3. system-wide file\n# Any configuration value is only changed the first time it is set.\n# Thus, host-specific definitions should be at the beginning of the\n# configuration file, and defaults at the end.\n\n# Site-wide defaults for some commonly used options. For a comprehensive\n# list of available options, their meanings and defaults, please see the\n# ssh_config(5) man page.\n\n# Host *\n# ForwardAgent no\n# ForwardX11 no\n# RhostsRSAAuthentication no\n# RSAAuthentication yes\n# PasswordAuthentication yes\n# HostbasedAuthentication no\n# GSSAPIAuthentication no\n# GSSAPIDelegateCredentials no\n# BatchMode no\n# CheckHostIP yes\n# AddressFamily any\n# ConnectTimeout 0\n# StrictHostKeyChecking ask\n# IdentityFile ~/.ssh/identity\n# IdentityFile ~/.ssh/id_rsa\n# IdentityFile ~/.ssh/id_dsa\n# IdentityFile ~/.ssh/id_ecdsa\n# IdentityFile ~/.ssh/id_ed25519\n# Port 22\n# Protocol 2\n# Cipher 3des\n# Ciphers aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc\n# MACs hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160\n# EscapeChar ~\n# Tunnel no\n# TunnelDevice any:any\n# PermitLocalCommand no\n# VisualHostKey no\n# ProxyCommand ssh -q -W %h:%p gateway.example.com\n# RekeyLimit 1G 1h\n")
0 go test fuzz v1
1 []byte("Host localhost 127.0.0.1 # A comment at the end of a host line.\n NoHostAuthenticationForLocalhost yes\n\n# A comment\n # A comment with leading spaces.\n\nHost wap\n User root\n KexAlgorithms diffie-hellman-group1-sha1\n\nHost [some stuff behind a NAT]\n Compression yes\n ProxyCommand ssh -qW %h:%p [NATrouter]\n\nHost wopr # there are 2 proxies available for this one...\n User root\n ProxyCommand sh -c \"ssh proxy1 -qW %h:22 || ssh proxy2 -qW %h:22\"\n\nHost dhcp-??\n UserKnownHostsFile /dev/null\n StrictHostKeyChecking no\n User root\n\nHost [my boxes] [*.mydomain]\n ForwardAgent yes\n ForwardX11 yes\n ForwardX11Trusted yes\n\nHost *\n #ControlMaster auto\n #ControlPath /tmp/ssh-master-%C\n #ControlPath /tmp/ssh-%u-%r@%h:%p\n #ControlPersist yes\n ForwardX11Timeout 52w\n XAuthLocation /usr/bin/xauth\n SendEnv LANG LC_*\n HostKeyAlgorithms ssh-ed25519,ssh-rsa\n AddressFamily inet\n #UpdateHostKeys ask\n")
0
1 Host hasidentity
2 IdentityFile file1
3
4 Host has2identity
5 IdentityFile f1
6 IdentityFile f2
7
8 Host protocol1
9 Protocol 1
10
159159 strings.ToLower("VisualHostKey"): "no",
160160 strings.ToLower("XAuthLocation"): "/usr/X11R6/bin/xauth",
161161 }
162
163 // these identities are used for SSH protocol 2
164 var defaultProtocol2Identities = []string{
165 "~/.ssh/id_dsa",
166 "~/.ssh/id_ecdsa",
167 "~/.ssh/id_ed25519",
168 "~/.ssh/id_rsa",
169 }
170
171 // these directives support multiple items that can be collected
172 // across multiple files
173 var pluralDirectives = map[string]bool{
174 "CertificateFile": true,
175 "IdentityFile": true,
176 "DynamicForward": true,
177 "RemoteForward": true,
178 "SendEnv": true,
179 "SetEnv": true,
180 }
181
182 // SupportsMultiple reports whether a directive can be specified multiple times.
183 func SupportsMultiple(key string) bool {
184 return pluralDirectives[strings.ToLower(key)]
185 }