blob: ffd359e5ff830bf053a5c88e6b75276d2d5300a2 (
plain) (
tree)
|
|
;;; defzone.el --- Generate zone files from Elisp -*- lexical-binding: t; -*-
(require 'dash)
(require 'dash-functional)
(require 's)
(defun record-to-record (zone record &optional subdomain)
"Evaluate a record definition and turn it into a zone file
record in ZONE, optionally prefixed with SUBDOMAIN."
(cl-labels ((plist->alist (plist)
(when plist
(cons
(cons (car plist) (cadr plist))
(plist->alist (cddr plist))))))
(let ((name (if subdomain (s-join "." (list subdomain zone)) zone)))
(pcase record
;; SOA RDATA (RFC 1035; 3.3.13)
((and `(SOA . (,ttl . ,keys))
(let (map (:mname mname) (:rname rname) (:serial serial)
(:refresh refresh) (:retry retry) (:expire expire)
(:minimum min))
(plist->alist keys)))
(if-let ((missing (-filter #'null (not (list mname rname serial
refresh retry expire min)))))
(error "Missing fields in SOA record: %s" missing)
(format "%s %s IN SOA %s %s %s %s %s %s %s"
name ttl mname rname serial refresh retry expire min)))
(`(NS . (,ttl . ,targets))
(->> targets
(-map (lambda (target) (format "%s %s IN NS %s" name ttl target)))
(s-join "\n")))
(`(MX . (,ttl . ,pairs))
(->> pairs
(-map (-lambda ((preference . exchange))
(format "%s %s IN MX %s %s" name ttl preference exchange)))
(s-join "\n")))
(`(TXT ,ttl ,text) (format "%s %s IN TXT %s" name ttl (prin1-to-string text)))
(`(A . (,ttl . ,ips))
(->> ips
(-map (lambda (ip) (format "%s %s IN A %s" name ttl ip)))
(s-join "\n")))
(`(CNAME ,ttl ,target) (format "%s %s IN CNAME %s" name ttl target))
((and `(,sub . ,records)
(guard (stringp sub)))
(s-join "\n" (-map (lambda (r) (record-to-record zone r sub)) records)))
(_ (error "Invalid record definition: %s" record))))))
(defmacro defzone (fqdn &rest records)
"Generate zone file for the zone at FQDN from a simple DSL."
(declare (indent defun))
`(s-join "\n" (-map (lambda (r) (record-to-record ,fqdn r)) (quote ,records))))
|