# Description: # Allows hubot to execute PenText framework commands # # Dependencies: # The PenText framework # # Configuration: # See the various handlers in bash/ # # Usage: # build # Builds a .pdf document of based on files in . The file is stored in target/ of the specified repository. # Usage: build [namespace=ros] [branch=master] [-v] # Can be either report or quote # Specifies the name of the gitlab repository where the files needed to do the job are located. # [namespace] This optional parameter refers to the gitlab user or group this repository is part of. Defaults to ros # [branch] This optional parameter specifies which branch to use. Defaults to master. # [-v] Specifying this flag will yield verbose output. # # convert # Converts gitlab issues in to xml files. The issues must be open and need to be labelled with either finding or non-finding. # Depending on the label, the xml files will be put in either the finding/ or non-finding/ directory in the repository. # Usage: convert [--closed] [--dry-run] [--issues] [--projects] [-v|--verbose] [-y] # Specifies the name of the gitlab repository where the files needed to do the job are located. # [--closed] If specified, will include closed issues # [--dry-run] If specified, will not write xml files, but only displays output on screen # [--issues] If specified, will list issues in given # [--projects] If specified, will list gitlab repositories # [-v|--verbose] If specified, will yield verbose output # [-y] Assumes yes on all questions # # validate # Validates the XML structure of a reports or quote to be able to generate a .pdf file. (See build command) # Usage: validate [-a|-all] [--autofix] [-c|--capitalization] [--debug] [--edit] [--learn] [--long] [--offer] [--spelling] [-v|--verbose] [--no-report] [--quiet] # Specifies the name of the gitlab repository where the files needed to do the job are located. # [-a|-all] Perform all checks # [--autofix] Try to automatically correct issues # [-c|--capitalization] Check capitalization # [--debug] Show debug information # [--edit] Open files with issues using an editor # [--learn] Store all unknown words in dictionary file # [--long] Check for long lines # [--offer] Validate offer master file # [--spelling] Check spelling # [-v|--verbose] If specified, will yield verbose output # [--no-report] Do not validate report master file # [--quiet] Don't output status messages # # # Commands: # hubot build - Builds a .pdf file from in # hubot convert - Builds a .xml file from in # hubot invoice - Builds pdf invoice from quote # hubot quickscope [branch=MASTER] - Converts quickscope into quotation # hubot startpentest - Bootstraps a pentest # hubot startquote - Bootstraps a quotation # hubot validate - Validates a report/quotation # hubot usage [command] - Displays usage information for command. If no command is specified, supported commands are displayed. # # Author: # Peter Mosmans # John Sinteur # Daniel Attevelt # # This is part of the PenText framework # USAGE_LABEL = '# Usage:' Fs = require 'fs' Path = require 'path' admins = ['peter'] ### This will initialise the usage information for the chatops command used in the pentext framework It will parse the file comment sections in search of usage information. See above for an example ! This section should always be above the commands section and should always be terminated by two consecutive #s ! ! Only single word commands are supported ! robot gets a new property .usages which can be used later on ### init_usage = (robot) -> robot.usages = [] load_scripts(robot) ### This will load all the scripts in the same folder as this one. It will then proceed to parsing those scripts in order to extract the rosbot command usage information ### load_scripts = (robot) -> if Fs.existsSync(__dirname) for file in Fs.readdirSync(__dirname).sort() fullPath = Path.join __dirname, file robot.logger.info "File: " + fullPath body = Fs.readFileSync fullPath, 'utf-8' parse_scripts(robot, body) ### This function will parse the script for usage information. It will do so by looping through every comment line until it hits the # Usage: label Then it will start to look for a command tag that is identified by: # (3 spaces) . The will let the function know it's dealing with a command identifiers. # (5 spaces) lets the function know it's dealing with info line that should go together with the previously encountered command. The sequence is broken by two sequential empty comment lines. e.g.: # # ### parse_scripts = (robot, body) -> parsing_usage = false current_command = null # https://regex101.com cmd_regex = /^#[ ]{3}[\S]+/i info_regex = /^#[ ]{5}[\S]+/i # Loop through each comment line until there is none. for line in body.split "\n" break unless line[0] is '#' or line.substr(0, 2) is '//' # Determine if we've reached the Usage: tag if line.indexOf(USAGE_LABEL) != -1 parsing_usage = true if parsing_usage # Pre-cleaning ... cleanedLine = line.replace(/^(#|\/\/)\s?/, "").trim() # Are we dealing with a command? if line.match cmd_regex current_command = cleanedLine robot.usages[current_command] = [] # Are we dealing with an info line ? if line.match info_regex if not robot.usages[current_command] robot.logger.debug "Error parsing chatpos usage info: found info line, but no command line" else robot.usages[current_command].push cleanedLine # Detects two sequential #'s and quits parsing usage when it does if cleanedLine.length == 0 if current_command == null parsing_usage = false else current_command = null # Sanity check robot.logger.info "--- Following usage information parsed ----" for command, lines of robot.usages for index, line of lines robot.logger.info "* #{command} : #{line}" module.exports = (robot) -> run_cmd = (cmd, args, cb ) -> spawn = require("child_process").spawn child = spawn(cmd, args) child.stdout.on "data", (buffer) -> cb buffer.toString() child.stderr.on "data", (buffer) -> cb buffer.toString() init_usage(robot) # *************************************************************************** # ChatOps command handlers # *************************************************************************** robot.respond /build (.*)/i, id:'chatops.build', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_build"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /convert (.*)/i, id:'chatops.convert', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_convert"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /invoice (.*)/i, id:'chatops.invoice', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_invoice"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /ratecard (.*)/i, id:'chatops.ratecard', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_ratecard"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /quickscope (.*)/i, id:'chatops.quickscope', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_quickscope"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /startpentest (.*)/i, id:'chatops.startpentest', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); if args[0].substring(0, 4) == "off-" msg.send "[-] Please do not start pen names with off-"; return; if args[0].substring(0, 4) == "pen-" msg.send "[-] Please do not start pen names with pen-"; return; roomName = "pen-" + args[0]; newroom = robot.adapter.callMethod('createPrivateGroup', roomName, admins) msg.send "[+] new channel created - Added " + admins + " to the new room " + roomName newroom.then (roomId) => robot.messageRoom roomId.rid, "@all hello!" args[1] = roomId.rid cmd = "bash/handler_pentest"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /startquote (.*)/i, id:'chatops.startquote',(msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); if args[0].substring(0, 4) == "pen-" msg.send "[-] Please do not start quote names with pen-"; return; if args[0].substring(0, 4) == "off-" msg.send "[-] Please do not start quote names with off-"; return; roomName = "off-" + args[0] newroom = robot.adapter.callMethod('createPrivateGroup', roomName, admins) msg.send "[+] new channel created - Added " + admins + " to the new room " + roomName newroom.then (roomId) => robot.messageRoom roomId.rid, "@all hello!" cmd = "bash/handler_quote"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); robot.respond /validate (.*)/i, id:'chatops.validate', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].split(" "); cmd = "bash/handler_validate"; run_cmd cmd, args, (text) -> msg.send text.replace("\n",""); # Handler for the usage command. # Note that the regex option group (.*) also captures the space after the command # This allows for the case when there is no command specified, supported commands can be displayed robot.respond /usage(.*)/i, id:'chatops.usage', (msg) -> msg.match[0] = msg.match[0].replace(/^[a-z0-9]+$/i); msg.match.shift(); args = msg.match[0].trim().split(" "); command = args[0] # If not command is provided, return information on which commands can be handled if command.trim().length == 0 msg.send "I can provide usage information for the following commands:" for command of robot.usages msg.send command msg.send "Issue the command: usage for usage information for this command" return # This deals when unsupported commands if not robot.usages[command] msg.send "I have no usage information for: " + command return # All odd case have been dealt with, let's get down to business for index, line of robot.usages[command] msg.send line