Ich hab mir mal Quick&Dirty in
Racket ein Skript geschrieben das
nmap benutzt, um sozusagen das Netzwerk abzuscannen.
Ich krieg dann eine HTML-Datei als Report, wo alle Rechner aufgeführt sind inkl. welche Ports da offen sind.
Das Ergebnis sieht ungefähr so aus:
Das Programm macht letztlich nix anderes als sich die nötigen Informationen per
nmap zu besorgen (wobei nmap angewiesen wird das Ergebnis als XML zu speichern, damit man die Ausgabe besser parsen kann).
Der Nachteil: Es werden ganze Adresseräume gescannt, was auch etwas dauern kann. Besser wäre es, wenn man das Host-Discovery durch stetige Beobachtung des LAN machen würde (z.B. mittels
arpwatch) und dann immer, wenn was Neues hinzu kommt das dann regelmäßig prüft, ob der Host noch da ist und welche Dienste er anbietet. Meist hängt man aber an einem Switch, so das der "Wächter" gar nicht alle Pakete kriegt (es sei denn man hat einen expliziten Monitor-Port).
Code:
#! /usr/local/bin/racket
#lang racket
(require xml
xexpr-path
xml/path
gregor )
; parallel (threaded) 'map'
(define (pmap/thread func lst)
(define cs (for/list ([x lst])
(make-channel)))
(for ([x lst]
[c cs])
(thread (thunk (channel-put c (func x)))))
(for/list ([c cs])
(channel-get c)))
(define (get-date-str [dt (now)])
(format "~a_~a" (~t dt "yyyy-MM-dd") (~t dt "HHmm")))
(define (get-humanreadable-date-str [dt (now)])
(format "~a ~a" (~t dt "dd.MM.yyyy") (~t dt "HH:mm")))
(define (run-cmd cmd [param ""])
(define ausgabe (open-output-string))
(parameterize ([current-output-port ausgabe])
(system (format "~a ~a" cmd param )))
(string-trim (get-output-string ausgabe)))
(define-syntax-rule (my-hash-set! h k val)
(let ([tmp val])
(when tmp
(hash-set! h k tmp))))
(define (file->xexpr file-path)
(permissive-xexprs #t)
(xml->xexpr
(document-element
(call-with-input-file file-path
(lambda(in)
(read-xml/document in))
#:mode 'text
))))
(define (scan-network net)
(define result-file (make-temporary-file))
(run-cmd "/usr/local/bin/sudo -c nmap /usr/local/bin/nmap" (format "-sP -oX ~a ~a" result-file net))
(define xe
(file->xexpr result-file))
(delete-file result-file)
(map
(lambda(elem)
(define h (make-hash))
(my-hash-set! h 'ipv4-addr (se-path* '(address #:addr) (xexpr-path-first '(address (addrtype "ipv4")) elem)))
h)
(xexpr-path-list '(host) xe)))
(define (scan-host host-hash)
(define result-file (make-temporary-file))
(run-cmd "/usr/local/bin/sudo -c nmap /usr/local/bin/nmap" (format "-A -P0 -oX ~a ~a" result-file (hash-ref host-hash 'ipv4-addr)))
(define xe
(file->xexpr result-file))
(my-hash-set! host-hash 'os (se-path* '(osmatch #:name) (xexpr-path-first '(host os osmatch) xe)))
(define mac-address (se-path* '(address #:addr) (xexpr-path-first '(host address (addrtype "mac")) xe)))
(unless mac-address
(let ([re (regexp-match #px"[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}" (run-cmd "arp" (hash-ref host-hash 'ipv4-addr)))])
(when re
(set! mac-address (car re)))))
(my-hash-set! host-hash 'mac-addr mac-address)
(my-hash-set! host-hash 'vendor (se-path* '(address #:vendor) (xexpr-path-first '(host address (addrtype "mac")) xe)))
(define re (regexp-match #rx"\\ ([^\\ ]+)\\.$" (run-cmd "host" (hash-ref host-hash 'ipv4-addr))))
(when re
(my-hash-set! host-hash 'hostname (second re)))
(delete-file result-file)
(my-hash-set!
host-hash
'ports
(map
(lambda(elem)
(define h (make-hash))
(my-hash-set! h 'portnr (se-path* '(port #:portid) elem))
(my-hash-set! h 'protocol (se-path* '(port #:protocol) elem))
(my-hash-set! h 'service-name (se-path* '(service #:name) (xexpr-path-first '(service) elem)))
(my-hash-set! h 'product-name (se-path* '(service #:product) (xexpr-path-first '(service) elem)))
(my-hash-set! h 'product-version (se-path* '(service #:version) (xexpr-path-first '(service) elem)))
(my-hash-set! h 'extrainfo (se-path* '(service #:extrainfo) (xexpr-path-first '(service) elem)))
h )
(xexpr-path-list '(host ports port) xe) ) )
host-hash )
(define (write-html-report net output-file)
(define (write-value hash key frmt-str)
(let ([val (hash-ref hash key #f)])
(if val
(format frmt-str val)
"") ) )
(define (compare-value service key val)
(if (hash-has-key? service key)
(string=? (hash-ref service key) val)
#f) )
(define (create-service-link host link-fmt-str)
(let* ([addr (if (= 0 (string-length (hash-ref host 'hostname "")))
(hash-ref host 'ipv4-addr)
(hash-ref host 'hostname))]
[link (format link-fmt-str addr)])
(format "<a href=\"~a\">~a</a>" link link) ) )
(define hosts
(pmap/thread (lambda(host)
(printf "Scanning ~a ...\n" (hash-ref host 'ipv4-addr))
(scan-host host) )
(scan-network net) ) )
(define dt (now))
(define dest-filename (string-append output-file "_" (get-date-str dt) ".html"))
(define symlink (string-append output-file ".html"))
(with-output-to-file dest-filename
(lambda()
(displayln "<html><head><meta charset=\"utf-8\" />")
(printf "<title>Report from ~a for ~a</title>\n" (get-humanreadable-date-str dt) net)
(displayln "<style type=\"text/css\">
table.hosts {
border-style: solid;
border-width: 2px;
}
td.host {
border-style: solid;
border-width: 1px;
vertical-align: top;
padding: 2px;
}
table.services {
border-style: none;
}
td.service {
border-style: none;
vertical-align: top;
}
td.serviceport {
border-style: none;
vertical-align: top;
text-align: right;
}
</style>")
(displayln "</head><body>")
(printf "<h1>Report from ~a for ~a</h1>" (get-humanreadable-date-str dt) net)
(displayln "<table class=\"hosts\">")
(displayln "<tr><th>Host</th><th>Services</th></tr>")
(for-each
(lambda(host)
(let ([service-links (list)])
(printf "<tr><td class=\"host\">")
(printf "~a" (hash-ref host 'ipv4-addr))
(printf (write-value host 'hostname " (~a)"))
(printf (write-value host 'mac-addr "<br> ~a"))
(printf (write-value host 'vendor " (~a)"))
(printf "<td class=\"host\">")
(printf (write-value host 'os "OS: ~a<br>"))
(printf "<table class=\"services\">")
(for-each
(lambda(service)
(printf "<tr><td class=\"serviceport\" >")
(printf "~a/~a" (hash-ref service 'portnr) (write-value service 'protocol "~a"))
(printf "</td>")
(printf "<td class=\"service\">")
(printf (write-value service 'service-name " ~a "))
(printf "</td>")
(printf "<td class=\"service\">")
(printf (write-value service 'product-name " ~a"))
(printf (write-value service 'product-version " ~a"))
(printf (write-value service 'extrainfo " (~a)"))
(displayln "</td></tr>")
(when (string=? "tcp" (hash-ref service 'protocol))
(cond
[(and
(compare-value service 'portnr "21")
(compare-value service 'service-name "ftp")
)
(set! service-links (cons (create-service-link host "ftp://~a/") service-links))]
[(and
(compare-value service 'portnr "631")
(compare-value service 'service-name "ipp")
)
(set! service-links (cons (create-service-link host "http://~a:631/") service-links))]
[(and
(compare-value service 'portnr "80")
(compare-value service 'service-name "http")
)
(set! service-links (cons (create-service-link host "http://~a/") service-links))]
[(and
(compare-value service 'portnr "443")
(compare-value service 'service-name "http")
)
(set! service-links (cons (create-service-link host "https://~a/") service-links))]
[(or
(compare-value service 'service-name "http")
(compare-value service 'service-name "http-alt")
)
(set! service-links (cons (create-service-link host (~a "http://~a" ":" (hash-ref service 'portnr) "/")) service-links))]
)
)
)
(hash-ref host 'ports)
)
(printf "</table>")
(when (> (length service-links) 0)
(printf "<br> <br>")
(printf
(string-join (reverse service-links) "<br>")))
(printf "</td></tr>\n") ) )
hosts )
(displayln "</table>")
(displayln "</body></html>")
)
#:mode 'text
#:exists 'truncate
)
(when (link-exists? symlink)
(delete-file symlink))
(make-file-or-directory-link (file-name-from-path dest-filename) symlink) )
(define (display-help)
(define prog-exec (~a (find-system-path 'exec-file) " " (file-name-from-path (find-system-path 'run-file))))
(displayln
(format
"Usage: ~a network output-file"
prog-exec ) )
(displayln
(format
"Example:\n ~a 192.168.179.0/24 /tmp/scanreport"
prog-exec ) ) )
(define argv (current-command-line-arguments))
(if (= 2 (vector-length argv))
(write-html-report (vector-ref argv 0) (vector-ref argv 1))
(display-help) )
Ist alles recht simpel gehalten, aber um den Überblick über sein Heimnetz zu haben reicht es vollkommen. Wenn man noch mehr haben möchte (wie z.B. Protokollierung) müsste man das Ganze noch ein wenig erweitern und ggf. die Hosts mit den nötigen Informationen in einer Datenbank speichern.