jarld checks the reverse DNS of the public zones' MX and SPF addresses as the internet sees it, every 6 hours (Exposure page, jarl exposure mail, jarl_mail_reverse_dns_ok). The Server options page, jarl option generated and GET /options/generated list what Jarl writes into named.conf by itself; stats.rate_limit_log turns off the query-errors logging. Option lists of three or more elements are shown one per line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
220 lines
5.0 KiB
Go
220 lines
5.0 KiB
Go
package namedconf
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// maxInline is the widest a block may be to be printed on one line.
|
|
const maxInline = 72
|
|
|
|
// Format returns stmts in canonical form, one statement per line, with
|
|
// nested blocks indented by tabs.
|
|
func Format(stmts []*Stmt) string {
|
|
var sb strings.Builder
|
|
for _, s := range stmts {
|
|
writeStmt(&sb, s, 0)
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// FormatStmt returns a single statement in canonical form, indented by
|
|
// indent tabs, including the trailing newline.
|
|
func FormatStmt(s *Stmt, indent int) string {
|
|
var sb strings.Builder
|
|
writeStmt(&sb, s, indent)
|
|
return sb.String()
|
|
}
|
|
|
|
// Compact returns the statement's items on a single line, without the
|
|
// trailing semicolon: `allow-query { 10.0.0.0/8; localhost; }`.
|
|
func Compact(items []Item) string {
|
|
var sb strings.Builder
|
|
writeItemsInline(&sb, items)
|
|
return sb.String()
|
|
}
|
|
|
|
// CompactStmt returns the statement on a single line with its semicolon.
|
|
func CompactStmt(s *Stmt) string { return Compact(s.Items) + ";" }
|
|
|
|
// FormatValue returns an option's value laid out as named.conf writes it:
|
|
// blocks over several lines, indented with tabs. Values without a block,
|
|
// and values that don't parse, are returned as they are.
|
|
func FormatValue(name, value string) string {
|
|
if !strings.Contains(value, "{") {
|
|
return value
|
|
}
|
|
st, err := ParseOptionValue(name, value)
|
|
if err != nil {
|
|
return value
|
|
}
|
|
out := strings.TrimSuffix(strings.TrimRight(FormatStmt(st, 0), "\n"), ";")
|
|
if rest, ok := strings.CutPrefix(out, st.Keyword()); ok {
|
|
return strings.TrimLeft(rest, " ")
|
|
}
|
|
return value
|
|
}
|
|
|
|
// QuoteString returns s as a named.conf quoted string.
|
|
func QuoteString(s string) string {
|
|
var sb strings.Builder
|
|
sb.WriteByte('"')
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '"' || s[i] == '\\' {
|
|
sb.WriteByte('\\')
|
|
}
|
|
sb.WriteByte(s[i])
|
|
}
|
|
sb.WriteByte('"')
|
|
return sb.String()
|
|
}
|
|
|
|
func valueText(v *Value) string {
|
|
if v.Quoted {
|
|
return QuoteString(v.Text)
|
|
}
|
|
return v.Text
|
|
}
|
|
|
|
func writeStmt(sb *strings.Builder, s *Stmt, indent int) {
|
|
tabs := strings.Repeat("\t", indent)
|
|
if s.Comment != "" {
|
|
sb.WriteString(tabs + "// " + s.Comment + "\n")
|
|
}
|
|
sb.WriteString(tabs)
|
|
inline := Compact(s.Items)
|
|
if !hasNestedBlocks(s.Items) && len(inline)+indent*8 <= maxInline+8 {
|
|
sb.WriteString(inline)
|
|
sb.WriteString(";\n")
|
|
return
|
|
}
|
|
needSpace := false
|
|
for _, it := range s.Items {
|
|
switch {
|
|
case it.Bang:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
sb.WriteByte('!')
|
|
needSpace = false
|
|
case it.Value != nil:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
sb.WriteString(valueText(it.Value))
|
|
needSpace = true
|
|
case it.Block != nil:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
blockInline := Compact([]Item{it})
|
|
if !blockHasBlocks(it.Block) && len(blockInline) <= 40 {
|
|
sb.WriteString(blockInline)
|
|
} else if len(it.Block.Stmts) == 0 {
|
|
sb.WriteString("{ }")
|
|
} else {
|
|
sb.WriteString("{\n")
|
|
for _, inner := range it.Block.Stmts {
|
|
writeStmt(sb, inner, indent+1)
|
|
}
|
|
sb.WriteString(tabs)
|
|
sb.WriteString("}")
|
|
}
|
|
needSpace = true
|
|
}
|
|
}
|
|
sb.WriteString(";\n")
|
|
}
|
|
|
|
func hasNestedBlocks(items []Item) bool {
|
|
for _, it := range items {
|
|
if it.Block != nil && blockHasBlocks(it.Block) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func blockHasBlocks(b *Block) bool {
|
|
for _, s := range b.Stmts {
|
|
for _, it := range s.Items {
|
|
if it.Block != nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func writeItemsInline(sb *strings.Builder, items []Item) {
|
|
needSpace := false
|
|
for _, it := range items {
|
|
switch {
|
|
case it.Bang:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
sb.WriteByte('!')
|
|
needSpace = false
|
|
case it.Value != nil:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
sb.WriteString(valueText(it.Value))
|
|
needSpace = true
|
|
case it.Block != nil:
|
|
if needSpace {
|
|
sb.WriteByte(' ')
|
|
}
|
|
if len(it.Block.Stmts) == 0 {
|
|
sb.WriteString("{ }")
|
|
} else {
|
|
sb.WriteString("{ ")
|
|
for _, s := range it.Block.Stmts {
|
|
if s.Comment != "" {
|
|
sb.WriteString("/* " + s.Comment + " */ ")
|
|
}
|
|
writeItemsInline(sb, s.Items)
|
|
sb.WriteString("; ")
|
|
}
|
|
sb.WriteString("}")
|
|
}
|
|
needSpace = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// FormatValueList is FormatValue, except that a value whose block is a
|
|
// plain list (no nested blocks) of at least min elements is laid out one
|
|
// element per line, for reading: allow-query, exempt-clients, forwarders.
|
|
func FormatValueList(name, value string, min int) string {
|
|
st, err := ParseOptionValue(name, value)
|
|
if err != nil {
|
|
return FormatValue(name, value)
|
|
}
|
|
items := st.Items[1:]
|
|
blocks := 0
|
|
var list *Block
|
|
for _, it := range items {
|
|
if it.Block != nil {
|
|
blocks++
|
|
list = it.Block
|
|
}
|
|
}
|
|
if blocks != 1 || len(list.Stmts) < min || blockHasBlocks(list) {
|
|
return FormatValue(name, value)
|
|
}
|
|
var sb strings.Builder
|
|
for _, it := range items {
|
|
if it.Block == nil {
|
|
sb.WriteString(Compact([]Item{it}) + " ")
|
|
continue
|
|
}
|
|
sb.WriteString("{\n")
|
|
for _, s := range list.Stmts {
|
|
sb.WriteString("\t" + Compact(s.Items) + ";\n")
|
|
}
|
|
sb.WriteString("} ")
|
|
}
|
|
return strings.TrimSpace(sb.String())
|
|
}
|