You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
8.4 KiB
350 lines
8.4 KiB
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use English;
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
use NGCP::API::Client;
|
|
use Readonly;
|
|
use XML::Simple;
|
|
use Template;
|
|
use Email::Sender::Simple qw();
|
|
use Email::Simple;
|
|
use Email::Simple::Creator;
|
|
use Email::Sender::Transport::Sendmail qw();
|
|
use POSIX qw(strftime);
|
|
use File::Pid;
|
|
|
|
Readonly my @required => qw();
|
|
Readonly my $config_file => '/etc/ngcp-panel/provisioning.conf';
|
|
|
|
my $PROGRAM_BASE = 'ngcp-fraud-notifier';
|
|
my $retcode = 0;
|
|
my $piddir = '/run/ngcp-fraud-notifier';
|
|
my $pidfile = "$piddir/ngcp-fraud-notifier.pid";
|
|
my $pf = File::Pid->new({ file => $pidfile });
|
|
|
|
local $PROGRAM_NAME = $PROGRAM_BASE;
|
|
local $OUTPUT_AUTOFLUSH = 1;
|
|
|
|
local $SIG{INT} = \&cleanup;
|
|
|
|
my $page_size = 100;
|
|
|
|
my $opts = {
|
|
verbose => 0,
|
|
};
|
|
|
|
my $config;
|
|
|
|
GetOptions($opts,
|
|
'help|h' => sub { usage() },
|
|
'verbose',
|
|
) or usage();
|
|
|
|
sub DESTROY {
|
|
cleanup();
|
|
}
|
|
|
|
sub check_params {
|
|
my @missing;
|
|
|
|
foreach my $param (@required) {
|
|
push @missing, $param unless $opts->{$param};
|
|
}
|
|
usage(join(' ', @missing)) if scalar @missing;
|
|
|
|
return;
|
|
}
|
|
|
|
sub usage {
|
|
my $missing = shift;
|
|
|
|
pod2usage(
|
|
-exitval => $missing ? 1 : 0,
|
|
-verbose => 1,
|
|
-message => $missing ? "Missing parameters: $missing" : '',
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
sub load_config {
|
|
$config = XML::Simple->new()->XMLin($config_file, ForceArray => 0)
|
|
or die "Cannot load config: $config_file: $ERRNO\n";
|
|
|
|
return;
|
|
}
|
|
|
|
sub get_data {
|
|
my ($uri, $link, $code) = @_;
|
|
|
|
my $client = NGCP::API::Client->new(
|
|
verbose => $opts->{verbose},
|
|
page_rows => $page_size,
|
|
);
|
|
|
|
my @result = ();
|
|
while (my $res = $client->next_page($uri)) {
|
|
return [] unless check_api_error($res);
|
|
my $res_hash = $res->as_hash;
|
|
my $data = $res_hash->{_embedded}{'ngcp:' . $link};
|
|
if ('ARRAY' eq ref $data) {
|
|
push @result, @{$data};
|
|
} elsif ($data) {
|
|
push @result, $data;
|
|
}
|
|
if (defined $code and 'CODE' eq ref $code) {
|
|
$code->(\@result);
|
|
@result = ();
|
|
}
|
|
}
|
|
return \@result;
|
|
}
|
|
|
|
sub check_api_error {
|
|
my $res = shift;
|
|
if ($res->is_success) {
|
|
return 1;
|
|
} else {
|
|
die($res->result . "\n") if (
|
|
$res->is_client_error
|
|
or ($res->is_server_error and not scalar grep { $_ == $res->code; } qw(502 503 504))
|
|
);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
sub get_email_template {
|
|
my $event = shift;
|
|
|
|
my $lock_type = $event->{interval_lock} ? 'lock' : 'warning';
|
|
my $reseller_id = $event->{reseller_id};
|
|
my @templates_data = ();
|
|
foreach my $reseller ($event->{reseller_id}, 'NULL') {
|
|
@templates_data = (
|
|
@templates_data,
|
|
@{get_data(sprintf('/api/emailtemplates/?reseller_id=%s', $reseller),
|
|
'emailtemplates')});
|
|
}
|
|
my $selected_template;
|
|
foreach my $template (@templates_data) {
|
|
next if $template->{name} ne 'customer_fraud_' . $lock_type . '_default_email'
|
|
&& $template->{name} ne 'customer_fraud_' . $lock_type . '_email';
|
|
next if $template->{reseller_id} && $template->{reseller_id} != $reseller_id;
|
|
$selected_template = $template;
|
|
last if $template->{reseller_id};
|
|
}
|
|
|
|
unless ($selected_template) {
|
|
die sprintf "No template 'customer_fraud_%s_default_email' OR 'customer_fraud_%s_email' is defined.\n",
|
|
$lock_type, $lock_type;
|
|
}
|
|
|
|
return $selected_template;
|
|
}
|
|
|
|
sub send_email {
|
|
my ($event, $subscribers) = @_;
|
|
|
|
my $template = get_email_template($event);
|
|
|
|
my $vars = {
|
|
adminmail => $config->{adminmail},
|
|
customer_id => $event->{contract_id},
|
|
interval => $event->{interval},
|
|
interval_cost => sprintf('%.2f', $event->{interval_cost} / 100),
|
|
interval_limit => sprintf('%.2f', $event->{interval_limit} / 100),
|
|
type => $event->{type} eq 'profile_limit'
|
|
? 'billing profile' : 'customer',
|
|
};
|
|
|
|
foreach my $subscriber (@{$subscribers}) {
|
|
$vars->{subscribers} .= sprintf "%s\@%s %s\n",
|
|
@{$subscriber}{qw(username domain)},
|
|
$subscriber->{external_id}
|
|
? '(' . $subscriber->{external_id} . ')'
|
|
: '';
|
|
}
|
|
|
|
my $tt = Template->new();
|
|
foreach my $field (keys %{$template}) {
|
|
my $out;
|
|
$tt->process(\$template->{$field}, $vars, \$out);
|
|
$template->{$field} = $out if $out;
|
|
}
|
|
|
|
die "'To' header is empty in the email\n" unless $event->{interval_notify};
|
|
die "'From' header is empty in the email\n" unless $template->{from_email};
|
|
die "'Subject' header is empty in the email\n" unless $template->{subject};
|
|
|
|
my $transport = Email::Sender::Transport::Sendmail->new;
|
|
my $email = Email::Simple->create(
|
|
header => [
|
|
To => $event->{interval_notify},
|
|
From => $template->{from_email},
|
|
Subject => $template->{subject},
|
|
],
|
|
body => $template->{body},
|
|
);
|
|
|
|
Email::Sender::Simple->send($email, { transport => $transport })
|
|
or die sprintf "Cannot send fraud daily lock notification to %s: $ERRNO\n",
|
|
$email->header('To');
|
|
|
|
update_notify_status($event);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub update_notify_status {
|
|
my $event = shift;
|
|
|
|
my $client = NGCP::API::Client->new(verbose => $opts->{verbose});
|
|
|
|
my $now = strftime('%Y-%m-%d %H:%M:%S', localtime);
|
|
my $uri = '/api/customerfraudevents/' . $event->{id};
|
|
my $data = [
|
|
{
|
|
op => 'replace',
|
|
path => '/notify_status',
|
|
value => 'notified',
|
|
},
|
|
{
|
|
op => 'replace',
|
|
path => '/notified_at',
|
|
value => $now,
|
|
},
|
|
];
|
|
|
|
my $res = $client->request('PATCH', $uri, $data);
|
|
|
|
return;
|
|
}
|
|
|
|
sub main {
|
|
if (my $num = $pf->running) {
|
|
print "$PROGRAM_BASE is already running.\n";
|
|
$retcode = 1;
|
|
exit $retcode;
|
|
}
|
|
mkdir $piddir unless -e $piddir;
|
|
$pf->write;
|
|
|
|
check_params();
|
|
load_config();
|
|
|
|
get_data(
|
|
sprintf('/api/customerfraudevents/?no_count=true¬ify_status=%s', 'new'),
|
|
'customerfraudevents',
|
|
sub {
|
|
my $events = shift;
|
|
foreach my $event (@{$events}) {
|
|
if ($event->{interval_notify}) {
|
|
my $subscribers = get_data(sprintf('/api/subscribers/?customer_id=%d',
|
|
$event->{contract_id}),
|
|
'subscribers');
|
|
next unless scalar @$subscribers;
|
|
eval {
|
|
send_email($event, $subscribers);
|
|
};
|
|
print $EVAL_ERROR if $EVAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
sub cleanup {
|
|
$pf->remove;
|
|
rmdir $piddir if $piddir;
|
|
}
|
|
|
|
eval { main() };
|
|
|
|
if ($EVAL_ERROR) {
|
|
print STDERR $EVAL_ERROR;
|
|
$retcode = 1;
|
|
}
|
|
|
|
exit $retcode;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
ngcp-fraud-notifier - send fraud notifications for customers exceeding thresholds
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
B<ngcp-fraud-nofifier> [I<options>...]
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<This program> checks for contract balances above fraud limit warning
|
|
thresholds and sends email notifications about the incidents.
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 8
|
|
|
|
=item B<--verbose>
|
|
|
|
Show additional debug information. Default false.
|
|
|
|
=item B<--help>
|
|
|
|
Print a brief help message.
|
|
|
|
=back
|
|
|
|
=head1 EXIT STATUS
|
|
|
|
=over
|
|
|
|
=item B<0>
|
|
|
|
Command completed successfully.
|
|
|
|
=item B<1>
|
|
|
|
Command failed with errors.
|
|
|
|
=back
|
|
|
|
=head1 SEE ALSO
|
|
|
|
NGCP::API::Client
|
|
|
|
=head1 BUGS AND LIMITATIONS
|
|
|
|
Please report problems you notice to the Sipwise
|
|
Development Team <support@sipwise.com>.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Kirill Solomko <ksolomko@sipwise.com>
|
|
|
|
=head1 LICENSE
|
|
|
|
Copyright (C) 2019 Sipwise GmbH, Austria
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
=cut
|