2 # Copyright (C) 2006-2008 Georgia Public Library Service
4 # Author: David J. Fiander
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.
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.
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,
25 use Sys::Syslog qw(syslog);
26 use Net::Server::PreFork;
27 use Net::Server::Proto;
30 use Data::Dumper; # For debugging
31 require UNIVERSAL::require;
33 #use Sip qw(readline);
34 use Sip::Constants qw(:all);
35 use Sip::Configuration;
36 use Sip::Checksum qw(checksum verify_cksum);
39 use constant LOG_SIP => "local6"; # Local alias for the logging facility
42 our @ISA = qw(Net::Server::PreFork);
48 RAW => \&raw_transport,
49 telnet => \&telnet_transport,
50 http => \&http_transport,
55 my $config = Sip::Configuration->new($ARGV[0]);
62 foreach my $svc (keys %{$config->{listeners}}) {
63 push @parms, "port=" . $svc;
69 # Log lines look like this:
70 # Jun 16 21:21:31 server08 steve_sip: Sip::MsgType::_initialize('Login', ...)
71 # [ TIMESTAMP ] [ HOST ] [ IDENT ]: Message...
73 # The IDENT is determined by $ENV{SIP_LOG_IDENT}, if present.
74 # Otherwise it is "_sip" appended to $USER, if present, or "acs-server" as a fallback.
77 my $syslog_ident = $ENV{SIP_LOG_IDENT} || ($ENV{USER} ? $ENV{USER} . "_sip" : 'acs-server');
80 "log_file=Sys::Syslog",
81 "syslog_ident=$syslog_ident",
82 "syslog_facility=" . LOG_SIP;
85 # Server Management: set parameters for the Net::Server::PreFork
86 # module. The module silently ignores parameters that it doesn't
87 # recognize, and complains about invalid values for parameters
90 if (defined($config->{'server-params'})) {
91 while (my ($key, $val) = each %{$config->{'server-params'}}) {
92 push @parms, $key . '=' . $val;
99 # This is the main event.
100 SIPServer->run(@parms);
106 # process_request is the callback used by Net::Server to handle
107 # an incoming connection request.
109 sub process_request {
113 my ($sockaddr, $port, $proto);
116 $self->{config} = $config;
118 $sockaddr = $self->{server}->{sockaddr};
119 $port = $self->{server}->{sockport};
120 $proto = $self->{server}->{client}->NS_proto();
121 syslog('LOG_INFO', "Inbound connection from $sockaddr on port $port and proto $proto");
123 $self->{service} = $config->find_service( $sockaddr, $port, $proto );
125 if (! defined($self->{service})) {
126 syslog( "LOG_ERR", "process_request: Unrecognized server connection: %s:%s/%s",
127 $sockaddr, $port, $proto );
128 die "process_request: Bad server connection";
131 $transport = $transports{ $self->{service}->{transport} };
133 if ( !defined($transport) ) {
134 syslog("LOG_WARNING", "Unknown transport '%s', dropping", $service->{transport});
149 my $service = $self->{service};
152 my $timeout = $self->{service}->{timeout} || $self->{config}->{timeout} || 0;
155 local $SIG{ALRM} = sub { die "raw_transport Timed Out!\n"; };
156 syslog("LOG_DEBUG", "raw_transport: timeout is $timeout");
160 $input = Sip::read_SIP_packet(*STDIN);
165 syslog("LOG_INFO", "raw_transport: shutting down: EOF during login");
167 } elsif ($input !~ /\S/) {
168 syslog("LOG_INFO", "raw_transport: received whitespace line (length %s) during login, skipping", length($input));
171 $input =~ s/[\r\n]+$//sm; # Strip off trailing line terminator
172 last if Sip::MsgType::handle($input, $self, LOGIN);
177 syslog("LOG_ERR", "raw_transport: LOGIN ERROR: '$@'");
178 die "raw_transport: login error (timeout? $@), exiting";
179 } elsif (!$self->{account}) {
180 syslog("LOG_ERR", "raw_transport: LOGIN FAILED");
181 die "raw_transport: Login failed (no account), exiting";
184 syslog("LOG_DEBUG", "raw_transport: uname/inst: '%s/%s'",
185 $self->{account}->{id},
186 $self->{account}->{institution});
188 $self->sip_protocol_loop();
190 syslog("LOG_INFO", "raw_transport: shutting down");
193 sub telnet_transport {
199 my $config = $self->{config};
200 my $timeout = $self->{service}->{timeout} || $config->{timeout} || 0;
201 syslog("LOG_DEBUG", "telnet_transport: timeout is %s", $timeout);
203 # Until the terminal has logged in, we don't trust it
204 # so use a timeout to protect ourselves from hanging.
206 local $SIG{ALRM} = sub { die "telnet_transport: Timed Out ($timeout seconds)!\n";; };
207 local $| = 1; # Unbuffered output
220 $uid =~ s/[\r\n]+$//;
221 $pwd =~ s/[\r\n]+$//;
223 if (exists($config->{accounts}->{$uid})
224 && ($pwd eq $config->{accounts}->{$uid}->password())) {
225 $account = $config->{accounts}->{$uid};
228 syslog("LOG_WARNING", "Invalid login attempt: '%s'", $uid);
229 print("Invalid login$CRLF");
235 syslog("LOG_ERR", "telnet_transport: Login timed out");
236 die "Telnet Login Timed out";
237 } elsif (!defined($account)) {
238 syslog("LOG_ERR", "telnet_transport: Login Failed");
241 print "Login OK. Initiating SIP$CRLF";
244 $self->{account} = $account;
245 syslog("LOG_DEBUG", "telnet_transport: uname/inst: '%s/%s'", $account->{id}, $account->{institution});
246 $self->sip_protocol_loop();
247 syslog("LOG_INFO", "telnet_transport: shutting down");
255 # The terminal has logged in, using either the SIP login process
256 # over a raw socket, or via the pseudo-unix login provided by the
257 # telnet transport. From that point on, both the raw and the telnet
258 # processes are the same:
259 sub sip_protocol_loop {
262 my $service = $self->{service};
263 my $config = $self->{config};
266 # Now that the terminal has logged in, the first message
267 # we recieve must be an SC_STATUS message. But it might be
268 # an SC_REQUEST_RESEND. So, as long as we keep receiving
269 # SC_REQUEST_RESEND, we keep waiting for an SC_STATUS
271 # Comprise reports that no other ILS actually enforces this
272 # constraint, so we'll relax about it too. As long as everybody
273 # uses the SIP "raw" login process, rather than telnet, this
274 # will be fine, becaues the LOGIN protocol exchange will force
275 # us into SIP 2.00 anyway. Machines that want to log in using
276 # telnet MUST send an SC Status message first, even though we're
279 #$expect = SC_STATUS;
282 while ( $input = Sip::read_SIP_packet(*STDIN) ) {
283 $input =~ s/[\r\n]+$//sm; # Strip off any trailing line ends
285 my $status = Sip::MsgType::handle($input, $self, $expect);
286 next if $status eq REQUEST_ACS_RESEND;
289 syslog("LOG_ERR", "raw_transport: failed to handle %s", substr($input,0,2));
290 die "sip_protocol_loop: failed Sip::MsgType::handle('$input', $self, '$expect')";
292 elsif ($expect && ($status ne $expect)) {
293 # We received a non-"RESEND" that wasn't what we were expecting.
294 syslog("LOG_ERR", "raw_transport: expected %s, received %s, exiting", $expect, $input);
295 die "sip_protocol_loop: exiting: expected '$expect', received '$status'";
298 # We successfully received and processed what we were expecting