From 207ed695cf3c16159264e1602b69331eeaad2b7b Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Fri, 26 May 2023 13:16:20 -0400 Subject: [PATCH] BIND: Automatically create the subdirectories leading up to zonefiles (#2397) --- documentation/providers/bind.md | 7 ++++++- providers/bind/bindProvider.go | 26 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/documentation/providers/bind.md b/documentation/providers/bind.md index 95174dbd6..452e0c23c 100644 --- a/documentation/providers/bind.md +++ b/documentation/providers/bind.md @@ -96,7 +96,6 @@ The filenameformat is a string with a few printf-like `%` verbs: * `%%` `%` * ordinary characters (not `%`) are copied unchanged to the output stream * FYI: format strings must not end with an incomplete `%` or `%?` - * FYI: `/` or other filesystem separators result in undefined behavior Typical values: @@ -116,6 +115,12 @@ a different `directory` setting. Otherwise `dnscontrol` will write both domains to the same file, flapping between the two back and forth. +(new in v4.2.0) `dnscontrol push` will create subdirectories along the path to +the filename. This includes both the portion of the path created by the +`directory` setting and the `filenameformat` setting. The automatic creation of +subdirectories is disabled if `dnscontrol` is running as root for security +reasons. + # FYI: get-zones The DNSControl `get-zones all` subcommand scans the directory for diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 673ba317e..4d642db81 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -156,7 +156,7 @@ func (c *bindProvider) ListZones() ([]string, error) { func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) { if _, err := os.Stat(c.directory); os.IsNotExist(err) { - printer.Printf("\nWARNING: BIND directory %q does not exist!\n", c.directory) + printer.Printf("\nWARNING: BIND directory %q does not exist! (will create)\n", c.directory) } if c.zonefile == "" { @@ -312,7 +312,11 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR Msg: msg, F: func() error { printer.Printf("WRITING ZONEFILE: %v\n", c.zonefile) - zf, err := os.Create(c.zonefile) + fname, err := preprocessFilename(c.zonefile) + if err != nil { + return fmt.Errorf("could not create zonefile: %w", err) + } + zf, err := os.Create(fname) if err != nil { return fmt.Errorf("could not create zonefile: %w", err) } @@ -335,3 +339,21 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR return corrections, nil } + +// preprocessFilename pre-processes a filename we're about to os.Create() +// * On Windows systems, it translates the seperator. +// * It attempts to mkdir the directories leading up to the filename. +// * If running on Linux as root, it does not attempt to create directories. +func preprocessFilename(name string) (string, error) { + universalName := filepath.FromSlash(name) + // Running as root? Don't create the parent directories. It is unsafe. + if os.Getuid() != 0 { + // Create the parent directories + dir := filepath.Dir(name) + universalDir := filepath.FromSlash(dir) + if err := os.MkdirAll(universalDir, 0750); err != nil { + return "", err + } + } + return universalName, nil +}