mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 09:15:26 +01:00 
			
		
		
		
	
							
								
								
									
										1
									
								
								vendor/github.com/kevinburke/ssh_config/.gitattributes
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/kevinburke/ssh_config/.gitattributes
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| testdata/dos-lines eol=crlf | ||||
							
								
								
									
										0
									
								
								vendor/github.com/kevinburke/ssh_config/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/kevinburke/ssh_config/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										1
									
								
								vendor/github.com/kevinburke/ssh_config/.mailmap
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/kevinburke/ssh_config/.mailmap
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| Kevin Burke <kevin@burke.dev> Kevin Burke <kev@inburke.com> | ||||
							
								
								
									
										14
									
								
								vendor/github.com/kevinburke/ssh_config/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/kevinburke/ssh_config/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| go_import_path: github.com/kevinburke/ssh_config | ||||
|  | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|   - 1.11.x | ||||
|   - 1.12.x | ||||
|   - master | ||||
|  | ||||
| before_script: | ||||
|     - go get -u ./... | ||||
|  | ||||
| script: | ||||
|     - make race-test | ||||
							
								
								
									
										5
									
								
								vendor/github.com/kevinburke/ssh_config/AUTHORS.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/kevinburke/ssh_config/AUTHORS.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| Eugene Terentev <eugene@terentev.net> | ||||
| Kevin Burke <kevin@burke.dev> | ||||
| Mark Nevill <nev@improbable.io> | ||||
| Sergey Lukjanov <me@slukjanov.name> | ||||
| Wayne Ashley Berry <wayneashleyberry@gmail.com> | ||||
							
								
								
									
										49
									
								
								vendor/github.com/kevinburke/ssh_config/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/kevinburke/ssh_config/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| Copyright (c) 2017 Kevin Burke. | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person | ||||
| obtaining a copy of this software and associated documentation | ||||
| files (the "Software"), to deal in the Software without | ||||
| restriction, including without limitation the rights to use, | ||||
| copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following | ||||
| conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||||
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||||
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||||
| OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| =================== | ||||
|  | ||||
| The lexer and parser borrow heavily from github.com/pelletier/go-toml. The | ||||
| license for that project is copied below. | ||||
|  | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										30
									
								
								vendor/github.com/kevinburke/ssh_config/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/kevinburke/ssh_config/Makefile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| BUMP_VERSION := $(GOPATH)/bin/bump_version | ||||
| STATICCHECK := $(GOPATH)/bin/staticcheck | ||||
| WRITE_MAILMAP := $(GOPATH)/bin/write_mailmap | ||||
|  | ||||
| $(STATICCHECK): | ||||
| 	go get honnef.co/go/tools/cmd/staticcheck | ||||
|  | ||||
| lint: $(STATICCHECK) | ||||
| 	go vet ./... | ||||
| 	$(STATICCHECK) | ||||
|  | ||||
| test: lint | ||||
| 	@# the timeout helps guard against infinite recursion | ||||
| 	go test -timeout=250ms ./... | ||||
|  | ||||
| race-test: lint | ||||
| 	go test -timeout=500ms -race ./... | ||||
|  | ||||
| $(BUMP_VERSION): | ||||
| 	go get -u github.com/kevinburke/bump_version | ||||
|  | ||||
| release: test | $(BUMP_VERSION) | ||||
| 	$(BUMP_VERSION) minor config.go | ||||
|  | ||||
| force: ; | ||||
|  | ||||
| AUTHORS.txt: force | $(WRITE_MAILMAP) | ||||
| 	$(WRITE_MAILMAP) > AUTHORS.txt | ||||
|  | ||||
| authors: AUTHORS.txt | ||||
							
								
								
									
										81
									
								
								vendor/github.com/kevinburke/ssh_config/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								vendor/github.com/kevinburke/ssh_config/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| # ssh_config | ||||
|  | ||||
| This is a Go parser for `ssh_config` files. Importantly, this parser attempts | ||||
| to preserve comments in a given file, so you can manipulate a `ssh_config` file | ||||
| from a program, if your heart desires. | ||||
|  | ||||
| It's designed to be used with the excellent | ||||
| [x/crypto/ssh](https://golang.org/x/crypto/ssh) package, which handles SSH | ||||
| negotiation but isn't very easy to configure. | ||||
|  | ||||
| The `ssh_config` `Get()` and `GetStrict()` functions will attempt to read values | ||||
| from `$HOME/.ssh/config` and fall back to `/etc/ssh/ssh_config`. The first | ||||
| argument is the host name to match on, and the second argument is the key you | ||||
| want to retrieve. | ||||
|  | ||||
| ```go | ||||
| port := ssh_config.Get("myhost", "Port") | ||||
| ``` | ||||
|  | ||||
| You can also load a config file and read values from it. | ||||
|  | ||||
| ```go | ||||
| var config = ` | ||||
| Host *.test | ||||
|   Compression yes | ||||
| ` | ||||
|  | ||||
| cfg, err := ssh_config.Decode(strings.NewReader(config)) | ||||
| fmt.Println(cfg.Get("example.test", "Port")) | ||||
| ``` | ||||
|  | ||||
| Some SSH arguments have default values - for example, the default value for | ||||
| `KeyboardAuthentication` is `"yes"`. If you call Get(), and no value for the | ||||
| given Host/keyword pair exists in the config, we'll return a default for the | ||||
| keyword if one exists. | ||||
|  | ||||
| ### Manipulating SSH config files | ||||
|  | ||||
| Here's how you can manipulate an SSH config file, and then write it back to | ||||
| disk. | ||||
|  | ||||
| ```go | ||||
| f, _ := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config")) | ||||
| cfg, _ := ssh_config.Decode(f) | ||||
| for _, host := range cfg.Hosts { | ||||
|     fmt.Println("patterns:", host.Patterns) | ||||
|     for _, node := range host.Nodes { | ||||
|         // Manipulate the nodes as you see fit, or use a type switch to | ||||
|         // distinguish between Empty, KV, and Include nodes. | ||||
|         fmt.Println(node.String()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Print the config to stdout: | ||||
| fmt.Println(cfg.String()) | ||||
| ``` | ||||
|  | ||||
| ## Spec compliance | ||||
|  | ||||
| Wherever possible we try to implement the specification as documented in | ||||
| the `ssh_config` manpage. Unimplemented features should be present in the | ||||
| [issues][issues] list. | ||||
|  | ||||
| Notably, the `Match` directive is currently unsupported. | ||||
|  | ||||
| [issues]: https://github.com/kevinburke/ssh_config/issues | ||||
|  | ||||
| ## Errata | ||||
|  | ||||
| This is the second [comment-preserving configuration parser][blog] I've written, after | ||||
| [an /etc/hosts parser][hostsfile]. Eventually, I will write one for every Linux | ||||
| file format. | ||||
|  | ||||
| [blog]: https://kev.inburke.com/kevin/more-comment-preserving-configuration-parsers/ | ||||
| [hostsfile]: https://github.com/kevinburke/hostsfile | ||||
|  | ||||
| ## Donating | ||||
|  | ||||
| Donations free up time to make improvements to the library, and respond to | ||||
| bug reports. You can send donations via Paypal's "Send Money" feature to | ||||
| kev@inburke.com. Donations are not tax deductible in the USA. | ||||
							
								
								
									
										649
									
								
								vendor/github.com/kevinburke/ssh_config/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										649
									
								
								vendor/github.com/kevinburke/ssh_config/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,649 @@ | ||||
| // Package ssh_config provides tools for manipulating SSH config files. | ||||
| // | ||||
| // Importantly, this parser attempts to preserve comments in a given file, so | ||||
| // you can manipulate a `ssh_config` file from a program, if your heart desires. | ||||
| // | ||||
| // The Get() and GetStrict() functions will attempt to read values from | ||||
| // $HOME/.ssh/config, falling back to /etc/ssh/ssh_config. The first argument is | ||||
| // the host name to match on ("example.com"), and the second argument is the key | ||||
| // you want to retrieve ("Port"). The keywords are case insensitive. | ||||
| // | ||||
| // 		port := ssh_config.Get("myhost", "Port") | ||||
| // | ||||
| // You can also manipulate an SSH config file and then print it or write it back | ||||
| // to disk. | ||||
| // | ||||
| //	f, _ := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config")) | ||||
| //	cfg, _ := ssh_config.Decode(f) | ||||
| //	for _, host := range cfg.Hosts { | ||||
| //		fmt.Println("patterns:", host.Patterns) | ||||
| //		for _, node := range host.Nodes { | ||||
| //			fmt.Println(node.String()) | ||||
| //		} | ||||
| //	} | ||||
| // | ||||
| //	// Write the cfg back to disk: | ||||
| //	fmt.Println(cfg.String()) | ||||
| // | ||||
| // BUG: the Match directive is currently unsupported; parsing a config with | ||||
| // a Match directive will trigger an error. | ||||
| package ssh_config | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	osuser "os/user" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| const version = "1.0" | ||||
|  | ||||
| var _ = version | ||||
|  | ||||
| type configFinder func() string | ||||
|  | ||||
| // 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 | ||||
| 	systemConfig       *Config | ||||
| 	systemConfigFinder configFinder | ||||
| 	userConfig         *Config | ||||
| 	userConfigFinder   configFinder | ||||
| 	loadConfigs        sync.Once | ||||
| 	onceErr            error | ||||
| } | ||||
|  | ||||
| func homedir() string { | ||||
| 	user, err := osuser.Current() | ||||
| 	if err == nil { | ||||
| 		return user.HomeDir | ||||
| 	} else { | ||||
| 		return os.Getenv("HOME") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func userConfigFinder() string { | ||||
| 	return filepath.Join(homedir(), ".ssh", "config") | ||||
| } | ||||
|  | ||||
| // DefaultUserSettings is the default UserSettings and is used by Get and | ||||
| // 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, | ||||
| } | ||||
|  | ||||
| func systemConfigFinder() string { | ||||
| 	return filepath.Join("/", "etc", "ssh", "ssh_config") | ||||
| } | ||||
|  | ||||
| func findVal(c *Config, alias, key string) (string, error) { | ||||
| 	if c == nil { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	val, err := c.Get(alias, key) | ||||
| 	if err != nil || val == "" { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if err := validate(key, val); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return val, nil | ||||
| } | ||||
|  | ||||
| // Get finds the first value for key within a declaration that matches the | ||||
| // alias. Get returns the empty string if no value was found, or if IgnoreErrors | ||||
| // is false and we could not parse the configuration file. Use GetStrict to | ||||
| // disambiguate the latter cases. | ||||
| // | ||||
| // The match for key is case insensitive. | ||||
| // | ||||
| // Get is a wrapper around DefaultUserSettings.Get. | ||||
| func Get(alias, key string) string { | ||||
| 	return DefaultUserSettings.Get(alias, key) | ||||
| } | ||||
|  | ||||
| // GetStrict finds the first value for key within a declaration that matches the | ||||
| // alias. If key has a default value and no matching configuration is found, the | ||||
| // default will be returned. For more information on default values and the way | ||||
| // patterns are matched, see the manpage for ssh_config. | ||||
| // | ||||
| // error will be non-nil if and only if a user's configuration file or the | ||||
| // system configuration file could not be parsed, and u.IgnoreErrors is false. | ||||
| // | ||||
| // GetStrict is a wrapper around DefaultUserSettings.GetStrict. | ||||
| func GetStrict(alias, key string) (string, error) { | ||||
| 	return DefaultUserSettings.GetStrict(alias, key) | ||||
| } | ||||
|  | ||||
| // Get finds the first value for key within a declaration that matches the | ||||
| // alias. Get returns the empty string if no value was found, or if IgnoreErrors | ||||
| // is false and we could not parse the configuration file. Use GetStrict to | ||||
| // disambiguate the latter cases. | ||||
| // | ||||
| // The match for key is case insensitive. | ||||
| func (u *UserSettings) Get(alias, key string) string { | ||||
| 	val, err := u.GetStrict(alias, key) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return val | ||||
| } | ||||
|  | ||||
| // GetStrict finds the first value for key within a declaration that matches the | ||||
| // alias. If key has a default value and no matching configuration is found, the | ||||
| // default will be returned. For more information on default values and the way | ||||
| // patterns are matched, see the manpage for ssh_config. | ||||
| // | ||||
| // error will be non-nil if and only if a user's configuration file or the | ||||
| // system configuration file could not be parsed, and u.IgnoreErrors is false. | ||||
| func (u *UserSettings) GetStrict(alias, key string) (string, error) { | ||||
| 	u.loadConfigs.Do(func() { | ||||
| 		// can't parse user file, that's ok. | ||||
| 		var filename string | ||||
| 		if u.userConfigFinder == nil { | ||||
| 			filename = userConfigFinder() | ||||
| 		} else { | ||||
| 			filename = u.userConfigFinder() | ||||
| 		} | ||||
| 		var err error | ||||
| 		u.userConfig, err = parseFile(filename) | ||||
| 		//lint:ignore S1002 I prefer it this way | ||||
| 		if err != nil && os.IsNotExist(err) == false { | ||||
| 			u.onceErr = err | ||||
| 			return | ||||
| 		} | ||||
| 		if u.systemConfigFinder == nil { | ||||
| 			filename = systemConfigFinder() | ||||
| 		} else { | ||||
| 			filename = u.systemConfigFinder() | ||||
| 		} | ||||
| 		u.systemConfig, err = parseFile(filename) | ||||
| 		//lint:ignore S1002 I prefer it this way | ||||
| 		if err != nil && os.IsNotExist(err) == false { | ||||
| 			u.onceErr = err | ||||
| 			return | ||||
| 		} | ||||
| 	}) | ||||
| 	//lint:ignore S1002 I prefer it this way | ||||
| 	if u.onceErr != nil && u.IgnoreErrors == false { | ||||
| 		return "", u.onceErr | ||||
| 	} | ||||
| 	val, err := findVal(u.userConfig, alias, key) | ||||
| 	if err != nil || val != "" { | ||||
| 		return val, err | ||||
| 	} | ||||
| 	val2, err2 := findVal(u.systemConfig, alias, key) | ||||
| 	if err2 != nil || val2 != "" { | ||||
| 		return val2, err2 | ||||
| 	} | ||||
| 	return Default(key), nil | ||||
| } | ||||
|  | ||||
| func parseFile(filename string) (*Config, error) { | ||||
| 	return parseWithDepth(filename, 0) | ||||
| } | ||||
|  | ||||
| func parseWithDepth(filename string, depth uint8) (*Config, error) { | ||||
| 	b, err := ioutil.ReadFile(filename) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return decodeBytes(b, isSystem(filename), depth) | ||||
| } | ||||
|  | ||||
| func isSystem(filename string) bool { | ||||
| 	// TODO: not sure this is the best way to detect a system repo | ||||
| 	return strings.HasPrefix(filepath.Clean(filename), "/etc/ssh") | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| 	b, err := ioutil.ReadAll(r) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return decodeBytes(b, false, 0) | ||||
| } | ||||
|  | ||||
| func decodeBytes(b []byte, system bool, depth uint8) (c *Config, err error) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			if _, ok := r.(runtime.Error); ok { | ||||
| 				panic(r) | ||||
| 			} | ||||
| 			if e, ok := r.(error); ok && e == ErrDepthExceeded { | ||||
| 				err = e | ||||
| 				return | ||||
| 			} | ||||
| 			err = errors.New(r.(string)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	c = parseSSH(lexSSH(b), system, depth) | ||||
| 	return c, err | ||||
| } | ||||
|  | ||||
| // Config represents an SSH config file. | ||||
| 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 | ||||
| } | ||||
|  | ||||
| // Get finds the first value in the configuration that matches the alias and | ||||
| // contains key. Get returns the empty string if no value was found, or if the | ||||
| // Config contains an invalid conditional Include value. | ||||
| // | ||||
| // The match for key is case insensitive. | ||||
| func (c *Config) Get(alias, key string) (string, error) { | ||||
| 	lowerKey := strings.ToLower(key) | ||||
| 	for _, host := range c.Hosts { | ||||
| 		if !host.Matches(alias) { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, node := range host.Nodes { | ||||
| 			switch t := node.(type) { | ||||
| 			case *Empty: | ||||
| 				continue | ||||
| 			case *KV: | ||||
| 				// "keys are case insensitive" per the spec | ||||
| 				lkey := strings.ToLower(t.Key) | ||||
| 				if lkey == "match" { | ||||
| 					panic("can't handle Match directives") | ||||
| 				} | ||||
| 				if lkey == lowerKey { | ||||
| 					return t.Value, nil | ||||
| 				} | ||||
| 			case *Include: | ||||
| 				val := t.Get(alias, key) | ||||
| 				if val != "" { | ||||
| 					return val, nil | ||||
| 				} | ||||
| 			default: | ||||
| 				return "", fmt.Errorf("unknown Node type %v", t) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // String returns a string representation of the Config file. | ||||
| func (c Config) String() string { | ||||
| 	return marshal(c).String() | ||||
| } | ||||
|  | ||||
| func (c Config) MarshalText() ([]byte, error) { | ||||
| 	return marshal(c).Bytes(), nil | ||||
| } | ||||
|  | ||||
| func marshal(c Config) *bytes.Buffer { | ||||
| 	var buf bytes.Buffer | ||||
| 	for i := range c.Hosts { | ||||
| 		buf.WriteString(c.Hosts[i].String()) | ||||
| 	} | ||||
| 	return &buf | ||||
| } | ||||
|  | ||||
| // Pattern is a pattern in a Host declaration. Patterns are read-only values; | ||||
| // create a new one with NewPattern(). | ||||
| 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. | ||||
| func (p Pattern) String() string { | ||||
| 	return p.str | ||||
| } | ||||
|  | ||||
| // Copied from regexp.go with * and ? removed. | ||||
| var specialBytes = []byte(`\.+()|[]{}^$`) | ||||
|  | ||||
| func special(b byte) bool { | ||||
| 	return bytes.IndexByte(specialBytes, b) >= 0 | ||||
| } | ||||
|  | ||||
| // NewPattern creates a new Pattern for matching hosts. NewPattern("*") creates | ||||
| // a Pattern that matches all hosts. | ||||
| // | ||||
| // From the manpage, a pattern consists of zero or more non-whitespace | ||||
| // characters, `*' (a wildcard that matches zero or more characters), or `?' (a | ||||
| // wildcard that matches exactly one character). For example, to specify a set | ||||
| // of declarations for any host in the ".co.uk" set of domains, the following | ||||
| // pattern could be used: | ||||
| // | ||||
| //	Host *.co.uk | ||||
| // | ||||
| // The following pattern would match any host in the 192.168.0.[0-9] network range: | ||||
| // | ||||
| //	Host 192.168.0.? | ||||
| func NewPattern(s string) (*Pattern, error) { | ||||
| 	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++ { | ||||
| 		// A byte loop is correct because all metacharacters are ASCII. | ||||
| 		switch b := s[i]; b { | ||||
| 		case '*': | ||||
| 			buf.WriteString(".*") | ||||
| 		case '?': | ||||
| 			buf.WriteString(".?") | ||||
| 		default: | ||||
| 			// borrowing from QuoteMeta here. | ||||
| 			if special(b) { | ||||
| 				buf.WriteByte('\\') | ||||
| 			} | ||||
| 			buf.WriteByte(b) | ||||
| 		} | ||||
| 	} | ||||
| 	buf.WriteByte('$') | ||||
| 	r, err := regexp.Compile(buf.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &Pattern{str: s, regex: r, not: negated}, nil | ||||
| } | ||||
|  | ||||
| // Host describes a Host directive and the keywords that follow it. | ||||
| type Host struct { | ||||
| 	// A list of host patterns that should match this host. | ||||
| 	Patterns []*Pattern | ||||
| 	// A Node is either a key/value pair or a comment line. | ||||
| 	Nodes []Node | ||||
| 	// EOLComment is the comment (if any) terminating the Host line. | ||||
| 	EOLComment   string | ||||
| 	hasEquals    bool | ||||
| 	leadingSpace int // TODO: handle spaces vs tabs here. | ||||
| 	// The file starts with an implicit "Host *" declaration. | ||||
| 	implicit bool | ||||
| } | ||||
|  | ||||
| // Matches returns true if the Host matches for the given alias. For | ||||
| // 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) { | ||||
| 			if h.Patterns[i].not { | ||||
| 				// 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 found | ||||
| } | ||||
|  | ||||
| // String prints h as it would appear in a config file. Minor tweaks may be | ||||
| // present in the whitespace in the printed file. | ||||
| func (h *Host) String() string { | ||||
| 	var buf bytes.Buffer | ||||
| 	//lint:ignore S1002 I prefer to write it this way | ||||
| 	if h.implicit == false { | ||||
| 		buf.WriteString(strings.Repeat(" ", int(h.leadingSpace))) | ||||
| 		buf.WriteString("Host") | ||||
| 		if h.hasEquals { | ||||
| 			buf.WriteString(" = ") | ||||
| 		} else { | ||||
| 			buf.WriteString(" ") | ||||
| 		} | ||||
| 		for i, pat := range h.Patterns { | ||||
| 			buf.WriteString(pat.String()) | ||||
| 			if i < len(h.Patterns)-1 { | ||||
| 				buf.WriteString(" ") | ||||
| 			} | ||||
| 		} | ||||
| 		if h.EOLComment != "" { | ||||
| 			buf.WriteString(" #") | ||||
| 			buf.WriteString(h.EOLComment) | ||||
| 		} | ||||
| 		buf.WriteByte('\n') | ||||
| 	} | ||||
| 	for i := range h.Nodes { | ||||
| 		buf.WriteString(h.Nodes[i].String()) | ||||
| 		buf.WriteByte('\n') | ||||
| 	} | ||||
| 	return buf.String() | ||||
| } | ||||
|  | ||||
| // Node represents a line in a Config. | ||||
| type Node interface { | ||||
| 	Pos() Position | ||||
| 	String() string | ||||
| } | ||||
|  | ||||
| // KV is a line in the config file that contains a key, a value, and possibly | ||||
| // a comment. | ||||
| type KV struct { | ||||
| 	Key          string | ||||
| 	Value        string | ||||
| 	Comment      string | ||||
| 	hasEquals    bool | ||||
| 	leadingSpace int // Space before the key. TODO handle spaces vs tabs. | ||||
| 	position     Position | ||||
| } | ||||
|  | ||||
| // Pos returns k's Position. | ||||
| func (k *KV) Pos() Position { | ||||
| 	return k.position | ||||
| } | ||||
|  | ||||
| // String prints k as it was parsed in the config file. There may be slight | ||||
| // changes to the whitespace between values. | ||||
| func (k *KV) String() string { | ||||
| 	if k == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	equals := " " | ||||
| 	if k.hasEquals { | ||||
| 		equals = " = " | ||||
| 	} | ||||
| 	line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", int(k.leadingSpace)), k.Key, equals, k.Value) | ||||
| 	if k.Comment != "" { | ||||
| 		line += " #" + k.Comment | ||||
| 	} | ||||
| 	return line | ||||
| } | ||||
|  | ||||
| // Empty is a line in the config file that contains only whitespace or comments. | ||||
| type Empty struct { | ||||
| 	Comment      string | ||||
| 	leadingSpace int // TODO handle spaces vs tabs. | ||||
| 	position     Position | ||||
| } | ||||
|  | ||||
| // Pos returns e's Position. | ||||
| func (e *Empty) Pos() Position { | ||||
| 	return e.position | ||||
| } | ||||
|  | ||||
| // String prints e as it was parsed in the config file. | ||||
| func (e *Empty) String() string { | ||||
| 	if e == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	if e.Comment == "" { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return fmt.Sprintf("%s#%s", strings.Repeat(" ", int(e.leadingSpace)), e.Comment) | ||||
| } | ||||
|  | ||||
| // Include holds the result of an Include directive, including the config files | ||||
| // that have been parsed as part of that directive. At most 5 levels of Include | ||||
| // statements will be parsed. | ||||
| type Include struct { | ||||
| 	// Comment is the contents of any comment at the end of the Include | ||||
| 	// statement. | ||||
| 	Comment string | ||||
| 	// an include directive can include several different files, and wildcards | ||||
| 	directives []string | ||||
|  | ||||
| 	mu sync.Mutex | ||||
| 	// 1:1 mapping between matches and keys in files array; matches preserves | ||||
| 	// ordering | ||||
| 	matches []string | ||||
| 	// actual filenames are listed here | ||||
| 	files        map[string]*Config | ||||
| 	leadingSpace int | ||||
| 	position     Position | ||||
| 	depth        uint8 | ||||
| 	hasEquals    bool | ||||
| } | ||||
|  | ||||
| const maxRecurseDepth = 5 | ||||
|  | ||||
| // ErrDepthExceeded is returned if too many Include directives are parsed. | ||||
| // Usually this indicates a recursive loop (an Include directive pointing to the | ||||
| // file it contains). | ||||
| var ErrDepthExceeded = errors.New("ssh_config: max recurse depth exceeded") | ||||
|  | ||||
| func removeDups(arr []string) []string { | ||||
| 	// Use map to record duplicates as we find them. | ||||
| 	encountered := make(map[string]bool, len(arr)) | ||||
| 	result := make([]string, 0) | ||||
|  | ||||
| 	for v := range arr { | ||||
| 		//lint:ignore S1002 I prefer it this way | ||||
| 		if encountered[arr[v]] == false { | ||||
| 			encountered[arr[v]] = true | ||||
| 			result = append(result, arr[v]) | ||||
| 		} | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // NewInclude creates a new Include with a list of file globs to include. | ||||
| // 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) { | ||||
| 	if depth > maxRecurseDepth { | ||||
| 		return nil, ErrDepthExceeded | ||||
| 	} | ||||
| 	inc := &Include{ | ||||
| 		Comment:      comment, | ||||
| 		directives:   directives, | ||||
| 		files:        make(map[string]*Config), | ||||
| 		position:     pos, | ||||
| 		leadingSpace: pos.Col - 1, | ||||
| 		depth:        depth, | ||||
| 		hasEquals:    hasEquals, | ||||
| 	} | ||||
| 	// no need for inc.mu.Lock() since nothing else can access this inc | ||||
| 	matches := make([]string, 0) | ||||
| 	for i := range directives { | ||||
| 		var path string | ||||
| 		if filepath.IsAbs(directives[i]) { | ||||
| 			path = directives[i] | ||||
| 		} else if system { | ||||
| 			path = filepath.Join("/etc/ssh", directives[i]) | ||||
| 		} else { | ||||
| 			path = filepath.Join(homedir(), ".ssh", directives[i]) | ||||
| 		} | ||||
| 		theseMatches, err := filepath.Glob(path) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		matches = append(matches, theseMatches...) | ||||
| 	} | ||||
| 	matches = removeDups(matches) | ||||
| 	inc.matches = matches | ||||
| 	for i := range matches { | ||||
| 		config, err := parseWithDepth(matches[i], depth) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		inc.files[matches[i]] = config | ||||
| 	} | ||||
| 	return inc, nil | ||||
| } | ||||
|  | ||||
| // Pos returns the position of the Include directive in the larger file. | ||||
| func (i *Include) Pos() Position { | ||||
| 	return i.position | ||||
| } | ||||
|  | ||||
| // Get finds the first value in the Include statement matching the alias and the | ||||
| // given key. | ||||
| func (inc *Include) Get(alias, key string) string { | ||||
| 	inc.mu.Lock() | ||||
| 	defer inc.mu.Unlock() | ||||
| 	// TODO: we search files in any order which is not correct | ||||
| 	for i := range inc.matches { | ||||
| 		cfg := inc.files[inc.matches[i]] | ||||
| 		if cfg == nil { | ||||
| 			panic("nil cfg") | ||||
| 		} | ||||
| 		val, err := cfg.Get(alias, key) | ||||
| 		if err == nil && val != "" { | ||||
| 			return val | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // String prints out a string representation of this Include directive. Note | ||||
| // included Config files are not printed as part of this representation. | ||||
| func (inc *Include) String() string { | ||||
| 	equals := " " | ||||
| 	if inc.hasEquals { | ||||
| 		equals = " = " | ||||
| 	} | ||||
| 	line := fmt.Sprintf("%sInclude%s%s", strings.Repeat(" ", int(inc.leadingSpace)), equals, strings.Join(inc.directives, " ")) | ||||
| 	if inc.Comment != "" { | ||||
| 		line += " #" + inc.Comment | ||||
| 	} | ||||
| 	return line | ||||
| } | ||||
|  | ||||
| var matchAll *Pattern | ||||
|  | ||||
| func init() { | ||||
| 	var err error | ||||
| 	matchAll, err = NewPattern("*") | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func newConfig() *Config { | ||||
| 	return &Config{ | ||||
| 		Hosts: []*Host{ | ||||
| 			&Host{ | ||||
| 				implicit: true, | ||||
| 				Patterns: []*Pattern{matchAll}, | ||||
| 				Nodes:    make([]Node, 0), | ||||
| 			}, | ||||
| 		}, | ||||
| 		depth: 0, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										240
									
								
								vendor/github.com/kevinburke/ssh_config/lexer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								vendor/github.com/kevinburke/ssh_config/lexer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | ||||
| package ssh_config | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| ) | ||||
|  | ||||
| // Define state functions | ||||
| type sshLexStateFn func() sshLexStateFn | ||||
|  | ||||
| type sshLexer struct { | ||||
| 	inputIdx int | ||||
| 	input    []rune // Textual source | ||||
|  | ||||
| 	buffer        []rune // Runes composing the current token | ||||
| 	tokens        chan token | ||||
| 	line          int | ||||
| 	col           int | ||||
| 	endbufferLine int | ||||
| 	endbufferCol  int | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) lexComment(previousState sshLexStateFn) sshLexStateFn { | ||||
| 	return func() sshLexStateFn { | ||||
| 		growingString := "" | ||||
| 		for next := s.peek(); next != '\n' && next != eof; next = s.peek() { | ||||
| 			if next == '\r' && s.follow("\r\n") { | ||||
| 				break | ||||
| 			} | ||||
| 			growingString += string(next) | ||||
| 			s.next() | ||||
| 		} | ||||
| 		s.emitWithValue(tokenComment, growingString) | ||||
| 		s.skip() | ||||
| 		return previousState | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // lex the space after an equals sign in a function | ||||
| func (s *sshLexer) lexRspace() sshLexStateFn { | ||||
| 	for { | ||||
| 		next := s.peek() | ||||
| 		if !isSpace(next) { | ||||
| 			break | ||||
| 		} | ||||
| 		s.skip() | ||||
| 	} | ||||
| 	return s.lexRvalue | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) lexEquals() sshLexStateFn { | ||||
| 	for { | ||||
| 		next := s.peek() | ||||
| 		if next == '=' { | ||||
| 			s.emit(tokenEquals) | ||||
| 			s.skip() | ||||
| 			return s.lexRspace | ||||
| 		} | ||||
| 		// TODO error handling here; newline eof etc. | ||||
| 		if !isSpace(next) { | ||||
| 			break | ||||
| 		} | ||||
| 		s.skip() | ||||
| 	} | ||||
| 	return s.lexRvalue | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) lexKey() sshLexStateFn { | ||||
| 	growingString := "" | ||||
|  | ||||
| 	for r := s.peek(); isKeyChar(r); r = s.peek() { | ||||
| 		// simplified a lot here | ||||
| 		if isSpace(r) || r == '=' { | ||||
| 			s.emitWithValue(tokenKey, growingString) | ||||
| 			s.skip() | ||||
| 			return s.lexEquals | ||||
| 		} | ||||
| 		growingString += string(r) | ||||
| 		s.next() | ||||
| 	} | ||||
| 	s.emitWithValue(tokenKey, growingString) | ||||
| 	return s.lexEquals | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) lexRvalue() sshLexStateFn { | ||||
| 	growingString := "" | ||||
| 	for { | ||||
| 		next := s.peek() | ||||
| 		switch next { | ||||
| 		case '\r': | ||||
| 			if s.follow("\r\n") { | ||||
| 				s.emitWithValue(tokenString, growingString) | ||||
| 				s.skip() | ||||
| 				return s.lexVoid | ||||
| 			} | ||||
| 		case '\n': | ||||
| 			s.emitWithValue(tokenString, growingString) | ||||
| 			s.skip() | ||||
| 			return s.lexVoid | ||||
| 		case '#': | ||||
| 			s.emitWithValue(tokenString, growingString) | ||||
| 			s.skip() | ||||
| 			return s.lexComment(s.lexVoid) | ||||
| 		case eof: | ||||
| 			s.next() | ||||
| 		} | ||||
| 		if next == eof { | ||||
| 			break | ||||
| 		} | ||||
| 		growingString += string(next) | ||||
| 		s.next() | ||||
| 	} | ||||
| 	s.emit(tokenEOF) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) read() rune { | ||||
| 	r := s.peek() | ||||
| 	if r == '\n' { | ||||
| 		s.endbufferLine++ | ||||
| 		s.endbufferCol = 1 | ||||
| 	} else { | ||||
| 		s.endbufferCol++ | ||||
| 	} | ||||
| 	s.inputIdx++ | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) next() rune { | ||||
| 	r := s.read() | ||||
|  | ||||
| 	if r != eof { | ||||
| 		s.buffer = append(s.buffer, r) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) lexVoid() sshLexStateFn { | ||||
| 	for { | ||||
| 		next := s.peek() | ||||
| 		switch next { | ||||
| 		case '#': | ||||
| 			s.skip() | ||||
| 			return s.lexComment(s.lexVoid) | ||||
| 		case '\r': | ||||
| 			fallthrough | ||||
| 		case '\n': | ||||
| 			s.emit(tokenEmptyLine) | ||||
| 			s.skip() | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if isSpace(next) { | ||||
| 			s.skip() | ||||
| 		} | ||||
|  | ||||
| 		if isKeyStartChar(next) { | ||||
| 			return s.lexKey | ||||
| 		} | ||||
|  | ||||
| 		// removed IsKeyStartChar and lexKey. probably will need to readd | ||||
|  | ||||
| 		if next == eof { | ||||
| 			s.next() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	s.emit(tokenEOF) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) ignore() { | ||||
| 	s.buffer = make([]rune, 0) | ||||
| 	s.line = s.endbufferLine | ||||
| 	s.col = s.endbufferCol | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) skip() { | ||||
| 	s.next() | ||||
| 	s.ignore() | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) emit(t tokenType) { | ||||
| 	s.emitWithValue(t, string(s.buffer)) | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) emitWithValue(t tokenType, value string) { | ||||
| 	tok := token{ | ||||
| 		Position: Position{s.line, s.col}, | ||||
| 		typ:      t, | ||||
| 		val:      value, | ||||
| 	} | ||||
| 	s.tokens <- tok | ||||
| 	s.ignore() | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) peek() rune { | ||||
| 	if s.inputIdx >= len(s.input) { | ||||
| 		return eof | ||||
| 	} | ||||
|  | ||||
| 	r := s.input[s.inputIdx] | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) follow(next string) bool { | ||||
| 	inputIdx := s.inputIdx | ||||
| 	for _, expectedRune := range next { | ||||
| 		if inputIdx >= len(s.input) { | ||||
| 			return false | ||||
| 		} | ||||
| 		r := s.input[inputIdx] | ||||
| 		inputIdx++ | ||||
| 		if expectedRune != r { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (s *sshLexer) run() { | ||||
| 	for state := s.lexVoid; state != nil; { | ||||
| 		state = state() | ||||
| 	} | ||||
| 	close(s.tokens) | ||||
| } | ||||
|  | ||||
| func lexSSH(input []byte) chan token { | ||||
| 	runes := bytes.Runes(input) | ||||
| 	l := &sshLexer{ | ||||
| 		input:         runes, | ||||
| 		tokens:        make(chan token), | ||||
| 		line:          1, | ||||
| 		col:           1, | ||||
| 		endbufferLine: 1, | ||||
| 		endbufferCol:  1, | ||||
| 	} | ||||
| 	go l.run() | ||||
| 	return l.tokens | ||||
| } | ||||
							
								
								
									
										191
									
								
								vendor/github.com/kevinburke/ssh_config/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								vendor/github.com/kevinburke/ssh_config/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| package ssh_config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type sshParser struct { | ||||
| 	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 | ||||
| 	depth  uint8 | ||||
| } | ||||
|  | ||||
| type sshParserStateFn func() sshParserStateFn | ||||
|  | ||||
| // Formats and panics an error message based on a token | ||||
| func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) { | ||||
| 	// TODO this format is ugly | ||||
| 	panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) | ||||
| } | ||||
|  | ||||
| func (p *sshParser) raiseError(tok *token, err error) { | ||||
| 	if err == ErrDepthExceeded { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	// TODO this format is ugly | ||||
| 	panic(tok.Position.String() + ": " + err.Error()) | ||||
| } | ||||
|  | ||||
| func (p *sshParser) run() { | ||||
| 	for state := p.parseStart; state != nil; { | ||||
| 		state = state() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *sshParser) peek() *token { | ||||
| 	if len(p.tokensBuffer) != 0 { | ||||
| 		return &(p.tokensBuffer[0]) | ||||
| 	} | ||||
|  | ||||
| 	tok, ok := <-p.flow | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	p.tokensBuffer = append(p.tokensBuffer, tok) | ||||
| 	return &tok | ||||
| } | ||||
|  | ||||
| func (p *sshParser) getToken() *token { | ||||
| 	if len(p.tokensBuffer) != 0 { | ||||
| 		tok := p.tokensBuffer[0] | ||||
| 		p.tokensBuffer = p.tokensBuffer[1:] | ||||
| 		return &tok | ||||
| 	} | ||||
| 	tok, ok := <-p.flow | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return &tok | ||||
| } | ||||
|  | ||||
| func (p *sshParser) parseStart() sshParserStateFn { | ||||
| 	tok := p.peek() | ||||
|  | ||||
| 	// end of stream, parsing is finished | ||||
| 	if tok == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	switch tok.typ { | ||||
| 	case tokenComment, tokenEmptyLine: | ||||
| 		return p.parseComment | ||||
| 	case tokenKey: | ||||
| 		return p.parseKV | ||||
| 	case tokenEOF: | ||||
| 		return nil | ||||
| 	default: | ||||
| 		p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *sshParser) parseKV() sshParserStateFn { | ||||
| 	key := p.getToken() | ||||
| 	hasEquals := false | ||||
| 	val := p.getToken() | ||||
| 	if val.typ == tokenEquals { | ||||
| 		hasEquals = true | ||||
| 		val = p.getToken() | ||||
| 	} | ||||
| 	comment := "" | ||||
| 	tok := p.peek() | ||||
| 	if tok == nil { | ||||
| 		tok = &token{typ: tokenEOF} | ||||
| 	} | ||||
| 	if tok.typ == tokenComment && tok.Position.Line == val.Position.Line { | ||||
| 		tok = p.getToken() | ||||
| 		comment = tok.val | ||||
| 	} | ||||
| 	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, " ") | ||||
| 		patterns := make([]*Pattern, 0) | ||||
| 		for i := range strPatterns { | ||||
| 			if strPatterns[i] == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 			pat, err := NewPattern(strPatterns[i]) | ||||
| 			if err != nil { | ||||
| 				p.raiseErrorf(val, "Invalid host pattern: %v", err) | ||||
| 				return nil | ||||
| 			} | ||||
| 			patterns = append(patterns, pat) | ||||
| 		} | ||||
| 		p.config.Hosts = append(p.config.Hosts, &Host{ | ||||
| 			Patterns:   patterns, | ||||
| 			Nodes:      make([]Node, 0), | ||||
| 			EOLComment: comment, | ||||
| 			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) | ||||
| 		if err == ErrDepthExceeded { | ||||
| 			p.raiseError(val, err) | ||||
| 			return nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			p.raiseErrorf(val, "Error parsing Include directive: %v", err) | ||||
| 			return nil | ||||
| 		} | ||||
| 		lastHost.Nodes = append(lastHost.Nodes, inc) | ||||
| 		return p.parseStart | ||||
| 	} | ||||
| 	kv := &KV{ | ||||
| 		Key:          key.val, | ||||
| 		Value:        val.val, | ||||
| 		Comment:      comment, | ||||
| 		hasEquals:    hasEquals, | ||||
| 		leadingSpace: key.Position.Col - 1, | ||||
| 		position:     key.Position, | ||||
| 	} | ||||
| 	lastHost.Nodes = append(lastHost.Nodes, kv) | ||||
| 	return p.parseStart | ||||
| } | ||||
|  | ||||
| func (p *sshParser) parseComment() sshParserStateFn { | ||||
| 	comment := p.getToken() | ||||
| 	lastHost := p.config.Hosts[len(p.config.Hosts)-1] | ||||
| 	lastHost.Nodes = append(lastHost.Nodes, &Empty{ | ||||
| 		Comment: comment.val, | ||||
| 		// account for the "#" as well | ||||
| 		leadingSpace: comment.Position.Col - 2, | ||||
| 		position:     comment.Position, | ||||
| 	}) | ||||
| 	return p.parseStart | ||||
| } | ||||
|  | ||||
| func parseSSH(flow chan token, system bool, depth uint8) *Config { | ||||
| 	// Ensure we consume tokens to completion even if parser exits early | ||||
| 	defer func() { | ||||
| 		for range flow { | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	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, | ||||
| 	} | ||||
| 	parser.run() | ||||
| 	return result | ||||
| } | ||||
							
								
								
									
										25
									
								
								vendor/github.com/kevinburke/ssh_config/position.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/kevinburke/ssh_config/position.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| package ssh_config | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // Position of a document element within a SSH document. | ||||
| // | ||||
| // Line and Col are both 1-indexed positions for the element's line number and | ||||
| // column number, respectively.  Values of zero or less will cause Invalid(), | ||||
| // to return true. | ||||
| type Position struct { | ||||
| 	Line int // line within the document | ||||
| 	Col  int // column within the line | ||||
| } | ||||
|  | ||||
| // String representation of the position. | ||||
| // Displays 1-indexed line and column numbers. | ||||
| func (p Position) String() string { | ||||
| 	return fmt.Sprintf("(%d, %d)", p.Line, p.Col) | ||||
| } | ||||
|  | ||||
| // Invalid returns whether or not the position is valid (i.e. with negative or | ||||
| // null values) | ||||
| func (p Position) Invalid() bool { | ||||
| 	return p.Line <= 0 || p.Col <= 0 | ||||
| } | ||||
							
								
								
									
										49
									
								
								vendor/github.com/kevinburke/ssh_config/token.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/kevinburke/ssh_config/token.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package ssh_config | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| type token struct { | ||||
| 	Position | ||||
| 	typ tokenType | ||||
| 	val string | ||||
| } | ||||
|  | ||||
| func (t token) String() string { | ||||
| 	switch t.typ { | ||||
| 	case tokenEOF: | ||||
| 		return "EOF" | ||||
| 	} | ||||
| 	return fmt.Sprintf("%q", t.val) | ||||
| } | ||||
|  | ||||
| type tokenType int | ||||
|  | ||||
| const ( | ||||
| 	eof = -(iota + 1) | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	tokenError tokenType = iota | ||||
| 	tokenEOF | ||||
| 	tokenEmptyLine | ||||
| 	tokenComment | ||||
| 	tokenKey | ||||
| 	tokenEquals | ||||
| 	tokenString | ||||
| ) | ||||
|  | ||||
| func isSpace(r rune) bool { | ||||
| 	return r == ' ' || r == '\t' | ||||
| } | ||||
|  | ||||
| func isKeyStartChar(r rune) bool { | ||||
| 	return !(isSpace(r) || r == '\r' || r == '\n' || r == eof) | ||||
| } | ||||
|  | ||||
| // I'm not sure that this is correct | ||||
| func isKeyChar(r rune) bool { | ||||
| 	// Keys start with the first character that isn't whitespace or [ and end | ||||
| 	// with the last non-whitespace character before the equals sign. Keys | ||||
| 	// cannot contain a # character." | ||||
| 	return !(r == '\r' || r == '\n' || r == eof || r == '=') | ||||
| } | ||||
							
								
								
									
										162
									
								
								vendor/github.com/kevinburke/ssh_config/validators.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								vendor/github.com/kevinburke/ssh_config/validators.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| package ssh_config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Default returns the default value for the given keyword, for example "22" if | ||||
| // the keyword is "Port". Default returns the empty string if the keyword has no | ||||
| // default, or if the keyword is unknown. Keyword matching is case-insensitive. | ||||
| // | ||||
| // Default values are provided by OpenSSH_7.4p1 on a Mac. | ||||
| func Default(keyword string) string { | ||||
| 	return defaults[strings.ToLower(keyword)] | ||||
| } | ||||
|  | ||||
| // 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", | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 6543
					6543