]> git.evergreen-ils.org Git - working/SIPServer.git/blob - SIPServer.pm
Update to current default EG values.
[working/SIPServer.git] / SIPServer.pm
1 #
2 # Copyright (C) 2006-2008  Georgia Public Library Service
3
4 # Author: David J. Fiander
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of version 2 of the GNU General Public
8 # License as published by the Free Software Foundation.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public
16 # License along with this program; if not, write to the Free
17 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
18 # MA 02111-1307 USA
19
20 package SIPServer;
21
22 use strict;
23 use warnings;
24 use Exporter;
25 use Sys::Syslog qw(syslog);
26 use Net::Server::PreFork;
27 use Net::Server::Proto;
28 use IO::Socket::INET;
29 use Socket qw(:crlf);
30 use Data::Dumper;               # For debugging
31 require UNIVERSAL::require;
32
33 #use Sip qw(readline);
34 use Sip::Constants qw(:all);
35 use Sip::Configuration;
36 use Sip::Checksum qw(checksum verify_cksum);
37 use Sip::MsgType;
38
39 use constant LOG_SIP => "local6"; # Local alias for the logging facility
40
41 our @ISA = qw(Net::Server::PreFork);
42 #
43 # Main
44 #
45
46 my %transports = (
47     RAW    => \&raw_transport,
48     telnet => \&telnet_transport,
49     http   => \&http_transport,
50 );
51
52 # Read configuration
53
54 my $config = new Sip::Configuration $ARGV[0];
55
56 my @parms;
57
58 #
59 # Ports to bind
60 #
61 foreach my $svc (keys %{$config->{listeners}}) {
62     push @parms, "port=" . $svc;
63 }
64
65 #
66 # Logging
67 #
68 # Log lines look like this:
69 # Jun 16 21:21:31 server08 steve_sip: Sip::MsgType::_initialize('Login', ...)
70 # [  TIMESTAMP  ] [ HOST ] [ IDENT ]: Message...
71 #
72 # The IDENT is determined by $ENV{SIP_LOG_IDENT}, if present.
73 # Otherwise it is "_sip" appended to $USER, if present, or "acs-server" as a fallback.
74 #
75
76 my $syslog_ident = $ENV{SIP_LOG_IDENT} || ($ENV{USER} ? $ENV{USER} . "_sip" : 'acs-server');
77
78 push @parms,
79     "log_file=Sys::Syslog",
80     "syslog_ident=$syslog_ident",
81     "syslog_facility=" . LOG_SIP;
82
83 #
84 # Server Management: set parameters for the Net::Server::PreFork
85 # module.  The module silently ignores parameters that it doesn't
86 # recognize, and complains about invalid values for parameters
87 # that it does.
88 #
89 if (defined($config->{'server-params'})) {
90     while (my ($key, $val) = each %{$config->{'server-params'}}) {
91         push @parms, $key . '=' . $val;
92     }
93 }
94
95 print Dumper(@parms);
96
97 #
98 # This is the main event.
99 SIPServer->run(@parms);
100
101 #
102 # Child
103 #
104
105 # process_request is the callback used by Net::Server to handle
106 # an incoming connection request.
107
108 sub process_request {
109     my $self = shift;
110     my $service;
111     my $sockname;
112     my ($sockaddr, $port, $proto);
113     my $transport;
114
115     $self->{config} = $config;
116
117     $sockaddr = $self->{server}->{sockaddr};
118     $port     = $self->{server}->{sockport};
119     $proto    = $self->{server}->{client}->NS_proto();
120     syslog('LOG_INFO', "Inbound connection from $sockaddr on port $port and proto $proto");
121
122     $self->{service} = $config->find_service( $sockaddr, $port, $proto );
123
124     if (! defined($self->{service})) {
125         syslog( "LOG_ERR", "process_request: Unknown recognized server connection: %s:%s/%s",
126             $sockaddr, $port, $proto );
127         die "process_request: Bad server connection";
128     }
129
130     $transport = $transports{ $self->{service}->{transport} };
131
132     if ( !defined($transport) ) {
133         syslog("LOG_WARNING", "Unknown transport '%s', dropping", $service->{transport});
134         return;
135     } else {
136         &$transport($self);
137     }
138 }
139
140 #
141 # Transports
142 #
143
144 sub raw_transport {
145     my $self = shift;
146     my ($uid, $pwd);
147     my $input;
148     my $service = $self->{service};
149     my $strikes = 3;
150     my $expect;
151     my $inst;
152
153     eval {
154         local $SIG{ALRM} = sub { die "raw_transport Timed Out!\n"; };
155         syslog("LOG_DEBUG", "raw_transport: timeout is %d", $service->{timeout});
156
157     while ($strikes--) {
158         alarm $service->{timeout};
159         $input = Sip::read_SIP_packet(*STDIN);
160         alarm 0;
161
162         if (!$input) {
163             # EOF on the socket
164             syslog("LOG_INFO", "raw_transport: shutting down: EOF during login");
165             return;
166         }
167         $input =~ s/[\r\n]+$//sm;       # Strip off trailing line terminator
168         last if Sip::MsgType::handle($input, $self, LOGIN);
169     }
170     };
171
172     if ($@) {
173         syslog("LOG_ERR", "raw_transport: LOGIN ERROR: '$@'");
174         die "raw_transport: login error (timeout? $@), exiting";
175     } elsif (!$self->{account}) {
176         syslog("LOG_ERR", "raw_transport: LOGIN FAILED");
177         die "raw_transport: Login failed (no account), exiting";
178     }
179
180     syslog("LOG_DEBUG", "raw_transport: uname/inst: '%s/%s'",
181         $self->{account}->{id},
182         $self->{account}->{institution});
183
184     $self->sip_protocol_loop();
185
186     syslog("LOG_INFO", "raw_transport: shutting down");
187 }
188
189 sub telnet_transport {
190     my $self = shift;
191     my ($uid, $pwd);
192     my $strikes = 3;
193     my $account = undef;
194     my $input;
195     my $config = $self->{config};
196     my $timeout = $self->{service}->{timeout} || $config->{timeout} || 0;
197     syslog("LOG_DEBUG", "telnet_transport: timeout is %s", $timeout);
198
199     # Until the terminal has logged in, we don't trust it
200     # so use a timeout to protect ourselves from hanging.
201     eval {
202     local $SIG{ALRM} = sub { die "telnet_transport: Timed Out ($timeout seconds)!\n";; };
203     local $| = 1;                       # Unbuffered output
204
205     while ($strikes--) {
206         print "login: ";
207         alarm $timeout;
208         $uid = <STDIN>;
209         alarm 0;
210
211         print "password: ";
212         alarm $timeout;
213         $pwd = <STDIN>;
214         alarm 0;
215
216         $uid =~ s/[\r\n]+$//;
217         $pwd =~ s/[\r\n]+$//;
218
219         if (exists($config->{accounts}->{$uid})
220         && ($pwd eq $config->{accounts}->{$uid}->password())) {
221             $account = $config->{accounts}->{$uid};
222             last;
223         } else {
224             syslog("LOG_WARNING", "Invalid login attempt: '%s'", $uid);
225             print("Invalid login$CRLF");
226         }
227     }
228     }; # End of eval
229
230     if ($@) {
231         syslog("LOG_ERR", "telnet_transport: Login timed out");
232         die "Telnet Login Timed out";
233     } elsif (!defined($account)) {
234         syslog("LOG_ERR", "telnet_transport: Login Failed");
235         die "Login Failure";
236     } else {
237         print "Login OK.  Initiating SIP$CRLF";
238     }
239
240     $self->{account} = $account;
241     syslog("LOG_DEBUG", "telnet_transport: uname/inst: '%s/%s'", $account->{id}, $account->{institution});
242     $self->sip_protocol_loop();
243     syslog("LOG_INFO", "telnet_transport: shutting down");
244 }
245
246
247 sub http_transport {
248 }
249
250 #
251 # The terminal has logged in, using either the SIP login process
252 # over a raw socket, or via the pseudo-unix login provided by the
253 # telnet transport.  From that point on, both the raw and the telnet
254 # processes are the same:
255 sub sip_protocol_loop {
256     my $self = shift;
257     my $expect;
258     my $service = $self->{service};
259     my $config  = $self->{config};
260     my $input;
261
262     # Now that the terminal has logged in, the first message
263     # we recieve must be an SC_STATUS message.  But it might be
264     # an SC_REQUEST_RESEND.  So, as long as we keep receiving
265     # SC_REQUEST_RESEND, we keep waiting for an SC_STATUS
266
267     # Comprise reports that no other ILS actually enforces this
268     # constraint, so we'll relax about it too.  As long as everybody
269     # uses the SIP "raw" login process, rather than telnet, this
270     # will be fine, becaues the LOGIN protocol exchange will force
271     # us into SIP 2.00 anyway.  Machines that want to log in using
272     # telnet MUST send an SC Status message first, even though we're
273     # not enforcing it.
274     # 
275     #$expect = SC_STATUS;
276     $expect = '';
277
278     while ( $input = Sip::read_SIP_packet(*STDIN) ) {
279         $input =~ s/[\r\n]+$//sm;    # Strip off any trailing line ends
280
281         my $status = Sip::MsgType::handle($input, $self, $expect);
282         next if $status eq REQUEST_ACS_RESEND;
283
284         if (!$status) {
285             syslog("LOG_ERR", "raw_transport: failed to handle %s", substr($input,0,2));
286             die "sip_protocol_loop: failed Sip::MsgType::handle('$input', $self, '$expect')";
287         }
288         elsif ($expect && ($status ne $expect)) {
289             # We received a non-"RESEND" that wasn't what we were expecting.
290             syslog("LOG_ERR", "raw_transport: expected %s, received %s, exiting", $expect, $input);
291             die "sip_protocol_loop: exiting: expected '$expect', received '$status'";
292         }
293
294         # We successfully received and processed what we were expecting
295         $expect = '';
296     }
297 }