#lang scheme
(require net/dns
(planet "main.ss" ("dherman" "memoize.plt" 3 1))
scheme/port
mzlib/process
mzlib/string
)
(define/memo* (get-name . args)
(apply dns-get-name args))
(define/memo* (get-address . args)
(apply dns-get-address args))
(define-syntax safely
(syntax-rules ()
((safely _expr)
(with-handlers ([exn:fail? (lambda (e) #f)])
_expr))))
(define (get-info hostname-or-ip-string)
(define address (safely (string->ip-address hostname-or-ip-string)))
(define name (and (not address) hostname-or-ip-string))
(when (not address)
(set!
address
(safely
(string->ip-address
(get-address *nameserver* name)))))
(when (not name)
(set!
name
(safely
(get-name *nameserver* (ip-address->string address)))))
(values
(or
name
(let ((address (ip-address->strings address)))
(or
(apply try address)
(apply try "in-addr.arpa" (reverse address))
(apply try "in-addr.arpa" (cdr (reverse address)))
(apply try "in-addr.arpa" (cddr (reverse address)))
"??")))
(safely
(or
(geoiplookup (ip-address->string address))
(guess-country-from-hostname name)
"??"))))
(define (guess-country-from-hostname str)
(match str
[(regexp #px"\\.([[:alpha:]]{2})$" (list _ kaching)) kaching]
[else #f]))
(define *nameserver*
(dns-find-nameserver) )
(define (split-on-newlines str)
(let ((ip (open-input-string str)))
(let loop ((lines '()))
(let ((one-line (read-line ip)))
(if (eof-object? one-line)
(reverse lines)
(loop (cons one-line lines)))))))
(define (string->ip-address str)
(match str
[(regexp #px"^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$"
args)
(apply public-make-ip-address (cdr args))]
[else (error 'string->ip-address "~s doesn't look like an IP address" str)]))
(define-struct ip-address (a b c d) #:transparent)
(define (ip-address->strings ip)
(map number->string (cdr (vector->list (struct->vector ip)))))
(define (ip-address->string ip)
(string-join (ip-address->strings ip) "."))
(define (public-make-ip-address a b c d)
(define (puke datum)
(error
'public-make-ip-address
"Wanted four dot-separated integers 'twixt 0 and 255 inclusive; but one of them was ~s"
datum))
(apply
make-ip-address
(map (lambda (str)
(let ((datum (read-from-string str (lambda (e) (puke str)))))
(when (not (byte? datum)) (puke datum))
datum))
(list a b c d))))
(define-struct (exn:fail:process exn:fail ) ( ) #:transparent)
(define-struct (exn:fail:process:exit exn:fail:process) (status exit-code) #:transparent)
(define-struct (exn:fail:process:not-found exn:fail:process) ( ) #:transparent)
(define (port->string/close ip)
(let ((op (open-output-string)))
(copy-port ip op)
(close-input-port ip)
(get-output-string op)))
(define (fep . args)
(apply find-executable-path args))
(define (shell-command->string . args)
(let ((command
(let again ((command (car args))
(tries 0))
(let ((found (fep command)))
(or found
(and (< tries 1)
(eq? (system-type 'os) 'windows)
(again (string-append command ".exe") (add1 tries))))))))
(when (not command)
(raise (make-exn:fail:process:not-found
(format "Subprocess ~s failed: ~a not found"
args (car args))
(current-continuation-marks))))
(match-let ([(list stdout stdin pid stderr controller)
(apply process* command (cdr args))])
(close-output-port stdin)
(controller 'wait)
(when (not (eq? 'done-ok (controller 'status)))
(raise (make-exn:fail:process:exit
(format "Subprocess ~s failed: status ~a; exit code ~a"
args
(controller 'status)
(controller 'exit-code))
(current-continuation-marks)
(controller 'status)
(controller 'exit-code))))
(port->string/close stdout))))
(define (try . components)
(let ((got (safely
(get-name
*nameserver*
(string-join components ".")))))
(and (not (equal? "nxdomain.guide.opendns.com" got))
got)))
(define/memo* (geoiplookup h)
(with-handlers
([exn:fail:process?
(lambda (e) #f)])
(match
(car (split-on-newlines
(let again ((exe "geoiplookup")
(tries 1))
(with-handlers
([exn:fail:process:not-found?
(lambda (e)
(if (= 1 tries)
(again "/usr/bin/geoiplookup" (add1 tries))
(raise e)))])
(shell-command->string exe h)))))
[(regexp #px"GeoIP Country Edition: (..)," (list _ iso-code))
(and (not (equal? iso-code "--"))
iso-code)]
[#t #f])))
(provide get-info)