diff --git a/config.go b/config.go index 7c2c8b6..b11bd5e 100644 --- a/config.go +++ b/config.go @@ -571,6 +571,11 @@ type KV struct { position Position } +// CanonicalKey returns k's Key, using the canonical casing from https://man.openbsd.org/ssh_config. +func (k *KV) CanonicalKey() string { + return GetCanonicalCase(k.Key) +} + // Pos returns k's Position. func (k *KV) Pos() Position { return k.position @@ -586,7 +591,7 @@ func (k *KV) String() string { if k.hasEquals { equals = " = " } - line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", int(k.leadingSpace)), k.Key, equals, k.Value) + line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", k.leadingSpace), k.CanonicalKey(), equals, k.Value) if k.Comment != "" { line += " #" + k.Comment } diff --git a/config_options.go b/config_options.go new file mode 100644 index 0000000..c754d37 --- /dev/null +++ b/config_options.go @@ -0,0 +1,150 @@ +package ssh_config + +import ( + "strings" +) + +// Current as of OpenSSH 8.2 +// Source: https://man.openbsd.org/ssh_config +var configOptions = []string{ + "Host", + "Match", + "AddKeysToAgent", + "AddressFamily", + "BatchMode", + "BindAddress", + "BindInterface", + "CanonicalDomains", + "CanonicalizeFallbackLocal", + "CanonicalizeHostname", + "CanonicalizeMaxDots", + "CanonicalizePermittedCNAMEs", + "CASignatureAlgorithms", + "CertificateFile", + "CheckHostIP", + "Ciphers", + "ClearAllForwardings", + "Compression", + "ConnectionAttempts", + "ConnectTimeout", + "ControlMaster", + "ControlPath", + "ControlPersist", + "DynamicForward", + "EnableSSHKeysign", + "EscapeChar", + "ExitOnForwardFailure", + "FingerprintHash", + "ForkAfterAuthentication", + "ForwardAgent", + "ForwardX11", + "ForwardX11Timeout", + "ForwardX11Trusted", + "GatewayPorts", + "GlobalKnownHostsFile", + "GSSAPIAuthentication", + "GSSAPIDelegateCredentials", + "HashKnownHosts", + "HostbasedAcceptedAlgorithms", + "HostbasedAuthentication", + "HostKeyAlgorithms", + "HostKeyAlias", + "Hostname", + "IdentitiesOnly", + "IdentityAgent", + "IdentityFile", + "IgnoreUnknown", + "Include", + "IPQoS", + "KbdInteractiveAuthentication", + "KbdInteractiveDevices", + "KexAlgorithms", + "KnownHostsCommand", + "LocalCommand", + "LocalForward", + "LogLevel", + "LogVerbose", + "MACs", + "NoHostAuthenticationForLocalhost", + "NumberOfPasswordPrompts", + "PasswordAuthentication", + "PermitLocalCommand", + "PermitRemoteOpen", + "PKCS11Provider", + "Port", + "PreferredAuthentications", + "ProxyCommand", + "ProxyJump", + "ProxyUseFdpass", + "PubkeyAcceptedAlgorithms", + "PubkeyAuthentication", + "RekeyLimit", + "RemoteCommand", + "RemoteForward", + "RequestTTY", + "RevokedHostKeys", + "SecurityKeyProvider", + "SendEnv", + "ServerAliveCountMax", + "ServerAliveInterval", + "SessionType", + "SetEnv", + "StdinNull", + "StreamLocalBindMask", + "StreamLocalBindUnlink", + "StrictHostKeyChecking", + "SyslogFacility", + "TCPKeepAlive", + "Tunnel", + "TunnelDevice", + "UpdateHostKeys", + "User", + "UserKnownHostsFile", + "VerifyHostKeyDNS", + "VisualHostKey", + "XAuthLocation", +} + +// // getConfigOptions retrieves a sorted list of all the current config options +// // from the official source: https://man.openbsd.org/ssh_config and filtered +// // with "github.com/PuerkitoBio/goquery". +// // The result is sorted and therefore may be searched with sort.SearchStrings() +// func getConfigOptions() (opts []string, err error) { +// SSHConfigURL := "https://man.openbsd.org/ssh_config" + +// resp, err := http.Get(SSHConfigURL) +// if err != nil { +// return opts, fmt.Errorf("getting %s: %w", SSHConfigURL, err) +// } +// defer resp.Body.Close() + +// doc, err := goquery.NewDocumentFromReader(resp.Body) +// if err != nil { +// return opts, fmt.Errorf("parsing %s: %w", SSHConfigURL, err) +// } + +// doc.Find("dt > a > code.Cm").Each(func(i int, c *goquery.Selection) { +// opts = append(opts, c.Text()) +// }) + +// sort.Strings(opts) +// return opts, nil +// } + +var configOptionsMap = map[string]string{} + +func init() { + for _, opt := range configOptions { + configOptionsMap[strings.ToLower(opt)] = opt + } +} + +// GetCanonicalCase checks for the given key in the known ssh config keys +// and returns with proper casing if found, otherwise returns what was given. +func GetCanonicalCase(key string) string { + if v, ok := configOptionsMap[strings.ToLower(key)]; ok { + return v + } + + return key +}