summaryrefslogtreecommitdiffstats
path: root/scripts/targets-xml.nse
blob: 8ebcd81cd5979c055752621a0cbeb2f809ce3a04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
local io = require "io"
local nmap = require "nmap"
local slaxml = require "slaxml"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local target = require "target"

description = [[
Loads addresses from an Nmap XML output file for scanning.

Address type (IPv4 or IPv6) is determined according to whether -6 is specified to nmap.
]]

---
--@args targets-xml.iX Filename of an Nmap XML file to import
--@args targets-xml.state Only hosts with this status will have their addresses
--                        input. Default: "up"
--
--@usage
-- nmap --script targets-xml --script-args newtargets,iX=oldscan.xml
--
--@output
--Pre-scan script results:
--|_targets-xml: Added 16 ipv4 addresses
--
--@xmloutput
--16

-- TODO: more filtering options: port status, string search, etc.

author = "Daniel Miller"
categories = {"safe"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"

local filename = stdnse.get_script_args(SCRIPT_NAME .. ".iX")

prerule = function ()
  if not filename then
    stdnse.verbose1("Need to supply a file name with the %s.iX argument", SCRIPT_NAME);
    return false
  end
  return true
end

local startElement = {
  host = function (state)
    state.addresses = {}
    state.up = nil
  end,
  status = function (state)
    state.parser._call.attribute = function (name, attribute)
      if name == "state" then
        state.up = attribute == state.status
      end
    end
  end,
  address = function (state)
    state.parser._call.attribute = function (name, attribute)
      if name == "addrtype" then
        state.valid = attribute == state.addrtype
      elseif name == "addr" then
        state.address = attribute
      end
    end
  end,
}

local closeElement = {
  host = function (state)
    if state.up then
      state.added = state.added + #state.addresses
      if target.ALLOW_NEW_TARGETS then
        target.add(table.unpack(state.addresses))
      end
    end
    state.up = nil
  end,
  status = function (state)
    state.parser._call.attribute = nil
  end,
  address = function (state)
    if state.valid and state.address then
      table.insert(state.addresses, state.address)
    end
    state.parser._call.attribute = nil
    state.address = nil
    state.valid = false
  end,
}

action = function ()
  local status = stdnse.get_script_args(SCRIPT_NAME .. ".state") or "up"
  local input, err = io.open(filename, "r")
  if not input then
    stdnse.debug1("Couldn't open %s: %s", filename, err)
    return nil
  end

  local state = {
    status = status,
    addrtype = "ipv4",
    added = 0,
  }
  if nmap.address_family() == "inet6" then
    state.addrtype = "ipv6"
  end

  state.parser = slaxml.parser:new({
      startElement = function (name)
        return startElement[name] and startElement[name](state) or nil
      end,
      closeElement = function (name)
        return startElement[name] and closeElement[name](state) or nil
      end,
    })

  local buf = ""
  local function next_chunk()
    local read, starts, ends
    repeat
      read = input:read(8192)
      if not read then
        return buf, true
      end
      starts, ends = string.find(read, ">.-$")
      if not starts then
        buf = buf .. read
      end
    until starts
    local ret = buf .. string.sub(read, 1, starts)
    buf = string.sub(read, starts+1)
    return ret, false
  end
  local chunk
  local eof = false
  while not eof do
    chunk, eof = next_chunk()
    state.parser:parseSAX(chunk)
  end
  return state.added, ("Found %s %s addresses"):format(state.added, state.addrtype)
end