local comm = require "comm"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
description = [[
Gathers information from an IRC server.
It uses STATS, LUSERS, and other queries to obtain this information.
]]
---
-- @output
-- 6665/tcp open irc
-- | irc-info:
-- | server: asimov.freenode.net
-- | version: ircd-seven-1.1.3(20111112-b71671d1e846,charybdis-3.4-dev). asimov.freenode.net
-- | servers: 31
-- | ops: 36
-- | chans: 48636
-- | users: 84883
-- | lservers: 1
-- | lusers: 4350
-- | uptime: 511 days, 23:02:29
-- | source host: source.example.com
-- |_ source ident: NONE or BLOCKED
--@xmloutput
-- asimov.freenode.net
-- ircd-seven-1.1.3(20111112-b71671d1e846,charybdis-3.4-dev). asimov.freenode.net
-- 31
-- 36
-- 48636
-- 84883
-- 1
-- 4350
-- 511 days, 23:02:29
-- source.example.com
-- NONE or BLOCKED
author = "Doug Hoyte, Patrick Donnelly"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
portrule = shortport.port_or_service({6666,6667,6697,6679},{"irc","ircs"})
local banner_timeout = 60
local function random_nick ()
return stdnse.generate_random_string(9, "abcdefghijklmnopqrstuvwxyz")
end
function action (host, port)
local sd = nmap.new_socket()
local nick = random_nick()
local output = stdnse.output_table()
local sd, line = comm.tryssl(host, port, "USER nmap +iw nmap :Nmap Wuz Here\nNICK " .. nick .. "\n")
if not sd then return "Unable to open connection" end
-- set a healthy banner timeout
sd:set_timeout(banner_timeout * 1000)
local buf = stdnse.make_buffer(sd, "\r?\n")
while line do
stdnse.debug2("%s", line)
-- This one lets us know we've connected, pre-PONGed, and got a NICK
-- Start of MOTD, we'll take the server name from here
local info = line:match "^:([%w-_.]+) 375"
if info then
output.server = info
sd:send("LUSERS\nVERSION\nSTATS u\nWHO " .. nick .. "\nQUIT\n")
end
-- MOTD could be missing, we want to handle that scenario as well
info = line:match "^:([%w-_.]+) 422"
if info then
output.server = info
sd:send("LUSERS\nVERSION\nSTATS u\nWHO " .. nick .. "\nQUIT\n")
end
-- NICK already in use
info = line:match "^:([%w-_.]+) 433"
if info then
nick = random_nick()
sd:send("NICK " .. nick .. "\n")
end
info = line:match "^:([%w-_.]+) 433"
if info then
nick = random_nick()
sd:send("NICK " .. nick .. "\n")
end
-- PING/PONG
local dummy = line:match "^PING :(.*)"
if dummy then
sd:send("PONG :" .. dummy .. "\n")
end
-- Server version info
info = line:match "^:[%w-_.]+ 351 %w+ ([^:]+)"
if info then
output.version = info
end
-- Various bits of info
local users, invisible, servers = line:match "^:[%w-_.]+ 251 %w+ :There are (%d+) users and (%d+) invisible on (%d+) servers"
if users then
output.users = users + invisible
output.servers = servers
end
local users, servers = line:match "^:[%w-_.]+ 251 %w+ :There are (%d+) users and %d+ services on (%d+) servers"
if users then
output.users = users
output.servers = servers
end
info = line:match "^:[%w-_.]+ 252 %w+ (%d+) :"
if info then
output.ops = info
end
info = line:match "^:[%w-_.]+ 254 %w+ (%d+) :"
if info then
output.chans = info
end
-- efnet
local clients, servers = line:match "^:[%w-_.]+ 255 %w+ :I have (%d+) clients and (%d+) server"
if clients then
output.lusers = clients
output.lservers = servers
end
-- ircnet
local clients, servers = line:match "^:[%w-_.]+ 255 %w+ :I have (%d+) users, %d+ services and (%d+) server"
if clients then
output.lusers = clients
output.lservers = servers
end
local uptime = line:match "^:[%w-_.]+ 242 %w+ :Server Up (%d+ days, [%d:]+)"
if uptime then
output.uptime = uptime
end
local ident, host = line:match "^:[%w-_.]+ 352 %w+ %S+ (%S+) ([%w-_.]+)"
if ident then
if ident:find "^~" then
output["source ident"] = "NONE or BLOCKED"
else
output["source ident"] = ident
end
output["source host"] = host
end
local err = line:match "^ERROR :(.*)"
if err then
output.error = err
end
line = buf()
end
if output.server then
return output
else
return nil
end
end