diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/unbound/config/parse.go')
-rw-r--r-- | src/go/plugin/go.d/modules/unbound/config/parse.go | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/unbound/config/parse.go b/src/go/plugin/go.d/modules/unbound/config/parse.go new file mode 100644 index 000000000..99a632d50 --- /dev/null +++ b/src/go/plugin/go.d/modules/unbound/config/parse.go @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package config + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" +) + +type option struct{ name, value string } + +const ( + optInclude = "include" + optIncludeToplevel = "include-toplevel" + optCumulative = "statistics-cumulative" + optEnable = "control-enable" + optInterface = "control-interface" + optPort = "control-port" + optUseCert = "control-use-cert" + optKeyFile = "control-key-file" + optCertFile = "control-cert-file" +) + +func isOptionUsed(opt option) bool { + switch opt.name { + case optInclude, + optIncludeToplevel, + optCumulative, + optEnable, + optInterface, + optPort, + optUseCert, + optKeyFile, + optCertFile: + return true + } + return false +} + +// TODO: +// If also using chroot, using full path names for the included files works, relative pathnames for the included names +// work if the directory where the daemon is started equals its chroot/working directory or is specified before +// the include statement with directory: dir. + +// Parse parses Unbound configuration files into UnboundConfig. +// It follows logic described in the 'man unbound.conf': +// - Files can be included using the 'include:' directive. It can appear anywhere, it accepts a single file name as argument. +// - Processing continues as if the text from the included file was copied into the config file at that point. +// - Wildcards can be used to include multiple files. +// +// It stops processing on any error: syntax error, recursive include, glob matches directory etc. +func Parse(entryPath string) (*UnboundConfig, error) { + options, err := parse(entryPath, nil) + if err != nil { + return nil, err + } + return fromOptions(options), nil +} + +func parse(filename string, visited map[string]bool) ([]option, error) { + if visited == nil { + visited = make(map[string]bool) + } + if visited[filename] { + return nil, fmt.Errorf("'%s' already visited", filename) + } + visited[filename] = true + + f, err := open(filename) + if err != nil { + return nil, err + } + defer func() { _ = f.Close() }() + + var options []option + sc := bufio.NewScanner(f) + + for sc.Scan() { + line := strings.TrimSpace(sc.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + opt, err := parseLine(line) + if err != nil { + return nil, fmt.Errorf("file '%s', error on parsing line '%s': %v", filename, line, err) + } + + if !isOptionUsed(opt) { + continue + } + + if opt.name != optInclude && opt.name != optIncludeToplevel { + options = append(options, opt) + continue + } + + filenames, err := globInclude(opt.value) + if err != nil { + return nil, err + } + + for _, name := range filenames { + opts, err := parse(name, visited) + if err != nil { + return nil, err + } + options = append(options, opts...) + } + } + return options, nil +} + +func globInclude(include string) ([]string, error) { + if isGlobPattern(include) { + return filepath.Glob(include) + } + return []string{include}, nil +} + +func parseLine(line string) (option, error) { + parts := strings.Split(line, ":") + if len(parts) < 2 { + return option{}, errors.New("bad syntax") + } + key, value := cleanKeyValue(parts[0], parts[1]) + return option{name: key, value: value}, nil +} + +func cleanKeyValue(key, value string) (string, string) { + if i := strings.IndexByte(value, '#'); i > 0 { + value = value[:i-1] + } + key = strings.TrimSpace(key) + value = strings.Trim(strings.TrimSpace(value), "\"'") + return key, value +} + +func isGlobPattern(value string) bool { + magicChars := `*?[` + if runtime.GOOS != "windows" { + magicChars = `*?[\` + } + return strings.ContainsAny(value, magicChars) +} + +func open(filename string) (*os.File, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + fi, err := f.Stat() + if err != nil { + return nil, err + } + if !fi.Mode().IsRegular() { + return nil, fmt.Errorf("'%s' is not a regular file", filename) + } + return f, nil +} |