1
0
mirror of https://github.com/nmap/nmap.git synced 2026-01-22 06:09:01 +00:00

add bjnp library and the scripts bjnp-discover and broadcast-bjnp-discover

commit d41a28813e4e4d26aeaab300ad30ad7c4116e37d
Merge: a45e4e2 23fc8f1
Author: Patrik Karlsson <patrik@cqure.net>
Date:   Sun Aug 5 20:53:04 2012 +0200

    Merge branch 'master' into bjnp

    Conflicts:
    	CHANGELOG

commit a45e4e2fd0c2579afc8d5b162bb5484327494b72
Author: Patrik Karlsson <patrik@cqure.net>
Date:   Sun Aug 5 20:44:19 2012 +0200

    add bjnp library and the scripts bjnp-discover and broadcast-bjnp-discover
This commit is contained in:
patrik
2012-08-05 18:55:40 +00:00
parent 36e2449a8a
commit fce517d4b8
5 changed files with 589 additions and 0 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added a BJNP library and the scripts broadcast-bjnp-discover and
bjnp-discover. [Patrik Karlsson]
o [NPING] Nping now prints out an error and exists when the user tries to use
the -p flag for a scan option where that is meaningless. [Sean Rivera]

363
nselib/bjnp.lua Normal file
View File

@@ -0,0 +1,363 @@
---
-- An implementation of the Canon BJNP protocol used to discover and query
-- Canon network printers and scanner devices.
--
-- The implementation is pretty much based on Wireshark decoded messages
-- the cups-bjnp implementation and the usual guesswork.
--
-- @author "Patrik Karlsson <patrik [at] cqure.net>"
--
local bin = require("bin")
local bit = require("bin")
local nmap = require("nmap")
local os = require("os")
local stdnse = require("stdnse")
local string = require("string")
local table = require("table")
_ENV = stdnse.module("bjnp", stdnse.seeall)
BJNP = {
-- The common BJNP header
Header = {
new = function(self, o)
o = o or {}
o = {
id = o.id or "BJNP",
type = o.type or 1,
code = o.code,
seq = o.seq or 1,
session = o.session or 0,
length = o.length or 0,
}
assert(o.code, "code argument required")
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local hdr = BJNP.Header:new({ code = -1 })
local pos
pos, hdr.id, hdr.type, hdr.code,
hdr.seq, hdr.session, hdr.length = bin.unpack(">A4CCISI", data)
return hdr
end,
__tostring = function(self)
return bin.pack(">ACCISI",
self.id,
self.type,
self.code,
self.seq,
self.session,
self.length
)
end
},
-- Scanner related code
Scanner = {
Code = {
DISCOVER = 1,
IDENTITY = 48,
},
Request = {
Discover = {
new = function(self)
local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.DISCOVER }) }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return tostring(self.header)
end,
},
Identity = {
new = function(self)
local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.IDENTITY, length = 4 }), data = 0 }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return tostring(self.header) .. bin.pack(">I", self.data)
end,
}
},
Response = {
Identity = {
new = function(self)
local o = {}
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local identity = BJNP.Scanner.Response.Identity:new()
identity.header = BJNP.Header.parse(data)
local pos = #tostring(identity.header) + 1
local pos, len = bin.unpack(">S", data, pos)
if ( len ) then
pos, identity.data = bin.unpack("A" .. len - 2, data, pos)
return identity
end
end,
}
}
},
-- Printer related code
Printer = {
Code = {
DISCOVER = 1,
IDENTITY = 48,
},
Request = {
Discover = {
new = function(self)
local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.DISCOVER }) }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return tostring(self.header)
end,
},
Identity = {
new = function(self)
local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.IDENTITY }) }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return tostring(self.header)
end,
}
},
Response = {
Identity = {
new = function(self)
local o = {}
setmetatable(o, self)
self.__index = self
return o
end,
parse = function(data)
local identity = BJNP.Printer.Response.Identity:new()
identity.header = BJNP.Header.parse(data)
local pos = #tostring(identity.header) + 1
local pos, len = bin.unpack(">S", data, pos)
if ( len ) then
pos, identity.data = bin.unpack("A" .. len - 2, data, pos)
return identity
end
end,
}
},
}
}
-- Helper class, the main script writer interface
Helper = {
-- Creates a new Helper instance
-- @param host table
-- @param port table
-- @param options table containing one or more of the following fields;
-- <code>timeout</code> - the timeout in milliseconds for socket communication
-- <code>bcast</code> - instructs the library that the host is a broadcast
-- address
-- @return o new instance of Helper
new = function(self, host, port, options)
local o = {
host = host, port = port, options = options or {}
}
o.options.timeout = o.options.timeout or 5000
setmetatable(o, self)
self.__index = self
return o
end,
-- Connects the socket to the device
-- This should always be called, regardless if the broadcast option is set
-- or not.
--
-- @return status, true on success, false on failure
-- @return err string containing the error message if status is false
connect = function(self)
self.socket = nmap.new_socket(( self.options.bcast and "udp" ))
self.socket:set_timeout(self.options.timeout)
if ( not(self.options.bcast) ) then
return self.socket:connect(self.host, self.port)
end
return true
end,
-- Discover network devices using either broadcast or unicast
-- @param packet discovery packet (printer or scanner)
-- @return status, true on success, false on failure
-- @return devices table containing discovered devices when status is true
-- errmsg string containing the error message when status is false
discoverDevice = function(self, packet)
if ( not(self.options.bcast) ) then
if ( not(self.socket:send(tostring(packet))) ) then
return false, "Failed to send request to server"
end
else
if ( not(self.socket:sendto(self.host, self.port, tostring(packet))) ) then
return false, "Failed to send request to server"
end
end
-- discover run in loop
local devices, tmp = {}, {}
local start = os.time()
while( true ) do
local status, data = self.socket:receive()
if ( not(status) or ( os.time() - start > ( self.options.timeout/1000 - 1 ) )) then
break
end
local status, _, _, rhost = self.socket:get_info()
tmp[rhost] = true
end
for host in pairs(tmp) do table.insert(devices, host) end
return true, ( self.options.bcast and devices or ( #devices > 0 and devices[1] ))
end,
-- Discover BJNP supporting scanners
discoverScanner = function(self)
return self:discoverDevice(BJNP.Scanner.Request.Discover:new())
end,
-- Discover BJNP supporting printers
discoverPrinter = function(self)
return self:discoverDevice(BJNP.Printer.Request.Discover:new())
end,
-- Gets a printer identity (additional information)
-- @param devtype string containing either the string printer or scanner
-- @return status, true on success, false on failure
-- @return attribs table containing device attributes when status is true
-- errmsg string containing the error message when status is false
getDeviceIdentity = function(self, devtype)
-- Were currenlty only decoding this as I don't know what the other cruft is
local attrib_names = {
["scanner"] = {
{ ['MFG'] = "Manufacturer" },
{ ['MDL'] = "Model" },
{ ['DES'] = "Description" },
{ ['CMD'] = "Command" },
},
["printer"] = {
{ ['MFG'] = "Manufacturer" },
{ ['MDL'] = "Model" },
{ ['DES'] = "Description" },
{ ['VER'] = "Firmware version" },
{ ['CMD'] = "Command" },
}
}
local identity
if ( "printer" == devtype ) then
identity = BJNP.Printer.Request.Identity:new()
elseif ( "scanner" == devtype ) then
identity = BJNP.Scanner.Request.Identity:new()
end
assert(not(self.options.bcast), "getIdentity is not supported for broadcast")
if ( not(self.socket:send(tostring(identity))) ) then
return false, "Failed to send request to server"
end
local status, data = self.socket:receive()
if ( not(status) ) then
return false, "Failed to receive response from server"
end
local identity
if ( "printer" == devtype ) then
identity = BJNP.Printer.Response.Identity.parse(data)
elseif ( "scanner" == devtype ) then
identity = BJNP.Scanner.Response.Identity.parse(data)
end
if ( not(identity) ) then
return false, "Failed to parse identity"
end
local attrs, kvps = {}, {}
for k, v in ipairs(stdnse.strsplit(";", identity.data)) do
local nm, val = v:match("^([^:]*):(.*)$")
if ( nm ) then kvps[nm] = val end
end
for _, attrib in ipairs(attrib_names[devtype]) do
local short, long = next(attrib)
if ( kvps[short] ) then
table.insert(attrs, ("%s: %s"):format(long, kvps[short]))
end
end
return true, attrs
end,
-- Retrieves information related to the printer
getPrinterIdentity = function(self)
return self:getDeviceIdentity("printer")
end,
-- Retrieves information related to the scanner
getScannerIdentity = function(self)
return self:getDeviceIdentity("scanner")
end,
-- Closes the connection
-- @return status, true on success, false on failure
-- @return errmsg string containing the error message when status is false
close = function(self)
return self.socket:close()
end
}
return _ENV;

49
scripts/bjnp-discover.nse Normal file
View File

@@ -0,0 +1,49 @@
description = [[
Retrievs printer or scanner information from a remote device supporting the
BJNP protocol. The protocol is known to be supported by network based Canon
devices.
]]
---
-- @usage
-- sudo nmap -sU -p 8611,8612 --script bjnp-discover <ip>
--
-- @output
-- PORT STATE SERVICE
-- 8611/udp open canon-bjnp1
-- | bjnp-discover:
-- | Manufacturer: Canon
-- | Model: MG5200 series
-- | Description: Canon MG5200 series
-- | Firmware version: 1.050
-- |_ Command: BJL,BJRaster3,BSCCe,NCCe,IVEC,IVECPLI
-- 8612/udp open canon-bjnp2
-- | bjnp-discover:
-- | Manufacturer: Canon
-- | Model: MG5200 series
-- | Description: Canon MG5200 series
-- |_ Command: MultiPass 2.1,IVEC
--
categories = {"safe", "discovery"}
local bjnp = require("bjnp")
local shortport = require("shortport")
local stdnse = require("stdnse")
portrule = shortport.portnumber({8611, 8612}, "udp")
action = function(host, port)
local helper = bjnp.Helper:new(host, port)
if ( not(helper:connect()) ) then
return "\n ERROR: Failed to connect to server"
end
local status, attrs
if ( port.number == 8611 ) then
status, attrs = helper:getPrinterIdentity()
else
status, attrs = helper:getScannerIdentity()
end
helper:close()
return stdnse.format_output(true, attrs)
end

View File

@@ -0,0 +1,172 @@
description = [[
Attempts to discover Canon devices (Printers/Scanners) supporting the BJNP
protocol. Discovery is performed by sending BJNP Discover requests to the
network broadcast address for both ports associated with the protocol.
The script then attempts to retrieve the model, version and some additional
information for all discovered devices.
]]
---
-- @usage
-- nmap --script broadcast-bjnp-discover
--
-- @output
-- | broadcast-bjnp-discover:
-- | 192.168.0.10
-- | Printer
-- | Manufacturer: Canon
-- | Model: MG5200 series
-- | Description: Canon MG5200 series
-- | Firmware version: 1.050
-- | Command: BJL,BJRaster3,BSCCe,NCCe,IVEC,IVECPLI
-- | Scanner
-- | Manufacturer: Canon
-- | Model: MG5200 series
-- | Description: Canon MG5200 series
-- |_ Command: MultiPass 2.1,IVEC
--
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"safe", "broadcast"}
local bjnp = require("bjnp")
local stdnse = require("stdnse")
local coroutine = require("coroutine")
local nmap = require("nmap")
local table = require("table")
local printer_port = { number = 8611, protocol = "udp"}
local scanner_port = { number = 8612, protocol = "udp"}
local arg_timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
prerule = function()
if ( nmap.address_family() ~= 'inet' ) then
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
return false
end
return true
end
local function identifyDevices(devices, devtype)
local result
local port = ( "printers" == devtype and printer_port or scanner_port )
for _, ip in ipairs(devices or {}) do
local helper = bjnp.Helper:new({ ip = ip }, port)
if ( helper:connect() ) then
local status, attrs
if ( "printers" == devtype ) then
status, attrs = helper:getPrinterIdentity()
end
if ( "scanners" == devtype ) then
status, attrs = helper:getScannerIdentity()
end
if ( status ) then
result = result or {}
result[ip] = attrs
end
end
helper:close()
end
return result
end
local function identifyScanners(scanners)
return identifyDevices(scanners, "scanners")
end
local function identifyPrinters(printers)
return identifyDevices(printers, "printers")
end
local function getKeys(devices)
local dupes = {}
local function iter()
for k, _ in pairs(devices) do
for k2, _ in pairs(devices[k]) do
if ( not(dupes[k2]) ) then
dupes[k2] = true
coroutine.yield(k2)
end
end
end
coroutine.yield(nil)
end
return coroutine.wrap(iter)
end
local function getPrinters(devices)
local condvar = nmap.condvar(devices)
local helper = bjnp.Helper:new( { ip = "255.255.255.255" }, printer_port, { bcast = true, timeout = arg_timeout } )
if ( not(helper:connect()) ) then
condvar "signal"
return
end
local status, printers = helper:discoverPrinter()
helper:close()
if ( status ) then
devices["printers"] = identifyPrinters(printers)
end
condvar "signal"
end
local function getScanners(devices)
local condvar = nmap.condvar(devices)
local helper = bjnp.Helper:new( { ip = "255.255.255.255" }, scanner_port, { bcast = true, timeout = arg_timeout } )
if ( not(helper:connect()) ) then
condvar "signal"
return
end
local status, scanners = helper:discoverScanner()
helper:close()
if ( status ) then
devices["scanners"] = identifyScanners(scanners)
end
condvar "signal"
end
action = function()
arg_timeout = ( arg_timeout and arg_timeout * 1000 or 5000)
local devices, result, threads = {}, {}, {}
local condvar = nmap.condvar(devices)
local co = stdnse.new_thread(getPrinters, devices)
threads[co] = true
co = stdnse.new_thread(getScanners, devices)
threads[co] = true
while(next(threads)) do
for t in pairs(threads) do
threads[t] = ( coroutine.status(t) ~= "dead" ) and true or nil
end
if ( next(threads) ) then
condvar "wait"
end
end
for ip in getKeys(devices) do
local result_part = {}
local printer = ( devices["printers"] and devices["printers"][ip] )
local scanner = ( devices["scanners"] and devices["scanners"][ip] )
if ( printer ) then
printer.name = "Printer"
table.insert(result_part, printer)
end
if ( scanner ) then
scanner.name = "Scanner"
table.insert(result_part, scanner)
end
if ( #result_part > 0 ) then
result_part.name = ip
table.insert(result, result_part)
end
end
if ( result ) then
return stdnse.format_output(true, result)
end
end

View File

@@ -21,8 +21,10 @@ Entry { filename = "bitcoin-getaddr.nse", categories = { "discovery", "safe", }
Entry { filename = "bitcoin-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "bitcoinrpc-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "bittorrent-discovery.nse", categories = { "discovery", "safe", } }
Entry { filename = "bjnp-discover.nse", categories = { "discovery", "safe", } }
Entry { filename = "broadcast-ataoe-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-avahi-dos.nse", categories = { "broadcast", "dos", "intrusive", "vuln", } }
Entry { filename = "broadcast-bjnp-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-db2-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dhcp-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }