19 | 19 |
# Taken from http://search.cpan.org/~flora/Net-SSLeay-1.36/lib/Net/SSLeay.pm#Low_level_API
|
20 | 20 |
Net::SSLeay::load_error_strings();
|
21 | 21 |
Net::SSLeay::SSLeay_add_ssl_algorithms();
|
22 | |
# TODO do we need this?
|
23 | |
#Net::SSLeay::ENGINE_load_builtin_engines(); # If you want built-in engines
|
24 | |
#Net::SSLeay::ENGINE_register_all_complete(); # If you want built-in engines
|
|
22 |
Net::SSLeay::ENGINE_load_builtin_engines();
|
|
23 |
Net::SSLeay::ENGINE_register_all_complete();
|
25 | 24 |
Net::SSLeay::randomize();
|
26 | 25 |
}
|
27 | 26 |
}
|
|
53 | 52 |
|
54 | 53 |
=func Client_SSLify
|
55 | 54 |
|
56 | |
Accepts a socket, returns a brand new socket SSLified. Optionally accepts SSL
|
57 | |
context data. Also accepts a subref to call when connection/negotiation is done.
|
58 | |
|
59 | |
my $socket = shift; # get the socket from somewhere
|
60 | |
$socket = Client_SSLify( $socket ); # the default
|
61 | |
$socket = Client_SSLify( $socket, $version, $options ); # sets more options for the context
|
62 | |
$socket = Client_SSLify( $socket, undef, undef, $ctx ); # pass in a custom context
|
63 | |
$socket = Client_SSLify( $socket, sub { warn "CONNECTED" } ); # call your connection function
|
64 | |
|
65 | |
If $ctx is defined, SSLify will ignore other args. If $ctx isn't defined, SSLify
|
66 | |
will create it from the $version + $options parameters.
|
67 | |
|
68 | |
Known versions:
|
69 | |
* sslv2
|
70 | |
* sslv3
|
71 | |
* tlsv1
|
72 | |
* default
|
73 | |
|
74 | |
By default we use the version: default
|
75 | |
|
76 | |
By default we don't set any options
|
|
55 |
This function sslifies a client-side socket. You can pass several options to it:
|
|
56 |
|
|
57 |
my $socket = shift;
|
|
58 |
$socket = Client_SSLify( $socket, $version, $options, $ctx, $callback );
|
|
59 |
$socket is the non-ssl socket you got from somewhere ( probably SocketFactory )
|
|
60 |
$version is the SSL version you want to use, see SSLify_ContextCreate
|
|
61 |
$options is the SSL options you want to use, see SSLify_ContextCreate
|
|
62 |
$ctx is the custom SSL context you want to use, see SSLify_ContextCreate
|
|
63 |
$callback is the callback hook on success/failure of sslification
|
|
64 |
|
|
65 |
sub callback {
|
|
66 |
my( $socket, $status, $errval ) = @_;
|
|
67 |
# $socket is the original sslified socket in case you need to play with it
|
|
68 |
# $status is either 'OK' or 'ERR'
|
|
69 |
# $errval will be defined if $status eq 'ERR' - it's the numeric SSL error code
|
|
70 |
}
|
|
71 |
|
|
72 |
If $ctx is defined, SSLify will ignore $version and $options. Otherwise, it will be created from the $version and
|
|
73 |
$options parameters. If all of them are undefined, it will follow the defaults in L</SSLify_ContextCreate>.
|
|
74 |
|
|
75 |
BEWARE: If you passed in a CTX, SSLify will do Net::SSLeay::CTX_free( $ctx ) when the
|
|
76 |
socket is destroyed. This means you cannot reuse contexts!
|
77 | 77 |
|
78 | 78 |
NOTE: The way to have a client socket with proper certificates set up is:
|
79 | 79 |
|
|
81 | 81 |
my $ctx = SSLify_ContextCreate( 'server.key', 'server.crt' );
|
82 | 82 |
$socket = Client_SSLify( $socket, undef, undef, $ctx );
|
83 | 83 |
|
84 | |
BEWARE: If you passed in a CTX, SSLify will do Net::SSLeay::CTX_free( $ctx ) when the
|
85 | |
socket is destroyed. This means you cannot reuse contexts!
|
86 | |
|
87 | |
NOTE: You can pass the subref anywhere in the arguments, we'll figure it out for you! If you want to call a POE event, please look
|
88 | |
into the postback/callback stuff in POE::Session. The subref will get the socket as the sole argument.
|
|
84 |
NOTE: You can pass the callback anywhere in the arguments, we'll figure it out for you! If you want to call a POE event, please look
|
|
85 |
into the postback/callback stuff in POE::Session.
|
89 | 86 |
|
90 | 87 |
$socket = Client_SSLify( $socket, $session->callback( 'got_connect' => @args ) );
|
|
88 |
|
91 | 89 |
=cut
|
92 | 90 |
|
93 | 91 |
sub Client_SSLify {
|
94 | |
# Get the socket + version + options + ctx
|
95 | |
my( $socket, $version, $options, $ctx, $connref ) = @_;
|
|
92 |
# Get the socket + version + options + ctx + callback
|
|
93 |
my( $socket, $version, $options, $ctx, $callback ) = @_;
|
96 | 94 |
|
97 | 95 |
# Validation...
|
98 | 96 |
if ( ! defined $socket ) {
|
99 | 97 |
die "Did not get a defined socket";
|
100 | 98 |
}
|
101 | 99 |
|
102 | |
# Mangle the connref stuff
|
|
100 |
# Mangle the callback stuff
|
103 | 101 |
if ( defined $version and ref $version and ref( $version ) eq 'CODE' ) {
|
104 | |
$connref = $version;
|
|
102 |
$callback = $version;
|
105 | 103 |
$version = $options = $ctx = undef;
|
106 | 104 |
} elsif ( defined $options and ref $options and ref( $options ) eq 'CODE' ) {
|
107 | |
$connref = $options;
|
|
105 |
$callback = $options;
|
108 | 106 |
$options = $ctx = undef;
|
109 | 107 |
} elsif ( defined $ctx and ref $ctx and ref( $ctx ) eq 'CODE' ) {
|
110 | |
$connref = $ctx;
|
|
108 |
$callback = $ctx;
|
111 | 109 |
$ctx = undef;
|
112 | 110 |
}
|
113 | 111 |
|
|
119 | 117 |
|
120 | 118 |
# Now, we create the new socket and bind it to our subclass of Net::SSLeay::Handle
|
121 | 119 |
my $newsock = gensym();
|
122 | |
tie( *$newsock, 'POE::Component::SSLify::ClientHandle', $socket, $version, $options, $ctx, $connref ) or die "Unable to tie to our subclass: $!";
|
123 | |
|
124 | |
# argh, store the newsock in the tied class to use for connref
|
125 | |
if ( defined $connref ) {
|
|
120 |
tie( *$newsock, 'POE::Component::SSLify::ClientHandle', $socket, $version, $options, $ctx, $callback ) or die "Unable to tie to our subclass: $!";
|
|
121 |
|
|
122 |
# argh, store the newsock in the tied class to use for callback
|
|
123 |
if ( defined $callback ) {
|
126 | 124 |
tied( *$newsock )->{'orig_socket'} = $newsock;
|
127 | 125 |
weaken( tied( *$newsock )->{'orig_socket'} );
|
128 | 126 |
}
|
|
133 | 131 |
|
134 | 132 |
=func Server_SSLify
|
135 | 133 |
|
136 | |
Accepts a socket, returns a brand new socket SSLified. Also accepts a custom context. Also accepts a subref
|
137 | |
to call when connection/negotiation is done.
|
138 | |
|
139 | |
my $socket = shift; # get the socket from somewhere
|
140 | |
$socket = Server_SSLify( $socket ); # the default
|
141 | |
$socket = Server_SSLify( $socket, $ctx ); # use your custom context
|
142 | |
$socket = Server_SSLify( $socket, sub { warn "CONNECTED" } ); # call your connection function
|
143 | |
|
144 | |
NOTE: SSLify_Options must be set first!
|
145 | |
|
146 | |
Furthermore, you can pass in your own $ctx object if you desire. This allows you to set custom parameters
|
147 | |
per-connection, for example.
|
|
134 |
This function sslifies a server-side socket. You can pass several options to it:
|
|
135 |
|
|
136 |
my $socket = shift;
|
|
137 |
$socket = Server_SSLify( $socket, $ctx, $callback );
|
|
138 |
$socket is the non-ssl socket you got from somewhere ( probably SocketFactory )
|
|
139 |
$ctx is the custom SSL context you want to use, see SSLify_ContextCreate ( overrides the global set in SSLify_Options )
|
|
140 |
$callback is the callback hook on success/failure of sslification
|
|
141 |
|
|
142 |
sub callback {
|
|
143 |
my( $socket, $status, $errval ) = @_;
|
|
144 |
# $socket is the original sslified socket in case you need to play with it
|
|
145 |
# $status is either 'OK' or 'ERR'
|
|
146 |
# $errval will be defined if $status eq 'ERR' - it's the numeric SSL error code
|
|
147 |
}
|
|
148 |
|
|
149 |
NOTE: SSLify_Options must be set first if you aren't passing a $ctx. If you want to set some options per-connection, do this:
|
148 | 150 |
|
149 | 151 |
my $socket = shift; # get the socket from somewhere
|
150 | 152 |
my $ctx = SSLify_ContextCreate();
|
|
154 | 156 |
NOTE: You can use SSLify_GetCTX to modify the global, and avoid doing this on every connection if the
|
155 | 157 |
options are the same...
|
156 | 158 |
|
157 | |
NOTE: You can pass the subref anywhere in the arguments, we'll figure it out for you! If you want to call a POE event, please look
|
158 | |
into the postback/callback stuff in POE::Session. The subref will get the socket as the sole argument.
|
|
159 |
NOTE: You can pass the callback anywhere in the arguments, we'll figure it out for you! If you want to call a POE event, please look
|
|
160 |
into the postback/callback stuff in POE::Session.
|
159 | 161 |
|
160 | 162 |
$socket = Server_SSLify( $socket, $session->callback( 'got_connect' => @args ) );
|
161 | 163 |
=cut
|
162 | 164 |
|
163 | 165 |
sub Server_SSLify {
|
164 | 166 |
# Get the socket!
|
165 | |
my( $socket, $custom_ctx, $connref ) = @_;
|
|
167 |
my( $socket, $custom_ctx, $callback ) = @_;
|
166 | 168 |
|
167 | 169 |
# Validation...
|
168 | 170 |
if ( ! defined $socket ) {
|
|
174 | 176 |
die 'Please do SSLify_Options() first ( or pass in a $ctx object )';
|
175 | 177 |
}
|
176 | 178 |
|
177 | |
# mangle custom_ctx depending on connref
|
|
179 |
# mangle custom_ctx depending on callback
|
178 | 180 |
if ( defined $custom_ctx and ref $custom_ctx and ref( $custom_ctx ) eq 'CODE' ) {
|
179 | |
$connref = $custom_ctx;
|
|
181 |
$callback = $custom_ctx;
|
180 | 182 |
$custom_ctx = undef;
|
181 | 183 |
}
|
182 | 184 |
|
|
188 | 190 |
|
189 | 191 |
# Now, we create the new socket and bind it to our subclass of Net::SSLeay::Handle
|
190 | 192 |
my $newsock = gensym();
|
191 | |
tie( *$newsock, 'POE::Component::SSLify::ServerHandle', $socket, ( $custom_ctx || $ctx ), $connref ) or die "Unable to tie to our subclass: $!";
|
|
193 |
tie( *$newsock, 'POE::Component::SSLify::ServerHandle', $socket, ( $custom_ctx || $ctx ), $callback ) or die "Unable to tie to our subclass: $!";
|
192 | 194 |
|
193 | 195 |
# argh, store the newsock in the tied class to use for connref
|
194 | |
if ( defined $connref ) {
|
|
196 |
if ( defined $callback ) {
|
195 | 197 |
tied( *$newsock )->{'orig_socket'} = $newsock;
|
196 | 198 |
weaken( tied( *$newsock )->{'orig_socket'} );
|
197 | 199 |
}
|
|
202 | 204 |
|
203 | 205 |
=func SSLify_ContextCreate
|
204 | 206 |
|
205 | |
Accepts some options, and returns a brand-new Net::SSLeay context object ( $ctx )
|
206 | |
my $ctx = SSLify_ContextCreate( $key, $cert, $version, $options );
|
207 | |
|
208 | |
You can then call various Net::SSLeay methods on the context
|
209 | |
my $mode = Net::SSLeay::CTX_get_mode( $ctx );
|
210 | |
|
211 | |
By default we don't use the SSL key + certificate files
|
212 | |
|
213 | |
By default we use the version: default
|
214 | |
|
215 | |
Known versions:
|
216 | |
* sslv2
|
217 | |
* sslv3
|
218 | |
* tlsv1
|
219 | |
* default
|
220 | |
|
221 | |
By default we don't set any options
|
|
207 |
Accepts some options, and returns a brand-new Net::SSLeay context object ( $ctx )
|
|
208 |
|
|
209 |
my $ctx = SSLify_ContextCreate( $key, $cert, $version, $options );
|
|
210 |
$key is the certificate key file
|
|
211 |
$cert is the certificate file
|
|
212 |
$version is the SSL version to use
|
|
213 |
$options is the SSL options to use
|
|
214 |
|
|
215 |
You can then call various Net::SSLeay methods on the context
|
|
216 |
|
|
217 |
my $mode = Net::SSLeay::CTX_get_mode( $ctx );
|
|
218 |
|
|
219 |
By default we don't use the SSL key + certificate files
|
|
220 |
|
|
221 |
By default we use the version: default. Known versions of the SSL connection - look at
|
|
222 |
L<http://www.openssl.org/docs/ssl/SSL_CTX_new.html> for more info.
|
|
223 |
|
|
224 |
* sslv2
|
|
225 |
* sslv3
|
|
226 |
* tlsv1
|
|
227 |
* sslv23
|
|
228 |
* default ( sslv23 )
|
|
229 |
|
|
230 |
By default we don't set any options - look at L<http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html> for more info.
|
222 | 231 |
=cut
|
223 | 232 |
|
224 | 233 |
sub SSLify_ContextCreate {
|
|
230 | 239 |
|
231 | 240 |
=func SSLify_Options
|
232 | 241 |
|
233 | |
Call this function to initialize the global server-side CTX. Accepts the location of the
|
234 | |
SSL key + certificate files, which is required.
|
235 | |
|
236 | |
Optionally accepts the SSL version + CTX options
|
237 | |
SSLify_Options( $key, $cert, $version, $options );
|
238 | |
|
239 | |
By default we use the version: default
|
240 | |
|
241 | |
Known versions:
|
242 | |
* sslv2
|
243 | |
* sslv3
|
244 | |
* tlsv1
|
245 | |
* default
|
246 | |
|
247 | |
By default we use the options: &Net::SSLeay::OP_ALL
|
|
242 |
Call this function to initialize the global server-side context object. This will be the default context whenever you call
|
|
243 |
L</Server_SSLify> without passing a custom context to it.
|
|
244 |
|
|
245 |
SSLify_Options( $key, $cert, $version, $options );
|
|
246 |
$key is the certificate key file ( required )
|
|
247 |
$cert is the certificate file ( required )
|
|
248 |
$version is the SSL version to use
|
|
249 |
$options is the SSL options to use
|
|
250 |
|
|
251 |
By default we use the version: default
|
|
252 |
|
|
253 |
By default we use the options: &Net::SSLeay::OP_ALL
|
|
254 |
|
|
255 |
Please look at L</SSLify_ContextCreate> for more info on the available versions/options.
|
248 | 256 |
=cut
|
249 | 257 |
|
250 | 258 |
sub SSLify_Options {
|
|
283 | 291 |
$context = Net::SSLeay::CTX_v3_new();
|
284 | 292 |
} elsif ( $version eq 'tlsv1' ) {
|
285 | 293 |
$context = Net::SSLeay::CTX_tlsv1_new();
|
|
294 |
} elsif ( $version eq 'sslv23' ) {
|
|
295 |
$context = Net::SSLeay::CTX_v23_new();
|
286 | 296 |
} elsif ( $version eq 'default' ) {
|
287 | 297 |
$context = Net::SSLeay::CTX_new();
|
288 | 298 |
} else {
|
|
321 | 331 |
|
322 | 332 |
=func SSLify_GetCTX
|
323 | 333 |
|
324 | |
Returns the actual Net::SSLeay context object in case you wanted to play with it :)
|
325 | |
|
326 | |
If passed in a socket, it will return that socket's $ctx instead of the global.
|
327 | |
my $ctx = SSLify_GetCTX(); # get the one set via SSLify_Options
|
328 | |
my $ctx = SSLify_GetCTX( $sslified_sock ); # get the one in the object
|
|
334 |
Returns the actual Net::SSLeay context object in case you wanted to play with it :)
|
|
335 |
|
|
336 |
If passed in a socket, it will return that socket's $ctx instead of the global.
|
|
337 |
|
|
338 |
my $ctx = SSLify_GetCTX(); # get the one set via SSLify_Options
|
|
339 |
my $ctx = SSLify_GetCTX( $sslified_sock ); # get the one in the object
|
329 | 340 |
=cut
|
330 | 341 |
|
331 | 342 |
sub SSLify_GetCTX {
|
|
339 | 350 |
|
340 | 351 |
=func SSLify_GetCipher
|
341 | 352 |
|
342 | |
Returns the cipher used by the SSLified socket
|
343 | |
|
344 | |
Example:
|
345 | |
print "SSL Cipher is: " . SSLify_GetCipher( $sslified_sock ) . "\n";
|
346 | |
|
347 | |
NOTE: Doing this immediately after Client_SSLify or Server_SSLify will result in "(NONE)" because the SSL handshake
|
348 | |
is not done yet. The socket is nonblocking, so you will have to wait a little bit for it to get ready.
|
349 | |
apoc@blackhole:~/mygit/perl-poe-sslify/examples$ perl serverclient.pl
|
350 | |
got connection from: 127.0.0.1 - commencing Server_SSLify()
|
351 | |
SSLified: 127.0.0.1 cipher type: ((NONE))
|
352 | |
Connected to server, commencing Client_SSLify()
|
353 | |
SSLified the connection to the server
|
354 | |
Connected to SSL server
|
355 | |
Input: hola
|
356 | |
got input from: 127.0.0.1 cipher type: (AES256-SHA) input: 'hola'
|
357 | |
Got Reply: hola
|
358 | |
Input: ^C
|
359 | |
stopped at serverclient.pl line 126.
|
|
353 |
Returns the cipher used by the SSLified socket
|
|
354 |
|
|
355 |
print "SSL Cipher is: " . SSLify_GetCipher( $sslified_sock ) . "\n";
|
|
356 |
|
|
357 |
NOTE: Doing this immediately after Client_SSLify or Server_SSLify will result in "(NONE)" because the SSL handshake
|
|
358 |
is not done yet. The socket is nonblocking, so you will have to wait a little bit for it to get ready.
|
|
359 |
|
|
360 |
apoc@blackhole:~/mygit/perl-poe-sslify/examples$ perl serverclient.pl
|
|
361 |
got connection from: 127.0.0.1 - commencing Server_SSLify()
|
|
362 |
SSLified: 127.0.0.1 cipher type: ((NONE))
|
|
363 |
Connected to server, commencing Client_SSLify()
|
|
364 |
SSLified the connection to the server
|
|
365 |
Connected to SSL server
|
|
366 |
Input: hola
|
|
367 |
got input from: 127.0.0.1 cipher type: (AES256-SHA) input: 'hola'
|
|
368 |
Got Reply: hola
|
|
369 |
Input: ^C
|
|
370 |
stopped at serverclient.pl line 126.
|
360 | 371 |
=cut
|
361 | 372 |
|
362 | 373 |
sub SSLify_GetCipher {
|
|
366 | 377 |
|
367 | 378 |
=func SSLify_GetSocket
|
368 | 379 |
|
369 | |
Returns the actual socket used by the SSLified socket, useful for stuff like getpeername()/getsockname()
|
370 | |
|
371 | |
Example:
|
372 | |
print "Remote IP is: " . inet_ntoa( ( unpack_sockaddr_in( getpeername( SSLify_GetSocket( $sslified_sock ) ) ) )[1] ) . "\n";
|
|
380 |
Returns the actual socket used by the SSLified socket, useful for stuff like getpeername()/getsockname()
|
|
381 |
|
|
382 |
print "Remote IP is: " . inet_ntoa( ( unpack_sockaddr_in( getpeername( SSLify_GetSocket( $sslified_sock ) ) ) )[1] ) . "\n";
|
373 | 383 |
=cut
|
374 | 384 |
|
375 | 385 |
sub SSLify_GetSocket {
|
|
379 | 389 |
|
380 | 390 |
=func SSLify_GetSSL
|
381 | 391 |
|
382 | |
Returns the actual Net::SSLeay object so you can call methods on it
|
383 | |
|
384 | |
Example:
|
385 | |
print Net::SSLeay::dump_peer_certificate( SSLify_GetSSL( $sslified_sock ) );
|
|
392 |
Returns the actual Net::SSLeay object so you can call methods on it
|
|
393 |
|
|
394 |
print Net::SSLeay::dump_peer_certificate( SSLify_GetSSL( $sslified_sock ) );
|
386 | 395 |
=cut
|
387 | 396 |
|
388 | 397 |
sub SSLify_GetSSL {
|
|
392 | 401 |
|
393 | 402 |
=func SSLify_GetStatus
|
394 | 403 |
|
395 | |
Returns the status of the SSL negotiation/handshake/connection.
|
396 | |
|
397 | |
-1 == still in negotiation stage
|
398 | |
0 == internal SSL error, connection will be dead
|
399 | |
1 == negotiation successful
|
|
404 |
Returns the status of the SSL negotiation/handshake/connection.
|
|
405 |
|
|
406 |
my $status = SSLify_GetStatus( $socket );
|
|
407 |
-1 = still in negotiation stage
|
|
408 |
0 = internal SSL error, connection will be dead
|
|
409 |
1 = negotiation successful
|
400 | 410 |
=cut
|
401 | 411 |
|
402 | 412 |
sub SSLify_GetStatus {
|
|
473 | 483 |
|
474 | 484 |
=head2 Socket methods doesn't work
|
475 | 485 |
|
476 | |
The new socket this module gives you actually is some tied socket magic, so you cannot do stuff like
|
|
486 |
The new socket this module gives you actually is tied socket magic, so you cannot do stuff like
|
477 | 487 |
getpeername() or getsockname(). The only way to do it is to use L</SSLify_GetSocket> and then operate on
|
478 | 488 |
the socket it returns.
|
479 | 489 |
|
|
520 | 530 |
L<http://security.freebsd.org/advisories/FreeBSD-SA-09:15.ssl.asc> which explains it in detail. The test will skip this function
|
521 | 531 |
if it detects that you're on a broken system. However, if you have the updated OpenSSL library that fixes this you can use it.
|
522 | 532 |
|
523 | |
NOTE: Calling this means the connection function you passed in L</Client_SSLify> or L</Server_SSLify> will not fire! If you need this
|
|
533 |
NOTE: Calling this means the callback function you passed in L</Client_SSLify> or L</Server_SSLify> will not fire! If you need this
|
524 | 534 |
please let me know and we can come up with a way to make it work.
|
525 | 535 |
|
526 | |
=head2 In-Situ sslification
|
|
536 |
=head2 Upgrading a non-ssl socket to SSL
|
527 | 537 |
|
528 | 538 |
You can have a normal plaintext socket, and convert it to SSL anytime. Just keep in mind that the client and the server must agree to sslify
|
529 | |
at the same time, or they will be waiting on each other forever! See C<t/3_insitu.t> for an example of how this works.
|
|
539 |
at the same time, or they will be waiting on each other forever! See C<t/3_upgrade.t> for an example of how this works.
|
|
540 |
|
|
541 |
=head2 Downgrading a SSL socket to non-ssl
|
|
542 |
|
|
543 |
As of now this is unsupported. If you need this feature please let us know and we'll work on it together!
|
530 | 544 |
|
531 | 545 |
=head2 MSWin32 is not supported
|
532 | 546 |
|
|
535 | 549 |
|
536 | 550 |
=head1 EXPORT
|
537 | 551 |
|
538 | |
Stuffs all of the above functions in @EXPORT_OK so you have to request them directly
|
|
552 |
Stuffs all of the functions in @EXPORT_OK so you have to request them directly.
|
539 | 553 |
|
540 | 554 |
=head1 SEE ALSO
|
541 | 555 |
POE
|
|
555 | 569 |
ASCENT also helped a lot with the nonblocking mode, without his hard work this
|
556 | 570 |
module would still be stuck in the stone age :)
|
557 | 571 |
|
558 | |
=cut
|
|
572 |
A lot of people helped add various features/functions - please look at the changelog for more detail.
|
|
573 |
|
|
574 |
=cut
|