SIPServer.pm - BEGIN block VERSION and vars related to export.
[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;
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 = 1.00;
25         @ISA = qw(Net::Server::PreFork);
26 }
27
28 #
29 # Main
30 #
31
32 my %transports = (
33     RAW    => \&raw_transport,
34     telnet => \&telnet_transport,
35     http   => \&http_transport,
36 );
37
38 # Read configuration
39
40 my $config = new Sip::Configuration $ARGV[0];
41
42 my @parms;
43
44 #
45 # Ports to bind
46 #
47 foreach my $svc (keys %{$config->{listeners}}) {
48     push @parms, "port=" . $svc;
49 }
50
51 #
52 # Logging
53 #
54 push @parms, "log_file=Sys::Syslog", "syslog_ident=acs-server",
55   "syslog_facility=" . LOG_SIP;
56
57 #
58 # Server Management: set parameters for the Net::Server::PreFork
59 # module.  The module silently ignores parameters that it doesn't
60 # recognize, and complains about invalid values for parameters
61 # that it does.
62 #
63 if (defined($config->{'server-params'})) {
64     while (my ($key, $val) = each %{$config->{'server-params'}}) {
65         push @parms, $key . '=' . $val;
66     }
67 }
68
69 print Dumper(@parms);
70
71 #
72 # This is the main event.
73 SIPServer->run(@parms);
74
75 #
76 # Child
77 #
78
79 # process_request is the callback used by Net::Server to handle
80 # an incoming connection request.
81
82 sub process_request {
83     my $self = shift;
84     my $service;
85     my $sockname;
86     my ($sockaddr, $port, $proto);
87     my $transport;
88
89     $self->{config} = $config;
90
91     $sockname = getsockname(STDIN);
92     ($port, $sockaddr) = sockaddr_in($sockname);
93     $sockaddr = inet_ntoa($sockaddr);
94     $proto = $self->{server}->{client}->NS_proto();
95
96     $self->{service} = $config->find_service($sockaddr, $port, $proto);
97
98     if (!defined($self->{service})) {
99         syslog("LOG_ERR", "process_request: Unknown recognized server connection: %s:%s/%s", $sockaddr, $port, $proto);
100         die "process_request: Bad server connection";
101     }
102
103     $transport = $transports{$self->{service}->{transport}};
104
105     if (!defined($transport)) {
106         syslog("LOG_WARN", "Unknown transport '%s', dropping", $service->{transport});
107         return;
108     } else {
109         &$transport($self);
110     }
111 }
112
113 #
114 # Transports
115 #
116
117 sub raw_transport {
118     my $self = shift;
119     my ($uid, $pwd);
120     my $input;
121     my $service = $self->{service};
122     my $strikes = 3;
123     my $expect;
124     my $inst;
125
126     eval {
127         local $SIG{ALRM} = sub { die "alarm\n"; };
128         syslog("LOG_DEBUG", "raw_transport: timeout is %d",
129                $service->{timeout});
130         while ($strikes--) {
131             alarm $service->{timeout};
132             $input = Sip::read_SIP_packet(*STDIN);
133             alarm 0;
134         if (!$input) {
135                 # EOF on the socket
136                 syslog("LOG_INFO", "raw_transport: shutting down: EOF during login");
137                 return;
138             }
139
140             $input =~ s/[\r\n]+$//sm;   # Strip off trailing line terminator
141
142             last if Sip::MsgType::handle($input, $self, LOGIN);
143         }
144     };
145
146     if ($@) {
147         syslog("LOG_ERR", "raw_transport: LOGIN ERROR: '$@'");
148         die "raw_transport: login error, exiting";
149     } elsif (!$self->{account}) {
150         syslog("LOG_ERR", "raw_transport: LOGIN FAILED");
151         die "raw_transport: Login failed, exiting";
152     }
153
154     syslog("LOG_DEBUG", "raw_transport: uname/inst: '%s/%s'",
155            $self->{account}->{id},
156            $self->{account}->{institution});
157
158     $self->sip_protocol_loop();
159
160     syslog("LOG_INFO", "raw_transport: shutting down");
161 }
162
163 sub telnet_transport {
164     my $self = shift;
165     my ($uid, $pwd);
166     my $strikes = 3;
167     my $account = undef;
168     my $input;
169     my $config = $self->{config};
170
171     # Until the terminal has logged in, we don't trust it
172     # so use a timeout to protect ourselves from hanging.
173     eval {
174         local $SIG{ALRM} = sub { die "alarm\n"; };
175         local $|;
176         my $timeout = 0;
177         $| = 1;                 # Unbuffered output
178         $timeout = $config->{timeout} if (exists($config->{timeout}));
179
180         while ($strikes--) {
181             print "login: ";
182             alarm $timeout;
183             $uid = <STDIN>;
184             alarm 0;
185
186             print "password: ";
187             alarm $timeout;
188             $pwd = <STDIN>;
189             alarm 0;
190
191             $uid =~ s/[\r\n]+$//;
192             $pwd =~ s/[\r\n]+$//;
193
194             if (exists($config->{accounts}->{$uid})
195                 && ($pwd eq $config->{accounts}->{$uid}->password())) {
196                 $account = $config->{accounts}->{$uid};
197                 last;
198             } else {
199                 syslog("LOG_WARNING", "Invalid login attempt: '%s'", $uid);
200                 print("Invalid login\n");
201             }
202         }
203     }; # End of eval
204
205     if ($@) {
206         syslog("LOG_ERR", "telnet_transport: Login timed out");
207         die "Telnet Login Timed out";
208     } elsif (!defined($account)) {
209         syslog("LOG_ERR", "telnet_transport: Login Failed");
210         die "Login Failure";
211     } else {
212         print "Login OK.  Initiating SIP\n";
213     }
214
215     $self->{account} = $account;
216
217     $self->sip_protocol_loop();
218     syslog("LOG_INFO", "telnet_transport: shutting down");
219 }
220
221
222 sub http_transport {
223 }
224
225 #
226 # The terminal has logged in, using either the SIP login process
227 # over a raw socket, or via the pseudo-unix login provided by the
228 # telnet transport.  From that point on, both the raw and the telnet
229 # processes are the same:
230 sub sip_protocol_loop {
231     my $self = shift;
232     my $expect;
233     my $service = $self->{service};
234     my $config = $self->{config};
235     my $input;
236     # Now that the terminal has logged in, the first message
237     # we recieve must be an SC_STATUS message.  But it might be
238     # an SC_REQUEST_RESEND.  So, as long as we keep receiving
239     # SC_REQUEST_RESEND, we keep waiting for an SC_STATUS
240
241     # Comprise reports that no other ILS actually enforces this
242     # constraint, so we'll relax about it too.  As long as everybody
243     # uses the SIP "raw" login process, rather than telnet, this
244     # will be fine, becaues the LOGIN protocol exchange will force
245     # us into SIP 2.00 anyway.  Machines that want to log in using
246     # telnet MUST send an SC Status message first, even though we're
247     # not enforcing it.
248     # 
249     #$expect = SC_STATUS;
250     $expect = '';
251
252     while ($input = Sip::read_SIP_packet(*STDIN)) {
253         my $status;
254
255         $input =~ s/[\r\n]+$//sm;       # Strip off any trailing line ends
256
257         $status = Sip::MsgType::handle($input, $self, $expect);
258         next if $status eq REQUEST_ACS_RESEND;
259 #### stopped here rch
260         if (!$status) {
261             syslog("LOG_ERR", "raw_transport: failed to handle %s",
262                    substr($input, 0, 2));
263             die "raw_transport: dying";
264         } elsif ($expect && ($status ne $expect)) {
265             # We received a non-"RESEND" that wasn't what we were
266             # expecting.
267             syslog("LOG_ERR",
268                    "raw_transport: expected %s, received %s, exiting",
269                    $expect, $input);
270             die "raw_transport: exiting: expected '$expect', received '$status'";
271         }
272         # We successfully received and processed what we were expecting
273         # to receive
274         $expect = '';
275     }
276 }
277
278 1;
279 __END__