diff --git a/README.md b/README.md
index 6bca5ca..0f1e769 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@ Commands:
grep
help
import
+ new
print
scan
```
@@ -95,6 +96,18 @@ Convert an nmap XML scan file to JSON:
$ ronin-nmap convert scan.xml scan.json
```
+Generate a new nmap scanner Ruby script:
+
+```shell
+$ ronin-nmap new scanner.rb --ports 22,80,443,8000-9000 --target example.com
+```
+
+Generate a new nmap XML parser script:
+
+```shell
+$ ronin-nmap new parser.rb --parser --xml-file path/to/nmap.xml --printing
+```
+
## Examples
Performing an `nmap` scan and returning the parsed nmap XML data:
diff --git a/data/templates/script.rb.erb b/data/templates/script.rb.erb
new file mode 100644
index 0000000..3fa6a00
--- /dev/null
+++ b/data/templates/script.rb.erb
@@ -0,0 +1,56 @@
+#!/usr/bin/env ruby
+
+require 'ronin/nmap'
+
+<%- if @script_type == :parser -%>
+<%- if @xml_file %>
+xml = Ronin::Nmap.parse(<%= @xml_file.inspect %>)
+<%- else -%>
+xml = Ronin::Nmap.parse(ARGV[0])
+<%- end -%>
+<%- else -%>
+xml = Ronin::Nmap.scan(
+<%- case @targets.length -%>
+<%- when 0 -%>
+ ARGV[0],
+<%- when 1 -%>
+ <%= @targets[0].inspect %>,
+<%- else -%>
+ <%= @targets.inspect %>,
+<%- end -%>
+<%- if @syn_scan -%>
+ syn_scan: true,
+<%- end -%>
+<%- if @ports -%>
+ ports: <%= @ports.inspect %>,
+<%- else -%>
+ # ports: [22, 80, 443, 8000..9000],
+<%- end -%>
+<%- if @xml_file -%>
+ xml_file: <%= @xml_file.inspect %>
+<%- else -%>
+ # xml_file: "path/to/file.xml"
+<%- end -%>
+)
+<%- end -%>
+<% if @print -%>
+
+xml.each_host do |host|
+ puts "[ #{host.ip} ]"
+
+ host.each_port do |port|
+ puts " #{port.number}/#{port.protocol}\t#{port.state}\t#{port.service}"
+
+ port.scripts.each do |id,script|
+ puts " [ #{id} ]"
+
+ script.output.each_line { |line| puts " #{line}" }
+ end
+ end
+end
+<%- end -%>
+<%- if @import -%>
+
+Ronin::DB.connect
+Ronin::Nmap::Importer.import(xml)
+<%- end -%>
diff --git a/gemspec.yml b/gemspec.yml
index 9176c51..64c5f94 100644
--- a/gemspec.yml
+++ b/gemspec.yml
@@ -26,6 +26,7 @@ generated_files:
- man/ronin-nmap-dump.1
- man/ronin-nmap-grep.1
- man/ronin-nmap-import.1
+ - man/ronin-nmap-new.1
- man/ronin-nmap-print.1
- man/ronin-nmap-scan.1
diff --git a/lib/ronin/nmap/cli/commands/new.rb b/lib/ronin/nmap/cli/commands/new.rb
new file mode 100644
index 0000000..61eabac
--- /dev/null
+++ b/lib/ronin/nmap/cli/commands/new.rb
@@ -0,0 +1,211 @@
+# frozen_string_literal: true
+#
+# ronin-nmap - A Ruby library for automating nmap and importing nmap scans.
+#
+# Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
+#
+# ronin-nmap is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ronin-nmap is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ronin-nmap. If not, see .
+#
+
+require 'ronin/nmap/cli/command'
+require 'ronin/nmap/root'
+
+require 'ronin/core/cli/generator'
+
+module Ronin
+ module Nmap
+ class CLI
+ module Commands
+ #
+ # Generates a new nmap ruby script.
+ #
+ # ## Usage
+ #
+ # ronin-nmap new [options] FILE
+ #
+ # ## Options
+ #
+ # --parser Generate a nmap XML parser script
+ # --scanner Generate a nmap scanner script
+ # --printing Adds additional printing of the nmap scan data
+ # --import Also import the nmap XML scan data
+ # --xml-file XML_FILE Sets the XML file to write to or parse
+ # -p {PORT | [PORT1]-[PORT2]},... Sets the port range to scan
+ # --ports
+ # --target TARGET Sets the targets to scan (Defaults: ARGV[0])
+ # -h, --help Print help information
+ #
+ # ## Arguments
+ #
+ # FILE The path to the new nmap ruby script.
+ #
+ # ## Examples
+ #
+ # ronin-nmap new scanner.rb --ports 22,80,443,8000-9000 --target example.com
+ # ronin-nmap new parser.rb --parser --xml-file path/to/nmap.xml --printing
+ #
+ class New < Command
+
+ include Core::CLI::Generator
+
+ template_dir File.join(ROOT,'data','templates')
+
+ usage '[options] FILE'
+
+ option :parser, desc: 'Generate a nmap XML parser script' do
+ @script_type = :parser
+ end
+
+ option :scanner, desc: 'Generate a nmap scanner script' do
+ @script_type = :scanner
+ end
+
+ option :printing, desc: 'Adds additional printing of the nmap scan data' do
+ @printing = true
+ end
+
+ option :import, desc: 'Also import the nmap XML scan data' do
+ @import = true
+ end
+
+ option :xml_file, value: {
+ type: String,
+ usage: 'XML_FILE'
+ },
+ desc: 'Sets the XML file to write to or parse' do |file|
+ @xml_file = file
+ end
+
+ option :ports, short: '-p',
+ value: {
+ type: String,
+ usage: '{PORT | [PORT1]-[PORT2]},...'
+ },
+ desc: 'Sets the port range to scan' do |ports|
+ @ports = parse_port_range(ports)
+ rescue ArgumentError => error
+ raise(OptionParser::InvalidArgument,error.message)
+ end
+
+ option :target, value: {
+ type: String,
+ usage: 'TARGET'
+ },
+ desc: 'Sets the targets to scan (Defaults: ARGV[0])' do |target|
+ @targets << target
+ end
+
+ argument :path, desc: 'The path to the new nmap ruby script'
+
+ description 'Generates a new nmap ruby script'
+
+ man_page 'ronin-nmap-new.1'
+
+ examples [
+ "scanner.rb --ports 22,80,443,8000-9000 --target example.com",
+ "parser.rb --parser --xml-file path/to/nmap.xml --printing"
+ ]
+
+ # The script type.
+ #
+ # @return [:scan, :parse]
+ attr_reader :script_type
+
+ # The optioanl XML file to write to or parse.
+ #
+ # @return [String, nil]
+ attr_reader :xml_file
+
+ # The optional ports to scan.
+ #
+ # @return [Array, "-", nil]
+ attr_reader :ports
+
+ # The targets to scan.
+ #
+ # @return [Array]
+ attr_reader :targets
+
+ #
+ # Initializes the `ronin-nmap new` command.
+ #
+ # @param [Hash{Symbol => Object}] kwargs
+ # Additional keyword arguments for the command.
+ #
+ def initialize(**kwargs)
+ super(**kwargs)
+
+ @script_type = :scan
+ @targets = []
+ end
+
+ #
+ # Runs the `ronin-nmap new` command.
+ #
+ # @param [String] file
+ # The path to the new nmap ruby script.
+ #
+ def run(file)
+ @directory = File.dirname(file)
+
+ mkdir @directory unless @directory == '.'
+
+ erb "script.rb.erb", file
+ chmod '+x', file
+ end
+
+ #
+ # Parses a port range.
+ #
+ # @param [String] ports
+ # The port range to parse.
+ #
+ # @return [Array, "-"]
+ # The parsed port range.
+ #
+ # @raise [ArgumentError]
+ # An invalid port range was given.
+ #
+ def parse_port_range(ports)
+ case ports
+ when '-' then '-'
+ else
+ ports.split(',').map do |port|
+ case port
+ when /\A\d+-\d+\z/
+ start, stop = port.split('-',2)
+
+ (start.to_i..stop.to_i)
+ when /\A\d+-\z/
+ start = port.chomp('-')
+
+ (start.to_i..)
+ when /\A-\d+\z/
+ stop = port[1..]
+
+ (..stop.to_i)
+ when /\A\d+\z/
+ port.to_i
+ else
+ raise(ArgumentError,"invalid port range: #{ports.inspect}")
+ end
+ end
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/man/ronin-nmap-new.1.md b/man/ronin-nmap-new.1.md
new file mode 100644
index 0000000..99cda9e
--- /dev/null
+++ b/man/ronin-nmap-new.1.md
@@ -0,0 +1,70 @@
+# ronin-nmap-new 1 "2023-03-01" Ronin Nmap "User Manuals"
+
+## NAME
+
+ronin-nmap-new - Generates a new nmap ruby script
+
+## SYNOPSIS
+
+`ronin-nmap new` [options] *FILE*
+
+## DESCRIPTION
+
+Generates a new nmap scanner or parser Ruby script that uses the `ronin-nmap`
+library.
+
+## ARGUMENTS
+
+*FILE*
+: The path to the new Ruby script to generate.
+
+## OPTIONS
+
+`--parser`
+: Generates a new nmap XML parser Ruby script.
+
+`--scanner`
+: Generates a new nmap scanner Ruby script.
+
+`--printing`
+: Adds additional code to the Ruby script that prints the nmap XML scan data.
+ Is compatible with both `--parser` and `--scanner`.
+
+`--import`
+: Adds additional code to the Ruby script that imports the nmap XML scan data.
+ Is compatible with both `--parser` and `--scanner`.
+
+`--xml-file` *XML_FILE*
+: Parses or writes the scan results to the given XML File.
+ Is compatible with both `--parser` and `--scanner`.
+
+`-p`, `--port` {*PORT* \| \[*PORT1*\]-\[*PORT2*\]},...
+: Specifies the ports to scan. Not compatible with the `--parser` option.
+
+`--target` *TARGET*
+: Adds a target to scan. May be a host name, IP, IP CIDR range (ex:
+ `192.168.1.1/24`), or IP glob range (ex: `192.168.*.1-4`).
+ Not compatible with the `--parser` option.
+
+`-h`, `--help`
+: Print help information
+
+## EXAMPLES
+
+Generates a new nmap scanner Ruby script that scans `example.com`, ports 22, 80,
+443, and 8000 through 9000:
+
+ $ ronin-nmap new scanner.rb --ports 22,80,443,8000-9000 --target example.com
+
+Generates a new nmap XML parser script that parses `path/to/nmap.xml` and prints
+the scan information.
+
+ $ ronin-nmap new parser.rb --parser --xml-file path/to/nmap.xml --printing
+
+## AUTHOR
+
+Postmodern
+
+## SEE ALSO
+
+[ronin-nmap-scan](ronin-nmap-scan.1.md), [ronin-nmap-print](ronin-nmap-print.1.md), [ronin-nmap-import](ronin-nmap-import.1.md)