(defvar *zone-output-stream* nil
"Stream to write zone data on.")
-(defmethod zone-write :around (format zone stream)
- (declare (ignore format))
+(defgeneric zone-write-raw-rrdata (format zr type data)
+ (:documentation "Write an otherwise unsupported record in a given FORMAT.
+
+ ZR gives the record object, which carries the name and TTL; the TYPE is
+ the numeric RRTYPE code; and DATA is an octet vector giving the RRDATA.
+ This is used by the default `zone-write-record' method to handle record
+ types which aren't directly supported by the format driver."))
+
+(export 'zone-write-header)
+(defgeneric zone-write-header (format zone)
+ (:documentation "Emit the header for a ZONE, in a given FORMAT.
+
+ The header includes any kind of initial comment, the SOA record, and any
+ other necessary preamble. There is no default implementation.
+
+ This is part of the protocol used by the default method on `zone-write';
+ if you override that method."))
+
+(export 'zone-write-trailer)
+(defgeneric zone-write-trailer (format zone)
+ (:documentation "Emit the header for a ZONE, in a given FORMAT.
+
+ The footer may be empty, and is so by default.
+
+ This is part of the protocol used by the default method on `zone-write';
+ if you override that method.")
+ (:method (format zone)
+ (declare (ignore format zone))
+ nil))
+
+(export 'zone-write-record)
+(defgeneric zone-write-record (format type zr)
+ (:documentation "Emit a record of the given TYPE (a keyword).
+
+ There is no default implementation."))
+
+(defmethod zone-write (format zone stream)
+ "This default method calls `zone-write-header', then `zone-write-record'
+ for each record in the zone, and finally `zone-write-trailer'. While it's
+ running, `*writing-zone*' is bound to the zone object, and
+ `*zone-output-stream*' to the output stream."
(let ((*writing-zone* zone)
(*zone-output-stream* stream))
- (call-next-method)))
+ (zone-write-header format zone)
+ (dolist (zr (zone-records-sorted zone))
+ (zone-write-record format (zr-type zr) zr))
+ (zone-write-trailer format zone)))
(export 'zone-save)
(defun zone-save (zones &key (format :bind))
(setf *bind-last-record-name* name)
name))))
-(export 'bind-record)
-(defgeneric bind-record (type zr))
+(defmethod zone-write :around ((format (eql :bind)) zone stream)
+ (let ((*bind-last-record-name* nil))
+ (call-next-method)))
-(defmethod zone-write ((format (eql :bind)) zone stream)
- (format stream "~
+(defmethod zone-write-header ((format (eql :bind)) zone)
+ (format *zone-output-stream* "~
;;; Zone file `~(~A~)'
;;; (generated ~A)
(zone-name zone)
(iso-date :now :datep t :timep t)
(zone-default-ttl zone))
- (let* ((*bind-last-record-name* nil)
- (soa (zone-soa zone))
+ (let* ((soa (zone-soa zone))
(admin (let* ((name (soa-admin soa))
(at (position #\@ name))
(copy (format nil "~(~A~)." name)))
(when at
(setf (char copy at) #\.))
copy)))
- (format stream "~
+ (format *zone-output-stream* "~
~A~30TIN SOA~40T~A (
~55@A~60T ;administrator
~45T~10D~60T ;serial
(soa-refresh soa)
(soa-retry soa)
(soa-expire soa)
- (soa-min-ttl soa))
- (dolist (zr (zone-records-sorted zone))
- (bind-record (zr-type zr) zr))))
+ (soa-min-ttl soa))))
(export 'bind-format-record)
-(defun bind-format-record (name ttl type format args)
+(defun bind-format-record (zr format &rest args)
(format *zone-output-stream*
"~A~20T~@[~8D~]~30TIN ~A~40T~?~%"
- (bind-output-hostname name)
- (and (/= ttl (zone-default-ttl *writing-zone*))
- ttl)
- (string-upcase (symbol-name type))
+ (bind-output-hostname (zr-name zr))
+ (let ((ttl (zr-ttl zr)))
+ (and (/= ttl (zone-default-ttl *writing-zone*))
+ ttl))
+ (string-upcase (symbol-name (zr-type zr)))
format args))
-(export 'bind-record-type)
-(defgeneric bind-record-type (type)
- (:method (type) type))
-
-(export 'bind-record-format-args)
-(defgeneric bind-record-format-args (type data)
- (:method ((type (eql :a)) data) (list "~A" (ipaddr-string data)))
- (:method ((type (eql :aaaa)) data) (list "~A" (ipaddr-string data)))
- (:method ((type (eql :ptr)) data) (list "~A" (bind-hostname data)))
- (:method ((type (eql :cname)) data) (list "~A" (bind-hostname data)))
- (:method ((type (eql :ns)) data) (list "~A" (bind-hostname data)))
- (:method ((type (eql :mx)) data)
- (list "~2D ~A" (cdr data) (bind-hostname (car data))))
- (:method ((type (eql :srv)) data)
- (destructuring-bind (prio weight port host) data
- (list "~2D ~5D ~5D ~A" prio weight port (bind-hostname host))))
- (:method ((type (eql :sshfp)) data)
- (cons "~2D ~2D ~A" data))
- (:method ((type (eql :txt)) data)
- (cons "~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]"
- (mapcar #'stringify data))))
-
-(defmethod bind-record (type zr)
- (destructuring-bind (format &rest args)
- (bind-record-format-args type (zr-data zr))
- (bind-format-record (zr-name zr)
- (zr-ttl zr)
- (bind-record-type type)
- format args)))
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :a)) zr)
+ (bind-format-record zr "~A" (ipaddr-string (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :aaaa)) zr)
+ (bind-format-record zr "~A" (ipaddr-string (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :ptr)) zr)
+ (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :cname)) zr)
+ (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :ns)) zr)
+ (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :mx)) zr)
+ (bind-format-record zr "~2D ~A"
+ (cdr (zr-data zr))
+ (bind-hostname (car (zr-data zr)))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :srv)) zr)
+ (destructuring-bind (prio weight port host) (zr-data zr)
+ (bind-format-record zr "~2D ~5D ~5D ~A"
+ prio weight port (bind-hostname host))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :sshfp)) zr)
+ (bind-format-record zr "~{~2D ~2D ~A~}" (zr-data zr)))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :txt)) zr)
+ (bind-format-record zr "~{~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]~}" (zr-data zr)))
;;;--------------------------------------------------------------------------
;;; tinydns-data output format.
(write-char (code-char byte) out)))))
(zr-ttl zr)))
-(defgeneric tinydns-record (type zr)
- (:method ((type (eql :a)) zr)
- (tinydns-output #\+ (zr-name zr)
- (ipaddr-string (zr-data zr)) (zr-ttl zr)))
- (:method ((type (eql :aaaa)) zr)
- (tinydns-output #\3 (zr-name zr)
- (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr)))
- (zr-ttl zr)))
- (:method ((type (eql :ptr)) zr)
- (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr)))
- (:method ((type (eql :cname)) zr)
- (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr)))
- (:method ((type (eql :ns)) zr)
- (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr)))
- (:method ((type (eql :mx)) zr)
- (let ((name (car (zr-data zr)))
- (prio (cdr (zr-data zr))))
- (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr))))
- (:method ((type (eql :txt)) zr)
- (tinydns-raw-record 16 zr
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :a)) zr)
+ (tinydns-output #\+ (zr-name zr)
+ (ipaddr-string (zr-data zr)) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :aaaa)) zr)
+ (tinydns-output #\3 (zr-name zr)
+ (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr)))
+ (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ptr)) zr)
+ (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :cname)) zr)
+ (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ns)) zr)
+ (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :mx)) zr)
+ (let ((name (car (zr-data zr)))
+ (prio (cdr (zr-data zr))))
+ (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :txt)) zr)
+ (tinydns-raw-record 16 zr
+ (build-record
+ (dolist (s (zr-data zr))
+ (rec-u8 (length s))
+ (rec-raw-string s)))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :srv)) zr)
+ (destructuring-bind (prio weight port host) (zr-data zr)
+ (tinydns-raw-record 33 zr
+ (build-record
+ (rec-u16 prio)
+ (rec-u16 weight)
+ (rec-u16 port)
+ (rec-name host)))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :sshfp)) zr)
+ (destructuring-bind (alg type fpr) (zr-data zr)
+ (tinydns-raw-record 44 zr
(build-record
- (dolist (s (zr-data zr))
- (rec-u8 (length s))
- (rec-raw-string s)))))
- (:method ((type (eql :srv)) zr)
- (destructuring-bind (prio weight port host) (zr-data zr)
- (tinydns-raw-record 33 zr
- (build-record
- (rec-u16 prio)
- (rec-u16 weight)
- (rec-u16 port)
- (rec-name host)))))
- (:method ((type (eql :sshfp)) zr)
- (destructuring-bind (alg type fpr) (zr-data zr)
- (tinydns-raw-record 44 zr
- (build-record
- (rec-u8 alg)
- (rec-u8 type)
- (do ((i 0 (+ i 2))
- (n (length fpr)))
- ((>= i n))
- (rec-u8 (parse-integer fpr
- :start i :end (+ i 2)
- :radix 16))))))))
-
-(defmethod zone-write ((format (eql :tinydns)) zone stream)
- (format stream "~
+ (rec-u8 alg)
+ (rec-u8 type)
+ (do ((i 0 (+ i 2))
+ (n (length fpr)))
+ ((>= i n))
+ (rec-u8 (parse-integer fpr
+ :start i :end (+ i 2)
+ :radix 16))))))))
+
+(defmethod zone-write-header ((format (eql :tinydns)) zone)
+ (format *zone-output-stream* "~
### Zone file `~(~A~)'
### (generated ~A)
~%"
(soa-refresh soa)
(soa-expire soa)
(soa-min-ttl soa)
- (zone-default-ttl zone)))
- (dolist (zr (zone-records-sorted zone))
- (tinydns-record (zr-type zr) zr)))
+ (zone-default-ttl zone))))
;;;----- That's all, folks --------------------------------------------------