sunet/scheme/lib/dns.scm

1526 lines
50 KiB
Scheme
Raw Normal View History

2002-06-08 11:07:01 -04:00
;
; dns.scm
;
; Implementation of the RFC1035
2002-08-27 05:03:22 -04:00
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 2002 by Marcus Crestani.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
2002-06-08 11:07:01 -04:00
; domain names - implementation and specification
; based on the PLT-implementation.
;
;
; TODO:
; - test, test, test
; - types from newer RFCs (41, unknown)
; - more documentation
;
2002-06-25 05:11:45 -04:00
; ---
; sample usage & documentation:
2002-06-08 11:07:01 -04:00
;
; <ip-address32> is a 32bit integer internet->address, shortly address32.
; <ip-string> is a string in standard dot notation "xxx.xxx.xxx.xxx".
; <name> is a string
2002-06-08 11:07:01 -04:00
;
; <nameserver> can either be a domainname, an ip-string or an ip-address32.
2002-06-25 05:11:45 -04:00
; if it is a domainname, its ip is looked up on a nameserver listed in
; /etc/resolv.conf.
;
; (dns-find-nameserver) --> <ip-address32>
2002-06-25 05:11:45 -04:00
; this parses the /etc/resolv.conf file and returns the first found
; nameserver in address32 format.
2002-06-25 05:11:45 -04:00
;
;
;
; (dns-lookup-name <name> [nameserver]) --> <ip-address32>
; (dns-lookup-ip <ip-string | ip-address32> [nameserver]) --> <name>
2002-06-25 05:11:45 -04:00
; (dns-lookup-nameserver <name> [nameserver])
; --> <list of ip-address32s of authoritative nameservers>
2002-06-25 05:11:45 -04:00
; (dns-lookup-mail-exchanger <name> [nameserver])
; --> <list of names of mail-exchangers>
2002-06-25 05:11:45 -04:00
;
; dns-lookup-name, dns-lookup-ip, dns-lookup-nameserver and
; dns-lookup-mail-exchanger are "simple lookup functions",
; they return the wanted information or #f.
; dns-lookup-ip can either be given an ip-string or an ip-address32.
2002-06-08 11:07:01 -04:00
;
; concurrent dns lookup:
2002-07-23 04:48:47 -04:00
; if a list of nameservers is given to the optional <nameserver> argument,
; a concurrent lookup to all nameservers in this list is started.
; The nameservers in this list could either be ip-strings or ip-address32s.
; example: (dns-lookup-name "www.uni-tuebingen.de" (dns-find-nameserver-list))
; starts an concurrent lookup which contacts all nameservers in
; /etc/resolv.conf.
2002-06-25 05:11:45 -04:00
;
;
; (dns-lookup <name | ip-string | ip-address32> <type> [nameserver])
; --> <dns-message>
2002-06-08 11:07:01 -04:00
; (show-dns-message <dns-message) --> the whole message, human readable
;
2002-06-25 05:11:45 -04:00
; a <dns-message> is a record, with several entries, which holds the whole
; query/response dialog. the simplest way to get detailed information about
; the record structure is to view the result of show-dns-message.
;
; dns-lookup returns much more information than the simple lookup functions,
; only useful in very special cases.
;
;
2002-06-08 11:07:01 -04:00
; some lookups return a hostname (e.g. mx).
; many applications need instead of a hostname a ip address.
; force-ip and force-ip-list guarantee that a ip address is
; returned.
;
; (force-ip <name>) --> <ip>
2002-06-08 11:07:01 -04:00
; (force-ip-list <list of names>) --> <list of ips>
;
;
; useful converters:
;
; (address32->ip-string <ip-address32>) -> <ip-string>
; (ip-string->address32 <ip-string>) -> <ip-address32>
2002-06-08 11:07:01 -04:00
;; --- error conditions
2002-09-16 12:16:04 -04:00
;; supertype of all errors signaled by this library
(define-condition-type 'dns-error '(error))
(define dns-error? (condition-predicate 'dns-error))
(define-condition-type 'invalid-type '(dns-error))
2002-06-08 11:07:01 -04:00
(define invalid-type? (condition-predicate 'invalid-type))
2002-09-16 12:16:04 -04:00
(define-condition-type 'invalid-class '(dns-error))
2002-06-08 11:07:01 -04:00
(define invalid-class? (condition-predicate 'invalid-class))
2002-09-16 12:16:04 -04:00
(define-condition-type 'parse-error '(dns-error))
2002-06-08 11:07:01 -04:00
(define parse-error? (condition-predicate 'parse))
2002-09-16 12:16:04 -04:00
(define-condition-type 'unexpected-eof-from-server '(dns-error))
2002-06-08 11:07:01 -04:00
(define unexpected-eof-from-server? (condition-predicate 'unexpected-eof-from-server))
2002-09-16 12:16:04 -04:00
(define-condition-type 'bad-address '(dns-error))
2002-06-08 11:07:01 -04:00
(define bad-address? (condition-predicate 'bad-address))
2002-09-16 12:16:04 -04:00
(define-condition-type 'no-nameservers '(dns-error))
2002-06-08 11:07:01 -04:00
(define no-nameservers? (condition-predicate 'no-nameservers))
(define-condition-type 'bad-nameserver '(dns-error))
(define bad-nameserver? (condition-predicate 'bad-nameserver))
2002-09-16 12:16:04 -04:00
(define-condition-type 'not-a-hostname '(dns-error))
2002-06-08 11:07:01 -04:00
(define not-a-hostname? (condition-predicate 'not-a-hostname))
2002-09-16 12:16:04 -04:00
(define-condition-type 'not-a-ip '(dns-error))
2002-06-08 11:07:01 -04:00
(define not-a-ip? (condition-predicate 'not-a-ip))
2002-09-16 12:16:04 -04:00
;; supertype of all errors signaled if the dns server returned a non-sero
;; reply code
(define-condition-type 'dns-server-error '(dns-error))
(define dns-server-error? (condition-predicate 'dns-server-error))
2002-06-08 11:07:01 -04:00
2002-09-16 12:16:04 -04:00
(define-condition-type 'dns-format-error '(dns-server-error))
2002-06-08 11:07:01 -04:00
(define dns-format-error? (condition-predicate 'dns-format-error))
2002-09-16 12:16:04 -04:00
(define-condition-type 'dns-server-failure '(dns-server-error))
2002-06-08 11:07:01 -04:00
(define dns-server-failure? (condition-predicate 'dns-server-failure))
2002-09-16 12:16:04 -04:00
(define-condition-type 'dns-name-error '(dns-server-error))
2002-06-08 11:07:01 -04:00
(define dns-name-error? (condition-predicate 'dns-name-error))
2002-09-16 12:16:04 -04:00
(define-condition-type 'dns-not-implemented '(dns-server-error))
2002-06-08 11:07:01 -04:00
(define dns-not-implemented? (condition-predicate 'dns-not-implemented))
2002-09-16 12:16:04 -04:00
(define-condition-type 'dns-refused '(dns-server-error))
2002-06-08 11:07:01 -04:00
(define dns-refused? (condition-predicate 'dns-refused))
(define (dns-error condition . stuff)
(apply signal condition (dns-error->string condition) stuff))
(define (dns-error->string condition)
(string-append
"dns-error: "
(case condition
((invalid-type)
"make-octet-question: invalid DNS query type")
((invalid-class)
"make-octet-question: invalid DNS query class")
((parse-error)
"parse: error parsing server message")
((unexpected-eof-from-server)
"send-receive-message: unexpected EOF from server")
((bad-address)
"dns-get-information: bad address (in combination with query type)")
((no-nameservers)
"dns-find-nameserver: no nameservers found in /etc/resolv.conf")
((bad-nameserver)
"send-receive-message: nameserver refused connection")
((not-a-hostname)
"no hostname given")
((not-a-ip)
"no ip given")
((dns-format-error)
"error from server: (1) format error")
((dns-server-failure)
"error from server: (2) server failure")
((dns-name-error)
"error from server: (3) name error")
((dns-not-implemented)
"error from server: (4) not implemented")
((dns-refused)
"error from server: (5) refused")
(else (error "Unknown dns-error" condition)))))
2002-06-08 11:07:01 -04:00
;;; -- globals and types
;; off
(define *nul* (ascii->char 0))
;; on
(define *on* (ascii->char 1))
;; message types
(define types
'((unknown 0); types, which are not yet implemented
(a 1) ; a host address
(ns 2) ; an authoritative name server
(md 3) ; (obsolete)
(mf 4) ; (obsolete)
(cname 5) ; the canonical name for an alias
(soa 6) ; marks the start of a zone of authority
(mb 7) ; (experimental)
(mg 8) ; (experimental)
(mr 9) ; (experimental)
(null 10) ; (experimental)
(wks 11) ; a well known service description
(ptr 12) ; a domain name pointer
(hinfo 13) ; host information
(minfo 14) ; (experimental)
(mx 15) ; mail exchange
(txt 16))) ; text strings
;; message classes
(define classes
'((in 1) ; the Internet
(cs 2) ; (obsolete)
(ch 3) ; the CHAOS class
(hs 4))) ; Hesoid
;;; -- useful stuff
;; assoc the other way round
(define (cossa i l)
(cond
((null? l) 'unknown)
((equal? (cadar l) i)
(car l))
(else (cossa i (cdr l)))))
;; number: 0<= x < 256
;; octet-pair: (char char)
;; octet-quad: (char char char char)
;; name: string *{"." string}
;; octets: *{(char *char)} nullchar
;; octet-ip: (char char char char)
;; address32: 0 <= x < 2^32-1
;; ip-string: "www.xxx.yyy.zzz"
;; ip-string-arpa: "zzz.yyy.xxx.www.in-addr.arpa"
2002-06-08 11:07:01 -04:00
;; encodes numbers (16bit) to octets
(define (number->octet-pair n)
(list (ascii->char (arithmetic-shift n -8))
(ascii->char (modulo n 256))))
;; decodes octets to numbers (16bit)
(define (octet-pair->number a b)
(+ (arithmetic-shift (char->ascii a) 8)
(char->ascii b)))
;; encodes numbers (32bit) to octets, needed for ttl
(define (number->octet-quad n)
(list (ascii->char (arithmetic-shift n -24))
(ascii->char (modulo (arithmetic-shift n -16) 256))
(ascii->char (modulo (arithmetic-shift n -8) 256))
(ascii->char (modulo n 256))))
;; decodes octets to numbers, needed for 32bit ttl
(define (octet-quad->number a b c d)
(+ (arithmetic-shift (char->ascii a) 24)
(arithmetic-shift (char->ascii b) 16)
(arithmetic-shift (char->ascii c) 8)
(char->ascii d)))
;; encodes a domain-name string to octets
(define (name->octets s)
(define (encode-portion s)
(cons
(ascii->char (string-length s))
(string->list s)))
(let loop ((s s))
(cond
((regexp-search (rx (: bos (submatch (* (~ "."))) "." (submatch (* any))))
s)
2002-06-08 11:07:01 -04:00
=> (lambda (match)
(append
(encode-portion (match:substring match 1))
(loop (match:substring match 2)))))
(else
(if (= 0 (string-length s))
(list *nul*)
;;; TODO isn't this case an error?
2002-06-08 11:07:01 -04:00
(append
(encode-portion s)
(list *nul*)))))))
2002-06-08 11:07:01 -04:00
;; for tcp: message has to be tagged with its length
(define (add-size-tag m)
(append (number->octet-pair (length m)) m))
;; converts an octeted-ip to a 32bit integer internet-address
(define (octet-ip->address32 ip)
(+ (arithmetic-shift (char->ascii (list-ref ip 0)) 24)
(arithmetic-shift (char->ascii (list-ref ip 1)) 16)
(arithmetic-shift (char->ascii (list-ref ip 2)) 8)
(char->ascii (list-ref ip 3))))
;; converts a 32 bit integer internet-address to an octeted-ip
(define (address32->octet-ip ip)
(list (arithmetic-shift ip -24)
(modulo (arithmetic-shift ip -16) 256)
(modulo (arithmetic-shift ip -8) 256)
(modulo ip 256)))
;; converts an ip-string to an 32bit integer internet-address
(define (ip-string->address32 ip)
(octet-ip->address32 (ip-string->octet-ip ip)))
;; converts an ip-string to an 32bit integer internet-address
(define (address32->ip-string ip)
(format #f
"~a.~a.~a.~a"
(arithmetic-shift ip -24)
(modulo (arithmetic-shift ip -16) 256)
(modulo (arithmetic-shift ip -8) 256)
(modulo ip 256)))
2002-06-08 11:07:01 -04:00
;; converts an octeted-ip to an human readable ip-string
(define (octet-ip->ip-string s)
2002-06-08 11:07:01 -04:00
(format #f
"~a.~a.~a.~a"
(char->ascii (list-ref s 0))
(char->ascii (list-ref s 1))
(char->ascii (list-ref s 2))
(char->ascii (list-ref s 3))))
(define ip-string-regexp (rx (: bos
(submatch (** 1 3 digit)) "."
(submatch (** 1 3 digit)) "."
(submatch (** 1 3 digit)) "."
(submatch (** 1 3 digit))
eos)))
2002-06-08 11:07:01 -04:00
;; converts an ip-string to octets
(define (ip-string->octet-ip s)
(cond
((regexp-search ip-string-regexp s)
=> (lambda (match)
(list
(ascii->char (string->number (match:substring match 1)))
(ascii->char (string->number (match:substring match 2)))
(ascii->char (string->number (match:substring match 3)))
(ascii->char (string->number (match:substring match 4))))))
(else
(error "invalid ip-string" s))))
2002-06-08 11:07:01 -04:00
;; calculates a "random" number, needed for message-ids
;; TODO use SRFI-27
2002-06-08 11:07:01 -04:00
(define random
(let ((crank (make-random (modulo (time) (- (expt 2 27) 1)))))
(lambda (limit)
(quotient (* (modulo (crank) 314159265)
limit)
314159265))))
;; checks if a string is a ip
(define (ip-string? s)
(define (byte-as-string? string)
(let ((number (string->number string)))
(and number
(>= number 0)
(< number 256))))
(cond
((regexp-search ip-string-regexp s)
=> (lambda (match)
(and (byte-as-string? (match:substring match 1))
(byte-as-string? (match:substring match 2))
(byte-as-string? (match:substring match 3))
(byte-as-string? (match:substring match 4)))))
(else #f)))
;; checks if v is a address32
(define (address32? v)
(and (number? v)
(<= 0 v #xffffffff)))
2002-06-08 11:07:01 -04:00
;; returns a in-addr.arpa name-string or #f (needed to resolve hostname by ip)
(define (ip-string->in-addr-arpa s)
(cond
((regexp-search ip-string-regexp s)
=> (lambda (match)
(string-append
(match:substring match 4) "."
(match:substring match 3) "."
(match:substring match 2) "."
(match:substring match 1) "."
"in-addr.arpa")))
(else #f)))
2002-06-08 11:07:01 -04:00
;; filters types in a list of rrs
(define (filter-type list type)
(filter (lambda (rr)
(eq? (rr-type rr) type))
2002-06-08 11:07:01 -04:00
list))
;; sorts a mx-rr-list by preference. needed for dns-lookup-mail-exchanger.
(define (sort-by-preference mx-list)
(sort-list mx-list
(lambda (a b)
(< (rr-data-mx-preference (rr-data a)) (rr-data-mx-preference (rr-data b))))))
2002-06-08 11:07:01 -04:00
;; returns a IP if available (additonal type-a processing)
(define (force-ip name)
(let loop ((result (dns-lookup-name name)))
(if (ip-string? result)
2002-06-08 11:07:01 -04:00
result
(loop (dns-lookup-name result)))))
;; returns a list of IPs (additional type-a processing)
(define (force-ip-list names)
(map (lambda (elem) (force-ip elem)) names))
;;; -- message constructors: encode to octet-messages
;; makes an message header
(define (make-octet-header id qr opcode aa tc rd ra z rcode qdcount ancount nscount arcount)
(let* ((header-id (number->octet-pair id))
(header-flags (list
(ascii->char (+ (arithmetic-shift qr 7)
(arithmetic-shift opcode 3)
(arithmetic-shift aa 2)
(arithmetic-shift tc 1)
rd))
(ascii->char (+ (arithmetic-shift ra 7)
(arithmetic-shift z 4)
rcode))))
(header-qdcount (number->octet-pair qdcount))
(header-ancount (number->octet-pair ancount))
(header-nscount (number->octet-pair nscount))
(header-arcount (number->octet-pair arcount)))
(append header-id
header-flags
header-qdcount
header-ancount
header-nscount
header-arcount)))
;; a standard query header, usefull for most queries
(define (make-std-octet-query-header id question-count)
(let* ((qr 0) ; querytype: query 0, response 1
(opcode 0) ; opcode: query 0, iquery 1 (OBSOLETE), status 2
(aa 0) ; authorative answer (in answers only)
(tc 0) ; truncation (size matters only with UDP)
(rd 1) ; recursion desired: nameserver pursues the query recursivly (optional)
(ra 0) ; recursion available (in answers only)
(z 0) ; future use
(rcode 0) ; response code: error conditions (in answers only)
(qdcount question-count)
(ancount 0) ; answer count (in answers only)
(nscount 0) ; name server resources (in answers only)
(arcount 0)) ; additional records (in answers only)
(make-octet-header id qr opcode aa tc rd ra z rcode qdcount ancount nscount arcount)))
;; makes a question (name, type, class)
(define (make-octet-question name type class)
(if (not (assoc type types))
(dns-error 'invalid-type))
2002-06-08 11:07:01 -04:00
(if (not (assoc class classes))
(dns-error 'invalid-class))
2002-06-08 11:07:01 -04:00
(let* ((qname (name->octets name))
(qtype (number->octet-pair (cadr (assoc type types))))
(qclass (number->octet-pair (cadr (assoc class classes)))))
(append qname qtype qclass)))
;; makes a query-message (header and question only)
(define (make-octet-query-message id name type class)
(append
(make-std-octet-query-header id 1)
(make-octet-question name type class)))
;; makes a resource record for ans, nss, ars (name, type, class, ttl, data)
(define (make-octet-rr name type class ttl rdata)
(let* ((name (name->octets name))
(type (number->octet-pair (cadr (assoc type types))))
(class (number->octet-pair (cadr (assoc class classes))))
(ttl (number->octet-quad ttl))
(rdlength (number->octet-pair (length rdata)))
(rdata rdata))
(append name type class ttl rdlength rdata)))
;;; -- parsed message records
;;; -- dns-message: complete data-structure of an dns-lookup
(define-record-type dns-message :dns-message
(make-dns-message query reply cache? protocol tried-nameservers)
dns-message?
(query dns-message-query)
(reply dns-message-reply)
(cache? dns-message-cache?)
(protocol dns-message-protocol)
(tried-nameservers dns-message-tried-nameservers))
2002-06-08 11:07:01 -04:00
;; message
(define-record-type message :message
(make-message header questions answers nameservers additionals source)
message?
(header message-header)
(questions message-questions)
(answers message-answers)
(nameservers message-nameservers)
(additionals message-additionals)
(source message-source))
2002-06-08 11:07:01 -04:00
;; header
(define-record-type header :header
(make-header id flags qdc anc nsc arc)
header?
(id header-id)
(flags header-flags)
(qdc header-qdc)
(anc header-anc)
(nsc header-nsc)
(arc header-arc))
2002-06-08 11:07:01 -04:00
;; flags
(define-record-type flags :flags
(make-flags querytype opcode auth trunc recursiondesired recursionavailable
z rcode)
flags?
(querytype flags-querytype)
(opcode flags-opcode)
(auth flags-auth)
(trunc flags-trunc)
(recursiondesired flags-recursiondesired)
(recursionavailable flags-recursionavailable)
(z flags-z)
(rcode flags-rcode))
2002-06-08 11:07:01 -04:00
;; question
(define-record-type question :question
(make-question name type class)
question?
(name question-name)
(type question-type)
(class question-class))
;;type rr
(define-record-type rr :rr
(make-rr name type class ttl data)
rr?
(name rr-name)
(type rr-type)
(class rr-class)
(ttl rr-ttl)
(data rr-data))
2002-06-08 11:07:01 -04:00
;; cache
(define-record-type cache :cache
(make-cache answer ttl time)
cache?
(answer cache-answer)
(ttl cache-ttl)
(time cache-time))
2002-06-08 11:07:01 -04:00
;;; -- message parser
;; parses a domain-name in an message. returns the name and the rest of the message.
(define (parse-name start message)
(let ((v (char->ascii (car start))))
(cond
((zero? v)
;; End of name
(values #f (cdr start)))
((zero? (bitwise-and #xc0 v))
;; Normal label
(let loop ((len v)
(start (cdr start))
(accum '()))
(cond
((zero? len)
(call-with-values
(lambda () (parse-name start message))
(lambda (s start)
(let ((s0 (list->string (reverse! accum))))
(values (if s
(string-append s0 "." s)
s0)
start)))))
(else (loop (- len 1)
(cdr start)
(cons (car start) accum))))))
(else
;; Compression offset
(let ((offset (+ (arithmetic-shift (bitwise-and #x3f v) 8)
(char->ascii (cadr start)))))
(call-with-values
(lambda () (parse-name (list-tail message offset) message))
(lambda (s ignore-start)
(values s (cddr start)))))))))
;; parses a question in a message. returns the question and the rest of the message.
(define (parse-question start message)
(call-with-values
(lambda () (parse-name start message))
(lambda (name start)
(let ((type (car (cossa (octet-pair->number (car start) (cadr start)) types)))
(start (cddr start)))
(let ((class (car (cossa (octet-pair->number (car start) (cadr start)) classes)))
(start (cddr start)))
(values (make-question name type class) start))))))
;; parses a resourcerecord in a message. returns the rr and the rest of the message.
(define (parse-rr start message)
(call-with-values
(lambda () (parse-name start message))
(lambda (name start)
(let ((type (car (cossa (octet-pair->number (car start) (cadr start)) types)))
(start (cddr start)))
(let ((class (car (cossa (octet-pair->number (car start) (cadr start)) classes)))
(start (cddr start)))
(let ((ttl (octet-quad->number (car start) (cadr start)
(caddr start) (cadddr start)))
(start (cddddr start)))
(let ((len (octet-pair->number (car start) (cadr start)))
(start (cddr start)))
;; Extract next len bytes of data:
(let loop ((len len)
(start start)
(accum '()))
(if (zero? len)
(values (make-rr name type class ttl (parse-rr-data type class (reverse! accum) message)) start)
(loop (- len 1)
(cdr start)
(cons (car start) accum)))))))))))
;;; -- rr-data-type records
(define-record-type rr-data-a :rr-data-a
(make-rr-data-a ip)
rr-data-a?
(ip rr-data-a-ip))
2002-06-08 11:07:01 -04:00
(define-record-type rr-data-ns :rr-data-ns
(make-rr-data-ns name)
rr-data-ns?
(name rr-data-ns-name))
2002-06-08 11:07:01 -04:00
(define-record-type rr-data-cname :rr-data-cname
(make-rr-data-cname name)
rr-data-cname?
(name rr-data-cname-name))
2002-06-08 11:07:01 -04:00
;; ###
;; hinfo not correctly implemented, trying to find examples
(define-record-type rr-data-hinfo :rr-data-hinfo
(make-rr-data-hinfo data)
rr-data-hinfo?
(data rr-data-hinfo-data))
(define-record-type rr-data-mx :rr-data-mx
(make-rr-data-mx preference exchanger)
rr-data-mx?
(preference rr-data-mx-preference)
(exchanger rr-data-mx-exchanger))
(define-record-type rr-data-ptr :rr-data-ptr
(make-rr-data-ptr name)
rr-data-ptr?
(name rr-data-ptr-name))
(define-record-type rr-data-soa :rr-data-soa
(make-rr-data-soa mname rname serial refresh retry expire minimum)
rr-data-soa?
(mname rr-data-soa-mname)
(rname rr-data-soa-rname)
(serial rr-data-soa-serial)
(refresh rr-data-soa-refresh)
(retry rr-data-soa-retry)
(expire rr-data-soa-expire)
(minimum rr-data-soa-minimum))
2002-06-08 11:07:01 -04:00
;; ### same as hinfo
(define-record-type rr-data-txt :rr-data-txt
(make-rr-data-txt text)
rr-data-txt?
(text rr-data-txt-text))
2002-06-08 11:07:01 -04:00
;; ### same as hinfo and txt
(define-record-type rr-data-wks :rr-data-wks
(make-rr-data-wks data)
rr-data-wks?
(data rr-data-wks-data))
2002-06-08 11:07:01 -04:00
;;
(define (parse-rr-data type class data message)
(cond
((eq? type 'a)
(make-rr-data-a (octet-ip->address32 data)))
2002-06-08 11:07:01 -04:00
((eq? type 'ns)
(make-rr-data-ns (call-with-values
(lambda () (parse-name data message))
(lambda (name rest) name))))
((eq? type 'cname)
(make-rr-data-cname (call-with-values
(lambda () (parse-name data message))
(lambda (name rest) name))))
((eq? type 'mx)
(make-rr-data-mx (octet-pair->number (car data) (cadr data))
(call-with-values
(lambda ()(parse-name (cddr data) message))
(lambda (name rest) name))))
((eq? type 'ptr)
(make-rr-data-ptr (call-with-values
(lambda () (parse-name data message))
(lambda (name rest) name))))
((eq? type 'soa)
(call-with-values
(lambda () (parse-name data message))
(lambda (mname rest)
(call-with-values
(lambda () (parse-name rest message))
(lambda (rname rest)
(let ((serial (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
(rest (cddddr rest)))
(let ((refresh (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
(rest (cddddr rest)))
(let ((retry (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
(rest (cddddr rest)))
(let ((expire (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
(rest (cddddr rest)))
(let ((minimum (octet-quad->number (car rest) (cadr rest) (caddr rest) (cadddr rest)))
(rest (cddddr rest)))
(make-rr-data-soa mname rname serial refresh retry expire minimum)))))))))))
((eq? type 'hinfo)
(make-rr-data-hinfo (list->string data)))
((eq? type 'txt)
(make-rr-data-txt (list->string data)))
((eq? type 'wks)
(make-rr-data-wks data))
(else (list data))))
;; parses n-times a message with parse. returns a list of parse-returns.
(define (parse-n parse start message n)
(let loop ((n n) (start start) (accum '()))
(if (zero? n)
(values (reverse! accum) start)
(call-with-values
(lambda () (parse start message))
(lambda (rr start)
(loop (- n 1) start (cons rr accum)))))))
;; parses a message-headers flags. returns the flags.
(define (parse-flags message)
(let ((v0 (list-ref message 2))
(v1 (list-ref message 3)))
;; Check for error code:
(let ((rcode (bitwise-and #xf (char->ascii v1)))
(z (arithmetic-shift (bitwise-and 112 (char->ascii v1)) -4))
(ra (arithmetic-shift (bitwise-and 64 (char->ascii v1)) -7))
(rd (bitwise-and 1 (char->ascii v0)))
(tc (arithmetic-shift (bitwise-and 2 (char->ascii v0)) -1))
(aa (arithmetic-shift (bitwise-and 4 (char->ascii v0)) -2))
(opcode (arithmetic-shift (bitwise-and 120 (char->ascii v0)) -3))
(qr (arithmetic-shift (bitwise-and 128 (char->ascii v0)) -7)))
(make-flags qr opcode aa tc rd ra z rcode))))
;; parses a message-header. returns the header.
(define (parse-header message)
(let ((id (octet-pair->number (list-ref message 0) (list-ref message 1)))
(flags (parse-flags message))
(qd-count (octet-pair->number (list-ref message 4) (list-ref message 5)))
(an-count (octet-pair->number (list-ref message 6) (list-ref message 7)))
(ns-count (octet-pair->number (list-ref message 8) (list-ref message 9)))
(ar-count (octet-pair->number (list-ref message 10) (list-ref message 11))))
(make-header id flags qd-count an-count ns-count ar-count)))
;; parses a message. returns the parsed message.
(define (parse message)
(let* ((header (parse-header message))
(start (list-tail message 12)))
(call-with-values
(lambda () (parse-n parse-question start message (header-qdc header)))
2002-06-08 11:07:01 -04:00
(lambda (qds start)
(call-with-values
(lambda () (parse-n parse-rr start message (header-anc header)))
2002-06-08 11:07:01 -04:00
(lambda (ans start)
(call-with-values
(lambda () (parse-n parse-rr start message (header-nsc header)))
2002-06-08 11:07:01 -04:00
(lambda (nss start)
(call-with-values
(lambda () (parse-n parse-rr start message (header-arc header)))
2002-06-08 11:07:01 -04:00
(lambda (ars start)
(if (not (null? start))
(dns-error 'parse-error))
2002-06-08 11:07:01 -04:00
(make-message header qds ans nss ars message)))))))))))
;;; -- send, receive and validate message
;; checks if the received reply is valid. returns #t or error-msg.
(define (reply-acceptable? reply query)
;; Check correct id
(if (not (and (char=? (car reply) (car query))
(char=? (cadr reply) (cadr query))))
(display "send-receive-message: bad reply id from server"))
;; Check for error code:
(let ((rcode (flags-rcode (parse-flags reply))))
2002-06-08 11:07:01 -04:00
(if (not (zero? rcode))
(case rcode
((1) (dns-error 'dns-format-error))
((2) (dns-error 'dns-server-failure))
((3) (dns-error 'dns-name-error))
((4) (dns-error 'dns-not-implemented))
((5) (dns-error 'dns-refused))))))
2002-06-08 11:07:01 -04:00
;; #t if message is truncated (could happen via UDP)
(define (truncated? reply)
(let ((trunc (flags-trunc (parse-flags reply))))
2002-11-19 17:41:37 -05:00
(= trunc 1)))
2002-06-08 11:07:01 -04:00
;; connects to nameserver and sends and receives messages. returns the reply.
;; here: via TCP
(define (send-receive-message-tcp nameservers query)
(receive (reply hit-ns other-nss)
(let ((sockets (map (lambda (nameserver)
(let ((sock (create-socket protocol-family/internet
socket-type/stream))
(addr (internet-address->socket-address
nameserver 53)))
;; we ignore the return value and select
;; unconditionally later
(connect-socket-no-wait sock addr)
sock))
nameservers)))
(let* ((ws (map socket:outport sockets))
(wport-nameserver-alist (map cons ws nameservers))
(wport-socket-alist (map cons ws sockets)))
(dynamic-wind
(lambda () #f)
(lambda ()
(let* ((ready-ports (apply select-port-channels #f ws))
(w (car ready-ports))
(hit-ns (cdr (assoc w wport-nameserver-alist)))
(sock (cdr (assoc w wport-socket-alist))))
(if (not (connect-socket-successful? sock))
(dns-error 'bad-nameserver hit-ns))
(let ((query-string (list->string query))
(r (socket:inport sock)))
(display (list->string (add-size-tag query)) w)
(force-output w)
(let ((a (read-char r))
(b (read-char r)))
(let ((len (octet-pair->number a b)))
(let ((s (read-string len r)))
(if (not (= len (string-length s)))
(dns-error 'unexpected-eof-from-server))
(values (string->list s)
hit-ns
(delete hit-ns nameservers))))))))
(lambda ()
(for-each close-socket sockets)))))
2002-06-08 11:07:01 -04:00
(reply-acceptable? reply query)
(values (parse reply)
hit-ns
other-nss)))
2002-06-08 11:07:01 -04:00
;; here: via UDP
(define (send-receive-message-udp nameservers query)
(receive (reply hit-ns other-nss)
(let ((sockets (map (lambda (nameserver)
(let ((sock (create-socket protocol-family/internet
socket-type/datagram))
(addr (internet-address->socket-address
nameserver 53)))
(connect-socket sock addr)
sock))
nameservers)))
(let ((rs (map socket:inport sockets))
(ws (map socket:outport sockets)))
(dynamic-wind
(lambda ()
'nothing-to-be-done-before)
(lambda ()
(let ((query-string (list->string query))
(rsv (list->vector rs))
(rport-nameserver-alist (map cons rs nameservers))
(rport-socket-alist (map cons rs sockets)))
(for-each (lambda (w) (display query-string w)) ws)
(for-each force-output ws)
(let* ((ready (apply select-port-channels #f rs))
(r (car ready))
(hit-ns (cdr (assoc r rport-nameserver-alist))))
(if (not (connect-socket-successful? (cdr (assoc r rport-socket-alist))))
(dns-error 'bad-nameserver hit-ns))
;;; 512 is the maximum udp-message size:
(values (string->list (read-string/partial 512 r))
hit-ns
(delete hit-ns nameservers)))))
(lambda ()
(for-each close-socket sockets)))))
(reply-acceptable? reply query)
(if (truncated? reply)
(send-receive-message-tcp nameservers query)
(values (parse reply)
hit-ns
other-nss))))
2002-06-08 11:07:01 -04:00
;;; -- cache
;; creates the cache, an empty string-table
2002-06-08 11:07:01 -04:00
(define cache (make-string-table))
;; resets the cache
(define (dns-clear-cache)
(set! cache (make-string-table)))
;; searches in a dns-msg for the shortest ttl. this is needed for cache-management.
(define (find-shortest-ttl dns-msg)
(letrec ((minimum #f)
(find-shortest-ttl-1
(lambda (dns-msg)
(cond
((dns-message? dns-msg)
(find-shortest-ttl-1 (dns-message-reply dns-msg)))
2002-06-08 11:07:01 -04:00
((message? dns-msg)
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message-answers dns-msg))
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message-nameservers dns-msg))
(for-each (lambda (x) (find-shortest-ttl-1 x)) (message-additionals dns-msg))
2002-06-08 11:07:01 -04:00
minimum)
((rr? dns-msg)
(cond
((not minimum) (set! minimum (rr-ttl dns-msg)))
2002-06-08 11:07:01 -04:00
(else
(if (and (not minimum) (> minimum (rr-ttl dns-msg)))
(set! minimum (rr-ttl dns-msg))))))))))
2002-06-08 11:07:01 -04:00
(find-shortest-ttl-1 dns-msg)))
2002-11-19 17:41:37 -05:00
(define (make-key qds nameserver)
(let*;; cache-key relevant data
((name (question-name (car qds)))
(type (question-type (car qds)))
(class (question-class (car qds))))
2002-11-19 17:41:37 -05:00
(format #f "~a;~a;~a;~a" nameserver name type class)))
(define (lookup-cache qds nameserver)
(let* ((key (make-key qds nameserver))
(found-data (table-ref cache key)))
(cond
((and found-data
;; checks if cached-data is still valid
(< (time) (+ (cache-time found-data) (cache-ttl found-data))))
2002-11-19 17:41:37 -05:00
found-data)
(else #f))))
(define (update-cache! key entry)
(table-set! cache key entry))
(define (dns-query-no-cache question protocol nameservers tried)
;; returns new retrieved data
(receive (dns-msg hit-ns nss-with-no-reply)
(send-receive-message nameservers question protocol)
(values
(make-dns-message (parse question) dns-msg #f protocol (reverse tried))
hit-ns
nss-with-no-reply)))
2002-11-19 17:41:37 -05:00
(define (dns-query-with-cache question protocol nameservers tried)
(let ((qds (message-questions (parse question))))
2002-11-19 17:41:37 -05:00
(let lp ((ns nameservers))
(if (null? ns)
(receive (reply-msg hit-ns nss-with-no-reply)
(send-receive-message nameservers question protocol)
(update-cache! (make-key qds hit-ns)
(make-cache reply-msg (find-shortest-ttl reply-msg) (time)))
2002-11-19 17:41:37 -05:00
;; returns new retrieved data and updates cache
(values (make-dns-message (parse question) reply-msg #f protocol (reverse tried))
hit-ns
nss-with-no-reply))
(cond ((lookup-cache qds (car ns))
=> (lambda (found-data)
;; returns cached data
(values (make-dns-message (parse question) (cache-answer found-data) #t protocol '())
2002-11-19 17:41:37 -05:00
#f
nameservers)))
(else (lp (cdr ns))))))))
(define (send-receive-message nameservers question protocol)
((cond
((eq? protocol 'tcp) send-receive-message-tcp)
((eq? protocol 'udp) send-receive-message-udp))
nameservers question))
2002-06-08 11:07:01 -04:00
;; makes a dns-query. optional cache-check.
;; returns a dns-message with cache-flag and either cache-data or new received data.
2002-11-19 17:41:37 -05:00
(define (dns-query/cache question use-cache? protocol nameservers tried)
2002-06-08 11:07:01 -04:00
(if use-cache?
2002-11-19 17:41:37 -05:00
(dns-query-with-cache question protocol nameservers tried)
(dns-query-no-cache question protocol nameservers tried)))
2002-06-08 11:07:01 -04:00
;; dns and recursion
;; recursion means, if the demanded information is not available from the
;; nameserver, another nameserver (usualy an authority) has to be contacted.
;; normally the recursion is done for us by the nameserver istself, but
;; this feature is technically optional (RFC 1035).
;; dns-get-information implements the resovler-side recursion.
;; it returns a dns-message
2002-11-19 17:41:37 -05:00
(define (dns-get-information question use-cache? protocol nameservers check-answer)
(let lp ((tried '()) (nss nameservers))
(if (null? nss)
(dns-error 'bad-address)
2002-11-19 17:41:37 -05:00
(receive (dns-msg hit-ns nss-with-no-reply)
(dns-query/cache question use-cache? protocol nss tried)
(if (check-answer dns-msg)
dns-msg
(let ((auth? (not
(zero?
(flags-auth (header-flags
(message-header
(dns-message-reply dns-msg))))))))
2002-11-19 17:41:37 -05:00
(if auth?
(dns-error 'bad-address)
2002-11-19 17:41:37 -05:00
;; other nameservers names are found in the nameserver-part,
;; but their ip-adresses are found in the additonal-rrs
(let ((other-nameservers
(filter (lambda (elem) (eq? (rr-type elem) 'a))
(message-additionals (dns-message-reply dns-msg)))))
2002-11-19 17:41:37 -05:00
(lp (if (not (member hit-ns tried)) (cons hit-ns tried) tried)
(lset-union equal?
nss-with-no-reply
(lset-difference equal? other-nameservers tried)))))))))))
2002-06-08 11:07:01 -04:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Parsing of /etc/resolv.conf
(define (parse-nameserver rest-of-line)
(let ((match (regexp-search
(rx (: (submatch (** 1 3 digit) "."
(** 1 3 digit) "."
(** 1 3 digit) "."
(** 1 3 digit))
(* white))); don't complain about tailing white space
rest-of-line)))
(if match
(cons 'nameserver (match:substring match 1))
(signal 'resolv.conf-parse-error))))
; could be more restrictive...
(define domain-name-regexp (rx (+ (| alphanum #\. #\-))))
(define (parse-domain rest-of-line)
(let ((match (regexp-search
(rx (: (submatch ,domain-name-regexp)
(* white))); don't complain about tailing white space
rest-of-line)))
(if match
(cons 'domain (match:substring match 1))
(signal 'resolv.conf-parse-error))))
(define (parse-search rest-of-line)
(let ((domains (regexp-fold-right domain-name-regexp
(lambda (match junk accu)
(cons (match:substring match 0) accu))
'()
rest-of-line)))
(if (null? domains)
(signal 'resolv.conf-parse-error)
(cons 'search domains))))
(define (parse-sortlist rest-of-line)
(let ((netmask-pairs (regexp-fold-right (rx (+ (| digit #\. #\/)))
(lambda (match junk accu)
(cons (match:substring match 0) accu))
'()
rest-of-line)))
(if (null? netmask-pairs)
(signal 'resolv.conf-parse-error)
(cons 'sortlist netmask-pairs))))
(define (parse-options rest-of-line)
(regexp-fold-right
(rx (| "debug" "no_tld_query" (: "ndots:" (submatch digit))))
(lambda (match junk accu)
(let ((str (match:substring match 0)))
(cond ((string=? str "debug")
(cons 'debug accu))
((string=? str "no_tld_query")
(cons 'no_tld_query accu))
(else (cons (cons 'ndots
(string->number (match:substring match 1))) accu)))))
'()
rest-of-line))
2002-12-22 08:45:38 -05:00
(define *resolv.conf-cache*)
(define *resolv.conf-cache-date* 0)
(define (parse-resolv.conf)
2002-12-22 08:45:38 -05:00
(let ((actual-m-time (file-info:mtime (file-info "/etc/resolv.conf"))))
(if (> actual-m-time *resolv.conf-cache-date*)
(let ((contents (really-parse-resolv.conf "/etc/resolv.conf")))
(set! *resolv.conf-cache* contents)
(set! *resolv.conf-cache-date* actual-m-time)
contents)
*resolv.conf-cache*)))
(define (really-parse-resolv.conf file-name)
;; accumulate nameserver entries
;; domain and search are mutual exclusive, take the last
(define (adjust-result rev-result have-search-or-domain? nameservers)
(cond ((null? rev-result)
(if (null? nameservers)
'()
(list (cons 'nameserver nameservers))))
((eq? (caar rev-result) 'domain)
(if have-search-or-domain?
(adjust-result (cdr rev-result) have-search-or-domain? nameservers)
(cons (car rev-result)
(adjust-result (cdr rev-result)
#t
nameservers))))
((eq? (caar rev-result) 'search)
(if have-search-or-domain?
(adjust-result (cdr rev-result) have-search-or-domain? nameservers)
(cons (car rev-result)
(adjust-result (cdr rev-result)
#t
nameservers))))
((eq? (caar rev-result) 'nameserver)
(adjust-result (cdr rev-result)
have-search-or-domain?
(cons (cdar rev-result)
nameservers)))
(else (cons (car rev-result)
(adjust-result (cdr rev-result)
have-search-or-domain?
nameservers)))))
2002-12-22 08:45:38 -05:00
(with-input-from-file file-name
2002-06-08 11:07:01 -04:00
(lambda ()
(let loop ((rev-result '()))
2002-06-08 11:07:01 -04:00
(let ((l (read-line)))
(cond
((eof-object? l)
(adjust-result rev-result #f '()))
((regexp-search
(rx (: "nameserver" (+ (| " " "\t")
(submatch (* any))
eos)))
l)
=> (lambda (match)
(loop (cons (parse-nameserver (match:substring match 1))
rev-result))))
((regexp-search
(rx (: "domain" (+ (| " " "\t")
(submatch (* any))
eos)))
l)
=> (lambda (match)
(loop (cons (parse-domain (match:substring match 1))
rev-result))))
((regexp-search
(rx (: "search" (+ (| " " "\t")
(submatch (* any))
eos)))
l)
=> (lambda (match)
(loop (cons (parse-search (match:substring match 1))
rev-result))))
((regexp-search
(rx (: "sortlist" (+ (| " " "\t")
(submatch (* any))
eos)))
l)
2002-06-08 11:07:01 -04:00
=> (lambda (match)
(parse-sortlist (match:substring match 1))))
2002-06-08 11:07:01 -04:00
((regexp-search
(rx (: "options" (+ (| " " "\t")
(submatch (* any))
eos)))
l)
=> (lambda (match)
(parse-options (match:substring match 1))))
(else (signal 'resolv.conf-parse-error))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Figure out the default name servers
(define (dns-find-nameserver-list)
(cond ((assoc 'nameserver (parse-resolv.conf))
=> (lambda (nameserver.list)
(cdr nameserver.list)))
(else '())))
2002-06-08 11:07:01 -04:00
;; returns the first found nameserver
(define (dns-find-nameserver)
(let ((ns (dns-find-nameserver-list)))
(if (null? ns)
(dns-error 'no-nameservers)
2002-06-08 11:07:01 -04:00
(car ns))))
;; checks the nameservers argument of the lookup functions.
2002-06-08 11:07:01 -04:00
;; if a nameserver-name is given and not a nameserver-ip
;; (dns-lookup-name nameserver) is called.
(define (check-args args)
(if (null? args)
(map ip-string->address32 (dns-find-nameserver-list))
(map (lambda (nameserver)
(cond
((address32? nameserver) nameserver)
((ip-string? nameserver) (ip-string->address32 nameserver))
(else (map (dns-lookup-name nameserver (dns-find-nameserver-list))))))
(car args))))
2002-06-08 11:07:01 -04:00
;; dns-lookup with more options than dns-lookup-*
(define (dns-lookup name type . nameservers)
(let* ((maybe-ip-string (if (address32? name)
(ip-string->in-addr-arpa (address32->ip-string name))
(ip-string->in-addr-arpa name)))
(question (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
(make-octet-query-message (random 256) maybe-ip-string type 'in)
(make-octet-query-message (random 256) name type 'in)))
(use-cache? #t)
(protocol 'udp)
(nameservers (check-args nameservers))
(check-answer (lambda (dns-msg) #t))
(dns-msg (dns-get-information question use-cache? protocol nameservers check-answer))
(answers (message-answers (dns-message-reply dns-msg))))
(if (not (null? answers))
(for-each (lambda (x) (show-dns-message x)(newline)) answers)
;;; TODO remove display
(display "no answers received - but resolved information in other sections.\n"))
dns-msg))
2002-06-08 11:07:01 -04:00
;; looks up a hostname, returns an ip.
2002-11-19 17:41:37 -05:00
;; (dns-lookup-name <name> nameservers)
(define (dns-lookup-name name . nameservers)
(let* ((maybe-ip-string (if (address32? name)
(ip-string->in-addr-arpa (address32->ip-string name))
(ip-string->in-addr-arpa name)))
(question (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
(dns-error 'not-a-hostname)
(make-octet-query-message (random 256) name 'a 'in)))
(use-cache? #t)
(protocol 'udp)
(nameservers (check-args nameservers))
(check-answer (lambda (dns-msg)
(let* ((reply (dns-message-reply dns-msg))
(answers (message-answers reply)))
(not (null? (filter-type answers 'a))))))
(dns-msg (dns-get-information question use-cache? protocol nameservers check-answer))
(answers (filter-type (message-answers (dns-message-reply dns-msg)) 'a)))
(rr-data-a-ip (rr-data (car answers)))))
2002-06-08 11:07:01 -04:00
;; looks up an ip, returns a hostname
;; (dns-inverse-lookup <name> [nameserver])
(define (dns-lookup-ip ip . nameservers)
(let* ((maybe-ip-string (if (address32? ip)
(ip-string->in-addr-arpa (address32->ip-string ip))
(ip-string->in-addr-arpa ip)))
(question (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
(make-octet-query-message (random 256) maybe-ip-string 'ptr 'in)
(dns-error 'not-a-ip)))
(use-cache? #t)
(protocol 'udp)
(nameservers (check-args nameservers))
(check-answer (lambda (dns-msg)
(let* ((reply (dns-message-reply dns-msg))
(answers (message-answers reply)))
(not (null? (filter-type answers 'ptr))))))
(dns-msg (dns-get-information question use-cache? protocol nameservers check-answer))
(answers (filter-type (message-answers (dns-message-reply dns-msg)) 'ptr)))
(rr-data-ptr-name (rr-data (car answers)))))
(define dns-inverse-lookup dns-lookup-ip)
2002-06-08 11:07:01 -04:00
;; looks up an authoritative nameserver for a hostname
;; returns a list of nameservers
;; (dns-lookup-nameserver <name> [nameserver])
(define (dns-lookup-nameserver name . nameservers)
(let* ((maybe-ip-string (if (address32? name)
(ip-string->in-addr-arpa (address32->ip-string name))
(ip-string->in-addr-arpa name)))
(question (if maybe-ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
(dns-error 'not-a-hostname)
(make-octet-query-message (random 256) name 'ns 'in)))
(use-cache? #t)
(protocol 'udp)
(nameservers (check-args nameservers))
(check-answer (lambda (dns-msg)
(let* ((reply (dns-message-reply dns-msg))
(answers (message-answers reply))
(nameservers (message-nameservers reply)))
(or (not (null? (filter-type nameservers 'soa)))
(not (null? (filter-type answers 'ns)))))))
(dns-msg (dns-get-information question use-cache? protocol nameservers check-answer))
(reply (dns-message-reply dns-msg))
(soa (filter-type (message-nameservers reply) 'soa))
(nss (filter-type (message-answers reply) 'ns))
(add (filter-type (message-additionals reply) 'a)))
(if (null? nss)
(list (dns-lookup-name (rr-data-soa-mname (rr-data (car soa)))))
(map (lambda (elem) (rr-data-a-ip (rr-data elem))) add))))
2002-06-08 11:07:01 -04:00
;; looks up a mail-exchanger for a hostname.
;; returns a list of mail-exchanger, sorted by their preference
;; if there are no mx-records in the answer-section,
;; implementation based on RFC2821
;; (dns-lookup-mail-exchanger <name> [nameserver])
(define (dns-lookup-mail-exchanger name . nameservers)
(let* ((ip-string (if (address32? name)
(ip-string->in-addr-arpa (address32->ip-string name))
(ip-string->in-addr-arpa name)))
(question (if ip-string ; if name is a ip-addr, the query is a in-addr.arpa address
(dns-error 'not-a-hostname)
(make-octet-query-message (random 256) name 'mx 'in)))
(use-cache? #t)
(protocol 'tcp)
(nameservers (check-args nameservers))
(check-answer (lambda (dns-msg)
(let* ((reply (dns-message-reply dns-msg))
(answers (message-answers reply))
(nameservers (message-nameservers reply)))
(or (not (null? (filter-type answers 'mx)))
(not (null? (filter-type answers 'cname)))
(not (null? (filter-type answers 'a)))))))
(dns-msg (dns-get-information question use-cache? protocol nameservers check-answer))
(reply (dns-message-reply dns-msg))
(mx (filter-type (message-answers reply) 'mx))
(soa (filter-type (message-nameservers reply) 'soa))
(cname (filter-type (message-answers reply) 'cname))
(a (filter-type (message-answers reply) 'a)))
2002-06-08 11:07:01 -04:00
(cond
((not (null? a))
(list (rr-data-a-ip (rr-data (car a)))))
((not (null? cname))
(dns-lookup-mail-exchanger (rr-data-cname-name (rr-data (car cname)))))
((null? mx)
(list (rr-data-soa-rname (rr-data (car soa)))))
(else
(map (lambda (elem) (rr-data-mx-exchanger (rr-data elem))) (sort-by-preference mx))))))
2002-06-08 11:07:01 -04:00
;;; pretty-prints a dns-msg
(define (show-dns-message dns-msg)
(let* ((d
(lambda (n s1 s2)
(letrec ((loop (lambda (n)
(if (zero? n)
""
(string-append " " (loop (- n 1)))))))
(display (loop n))
(display s1)
(display ": ")
(display s2)
(newline)))))
(cond
((dns-message? dns-msg)
(begin
(d 0 "DNS-MESSAGE" "")
(d 1 "QUERY" "")(show-dns-message (dns-message-query dns-msg))(newline)
(d 1 "REPLY" "")(show-dns-message (dns-message-reply dns-msg))(newline)
(d 1 "CACHE?" (if (dns-message-cache? dns-msg)
2002-06-08 11:07:01 -04:00
"found in cache"
"not found in cache"))
(d 1 "PROTOCOL" (let ((protocol (dns-message-protocol dns-msg)))
2002-06-08 11:07:01 -04:00
(cond
((eq? protocol 'tcp) "TCP")
((eq? protocol 'udp) "UDP"))))
(d 1 "TRIED-NAMESERVERS" (if (> (length (dns-message-tried-nameservers dns-msg)) 1)
2002-06-08 11:07:01 -04:00
(begin
(display " had perform recursion: ")
(dns-message-tried-nameservers dns-msg))
2002-06-08 11:07:01 -04:00
(begin
(display " without recursion: ")
(dns-message-tried-nameservers dns-msg))))))
2002-06-08 11:07:01 -04:00
((message? dns-msg)
(begin
(d 2 "MESSAGE" "")
(d 3 "Header " "")(show-dns-message (message-header dns-msg))
(d 3 "Questions " "")(for-each (lambda (x) (show-dns-message x)(newline)) (message-questions dns-msg))
(d 3 "Answers " "")(for-each (lambda (x) (show-dns-message x)(newline)) (message-answers dns-msg))
(d 3 "Nameservers" "")(for-each (lambda (x) (show-dns-message x)(newline)) (message-nameservers dns-msg))
(d 3 "Additionals" "")(for-each (lambda (x) (show-dns-message x)(newline)) (message-additionals dns-msg))))
2002-06-08 11:07:01 -04:00
((header? dns-msg)
(begin
(d 4 "id" (header-id dns-msg))
(d 4 "Flags" "")(show-dns-message (header-flags dns-msg))
(d 4 "question-count " (header-qdc dns-msg))
(d 4 "answer-count " (header-anc dns-msg))
(d 4 "nameserver-count " (header-nsc dns-msg))
(d 4 "additional-count " (header-arc dns-msg))))
2002-06-08 11:07:01 -04:00
((flags? dns-msg)
(begin
(d 5 "querytype" (flags-querytype dns-msg))
(d 5 "opcode" (flags-opcode dns-msg))
(d 5 "auth" (flags-auth dns-msg))
(d 5 "trunc" (flags-trunc dns-msg))
(d 5 "recursiondesired" (flags-recursiondesired dns-msg))
(d 5 "recursionavailable" (flags-recursionavailable dns-msg))
(d 5 "z" (flags-z dns-msg))
(d 5 "rcode" (flags-rcode dns-msg))))
2002-06-08 11:07:01 -04:00
((question? dns-msg)
(begin
(d 4 "name " (question-name dns-msg))
(d 4 "type " (question-type dns-msg))
(d 4 "class" (question-class dns-msg))))
2002-06-08 11:07:01 -04:00
((rr? dns-msg)
(begin
(d 4 "name " (rr-name dns-msg))
(d 4 "type " (rr-type dns-msg))
(d 4 "class" (rr-class dns-msg))
(d 4 "ttl " (rr-ttl dns-msg))
(d 4 "data " "") (show-dns-message (rr-data dns-msg))))
2002-06-08 11:07:01 -04:00
((rr-data-a? dns-msg)
(d 5 "ip " (rr-data-a-ip dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-ns? dns-msg)
(d 5 "name " (rr-data-ns-name dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-cname? dns-msg)
(d 5 "name " (rr-data-cname-name dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-mx? dns-msg)
(begin
(d 5 "preference " (rr-data-mx-preference dns-msg))
(d 5 "exchanger " (rr-data-mx-exchanger dns-msg))))
2002-06-08 11:07:01 -04:00
((rr-data-ptr? dns-msg)
(d 5 "name " (rr-data-ptr-name dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-soa? dns-msg)
(begin
(d 5 "mname " (rr-data-soa-mname dns-msg))
(d 5 "rname " (rr-data-soa-rname dns-msg))
(d 5 "serial " (rr-data-soa-serial dns-msg))
(d 5 "refresh " (rr-data-soa-refresh dns-msg))
(d 5 "expire " (rr-data-soa-expire dns-msg))
(d 5 "minimum " (rr-data-soa-expire dns-msg))))
2002-06-08 11:07:01 -04:00
;; ###
((rr-data-hinfo? dns-msg)
(d 5 "data " (rr-data-hinfo-data dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-txt? dns-msg)
(d 5 "text " (rr-data-txt-text dns-msg)))
2002-06-08 11:07:01 -04:00
((rr-data-wks? dns-msg)
(d 5 "data " (rr-data-wks-data dns-msg)))
2002-06-08 11:07:01 -04:00
)))
(define *fqdn-lock* (make-lock))
(define *fqdn-cache* '())
(define (socket-address->fqdn addr cache?)
(receive (ip32 port)
(socket-address->internet-address addr)
(internet-address->fqdn ip32 cache?)))
(define (internet-address->fqdn ip32 cache?)
(if cache?
(begin
(obtain-lock *fqdn-lock*)
(cond
((assv ip32 *fqdn-cache*) =>
(lambda (pair)
(release-lock *fqdn-lock*)
(cdr pair)))
(else
(release-lock *fqdn-lock*)
(let ((fqdn (dns-lookup-ip ip32)))
(set! *fqdn-cache*
(cons (cons ip32 fqdn) *fqdn-cache*))
fqdn))))
(dns-lookup-ip ip32)))
(define (is-fqdn? name)
(regexp-search? (rx #\.) name))
(define (maybe-dns-lookup-name name)
(call-with-current-continuation
(lambda (k)
(with-handler (lambda (cond more)
(if (dns-error? cond)
(k #f)
(more)))
(lambda ()
(dns-lookup-name name))))))
(define (domains-for-search)
(let ((resolv.conf (parse-resolv.conf)))
(cond ((assoc 'domain resolv.conf)
=> (lambda (pair)
(list (cdr pair))))
((assoc 'search resolv.conf)
=> (lambda (pair)
(cdr pair)))
(else '()))))
(define (host-fqdn name-or-socket-address)
(if (socket-address? name-or-socket-address)
(socket-address->fqdn name-or-socket-address #f)
(let ((name name-or-socket-address))
(if (is-fqdn? name)
name
(let lp ((domains (domains-for-search)))
(if (null? domains)
#f
(cond ((maybe-dns-lookup-name (string-append name "." (car domains)))
=> (lambda (ip)
(dns-lookup-ip ip)))
(else (lp (cdr domains))))))))))
(define (system-fqdn)
(host-fqdn (system-name)))