| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Net::SNPP::Server; |
|
2
|
2
|
|
|
2
|
|
2134
|
use strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
78
|
|
|
3
|
2
|
|
|
2
|
|
14
|
use warnings; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
72
|
|
|
4
|
2
|
|
|
2
|
|
2364
|
use Socket; |
|
|
2
|
|
|
|
|
10304
|
|
|
|
2
|
|
|
|
|
1536
|
|
|
5
|
2
|
|
|
2
|
|
24
|
use IO::Handle; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
98
|
|
|
6
|
2
|
|
|
2
|
|
2130
|
use Net::Cmd; |
|
|
2
|
|
|
|
|
11248
|
|
|
|
2
|
|
|
|
|
202
|
|
|
7
|
2
|
|
|
2
|
|
24
|
use Fcntl qw(:flock); |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
386
|
|
|
8
|
2
|
|
|
2
|
|
18
|
use Carp; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
138
|
|
|
9
|
2
|
|
|
2
|
|
14
|
use vars qw( @ISA $counter ); |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
4194
|
|
|
10
|
|
|
|
|
|
|
@ISA = qw( IO::Handle Net::Cmd ); |
|
11
|
|
|
|
|
|
|
$counter = 0; |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 NAME |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
Net::SNPP::Server |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
An object interface for creating SNPP servers. Almost everything you |
|
20
|
|
|
|
|
|
|
need to create your very own SNPP server is here in this module. |
|
21
|
|
|
|
|
|
|
There is a callback() method that can replace default function with |
|
22
|
|
|
|
|
|
|
your own. |
|
23
|
|
|
|
|
|
|
them. Any SNPP command can be overridden or new/custom ones can be |
|
24
|
|
|
|
|
|
|
created using custom_command(). To disable commands you just don't |
|
25
|
|
|
|
|
|
|
want to deal with, use disable_command(). |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
There may be a synopsis here someday ... |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 METHODS |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=over 4 |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=item new() |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
Create a Net::SNPP::Server object listening on a port. By default, it only |
|
38
|
|
|
|
|
|
|
listens on the localhost (127.0.0.1) - specify MultiHomed to listen on all |
|
39
|
|
|
|
|
|
|
addresses or LocalAddr to listen on only one. |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $svr = Net::SNPP::Server->new( |
|
42
|
|
|
|
|
|
|
Port => port to listen on |
|
43
|
|
|
|
|
|
|
BindTo => interface address to bind to |
|
44
|
|
|
|
|
|
|
MultiHomed => listen on all interfaces if true (and BindTo is unset) |
|
45
|
|
|
|
|
|
|
Listen => how many simultaneous connections to handle (SOMAXCONN) |
|
46
|
|
|
|
|
|
|
# the following two options are only used by handle_client() |
|
47
|
|
|
|
|
|
|
MaxErrors => maximum number of errors before disconnecting client |
|
48
|
|
|
|
|
|
|
Timeout => timeout while waiting for data (uses SIGARLM) |
|
49
|
|
|
|
|
|
|
); |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=cut |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub new { |
|
54
|
2
|
|
|
2
|
1
|
17612
|
my( $class, %args ) = @_; |
|
55
|
2
|
|
|
|
|
8
|
my $self = {}; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# set defaults for basic parameters |
|
58
|
2
|
50
|
|
|
|
36
|
if ( !exists($args{Listen}) ) { $args{Listen} = SOMAXCONN } |
|
|
2
|
|
|
|
|
8
|
|
|
59
|
2
|
50
|
|
|
|
8
|
if ( !exists($args{Port}) ) { $args{Port} = 444 } |
|
|
0
|
|
|
|
|
0
|
|
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# choose either a unix domain socket or an inet socket |
|
62
|
2
|
50
|
|
|
|
10
|
if ( !exists($args{UnixSocket}) ) { $args{Domain} = AF_INET } |
|
|
2
|
|
|
|
|
8
|
|
|
63
|
0
|
|
|
|
|
0
|
else { $args{Domain} = PF_UNIX } |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# by default, bind only to the loopback interface |
|
66
|
|
|
|
|
|
|
# i.e. MultiHomed and BindTo were not specified |
|
67
|
2
|
50
|
33
|
|
|
112
|
if ( !exists($args{MultiHomed}) && !exists($args{BindTo}) ) { |
|
|
|
0
|
|
|
|
|
|
|
68
|
2
|
|
|
|
|
8
|
$args{BindTo} = INADDR_LOOPBACK; |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
# if a bind address is passed in, bind to it |
|
71
|
|
|
|
|
|
|
elsif ( exists($args{BindTo}) ) { |
|
72
|
0
|
|
|
|
|
0
|
$args{BindTo} = inet_aton( $args{BindTo} ); |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
# bind to all interfaces if MultiHomed is defined |
|
75
|
|
|
|
|
|
|
# and BindTo is not |
|
76
|
|
|
|
|
|
|
else { |
|
77
|
0
|
|
|
|
|
0
|
$args{BindTo} = INADDR_ANY; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# these two values are only used by the handle_client method |
|
81
|
2
|
|
|
|
|
8
|
$self->{'MaxErrors'} = delete($args{MaxErrors}); |
|
82
|
2
|
|
|
|
|
6
|
$self->{'Timeout'} = delete($args{Timeout}); |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# create the socket by hand instead of IO::Socket::INET to |
|
85
|
|
|
|
|
|
|
# make manipulation a little easier within this module |
|
86
|
2
|
|
|
|
|
24
|
$self->{sock} = IO::Handle->new(); |
|
87
|
2
|
50
|
|
|
|
3224
|
socket( $self->{sock}, $args{Domain}, SOCK_STREAM, getprotobyname('tcp') ) |
|
88
|
|
|
|
|
|
|
|| croak "couldn't create socket: $!"; |
|
89
|
2
|
|
|
|
|
22
|
setsockopt( $self->{sock}, SOL_SOCKET, SO_REUSEADDR, 1 ); |
|
90
|
|
|
|
|
|
|
|
|
91
|
2
|
50
|
|
|
|
322
|
if ( $args{Domain} == PF_UNIX ) { |
|
92
|
0
|
0
|
|
|
|
0
|
if ( -e $args{UnixSocket} ) { unlink( $args{UnixSocket} ) } |
|
|
0
|
|
|
|
|
0
|
|
|
93
|
0
|
|
0
|
|
|
0
|
$self->{sockaddr} = sockaddr_un( $args{UnixSocket} ) |
|
94
|
|
|
|
|
|
|
|| croak "couldn't get socket address: $!"; |
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
else { |
|
97
|
2
|
|
33
|
|
|
24
|
$self->{sockaddr} = sockaddr_in( $args{Port}, $args{BindTo} ) |
|
98
|
|
|
|
|
|
|
|| croak "couldn't get socket address: $!"; |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
|
|
101
|
2
|
50
|
|
|
|
68
|
bind( $self->{sock}, $self->{sockaddr} ) |
|
102
|
|
|
|
|
|
|
|| croak "could not bind socket: $!"; |
|
103
|
|
|
|
|
|
|
|
|
104
|
2
|
50
|
|
|
|
42
|
listen( $self->{sock}, $args{Listen} ) |
|
105
|
|
|
|
|
|
|
|| croak "could not listen on socket: $!"; |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# set default callbacks |
|
108
|
|
|
|
|
|
|
$self->{CB} = { |
|
109
|
|
|
|
|
|
|
process_page => sub { |
|
110
|
0
|
|
|
0
|
|
0
|
my( $pgr, $page, $results ) = @_; |
|
111
|
0
|
|
|
|
|
0
|
push( @$results, [ $pgr, $page ] ); |
|
112
|
|
|
|
|
|
|
}, |
|
113
|
|
|
|
|
|
|
validate_pager_id => sub { |
|
114
|
2
|
50
|
33
|
2
|
|
54
|
return undef if ( $_[0] =~ /\D/ || length($_[0]) < 7 ); |
|
115
|
2
|
|
|
|
|
11
|
return $_[0]; |
|
116
|
|
|
|
|
|
|
}, |
|
117
|
2
|
50
|
|
2
|
|
17
|
validate_pager_pin => sub { $_[1] || 1 }, |
|
118
|
0
|
|
|
0
|
|
0
|
write_log => sub { print STDERR "@_\n" }, |
|
119
|
|
|
|
|
|
|
create_id_and_pin => sub { |
|
120
|
0
|
|
|
0
|
|
0
|
srand(); # re-seed the pseudrandom number generator |
|
121
|
0
|
|
|
|
|
0
|
return( time().$counter, int(rand(1000000000)) ); |
|
122
|
|
|
|
|
|
|
} |
|
123
|
2
|
|
|
|
|
44
|
}; |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# initialize disabled and custom commands hashrefs |
|
126
|
2
|
|
|
|
|
4
|
$self->{disabled} = {}; |
|
127
|
2
|
|
|
|
|
4
|
$self->{custom} = {}; |
|
128
|
|
|
|
|
|
|
|
|
129
|
2
|
|
|
|
|
20
|
return bless( $self, $class ); |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=item client() |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Calls accept() for you and returns a client handle. This method |
|
135
|
|
|
|
|
|
|
will block if there is no waiting client. The handle returned |
|
136
|
|
|
|
|
|
|
is a subclass of IO::Handle, so all IO::Handle methods should work. |
|
137
|
|
|
|
|
|
|
my $client = $server->client(); |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub client { |
|
142
|
1
|
|
|
1
|
1
|
84
|
my $handle = IO::Handle->new(); |
|
143
|
1
|
|
|
|
|
2115
|
accept( $handle, $_[0]->{sock} ); |
|
144
|
1
|
|
|
|
|
45
|
return bless($handle, ref($_[0])); |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=item ip() |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Return the IP address associated with a client handle. |
|
150
|
|
|
|
|
|
|
printf "connection from %s", $client->ip(); |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=cut |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub ip { |
|
155
|
0
|
|
|
0
|
1
|
0
|
my $remote_client = getpeername($_[0]); |
|
156
|
0
|
0
|
|
|
|
0
|
return 'xxx.xxx.xxx.xxx' if ( !defined($remote_client) ); |
|
157
|
0
|
|
|
|
|
0
|
my($port,$iaddr) = unpack_sockaddr_in($remote_client); |
|
158
|
0
|
|
|
|
|
0
|
return inet_ntoa($iaddr); |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=item socket() |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Returns the raw socket handle. This mainly exists for use with select() or |
|
164
|
|
|
|
|
|
|
IO::Select. |
|
165
|
|
|
|
|
|
|
my $select = IO::Select->new(); |
|
166
|
|
|
|
|
|
|
$select->add( $server->socket() ); |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=cut |
|
169
|
|
|
|
|
|
|
|
|
170
|
0
|
|
|
0
|
1
|
0
|
sub socket { $_[0]->{sock}; } |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item connected() |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
For use with a client handle. True if server socket is still alive. |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=cut |
|
177
|
|
|
|
|
|
|
|
|
178
|
1
|
50
|
|
1
|
1
|
23
|
sub connected { $_[0]->opened() && getpeername($_[0]) } |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item shutdown() |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Shuts down the server socket. |
|
183
|
|
|
|
|
|
|
$server->shutdown(2); |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=cut |
|
186
|
|
|
|
|
|
|
|
|
187
|
1
|
|
50
|
1
|
1
|
56
|
sub shutdown { shutdown($_[0],$_[1] || 2) } |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item callback() |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Insert a callback into Server.pm. |
|
192
|
|
|
|
|
|
|
$server->callback( 'process_page', \&my_function ); |
|
193
|
|
|
|
|
|
|
$server->callback( 'validate_pager_id', \&my_function ); |
|
194
|
|
|
|
|
|
|
$server->callback( 'validate_pager_pin', \&my_function ); |
|
195
|
|
|
|
|
|
|
$server->callback( 'write_log', \&my_function ); |
|
196
|
|
|
|
|
|
|
$server->callback( 'create_id_and_pin', \&my_function ); |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=over 2 |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=item process_page( $PAGER_ID, \%PAGE, \@RESULTS ) |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
$PAGER_ID = [ |
|
203
|
|
|
|
|
|
|
0 => retval of validate_pager_id |
|
204
|
|
|
|
|
|
|
1 => retval of validate_pager_pin |
|
205
|
|
|
|
|
|
|
] |
|
206
|
|
|
|
|
|
|
$PAGE = { |
|
207
|
|
|
|
|
|
|
mess => $, |
|
208
|
|
|
|
|
|
|
responses => [], |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=item validate_pager_id( PAGER_ID ) |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
The return value of this callback will be saved as the pager id |
|
214
|
|
|
|
|
|
|
that is passed to the process_page callback as the first list |
|
215
|
|
|
|
|
|
|
element of the first argument. |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=item validate_pager_pin( VALIDATED_PAGER_ID, PIN ) |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
The value returned by this callback will be saved as the second |
|
220
|
|
|
|
|
|
|
list element in the first argument to process_page. |
|
221
|
|
|
|
|
|
|
The PAGER_ID input to this callback is the output from the |
|
222
|
|
|
|
|
|
|
validate_pager_id callback. |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
NOTE: If you really care about the PIN, you must use this callback. The default callback will return 1 if the pin is not set. |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=item write_log |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
First argument is a Unix syslog level, such as "warning" or "info." |
|
229
|
|
|
|
|
|
|
The rest of the arguments are the message. Return value is ignored. |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=item create_id_and_pin |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Create an ID and PIN for a 2way message. |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=back |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=cut |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub callback ($ $ $) { |
|
240
|
3
|
50
|
|
3
|
1
|
37
|
croak "first argument callback() to must be one of: ", join(', ', keys(%{$_[0]->{CB}})) |
|
|
0
|
|
|
|
|
0
|
|
|
241
|
|
|
|
|
|
|
if ( !exists($_[0]->{CB}{$_[1]}) ); |
|
242
|
3
|
50
|
|
|
|
42
|
croak "second argument callback() to must be a CODE ref" |
|
243
|
|
|
|
|
|
|
if ( ref($_[2]) ne 'CODE' ); |
|
244
|
3
|
|
|
|
|
10
|
$_[0]->{CB}{$_[1]} = $_[2]; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=item custom_command() |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Create a custom command or override a default command in handle_client(). |
|
250
|
|
|
|
|
|
|
The command name must be 4 letters or numbers. The second argument is a coderef |
|
251
|
|
|
|
|
|
|
that should return a text command, i.e. "250 OK" and some "defined" value to continue the |
|
252
|
|
|
|
|
|
|
client loop. +++If no value is set, the client will be disconnected after |
|
253
|
|
|
|
|
|
|
executing your command.+++ If you need MSTA or KTAG, this |
|
254
|
|
|
|
|
|
|
is the hook you need to implement them. |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
The subroutine will be passed the command arguments, split on whitespace. |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub my_MSTA_sub { |
|
259
|
|
|
|
|
|
|
my( $id, $password ) = @_; |
|
260
|
|
|
|
|
|
|
# ... |
|
261
|
|
|
|
|
|
|
return "250 OK", 1; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
$server->custom_command( "MSTA", \&my_MSTA_sub ); |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=cut |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
sub custom_command ($ $ $) { |
|
268
|
0
|
0
|
|
0
|
1
|
0
|
croak "first argument to custom_command must be exactly 4 characters" |
|
269
|
|
|
|
|
|
|
if ( length($_[1]) != 4 ); |
|
270
|
0
|
0
|
|
|
|
0
|
croak "second argument to custom_command must be a coderef" |
|
271
|
|
|
|
|
|
|
if ( ref($_[2]) ne 'CODE' ); |
|
272
|
0
|
|
|
|
|
0
|
$_[0]->{custom}{uc($_[1])} = $_[2]; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=item disable_command() |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
Specify a command to disable in the server. This is useful, for instance, |
|
278
|
|
|
|
|
|
|
if you don't want to support level 3 commands. |
|
279
|
|
|
|
|
|
|
$server->disable_command( "2WAY", "550 2WAY not supported here" ); |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
The second argument is an optional custom error message. The default is: |
|
282
|
|
|
|
|
|
|
"500 Command Not Implemented, Try Again" |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=cut |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub disable_command { |
|
287
|
|
|
|
|
|
|
# shorten & uppercase it so it matches in handle_client |
|
288
|
0
|
|
|
0
|
1
|
0
|
my $cmd = unpack('A4',uc($_[1])); |
|
289
|
|
|
|
|
|
|
|
|
290
|
0
|
0
|
|
|
|
0
|
if ( defined($_[2]) ) { |
|
291
|
0
|
|
|
|
|
0
|
$_[0]->{disabled}{$cmd} = $_[2]; |
|
292
|
|
|
|
|
|
|
} |
|
293
|
|
|
|
|
|
|
else { |
|
294
|
0
|
|
|
|
|
0
|
$_[0]->{disabled}{$cmd} = "500 Command Not Implemented, Try Again"; |
|
295
|
|
|
|
|
|
|
} |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=item handle_client() |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Takes the result of $server->client() and takes care of parsing |
|
301
|
|
|
|
|
|
|
the user input. This should be quite close to being rfc1861 |
|
302
|
|
|
|
|
|
|
compliant. If you specified Timeout to be something other |
|
303
|
|
|
|
|
|
|
than 0 in new(), SIGARLM will be used to set a timeout. If you |
|
304
|
|
|
|
|
|
|
use this, make sure to take signals into account when writing your |
|
305
|
|
|
|
|
|
|
code. fork()'ing before calling handle_client is a good way |
|
306
|
|
|
|
|
|
|
to avoid interrupting code that shouldn't be interrupted. |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=cut |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub handle_client ($ $) { |
|
311
|
1
|
|
|
1
|
1
|
10
|
my( $self, $client ) = @_; |
|
312
|
1
|
|
|
|
|
15
|
my $page = {}; # store the stuff the user gives us in this hash |
|
313
|
1
|
|
|
|
|
8
|
my @pgrs = (); # store the list of pagers |
|
314
|
|
|
|
|
|
|
# each pager is an array ref [ $pager_id, $pin ] |
|
315
|
1
|
|
|
|
|
3
|
my @retvals = (); # build up a list of return values |
|
316
|
1
|
|
|
|
|
9
|
my $errors = 0; # count the errors for maximum errors |
|
317
|
1
|
|
|
|
|
10
|
my $timeout = 0; |
|
318
|
1
|
|
|
|
|
30
|
local(%SIG); |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# enable timeouts if user requested passed Timeout to new() |
|
321
|
1
|
50
|
|
|
|
17
|
if ( $self->{'Timeout'} ) { |
|
322
|
|
|
|
|
|
|
$SIG{ALRM} = sub { |
|
323
|
0
|
|
|
0
|
|
0
|
$self->{CB}{write_log}->( 'debug', "client timeout" ); |
|
324
|
0
|
|
|
|
|
0
|
$client->command( "421 Timeout, Goodbye" ); |
|
325
|
0
|
|
|
|
|
0
|
$client->shutdown(2); |
|
326
|
0
|
|
|
|
|
0
|
$timeout = 1; |
|
327
|
0
|
|
|
|
|
0
|
}; |
|
328
|
0
|
|
|
|
|
0
|
alarm( $self->{'Timeout'} ); |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# let the client know we're ready for them |
|
332
|
1
|
|
|
|
|
138
|
$client->command( "220 SNPP Gateway Ready" ); |
|
333
|
|
|
|
|
|
|
|
|
334
|
1
|
|
|
|
|
1986
|
$self->{CB}{write_log}->( 'debug', "client connected" ); |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# loop until timeout or client quits |
|
337
|
1
|
|
33
|
|
|
255
|
while ( $timeout == 0 && (my $input = $client->getline()) ) { |
|
338
|
|
|
|
|
|
|
# clean \n\r's out of input, then split it up by whitespace |
|
339
|
15
|
|
|
|
|
3920
|
$input =~ s/[\r\n]+//gs; |
|
340
|
15
|
|
|
|
|
64
|
my @cmd = split( /\s+/, $input ); |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# uppercase and truncate the command shifted from @cmd to 4 characters |
|
343
|
15
|
|
|
|
|
94
|
my $user_cmd = unpack('A4',uc(shift(@cmd))); |
|
344
|
15
|
50
|
|
|
|
59
|
if ( length($user_cmd) != 4 ) { |
|
345
|
|
|
|
|
|
|
# FIXME: put in correct full text from RFC document |
|
346
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Invalid Command" ); |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
|
|
349
|
15
|
|
|
|
|
117
|
$self->{CB}{write_log}->( 'debug', "processing command '$user_cmd @cmd'" ); |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# //////////////////////////////////////////////////////////////////// # |
|
352
|
|
|
|
|
|
|
# BEGIN COMMANDS PARSING # |
|
353
|
|
|
|
|
|
|
# \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
######################################################################## |
|
356
|
|
|
|
|
|
|
# user disabled commands --------------------------------------------- # |
|
357
|
15
|
50
|
|
|
|
328
|
if ( exists($self->{disabled}{$user_cmd}) ) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
358
|
0
|
|
|
|
|
0
|
$errors++; |
|
359
|
0
|
|
|
|
|
0
|
$client->command( $self->{disabled}{$user_cmd} ); |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
######################################################################## |
|
362
|
|
|
|
|
|
|
# user custom commands ----------------------------------------------- # |
|
363
|
|
|
|
|
|
|
elsif ( exists($self->{custom}{$user_cmd}) ) { |
|
364
|
0
|
|
|
|
|
0
|
my ($cmdtxt,$cont) = $self->{custom}{$user_cmd}->( @cmd ); |
|
365
|
0
|
|
|
|
|
0
|
$client->command( $cmdtxt ); |
|
366
|
0
|
0
|
|
|
|
0
|
last if ( !$cont ); |
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
######################################################################## |
|
369
|
|
|
|
|
|
|
# 4.3 Level 1 Commands ################################################# |
|
370
|
|
|
|
|
|
|
######################################################################## |
|
371
|
|
|
|
|
|
|
# 4.3.1 PAGEr --------------------------------------------- # |
|
372
|
|
|
|
|
|
|
# 4.5.2 PAGEr [Password/PIN] ------------------------------- # |
|
373
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'PAGE' ) { |
|
374
|
2
|
|
|
|
|
10
|
my $valid_pgr_id = $self->{CB}{validate_pager_id}->($cmd[0]); |
|
375
|
2
|
|
|
|
|
17
|
my $valid_pin = $self->{CB}{validate_pager_pin}->($valid_pgr_id,$cmd[1]); |
|
376
|
2
|
50
|
33
|
|
|
33
|
if ( $valid_pgr_id && $valid_pin ) { |
|
377
|
2
|
|
|
|
|
5
|
push( @pgrs, [$valid_pgr_id,$valid_pin] ); |
|
378
|
2
|
|
|
|
|
9
|
$client->command( "250 Pager ID Accepted" ); |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
else { |
|
381
|
0
|
|
|
|
|
0
|
$errors++; |
|
382
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Invalid Pager ID" ); |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
######################################################################## |
|
387
|
|
|
|
|
|
|
# 4.3.2 MESSage --------------------------- # |
|
388
|
|
|
|
|
|
|
# 4.5.8 SUBJect ------------------------------------- # |
|
389
|
|
|
|
|
|
|
elsif ( $user_cmd =~ /(MESS|SUBJ)/ ) { |
|
390
|
1
|
|
|
|
|
16
|
my $key = $1; |
|
391
|
1
|
50
|
33
|
|
|
18
|
if ( $key && $key eq 'MESS' && defined($page->{mess}) ) { |
|
|
|
|
33
|
|
|
|
|
|
392
|
0
|
|
|
|
|
0
|
$errors++; |
|
393
|
0
|
|
|
|
|
0
|
$client->command( "503 ERROR, Message Already Entered" ); |
|
394
|
0
|
|
|
|
|
0
|
next; |
|
395
|
|
|
|
|
|
|
} |
|
396
|
1
|
50
|
33
|
|
|
25
|
if ( !defined($cmd[0]) || $cmd[0] eq '' ) { |
|
397
|
0
|
|
|
|
|
0
|
$errors++; |
|
398
|
0
|
|
|
|
|
0
|
$client->command( "550 ERROR, Invalid Message" ); |
|
399
|
0
|
|
|
|
|
0
|
next; |
|
400
|
|
|
|
|
|
|
} |
|
401
|
1
|
|
|
|
|
28
|
$page->{lc($key)} = join(' ', @cmd); |
|
402
|
1
|
|
|
|
|
5
|
$client->command( "250 Message OK" ); |
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
######################################################################## |
|
406
|
|
|
|
|
|
|
# 4.3.3 RESEt -------------------------------------------------------- # |
|
407
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'RESE' ) { |
|
408
|
1
|
|
|
|
|
3
|
$page = {}; |
|
409
|
1
|
|
|
|
|
3
|
@pgrs = (); |
|
410
|
1
|
|
|
|
|
4
|
$client->command( "250 RESET OK" ); |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
######################################################################## |
|
414
|
|
|
|
|
|
|
# 4.3.4 SEND --------------------------------------------------------- # |
|
415
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'SEND' ) { |
|
416
|
1
|
50
|
|
|
|
7
|
if ( @pgrs == 0 ) { |
|
417
|
0
|
|
|
|
|
0
|
$errors++; |
|
418
|
0
|
|
|
|
|
0
|
$client->command( "503 Error, Pager ID needed" ); |
|
419
|
0
|
|
|
|
|
0
|
next; |
|
420
|
|
|
|
|
|
|
} |
|
421
|
1
|
50
|
|
|
|
5
|
if ( !exists($page->{mess}) ) { |
|
422
|
0
|
|
|
|
|
0
|
$errors++; |
|
423
|
0
|
|
|
|
|
0
|
$client->command( "503 Error, Pager ID or Message Incomplete" ); |
|
424
|
0
|
|
|
|
|
0
|
next; |
|
425
|
|
|
|
|
|
|
} |
|
426
|
|
|
|
|
|
|
|
|
427
|
1
|
|
|
|
|
2
|
my $res = undef; |
|
428
|
1
|
|
|
|
|
7
|
for ( my $i=0; $i<@pgrs; $i++ ) { |
|
429
|
1
|
50
|
|
|
|
5
|
if ( !exists($page->{alert}) ) { $page->{alert} = 0 } |
|
|
0
|
|
|
|
|
0
|
|
|
430
|
1
|
50
|
|
|
|
5
|
if ( !exists($page->{hold}) ) { $page->{hold} = 0 } |
|
|
0
|
|
|
|
|
0
|
|
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# call the callback subroutine with the data |
|
433
|
|
|
|
|
|
|
# the default callback just pushes the data onto @retvals |
|
434
|
1
|
|
|
|
|
26
|
$res = $self->{CB}{process_page}->( $pgrs[$i], $page, \@retvals ); |
|
435
|
|
|
|
|
|
|
} |
|
436
|
1
|
50
|
33
|
|
|
23
|
if ( $res && exists($page->{twoway}) ) { |
|
|
|
50
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# this callback generates the two numbers for identifying a page |
|
438
|
0
|
|
|
|
|
0
|
my @tags = $self->{CB}{create_id_and_pin}->( \@pgrs, $page ); |
|
439
|
0
|
|
|
|
|
0
|
$client->command( "960 @tags OK, Message QUEUED for Delivery" ); |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
elsif ( $res ) { |
|
442
|
1
|
|
|
|
|
6
|
$client->command( "250 Message Sent Successfully" ); |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
else { |
|
445
|
0
|
|
|
|
|
0
|
$client->command( "554 Error, failed" ); |
|
446
|
0
|
|
|
|
|
0
|
next; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
# RESEt |
|
449
|
1
|
|
|
|
|
1001
|
@pgrs = (); |
|
450
|
1
|
|
|
|
|
5
|
$page = {}; |
|
451
|
|
|
|
|
|
|
} |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
######################################################################## |
|
454
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'QUIT' ) { |
|
455
|
1
|
|
|
|
|
13
|
$client->command( "221 OK, Goodbye" ); |
|
456
|
1
|
|
|
|
|
89
|
last; |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
######################################################################## |
|
460
|
|
|
|
|
|
|
# 4.3.6 HELP (optional) ---------------------------------------------- # |
|
461
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'HELP' ) { |
|
462
|
|
|
|
|
|
|
{ |
|
463
|
2
|
|
|
2
|
|
16
|
no warnings; # so we can use |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
3142
|
|
|
|
0
|
|
|
|
|
0
|
|
|
464
|
0
|
|
|
|
|
0
|
while () { $client->command( $_ ) } |
|
|
0
|
|
|
|
|
0
|
|
|
465
|
0
|
|
|
|
|
0
|
$client->command( "250 End of Help Information" ); |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
} |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
######################################################################## |
|
470
|
|
|
|
|
|
|
## 4.4 Level 2 - Minimum Extensions #################################### |
|
471
|
|
|
|
|
|
|
######################################################################## |
|
472
|
|
|
|
|
|
|
# 4.4.1 DATA --------------------------------------------------------- # |
|
473
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'DATA' ) { |
|
474
|
1
|
|
|
|
|
6
|
$client->command( "354 Begin Input; End with '.'" ); |
|
475
|
1
|
|
|
|
|
507
|
my $buffer = join( '', @{ $client->read_until_dot() } ); |
|
|
1
|
|
|
|
|
38
|
|
|
476
|
1
|
50
|
33
|
|
|
37452
|
if ( !defined($buffer) || !length($buffer) ) { |
|
477
|
0
|
|
|
|
|
0
|
$errors++; |
|
478
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Blank Message" ); |
|
479
|
|
|
|
|
|
|
} |
|
480
|
|
|
|
|
|
|
else { |
|
481
|
1
|
|
|
|
|
21
|
$buffer =~ s/[\r\n]+/\n/gs; |
|
482
|
1
|
|
|
|
|
4
|
$page->{mess} = $buffer; |
|
483
|
1
|
|
|
|
|
8
|
$client->command( "250 Message OK" ); |
|
484
|
|
|
|
|
|
|
} |
|
485
|
|
|
|
|
|
|
} |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
######################################################################## |
|
488
|
|
|
|
|
|
|
## 4.5 Level 2 - Optional Extensions ################################### |
|
489
|
|
|
|
|
|
|
######################################################################## |
|
490
|
|
|
|
|
|
|
# 4.5.4 ALERt ---------------------------------------- # |
|
491
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'ALER' ) { |
|
492
|
1
|
50
|
33
|
|
|
37
|
if ( defined($cmd[0]) && ($cmd[0] == 1 || $cmd[0] == 0) ) { |
|
|
|
|
33
|
|
|
|
|
|
493
|
1
|
|
|
|
|
3
|
$page->{alert} = $cmd[0]; |
|
494
|
1
|
|
|
|
|
5
|
$client->command( "250 OK, Alert Override Accepted" ); |
|
495
|
|
|
|
|
|
|
} |
|
496
|
|
|
|
|
|
|
else { |
|
497
|
0
|
|
|
|
|
0
|
$errors++; |
|
498
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Invalid Alert Parameter" ); |
|
499
|
|
|
|
|
|
|
} |
|
500
|
|
|
|
|
|
|
} |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
######################################################################## |
|
503
|
|
|
|
|
|
|
# 4.5.6 HOLDuntil [+/-GMTdifference] ------------------ # |
|
504
|
|
|
|
|
|
|
# non-rfc to accept 4-digit years is also accepted ---- # |
|
505
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'HOLD' ) { |
|
506
|
1
|
50
|
33
|
|
|
24
|
if ( defined($cmd[0]) && $cmd[0] !~ /[^0-9]/ |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
507
|
|
|
|
|
|
|
&& (length($cmd[0]) == 12 || length($cmd[0]) == 14) ) { |
|
508
|
1
|
|
|
|
|
7
|
$page->{hold} = $cmd[0]; |
|
509
|
1
|
50
|
|
|
|
6
|
if ( $cmd[1] =~ /([+-]\d+)/ ) { $page->{hold_gmt_diff} = $1; } |
|
|
1
|
|
|
|
|
4
|
|
|
510
|
1
|
|
|
|
|
6
|
$client->command( "250 Delayed Messaging Selected" ); |
|
511
|
|
|
|
|
|
|
} |
|
512
|
|
|
|
|
|
|
else { |
|
513
|
0
|
|
|
|
|
0
|
$errors++; |
|
514
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Invalid Delivery Date/Time" ); |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
} |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
######################################################################## |
|
519
|
|
|
|
|
|
|
## 4.6 Level 3 - Two-Way Extensions #################################### |
|
520
|
|
|
|
|
|
|
######################################################################## |
|
521
|
|
|
|
|
|
|
# 4.6.1 2WAY --------------------------------------------------------- # |
|
522
|
|
|
|
|
|
|
elsif ( $user_cmd eq '2WAY' ) { |
|
523
|
1
|
50
|
33
|
|
|
20
|
if ( exists($page->{mess}) || @pgrs > 0 ) { |
|
524
|
0
|
|
|
|
|
0
|
$errors++; |
|
525
|
0
|
|
|
|
|
0
|
$client->command( "550 Error, Standard Transaction Already Underway, use RESEt" ); |
|
526
|
0
|
|
|
|
|
0
|
next; |
|
527
|
|
|
|
|
|
|
} |
|
528
|
1
|
|
|
|
|
4
|
$page->{twoway} = 1; |
|
529
|
1
|
|
|
|
|
10
|
$client->command( "250 OK, Beginning 2-Way Transaction" ); |
|
530
|
|
|
|
|
|
|
} |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
######################################################################## |
|
533
|
|
|
|
|
|
|
# 4.6.2 PING --------------------------------------- # |
|
534
|
|
|
|
|
|
|
# FIXME: what the heck should this do by default? |
|
535
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'PING' ) { |
|
536
|
1
|
|
|
|
|
16
|
$client->command( "250 OK, Cannot access device status" ); |
|
537
|
|
|
|
|
|
|
} |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
######################################################################## |
|
540
|
|
|
|
|
|
|
# 4.6.7 MCREsponse <2-byte_Code> Response_Text (not implemented) ----- # |
|
541
|
|
|
|
|
|
|
elsif ( $user_cmd eq 'MCRE' ) { |
|
542
|
4
|
50
|
33
|
|
|
80
|
if ( !exists($page->{twoway}) ) { |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
543
|
0
|
|
|
|
|
0
|
$errors++; |
|
544
|
0
|
|
|
|
|
0
|
$client->command( "550 MCResponses Not Enabled" ); |
|
545
|
|
|
|
|
|
|
} |
|
546
|
|
|
|
|
|
|
elsif ( $cmd[0] !~ /[^0-9]/ && length($cmd[0]) < 3 && |
|
547
|
|
|
|
|
|
|
length($cmd[1]) >= 1 && length($cmd[1]) < 16 ) { |
|
548
|
4
|
50
|
|
|
|
22
|
if ( exists($page->{responses}{$cmd[0]}) ) { |
|
549
|
0
|
|
|
|
|
0
|
$client->command( "502 Error! Would Duplicate Previously Entered MCResponse" ); |
|
550
|
0
|
|
|
|
|
0
|
next; |
|
551
|
|
|
|
|
|
|
} |
|
552
|
4
|
|
|
|
|
8392
|
$page->{responses}{shift @cmd} = join(' ',@cmd); |
|
553
|
4
|
|
|
|
|
27
|
$client->command( "250 Response Added to Transaction" ); |
|
554
|
|
|
|
|
|
|
} |
|
555
|
|
|
|
|
|
|
else { |
|
556
|
0
|
|
|
|
|
0
|
$errors++; |
|
557
|
0
|
|
|
|
|
0
|
$client->command( "554 Error, failed" ); |
|
558
|
|
|
|
|
|
|
} |
|
559
|
|
|
|
|
|
|
} |
|
560
|
|
|
|
|
|
|
######################################################################## |
|
561
|
|
|
|
|
|
|
# UNKNOWN/UNDEFINED COMMANDS ----------------------------------------- # |
|
562
|
|
|
|
|
|
|
# -------------------------------------------------------------------- # |
|
563
|
|
|
|
|
|
|
# 4.5.1 LOGIn [password] (not implemented) ----------------- # |
|
564
|
|
|
|
|
|
|
# 4.5.3 LEVEl (not implemented) ----------------- # |
|
565
|
|
|
|
|
|
|
# 4.5.5 COVErage (not implemented) ----------------- # |
|
566
|
|
|
|
|
|
|
# 4.5.7 CALLerid (not implemented) ----------------- # |
|
567
|
|
|
|
|
|
|
# 4.6.3 EXPTag (not implemented) ----------------- # |
|
568
|
|
|
|
|
|
|
# 4.6.5 ACKRead <0|1> (not implemented) ----------------- # |
|
569
|
|
|
|
|
|
|
# 4.6.6 RTYPe (not implemented) ----------------- # |
|
570
|
|
|
|
|
|
|
# MSTA --------------------------------------------------------------- # |
|
571
|
|
|
|
|
|
|
# KTAG (not implemented) ----------------- # |
|
572
|
|
|
|
|
|
|
######################################################################## |
|
573
|
|
|
|
|
|
|
else { |
|
574
|
0
|
|
|
|
|
0
|
$errors++; |
|
575
|
0
|
|
|
|
|
0
|
$client->command( "500 Command Not Implemented, Try Again" ); |
|
576
|
|
|
|
|
|
|
} |
|
577
|
|
|
|
|
|
|
# //////////////////////////////////////////////////////////////////// # |
|
578
|
|
|
|
|
|
|
# END COMMANDS PARSING # |
|
579
|
|
|
|
|
|
|
# \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ # |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# check the number of errors |
|
582
|
14
|
50
|
33
|
|
|
5479
|
if ( $self->{MaxErrors} && $errors >= $self->{MaxErrors} ) { |
|
583
|
0
|
|
|
|
|
0
|
$client->command( "421 Too Many Errors, Goodbye (terminate connection)" ); |
|
584
|
0
|
|
|
|
|
0
|
last; |
|
585
|
|
|
|
|
|
|
} |
|
586
|
|
|
|
|
|
|
# reset the alarm on input |
|
587
|
14
|
50
|
|
|
|
546
|
if ( $self->{Timeout} ) { alarm(0); alarm( $self->{Timeout} ); } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
588
|
|
|
|
|
|
|
} # while() |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
# turn off the alarm |
|
591
|
1
|
50
|
|
|
|
14
|
if ( $self->{Timeout} ) { alarm(0); } |
|
|
0
|
|
|
|
|
0
|
|
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
# disconnect if we're still connected |
|
594
|
1
|
50
|
|
|
|
24
|
if ( $client->connected() ) { $client->shutdown(2) } |
|
|
1
|
|
|
|
|
77
|
|
|
595
|
|
|
|
|
|
|
|
|
596
|
1
|
|
|
|
|
8
|
return @retvals; |
|
597
|
|
|
|
|
|
|
} |
|
598
|
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=item forked_server() |
|
600
|
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
Creates a server in a forked process. The return value is |
|
602
|
|
|
|
|
|
|
an array (or arrayref depending on context) containing a read-only pipe and |
|
603
|
|
|
|
|
|
|
the pid of the new process. Pages completed will be written to the pipe as |
|
604
|
|
|
|
|
|
|
a semicolon delimited array. |
|
605
|
|
|
|
|
|
|
my($pipe,$pid) = $server->forked_server(); |
|
606
|
|
|
|
|
|
|
my $line = $pipe->getline(); |
|
607
|
|
|
|
|
|
|
chomp( $line ); |
|
608
|
|
|
|
|
|
|
my( $pgr, $pgr, %pagedata ) = split( /;/, $line ); |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=cut |
|
611
|
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# when testing, pass in an integer argument to limit the number of clients |
|
613
|
|
|
|
|
|
|
# the server will process before exiting |
|
614
|
|
|
|
|
|
|
sub forked_server { |
|
615
|
2
|
|
|
2
|
1
|
4
|
my( $self, $count_arg ) = @_; |
|
616
|
2
|
|
|
|
|
4
|
my $count = -1; |
|
617
|
2
|
50
|
|
|
|
6
|
if ( $count_arg ) { $count = $count_arg } |
|
|
2
|
|
|
|
|
6
|
|
|
618
|
2
|
|
|
|
|
6
|
my @pids = (); # pids to merge before exit |
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
# create a pipe for communication from child back to this process |
|
621
|
2
|
|
|
|
|
14
|
our( $rp, $wp ) = ( IO::Handle->new(), IO::Handle->new() ); |
|
622
|
2
|
50
|
|
|
|
130
|
pipe( $rp, $wp ) |
|
623
|
|
|
|
|
|
|
|| die "could not create READ/WRITE pipes"; |
|
624
|
2
|
|
|
|
|
16
|
$wp->autoflush(1); |
|
625
|
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
# declare our callback subroutine for process_page |
|
627
|
|
|
|
|
|
|
# has it's own ugly serialization that should probably be replaced |
|
628
|
|
|
|
|
|
|
# with Storable or Dumper |
|
629
|
|
|
|
|
|
|
sub write_to_pipe { |
|
630
|
1
|
|
|
1
|
0
|
2
|
my( $pgr, $page, $results ) = @_; |
|
631
|
1
|
|
|
|
|
3
|
my( @parts, @resps ) = (); |
|
632
|
1
|
50
|
|
|
|
5
|
if ( my $href = delete($page->{responses}) ) { |
|
633
|
0
|
|
|
|
|
0
|
while ( my($k,$v) = each(%$href) ) { |
|
634
|
0
|
|
|
|
|
0
|
$v =~ s/;/\%semicolon%/g; |
|
635
|
0
|
|
|
|
|
0
|
$k = "responses[$k]"; |
|
636
|
0
|
|
|
|
|
0
|
push( @resps, $k, $v ); |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
} |
|
639
|
1
|
|
|
|
|
7
|
while ( my($k,$v) = each(%$page) ) { |
|
640
|
4
|
50
|
|
|
|
10
|
if ( !defined($v) ) { $v = '' } |
|
|
0
|
|
|
|
|
0
|
|
|
641
|
4
|
|
|
|
|
22
|
push( @parts, $k, $v ); |
|
642
|
|
|
|
|
|
|
} |
|
643
|
1
|
50
|
|
|
|
13
|
if ( !defined($pgr->[1]) ) { $pgr->[1] = '1' } |
|
|
0
|
|
|
|
|
0
|
|
|
644
|
1
|
|
|
|
|
6
|
my $out = join( ';', @$pgr, @parts, @resps ); |
|
645
|
1
|
|
|
|
|
3
|
$out =~ s/[\r\n]+//gs; # make sure there aren't any unexpected newlines |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# send the page semicolon delimited down the pipe |
|
648
|
1
|
|
|
|
|
15
|
flock( $wp, LOCK_EX ); |
|
649
|
1
|
|
|
|
|
20
|
$wp->print( "$out\n" ); |
|
650
|
1
|
|
|
|
|
42
|
flock( $wp, LOCK_UN ); |
|
651
|
|
|
|
|
|
|
} |
|
652
|
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# fork a child process to act as a server |
|
654
|
2
|
|
|
|
|
3560
|
my $pid = fork(); |
|
655
|
2
|
100
|
|
|
|
310
|
if ( $pid ) { |
|
656
|
1
|
|
|
|
|
82
|
$wp->close(); |
|
657
|
1
|
50
|
|
|
|
119
|
return wantarray ? ($rp,$pid) : [$rp,$pid]; |
|
658
|
|
|
|
|
|
|
} |
|
659
|
|
|
|
|
|
|
else { |
|
660
|
1
|
|
|
|
|
77
|
$rp->close(); |
|
661
|
|
|
|
|
|
|
# replace the page callback with our own subroutine |
|
662
|
1
|
|
|
|
|
426
|
$self->callback( 'process_page', \&write_to_pipe ); |
|
663
|
1
|
|
33
|
|
|
139
|
while ( !$count_arg || $count > 0 ) { |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# attempt reap child processes on every loop |
|
666
|
1
|
|
|
|
|
17
|
for ( my $i=0; $i<@pids; $i++ ) { |
|
667
|
0
|
|
|
|
|
0
|
my $pid = waitpid( $pids[$i], 0 ); |
|
668
|
0
|
0
|
|
|
|
0
|
if ( $pid < 1 ) { splice( @pids, $i, 1 ); } |
|
|
0
|
|
|
|
|
0
|
|
|
669
|
|
|
|
|
|
|
} |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
# get a client socket handle |
|
672
|
1
|
|
|
|
|
14
|
my $client = $self->client(); |
|
673
|
|
|
|
|
|
|
|
|
674
|
1
|
|
|
|
|
7
|
$count--; |
|
675
|
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# fork again so we can handle simultaneous connections |
|
677
|
1
|
|
|
|
|
9232
|
my $pid = fork(); |
|
678
|
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
# parent process goes back to top of loop |
|
680
|
1
|
50
|
|
|
|
56
|
if ( $pid ) { |
|
681
|
0
|
|
|
|
|
0
|
push( @pids, $pid ); |
|
682
|
0
|
|
|
|
|
0
|
next; |
|
683
|
|
|
|
|
|
|
} |
|
684
|
|
|
|
|
|
|
|
|
685
|
1
|
|
|
|
|
57
|
$self->handle_client( $client ); |
|
686
|
1
|
|
|
|
|
379
|
exit 0; |
|
687
|
|
|
|
|
|
|
} |
|
688
|
0
|
|
|
|
|
0
|
$wp->close(); |
|
689
|
0
|
|
|
|
|
0
|
exit 0; |
|
690
|
|
|
|
|
|
|
} |
|
691
|
|
|
|
|
|
|
} |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=back |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=head1 AUTHOR |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
Al Tobey |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
Some ideas from Sendpage::SNPPServer |
|
700
|
|
|
|
|
|
|
Kees Cook http://outflux.net/ |
|
701
|
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=head1 TODO |
|
703
|
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
Add more hooks for callbacks |
|
705
|
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
Implement the following level 2 and level 3 commands |
|
707
|
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
4.5.1 LOGIn [password] |
|
709
|
|
|
|
|
|
|
4.5.3 LEVEl |
|
710
|
|
|
|
|
|
|
4.5.5 COVErage |
|
711
|
|
|
|
|
|
|
4.5.7 CALLerid |
|
712
|
|
|
|
|
|
|
4.6.3 EXPTag |
|
713
|
|
|
|
|
|
|
4.6.5 ACKRead <0|1> |
|
714
|
|
|
|
|
|
|
4.6.6 RTYPe |
|
715
|
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
717
|
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
Net::Cmd Socket |
|
719
|
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
=cut |
|
721
|
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
1; |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# FIXME: update this from the RFC |
|
725
|
|
|
|
|
|
|
__DATA__ |