File Coverage

blib/lib/Net/Daemon.pm
Criterion Covered Total %
statement 33 282 11.7
branch 0 166 0.0
condition 0 60 0.0
subroutine 11 30 36.6
pod 0 16 0.0
total 44 554 7.9


line stmt bran cond sub pod time code
1             ############################################################################
2             #
3             # Net::Daemon - Base class for implementing TCP/IP daemons
4             #
5             # Copyright (C) 1998, Jochen Wiedmann
6             # Am Eisteich 9
7             # 72555 Metzingen
8             # Germany
9             #
10             # Phone: +49 7123 14887
11             # Email: joe@ispsoft.de
12             #
13             # All rights reserved.
14             #
15             # You may distribute this package under the terms of either the GNU
16             # General Public License or the Artistic License, as specified in the
17             # Perl README file.
18             #
19             ############################################################################
20              
21             package Net::Daemon;
22              
23 33     33   953 use strict;
  33         53  
  33         879  
24 33     33   149 use warnings;
  33         40  
  33         692  
25              
26 33     33   150 use Config;
  33         37  
  33         943  
27              
28 33     33   22524 use Getopt::Long ();
  33         407568  
  33         1105  
29 33     33   774 use Symbol ();
  33         759  
  33         428  
30 33     33   556 use IO::Socket ();
  33         16877  
  33         413  
31 33     33   12853 use Net::Daemon::Log ();
  33         74  
  33         815  
32 33     33   14938 use POSIX ();
  33         202816  
  33         1638  
33 33     33   238 use File::Spec ();
  33         60  
  33         31737  
34              
35             our $VERSION = '0.49';
36             our @ISA = qw(Net::Daemon::Log);
37              
38             our $RegExpLock = 1;
39              
40             # Dummy share() in case we're >= 5.10. If we are, require/import of
41             # threads::shared will replace it appropriately.
42             # But ONLY if we are built with threads and forks has not already been loaded.
43             my $use_ithreads = ( $^V ge v5.10.0 && $Config{'useithreads'} && !$INC{'forks.pm'} ) ? 1 : 0;
44             if ($use_ithreads) {
45             eval { require threads; };
46             eval { require threads::shared; };
47             threads::shared::share( $RegExpLock ) if $forks::threads; # Assuming this isn't threads masquerading as forks.
48             }
49              
50             our $exit;
51              
52             ############################################################################
53             #
54             # Name: Options (Class method)
55             #
56             # Purpose: Returns a hash ref of command line options
57             #
58             # Inputs: $class - This class
59             #
60             # Result: Options array; any option is represented by a hash ref;
61             # used keys are 'template', a string suitable for describing
62             # the option to Getopt::Long::GetOptions and 'description',
63             # a string for the Usage message
64             #
65             ############################################################################
66              
67             sub Options ($) {
68             {
69 0     0 0   'catchint' => {
70             'template' => 'catchint!',
71             'description' => '--nocatchint ' . "Try to catch interrupts when calling system\n" . ' ' . 'functions like bind(), recv()), ...'
72             },
73             'childs' => {
74             'template' => 'childs=i',
75             'description' => '--childs ' . 'Set number of preforked childs, implies mode=single.'
76             },
77             'chroot' => {
78             'template' => 'chroot=s',
79             'description' => '--chroot ' . 'Change rootdir to given after binding to port.'
80             },
81             'configfile' => {
82             'template' => 'configfile=s',
83             'description' => '--configfile ' . 'Read options from config file .'
84             },
85             'debug' => {
86             'template' => 'debug',
87             'description' => '--debug ' . 'Turn debugging mode on'
88             },
89             'facility' => {
90             'template' => 'facility=s',
91             'description' => '--facility ' . 'Syslog facility; defaults to \'daemon\''
92             },
93             'group' => {
94             'template' => 'group=s',
95             'description' => '--group ' . 'Change gid to given group after binding to port.'
96             },
97             'help' => {
98             'template' => 'help',
99             'description' => '--help ' . 'Print this help message'
100             },
101             'localaddr' => {
102             'template' => 'localaddr=s',
103             'description' => '--localaddr ' . 'IP number to bind to; defaults to INADDR_ANY'
104             },
105             'localpath' => {
106             'template' => 'localpath=s',
107             'description' => '--localpath ' . 'UNIX socket domain path to bind to'
108             },
109             'localport' => {
110             'template' => 'localport=s',
111             'description' => '--localport ' . 'Port number to bind to'
112             },
113             'logfile' => {
114             'template' => 'logfile=s',
115             'description' => '--logfile ' . 'Force logging to '
116             },
117             'loop-child' => {
118             'template' => 'loop-child',
119             'description' => '--loop-child ' . 'Create a child process for loops'
120             },
121             'loop-timeout' => {
122             'template' => 'loop-timeout=f',
123             'description' => '--loop-timeout ' . 'Looping mode, seconds per loop'
124             },
125             'mode' => {
126             'template' => 'mode=s',
127             'description' => '--mode ' . 'Operation mode (threads, fork or single)'
128             },
129             'pidfile' => {
130             'template' => 'pidfile=s',
131             'description' => '--pidfile ' . 'Use as PID file'
132             },
133             'proto' => {
134             'template' => 'proto=s',
135             'description' => '--proto ' . 'transport layer protocol: tcp (default) or unix'
136             },
137             'user' => {
138             'template' => 'user=s',
139             'description' => '--user ' . 'Change uid to given user after binding to port.'
140             },
141             'version' => {
142             'template' => 'version',
143             'description' => '--version ' . 'Print version number and exit'
144             }
145             }
146             }
147              
148             ############################################################################
149             #
150             # Name: Version (Class method)
151             #
152             # Purpose: Returns version string
153             #
154             # Inputs: $class - This class
155             #
156             # Result: Version string; suitable for printed by "--version"
157             #
158             ############################################################################
159              
160             sub Version ($) {
161 0     0 0   "Net::Daemon server, Copyright (C) 1998, Jochen Wiedmann";
162             }
163              
164             ############################################################################
165             #
166             # Name: Usage (Class method)
167             #
168             # Purpose: Prints usage message
169             #
170             # Inputs: $class - This class
171             #
172             # Result: Nothing; aborts with error status
173             #
174             ############################################################################
175              
176             sub Usage ($) {
177 0     0 0   my ($class) = shift;
178 0           my ($options) = $class->Options();
179 0           my (@options) = sort ( keys %$options );
180              
181 0           print STDERR "Usage: $0 \n\nPossible options are:\n\n";
182 0           my ($key);
183 0           foreach $key ( sort ( keys %$options ) ) {
184 0           my ($o) = $options->{$key};
185 0 0         print STDERR " ", $o->{'description'}, "\n" if $o->{'description'};
186             }
187 0           print STDERR "\n", $class->Version(), "\n";
188 0           exit(1);
189             }
190              
191             ############################################################################
192             #
193             # Name: ReadConfigFile (Instance method)
194             #
195             # Purpose: Reads the config file.
196             #
197             # Inputs: $self - Instance
198             # $file - config file name
199             # $options - Hash of command line options; these are not
200             # really for being processed by this method. We pass
201             # it just in case. The new() method will process them
202             # at a later time.
203             # $args - Array ref of other command line options.
204             #
205             ############################################################################
206              
207             sub ReadConfigFile {
208 0     0 0   my ( $self, $file, $options, $args ) = @_;
209 0 0         if ( !-f $file ) {
210 0           $self->Fatal("No such config file: $file");
211             }
212 0           my $copts = do File::Spec->rel2abs($file);
213 0 0         if ($@) {
214 0           $self->Fatal("Error while processing config file $file: $@");
215             }
216 0 0 0       if ( !$copts || ref($copts) ne 'HASH' ) {
217 0           $self->Fatal("Config file $file did not return a hash ref.");
218             }
219              
220             # Override current configuration with config file options.
221 0           while ( my ( $var, $val ) = each %$copts ) {
222 0           $self->{$var} = $val;
223             }
224             }
225              
226             ############################################################################
227             #
228             # Name: new (Class method)
229             #
230             # Purpose: Constructor
231             #
232             # Inputs: $class - This class
233             # $attr - Hash ref of attributes
234             # $args - Array ref of command line arguments
235             #
236             # Result: Server object for success, error message otherwise
237             #
238             ############################################################################
239              
240             sub new ($$;$) {
241 0     0 0   my ( $class, $attr, $args ) = @_;
242 0 0         my ($self) = $attr ? \%$attr : {};
243 0   0       bless( $self, ( ref($class) || $class ) );
244              
245 0   0       my $options = ( $self->{'options'} ||= {} );
246 0   0       $self->{'args'} ||= [];
247 0 0         if ($args) {
248 0           my @optList = map { $_->{'template'} } values( %{ $class->Options() } );
  0            
  0            
249              
250 0           local @ARGV = @$args;
251 0 0         if ( !Getopt::Long::GetOptions( $options, @optList ) ) {
252 0           $self->Usage();
253             }
254 0           @{ $self->{'args'} } = @ARGV;
  0            
255              
256 0 0         if ( $options->{'help'} ) {
257 0           $self->Usage();
258             }
259 0 0         if ( $options->{'version'} ) {
260 0           print STDERR $self->Version(), "\n";
261 0           exit 1;
262             }
263             }
264              
265 0   0       my $file = $options->{'configfile'} || $self->{'configfile'};
266 0 0         if ($file) {
267 0           $self->ReadConfigFile( $file, $options, $args );
268             }
269 0           while ( my ( $var, $val ) = each %$options ) {
270 0           $self->{$var} = $val;
271             }
272              
273 0 0         if ( $self->{'childs'} ) {
    0          
274 0           $self->{'mode'} = 'single';
275             }
276             elsif ( !defined( $self->{'mode'} ) ) {
277 0 0         if ( eval { require threads } ) {
  0            
278 0           $self->{'mode'} = 'ithreads';
279             }
280             else {
281 0           my $fork = 0;
282 0 0         if ( $^O ne "MSWin32" ) {
283 0           my $pid = eval { fork() };
  0            
284 0 0         if ( defined($pid) ) {
285 0 0         if ( !$pid ) { exit; } # Child
  0            
286 0           $fork = 1;
287 0           wait;
288             }
289             }
290 0 0         if ($fork) {
291 0           $self->{'mode'} = 'fork';
292             }
293             else {
294 0           $self->{'mode'} = 'single';
295             }
296             }
297             }
298              
299 0 0         if ( $self->{'mode'} eq 'ithreads' ) {
  0 0          
    0          
300 33     33   263 no warnings 'redefine';
  33         67  
  33         1853  
301 0           require threads;
302 33     33   178 use warnings 'redefine';
  33         101  
  33         76509  
303             }
304             elsif ( $self->{'mode'} eq 'fork' ) {
305              
306             # Initialize forking mode ...
307             }
308             elsif ( $self->{'mode'} eq 'single' ) {
309              
310             # Initialize single mode ...
311             }
312             else {
313 0           $self->Fatal("Unknown operation mode: $self->{'mode'}");
314             }
315 0 0         $self->{'catchint'} = 1 unless exists( $self->{'catchint'} );
316 0           $self->Debug("Server starting in operation mode $self->{'mode'}");
317 0 0         if ( $self->{'childs'} ) {
318 0           $self->Debug("Preforking $self->{'childs'} child processes ...");
319             }
320              
321 0           $self;
322             }
323              
324             sub Clone ($$) {
325 0     0 0   my ( $proto, $client ) = @_;
326 0           my $self = {%$proto};
327 0           $self->{'socket'} = $client;
328 0           $self->{'parent'} = $proto;
329 0           bless( $self, ref($proto) );
330 0           $self;
331             }
332              
333             ############################################################################
334             #
335             # Name: Accept (Instance method)
336             #
337             # Purpose: Called for authentication purposes
338             #
339             # Inputs: $self - Server instance
340             #
341             # Result: TRUE, if the client has successfully authorized, FALSE
342             # otherwise.
343             #
344             ############################################################################
345              
346             sub Accept ($) {
347 0     0 0   my $self = shift;
348 0           my $socket = $self->{'socket'};
349 0           my $clients = $self->{'clients'};
350 0 0         my $from = $self->{'proto'} eq 'unix' ? "Unix socket" : sprintf(
351             "%s, port %s",
352             $socket->peerhost(), $socket->peerport()
353             );
354              
355             # Host based authorization
356 0 0         if ( $self->{'clients'} ) {
357 0           my ( $name, $aliases, $addrtype, $length, @addrs );
358 0 0         if ( $self->{'proto'} eq 'unix' ) {
359 0           ( $name, $aliases, $addrtype, $length, @addrs ) = (
360             'localhost', '', Socket::AF_INET(),
361             length( Socket::IN_ADDR_ANY() ),
362             Socket::inet_aton('127.0.0.1')
363             );
364             }
365             else {
366 0           ( $name, $aliases, $addrtype, $length, @addrs ) =
367             gethostbyaddr( $socket->peeraddr(), Socket::AF_INET() );
368             }
369             my @patterns =
370             @addrs
371 0 0         ? map { Socket::inet_ntoa($_) } @addrs
  0            
372             : $socket->peerhost();
373 0 0         push( @patterns, $name ) if ($name);
374 0 0         push( @patterns, split( / /, $aliases ) ) if $aliases;
375              
376 0           my $found;
377 0           OUTER: foreach my $client (@$clients) {
378 0 0         if ( !$client->{'mask'} ) {
379 0           $found = $client;
380 0           last;
381             }
382 0 0         my $masks = ref( $client->{'mask'} ) ? $client->{'mask'} : [ $client->{'mask'} ];
383              
384             #
385             # Regular expressions aren't thread safe, as of
386             # 5.00502 :-(
387             #
388 0           my $lock;
389             $lock = lock($RegExpLock)
390 0 0         if ( $self->{'mode'} eq 'threads' );
391 0           foreach my $mask (@$masks) {
392 0           foreach my $alias (@patterns) {
393 0 0         if ( $alias =~ /$mask/ ) {
394 0           $found = $client;
395 0           last OUTER;
396             }
397             }
398             }
399             }
400              
401 0 0 0       if ( !$found || !$found->{'accept'} ) {
402 0           $self->Error("Access not permitted from $from");
403 0           return 0;
404             }
405 0           $self->{'client'} = $found;
406             }
407              
408 0           $self->Debug("Accepting client from $from");
409 0           1;
410             }
411              
412             ############################################################################
413             #
414             # Name: Run (Instance method)
415             #
416             # Purpose: Does the real work
417             #
418             # Inputs: $self - Server instance
419             #
420             # Result: Nothing; returning will make the connection to be closed
421             #
422             ############################################################################
423              
424       0 0   sub Run ($) {
425             }
426              
427             ############################################################################
428             #
429             # Name: Done (Instance method)
430             #
431             # Purpose: Called by the server before doing an accept(); a TRUE
432             # value makes the server terminate.
433             #
434             # Inputs: $self - Server instance
435             #
436             # Result: TRUE or FALSE
437             #
438             # Bugs: Doesn't work in forking mode.
439             #
440             ############################################################################
441              
442             sub Done ($;$) {
443 0     0 0   my $self = shift;
444 0 0         $self->{'done'} = shift if @_;
445 0           $self->{'done'};
446             }
447              
448             ############################################################################
449             #
450             # Name: Loop (Instance method)
451             #
452             # Purpose: If $self->{'loop-timeout'} option is set, then this method
453             # will be called every "loop-timeout" seconds.
454             #
455             # Inputs: $self - Server instance
456             #
457             # Result: Nothing; aborts in case of trouble. Note, that this is *not*
458             # trapped and forces the server to exit.
459             #
460             ############################################################################
461              
462       0 0   sub Loop {
463             }
464              
465             ############################################################################
466             #
467             # Name: ChildFunc (Instance method)
468             #
469             # Purpose: If possible, spawn a child process which calls a given
470             # method. In server mode single the method is called
471             # directly.
472             #
473             # Inputs: $self - Instance
474             # $method - Method name
475             # @args - Method arguments
476             #
477             # Returns: Nothing; aborts in case of problems.
478             #
479             ############################################################################
480              
481             sub ChildFunc {
482 0     0 0   my ( $self, $method, @args ) = @_;
483 0 0         if ( $self->{'mode'} eq 'single' ) {
    0          
484 0           $self->$method(@args);
485             }
486             elsif ( $self->{'mode'} eq 'ithreads' ) {
487             my $startfunc = sub {
488 0     0     my $self = shift;
489 0           my $method = shift;
490 0           $self->$method(@_);
491 0           };
492 0 0         threads->new( $startfunc, $self, $method, @args )
493             or die "Failed to create a new thread: $!";
494             }
495             else {
496 0           my $pid = fork();
497 0 0         die "Cannot fork: $!" unless defined $pid;
498 0 0         return if $pid; # Parent
499 0           $self->$method(@args); # Child
500 0           exit(0);
501             }
502             }
503              
504             ############################################################################
505             #
506             # Name: Bind (Instance method)
507             #
508             # Purpose: Binds to a port; if successfull, it never returns. Instead
509             # it accepts connections. For any connection a new thread is
510             # created and the Accept method is executed.
511             #
512             # Inputs: $self - Server instance
513             #
514             # Result: Error message in case of failure
515             #
516             ############################################################################
517              
518             sub HandleChild {
519 0     0 0   my $self = shift;
520 0           $self->Debug("New child starting ($self).");
521 0           eval {
522 0 0         if ( !$self->Accept() ) {
523 0           $self->Error('Refusing client');
524             }
525             else {
526 0           $self->Debug('Accepting client');
527 0           $self->Run();
528             }
529             };
530 0 0         $self->Error("Child died: $@") if $@;
531 0           $self->Debug("Child terminating.");
532 0           $self->Close();
533             }
534              
535             sub SigChildHandler {
536 0     0 0   my $self = shift;
537 0           my $ref = shift;
538 0 0 0       return 'IGNORE' if $self->{'mode'} eq 'fork' || $self->{'childs'};
539 0           return undef; # Don't care for childs.
540             }
541              
542             sub Bind ($) {
543 0     0 0   my $self = shift;
544 0           my $fh;
545             my $child_pid;
546              
547 0           my $reaper = $self->SigChildHandler( \$child_pid );
548 0 0         $SIG{'CHLD'} = $reaper if $reaper;
549              
550 0 0         if ( !$self->{'socket'} ) {
551 0 0 0       $self->{'proto'} ||= ( $self->{'localpath'} ) ? 'unix' : 'tcp';
552              
553 0 0         if ( $self->{'proto'} eq 'unix' ) {
554 0 0         my $path = $self->{'localpath'}
555             or $self->Fatal('Missing option: localpath');
556 0           unlink $path;
557 0 0         $self->Fatal("Can't remove stale Unix socket ($path): $!")
558             if -e $path;
559 0           my $old_umask = umask 0;
560             $self->{'socket'} = IO::Socket::UNIX->new(
561             'Local' => $path,
562 0 0 0       'Listen' => $self->{'listen'} || 10
563             ) or $self->Fatal("Cannot create Unix socket $path: $!");
564 0           umask $old_umask;
565             }
566             else {
567             $self->{'socket'} = IO::Socket::INET->new(
568             'LocalAddr' => $self->{'localaddr'},
569             'LocalPort' => $self->{'localport'},
570             'Proto' => $self->{'proto'} || 'tcp',
571 0 0 0       'Listen' => $self->{'listen'} || 10,
      0        
572             'Reuse' => 1
573             ) or $self->Fatal("Cannot create socket: $!");
574             }
575             }
576 0           $self->Log( 'notice', "Server starting" );
577              
578 0 0 0       if ( ( my $pidfile = ( $self->{'pidfile'} || '' ) ) ne 'none' ) {
579 0           $self->Debug("Writing PID to $pidfile");
580 0           my $fh = Symbol::gensym();
581 0 0 0       $self->Fatal("Cannot write to $pidfile: $!")
      0        
582             unless ( open( OUT, ">$pidfile" )
583             and ( print OUT "$$\n" )
584             and close(OUT) );
585             }
586              
587 0 0         if ( my $dir = $self->{'chroot'} ) {
588 0           $self->Debug("Changing root directory to $dir");
589 0 0         if ( !chroot($dir) ) {
590 0           $self->Fatal("Cannot change root directory to $dir: $!");
591             }
592             }
593 0 0         if ( my $group = $self->{'group'} ) {
594 0           $self->Debug("Changing GID to $group");
595 0           my $gid;
596 0 0         if ( $group !~ /^\d+$/ ) {
597 0 0         if ( defined( my $gid = getgrnam($group) ) ) {
598 0           $group = $gid;
599             }
600             else {
601 0           $self->Fatal("Cannot determine gid of $group: $!");
602             }
603             }
604 0           $( = ( $) = $group );
605             }
606 0 0         if ( my $user = $self->{'user'} ) {
607 0           $self->Debug("Changing UID to $user");
608 0           my $uid;
609 0 0         if ( $user !~ /^\d+$/ ) {
610 0 0         if ( defined( my $uid = getpwnam($user) ) ) {
611 0           $user = $uid;
612             }
613             else {
614 0           $self->Fatal("Cannot determine uid of $user: $!");
615             }
616             }
617 0           $< = ( $> = $user );
618             }
619              
620 0 0         if ( $self->{'childs'} ) {
621 0           my $pid;
622              
623 0           my $childpids = $self->{'childpids'} = {};
624 0           for ( my $n = 0; $n < $self->{'childs'}; $n++ ) {
625 0           $pid = fork();
626 0 0         die "Cannot fork: $!" unless defined $pid;
627 0 0         if ( !$pid ) { #Child
628 0           $self->{'mode'} = 'single';
629 0           last;
630             }
631              
632             # Parent
633 0           $childpids->{$pid} = 1;
634             }
635 0 0         if ($pid) {
636              
637             # Parent waits for childs in a loop, then exits ...
638             # We could also terminate the parent process, but
639             # if the parent is still running we can kill the
640             # whole group by killing the childs.
641 0           my $childpid;
642 0           $exit = 0;
643 0     0     $SIG{'TERM'} = sub { die };
  0            
644 0     0     $SIG{'INT'} = sub { die };
  0            
645 0           eval {
646 0   0       do {
      0        
647 0           $childpid = wait;
648 0           delete $childpids->{$childpid};
649 0           $self->Debug("Child $childpid has exited");
650             } until ( $childpid <= 0 || $exit || keys(%$childpids) == 0 );
651             };
652 0           my @pids = keys %{ $self->{'childpids'} };
  0            
653 0 0         if (@pids) {
654 0           $self->Debug( "kill TERM childs: " . join( ",", @pids ) );
655 0 0         kill 'TERM', @pids if @pids; # send a TERM to all childs
656             }
657 0           exit(0);
658             }
659             }
660              
661 0 0         my $time = $self->{'loop-timeout'} ? ( time() + $self->{'loop-timeout'} ) : 0;
662              
663 0           my $client;
664 0           while ( !$self->Done() ) {
665 0           undef $child_pid;
666 0           my $rin = '';
667 0           vec( $rin, $self->{'socket'}->fileno(), 1 ) = 1;
668 0           my ( $rout, $t );
669 0 0         if ($time) {
670 0           my $tm = time();
671 0           $t = $time - $tm;
672 0 0         $t = 0 if $t < 0;
673 0           $self->Debug("Loop time: time=$time now=$tm, t=$t");
674             }
675 0           my ($nfound) = select( $rout = $rin, undef, undef, $t );
676 0 0         if ( $nfound < 0 ) {
    0          
677 0 0 0       if ( !$child_pid
      0        
678             and ( $! != POSIX::EINTR() or !$self->{'catchint'} ) ) {
679             $self->Fatal(
680             "%s server failed to select(): %s",
681 0   0       ref($self), $self->{'socket'}->error() || $!
682             );
683             }
684             }
685             elsif ($nfound) {
686 0           my $client = $self->{'socket'}->accept();
687 0 0         if ( !$client ) {
688 0 0 0       if ( !$child_pid
      0        
689             and ( $! != POSIX::EINTR() or !$self->{'catchint'} ) ) {
690             $self->Error(
691             "%s server failed to accept: %s",
692 0   0       ref($self), $self->{'socket'}->error() || $!
693             );
694             }
695             }
696             else {
697 0 0         if ( $self->{'debug'} ) {
698             my $from =
699 0 0         $self->{'proto'} eq 'unix'
700             ? 'Unix socket'
701             : sprintf(
702             '%s, port %s',
703              
704             # SE 19990917: display client data!!
705             $client->peerhost(),
706             $client->peerport()
707             );
708 0           $self->Debug("Connection from $from");
709             }
710 0           my $sth = $self->Clone($client);
711 0           $self->Debug("Child clone: $sth\n");
712 0 0         $sth->ChildFunc('HandleChild') if $sth;
713 0 0         if ( $self->{'mode'} eq 'fork' ) {
714 0           $self->ServClose($client);
715             }
716             }
717             }
718 0 0         if ($time) {
719 0           my $t = time();
720 0 0         if ( $t >= $time ) {
721 0           $time = $t;
722 0 0         if ( $self->{'loop-child'} ) {
723 0           $self->ChildFunc('Loop');
724             }
725             else {
726 0           $self->Loop();
727             }
728 0           $time += $self->{'loop-timeout'};
729             }
730             }
731             }
732 0           $self->Log( 'notice', "%s server terminating", ref($self) );
733             }
734              
735             sub Close {
736 0     0 0   my $socket = shift->{'socket'};
737 0 0         $socket->close() if $socket;
738             }
739              
740             sub ServClose {
741 0     0 0   my $self = shift;
742 0           my $socket = shift;
743 0 0         $socket->close() if $socket;
744             }
745              
746             1;
747              
748             __END__