#!/usr/bin/env ruby
#
# This file is part of JiraScan
# https://github.com/bcoles/jira_scan
#

require 'jira_scan'
require 'optparse'
require 'terminal-table'
require 'resolv'

def banner
  puts "
       _ _            _____                 
      | (_)          / ____|                
      | |_ _ __ __ _| (___   ___ __ _ _ __  
  _   | | | '__/ _` |\\___ \\ / __/ _` | '_ \\ 
 | |__| | | | | (_| |____) | (_| (_| | | | |
  \\____/|_|_|  \\__,_|_____/ \\___\\__,_|_| |_|
                               version #{JiraScan::VERSION}"
  puts
  puts '-' * 60
end

banner
options = {}
opts = OptionParser.new do |o|
  o.banner = 'Usage: jira-scan [options]'

  o.on('-u URL', '--url URL', 'Jira URL to scan') do |v|
    unless v.match(%r{\Ahttps?://})
      puts "- Invalid URL: #{v}"
      exit(1)
    end
    options[:url] = v
  end

  o.on('-s', '--skip', 'Skip check for Jira') do
    options[:skip] = true
  end

  o.on('-i', '--insecure', 'Skip SSL/TLS validation') do
    options[:insecure] = true
  end

  o.on('-v', '--verbose', 'Enable verbose output') do
    options[:verbose] = true
  end

  o.on('-h', '--help', 'Show this help') do
    puts opts
    exit
  end
end

opts.parse!

if options[:url].nil?
 puts opts
 exit(1)
end

def scan(url, check: true, insecure: false, verbose: false)
  JiraScan.logger = ::Logger.new($stdout).tap do |log|
    log.progname = 'jira-scan'
    log.level = verbose ? ::Logger::INFO : ::Logger::WARN
    log.datetime_format = '%Y-%m-%d %H:%M:%S '
  end

  JiraScan.insecure = insecure

  puts "Scan started at #{Time.now.getutc}"
  puts "URL: #{url}"

  # parse URL
  target = nil
  begin
    target = URI::parse(url.split('?').first)
  rescue
    puts "- Could not parse target URL: #{url}"
  end
  exit(1) if target.nil?

  # resolve IP address
  begin
    ip = Resolv.getaddress(target.host).to_s
    puts "IP: #{ip}" unless ip.nil?
  rescue
    puts "- Could not resolve hostname #{target.host}"
  end

  puts "Port: #{target.port}"
  puts '-' * 60

  # Check if the URL is Jira
  if check
    is_jira = JiraScan::detectJiraDashboard(url)
    is_jira = JiraScan::detectJiraLogin(url) unless is_jira
    unless is_jira
      puts '- Jira not found'
      exit(1)
    end
    puts '+ Found Jira'
  end

  # Get Jira version
  version = JiraScan::getVersionFromDashboard(url)
  version = JiraScan::getVersionFromLogin(url) unless version
  puts "+ Version: #{version}" if version

  # Retrieve Jira software information
  info = JiraScan::getServerInfo(url)
  unless info.empty?
    puts "+ Server info:"
    table = Terminal::Table.new :rows => info
    puts table
  end

  # Dev mode enabled
  puts '+ Dev mode is enabled' if JiraScan::devMode(url)

  # User registration enabled
  puts '+ User registration is enabled' if JiraScan::userRegistration(url)

  # Service Desk user registration enabled
  puts '+ Service Desk user registration is enabled' if JiraScan::userServiceDeskRegistration(url)

  # Check if User Picker Browser is accessible
  if JiraScan::userPickerBrowser(url)
    puts '+ User Picker Browser is available'
    # Retrieve list of first 1,000 users 
    users = JiraScan::getUsersFromUserPickerBrowser(url)
    unless users.empty?
      puts "+ Found users (#{users.length}):"
      table = Terminal::Table.new :headings => ['Username', 'Full Name', 'Email'], :rows => users
      puts table
    end
  end

  # Check if REST User Picker is accessible
  puts "+ REST UserPicker is available" if JiraScan::restUserPicker(url)

  # Check if REST Group User Picker is accessible
  puts "+ REST GroupUserPicker is available" if JiraScan::restGroupUserPicker(url)

  # Check if ViewUserHover.jspa is accessible
  puts "+ ViewUserHover.jspa is available" if JiraScan::viewUserHover(url)

  # Check if META-INF contents are accessible
  puts '+ META-INF directory contents are accessible' if JiraScan::metaInf(url)

  # Retrieve list of dashboards
  dashboards = JiraScan::getDashboards(url)
  unless dashboards.empty?
    puts "+ Found dashboards (#{dashboards.length}):"
    table = Terminal::Table.new :headings => ['ID', 'Name'], :rows => dashboards
    puts table
  end

  # Retrieve list of popular filters
  filters = JiraScan::getPopularFilters(url)
  unless filters.empty?
    puts "+ Found popular filters (#{filters.length}):"
    table = Terminal::Table.new :headings => ['Filter Name'], :rows => filters
    puts table
  end

  # Retrieve list of installed gadgets
  gadgets = JiraScan::getGadgets(url)
  unless gadgets.empty?
    puts "+ Found gadgets (#{gadgets.length}):"
    table = Terminal::Table.new :headings => ['Title', 'Author Name', 'Author Email', 'Description'], :rows => gadgets
    puts table
  end

  # Retrieve list of resolutions
  resolutions = JiraScan::getResolutions(url)
  unless resolutions.empty?
    puts "+ Found resolutions (#{resolutions.length}):"
    table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => resolutions
    puts table
  end

  # Retrieve list of projects
  projects = JiraScan::getProjects(url)
  unless projects.empty?
    puts "+ Found projects (#{projects.length}):"
    table = Terminal::Table.new :headings => ['ID', 'Key', 'Name'], :rows => projects
    puts table
  end

  # Retrieve list of project categories
  project_categories = JiraScan::getProjectCategories(url)
  unless project_categories.empty?
    puts "+ Found project categories (#{project_categories.length}):"
    table = Terminal::Table.new :headings => ['ID', 'Name', 'Description'], :rows => project_categories
    puts table
  end

  # Retrieve list of linked applications
  apps = JiraScan::getLinkedApps(url)
  unless apps.empty?
    puts "+ Found linked applications (#{apps.length}):"
    table = Terminal::Table.new :headings => ['Link', 'Label', 'Application Type'], :rows => apps
    puts table
  end

  # Retrieve list of field names
  field_names = JiraScan::getFieldNamesQueryComponentDefault(url)
  unless field_names.empty?
    puts "+ Found field names (#{field_names.length}):"
    table = Terminal::Table.new :headings => ['Name', 'ID', 'Key', 'IsShown', 'Last Viewed'], :rows => field_names
    puts table
  end

  # Retrieve list of field names
  field_names = JiraScan::getFieldNamesQueryComponentJql(url)
  unless field_names.empty?
    puts "+ Found field names (#{field_names.length}):"
    table = Terminal::Table.new :headings => ['Name', 'ID', 'Key', 'IsShown', 'Last Viewed'], :rows => field_names
    puts table
  end

  puts "Scan finished at #{Time.now.getutc}"
  puts '-' * 60
end

scan(
  options[:url],
  insecure: options[:insecure],
  check: !options[:skip],
  verbose: options[:verbose]
)
