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)