4e714b30262cb344a8999297916bb5e64840e7db
[koha.git] / C4 / SIP / SIPServer.pm
1 package SIPServer;
2
3 use strict;
4 use warnings;
5 # use Exporter;
6 use Sys::Syslog qw(syslog);
7 use Net::Server::PreFork;
8 use IO::Socket::INET;
9 use Socket qw(:DEFAULT :crlf);
10 use Data::Dumper;               # For debugging
11 require UNIVERSAL::require;
12
13 #use Sip qw(readline);
14 use Sip::Constants qw(:all);
15 use Sip::Configuration;
16 use Sip::Checksum qw(checksum verify_cksum);
17 use Sip::MsgType;
18
19 use constant LOG_SIP => "local6"; # Local alias for the logging facility
20
21 use vars qw(@ISA $VERSION);
22
23 BEGIN {
24     $VERSION = 3.07.00.049;
25         @ISA = qw(Net::Server::PreFork);
26 }
27
28 #
29 # Main  # not really, since package SIPServer
30 #
31 # FIXME: Is this a module or a script?  
32 # A script with no MAIN namespace?
33 # A module that takes command line args?
34
35 my %transports = (
36     RAW    => \&raw_transport,
37     telnet => \&telnet_transport,
38 );
39
40 #
41 # Read configuration
42 #
43 my $config = new Sip::Configuration $ARGV[0];
44 print STDERR "SIPServer config: \n" . Dumper($config) . "\nEND SIPServer config.\n";
45 my @parms;
46
47 #
48 # Ports to bind
49 #
50 foreach my $svc (keys %{$config->{listeners}}) {
51     push @parms, "port=" . $svc;
52 }
53
54 #
55 # Logging
56 #
57 # Log lines look like this:
58 # Jun 16 21:21:31 server08 steve_sip[19305]: ILS::Transaction::Checkout performing checkout...
59 # [  TIMESTAMP  ] [ HOST ] [ IDENT ]  PID  : Message...
60 #
61 # The IDENT is determined by config file 'server-params' arguments
62
63
64 #
65 # Server Management: set parameters for the Net::Server::PreFork
66 # module.  The module silently ignores parameters that it doesn't
67 # recognize, and complains about invalid values for parameters
68 # that it does.
69 #
70 if (defined($config->{'server-params'})) {
71     while (my ($key, $val) = each %{$config->{'server-params'}}) {
72                 push @parms, $key . '=' . $val;
73     }
74 }
75
76 print scalar(localtime),  " -- startup -- procid:$$\n";
77 print "Params for Net::Server::PreFork : \n" . Dumper(\@parms);
78
79 #
80 # This is the main event.
81 __PACKAGE__ ->run(@parms);
82
83 #
84 # Child
85 #
86
87 # process_request is the callback used by Net::Server to handle
88 # an incoming connection request.
89
90 sub process_request {
91     my $self = shift;
92     my $service;
93     my ($sockaddr, $port, $proto);
94     my $transport;
95
96     $self->{config} = $config;
97
98     my $sockname = getsockname(STDIN);
99     ($port, $sockaddr) = sockaddr_in($sockname);
100     $sockaddr = inet_ntoa($sockaddr);
101     $proto = $self->{server}->{client}->NS_proto();
102
103     $self->{service} = $config->find_service($sockaddr, $port, $proto);
104
105     if (!defined($self->{service})) {
106                 syslog("LOG_ERR", "process_request: Unknown recognized server connection: %s:%s/%s", $sockaddr, $port, $proto);
107                 die "process_request: Bad server connection";
108     }
109
110     $transport = $transports{$self->{service}->{transport}};
111
112     if (!defined($transport)) {
113                 syslog("LOG_WARNING", "Unknown transport '%s', dropping", $service->{transport});
114                 return;
115     } else {
116                 &$transport($self);
117     }
118 }
119
120 #
121 # Transports
122 #
123
124 sub raw_transport {
125     my $self = shift;
126     my ($input);
127     my $service = $self->{service};
128
129     while (!$self->{account}) {
130     local $SIG{ALRM} = sub { die "raw_transport Timed Out!\n"; };
131     syslog("LOG_DEBUG", "raw_transport: timeout is %d", $service->{timeout});
132     $input = Sip::read_SIP_packet(*STDIN);
133     if (!$input) {
134         # EOF on the socket
135         syslog("LOG_INFO", "raw_transport: shutting down: EOF during login");
136         return;
137     }
138     $input =~ s/[\r\n]+$//sm;   # Strip off trailing line terminator(s)
139     last if Sip::MsgType::handle($input, $self, LOGIN);
140     }
141
142     syslog("LOG_DEBUG", "raw_transport: uname/inst: '%s/%s'",
143            $self->{account}->{id},
144            $self->{account}->{institution});
145
146     $self->sip_protocol_loop();
147     syslog("LOG_INFO", "raw_transport: shutting down");
148 }
149
150 sub get_clean_string {
151         my $string = shift;
152         if (defined $string) {
153                 syslog("LOG_DEBUG", "get_clean_string  pre-clean(length %s): %s", length($string), $string);
154                 chomp($string);
155                 $string =~ s/^[^A-z0-9]+//;
156                 $string =~ s/[^A-z0-9]+$//;
157                 syslog("LOG_DEBUG", "get_clean_string post-clean(length %s): %s", length($string), $string);
158         } else {
159                 syslog("LOG_INFO", "get_clean_string called on undefined");
160         }
161         return $string;
162 }
163
164 sub get_clean_input {
165         local $/ = "\012";
166         my $in = <STDIN>;
167         $in = get_clean_string($in);
168         while (my $extra = <STDIN>){
169                 syslog("LOG_ERR", "get_clean_input got extra lines: %s", $extra);
170         }
171         return $in;
172 }
173
174 sub telnet_transport {
175     my $self = shift;
176     my ($uid, $pwd);
177     my $strikes = 3;
178     my $account = undef;
179     my $input;
180     my $config  = $self->{config};
181         my $timeout = $self->{service}->{timeout} || $config->{timeout} || 30;
182         syslog("LOG_DEBUG", "telnet_transport: timeout is %s", $timeout);
183
184     eval {
185         local $SIG{ALRM} = sub { die "telnet_transport: Timed Out ($timeout seconds)!\n"; };
186         local $| = 1;                   # Unbuffered output
187         $/ = "\015";            # Internet Record Separator (lax version)
188     # Until the terminal has logged in, we don't trust it
189     # so use a timeout to protect ourselves from hanging.
190
191         while ($strikes--) {
192             print "login: ";
193                 alarm $timeout;
194                 # $uid = &get_clean_input;
195                 $uid = <STDIN>;
196             print "password: ";
197             # $pwd = &get_clean_input || '';
198                 $pwd = <STDIN>;
199                 alarm 0;
200
201                 syslog("LOG_DEBUG", "telnet_transport 1: uid length %s, pwd length %s", length($uid), length($pwd));
202                 $uid = get_clean_string ($uid);
203                 $pwd = get_clean_string ($pwd);
204                 syslog("LOG_DEBUG", "telnet_transport 2: uid length %s, pwd length %s", length($uid), length($pwd));
205
206             if (exists ($config->{accounts}->{$uid})
207                 && ($pwd eq $config->{accounts}->{$uid}->password())) {
208                         $account = $config->{accounts}->{$uid};
209                         Sip::MsgType::login_core($self,$uid,$pwd) and last;
210             }
211                 syslog("LOG_WARNING", "Invalid login attempt: '%s'", ($uid||''));
212                 print("Invalid login$CRLF");
213         }
214     }; # End of eval
215
216     if ($@) {
217                 syslog("LOG_ERR", "telnet_transport: Login timed out");
218                 die "Telnet Login Timed out";
219     } elsif (!defined($account)) {
220                 syslog("LOG_ERR", "telnet_transport: Login Failed");
221                 die "Login Failure";
222     } else {
223                 print "Login OK.  Initiating SIP$CRLF";
224     }
225
226     $self->{account} = $account;
227     syslog("LOG_DEBUG", "telnet_transport: uname/inst: '%s/%s'", $account->{id}, $account->{institution});
228     $self->sip_protocol_loop();
229     syslog("LOG_INFO", "telnet_transport: shutting down");
230 }
231
232 #
233 # The terminal has logged in, using either the SIP login process
234 # over a raw socket, or via the pseudo-unix login provided by the
235 # telnet transport.  From that point on, both the raw and the telnet
236 # processes are the same:
237 sub sip_protocol_loop {
238         my $self = shift;
239         my $service = $self->{service};
240         my $config  = $self->{config};
241         my $input;
242
243     # The spec says the first message will be:
244         #       SIP v1: SC_STATUS
245         #       SIP v2: LOGIN (or SC_STATUS via telnet?)
246     # But it might be SC_REQUEST_RESEND.  As long as we get
247     # SC_REQUEST_RESEND, we keep waiting.
248
249     # Comprise reports that no other ILS actually enforces this
250     # constraint, so we'll relax about it too.
251     # Using the SIP "raw" login process, rather than telnet,
252     # requires the LOGIN message and forces SIP 2.00.  In that
253         # case, the LOGIN message has already been processed (above).
254         # 
255         # In short, we'll take any valid message here.
256         #my $expect = SC_STATUS;
257     my $expect = '';
258     while (1) {
259         $input = Sip::read_SIP_packet(*STDIN);
260         unless ($input) {
261             return;             # EOF
262         }
263                 # begin input hacks ...  a cheap stand in for better Telnet layer
264                 $input =~ s/^[^A-z0-9]+//s;     # Kill leading bad characters... like Telnet handshakers
265                 $input =~ s/[^A-z0-9]+$//s;     # Same on the end, should get DOSsy ^M line-endings too.
266                 while (chomp($input)) {warn "Extra line ending on input";}
267                 unless ($input) {
268             syslog("LOG_ERR", "sip_protocol_loop: empty input skipped");
269             print("96$CR");
270             next;
271                 }
272                 # end cheap input hacks
273                 my $status = Sip::MsgType::handle($input, $self, $expect);
274                 if (!$status) {
275                         syslog("LOG_ERR", "sip_protocol_loop: failed to handle %s",substr($input,0,2));
276                 }
277                 next if $status eq REQUEST_ACS_RESEND;
278                 if ($expect && ($status ne $expect)) {
279                         # We received a non-"RESEND" that wasn't what we were expecting.
280                     syslog("LOG_ERR", "sip_protocol_loop: expected %s, received %s, exiting", $expect, $input);
281                 }
282                 # We successfully received and processed what we were expecting
283                 $expect = '';
284         }
285 }
286
287 1;
288 __END__