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)
analyzeTokens(ctx)

View File

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

View File

@ -13,13 +13,13 @@ import (
func analyzeTokens(
ctx *analyzerContext,
) {
for _, option := range ctx.document.Config.GetAllOptions() {
if option.Key == nil || option.OptionValue == nil {
for _, info := range ctx.document.Config.GetAllOptions() {
if info.Option.Key == nil || info.Option.OptionValue == nil {
continue
}
key := option.Key.Key
text := option.OptionValue.Value.Value
key := info.Option.Key.Key
text := info.Option.OptionValue.Value.Value
var tokens []string
if foundTokens, found := fields.OptionsTokensMap[key]; found {
@ -39,7 +39,7 @@ func analyzeTokens(
}
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),
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
}
func (c SSHDConfig) GetAllOptions() []*SSHDOption {
options := make(
[]*SSHDOption,
func (c SSHDConfig) GetAllOptions() []SSHDOptionInfo {
infos := make(
[]SSHDOptionInfo,
0,
// Approximation, this does not need to be exact
c.Options.Size()+10,
)
var currentMatchBlock *SSHDMatchBlock = nil
for _, rawEntry := range c.Options.Values() {
switch entry := rawEntry.(type) {
case *SSHDOption:
options = append(options, entry)
infos = append(infos, SSHDOptionInfo{
Option: entry,
MatchBlock: currentMatchBlock,
})
case *SSHDMatchBlock:
options = append(options, entry.MatchOption)
currentMatchBlock = entry
for _, rawOption := range entry.Options.Values() {
options = append(options, rawOption.(*SSHDOption))
}
infos = append(infos, SSHDOptionInfo{
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))
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{
AllOptionsPerName: make(map[fields.NormalizedOptionName](map[*ast.SSHDMatchBlock]([]*ast.SSHDOption))),
Includes: make(map[uint32]*SSHDIndexIncludeLine),
UnknownOptions: make(map[uint32]ast.SSHDOptionInfo),
}
it := config.Options.Iterator()