summaryrefslogtreecommitdiffstats
path: root/nselib/ipp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/ipp.lua')
-rw-r--r--nselib/ipp.lua438
1 files changed, 438 insertions, 0 deletions
diff --git a/nselib/ipp.lua b/nselib/ipp.lua
new file mode 100644
index 0000000..d3a7be2
--- /dev/null
+++ b/nselib/ipp.lua
@@ -0,0 +1,438 @@
+local datetime = require "datetime"
+local http = require "http"
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+local string = require "string"
+local tab = require "tab"
+local table = require "table"
+_ENV = stdnse.module("ipp", stdnse.seeall)
+
+---
+--
+-- A small CUPS ipp (Internet Printing Protocol) library implementation
+--
+-- @author Patrik Karlsson
+--
+
+-- The IPP layer
+IPP = {
+
+ StatusCode = {
+ OK = 0,
+ },
+
+ State = {
+ IPP_JOB_PENDING = 3,
+ IPP_JOB_HELD = 4,
+ IPP_JOB_PROCESSING = 5,
+ IPP_JOB_STOPPED = 6,
+ IPP_JOB_CANCELED = 7,
+ IPP_JOB_ABORTED = 8,
+ IPP_JOB_COMPLETED = 9,
+ },
+
+ StateName = {
+ [3] = "Pending",
+ [4] = "Held",
+ [5] = "Processing",
+ [6] = "Stopped",
+ [7] = "Canceled",
+ [8] = "Aborted",
+ [9] = "Completed",
+ },
+
+ OperationID = {
+ IPP_CANCEL_JOB = 0x0008,
+ IPP_GET_JOB_ATTRIBUTES = 0x0009,
+ IPP_GET_JOBS = 0x000a,
+ CUPS_GET_PRINTERS = 0x4002,
+ CUPS_GET_DOCUMENT = 0x4027
+ },
+
+ PrinterState = {
+ IPP_PRINTER_IDLE = 3,
+ IPP_PRINTER_PROCESSING = 4,
+ IPP_PRINTER_STOPPED = 5,
+ },
+
+ Attribute = {
+
+ IPP_TAG_OPERATION = 0x01,
+ IPP_TAG_JOB = 0x02,
+ IPP_TAG_END = 0x03,
+ IPP_TAG_PRINTER = 0x04,
+ IPP_TAG_INTEGER = 0x21,
+ IPP_TAG_ENUM = 0x23,
+ IPP_TAG_NAME = 0x42,
+ IPP_TAG_KEYWORD = 0x44,
+ IPP_TAG_URI = 0x45,
+ IPP_TAG_CHARSET = 0x47,
+ IPP_TAG_LANGUAGE = 0x48,
+
+ new = function(self, tag, name, value)
+ local o = { tag = tag, name = name, value = value }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ parse = function(data, pos)
+ local attrib = IPP.Attribute:new()
+ local val
+ attrib.tag, attrib.name, val, pos = string.unpack(">Bs2s2", data, pos)
+ attrib.value = {}
+ table.insert(attrib.value, { tag = attrib.tag, val = val })
+
+ repeat
+ local tag, name_len, val
+
+ if ( #data < pos + 3 ) then
+ break
+ end
+
+ tag, name_len, pos = string.unpack(">BI2", data, pos)
+ if ( name_len == 0 ) then
+ val, pos = string.unpack(">s2", data, pos)
+ table.insert(attrib.value, { tag = tag, val = val })
+ else
+ pos = pos - 3
+ end
+ until( name_len ~= 0 )
+
+ -- do minimal decoding
+ for i=1, #attrib.value do
+ if ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_INTEGER ) then
+ attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
+ elseif ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_ENUM ) then
+ attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
+ end
+ end
+
+ if ( 1 == #attrib.value ) then
+ attrib.value = attrib.value[1].val
+ end
+ --print(attrib.name, attrib.value, stdnse.tohex(val))
+
+ return pos, attrib
+ end,
+
+ __tostring = function(self)
+ if ( "string" == type(self.value) ) then
+ return string.pack(">Bs2s2", self.tag, self.name, self.value)
+ else
+ local data = {string.pack(">Bs2s2", self.tag, self.name, self.value[1].val)}
+ for i=2, #self.value do
+ data[#data+1] = string.pack(">BI2s2", self.value[i].tag, 0, self.value[i].val)
+ end
+ return table.concat(data)
+ end
+ end
+
+ },
+
+ -- An attribute group, groups several attributes
+ AttributeGroup = {
+
+ new = function(self, tag, attribs)
+ local o = { tag = tag, attribs = attribs or {} }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ addAttribute = function(self, attrib)
+ table.insert(self.attribs, attrib)
+ end,
+
+ --
+ -- Gets the first attribute matching name and optionally tag from the
+ -- attribute group.
+ --
+ -- @param name string containing the attribute name
+ -- @param tag number containing the attribute tag
+ getAttribute = function(self, name, tag)
+ for _, attrib in ipairs(self.attribs) do
+ if ( attrib.name == name ) then
+ if ( not(tag) ) then
+ return attrib
+ elseif ( tag and attrib.tag == tag ) then
+ return attrib
+ end
+ end
+ end
+ end,
+
+ getAttributeValue = function(self, name, tag)
+ for _, attrib in ipairs(self.attribs) do
+ if ( attrib.name == name ) then
+ if ( not(tag) ) then
+ return attrib.value
+ elseif ( tag and attrib.tag == tag ) then
+ return attrib.value
+ end
+ end
+ end
+ end,
+
+ __tostring = function(self)
+ local data = {string.pack("B", self.tag)}
+
+ for _, attrib in ipairs(self.attribs) do
+ data[#data+1] = tostring(attrib)
+ end
+ return table.concat(data)
+ end
+
+ },
+
+ -- The IPP request
+ Request = {
+
+ new = function(self, opid, reqid)
+ local o = {
+ version = 0x0101,
+ opid = opid,
+ reqid = reqid,
+ attrib_groups = {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ addAttributeGroup = function(self, group)
+ table.insert( self.attrib_groups, group )
+ end,
+
+ __tostring = function(self)
+ local data = {string.pack(">I2I2I4", self.version, self.opid, self.reqid )}
+
+ for _, group in ipairs(self.attrib_groups) do
+ data[#data+1] = tostring(group)
+ end
+ data[#data+1] = string.pack("B", IPP.Attribute.IPP_TAG_END)
+ return table.concat(data)
+ end,
+
+ },
+
+ -- A class to handle responses from the server
+ Response = {
+
+ -- Creates a new instance of response
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ getAttributeGroups = function(self, tag)
+ local groups = {}
+ for _, v in ipairs(self.attrib_groups or {}) do
+ if ( v.tag == tag ) then
+ table.insert(groups, v)
+ end
+ end
+ return groups
+ end,
+
+ parse = function(data)
+ local resp = IPP.Response:new()
+ local pos
+
+ resp.version, resp.status, resp.reqid, pos = string.unpack(">I2I2I4", data)
+
+ resp.attrib_groups = {}
+ local group = nil
+ repeat
+ local tag = data:byte(pos, pos)
+
+ if ( tag == IPP.Attribute.IPP_TAG_OPERATION or
+ tag == IPP.Attribute.IPP_TAG_JOB or
+ tag == IPP.Attribute.IPP_TAG_PRINTER or
+ tag == IPP.Attribute.IPP_TAG_END ) then
+
+ if group then
+ table.insert(resp.attrib_groups, group)
+ end
+ if tag ~= IPP.Attribute.IPP_TAG_END then
+ group = IPP.AttributeGroup:new(tag)
+ else
+ group = nil
+ end
+ pos = pos + 1
+ else
+ if not group then
+ stdnse.debug2("Unexpected tag: %d", tag)
+ return
+ end
+ local attrib
+ pos, attrib = IPP.Attribute.parse(data, pos)
+ group:addAttribute(attrib)
+ end
+ until pos > #data
+
+ return resp
+ end,
+
+ },
+
+
+}
+
+HTTP = {
+
+ Request = function(host, port, request)
+ local headers = {
+ ['Content-Type'] = 'application/ipp',
+ ['User-Agent'] = 'CUPS/1.5.1',
+ }
+ port.version.service_tunnel = "ssl"
+ local http_resp = http.post(host, port, '/', { header = headers }, nil, tostring(request))
+ if ( http_resp.status ~= 200 ) then
+ return false, "Unexpected response from server"
+ end
+
+ local response = IPP.Response.parse(http_resp.body)
+ if ( not(response) ) then
+ return false, "Failed to parse response"
+ end
+
+ return true, response
+ end,
+
+}
+
+
+Helper = {
+
+ new = function(self, host, port, options)
+ local o = { host = host, port = port, options = options or {} }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ connect = function(self)
+ self.socket = nmap.new_socket()
+ self.socket:set_timeout(self.options.timeout or 10000)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ getPrinters = function(self)
+
+ local attribs = {
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ),
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en"),
+ }
+
+ local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs)
+ local request = IPP.Request:new(IPP.OperationID.CUPS_GET_PRINTERS, 1)
+ request:addAttributeGroup(ag)
+
+ local status, response = HTTP.Request( self.host, self.port, tostring(request) )
+ if ( not(response) ) then
+ return status, response
+ end
+
+ local printers = {}
+
+ for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_PRINTER)) do
+ local attrib = {
+ ["printer-name"] = "name",
+ ["printer-location"] = "location",
+ ["printer-make-and-model"] = "model",
+ ["printer-state"] = "state",
+ ["queued-job-count"] = "queue_count",
+ ["printer-dns-sd-name"] = "dns_sd_name",
+ }
+
+ local printer = {}
+ for k, v in pairs(attrib) do
+ if ( ag:getAttributeValue(k) ) then
+ printer[v] = ag:getAttributeValue(k)
+ end
+ end
+ table.insert(printers, printer)
+ end
+ return true, printers
+ end,
+
+ getQueueInfo = function(self, uri)
+ local uri = uri or ("ipp://%s/"):format(self.host.ip)
+
+ local attribs = {
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ),
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en-us"),
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_URI, "printer-uri", uri),
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "requested-attributes", {
+ -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-host-name"},
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobName"},
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobOwner"},
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-id" },
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-k-octets" },
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-name" },
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-state" },
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "printer-uri" },
+ -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-user-name" },
+ -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-state-message" },
+ -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" },
+ { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ),
+ IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "which-jobs", "not-completed" )
+ }
+
+ local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs)
+ local request = IPP.Request:new(IPP.OperationID.IPP_GET_JOBS, 1)
+ request:addAttributeGroup(ag)
+
+ local status, response = HTTP.Request( self.host, self.port, tostring(request) )
+ if ( not(response) ) then
+ return status, response
+ end
+
+ local results = {}
+ for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do
+ local uri = ag:getAttributeValue("printer-uri")
+ local printer = uri:match(".*/(.*)$") or "Unknown"
+ -- some jobs have multiple state attributes, so far the ENUM ones have been correct
+ local state = ag:getAttributeValue("job-state", IPP.Attribute.IPP_TAG_ENUM) or ag:getAttributeValue("job-state")
+ -- some jobs have multiple id tag, so far the INTEGER type have shown the correct ID
+ local id = ag:getAttributeValue("job-id", IPP.Attribute.IPP_TAG_INTEGER) or ag:getAttributeValue("job-id")
+ local attr = ag:getAttribute("time-at-creation")
+ local tm = ag:getAttributeValue("time-at-creation")
+ local size = ag:getAttributeValue("job-k-octets") .. "k"
+ local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName") or "Unknown"
+ local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner") or "Unknown"
+
+ results[printer] = results[printer] or {}
+ table.insert(results[printer], {
+ id = id,
+ time = datetime.format_timestamp(tm),
+ state = ( IPP.StateName[tonumber(state)] or "Unknown" ),
+ size = size,
+ owner = owner,
+ jobname = jobname })
+ end
+
+ local output = {}
+ for name, entries in pairs(results) do
+ local t = tab.new(5)
+ tab.addrow(t, "id", "time", "state", "size (kb)", "owner", "jobname")
+ for _, entry in ipairs(entries) do
+ tab.addrow(t, entry.id, entry.time, entry.state, entry.size, entry.owner, entry.jobname)
+ end
+ if ( 1<#t ) then
+ table.insert(output, { name = name, tab.dump(t) })
+ end
+ end
+
+ return output
+ end,
+
+ close = function(self)
+ return self.socket:close()
+ end,
+}
+
+return _ENV;