package NGCP::Panel::Controller::CallRouting; use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; use parent 'Catalyst::Controller'; use NGCP::Panel::Form; use NGCP::Panel::Utils::Navigation; use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Utils::Peering; use Sys::Hostname; sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) { my ($self, $c) = @_; $c->log->debug(__PACKAGE__ . '::auto'); NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); return 1; } sub root :PathPart('/') :CaptureArgs(0) { my ( $self, $c ) = @_; } sub callroutingverify :Chained('/') :PathPart('callroutingverify') :Args(0) { my ( $self, $c ) = @_; my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::CallRouting::Verify", $c); my $params = merge({}, $c->session->{created_objects}); my $posted = ($c->req->method eq 'POST'); my $data = $c->req->params; my @log; $form->process( posted => $posted, params => $c->request->params, item => $params, ); unless ($posted && $form->validated) { $c->stash( template => 'callrouting/verify.tt', form => $form, ); return; } # TODO: relocate the logic to a common module that can be centralised and # reused by other components # caller/callee general parsing # remove leading/trailing spaces # save the domain part foreach my $type (qw(caller callee)) { $data->{$type} =~ s/(^\s+|\s+$)//g; $data->{$type} =~ s/^sip://; if ($data->{$type} =~ s/\@(.+)$//) { $data->{$type.'_domain'} = $1; } } # caller lookup if ($data->{caller_subscriber_id}) { my $rs = $c->model('DB')->resultset('voip_subscribers')->search({ id => $data->{caller_subscriber_id}, }); unless ($rs->first) { push @log, sprintf "no caller subscriber found with id %d", $data->{caller_subscriber_id}; goto RESULT; } $data->{caller_subscriber} = $rs->first; } elsif ($data->{caller_peer_id}) { my $rs = $c->model('DB')->resultset('voip_peer_groups')->search({ id => $data->{caller_peer_id}, }); unless ($rs->first) { push @log, sprintf "no caller peer found with id %d", $data->{caller_peer_id}; goto RESULT; } $data->{caller_peer} = $rs->first; unless ($data->{caller_peer}->voip_peer_hosts->first) { push @log, sprintf "caller peer with id %d does not contain any peer hosts", $rs->id; } $data->{caller_peer_host} = $data->{caller_peer}->voip_peer_hosts->first; } else { my $caller_uri = $data->{caller_domain} ? 'sip:'.$data->{caller}.'@'.$data->{caller_domain} : $data->{caller}; push @log, sprintf "no caller subscriber/peer was specified, using subscriber lookup based on caller %s", $caller_uri; $data->{caller_subscriber} = NGCP::Panel::Utils::Subscriber::lookup( c => $c, lookup => $caller_uri ); } if ($data->{caller_subscriber}) { $data->{caller_subscriber_id} = $data->{caller_subscriber}->id; my $caller_uri = sprintf 'sip:%s@%s', $data->{caller_subscriber}->username, $data->{caller_subscriber}->domain->domain; $data->{caller_domain} = $data->{caller_subscriber}->domain->domain; push @log, sprintf "found caller subscriber '%s' with id %d", $caller_uri, $data->{caller_subscriber_id}; } else { push @log, sprintf "no caller subscriber found."; goto RESULT; } # caller sum up push @log, sprintf "call from %s", $data->{caller}; $log[-1] .= $data->{caller_subscriber} ? sprintf " using subscriber '%s\@%s' id %s", $data->{caller_subscriber}->username, $data->{caller_subscriber}->domain->domain, $data->{caller_subscriber}->id : sprintf " using peer '%s' id %s", $data->{caller_peer}->name, $data->{caller_peer}->id; if ($data->{caller_peer_host}) { $log[-1] .= sprintf " and peer host %s (ip: %s) with id %d", $data->{caller_peer_host}->name, $data->{caller_peer_host}->ip, $data->{caller_peer_host}->id; } # caller inbound rewrite rules lookup if ($data->{caller_rewrite_id}) { my $rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ id => $data->{caller_rewrite_id}, }); if ($rs->first) { push @log, sprintf "using caller rewrite rule set '%s' with id %d", $rs->first->name, $rs->first->id; $data->{caller_rewrite} = $rs->first; } } else { my ($lookup_rws, $lookup_rws_type, $rws_rs); if ($data->{'caller_subscriber_id'}) { my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( c => $c, attribute => "rewrite_caller_in_dpid", prov_subscriber => $data->{caller_subscriber}->provisioning_voip_subscriber, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ caller_in_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'subscriber'; } else { $rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs( c => $c, attribute => "rewrite_caller_in_dpid", prov_domain => $data->{caller_subscriber}->provisioning_voip_subscriber->domain, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ caller_in_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'domain'; } else { push @log, sprintf "no caller subscriber/domain rewrite rule sets were found"; } } } elsif ($data->{caller_peer_id}) { my $rs = NGCP::Panel::Utils::Preferences::get_peer_preference_rs( c => $c, attribute => "rewrite_caller_in_dpid", peer_host => $data->{caller_peer_host}, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ caller_in_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'peer'; } else { push @log, sprintf "no caller peer rewrite rule sets with were found"; } } if ($lookup_rws) { push @log, sprintf "using caller %s inbound rewrite rule set '%s' with id %d", $lookup_rws_type, $lookup_rws->name, $lookup_rws->id; $data->{caller_rewrite} = $lookup_rws; } } # apply inbound rewrite rules foreach my $type (qw(caller callee)) { $data->{$type.'_in'} = $data->{$type}; next unless $data->{caller_rewrite}; my $new; if ($data->{caller_subscriber_id}) { $new = NGCP::Panel::Utils::Subscriber::apply_rewrite( c => $c, subscriber => $data->{caller_subscriber}, direction => $type.'_in', number => $data->{$type}, rws_id => $data->{caller_rewrite}->id, ); } elsif ($data->{caller_peer_id}) { $new = NGCP::Panel::Utils::Peering::apply_rewrite( c => $c, peer_host => $data->{caller_peer_host}, direction => $type.'_in', number => $data->{$type}, rws_id => $data->{caller_rewrite}->id, ); } if ($new && $new ne $data->{$type}) { push @log, sprintf "%s %s is rewritten based on the inbound rules into %s", $type, $data->{$type}, $new; } $data->{$type.'_in'} = $new || $data->{$type}; } # subscriber allowed_cli checks if ($data->{caller_subscriber}) { my %usr_prefs; foreach my $pref (qw(allowed_clis allowed_clis_reject_policy cli user_cli)) { my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( c => $c, attribute => $pref, prov_subscriber => $data->{caller_subscriber}->provisioning_voip_subscriber, ); if ($rs->first) { @{$usr_prefs{$pref}} = map { $_->value } $rs->all; } else { $rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs( c => $c, attribute => $pref, prov_domain => $data->{caller_subscriber}->provisioning_voip_subscriber->domain, ); if ($rs->first) { @{$usr_prefs{$pref}} = map { $_->value } $rs->all; } } } my $match = map { my $val = s/\*/.*/gr; $val =~ s/\?/.?/g; $data->{caller_in} =~ /^$val$/; } @{$usr_prefs{allowed_clis}}; if ($match) { push @log, sprintf "caller %s is accepted as it matches subscriber's 'allowed_clis'", $data->{caller_in}; } else { push @log, sprintf "caller %s is rejected as it does not match subscriber's 'allowed_clis'", $data->{caller_in}; if (defined $usr_prefs{allowed_clis_reject_policy}) { SWITCH: for ($usr_prefs{allowed_clis_reject_policy}[0]) { /^override_by_clir$/ && do { push @log, "'allowed_cli' reject policy is 'override_by_clir', anonymising caller"; $data->{caller_in} = 'anonymous'; last SWITCH; }; /^override_by_usernpn$/ && do { push @log, "'allowed_cli' reject policy is 'override_by_usernpn'"; foreach my $cli (qw(user_cli cli)) { if (defined $usr_prefs{$cli}) { $data->{caller_in} = $usr_prefs{$cli}[0]; $log[-1] .= sprintf ", taken from '$cli' %s", $usr_prefs{$cli}[0]; last; } } last SWITCH; }; /^reject$/ && do { push @log, "'allowed_cli' reject policy is 'reject', terminating the call"; goto RESULT; last SWITCH; }; } } } } # callee lookup if ($data->{callee_peer_id}) { my $rs = $c->model('DB')->resultset('voip_peer_groups')->search({ id => $data->{callee_peer_id}, }); unless ($rs->first) { push @log, sprintf "no callee peer found with id %d", $data->{callee_peer_id}; goto RESULT; } $data->{callee_peer} = $rs->first; unless ($data->{callee_peer}->voip_peer_hosts->first) { push @log, sprintf "callee peer with id %d does not contain any peer hosts", $rs->id; } $data->{callee_peer_host} = $data->{callee_peer}->voip_peer_hosts->first; } else { push @log, sprintf "callee subscriber lookup based on %s", $data->{callee_in}; $data->{callee_subscriber} = NGCP::Panel::Utils::Subscriber::lookup( c => $c, lookup => $data->{callee_in}, ); if ($data->{callee_subscriber}) { $data->{callee_subscriber_id} = $data->{callee_subscriber}->id; my $sub = sprintf '%s@%s', $data->{callee_subscriber}->username, $data->{callee_subscriber}->domain->domain; push @log, sprintf "found callee subscriber '%s' with id %d", $sub, $data->{callee_subscriber_id}; } else { foreach my $type (qw(caller callee)) { $data->{$type.'_uri'} = 'sip:'.$data->{$type.'_in'}; if ($data->{$type.'_domain'}) { $data->{$type.'_uri'} .= '@' . $data->{$type.'_domain'}; } elsif ($type eq 'callee' && $data->{caller_domain}) { $data->{$type.'_uri'} .= '@' . $data->{caller_domain}; } } push @log, sprintf "no callee subscriber found, performing peer lookup with caller uri %s and callee uri %s and callee %s", @{$data}{qw(caller_uri callee_uri callee_in)}; $data->{callee_peers} = NGCP::Panel::Utils::Peering::lookup( c => $c, prefix => $data->{callee_in}, caller => $data->{caller_uri}, callee => $data->{callee_uri}, ); unless ($data->{callee_peers} && scalar @{$data->{callee_peers}}) { push @log, sprintf "no callee peers found"; goto RESULT; } # as we cannot check peer reply codes here, use first peer for now $data->{callee_peer} = $data->{callee_peers}->[0]; $data->{callee_peer_id} = $data->{callee_peer}->id; push @log, sprintf "matched peer '%s' with id %d", $data->{callee_peer}->name, $data->{callee_peer}->id; unless ($data->{callee_peer}->voip_peer_hosts->first) { push @log, sprintf "callee peer with id %d does not contain any peer hosts", $data->{callee_peer}->id; } $data->{callee_peer_host} = $data->{callee_peer}->voip_peer_hosts->first; } } # callee sum up push @log, sprintf "call to %s", $data->{callee}; if ($data->{callee_subscriber}) { $log[-1] .= $data->{callee_subscriber} ? sprintf ", subscriber '%s\@%s' with id %d", $data->{callee_subscriber}->username, $data->{callee_subscriber}->domain->domain, $data->{callee_subscriber}->id : sprintf ", peer %s id %s", $data->{callee_peer}->name, $data->{callee_peer}->id; } elsif ($data->{callee_peer_host}) { $log[-1] .= sprintf " and peer host '%s' (ip: %s) with id %d", $data->{callee_peer_host}->name, $data->{callee_peer_host}->ip, $data->{callee_peer_host}->id; } else { push @log, "this call does not have any termination point"; } # callee outbound rewrite rules lookup if ($data->{callee_rewrite_id}) { my $rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ id => $data->{callee_rewrite_id}, }); if ($rs->first) { push @log, sprintf "using callee rewrite rule set '%s' with id %d", $rs->first->name, $rs->first->id; $data->{callee_rewrite} = $rs->first; } } else { my ($lookup_rws, $lookup_rws_type, $rws_rs); if ($data->{'callee_subscriber_id'}) { my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( c => $c, attribute => "rewrite_callee_out_dpid", prov_subscriber => $data->{callee_subscriber}->provisioning_voip_subscriber, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ callee_out_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'subscriber'; } else { $rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs( c => $c, attribute => "rewrite_callee_out_dpid", prov_domain => $data->{callee_subscriber}->provisioning_voip_subscriber->domain, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ callee_out_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'domain'; } else { push @log, sprintf "no callee subscriber/domain rewrite rule sets were found"; } } } elsif ($data->{callee_peer_id}) { my $rs = NGCP::Panel::Utils::Preferences::get_peer_preference_rs( c => $c, attribute => "rewrite_callee_out_dpid", peer_host => $data->{callee_peer_host}, ); if ($rs->first) { $rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({ callee_out_dpid => $rs->first->value }); } if ($rws_rs && $rws_rs->first) { $lookup_rws = $rws_rs->first; $lookup_rws_type = 'peer'; } else { push @log, sprintf "no callee peer rewrite rule sets with were found"; } } if ($lookup_rws) { push @log, sprintf "using callee %s outbound rewrite rule set '%s' with id %d", $lookup_rws_type, $lookup_rws->name, $lookup_rws->id; $data->{callee_rewrite} = $lookup_rws; } } # apply outbound rewrite rules foreach my $type (qw(caller callee)) { $data->{$type.'_out'} = $data->{$type.'_in'}; next unless $data->{callee_rewrite}; my $new; if ($data->{callee_subscriber_id}) { $new = NGCP::Panel::Utils::Subscriber::apply_rewrite( c => $c, subscriber => $data->{callee_subscriber}, direction => $type.'_out', number => $data->{$type.'_in'}, rws_id => $data->{callee_rewrite}->id, ); } elsif ($data->{callee_peer_id}) { $new = NGCP::Panel::Utils::Peering::apply_rewrite( c => $c, peer_host => $data->{callee_peer_host}, direction => $type.'_out', number => $data->{$type.'_in'}, rws_id => $data->{callee_rewrite}->id, ); } if ($new && $new ne $data->{$type.'_in'}) { push @log, sprintf "%s %s is rewritten based on the outbound rules into %s", $type, $data->{$type.'_in'}, $new; } $data->{$type.'_out'} = $new || $data->{$type.'_in'}; } RESULT: foreach my $type (qw(caller callee)) { $data->{$type.'_type'} = $data->{$type.'_subscriber'} ? 'subscriber' : $data->{$type.'_peer'} ? 'peer' : 'unknown'; #foreach my $dir (qw(in out)) { #$data->{$type.'_'.$dir} ||= $data->{$type.'_type'} ne 'unknown' # ? $data->{$type} # : ''; # fill in a value even if caller/callee is not identified #} } $c->stash( template => 'callrouting/result.tt', close_target => '/callroutingverify', caller => $data->{caller}, callee => $data->{callee}, caller_in => $data->{caller_in}, callee_in => $data->{callee_in}, caller_out => $data->{caller_out}, callee_out => $data->{callee_out}, caller_type => $data->{caller_type}, callee_type => $data->{callee_type}, log => \@log, form => undef, ); return; } 1; __END__ =head1 NAME NGCP::Panel::Controller::CallRouting =head1 DESCRIPTION A helper to manipulate the call routing data =head1 AUTHOR Kirill Solomko =head1 LICENSE This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut # vim: set tabstop=4 expandtab: