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 --- nselib/bjnp.lua | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 nselib/bjnp.lua (limited to 'nselib/bjnp.lua') diff --git a/nselib/bjnp.lua b/nselib/bjnp.lua new file mode 100644 index 0000000..0922d8d --- /dev/null +++ b/nselib/bjnp.lua @@ -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 +-- + +local nmap = require("nmap") +local os = require("os") +local stdnse = require("stdnse") +local table = require("table") +local string = require "string" +local stringaux = require "stringaux" + +_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 }) + + hdr.id, hdr.type, hdr.code, + hdr.seq, hdr.session, hdr.length = string.unpack(">c4BBI4I2I4", data) + return hdr + end, + + __tostring = function(self) + return string.pack(">c4BBI4I2I4", + 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) .. string.pack(">I4", 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 + if pos - 1 > #data - 2 then + return nil + end + local len, pos = string.unpack(">I2", data, pos) + identity.data = string.unpack("c" .. len - 2, data, pos) + return identity + 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 + if pos - 1 > #data - 2 then + return nil + end + local len, pos = string.unpack(">I2", data, pos) + identity.data = string.unpack("c" .. len - 2, data, pos) + return identity + 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; + -- timeout - the timeout in milliseconds for socket communication + -- bcast - 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 currently 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(stringaux.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; -- cgit v1.2.3