From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- scripts/quake3-info.nse | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 scripts/quake3-info.nse (limited to 'scripts/quake3-info.nse') diff --git a/scripts/quake3-info.nse b/scripts/quake3-info.nse new file mode 100644 index 0000000..197137e --- /dev/null +++ b/scripts/quake3-info.nse @@ -0,0 +1,256 @@ +local comm = require "comm" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +local table = require "table" + +description = [[ +Extracts information from a Quake3 game server and other games which use the same protocol. +]] + +--- +-- @usage +-- nmap -sU -sV -Pn --script quake3-info.nse -p +-- +-- @output +-- PORT STATE SERVICE VERSION +-- 27960/udp open quake3 Quake 3 dedicated server +-- | quake3-info: +-- | PLAYERS: +-- | 1. cyberix (frags: 0/20, ping: 4) +-- | BASIC OPTIONS: +-- | capturelimit: 8 +-- | dmflags: 0 +-- | elimflags: 0 +-- | fraglimit: 20 +-- | gamename: baseoa +-- | mapname: oa_dm1 +-- | protocol: 71 +-- | timelimit: 0 +-- | version: ioq3 1.36+svn1933-1/Ubuntu linux-x86_64 Apr 4 2011 +-- | videoflags: 7 +-- | voteflags: 767 +-- | OTHER OPTIONS: +-- | bot_minplayers: 0 +-- | elimination_roundtime: 120 +-- | g_allowVote: 1 +-- | g_altExcellent: 0 +-- | g_delagHitscan: 0 +-- | g_doWarmup: 0 +-- | g_enableBreath: 0 +-- | g_enableDust: 0 +-- | g_gametype: 0 +-- | g_instantgib: 0 +-- | g_lms_mode: 0 +-- | g_maxGameClients: 0 +-- | g_needpass: 0 +-- | g_obeliskRespawnDelay: 10 +-- | g_rockets: 0 +-- | g_voteGametypes: /0/1/3/4/5/6/7/8/9/10/11/12/ +-- | g_voteMaxFraglimit: 0 +-- | g_voteMaxTimelimit: 0 +-- | g_voteMinFraglimit: 0 +-- | g_voteMinTimelimit: 0 +-- | sv_allowDownload: 0 +-- | sv_floodProtect: 1 +-- | sv_hostname: noname +-- | sv_maxPing: 0 +-- | sv_maxRate: 0 +-- | sv_maxclients: 8 +-- | sv_minPing: 0 +-- | sv_minRate: 0 +-- |_ sv_privateClients: 0 + +author = "Toni Ruottu" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"default", "discovery", "safe", "version"} + + +local function range(first, last) + local list = {} + for i = first, last do + table.insert(list, i) + end + return list +end + +portrule = shortport.version_port_or_service(range(27960, 27970), {'quake3'}, 'udp') + +local function parsefields(data) + local fields = {} + local parts = stringaux.strsplit("\\", data) + local nullprefix = table.remove(parts, 1) + if nullprefix ~= "" then + stdnse.debug2("unrecognized field format, skipping options") + return {} + end + for i = 1, #parts, 2 do + local key = parts[i] + local value = parts[i + 1] + fields[key] = value + end + return fields +end + +local function parsename(data) + local parts = stringaux.strsplit('"', data) + if #parts ~= 3 then + return nil + end + local e1 = parts[1] + local name = parts[2] + local e2 = parts[3] + local extra = e1 .. e2 + if extra ~= "" then + return nil + end + return name +end + +local function parseplayer(data) + local parts = stringaux.strsplit(" ", data) + if #parts < 3 then + stdnse.debug2("player info line is missing elements, skipping a player") + return nil + end + if #parts > 3 then + stdnse.debug2("player info line has unknown elements, skipping a player") + return nil + end + local player = {} + player.frags = parts[1] + player.ping = parts[2] + player.name = parsename(parts[3]) + if player.name == nil then + stdnse.debug2("invalid player name serialization, skipping a player") + return nil + end + return player +end + +local function parseplayers(data) + local players = {} + for _, p in ipairs(data) do + local player = parseplayer(p) + if player then + table.insert(players, player) + end + end + return players +end + +local function is_leader(a, b) + local collide = a.name == b.name + local even = a.frags == b.frags + local leads = a.frags > b.frags + local alphab = a.name > b.name + local faster = a.ping > b.ping + return leads or (even and alphab) or (even and collide and faster) +end + +local function formatplayers(players, fraglimit) + table.sort(players, is_leader) + local printable = {} + for i, player in ipairs(players) do + local name = player.name + local ping = player.ping + local frags = player.frags + if fraglimit then + frags = string.format("%s/%s", frags, fraglimit) + end + table.insert(printable, string.format("%d. %s (frags: %s, ping: %s)", i, name, frags, ping)) + end + printable["name"] = "PLAYERS:" + return printable +end + +local function formatfields(fields, title) + local printable = {} + for key, value in pairs(fields) do + local kv = string.format("%s: %s", key, value) + table.insert(printable, kv) + end + table.sort(printable) + printable["name"] = title + return printable +end + +local function assorted(fields) + local basic = {} + local other = {} + for key, value in pairs(fields) do + if string.find(key, "_") == nil then + basic[key] = value + else + other[key] = value + end + end + return basic, other +end + +action = function(host, port) + local GETSTATUS = "\xff\xff\xff\xffgetstatus\n" + local STATUSRESP = "\xff\xff\xff\xffstatusResponse" + + local status, data = comm.exchange(host, port, GETSTATUS, {["proto"] = "udp"}) + if not status then + return + end + local parts = stringaux.strsplit("\n", data) + local header = table.remove(parts, 1) + if header ~= STATUSRESP then + return + end + if #parts < 2 then + stdnse.debug2("incomplete status response, script abort") + return + end + local nullend = table.remove(parts) + if nullend ~= "" then + stdnse.debug2("missing terminating endline, script abort") + return + end + local field_data = table.remove(parts, 1) + local player_data = parts + + local fields = parsefields(field_data) + local players = parseplayers(player_data) + + local basic, other = assorted(fields) + + -- Previously observed version strings: + -- "tremulous 1.1.0 linux-x86_64 Aug  5 2010" + -- "ioq3 1.36+svn1933-1/Ubuntu linux-x86_64 Apr  4 2011" + local versionline = basic["version"] + if versionline then + local fields = stringaux.strsplit(" ", versionline) + local product = fields[1] + local version = fields[2] + local osline = fields[3] + port.version.name = "quake3" + port.version.product = product + port.version.version = version + if string.find(osline, "linux") then + port.version.ostype = "Linux" + end + if string.find(osline, "win") then + port.version.ostype = "Windows" + end + nmap.set_port_version(host, port) + end + + local fraglimit = fields["fraglimit"] + if not fraglimit then + fraglimit = "?" + end + + local response = {} + table.insert(response, formatplayers(players, fraglimit)) + table.insert(response, formatfields(basic, "BASIC OPTIONS:")) + if nmap.verbosity() > 0 then + table.insert(response, formatfields(other, "OTHER OPTIONS:")) + end + return stdnse.format_output(true, response) +end -- cgit v1.2.3