From 2e8b3fda3b1f261dc7ce94a0951c6848b31cfc0c Mon Sep 17 00:00:00 2001 From: lmstrand Date: Fri, 31 Jul 2020 10:39:39 +0300 Subject: [PATCH] SIP2oHTTPS --- Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm | 297 +++++++++++++++++++++++++++++ api/v1/swagger/definitions.json | 3 + api/v1/swagger/definitions/sipmessage.json | 4 + api/v1/swagger/parameters/sipmessage.json | 9 + api/v1/swagger/paths.json | 3 + api/v1/swagger/paths/sipmessages.json | 51 +++++ etc/sip2ohttp-config.xml | 37 ++++ koha-tmpl/sipschema.xsd | 29 +++ 8 files changed, 433 insertions(+) create mode 100644 Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm create mode 100644 api/v1/swagger/definitions/sipmessage.json create mode 100644 api/v1/swagger/parameters/sipmessage.json create mode 100644 api/v1/swagger/paths/sipmessages.json create mode 100755 etc/sip2ohttp-config.xml create mode 100644 koha-tmpl/sipschema.xsd diff --git a/Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm b/Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm new file mode 100644 index 0000000..d12bbf4 --- /dev/null +++ b/Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm @@ -0,0 +1,297 @@ +package Koha::REST::V1::SIPoHTTP::SIPoHTTP; + +# This file is part of Koha. +# +# Koha 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. +# +# Koha 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 Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use FindBin qw($Bin); +use lib "$Bin"; +use Koha::Exceptions; +use Koha::Logger; +use XML::LibXML; +use IO::Socket::INET; +use IO::Socket qw(AF_INET AF_UNIX SOCK_STREAM SHUT_WR); +use Socket qw(:crlf); +use Try::Tiny; +use Mojo::Log; +use File::Basename; +use C4::Context; + +use strict; +use warnings qw( all ); + +my $CONFPATH = dirname( $ENV{'KOHA_CONF'} ); +my $KOHAPATH = C4::Context->config('intranetdir'); + +my $log = Koha::Logger->get(); + +#This gets called from REST api +sub process { + + my $c = shift->openapi->valid_input or return; + + my $body = $c->req->body; + my $xmlrequest = $body; + + $log->info("Request received."); + + my $validation = validateXml( $c, $xmlrequest ); + + if ( $validation != 1 ) { + + $c->render( + text => "Invalid Request. XML Validation failed.", + status => 400 + ); + + return; + } + + #process sip here + my ( $login, $password ) = getLogin($xmlrequest); + + if ( !$login or !$password ) { + + $log->error("Invalid request. Missing login/pw in XML."); + + $c->render( + text => "Invalid request. Missing login/pw in XML.", + status => 400 + ); + return; + } + my $sipmes = extractSip( $xmlrequest, $c ); + + unless ($sipmes) { + + $log->error("Invalid request. Missing SIP Request in XML."); + + $c->render( + text => "Invalid request. Missing SIP Request in XML.", + status => 400 + ); + return; + } + + my ( $siphost, $sipport ) = extractServer( $xmlrequest, $c ); + + unless ( $siphost && $sipport ) { + + $log->error("No config found for login device. "); + + return $c->render( + text => "No config found for login device. ", + status => 400 + ); + + } + + my $sipresponse = + tradeSip( $login, $password, $siphost, $sipport, $sipmes, $c ); + + #remove carriage return from response (\r) + $sipresponse =~ s/\r//g; + + my $xmlresponse = buildXml($sipresponse); + + return try { + $c->render( status => 200, text => $xmlresponse ); + $log->info("XML response passed to endpoint."); + + } + catch { + Koha::Exceptions::rethrow_exception($_); + } +} + +sub tradeSip { + + my ( $login, $password, $host, $port, $command_message, $c ) = @_; + + my $sipsock = IO::Socket::INET->new( + PeerAddr => $host, + PeerPort => $port, + Proto => 'tcp' + ) or die $log->fatal("Can't create a socket for sipserver. Sipserver down?"); + + $sipsock->autoflush(1); + + my $loginsip = buildLogin( $login, $password ); + + my $terminator = q{}; + $terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF; + + # Set perl to expect the same record terminator it is sending + $/ = $terminator; + + $log->info("Trying login: $loginsip"); + + my $respdata; + + print $sipsock $loginsip . $terminator; + + $sipsock->recv( $respdata, 1024 ); + $sipsock->flush; + + if ( $respdata == "941" ) { + + $log->info("Login OK. Sending: $command_message"); + + print $sipsock $command_message . $terminator; + + $sipsock->recv( $respdata, 1024 ); + $sipsock->flush; + + $sipsock->shutdown(SHUT_WR); + $sipsock->shutdown(SHUT_RDWR); # we stopped using this socket + $sipsock->close; + + my $respmes = $respdata; + $respmes =~ s/.{1}$//; + + $log->info("Received: $respmes"); + + return $respdata; + } + + chomp $respdata; + $log->error( +"Unauthorized login for $login: $respdata. Can't process attached SIP message." + ); + + return $respdata; +} + +sub buildLogin { + + my ( $login, $password ) = @_; + + my $siptempl = "9300CN|CO|CPSIPLOCATION|"; + return "9300CN" . shift . "|CO" . shift . "|CPSIP2OHTTP|"; +} + +sub buildXml { + + my $responsemessage = shift; + + my $respxml = ' + + + + + +'; + + $respxml =~ s||$responsemessage|; + + return $respxml; +} + +sub extractSip { + + my ( $xmlmessage, $c ) = @_; + + my $parser = XML::LibXML->new(); + my $xmldoc = $parser->load_xml( string => $xmlmessage ); + my $xc = XML::LibXML::XPathContext->new( $xmldoc->documentElement() ); + + my ($node) = $xc->findnodes('//request'); + + my $sample = $node->textContent; + return $sample; + +} + +sub getLogin { + + #Retrieve the self check machine login info from XML + my $xmlmessage = shift; + + my ( $login, $passw ); + + my $parser = XML::LibXML->new(); + my $doc = $parser->load_xml( string => $xmlmessage ); + my $xc = XML::LibXML::XPathContext->new( $doc->documentElement() ); + + my ($node) = $xc->findnodes('//ns1:sip'); + + try { + $login = $node->getAttribute("login"); + $passw = $node->getAttribute("password"); + + return $login, $passw; + } + catch { + return 0; + } + +} + +sub extractServer { + + my ( $host, $port ); + + my ( $xmlmessage, $c ) = @_; + my ( $term, $pass ) = getLogin($xmlmessage); + + my $doc = + XML::LibXML->load_xml( location => $CONFPATH . '/sip2ohttp-config.xml' ); + my $xc = XML::LibXML::XPathContext->new( $doc->documentElement() ); + + my ($node) = $xc->findnodes( '//' . $term ); + + unless ($node) { + $log->error( + "Missing server config parameters for $term in sipdevices.xml"); + return 0; + } + + $host = $node->findvalue('./host'); + $port = $node->findvalue('./port'); + return $host, $port; + +} + +sub validateXml { + + #For validating the content of the XML SIP message + my ( $c, $xmlbody ) = @_; + my $parser = XML::LibXML->new(); + + # parse and validate the xml against sipschema + # https://koha-suomi.fi/sipschema.xsd + my $schema = + XML::LibXML::Schema->new( + location => $KOHAPATH . '/koha-tmpl/sipschema.xsd' ); + + try { + + my $xmldoc = $parser->load_xml( string => $xmlbody ); + $schema->validate($xmldoc); + $log->info("XML Validated OK."); + return 1; + } + catch { + $log->error("Could not validate XML - @_"); + return 0; + + }; + +} + +1; diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json index 2d5b354..ba27dd7 100644 --- a/api/v1/swagger/definitions.json +++ b/api/v1/swagger/definitions.json @@ -106,5 +106,8 @@ }, "mongolog": { "$ref": "definitions/mongolog.json" + }, + "sipmessage": { + "$ref": "definitions/sipmessage.json" } } diff --git a/api/v1/swagger/definitions/sipmessage.json b/api/v1/swagger/definitions/sipmessage.json new file mode 100644 index 0000000..6c63efb --- /dev/null +++ b/api/v1/swagger/definitions/sipmessage.json @@ -0,0 +1,4 @@ +{ + "type": "string", + "additionalProperties": true +} diff --git a/api/v1/swagger/parameters/sipmessage.json b/api/v1/swagger/parameters/sipmessage.json new file mode 100644 index 0000000..8ad1fe1 --- /dev/null +++ b/api/v1/swagger/parameters/sipmessage.json @@ -0,0 +1,9 @@ +{ + "sipMessage": { + "name": "request", + "in": "formData", + "allowReserved": true, + "description": "sipmessage", + "required": false + } +} diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index bce6bcc..6f45c73 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -217,5 +217,8 @@ }, "/suggestions/{suggestionid}": { "$ref": "paths/suggestions.json#/~1suggestions~1{suggestionid}" + }, + "/sipmessages": { + "$ref": "paths/sipmessages.json#/~1sipmessages" } } diff --git a/api/v1/swagger/paths/sipmessages.json b/api/v1/swagger/paths/sipmessages.json new file mode 100644 index 0000000..3e8b220 --- /dev/null +++ b/api/v1/swagger/paths/sipmessages.json @@ -0,0 +1,51 @@ +{ + "/sipmessages": { + "post": { + "x-mojo-to": "SIPoHTTP::SIPoHTTP#process", + "operationId": "SIPoHTTP", + "tags": [ + "sipmessages" + ], + "parameters": [ + { + "name": "query", + "in": "query", + "description": "sip in XML.", + "type": "string" + } + ], + "consumes": [ + "application/xml" + ], + "produces": [ + "application/xml" + ], + "responses": { + "200": { + "description": "Sip message ok", + "schema": { + "$ref": "../definitions.json#/sipmessage" + } + }, + "400": { + "description": "Missing or wrong parameters", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "503": { + "description": "Under maintenance", + "schema": { + "$ref": "../definitions.json#/error" + } + } + } + } + } +} diff --git a/etc/sip2ohttp-config.xml b/etc/sip2ohttp-config.xml new file mode 100755 index 0000000..0a9738c --- /dev/null +++ b/etc/sip2ohttp-config.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/koha-tmpl/sipschema.xsd b/koha-tmpl/sipschema.xsd new file mode 100644 index 0000000..bb9bab8 --- /dev/null +++ b/koha-tmpl/sipschema.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.7.4