Codebase list golang-github-kevinburke-ssh-config / 8d410ef6-738d-4696-a9e1-72827886d886/upstream
Import upstream version 1.2.0+git20220615.1.7df8445+ds Debian Janitor 1 year, 6 months ago
18 changed file(s) with 506 addition(s) and 73 deletion(s). Raw diff Collapse all Expand all
0 on: [push, pull_request]
1 name: Test
2 jobs:
3 lint:
4 runs-on: ubuntu-latest
5 steps:
6 - name: Install Go
7 uses: WillAbides/setup-go-faster@main
8 with:
9 go-version: 1.18.x
10 - uses: actions/checkout@v2
11 with:
12 path: './src/github.com/kevinburke/ssh_config'
13 # staticcheck needs this for GOPATH
14 - run: |
15 echo "GO111MODULE=off" >> $GITHUB_ENV
16 echo "GOPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
17 echo "PATH=$GITHUB_WORKSPACE/bin:$PATH" >> $GITHUB_ENV
18 - name: Run tests
19 run: make lint
20 working-directory: './src/github.com/kevinburke/ssh_config'
21
22 test:
23 strategy:
24 matrix:
25 go-version: [1.17.x, 1.18.x]
26 runs-on: ubuntu-latest
27 steps:
28 - name: Install Go
29 uses: WillAbides/setup-go-faster@main
30 with:
31 go-version: ${{ matrix.go-version }}
32 - uses: actions/checkout@v2
33 with:
34 path: './src/github.com/kevinburke/ssh_config'
35 - run: |
36 echo "GO111MODULE=off" >> $GITHUB_ENV
37 echo "GOPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
38 echo "PATH=$GITHUB_WORKSPACE/bin:$PATH" >> $GITHUB_ENV
39 - name: Run tests with race detector on
40 run: make race-test
41 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.
88 go vet ./...
99 $(STATICCHECK)
1010
11 test: lint
11 test:
1212 @# the timeout helps guard against infinite recursion
1313 go test -timeout=250ms ./...
1414
15 race-test: lint
15 race-test:
1616 go test -timeout=500ms -race ./...
1717
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
5352 // files are parsed and cached the first time Get() or GetStrict() is called.
5453 type UserSettings struct {
5554 IgnoreErrors bool
55 customConfig *Config
56 customConfigFinder configFinder
5657 systemConfig *Config
5758 systemConfigFinder configFinder
5859 userConfig *Config
101102 return val, nil
102103 }
103104
105 func findAll(c *Config, alias, key string) ([]string, error) {
106 if c == nil {
107 return nil, nil
108 }
109 return c.GetAll(alias, key)
110 }
111
104112 // Get finds the first value for key within a declaration that matches the
105113 // alias. Get returns the empty string if no value was found, or if IgnoreErrors
106114 // is false and we could not parse the configuration file. Use GetStrict to
113121 return DefaultUserSettings.Get(alias, key)
114122 }
115123
124 // GetAll retrieves zero or more directives for key for the given alias. GetAll
125 // returns nil if no value was found, or if IgnoreErrors is false and we could
126 // not parse the configuration file. Use GetAllStrict to disambiguate the
127 // latter cases.
128 //
129 // In most cases you want to use Get or GetStrict, which returns a single value.
130 // However, a subset of ssh configuration values (IdentityFile, for example)
131 // allow you to specify multiple directives.
132 //
133 // The match for key is case insensitive.
134 //
135 // GetAll is a wrapper around DefaultUserSettings.GetAll.
136 func GetAll(alias, key string) []string {
137 return DefaultUserSettings.GetAll(alias, key)
138 }
139
116140 // GetStrict finds the first value for key within a declaration that matches the
117141 // alias. If key has a default value and no matching configuration is found, the
118142 // default will be returned. For more information on default values and the way
119143 // patterns are matched, see the manpage for ssh_config.
120144 //
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.
145 // The returned error will be non-nil if and only if a user's configuration file
146 // or the system configuration file could not be parsed, and u.IgnoreErrors is
147 // false.
123148 //
124149 // GetStrict is a wrapper around DefaultUserSettings.GetStrict.
125150 func GetStrict(alias, key string) (string, error) {
126151 return DefaultUserSettings.GetStrict(alias, key)
152 }
153
154 // GetAllStrict retrieves zero or more directives for key for the given alias.
155 //
156 // In most cases you want to use Get or GetStrict, which returns a single value.
157 // However, a subset of ssh configuration values (IdentityFile, for example)
158 // allow you to specify multiple directives.
159 //
160 // The returned error will be non-nil if and only if a user's configuration file
161 // or the system configuration file could not be parsed, and u.IgnoreErrors is
162 // false.
163 //
164 // GetAllStrict is a wrapper around DefaultUserSettings.GetAllStrict.
165 func GetAllStrict(alias, key string) ([]string, error) {
166 return DefaultUserSettings.GetAllStrict(alias, key)
127167 }
128168
129169 // Get finds the first value for key within a declaration that matches the
140180 return val
141181 }
142182
183 // GetAll retrieves zero or more directives for key for the given alias. GetAll
184 // returns nil if no value was found, or if IgnoreErrors is false and we could
185 // not parse the configuration file. Use GetStrict to disambiguate the latter
186 // cases.
187 //
188 // The match for key is case insensitive.
189 func (u *UserSettings) GetAll(alias, key string) []string {
190 val, _ := u.GetAllStrict(alias, key)
191 return val
192 }
193
143194 // GetStrict finds the first value for key within a declaration that matches the
144195 // alias. If key has a default value and no matching configuration is found, the
145196 // default will be returned. For more information on default values and the way
148199 // error will be non-nil if and only if a user's configuration file or the
149200 // system configuration file could not be parsed, and u.IgnoreErrors is false.
150201 func (u *UserSettings) GetStrict(alias, key string) (string, error) {
202 u.doLoadConfigs()
203 //lint:ignore S1002 I prefer it this way
204 if u.onceErr != nil && u.IgnoreErrors == false {
205 return "", u.onceErr
206 }
207 // TODO this is getting repetitive
208 if u.customConfig != nil {
209 val, err := findVal(u.customConfig, alias, key)
210 if err != nil || val != "" {
211 return val, err
212 }
213 }
214 val, err := findVal(u.userConfig, alias, key)
215 if err != nil || val != "" {
216 return val, err
217 }
218 val2, err2 := findVal(u.systemConfig, alias, key)
219 if err2 != nil || val2 != "" {
220 return val2, err2
221 }
222 return Default(key), nil
223 }
224
225 // GetAllStrict retrieves zero or more directives for key for the given alias.
226 // If key has a default value and no matching configuration is found, the
227 // default will be returned. For more information on default values and the way
228 // patterns are matched, see the manpage for ssh_config.
229 //
230 // The returned error will be non-nil if and only if a user's configuration file
231 // or the system configuration file could not be parsed, and u.IgnoreErrors is
232 // false.
233 func (u *UserSettings) GetAllStrict(alias, key string) ([]string, error) {
234 u.doLoadConfigs()
235 //lint:ignore S1002 I prefer it this way
236 if u.onceErr != nil && u.IgnoreErrors == false {
237 return nil, u.onceErr
238 }
239 if u.customConfig != nil {
240 val, err := findAll(u.customConfig, alias, key)
241 if err != nil || val != nil {
242 return val, err
243 }
244 }
245 val, err := findAll(u.userConfig, alias, key)
246 if err != nil || val != nil {
247 return val, err
248 }
249 val2, err2 := findAll(u.systemConfig, alias, key)
250 if err2 != nil || val2 != nil {
251 return val2, err2
252 }
253 // TODO: IdentityFile has multiple default values that we should return.
254 if def := Default(key); def != "" {
255 return []string{def}, nil
256 }
257 return []string{}, nil
258 }
259
260 // ConfigFinder will invoke f to try to find a ssh config file in a custom
261 // location on disk, instead of in /etc/ssh or $HOME/.ssh. f should return the
262 // name of a file containing SSH configuration.
263 //
264 // ConfigFinder must be invoked before any calls to Get or GetStrict and panics
265 // if f is nil. Most users should not need to use this function.
266 func (u *UserSettings) ConfigFinder(f func() string) {
267 if f == nil {
268 panic("cannot call ConfigFinder with nil function")
269 }
270 u.customConfigFinder = f
271 }
272
273 func (u *UserSettings) doLoadConfigs() {
151274 u.loadConfigs.Do(func() {
152 // can't parse user file, that's ok.
153275 var filename string
276 var err error
277 if u.customConfigFinder != nil {
278 filename = u.customConfigFinder()
279 u.customConfig, err = parseFile(filename)
280 // IsNotExist should be returned because a user specified this
281 // function - not existing likely means they made an error
282 if err != nil {
283 u.onceErr = err
284 }
285 return
286 }
154287 if u.userConfigFinder == nil {
155288 filename = userConfigFinder()
156289 } else {
157290 filename = u.userConfigFinder()
158291 }
159 var err error
160292 u.userConfig, err = parseFile(filename)
161293 //lint:ignore S1002 I prefer it this way
162294 if err != nil && os.IsNotExist(err) == false {
175307 return
176308 }
177309 })
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
191310 }
192311
193312 func parseFile(filename string) (*Config, error) {
195314 }
196315
197316 func parseWithDepth(filename string, depth uint8) (*Config, error) {
198 b, err := ioutil.ReadFile(filename)
317 b, err := os.ReadFile(filename)
199318 if err != nil {
200319 return nil, err
201320 }
210329 // Decode reads r into a Config, or returns an error if r could not be parsed as
211330 // an SSH config file.
212331 func Decode(r io.Reader) (*Config, error) {
213 b, err := ioutil.ReadAll(r)
332 b, err := io.ReadAll(r)
214333 if err != nil {
215334 return nil, err
216335 }
336 return decodeBytes(b, false, 0)
337 }
338
339 // DecodeBytes reads b into a Config, or returns an error if r could not be
340 // parsed as an SSH config file.
341 func DecodeBytes(b []byte) (*Config, error) {
217342 return decodeBytes(b, false, 0)
218343 }
219344
281406 return "", nil
282407 }
283408
409 // GetAll returns all values in the configuration that match the alias and
410 // contains key, or nil if none are present.
411 func (c *Config) GetAll(alias, key string) ([]string, error) {
412 lowerKey := strings.ToLower(key)
413 all := []string(nil)
414 for _, host := range c.Hosts {
415 if !host.Matches(alias) {
416 continue
417 }
418 for _, node := range host.Nodes {
419 switch t := node.(type) {
420 case *Empty:
421 continue
422 case *KV:
423 // "keys are case insensitive" per the spec
424 lkey := strings.ToLower(t.Key)
425 if lkey == "match" {
426 panic("can't handle Match directives")
427 }
428 if lkey == lowerKey {
429 all = append(all, t.Value)
430 }
431 case *Include:
432 val, _ := t.GetAll(alias, key)
433 if len(val) > 0 {
434 all = append(all, val...)
435 }
436 default:
437 return nil, fmt.Errorf("unknown Node type %v", t)
438 }
439 }
440 }
441
442 return all, nil
443 }
444
284445 // String returns a string representation of the Config file.
285446 func (c Config) String() string {
286447 return marshal(c).String()
373534 // A Node is either a key/value pair or a comment line.
374535 Nodes []Node
375536 // EOLComment is the comment (if any) terminating the Host line.
376 EOLComment string
537 EOLComment string
538 // Whitespace if any between the Host declaration and a trailing comment.
539 spaceBeforeComment string
540
377541 hasEquals bool
378542 leadingSpace int // TODO: handle spaces vs tabs here.
379543 // The file starts with an implicit "Host *" declaration.
405569 // String prints h as it would appear in a config file. Minor tweaks may be
406570 // present in the whitespace in the printed file.
407571 func (h *Host) String() string {
408 var buf bytes.Buffer
572 var buf strings.Builder
409573 //lint:ignore S1002 I prefer to write it this way
410574 if h.implicit == false {
411575 buf.WriteString(strings.Repeat(" ", int(h.leadingSpace)))
421585 buf.WriteString(" ")
422586 }
423587 }
588 buf.WriteString(h.spaceBeforeComment)
424589 if h.EOLComment != "" {
425 buf.WriteString(" #")
590 buf.WriteByte('#')
426591 buf.WriteString(h.EOLComment)
427592 }
428593 buf.WriteByte('\n')
443608 // KV is a line in the config file that contains a key, a value, and possibly
444609 // a comment.
445610 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
611 Key string
612 Value string
613 // Whitespace after the value but before any comment
614 spaceAfterValue string
615 Comment string
616 hasEquals bool
617 leadingSpace int // Space before the key. TODO handle spaces vs tabs.
618 position Position
452619 }
453620
454621 // Pos returns k's Position.
456623 return k.position
457624 }
458625
459 // String prints k as it was parsed in the config file. There may be slight
460 // changes to the whitespace between values.
626 // String prints k as it was parsed in the config file.
461627 func (k *KV) String() string {
462628 if k == nil {
463629 return ""
466632 if k.hasEquals {
467633 equals = " = "
468634 }
469 line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", int(k.leadingSpace)), k.Key, equals, k.Value)
635 line := strings.Repeat(" ", int(k.leadingSpace)) + k.Key + equals + k.Value + k.spaceAfterValue
470636 if k.Comment != "" {
471 line += " #" + k.Comment
637 line += "#" + k.Comment
472638 }
473639 return line
474640 }
610776 return ""
611777 }
612778
779 // GetAll finds all values in the Include statement matching the alias and the
780 // given key.
781 func (inc *Include) GetAll(alias, key string) ([]string, error) {
782 inc.mu.Lock()
783 defer inc.mu.Unlock()
784 var vals []string
785
786 // TODO: we search files in any order which is not correct
787 for i := range inc.matches {
788 cfg := inc.files[inc.matches[i]]
789 if cfg == nil {
790 panic("nil cfg")
791 }
792 val, err := cfg.GetAll(alias, key)
793 if err == nil && len(val) != 0 {
794 // In theory if SupportsMultiple was false for this key we could
795 // stop looking here. But the caller has asked us to find all
796 // instances of the keyword (and could use Get() if they wanted) so
797 // let's keep looking.
798 vals = append(vals, val...)
799 }
800 }
801 return vals, nil
802 }
803
613804 // String prints out a string representation of this Include directive. Note
614805 // included Config files are not printed as part of this representation.
615806 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 }
377454 t.Errorf("wrong port: got %q want 4242", port)
378455 }
379456 }
457
458 func TestCustomFinder(t *testing.T) {
459 us := &UserSettings{}
460 us.ConfigFinder(func() string {
461 return "testdata/config1"
462 })
463
464 val := us.Get("wap", "User")
465 if val != "root" {
466 t.Errorf("expected to find User root, got %q", val)
467 }
468 }
11
22 import (
33 "fmt"
4 "path/filepath"
45 "strings"
56
67 "github.com/kevinburke/ssh_config"
4546 // 22
4647 //
4748 }
49
50 func ExampleUserSettings_ConfigFinder() {
51 // This can be used to test SSH config parsing.
52 u := ssh_config.UserSettings{}
53 u.ConfigFinder(func() string {
54 return filepath.Join("testdata", "test_config")
55 })
56 u.Get("example.com", "Host")
57 }
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 }
0 module github.com/kevinburke/ssh_config
1
2 go 1.18
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 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 }