#!/usr/bin/env perl
#
# $Id: onyphe,v 6200d9520468 2025/03/14 14:28:43 gomor $
#
use strict;
use warnings;

use Getopt::Long;
use Onyphe::Api;
use OPP;
use OPP::State;
use OPP::Output;

my $oa = Onyphe::Api->new;
my $opp = OPP->new;
$opp->nested([ 'app.http.component', 'app.http.header', 'alert' ]);
$opp->state(OPP::State->new->init);
$opp->output(OPP::Output->new->init);

$|++;

# Default values
my %lopts = (
   # General options:
   config => $ENV{HOME}.'/.onyphe.ini',
);

# Option parsing
GetOptions(
   # General options:
   "config=s" => \$lopts{config},         # -config ~/.onyphe.ini
   "verbose" => \$lopts{verbose},         # -verbose
   "version" => \$lopts{version},         # -version
   "help" => \$lopts{help},               # -help
   # API options:
   "size=i" => \$lopts{size},             # -size 10
   "maxpage=i" => \$lopts{maxpage},       # -maxpage 10
   "trackquery=i" => \$lopts{trackquery}, # -trackquery 1
   "calculated=i" => \$lopts{calculated}, # -calculated 1
   "keepalive=i" => \$lopts{keepalive},   # -keepalive 1
   "key=s" => \$lopts{key},               # -key XXX
   "maxscantime=i" => \$lopts{'maxscantime'}, # -maxscantime 120
   "aslines=i" => \$lopts{'aslines'}, # -aslines 1
   "aslink=i" => \$lopts{'aslink'}, # -aslink 1
   "astask=i" => \$lopts{'astask'}, # -astask 1
   "full=i" => \$lopts{'full'}, # -full 1
   "urlscan=i" => \$lopts{'urlscan'}, # -urlscan 1
   "vulnscan=i" => \$lopts{'vulnscan'}, # -vulnscan 1
   "riskscan=i" => \$lopts{'riskscan'}, # -riskscan 1
   "asm=i" => \$lopts{'asm'}, # -asm 1
   "import=i" => \$lopts{'import'}, # -import 1
   "ports=s" => \$lopts{'ports'},  # -ports 80,443
   "count" => \$lopts{'count'}, # -count
   "trusted=i" => \$lopts{'trusted'}, # -trusted 0
   "field=s" => \$lopts{'field'}, # -field subject.organization
   "includep=s" => \$lopts{'includep'}, # -includep exa1,exa2,...
   "excludep=s" => \$lopts{'excludep'}, # -excludep exa1,exa2,...
   # General APIs:
   "user" => \$lopts{user},                     # -user
   "summary=s" => \$lopts{summary},             # -summary ip|domain|hostname
   "simple=s" => \$lopts{simple},               # -simple synscan|datascan|vulnscan|...
   "simple-best=s" => \$lopts{'simple-best'},   # -simple-best whois|threatlist|inetnum|geoloc
   "bulk-summary=s" => \$lopts{'bulk-summary'}, # -bulk-summary ip|domain|hostname
   "bulk-simple=s" => \$lopts{'bulk-simple'}, # -bulk-simple ctl|datascan|resolve|...
   "bulk-simple-best=s" => \$lopts{'bulk-simple-best'}, # -bulk-simple-best whois|...
   "discovery=s" => \$lopts{discovery},       # -discovery datascan|resolver|ctl|...
   "search" => \$lopts{search},               # -search
   "postsearch" => \$lopts{postsearch},       # -postsearch
   "export" => \$lopts{export},               # -export
   "postexport" => \$lopts{postexport},       # -postexport
   "alert-list" => \$lopts{'alert-list'},              # -alert-list
   "alert-add" => \$lopts{'alert-add'},                # -alert-add
   "alert-del=s" => \$lopts{'alert-del'},              # -alert-del 0
   "alert-name=s" => \$lopts{'alert-name'},            # -alert-name "New alert"
   "alert-email=s" => \$lopts{'alert-email'},          # -alert-email example@example.com
   "alert-threshold=s" => \$lopts{'alert-threshold'},  # -alert-threshold '>0'
   # Ondemand APIs:
   "ondemand-scope-ip=s" => \$lopts{'ondemand-scope-ip'},
   "ondemand-scope-ip-bulk=s" => \$lopts{'ondemand-scope-ip-bulk'},
   "ondemand-scope-domain=s" => \$lopts{'ondemand-scope-domain'},
   "ondemand-scope-domain-bulk=s" => \$lopts{'ondemand-scope-domain-bulk'},
   "ondemand-scope-hostname=s" => \$lopts{'ondemand-scope-hostname'},
   "ondemand-scope-hostname-bulk=s" => \$lopts{'ondemand-scope-hostname-bulk'},
   "ondemand-scope-port=s" => \$lopts{'ondemand-scope-port'},
   "ondemand-scope-result=s" => \$lopts{'ondemand-scope-result'},
   "ondemand-resolver-domain=s" => \$lopts{'ondemand-resolver-domain'},
   "ondemand-resolver-domain-bulk=s" => \$lopts{'ondemand-resolver-domain-bulk'},
   "ondemand-resolver-result=s" => \$lopts{'ondemand-resolver-result'},
   # ASD APIs:
   "asd-pivot-query=s" => \$lopts{'asd-pivot-query'},
   "asd-domain-tld=s" => \$lopts{'asd-domain-tld'},
   "asd-domain-certso=s" => \$lopts{'asd-domain-certso'},
   "asd-domain-wildcard=s" => \$lopts{'asd-domain-wildcard'},
   "asd-certso-domain=s" => \$lopts{'asd-certso-domain'},
   "asd-certso-wildcard=s" => \$lopts{'asd-certso-wildcard'},
   "asd-org-inventory=s" => \$lopts{'asd-org-inventory'},
   "asd-ip-inventory=s" => \$lopts{'asd-ip-inventory'},
   "asd-subnet-inventory=s" => \$lopts{'asd-subnet-inventory'},
   "asd-vhost-inventory=s" => \$lopts{'asd-vhost-inventory'},
   "asd-score-inventory=s" => \$lopts{'asd-score-inventory'},
   "asd-dns-domain-ns=s" => \$lopts{'asd-dns-domain-ns'},
   "asd-dns-domain-mx=s" => \$lopts{'asd-dns-domain-mx'},
   "asd-dns-domain-soa=s" => \$lopts{'asd-dns-domain-soa'},
   "asd-dns-domain-exist=s" => \$lopts{'asd-dns-domain-exist'},
   "asd-task-id=s" => \$lopts{'asd-task-id'},
   "asd-task-poll=s" => \$lopts{'asd-task-poll'},
   "asd-task-list" => \$lopts{'asd-task-list'},
   "asd-task-kill=s" => \$lopts{'asd-task-kill'}
) or exit(0);

if ($lopts{help}) {
   usage();
   exit(0);
}

if ($lopts{version}) {
   print $oa->version."\n";
   exit(0);
}

$oa->verbose(1) if $lopts{verbose};

my $arg = pop @ARGV;  # Global argument

my ($oql, $opl) = split(/\s*\|\s*/, $arg, 2) if defined $arg;

$oa->init($lopts{config}) or die("FATAL: cannot initialize Onyphe::Api\n");

# Override default values from config:
$oa->config->{''}{api_size} = $lopts{size} if $lopts{size};
$oa->config->{''}{api_maxpage} = $lopts{maxpage} if $lopts{maxpage};
$oa->config->{''}{api_trackquery} = $lopts{trackquery} if $lopts{trackquery};
$oa->config->{''}{api_calculated} = $lopts{calculated} if $lopts{calculated};
$oa->config->{''}{api_keepalive} = $lopts{keepalive} if $lopts{keepalive};
$oa->config->{''}{api_key} = $lopts{key} if $lopts{key};
$oa->config->{''}{api_ondemand_key} = $lopts{key} if $lopts{key};

my $asd_api_opt = undef;
my $asd_api_func = undef;
my $asd_api_value = undef;
for my $opt (keys %lopts) {
   if ($opt =~ m{^asd} && defined($lopts{$opt})) {
      $asd_api_opt = $opt;
      $asd_api_func = $asd_api_opt;
      $asd_api_func =~ s{-}{_}g;
      $asd_api_value = $lopts{$opt};
      last;
   }
}

if ($oa->verbose) {
   print STDERR "VERBOSE: api_size: ".$oa->config->{''}{api_size}."\n";
   print STDERR "VERBOSE: api_maxpage: ".$oa->config->{''}{api_maxpage}."\n";
   print STDERR "VERBOSE: api_trackquery: ".$oa->config->{''}{api_trackquery}."\n";
   print STDERR "VERBOSE: api_calculated: ".$oa->config->{''}{api_calculated}."\n";
   print STDERR "VERBOSE: api_keepalive: ".$oa->config->{''}{api_keepalive}."\n";
}

# Default result callback: don't touch results:
my $result_cb = undef;

my $opp_perl_cb = sub {
   my ($results, $opl) = @_;
   $opl ||= 'noop';
   my @results = ();
   if (ref($results) eq 'ARRAY') {
      for (@$results) {
         next if defined($_->{'@category'}) && $_->{'@category'} eq 'none';
         if (defined($result_cb)) {
            next unless $result_cb->($_);
         }
         push @results, $_;
      }
   }
   else {
      push @results, $results;
   }
   $opp->process_as_perl($results, $opl);
};

my $opp_json_cb = sub {
   my ($results, $opl) = @_;
   $opl ||= 'noop';
   my @results = ();
   if (ref($results) eq 'ARRAY') {
      for (@$results) {
         next if m{.\@category.\s*:\s*.none.};
         if (defined($result_cb)) {
            next unless $result_cb->($_);
         }
         push @results, $_;
      }
   }
   else {
      push @results, $results;
   }
   $opp->process_as_json(\@results, $opl);
};

# Exception for ASD Score Inventory API:
if (defined($lopts{'asd-score-inventory'}) && !defined($lopts{aslines})) {
   $lopts{aslines} = 0;
}
else {
   # Set default values for arguments:
   $lopts{aslines} = 1 unless defined($lopts{aslines});
}

my $opp_cb = $lopts{aslines} ? $opp_json_cb : $opp_perl_cb;

# Used for API call:
my $params;

if ($lopts{user}) {
   $oa->user($opp_perl_cb, $opl);
}
elsif ($lopts{summary}) {
   my $v = $lopts{summary};
   if ($v ne 'ip' && $v ne 'domain' && $v ne 'hostname') {
      die("FATAL: -summary: only accepts ip, domain or hostname values\n");
   }
   $oa->summary($v, $oql, $params, $opp_perl_cb, $opl);
}
elsif ($lopts{simple}) {
   my $v = $lopts{simple};
   $oa->simple($v, $oql, $params, $opp_perl_cb, $opl);
}
elsif ($lopts{'simple-best'}) {
   my $v = $lopts{'simple-best'};
   if ($v ne 'whois' && $v ne 'threatlist' && $v ne 'geoloc' && $v ne 'inetnum') {
      die("FATAL: -simple-best: only accepts whois, threatlist, geoloc or inetnum values\n");
   }
   $oa->simple_best($v, $oql, $params, $opp_perl_cb, $opl);
}
elsif ($lopts{'bulk-summary'}) {
   my $v = $lopts{'bulk-summary'};
   if ($v ne 'ip' && $v ne 'domain' && $v ne 'hostname') {
      die("FATAL: -bulk-summary: only accepts ip, domain or hostname values\n");
   }
   $oa->bulk_summary($v, $oql, $params, $opp_json_cb, $opl);
}
elsif ($lopts{'bulk-simple'}) {
   my $v = $lopts{'bulk-simple'};
   $oa->bulk_simple($v, $oql, $params, $opp_json_cb, $opl);
}
elsif ($lopts{'bulk-simple-best'}) {
   my $v = $lopts{'bulk-simple-best'};
   if ($v ne 'whois' && $v ne 'threatlist' && $v ne 'geoloc' && $v ne 'inetnum') {
      die("FATAL: -bulk-simple-best: only accepts whois, threatlist, geoloc or inetnum values\n");
   }
   $oa->bulk_simple_best($v, $oql, $params, $opp_json_cb, $opl);
}
elsif ($lopts{discovery}) {
   my $v = $lopts{discovery};
   unless (defined($oql)) {
      die("FATAL: no query given\n");
   }
   my ($filename, $query) = $oql =~ m{^(\S+)\s*((?:.*?))\s*$};
   unless (defined($filename)) {
      die("FATAL: no file given\n");
   }
   unless (-f $filename) {
      die("FATAL: file not found: $filename\n");
   }
   unless (defined($query)) {
      die("FATAL: query not given\n");
   }
   $params->{size} = 10000;  # Force 10000 by default
   $oa->bulk_discovery($v, $filename, $query, $params, $opp_json_cb, $opl);
}
elsif ($lopts{search}) {
   $params->{size} = $lopts{size} if defined($lopts{size});
   if ($lopts{count}) {
      $params->{size} = 1;
      $lopts{maxpage} = 1;
      $params->{count} = 1;
   }
   $oa->search($oql, 1, $lopts{maxpage}, $params, $opp_perl_cb, $opl);  # But is also in config()
}
elsif ($lopts{postsearch}) {
   $params->{size} = $lopts{size} if defined($lopts{size});
   $oa->post_search($oql, 1, $lopts{maxpage}, $params, $opp_perl_cb, $opl);  # But is also in config()
}
elsif ($lopts{export}) {
   $oa->export($oql, $params, $opp_json_cb, $opl);
}
elsif ($lopts{postexport}) {
   $params->{size} = $lopts{size} if defined($lopts{size});
   $oa->post_export($oql, $params, $opp_json_cb, $opl);  # But is also in config()
}
elsif ($lopts{'alert-list'}) {
   $oa->alert_list($opp_perl_cb, $opl);
}
elsif ($lopts{'alert-add'}) {
   my $name = $lopts{'alert-name'};
   my $email = $lopts{'alert-email'};
   my $threshold = $lopts{'alert-threshold'} || '>0';
   if (!defined($name) || !defined($email)) {
      die("FATAL: -alert-add: needs at least -alert-name & -alert-email\n");
   }
   $oa->alert_add($name, $oql, $email, $threshold, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'alert-del'})) {
   my $v = $lopts{'alert-del'};
   if ($v !~ m{^\d+$} || $v < 0) {
      die("FATAL: -alert-del: need an ID as an interger >= 0\n");
   }
   $oa->alert_del($v, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-ip'})) {
   my $v = $lopts{'ondemand-scope-ip'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_ip($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-ip-bulk'})) {
   my $v = $lopts{'ondemand-scope-ip-bulk'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_ip_bulk($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-port'})) {
   my $v = $lopts{'ondemand-scope-port'};
   $params->{maxscantime} = 24*60*60;  # 24-hours forced
   $params->{import} = $lopts{import} if defined $lopts{import};
   $oa->ondemand_scope_port($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-domain'})) {
   my $v = $lopts{'ondemand-scope-domain'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_domain($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-domain-bulk'})) {
   my $v = $lopts{'ondemand-scope-domain-bulk'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_domain_bulk($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-hostname'})) {
   my $v = $lopts{'ondemand-scope-hostname'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_hostname($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-hostname-bulk'})) {
   my $v = $lopts{'ondemand-scope-hostname-bulk'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $params->{urlscan} = $lopts{urlscan} if defined $lopts{urlscan};
   $params->{vulnscan} = $lopts{vulnscan} if defined $lopts{vulnscan};
   $params->{riskscan} = $lopts{riskscan} if defined $lopts{riskscan};
   $params->{asm} = $lopts{asm} if defined $lopts{asm};
   $params->{import} = $lopts{import} if defined $lopts{import};
   $params->{ports} = $lopts{ports} if defined $lopts{ports};
   $oa->ondemand_scope_hostname_bulk($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-scope-result'})) {
   my $v = $lopts{'ondemand-scope-result'};
   $params->{aslines} = $lopts{aslines} if defined $lopts{aslines};
   $params->{aslink} = $lopts{aslink} if defined $lopts{aslink};
   $params->{full} = $lopts{full} if defined $lopts{full};
   $oa->ondemand_scope_result($v, $params, $opp_cb, $opl);
}
elsif (defined($lopts{'ondemand-resolver-domain'})) {
   my $v = $lopts{'ondemand-resolver-domain'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $oa->ondemand_resolver_domain($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-resolver-domain-bulk'})) {
   my $v = $lopts{'ondemand-resolver-domain-bulk'};
   $params->{maxscantime} = $lopts{maxscantime} if defined $lopts{maxscantime};
   $oa->ondemand_resolver_domain_bulk($v, $params, $opp_perl_cb, $opl);
}
elsif (defined($lopts{'ondemand-resolver-result'})) {
   my $v = $lopts{'ondemand-resolver-result'};
   $params->{aslines} = $lopts{aslines} if defined $lopts{aslines};
   $params->{full} = $lopts{full} if defined $lopts{full};
   $oa->ondemand_resolver_result($v, $params, $opp_cb, $opl);
}
#
# ASD APIs:
#
elsif (defined($asd_api_opt)) {
   my $v = $lopts{$asd_api_opt};
   # Shared with all ASD APIs:
   $params->{aslines} = $lopts{aslines} if defined $lopts{aslines};
   # Most ASD APIs share the same HTTP parameters, except some:
   if ($asd_api_opt =~ m{^asd-task-poll}) {
      my $v = $lopts{$asd_api_opt};
      $result_cb = sub {
         my ($result) = @_;
         # Don't display task in progress results/
         if (ref($result) eq 'HASH') {
            return $result->{error} == 1013 ? 0 : 1;
         }
         else {
            return $result =~ m{"error":1013} ? 0 : 1;
         }
         return 1;
      };
      $oa->$asd_api_func($v, $params, $opp_cb, $opl);
   }
   elsif ($asd_api_opt =~ m{^asd-task}) {
      my $v = $lopts{$asd_api_opt};
      $oa->$asd_api_func($v, $params, $opp_cb, $opl);
   }
   elsif ($asd_api_opt =~ m{^asd-pivot}) {
      my $v = $lopts{$asd_api_opt};
      $params->{field} = $lopts{field} if defined $lopts{field};
      $params->{astask} = $lopts{astask} if defined $lopts{astask};
      $params->{includep} = $lopts{includep} if defined $lopts{includep};
      $params->{excludep} = $lopts{excludep} if defined $lopts{excludep};
      $oa->$asd_api_func($v, $params, $opp_cb, $opl);
   }
   else {
      $params->{astask} = $lopts{astask} if defined $lopts{astask};
      $params->{trusted} = $lopts{trusted} if defined $lopts{trusted};
      $params->{includep} = $lopts{includep} if defined $lopts{includep};
      $params->{excludep} = $lopts{excludep} if defined $lopts{excludep};
      $oa->$asd_api_func($v, $params, $opp_cb, $opl);
   }
}
else {
   usage();
}

exit(0);

#
# Local subroutines
#

sub usage {
   print<<EOF

Usage: onyphe [options] [api] [api args]

General options:

   -verbose <0|1>                          verbosity level (default: 0)

API options:

   -key <API_KEY>                          use given API key to override configuration
   -maxpage <MAXPAGE>                      max page to fetch with Search API (default: 1)
   -size <0|1|..|10000>                    fetch pages of size results (default: 10)
   -trackquery <0|1>                       add trackquery field into results (default: 0)
   -calculated <0|1>                       add calculated fields into results (default: 0)
   -keepalive <0|1>                        use a keepalive message (default: 0)
   -maxscantime <1|120|...|0>              maximum duration for a scan in seconds (default: 120)
   -aslines <0|1>                          output scan results as lines of JSON
   -full <0|1>                             output full scan results instead of a selected list of fields
   -urlscan <0|1>                          turn off/on URL scanning step in scan mode
   -vulnscan <0|1>                         turn off/on vulnerability scanning step in scan mode
   -riskscan <0|1>                         turn off/on riskscan detection step in scan mode
   -asm <0|1>                              turn off/on ASM step in scan mode
   -ports <list>                           for On-demand APIs, scan given list of ports, comma separated list (example: 21,80,443)
   -import <0|1>                           turn off/on import of results into ONYPHE
   -trusted <0|1>                          turn off/on trusted results from ONYPHE ASD APIs
   -field <FIELD>                          use given field to set field param on API call
   -includep <WORD1,WORD2,...,WORD3>       use given inclusion patterns on param API call
   -excludep <WORD1,WORD2,...,WORD3>       use given exclusion patterns on param API call

General APIs:

   -user '| OPP'                                get information on your license
   -search 'OQL | OPP'                          use Search API for query
   -postsearch 'OQL | OPP'                      use Search API for query, POST version
   -export 'OQL | OPP'                          use Export API for query
   -postexport 'OQL | OPP'                      use Export API for query, POST version
   -discovery CATEGORY 'input.txt | OPP'        use Discovery API on CATEGORY for query
   -simple CATEGORY 'OQL | OPP'                 use Simple API on CATEGORY for query
   -simple-best CATEGORY 'OQL | OPP'            use Simple Best API on CATEGORY for query
   -bulk-simple CATEGORY 'input.txt | OPP'      use Bulk Simple API on CATEGORY for query
   -bulk-simple-best CATEGORY 'ip.txt | OPP'    use Bulk Simple Best API on CATEGORY for query
   -summary ip 'IP | OPP'                                use ip Summary API for query
   -summary domain 'DOMAIN | OPP'                        use domain Summary API for query
   -summary hostname 'HOSTNAME | OPP'                    use hostname Summary API for query
   -bulk-summary <ip|domain|hostname> 'input.txt | OPP'  use Bulk Summary API for query
   -alert-list                                           use Alert List API to list configured alerts
   -alert-del \$id                                        use Alert Del API to remove an alert
   -alert-add -alert-name NAME -alert-email EMAIL 'OQL'  use Alert Add API to create a new alert

Ondemand APIs:

   -ondemand-scope-ip '<IP|CIDR>'              launch a scan against IP or CIDR
   -ondemand-scope-ip-bulk 'input.txt'         launch a scan against a given list of IPs from an input file
   -ondemand-scope-domain 'DOMAIN'             launch a scan against given domain
   -ondemand-scope-domain-bulk 'input.txt'     launch a scan against a given list of domain from an input file
   -ondemand-scope-hostname 'HOSTNAME'         launch a scan against given hostname
   -ondemand-scope-hostname-bulk 'input.txt'   launch a scan against a given list of hostname from an input file
   -ondemand-scope-port PORT                   launch a scan against a given port on full IPv4 address space
   -ondemand-scope-result SCAN_ID              retrieve a scope scan results given a scan ID
   -ondemand-resolver-domain 'DOMAIN'          launch a DNS enumeration and resolution against given domain
   -ondemand-resolver-domain-bulk 'input.txt'  launch a DNS enumeration and resolution against a given list of domain from an input file
   -ondemand-resolver-result SCAN_ID           retrieve a resolver scan results given a scan ID

ASD APIs:

   -asd-pivot-query OQL -field FIELD               search unique value(s) for a given field from an OQL query
   -asd-domain-tld DOMAIN1,DOMAIN2,...|input.txt   search related domain(s) by TLDs
   -asd-domain-certso SO1,SO2,...|input.txt             search domain(s) belonging to given subject orgnanization(s)
   -asd-domain-wildcard DOMAIN1,DOMAIN2,...|input.txt   search domain(s) matching given wildcard(s)
   -asd-certso-domain DOMAIN1,DOMAIN2,...|input.txt     search subject organization(s) belonging to given domain(s)
   -asd-certso-wildcard SO1,SO2,...|input.txt           search subject organization(s) matching given wildcard(s)
   -asd-org-inventory INVENTORY.txt                     search hosting organization(s) for given inventory data
   -asd-ip-inventory INVENTORY.txt                      search ip(s) for given inventory data
   -asd-subnet-inventory INVENTORY.txt                  search subnet(s) belonging to given inventory data
   -asd-vhost-inventory INVENTORY.txt                   search forward(s) for given inventory data
   -asd-score-inventory INVENTORY.txt                   search risks for given inventory, compute a score and returns data
   -asd-task-id TASK_ID          retrieve an ASD Task results for given task ID
   -asd-task-poll TASK_ID        retrieve an ASD Task results by polling given task ID
   -asd-task-list                list all tasks, actually running or finished
   -asd-task-kill TASK_ID        kill a given task ID

   WARNING: the following APIs will execute DNS requests:

   -asd-dns-domain-ns DOMAIN1,DOMAIN2,...|input.txt     search related domain(s) by using nameservers
   -asd-dns-domain-mx DOMAIN1,DOMAIN2,...|input.txt     search related domain(s) by using MX records
   -asd-dns-domain-soa DOMAIN1,DOMAIN2,...|input.txt    search related domain(s) by using SOA records
   -asd-dns-domain-exist DOMAIN1,DOMAIN2,...|input.txt  search existing domain(s)

EOF
;

   exit(0);
}

1;

__END__

=head1 NAME

ONYPHE - ONYPHE Command Line Interface

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2025, ONYPHE SAS

You may distribute this module under the terms of The BSD 3-Clause License.
See LICENSE file in the source distribution archive.

=head1 AUTHOR

ONYPHE E<lt>contact_at_onyphe.ioE<gt>

=cut
