File Coverage

blib/lib/Net/Daemon.pm
Criterion Covered Total %
statement 30 288 10.4
branch 0 174 0.0
condition 0 60 0.0
subroutine 10 30 33.3
pod 0 16 0.0
total 40 568 7.0


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