File Coverage

blib/lib/Mail/POP3/Daemon.pm
Criterion Covered Total %
statement 12 93 12.9
branch 0 8 0.0
condition 0 2 0.0
subroutine 4 15 26.6
pod 6 6 100.0
total 22 124 17.7


line stmt bran cond sub pod time code
1             package Mail::POP3::Daemon;
2              
3 4     4   28 use strict;
  4         8  
  4         117  
4 4     4   19 use IO::Socket;
  4         9  
  4         26  
5 4     4   1792 use IO::File;
  4         10  
  4         486  
6 4     4   28 use POSIX;
  4         7  
  4         27  
7              
8             # horrible globals!
9             my $CONFIG_FILE = './mpopd.conf';
10              
11             sub new {
12 0     0 1   my ($class, $config, $connection_class) = @_;
13 0           my $self = {};
14 0           bless $self, $class;
15 0           $self->{CONFIG} = $config;
16 0           $self;
17             }
18              
19             # server daemon that will fork
20             sub start {
21 0     0 1   my ($self) = @_;
22 0           $self->_setup_sighandlers;
23             # Counter used to track number of children
24 0           $self->{KID_COUNT} = 0;
25             # Build the server socket and bind to $self->{CONFIG}->{port}
26 0           $self->_build_server;
27             # Listen for a client and fork off a child server process
28 0           while (1) {
29             # Trap errors caused by peer-resets etc.
30 0           my $client;
31 0 0         unless ( eval { $client = $self->{SERVER_SOCKET}->accept } ) {
  0            
32 0           eval { $client->close; };
  0            
33 0           next;
34             }
35             # Close the connection if this one would exceed the maximum
36             # concurrent servers allowed, $self->{CONFIG}->{max_servers}.
37 0 0         unless ($self->{KID_COUNT} < $self->{CONFIG}->{max_servers}) {
38 0           eval { $client->close; };
  0            
39 0           sleep 2;
40 0           next;
41             }
42             # Try and fork
43 0           my $kidpid;
44 0 0         if ($kidpid = fork) {
45 0           ++$self->{KID_COUNT};
46 0           $self->{KID_PIDS}->{$kidpid} = 1;
47 0           next;
48             }
49             # If fork fails log the event to a file
50 0 0         unless (defined $kidpid) {
51             $self->_to_file(
52             $self->{CONFIG}->{fork_alert},
53 0           "Fork failed at: " . localtime(time) . ": $!\n",
54             );
55 0           eval { $client->close; };
  0            
56 0           next;
57             }
58             # Everything below here (almost) belongs to the child server.
59 0           $self->{SERVER_SOCKET}->close; # Close clone of SERVER handle.
60             my $server = Mail::POP3::Server->new(
61             $self->{CONFIG},
62 0           );
63 0           $server->start(
64             $client,
65             $client,
66             $client->peerhost,
67             );
68             }
69             }
70              
71             # Create parent socket and bind to POP3 port (or custom port)
72             sub _build_server {
73 0     0     my $self = shift;
74             my $server_socket = IO::Socket::INET->new(
75             LocalPort => $self->{CONFIG}->{port},
76 0   0       Proto => 'tcp',
77             Reuse => 1,
78             Listen => IO::Socket::SOMAXCONN,
79             ) || die "Couldn't bind to port $self->{CONFIG}->{port} : $!";
80 0           $self->{SERVER_SOCKET} = $server_socket;
81             # Write a pid file with the port used on line 2
82             $self->_to_file(
83             $self->{CONFIG}->{mpopd_pid_file},
84 0           "$$\n$self->{CONFIG}->{port}\n",
85             );
86             }
87              
88             sub MPOPDQUIT {
89 0     0 1   my $self = shift;
90 0           my $key;
91 0           foreach $key (keys %{ $self->{KID_PIDS} }) {
  0            
92 0           kill "USR1", $key;
93 0           $self->REAPER;
94             }
95 0           $self->{SERVER_SOCKET}->close;
96 0           unlink $self->{CONFIG}->{mpopd_pid_file};
97 0           exit;
98             }
99              
100             sub READCONFIG {
101 0     0 1   my $self = shift;
102 0           $self->{CONFIG} = (ref $self)->read_config($CONFIG_FILE);
103 0           $SIG{USR1} = $self->_make_closure(\&READCONFIG);
104             }
105              
106             sub MPOPDRESTART {
107 0     0 1   my $self = shift;
108 0           kill "USR1", keys %{ $self->{KID_PIDS} };
  0            
109 0           $self->REAPER;
110 0           $self->{SERVER_SOCKET}->close;
111 0           $self->READCONFIG;
112 0           $self->_build_server;
113 0           $SIG{HUP} = $self->_make_closure(\&MPOPDRESTART);
114 0           $SIG{PIPE} = $self->_make_closure(\&MPOPDRESTART);
115 0           $SIG{INT} = $self->_make_closure(\&MPOPDRESTART);
116             }
117              
118             sub REAPER {
119 0     0 1   my $self = shift;
120 0           while ((my $kidpid = waitpid(-1, POSIX::WNOHANG)) > 0) {
121 0           --$self->{KID_COUNT};
122 0           delete $self->{KID_PIDS}->{$kidpid};
123             }
124 0           $SIG{CHLD} = $self->_make_closure(\&REAPER);
125             }
126              
127             sub _setup_sighandlers {
128 0     0     my $self = shift;
129             # do this with lexical closures
130             # Try and rescue a broken pipe by rebuilding
131             # the server-socket etc.
132 0           $SIG{PIPE} = $self->_make_closure(\&MPOPDRESTART);
133             # Ignore alarm signals from kernel
134 0           $SIG{ALRM} = "IGNORE";
135             # If we get a plain kill then try and close down all child
136             # servers, remove pid file and exit.
137 0           $SIG{TERM} = $self->_make_closure(\&MPOPDQUIT);
138 0           $SIG{INT} = $self->_make_closure(\&MPOPDQUIT);
139             # Just re-read the config file on a SIGUSR1, don't restart.
140 0           $SIG{USR1} = $self->_make_closure(\&READCONFIG);
141             # If we receive a SIGHUP kill off the forked servers gracefully(?)
142             # with a SIGUSR1, close and re-open the server socket, reset as much
143             # as possible and then re-read the config file.
144 0           $SIG{HUP} = $self->_make_closure(\&MPOPDRESTART);
145             # Catch SIGCHLD
146 0           $SIG{CHLD} = $self->_make_closure(\&REAPER);
147             }
148              
149             sub _make_closure {
150 0     0     my ($self, $subref) = @_;
151 0     0     sub { $subref->($self) };
  0            
152             }
153              
154             sub _to_file {
155 0     0     my ($self, $file, $data) = @_;
156 0           local *FH;
157 0           open FH, ">$file";
158 0           print FH $data;
159 0           close FH;
160             }
161              
162             1;
163              
164             =head1 METHODS
165              
166             =head2 new
167              
168             Takes C<$config> and C<$connection_class>.
169              
170             =head2 start
171              
172             Starts forking server.
173              
174             =head2 MPOPDQUIT
175              
176             Signal handler, shuts down.
177              
178             =head2 MPOPDRESTART
179              
180             Signal handler, restarts.
181              
182             =head2 READCONFIG
183              
184             Signal handler, re-reads config.
185              
186             =head2 REAPER
187              
188             Signal handler, reaps zombies.