> Synopsis: iked.conf(5) incompletely documents comment syntax and has
> potentially problematic behavior where comments can be continued with line
> continuations (\), leading to unexpected configuration parsing. Man page also
> fails to document that comments can have preceding whitespace and appear at
> end of lines.
> Category: bin
> Description:
The iked.conf(5) man page currently states:
> Lines beginning with ‘#’ and empty lines are regarded as comments,
and ignored. Lines may be split using the ‘\’ character.
However, the parser actually allows for
- comments to be continued with line continuations `\\\n` (Possibly a
bug)
- preceding white space
- trailing comments
# Comments Continued with Line Continuations (Possible Bug)
A comment that uses a line continuation will keep reading into the next
line instead of stopping at the newline character.
Most programmers would expect a comment to end at the newline, so this
could cause hard-to-find bugs.
For example:
```conf
ikev2 \
from dynamic to any \
peer 10.0.0.1 \
# request address 10.1.0.10 \
iface lo1
```
In this IKE config, you might expect the commented address line to be
ignored, but the line continuation causes `iface lo1` to be part of the comment
and get ignored too.
```conf
ikev2 \
from dynamic to any \
peer 10.0.0.1 # temporary IP for dev environment \
request address 10.1.0.10 \
iface lo1
```
Here, placing an explanatory comment before the line actually comments
out both the address line and the interface line due to the line continuation.
Both of these can lead to a subtly broken configuration.
## Comment and Line Continuation Processing in the Lexer
The lexer handles comments and line continuations at different levels
of processing:
### Comment Processing (in `yylex`)
```c
if (c == '#')
while ((c = lgetc(0)) != '\n' && c != EOF)
; /* nothing */
```
This simple loop discards all characters after a `#` until it hits a
newline or EOF.
### Line Continuation Processing (in `lgetc`)
```c
while ((c = igetc()) == '\\') {
next = igetc();
if (next != '\n') {
c = next;
break;
}
yylval.lineno = file->lineno;
file->lineno++;
}
```
When encountering a backslash, the code checks if it's followed by a
newline. If so, it updates the line counter and continues processing,
effectively joining the lines. If not, it preserves the character after the
backslash.
## Interaction and Precedence
Since `yylex` uses `lgetc` to read input, line continuations are
processed before comment detection. This leads to an important behavior:
```c
# comment with a continuation \
still a comment
```
The line continuation is processed first, making both lines part of the
comment. This layered processing design makes sense structurally but creates
potentially unexpected behavior where comments can continue across line
continuations, which might surprise users expecting comments to be strictly
line-based.
# Preceding White Space
Preceding white space is allowed before comments.
```iked.conf
# This is a comment with preceding spaces
# This is a comment with a preceding tab
```
The relevant code is in parse.y at the beginning of `yylex`
```c
while ((c = lgetc(0)) == ' ' || c == '\t')
; /* nothing */
yylval.lineno = file->lineno;
if (c == '#')
while ((c = lgetc(0)) != '\n' && c != EOF)
; /* nothing */
```
# Trailing Comments
Trailing comments are actually allowed.
```iked.conf
ikev2 esp from 10.0.0.0/24 to 192.168.1.0/24 # This is a comment
```
It works by:
1. `yyparse` calls `yylex` to get each token it needs
2. After getting a token that completes a valid grammar rule, `yyparse`
calls `yylex` again
3. In this subsequent call, `yylex` first skips whitespace:
```c
while ((c = lgetc(0)) == ' ' || c == '\t')
; /* nothing */
```
4. Then if it finds a comment, it consumes/discards it:
```c
if (c == '#')
while ((c = lgetc(0)) != '\n' && c != EOF)
; /* nothing */
```
So trailing comments are allowed not by any special grammar rule or
comment-aware tokens, but simply by the fact that `yyparse` keeps calling
`yylex` and `yylex` naturally discards whitespace and comments at the start of
each tokenization attempt.
> Fix:
If treating comments with line continuations as a bug:
Update iked.conf.5 to
> Lines beginning with '#' and empty lines are regarded as comments,
and ignored. Lines may be split using the '\' character, except within comments
which always terminate at a newline regardless of line continuations. This
allows individual options within a continued line to be commented out without
commenting out the entire continued line.
Update parse.y yylex so comments contain the continuation and are not
continued by it
```diff
-if (c == '#')
- while ((c = lgetc(0)) != '\n' && c != EOF)
- ; /* nothing */
+if (c == '#') {
+ do {
+ c = igetc(); /* Use igetc directly to ignore line
continuations */
+ } while (c != '\n' && c != EOF);
+ if (c == EOF)
+ return (0);
+}
```
If comments should continue with line continuations:
Update iked.conf.5 to
> Lines beginning with '#' and empty lines are regarded as comments,
and ignored. Lines may be split using the '\' character, including within
comments which will continue across line continuations. The parser will log a
warning when a comment contains a line continuation to help prevent unintended
comment continuation.
Update parse.y yylex so comments that end with a continuation emit a
warning
```diff
-if (c == '#')
- while ((c = lgetc(0)) != '\n' && c != EOF)
- ; /* nothing */
+if (c == '#') {
+ while ((c = lgetc(0)) != '\n' && c != EOF) {
+ if (c == '\\') {
+ if ((c = igetc()) == '\n') {
+ log_info("line %d: comment contains line continuation
- "
+ "following line will be part of comment",
file->lineno);
+ file->lineno++;
+ } else {
+ lungetc(c);
+ c = '\\';
+ }
+ }
+ }
+ if (c == EOF)
+ return (0);
+}
```