diff --git a/CHANGELOG b/CHANGELOG
index f0dc194a1..0c89c0d91 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added allseeingeye-info for gathering information from games
+ using this query protocol. A version detection probe was also
+ added. [Marin Maržić]
+
o [NSE] Add freelancer-info to gather information about the Freelancer
game server. Also added a related version detection probe and UDP
protocol payload for detecting the service. [Marin Maržić]
diff --git a/nmap-service-probes b/nmap-service-probes
index dec20c9b9..38cf1a781 100644
--- a/nmap-service-probes
+++ b/nmap-service-probes
@@ -12336,3 +12336,15 @@ ports 2302
match freelancer m|^\x00\x03\xf1\x26.{88}(.*)\0\0(?:.*?:){5}(.*)\0\0$|s p/Freelancer/ i/name: $P(1); description: $P(2)/
+# All-Seeing Eye service provided by some game servers for querying
+# the server's status
+# For more info on the protocol see:
+# http://int64.org/docs/gamestat-protocols/ase.html
+# http://aluigi.altervista.org/papers.htm#ase
+# http://sourceforge.net/projects/gameq/
+# (relevant files: games.ini, packets.ini, ase.php)
+Probe UDP ASE q|s|
+rarity 9
+ports 1258,2126,3123,12444,13200,23196,26000,27138,27244,27777,28138
+
+match allseeingeye m/^EYE1.(.*?)(?|\x02(\d)|\x03(\d{2})|\x04(\d{3})|\x05(\d{4})|\x06(\d{5}))/s p/All-Seeing Eye/ i/game: $1; port: $2/
diff --git a/scripts/allseeingeye-info.nse b/scripts/allseeingeye-info.nse
new file mode 100644
index 000000000..671010bbd
--- /dev/null
+++ b/scripts/allseeingeye-info.nse
@@ -0,0 +1,232 @@
+local comm = require "comm"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local string = require "string"
+local bit = require "bit"
+local stdnse = require "stdnse"
+
+description = [[
+Detects the All-Seeing Eye service. Provided by some game servers for
+querying the server's status.
+
+The All-Seeing Eye service can listen on a UDP port separate from the
+main game server port (usually game port + 123). On receiving a packet
+with the payload "s", it replies with various game server status info.
+
+When run as a version detection script (-sV), the script
+will report on the game name, version, actual port, and whether it has a
+password. When run explicitly (--script ase-info), the
+script will additionally report on the server name, game type, map name,
+current number of players, maximum number of players, player
+information, and various other information.
+
+For more info on the protocol see:
+http://int64.org/docs/gamestat-protocols/ase.html
+http://aluigi.altervista.org/papers.htm#ase
+http://sourceforge.net/projects/gameq/
+(relevant files: games.ini, packets.ini, ase.php)
+]]
+
+-- @usage
+-- nmap -sV
+-- @usage
+-- nmap -Pn -sU -sV --script allseeingeye-info -p
+--
+-- @output
+-- PORT STATE SERVICE REASON VERSION
+-- 27138/udp open allseeingeye udp-response All-Seeing Eye (game: chrome 1.2.0.0ww; port: 27015; no password)
+-- | ase-info:
+-- | game: chrome
+-- | port: 27015
+-- | server name: ChromeNet Server
+-- | game type: Team Death Match
+-- | map: Data/LevelsNet/Narrow/Narrow.map
+-- | version: 1.2.0.0ww
+-- | passworded: 0
+-- | num players: 2
+-- | max players: 16
+-- | settings:
+-- | Dedicated: No
+-- | Password Required: No
+-- | Time Limit: 30
+-- | Points Limit: 200 min.
+-- | Respawns Limit: unlimited
+-- | Respawn Delay: 10 sec.
+-- | Enemies Visible On Map: No
+-- | Available Inventory Room: Yes
+-- | Identify Enemy Players: No
+-- | Available Vehicles: Yes
+-- | Vehicle Respaws Limit: unlimited
+-- | Vehicle Respawn Delay: 30 sec.
+-- | Vehicle Auto Return Time: 90 sec.
+-- | Vehicles Visible On Map: Yes
+-- | Team Balance: Off
+-- | Friendly Fire: On
+-- | Friends Visible On Map: Yes
+-- | players:
+-- | player 0:
+-- | name: NoVoDondo
+-- | team: BLUE
+-- | skin:
+-- | score: 71
+-- | ping: 0
+-- | time:
+-- | player 1:
+-- | name: HeroX
+-- | team: RED
+-- | skin:
+-- | score: 0
+-- | ping: 11
+-- |_ time:
+--
+-- @xmloutput
+-- chrome
+-- 27015
+-- ChromeNet Server
+-- Team Death Match
+-- Data/LevelsNet/Narrow/Narrow.map
+-- 1.2.0.0ww
+-- 0
+-- 2
+-- 16
+--
+-- No
+-- No
+-- 30
+-- 200 min.
+-- unlimited
+-- 10 sec.
+-- No
+-- Yes
+-- No
+-- Yes
+-- unlimited
+-- 30 sec.
+-- 90 sec.
+-- Yes
+-- Off
+-- On
+-- Yes
+--
+--
+--
+-- NoVoDondo
+-- BLUE
+--
+-- 71
+-- 0
+--
+--
+--
+-- HeroX
+-- RED
+--
+-- 0
+-- 11
+--
+--
+--
+
+author = "Marin Maržić"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = { "discovery", "safe", "version" }
+
+-- Unpacks a string preceded by a 1-byte integer length
+-- (+1 for the length byte) from another string, starting
+-- at the given position.
+-- @param str the string from which to unpack
+-- @param pos the position in the string from which to start unpacking (position of the length byte)
+-- @return ret_pos the position after the last unpacked byte
+-- @return string the unpacked string
+local unpack_str = function(str, pos)
+ local ret_pos = pos + str:byte(pos)
+ return ret_pos, string.sub(str, pos + 1, ret_pos - 1)
+end
+
+portrule = shortport.version_port_or_service({1258,2126,3123,12444,13200,23196,26000,27138,27244,27777,28138}, "allseeingeye", "udp")
+
+action = function(host, port)
+ local status, data = comm.exchange(host, port.number, "s", { proto = "udp", timeout = 3000 })
+ if not status then
+ return
+ end
+
+ -- UDP port is open
+ nmap.set_port_state(host, port, "open")
+
+ if not string.match(data, "^EYE1") then
+ return
+ end
+
+ -- Detected; extract fields
+ local o = stdnse.output_table()
+ local pos = 5
+
+ pos, o["game"] = unpack_str(data, pos)
+ pos, o["port"] = unpack_str(data, pos)
+ pos, o["server name"] = unpack_str(data, pos)
+ pos, o["game type"] = unpack_str(data, pos)
+ pos, o["map"] = unpack_str(data, pos)
+ pos, o["version"] = unpack_str(data, pos)
+ pos, o["passworded"] = unpack_str(data, pos)
+ pos, o["num players"] = unpack_str(data, pos)
+ pos, o["max players"] = unpack_str(data, pos)
+
+ -- extract the key-value pairs
+ local kv = stdnse.output_table()
+ o["settings"] = kv
+ while data:byte(pos) ~= 1 do
+ local key, value
+ pos, key = unpack_str(data, pos)
+ pos, value = unpack_str(data, pos)
+ kv[key] = value
+ end
+ pos = pos + 1
+
+ -- extract player info
+ local players = stdnse.output_table()
+ o["players"] = players
+ local playernum = 0
+ while pos <= #data do
+ local flags = data:byte(pos)
+ pos = pos + 1
+
+ local player = stdnse.output_table()
+ if bit.band(flags, 1) ~= 0 then
+ pos, player.name = unpack_str(data, pos)
+ end
+ if bit.band(flags, 2) ~= 0 then
+ pos, player.team = unpack_str(data, pos)
+ end
+ if bit.band(flags, 4) ~= 0 then
+ pos, player.skin = unpack_str(data, pos)
+ end
+ if bit.band(flags, 8) ~= 0 then
+ pos, player.score = unpack_str(data, pos)
+ end
+ if bit.band(flags, 16) ~= 0 then
+ pos, player.ping = unpack_str(data, pos)
+ end
+ if bit.band(flags, 32) ~= 0 then
+ pos, player.time = unpack_str(data, pos)
+ end
+
+ players["player " .. playernum] = player
+ playernum = playernum + 1
+ end
+
+ port.version.name = "ase"
+ port.version.name_confidence = 10
+ port.version.product = "All-Seeing Eye"
+ local passworded_string
+ if o["passworded"] == "0" then
+ passworded_string = "; no password"
+ else
+ passworded_string = "; has password"
+ end
+ port.version.extrainfo = "game: " .. o["game"] .. " " .. o["version"] .. "; port: " .. o["port"] .. passworded_string
+
+ nmap.set_port_version(host, port, "hardmatched")
+
+ return o
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 3c4f99c55..f3a58c7d0 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -10,6 +10,7 @@ Entry { filename = "ajp-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "ajp-headers.nse", categories = { "discovery", "safe", } }
Entry { filename = "ajp-methods.nse", categories = { "default", "safe", } }
Entry { filename = "ajp-request.nse", categories = { "discovery", "safe", } }
+Entry { filename = "allseeingeye-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "amqp-info.nse", categories = { "default", "discovery", "safe", "version", } }
Entry { filename = "asn-query.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }