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__ |