diff --git a/handlers/sshd_config/Config.g4 b/handlers/sshd_config/Config.g4 index 65c951a..e959976 100644 --- a/handlers/sshd_config/Config.g4 +++ b/handlers/sshd_config/Config.g4 @@ -13,22 +13,17 @@ key ; value - : STRING + : (STRING WHITESPACE)? STRING ; leadingComment : HASH WHITESPACE? (STRING WHITESPACE?)+ ; - HASH : '#' ; -MATCH - : ('M' | 'm') ('A' | 'a') ('T' | 't') ('C' | 'c') ('H' | 'h') - ; - WHITESPACE : [ \t]+ ; diff --git a/handlers/sshd_config/ast/listener.go b/handlers/sshd_config/ast/listener.go index d7d51db..7345a54 100644 --- a/handlers/sshd_config/ast/listener.go +++ b/handlers/sshd_config/ast/listener.go @@ -3,6 +3,8 @@ package ast import ( "config-lsp/common" "config-lsp/handlers/sshd_config/ast/parser" + "github.com/emirpasic/gods/maps/treemap" + gods "github.com/emirpasic/gods/utils" "strings" ) @@ -47,21 +49,7 @@ func (s *sshParserListener) EnterEntry(ctx *parser.EntryContext) { Value: ctx.GetText(), } - if s.sshContext.currentMatchBlock == nil { - s.Config.Options.Put( - location.Start.Line, - option, - ) - - s.sshContext.currentOption = option - } else { - s.sshContext.currentMatchBlock.Options.Put( - location.Start.Line, - option, - ) - - s.sshContext.currentOption = option - } + s.sshContext.currentOption = option } func (s *sshParserListener) EnterKey(ctx *parser.KeyContext) { @@ -91,17 +79,16 @@ func (s *sshParserListener) EnterValue(ctx *parser.ValueContext) { } func (s *sshParserListener) ExitValue(ctx *parser.ValueContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + if s.sshContext.isKeyAMatchBlock { - location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) - location.ChangeBothLines(s.sshContext.line) - - rawEntry, _ := s.Config.Options.Get(location.Start.Line) - entry := rawEntry.(*SSHOption) - - // Overwrite the current match block + // Add new match block matchBlock := &SSHMatchBlock{ LocationRange: location, - MatchEntry: entry, + MatchEntry: s.sshContext.currentOption, + Options: treemap.NewWith(gods.UInt32Comparator), } s.Config.Options.Put( location.Start.Line, @@ -110,6 +97,16 @@ func (s *sshParserListener) ExitValue(ctx *parser.ValueContext) { s.sshContext.currentMatchBlock = matchBlock s.sshContext.isKeyAMatchBlock = false + } else if s.sshContext.currentMatchBlock != nil { + s.sshContext.currentMatchBlock.Options.Put( + location.Start.Line, + s.sshContext.currentOption, + ) + } else { + s.Config.Options.Put( + location.Start.Line, + s.sshContext.currentOption, + ) } s.sshContext.currentOption = nil diff --git a/handlers/sshd_config/ast/parser.go b/handlers/sshd_config/ast/parser.go index fc510ee..7624284 100644 --- a/handlers/sshd_config/ast/parser.go +++ b/handlers/sshd_config/ast/parser.go @@ -35,12 +35,12 @@ func (c *SSHConfig) Parse(input string) []common.LSPError { for rawLineNumber, line := range lines { context.line = uint32(rawLineNumber) - if commentPattern.MatchString(line) { - c.CommentLines[context.line] = struct{}{} + if emptyPattern.MatchString(line) { continue } - if emptyPattern.MatchString(line) { + if commentPattern.MatchString(line) { + c.CommentLines[context.line] = struct{}{} continue } diff --git a/handlers/sshd_config/ast/parser/Config.interp b/handlers/sshd_config/ast/parser/Config.interp index 74f62e9..1a0d4d0 100644 --- a/handlers/sshd_config/ast/parser/Config.interp +++ b/handlers/sshd_config/ast/parser/Config.interp @@ -4,12 +4,10 @@ null null null null -null token symbolic names: null HASH -MATCH WHITESPACE STRING NEWLINE @@ -23,4 +21,4 @@ leadingComment atn: -[4, 1, 5, 55, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 3, 0, 13, 8, 0, 1, 0, 1, 0, 3, 0, 17, 8, 0, 3, 0, 19, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 24, 8, 1, 1, 1, 3, 1, 27, 8, 1, 1, 1, 1, 1, 3, 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 1, 3, 1, 37, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 45, 8, 4, 1, 4, 1, 4, 3, 4, 49, 8, 4, 4, 4, 51, 8, 4, 11, 4, 12, 4, 52, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 0, 61, 0, 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, 8, 42, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, 13, 5, 3, 0, 0, 12, 11, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 19, 3, 8, 4, 0, 15, 17, 5, 3, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, 18, 16, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 5, 3, 0, 0, 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 27, 3, 4, 2, 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 5, 3, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 3, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 5, 1, 0, 0, 0, 40, 41, 5, 4, 0, 0, 41, 7, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 5, 3, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 50, 1, 0, 0, 0, 46, 48, 5, 4, 0, 0, 47, 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 46, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 9, 1, 0, 0, 0, 11, 12, 16, 18, 23, 26, 30, 33, 36, 44, 48, 52] \ No newline at end of file +[4, 1, 4, 59, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 3, 0, 13, 8, 0, 1, 0, 1, 0, 3, 0, 17, 8, 0, 3, 0, 19, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 24, 8, 1, 1, 1, 3, 1, 27, 8, 1, 1, 1, 1, 1, 3, 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 1, 3, 1, 37, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 3, 3, 43, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 49, 8, 4, 1, 4, 1, 4, 3, 4, 53, 8, 4, 4, 4, 55, 8, 4, 11, 4, 12, 4, 56, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 0, 66, 0, 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 42, 1, 0, 0, 0, 8, 46, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, 13, 5, 2, 0, 0, 12, 11, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 19, 3, 8, 4, 0, 15, 17, 5, 2, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, 18, 16, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 5, 2, 0, 0, 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 27, 3, 4, 2, 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 5, 2, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 2, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 3, 0, 0, 39, 5, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, 41, 43, 5, 2, 0, 0, 42, 40, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 45, 5, 3, 0, 0, 45, 7, 1, 0, 0, 0, 46, 48, 5, 1, 0, 0, 47, 49, 5, 2, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 54, 1, 0, 0, 0, 50, 52, 5, 3, 0, 0, 51, 53, 5, 2, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 55, 1, 0, 0, 0, 54, 50, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 9, 1, 0, 0, 0, 12, 12, 16, 18, 23, 26, 30, 33, 36, 42, 48, 52, 56] \ No newline at end of file diff --git a/handlers/sshd_config/ast/parser/Config.tokens b/handlers/sshd_config/ast/parser/Config.tokens index 30ff5e0..aacc14c 100644 --- a/handlers/sshd_config/ast/parser/Config.tokens +++ b/handlers/sshd_config/ast/parser/Config.tokens @@ -1,6 +1,5 @@ HASH=1 -MATCH=2 -WHITESPACE=3 -STRING=4 -NEWLINE=5 +WHITESPACE=2 +STRING=3 +NEWLINE=4 '#'=1 diff --git a/handlers/sshd_config/ast/parser/ConfigLexer.interp b/handlers/sshd_config/ast/parser/ConfigLexer.interp index 4ac663c..d61e14d 100644 --- a/handlers/sshd_config/ast/parser/ConfigLexer.interp +++ b/handlers/sshd_config/ast/parser/ConfigLexer.interp @@ -4,19 +4,16 @@ null null null null -null token symbolic names: null HASH -MATCH WHITESPACE STRING NEWLINE rule names: HASH -MATCH WHITESPACE STRING NEWLINE @@ -29,4 +26,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 5, 34, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 21, 8, 2, 11, 2, 12, 2, 22, 1, 3, 4, 3, 26, 8, 3, 11, 3, 12, 3, 27, 1, 4, 3, 4, 31, 8, 4, 1, 4, 1, 4, 0, 0, 5, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 1, 0, 7, 2, 0, 77, 77, 109, 109, 2, 0, 65, 65, 97, 97, 2, 0, 84, 84, 116, 116, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, 35, 36, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 1, 11, 1, 0, 0, 0, 3, 13, 1, 0, 0, 0, 5, 20, 1, 0, 0, 0, 7, 25, 1, 0, 0, 0, 9, 30, 1, 0, 0, 0, 11, 12, 5, 35, 0, 0, 12, 2, 1, 0, 0, 0, 13, 14, 7, 0, 0, 0, 14, 15, 7, 1, 0, 0, 15, 16, 7, 2, 0, 0, 16, 17, 7, 3, 0, 0, 17, 18, 7, 4, 0, 0, 18, 4, 1, 0, 0, 0, 19, 21, 7, 5, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 6, 1, 0, 0, 0, 24, 26, 8, 6, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 8, 1, 0, 0, 0, 29, 31, 5, 13, 0, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 33, 5, 10, 0, 0, 33, 10, 1, 0, 0, 0, 4, 0, 22, 27, 30, 0] \ No newline at end of file +[4, 0, 4, 26, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, 11, 2, 12, 2, 19, 1, 3, 3, 3, 23, 8, 3, 1, 3, 1, 3, 0, 0, 4, 1, 1, 3, 2, 5, 3, 7, 4, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, 35, 28, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, 0, 5, 17, 1, 0, 0, 0, 7, 22, 1, 0, 0, 0, 9, 10, 5, 35, 0, 0, 10, 2, 1, 0, 0, 0, 11, 13, 7, 0, 0, 0, 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 12, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 8, 1, 0, 0, 17, 16, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 6, 1, 0, 0, 0, 21, 23, 5, 13, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 25, 5, 10, 0, 0, 25, 8, 1, 0, 0, 0, 4, 0, 14, 19, 22, 0] \ No newline at end of file diff --git a/handlers/sshd_config/ast/parser/ConfigLexer.tokens b/handlers/sshd_config/ast/parser/ConfigLexer.tokens index 30ff5e0..aacc14c 100644 --- a/handlers/sshd_config/ast/parser/ConfigLexer.tokens +++ b/handlers/sshd_config/ast/parser/ConfigLexer.tokens @@ -1,6 +1,5 @@ HASH=1 -MATCH=2 -WHITESPACE=3 -STRING=4 -NEWLINE=5 +WHITESPACE=2 +STRING=3 +NEWLINE=4 '#'=1 diff --git a/handlers/sshd_config/ast/parser/config_lexer.go b/handlers/sshd_config/ast/parser/config_lexer.go index cf0a348..ace491d 100644 --- a/handlers/sshd_config/ast/parser/config_lexer.go +++ b/handlers/sshd_config/ast/parser/config_lexer.go @@ -46,30 +46,25 @@ func configlexerLexerInit() { "", "'#'", } staticData.SymbolicNames = []string{ - "", "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + "", "HASH", "WHITESPACE", "STRING", "NEWLINE", } staticData.RuleNames = []string{ - "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + "HASH", "WHITESPACE", "STRING", "NEWLINE", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 0, 5, 34, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, - 4, 7, 4, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 21, - 8, 2, 11, 2, 12, 2, 22, 1, 3, 4, 3, 26, 8, 3, 11, 3, 12, 3, 27, 1, 4, 3, - 4, 31, 8, 4, 1, 4, 1, 4, 0, 0, 5, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 1, 0, 7, - 2, 0, 77, 77, 109, 109, 2, 0, 65, 65, 97, 97, 2, 0, 84, 84, 116, 116, 2, - 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 9, 9, 32, 32, 4, 0, 9, - 10, 13, 13, 32, 32, 35, 35, 36, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, - 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 1, 11, 1, 0, 0, 0, 3, - 13, 1, 0, 0, 0, 5, 20, 1, 0, 0, 0, 7, 25, 1, 0, 0, 0, 9, 30, 1, 0, 0, 0, - 11, 12, 5, 35, 0, 0, 12, 2, 1, 0, 0, 0, 13, 14, 7, 0, 0, 0, 14, 15, 7, - 1, 0, 0, 15, 16, 7, 2, 0, 0, 16, 17, 7, 3, 0, 0, 17, 18, 7, 4, 0, 0, 18, - 4, 1, 0, 0, 0, 19, 21, 7, 5, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, - 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 6, 1, 0, 0, 0, 24, 26, 8, - 6, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, - 28, 1, 0, 0, 0, 28, 8, 1, 0, 0, 0, 29, 31, 5, 13, 0, 0, 30, 29, 1, 0, 0, - 0, 30, 31, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 33, 5, 10, 0, 0, 33, 10, - 1, 0, 0, 0, 4, 0, 22, 27, 30, 0, + 4, 0, 4, 26, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, + 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, + 11, 2, 12, 2, 19, 1, 3, 3, 3, 23, 8, 3, 1, 3, 1, 3, 0, 0, 4, 1, 1, 3, 2, + 5, 3, 7, 4, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, + 35, 28, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, + 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, 0, 5, 17, 1, 0, 0, 0, 7, 22, + 1, 0, 0, 0, 9, 10, 5, 35, 0, 0, 10, 2, 1, 0, 0, 0, 11, 13, 7, 0, 0, 0, + 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 12, 1, 0, 0, 0, 14, 15, 1, + 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 8, 1, 0, 0, 17, 16, 1, 0, 0, 0, 18, + 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 6, 1, 0, 0, + 0, 21, 23, 5, 13, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, + 1, 0, 0, 0, 24, 25, 5, 10, 0, 0, 25, 8, 1, 0, 0, 0, 4, 0, 14, 19, 22, 0, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -111,8 +106,7 @@ func NewConfigLexer(input antlr.CharStream) *ConfigLexer { // ConfigLexer tokens. const ( ConfigLexerHASH = 1 - ConfigLexerMATCH = 2 - ConfigLexerWHITESPACE = 3 - ConfigLexerSTRING = 4 - ConfigLexerNEWLINE = 5 + ConfigLexerWHITESPACE = 2 + ConfigLexerSTRING = 3 + ConfigLexerNEWLINE = 4 ) diff --git a/handlers/sshd_config/ast/parser/config_parser.go b/handlers/sshd_config/ast/parser/config_parser.go index a52548d..dbfca6f 100644 --- a/handlers/sshd_config/ast/parser/config_parser.go +++ b/handlers/sshd_config/ast/parser/config_parser.go @@ -36,37 +36,39 @@ func configParserInit() { "", "'#'", } staticData.SymbolicNames = []string{ - "", "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + "", "HASH", "WHITESPACE", "STRING", "NEWLINE", } staticData.RuleNames = []string{ "lineStatement", "entry", "key", "value", "leadingComment", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 1, 5, 55, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, + 4, 1, 4, 59, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 3, 0, 13, 8, 0, 1, 0, 1, 0, 3, 0, 17, 8, 0, 3, 0, 19, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 24, 8, 1, 1, 1, 3, 1, 27, 8, 1, 1, 1, 1, 1, 3, 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 1, 3, 1, 37, 8, 1, 1, 2, 1, 2, 1, - 3, 1, 3, 1, 4, 1, 4, 3, 4, 45, 8, 4, 1, 4, 1, 4, 3, 4, 49, 8, 4, 4, 4, - 51, 8, 4, 11, 4, 12, 4, 52, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 0, 61, 0, - 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, - 8, 42, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, 13, 5, 3, 0, 0, 12, 11, 1, 0, - 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 19, 3, 8, 4, 0, 15, 17, - 5, 3, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, - 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, 18, 16, 1, 0, 0, 0, 19, 20, 1, - 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 5, 3, 0, 0, 23, - 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 27, 3, 4, 2, - 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, - 5, 3, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, - 31, 33, 1, 0, 0, 0, 32, 34, 5, 3, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, - 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, - 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 5, 1, 0, 0, - 0, 40, 41, 5, 4, 0, 0, 41, 7, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 5, - 3, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 50, 1, 0, 0, 0, 46, - 48, 5, 4, 0, 0, 47, 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, - 0, 49, 51, 1, 0, 0, 0, 50, 46, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 50, - 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 9, 1, 0, 0, 0, 11, 12, 16, 18, 23, - 26, 30, 33, 36, 44, 48, 52, + 3, 1, 3, 3, 3, 43, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 49, 8, 4, 1, 4, + 1, 4, 3, 4, 53, 8, 4, 4, 4, 55, 8, 4, 11, 4, 12, 4, 56, 1, 4, 0, 0, 5, + 0, 2, 4, 6, 8, 0, 0, 66, 0, 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, + 0, 0, 0, 6, 42, 1, 0, 0, 0, 8, 46, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, + 13, 5, 2, 0, 0, 12, 11, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, + 0, 14, 19, 3, 8, 4, 0, 15, 17, 5, 2, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, + 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, + 18, 16, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, + 0, 0, 22, 24, 5, 2, 0, 0, 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, + 1, 0, 0, 0, 25, 27, 3, 4, 2, 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, + 27, 28, 1, 0, 0, 0, 28, 30, 5, 2, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, + 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 2, 0, 0, 33, + 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, + 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, + 3, 0, 0, 39, 5, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, 41, 43, 5, 2, 0, 0, 42, + 40, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 45, 5, 3, 0, + 0, 45, 7, 1, 0, 0, 0, 46, 48, 5, 1, 0, 0, 47, 49, 5, 2, 0, 0, 48, 47, 1, + 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 54, 1, 0, 0, 0, 50, 52, 5, 3, 0, 0, 51, + 53, 5, 2, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 55, 1, 0, 0, + 0, 54, 50, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 57, + 1, 0, 0, 0, 57, 9, 1, 0, 0, 0, 12, 12, 16, 18, 23, 26, 30, 33, 36, 42, + 48, 52, 56, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -106,10 +108,9 @@ func NewConfigParser(input antlr.TokenStream) *ConfigParser { const ( ConfigParserEOF = antlr.TokenEOF ConfigParserHASH = 1 - ConfigParserMATCH = 2 - ConfigParserWHITESPACE = 3 - ConfigParserSTRING = 4 - ConfigParserNEWLINE = 5 + ConfigParserWHITESPACE = 2 + ConfigParserSTRING = 3 + ConfigParserNEWLINE = 4 ) // ConfigParser rules. @@ -652,7 +653,9 @@ type IValueContext interface { GetParser() antlr.Parser // Getter signatures - STRING() antlr.TerminalNode + AllSTRING() []antlr.TerminalNode + STRING(i int) antlr.TerminalNode + WHITESPACE() antlr.TerminalNode // IsValueContext differentiates from other interfaces. IsValueContext() @@ -690,8 +693,16 @@ func NewValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invoki func (s *ValueContext) GetParser() antlr.Parser { return s.parser } -func (s *ValueContext) STRING() antlr.TerminalNode { - return s.GetToken(ConfigParserSTRING, 0) +func (s *ValueContext) AllSTRING() []antlr.TerminalNode { + return s.GetTokens(ConfigParserSTRING) +} + +func (s *ValueContext) STRING(i int) antlr.TerminalNode { + return s.GetToken(ConfigParserSTRING, i) +} + +func (s *ValueContext) WHITESPACE() antlr.TerminalNode { + return s.GetToken(ConfigParserWHITESPACE, 0) } func (s *ValueContext) GetRuleContext() antlr.RuleContext { @@ -718,8 +729,32 @@ func (p *ConfigParser) Value() (localctx IValueContext) { localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState()) p.EnterRule(localctx, 6, ConfigParserRULE_value) p.EnterOuterAlt(localctx, 1) + p.SetState(42) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 8, p.GetParserRuleContext()) == 1 { + { + p.SetState(40) + p.Match(ConfigParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(41) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } { - p.SetState(40) + p.SetState(44) p.Match(ConfigParserSTRING) if p.HasError() { // Recognition error - abort rule @@ -837,14 +872,14 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(42) + p.SetState(46) p.Match(ConfigParserHASH) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(44) + p.SetState(48) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -853,7 +888,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(43) + p.SetState(47) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -862,7 +897,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } } - p.SetState(50) + p.SetState(54) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -871,14 +906,14 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { for ok := true; ok; ok = _la == ConfigParserSTRING { { - p.SetState(46) + p.SetState(50) p.Match(ConfigParserSTRING) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(48) + p.SetState(52) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -887,7 +922,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(47) + p.SetState(51) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -897,7 +932,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } - p.SetState(52) + p.SetState(56) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit diff --git a/handlers/sshd_config/ast/parser_test.go b/handlers/sshd_config/ast/parser_test.go index 53a0458..6f5d0a3 100644 --- a/handlers/sshd_config/ast/parser_test.go +++ b/handlers/sshd_config/ast/parser_test.go @@ -58,3 +58,321 @@ PasswordAuthentication yes t.Errorf("Expected second entry to be PasswordAuthentication yes, but got: %v", secondEntry) } } + +func TestMatchSimpleBlock( + t *testing.T, +) { + input := utils.Dedent(` +PermitRootLogin no + +Match 192.168.0.1 + PasswordAuthentication yes +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 2 && + len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected 1 option and no comment lines, but got: %v, %v", p.Options, p.CommentLines) + } + + rawFirstEntry, _ := p.Options.Get(uint32(0)) + firstEntry := rawFirstEntry.(*SSHOption) + if !(firstEntry.Value == "PermitRootLogin no") { + t.Errorf("Expected first entry to be 'PermitRootLogin no', but got: %v", firstEntry.Value) + } + + rawSecondEntry, _ := p.Options.Get(uint32(2)) + secondEntry := rawSecondEntry.(*SSHMatchBlock) + if !(secondEntry.MatchEntry.Value == "Match 192.168.0.1") { + t.Errorf("Expected second entry to be 'Match 192.168.0.1', but got: %v", secondEntry.MatchEntry.Value) + } + + if !(secondEntry.Options.Size() == 1) { + t.Errorf("Expected 1 option in match block, but got: %v", secondEntry.Options) + } + + rawThirdEntry, _ := secondEntry.Options.Get(uint32(3)) + thirdEntry := rawThirdEntry.(*SSHOption) + if !(thirdEntry.Key.Value == "PasswordAuthentication" && thirdEntry.OptionValue.Value == "yes") { + t.Errorf("Expected third entry to be 'PasswordAuthentication yes', but got: %v", thirdEntry.Value) + } +} + +func TestMultipleMatchBlocks( + t *testing.T, +) { + input := utils.Dedent(` +PermitRootLogin no + +Match 192.168.0.1 + PasswordAuthentication yes + AllowUsers root user + +Match 192.168.0.2 + MaxAuthTries 3 +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 3 && + len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected 3 options and no comment lines, but got: %v, %v", p.Options, p.CommentLines) + } + + rawSecondEntry, _ := p.Options.Get(uint32(2)) + secondEntry := rawSecondEntry.(*SSHMatchBlock) + if !(secondEntry.Options.Size() == 2) { + t.Errorf("Expected 2 options in second match block, but got: %v", secondEntry.Options) + } + + rawThirdEntry, _ := secondEntry.Options.Get(uint32(3)) + thirdEntry := rawThirdEntry.(*SSHOption) + if !(thirdEntry.Key.Value == "PasswordAuthentication" && thirdEntry.OptionValue.Value == "yes") { + t.Errorf("Expected third entry to be 'PasswordAuthentication yes', but got: %v", thirdEntry.Value) + } + + rawFourthEntry, _ := secondEntry.Options.Get(uint32(4)) + fourthEntry := rawFourthEntry.(*SSHOption) + if !(fourthEntry.Key.Value == "AllowUsers" && fourthEntry.OptionValue.Value == "root user") { + t.Errorf("Expected fourth entry to be 'AllowUsers root user', but got: %v", fourthEntry.Value) + } + + rawFifthEntry, _ := p.Options.Get(uint32(6)) + fifthEntry := rawFifthEntry.(*SSHMatchBlock) + if !(fifthEntry.Options.Size() == 1) { + t.Errorf("Expected 1 option in fifth match block, but got: %v", fifthEntry.Options) + } + + rawSixthEntry, _ := fifthEntry.Options.Get(uint32(7)) + sixthEntry := rawSixthEntry.(*SSHOption) + if !(sixthEntry.Key.Value == "MaxAuthTries" && sixthEntry.OptionValue.Value == "3") { + t.Errorf("Expected sixth entry to be 'MaxAuthTries 3', but got: %v", sixthEntry.Value) + } +} + +func TestComplexExample( + t *testing.T, +) { + // From https://gist.github.com/kjellski/5940875 + input := utils.Dedent(` +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options change a +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# The default requires explicit activation of protocol 1 +#Protocol 2 + +# HostKey for protocol version 1 +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_dsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 1024 + +# Logging +# obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#BC# Root only allowed to login from LAN IP ranges listed at end +PermitRootLogin no +#PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#RSAAuthentication yes +#PubkeyAuthentication yes +#AuthorizedKeysFile .ssh/authorized_keys + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#BC# Disable password authentication by default (except for LAN IP ranges listed later) +PasswordAuthentication no +PermitEmptyPasswords no +#BC# Have to allow root here because AllowUsers not allowed in Match block. It will not work though because of PermitRootLogin. +#BC# This is no longer true as of 6.1. AllowUsers is now allowed in a Match block. +AllowUsers kmk root + +# Change to no to disable s/key passwords +#BC# I occasionally use s/key one time passwords generated by a phone app +ChallengeResponseAuthentication yes + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +#BC# I would turn this off but I compiled ssh without PAM support so it errors if I set this. +#UsePAM no + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#UseLogin no +#UsePrivilegeSeparation yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS yes +#PidFile /var/run/sshd.pid +#MaxStartups 10 +#PermitTunnel no +#ChrootDirectory none + +# no default banner path +#Banner none + +# override default of no subsystems +#Subsystem sftp /usr/lib/misc/sftp-server +Subsystem sftp internal-sftp + +# the following are HPN related configuration options +# tcp receive buffer polling. disable in non autotuning kernels +#TcpRcvBufPoll yes + +# allow the use of the none cipher +#NoneEnabled no + +# disable hpn performance boosts. +#HPNDisabled no + +# buffer size for hpn to non-hpn connections +#HPNBufferSize 2048 + + +# Example of overriding settings on a per-user basis +Match User anoncvs + X11Forwarding no + AllowTcpForwarding no + ForceCommand cvs server + +#BC# My internal networks +#BC# Root can log in from here but only with a key and kmk can log in here with a password. +Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1 + PermitRootLogin without-password + PasswordAuthentication yes +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 9 && + len(utils.KeysOfMap(p.CommentLines)) == 105) { + t.Errorf("Expected 9 options and 105 comment lines, but got: %v, %v", p.Options, p.CommentLines) + } + + rawFirstEntry, _ := p.Options.Get(uint32(38)) + firstEntry := rawFirstEntry.(*SSHOption) + if !(firstEntry.Key.Value == "PermitRootLogin" && firstEntry.OptionValue.Value == "no") { + t.Errorf("Expected first entry to be 'PermitRootLogin no', but got: %v", firstEntry.Value) + } + + rawSecondEntry, _ := p.Options.Get(uint32(60)) + secondEntry := rawSecondEntry.(*SSHOption) + if !(secondEntry.Key.Value == "PasswordAuthentication" && secondEntry.OptionValue.Value == "no") { + t.Errorf("Expected second entry to be 'PasswordAuthentication no', but got: %v", secondEntry.Value) + } + + rawThirdEntry, _ := p.Options.Get(uint32(118)) + thirdEntry := rawThirdEntry.(*SSHOption) + if !(thirdEntry.Key.Value == "Subsystem" && thirdEntry.OptionValue.Value == "sftp\tinternal-sftp") { + t.Errorf("Expected third entry to be 'Subsystem sftp internal-sftp', but got: %v", thirdEntry.Value) + } + + rawFourthEntry, _ := p.Options.Get(uint32(135)) + fourthEntry := rawFourthEntry.(*SSHMatchBlock) + if !(fourthEntry.MatchEntry.Value == "Match User anoncvs") { + t.Errorf("Expected fourth entry to be 'Match User anoncvs', but got: %v", fourthEntry.MatchEntry.Value) + } + + if !(fourthEntry.Options.Size() == 3) { + t.Errorf("Expected 3 options in fourth match block, but got: %v", fourthEntry.Options) + } + + rawFifthEntry, _ := fourthEntry.Options.Get(uint32(136)) + fifthEntry := rawFifthEntry.(*SSHOption) + if !(fifthEntry.Key.Value == "X11Forwarding" && fifthEntry.OptionValue.Value == "no") { + t.Errorf("Expected fifth entry to be 'X11Forwarding no', but got: %v", fifthEntry.Value) + } + + rawSixthEntry, _ := p.Options.Get(uint32(142)) + sixthEntry := rawSixthEntry.(*SSHMatchBlock) + if !(sixthEntry.MatchEntry.Value == "Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1") { + t.Errorf("Expected sixth entry to be 'Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1', but got: %v", sixthEntry.MatchEntry.Value) + } + + if !(sixthEntry.MatchEntry.Key.Value == "Match" && sixthEntry.MatchEntry.OptionValue.Value == "Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1") { + t.Errorf("Expected sixth entry to be 'Match Address 172.22.100.0/24,172.22.5.0/24,127.0.0.1', but got: %v", sixthEntry.MatchEntry.Value) + } + + if !(sixthEntry.Options.Size() == 2) { + t.Errorf("Expected 2 options in sixth match block, but got: %v", sixthEntry.Options) + } + + rawSeventhEntry, _ := sixthEntry.Options.Get(uint32(143)) + seventhEntry := rawSeventhEntry.(*SSHOption) + if !(seventhEntry.Key.Value == "PermitRootLogin" && seventhEntry.OptionValue.Value == "without-password") { + t.Errorf("Expected seventh entry to be 'PermitRootLogin without-password', but got: %v", seventhEntry.Value) + } +}