package main /* convertzone: Read BIND-style zonefile and output. convertzone [-mode=MODE] zonename [filename] -mode=tsv TAB-separated values (default) -mode=dsl DNSControl DSL -mode=pretty Sort and pretty-print records. zonename The FQDN of the zone name. filename File to read (default: stdin) */ import ( "bufio" "flag" "fmt" "io" "log" "os" "strconv" "strings" "github.com/StackExchange/dnscontrol/providers/bind" "github.com/miekg/dns" "github.com/miekg/dns/dnsutil" "github.com/pkg/errors" ) var flagMode = flag.String("mode", "tsv", "tsv|dsl|pretty") var flagDefaultTTL = flag.Uint("ttl", 300, "Default TTL") var flagRegText = flag.String("registrar", "REG_FILL_IN", "registrar text") var flagProviderText = flag.String("provider", "DNS_FILL_IN", "provider text") // parseargs parses the non-flag arguments. func parseargs(args []string) (zonename string, filename string, r io.Reader, err error) { // 1 args: first arg is the zonename. Read stdin. // 2 args: first arg is the zonename. 2nd is the filename. // Anything else returns an error. if len(args) < 1 { return "", "", nil, fmt.Errorf("no command line parameters. Zone name required") } zonename = args[0] if len(args) == 1 { filename = "stdin" r = bufio.NewReader(os.Stdin) } else if len(args) == 2 { filename = flag.Arg(1) r, err = os.Open(filename) if err != nil { return "", "", nil, errors.Wrapf(err, "Could not open file: %s", filename) } } else { return "", "", nil, fmt.Errorf("too many command line parameters") } return zonename, filename, r, nil } // pretty outputs the zonefile using the prettyprinter. func pretty(zonename string, filename string, r io.Reader, defaultTTL uint32) { var l []dns.RR for x := range dns.ParseZone(r, zonename, filename) { if x.Error == nil { l = append(l, x.RR) } } bind.WriteZoneFile(os.Stdout, l, zonename) } // rrFormat outputs the zonefile in either DSL or TSV format. func rrFormat(zonename string, filename string, r io.Reader, defaultTTL uint32, dsl bool) { zonenamedot := zonename + "." for x := range dns.ParseZone(r, zonename, filename) { if x.Error != nil { continue } // Skip comments. Parse the formatted version. line := x.String() if line[0] == ';' { continue } items := strings.SplitN(line, "\t", 5) if len(items) < 5 { log.Fatalf("Too few items in: %v", line) } target := items[4] hdr := x.Header() nameFqdn := hdr.Name name := dnsutil.TrimDomainName(nameFqdn, zonenamedot) ttl := strconv.FormatUint(uint64(hdr.Ttl), 10) classStr := dns.ClassToString[hdr.Class] typeStr := dns.TypeToString[hdr.Rrtype] // MX records should split out the prio vs. target. if hdr.Rrtype == dns.TypeMX { target = strings.Replace(target, " ", "\t", 1) } if !dsl { // TSV format: fmt.Printf("%s\t%s\t%s\t%s\t%s\n", name, ttl, classStr, typeStr, target) } else { // DSL format: switch hdr.Rrtype { case dns.TypeMX: m := strings.SplitN(target, "\t", 2) target = m[0] + ", '" + m[1] + "'" case dns.TypeSOA: continue case dns.TypeTXT: // Leave target as-is. default: target = "'" + target + "'" } if hdr.Ttl == defaultTTL { ttl = "" } else { ttl = fmt.Sprintf(", TTL(%d)", hdr.Ttl) } fmt.Printf(",\n\t%s('%s', %s%s)", typeStr, name, target, ttl) } } } func main() { flag.Parse() zonename, filename, reader, err := parseargs(flag.Args()) if err != nil { flag.Usage() } defTTL := uint32(*flagDefaultTTL) switch *flagMode { case "pretty": pretty(zonename, filename, reader, defTTL) case "dsl": fmt.Printf(`D("%s", %s, DnsProvider(%s)`, zonename, *flagRegText, *flagProviderText) rrFormat(zonename, filename, reader, defTTL, true) fmt.Println("\n)") case "tsv": rrFormat(zonename, filename, reader, defTTL, false) default: flag.Usage() } }