use strict; eval 'use lib "/home/rkrenn/sipwise/git/sipwise-base/lib";'; eval 'use lib "/home/rkrenn/sipwise/git/ngcp-panel/lib";'; eval 'use NGCP::Panel::Utils::Message qw();'; my $panel_util_message_loaded = 1; if ($@) { warn('cannot load NGCP::Panel::Utils::Message'); $panel_util_message_loaded = 0; } use File::Find qw(); use File::Basename qw(); use File::Spec qw(); use YAML::XS; my @perlfileextensions = ('.pl','.pm'); my $rperlextensions = join('|',map { quotemeta($_); } @perlfileextensions); my $root = "/home/rkrenn/sipwise/git/ngcp-panel/"; my @dirstoskip = (); push @dirstoskip,$root."sandbox/"; my @filestoskip = (); push @filestoskip,$root."lib/NGCP/Panel/Utils/Message.pm"; my %dirsdone; my %mock_objs = (); my %result = ( 'panel-debug.log' => [], 'api.log' => [], 'panel.log' => [], ); my $messages_count; my $invocations_count; my %distinct_variables; my $extracted_message = undef; my $is_sensitive = 0; my $message_role = undef; scancatalystlog(); scanmessageslog(); write_ymls(); exit(); sub scancatalystlog { $messages_count = 0; $invocations_count = 0; %dirsdone = (); %distinct_variables = (); File::Find::find({ wanted => \&scancatalystlog_dir_names, follow => 1 }, $root); print "scan for \$c->log:\n method invocations: $invocations_count\n identified messages: $messages_count\n distinct variables: " . (scalar keys %distinct_variables) . "\n"; } sub scancatalystlog_dir_names { _scandirs(sub { my $dir = shift; _scandirfiles($dir,0,sub { my ($line,$inputfilename,$inputfiledir,$inputfilesuffix,$next_line) = @_; if ($line =~ /->log(\(\))?->(debug|info|warn|error|fatal)/ and substr(_trim($line),0,1) ne '#') { my $method = $2; my $source_file_name = _get_source_file_name($inputfilename,$inputfiledir,$inputfilesuffix); my $message = _trim($line); $message =~ s/^\$.+->log(\(\))?->$method\(//; $message =~ s/(\s*#.+)?$//; while ($message !~ /;$/) { my $next_line = &$next_line(); next if substr(_trim($next_line),0,1) eq '#'; $next_line = _trim($next_line); $next_line =~ s/(\s*#.+)?$//; $message .= $next_line; } $message =~ s/\)(\s+(if|unless)[^;]+)?;(\s*#.+)?$//; _dispatch_logfile(_list_variables({ gdpr_status => ($message =~ /->qs\(/ ? 2 : 0), level => $method, log_line => $message, source_file => $source_file_name, })); #print $message . "\n"; $messages_count++; $invocations_count++; } return 1; }); }); } sub scanmessageslog { $messages_count = 0; $invocations_count = 0; %dirsdone = (); %distinct_variables = (); File::Find::find({ wanted => \&scanmessageslog_dir_names, follow => 1 }, $root); print "scan for NGCP::Panel::Utils::Message:\n method invocations: $invocations_count\n identified messages: $messages_count\n distinct variables: " . (scalar keys %distinct_variables) . "\n"; } sub scanmessageslog_dir_names { _scandirs(sub { my $dir = shift; _scandirfiles($dir,1,sub { my ($data_ref,$inputfilename,$inputfiledir,$inputfilesuffix) = @_; while ($$data_ref =~ /((\s*#\s*)?NGCP::Panel::Utils::Message::(info|error)\([^;]+\)(\s+(if|unless)[^;]+)?;)/gm) { my $line = $1; my $method = $3; next if substr(_trim($line),0,1) eq '#'; $line =~ s/^NGCP::Panel::Utils::Message::(info|error)\(/{/m; $line =~ s/\)(\s+(if|unless)[^;]+)?;/}/m; my $source_file_name = _get_source_file_name($inputfilename,$inputfiledir,$inputfilesuffix); my %messages = _extract_messages($line,$method); foreach my $message (keys %messages) { _dispatch_logfile(_list_variables({ gdpr_status => ($messages{$message} ? 2 : 0), level => $method, log_line => $message, source_file => $source_file_name, })); $messages_count++; #print $count . ". " . $message . "\n\n"; } $invocations_count++; } }); }); } sub _scandirs { my ($scandirfiles_code) = @_; my $path = $File::Find::dir; if (-d $path) { my $dir = $path . '/'; if (not $dirsdone{$dir} and not scalar grep { substr($dir, 0, length($_)) eq $_; } @dirstoskip) { &$scandirfiles_code($dir); } $dirsdone{$dir} = 1; } } sub _scandirfiles { my ($inputdir,$slurp,$code) = @_; local *DIR; if (not opendir(DIR, $inputdir)) { die('cannot opendir ' . $inputdir . ': ' . $!); } my @files = grep { /$rperlextensions$/ && -f $inputdir . $_} readdir(DIR); closedir DIR; return unless (scalar @files) > 0; foreach my $file (@files) { my $inputfilepath = $inputdir . $file; next if grep { $_ eq $inputfilepath; } @filestoskip; my ($inputfilename,$inputfiledir,$inputfilesuffix) = File::Basename::fileparse($inputfilepath, $rperlextensions); local *FILE; if (not open (FILE,'<' . $inputfilepath)) { die('cannot open file ' . $inputfilepath . ': ' . $!); } if ($slurp) { my $line_terminator = $/; $/ = undef; my $data = ; &$code(\$data,$inputfilename,$inputfiledir,$inputfilesuffix); $/ = $line_terminator; } else { while () { last unless &$code($_,$inputfilename,$inputfiledir,$inputfilesuffix, sub { ; }); } } close(FILE); } } sub _trim { my $str = shift; $str =~ s/^\s+//g; $str =~ s/\s+$//g; return $str; } sub _extract_messages { my ($message_invocation,$method) = @_; my %dupe_results = (); foreach my $vector (_generate_combinations( [ { role => 'subscriber' }, ],[ { e => '$e' }, ],[ { error => undef }, { error => '$error' }, { error => _create_mock('error', _error => '$error->{error}', _description => '$error->{description}', _create_sub_mock('type'), _create_sub_mock('info'), ) }, ], )) { my %vector = map { %{$_}; } @$vector; my $args = _deserialize_messagesargs($message_invocation,%vector); if ($args and _run_message($method,$args,%vector)) { if ($extracted_message and index($extracted_message,'=HASH(0x') < 0) { # filter out nonsense vector stringifications $dupe_results{$extracted_message} = $dupe_results{$extracted_message} || $is_sensitive; } } } return %dupe_results; } sub _run_message { my $method = shift; my $args = shift; my %_params = @_; ($message_role) = @_params{qw/ role /}; $extracted_message = undef; $is_sensitive = 0; $args->{flash} = 0; $args->{stash} = 0; $args->{c} = _create_mock('_c', _stash => { }, stash => sub { my $self = shift; my $key = shift; return $self->{stash}->{$key} if $key; return $self->{stash}; }, user => sub { my $self = shift; return _create_mock('_user', roles => sub { my $self = shift; return $message_role; }, webusername => sub { my $self = shift; return '$c->user->webusername'; }, domain => sub { my $self = shift; return _create_mock('_domain', domain => sub { my $self = shift; return '$c->user->domain->domain'; }, ); }, ); }, user_exists => sub { my $self = shift; return 1; }, qs => sub { my $self = shift; my $str = shift; $is_sensitive = 1; #return "<<" . $str . ">>" if $str; return "\x{ab}" . $str . "\x{bb}" if $str; }, request => sub { my $self = shift; return _create_mock('_request', # _params => { # '$c->request->params' => undef, # }, # params => sub { # my $self = shift; # #return $self->{params}->{$key} if $key; # return $self->{params}; # }, method => sub { my $self = shift; return '$c->request->method'; }, path => sub { my $self = shift; return '$c->request->path'; }, address => sub { my $self = shift; return '$c->request->address'; }, query_params => sub { my $self = shift; return { '$c->request->query_params' => undef, }; }, parameters => sub { my $self = shift; return { '$c->request->parameters' => undef, }; }, ); }, response => sub { my $self = shift; return _create_mock('_response', code => sub { my $self = shift; return 999; #'$c->response->code'; }, ); }, _session => { api_request_tx_id => '$c->{session}->{api_request_tx_id}', }, session => sub { my $self = shift; my $key = shift; return $self->{session}->{$key} if $key; return $self->{session}; }, _config => { security => { log_passwords => 0, }, }, config => sub { my $self = shift; #my $key = shift; #return $self->{config}->{$key} if $key; return $self->{config}; }, log => sub { my $self = shift; return _create_mock('_log', error => sub { my $self = shift; $extracted_message = shift; #global closure needed! }, info => sub { my $self = shift; $extracted_message = shift; #global closure needed! }, ); }, ); eval { no strict "refs"; ## no critic (ProhibitNoStrict) "NGCP::Panel::Utils::Message::$method"->(%$args); }; if ($@) { warn($@); return 0; } else { return 1; } } sub _deserialize_messagesargs { my $_line = shift; my %_params = @_; ( my $e, my $error ) = @_params{qw/ e error /}; my $subscriber = _create_mock('subscriber', _create_sub_mock('get_inflated_columns'), _create_sub_mock('username'), _create_sub_mock('uuid'), _create_sub_mock('id'), contact => sub { my $self = shift; return _create_mock('contact',_create_sub_mock('email'),); }, domain => sub { my $self = shift; return _create_mock('domain',_create_sub_mock('domain'),); }, ); my $contact = _create_mock('contact', _create_sub_mock('get_inflated_columns'), _create_sub_mock('email'), ); my $contract = _create_mock('contract', _create_sub_mock('get_inflated_columns'), _create_sub_mock('max_subscribers'), _create_sub_mock('id'), ); my $s = $subscriber; my $invoice = _create_mock('invoice', _create_sub_mock('get_inflated_columns'), _create_sub_mock('id'), ); my $pbx_device = _create_mock('pbx_device', _create_sub_mock('get_inflated_columns'), _create_sub_mock('id'), ); my $c = _create_mock('c', _stash => { body => '$c->{stash}->{body}', hm_rule_result => _create_mock('hm_rule_result',_create_sub_mock('get_inflated_columns'),), hm_condition_result => _create_mock('hm_condition_result',_create_sub_mock('get_inflated_columns'),), hm_action_result => _create_mock('hm_action_result',_create_sub_mock('get_inflated_columns'),), subscriber => $subscriber, voicemail => _create_mock('voicemail',_create_sub_mock('get_inflated_columns'),), recording => _create_mock('recording',_create_sub_mock('id'),), registered => _create_mock('registered',_create_sub_mock('get_inflated_columns'),), trusted => _create_mock('trusted',_create_sub_mock('get_inflated_columns'),), speeddial => _create_mock('speeddial',_create_sub_mock('get_inflated_columns'),), autoattendant => _create_mock('autoattendant',_create_sub_mock('get_inflated_columns'),), ccmapping => _create_mock('ccmapping',_create_sub_mock('get_inflated_columns'),), body => '$c->{stash}->{number}', pattern_result => _create_mock('pattern_result',_create_sub_mock('get_inflated_columns'),), #ncos lnp_result => _create_mock('lnp_result',_create_sub_mock('get_inflated_columns'),), block => _create_mock('block',_create_sub_mock('get_inflated_columns'),), phonebook_result => _create_mock('phonebook_result', _create_sub_mock('id'), _create_sub_mock('get_inflated_columns'),), devmod => _create_mock('devmod', _create_sub_mock('id'), _create_sub_mock('model'), _create_sub_mock('vendor'), ), devfw => _create_mock('devfw', _create_sub_mock('id'), _create_sub_mock('get_inflated_columns'),), devconf => _create_mock('devconf', _create_sub_mock('id'), _create_sub_mock('get_inflated_columns'),), devprof => _create_mock('devprof', _create_sub_mock('id'), _create_sub_mock('get_inflated_columns'),), preference_meta => _create_mock('preference_meta', _create_sub_mock('id'), _create_sub_mock('attribute'),), inv => $invoice, sup => _create_mock('sup',_create_sub_mock('get_inflated_columns'),), domain_result => _create_mock('domain_result', _create_sub_mock('get_inflated_columns'),), set_result => _create_mock('set_result', _create_sub_mock('get_inflated_columns'),), rule_result => _create_mock('rule_result', _create_sub_mock('get_inflated_columns'),), voucher_result => _create_mock('voucher_result', _create_sub_mock('id'),), administrator => _create_mock('administrator',_create_sub_mock('get_inflated_columns'),), profile => _create_mock('profile', #profile set _id => '$c->{stash}->{profile}->{id}', _create_sub_mock('get_inflated_columns'),), contract => $contract, pbx_device => $pbx_device, tmpl => _create_mock('tmpl',_create_sub_mock('get_inflated_columns'),), hm_set_result => _create_mock('hm_set_result',_create_sub_mock('get_inflated_columns'),), set => _create_mock('set', _create_sub_mock('get_inflated_columns'),), #profile set mcid_res => _create_mock('mcid_res', _create_sub_mock('id'),), contact => $contact, server_result => _create_mock('server_result', _create_sub_mock('get_inflated_columns'),), #peering rule_result => _create_mock('rule_result', _create_sub_mock('get_inflated_columns'),), #peering group_result => _create_mock('group_result', _create_sub_mock('get_inflated_columns'),), #peering inbound_rule_result => _create_mock('inbound_rule_result', _create_sub_mock('get_inflated_columns'),), #peering file_result => _create_mock('file_result', _create_sub_mock('get_inflated_columns'),), }, stash => sub { my $self = shift; my $key = shift; return $self->{stash}->{$key} if $key; return $self->{stash}; }, loc => sub { my $self = shift; my $str = shift; my @params = @_; for (my $i = 1; $i <= scalar @params; $i++) { my $subst = quotemeta("[_$i]"); my $repl = $params[$i - 1]; $str =~ s/$subst/$repl/g; } return $str; }, qs => sub { my $self = shift; my $str = shift; $is_sensitive = 1; #return "<<" . $str . ">>" if $str; return "\x{ab}" . $str . "\x{bb}" if $str; }, user => sub { my $self = shift; return _create_mock('user', _create_sub_mock('id'), _create_sub_mock('account_id'), _create_sub_mock('uuid'),); }, request => sub { my $self = shift; return _create_mock('request', _params => { '$c->request->params' => undef, }, params => sub { my $self = shift; #return $self->{params}->{$key} if $key; return $self->{params}; }, ); }, ); #my $e = '$e'; my $msg = '$msg'; my $vars = { invoice => '$vars->{invoice}', }; #my $error = _create_mock('error', # _error => '$error->{error}', # _description => '$error->{description}', # _create_sub_mock('type'), # _create_sub_mock('info'), #); my %log_data = ('%log_data' => undef, ); my $text = \'$text'; my $text_success = \'$text_success'; my $response_body = '$response_body'; my $params_data = '$params_data'; my $attribute = '$attribute'; my $subscriber_id = '$subscriber_id'; my $set = _create_mock('set', _create_sub_mock('get_inflated_columns'),); my $pref_id = '$pref_id'; my $cf_type = '$cf_type'; my $type = '$type'; #voicemail greeting type my $vm_id = '$vm_id'; my $rec_id = '$rec_id'; my $stream_id = '$stream_id'; my $data = '$data'; my $reg_id = '$reg_id'; my $trusted_id = '$trusted_id'; my $rws_id = '$rws_id'; my $sd_id = '$sd_id'; my $aa_id = '$aa_id'; my $phonebook_id = '$phonebook_id'; my $carrier_id = '$carrier_id'; my $number_id = '$number_id'; #lnp my $form = _create_mock('form', _values => { lnp_provider_id => '$form->{values}->{lnp_provider_id}', name => '$form->{values}->{name}', emergency_container_id => '$form->{values}->{emergency_container_id}', code => '$form->{values}->{code}', }, values => sub { my $self = shift; my $key = shift; return $self->{values}->{$key} if $key; return $self->{values}; }, ); my $ip = '$ip'; #banned my $user = '$user'; #banned my $contract_id = '$contract_id'; my $devmod_id = '$devmod_id'; my $devfw_id = '$devfw_id'; my $devconf_id = '$devconf_id'; my $devprof_id = '$devprof_id'; my $emergency_container_id = '$emergency_container_id'; my $emergency_mapping_id = '$emergency_mapping_id'; my $reseller_id = '$reseller_id'; my $domain_id = '$domain_id'; my $network_id = '$network_id'; my $voucher_id = '$voucher_id'; my $administrator_id = '$administrator_id'; my $message = '$message'; my $special_user_login = '$special_user_login'; my $result = '$result'; #certificate my $reseller = _create_mock('reseller', _create_sub_mock('get_inflated_columns'),); my %defaults = ( admins => { login => '$defaults{admins}->{login}', }, ); my $default_pass = '$default_pass'; my $timeset_id = '$timeset_id'; my $package_id = '$package_id'; my $profile_id = '$profile_id'; my $fee_id = '$fee_id'; my $zone_id = '$zone_id'; my $zone_info = '$zone_info'; my $weekday_id = '$weekday_id'; my $rs = _create_mock('rs', _create_sub_mock('get_inflated_columns'),); #timerange rs my $special_id = '$special_id'; my $special_result_info = '$special_result_info'; my $product = '$product'; my $uri = '$uri'; my $fraud_prefs = _create_mock('fraud_prefs', _create_sub_mock('get_inflated_columns'),); my $group_id = '$group_id'; #pbx group id my $dev_id = '$dev_id'; my $line = _create_mock('line', field => sub { my $self = shift; my $field = shift; return _create_mock('field', _create_sub_mock('value'),); }, ); my %hm_rule_columns = ('%hm_rule_columns' => undef); my @out = ('@out'); my $location_id = '$location_id'; my $contact_id = '$contact_id'; my $group_name = '$group_name'; #app server use Data::Dumper; my $args = eval $_line; if ($@) { warn($_line ."\n" .$@); return undef; } else { return $args; } } sub _create_sub_mock { my $subname = shift; return ($subname,sub { my $self = shift; return '$' . (ref $self) . '->' . $subname; # . '()'; #(caller(0))[3]; }); } sub _create_mock { my $class = shift; return $mock_objs{$class} if exists $mock_objs{$class}; my %members = @_; my $obj = bless({},$class); foreach my $member (keys %members) { if ('CODE' eq ref $members{$member}) { no strict "refs"; ## no critic (ProhibitNoStrict) *{$class.'::'.$member} = $members{$member}; } else { $obj->{substr($member,1)} = $members{$member}; } } $mock_objs{$class} = $obj; return $obj; } #foreach my $x (_generate_combinations([qw(1 2 3)], [qw(a b)], [qw(C D)])) { # print join("-",@$x)."\n"; #} sub _generate_combinations { my @arrays = grep @$_, @_; my $min = @arrays; my @current = ([]); for my $array (@arrays) { $min++; @current = map { my $n = $_; my @res = map { my $r = [@$n]; push(@$r,$_); $r; } @$array; unless (scalar @$n < $min) { unshift(@res,$n); } @res; } @current; } return @current; } sub _get_source_file_name { my ($inputfilename,$inputfiledir,$inputfilesuffix) = @_; my @dirparts = File::Spec->splitdir($inputfiledir); return $dirparts[$#dirparts - 1] . '/' . $inputfilename.$inputfilesuffix; } sub _list_variables { my $log_line = shift; my @variables = (); #while ($log_line->{log_line} =~ /(\$[^\s:.)'"]+)/gm) { while ($log_line->{log_line} =~ /(\$[\$a-zA-Z0-9_>{}-]+)/gm) { my $var = $1; push(@variables,{ variable => $var, description => '#todo', }); #print $var . "\n" unless exists $distinct_variables{$var}; $distinct_variables{$var} = 0 unless exists $distinct_variables{$var}; $distinct_variables{$var} += 1; } $log_line->{variables} = \@variables if scalar @variables > 0; return $log_line; } sub _dispatch_logfile { my $log_line = shift; # see rsyslog.conf: #if $programname == 'ngcp-panel' and $msg startswith ' DEBUG' then { # -/var/log/ngcp/panel-debug.log # stop #} # #if $programname == 'ngcp-panel' and $msg contains 'CALLED=API' then { # -/var/log/ngcp/api.log # stop #} # #:programname, isequal, "ngcp-panel" { # -/var/log/ngcp/panel.log # stop #} if ($log_line->{level} eq 'debug') { push(@{$result{'panel-debug.log'}},$log_line); } elsif (index($log_line->{log_line},'CALLED=API') >= 0) { push(@{$result{'api.log'}},$log_line); #print $log_line->{log_line} . "\n"; } else { push(@{$result{'panel.log'}},$log_line); #print $log_line->{log_line} . "\n"; } } sub write_ymls { print "log inventory .yml output:\n"; foreach my $logfile (keys %result) { my ($filename,$dir,$suffix) = File::Basename::fileparse($logfile, '.log'); my $output_filename = $filename.'.yml'; unlink $output_filename; YAML::XS::DumpFile($output_filename, { blacklist_pattern => '#todo', date => _datestamp(), gdpr_status => { 0 => 'not affected', 1 => 'potentially affected', 2 => 'affected', }, log_file => { description => $filename . $suffix . ' file generated by ngcp admin -panel and rest-api', gdpr_status => 2, log_lines => $result{$logfile}, } }); print " $output_filename: " . scalar @{$result{$logfile}} . " messages\n"; } } sub _datestamp { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf "%4d-%02d-%02d",$year+1900,$mon+1,$mday; }