mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Note: Ndiff build will be broken until subsequent changes are made. Deprecation warnings will need to be addressed in future changes. Closes #2088
1407 lines
50 KiB
Python
1407 lines
50 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# This is an Nmap command line parser. It has two main parts:
|
|
#
|
|
# getopt_long_only_extras, which is like getopt_long_only with robust
|
|
# handling of unknown options.
|
|
#
|
|
# NmapOptions, a class representing a set of Nmap options.
|
|
#
|
|
# NmapOptions is the class for external use. NmapOptions.parse parses a list of
|
|
# a command followed by command-line arguments. NmapOptions.render returns a
|
|
# list of of a command followed by arguments. NmapOptions.parse_string and
|
|
# NmapOptions.render_string first split strings into lists, following certain
|
|
# quoting rules.
|
|
#
|
|
# >>> ops = NmapOptions()
|
|
# >>> ops.parse(["nmap", "-v", "--script", "safe", "localhost"])
|
|
# >>> ops.executable
|
|
# 'nmap'
|
|
# >>> ops.target_specs
|
|
# ['localhost']
|
|
# >>> ops["-v"]
|
|
# 1
|
|
# >>> ops["--script"]
|
|
# 'safe'
|
|
#
|
|
# The command line may be modified by accessing member variables:
|
|
#
|
|
# >>> ops.executable = "C:\Program Files\Nmap\nmap.exe"
|
|
# >>> ops["-v"] = 2
|
|
# >>> ops["-oX"] = "output.xml"
|
|
# >>> ops.render()
|
|
# ['C:\\Program Files\\Nmap\\nmap.exe', '-v', '-v', '-oX', 'output.xml',
|
|
# '--script', 'safe', 'localhost']
|
|
# >>> ops.render_string()
|
|
# '"C:\\Program Files\\Nmap\\nmap.exe" -v -v -oX output.xml\
|
|
# --script safe localhost'
|
|
#
|
|
# A primary design consideration was robust handling of unknown options. That
|
|
# gives this code a degree of independence from Nmap's own list of options. If
|
|
# an option is added to Nmap but not added here, that option is treated as an
|
|
# "extra," an uninterpreted string that is inserted verbatim into the option
|
|
# list. Because the unknown option may or may not take an argument, pains are
|
|
# taken to avoid interpreting any option ambiguously.
|
|
#
|
|
# Consider the following case, where -x is an unknown option:
|
|
# nmap -x -e eth0 scanme.nmap.org
|
|
# If -x, whatever it is, does not take an argument, it is equivalent to
|
|
# nmap -e eth0 scanme.nmap.org -x
|
|
# that is, a scan of scanme.nmap.org over interface eth0. But if it does take
|
|
# an argument, its argument is "-e", and the command line is the same as
|
|
# nmap eth0 scanme.nmap.org -x -e
|
|
# which is a scan of the two hosts eth0 and scanme.nmap.org, over the default
|
|
# interface. In either case scanme.nmap.org is a target but the other arguments
|
|
# are ambiguous. To resolve this, once an unknown option is found, all
|
|
# following arguments that can be interpreted ambiguously are removed with it
|
|
# and placed in the extras, with normal option processing resumed only when
|
|
# there is no more ambiguity. This ensures that such options maintain their
|
|
# relative order when rendered again to output. In this example "-x -e eth0"
|
|
# will always appear in that order, and the -e option will be uninterpreted.
|
|
#
|
|
# To add a new option, one should do the following:
|
|
# 1) Add a test case to the NmapOptionsTest::test_options() method for the new
|
|
# option and make sure it initially fails.
|
|
# 2) Add the new option to NmapOptions.SHORT_OPTIONS and/or
|
|
# NmapOptions.LONG_OPTIONS.
|
|
# 3) Add an appropriate case to NmapOptions::handle_result(). This should
|
|
# include a line something like
|
|
# self[opt] = True
|
|
# or, if the option has an argument 'arg':
|
|
# self[opt] = arg
|
|
# 4) Add an appropriate case to NmapOptions::render()
|
|
# This should include a check to make sure the option was set in
|
|
# handle_result:
|
|
# if self[opt]:
|
|
# or, if self[opt] contains arguments
|
|
# if self[opt] is not None:
|
|
# If the check passed, then opt should be added to opt_list.
|
|
# 5) Edit profile_editor.xml to display the new option in the GUI.
|
|
# 6) Depending on the option, one may need to edit
|
|
# get_option_check_auxiliary_widget in OptionBuilder.py.
|
|
# 7) Make sure the test case works now.
|
|
|
|
from functools import reduce
|
|
|
|
|
|
class option:
|
|
"""A single option, part of a pool of potential options. It's just a name
|
|
and a flag saying if the option takes no argument, if an argument is
|
|
optional, or if an argument is required."""
|
|
NO_ARGUMENT = 0
|
|
REQUIRED_ARGUMENT = 1
|
|
OPTIONAL_ARGUMENT = 2
|
|
|
|
def __init__(self, name, has_arg):
|
|
self.name = name
|
|
self.has_arg = has_arg
|
|
|
|
|
|
def split_quoted(s):
|
|
"""Like str.split, except that no splits occur inside quoted strings, and
|
|
quoted strings are unquoted."""
|
|
r = []
|
|
i = 0
|
|
while i < len(s) and s[i].isspace():
|
|
i += 1
|
|
while i < len(s):
|
|
part = []
|
|
while i < len(s) and not s[i].isspace():
|
|
c = s[i]
|
|
if c == "\"" or c == "'":
|
|
begin = c
|
|
i += 1
|
|
while i < len(s):
|
|
c = s[i]
|
|
if c == begin:
|
|
i += 1
|
|
break
|
|
elif c == "\\":
|
|
i += 1
|
|
if i < len(s):
|
|
c = s[i]
|
|
# Otherwise, ignore the error and leave the backslash
|
|
# at the end of the string.
|
|
part.append(c)
|
|
i += 1
|
|
else:
|
|
part.append(c)
|
|
i += 1
|
|
r.append("".join(part))
|
|
while i < len(s) and s[i].isspace():
|
|
i += 1
|
|
|
|
return r
|
|
|
|
|
|
def maybe_quote(s):
|
|
"""Return s quoted if it needs to be, otherwise unchanged."""
|
|
for c in s:
|
|
if c == "\"" or c == "\\" or c == "'" or c.isspace():
|
|
break
|
|
else:
|
|
return s
|
|
|
|
r = []
|
|
for c in s:
|
|
if c == "\"":
|
|
r.append("\\\"")
|
|
elif c == "\\":
|
|
r.append("\\\\")
|
|
else:
|
|
r.append(c)
|
|
|
|
return "\"" + "".join(r) + "\""
|
|
|
|
|
|
def join_quoted(l):
|
|
return " ".join([maybe_quote(x) for x in l])
|
|
|
|
|
|
def make_options(short_opts, long_opts):
|
|
"""Parse a short option specification string and long option tuples into a
|
|
list of option objects."""
|
|
options = []
|
|
for name, has_arg in long_opts:
|
|
options.append(option(name, has_arg))
|
|
|
|
while len(short_opts) > 0:
|
|
name = short_opts[0]
|
|
short_opts = short_opts[1:]
|
|
assert name != ":"
|
|
num_colons = 0
|
|
while len(short_opts) > 0 and short_opts[0] == ":":
|
|
short_opts = short_opts[1:]
|
|
num_colons += 1
|
|
if num_colons == 0:
|
|
has_arg = option.NO_ARGUMENT
|
|
elif num_colons == 1:
|
|
has_arg = option.REQUIRED_ARGUMENT
|
|
else:
|
|
has_arg = option.OPTIONAL_ARGUMENT
|
|
options.append(option(name, has_arg))
|
|
|
|
return options
|
|
|
|
lookup_option_cache = {}
|
|
|
|
|
|
def lookup_option(name, options):
|
|
"""Find an option with the given (possibly abbreviated) name. None is
|
|
returned if no options match or if the name is ambiguous (more than one
|
|
option matches with no exact match)."""
|
|
|
|
# This function turns out to be a huge bottleneck. Therefore we memoize it.
|
|
# We hash on the option name and the id of the options list, because lists
|
|
# aren't hashable. This means that the options list can't change after the
|
|
# first time you call this function, or you will get stale results. Turning
|
|
# the list into a tuple and hashing that is too slow.
|
|
cache_code = (name, id(options))
|
|
try:
|
|
return lookup_option_cache[cache_code]
|
|
except KeyError:
|
|
pass
|
|
|
|
# Nmap treats '_' the same as '-' in long option names.
|
|
def canonicalize_name(name):
|
|
return name.replace("_", "-")
|
|
|
|
name = canonicalize_name(name)
|
|
matches = [o for o in options
|
|
if canonicalize_name(o.name).startswith(name)]
|
|
if len(matches) == 0:
|
|
# No match.
|
|
lookup_option_cache[cache_code] = None
|
|
elif len(matches) == 1:
|
|
# Only one match--not an ambiguous abbreviation.
|
|
lookup_option_cache[cache_code] = matches[0]
|
|
else:
|
|
# More than one match--return only an exact match.
|
|
for match in matches:
|
|
if canonicalize_name(match.name) == name:
|
|
lookup_option_cache[cache_code] = match
|
|
break
|
|
else:
|
|
# No exact matches
|
|
lookup_option_cache[cache_code] = None
|
|
return lookup_option_cache[cache_code]
|
|
|
|
|
|
def split_option(cmd_arg, options):
|
|
"""Split an option into a name, argument (if any), and possible remainder.
|
|
It is not an error if the option does not include an argument even though
|
|
it is required; the caller must take the argument from the next
|
|
command-line argument. The remainder is what's left over after stripping a
|
|
single short option that doesn't take an argument. At most one of argument
|
|
and remainder will be non-None.
|
|
Examples:
|
|
>>> split_option("-v", [option("v", option.NO_ARGUMENT)])
|
|
('v', None, None)
|
|
>>> split_option("--min-rate",
|
|
... [option("min-rate", option.REQUIRED_ARGUMENT)])
|
|
('min-rate', None, None)
|
|
>>> split_option("--min-rate=100",
|
|
... [option("min-rate", option.REQUIRED_ARGUMENT)])
|
|
('min-rate', '100', None)
|
|
>>> split_option("-d9", [option("d", option.OPTIONAL_ARGUMENT)])
|
|
('d', '9', None)
|
|
>>> split_option("-AFn", [option("A", option.NO_ARGUMENT)])
|
|
('A', None, '-Fn')
|
|
>>> split_option("-Amin-rate", [option("A", option.NO_ARGUMENT)])
|
|
('A', None, '-min-rate')
|
|
"""
|
|
if cmd_arg.startswith("--"):
|
|
name = cmd_arg[2:]
|
|
index = name.find('=')
|
|
if index < 0:
|
|
arg = None
|
|
else:
|
|
name, arg = name[:index], name[index + 1:]
|
|
return name, arg, None
|
|
elif cmd_arg.startswith("-"):
|
|
name = cmd_arg[1:]
|
|
# Check for a lone -.
|
|
if name == "":
|
|
return name, None, None
|
|
# First see if it's really a long option (or a single short option).
|
|
index = name.find('=')
|
|
if index < 0:
|
|
arg = None
|
|
else:
|
|
name, arg = name[:index], name[index + 1:]
|
|
if lookup_option(name, options) is not None:
|
|
return name, arg, None
|
|
# No luck. Must be a short option.
|
|
name = cmd_arg[1]
|
|
option = lookup_option(name, options)
|
|
if option is None:
|
|
# An unknown short option. Return the whole thing.
|
|
return cmd_arg[1:], None, None
|
|
rest = cmd_arg[2:]
|
|
if rest == "":
|
|
return name, None, None
|
|
if option.has_arg == option.NO_ARGUMENT:
|
|
return name, None, "-" + rest
|
|
else:
|
|
return name, rest, None
|
|
else:
|
|
assert False, cmd_arg
|
|
|
|
|
|
def get_option(cmd_args, options):
|
|
"""Find and return the first option (plus a possible option argument) or
|
|
positional argument from the command-line option list in cmd_args. The
|
|
return value will have one of the following forms:
|
|
* a string, representing a positional argument;
|
|
* an (option, argument) pair (argument may be None);
|
|
* a (None, extra, ...) tuple, where extra, ... is a chain of an unknown
|
|
option and its following arguments that cannot be interpreted
|
|
unambiguously; or
|
|
* None, at the end of the option list."""
|
|
if len(cmd_args) == 0:
|
|
return None
|
|
cmd_arg = cmd_args.pop(0)
|
|
if cmd_arg == "--":
|
|
if len(cmd_args) == 0:
|
|
return None
|
|
# Grab the positional argument and replace the --.
|
|
name = cmd_args[0]
|
|
cmd_args[0] = "--"
|
|
return name
|
|
# A normal positional argument.
|
|
if not cmd_arg.startswith("-"):
|
|
return cmd_arg
|
|
name, arg, remainder = split_option(cmd_arg, options)
|
|
if remainder is not None:
|
|
cmd_args.insert(0, remainder)
|
|
option = lookup_option(name, options)
|
|
if option is None:
|
|
# Unrecognized option.
|
|
if arg is not None:
|
|
return (None, cmd_arg)
|
|
else:
|
|
extras = [None, cmd_arg]
|
|
# We found an unknown option but we have a problem--we don't know
|
|
# if it takes an argument or not. So what we do is, we simulate
|
|
# what would happen both if the option took and argument and if it
|
|
# didn't. The sync function does that by calling this function in a
|
|
# loop.
|
|
rest = sync(cmd_args[1:], cmd_args[:], options)
|
|
# rest is the part of the argument list that is the same whether or
|
|
# not the unknown option takes an argument. Put everything up until
|
|
# rest begins in the extras, then set cmd_args to rest.
|
|
extras += cmd_args[0:len(cmd_args) - len(rest)]
|
|
del cmd_args[0:len(cmd_args) - len(rest)]
|
|
return tuple(extras)
|
|
elif option.has_arg == option.NO_ARGUMENT and arg is not None:
|
|
# It has an arg but it shouldn't (like --send-ip=5). Treat it as
|
|
# an extra.
|
|
return (None, cmd_arg)
|
|
elif option.has_arg == option.REQUIRED_ARGUMENT and arg is None:
|
|
# An argument is required but not yet read.
|
|
if len(cmd_args) == 0:
|
|
# No more args. Treat it as an extra.
|
|
return (None, cmd_arg)
|
|
else:
|
|
arg = cmd_args.pop(0)
|
|
return (option.name, arg)
|
|
else:
|
|
return (option.name, arg)
|
|
|
|
|
|
def sync(a, b, options):
|
|
"""Given two command-line argument lists, incrementally get an option from
|
|
whichever is longer until both lists are equal. Return the resulting
|
|
list."""
|
|
while a != b:
|
|
if len(a) > len(b):
|
|
get_option(a, options)
|
|
else:
|
|
get_option(b, options)
|
|
return a
|
|
|
|
|
|
def getopt_long_only_extras(cmd_args, short_opts, long_opts):
|
|
"""This is a generator version of getopt_long_only that additionally has
|
|
robust handling of unknown options. Each of the items in the sequence it
|
|
yields will be one of the following:
|
|
* a string, representing a positional argument;
|
|
* an (option, argument) pair (argument may be None);
|
|
* a (None, extra, ...) tuple, where extra, ... is a chain of an unknown
|
|
option and its following arguments that cannot be interpreted
|
|
unambiguously; or
|
|
* None, at the end of the option list."""
|
|
options = make_options(short_opts, long_opts)
|
|
# get_option modifies its list of arguments in place. Don't modify the
|
|
# original list.
|
|
cmd_args_copy = cmd_args[:]
|
|
while True:
|
|
result = get_option(cmd_args_copy, options)
|
|
if result is None:
|
|
break
|
|
yield result
|
|
|
|
|
|
class NmapOptions(object):
|
|
SHORT_OPTIONS = "6Ab:D:d::e:Ffg:hi:M:m:nO::o:P:p:RrS:s:T:v::V"
|
|
LONG_OPTIONS = (
|
|
("allports", option.NO_ARGUMENT),
|
|
("append-output", option.NO_ARGUMENT),
|
|
("badsum", option.NO_ARGUMENT),
|
|
("data-length", option.REQUIRED_ARGUMENT),
|
|
("datadir", option.REQUIRED_ARGUMENT),
|
|
("debug", option.OPTIONAL_ARGUMENT),
|
|
("defeat-rst-ratelimit", option.NO_ARGUMENT),
|
|
("dns-servers", option.REQUIRED_ARGUMENT),
|
|
("exclude", option.REQUIRED_ARGUMENT),
|
|
("excludefile", option.REQUIRED_ARGUMENT),
|
|
("fuzzy", option.NO_ARGUMENT),
|
|
("help", option.NO_ARGUMENT),
|
|
("host-timeout", option.REQUIRED_ARGUMENT),
|
|
("iL", option.REQUIRED_ARGUMENT),
|
|
("iR", option.REQUIRED_ARGUMENT),
|
|
("iflist", option.NO_ARGUMENT),
|
|
("initial-rtt-timeout", option.REQUIRED_ARGUMENT),
|
|
("ip-options", option.REQUIRED_ARGUMENT),
|
|
("log-errors", option.NO_ARGUMENT),
|
|
("max-hostgroup", option.REQUIRED_ARGUMENT),
|
|
("max-os-tries", option.REQUIRED_ARGUMENT),
|
|
("max-parallelism", option.REQUIRED_ARGUMENT),
|
|
("max-rate", option.REQUIRED_ARGUMENT),
|
|
("max-retries", option.REQUIRED_ARGUMENT),
|
|
("max-rtt-timeout", option.REQUIRED_ARGUMENT),
|
|
("max-scan-delay", option.REQUIRED_ARGUMENT),
|
|
("min-hostgroup", option.REQUIRED_ARGUMENT),
|
|
("min-parallelism", option.REQUIRED_ARGUMENT),
|
|
("min-rate", option.REQUIRED_ARGUMENT),
|
|
("min-retries", option.REQUIRED_ARGUMENT),
|
|
("min-rtt-timeout", option.REQUIRED_ARGUMENT),
|
|
("mtu", option.REQUIRED_ARGUMENT),
|
|
("no-stylesheet", option.NO_ARGUMENT),
|
|
("oA", option.REQUIRED_ARGUMENT),
|
|
("oG", option.REQUIRED_ARGUMENT),
|
|
("oM", option.REQUIRED_ARGUMENT),
|
|
("oN", option.REQUIRED_ARGUMENT),
|
|
("oS", option.REQUIRED_ARGUMENT),
|
|
("oX", option.REQUIRED_ARGUMENT),
|
|
("open", option.NO_ARGUMENT),
|
|
("osscan-guess", option.NO_ARGUMENT),
|
|
("osscan-limit", option.NO_ARGUMENT),
|
|
("packet-trace", option.NO_ARGUMENT),
|
|
("port-ratio", option.REQUIRED_ARGUMENT),
|
|
("privileged", option.NO_ARGUMENT),
|
|
("randomize-hosts", option.NO_ARGUMENT),
|
|
("reason", option.NO_ARGUMENT),
|
|
("release-memory", option.NO_ARGUMENT),
|
|
("scan-delay", option.REQUIRED_ARGUMENT),
|
|
("scanflags", option.REQUIRED_ARGUMENT),
|
|
("sI", option.REQUIRED_ARGUMENT),
|
|
("script", option.REQUIRED_ARGUMENT),
|
|
("script-args", option.REQUIRED_ARGUMENT),
|
|
("script-trace", option.NO_ARGUMENT),
|
|
("script-updatedb", option.NO_ARGUMENT),
|
|
("script-help", option.REQUIRED_ARGUMENT),
|
|
("send-eth", option.NO_ARGUMENT),
|
|
("send-ip", option.NO_ARGUMENT),
|
|
("servicedb", option.REQUIRED_ARGUMENT),
|
|
("source-port", option.REQUIRED_ARGUMENT),
|
|
("spoof-mac", option.REQUIRED_ARGUMENT),
|
|
("stylesheet", option.REQUIRED_ARGUMENT),
|
|
("system-dns", option.NO_ARGUMENT),
|
|
("timing", option.REQUIRED_ARGUMENT),
|
|
("top-ports", option.REQUIRED_ARGUMENT),
|
|
("traceroute", option.NO_ARGUMENT),
|
|
("ttl", option.REQUIRED_ARGUMENT),
|
|
("unprivileged", option.NO_ARGUMENT),
|
|
("verbose", option.OPTIONAL_ARGUMENT),
|
|
("version", option.NO_ARGUMENT),
|
|
("version-all", option.NO_ARGUMENT),
|
|
("version-intensity", option.REQUIRED_ARGUMENT),
|
|
("version-light", option.NO_ARGUMENT),
|
|
("version-trace", option.NO_ARGUMENT),
|
|
("versiondb", option.REQUIRED_ARGUMENT),
|
|
("webxml", option.NO_ARGUMENT),
|
|
)
|
|
|
|
# Sets of options that should be treated as equivalent from the point of
|
|
# view of the external interface. For example, ops["--timing"] means the
|
|
# same thing as ops["-T"].
|
|
EQUIVALENT_OPTIONS = (
|
|
("debug", "d"),
|
|
("help", "h"),
|
|
("iL", "i"),
|
|
("max-parallelism", "M"),
|
|
("osscan-guess", "fuzzy"),
|
|
("oG", "oM", "m"),
|
|
("oN", "o"),
|
|
("sP", "sn"),
|
|
("P", "PE", "PI"),
|
|
("PA", "PT"),
|
|
("P0", "PD", "PN", "Pn"),
|
|
("rH", "randomize-hosts"),
|
|
("source-port", "g"),
|
|
("timing", "T"),
|
|
("verbose", "v"),
|
|
("version", "V"),
|
|
)
|
|
EQUIVALENCE_MAP = {}
|
|
for set in EQUIVALENT_OPTIONS:
|
|
base = set[0]
|
|
aliases = set[1:]
|
|
for alias in aliases:
|
|
EQUIVALENCE_MAP[alias] = base
|
|
|
|
TIMING_PROFILE_NAMES = {
|
|
"paranoid": 0, "sneaky": 1, "polite": 2,
|
|
"normal": 3, "aggressive": 4, "insane": 5
|
|
}
|
|
|
|
def __init__(self):
|
|
self.options = make_options(self.SHORT_OPTIONS, self.LONG_OPTIONS)
|
|
|
|
self.clear()
|
|
|
|
def clear(self):
|
|
self._executable = None
|
|
self.target_specs = []
|
|
self.extras = []
|
|
|
|
# This is the internal mapping of option names to values.
|
|
self.d = {}
|
|
|
|
def _set_executable(self, executable):
|
|
self._executable = executable
|
|
|
|
executable = property(lambda self: self._executable or "nmap",
|
|
_set_executable)
|
|
|
|
def canonicalize_name(self, name):
|
|
opt, arg, remainder = split_option(name, self.options)
|
|
assert remainder is None
|
|
if arg is None:
|
|
option = lookup_option(opt, self.options)
|
|
if option:
|
|
option = option.name
|
|
else:
|
|
option = opt
|
|
else:
|
|
option = name.lstrip("-")
|
|
option = NmapOptions.EQUIVALENCE_MAP.get(option, option)
|
|
return option
|
|
|
|
def __getitem__(self, key):
|
|
return self.d.get(self.canonicalize_name(key))
|
|
|
|
def __setitem__(self, key, value):
|
|
self.d[self.canonicalize_name(key)] = value
|
|
|
|
def setdefault(self, key, default):
|
|
return self.d.setdefault(self.canonicalize_name(key), default)
|
|
|
|
def handle_result(self, result):
|
|
if isinstance(result, str):
|
|
# A positional argument.
|
|
self.target_specs.append(result)
|
|
return
|
|
elif result[0] is None:
|
|
# An unknown option.
|
|
self.extras.extend(result[1:])
|
|
return
|
|
|
|
# A normal option.
|
|
opt, arg = result
|
|
if opt in ("6", "A", "F", "h", "n", "R", "r", "V"):
|
|
self["-" + opt] = True
|
|
elif opt in (
|
|
"allports",
|
|
"append-output",
|
|
"badsum",
|
|
"defeat-rst-ratelimit",
|
|
"fuzzy",
|
|
"help",
|
|
"iflist",
|
|
"log-errors",
|
|
"no-stylesheet",
|
|
"open",
|
|
"osscan-guess",
|
|
"osscan-limit",
|
|
"packet-trace",
|
|
"privileged",
|
|
"randomize-hosts",
|
|
"reason",
|
|
"release-memory",
|
|
"script-trace",
|
|
"script-updatedb",
|
|
"send-eth",
|
|
"send-ip",
|
|
"system-dns",
|
|
"traceroute",
|
|
"unprivileged",
|
|
"version",
|
|
"version-all",
|
|
"version-light",
|
|
"version-trace",
|
|
"webxml",
|
|
):
|
|
self["--" + opt] = True
|
|
elif opt in ("b", "D", "e", "g", "i", "iL", "m", "M", "o", "oA", "oG",
|
|
"oM", "oN", "oS", "oX", "p", "S", "sI"):
|
|
assert arg is not None
|
|
if self["-" + opt] is None:
|
|
self["-" + opt] = arg
|
|
else:
|
|
self.extras.extend(("-" + opt, arg))
|
|
elif opt in (
|
|
"datadir",
|
|
"data-length",
|
|
"dns-servers",
|
|
"exclude",
|
|
"excludefile",
|
|
"host-timeout",
|
|
"initial-rtt-timeout",
|
|
"ip-options",
|
|
"max-hostgroup",
|
|
"max-os-tries",
|
|
"max-parallelism",
|
|
"max-rate",
|
|
"max-retries",
|
|
"max-rtt-timeout",
|
|
"max-scan-delay",
|
|
"min-hostgroup",
|
|
"min-parallelism",
|
|
"min-rate",
|
|
"min-retries",
|
|
"min-rtt-timeout",
|
|
"mtu",
|
|
"port-ratio",
|
|
"scan-delay",
|
|
"scanflags",
|
|
"script",
|
|
"script-args",
|
|
"script-help",
|
|
"servicedb",
|
|
"source-port",
|
|
"spoof-mac",
|
|
"stylesheet",
|
|
"top-ports",
|
|
"ttl",
|
|
"versiondb",
|
|
"version-intensity",
|
|
):
|
|
assert arg is not None
|
|
if self["--" + opt] is None:
|
|
self["--" + opt] = arg
|
|
else:
|
|
self.extras.extend(("--" + opt, arg))
|
|
elif opt == "d" or opt == "debug":
|
|
if arg is None:
|
|
arg = ""
|
|
try:
|
|
self["-d"] = int(arg)
|
|
except ValueError:
|
|
if reduce(lambda x, y: x and y,
|
|
[z == "d" for z in arg], True):
|
|
self.setdefault("-d", 0)
|
|
self["-d"] += len(arg) + 1
|
|
else:
|
|
self.extras.append("-d%s" % arg)
|
|
elif opt == "f":
|
|
self.setdefault("-f", 0)
|
|
self["-f"] += 1
|
|
elif opt == "iR":
|
|
if self["-iR"] is None:
|
|
try:
|
|
self["-iR"] = int(arg)
|
|
except ValueError:
|
|
self.extras.extend(("-iR", arg))
|
|
else:
|
|
self.extras.extend(("-iR", arg))
|
|
elif opt == "O":
|
|
if arg is None:
|
|
if self["-O"] is None:
|
|
self["-O"] = True
|
|
else:
|
|
self.extras.append("-O")
|
|
else:
|
|
if self["-O"] is None:
|
|
self["-O"] = arg
|
|
else:
|
|
self.extras.append("-O%s" % arg)
|
|
elif opt == "P":
|
|
type, ports = arg[:1], arg[1:]
|
|
if (type == "0" or type == "D" or type == "N" or
|
|
type == "n" and ports == ""):
|
|
self["-Pn"] = True
|
|
elif (type == "" or type == "I" or type == "E") and ports == "":
|
|
self["-PE"] = True
|
|
elif type == "M" and ports == "":
|
|
self["-PM"] = True
|
|
elif type == "P" and ports == "":
|
|
self["-PP"] = True
|
|
elif type == "R" and ports == "":
|
|
self["-PR"] = True
|
|
elif type == "S":
|
|
self["-PS"] = ports
|
|
elif type == "T" or type == "A":
|
|
self["-PA"] = ports
|
|
elif type == "U":
|
|
self["-PU"] = ports
|
|
elif type == "O":
|
|
self["-PO"] = ports
|
|
elif type == "B":
|
|
self["-PB"] = ports
|
|
elif type == "Y":
|
|
self["-PY"] = ports
|
|
else:
|
|
self.extras.append("-P%s" % arg)
|
|
elif opt == "s":
|
|
for type in arg:
|
|
if type in "ACFLMNOPRSTUVWXYZn":
|
|
self["-s%s" % type] = True
|
|
else:
|
|
self.extras.append("-s%s" % type)
|
|
elif opt == "T" or opt == "timing":
|
|
if self["-T"] is None:
|
|
try:
|
|
self["-T"] = int(arg)
|
|
except ValueError:
|
|
try:
|
|
self["-T"] = self.TIMING_PROFILE_NAMES[arg.lower()]
|
|
except KeyError:
|
|
self.extras.extend(("-T", arg))
|
|
else:
|
|
self.extras.extend(("-T", arg))
|
|
elif opt == "v" or opt == "verbose":
|
|
if arg is None:
|
|
arg = ""
|
|
try:
|
|
self["-v"] = int(arg)
|
|
if self["-v"] == 0:
|
|
self["-v"] = -1
|
|
except ValueError:
|
|
if reduce(lambda x, y: x and y,
|
|
[z == "v" for z in arg], True):
|
|
self.setdefault("-v", 0)
|
|
self["-v"] += len(arg) + 1
|
|
else:
|
|
self.extras.append("-v%s" % arg)
|
|
else:
|
|
assert False, (opt, arg)
|
|
|
|
def parse(self, opt_list):
|
|
self.clear()
|
|
|
|
if len(opt_list) > 0:
|
|
self.executable = opt_list[0]
|
|
|
|
for result in getopt_long_only_extras(
|
|
opt_list[1:], self.SHORT_OPTIONS, self.LONG_OPTIONS):
|
|
self.handle_result(result)
|
|
|
|
def parse_string(self, opt_string):
|
|
self.parse(split_quoted(opt_string))
|
|
|
|
def render(self):
|
|
opt_list = []
|
|
|
|
for opt in ("-sA", "-sC", "-sF", "-sL", "-sM", "-sN", "-sO", "-sn",
|
|
"-sR", "-sS", "-sT", "-sU", "-sV", "-sW", "-sX", "-sY", "-sZ"):
|
|
if self[opt]:
|
|
opt_list.append(opt)
|
|
|
|
if self["-sI"] is not None:
|
|
opt_list.extend(("-sI", self["-sI"]))
|
|
|
|
for opt in ("-6",):
|
|
if self[opt]:
|
|
opt_list.append(opt)
|
|
|
|
if self["-p"] is not None:
|
|
opt_list.extend(("-p", self["-p"]))
|
|
|
|
if self["-T"] is not None:
|
|
opt_list.append("-T%s" % str(self["-T"]))
|
|
|
|
if self["-O"] is not None:
|
|
if isinstance(self["-O"], str):
|
|
opt_list.append("-O%s" % self["-O"])
|
|
elif self["-O"]:
|
|
opt_list.append("-O")
|
|
|
|
if self["-A"]:
|
|
opt_list.append("-A")
|
|
|
|
if self["-d"]:
|
|
if self["-d"] == 1:
|
|
opt_list.append("-d")
|
|
elif self["-d"] > 1:
|
|
opt_list.append("-d%s" % self["-d"])
|
|
|
|
if self["-f"]:
|
|
opt_list.extend(["-f"] * self["-f"])
|
|
if self["-v"]:
|
|
if self["-v"] == -1:
|
|
opt_list.append("-v0")
|
|
opt_list.extend(["-v"] * self["-v"])
|
|
|
|
if self["-F"]:
|
|
opt_list.append("-F")
|
|
if self["-n"]:
|
|
opt_list.append("-n")
|
|
|
|
if self["-iL"] is not None:
|
|
opt_list.extend(("-iL", self["-iL"]))
|
|
if self["-iR"] is not None:
|
|
opt_list.extend(("-iR", str(self["-iR"])))
|
|
|
|
for opt in ("-oA", "-oG", "-oN", "-oS", "-oX"):
|
|
if self[opt] is not None:
|
|
opt_list.extend((opt, self[opt]))
|
|
|
|
for opt in ("--min-hostgroup", "--max-hostgroup",
|
|
"--min-parallelism", "--max-parallelism",
|
|
"--min-rtt-timeout", "--max-rtt-timeout",
|
|
"--initial-rtt-timeout",
|
|
"--scan-delay", "--max-scan-delay",
|
|
"--min-rate", "--max-rate",
|
|
"--max-retries", "--max-os-tries", "--host-timeout"):
|
|
if self[opt] is not None:
|
|
opt_list.extend((opt, self[opt]))
|
|
|
|
for ping_option in ("-Pn", "-PE", "-PM", "-PP", "-PR"):
|
|
if self[ping_option]:
|
|
opt_list.append(ping_option)
|
|
for ping_option in ("-PS", "-PA", "-PU", "-PO", "-PY"):
|
|
if self[ping_option] is not None:
|
|
opt_list.append(ping_option + self[ping_option])
|
|
if self["-PB"] is not None:
|
|
if isinstance(self["-PB"], str):
|
|
opt_list.append("-PB" + self["-PB"])
|
|
elif self["-PB"]:
|
|
opt_list.append("-PB")
|
|
|
|
for opt in (
|
|
"--allports",
|
|
"--append-output",
|
|
"--badsum",
|
|
"--defeat-rst-ratelimit",
|
|
"--fuzzy",
|
|
"--help",
|
|
"--iflist",
|
|
"--log-errors",
|
|
"--no-stylesheet",
|
|
"--open",
|
|
"--osscan-guess",
|
|
"--osscan-limit",
|
|
"--packet-trace",
|
|
"--privileged",
|
|
"-r",
|
|
"-R",
|
|
"--randomize-hosts",
|
|
"--reason",
|
|
"--release-memory",
|
|
"--script-trace",
|
|
"--script-updatedb",
|
|
"--send-eth",
|
|
"--send-ip",
|
|
"--system-dns",
|
|
"--traceroute",
|
|
"--unprivileged",
|
|
"--version",
|
|
"--version-all",
|
|
"--version-light",
|
|
"--version-trace",
|
|
"--webxml",
|
|
):
|
|
if self[opt]:
|
|
opt_list.append(opt)
|
|
|
|
for opt in (
|
|
"-b",
|
|
"-D",
|
|
"--datadir",
|
|
"--data-length",
|
|
"--dns-servers",
|
|
"-e",
|
|
"--exclude",
|
|
"--excludefile",
|
|
"-g",
|
|
"--ip-options",
|
|
"--mtu",
|
|
"--port-ratio",
|
|
"-S",
|
|
"--scanflags",
|
|
"--script",
|
|
"--script-args",
|
|
"--script-help",
|
|
"--servicedb",
|
|
"--spoof-mac",
|
|
"--stylesheet",
|
|
"--top-ports",
|
|
"--ttl",
|
|
"--versiondb",
|
|
"--version-intensity",
|
|
):
|
|
if self[opt] is not None:
|
|
opt_list.extend((opt, self[opt]))
|
|
|
|
opt_list.extend(self.target_specs)
|
|
|
|
opt_list.extend(self.extras)
|
|
|
|
return [self.executable] + opt_list
|
|
|
|
def render_string(self):
|
|
return join_quoted(self.render())
|
|
|
|
import doctest
|
|
import unittest
|
|
|
|
|
|
class NmapOptionsTest(unittest.TestCase):
|
|
def test_clear(self):
|
|
"""Test that a new object starts without defining any options, that the
|
|
clear method removes all options, and that parsing the empty string or
|
|
an empty list removes all options."""
|
|
TEST = "nmap -T4 -A -v localhost --webxml"
|
|
ops = NmapOptions()
|
|
self.assertTrue(len(ops.render()) == 1)
|
|
ops.parse_string(TEST)
|
|
self.assertFalse(len(ops.render()) == 1)
|
|
ops.clear()
|
|
self.assertTrue(len(ops.render()) == 1)
|
|
ops.parse_string(TEST)
|
|
ops.parse_string("")
|
|
self.assertEqual(ops.render_string(), "nmap")
|
|
ops.parse_string(TEST)
|
|
ops.parse([])
|
|
self.assertEqual(ops.render_string(), "nmap")
|
|
|
|
def test_default_executable(self):
|
|
"""Test that there is a default executable member set."""
|
|
ops = NmapOptions()
|
|
self.assertNotNull(ops.executable)
|
|
|
|
def test_default_executable(self):
|
|
"""Test that you can set the executable."""
|
|
ops = NmapOptions()
|
|
ops.executable = "foo"
|
|
self.assertEqual(ops.executable, "foo")
|
|
self.assertEqual(ops.render(), ["foo"])
|
|
|
|
def test_render(self):
|
|
"""Test that the render method returns a list."""
|
|
TEST = "nmap -T4 -A -v localhost --webxml"
|
|
ops = NmapOptions()
|
|
ops.parse_string(TEST)
|
|
self.assertTrue(type(ops.render()) == list,
|
|
"type == %s" % type(ops.render))
|
|
|
|
def test_quoted(self):
|
|
"""Test that strings can be quoted."""
|
|
ops = NmapOptions()
|
|
|
|
ops.parse_string('nmap --script ""')
|
|
self.assertEqual(ops["--script"], "")
|
|
ops.parse_string("nmap --script ''")
|
|
self.assertEqual(ops["--script"], "")
|
|
|
|
ops.parse_string('nmap --script test one two three')
|
|
self.assertEqual(ops["--script"], "test")
|
|
self.assertEqual(ops.target_specs, ["one", "two", "three"])
|
|
ops.parse_string('nmap --script "test" one two three')
|
|
self.assertEqual(ops["--script"], "test")
|
|
self.assertEqual(ops.target_specs, ["one", "two", "three"])
|
|
ops.parse_string('nmap --script "test one" two three')
|
|
self.assertEqual(ops["--script"], "test one")
|
|
self.assertEqual(ops.target_specs, ["two", "three"])
|
|
ops.parse_string('nmap --script test" one" two three')
|
|
self.assertEqual(ops["--script"], "test one")
|
|
self.assertEqual(ops.target_specs, ["two", "three"])
|
|
ops.parse_string('nmap --script test" one"""" two" three')
|
|
self.assertEqual(ops["--script"], "test one two")
|
|
self.assertEqual(ops.target_specs, ["three"])
|
|
|
|
ops.parse_string("nmap --script test one two three")
|
|
self.assertEqual(ops["--script"], "test")
|
|
self.assertEqual(ops.target_specs, ["one", "two", "three"])
|
|
ops.parse_string("nmap --script 'test' one two three")
|
|
self.assertEqual(ops["--script"], "test")
|
|
self.assertEqual(ops.target_specs, ["one", "two", "three"])
|
|
ops.parse_string("nmap --script 'test one' two three")
|
|
self.assertEqual(ops["--script"], "test one")
|
|
self.assertEqual(ops.target_specs, ["two", "three"])
|
|
ops.parse_string("nmap --script test' one' two three")
|
|
self.assertEqual(ops["--script"], "test one")
|
|
self.assertEqual(ops.target_specs, ["two", "three"])
|
|
ops.parse_string("nmap --script test' one'''' two' three")
|
|
self.assertEqual(ops["--script"], "test one two")
|
|
self.assertEqual(ops.target_specs, ["three"])
|
|
|
|
ops.parse_string('nmap --script "ab\\\"cd"')
|
|
self.assertEqual(ops["--script"], "ab\"cd")
|
|
ops.parse_string('nmap --script "ab\\\\cd"')
|
|
self.assertEqual(ops["--script"], "ab\\cd")
|
|
ops.parse_string('nmap --script "ab\\\'cd"')
|
|
self.assertEqual(ops["--script"], "ab'cd")
|
|
ops.parse_string("nmap --script 'ab\\\"cd'")
|
|
self.assertEqual(ops["--script"], 'ab"cd')
|
|
|
|
ops.parse_string('nmap "--script" test')
|
|
self.assertEqual(ops["--script"], "test")
|
|
ops.parse_string("nmap '--script' test")
|
|
self.assertEqual(ops["--script"], "test")
|
|
|
|
ops.parse_string('"nmap foo" --script test')
|
|
self.assertEqual(ops.executable, "nmap foo")
|
|
ops.parse_string("'nmap foo' --script test")
|
|
self.assertEqual(ops.executable, "nmap foo")
|
|
|
|
def test_render_quoted(self):
|
|
"""Test that strings that need to be quoted are quoted."""
|
|
ops = NmapOptions()
|
|
ops.parse_string('"/path/ /nmap" --script "test one two three"')
|
|
self.assertEqual(ops.executable, "/path/ /nmap")
|
|
self.assertEqual(ops["--script"], "test one two three")
|
|
self.assertEqual(ops.target_specs, [])
|
|
s = ops.render_string()
|
|
ops.parse_string(s)
|
|
self.assertEqual(ops.executable, "/path/ /nmap")
|
|
self.assertEqual(ops["--script"], "test one two three")
|
|
self.assertEqual(ops.target_specs, [])
|
|
|
|
def test_end(self):
|
|
"""Test that -- ends argument processing."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap -v -- -v")
|
|
self.assertTrue(ops["-v"] == 1)
|
|
self.assertTrue(ops.target_specs == ["-v"])
|
|
|
|
def test_roundtrip(self):
|
|
"""Test that parsing and re-rendering a previous rendering gives the
|
|
same thing as the previous rendering."""
|
|
TESTS = (
|
|
"nmap",
|
|
"nmap -v",
|
|
"nmap -vv",
|
|
"nmap -d -v",
|
|
"nmap -d -d",
|
|
"nmap -d -v -d",
|
|
"nmap localhost",
|
|
"nmap -oX - 192.168.0.1 -PS10",
|
|
)
|
|
ops = NmapOptions()
|
|
for test in TESTS:
|
|
ops.parse_string(test)
|
|
opt_string_1 = ops.render_string()
|
|
ops.parse_string(opt_string_1)
|
|
opt_string_2 = ops.render_string()
|
|
self.assertEqual(opt_string_1, opt_string_2)
|
|
|
|
def test_underscores(self):
|
|
"""Test that underscores in option names are treated the same as
|
|
dashes (and are canonicalized to dashes)."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap --osscan_guess")
|
|
self.assertTrue("--osscan-guess" in ops.render_string())
|
|
|
|
def test_args(self):
|
|
"""Test potentially tricky argument scenarios."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap -d9")
|
|
self.assertTrue(len(ops.target_specs) == 0)
|
|
self.assertTrue(ops["-d"] == 9, ops["-d"])
|
|
ops.parse_string("nmap -d 9")
|
|
self.assertTrue(ops.target_specs == ["9"])
|
|
self.assertTrue(ops["-d"] == 1)
|
|
|
|
def test_repetition(self):
|
|
"""Test options that can be repeated to increase their effect."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap -vv")
|
|
self.assertTrue(ops["-v"] == 2)
|
|
ops.parse_string("nmap -v -v")
|
|
self.assertTrue(ops["-v"] == 2)
|
|
ops.parse_string("nmap -ff")
|
|
self.assertTrue(ops["-f"] == 2)
|
|
ops.parse_string("nmap -f -f")
|
|
self.assertTrue(ops["-f"] == 2)
|
|
# Note: unlike -d, -v doesn't take an optional numeric argument.
|
|
ops.parse_string("nmap -d2 -d")
|
|
self.assertTrue(ops["-d"] == 3)
|
|
|
|
def test_scan_types(self):
|
|
"""Test that multiple scan types given to the -s option are all
|
|
interpreted correctly."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap -s")
|
|
self.assertTrue(ops.extras == ["-s"])
|
|
ops.parse_string("nmap -sS")
|
|
self.assertTrue(ops.extras == [])
|
|
self.assertTrue(ops["-sS"])
|
|
self.assertTrue(not ops["-sU"])
|
|
ops.parse_string("nmap -sSU")
|
|
self.assertTrue(ops["-sS"])
|
|
self.assertTrue(ops["-sU"])
|
|
|
|
def test_extras(self):
|
|
"""Test that unknown arguments are correctly recorded. A few subtleties
|
|
are tested:
|
|
1. Unknown options are not simply discarded.
|
|
2. When an unknown option is found, any following arguments that could
|
|
have a different meaning depending on whether the unknown option
|
|
takes an argument are moved with the argument to the extras.
|
|
3. Any arguments moved to the extras are not otherwise interpreted.
|
|
4. Extra options so copied are copied in blocks, keeping their original
|
|
ordering with each block."""
|
|
ops = NmapOptions()
|
|
|
|
ops.parse_string("nmap --fee")
|
|
self.assertTrue(ops.extras == ["--fee"])
|
|
self.assertTrue(ops.render_string() == "nmap --fee")
|
|
|
|
# Note: -x is not a real Nmap option.
|
|
|
|
ops.parse_string("nmap -x")
|
|
self.assertTrue(ops.extras == ["-x"])
|
|
self.assertTrue(ops.render_string() == "nmap -x")
|
|
|
|
ops.parse_string("nmap -v --fie scanme.nmap.org -d")
|
|
self.assertTrue(ops.extras == ["--fie", "scanme.nmap.org"])
|
|
self.assertTrue(ops["-v"] == 1)
|
|
self.assertTrue(ops["-d"] == 1)
|
|
self.assertTrue(len(ops.target_specs) == 0)
|
|
|
|
ops.parse_string("nmap -v --foe=5 scanme.nmap.org -d")
|
|
self.assertTrue(ops.extras == ["--foe=5"])
|
|
self.assertTrue(ops.target_specs == ["scanme.nmap.org"])
|
|
|
|
ops.parse_string("nmap --fum -oX out.xml -v")
|
|
self.assertTrue(ops.extras == ["--fum", "-oX", "out.xml"])
|
|
self.assertTrue(ops["-v"] == 1)
|
|
|
|
ops.parse_string("nmap -x -A localhost")
|
|
self.assertTrue(ops.extras == ["-x", "-A"])
|
|
|
|
ops.parse_string("nmap -x --fee -A localhost")
|
|
self.assertTrue(ops.extras == ["-x", "--fee", "-A"])
|
|
|
|
ops.parse_string("nmap -x -x --timing 3 localhost")
|
|
self.assertTrue(ops.extras == ["-x", "-x", "--timing", "3"])
|
|
self.assertTrue(ops.target_specs == ["localhost"])
|
|
|
|
ops.parse_string("nmap -x -x --timing=3 localhost")
|
|
self.assertTrue(ops.extras == ["-x", "-x", "--timing=3"])
|
|
self.assertTrue(ops.target_specs == ["localhost"])
|
|
|
|
ops.parse_string("nmap -x -Ad9")
|
|
self.assertTrue(ops.extras == ["-x", "-Ad9"])
|
|
|
|
ops.parse_string("nmap -xrest")
|
|
self.assertTrue(ops.extras == ["-xrest"])
|
|
|
|
# Options that can't be given more than once should end up in extras.
|
|
ops.parse_string("nmap -p 53 -p 80 -O --mtu 50 --mtu 100 -O2")
|
|
self.assertTrue(ops["-p"] == "53")
|
|
self.assertTrue(ops["--mtu"] == "50")
|
|
self.assertTrue(ops["-O"])
|
|
self.assertTrue(ops.extras == ["-p", "80", "--mtu", "100", "-O2"])
|
|
|
|
def test_quirks(self):
|
|
"""Test the handling of constructions whose interpretation isn't
|
|
specified in documentation, but should match that of GNU getopt."""
|
|
ops = NmapOptions()
|
|
# Long options can be written with one dash.
|
|
ops.parse_string("nmap -min-rate 100")
|
|
self.assertTrue(ops["--min-rate"] == "100")
|
|
ops.parse_string("nmap -min-rate=100")
|
|
self.assertTrue(ops["--min-rate"] == "100")
|
|
|
|
# Short options not taking an argument can be followed by a long
|
|
# option.
|
|
ops.parse_string("nmap -nFmin-rate 100")
|
|
self.assertTrue(ops["-n"])
|
|
self.assertTrue(ops["-F"])
|
|
self.assertTrue(ops["--min-rate"] == "100")
|
|
|
|
# Short options taking an argument consume the rest of the argument.
|
|
ops.parse_string("nmap -nFp1-100")
|
|
self.assertTrue(ops["-n"])
|
|
self.assertTrue(ops["-F"])
|
|
self.assertTrue(ops["-p"] == "1-100")
|
|
|
|
def test_conversion(self):
|
|
"""Test that failed integer conversions cause the option to wind up in
|
|
the extras."""
|
|
ops = NmapOptions()
|
|
ops.parse_string("nmap -d#")
|
|
self.assertTrue(ops.extras == ["-d#"])
|
|
ops.parse_string("nmap -T monkeys")
|
|
self.assertTrue(ops["-T"] is None)
|
|
self.assertTrue(ops.extras == ["-T", "monkeys"])
|
|
ops.parse_string("nmap -iR monkeys")
|
|
self.assertTrue(ops["-iR"] is None)
|
|
self.assertTrue(ops.extras == ["-iR", "monkeys"])
|
|
|
|
def test_read_unknown(self):
|
|
"""Test that getting the value of non-options returns None."""
|
|
ops = NmapOptions()
|
|
self.assertEqual(ops["-x"], None)
|
|
self.assertEqual(ops["--nonoption"], None)
|
|
|
|
def test_canonical_option_names(self):
|
|
"""Test that equivalent option names are properly canonicalized, so
|
|
that ops["--timing"] and ops["-T"] mean the same thing, for example."""
|
|
EQUIVS = (
|
|
("--debug", "-d"),
|
|
("--help", "-h"),
|
|
("-iL", "-i"),
|
|
("--max-parallelism", "-M"),
|
|
("--osscan-guess", "--fuzzy"),
|
|
("-oG", "-oM", "-m"),
|
|
("-oN", "-o"),
|
|
("-sP", "-sn"),
|
|
("-P", "-PE", "-PI"),
|
|
("-PA", "-PT"),
|
|
("-P0", "-PD", "-PN", "-Pn"),
|
|
("--source-port", "-g"),
|
|
("--timing", "-T"),
|
|
("--verbose", "-v"),
|
|
("--version", "-V"),
|
|
("--min-rate", "-min-rate", "--min_rate", "-min_rate")
|
|
)
|
|
ops = NmapOptions()
|
|
for set in EQUIVS:
|
|
for opt in set:
|
|
ops.clear()
|
|
ops[opt] = "test"
|
|
for other in set:
|
|
self.assertTrue(ops[other] == "test",
|
|
"%s and %s not the same" % (opt, other))
|
|
|
|
def test_options(self):
|
|
"""Test that all options that are supposed to be supported are really
|
|
supported. They must be parsed and not as extras, and must produce
|
|
output on rendering that can be parsed again."""
|
|
TESTS = ["-" + opt for opt in "6AFfhnRrVv"]
|
|
TESTS += ["-b host", "-D 192.168.0.1,ME,RND", "-d", "-d -d", "-d2",
|
|
"-e eth0", "-f -f", "-g 53", "-i input.txt", "-M 100",
|
|
"-m output.gnmap", "-O", "-O2", "-o output.nmap", "-p 1-100",
|
|
"-S 192.168.0.1", "-T0", "-v -v"]
|
|
TESTS += ["-s" + opt for opt in "ACFLMNnOPRSTUVWXYZ"]
|
|
TESTS += ["-P" + opt for opt in "IEMP0NnDRBSTAUOY"]
|
|
TESTS += ["-P" + opt + "100" for opt in "STAUOY"]
|
|
TESTS += [
|
|
"--version",
|
|
"--verbose",
|
|
"--datadir=dir",
|
|
"--datadir dir",
|
|
"--servicedb=db",
|
|
"--servicedb db",
|
|
"--versiondb=db",
|
|
"--versiondb db",
|
|
"--debug",
|
|
"--debug=3",
|
|
"--debug 3",
|
|
"--help",
|
|
"--iflist",
|
|
"--release-memory",
|
|
"--max-os-tries=10",
|
|
"--max-os-tries 10",
|
|
"--max-parallelism=10",
|
|
"--min-parallelism 10",
|
|
"--timing=0",
|
|
"--timing 0",
|
|
"--max-rtt-timeout=10",
|
|
"--max-rtt-timeout 10",
|
|
"--min-rtt-timeout=10",
|
|
"--min-rtt-timeout 10",
|
|
"--initial-rtt-timeout=10",
|
|
"--initial-rtt-timeout 10",
|
|
"--excludefile=file",
|
|
"--excludefile file",
|
|
"--exclude=192.168.0.0",
|
|
"--exclude 192.168.0.0",
|
|
"--max-hostgroup=10",
|
|
"--max-hostgroup 10",
|
|
"--min-hostgroup=10",
|
|
"--min-hostgroup 10",
|
|
"--open",
|
|
"--scanflags=RST,ACK",
|
|
"--scanflags RST,ACK",
|
|
"--defeat-rst-ratelimit",
|
|
"--host-timeout=10",
|
|
"--host-timeout 10",
|
|
"--scan-delay=10",
|
|
"--scan-delay 10",
|
|
"--max-scan-delay=10",
|
|
"--max-scan-delay 10",
|
|
"--max-retries=10",
|
|
"--max-retries 10",
|
|
"--source-port=53",
|
|
"--source-port 53",
|
|
"--randomize-hosts",
|
|
"--osscan-limit",
|
|
"--osscan-guess",
|
|
"--fuzzy",
|
|
"--packet-trace",
|
|
"--version-trace",
|
|
"--data-length=10",
|
|
"--data-length 10",
|
|
"--send-eth",
|
|
"--send-ip",
|
|
"--stylesheet=style.xml",
|
|
"--stylesheet style.xml",
|
|
"--no-stylesheet",
|
|
"--webxml",
|
|
"--privileged",
|
|
"--unprivileged",
|
|
"--mtu=1500",
|
|
"--mtu 1500",
|
|
"--append-output",
|
|
"--spoof-mac=00:00:00:00:00:00",
|
|
"--spoof-mac 00:00:00:00:00:00",
|
|
"--badsum",
|
|
"--ttl=64",
|
|
"--ttl 64",
|
|
"--traceroute",
|
|
"--reason",
|
|
"--allports",
|
|
"--version-intensity=5",
|
|
"--version-intensity 5",
|
|
"--version-light",
|
|
"--version-all",
|
|
"--system-dns",
|
|
"--log-errors",
|
|
"--dns-servers=localhost",
|
|
"--dns-servers localhost",
|
|
"--port-ratio=0.5",
|
|
"--port-ratio 0.5",
|
|
"--top-ports=1000",
|
|
"--top-ports 1000",
|
|
"--script=script.nse",
|
|
"--script script.nse",
|
|
"--script-trace",
|
|
"--script-updatedb",
|
|
"--script-args=none",
|
|
"--script-args none",
|
|
"--script-help=script.nse",
|
|
"--script-help script.nse",
|
|
"--ip-options=S",
|
|
"--ip-options S",
|
|
"--min-rate=10",
|
|
"--min-rate 10",
|
|
"--max-rate=10",
|
|
"--max-rate 10",
|
|
"-iL=input.txt",
|
|
"-iL input.txt",
|
|
"-iR=1000",
|
|
"-iR 1000",
|
|
"-oA=out",
|
|
"-oA out",
|
|
"-oG=out.gnmap",
|
|
"-oG out.gnmap",
|
|
"-oM=out.gnmap",
|
|
"-oM out.gnmap",
|
|
"-oN=out.nmap",
|
|
"-oN out.nmap",
|
|
"-oS=out.skid",
|
|
"-oS out.skid",
|
|
"-oX=out.xml",
|
|
"-oX out.xml",
|
|
"-sI=zombie.example.com",
|
|
"-sI zombie.example.com",
|
|
]
|
|
|
|
# The following options are present in the Nmap source but are not
|
|
# tested for because they are deprecated or not documented or whatever.
|
|
# "-I",
|
|
# "--noninteractive",
|
|
# "--thc",
|
|
# "--nogcc",
|
|
# "-rH",
|
|
# "-ff",
|
|
# "-vv",
|
|
# "-oH",
|
|
|
|
ops = NmapOptions()
|
|
for test in TESTS:
|
|
ops.parse_string("nmap " + test)
|
|
opt_list_1 = ops.render()
|
|
self.assertTrue(len(opt_list_1) > 1, "%s missing on render" % test)
|
|
self.assertTrue(len(ops.extras) == 0,
|
|
"%s caused extras: %s" % (test, repr(ops.extras)))
|
|
ops.parse(opt_list_1)
|
|
opt_list_2 = ops.render()
|
|
self.assertTrue(opt_list_1 == opt_list_2,
|
|
"Result of parsing and rendering %s not parsable again" % (
|
|
test))
|
|
self.assertTrue(len(ops.extras) == 0,
|
|
"Result of parsing and rendering %s left extras: %s" % (
|
|
test, ops.extras))
|
|
|
|
|
|
class SplitQuotedTest(unittest.TestCase):
|
|
"""A unittest class that tests the split_quoted function."""
|
|
|
|
def test_split(self):
|
|
self.assertEqual(split_quoted(''), [])
|
|
self.assertEqual(split_quoted('a'), ['a'])
|
|
self.assertEqual(split_quoted('a b c'), 'a b c'.split())
|
|
|
|
def test_quotes(self):
|
|
self.assertEqual(split_quoted('a "b" c'), ['a', 'b', 'c'])
|
|
self.assertEqual(split_quoted('a "b c"'), ['a', 'b c'])
|
|
self.assertEqual(split_quoted('a "b c""d e"'), ['a', 'b cd e'])
|
|
self.assertEqual(split_quoted('a "b c"z"d e"'), ['a', 'b czd e'])
|
|
|
|
def test_backslash(self):
|
|
self.assertEqual(split_quoted('"\\""'), ['"'])
|
|
self.assertEqual(split_quoted('\\"\\""'), ['\\"'])
|
|
self.assertEqual(split_quoted('"\\"\\""'), ['""'])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
doctest.testmod()
|
|
unittest.main()
|