diff --git a/handlers/sshd_config/analyzer/match.go b/handlers/sshd_config/analyzer/match.go index c773004..cf1491e 100644 --- a/handlers/sshd_config/analyzer/match.go +++ b/handlers/sshd_config/analyzer/match.go @@ -15,7 +15,7 @@ func analyzeMatchBlocks( ) []common.LSPError { errs := make([]common.LSPError, 0) - for matchBlock, options := range d.Indexes.GetAllOptionsForName("Match") { + for matchBlock, options := range d.Indexes.AllOptionsPerName["Match"] { option := options[0] // Check if the match block has filled out all fields if matchBlock == nil || matchBlock.MatchValue == nil || len(matchBlock.MatchValue.Entries) == 0 { diff --git a/handlers/sshd_config/handlers/definition.go b/handlers/sshd_config/handlers/definition.go index 19449c3..50de94d 100644 --- a/handlers/sshd_config/handlers/definition.go +++ b/handlers/sshd_config/handlers/definition.go @@ -10,13 +10,13 @@ import ( ) func GetIncludeOptionLocation( - include *indexes.SSHIndexIncludeLine, + include *indexes.SSHDIndexIncludeLine, cursor uint32, ) []protocol.Location { index, found := slices.BinarySearchFunc( include.Values, cursor, - func(i *indexes.SSHIndexIncludeValue, cursor uint32) int { + func(i *indexes.SSHDIndexIncludeValue, cursor uint32) int { if cursor < i.Start.Character { return -1 } diff --git a/handlers/sshd_config/indexes/handlers.go b/handlers/sshd_config/indexes/handlers.go new file mode 100644 index 0000000..371a200 --- /dev/null +++ b/handlers/sshd_config/indexes/handlers.go @@ -0,0 +1,125 @@ +package indexes + +import ( + "config-lsp/common" + "config-lsp/handlers/sshd_config/ast" + "config-lsp/handlers/sshd_config/fields" + "errors" + "fmt" + "regexp" +) + +var whitespacePattern = regexp.MustCompile(`\S+`) + +func CreateIndexes(config ast.SSHDConfig) (*SSHDIndexes, []common.LSPError) { + errs := make([]common.LSPError, 0) + indexes := &SSHDIndexes{ + AllOptionsPerName: make(map[string](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption))), + Includes: make(map[uint32]*SSHDIndexIncludeLine), + } + + it := config.Options.Iterator() + for it.Next() { + entry := it.Value().(ast.SSHDEntry) + + switch entry.(type) { + case *ast.SSHDOption: + option := entry.(*ast.SSHDOption) + + errs = append(errs, addOption(indexes, option, nil)...) + case *ast.SSHDMatchBlock: + matchBlock := entry.(*ast.SSHDMatchBlock) + + errs = append(errs, addOption(indexes, matchBlock.MatchEntry, matchBlock)...) + + it := matchBlock.Options.Iterator() + for it.Next() { + option := it.Value().(*ast.SSHDOption) + + errs = append(errs, addOption(indexes, option, matchBlock)...) + } + } + } + + // Add Includes + for matchBlock, options := range indexes.AllOptionsPerName["Include"] { + includeOption := options[0] + rawValue := includeOption.OptionValue.Value.Value + pathIndexes := whitespacePattern.FindAllStringIndex(rawValue, -1) + paths := make([]*SSHDIndexIncludeValue, 0) + + for _, pathIndex := range pathIndexes { + startIndex := pathIndex[0] + endIndex := pathIndex[1] + + rawPath := rawValue[startIndex:endIndex] + + offset := includeOption.OptionValue.Start.Character + path := SSHDIndexIncludeValue{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: includeOption.Start.Line, + Character: uint32(startIndex) + offset, + }, + End: common.Location{ + Line: includeOption.Start.Line, + Character: uint32(endIndex) + offset - 1, + }, + }, + Value: rawPath, + Paths: make([]ValidPath, 0), + } + + paths = append(paths, &path) + } + + indexes.Includes[includeOption.Start.Line] = &SSHDIndexIncludeLine{ + Values: paths, + Option: includeOption, + MatchBlock: matchBlock, + } + } + + return indexes, errs +} + +func addOption( + i *SSHDIndexes, + option *ast.SSHDOption, + matchBlock *ast.SSHDMatchBlock, +) []common.LSPError { + var errs []common.LSPError + + if optionsMap, found := i.AllOptionsPerName[option.Key.Key]; found { + if options, found := optionsMap[matchBlock]; found { + if _, duplicatesAllowed := fields.AllowedDuplicateOptions[option.Key.Key]; !duplicatesAllowed { + firstDefinedOption := options[0] + errs = append(errs, common.LSPError{ + Range: option.Key.LocationRange, + Err: errors.New(fmt.Sprintf( + "Option '%s' has already been defined on line %d", + option.Key.Key, + firstDefinedOption.Start.Line+1, + )), + }) + } else { + i.AllOptionsPerName[option.Key.Key][matchBlock] = append( + i.AllOptionsPerName[option.Key.Key][matchBlock], + option, + ) + } + } else { + i.AllOptionsPerName[option.Key.Key][matchBlock] = []*ast.SSHDOption{ + option, + } + } + } else { + i.AllOptionsPerName[option.Key.Key] = map[*ast.SSHDMatchBlock]([]*ast.SSHDOption){ + matchBlock: { + option, + }, + } + } + + return errs +} diff --git a/handlers/sshd_config/indexes/indexes.go b/handlers/sshd_config/indexes/indexes.go index 37247c4..4e8db6f 100644 --- a/handlers/sshd_config/indexes/indexes.go +++ b/handlers/sshd_config/indexes/indexes.go @@ -3,10 +3,6 @@ package indexes import ( "config-lsp/common" "config-lsp/handlers/sshd_config/ast" - "config-lsp/handlers/sshd_config/fields" - "errors" - "fmt" - "regexp" ) type ValidPath string @@ -15,13 +11,13 @@ func (v ValidPath) AsURI() string { return "file://" + string(v) } -// SSHIndexIncludeValue Used to store the individual includes +// SSHDIndexIncludeValue Used to store the individual includes // An `Include` statement can have multiple paths, -// each [SSHIndexIncludeValue] represents a single entered path. +// each [SSHDIndexIncludeValue] represents a single entered path. // Note that an entered path can represent multiple real paths, as // the path can contain wildcards. // All true paths are stored in the [Paths] field. -type SSHIndexIncludeValue struct { +type SSHDIndexIncludeValue struct { common.LocationRange Value string @@ -29,142 +25,15 @@ type SSHIndexIncludeValue struct { Paths []ValidPath } -type SSHIndexIncludeLine struct { - Values []*SSHIndexIncludeValue +type SSHDIndexIncludeLine struct { + Values []*SSHDIndexIncludeValue Option *ast.SSHDOption MatchBlock *ast.SSHDMatchBlock } -type SSHIndexes struct { +type SSHDIndexes struct { // This is a map of `Option name` to a list of options with that name - AllOptionsPerName map[*ast.SSHDMatchBlock](map[string]([]*ast.SSHDOption)) + AllOptionsPerName map[string](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption)) - Includes map[uint32]*SSHIndexIncludeLine -} - -func (i SSHIndexes) GetAllOptionsForName(name string) map[*ast.SSHDMatchBlock][]*ast.SSHDOption { - allOptions := make(map[*ast.SSHDMatchBlock][]*ast.SSHDOption) - - for matchBlock, options := range i.AllOptionsPerName { - if opts, found := options[name]; found { - allOptions[matchBlock] = opts - } - } - - return allOptions -} - -var whitespacePattern = regexp.MustCompile(`\S+`) - -func CreateIndexes(config ast.SSHDConfig) (*SSHIndexes, []common.LSPError) { - errs := make([]common.LSPError, 0) - indexes := &SSHIndexes{ - AllOptionsPerName: make(map[*ast.SSHDMatchBlock](map[string]([]*ast.SSHDOption))), - Includes: make(map[uint32]*SSHIndexIncludeLine), - } - - it := config.Options.Iterator() - for it.Next() { - entry := it.Value().(ast.SSHDEntry) - - switch entry.(type) { - case *ast.SSHDOption: - option := entry.(*ast.SSHDOption) - - errs = append(errs, addOption(indexes, option, nil)...) - case *ast.SSHDMatchBlock: - matchBlock := entry.(*ast.SSHDMatchBlock) - - errs = append(errs, addOption(indexes, matchBlock.MatchEntry, matchBlock)...) - - it := matchBlock.Options.Iterator() - for it.Next() { - option := it.Value().(*ast.SSHDOption) - - errs = append(errs, addOption(indexes, option, matchBlock)...) - } - } - } - - // Add Includes - for matchBlock, options := range indexes.GetAllOptionsForName("Include") { - includeOption := options[0] - rawValue := includeOption.OptionValue.Value.Value - pathIndexes := whitespacePattern.FindAllStringIndex(rawValue, -1) - paths := make([]*SSHIndexIncludeValue, 0) - - for _, pathIndex := range pathIndexes { - startIndex := pathIndex[0] - endIndex := pathIndex[1] - - rawPath := rawValue[startIndex:endIndex] - - offset := includeOption.OptionValue.Start.Character - path := SSHIndexIncludeValue{ - LocationRange: common.LocationRange{ - Start: common.Location{ - Line: includeOption.Start.Line, - Character: uint32(startIndex) + offset, - }, - End: common.Location{ - Line: includeOption.Start.Line, - Character: uint32(endIndex) + offset - 1, - }, - }, - Value: rawPath, - Paths: make([]ValidPath, 0), - } - - paths = append(paths, &path) - } - - indexes.Includes[includeOption.Start.Line] = &SSHIndexIncludeLine{ - Values: paths, - Option: includeOption, - MatchBlock: matchBlock, - } - } - - return indexes, errs -} - -func addOption( - i *SSHIndexes, - option *ast.SSHDOption, - matchBlock *ast.SSHDMatchBlock, -) []common.LSPError { - var errs []common.LSPError - - if optionsMap, found := i.AllOptionsPerName[matchBlock]; found { - if options, found := optionsMap[option.Key.Key]; found { - if _, duplicatesAllowed := fields.AllowedDuplicateOptions[option.Key.Key]; !duplicatesAllowed { - firstDefinedOption := options[0] - errs = append(errs, common.LSPError{ - Range: option.Key.LocationRange, - Err: errors.New(fmt.Sprintf( - "Option '%s' has already been defined on line %d", - option.Key.Key, - firstDefinedOption.Start.Line+1, - )), - }) - } else { - i.AllOptionsPerName[matchBlock][option.Key.Key] = append( - i.AllOptionsPerName[matchBlock][option.Key.Key], - option, - ) - } - } else { - i.AllOptionsPerName[matchBlock][option.Key.Key] = []*ast.SSHDOption{ - option, - } - } - } else { - i.AllOptionsPerName[matchBlock] = map[string]([]*ast.SSHDOption){ - option.Key.Key: { - option, - }, - } - } - - return errs + Includes map[uint32]*SSHDIndexIncludeLine } diff --git a/handlers/sshd_config/indexes/indexes_test.go b/handlers/sshd_config/indexes/indexes_test.go index f2f8691..7992ba0 100644 --- a/handlers/sshd_config/indexes/indexes_test.go +++ b/handlers/sshd_config/indexes/indexes_test.go @@ -34,7 +34,7 @@ Match Address 192.168.0.1/24 } firstMatchBlock := config.FindMatchBlock(uint32(6)) - opts := indexes.GetAllOptionsForName("PermitRootLogin") + opts := indexes.AllOptionsPerName["PermitRootLogin"] if !(len(opts) == 2 && len(opts[nil]) == 1 && opts[nil][0].Value.Value == "PermitRootLogin yes" && diff --git a/handlers/sshd_config/shared.go b/handlers/sshd_config/shared.go index 66e133f..aa2d56f 100644 --- a/handlers/sshd_config/shared.go +++ b/handlers/sshd_config/shared.go @@ -9,7 +9,7 @@ import ( type SSHDocument struct { Config *ast.SSHDConfig - Indexes *indexes.SSHIndexes + Indexes *indexes.SSHDIndexes } var DocumentParserMap = map[protocol.DocumentUri]*SSHDocument{}