6 use Sys::Syslog qw(syslog);
7 use Net::Server::PreFork;
10 use Data::Dumper; # For debugging
11 require UNIVERSAL::require;
13 #use Sip qw(readline);
14 use Sip::Constants qw(:all);
15 use Sip::Configuration;
16 use Sip::Checksum qw(checksum verify_cksum);
19 use constant LOG_SIP => "local6"; # Local alias for the logging facility
21 use vars qw(@ISA $VERSION);
25 @ISA = qw(Net::Server::PreFork);
29 # Main # not really, since package SIPServer
31 # FIXME: Is this a module or a script?
32 # A script with no MAIN namespace?
33 # A module that takes command line args?
36 RAW => \&raw_transport,
37 telnet => \&telnet_transport,
38 # http => \&http_transport, # for http just use the OPAC
44 my $config = new Sip::Configuration $ARGV[0];
45 print STDERR "SIPServer config: \n" . Dumper($config) . "\nEND SIPServer config.\n";
51 foreach my $svc (keys %{$config->{listeners}}) {
52 push @parms, "port=" . $svc;
58 push @parms, "log_file=Sys::Syslog", "syslog_ident=acs-server",
59 "syslog_facility=" . LOG_SIP;
62 # Server Management: set parameters for the Net::Server::PreFork
63 # module. The module silently ignores parameters that it doesn't
64 # recognize, and complains about invalid values for parameters
67 if (defined($config->{'server-params'})) {
68 while (my ($key, $val) = each %{$config->{'server-params'}}) {
69 push @parms, $key . '=' . $val;
73 print "Params for Net::Server::PreFork : \n" . Dumper(@parms);
76 # This is the main event.
77 __PACKAGE__ ->run(@parms);
83 # process_request is the callback used by Net::Server to handle
84 # an incoming connection request.
89 my ($sockaddr, $port, $proto);
92 $self->{config} = $config;
94 my $sockname = getsockname(STDIN);
95 ($port, $sockaddr) = sockaddr_in($sockname);
96 $sockaddr = inet_ntoa($sockaddr);
97 $proto = $self->{server}->{client}->NS_proto();
99 $self->{service} = $config->find_service($sockaddr, $port, $proto);
101 if (!defined($self->{service})) {
102 syslog("LOG_ERR", "process_request: Unknown recognized server connection: %s:%s/%s", $sockaddr, $port, $proto);
103 die "process_request: Bad server connection";
106 $transport = $transports{$self->{service}->{transport}};
108 if (!defined($transport)) {
109 syslog("LOG_WARNING", "Unknown transport '%s', dropping", $service->{transport});
123 my $service = $self->{service};
127 local $SIG{ALRM} = sub { die "Timed Out!\n"; };
128 syslog("LOG_DEBUG", "raw_transport: timeout is %d", $service->{timeout});
130 alarm $service->{timeout};
131 $input = Sip::read_SIP_packet(*STDIN);
135 syslog("LOG_INFO", "raw_transport: shutting down: EOF during login");
138 $input =~ s/[\r\n]+$//sm; # Strip off trailing line terminator
139 last if Sip::MsgType::handle($input, $self, LOGIN);
144 syslog("LOG_ERR", "raw_transport: LOGIN ERROR: '$@'");
145 die "raw_transport: login error (timeout? $@), exiting";
146 } elsif (!$self->{account}) {
147 syslog("LOG_ERR", "raw_transport: LOGIN FAILED");
148 die "raw_transport: Login failed (no account), exiting";
151 syslog("LOG_DEBUG", "raw_transport: uname/inst: '%s/%s'",
152 $self->{account}->{id},
153 $self->{account}->{institution});
155 $self->sip_protocol_loop();
156 syslog("LOG_INFO", "raw_transport: shutting down");
159 sub telnet_transport {
165 my $config = $self->{config};
166 my $timeout = $self->{service}->{timeout} || $config->{timeout} || 30;
167 # syslog("LOG_DEBUG", "telnet_transport: timeout is %s", $timeout);
170 local $SIG{ALRM} = sub { die "Timed Out ($timeout seconds)!\n"; };
171 local $| = 1; # Unbuffered output
172 # Until the terminal has logged in, we don't trust it
173 # so use a timeout to protect ourselves from hanging.
184 $pwd = <STDIN> || '';
187 syslog("LOG_DEBUG", "telnet_transport 1: uid length %s, pwd length %s", length($uid), length($pwd));
188 while (chomp($uid)) {1;}
189 while (chomp($pwd)) {1;}
190 syslog("LOG_DEBUG", "telnet_transport 2: uid length %s, pwd length %s", length($uid), length($pwd));
193 $uid =~ s/[\r\n]+$//gms; #
194 $pwd =~ s/[\r\n]+$//gms; #
195 $uid =~ s/[[:cntrl:]]//g; #
196 $pwd =~ s/[[:cntrl:]]//g; #
197 syslog("LOG_DEBUG", "telnet_transport 3: uid length %s, pwd length %s", length($uid), length($pwd));
199 if (exists ($config->{accounts}->{$uid})
200 && ($pwd eq $config->{accounts}->{$uid}->password())) {
201 $account = $config->{accounts}->{$uid};
205 syslog("LOG_WARNING", "Invalid login attempt: '%s'", ($uid||''));
206 print("Invalid login\n");
211 syslog("LOG_ERR", "telnet_transport: Login timed out");
212 die "Telnet Login Timed out";
213 } elsif (!defined($account)) {
214 syslog("LOG_ERR", "telnet_transport: Login Failed");
217 print "Login OK. Initiating SIP\n";
220 $self->{account} = $account;
221 syslog("LOG_DEBUG", "telnet_transport: uname/inst: '%s/%s'", $account->{id}, $account->{institution});
222 $self->sip_protocol_loop();
223 syslog("LOG_INFO", "telnet_transport: shutting down");
227 # The terminal has logged in, using either the SIP login process
228 # over a raw socket, or via the pseudo-unix login provided by the
229 # telnet transport. From that point on, both the raw and the telnet
230 # processes are the same:
231 sub sip_protocol_loop {
233 my $service = $self->{service};
234 my $config = $self->{config};
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
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
249 #my $expect = SC_STATUS;
252 while ($input = Sip::read_SIP_packet(*STDIN)) {
253 # begin cheap input hacks
254 $input =~ s/^\s+//; # Kill leading whitespace... a cheap stand in for better Telnet layer
255 $input =~ s/[\r\n]+$//sm; # Strip off any trailing line ends (chomp?)
256 while (chomp($input)) {warn "Extra line ending on input";}
259 syslog("LOG_ERR", "sip_protocol_loop: empty input skipped");
262 syslog("LOG_ERR", "sip_protocol_loop: quitting after too many errors");
263 die "sip_protocol_loop: quitting after too many errors";
266 # end cheap input hacks
267 my $status = Sip::MsgType::handle($input, $self, $expect);
269 syslog("LOG_ERR", "sip_protocol_loop: failed to handle %s",substr($input,0,2));
270 die "sip_protocol_loop: failed Sip::MsgType::handle('$input', $self, '$expect')";
272 next if $status eq REQUEST_ACS_RESEND;
273 if ($expect && ($status ne $expect)) {
274 # We received a non-"RESEND" that wasn't what we were expecting.
275 syslog("LOG_ERR", "sip_protocol_loop: expected %s, received %s, exiting", $expect, $input);
276 die "sip_protocol_loop: exiting: expected '$expect', received '$status'";
278 # We successfully received and processed what we were expecting