diff --git a/bin/check.sh b/bin/check.sh index ff6ce1be..01917028 100755 --- a/bin/check.sh +++ b/bin/check.sh @@ -125,6 +125,12 @@ function create_voip_prefs "${BIN_DIR}/create_ncos.pl" "${SCEN_CHECK_DIR}/ncos.yml" fi + if [ -f "${SCEN_CHECK_DIR}/soundsets.yml" ]; then + echo "$(date) - Creating soundsets" + "${BIN_DIR}/create_soundsets.pl" \ + "${SCEN_CHECK_DIR}/soundsets.yml" "${SCEN_CHECK_DIR}/scenario_ids.yml" + fi + if [ -f "${SCEN_CHECK_DIR}/peer.yml" ]; then echo "$(date) - Creating peers" "${BIN_DIR}/create_peers.pl" \ @@ -176,6 +182,11 @@ function delete_voip sed -e "s:$(cat "${SCEN_CHECK_DIR}/hosts")::" -i /etc/hosts rm "${SCEN_CHECK_DIR}/hosts" fi + + if [ -f "${SCEN_CHECK_DIR}/soundsets.yml" ]; then + echo "$(date) - Deleting soundsets" + "${BIN_DIR}/create_soundsets.pl" -delete "${SCEN_CHECK_DIR}/soundsets.yml" + fi } function delete_locations diff --git a/bin/create_soundsets.pl b/bin/create_soundsets.pl new file mode 100755 index 00000000..04e44ca3 --- /dev/null +++ b/bin/create_soundsets.pl @@ -0,0 +1,140 @@ +#!/usr/bin/perl +# +# Copyright: 2013-2016 Sipwise Development Team +# +# 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 package 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 . +# +# On Debian systems, the complete text of the GNU General +# Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". +# +use strict; +use warnings; + +use English; +use Getopt::Long; +use Cwd 'abs_path'; +use Config::Tiny; +use Sipwise::API qw(all); +use YAML qw{ DumpFile LoadFile }; +use File::Basename qw{ basename }; + +my $config = Config::Tiny->read('/etc/default/ngcp-api'); +my $opts; +if ($config) { + $opts = {}; + $opts->{host} = $config->{_}->{NGCP_API_IP}; + $opts->{port} = $config->{_}->{NGCP_API_PORT}; + $opts->{sslverify} = $config->{_}->{NGCP_API_SSLVERIFY}; +} +my $api = Sipwise::API->new($opts); +$opts = $api->opts; +my $del = 0; +my $ids; +my $ids_path; + +sub usage { + return "Usage:\n$PROGRAM_NAME scenario.yml scenarios_ids.yml\n". + "Options:\n". + " -delete\n". + " -d debug\n". + " -h this help\n"; +} +my $help = 0; +GetOptions ("h|help" => \$help, + "d|debug" => \$opts->{verbose}, + "delete" => \$del) + or die("Error in command line arguments\n".usage()); + +die(usage()) unless (!$help); +if(!$del) { + die("Error: wrong number of arguments\n".usage()) unless ($#ARGV == 1); +} else { + die("Error: wrong number of arguments\n".usage()) unless ($#ARGV == 0); +} + +sub manage_soundfiles +{ + my ($data, $id) = @_; + + foreach my $sf (sort keys %{$data->{sounds}}) + { + my $sf_data = $data->{sounds}->{$sf}; + my $filename = $sf_data->{filename}; + $sf_data->{set_id} = $data->{id}; + $sf_data->{handle} = $sf; + $sf_data->{filename} = basename($filename, '.wav'); + $api->upload_soundfile($sf_data, abs_path($filename)); + print "[$filename] uploaded\n"; + } + return; +} + +sub do_create +{ + my $data = shift; + foreach my $st (sort keys %{$data->{soundsets}}) + { + my $st_data = { + name => $st, + reseller_id => $data->{soundsets}->{$st}->{reseller_id} + }; + $data->{soundsets}->{$st}->{id} = $api->check_soundset_exists($st_data); + if(defined $data->{soundsets}->{$st}->{id}) { + print "soundset[$st] already there [$data->{soundsets}->{$st}->{id}]\n"; + } else { + $data->{soundsets}->{$st}->{id} = $api->create_soundset($st_data); + print "soundset [$st]: created [$data->{soundsets}->{$st}->{id}]\n"; + manage_soundfiles($data->{soundsets}->{$st}); + } + $ids->{soundsets}->{$st}->{id} = $data->{soundsets}->{$st}->{id}; + } + return; +} + +sub do_delete +{ + my $data = shift; + foreach my $st (sort keys %{$data->{soundsets}}) + { + my $st_data = { + name => $st, + reseller_id => $data->{soundsets}->{$st}->{reseller_id} + }; + $data->{soundsets}->{$st}->{id} = $api->check_soundset_exists($st_data); + if(defined $data->{soundsets}->{$st}->{id}) { + $api->delete_soundset($data->{soundsets}->{$st}->{id}); + print "delete soundset[$st] -> [$data->{soundsets}->{$st}->{id}]\n"; + } + } + return; +} + +sub main { + my $r = shift; + + if ($del) { + return do_delete($r); + } else { + return do_create($r); + } +} + +if(! $del) { + $ids_path = abs_path($ARGV[1]); + $ids = LoadFile($ids_path); +} +main(LoadFile(abs_path($ARGV[0])), $ids); +if(! $del) { + DumpFile($ids_path, $ids); +} diff --git a/bin/set_preferences.pl b/bin/set_preferences.pl index 8418c472..97fba9b1 100755 --- a/bin/set_preferences.pl +++ b/bin/set_preferences.pl @@ -54,6 +54,23 @@ GetOptions ("h|help" => \$help, "d|debug" => \$opts->{verbose}) die(usage()) unless (!$help); die("Error: wrong number of arguments\n".usage()) unless ($#ARGV == 0); +sub resolve_ids { + my $prefs = shift; + return $prefs; + my $key; + if(defined $prefs->{sound_set}) { + $key = $prefs->{sound_set}; + my $id = $api->check_soundset_exists({name => $key}); + if($id) { + $prefs->{sound_set} = $id; + print "soundset[$key] => $prefs->{sound_set}\n"; + } else { + die("soundset[$key] not found\n"); + } + } + return $prefs; +} + sub merge { my $a = shift; @@ -163,21 +180,20 @@ sub set_peer_preferences sub main { my $prefs = shift; - my $peers; - my $rule_set; for my $key (keys %{$prefs}) { + my $prefs_id = resolve_ids($prefs->{$key}); print "processing $key\n"; if ( $key =~ /@/ ) { my @fields = split /@/, $key; if (!$fields[0]) { - set_domain_preferences($fields[1], $prefs->{$key}); + set_domain_preferences($fields[1], $prefs_id); } else { - set_subscriber_preferences($fields[0], $fields[1], $prefs->{$key}); + set_subscriber_preferences($fields[0], $fields[1], $prefs_id); } } else { - set_peer_preferences($key, $prefs->{$key}); + set_peer_preferences($key, $prefs_id); } } exit; @@ -197,5 +213,4 @@ sub get_json { } my $cf = get_json($ARGV[0]); - main($cf); diff --git a/debian/control b/debian/control index c286432c..3397ce91 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Package: kamailio-config-tests Architecture: all Depends: curl, + libfile-slurp-perl, libgraphviz-perl, libjson-perl, libtemplate-perl, diff --git a/lib/Sipwise/API.pm b/lib/Sipwise/API.pm index 502be4e0..65901835 100644 --- a/lib/Sipwise/API.pm +++ b/lib/Sipwise/API.pm @@ -29,6 +29,7 @@ use LWP::UserAgent; use IO::Socket::SSL; use URI; use Data::Dumper; +use File::Slurp; my $opts_default = { host => '127.0.0.1', @@ -90,6 +91,21 @@ sub do_request { return $res; } +sub _do_binary_request { + my ($self, $ua, $url, $filename, $ct) = @_; + my $content; + + my $req = HTTP::Request->new('POST', $url); + $req->header('Content-Type' => $ct); + $req->header('Prefer' => 'return=representation'); + $req->content(read_file($filename)); + my $res = $ua->request($req); + if(!$res->is_success) { + print "$url\n"; + } + return $res; +} + sub do_query { my $self = shift; my $ua = shift; @@ -645,4 +661,42 @@ sub delete_ncoslnpcarrier { return $self->_delete($urldata); } +sub check_soundset_exists { + my ($self, $data) = @_; + my $urldata = "/api/soundsets/"; + my $collection_id = 'ngcp:soundsets'; + + return $self->_exists($data, $urldata, $collection_id); +} + +sub create_soundset { + my ($self, $data) = @_; + my $urldata = '/api/soundsets/'; + + return $self->_create($data, $urldata); +} + +sub delete_soundset { + my ($self, $id) = @_; + my $urldata = "/api/soundsets/${id}"; + + return $self->_delete($urldata); +} + +sub upload_soundfile { + my ($self, $data, $filepath) = @_; + my $urldata = "/api/soundfiles/?". + "filename=$data->{filename}&handle=$data->{handle}". + "&set_id=$data->{set_id}&loopplay=$data->{loopplay}"; + my $urlbase = 'https://'.$self->{opts}->{host}.':'.$self->{opts}->{port}; + + my $ua = $self->create_ua(); + my $res = $self->_do_binary_request($ua, $urlbase.$urldata, + $filepath, 'audio/x-wav'); + if(! $res->is_success) { + die $res->as_string; + } + return; +} + 1; diff --git a/scenarios/invite_allowip_soundset/0001_test.yml.tt2 b/scenarios/invite_allowip_soundset/0001_test.yml.tt2 new file mode 100644 index 00000000..bc4420e6 --- /dev/null +++ b/scenarios/invite_allowip_soundset/0001_test.yml.tt2 @@ -0,0 +1,30 @@ +flow: + - start|DEFAULT_ROUTE: + - start|ROUTE_NET_INFO: + - return|ROUTE_NET_INFO: + - start|ROUTE_PRX_REQUEST: + - start|ROUTE_INITVARS: + - return|ROUTE_INITVARS: + - start|ROUTE_INVITE: + - start|ROUTE_LOAD_CALLEE_DOMAIN_PREF: + - start|ROUTE_CLEAR_CALLEE_DOMAIN_PREF: + - return|ROUTE_CLEAR_CALLEE_DOMAIN_PREF: + - return|ROUTE_LOAD_CALLEE_DOMAIN_PREF: + - start|ROUTE_FIND_CALLER: + - start|ROUTE_AUTH: + - exit|ROUTE_AUTH: +sip_in: + - '^INVITE' + - 'Contact: sip:testuser1002@' + - 'CSeq: 1 INVITE' + - 'Max-Forwards: 69' + - 'Content-Type: application/sdp' +sip_out: + - [ + '^SIP/2.0 100 Trying', + 'CSeq: 1 INVITE' + ] + - [ + '^SIP/2.0 407 Proxy Authentication Required', + 'CSeq: 1 INVITE' + ] diff --git a/scenarios/invite_allowip_soundset/0003_test.yml.tt2 b/scenarios/invite_allowip_soundset/0003_test.yml.tt2 new file mode 100644 index 00000000..de323c2f --- /dev/null +++ b/scenarios/invite_allowip_soundset/0003_test.yml.tt2 @@ -0,0 +1,98 @@ +flow: + - start|DEFAULT_ROUTE: + - start|ROUTE_NET_INFO: + - return|ROUTE_NET_INFO: + - start|ROUTE_PRX_REQUEST: + - start|ROUTE_INITVARS: + - return|ROUTE_INITVARS: + - start|ROUTE_INVITE: + - start|ROUTE_LOAD_CALLEE_DOMAIN_PREF: + - start|ROUTE_CLEAR_CALLEE_DOMAIN_PREF: + - return|ROUTE_CLEAR_CALLEE_DOMAIN_PREF: + - return|ROUTE_LOAD_CALLEE_DOMAIN_PREF: + - start|ROUTE_FIND_CALLER: + - start|ROUTE_AUTH: + - start|ROUTE_AUTH_HELPER: + $fd: spce.test + $var(realm_user): testuser1002 + $var(realm_domain): spce.test + - return|ROUTE_AUTH_HELPER: + $avp(orig_acc_caller_user): ['testuser1002'] + $avp(orig_acc_caller_domain): ['spce.test'] + - start|ROUTE_ADD_CALLINFO_REPLY: + - start|ROUTE_ADD_CALLINFO_CALLER_PRIMARY: + - return|ROUTE_ADD_CALLINFO_CALLER_PRIMARY: + - return|ROUTE_ADD_CALLINFO_REPLY: + - return|ROUTE_AUTH: + - return|ROUTE_FIND_CALLER: + - start|ROUTE_LOAD_CALLER_PREF: + - start|ROUTE_CLEAR_CALLER_PREF: + - return|ROUTE_CLEAR_CALLER_PREF: + - start|ROUTE_LOAD_CALLER_CONTRACT_PREF: + - return|ROUTE_LOAD_CALLER_CONTRACT_PREF: + - return|ROUTE_LOAD_CALLER_PREF: + $xavp(caller_usr_prefs[0]=>allowed_ips_grp[*]): '\d+' + $xavp(caller_real_prefs[0]=>allowed_ips_grp[*]): '\d+' + - start|ROUTE_DLG_MANAGE: + - return|ROUTE_DLG_MANAGE: + - start|ROUTE_ACC_FAILURE: + - start|ROUTE_ACC_CALLER: + - return|ROUTE_ACC_CALLER: + - start|ROUTE_ACC_CALLEE: + - return|ROUTE_ACC_CALLEE: + - return|ROUTE_ACC_FAILURE: + - start|ROUTE_EARLY_REJECT: + - start|ROUTE_TO_APPSRV: + - start|ROUTE_LOAD_APPSRV: + - start|ROUTE_CNT_DLG_CHECK: + - return|ROUTE_CNT_DLG_CHECK: + - return|ROUTE_LOAD_APPSRV: + - start|ROUTE_OUTBOUND: + - start|BRANCH_ROUTE_NO_SBC: + - start|ROUTE_BRANCH_ACC_RTP: + - return|ROUTE_BRANCH_ACC_RTP: + - start|ROUTE_FILTER_PRACK: + - return|ROUTE_FILTER_PRACK: + - start|ROUTE_ADD_CALLINFO: + - start|ROUTE_ADD_CALLINFO_CALLER_PRIMARY: + - return|ROUTE_ADD_CALLINFO_CALLER_PRIMARY: + - start|ROUTE_ADD_CALLINFO_CALLEE_PRIMARY: + - return|ROUTE_ADD_CALLINFO_CALLEE_PRIMARY: + - return|ROUTE_ADD_CALLINFO: + - return|BRANCH_ROUTE_NO_SBC: + - return|BRANCH_ROUTE_NO_SBC: + - exit|ROUTE_OUTBOUND: +sip_in: + - '^INVITE' + - 'Contact: sip:testuser1002@' + - 'CSeq: 2 INVITE' + - 'Max-Forwards: 69' + - 'Content-Type: application/sdp' + - 'Proxy-Authorization: Digest username="testuser1002"' +sip_out: + - [ + '^SIP/2.0 100 Trying', + 'CSeq: 2 INVITE', + 'From: ;ip=127.126.0.1;port=\d+;primary=4311002', + 'P-NGCP-Callee-Info: ;ip=127.0.0.1;port=5080;primary=4311002', + ] diff --git a/scenarios/invite_allowip_soundset/0004_test.yml.tt2 b/scenarios/invite_allowip_soundset/0004_test.yml.tt2 new file mode 100644 index 00000000..2b350702 --- /dev/null +++ b/scenarios/invite_allowip_soundset/0004_test.yml.tt2 @@ -0,0 +1,19 @@ +flow: + - start|REPLY_ROUTE_NAT: + - exit|REPLY_ROUTE_NAT: +sip_in: + - '^SIP/2.0 183 Progress' + - 'From: ' + - 'Content-Type: application/sdp' + - 'Content-Length: \d+' +sip_out: + - [ + '^SIP/2.0 183 Progress', + 'Contact: ', + 'Content-Type: application/sdp', + 'Content-Length: \d+', + 'P-Out-Socket: udp:127.0.0.1:5060' + ] diff --git a/scenarios/invite_allowip_soundset/0005_test.yml.tt2 b/scenarios/invite_allowip_soundset/0005_test.yml.tt2 new file mode 100644 index 00000000..2ef1f28d --- /dev/null +++ b/scenarios/invite_allowip_soundset/0005_test.yml.tt2 @@ -0,0 +1,33 @@ +flow: + - start|REPLY_ROUTE_NAT: + - start|FAILURE_ROUTE_EARLY_REJECT: + - start|ROUTE_STOP_RTPPROXY_BRANCH: + - start|ROUTE_RESTORE_CLUSTERSET: + - return|ROUTE_RESTORE_CLUSTERSET: + - return|ROUTE_STOP_RTPPROXY_BRANCH: + - exit|FAILURE_ROUTE_EARLY_REJECT: + - start|dialog:failed: + - return|dialog:failed: + $avp(lua_dlg_profile): None + - return|dialog:failed: +sip_in: + - '^SIP/2.0 403 Unauthorized IP detected' + - 'From: ;tag=' + - 'To: ;tag=' + - 'CSeq: 2 INVITE' + - 'Content-Length: 0' +sip_out: + - [ + '^ACK sip:earlyannounce@app.local SIP/2.0', + 'From: ;tag=', + 'To: ;tag=', + 'CSeq: 2 ACK', + 'Max-Forwards: 68', + 'Content-Length: 0' + ] + - [ + '^SIP/2.0 403 Unauthorized IP detected', + 'CSeq: 2 INVITE', + 'Content-Length: 0', + 'P-Out-Socket: udp:127.0.0.1:5060' + ] diff --git a/scenarios/invite_allowip_soundset/0006_test.yml.tt2 b/scenarios/invite_allowip_soundset/0006_test.yml.tt2 new file mode 100644 index 00000000..758766c5 --- /dev/null +++ b/scenarios/invite_allowip_soundset/0006_test.yml.tt2 @@ -0,0 +1,23 @@ +flow: + - start|DEFAULT_ROUTE: + - start|ROUTE_NET_INFO: + - return|ROUTE_NET_INFO: + - start|ROUTE_PRX_REQUEST: + - start|ROUTE_INITVARS: + - return|ROUTE_INITVARS: + - start|ROUTE_LOCAL: + - return|ROUTE_LOCAL: +sip_in: + - '^ACK sip:testuser1003@spce.test:5060 SIP/2.0' + - 'From: ;tag=' + - 'To: ;tag=' + - 'CSeq: 2 ACK' + - 'Contact: sip:testuser1002@127.126.0.1:\d+' + - 'Max-Forwards: 69' + - 'Content-Length: 0' + - 'P-NGCP-Src-Ip: 127.126.0.1' + - 'P-NGCP-Src-Port: \d+' + - 'P-NGCP-Src-Proto: udp' + - 'P-NGCP-Src-Af: 4' + - 'P-Sock-Info: udp:127.0.0.1:5060' +sip_out: [] diff --git a/scenarios/invite_allowip_soundset/prefs.json b/scenarios/invite_allowip_soundset/prefs.json new file mode 100644 index 00000000..c18696fb --- /dev/null +++ b/scenarios/invite_allowip_soundset/prefs.json @@ -0,0 +1,11 @@ +{ + "@spce.test": { + "sound_set": "default_soundset" + }, + "testuser1002@spce.test": { + "allowed_ips": [ + "1.2.3.4" + ], + "cli": 4311002 + } +} diff --git a/scenarios/invite_allowip_soundset/scenario.yml b/scenarios/invite_allowip_soundset/scenario.yml new file mode 100644 index 00000000..3f131b79 --- /dev/null +++ b/scenarios/invite_allowip_soundset/scenario.yml @@ -0,0 +1,38 @@ +test_uuid: invite_allowip_soundset +domains: + 'spce.test': + reseller_id: 1 +customers: + 'customer.test': + contacts: + - email: "customer.test@spce.test" + reseller_id: 1 + details: + status: 'active' + type: 'sipaccount' + billing_profile_id: 1 + reseller_id: 1 +subscribers: + spce.test: + testuser1003: + customer: 'customer.test' + password: testuser + cc: 43 + ac: 1 + sn: 1003 + testuser1002: + customer: 'customer.test' + password: testuser + cc: 43 + ac: 1 + sn: 1002 +scenarios: + - ip: 127.126.0.1 + username: testuser1002 + domain: spce.test + responders: + - ip: 127.1.0.1 + username: testuser1003 + domain: spce.test + register: no + active: no diff --git a/scenarios/invite_allowip_soundset/sipp_scenario00.xml b/scenarios/invite_allowip_soundset/sipp_scenario00.xml new file mode 100644 index 00000000..9820fc2f --- /dev/null +++ b/scenarios/invite_allowip_soundset/sipp_scenario00.xml @@ -0,0 +1,110 @@ + + + + + ;tag=[pid]SIPpTag00[call_number] + To: + Call-ID: NGCP%[field4 file="callee.csv" line=0]%///[call_id] + CSeq: 1 INVITE + Contact: sip:[field0 file="caller.csv" line=0]@[local_ip]:[local_port] + Max-Forwards: 70 + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 8 + a=rtpmap:8 PCMA/8000 + a=ptime:50 + + ]]> + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [peer_tag_param] + Call-ID: NGCP%[field4 file="callee.csv" line=0]%///[call_id] + CSeq: 1 ACK + Contact: sip:[field0 file="caller.csv" line=0]@[local_ip]:[local_port] + Max-Forwards: 70 + Content-Length: 0 + + ]]> + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: + Call-ID: NGCP%[field4 file="callee.csv" line=0]%///[call_id] + CSeq: 2 INVITE + Contact: sip:[field0 file="caller.csv" line=0]@[local_ip]:[local_port] + Max-Forwards: 70 + [field1 file="caller.csv" line=0] + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 8 + a=rtpmap:8 PCMA/8000 + a=ptime:50 + + ]]> + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [peer_tag_param] + Call-ID: NGCP%[field4 file="callee.csv" line=0]%///[call_id] + CSeq: 2 ACK + Contact: sip:[field0 file="caller.csv" line=0]@[local_ip]:[local_port] + Max-Forwards: 70 + Content-Length: 0 + + ]]> + + + + + + + + diff --git a/scenarios/invite_allowip_soundset/soundsets.yml b/scenarios/invite_allowip_soundset/soundsets.yml new file mode 100644 index 00000000..b22ee0e3 --- /dev/null +++ b/scenarios/invite_allowip_soundset/soundsets.yml @@ -0,0 +1,7 @@ +soundsets: + 'default_soundset': + reseller_id: 1 + sounds: + unauth_caller_ip: + filename: sounds/no_sh.wav + loopplay: false diff --git a/sounds/no_sh.wav b/sounds/no_sh.wav new file mode 100644 index 00000000..5c9471ab Binary files /dev/null and b/sounds/no_sh.wav differ