feat(server): Improve sshd_config; Add unknown option detection

This commit is contained in:
Myzel394 2025-02-17 21:50:56 +01:00
parent 3ac3ebbe50
commit 0c827b04cd
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
10 changed files with 129 additions and 19 deletions

View File

@ -54,6 +54,7 @@ func Analyze(
} }
} }
analyzeValuesAreValid(ctx)
analyzeMatchBlocks(ctx) analyzeMatchBlocks(ctx)
analyzeTokens(ctx) analyzeTokens(ctx)

View File

@ -11,12 +11,12 @@ import (
func analyzeQuotesAreValid( func analyzeQuotesAreValid(
ctx *analyzerContext, ctx *analyzerContext,
) { ) {
for _, option := range ctx.document.Config.GetAllOptions() { for _, info := range ctx.document.Config.GetAllOptions() {
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) checkIsUsingDoubleQuotes(ctx, info.Option.Key.Value, info.Option.Key.LocationRange)
checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) checkIsUsingDoubleQuotes(ctx, info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange)
checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange) checkQuotesAreClosed(ctx, info.Option.Key.Value, info.Option.Key.LocationRange)
checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) checkQuotesAreClosed(ctx, info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange)
} }
} }

View File

@ -13,13 +13,13 @@ import (
func analyzeTokens( func analyzeTokens(
ctx *analyzerContext, ctx *analyzerContext,
) { ) {
for _, option := range ctx.document.Config.GetAllOptions() { for _, info := range ctx.document.Config.GetAllOptions() {
if option.Key == nil || option.OptionValue == nil { if info.Option.Key == nil || info.Option.OptionValue == nil {
continue continue
} }
key := option.Key.Key key := info.Option.Key.Key
text := option.OptionValue.Value.Value text := info.Option.OptionValue.Value.Value
var tokens []string var tokens []string
if foundTokens, found := fields.OptionsTokensMap[key]; found { if foundTokens, found := fields.OptionsTokensMap[key]; found {
@ -39,7 +39,7 @@ func analyzeTokens(
} }
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: option.OptionValue.ToLSPRange(), Range: info.Option.OptionValue.ToLSPRange(),
Message: fmt.Sprintf("Token '%s' is not allowed for option '%s'", token, optionName), Message: fmt.Sprintf("Token '%s' is not allowed for option '%s'", token, optionName),
Severity: &common.SeverityError, Severity: &common.SeverityError,
}) })

View File

@ -0,0 +1,39 @@
package analyzer
import (
"config-lsp/handlers/sshd_config/diagnostics"
"config-lsp/handlers/sshd_config/fields"
)
func analyzeValuesAreValid(
ctx *analyzerContext,
) {
// Check if there are unknown options
for _, info := range ctx.document.Config.GetAllOptions() {
normalizedName := fields.CreateNormalizedName(info.Option.Key.Value.Value)
var isUnknown bool = true
// Check if the option is unknown
if info.MatchBlock == nil {
// All options are allowed
if _, found := fields.Options[normalizedName]; found {
isUnknown = false
}
} else {
// Only `MatchAllowedOptions` are allowed
if _, found := fields.MatchAllowedOptions[normalizedName]; found {
isUnknown = false
}
}
if isUnknown {
ctx.diagnostics = append(ctx.diagnostics, diagnostics.GenerateUnknownOption(
info.Option.Key.ToLSPRange(),
info.Option.Key.Value.Value,
))
ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info
}
}
}

View File

@ -0,0 +1,34 @@
package analyzer
import (
testutils_test "config-lsp/handlers/sshd_config/test_utils"
"testing"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TestUnknownOptionExample(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
ThisOptionDoesNotExist okay
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeValuesAreValid(ctx)
if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
}
if !(len(ctx.document.Indexes.UnknownOptions) == 1) {
t.Errorf("Expected 1 unknown option, got %v", len(ctx.document.Indexes.UnknownOptions))
}
if !(ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value == "ThisOptionDoesNotExist") {
t.Errorf("Expected 'ThisOptionDoesNotExist', got %v", ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value)
}
}

View File

@ -0,0 +1,8 @@
// Contains structs that are used as utilities, but are
// not used for the AST itself
package ast
type SSHDOptionInfo struct {
MatchBlock *SSHDMatchBlock
Option *SSHDOption
}

View File

@ -64,26 +64,32 @@ func (c SSHDConfig) FindOption(line uint32) (*SSHDOption, *SSHDMatchBlock) {
return nil, nil return nil, nil
} }
func (c SSHDConfig) GetAllOptions() []*SSHDOption { func (c SSHDConfig) GetAllOptions() []SSHDOptionInfo {
options := make( infos := make(
[]*SSHDOption, []SSHDOptionInfo,
0, 0,
// Approximation, this does not need to be exact // Approximation, this does not need to be exact
c.Options.Size()+10, c.Options.Size()+10,
) )
var currentMatchBlock *SSHDMatchBlock = nil
for _, rawEntry := range c.Options.Values() { for _, rawEntry := range c.Options.Values() {
switch entry := rawEntry.(type) { switch entry := rawEntry.(type) {
case *SSHDOption: case *SSHDOption:
options = append(options, entry) infos = append(infos, SSHDOptionInfo{
Option: entry,
MatchBlock: currentMatchBlock,
})
case *SSHDMatchBlock: case *SSHDMatchBlock:
options = append(options, entry.MatchOption) currentMatchBlock = entry
for _, rawOption := range entry.Options.Values() { infos = append(infos, SSHDOptionInfo{
options = append(options, rawOption.(*SSHDOption)) Option: entry.MatchOption,
} MatchBlock: currentMatchBlock,
})
} }
} }
return options return infos
} }

View File

@ -0,0 +1,19 @@
package diagnostics
import (
"config-lsp/common"
"fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func GenerateUnknownOption(
diagnosticRange protocol.Range,
optionName string,
) protocol.Diagnostic {
return protocol.Diagnostic{
Range: diagnosticRange,
Message: fmt.Sprintf("Unknown option: %s", optionName),
Severity: &common.SeverityError,
}
}

View File

@ -37,4 +37,6 @@ type SSHDIndexes struct {
AllOptionsPerName map[fields.NormalizedOptionName](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption)) AllOptionsPerName map[fields.NormalizedOptionName](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption))
Includes map[uint32]*SSHDIndexIncludeLine Includes map[uint32]*SSHDIndexIncludeLine
UnknownOptions map[uint32]ast.SSHDOptionInfo
} }

View File

@ -18,6 +18,7 @@ func CreateIndexes(config ast.SSHDConfig) (*SSHDIndexes, []common.LSPError) {
indexes := &SSHDIndexes{ indexes := &SSHDIndexes{
AllOptionsPerName: make(map[fields.NormalizedOptionName](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption))), AllOptionsPerName: make(map[fields.NormalizedOptionName](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption))),
Includes: make(map[uint32]*SSHDIndexIncludeLine), Includes: make(map[uint32]*SSHDIndexIncludeLine),
UnknownOptions: make(map[uint32]ast.SSHDOptionInfo),
} }
it := config.Options.Iterator() it := config.Options.Iterator()