File Coverage

blib/lib/Mail/Milter/Authentication.pm
Criterion Covered Total %
statement 460 614 74.9
branch 102 210 48.5
condition 25 77 32.4
subroutine 52 60 86.6
pod 36 37 97.3
total 675 998 67.6


line stmt bran cond sub pod time code
1             package Mail::Milter::Authentication;
2             # ABSTRACT: A Perl Mail Authentication Milter
3 99     99   97729 use 5.010;
  99         425  
4 99     99   404 use strict;
  99         216  
  99         1646  
5 99     99   400 use warnings;
  99         106  
  99         2285  
6 99     99   407 use base 'Net::Server::PreFork';
  99         206  
  99         45629  
7             our $VERSION = '20191206'; # VERSION
8              
9              
10 99     99   3612781 use English qw{ -no_match_vars };
  99         136056  
  99         921  
11 99     99   68792 use ExtUtils::Installed;
  99         8606237  
  99         3714  
12 99     99   5084 use JSON;
  99         69953  
  99         687  
13 99     99   60144 use Mail::Milter::Authentication::Config qw{ get_config };
  99         253  
  99         5165  
14 99     99   40298 use Mail::Milter::Authentication::Constants qw{ :all };
  99         245  
  99         25107  
15 99     99   59869 use Mail::Milter::Authentication::Handler;
  99         361  
  99         4079  
16 99     99   50555 use Log::Dispatchouli;
  99         20206354  
  99         3094  
17 99     99   42034 use Mail::Milter::Authentication::Metric;
  99         383  
  99         3390  
18 99     99   49963 use Mail::Milter::Authentication::Protocol::Milter;
  99         3379  
  99         3820  
19 99     99   50874 use Mail::Milter::Authentication::Protocol::SMTP;
  99         342  
  99         3647  
20 99     99   746 use Module::Load;
  99         198  
  99         986  
21 99     99   5456 use Module::Loaded;
  99         202  
  99         5552  
22 99     99   679 use Net::DNS::Resolver;
  99         242  
  99         2592  
23 99     99   528 use Net::IP;
  99         199  
  99         9824  
24 99     99   45386 use Proc::ProcessTable;
  99         253700  
  99         4081  
25 99     99   691 use Sys::Syslog qw{:standard :macros};
  99         194  
  99         22571  
26              
27 99     99   682 use vars qw(@ISA);
  99         202  
  99         571999  
28              
29             {
30             my $LOGGER;
31             sub logger {
32              
33 30714 100   30714 0 180066 return $LOGGER if $LOGGER;
34 35         184 my $config = get_config();
35              
36 35         847 my $MYARGS = {
37             'ident' => $Mail::Milter::Authentication::Config::IDENT,
38             'to_stderr' => 0, # handled elsewhere
39             'log_pid' => 1,
40             'facility' => LOG_MAIL,
41             };
42 35 50       203 if ( exists $config->{ 'log_dispatchouli' } ) {
43 0         0 $MYARGS = $config->{ 'log_dispatchouli' };
44             }
45              
46 35         1620 $LOGGER = Log::Dispatchouli->new( $MYARGS );
47 35         2346155 $LOGGER->log('Logging instantiated');
48 35         49669 return $LOGGER;
49             }
50             }
51              
52             sub _warn {
53 30926     30926   60094 my ( $msg ) = @_;
54 30926         85448 my @parts = split "\n", $msg;
55 30926         59625 foreach my $part ( @parts ) {
56 31908 50       67565 next if $part eq q{};
57 31908         1200613 print STDERR scalar(localtime) . ' ' . $Mail::Milter::Authentication::Config::IDENT . "[$PID] $part\n";
58             }
59 30926         149008 return;
60             }
61              
62              
63             sub preload_modules {
64 28     28 1 325 my ( $self, $from, $matching ) = @_;
65 28         1402 my $installed = ExtUtils::Installed->new( 'skip_cwd' => 1 );
66              
67 28         10942148 my $path_matching = $matching;
68 28         772 $path_matching =~ s/::/\//g;
69 28         467 foreach my $module ( grep { /$from/ } $installed->modules() ) {
  6748         25453  
70             FILE:
71 84         35058 foreach my $file ( grep { /$path_matching\/\w+\.pm$/ } $installed->files( $module ) ) {
  2352         56463  
72 1652 50       2450194 next FILE if ! -e $file;
73 1652         10221 my ( $module ) = reverse split '/', $file;
74 1652         10072 $module =~ s/\.pm$//;
75 1652         4820 $module = join( '::', $matching, $module );
76 1652 100       6078 if ( ! is_loaded( $module ) ) {
77 1409         34822 $self->logdebug( "Preloading Module $module" );
78 1409         4750 load $module;
79             }
80             else {
81 243         5859 $self->logdebug( "Preloading Module $module already loaded" );
82             }
83             }
84             }
85 28         62748 return;
86             }
87              
88              
89             sub get_installed_handlers {
90 0     0 1 0 my @installed_handlers;
91 0         0 my $installed = ExtUtils::Installed->new( 'skip_cwd' => 1 );
92 0         0 foreach my $module ( grep { /Mail::Milter::Authentication/ } $installed->modules() ) {
  0         0  
93             FILE:
94 0         0 foreach my $file ( grep { /Mail\/Milter\/Authentication\/Handler\/\w+\.pm$/ } $installed->files( $module ) ) {
  0         0  
95 0 0       0 next FILE if ! -e $file;
96 0         0 my ( $handler ) = reverse split '/', $file;
97 0         0 $handler =~ s/\.pm$//;
98 0         0 push @installed_handlers, $handler;
99             }
100             }
101 0         0 return \@installed_handlers;
102             }
103              
104              
105             sub write_to_log_hook {
106 176     176 1 717655 my ( $self, $priority, $line ) = @_;
107 176 0       1456 my $log_priority = $priority == 0 ? 'debug'
    0          
    50          
    100          
    100          
108             : $priority == 1 ? 'info'
109             : $priority == 2 ? 'notice'
110             : $priority == 3 ? 'warning'
111             : $priority == 4 ? 'error'
112             : 'debug';
113 176         962 logger()->log( { 'level' => $log_priority }, $line );
114 176         112798 return;
115             }
116              
117              
118             sub idle_loop_hook {
119 136     136 1 367237580 my ( $self ) = @_;
120 136         1981 $self->{'metric'}->master_metric_update( $self );
121 136         571 return;
122             }
123              
124              
125             sub pre_loop_hook {
126 28     28 1 33216 my ( $self ) = @_;
127              
128 28         812 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':master';
129              
130 28         329 $self->preload_modules( 'Net::DNS', 'Net::DNS::RR' );
131 28         1499 $self->{'metric'} = Mail::Milter::Authentication::Metric->new();
132              
133             # Load handlers
134 28         217 my $config = get_config();
135 28         93 foreach my $name ( @{$config->{'load_handlers'}} ) {
  28         326  
136 262         1255 $self->load_handler( $name );
137              
138 262         765 my $package = "Mail::Milter::Authentication::Handler::$name";
139 262         3387 my $object = $package->new( $self );
140 262 100       2283 if ( $object->can( 'pre_loop_setup' ) ) {
141 60         273 $object->pre_loop_setup();
142             }
143 262 50       1556 if ( $object->can( 'register_metrics' ) ) {
144 262         1056 $self->{'metric'}->register_metrics( $object->register_metrics() );
145             }
146              
147             }
148              
149 28         299 $self->{'metric'}->register_metrics( {
150             'forked_children_total' => 'Total number of child processes forked',
151             'reaped_children_total' => 'Total number of child processes reaped',
152             } );
153              
154 28         222 $self->{'metric'}->register_metrics( Mail::Milter::Authentication::Handler->register_metrics() );
155              
156 28 100       213 if ( $config->{'protocol'} eq 'milter' ) {
    50          
157 16         482 $self->{'metric'}->register_metrics( Mail::Milter::Authentication::Protocol::Milter->register_metrics() );
158             }
159             elsif ( $config->{'protocol'} eq 'smtp' ) {
160 12         474 $self->{'metric'}->register_metrics( Mail::Milter::Authentication::Protocol::SMTP->register_metrics() );
161             }
162             else {
163 0         0 die "Unknown protocol " . $config->{'protocol'} . "\n";
164             }
165              
166 28 50       192 if ( $config->{'error_log'} ) {
167 28 50       1669 open( STDERR, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
168 28 50       1038 open( STDOUT, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
169             }
170              
171 28         172 return;
172              
173             }
174              
175              
176             sub run_n_children_hook {
177 30     30 1 10445 my ( $self ) = @_;
178              
179             # Load handlers
180 30         172 my $config = get_config();
181 30         87 foreach my $name ( @{$config->{'load_handlers'}} ) {
  30         311  
182              
183 264         595 my $package = "Mail::Milter::Authentication::Handler::$name";
184 264         1671 my $object = $package->new( $self );
185 264 100       1984 if ( $object->can( 'pre_fork_setup' ) ) {
186 24         159 $object->pre_fork_setup();
187             }
188              
189             }
190              
191 30         105 return;
192             }
193              
194              
195             sub child_init_hook {
196 19     19 1 166072 my ( $self ) = @_;
197 19         981 srand();
198              
199 19         1310 my $config = get_config();
200 19         636 $self->{'config'} = $config;
201              
202 19 50       701 if ( $config->{'error_log'} ) {
203 19         499 eval {
204 19 50       1934 open( STDERR, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
205 19 50       1294 open( STDOUT, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
206             };
207 19 50       763 if ( my $error = $@ ) {
208 0         0 $self->logerror( "Child process $PID could not open the error log: $error" );
209             }
210             }
211              
212 19         1166 $self->loginfo( "Child process $PID starting up" );
213 19         587 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':starting';
214              
215 19         288 my $base;
216 19 100       731 if ( $config->{'protocol'} eq 'milter' ) {
    50          
217 11         192 $base = 'Mail::Milter::Authentication::Protocol::Milter';
218             }
219             elsif ( $config->{'protocol'} eq 'smtp' ) {
220 8         158 $base = 'Mail::Milter::Authentication::Protocol::SMTP';
221             }
222             else {
223 0         0 die "Unknown protocol " . $config->{'protocol'} . "\n";
224             }
225 19         2484 push @ISA, $base;
226              
227             # BEGIN MILTER PROTOCOL BLOCK
228 19 100       871 if ( $config->{'protocol'} eq 'milter' ) {
229 11         191 my $protocol = SMFIP_NONE & ~(SMFIP_NOCONNECT|SMFIP_NOMAIL);
230 11         59 $protocol &= ~SMFIP_NOHELO;
231 11         103 $protocol &= ~SMFIP_NORCPT;
232 11         248 $protocol &= ~SMFIP_NOBODY;
233 11         152 $protocol &= ~SMFIP_NOHDRS;
234 11         166 $protocol &= ~SMFIP_NOEOH;
235 11         609 $self->{'protocol'} = $protocol;
236              
237 11         132 my $callback_flags = SMFI_CURR_ACTS|SMFIF_CHGBODY|SMFIF_QUARANTINE|SMFIF_SETSENDER;
238 11         213 $self->{'callback_flags'} = $callback_flags;
239             }
240             # END MILTER PROTOCOL BLOCK
241              
242 19         244 my $callbacks_list = {};
243 19         265 my $callbacks = {};
244 19         281 my $handler = {};
245 19         312 my $object = {};
246 19         344 my $object_maker = {};
247 19         208 my $count = 0;
248              
249 19         502 $self->{'callbacks_list'} = $callbacks_list;
250 19         568 $self->{'callbacks'} = $callbacks;
251 19         345 $self->{'count'} = $count;
252 19         303 $self->{'handler'} = $handler;
253 19         392 $self->{'object'} = $object;
254 19         380 $self->{'object_maker'} = $object_maker;
255              
256 19         765 $self->setup_handlers();
257              
258 19         299 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':waiting(0)';
259 19         158 return;
260             }
261              
262              
263             sub child_finish_hook {
264 18     18 1 62843932 my ($self) = @_;
265 18         321 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':exiting';
266 18         244 $self->loginfo( "Child process $PID shutting down" );
267 18         53 eval {
268 18         162 $self->{'handler'}->{'_Handler'}->metric_count( 'reaped_children_total', {}, 1 );
269             };
270 18         153 $self->destroy_objects();
271 6         50 return;
272             }
273              
274              
275             sub pre_server_close_hook {
276 9     9 1 12729 my ($self) = @_;
277 9         287 $self->loginfo( 'Server closing down' );
278 9         70 return;
279             }
280              
281              
282             sub get_client_proto {
283 200     200 1 820 my ( $self ) = @_;
284 200         621 my $socket = $self->{server}{client};
285 200 50       2559 if ($socket->isa("Net::Server::Proto")) {
286 0         0 my $proto = $socket->NS_proto;
287 0 0       0 $proto = "UNIX" if $proto =~ m/^UNIX/;
288 0         0 return $proto;
289             }
290              
291 200 50       1897 if ($socket->isa("IO::Socket::INET")) {
292 0         0 return "TCP";
293             }
294              
295 200 50       1563 if ($socket->isa("IO::Socket::INET6")) {
296 0         0 return "TCP";
297             }
298              
299 200 50       1116 if ($socket->isa("IO::Socket::UNIX")) {
300 200         1251 return "UNIX";
301             }
302              
303 0         0 $self->logerror( "Could not determine connection protocol: " . ref($socket) );
304              
305 0         0 return;
306             }
307              
308              
309             sub get_client_port {
310 0     0 1 0 my ( $self ) = @_;
311 0         0 my $socket = $self->{server}{client};
312 0         0 return $socket->sockport();
313             }
314              
315              
316             sub get_client_host {
317 0     0 1 0 my ( $self ) = @_;
318 0         0 my $socket = $self->{server}{client};
319 0         0 return $socket->sockhost();
320             }
321              
322              
323             sub get_client_path {
324 200     200 1 648 my ( $self ) = @_;
325 200         593 my $socket = $self->{server}{client};
326 200         1351 return $socket->hostpath();
327             }
328              
329              
330             sub get_client_details {
331 135     135 1 347 my ( $self ) = @_;
332 135         512 my $proto = lc $self->get_client_proto();
333 135 50       1695 if ( $proto eq 'tcp' ) {
    50          
334 0         0 return 'inet:' . $self->get_client_port();
335             }
336             elsif ( $proto eq 'unix' ) {
337 135         502 return 'unix:' . $self->get_client_path();
338             }
339 0         0 return;
340             }
341              
342              
343             sub process_request {
344 65     65 1 538099772 my ( $self ) = @_;
345 65         459 my $config = $self->{'config'};
346              
347 65         725 my $metric_type;
348             my $metric_path;
349 65         0 my $metric_host;
350              
351 65 50       445 if ( defined( $config->{'metric_connection'} ) ) {
352 65         247 my $connection = $config->{'metric_connection'};
353 65         323 my $umask = $config->{'metric_umask'};
354              
355 65         876 $connection =~ /^([^:]+):([^:@]+)(?:@([^:@]+|\[[0-9a-f:\.]+\]))?$/;
356 65         744 $metric_type = $1;
357 65         505 $metric_path = $2;
358 65   50     1485 $metric_host = $3 || q{};
359             }
360              
361             ## ToDo, match also on client_host
362              
363             # Legacy metrics
364 65 50 33     2464 if ( defined( $config->{ 'metric_port' } ) && $self->get_client_proto() eq 'TCP' && $self->get_client_port() eq $config->{'metric_port'} ) {
    50 33        
    100 33        
      33        
      33        
      33        
      33        
      66        
365 0         0 $self->{'metric'}->child_handler( $self );
366             }
367              
368             elsif ( defined( $config->{ 'metric_connection' } ) && $metric_type eq 'inet' && $self->get_client_proto eq 'TCP' && $self->get_client_port() eq $metric_path ) {
369 0         0 $self->{'metric'}->child_handler( $self );
370             }
371              
372             elsif ( defined( $config->{ 'metric_connection' } ) && $metric_type eq 'unix' && $self->get_client_proto eq 'UNIX' && $self->get_client_path() eq $metric_path ) {
373 9         1014 $self->{'metric'}->child_handler( $self );
374             }
375              
376             else {
377 56         3831 $self->process_main();
378             }
379              
380 64         298 my $count = $self->{'count'};
381 64         728 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':waiting(' . $count . ')';
382 64         470 return;
383             }
384              
385              
386             sub process_main {
387 56     56 1 184 my ( $self ) = @_;
388              
389 56         246 $self->{'count'}++;
390 56         202 my $count = $self->{'count'};
391 56         195 my $config = $self->{'config'};
392              
393 56         858 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':processing(' . $count . ')';
394 56         500 $self->logdebug( 'Processing request ' . $self->{'count'} );
395 56         395 $self->{'socket'} = $self->{'server'}->{'client'};
396              
397 56         1570 $self->protocol_process_request();
398              
399             # Call close callback
400 56         443 $self->{'handler'}->{'_Handler'}->top_close_callback();
401 56 100       297 if ( $self->{'handler'}->{'_Handler'}->{'exit_on_close'} ) {
402 1         15 $self->fatal('exit_on_close requested');
403             }
404              
405 55 50       235 if ( $config->{'debug'} ) {
406 55         906 my $process_table = Proc::ProcessTable->new();
407 55         74147 foreach my $process ( @{$process_table->table} ) {
  55         207020  
408 770 100       12648 if ( $process->pid == $PID ) {
409 55         969 my $size = $process->size;
410 55         926 my $rss = $process->rss;
411 55         900 my $pctmem = $process->pctmem;
412 55         888 my $pctcpu = $process->pctcpu;
413 55         1489 $self->loginfo( "Resource usage: ($count) size $size/rss $rss/memory $pctmem\%/cpu $pctcpu\%" );
414             }
415             }
416             }
417              
418 55         4231 delete $self->{'handler'}->{'_Handler'}->{'reject_mail'};
419 55         189 delete $self->{'handler'}->{'_Handler'}->{'return_code'};
420 55         161 delete $self->{'socket'};
421 55         313 $self->logdebug( 'Request processing completed' );
422 55         222 return;
423             }
424              
425              
426              
427              
428             sub get_valid_pid {
429 0     0 1 0 my ( $pid_file ) = @_;
430 0 0       0 if ( ! $pid_file ) {
431 0         0 return undef; ## no critic
432             }
433 0 0       0 if ( ! -e $pid_file ) {
434 0         0 return undef; ## no critic
435             }
436              
437 0   0     0 open my $inf, '<', $pid_file || return undef; ## no critic
438 0         0 my $pid = <$inf>;
439 0         0 close $inf;
440              
441 0         0 my $self_pid = $PID;
442 0         0 my $found_self = 0;
443 0         0 my $found_pid = 0;
444              
445 0         0 my $process_table = Proc::ProcessTable->new();
446 0         0 foreach my $process ( @{$process_table->table} ) {
  0         0  
447 0 0       0 if ( $process->pid == $self_pid ) {
448 0 0       0 if ( $process->cmndline eq $Mail::Milter::Authentication::Config::IDENT . ':control' ) {
449 0         0 $found_self = 1;
450             }
451             }
452 0 0       0 if ( $process->pid == $pid ) {
453 0         0 $found_pid = 1;
454 0 0       0 if ( $process->cmndline eq $Mail::Milter::Authentication::Config::IDENT . ':master' ) {
455 0         0 return $pid;
456             }
457             }
458             }
459              
460             # If we didn't find ourself in the process table then we can assume that
461             # $0 is read only on our current operating system, and return the pid that we read from the
462             # pidfile if it is in the process table regardness of it's process name..
463 0 0       0 if ( ! $found_self ) {
464 0 0       0 if ( $found_pid ) {
465 0         0 return $pid;
466             }
467             }
468              
469 0         0 return undef; ## no critic
470             }
471              
472              
473             sub find_process {
474 0     0 1 0 my $process_table = Proc::ProcessTable->new();
475 0         0 foreach my $process ( @{$process_table->table} ) {
  0         0  
476 0 0       0 if ( $process->cmndline eq $Mail::Milter::Authentication::Config::IDENT . ':master' ) {
477 0         0 return $process->pid;
478             }
479             }
480 0         0 return undef; ## no critic
481             }
482              
483              
484             sub control {
485 0     0 1 0 my ( $args ) = @_;
486 0         0 my $pid_file = $args->{'pid_file'};
487 0         0 my $command = $args->{'command'};
488              
489 0         0 my $OriginalProgramName = $PROGRAM_NAME;
490 0         0 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':control';
491              
492 0 0 0     0 if ( $command eq 'stop' ) {
    0          
    0          
493 0   0     0 my $pid = get_valid_pid( $pid_file ) || find_process();
494 0 0       0 if ( $pid ) {
495 0         0 print "Process found, stopping\n";
496 0         0 kill 'QUIT', $pid;
497             }
498             else {
499 0         0 print "No process found\n";
500             }
501             }
502             elsif ( $command eq 'restart' || $command eq 'start' ) {
503 0   0     0 my $pid = get_valid_pid( $pid_file ) || find_process();
504 0 0       0 if ( $pid ) {
505 0         0 print "Process found, restarting\n";
506 0         0 kill 'HUP', $pid;
507             }
508             else {
509 0         0 print "No process found, starting up\n";
510 0         0 $PROGRAM_NAME = $OriginalProgramName;
511 0         0 start({
512             'pid_file' => $pid_file,
513             'daemon' => 1,
514             });
515             }
516             }
517             elsif ( $command eq 'status' ) {
518 0   0     0 my $pid = get_valid_pid( $pid_file ) || find_process();
519 0 0       0 if ( $pid ) {
520 0         0 print "Process running with pid $pid\n";
521 0 0       0 if ( ! get_valid_pid( $pid_file ) ) {
522 0         0 print "pid file $pid_file is invalid\n";
523             }
524             }
525             else {
526 0         0 print "No process found\n";
527             }
528             }
529             else {
530 0         0 die 'unknown command';
531             }
532              
533 0         0 return;
534             }
535              
536              
537             sub start {
538 28     28 1 195 my ($args) = @_;
539              
540             local $SIG{__WARN__} = sub {
541 122     122   471443 foreach my $msg ( @_ ) {
542 122         493 logger()->log( { level => 'warning' }, "Warning: $msg" );
543 122         91498 _warn( "Warning: $msg" );
544             }
545 122         967 return;
546 28         1298 };
547              
548 28         1443 my $config = get_config();
549              
550 28   50     394 my $default_connection = $config->{'connection'} || die('No connection details given');
551              
552 28         111 my $pid_file = $args->{'pid_file'};
553              
554 28   50     289 my $listen_backlog = $config->{'listen_backlog'} || 20;
555 28   50     171 my $max_children = $config->{'max_children'} || 100;
556 28   50     283 my $max_requests_per_child = $config->{'max_requests_per_child'} || 200;
557 28   50     217 my $min_children = $config->{'min_children'} || 20;
558 28   50     294 my $max_spare_children = $config->{'max_spare_children'} || 20;
559 28   50     178 my $min_spare_children = $config->{'min_spare_children'} || 10;
560              
561 28         62 my %srvargs;
562              
563 28         436 $srvargs{'no_client_stdout'} = 1;
564              
565             # Early redirection to log file if possible
566 28 50       154 if ( $config->{'error_log'} ) {
567 28 50       3825 open( STDERR, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
568 28 50       2092 open( STDOUT, '>>', $config->{'error_log'} ) || die "Cannot open errlog [$!]";
569             }
570              
571 28 50       223 if ( $args->{'daemon'} ) {
572 0 0       0 if ( $EUID == 0 ) {
573 0         0 _warn(
574             join( ' ',
575             'daemonize',
576             "servers=$min_children/$max_children",
577             "spares=$min_spare_children/$max_spare_children",
578             "requests=$max_requests_per_child",
579             )
580             );
581 0         0 $srvargs{'background'} = 1;
582 0         0 $srvargs{'setsid'} = 1;
583             }
584             else {
585 0         0 _warn("Not running as root, daemonize ignored!");
586             }
587             }
588 28         77 $srvargs{'pid_file'} = $pid_file;
589 28         374 $srvargs{'max_servers'} = $max_children;
590 28         403 $srvargs{'max_requests'} = $max_requests_per_child;
591 28         403 $srvargs{'min_servers'} = $min_children;
592 28         103 $srvargs{'min_spare_servers'} = $min_spare_children;
593 28         173 $srvargs{'max_spare_servers'} = $max_spare_children;
594              
595 28 50       734 if ( $EUID == 0 ) {
596 28         126 my $user = $config->{'runas'};
597 28         423 my $group = $config->{'rungroup'};
598 28 50 33     278 if ( $user && $group ) {
599 0         0 _warn("run as user=$user group=$group");
600 0         0 $srvargs{'user'} = $user;
601 0         0 $srvargs{'group'} = $group;
602             }
603             else {
604 28         856 _warn("No runas details supplied, could not drop privs - be careful!");
605             }
606             # Note, Chroot requires a chroot environment which is out of scope at present
607 28 50       180 if ( $config->{'error_log'} ) {
608 28 50       539 if ( ! -e $config->{'error_log'} ) {
609 0   0     0 open my $outf, '>', $config->{'error_log'} || die "Could not create error log: $!\n";;
610 0         0 close $outf;
611             }
612 28 50       226 if ( $user ) {
613 0         0 my ($login,$pass,$uid,$gid) = getpwnam($user);
614 0         0 chown $uid, $gid, $config->{'error_log'};
615             }
616             }
617 28 50       143 if ( exists( $config->{'chroot'} ) ) {
618 0         0 _warn('Chroot to ' . $config->{'chroot'});
619 0         0 $srvargs{'chroot'} = $config->{'chroot'};
620             }
621             }
622             else {
623 0         0 _warn("Not running as root, could not drop privs - be careful!");
624             }
625              
626 28         156 my $connections = {};
627              
628 28 50       149 if ( exists $config->{'connections'} ) {
629 0         0 $connections = $config->{'connections'};
630             }
631              
632             $connections->{'default'} = {
633             'connection' => $default_connection,
634 28         389 'umask' => $config->{'umask'},
635             };
636              
637 28         108 my @ports;
638 28         364 foreach my $key ( keys %$connections ) {
639 28         109 my $connection = $connections->{$key}->{'connection'};
640 28         78 my $umask = $connections->{$key}->{'umask'};
641              
642 28         396 $connection =~ /^([^:]+):([^:@]+)(?:@([^:@]+|\[[0-9a-f:\.]+\]))?$/;
643 28         125 my $type = $1;
644 28         371 my $path = $2;
645 28   50     572 my $host = $3 || q{};
646 28 50       309 if ( $type eq 'inet' ) {
    50          
647 0         0 _warn(
648             join( ' ',
649             'listening on inet',
650             "host=$host",
651             "port=$path",
652             "backlog=$listen_backlog",
653             )
654             );
655 0         0 push @ports, {
656             'host' => $host,
657             'port' => $path,
658             'ipv' => '*',
659             'proto' => 'tcp',
660             };
661             }
662             elsif ( $type eq 'unix' ) {
663 28         506 _warn(
664             join( ' ',
665             'listening on unix',
666             "socket=$path",
667             "backlog=$listen_backlog",
668             )
669             );
670 28         471 push @ports, {
671             'port' => $path,
672             'proto' => 'unix',
673             };
674              
675 28 50       178 if ($umask) {
676 28         404 umask ( oct( $umask ) );
677 28         171 _warn( 'setting umask to ' . $umask );
678             }
679              
680             }
681             else {
682 0         0 die 'Invalid connection';
683             }
684             }
685              
686 28 50       175 if ( defined( $config->{'metric_connection'} ) ) {
    0          
687 28         87 my $connection = $config->{'metric_connection'};
688 28         61 my $umask = $config->{'metric_umask'};
689              
690 28         258 $connection =~ /^([^:]+):([^:@]+)(?:@([^:@]+|\[[0-9a-f:\.]+\]))?$/;
691 28         106 my $type = $1;
692 28         146 my $path = $2;
693 28   50     420 my $host = $3 || q{};
694 28 50       274 if ( $type eq 'inet' ) {
    50          
695 0         0 _warn(
696             join( ' ',
697             'metrics listening on inet',
698             "host=$host",
699             "port=$path",
700             "backlog=$listen_backlog",
701             )
702             );
703 0         0 push @ports, {
704             'host' => $host,
705             'port' => $path,
706             'ipv' => '*',
707             'proto' => 'tcp',
708             };
709 0         0 $srvargs{'child_communication'} = 1;
710             }
711             elsif ( $type eq 'unix' ) {
712 28         238 _warn(
713             join( ' ',
714             'metrics listening on unix',
715             "socket=$path",
716             "backlog=$listen_backlog",
717             )
718             );
719 28         203 push @ports, {
720             'port' => $path,
721             'proto' => 'unix',
722             };
723 28         371 $srvargs{'child_communication'} = 0;
724              
725 28 50       146 if ($umask) {
726 28         160 umask ( oct( $umask ) );
727 28         179 _warn( 'setting umask to ' . $umask );
728             }
729              
730             }
731             else {
732 0         0 die 'Invalid metrics connection';
733             }
734              
735 28 50       185 if ( defined( $config->{'metric_port'} ) ) {
736 0         0 _warn( 'metric_port ignored when metric_connection supplied' );
737             }
738              
739             }
740             elsif ( defined( $config->{'metric_port'} ) ) {
741 0   0     0 my $metric_host = $config->{ 'metric_host' } || '127.0.0.1';
742             push @ports, {
743             'host' => $metric_host,
744 0         0 'port' => $config->{'metric_port'},
745             'ipv' => '*',
746             'proto' => 'tcp',
747             };
748 0         0 $srvargs{'child_communication'} = 1;
749 0         0 _warn( 'Metrics available on ' . $metric_host . ':' . $config->{'metric_port'} );
750 0         0 _warn( 'metric_host/metric_port are depricated, please use metric_connection/metric_umask instead' );
751             }
752              
753 28         102 $srvargs{'port'} = \@ports;
754 28         227 $srvargs{'listen'} = $listen_backlog;
755 28         309 $srvargs{'leave_children_open_on_hup'} = 1;
756              
757 28         206 _warn "==========";
758 28         156 _warn "Starting server";
759 28         1757 _warn "Running with perl $PERL_VERSION";
760 28         612 _warn "==========";
761              
762 28         77 my @start_times;
763 28         114 my $parent_pid = $PID;
764 28         55 while ( 1 ) {
765 28         93 unshift @start_times, time();
766              
767 28         89 eval {
768 28         2836 __PACKAGE__->run( %srvargs );
769             };
770 13         3981 my $error = $@;
771 13 50       99 if ( $PID != $parent_pid ) {
772 13         65 _warn "Child exiting";
773 13         3218 die;
774             }
775 0 0       0 $error = 'unknown error' if ! $error;
776 0         0 _warn "Server failed: $error";
777              
778 0 0       0 if ( scalar @start_times >= 4 ) {
779 0 0       0 if ( $start_times[3] > ( time() - 120 ) ) {
780 0         0 _warn "Abandoning automatic restart: too many restarts in a short time";
781 0         0 last;
782             }
783             }
784              
785 0         0 _warn "Attempting automatic restart";
786 0         0 sleep 10;
787             }
788 0         0 _warn "Server exiting abnormally";
789 0         0 die;
790              
791 0         0 return; ## no critic
792             }
793              
794             ##### Protocol methods
795              
796              
797             sub fatal {
798 1     1 1 3 my ( $self, $error ) = @_;
799 1         16 $self->logerror( "Child process $PID shutting down due to fatal error: $error" );
800 1         10 die "$error\n";
801             }
802              
803              
804             sub fatal_global {
805 0     0 1 0 my ( $self, $error ) = @_;
806 0         0 my $ppid = $self->{'server'}->{'ppid'};
807 0 0       0 if ( $ppid == $PID ) {
808 0         0 $self->logerror( "Global shut down due to fatal error: $error" );
809             }
810             else {
811 0         0 $self->logerror( "Child process $PID signalling global shut down due to fatal error: $error" );
812 0         0 kill 'Term', $ppid;
813             }
814 0         0 die "$error\n";
815             }
816              
817              
818             sub setup_handlers {
819 28     28 1 284 my ( $self ) = @_;
820              
821 28         511 $self->logdebug( 'setup objects' );
822 28         1077 my $handler = Mail::Milter::Authentication::Handler->new( $self );
823 28         419 $self->{'handler'}->{'_Handler'} = $handler;
824              
825 28         672 $handler->metric_count( 'forked_children_total', {}, 1 );
826              
827 28         123 my $config = $self->{'config'};
828 28         236 foreach my $name ( @{$config->{'load_handlers'}} ) {
  28         374  
829 191         868 $self->setup_handler( $name );
830             }
831 28         559 $self->sort_all_callbacks();
832 28         130 return;
833             }
834              
835              
836             sub load_handler {
837 278     278 1 850 my ( $self, $name ) = @_;
838              
839             ## TODO error handling here
840 278         1752 $self->logdebug( "Load Handler $name" );
841              
842 278         842 my $package = "Mail::Milter::Authentication::Handler::$name";
843 278 100       1167 if ( ! is_loaded ( $package ) ) {
844 270         6957 $self->logdebug( "Load Handler Module $name" );
845 270         610 eval { load $package; };
  270         1034  
846 270 50       13978 if ( my $error = $@ ) {
847 0         0 $self->fatal_global('Could not load handler ' . $name . ' : ' . $error);
848             }
849             }
850 278         1011 return;
851             }
852              
853              
854             sub setup_handler {
855 191     191 1 1140 my ( $self, $name ) = @_;
856              
857             ## TODO error handling here
858 191         1522 $self->logdebug( "Instantiate Handler $name" );
859              
860 191         710 my $package = "Mail::Milter::Authentication::Handler::$name";
861 191         3550 my $object = $package->new( $self );
862 191         681 $self->{'handler'}->{$name} = $object;
863              
864 191         727 foreach my $callback ( qw { setup connect helo envfrom envrcpt header eoh body eom addheader abort close } ) {
865 2292 100       24446 if ( $object->can( $callback . '_callback' ) ) {
866 746         2771 $self->register_callback( $name, $callback );
867             }
868             }
869              
870 191         747 return;
871             }
872              
873              
874             sub destroy_handler {
875             # Unused!
876 44     44 1 90 my ( $self, $name ) = @_;
877             # Remove some back references
878 44         273 delete $self->{'handler'}->{$name}->{'thischild'};
879             # Remove reference to handler
880 44         105 delete $self->{'handler'}->{$name};
881 44         80 return;
882             }
883              
884              
885             sub register_callback {
886 746     746 1 2329 my ( $self, $name, $callback ) = @_;
887 746         18955 $self->logdebug( "Register Callback $name:$callback" );
888 746 100       3187 if ( ! exists $self->{'callbacks'}->{$callback} ) {
889 249         2421 $self->{'callbacks'}->{$callback} = [];
890             }
891 746         1396 push @{ $self->{'callbacks'}->{$callback} }, $name;
  746         2435  
892 746         2021 return;
893             }
894              
895              
896             sub sort_all_callbacks {
897 28     28 1 159 my ($self) = @_;
898 28         207 foreach my $callback ( qw { setup connect helo envfrom envrcpt header eoh body eom addheader abort close } ) {
899 336         970 $self->sort_callbacks( $callback );
900             }
901 28         91 return;
902             }
903              
904              
905             sub sort_callbacks {
906 336     336 1 1301 my ( $self, $callback ) = @_;
907              
908 336 100       990 if ( ! exists $self->{'callbacks'}->{$callback} ) {
909 87         488 $self->{'callbacks'}->{$callback} = [];
910             }
911              
912 336 50       903 if ( ! exists $self->{'callbacks_list'}->{$callback} ) {
913 336         875 $self->{'callbacks_list'}->{$callback} = [];
914             }
915             else {
916 0         0 return $self->{'callbacks_list'}->{$callback};
917             }
918              
919 336         623 my $callbacks_ref = $self->{'callbacks'}->{$callback};
920              
921 336         612 my $added = {};
922 336         545 my @order;
923              
924 336         562 my @todo = sort @{$callbacks_ref};
  336         1042  
925 336         665 my $todo_count = scalar @todo;
926              
927             # Process requirements
928 336         764 my $requirements = {};
929 336         785 foreach my $item ( @todo ) {
930 746         1781 $requirements->{ $item } = [];
931 746         1280 my $handler = $self->{'handler'}->{ $item };
932 746         1385 my $requires_method = $callback . '_requires';
933 746 100       5618 if ( $handler->can( $requires_method ) ) {
934 51         697 my $requires = $handler->$requires_method;
935 51         319 foreach my $require ( @{ $requires } ) {
  51         206  
936 85         279 push @{ $requirements->{ $item } }, $require;
  85         373  
937             }
938             }
939             }
940 336         676 foreach my $item ( @todo ) {
941 746         1494 my $handler = $self->{'handler'}->{ $item };
942 746         1420 my $requires_method = $callback . '_required_before';
943 746 50       4750 if ( $handler->can( $requires_method ) ) {
944 0         0 my $requires = $handler->$requires_method;
945 0         0 foreach my $require ( @{ $requires } ) {
  0         0  
946 0         0 push @{ $requirements->{ $require } }, $item;
  0         0  
947             }
948             }
949             }
950              
951 336         809 while ( $todo_count ) {
952 283         519 my @defer;
953 283         529 foreach my $item ( @todo ) {
954 780         1198 my $requires_met = 1;
955 780         1210 my $requires = $requirements->{ $item };
956 780         1189 foreach my $require ( @{ $requires } ) {
  780         1484  
957 153 100       522 if ( ! exists $added->{$require} ) {
958 51         101 $requires_met = 0;
959             }
960             }
961 780 100       1587 if ( $requires_met == 1 ) {
962 746         1428 push @order, $item;
963 746         1555 $added->{$item} = 1;
964             }
965             else {
966 34         154 push @defer, $item;
967             }
968             }
969              
970 283         525 my $defer_count = scalar @defer;
971 283 50       696 if ( $defer_count == $todo_count ) {
972 0         0 $self->fatal_global('Could not build order list');
973             }
974 283         466 $todo_count = $defer_count;
975 283         820 @todo = @defer;
976             }
977              
978 336         833 $self->{'callbacks_list'}->{$callback} = \@order;
979 336         1104 return;
980             }
981              
982              
983             sub destroy_objects {
984 18     18 1 65 my ( $self ) = @_;
985 18         100 $self->logdebug ( 'destroy objects' );
986 18         83 my $handler = $self->{'handler'}->{'_Handler'};
987 18 50       103 if ( $handler ) {
988 18         579 $handler->destroy_all_objects();
989 6         19 my $config = $self->{'config'};
990 6         25 foreach my $name ( @{$config->{'load_handlers'}} ) {
  6         65  
991 44         102 $self->destroy_handler( $name );
992             }
993 6         45 delete $self->{'handler'}->{'_Handler'}->{'config'};
994 6         19 delete $self->{'handler'}->{'_Handler'}->{'thischild'};
995 6         18 delete $self->{'handler'}->{'_Handler'};
996             }
997 6         245 return;
998             }
999              
1000              
1001              
1002              
1003             ## Logging
1004              
1005              
1006             sub get_queue_id {
1007 5838     5838 1 10964 my ( $self ) = @_;
1008 5838         8850 my $queue_id;
1009              
1010 5838 100       19821 if ( exists ( $self->{'smtp'} ) ) {
    100          
1011 447 50       1492 if ( $self->{'smtp'}->{'queue_id'} ) {
1012 447         908 $queue_id = $self->{'smtp'}->{'queue_id'};
1013             }
1014             }
1015             elsif ( exists ( $self->{'handler'}->{'_Handler'} ) ) {
1016 3110         11048 $queue_id = $self->{'handler'}->{'_Handler'}->get_symbol('i');
1017             }
1018              
1019 5838         15913 return $queue_id;
1020             }
1021              
1022              
1023             sub enable_extra_debugging {
1024 0     0 1 0 my ($self) = @_;
1025 0   0     0 my $config = $self->{'config'} || get_config();
1026 0         0 $config->{'logtoerr'} = 1;
1027 0         0 $config->{'debug'} = 1;
1028 0         0 $self->{'extra_debugging'} = 1;
1029 0         0 $self->logerror( 'Extra debugging enabled. Child will exit on close.' );
1030             # We don't want to persist this, so force an exit on close state.
1031 0         0 $self->{'handler'}->{'_Handler'}->{'exit_on_close'} = 1;
1032 0         0 return;
1033             }
1034              
1035              
1036             sub extra_debugging {
1037 1906     1906 1 4228 my ($self,$line) = @_;
1038 1906 50       4558 if ( $self->{'extra_debugging'} ) {
1039 0         0 $self->logerror( $line );
1040             }
1041 1906         4051 return;
1042             }
1043              
1044              
1045             sub logerror {
1046 1     1 1 4 my ($self,$line) = @_;
1047 1   33     5 my $config = $self->{'config'} || get_config();
1048 1 50       5 if ( my $queue_id = $self->get_queue_id() ) {
1049 0         0 $line = $queue_id . ': ' . $line;
1050             }
1051 1 50       7 _warn( $line ) if $config->{'logtoerr'};
1052 1         4 logger()->log( { 'level' => 'error' }, $line );
1053 1         519 return;
1054             }
1055              
1056              
1057             sub loginfo {
1058 151     151 1 984 my ($self,$line) = @_;
1059 151   66     1642 my $config = $self->{'config'} || get_config();
1060 151 100       1216 if ( my $queue_id = $self->get_queue_id() ) {
1061 6         26 $line = $queue_id . ': ' . $line;
1062             }
1063 151 50       1742 _warn( $line ) if $config->{'logtoerr'};
1064 151         1082 logger()->log( { 'level' => 'info' }, $line );
1065 151         148632 return;
1066             }
1067              
1068              
1069             sub logdebug {
1070 5686     5686 1 13339 my ($self,$line) = @_;
1071 5686   66     36873 my $config = $self->{'config'} || get_config();
1072 5686 100       14018 if ( my $queue_id = $self->get_queue_id() ) {
1073 921         2295 $line = $queue_id . ': ' . $line;
1074             }
1075 5686 50       14584 if ( $config->{'debug'} ) {
1076 5686 50       20485 _warn( $line ) if $config->{'logtoerr'};
1077 5686         16991 logger()->log( { 'level' => 'debug' }, $line );
1078             }
1079 5686         3545623 return;
1080             }
1081              
1082             1;
1083              
1084             __END__
1085              
1086             =pod
1087              
1088             =encoding UTF-8
1089              
1090             =head1 NAME
1091              
1092             Mail::Milter::Authentication - A Perl Mail Authentication Milter
1093              
1094             =head1 VERSION
1095              
1096             version 20191206
1097              
1098             =head1 SYNOPSIS
1099              
1100             Subclass of Net::Server::PreFork for bringing up the main server process for authentication_milter.
1101              
1102             This class handles the server aspects of Authentication Milter.
1103              
1104             For individual Protocol handling please see the Mail::Milter::Authentication::Protocol::* classes
1105              
1106             For request handling please see Mail::Milter::Authentication::Handler
1107              
1108             Please see Net::Server docs for more detail of the server code.
1109              
1110             Please see the output of 'authentication_milter --help' for usage help.
1111              
1112             =head1 DESCRIPTION
1113              
1114             A Perl Implementation of email authentication standards rolled up into a single easy to use milter.
1115              
1116             =head1 METHODS
1117              
1118             =head2 preload_modules( $from, $matching )
1119              
1120             Preload (pre-fork) lazy loading modules.
1121              
1122             Takes a Package Name and a Base module, and loads all modules which match.
1123              
1124             =head2 I<write_to_log_hook()>
1125              
1126             Hook which runs to write logs
1127              
1128             =head2 I<idle_loop_hook()>
1129              
1130             Hook which runs in the master periodically.
1131              
1132             =head2 I<pre_loop_hook()>
1133              
1134             Hook which runs in the master before looping.
1135              
1136             =head2 I<run_n_children_hook()>
1137              
1138             Hook which runs in parent before it forks children.
1139              
1140             =head2 I<child_init_hook()>
1141              
1142             Hook which runs after forking, sets up per process items.
1143              
1144             =head2 I<child_finish_hook()>
1145              
1146             Hook which runs when the child is about to finish.
1147              
1148             =head2 I<pre_server_close_hook()>
1149              
1150             Hook which runs before the server closes.
1151              
1152             =head2 I<get_client_proto()>
1153              
1154             Get the protocol of the connecting client.
1155              
1156             =head2 I<get_client_port()>
1157              
1158             Get the port of the connecting client.
1159              
1160             =head2 I<get_client_host()>
1161              
1162             Get the host of the connecting client.
1163              
1164             =head2 I<get_client_path()>
1165              
1166             Get the path of the connecting client.
1167              
1168             =head2 I<get_client_details()>
1169              
1170             Get the details of the connecting client.
1171              
1172             =head2 I<process_request()>
1173              
1174             Hook which runs for each request, passes control to metrics handler or process_main as appropriate.
1175              
1176             =head2 I<process_main()>
1177              
1178             Method which runs for each request, sets up per request items and processes the request.
1179              
1180             =head2 I<fatal($error)>
1181              
1182             Log a fatal error and die in child
1183              
1184             =head2 I<fatal_global($error)>
1185              
1186             Log a fatal error and die in child and parent
1187              
1188             =head2 I<setup_handlers()>
1189              
1190             Setup the Handler objects.
1191              
1192             =head2 I<load_handler( $name )>
1193              
1194             Load the $name Handler module
1195              
1196             =head2 I<setup_handler( $name )>
1197              
1198             Setup the $name Handler object
1199              
1200             =head2 I<destroy_handler( $name )>
1201              
1202             Remove the $name Handler
1203              
1204             =head2 I<register_callback( $name, $callback )>
1205              
1206             Register the specified callback
1207              
1208             =head2 I<sort_all_callbacks()>
1209              
1210             Sort the callbacks into the order in which they must be called
1211              
1212             =head2 I<sort_callbacks( $callback )>
1213              
1214             Sort the callbacks for the $callback callback into the right order
1215              
1216             =head2 I<destroy_objects()>
1217              
1218             Remove references to all objects
1219              
1220             =head2 I<get_queue_id()>
1221              
1222             Return the queue ID (for logging) if possible.
1223              
1224             =head2 I<enable_extra_debugging()>
1225              
1226             Turn on extra debugging mode, will cause child to exit on close.
1227              
1228             =head2 I<extra_debugging( $line )>
1229              
1230             Cause $line to be written to log if extra debugging mode is enabled.
1231              
1232             =head2 I<logerror( $line )>
1233              
1234             Log to the error log.
1235              
1236             =head2 I<loginfo( $line )>
1237              
1238             Log to the info log.
1239              
1240             =head2 I<logdebug( $line )>
1241              
1242             Log to the debug log.
1243              
1244             =head1 FUNCTIONS
1245              
1246             =head2 I<get_installed_handlers()>
1247              
1248             Return an array ref of installed handler modules.
1249              
1250             =head2 I<get_valid_pid($pid_file)>
1251              
1252             Given a pid file, check for a valid process ID and return if valid.
1253              
1254             =head2 I<find_process()>
1255              
1256             Search the process table for an authentication_milter master process
1257              
1258             =head2 I<control($command)>
1259              
1260             Run a daemon command. Command can be one of start/restart/stop/status.
1261              
1262             =head2 I<start($hashref)>
1263              
1264             Start the server. This method does not return.
1265              
1266             $hashref = {
1267             'pid_file' => 'The pid file to use', #
1268             'daemon' => 1/0, # Daemonize process?
1269             }
1270              
1271             =head1 AUTHOR
1272              
1273             Marc Bradshaw <marc@marcbradshaw.net>
1274              
1275             =head1 COPYRIGHT AND LICENSE
1276              
1277             This software is copyright (c) 2018 by Marc Bradshaw.
1278              
1279             This is free software; you can redistribute it and/or modify it under
1280             the same terms as the Perl 5 programming language system itself.
1281              
1282             =cut