kopie van https://github.com/LilithSec/Lilith.git
751 regels
18 KiB
Perl
Executable File
751 regels
18 KiB
Perl
Executable File
#!perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Getopt::Long;
|
|
use Lilith;
|
|
use TOML qw(from_toml to_toml);
|
|
use File::Slurp qw(read_file);
|
|
use JSON;
|
|
use Text::ANSITable;
|
|
use Term::ANSIColor;
|
|
use Net::Server::Daemonize qw(daemonize);
|
|
|
|
sub version {
|
|
print "lilith v. 0.0.1\n";
|
|
}
|
|
|
|
sub help {
|
|
&version;
|
|
|
|
print '
|
|
|
|
--config <ini> Config INI file.
|
|
Default :: /usr/local/etc/lilith.ini
|
|
|
|
-a <action> Action to perform.
|
|
Default :: search
|
|
|
|
Action :: run
|
|
Description :: Runs the ingestion loop.
|
|
|
|
Action :: class_map
|
|
Description :: Display class to short class mappings.
|
|
|
|
Action :: create_tables
|
|
Description :: Creates the tables in PostgreSQL.
|
|
|
|
Action :: event
|
|
Description ::
|
|
|
|
Action :: search
|
|
Description :: Searches the specified table returns the results.
|
|
|
|
-r <return> Return type. Either table or json.
|
|
Default: table
|
|
|
|
--si <src ip> Source IP.
|
|
Default :: undef
|
|
|
|
--di <dst ip> Destination IP.
|
|
Default :: undef
|
|
|
|
--sp <src port> Source port.
|
|
Default :: undef
|
|
|
|
--dp <dst port> Destination port.
|
|
Default :: undef
|
|
|
|
--ip <ip> IP, either dst or src.
|
|
Default :: undef
|
|
|
|
--p <port> Port, either dst or src.
|
|
Default :: undef
|
|
|
|
--id <alert id> Alert ID.
|
|
Default :: undef
|
|
|
|
-t <table> Table to search. suricata/sagan
|
|
Default :: suricata
|
|
|
|
--host <host> Host.
|
|
Default :: undef
|
|
|
|
--hl Use like for matching host.
|
|
Default :: undef
|
|
|
|
-i <host> Instance.
|
|
Default :: undef
|
|
|
|
--il Use like for matching instance.
|
|
Default :: undef
|
|
|
|
-C <class> Classification.
|
|
Default :: undef
|
|
|
|
-Cl Use like for matching classification.
|
|
Default :: undef
|
|
|
|
-d <dsc> Alert description.
|
|
Default :: undef
|
|
|
|
-dl Use like for matching alert.
|
|
Default :: undef
|
|
';
|
|
}
|
|
|
|
# get the commandline options
|
|
my $help = 0;
|
|
my $version = 0;
|
|
my $config_file = '/usr/local/etc/lilith.toml';
|
|
my $rules_file = '/usr/local/etc/lilith_rules.toml';
|
|
my $action = 'search';
|
|
my $return_type = 'table';
|
|
my $src_ip;
|
|
my $dest_ip;
|
|
my $src_port;
|
|
my $dest_port;
|
|
my $alert_id;
|
|
my $table = 'suricata';
|
|
my $host;
|
|
my $host_like;
|
|
my $instance_host;
|
|
my $instance_host_like;
|
|
my $instance;
|
|
my $instance_like;
|
|
my $class;
|
|
my $class_like;
|
|
my $signature;
|
|
my $signature_like;
|
|
my $ip;
|
|
my $port;
|
|
my $go_back_minutes = 1440;
|
|
my $in_iface;
|
|
my $in_iface_like;
|
|
my $proto;
|
|
my $app_proto;
|
|
my $app_proto_like;
|
|
my $gid;
|
|
my $sid;
|
|
my $rev;
|
|
my $limit;
|
|
my $order_by;
|
|
my $order_dir;
|
|
my $offset;
|
|
my $search_output = 'table';
|
|
my $pretty;
|
|
my $columns;
|
|
my $column_set = 'default';
|
|
my $class_shortern;
|
|
my $debug;
|
|
my $event_id;
|
|
my $id;
|
|
my $decode_raw;
|
|
my $daemonize;
|
|
my $user = 0;
|
|
my $group = 0;
|
|
Getopt::Long::Configure('no_ignore_case');
|
|
Getopt::Long::Configure('bundling');
|
|
GetOptions(
|
|
'version' => \$version,
|
|
'v' => \$version,
|
|
'help' => \$help,
|
|
'h' => \$help,
|
|
'config=s' => \$config_file,
|
|
'r=s' => \$rules_file,
|
|
'a=s' => \$action,
|
|
'si=s' => \$src_ip,
|
|
'sp=s' => \$src_port,
|
|
'di=s' => \$dest_ip,
|
|
'dp=s' => \$dest_port,
|
|
'id=s' => \$alert_id,
|
|
't=s' => \$table,
|
|
'h=s' => \$host,
|
|
'hl' => \$host_like,
|
|
'ihl' => \$instance_host_like,
|
|
'ih=s' => \$instance_host,
|
|
'i=s' => \$instance,
|
|
'il' => \$instance_like,
|
|
'c=s' => \$class,
|
|
'cl' => \$class_like,
|
|
's=s' => \$signature,
|
|
'sl' => \$signature_like,
|
|
'ip=s' => \$ip,
|
|
'p=s' => \$port,
|
|
'm=s' => \$go_back_minutes,
|
|
'if=s' => \$in_iface,
|
|
'il' => \$in_iface_like,
|
|
'proto=s' => \$proto,
|
|
'ap=s' => \$app_proto,
|
|
'apl' => \$app_proto_like,
|
|
'gid=s' => \$gid,
|
|
'sid=s' => \$sid,
|
|
'rev=s' => \$rev,
|
|
'limit=s' => \$limit,
|
|
'offset=s' => \$offset,
|
|
'order=s' => \$order_by,
|
|
'orderdir=s' => \$order_dir,
|
|
'output=s' => \$search_output,
|
|
'pretty' => \$pretty,
|
|
'columns=s' => \$columns,
|
|
'columnset=s' => \$column_set,
|
|
'debug' => \$debug,
|
|
'id=s' => \$id,
|
|
'event=s' => \$event_id,
|
|
'raw' => \$decode_raw,
|
|
'daemonize' => \$daemonize,
|
|
'user=s' => \$user,
|
|
'group=s' => \$user,
|
|
);
|
|
|
|
# print version or help if requested
|
|
if ($help) {
|
|
&help;
|
|
exit 42;
|
|
}
|
|
if ($version) {
|
|
&version;
|
|
exit 42;
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_table_color} ) ) {
|
|
$ENV{Lilith_table_color} = 'Text::ANSITable::Standard::NoGradation';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_table_border} ) ) {
|
|
$ENV{Lilith_table_border} = 'ASCII::None';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_IP_color} ) ) {
|
|
$ENV{Lilith_IP_color} = '1';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_IP_private_color} ) ) {
|
|
$ENV{Lilith_IP_private_color} = 'bright_green';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_IP_remote_color} ) ) {
|
|
$ENV{Lilith_IP_remote_color} = 'bright_yellow';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_IP_local_color} ) ) {
|
|
$ENV{Lilith_IP_local_color} = 'bright_red';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_timesamp_drop_micro} ) ) {
|
|
$ENV{Lilith_timestamp_drop_micro} = '0';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_timesamp_drop_offset} ) ) {
|
|
$ENV{Lilith_timestamp_drop_offset} = '0';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_instance_color} ) ) {
|
|
$ENV{Lilith_instance_color} = '1';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_instance_type_color} ) ) {
|
|
$ENV{Lilith_instance_type_color} = 'bright_blue';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_instance_slug_color} ) ) {
|
|
$ENV{Lilith_instance_slug_color} = 'bright_magenta';
|
|
}
|
|
|
|
if ( !defined( $ENV{Lilith_instance_loc_color} ) ) {
|
|
$ENV{Lilith_instance_loc_color} = 'bright_cyan';
|
|
}
|
|
|
|
if ( !defined($action) ) {
|
|
die('No action defined via -a');
|
|
}
|
|
|
|
# make sure the file exists
|
|
if ( !-f $config_file ) {
|
|
die( '"' . $config_file . '" does not exist' );
|
|
}
|
|
|
|
# read the in or die
|
|
my $toml_raw = read_file($config_file) or die 'Failed to read "' . $config_file . '"';
|
|
|
|
# read the specified config
|
|
my ( $toml, $err ) = from_toml($toml_raw);
|
|
unless ($toml) {
|
|
die "Error parsing toml,'" . $config_file . "'" . $err;
|
|
}
|
|
|
|
my $lilith = Lilith->new(
|
|
dsn => $toml->{dsn},
|
|
sagan => $toml->{sagan},
|
|
suricata => $toml->{suricata},
|
|
user => $toml->{user},
|
|
pass => $toml->{pass},
|
|
debug => $debug,
|
|
class_ignore => $toml->{class_ignore},
|
|
sid_ignore => $toml->{sid_ignore},
|
|
);
|
|
|
|
# create the tables if requested
|
|
if ( $action eq 'create_tables' ) {
|
|
$lilith->create_tables();
|
|
exit;
|
|
}
|
|
|
|
my %files;
|
|
my @toml_keys = keys( %{$toml} );
|
|
my $int = 0;
|
|
while ( defined( $toml_keys[$int] ) ) {
|
|
my $item = $toml_keys[$int];
|
|
|
|
if ( ref( $toml->{$item} ) eq "HASH" ) {
|
|
|
|
# add the file in question
|
|
$files{$item} = $toml->{$item};
|
|
}
|
|
|
|
$int++;
|
|
}
|
|
|
|
# default to a empty rule set if the file does not exist
|
|
my $rules_toml = {};
|
|
if ( -f $rules_file ) {
|
|
|
|
# read the rule toml in or die
|
|
my $rule_toml_raw = read_file($rules_file) or die 'Failed to read "' . $rules_file . '"';
|
|
|
|
# read the specified rules
|
|
my ( $rules_toml, $rule_err ) = from_toml($rule_toml_raw);
|
|
unless ($rules_toml) {
|
|
die "Error parsing toml,'" . $rules_file . "'" . $rule_err;
|
|
}
|
|
}
|
|
|
|
if ( $action eq 'run' ) {
|
|
print "Lilith starting...\n" . "dsn: ";
|
|
if ( defined( $toml->{dsn} ) ) {
|
|
print $toml->{dsn} . "\n";
|
|
}
|
|
else {
|
|
print "***undefined***\n";
|
|
}
|
|
print "sagan: ";
|
|
if ( defined( $toml->{sagan} ) ) {
|
|
print $toml->{sagan} . "\n";
|
|
}
|
|
else {
|
|
print "***undefined***\n";
|
|
}
|
|
print "suricata: ";
|
|
if ( defined( $toml->{suricata} ) ) {
|
|
print $toml->{suricata} . "\n";
|
|
}
|
|
else {
|
|
print "***undefined***\n";
|
|
}
|
|
print "user: ";
|
|
if ( defined( $toml->{user} ) ) {
|
|
print $toml->{user} . "\n";
|
|
}
|
|
else {
|
|
print "***undefined***\n";
|
|
}
|
|
print "pass: ";
|
|
if ( defined( $toml->{pass} ) ) {
|
|
print "***defined***\n";
|
|
}
|
|
else {
|
|
print "***undefined***\n";
|
|
}
|
|
|
|
print "\nConfigured Instances...\n" . to_toml( \%files ) . "\n\n\nCalling Lilith->run now....\n";
|
|
|
|
if ($daemonize) {
|
|
daemonize( $user, $group, '/var/run/lilith/pid' );
|
|
}
|
|
|
|
$lilith->run( files => \%files, );
|
|
}
|
|
|
|
if ( $action eq 'extend' ) {
|
|
|
|
my $to_return=$lilith->extend(
|
|
|
|
);
|
|
my $json = JSON->new;
|
|
if ($pretty) {
|
|
$json->canonical(1);
|
|
$json->pretty(1);
|
|
}
|
|
print $json->encode($to_return);
|
|
if ( !$pretty ) {
|
|
print "\n";
|
|
}
|
|
|
|
exit 0;
|
|
}
|
|
|
|
if ( $action eq 'search' ) {
|
|
|
|
#
|
|
# run the search
|
|
#
|
|
my $returned = $lilith->search(
|
|
dsn => $toml->{dsn},
|
|
sagan => $toml->{sagan},
|
|
suricata => $toml->{suricata},
|
|
user => $toml->{user},
|
|
pass => $toml->{pass},
|
|
src_ip => $src_ip,
|
|
src_port => $src_port,
|
|
dest_ip => $dest_ip,
|
|
dest_port => $dest_port,
|
|
ip => $ip,
|
|
port => $port,
|
|
alert_id => $alert_id,
|
|
table => $table,
|
|
host => $host,
|
|
host_like => $host_like,
|
|
instance_host => $instance_host,
|
|
instance_host_like => $instance_host_like,
|
|
instance => $instance,
|
|
instance_like => $instance_like,
|
|
class => $class,
|
|
class_like => $class_like,
|
|
signature => $signature,
|
|
signature_like => $signature_like,
|
|
ip => $ip,
|
|
port => $port,
|
|
app_proto => $app_proto,
|
|
app_proto_like => $app_proto_like,
|
|
proto => $proto,
|
|
gid => $gid,
|
|
sid => $sid,
|
|
rev => $rev,
|
|
order_by => $order_by,
|
|
order_dir => $order_dir,
|
|
limit => $limit,
|
|
offset => $offset,
|
|
go_back_minutes => $go_back_minutes,
|
|
);
|
|
|
|
#
|
|
# assemble the selected output
|
|
#
|
|
if ( $search_output eq 'json' ) {
|
|
my $json = JSON->new;
|
|
if ($pretty) {
|
|
$json->canonical(1);
|
|
$json->pretty(1);
|
|
}
|
|
print $json->encode($returned);
|
|
if ( !$pretty ) {
|
|
print "\n";
|
|
}
|
|
exit 0;
|
|
}
|
|
elsif ( $search_output eq 'table' ) {
|
|
|
|
#
|
|
# set the columns they had not been manually specified
|
|
#
|
|
if ( !defined($columns) ) {
|
|
if ( $table eq 'suricata' ) {
|
|
if ( $column_set eq 'default' ) {
|
|
$columns
|
|
= 'id,instance,in_iface,src_ip,src_port,dest_ip,dest_port,proto,app_proto,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_timestamp' ) {
|
|
$columns
|
|
= 'timestamp,instance,in_iface,src_ip,src_port,dest_ip,dest_port,proto,app_proto,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_event' ) {
|
|
$columns
|
|
= 'id,event_id,instance,in_iface,src_ip,src_port,dest_ip,dest_port,proto,app_proto,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_timestamp_event' ) {
|
|
$columns
|
|
= 'timestamp,event_id,instance,in_iface,src_ip,src_port,dest_ip,dest_port,proto,app_proto,signature,classification,rule_id';
|
|
}
|
|
else {
|
|
die( '"' . $column_set . '" is not a known column set' );
|
|
}
|
|
}
|
|
else {
|
|
if ( $column_set eq 'default' ) {
|
|
$columns
|
|
= 'id,instance,src_ip,host,xff,facility,level,priority,program,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_timestamp' ) {
|
|
$columns
|
|
= 'timestamp,instance,src_ip,host,xff,facility,level,priority,program,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_event' ) {
|
|
$columns
|
|
= 'id,event_id,instance,src_ip,host,xff,facility,level,priority,program,signature,classification,rule_id';
|
|
}
|
|
elsif ( $column_set eq 'default_timestamp_event' ) {
|
|
$columns
|
|
= 'timestamp,event_id,instance,src_ip,host,xff,facility,level,priority,program,signature,classification,rule_id';
|
|
}
|
|
else {
|
|
die( '"' . $column_set . '" is not a known column set' );
|
|
}
|
|
}
|
|
}
|
|
|
|
# friendly column names
|
|
my $column_names = {
|
|
'id' => 'id',
|
|
'instance' => 'instance',
|
|
'host' => 'host',
|
|
'timestamp' => 'timestamp',
|
|
'event_id' => 'event_id',
|
|
'flow_id' => 'flow_id',
|
|
'in_iface' => 'if',
|
|
'src_ip' => 'src_ip',
|
|
'src_port' => 'sport',
|
|
'dest_ip' => 'dest_ip',
|
|
'dest_port' => 'dport',
|
|
'proto' => 'proto',
|
|
'app_proto' => 'aproto',
|
|
'flow_pkts_toserver' => 'PtS',
|
|
'flow_bytes_toserver' => 'BtS',
|
|
'flow_pkts_toclient' => 'PtC',
|
|
'flow_bytes_toclient' => 'BtC',
|
|
'flow_start' => 'flow_start',
|
|
'classification' => 'class',
|
|
'signature' => 'signature',
|
|
'gid' => 'gid',
|
|
'sid' => 'sid',
|
|
'rev' => 'rev',
|
|
'rule_id' => 'rule_id',
|
|
'facility' => 'facility',
|
|
'level' => 'level',
|
|
'priority' => 'priority',
|
|
'program' => 'program',
|
|
'xff' => 'xff',
|
|
'stream' => 'stream',
|
|
'event_id' => 'event',
|
|
};
|
|
|
|
#
|
|
# init the table
|
|
#
|
|
my $tb = Text::ANSITable->new;
|
|
$tb->border_style( $ENV{Lilith_table_border} );
|
|
$tb->color_theme( $ENV{Lilith_table_color} );
|
|
|
|
my @columns_array = split( /,/, $columns );
|
|
my $header_int = 0;
|
|
my $padding = 0;
|
|
my @headers;
|
|
foreach my $header (@columns_array) {
|
|
|
|
push( @headers, $column_names->{$header} );
|
|
|
|
if ( ( $header_int % 2 ) != 0 ) { $padding = 1; }
|
|
else { $padding = 0; }
|
|
|
|
$tb->set_column_style( $header_int, pad => $padding );
|
|
|
|
$header_int++;
|
|
}
|
|
|
|
$tb->columns( \@headers );
|
|
|
|
#
|
|
# process each found row
|
|
#
|
|
my @td;
|
|
foreach my $row ( @{$returned} ) {
|
|
my @new_line;
|
|
|
|
foreach my $column (@columns_array) {
|
|
|
|
if ( $column eq 'rule_id' ) {
|
|
$row->{rule_id} = $row->{gid} . ':' . $row->{sid} . ':' . $row->{rev};
|
|
}
|
|
|
|
if ( defined( $row->{$column} ) && $column eq 'rule_id' ) {
|
|
push( @new_line, $row->{gid} . ':' . $row->{sid} . ':' . $row->{rev} );
|
|
}
|
|
elsif ( defined( $row->{$column} ) && $column eq 'classification' ) {
|
|
|
|
if ( defined( $lilith->{lc_class_map}->{ lc( $row->{$column} ) } ) ) {
|
|
push( @new_line, $lilith->{lc_class_map}->{ lc( $row->{$column} ) } );
|
|
}
|
|
else {
|
|
push( @new_line, $row->{$column} );
|
|
}
|
|
}
|
|
elsif ( defined( $row->{$column} ) && ( $column eq 'src_ip' || $column eq 'dest_ip' ) ) {
|
|
if ( defined( $ENV{Lilith_IP_color} ) ) {
|
|
if ( $row->{$column} =~ /^192\.168\./
|
|
|| $row->{$column} =~ /^10\./
|
|
|| $row->{$column} =~ /^172\.16/
|
|
|| $row->{$column} =~ /^172\.17/
|
|
|| $row->{$column} =~ /^172\.19/
|
|
|| $row->{$column} =~ /^172\.19/
|
|
|| $row->{$column} =~ /^172\.20/
|
|
|| $row->{$column} =~ /^172\.21/
|
|
|| $row->{$column} =~ /^172\.22/
|
|
|| $row->{$column} =~ /^172\.23/
|
|
|| $row->{$column} =~ /^172\.24/
|
|
|| $row->{$column} =~ /^172\.25/
|
|
|| $row->{$column} =~ /^172\.26/
|
|
|| $row->{$column} =~ /^172\.26/
|
|
|| $row->{$column} =~ /^172\.27/
|
|
|| $row->{$column} =~ /^172\.28/
|
|
|| $row->{$column} =~ /^172\.29/
|
|
|| $row->{$column} =~ /^172\.30/
|
|
|| $row->{$column} =~ /^172\.31/ )
|
|
{
|
|
$row->{$column} = color( $ENV{Lilith_IP_private_color} ) . $row->{$column} . color('reset');
|
|
}
|
|
elsif ( $row->{$column} =~ /^127\./ ) {
|
|
$row->{$column} = color( $ENV{Lilith_IP_local_color} ) . $row->{$column} . color('reset');
|
|
}
|
|
else {
|
|
$row->{$column} = color( $ENV{Lilith_IP_remote_color} ) . $row->{$column} . color('reset');
|
|
}
|
|
}
|
|
push( @new_line, $row->{$column} );
|
|
}
|
|
elsif ( defined( $row->{$column} ) && $column eq 'timestamp' ) {
|
|
if ( $ENV{Lilith_timesamp_drop_micro} ) {
|
|
$row->{$column} =~ s/\.[0-9]+//;
|
|
}
|
|
if ( $ENV{Lilith_timesamp_drop_offset} ) {
|
|
$row->{$column} =~ s/\-[0-9]+$//;
|
|
}
|
|
push( @new_line, $row->{$column} );
|
|
}
|
|
elsif ( defined( $row->{$column} ) && $column eq 'instance' && $ENV{Lilith_instance_color} ) {
|
|
my $color0 = color( $ENV{Lilith_instance_slug_color} );
|
|
my $color1 = color('reset');
|
|
my $color3 = color( $ENV{Lilith_instance_type_color} );
|
|
my $color4 = color( $ENV{Lilith_instance_loc_color} );
|
|
$row->{$column} =~ s/(^[A-Za-z0-9]+)\-/$color0$1$color1-/;
|
|
$row->{$column} =~ s/\-(ids|pie|lae)$/-$color3$1$color1/;
|
|
$row->{$column} =~ s/\-([A-Za-z0-9\-]+)\-/-$color4$1$color1/;
|
|
push( @new_line, $row->{$column} );
|
|
}
|
|
elsif ( defined( $row->{$column} ) ) {
|
|
push( @new_line, $row->{$column} );
|
|
}
|
|
else {
|
|
push( @new_line, '' );
|
|
}
|
|
|
|
}
|
|
|
|
push( @td, \@new_line );
|
|
}
|
|
|
|
#
|
|
# print the table
|
|
#
|
|
$tb->add_rows( \@td );
|
|
print $tb->draw;
|
|
exit 0;
|
|
}
|
|
|
|
# bad selection via --output
|
|
die('No applicable output found');
|
|
}
|
|
|
|
#
|
|
# gets a event
|
|
#
|
|
|
|
if ( $action eq 'event' ) {
|
|
if ( defined($id) && defined($event_id) ) {
|
|
die('Can not search via both --id and --event for fetching a event');
|
|
}
|
|
|
|
my $returned = $lilith->search(
|
|
table => $table,
|
|
id => $id,
|
|
event_id => $event_id,
|
|
debug => $debug,
|
|
no_time => 1,
|
|
limit => 1,
|
|
);
|
|
|
|
if ( !defined( $returned->[0] ) ) {
|
|
print "{}\n";
|
|
exit 42;
|
|
}
|
|
|
|
if ( $decode_raw
|
|
&& defined( $returned->[0] )
|
|
&& defined( $returned->[0]{raw} ) )
|
|
{
|
|
$returned->[0]{raw} = decode_json( $returned->[0]{raw} );
|
|
}
|
|
|
|
my $json = JSON->new;
|
|
if ($pretty) {
|
|
$json->canonical(1);
|
|
$json->pretty(1);
|
|
}
|
|
print $json->encode( $returned->[0] );
|
|
if ( !$pretty ) {
|
|
print "\n";
|
|
}
|
|
exit 0;
|
|
}
|
|
|
|
#
|
|
# print the class_map
|
|
#
|
|
|
|
if ( $action eq 'class_map' ) {
|
|
#
|
|
# init the table
|
|
#
|
|
my $tb = Text::ANSITable->new;
|
|
$tb->border_style( $ENV{Lilith_table_border} );
|
|
$tb->color_theme( $ENV{Lilith_table_color} );
|
|
|
|
my @columns = ( 'Class', 'Mapping' );
|
|
|
|
my $header_int = 0;
|
|
my $padding;
|
|
my @headers;
|
|
foreach my $header (@columns) {
|
|
push( @headers, $header );
|
|
|
|
if ( ( $header_int % 2 ) != 0 ) { $padding = 1; }
|
|
else { $padding = 0; }
|
|
|
|
$tb->set_column_style( $header_int, pad => $padding );
|
|
|
|
$header_int++;
|
|
}
|
|
|
|
$tb->columns( \@headers );
|
|
|
|
#
|
|
#
|
|
#
|
|
my @td;
|
|
foreach my $key ( sort( keys( %{ $lilith->{class_map} } ) ) ) {
|
|
my @row = ( $key, $lilith->{class_map}{$key} );
|
|
push( @td, \@row );
|
|
}
|
|
|
|
#
|
|
# print the table
|
|
#
|
|
$tb->add_rows( \@td );
|
|
print $tb->draw;
|
|
|
|
exit 0;
|
|
}
|
|
|
|
#
|
|
# means we did not match anything
|
|
#
|
|
die( 'No matching action, -a, found for "' . $action . '"' );
|