postmodern/ruby-nmap

View on GitHub
lib/nmap/command.rb

Summary

Maintainability
D
2 days
Test Coverage
# frozen_string_literal: true

require 'command_mapper/command'

module Nmap
  #
  # ## Nmap options:
  #
  # ### Target Specifications:
  #
  # * `-iL path/to/file` - `nmap.target_file = "path/to/file"`
  # * `-iR 10` - `nmap.random_targets = 10`
  # * `--exclude host1 --exclude host2` - `nmap.exclude = ["host1", "host2"`
  # * `--excludefile path/to/file` - `nmap.exclude_file = "path/to/file"`
  #
  # ### Host Discovery:
  #
  # * `-sL` - `nmap.list = true`
  # * `-sn` - `nmap.ping = true`
  # * `-Pn` - `nmap.skip_discovery = true`
  # * `-PS` - `nmap.syn_discovery = [20..80, 443]`
  # * `-PA` - `nmap.ack_discovery = [20..80, 443]`
  # * `-PU` - `nmap.udp_discovery = [20..80, 443]`
  # * '-PY' - `nmap.sctp_init_ping = [20..80, 443]`
  # * `-PE` - `nmap.icmp_echo_discovery = true`
  # * `-PP` - `nmap.icmp_timestamp_discovery = true`
  # * `-PM` - `nmap.icmp_netmask_discovery = true`
  # * `-PO` - `nmap.ip_ping = [1, 2, 3, 4, ...]`
  # * `-PR` - `nmap.arp_ping = true`
  # * `--traceroute` - `nmap.traceroute = true`
  # * `-n` - `nmap.disable_dns = true`
  # * `-R` - `nmap.enable_dns = true`
  # * `--resolve-all` - `nmap.resolve_all = true`
  # * `--unique` - `nmap.unique = true`
  # * `--dns-servers nameserver1,nameserver2` - `nmap.dns_servers = ["nameserver1", "nameserver2"]`
  # * `--systems-dns` - `nmap.systems_dns = true`
  #
  # ### Port Scanning Techniques:
  #
  # * `-sS` - `nmap.syn_scan = true`
  # * `-sT` - `nmap.connect_scan = true`
  # * `-sU` - `nmap.udp_scan = true`
  # * `-sY` - `nmap.sctp_init_scan = true`
  # * `-sN` - `nmap.null_scan = true`
  # * `-sF` - `nmap.fin_scan = true`
  # * `-sX` - `nmap.xmas_scan = true`
  # * `-sA` - `nmap.ack_scan = true`
  # * `-sW` - `nmap.window_scan = true`
  # * `-sM` - `nmap.maimon_scan = true`
  # * `--scanflags` - `nmap.scan_flags = {syn: true, ack: true, rst: true}` / `nmap.scan_flags = [:syn, :ack, :rst]` / `nmap.scan_flags = 9` / `nmap.scan_flags = "SYNACKRST"`
  # * `-sZ` - `nmap.sctp_cookie_echo_scan = true`
  # * `-sI zombiehost:probeport` - `nmap.idle_scan = "zombiehost:probeport"`
  # * `-sO` - `nmap.ip_scan = true`
  # * `-b ftp.relay-host.com` - `nmap.ftp_bounce_scan = "ftp.relay-host.com"`
  #
  # ### Port Specification and Scan Order:
  #
  # * `-p 22,80,443,8000-9000` - `nmap.ports = [22, 80, 443, 8000..9000]`
  # * `--exclude-ports 1-20,1024-2000` - `nmap.exclude_ports = [1..20, 1024..2000]`
  # * `-F` - `nmap.fast = true`
  # * `-r` - `nmap.consecutively = true`
  # * `--top-ports 10` - `nmap.top_ports = 10`
  # * `--port-ratio 0.5` - `nmap.port_ratio = 0.5`
  #
  # ### Service/Version Detection:
  #
  # * `-sV` - `nmap.service_scan = true`
  # * `--allports` - `nmap.all_ports = true`
  # * `--version-intensity 9` - `nmap.version_intensity = 9`
  # * `--version-light` - `nmap.version_light = true`
  # * `--version-all` - `nmap.version_all = true`
  # * `--version-trace` - `nmap.version_trace = true`
  # * `-sR` - `nmap.rpc_scan = true`
  #
  # ### Script Scan:
  #
  # * `-sC` - `nmap.default_script = true`
  # * `--script script1,script2,script3` - `nmap.script = ["script1", "script2", "script3"]`
  # * `--script-args=arg1=value,arg2=value2` - `nmap.script_args = {arg1: `value1", arg2: "value2"}`
  # * `--script-args-file path/to/file` - `nmap.script_args_file = "path/to/file"`
  # * `--script-help script1,script2,script3` - `nmap.script_help = ["script1", "script2", "script3"]`
  # * `--script-trace` - `nmap.script_trace = true`
  # * `--script-updatedb` - `nmap.update_scriptdb = treu`
  #
  # ### OS Detection:
  #
  # * `-O` - `nmap.os_fingerprint = true`
  # * `--osscan-limit` - `nmap.limit_os_scan = true`
  # * `--osscan-guess` - `nmap.max_os_scan = true`
  #
  # ### Timing and Performance:
  #
  # * `--min-hostgroup 42` - `nmap.min_host_group = 42`
  # * `--max-hostgroup 42` - `nmap.max_host_group = 42`
  # * `--min-parallelism 42` - `nmap.min_parallelism = 42`
  # * `--max-parallelism 42` - `nmap.max_parallelism = 42`
  # * `--min-rtt-timeout 100ms` - `nmap.min_rtt_timeout = "100ms"`
  # * `--max-rtt-timeout 500ms` - `nmap.max_rtt_timeout = "500ms"`
  # * `--initial-rtt-timeout 100ms` - `nmap.initial_rtt_timeout = "100ms"`
  # * `--max-retries 4` - `nmap.max_retries = 4`
  # * `--host-timeout 10s` - `nmap.host_timeout = "10s"`
  # * `--script-timeout 10s` - `nmap.script_timeout = "10s"`
  # * `--scan-delay 1s` - `nmap.scan_delay = "1s"`
  # * `--max-scan-delay 42s` - `nmap.max_scan_delay = "42s"`
  # * `--min-rate 10` - `nmap.min_rate = 10`
  # * `--max-rate 100` - `nmap.max_rate = 100`
  # * `--defeat-rst-ratelimit` - `nmap.defeat_rst_ratelimit = true`
  # * `--defeat-icmp-ratelimit` - `nmap.defeat_icmp_ratelimit = true`
  # * `--nsock-engine kqueue` - `nmap.nsock_engine = :kqueue`
  # * `-T polite` - `nmap.timing_template = :polite`
  # * `-T0` - `nmap.paranoid_timing = true`
  # * `-T1` - `nmap.sneaky_timing = true`
  # * `-T2` - `nmap.polite_timing = true`
  # * `-T3` - `nmap.normal_timing = true`
  # * `-T4` - `nmap.aggressive_timing = true`
  # * `-T5` - `nmap.insane_timing = true`
  #
  # ### Firewall/IDS Evasion and Spoofing:
  #
  # * `-f` - `nmap.packet_fragments = true`
  # * `--mtu` - `nmap.mtu = true`
  # * `-D decoy1,decoy2` - `nmap.decoys = ["decoy1", "decoy2"]`
  # * `-S 8.8.8.8` - `nmap.spoof = "8.8.8.8"`
  # * `-e eth0` - `nmap.interface = "eth0"`
  # * `-g 1024` - `nmap.source_port = 1024`
  # * `--proxies proxy1,proxy2` - `nmap.proxies = ["proxy1", "proxy2"]`
  # * `--data AABBCCDDEEFF` - `nmap.data = "AABBCCDDEEFF"`
  # * `--data-string foobar` - `nmap.data_string = "foobar"`
  # * `--data-length 42` - `nmap.data_length = 42`
  # * `--ip-options T` - `nmap.ip_options = 'T'`
  # * `--ttl 42` - `nmap.ttl = 42`
  # * `--randomize-hosts` - `nmap.randomize_hosts = true`
  # * `--spoof-mac XX:XX:XX:XX:XX:XX` - `nmap.spoof_mac = "XX:XX:XX:XX:XX:XX"`
  # * `--badsum` - `nmap.bad_checksum = true`
  # * `--adler32` - `nmap.sctp_adler32 = true`
  #
  # ### Output:
  #
  # * `-oN path/to/file` - `nmap.output_normal = "path/to/file"`
  # * `-oX path/to/file` - `nmap.output_xml = "path/to/file"`
  # * `-oS path/to/file` - `nmap.output_skiddie = "path/to/file"`
  # * `-oG path/to/file` - `nmap.output_grepable = "path/to/file"`
  # * `-oA path/to/basename` - `nmap.output_all = "path/to/basename"`
  #
  # ### Verbosity and Debugging:
  #
  # * `-v` - `nmap.verbose = true`
  # * `-v3` - `nmap.verbose = 3`
  # * `-vv` - `nmap.extra_verbose = true`
  # * `-v0` - `nmap.quiet = true`
  # * `-d` - `nmap.debug = true`
  # * `-d9` - `nmap.debug = 9`
  # * `--reason` - `nmap.show_reason = true`
  # * `--stats-every 2s` - `nmap.stats_every = "2s"`
  # * `--packet-trace` - `nmap.show_packets = true`
  # * `--open` - `nmap.show_open_ports = true`
  # * `--iflist` - `nmap.show_interfaces = true`
  # * `--log-errors` - `nmap.show_log_errors = true`
  #
  # ### Miscellaneous Output:
  #
  # * `--append-output` - `nmap.append_output = true`
  # * `--resume` - `nmap.resume = true`
  # * `--stylesheet path/to/stylesheet.xsl` - `nmap.stylesheet = "path/to/stylesheet.xsl"`
  # * `--webxml` - `nmap.webxml = true`
  # * `--no-stylesheet` - `nmap.no_stylesheet = true`
  #
  # ### Misc:
  #
  # * `-6` - `nmap.ipv6 = true`
  # * `-A` - `nmap.all = true`
  # * `--datadir path/to/nmap/dir` - `nmap.nmap_datadir = "path/to/nmap/dir"`
  # * `--servicedb path/to/services.txt` - `nmap.servicedb = "path/to/services.txt"`
  # * `--versiondb path/to/versions.txt` - `nmap.versiondb = "path/to/versions.txt"`
  # * `--send-eth` - `nmap.send_eth = true`
  # * `--send-ip` - `nmap.send_ip = true`
  # * `--privileged` - `nmap.privileged = true`
  # * `--unprivileged` - `nmap.unprivileged = true`
  # * `--release-memory` - `nmap.release_memory = true`
  # * `--noninteractive` - `nmap.non_interactive = true`
  # * `-V` - `nmap.version = true`
  # * `-h` - `nmap.help = true`
  #
  # * `google.com 1.1.1.1 192.168.1-2.*` - `nmap.targets = ["google.com", "1.1.1.1", "192.168.1-2.*"]`
  #
  # @see http://nmap.org/book/man.html
  #
  class Command < CommandMapper::Command

    #
    # Represents a port number.
    #
    # @api private
    #
    class Port < CommandMapper::Types::Num

      # Regular expression that validates a port number.
      PORT_NUMBER_REGEXP = /[1-9][0-9]{0,3}|[1-5][0-9][0-9][0-9][0-9]|6[0-4][0-9][0-9][0-9]|65[0-4][0-9][0-9]|655[0-2][0-9]|6553[0-5]/

      # Regular expression that validates a service name.
      SERVICE_NAME_REGEXP = /[A-Za-z][A-Za-z0-9]*(?:[\/_-][A-Za-z0-9]+)*\*?/

      # Regular expression that validates either a port number or service name.
      PORT_REGEXP = /(?:#{PORT_NUMBER_REGEXP}|#{SERVICE_NAME_REGEXP})/

      # Regular expression that validates either a port number or service name.
      REGEXP = /\A#{PORT_REGEXP}\z/

      #
      # Initializes the port type.
      #
      def initialize
        super(range: 1..65535)
      end

      #
      # Validates the given port number value.
      #
      # @param [Object] value
      #   The value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is valid, or `false` and a validation error
      #   message if the value is not compatible.
      #
      def validate(value)
        case value
        when String
          if value =~ REGEXP
            return true
          else
            return [false, "must be a valid port number or service name (#{value.inspect})"]
          end
        else
          super(value)
        end
      end

      #
      # Formats the given value.
      #
      # @param [Integer, String] value
      #   The port number value to format.
      #
      # @return [String]
      #   The formatted port number.
      #
      def format(value)
        case value
        when String
          value
        else
          super(value)
        end
      end

    end

    #
    # Represents a port range.
    #
    # @api private
    #
    class PortRange < Port

      # Regular expression to validate either a port or a port range.
      PORT_RANGE_REGEXP = /(?:#{PORT_NUMBER_REGEXP})?-(?:#{PORT_NUMBER_REGEXP})?|#{PORT_REGEXP}/

      # Regular expression to validate either a port or a port range.
      REGEXP = /\A#{PORT_RANGE_REGEXP}\z/

      #
      # Validates the given port or port range value.
      #
      # @param [Object] value
      #   The port or port range value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is valid, or `false` and a validation error
      #   message if the value is not compatible.
      #
      def validate(value)
        case value
        when Range
          valid, message = super(value.begin)

          unless valid
            return [valid, message]
          end

          valid, message = super(value.end)

          unless valid
            return [valid, message]
          end

          return true
        when String
          if value =~ REGEXP
            return true
          else
            return [false, "must be a valid port number, port range, or service name (#{value.inspect})"]
          end
        else
          super(value)
        end
      end

      #
      # Formats the given port or port range value.
      #
      # @param [Range, Integer, String] value
      #   The port or port range value to format.
      #
      # @return [String]
      #   The formatted port or port range.
      #
      def format(value)
        case value
        when Range
          "#{value.begin}-#{value.end}"
        else
          super(value)
        end
      end

    end

    #
    # Represents a list of ports or port ranges.
    #
    # @api private
    #
    class PortRangeList < CommandMapper::Types::List

      # Regular expression for validating a port or port range.
      PORT_RANGE_REGEXP = PortRange::PORT_RANGE_REGEXP

      # Regular expression for validating an nmap port range.
      REGEXP = /\A(?:[TUS]:)?#{PORT_RANGE_REGEXP}(?:,(?:[TUS]:)?#{PORT_RANGE_REGEXP})*\z/

      #
      # Initializes the port range list type.
      #
      def initialize
        super(type: PortRange.new)
      end

      #
      # Validates the given port range list value.
      #
      # @param [Object] value
      #   The given port range list value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is valid, or `false` and a validation error
      #   message if the value is not compatible.
      #
      def validate(value)
        case value
        when Hash
          if value.empty?
            return [false, "cannot be empty"]
          end

          value.each do |protocol,ports|
            unless PROTOCOL_LETTERS.has_key?(protocol)
              return [false, "unknown protocol (#{protocol.inspect}) must be :tcp, :udp, or :sctp"]
            end

            valid, message = validate(ports)

            unless valid
              return [valid, message]
            end
          end

          return true
        when Range
          @type.validate(value)
        when String
          unless value =~ REGEXP
            return [false, "not a valid port range list (#{value.inspect})"]
          end

          return true
        else
          super(value)
        end
      end

      # Mapping of protocol names to single letters used in port range syntax.
      PROTOCOL_LETTERS = {
        tcp:  'T',
        udp:  'U',
        sctp: 'S'
      }

      #
      # Formats the given port range list value.
      #
      # @param [Hash, Range, String, Integer] value
      #   The port range list value.
      #
      # @return [String]
      #   The formatted port range list.
      #
      def format(value)
        case value
        when Hash
          # format a hash of protocols and port ranges
          value.map { |protocol,ports|
            letter = PROTOCOL_LETTERS.fetch(protocol)

            "#{letter}:#{format(ports)}"
          }.join(',')
        when Range
          # format an individual port range
          @type.format(value)
        when String
          # pass strings directly through
          value
        else
          super(value)
        end
      end

    end

    #
    # Represents a list of protocols.
    #
    # @api private
    #
    ProtocolList = PortRangeList

    #
    # Represents a unit of time.
    #
    # @api private
    #
    class Time < CommandMapper::Types::Str

      # Regular expression for validating a unit of time.
      REGEXP = /\A\d+(?:h|m|s|ms)?\z/

      #
      # Validates a time value.
      #
      # @param [String, Integer] value
      #   The time value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is considered valid, or false and a
      #   validation message if the value is not valid.
      #
      def validate(value)
        case value
        when Integer then true
        else
          valid, message = super(value)

          unless valid
            return [valid, message]
          end

          value = value.to_s

          unless value =~ REGEXP
            return [false, "must be a number and end with 'ms', 's', 'm', or 'h'"]
          end

          return true
        end
      end

    end

    #
    # Represents a hex string.
    #
    # @api private
    #
    class HexString < CommandMapper::Types::Str

      REGEXP = /\A(?:(?:0x)?[0-9A-F]+|(?:\\x[0-9A-F]{2})+)\z/

      #
      # Validates a hex string value.
      #
      # @param [String, #to_s] value
      #   The hex string value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is considered valid, or false and a
      #   validation message if the value is not valid.
      #
      def validate(value)
        valid, message = super(value)

        unless valid
          return [valid, message]
        end

        value = value.to_s

        unless value =~ REGEXP
          return [false, "must be of the format 0xAABBCCDDEEFF..., AABBCCDDEEFF..., or \\xAA\\xBB\\xCC\\xDD\\xEE\\xFF..."]
        end

        return true
      end

    end

    #
    # Represents one or more TCP scan flags.
    #
    # @api private
    #
    class ScanFlags < CommandMapper::Types::Str

      # Mapping of symbol scan flags to String values.
      FLAGS = {
        urg: 'URG',
        ack: 'ACK',
        psh: 'PSH',
        rst: 'RST',
        syn: 'SYN',
        fin: 'FIN'
      }

      # Regular expression to validate the given scan flags.
      REGEXP = /\A(?:\d+|(?:URG|ACK|PSH|RST|SYN|FIN)+)\z/

      #
      # Validates a scanflags value.
      #
      # @param [String, Hash{Symbol => Boolean}, #to_s] value
      #   The scanflags value to validate.
      #
      # @return [true, (false, String)]
      #   Returns true if the value is considered valid, or false and a
      #   validation message if the value is not valid.
      #
      def validate(value)
        case value
        when Hash
          if value.empty?
            return [false, "Hash value cannot be empty"]
          end

          unless value.keys.all? { |key| FLAGS.has_key?(key) }
            return [false, "Hash must only contain the keys :urg, :ack, :psh, :rst, :syn, or :fin"]
          end

          unless value.values.all? { |value| value == nil || value == false || value == true }
            return [false, "Hash must only contain the values true, false, or nil"]
          end

          return true
        when Array
          if value.empty?
            return [false, "Array value cannot be empty"]
          end

          unless value.all? { |flag| FLAGS.has_key?(flag) }
            return [false, "Array must only contain the values :urg, :ack, :psh, :rst, :syn, or :fin"]
          end

          return true
        else
          valid, message = super(value)

          unless valid
            return [valid, message]
          end

          value = value.to_s

          unless value =~ REGEXP
            return [false, "must only contain URG, ACK, PSH, RST, SYN, or FIN"]
          end

          return true
        end
      end

      #
      # Formats a scanflags value.
      #
      # @param [Hash{Symbol => Boolean}, Array<String>, #to_s] value
      #   The scanflags value to format.
      #
      # @return [String]
      #   The formatted scanflags value.
      #
      def format(value)
        case value
        when Hash
          string = String.new

          value.each do |key,value|
            string << FLAGS[key] if value
          end

          return string
        when Array
          string = String.new

          value.each do |flag|
            string << FLAGS[flag]
          end

          return string
        else
          super(value)
        end
      end

    end

    command 'nmap' do
      # TARGET SPECIFICATIONS:
      option '-iL', name: :target_file, value: {type: InputFile.new}
      option '-iR', name: :random_targets, value: {type: Num.new}
      option '--exclude', name: :exclude, value: {type: List.new}
      option '--excludefile', name: :exclude_file, value: {type: InputFile.new}

      # HOST DISCOVERY:
      option '-sL', name: :list
      option '-sn', name: :ping
      option '-Pn', name: :skip_discovery
      option '-PS', name: :syn_discovery,
                    value_in_flag: true,
                    value: {type: PortRangeList.new, required: false}
      option '-PA', name: :ack_discovery,
                    value_in_flag: true,
                    value: {type: PortRangeList.new, required: false}
      option '-PU', name: :udp_discovery,
                    value_in_flag: true,
                    value: {type: PortRangeList.new, required: false}
      option '-PY', name: :sctp_init_ping,
                    value_in_flag: true,
                    value: {type: PortRangeList.new, required: false}
      option '-PE', name: :icmp_echo_discovery
      option '-PP', name: :icmp_timestamp_discovery
      option '-PM', name: :icmp_netmask_discovery
      option '-PO', name: :ip_ping,
                    value_in_flag: true,
                    value: {type: ProtocolList.new, required: false}
      option '-PR', name: :arp_ping
      option '--traceroute', name: :traceroute
      option '-n', name: :disable_dns
      option '-R', name: :enable_dns
      option '--resolve-all'
      option '--unique'
      option '--dns-servers', value: {type: List.new}
      option '--system-dns'

      # PORT SCANNING TECHNIQUES:
      option '-sS', name: :syn_scan
      option '-sT', name: :connect_scan
      option '-sU', name: :udp_scan
      option '-sY', name: :sctp_init_scan
      option '-sN', name: :null_scan
      option '-sF', name: :fin_scan
      option '-sX', name: :xmas_scan
      option '-sA', name: :ack_scan
      option '-sW', name: :window_scan
      option '-sM', name: :maimon_scan
      option '--scanflags', name: :scan_flags, value: {type: ScanFlags.new}
      option '-sZ', name: :sctp_cookie_echo_scan
      option '-sI', name: :idle_scan, value: true
      option '-sO', name: :ip_scan
      option '-b', name: :ftp_bounce_scan, value: true

      # PORT SPECIFICATION AND SCAN ORDER:
      option '-p', name: :ports, value: {type: PortRangeList.new}
      option '--exclude-ports', value: {type: PortRangeList.new}
      option '-F', name: :fast
      option '-r', name: :consecutively
      option '--top-ports', value: {type: Num.new}
      option '--port-ratio', value: {type: Dec.new(range: 0.0..1.0)}

      # SERVICE/VERSION DETECTION:
      option '-sV', name: :service_scan
      option '--allports', name: :all_ports
      option '--version-intensity', value: {type: Num.new(range: 0..9)}
      option '--version-light'
      option '--version-all'
      option '--version-trace'
      option '-sR', name: :rpc_scan

      # SCRIPT SCAN:
      option '-sC', name: :default_script
      option '--script', value: {type: List.new}
      option '--script-args', value: {type: KeyValueList.new}
      option '--script-args-file', value: {type: InputFile.new}
      option '--script-help', value: {type: List.new}
      option '--script-trace'
      option '--script-updatedb', name: :update_scriptdb

      # OS DETECTION:
      option '-O', name: :os_fingerprint
      option '--osscan-limit', name: :limit_os_scan
      option '--osscan-guess', name: :max_os_scan
      option '--max-os-tries', name: :max_os_tries

      # TIMING AND PERFORMANCE:
      option '--min-hostgroup', name: :min_host_group, value: {type: Num.new}
      option '--max-hostgroup', name: :max_host_group, value: {type: Num.new}
      option '--min-parallelism', value: {type: Num.new}
      option '--max-parallelism', value: {type: Num.new}
      option '--min-rtt-timeout', value: {type: Time.new}
      option '--max-rtt-timeout', value: {type: Time.new}
      option '--initial-rtt-timeout', value: {type: Time.new}
      option '--max-retries', value: {type: Num.new}
      option '--host-timeout', value: {type: Time.new}
      option '--script-timeout', value: {type: Time.new}
      option '--scan-delay', value: {type: Time.new}
      option '--max-scan-delay', value: {type: Time.new}
      option '--min-rate', value: {type: Num.new}
      option '--max-rate', value: {type: Num.new}
      option '--defeat-rst-ratelimit'
      option '--defeat-icmp-ratelimit'
      option '--nsock-engine', value: {type: Enum[:iocp, :epoll, :kqueue, :poll, :select]}
      option '-T', name: :timing_template,
                   value: {type: Enum[:paranoid, :sneaky, :polite, :normal, :aggressive, :insane]}
      option '-T0', name: :paranoid_timing
      option '-T1', name: :sneaky_timing
      option '-T2', name: :polite_timing
      option '-T3', name: :normal_timing
      option '-T4', name: :aggressive_timing
      option '-T5', name: :insane_timing

      # FIREWALL/IDS EVASION AND SPOOFING:
      option '-f', name: :packet_fragments
      option '--mtu', value: {type: Num.new, required: false}
      option '-D', name: :decoys, value: {type: List.new}
      option '-S', name: :spoof, value: true
      option '-e', name: :interface, value: true
      option '-g', name: :source_port, value: {type: Port.new}
      option '--proxies', value: {type: List.new}
      option '--data', value: {type: HexString.new}
      option '--data-string', value: true
      option '--data-length', value: {type: Num.new}
      option '--ip-options', value: true
      option '--ttl', value: {type: Num.new}
      option '--randomize-hosts'
      option '--spoof-mac', value: true
      option '--badsum', name: :bad_checksum
      option '--adler32', name: :sctp_adler32

      # OUTPUT:
      option '-oN', name: :output_normal, value: true
      option '-oX', name: :output_xml, value: true
      option '-oS', name: :output_skiddie, value: true
      option '-oG', name: :output_grepable, value: true
      option '-oA', name: :output_all, value: true

      # Verbosity and Debugging:
      option '-v', name: :verbose,
                   value_in_flag: true,
                   value: {type: Num.new, required: false}
      option '-vv', name: :extra_verbose
      option '-v0', name: :quiet
      option '-d', name: :debug,
                   value_in_flag: true,
                   value: {type: Num.new, required: false}
      option '--reason', name: :show_reason
      option '--stats-every', value: {type: Time.new}
      option '--packet-trace', name: :show_packets
      option '--open', name: :show_open_ports
      option '--iflist', name: :show_interfaces
      option '--log-errors', name: :show_log_errors

      # Miscellaneous output:
      option '--append-output'
      option '--resume', value: true
      option '--stylesheet', value: true
      option '--webxml', name: :nmap_stylesheet
      option '--no-stylesheet'

      # MISC:
      option '-6', name: :ipv6
      option '-A', name: :all
      option '--datadir', name: :nmap_datadir, value: {type: InputDir.new}
      option '--servicedb', value: {type: InputFile.new}
      option '--versiondb', value: {type: InputFile.new}
      option '--send-eth'
      option '--send-ip'
      option '--privileged'
      option '--unprivileged'
      option '--release-memory'
      option '--noninteractive', name: :non_interactive
      option '-V', name: :version
      option '-h', name: :help

      argument :targets, required: false, repeats: true
    end

  end
end