initial release for dnsd.
This commit is contained in:
parent
4b9a16653a
commit
50df77a8a8
|
@ -0,0 +1,674 @@
|
|||
;; ---------------------
|
||||
;; --- dnsd-database ---
|
||||
;; ---------------------
|
||||
|
||||
; A simple database for dnsd.scm
|
||||
|
||||
; This file is part of the Scheme Untergrund Networking package
|
||||
|
||||
; Copyright (c) 2005/2006 by Norbert Freudemann
|
||||
; <nofreude@informatik.uni-tuebingen.de>
|
||||
|
||||
; For copyright information, see the file COPYING which comes with
|
||||
; the distribution.
|
||||
|
||||
; Naming-Scheme:
|
||||
; --------------
|
||||
|
||||
; dbi- == No locks (should not be exported)
|
||||
; db- == With locks
|
||||
|
||||
; Lock-Safe Database-Interface:
|
||||
; -----------------------------
|
||||
|
||||
; (db-clear-database)
|
||||
; (db-clear-zone name class)
|
||||
; (db-update-zone zone-list)
|
||||
; (db-get-zone name class)
|
||||
; (db-get-zone-for-axfr name class)
|
||||
; (db-get-zone-soa-rr name class)
|
||||
; (db-pretty-print)
|
||||
|
||||
; Query/Database-Interface
|
||||
; ------------------------
|
||||
|
||||
; (db-lookup-rec qname class type)
|
||||
|
||||
; Database Structure:
|
||||
; -------------------
|
||||
; db-class-table: hash-table to db-zones
|
||||
; |
|
||||
; |-->db-zones-table: hash-table to db-zone
|
||||
; |
|
||||
; |-->db-zone: hash-table to db-rr
|
||||
; |
|
||||
; |-->db-rr-table: hash-table to lists of resource-records
|
||||
; of a given message-type
|
||||
|
||||
|
||||
;; Some stuff:
|
||||
;; -----------
|
||||
|
||||
;; Should be a dnsd-option?
|
||||
(define *debug-info* #t)
|
||||
|
||||
|
||||
;; Make a key for the database:
|
||||
;; TYPE: string -> string
|
||||
(define (make-key-name name)
|
||||
(let ((last-char (string-ref name (- (string-length name) 1))))
|
||||
(string-downcase (if (not (char=? #\. last-char))
|
||||
(string-append name ".")
|
||||
name))))
|
||||
|
||||
;; Compare the first string with the rear of the second string.
|
||||
;; TYPE: string x string -> boolean
|
||||
(define (string-ci-zone-name=? zone name)
|
||||
(let ((l1 (string-length zone))
|
||||
(l2 (string-length name)))
|
||||
(if (<= l1 l2) (string-ci=? zone (substring name (- l2 l1) l2)) #f)))
|
||||
|
||||
;; Search a list of resource-records for the soa-rr:
|
||||
;; TYPE: list-of-rrs -> soa-rr or #f
|
||||
(define (maybe-get-soa-rr l)
|
||||
(let loop ((l l))
|
||||
(if (null? l)
|
||||
#f
|
||||
(let ((e (car l)))
|
||||
(if (resource-record-data-soa?
|
||||
(resource-record-data e))
|
||||
e
|
||||
(loop (cdr l)))))))
|
||||
|
||||
|
||||
;; Get the name of a zone from a soa-rr within a zone-list:
|
||||
;; TYPE: list-of-rrs -> zone-name or #f
|
||||
(define (maybe-get-soa-rr-name l)
|
||||
(and-let* ((soa-rr (maybe-get-soa-rr l)))
|
||||
(resource-record-name soa-rr)))
|
||||
|
||||
|
||||
;; TYPE : list-or-rrs -> list-of-rrs
|
||||
(define (get-zone-list-w/o-soa l)
|
||||
(fold-right
|
||||
(lambda (e l)
|
||||
(if (resource-record-data-soa? (resource-record-data e)) l (cons e l)))
|
||||
'() l))
|
||||
|
||||
;; TODO: Do this different...
|
||||
(define display-debug
|
||||
(lambda args
|
||||
(if *debug-info*
|
||||
(begin
|
||||
(display "dnsd: ")
|
||||
(map (lambda (e) (display e) (display " ")) args)
|
||||
(newline))
|
||||
#f)))
|
||||
|
||||
;; Duplicate a resource-record: (Needed for wildcard-replies)
|
||||
(define (duplicate-rr name rr)
|
||||
(make-resource-record name
|
||||
(resource-record-type rr)
|
||||
(resource-record-class rr)
|
||||
(resource-record-ttl rr)
|
||||
(resource-record-data rr)))
|
||||
|
||||
|
||||
; ---------------------------
|
||||
; --- Database definition ---
|
||||
; ---------------------------
|
||||
|
||||
; Record-types:
|
||||
; -------------
|
||||
|
||||
; db-rr-table stores the resource-records of ONE domain-name.
|
||||
; hash-table is a symbol-table with 'message-type' as keys
|
||||
; and a list of resource-record of the key-message-type as data.
|
||||
; glue-data stores the information (as boolean) if the given domain-name
|
||||
; is for glue-data or official.
|
||||
(define-record-type db-rr-table :db-rr-table
|
||||
(really-make-db-rr-table hash-table glue-data)
|
||||
db-rr-table?
|
||||
(hash-table db-rr-table-hash-table)
|
||||
(glue-data db-rr-table-glue-data? set-db-rr-table-glue-data?!))
|
||||
|
||||
(define (make-db-rr-table) (really-make-db-rr-table (make-symbol-table) #f))
|
||||
|
||||
|
||||
; db-zone stores data (in form of db-rr-tables) for an entire zone
|
||||
; as given by e.g. a masterfile
|
||||
; hash-table a string-table. Keys are the domain-names of the zone
|
||||
; to link to db-rr-tables.
|
||||
; name the name of the zone.
|
||||
; soa-rr for easy-access :-)
|
||||
(define-record-type db-zone :db-zone
|
||||
(really-make-db-zone hash-table name soa-rr)
|
||||
db-zone?
|
||||
(hash-table db-zone-table)
|
||||
(name db-zone-name)
|
||||
(soa-rr get-db-zone-soa-rr))
|
||||
|
||||
(define (make-db-zone name soa-rr)
|
||||
(let ((primary-name (resource-record-data-soa-mname
|
||||
(resource-record-data soa-rr))))
|
||||
(really-make-db-zone (make-string-table) name soa-rr)))
|
||||
|
||||
|
||||
; db-zones-table stores all zones of a given message-class
|
||||
; hash-table key is the zone-name.
|
||||
(define-record-type db-zones-table :db-zones-table
|
||||
(really-make-db-zones-table hash-table)
|
||||
db-zones-table?
|
||||
(hash-table db-zones-table-hash-table))
|
||||
|
||||
(define (make-db-zones-table) (really-make-db-zones-table (make-string-table)))
|
||||
|
||||
|
||||
; db-class-table entry-point for the db.
|
||||
; hash-table key is the message-class (e.g. in) data are db-zones-tables
|
||||
; r/w-lock lock for exclusive-write-access.
|
||||
(define-record-type db-class-table :db-class-table
|
||||
(really-make-db-class-table hash-table r/w-lock)
|
||||
db-class-table?
|
||||
(hash-table db-class-table-hash-table set-db-class-table-hash-table!)
|
||||
(r/w-lock db-class-table-r/w-lock))
|
||||
|
||||
(define *database* (really-make-db-class-table (make-symbol-table)
|
||||
(make-r/w-lock)))
|
||||
|
||||
|
||||
; Predicates:
|
||||
; -----------
|
||||
|
||||
; Check if there is data for a given message-class:
|
||||
; TYPE: message-class -> boolean
|
||||
(define (dbi-class? class)
|
||||
(if (table-ref (db-class-table-hash-table *database*)
|
||||
(message-class-name class))
|
||||
#t #f))
|
||||
|
||||
|
||||
;; Modifiers:
|
||||
;; ----------
|
||||
|
||||
;; Delete the whole data in the database:
|
||||
(define (db-clear-database)
|
||||
(with-r/W-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(set-db-class-table-hash-table! *database* (make-symbol-table)))))
|
||||
|
||||
|
||||
;; Delete a zone (if present) with name 'name' from the database:
|
||||
;; TYPE: string x message-class -> boolean
|
||||
(define (db-clear-zone name class)
|
||||
(with-r/W-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(and-let* ((whatever (dbi-class? class))
|
||||
(class-table (db-class-table-hash-table *database*))
|
||||
(zones-type (table-ref class-table (message-class-name class)))
|
||||
(zones-table (db-zones-table-hash-table zones-type))
|
||||
(key-name (make-key-name name))
|
||||
(whatever (table-ref zones-table key-name)))
|
||||
(table-set! zones-table key-name #f)))))
|
||||
|
||||
|
||||
;; Stuff for db-add-zone:
|
||||
|
||||
;; Add a new class (if not already present) to the database:
|
||||
;; TYPE: message-class -> unspecific
|
||||
(define (dbi-maybe-add-class class)
|
||||
(if (not (dbi-class? class))
|
||||
(table-set! (db-class-table-hash-table *database*)
|
||||
(message-class-name class)
|
||||
(make-db-zones-table))))
|
||||
|
||||
|
||||
|
||||
;; --- Detection of Zone-Rules ---
|
||||
|
||||
|
||||
;; Detect and mark glue data (domains with NS and all of their subdomains)
|
||||
;; Give a warning, if the zone-tree is broken
|
||||
;; TYPE: db-def-table x string -> unspecific
|
||||
(define (dbi-mark-glue-in-zone def-table zone-name)
|
||||
(let ((tree (db-zone-table def-table)))
|
||||
(table-walk
|
||||
(lambda (key element)
|
||||
(if (table-ref (db-rr-table-hash-table element)
|
||||
(message-type-name (message-type a)))
|
||||
(let loop ((name key))
|
||||
(if (string-ci=? name zone-name)
|
||||
#t
|
||||
(let ((zone-entry (table-ref tree name)))
|
||||
(if zone-entry
|
||||
(if (table-ref (db-rr-table-hash-table zone-entry)
|
||||
(message-type-name (message-type ns)))
|
||||
(set-db-rr-table-glue-data?! element #t)
|
||||
(loop (cut-name name)))
|
||||
;; Be tolerant if the domain tree is broken...
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Warning (re)loading zone ~S. Broken tree: Domain ~S is missing!"
|
||||
zone-name name)
|
||||
(loop (cut-name name)))))))
|
||||
#t))
|
||||
tree)))
|
||||
|
||||
|
||||
;; Ensures the min. TTL from the soa-rr of the zone. Has to be called
|
||||
;; after dbi-mark-glue-in-zone!
|
||||
;; TYPE: db-def-table x soa-rr -> unspecific
|
||||
(define (dbi-ensure-min-ttl def-table soa-rr)
|
||||
(let ((min-ttl (resource-record-data-soa-minimum
|
||||
(resource-record-data soa-rr))))
|
||||
(table-walk
|
||||
(lambda (key element)
|
||||
(if (not (db-rr-table-glue-data? element))
|
||||
(table-walk
|
||||
(lambda (tkey telement)
|
||||
(table-set! (db-rr-table-hash-table element)
|
||||
tkey
|
||||
(map (lambda (e)
|
||||
(let ((rr-ttl (resource-record-ttl e)))
|
||||
(make-resource-record
|
||||
(resource-record-name e)
|
||||
(resource-record-type e)
|
||||
(resource-record-class e)
|
||||
(if (< rr-ttl min-ttl)
|
||||
min-ttl rr-ttl)
|
||||
(resource-record-data e))))
|
||||
telement)))
|
||||
(db-rr-table-hash-table element))))
|
||||
(db-zone-table def-table))))
|
||||
|
||||
|
||||
;; Give a warning, if a Zone with a CNAME-RR contains other stuff...
|
||||
;; TYPE: db-def-table -> unspecific
|
||||
(define (dbi-cname-warning def-table zone-name)
|
||||
(table-walk
|
||||
(lambda (key element)
|
||||
(let ((rr-table (db-rr-table-hash-table element))
|
||||
(cname (message-type-name (message-type cname))))
|
||||
(if (table-ref rr-table cname)
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(if (not (eq? k cname))
|
||||
(dnsd-log (syslog-level info)
|
||||
"Warning (re)loading zone ~S. Domain ~S contains a CNAME-RR and other RRs at the same time."
|
||||
zone-name key)
|
||||
(if (not (= 1 (length e)))
|
||||
(dnsd-log (syslog-level info)
|
||||
"Warning (re)loading zone ~S. Domain ~S contains 2 or more CNAME-RRs!"
|
||||
zone-name key))))
|
||||
rr-table))))
|
||||
(db-zone-table def-table)))
|
||||
|
||||
|
||||
;; This functions have to be called in the given order:
|
||||
;; TYPE: db-def-table x string x soa-rr -> unspecific
|
||||
(define (dbi-set-zone-requirements def-table zone-name soa-rr)
|
||||
(dbi-mark-glue-in-zone def-table zone-name)
|
||||
(dbi-ensure-min-ttl def-table soa-rr)
|
||||
(dbi-cname-warning def-table zone-name))
|
||||
|
||||
|
||||
;; Adds a list of resource-records to a zone-definition-table:
|
||||
(define (dbi-add-zone-list def-table rr-list)
|
||||
(let ((tree (db-zone-table def-table)))
|
||||
(for-each
|
||||
(lambda (e)
|
||||
(let* ((domain-key (make-key-name (resource-record-name e)))
|
||||
(type-key (message-type-name (resource-record-type e)))
|
||||
(rr-type (table-ref tree domain-key)))
|
||||
;; Create & link a new rr-table for the first entry of the rr-type:
|
||||
(if (not (db-rr-table? rr-type))
|
||||
(begin (set! rr-type (make-db-rr-table))
|
||||
(table-set! tree domain-key rr-type)))
|
||||
(let* ((rr-table (db-rr-table-hash-table rr-type))
|
||||
(entry (table-ref rr-table type-key)))
|
||||
(if entry
|
||||
(table-set! rr-table type-key (cons e entry))
|
||||
(table-set! rr-table type-key (cons e '()))))))
|
||||
rr-list)))
|
||||
|
||||
|
||||
;; Adds a zone to the database which is given as a list of resource-records.
|
||||
;; Notes: * db-add-zone doesn't overwrite existing zones.
|
||||
;; * Just for internal use.
|
||||
;; TYPE: list-of-rrs -> boolean
|
||||
(define (db-add-zone zone-list)
|
||||
(with-r/W-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(and-let* ((soa-rr (maybe-get-soa-rr zone-list))
|
||||
(zone-name (resource-record-name soa-rr))
|
||||
(zone-key (make-key-name zone-name))
|
||||
(zone-class (resource-record-class soa-rr)))
|
||||
;; Add another class to the database?
|
||||
(dbi-maybe-add-class zone-class)
|
||||
;; Get the zone-stuff to insert the zone into together:
|
||||
(let* ((zone-table (db-zones-table-hash-table
|
||||
(table-ref (db-class-table-hash-table *database*)
|
||||
(message-class-name zone-class)))))
|
||||
;; Don't overwrite an existing zone
|
||||
(if (table-ref zone-table zone-key) #f
|
||||
;; Add the zone to the db & ensure data integrity:
|
||||
(let* ((zone-dtable (make-db-zone zone-key soa-rr)))
|
||||
(table-set! zone-table zone-key zone-dtable)
|
||||
(dbi-add-zone-list zone-dtable zone-list)
|
||||
(dbi-set-zone-requirements zone-dtable zone-name soa-rr))))))))
|
||||
|
||||
|
||||
;; Update a zone if the serial of the new soa isn't the same or less.
|
||||
;; TYPE: list-of-rrs -> boolean
|
||||
(define (db-update-zone zone-list)
|
||||
(and-let* ((new-soa-rr (maybe-get-soa-rr zone-list))
|
||||
(new-serial (resource-record-data-soa-serial
|
||||
(resource-record-data new-soa-rr)))
|
||||
(zone-name (make-key-name (resource-record-name new-soa-rr)))
|
||||
(zone-class (resource-record-class new-soa-rr)))
|
||||
(let ((old-soa-rr (db-get-zone-soa-rr zone-name zone-class)))
|
||||
(cond
|
||||
((or (not old-soa-rr)
|
||||
(and old-soa-rr
|
||||
(> new-serial (resource-record-data-soa-serial
|
||||
(resource-record-data old-soa-rr)))))
|
||||
(db-clear-zone zone-name zone-class)
|
||||
(db-add-zone zone-list))
|
||||
((= new-serial (resource-record-data-soa-serial
|
||||
(resource-record-data old-soa-rr)))
|
||||
#t) ;; !!! If the serial hasn't changed it's considered successfull.
|
||||
(else #f)))))
|
||||
|
||||
|
||||
; Get all resource records for a zone.
|
||||
; TYPE: string x message-class -> list-of-rrs or #f
|
||||
(define (db-get-zone name class)
|
||||
(with-R/w-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
|
||||
(message-class-name class)))
|
||||
(the-zone-type (table-ref (db-zones-table-hash-table zone-type)
|
||||
(make-key-name name)))
|
||||
(zone-tree-tree (db-zone-table the-zone-type))
|
||||
(res-list '()))
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(if e
|
||||
(table-walk (lambda (k1 e1)
|
||||
(set! res-list (append e1 res-list)))
|
||||
(db-rr-table-hash-table e))))
|
||||
zone-tree-tree)
|
||||
res-list))))
|
||||
|
||||
|
||||
; ; Get the timestamp for a zone.
|
||||
; ; TYPE: string x message-class -> number or #f
|
||||
; (define (db-get-zone-timestamp name class)
|
||||
; (with-R/w-lock
|
||||
; (db-class-table-r/w-lock *database*)
|
||||
; (lambda ()
|
||||
; (and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
|
||||
; (message-class-name class)))
|
||||
; (the-zone-type (table-ref (db-zones-table-hash-table zone-type)
|
||||
; (make-key-name name))))
|
||||
; (get-db-zone-timestamp the-zone-type)))))
|
||||
|
||||
|
||||
;; Get the soa-rr of a zone.
|
||||
;; TYPE: string x message-class -> soa-rr or #f
|
||||
(define (db-get-zone-soa-rr name class)
|
||||
(with-R/w-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
|
||||
(message-class-name class)))
|
||||
(the-zone-type (table-ref (db-zones-table-hash-table zone-type)
|
||||
(make-key-name name))))
|
||||
(get-db-zone-soa-rr the-zone-type)))))
|
||||
|
||||
|
||||
; Get all rrs of a zone in an AXFR-ready list: '(soa-rr rr rr ... rr soa-rr)
|
||||
; TYPE: string x message-class -> list-of-rrs or #f
|
||||
(define (db-get-zone-for-axfr name class)
|
||||
(and-let* ((zone-list (db-get-zone name class))
|
||||
(soa-l (list (maybe-get-soa-rr zone-list)))
|
||||
(rest-l (get-zone-list-w/o-soa zone-list)))
|
||||
(append soa-l rest-l soa-l)))
|
||||
|
||||
|
||||
;; Look for the zone in which 'name' is a subdomain or the domain of the
|
||||
;; given zones. Returns the zone which is the nearest ancestor to 'name'.
|
||||
;; TYPE: name x message-class -> db-zone-record-type or #f
|
||||
(define (dbi-lookup-zone-for-name name class)
|
||||
(and-let* ((zone-record (table-ref (db-class-table-hash-table *database*)
|
||||
(message-class-name class)))
|
||||
(zone-table (db-zones-table-hash-table zone-record))
|
||||
(ancestors '())
|
||||
(zone-key ""))
|
||||
;; Look for zones who are ancestors to key:
|
||||
(table-walk (lambda (k e)
|
||||
(if (string-ci-zone-name=? k (make-key-name name))
|
||||
(set! ancestors (cons k ancestors))))
|
||||
zone-table)
|
||||
(cond
|
||||
((null? ancestors) #f)
|
||||
((= 1 (length ancestors)) (set! zone-key (car ancestors)))
|
||||
;; If more ancestors are found get the closest one:
|
||||
(else (set! zone-key (fold-right (lambda (a b) (if (< (string-length a)
|
||||
(string-length b))
|
||||
b a))
|
||||
"" ancestors))))
|
||||
(table-ref zone-table zone-key)))
|
||||
|
||||
|
||||
; Look for the entries of type 'type' in a given db-rr-table
|
||||
; TYPE: db-rr-table-rec-type x message-type -> list-of-rrs
|
||||
(define (dbi-lookup-rrs rr-record-type type)
|
||||
(let ((rr-table (db-rr-table-hash-table rr-record-type)))
|
||||
(cond
|
||||
((eq? (message-type *) type) ; ... return all records.
|
||||
(let ((res '())) (table-walk (lambda (k e) (set! res (cons e res)))
|
||||
rr-table)
|
||||
res))
|
||||
(else (let ((res (table-ref rr-table (message-type-name type))))
|
||||
(if res res '()))))))
|
||||
|
||||
|
||||
;; Look for the entries of type 'type' in a given db-rr-table
|
||||
;; TYPE: db-rr-table-rec-type x messag-type -> list-of-rrs or #f
|
||||
(define (dbi-lookup-rrs? rr-record-type type)
|
||||
(let ((res (dbi-lookup-rrs rr-record-type type)))
|
||||
(if (null? res) #f res)))
|
||||
|
||||
|
||||
;; --------------------------------
|
||||
;; --- Query/Database Interface ---
|
||||
;; --------------------------------
|
||||
|
||||
;; Requests for mailbox-related resource-records will be handled as mx requests:
|
||||
;; TYPE: string x type x class ->
|
||||
;; '(list-of-answers-rrs list-of-nameservers-rrs list-of-additional-rrs boolean)
|
||||
(define (db-lookup-rec qname class type)
|
||||
(obtain-R/w-lock (db-class-table-r/w-lock *database*))
|
||||
(receive
|
||||
(anli auli adli aufl)
|
||||
(dbi-lookup-rec-int qname class (if (eq? type (message-type mailb))
|
||||
(message-type mx)
|
||||
type) ; Mailb == mx query
|
||||
'())
|
||||
(release-R/w-lock (db-class-table-r/w-lock *database*))
|
||||
(values anli auli adli aufl)))
|
||||
|
||||
|
||||
;; Main part of the algorithm as described in RFC 1034. Returns found rrs and
|
||||
;; a flag, indicating if the answer is authoritative.
|
||||
;; The flag ist needed, because of glue-data, that could be part of the
|
||||
;; response. The operand 'c-list' is used to detect and avoid cname-loops.
|
||||
;; TYPE: string x type x class x c-list ->
|
||||
;; '(list-of-answers-rrs list-of-nameservers-rrs list-of-additional-rrs boolean)
|
||||
(define (dbi-lookup-rec-int qname class type c-list)
|
||||
(let ((zone (dbi-lookup-zone-for-name qname class)))
|
||||
(if (not zone)
|
||||
(values '() '() '() #f) ; no zone in db
|
||||
(let ((zone-name (db-zone-name zone)))
|
||||
;; loop over the labels of the name. eg. my.example. / example. / .
|
||||
;; keep track of the iterations (mostly for wildcard-match support)
|
||||
(let loop ((name qname) (loop-count 0))
|
||||
(let ((rr-table (table-ref (db-zone-table zone)
|
||||
(make-key-name name))))
|
||||
(if rr-table
|
||||
(cond
|
||||
;; A wildcard match
|
||||
((= 1 loop-count)
|
||||
;; Set the name of the rrs from * to qname.
|
||||
(values (map (lambda (e) (duplicate-rr qname e))
|
||||
(dbi-lookup-rrs rr-table type)) '() '() #t))
|
||||
;; Direct match (0) or glue-data match (>1)
|
||||
((or (= 0 loop-count) (< 1 loop-count))
|
||||
(cond ;c2
|
||||
;; Found glue data.
|
||||
((and (dbi-lookup-rrs? rr-table (message-type ns))
|
||||
(not (string-ci=? name zone-name))
|
||||
(not (eq? (message-type ns) type)))
|
||||
(let* ((ns-rr-list (dbi-lookup-rrs?
|
||||
rr-table (message-type ns)))
|
||||
(res-l
|
||||
(fold-right
|
||||
(lambda (e l)
|
||||
(receive
|
||||
(anli auli adli aufl)
|
||||
(dbi-lookup-rec-int
|
||||
(resource-record-data-ns-name
|
||||
(resource-record-data e))
|
||||
class (message-type a) c-list)
|
||||
(list (car l) (cadr l)
|
||||
(append anli (caddr l)) #f)))
|
||||
'(() () () #t) ns-rr-list)))
|
||||
(values (car res-l) (append ns-rr-list (cadr res-l))
|
||||
(caddr res-l) #f)))
|
||||
;; Looking for correct information (direct match)
|
||||
((= 0 loop-count)
|
||||
(cond ;c3
|
||||
;; CNAME: Causes an additional lookup
|
||||
((dbi-lookup-rrs? rr-table (message-type cname))
|
||||
=> (lambda (cname-rr-list)
|
||||
(let ((cname-rr (car cname-rr-list)))
|
||||
(if (eq? (message-type cname) type)
|
||||
(values (list cname-rr) '() '() #t)
|
||||
(begin
|
||||
(if (fold-right
|
||||
(lambda (e b)
|
||||
(or (string-ci=? e name) b))
|
||||
#f c-list)
|
||||
(begin
|
||||
;; Problem?: The loop will be send
|
||||
;; as a response... .
|
||||
(display-debug " Found cname-loop")
|
||||
(values '() '() '() #t))
|
||||
(receive
|
||||
(anli auli adli aufl)
|
||||
(dbi-lookup-rec-int
|
||||
(resource-record-data-cname-name
|
||||
(resource-record-data cname-rr))
|
||||
class type (cons name c-list))
|
||||
(values (append (list cname-rr) anli)
|
||||
auli adli
|
||||
(and aufl #t)))))))))
|
||||
;; MX: Causes an additional lookup
|
||||
((eq? (message-type mx) type)
|
||||
(let* ((mx-rrs (dbi-lookup-rrs rr-table type))
|
||||
(res-l
|
||||
(fold-right
|
||||
(lambda (e l)
|
||||
(receive
|
||||
(anli auli adli aufl)
|
||||
(dbi-lookup-rec-int
|
||||
(resource-record-data-mx-exchanger
|
||||
(resource-record-data e))
|
||||
class (message-type a) c-list)
|
||||
(list (car l) (cadr l)
|
||||
(append anli (caddr l))
|
||||
(and #t (cadddr l)))))
|
||||
'(() () () #t) mx-rrs)))
|
||||
(values (append mx-rrs (car res-l)) (cadr res-l)
|
||||
(caddr res-l) (and #t (cadddr res-l)))))
|
||||
;; Glue-Data entries aren't authoritative:
|
||||
((db-rr-table-glue-data? rr-table)
|
||||
(values (dbi-lookup-rrs rr-table type) '() '() #f))
|
||||
;; Found a match with no additional lookups.
|
||||
(else
|
||||
(values (dbi-lookup-rrs rr-table type) '() '() #t))))
|
||||
;; Got a dns-name-error (RCODE=3)
|
||||
(else (values '() '() '() #t)))))
|
||||
;; Found no match for the current name.
|
||||
(cond
|
||||
((> (string-length zone-name) (string-length name))
|
||||
(error "Woh, found a bug... ")) ; Just for safety...
|
||||
;; Search for wildcards in the first iteration:
|
||||
((= 0 loop-count)
|
||||
(loop (string-append "*." (cut-name name)) 1))
|
||||
(else (loop (cut-name name) (+ 1 loop-count)))))))))))
|
||||
|
||||
|
||||
;; ------------------------------
|
||||
;; --- Database pretty-print: ---
|
||||
;; ------------------------------
|
||||
|
||||
(define (pretty-print-record-type rt)
|
||||
(cond
|
||||
((db-class-table? rt)
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(newline)
|
||||
(display "DB-Class: ")
|
||||
(display k)(newline)
|
||||
(pretty-print-record-type e))
|
||||
(db-class-table-hash-table rt)))
|
||||
((db-zones-table? rt)
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(display " DB-Zone: ")
|
||||
(display k)
|
||||
(newline)
|
||||
(pretty-print-record-type e))
|
||||
(db-zones-table-hash-table rt)))
|
||||
((db-zone? rt)
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(display " DB-Zone-Entries: ")
|
||||
(display k)
|
||||
(newline)
|
||||
(pretty-print-record-type e))
|
||||
(db-zone-table rt)))
|
||||
((db-rr-table? rt)
|
||||
(table-walk
|
||||
(lambda (k e)
|
||||
(display " DB-RR-Table: ")
|
||||
(display k)
|
||||
(newline)
|
||||
(display " Glue-data: ")
|
||||
(display (db-rr-table-glue-data? rt))
|
||||
(newline)
|
||||
(newline)
|
||||
(pretty-print-record-type e))
|
||||
(db-rr-table-hash-table rt)))
|
||||
((list? rt)
|
||||
(for-each
|
||||
(lambda (e)
|
||||
(pretty-print-dns-message e)
|
||||
(newline))
|
||||
rt))
|
||||
(else (newline))))
|
||||
|
||||
(define (db-pretty-print)
|
||||
(with-R/w-lock
|
||||
(db-class-table-r/w-lock *database*)
|
||||
(lambda ()
|
||||
(newline)
|
||||
(display "DNS-Server-Database:")(newline)
|
||||
(display "--------------------")(newline)
|
||||
(pretty-print-record-type *database*))))
|
|
@ -0,0 +1,836 @@
|
|||
; ------------------
|
||||
; --- DNS-Server ---
|
||||
; ------------------
|
||||
|
||||
; A DNS-Server based on the RFCs: 1034 / 1035
|
||||
|
||||
; This file is (maybe) part of the Scheme Untergrund Networking package
|
||||
|
||||
; Copyright (c) 2005/2006 by Norbert Freudemann
|
||||
; <nofreude@informatik.uni-tuebingen.de>
|
||||
|
||||
; For copyright information, see the file COPYING which comes with
|
||||
; the distribution.
|
||||
|
||||
; TODO:
|
||||
; -----
|
||||
|
||||
; Testing, testing, testing...
|
||||
|
||||
; Nice stuff to have:
|
||||
; * IXFR
|
||||
; * IPv6-Support
|
||||
; * Support more types (& other classes)
|
||||
; * Masterfile-parser: $GENERATE ...
|
||||
; * Some accurate way to limit the cache to a certain mem-size?
|
||||
; * Better syslog interaction.
|
||||
|
||||
; Doc-TODO:
|
||||
; - Master-File-Parser
|
||||
; - Cache
|
||||
; - Database
|
||||
; - dnsd messages
|
||||
; - dnsd-options
|
||||
|
||||
; Message Example (Query):
|
||||
; ------------------------
|
||||
|
||||
; (define *query-example*
|
||||
; (make-message (make-header 0815 (make-flags 1 0 #f #f #f #f 0 0) 1 0 0 0)
|
||||
; (list (make-question "uni-tuebingen.de."
|
||||
; (message-type a)
|
||||
; (message-class in)))
|
||||
; '() '() '() '()))
|
||||
|
||||
|
||||
;; Assignment procedures for messages (basically dns.scm extension)
|
||||
;; ----------------------------------------------------------------
|
||||
|
||||
;; Set the truncation bit of an octet-message (for UDP):
|
||||
;; TYPE: message x boolean -> message
|
||||
(define (octet-msg-change-truncation msg bool)
|
||||
(let* ((id (take msg 2))
|
||||
(rest (drop msg 3))
|
||||
(flag (char->ascii (caddr msg)))
|
||||
(flag-RD (if (even? flag) 0 1))
|
||||
(flag-shift (arithmetic-shift flag -2)))
|
||||
(append id (list (ascii->char
|
||||
(+ flag-RD (arithmetic-shift
|
||||
(+ (if bool 1 0)
|
||||
(arithmetic-shift flag-shift 1)) 1))))
|
||||
rest)))
|
||||
|
||||
|
||||
;; Interpreting the results of db-lookup-rec. Is there a zone in the db:
|
||||
;; TYPE: '(list-of-ans list-of-aut list-of-add boolean) -> boolean
|
||||
(define (no-zone? res-l)
|
||||
(and (null? (car res-l)) (null? (cadr res-l))
|
||||
(null? (caddr res-l)) (not (cadddr res-l))))
|
||||
|
||||
|
||||
;; A reply is chacheworthy if it contains no errors and is authoritative.
|
||||
;; TYPE: message -> boolean
|
||||
(define (msg-cachable? msg)
|
||||
(and (eq? 'dns-no-error (flags-response-code
|
||||
(header-flags (message-header msg))))
|
||||
(flags-authoritative? (header-flags (message-header msg)))))
|
||||
|
||||
|
||||
;; ------------
|
||||
;; --- AXFR ---
|
||||
;; ------------
|
||||
|
||||
;; AXFR is triggered by the zone-management-thread below:
|
||||
;; TYPE: rr x string x message-class x dnsd-options -> boolean
|
||||
(define (axfr-update soa-rr zone-name class dnsd-options dnsddb-options)
|
||||
|
||||
;; Search for the primary nameserver (msg) & get the soa-rr (msg2)
|
||||
;; TYPE: string x string x message-class x dnsd-options -> soa-rr x ns-ip
|
||||
(define (receive-soa-message ns-name name class dnsd-options dnsddb-options)
|
||||
(let* ((ip? (dnsddb-options-master-ip dnsddb-options))
|
||||
;; Lookup the IP or use dnsddb-options-master-ip
|
||||
(nameserver
|
||||
(if (and ip? (ip-string? ip?))
|
||||
(ip-string->address32 ip?)
|
||||
(let* ((msg (dnsd-ask-resolver-rec
|
||||
(make-simple-query-message ns-name
|
||||
(message-type a) class)
|
||||
(network-protocol udp) dnsd-options))
|
||||
(error-cond (flags-response-code
|
||||
(header-flags
|
||||
(message-header msg)))))
|
||||
(if (eq? 'dns-no-error error-cond)
|
||||
(resource-record-data-a-ip
|
||||
(resource-record-data
|
||||
(car (message-answers msg))))
|
||||
(begin
|
||||
(dnsd-log (syslog-level debug)
|
||||
"AXFR: Error (~S) during rec.-lookup for the address of the primary NS for zone ~S."
|
||||
error-cond
|
||||
name)
|
||||
#f))))))
|
||||
(if nameserver
|
||||
(let* ((msg2 (dnsd-ask-resolver-direct
|
||||
(make-simple-query-message name (message-type soa)
|
||||
class)
|
||||
(list nameserver) (network-protocol udp) dnsd-options))
|
||||
(error-cond (flags-response-code
|
||||
(header-flags (message-header msg2)))))
|
||||
(if (eq? 'dns-no-error error-cond)
|
||||
(values (car (message-answers msg2)) nameserver)
|
||||
(begin
|
||||
(dnsd-log (syslog-level debug)
|
||||
"AXFR: Error (~S) during rec.-lookup for the SOA-record of the primary NS for zone ~S."
|
||||
error-cond
|
||||
name)
|
||||
(values #f #f))))
|
||||
(values #f #f))))
|
||||
|
||||
;; Try to receive an zone with an AXFR-request:
|
||||
(define (receive-axfr-message name class nameserver dnsd-options)
|
||||
(let* ((msg (dnsd-ask-resolver-direct
|
||||
(make-simple-query-message name (message-type axfr) class)
|
||||
nameserver (network-protocol tcp) dnsd-options))
|
||||
(error-cond (flags-response-code (header-flags
|
||||
(message-header msg)))))
|
||||
(if (eq? error-cond 'dns-no-error)
|
||||
(message-answers msg)
|
||||
(begin
|
||||
(dnsd-log (syslog-level debug)
|
||||
"AXFR: Error (~S) during AXFR-request for zone ~S"
|
||||
error-cond
|
||||
name)
|
||||
#f))))
|
||||
|
||||
(let* ((soa-data (resource-record-data soa-rr))
|
||||
(zone-mname (resource-record-data-soa-mname soa-data))
|
||||
(zone-serial (resource-record-data-soa-serial soa-data)))
|
||||
(dnsd-log (syslog-level info)
|
||||
"AXFR: Starting AXFR-Update for zone ~S"
|
||||
(resource-record-name soa-rr))
|
||||
(receive
|
||||
(new-soa nameserver)
|
||||
(receive-soa-message zone-mname zone-name class dnsd-options dnsddb-options)
|
||||
(if (not new-soa)
|
||||
#f
|
||||
;; Compare the serials of the local and remote soa-rrs to decide
|
||||
;; if an update is neccessary.
|
||||
(if (< zone-serial (resource-record-data-soa-serial
|
||||
(resource-record-data new-soa)))
|
||||
;; Try an (AXFR)-Update...
|
||||
(let ((axfr-zone (receive-axfr-message zone-name class
|
||||
(list nameserver)
|
||||
dnsd-options)))
|
||||
(if axfr-zone
|
||||
(begin
|
||||
(let ((first (resource-record-data (car axfr-zone)))
|
||||
(last (resource-record-data
|
||||
(list-ref axfr-zone
|
||||
(- (length axfr-zone) 1)))))
|
||||
(if (and (resource-record-data-soa? first)
|
||||
(resource-record-data-soa? last))
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"AXFR: Received AXFR-Reply for zone ~S. Starting database-update."
|
||||
zone-name)
|
||||
(db-update-zone (cdr axfr-zone)))
|
||||
#f)))
|
||||
#f))
|
||||
#t)))))
|
||||
|
||||
|
||||
;; ---------------------------------------------
|
||||
;; --- Query-lookup in database and/or cache ---
|
||||
;; ---------------------------------------------
|
||||
|
||||
;; Currently supported types:
|
||||
;; TYPE: message-type -> boolean
|
||||
(define (dnsd-supported-type? type)
|
||||
(not (null? (filter (lambda (e) (eq? type e))
|
||||
(list (message-type a)
|
||||
(message-type ns)
|
||||
(message-type cname)
|
||||
(message-type soa)
|
||||
(message-type ptr)
|
||||
(message-type hinfo)
|
||||
(message-type mx)
|
||||
(message-type txt)
|
||||
(message-type axfr)
|
||||
(message-type mailb); Mailbox-related rrs. Here: mx
|
||||
(message-type *))))))
|
||||
|
||||
|
||||
;; TODO: Find out how to handle a standard query with multiple questions?
|
||||
;; Should that be allowed at all?
|
||||
|
||||
|
||||
;; Main algorithm for incoming queries. Responsibilities:
|
||||
;; - decides if the query-type is implemented
|
||||
;; - decides if and when to use cache/db-lookup/recursive lookup
|
||||
;; TYPE: message x dnsd-options -> message
|
||||
(define (lookup-query query dnsd-options)
|
||||
(let ((query-flags (header-flags (message-header query))))
|
||||
;; What OPCODE do we have here?
|
||||
(cond
|
||||
;; * [1] standard query (the only supported so far)
|
||||
((= 0 (flags-opcode query-flags))
|
||||
(let* ((question (car (message-questions query)))
|
||||
(qname (question-name question))
|
||||
(qclass (question-class question))
|
||||
(qtype (question-type question)))
|
||||
;; What kind of QTYPE do we have?
|
||||
(cond
|
||||
;; AXFR (252): A zone transfer... .
|
||||
((and (eq? (message-type axfr) qtype)
|
||||
(dnsd-options-use-axfr? dnsd-options))
|
||||
(let ((zone (db-get-zone-for-axfr qname qclass)))
|
||||
;; TODO: Is it okay to send the whole zone?
|
||||
;; Maybe there should be checked who is asking?
|
||||
(make-response query (list zone '() '() #t) dnsd-options)))
|
||||
;; Supported QTYPES:
|
||||
((dnsd-supported-type? qtype)
|
||||
;; Try to get a database reply
|
||||
(let ((res-l (if (dnsd-options-use-db? dnsd-options)
|
||||
(receive
|
||||
(anli auli adli aufl)
|
||||
(db-lookup-rec qname qclass qtype)
|
||||
(list anli auli adli aufl))
|
||||
(list '() '() '() #f))))
|
||||
;; Use recursion for local-result: '(() () () #f)
|
||||
(if (and (dnsd-options-use-recursion? dnsd-options)
|
||||
(no-zone? res-l)
|
||||
(flags-recursion-desired? query-flags))
|
||||
(dnsd-ask-resolver-rec query (network-protocol udp) dnsd-options)
|
||||
(make-response query res-l dnsd-options))))
|
||||
;; Unsupported QTYPEs:
|
||||
(else (msg-set-rcode! query 4) query))))
|
||||
;; This kind of queries are not implemented:
|
||||
;; * [2] inverse query (not really used anymore (see RFC 3425))
|
||||
;; * [3] server status request (marked experimental in RFC 1035)
|
||||
;; * [4-15] reserved for future use (RFC 1035)
|
||||
(else (msg-set-rcode! query 4) query))))
|
||||
|
||||
|
||||
;; --------------
|
||||
;; --- Server ---
|
||||
;; --------------
|
||||
|
||||
;; Management of a zone:
|
||||
;; ---------------------
|
||||
|
||||
;; Management consists of periodically checking the local files for
|
||||
;; new information for primary-zones and to trigger AXFR-Updates for secondary
|
||||
;; zones.
|
||||
;; TYPE channel x channel x dnsd-options x dnsddb-options -> new-thread
|
||||
(define (dnsd-zone-mgt-thread ch-usr1 ch-usr2 dnsd-options dnsddb-options)
|
||||
|
||||
(define (wait-thread zone-refresh ch-wakeup dnsd-options)
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let ((refresh (* zone-refresh 1000)))
|
||||
(if (< refresh (dnsd-options-retry-interval dnsd-options))
|
||||
(sleep (dnsd-options-retry-interval dnsd-options))
|
||||
(sleep refresh))
|
||||
(sync (send-rv ch-wakeup #t))))))
|
||||
|
||||
(let* ((dnsd-options dnsd-options)
|
||||
(ch-wakeup (make-channel))
|
||||
(zone-name (dnsddb-options-name dnsddb-options))
|
||||
(type (dnsddb-options-type dnsddb-options))
|
||||
(primary? (or (string-ci=? type "master")
|
||||
(string-ci=? type "primary")))
|
||||
(class (dnsddb-options-class dnsddb-options)))
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let refresh-loop ()
|
||||
(let* ((soa-data (resource-record-data
|
||||
(db-get-zone-soa-rr zone-name class)))
|
||||
(zone-refresh (resource-record-data-soa-refresh soa-data))
|
||||
(retry-val (resource-record-data-soa-retry soa-data))
|
||||
(expire-val (resource-record-data-soa-expire soa-data)))
|
||||
;; Start thread for wakeup-channel:
|
||||
(wait-thread zone-refresh ch-wakeup dnsd-options)
|
||||
(let inner-loop ()
|
||||
(sync
|
||||
(choose
|
||||
;; Set new dnsd-options:
|
||||
(wrap (receive-rv ch-usr1)
|
||||
(lambda (new-dnsd-options)
|
||||
(set! dnsd-options new-dnsd-options)
|
||||
(inner-loop)))
|
||||
;; Terminate the thread if a reload is signaled:
|
||||
(wrap (receive-rv ch-usr2)
|
||||
(lambda (ignore) #t))
|
||||
;; Try a refresh:
|
||||
(wrap (receive-rv ch-wakeup)
|
||||
(lambda (ignore)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Reloading zone ~S"
|
||||
zone-name)
|
||||
;; Primary or secondary zone?
|
||||
(if (if primary?
|
||||
(not
|
||||
(dnsd-reload-zone dnsd-options dnsddb-options))
|
||||
(axfr-update (db-get-zone-soa-rr zone-name class)
|
||||
zone-name class dnsd-options
|
||||
dnsddb-options))
|
||||
;; Case the refresh didn't work:
|
||||
(if (< expire-val 0)
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Zone ~S expired. Deleting from db!"
|
||||
zone-name)
|
||||
(db-clear-zone zone-name class)
|
||||
(inner-loop)) ;; Wait for termination...
|
||||
(begin
|
||||
(set! expire-val (- expire-val retry-val))
|
||||
(wait-thread retry-val ch-wakeup dnsd-options)
|
||||
(set! retry-val (* 2 retry-val))
|
||||
(inner-loop)))
|
||||
(refresh-loop)))))))))))))
|
||||
|
||||
|
||||
;; Reload options from dnsd-options.scm:
|
||||
;; -------------------------------------
|
||||
|
||||
;; If an error occures (malformed file etc.) the old options are used as the
|
||||
;; return value.
|
||||
;; TYPE: dnsd-options -> dnsd-options
|
||||
(define (dnsd-reload-options dnsd-options)
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while reloading dnsd-options.scm")
|
||||
;(dnsd-log (syslog-level debug)"Above condition is: ~A" condition)
|
||||
dnsd-options)
|
||||
(lambda ()
|
||||
(let ((path (dnsd-options-dir dnsd-options)))
|
||||
(dnsd-log (syslog-level info)
|
||||
"Reloading dnsd-options.scm with path: ~S"
|
||||
path)
|
||||
(let* ((port (if (file-name-directory? path)
|
||||
(open-input-file (string-append path "dnsd-options.scm"))
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Bad path (~S) in dnsd-options. Trying ./dnsd-options.scm"
|
||||
path)
|
||||
(open-input-file "./dnsd-options.scm"))))
|
||||
(options? (read port)))
|
||||
(close-input-port port)
|
||||
(make-options-from-list options? dnsd-options))))))
|
||||
|
||||
|
||||
;; (Re)load zones from dnsd-zones.scm:
|
||||
;; -----------------------------------
|
||||
|
||||
;; Make a fake secondary zone for the management thread:
|
||||
;; TYPE: dnsddb-options -> list-of-rrs
|
||||
(define (make-sec-zone dnsddb-options)
|
||||
(list
|
||||
(dns-rr-soa (dnsddb-options-name dnsddb-options)
|
||||
(message-class in)
|
||||
0
|
||||
(list
|
||||
(dnsddb-options-master-name dnsddb-options)
|
||||
"unknown.mail-adress."
|
||||
0 ;; smallest serial possible
|
||||
5 ;; fast first fetch
|
||||
(* 60 10) ;; fast retry
|
||||
(* 60 60 24 7) ;; expires
|
||||
0)))) ;; min TTL
|
||||
|
||||
|
||||
;; Reload a zone...
|
||||
;; TYPE: zone x string x dnsd-options -> boolean
|
||||
(define (dnsd-reload-zone dnsd-options dnsddb-options)
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while reloading a zone.")
|
||||
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
|
||||
#f)
|
||||
(lambda ()
|
||||
(let* ((path (dnsd-options-dir dnsd-options))
|
||||
(file (dnsddb-options-file dnsddb-options))
|
||||
(zone-name (dnsddb-options-name dnsddb-options)))
|
||||
;; Handle secondary zones...
|
||||
(if (dnsddb-options-master-name dnsddb-options)
|
||||
(db-update-zone (make-sec-zone dnsddb-options))
|
||||
;; handle primary zones
|
||||
(and-let* ((zone-list (if (string-ci=?
|
||||
(dnsddb-options-filetype dnsddb-options)
|
||||
"rfc")
|
||||
(parse-mf file dnsd-options)
|
||||
(load (string-append path file))))
|
||||
(soa-zone-name (maybe-get-soa-rr-name zone-list)))
|
||||
(if (string-ci=? zone-name soa-zone-name)
|
||||
(db-update-zone zone-list)
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Zone names doesn't fit between file (%S) and dnsd-zones (%S)"
|
||||
soa-zone-name zone-name)
|
||||
(error " ")))))))))
|
||||
|
||||
|
||||
;; Initialize // reload the zones which are defined in dnsd-zones.scm
|
||||
;; TYPE: channel x channel x dnsd-options -> unspecific
|
||||
(define (dnsd-reload-dnsd-zones ch-usr1 ch-usr2 dnsd-options)
|
||||
(let ((usr1-channel-list '())
|
||||
(usr2-channel-list '())
|
||||
(dnsd-options dnsd-options))
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(sync
|
||||
(choose
|
||||
(wrap (receive-rv ch-usr1)
|
||||
(lambda (new-dnsd-options)
|
||||
(set! dnsd-options new-dnsd-options)
|
||||
(for-each (lambda (e) (sync (send-rv e new-dnsd-options)))
|
||||
usr1-channel-list)
|
||||
(loop)))
|
||||
(wrap
|
||||
(receive-rv ch-usr2)
|
||||
(lambda (ignore)
|
||||
;; Terminate all old management-threads:
|
||||
(for-each (lambda (e) (sync (send-rv e 'terminate)))
|
||||
usr2-channel-list)
|
||||
(set! usr1-channel-list '())
|
||||
(set! usr2-channel-list '())
|
||||
;; Clear database:
|
||||
(db-clear-database)
|
||||
(if (dnsd-options-use-db? dnsd-options)
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while reloading dnsd-zones.scm")
|
||||
#f)
|
||||
(lambda ()
|
||||
(let* ((path (dnsd-options-dir dnsd-options))
|
||||
(port (if (file-name-directory? path)
|
||||
(open-input-file
|
||||
(string-append path "dnsd-zones.scm"))
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Bad path (~S) in dnsd-zones. Trying ./dnsd-zones.scm"
|
||||
path)
|
||||
(open-input-file "./dnsd-zones.scm"))))
|
||||
(zone-l (read port)))
|
||||
(close-input-port port)
|
||||
(if (list? zone-l)
|
||||
(for-each
|
||||
(lambda (e)
|
||||
(let ((dnsddb-options (make-db-options-from-list e))
|
||||
(ch-usr1-thread (make-channel))
|
||||
(ch-usr2-thread (make-channel)))
|
||||
(if (dnsd-reload-zone dnsd-options dnsddb-options)
|
||||
(begin
|
||||
(dnsd-zone-mgt-thread ch-usr1-thread
|
||||
ch-usr2-thread
|
||||
dnsd-options
|
||||
dnsddb-options)
|
||||
(set! usr1-channel-list
|
||||
(cons ch-usr1-thread
|
||||
usr1-channel-list))
|
||||
(set! usr2-channel-list
|
||||
(cons ch-usr2-thread
|
||||
usr2-channel-list))))))
|
||||
zone-l)
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Bad sytax in dnsd-zones.scm.")
|
||||
#f)))))
|
||||
#f)
|
||||
(loop))))))))))
|
||||
|
||||
|
||||
;; Management of the datastructures (Cache / SLIST / Blacklist)
|
||||
;; ------------------------------------------------------------
|
||||
|
||||
;; Clean dnsd-cache/slist every now and then.
|
||||
;; TYPE: channel x dnsd-options -> unspecific
|
||||
(define (dnsd-management-thread ch-usr1 dnsd-options)
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let ((ch-wait (make-channel))
|
||||
(dnsd-options dnsd-options))
|
||||
(let loop ()
|
||||
(let ((time-in-sec (dnsd-options-cleanup-interval dnsd-options)))
|
||||
;; Starting this thread to wait on ch-wait:
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(sleep (* time-in-sec 1000))
|
||||
(sync (send-rv ch-wait 'whatever))))
|
||||
(sync
|
||||
(choose
|
||||
(wrap (receive-rv ch-wait)
|
||||
(lambda (ignore)
|
||||
(if (dnsd-options-use-cache? dnsd-options)
|
||||
(dnsd-cache-clean!))
|
||||
(dnsd-slist-clean!)
|
||||
;; deprecated (dnsd-blacklist-clean! dnsd-options)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Cleaned CACHE and SLIST. Current interval is ~D seconds."
|
||||
time-in-sec)
|
||||
#t))
|
||||
(wrap (receive-rv ch-usr1)
|
||||
(lambda (value) (set! dnsd-options value)))))
|
||||
(loop)))))))
|
||||
|
||||
|
||||
;; Pre- and post-processing of messages:
|
||||
;; -------------------------------------
|
||||
|
||||
(define (dnsd-pre message socket-addr dnsd-options)
|
||||
(dnsd-pre/post message socket-addr dnsd-options "dnsd-pre.scm"))
|
||||
|
||||
(define (dnsd-post message socket-addr dnsd-options)
|
||||
(dnsd-pre/post message socket-addr dnsd-options "dnsd-post.scm"))
|
||||
|
||||
;; Load the pre- and post-processing files...
|
||||
;; TYPE: msg x socket-addr x dnsd-options x string -> msg x dnsd-options
|
||||
(define (dnsd-pre/post message socket-addr dnsd-options file)
|
||||
(if (dnsd-options-use-pre/post dnsd-options)
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(values message dnsd-options))
|
||||
(lambda ()
|
||||
(let* ((dir (dnsd-options-dir dnsd-options))
|
||||
(path (if (file-name-directory? dir)
|
||||
(string-append dir file)
|
||||
(begin
|
||||
(dnsd-log (syslog-level info)
|
||||
"Bad dir (~S) in options. Trying ./~S"
|
||||
dir file)
|
||||
(string-append "./" file)))))
|
||||
((load path) message socket-addr dnsd-options))))
|
||||
(values message dnsd-options)))
|
||||
|
||||
|
||||
;; UDP thread:
|
||||
;; -----------
|
||||
|
||||
;; Starts the main UDP-loop:
|
||||
;; TYPE: socket x channel x dnsd-options -> unspecific
|
||||
(define (dnsd-server-loop-udp socket ch-usr1 dnsd-options)
|
||||
(let ((ch-receive (make-channel))
|
||||
(max-con (make-semaphore (dnsd-options-max-connections dnsd-options)))
|
||||
(dnsd-options dnsd-options))
|
||||
;; Thread for incoming UDP-messages:
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while processing a UDP-query.")
|
||||
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
|
||||
;(loop))
|
||||
decline)
|
||||
(lambda ()
|
||||
(semaphore-wait max-con)
|
||||
(receive
|
||||
(msg addr)
|
||||
(receive-message/partial socket 512)
|
||||
(sync (send-rv ch-receive (cons msg addr)))
|
||||
(loop)))))))
|
||||
;; Choose between user-interrupt or query-processing
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(sync
|
||||
(choose
|
||||
(wrap (receive-rv ch-receive)
|
||||
(lambda (value)
|
||||
(udp-processing-thread (car value) (cdr value)
|
||||
socket max-con dnsd-options)))
|
||||
(wrap (receive-rv ch-usr1)
|
||||
(lambda (value)
|
||||
(set! dnsd-options value)
|
||||
(set-semaphore! max-con (dnsd-options-max-connections
|
||||
dnsd-options))))))
|
||||
(loop))))))
|
||||
|
||||
|
||||
;; Start the thread for processing a UDP-query.
|
||||
;; TYPE: message x address x socket x dnsd-options -> unspecific
|
||||
(define (udp-processing-thread msg addr socket max-con dnsd-options)
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while processing a UDP-query.")
|
||||
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
|
||||
(semaphore-post max-con)
|
||||
;#f)
|
||||
decline)
|
||||
(lambda ()
|
||||
(let ((msg (parse (string->list msg))))
|
||||
(if (not msg)(error "Couldn't parse the message."))
|
||||
;; Preprocess the message...
|
||||
(receive
|
||||
(msg dnsd-options)
|
||||
(dnsd-pre msg addr dnsd-options)
|
||||
(if (not msg) (semaphore-post max-con)
|
||||
(let* ((msg-header (message-header msg))
|
||||
(msg-flags (header-flags msg-header))
|
||||
(msg-trunc? (flags-truncated? msg-flags)))
|
||||
(if msg-trunc? (error "Couldn't process truncated query."))
|
||||
(let ((reply (lookup-query msg dnsd-options)))
|
||||
(if (not reply) (error "Lookup produced no reply."))
|
||||
;; Postprocessing the message:
|
||||
(receive
|
||||
(reply dnsd-options)
|
||||
(dnsd-post reply addr dnsd-options)
|
||||
(if (not reply) (semaphore-post max-con)
|
||||
(let* ((octet-list (mc-message->octets reply))
|
||||
(l (length octet-list)))
|
||||
(if (> l 512) ; Use message-truncation?
|
||||
(let* ((msg (octet-msg-change-truncation
|
||||
octet-list #t))
|
||||
(to-send (list->string (take msg 512))))
|
||||
(receive
|
||||
(host-addr port)
|
||||
(socket-address->internet-address addr)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Sending truncated UDP-response to: ~A"
|
||||
(address32->ip-string host-addr))
|
||||
(send-message socket to-send 0 511 0 addr)))
|
||||
(begin
|
||||
(send-message socket (list->string octet-list)
|
||||
0 l 0
|
||||
addr)))
|
||||
(semaphore-post max-con))))))))))))))
|
||||
|
||||
|
||||
|
||||
;; TCP thread:
|
||||
;; -----------
|
||||
|
||||
;; Main TCP-loop:
|
||||
;; TYPE: socket x channel x dnsd-options -> unspecific
|
||||
(define (dnsd-server-loop-tcp socket ch-usr1 dnsd-options)
|
||||
(let ((ch-receive (make-channel))
|
||||
(max-con (make-semaphore (dnsd-options-max-connections dnsd-options)))
|
||||
(dnsd-options dnsd-options))
|
||||
;; Thread for incoming TCP-messages:
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while processing a TCP-query.")
|
||||
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
|
||||
(loop))
|
||||
;decline)
|
||||
(lambda ()
|
||||
(semaphore-wait max-con)
|
||||
(receive
|
||||
(private-socket addr)
|
||||
(accept-connection socket)
|
||||
(sync (send-rv ch-receive (cons private-socket addr)))
|
||||
(loop)))))))
|
||||
;; Choose between user-interrupt or query-processing
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(let loop ()
|
||||
(sync
|
||||
(choose
|
||||
(wrap (receive-rv ch-receive)
|
||||
(lambda (value)
|
||||
(tcp-processing-thread (car value) (cdr value)
|
||||
max-con dnsd-options)))
|
||||
(wrap (receive-rv ch-usr1)
|
||||
(lambda (value)
|
||||
(set! dnsd-options value)
|
||||
(set-semaphore! max-con (dnsd-options-max-connections
|
||||
dnsd-options))))))
|
||||
(loop))))))
|
||||
|
||||
|
||||
;; Start the thread for processing a TCP-query:
|
||||
;; TYPE: address x socket x dnsd-options -> unspecific
|
||||
(define (tcp-processing-thread socket addr max-con dnsd-options)
|
||||
(fork-thread
|
||||
(lambda ()
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Error while processing a TCP-query.")
|
||||
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
|
||||
(semaphore-post max-con)
|
||||
(close-socket socket) #f)
|
||||
(lambda ()
|
||||
(let* ((inport (socket:inport socket))
|
||||
(outport (socket:outport socket))
|
||||
;; A tcp-message has a 2-octet-length size tag:
|
||||
(front (read-char inport))
|
||||
(rear (read-char inport))
|
||||
(size-tag (octet-pair->number front rear))
|
||||
(octet-msg (read-string size-tag inport))
|
||||
(msg (parse (string->list octet-msg))))
|
||||
(if (not msg)(error "Couldn't parse the message"))
|
||||
;; Preprocessing:
|
||||
(receive
|
||||
(msg dnsd-options)
|
||||
(dnsd-pre msg addr dnsd-options)
|
||||
(if (not msg)
|
||||
(begin
|
||||
(semaphore-post max-con)
|
||||
(close-socket socket))
|
||||
(let* ((msg-header (message-header msg))
|
||||
(msg-flags (header-flags msg-header))
|
||||
(msg-trunc? (flags-truncated? msg-flags)))
|
||||
(if msg-trunc? (error "Couldn't process truncated query."))
|
||||
(let ((reply (lookup-query msg dnsd-options)))
|
||||
(if (not reply) (error "Lookup produced no reply."))
|
||||
;; Postprocessing:
|
||||
(receive
|
||||
(reply dnsd-options)
|
||||
(dnsd-post reply addr dnsd-options)
|
||||
(if (not reply)
|
||||
(begin
|
||||
(semaphore-post max-con)
|
||||
(close-socket socket))
|
||||
(let* ((reply (mc-message->octets reply))
|
||||
(l (number->octet-pair (length reply))))
|
||||
(write-string (list->string (append l reply)) outport)
|
||||
(semaphore-post max-con)
|
||||
(close-socket socket))))))))))))))
|
||||
|
||||
|
||||
;; Initialize and start UDP and TCP threads:
|
||||
;; TYPE: dnsd-options -> unspecific
|
||||
(define (init-dnsd dnsd-options)
|
||||
(let ((ch-usr1-udp (make-channel))
|
||||
(ch-usr1-tcp (make-channel))
|
||||
(ch-usr1-mgt (make-channel))
|
||||
(ch-usr1-zones (make-channel))
|
||||
(ch-usr2-zones (make-channel))
|
||||
(dnsd-options dnsd-options))
|
||||
(call-with-current-continuation
|
||||
(lambda (escape)
|
||||
;; Maybe load the options from file:
|
||||
(set! dnsd-options (dnsd-reload-options dnsd-options))
|
||||
;; Initializing signal-handler(s)
|
||||
;; * USR1 (reload dnsd-options.scm)
|
||||
;; Log debug-level in syslog?
|
||||
(with-syslog-destination
|
||||
(string-append "dnsd (" (number->string (pid)) ")")
|
||||
#f
|
||||
#f
|
||||
(if (dnsd-options-debug-mode dnsd-options)
|
||||
(syslog-mask-upto (syslog-level info))
|
||||
#f)
|
||||
(lambda ()
|
||||
(set-interrupt-handler
|
||||
interrupt/usr1
|
||||
(lambda (ignore)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Interrupt/USR1: Reloading options.")
|
||||
(set! dnsd-options (dnsd-reload-options dnsd-options))
|
||||
(fork-thread
|
||||
(lambda () (sync (send-rv ch-usr1-udp dnsd-options))))
|
||||
(fork-thread
|
||||
(lambda () (sync (send-rv ch-usr1-tcp dnsd-options))))
|
||||
(fork-thread
|
||||
(lambda () (sync (send-rv ch-usr1-mgt dnsd-options))))
|
||||
(fork-thread
|
||||
(lambda () (sync (send-rv ch-usr1-zones dnsd-options))))))
|
||||
;; * USR2 (reload dnsd-zones.scm)
|
||||
(set-interrupt-handler
|
||||
interrupt/usr2
|
||||
(lambda (ignore)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Interrupt/USR2: Reloading zones.")
|
||||
(sync (send-rv ch-usr2-zones 'ignore))))
|
||||
;; Initializing cleanup thread:
|
||||
(dnsd-management-thread ch-usr1-mgt dnsd-options)
|
||||
;; Initialize & load the database:
|
||||
(dnsd-reload-dnsd-zones ch-usr1-zones ch-usr2-zones dnsd-options)
|
||||
(sync (send-rv ch-usr2-zones 'ignore))
|
||||
;; Initializing tcp/upd sockets & start thread:
|
||||
(let* ((the-port (dnsd-options-port dnsd-options))
|
||||
(udp-socket (create-socket protocol-family/internet
|
||||
socket-type/datagram))
|
||||
(tcp-socket (create-socket protocol-family/internet
|
||||
socket-type/stream))
|
||||
(socket-addr (internet-address->socket-address
|
||||
internet-address/any the-port)))
|
||||
(with-fatal-error-handler*
|
||||
(lambda (condition decline)
|
||||
(dnsd-log (syslog-level info)
|
||||
"Coudn't start dnsd. Port ~D is already in use."
|
||||
the-port)
|
||||
(close-socket udp-socket)
|
||||
(close-socket tcp-socket)
|
||||
(escape 'douh!))
|
||||
(lambda ()
|
||||
(dnsd-log (syslog-level info)
|
||||
"Starting the service on port: ~D"
|
||||
the-port)
|
||||
(bind-socket udp-socket socket-addr)
|
||||
(bind-socket tcp-socket socket-addr)
|
||||
(listen-socket tcp-socket 10))) ; TODO: How big should the queue be?
|
||||
;; Start the UDP-Loop:
|
||||
(fork-thread (lambda () (dnsd-server-loop-udp udp-socket ch-usr1-udp
|
||||
dnsd-options)))
|
||||
;; Start the TCP-Loop:
|
||||
(fork-thread (lambda () (dnsd-server-loop-tcp tcp-socket ch-usr1-tcp
|
||||
dnsd-options))))))))))
|
||||
|
||||
;; Entry-Point for run-dnsd
|
||||
;; ------------------------
|
||||
|
||||
(define (dnsd-start . dir)
|
||||
(with-syslog-destination
|
||||
(string-append "dnsd (" (number->string (pid)) ")") #f #f #f
|
||||
(lambda ()
|
||||
(if (null? dir)
|
||||
(init-dnsd (make-default-dnsd-options))
|
||||
(init-dnsd (with-dir
|
||||
(file-name-as-directory (car dir))
|
||||
(make-default-dnsd-options)))))))
|
||||
|
Loading…
Reference in New Issue