From 3c24eda4be701d00e01f29de66c234d1dbea3dee Mon Sep 17 00:00:00 2001 From: dmiller Date: Fri, 20 Sep 2019 04:40:20 +0000 Subject: [PATCH] Consolidate some error handling, standardize geoip coordinates, fix output bugs. Fixes #1744 --- nselib/geoip.lua | 63 +++++++++++++++++++++++++++ scripts/ip-geolocation-geoplugin.nse | 44 ++++++++----------- scripts/ip-geolocation-ipinfodb.nse | 45 +++++++++---------- scripts/ip-geolocation-map-bing.nse | 18 +++----- scripts/ip-geolocation-map-google.nse | 18 +++----- scripts/ip-geolocation-map-kml.nse | 18 +++----- scripts/ip-geolocation-maxmind.nse | 21 ++++++--- 7 files changed, 138 insertions(+), 89 deletions(-) diff --git a/nselib/geoip.lua b/nselib/geoip.lua index 15a54f12b..d8e9c1c77 100644 --- a/nselib/geoip.lua +++ b/nselib/geoip.lua @@ -1,6 +1,7 @@ local nmap = require "nmap" local stdnse = require "stdnse" local table = require "table" +local coroutine = require "coroutine" _ENV = stdnse.module("geoip", stdnse.seeall) @@ -72,4 +73,66 @@ get_all_by_gps = function() return t end +-- Order in which field names will be shown in XML +local field_order = { + "latitude", + "longitude", + "city", + "region", + "country" +} + +--- Location object +-- +-- The object supports setting the following fields using functions like +-- set_fieldname: +-- * latitude +-- * longitude +-- * city +-- * region +-- * country +-- +-- The location object is suitable for returning from a script, and will +-- produce appropriate string and structured XML output. +Location = { + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, + + -- Ensure fields are put in the XML in proper order + __pairs = function(self) + local function iterator () + for i, key in ipairs(field_order) do + coroutine.yield(key, self[key]) + end + end + return coroutine.wrap(iterator) + end, + + __tostring = function(self) + local out = { + ("coordinates: %s, %s"):format(self.latitude, self.longitude) + } + -- if any of these are nil, it doesn't increase #place + local place = {self.city} + place[#place+1] = self.region + place[#place+1] = self.country + if #place > 0 then + out[#out+1] = ("location: %s"):format(table.concat(place, ", ")) + end + + return table.concat(out, "\n") + end, +} + +-- Generate setter functions +for _, field in ipairs(field_order) do + Location["set_" .. field] = function(self, value) + self[field] = value + end +end + return _ENV; diff --git a/scripts/ip-geolocation-geoplugin.nse b/scripts/ip-geolocation-geoplugin.nse index e62f41d94..b04cad858 100644 --- a/scripts/ip-geolocation-geoplugin.nse +++ b/scripts/ip-geolocation-geoplugin.nse @@ -4,6 +4,7 @@ local ipOps = require "ipOps" local json = require "json" local stdnse = require "stdnse" local table = require "table" +local oops = require "oops" description = [[ Tries to identify the physical location of an IP address using the @@ -17,9 +18,13 @@ is no limit on lookups using this service. -- -- @output -- | ip-geolocation-geoplugin: --- | 74.207.244.221 (scanme.nmap.org) --- | coordinates (lat,lon): 39.4208984375,-74.497703552246 --- |_ state: New Jersey, United States +-- | coordinates: 39.4208984375, -74.497703552246 +-- |_location: New Jersey, United States +-- @xmloutput +-- 37.5605 +-- -121.9999 +-- California +-- United States -- -- @see ip-geolocation-ipinfodb.nse -- @see ip-geolocation-map-bing.nse @@ -44,15 +49,18 @@ end -- No limit on requests local geoplugin = function(ip) local response = http.get("www.geoplugin.net", 80, "/json.gp?ip="..ip, {any_af=true}) - local stat, loc = json.parse(response.body) + local stat, loc = oops.raise( + "The geoPlugin service has likely blocked you due to excessive usage", + json.parse(response.body)) if not stat then - return false, loc + return stat, loc end - local output = {} - table.insert(output, "coordinates (lat,lon): "..loc.geoplugin_latitude..","..loc.geoplugin_longitude) - local regionName = (loc.geoplugin_regionName == json.NULL) and "Unknown" or loc.geoplugin_regionName - table.insert(output,"state: ".. regionName ..", ".. loc.geoplugin_countryName) + local output = geoip.Location:new() + output:set_latitude(loc.geoplugin_latitude) + output:set_longitude(loc.geoplugin_longitude) + output:set_region((loc.geoplugin_regionName == json.NULL) and "Unknown" or loc.geoplugin_regionName) + output:set_country(loc.geoplugin_countryName) geoip.add(ip, loc.geoplugin_latitude, loc.geoplugin_longitude) @@ -60,21 +68,5 @@ local geoplugin = function(ip) end action = function(host,port) - local output = stdnse.output_table() - - local status, result = geoplugin(host.ip) - if not status then - if result == "syntax error" then - result = "The geoPlugin service has likely blocked you due to excessive usage, but the response received was 'syntax error'." - end - output.ERROR = result - return output, output.ERROR - end - - output.name = host.ip - if host.targetname then - output.name = output.name.." ("..host.targetname..")" - end - - return stdnse.format_output(true,output) + return oops.output(geoplugin(host.ip)) end diff --git a/scripts/ip-geolocation-ipinfodb.nse b/scripts/ip-geolocation-ipinfodb.nse index b8fd73d86..f1fbdb6e8 100644 --- a/scripts/ip-geolocation-ipinfodb.nse +++ b/scripts/ip-geolocation-ipinfodb.nse @@ -2,6 +2,7 @@ local geoip = require "geoip" local http = require "http" local ipOps = require "ipOps" local json = require "json" +local oops = require "oops" local stdnse = require "stdnse" local table = require "table" @@ -24,9 +25,15 @@ needs to be obtained through free registration for this service: -- -- @output -- | ip-geolocation-ipinfodb: --- | 74.207.244.221 (scanme.nmap.org) --- | coordinates (lat,lon): 37.5384,-121.99 --- |_ city: FREMONT, CALIFORNIA, UNITED STATES +-- | coordinates: 37.5384, -121.99 +-- |_location: FREMONT, CALIFORNIA, UNITED STATES +-- +-- @xmloutput +-- 37.5384 +-- -121.99 +-- FREMONT +-- CALIFORNIA +-- UNITED STATES -- -- @see ip-geolocation-geoplugin.nse -- @see ip-geolocation-map-bing.nse @@ -62,34 +69,28 @@ end local ipinfodb = function(ip) local api_key = stdnse.get_script_args(SCRIPT_NAME..".apikey") local response = http.get("api.ipinfodb.com", 80, "/v3/ip-city/?key="..api_key.."&format=json".."&ip="..ip, {any_af=true}) - local stat, loc = json.parse(response.body) + local stat, loc = oops.raise( + "Unable to parse ipinfodb.com response", + json.parse(response.body)) if not stat then - stdnse.debug1("No response, possibly a network problem.") - return nil + return stat, loc end if loc.statusMessage and loc.statusMessage == "Invalid API key." then - stdnse.debug1(loc.statusMessage) - return nil + return false, oops.err(loc.statusMessage) end - local output = {} - table.insert(output, "coordinates (lat,lon): "..loc.latitude..","..loc.longitude) - table.insert(output,"city: ".. loc.cityName..", ".. loc.regionName..", ".. loc.countryName) + local output = geoip.Location:new() + output:set_latitude(loc.latitude) + output:set_longitude(loc.longitude) + output:set_city(loc.cityName) + output:set_region(loc.regionName) + output:set_country(loc.countryName) geoip.add(ip, loc.latitude, loc.longitude) - return output + return true, output end action = function(host,port) - local output = ipinfodb(host.ip) - - if(output and #output~=0) then - output.name = host.ip - if host.targetname then - output.name = output.name.." ("..host.targetname..")" - end - end - - return stdnse.format_output(true,output) + return oops.output(ipinfodb(host.ip)) end diff --git a/scripts/ip-geolocation-map-bing.nse b/scripts/ip-geolocation-map-bing.nse index dcae9ab20..5e704a4ca 100644 --- a/scripts/ip-geolocation-map-bing.nse +++ b/scripts/ip-geolocation-map-bing.nse @@ -1,6 +1,7 @@ local http = require "http" local geoip = require "geoip" local io = require "io" +local oops = require "oops" local stdnse = require "stdnse" local string = require "string" local table = require "table" @@ -177,21 +178,14 @@ postrule = function() end action = function() - local output = stdnse.output_table() - -- Parse and sanity check the command line arguments. - local status, params, options = parse_args() + local status, params, options = oops.raise( + "Script argument problem", + parse_args()) if not status then - output.ERROR = params - return output, output.ERROR + return params end -- Render the map. - local status, msg = render(params, options) - if not status then - output.ERROR = msg - return output, output.ERROR - end - - return output, stdnse.format_output(true, msg) + return oops.output(render(params, options)) end diff --git a/scripts/ip-geolocation-map-google.nse b/scripts/ip-geolocation-map-google.nse index 4341bb44b..807c8ea90 100644 --- a/scripts/ip-geolocation-map-google.nse +++ b/scripts/ip-geolocation-map-google.nse @@ -1,6 +1,7 @@ local http = require "http" local geoip = require "geoip" local io = require "io" +local oops = require "oops" local stdnse = require "stdnse" local table = require "table" local url = require "url" @@ -167,21 +168,14 @@ postrule = function() end action = function() - local output = stdnse.output_table() - -- Parse and sanity check the command line arguments. - local status, params, options = parse_args() + local status, params, options = oops.raise( + "Script argument problem", + parse_args()) if not status then - output.ERROR = params - return output, output.ERROR + return params end -- Render the map. - local status, msg = render(params, options) - if not status then - output.ERROR = msg - return output, output.ERROR - end - - return output, stdnse.format_output(true, msg) + return oops.output(render(params, options)) end diff --git a/scripts/ip-geolocation-map-kml.nse b/scripts/ip-geolocation-map-kml.nse index 9ba18bfed..bd18a38cf 100644 --- a/scripts/ip-geolocation-map-kml.nse +++ b/scripts/ip-geolocation-map-kml.nse @@ -1,5 +1,6 @@ local geoip = require "geoip" local io = require "io" +local oops = require "oops" local stdnse = require "stdnse" local table = require "table" @@ -76,21 +77,14 @@ postrule = function() end action = function() - local output = stdnse.output_table() - -- Parse and sanity check the command line arguments. - local status, path = parse_args() + local status, path = oops.raise( + "Script argument problem", + parse_args()) if not status then - output.ERROR = path - return output, output.ERROR + return path end -- Render the map. - local status, msg = render(path) - if not status then - output.ERROR = msg - return output, output.ERROR - end - - return msg + return oops.output(render(path)) end diff --git a/scripts/ip-geolocation-maxmind.nse b/scripts/ip-geolocation-maxmind.nse index 7290624e8..018529bcd 100644 --- a/scripts/ip-geolocation-maxmind.nse +++ b/scripts/ip-geolocation-maxmind.nse @@ -25,9 +25,15 @@ the commercial ones. -- -- @output -- | ip-geolocation-maxmind: --- | 74.207.244.221 (scanme.nmap.org) --- | coordinates (lat,lon): 39.4899,-74.4773 --- |_ city: Absecon, Philadelphia, PA, United States +-- | coordinates: 39.4899, -74.4773 +-- |_location: Absecon, Philadelphia, PA, United States +-- +-- @xmloutput +-- 39.4899 +-- -74.4773 +-- Absecon +-- Philadelphia, PA +-- United States -- -- @see ip-geolocation-geoplugin.nse -- @see ip-geolocation-ipinfodb.nse @@ -498,8 +504,13 @@ local GeoIP = { local loc = self:record_by_addr(addr) if not loc then return nil end geoip.add(addr, loc.latitude, loc.longitude) - setmetatable(loc, record_metatable) - return loc + local output = geoip.Location:new() + output:set_latitude(loc.latitude) + output:set_longitude(loc.longitude) + output:set_city(loc.city) + output:set_region(loc.metro_code) + output:set_country(loc.country_name) + return output end, record_by_addr=function(self,addr)