diff --git a/CHANGELOG b/CHANGELOG index db263fe88..39ac8bdad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added gps library and the gpsd-info script that collects GPS data + from the gpsd daemon. [Patrik Karlsson] + o [NSE] Ported the pop3-brute script to use the brute library. [Piotr Olma] diff --git a/nselib/gps.lua b/nselib/gps.lua new file mode 100644 index 000000000..be56d85bd --- /dev/null +++ b/nselib/gps.lua @@ -0,0 +1,116 @@ +--- +-- A smallish gps parsing module. +-- Currently does GPRMC NMEA decoding +-- +-- @author "Patrik Karlsson " +-- +-- + +module(... or "gps", package.seeall) + +local bit = require('bit') + +NMEA = { + + -- Parser for the RMC sentence + RMC = { + + parse = function(str) + + local time, status, latitude, ns_indicator, longitude, + ew_indicator, speed, course, date, variation, + ew_variation, checksum = str:match("^%$GPRMC,([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^%*]*)(.*)$") + + if ( not(latitude) or not(longitude) ) then + return + end + + local deg, min = latitude:match("^(..)(.*)$") + if ( not(deg) or not(min) ) then + return + end + latitude = tonumber(deg) + (tonumber(min)/60) + + deg, min = longitude:match("^(..)(.*)$") + if ( not(deg) or not(min) ) then + return + end + longitude = tonumber(deg) + (tonumber(min)/60) + if ( ew_indicator == 'W' ) then + longitude = -longitude + end + + if ( ns_indicator == 'S' ) then + latitude = -latitude + end + + return { time = time, status = status, latitude = latitude, + longitude = longitude, speed = speed, course = course, + date = date, variation = variation, + ew_variation = ew_variation } + end, + + }, + + -- Calculates an verifies the message checksum + -- + -- @param str containing the GPS sentence + -- @return status true on success, false if the checksum does not match + -- @return err string if status is false + checksum = function(str) + local val = 0 + for c in str:sub(2,-4):gmatch(".") do + val = bit.bxor(val, string.byte(c)) + end + + if ( str:sub(-2):upper() ~= stdnse.tohex(string.char(val)):upper() ) then + return false, ("Failed to verify checksum (got: %s; expected: %s)"):format(stdnse.tohex(string.char(val)), str:sub(-2)) + end + return true + end, + + -- Parses a GPS sentence using the apropriate parser + -- + -- @param str containing the GPS sentence + -- @return entry table containing the parsed response or + -- err string if status is false + -- @return status true on success, false on failure + parse = function(str) + + local status, err = NMEA.checksum(str) + if ( not(status) ) then + return false, err + end + + local prefix = str:match("^%$GP([^,]*)") + if ( not(prefix) ) then + return false, "Not a NMEA sentence" + end + + if ( NMEA[prefix] and NMEA[prefix].parse ) then + local e = NMEA[prefix].parse(str) + if (not(e)) then + return false, ("Failed to parse entry: %s"):format(str) + end + return true, e + else + local err = ("No parser for prefix: %s"):format(prefix) + stdnse.print_debug(2, err) + return false, err + end + + end + +} + +Util = { + + convertTime = function(date, time) + local d = {} + d.hour, d.min, d.sec = time:match("(..)(..)(..)") + d.day, d.month, d.year = date:match("(..)(..)(..)") + d.year = d.year + 2000 + return os.time(d) + end +} + diff --git a/scripts/gpsd-info.nse b/scripts/gpsd-info.nse new file mode 100644 index 000000000..9f410478f --- /dev/null +++ b/scripts/gpsd-info.nse @@ -0,0 +1,99 @@ +description = [[ +Retrieves GPS time, coordinates and speed from the GPSD network daemon. +]] + +--- +-- @usage +-- nmap -p 2947 --script gpsd-info +-- +-- @output +-- PORT STATE SERVICE REASON +-- 2947/tcp open gpsd-ng syn-ack +-- | gpsd-info: +-- | Time of fix: Sat Apr 14 15:54:23 2012 +-- | Coordinates: 59.321685,17.886493 +-- |_ Speed: - knots +-- + + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +local shortport = require 'shortport' +local gps = require 'gps' +local json = require 'json' +local match = require 'match' + +portrule = shortport.port_or_service(2947, "gpsd-ng", "tcp") + +local arg_timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 10 + +local function updateData(gpsinfo, entry) + for k, v in pairs(gpsinfo) do + if ( entry[k] and 0 < #tostring(entry[k]) ) then + gpsinfo[k] = entry[k] + end + end +end + +local function hasAllData(gpsinfo) + for k, v in pairs(gpsinfo) do + if ( k ~= "speed" and v == '-' ) then + return false + end + end + return true +end + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +action = function(host, port) + + local gpsinfo = { + longitude = "-", + latitude = "-", + speed = "-", + time = "-", + date = "-", + } + + local socket = nmap.new_socket() + socket:set_timeout(1000) + + local status = socket:connect(host, port) + + if ( not(status) ) then + return fail("Failed to connect to server") + end + + -- get the banner + local status, line = socket:receive_lines(1) + socket:send('?WATCH={"enable":true,"nmea":true}\r\n') + + local start_time = os.time() + + repeat + local entry + status, line = socket:receive_buf("\r\n") + if ( status ) then + status, entry = gps.NMEA.parse(line) + if ( status ) then + updateData(gpsinfo, entry) + end + end + until( os.time() - start_time > arg_timeout or hasAllData(gpsinfo) ) + + socket:send('?WATCH={"enable":false}\r\n') + + if ( not(hasAllData(gpsinfo)) ) then + return + end + + local output = { + ("Time of fix: %s UTC"):format(os.date("%c", gps.Util.convertTime(gpsinfo.date, gpsinfo.time))), + ("Coordinates: %.4f,%.4f"):format(tonumber(gpsinfo.latitude), tonumber(gpsinfo.longitude)), + ("Speed: %s knots"):format(gpsinfo.speed) + } + return stdnse.format_output(true, output) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index d9a7ffe00..8f8a02fa4 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -94,6 +94,7 @@ Entry { filename = "ftp-vuln-cve2010-4221.nse", categories = { "intrusive", "vul Entry { filename = "ganglia-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "giop-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "gopher-ls.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "gpsd-info.nse", categories = { "discovery", "safe", } } Entry { filename = "hadoop-datanode-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "hadoop-jobtracker-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "hadoop-namenode-info.nse", categories = { "default", "discovery", "safe", } }