diff --git a/debian/control b/debian/control index 5d72102584..0c7dcc072e 100644 --- a/debian/control +++ b/debian/control @@ -29,6 +29,7 @@ Depends: gettext, libdata-printer-perl, libdata-record-perl, libdata-serializer-perl, + libdata-structure-util-perl, libdata-validate-ip-perl, libdatetime-format-http-perl, libdatetime-format-iso8601-perl, @@ -83,6 +84,7 @@ Depends: gettext, libsereal-decoder-perl, libsereal-encoder-perl, libsipwise-base-perl, + libsoap-lite-perl, libstring-mkpasswd-perl, libtemplate-perl, libtemplate-plugin-json-escape-perl, diff --git a/etc/Intercept.wsdl b/etc/Intercept.wsdl new file mode 100644 index 0000000000..b43c50afb6 --- /dev/null +++ b/etc/Intercept.wsdl @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/NGCP/Panel/Controller/Root.pm b/lib/NGCP/Panel/Controller/Root.pm index 2dddab326a..923e4566ab 100644 --- a/lib/NGCP/Panel/Controller/Root.pm +++ b/lib/NGCP/Panel/Controller/Root.pm @@ -58,6 +58,7 @@ sub auto :Private { or $c->req->uri->path =~ m|^/recoverwebpassword/?$| or $c->req->uri->path =~ m|^/resetwebpassword/?$| or $c->req->uri->path =~ m|^/internalsms/receive/?$| + or $c->req->uri->path =~ m|^/soap/intercept(\.wsdl)?/?$|i ) { $c->log->debug("*** Root::auto skip authn, grant access to " . $c->request->path); return 1; diff --git a/lib/NGCP/Panel/Controller/SOAP/Intercept.pm b/lib/NGCP/Panel/Controller/SOAP/Intercept.pm new file mode 100644 index 0000000000..4f9c8f677b --- /dev/null +++ b/lib/NGCP/Panel/Controller/SOAP/Intercept.pm @@ -0,0 +1,435 @@ +package NGCP::Panel::Controller::SOAP::Intercept; +use NGCP::Panel::Utils::Generic qw(:all); +use Sipwise::Base; + +use parent 'Catalyst::Controller'; + +use File::Slurp; +use SOAP::Transport::LOCAL; + +sub thewsdl : GET Path('/SOAP/Intercept.wsdl') :Local :Args() { + my ($self, $c, $args) = @_; + + my $thewsdl = read_file('/etc/ngcp-panel/Intercept.wsdl'); + $c->response->body($thewsdl); + $c->response->content_type('text/xml'); +} + +sub index : POST Path('/SOAP/Intercept') { + my ($self, $c) = @_; + my $h = Sipwise::SOAP::Intercept->new(c => $c); + my $out = SOAP::Transport::LOCAL::Client->new + ->dispatch_with({ 'urn:/SOAP/Intercept' => $h }) + ->handle($c->req->body); + $c->response->content_type('text/xml'); + $c->response->body($out); +} + +package Sipwise::SOAP::Intercept; +use Sipwise::Base; +use NGCP::Panel::Form::Intercept::Authentication; +use NGCP::Panel::Form::Intercept::Create; +use NGCP::Panel::Form::Intercept::Update; +use NGCP::Panel::Form::Intercept::Delete; +use Data::Structure::Util qw/unbless/; +use UUID; +use Moose; +has 'c' => (is => 'rw', isa => 'Object'); + +sub _validate { + my ($self, $form, $data) = @_; + + unbless($data); + $form->process(params => $data); + unless($form->validated) { + if($form->has_form_errors) { + my @errs = $form->form_errors; + die SOAP::Fault + ->faultcode('Client.Syntax.MissingParameter') + ->faultstring(shift @errs); + } else { + my @fields = $form->error_fields; + my $field = shift @fields; + my @errs = $field->errors; + my $err = shift @errs; + die SOAP::Fault + ->faultcode('Client.Syntax.MalformedParameter') + ->faultstring($field->label . ": " . join('; ', @{ $err })); + } + } +} + + +sub _auth { + my ($self, $auth) = @_; + my $c = $self->c; + + $self->_validate(NGCP::Panel::Form::Intercept::Authentication->new(ctx => $c), $auth); + + try { + my $admin = $c->model('DB')->resultset('admins')->search({ + login => $auth->{username}, + md5pass => { '=' => \['MD5("'.$auth->{password}.'")'] }, + })->first; + die unless($admin && ($admin->is_superuser || $admin->lawful_intercept)); + } catch($e) { + die SOAP::Fault + ->faultcode('Client.Auth.Refused') + ->faultstring("admin may not access LI data (wrong credentials or lawful_intercept flag not set)"); + } +} + +sub create_interception { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + $self->_validate(NGCP::Panel::Form::Intercept::Create->new(ctx => $c), $params); + + my $i; + my $num; + try { + $num = $c->model('DB')->resultset('voip_dbaliases')->find({ + username => $params->{number} + }); + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + unless($num) { + die SOAP::Fault + ->faultcode('Client.Voip.NoSuchSubscriber') + ->faultstring("number '$$params{number}' is not assigned to any subscriber"); + } + my ($uuid_bin, $uuid_string); + UUID::generate($uuid_bin); + UUID::unparse($uuid_bin, $uuid_string); + my $guard = $c->model('DB')->txn_scope_guard; + { + try { + $i = $self->c->model('DB')->resultset('voip_intercept')->create({ + reseller_id => $num->subscriber->voip_subscriber->contract->contact->reseller_id, + LIID => $params->{LIID}, + number => $params->{number}, + cc_required => $params->{cc_required}, + delivery_host => $params->{iri_delivery}->{host}, + delivery_port => $params->{iri_delivery}->{port}, + delivery_user => $params->{iri_delivery}->{user}, + delivery_pass => $params->{iri_delivery}->{pass}, + deleted => 0, + uuid => $uuid_string, + sip_username => $num->subscriber->username, + sip_domain => $num->domain->domain, + create_timestamp => \['NOW()'], + $params->{cc_required} ? (cc_delivery_host => $params->{cc_delivery}->{host}) : (), + $params->{cc_required} ? (cc_delivery_port => $params->{cc_delivery}->{port}) : (), + }); + $guard->commit; + + NGCP::Panel::Utils::Interception::request($c, 'POST', undef, { + liid => $i->LIID, + uuid => $i->uuid, + number => $i->number, + sip_username => $num->subscriber->username, + sip_domain => $num->domain->domain, + delivery_host => $i->delivery_host, + delivery_port => $i->delivery_port, + delivery_user => $i->delivery_user, + delivery_password => $i->delivery_pass, + cc_required => $i->cc_required, + cc_delivery_host => $i->cc_delivery_host, + cc_delivery_port => $i->cc_delivery_port, + }); + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + } + return $i->id; +} + +sub update_interception { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + $self->_validate(NGCP::Panel::Form::Intercept::Update->new(ctx => $c), $params); + + my $i; + try { + $i = $c->model('DB')->resultset('voip_intercept')->search({ + id => $params->{id}, + deleted => 0, + })->first; + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + + unless($i) { + die SOAP::Fault + ->faultcode('Client.Intercept.NoSuchInterception') + ->faultstring("interception ID '$$params{id}' does not exist"); + } + + my $guard = $c->model('DB')->txn_scope_guard; + { + if($params->{data}->{iri_delivery}) { + $i->delivery_host($params->{data}->{iri_delivery}->{host}); + $i->delivery_port($params->{data}->{iri_delivery}->{port}); + $i->delivery_user($params->{data}->{iri_delivery}->{username}); + $i->delivery_pass($params->{data}->{iri_delivery}->{password}); + } + if($params->{data}->{cc_delivery}) { + $i->cc_delivery_host($params->{data}->{cc_delivery}->{host}); + $i->cc_delivery_port($params->{data}->{cc_delivery}->{port}); + } + $i->cc_required($params->{data}->{cc_required}); + + try { + $i->update(); + $guard->commit; + + NGCP::Panel::Utils::Interception::request($c, 'PUT', $i->uuid, { + liid => $i->LIID, + uuid => $i->uuid, + number => $i->number, + sip_username => $i->sip_username, + sip_domain => $i->sip_domain, + delivery_host => $i->delivery_host, + delivery_port => $i->delivery_port, + delivery_user => $i->delivery_user, + delivery_password => $i->delivery_pass, + cc_required => $i->cc_required, + cc_delivery_host => $i->cc_delivery_host, + cc_delivery_port => $i->cc_delivery_port, + }); + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + } + return; +} + +sub delete_interception { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + $self->_validate(NGCP::Panel::Form::Intercept::Delete->new(ctx => $c), $params); + + my $i; + try { + $i = $c->model('DB')->resultset('voip_intercept')->search({ + id => $params->{id}, + deleted => 0, + })->first; + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + + unless($i) { + die SOAP::Fault + ->faultcode('Client.Intercept.NoSuchInterception') + ->faultstring("interception ID '$$params{id}' does not exist"); + } + + my $guard = $c->model('DB')->txn_scope_guard; + { + try { + my $uuid = $i->uuid; + $i->update({ + deleted => 1, + reseller_id => undef, + LIID => undef, + number => undef, + cc_required => 0, + delivery_host => undef, + delivery_port => undef, + delivery_user => undef, + delivery_pass => undef, + cc_delivery_host => undef, + cc_delivery_port => undef, + sip_username => undef, + sip_domain => undef, + uuid => undef, + }); + $guard->commit; + + NGCP::Panel::Utils::Interception::request($c, 'DELETE', $uuid); + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + } + return; +} + +sub get_interception_by_id { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + + my $i; + try { + $i = $c->model('DB')->resultset('voip_intercept')->search({ + id => $params->{id}, + deleted => 0, + })->first; + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + + unless($i) { + die SOAP::Fault + ->faultcode('Client.Intercept.NoSuchInterception') + ->faultstring("interception ID '$$params{id}' does not exist"); + } + + return { + id => $i->id, + LIID => $i->LIID, + number => $i->number, + cc_required => $i->cc_required, + iri_delivery => { + host => $i->delivery_host, + port => $i->delivery_port, + username => $i->delivery_user, + password => $i->delivery_pass, + }, + cc_delivery => { + host => $i->cc_delivery_host, + port => $i->cc_delivery_port, + } + }; +} + +sub get_interceptions_by_liid { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + my @interceptions = (); + + try { + my $rs = $c->model('DB')->resultset('voip_intercept')->search({ + LIID => $params->{LIID}, + deleted => 0, + }); + while(my $i = $rs->next) { + push @interceptions, { + id => $i->id, + LIID => $i->LIID, + number => $i->number, + cc_required => $i->cc_required, + iri_delivery => { + host => $i->delivery_host, + port => $i->delivery_port, + username => $i->delivery_user, + password => $i->delivery_pass, + }, + cc_delivery => { + host => $i->cc_delivery_host, + port => $i->cc_delivery_port, + } + }; + } + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + return \@interceptions; +} + +sub get_interceptions_by_number { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + my @interceptions = (); + + try { + my $rs = $c->model('DB')->resultset('voip_intercept')->search({ + number => $params->{number}, + deleted => 0, + }); + while(my $i = $rs->next) { + push @interceptions, { + id => $i->id, + LIID => $i->LIID, + number => $i->number, + cc_required => $i->cc_required, + iri_delivery => { + host => $i->delivery_host, + port => $i->delivery_port, + username => $i->delivery_user, + password => $i->delivery_pass, + }, + cc_delivery => { + host => $i->cc_delivery_host, + port => $i->cc_delivery_port, + } + }; + } + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + return \@interceptions; +} + +sub get_interceptions { + my ($self, $auth, $params) = @_; + my $c = $self->c; + + $self->_auth($auth); + my @interceptions = (); + + try { + my $rs = $c->model('DB')->resultset('voip_intercept')->search({ + deleted => 0, + }); + while(my $i = $rs->next) { + push @interceptions, { + id => $i->id, + LIID => $i->LIID, + number => $i->number, + cc_required => $i->cc_required, + iri_delivery => { + host => $i->delivery_host, + port => $i->delivery_port, + username => $i->delivery_user, + password => $i->delivery_pass, + }, + cc_delivery => { + host => $i->cc_delivery_host, + port => $i->cc_delivery_port, + } + }; + } + } catch($e) { + die SOAP::Fault + ->faultcode('Server.Internal') + ->faultstring($e); + } + return \@interceptions; +} + + + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Intercept/Authentication.pm b/lib/NGCP/Panel/Form/Intercept/Authentication.pm new file mode 100644 index 0000000000..dc59c21a78 --- /dev/null +++ b/lib/NGCP/Panel/Form/Intercept/Authentication.pm @@ -0,0 +1,28 @@ +package NGCP::Panel::Form::Intercept::Authentication; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'username' => ( + type => 'Text', + label => 'username', + required => 1, +); + +has_field 'password' => ( + type => 'Text', + label => 'password', + required => 1, +); + +has_field 'type' => ( + type => 'Select', + label => 'type', + required => 1, + options => [ + { value => 'admin', 'label' => 'admin' }, + ], +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Intercept/Create.pm b/lib/NGCP/Panel/Form/Intercept/Create.pm new file mode 100644 index 0000000000..e8229d1803 --- /dev/null +++ b/lib/NGCP/Panel/Form/Intercept/Create.pm @@ -0,0 +1,88 @@ +package NGCP::Panel::Form::Intercept::Create; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'LIID' => ( + type => 'PosInteger', + label => 'LIID', + required => 1, +); + +has_field 'number' => ( + type => 'Text', + label => 'number', + required => 1, +); + +has_field 'cc_required' => ( + type => 'PosInteger', + label => 'cc_required', + range_start => 0, + range_end => 1, + required => 1, +); + +has_field 'iri_delivery' => ( + type => 'Compound', + label => 'iri_delivery', + required => 1, + validate_when_empty => 1, +); + +has_field 'iri_delivery.host' => ( + type => 'Text', + label => 'iri_delivery.host', + required => 1, +); + +has_field 'iri_delivery.port' => ( + type => 'PosInteger', + label => 'iri_delivery.port', + required => 1, + range_start => 1, + range_end => 65535, +); + +has_field 'iri_delivery.username' => ( + type => 'Text', + label => 'iri_delivery.username', + required => 0, +); + +has_field 'iri_delivery.password' => ( + type => 'Text', + label => 'iri_delivery.password', + required => 0, +); + +has_field 'cc_delivery' => ( + type => 'Compound', + required => 0, + validate_when_empty => 0, +); + +has_field 'cc_delivery.host' => ( + type => 'Text', + label => 'cc_delivery.host', + required => 1, +); + +has_field 'cc_delivery.port' => ( + type => 'PosInteger', + label => 'cc_delivery.port', + required => 1, + range_start => 1, + range_end => 65535, +); + + +sub valprint { + my($self, $field) = @_; + my $c = $field->form->ctx; + + $c->log->info("validating " . $field->name . "=" . $field->value); +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Intercept/Delete.pm b/lib/NGCP/Panel/Form/Intercept/Delete.pm new file mode 100644 index 0000000000..7139bac101 --- /dev/null +++ b/lib/NGCP/Panel/Form/Intercept/Delete.pm @@ -0,0 +1,13 @@ +package NGCP::Panel::Form::Intercept::Delete; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'id' => ( + type => 'PosInteger', + label => 'id', + required => 1, +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Intercept/Update.pm b/lib/NGCP/Panel/Form/Intercept/Update.pm new file mode 100644 index 0000000000..5e2becb25d --- /dev/null +++ b/lib/NGCP/Panel/Form/Intercept/Update.pm @@ -0,0 +1,82 @@ +package NGCP::Panel::Form::Intercept::Update; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'id' => ( + type => 'PosInteger', + label => 'id', + required => 1, +); + +has_field 'data' => ( + type => 'Compound', + label => 'data', + required => 1, + validate_when_empty => 1, +); + +has_field 'data.cc_required' => ( + type => 'PosInteger', + label => 'data.cc_required', + range_start => 0, + range_end => 1, + required => 1, +); + +has_field 'data.iri_delivery' => ( + type => 'Compound', + label => 'data.iri_delivery', + required => 1, + validate_when_empty => 1, +); + +has_field 'data.iri_delivery.host' => ( + type => 'Text', + label => 'data.iri_delivery.host', + required => 1, +); + +has_field 'data.iri_delivery.port' => ( + type => 'PosInteger', + label => 'data.iri_delivery.port', + required => 1, + range_start => 1, + range_end => 65535, +); + +has_field 'data.iri_delivery.username' => ( + type => 'Text', + label => 'data.iri_delivery.username', + required => 0, +); + +has_field 'data.iri_delivery.password' => ( + type => 'Text', + label => 'data.iri_delivery.password', + required => 0, +); + +has_field 'data.cc_delivery' => ( + type => 'Compound', + required => 0, + validate_when_empty => 0, +); + +has_field 'data.cc_delivery.host' => ( + type => 'Text', + label => 'data.cc_delivery.host', + required => 1, +); + +has_field 'data.cc_delivery.port' => ( + type => 'PosInteger', + label => 'data.cc_delivery.port', + required => 1, + range_start => 1, + range_end => 65535, +); + + +1; +# vim: set tabstop=4 expandtab: diff --git a/sandbox/intercept-nusoap.php b/sandbox/intercept-nusoap.php new file mode 100644 index 0000000000..0ae80c4178 --- /dev/null +++ b/sandbox/intercept-nusoap.php @@ -0,0 +1,270 @@ +setCredentials('testuser', 'testpass', 'basic'); +$error = $client->getError(); +if($error) { + echo "Error: " . $error; + return; +} + +echo "Fetched wsdl, starting tasks\n"; + +$res = $client->call('create_interception', + array( + 'authentication' => array( + 'username' => 'intercept', + 'password' => 'secret', + 'type' => 'admin' + ), + 'parameters' => array( + 'LIID' => '1234', + 'number' => '439991001', + 'cc_required' => 0, + 'iri_delivery' => array( + 'host' => '1.2.3.4', + 'port' => 1234, + ), + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); + exit; +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + exit; + } else { + echo "Result:"; + echo $res; + echo "\n\n\n"; + } +} + +$res = $client->call('update_interception', + array( + 'authentication' => array( + 'username' => 'intercept', + 'password' => 'secret', + 'type' => 'admin' + ), + 'parameters' => array( + 'id' => 1, + 'data' => array( + 'cc_required' => 1, + 'iri_delivery' => array( + 'host' => '1.2.3.5', + 'port' => 1234, + ), + 'cc_delivery' => array( + 'host' => '1.2.3.6', + 'port' => 1236, + ) + ) + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); + exit; +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + exit; + } else { + echo "Result:"; + echo $res; + echo "\n\n\n"; + } +} + + +$res = $client->call('get_interceptions_by_liid', + array( + 'authentication' => array( + 'username' => 'administrator', + 'password' => 'administrator', + 'type' => 'admin' + ), + 'parameters' => array( + 'LIID' => 1234 + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + } else { + echo "Result:"; + print_r($res); + echo "\n\n\n"; + } +} + +$res = $client->call('delete_interception', + array( + 'authentication' => array( + 'username' => 'administrator', + 'password' => 'administrator', + 'type' => 'admin' + ), + 'parameters' => array( + 'id' => 1 + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + } else { + echo "Result:"; + print_r($res); + echo "\n\n\n"; + } +} + +$res = $client->call('get_interception_by_id', + array( + 'authentication' => array( + 'username' => 'administrator', + 'password' => 'administrator', + 'type' => 'admin' + ), + 'parameters' => array( + 'id' => 1 + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + } else { + echo "Result:"; + print_r($res); + echo "\n\n\n"; + } +} + + +$res = $client->call('get_interceptions_by_number', + array( + 'authentication' => array( + 'username' => 'administrator', + 'password' => 'administrator', + 'type' => 'admin' + ), + 'parameters' => array( + 'number' => '439991001' + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + } else { + echo "Result:"; + print_r($res); + echo "\n\n\n"; + } +} + +$res = $client->call('get_interceptions', + array( + 'authentication' => array( + 'username' => 'administrator', + 'password' => 'administrator', + 'type' => 'admin' + ), + 'parameters' => array( + ) + ) +); +echo "Request:"; +echo $client->request; +echo "\n"; +echo "Response:"; +echo $client->response; +echo "\n\n\n"; + +if($client->fault) { + echo "Fault"; + print_r($res); +} else { + $error = $client->getError(); + if($error) { + echo "Server Error: " . $error; + } else { + echo "Result:"; + print_r($res); + echo "\n\n\n"; + } +} + +?>