File Coverage

blib/lib/Mail/Milter/Authentication/Handler.pm
Criterion Covered Total %
statement 1132 1310 86.4
branch 325 472 68.8
condition 56 89 62.9
subroutine 136 148 91.8
pod 109 110 99.0
total 1758 2129 82.5


line stmt bran cond sub pod time code
1             package Mail::Milter::Authentication::Handler;
2 126     126   1708 use 5.20.0;
  126         463  
3 126     126   653 use strict;
  126         264  
  126         2527  
4 126     126   638 use warnings;
  126         210  
  126         2886  
5 126     126   702 use Mail::Milter::Authentication::Pragmas;
  126         258  
  126         828  
6             # ABSTRACT: Handler superclass
7             our $VERSION = '3.20230629'; # VERSION
8 126     126   84640 use Mail::Milter::Authentication::Exception;
  126         446  
  126         4063  
9 126     126   55332 use Mail::Milter::Authentication::Resolver;
  126         855  
  126         6155  
10 126     126   61612 use Date::Format qw{ time2str };
  126         948857  
  126         10955  
11 126     126   1317 use Digest::MD5 qw{ md5_hex };
  126         282  
  126         9092  
12 126     126   73362 use List::MoreUtils qw{ uniq };
  126         1746445  
  126         960  
13 126     126   202109 use Lock::File;
  126         1937837  
  126         7048  
14 126     126   62690 use MIME::Base64;
  126         80519  
  126         8199  
15 126     126   55686 use Mail::SPF;
  126         10248032  
  126         6955  
16 126     126   1239 use Net::DNS::Resolver;
  126         280  
  126         2982  
17 126     126   95592 use Net::IP;
  126         7475486  
  126         21217  
18 126     126   68772 use Proc::ProcessTable;
  126         412764  
  126         10046  
19 126     126   68430 use Sereal qw{encode_sereal decode_sereal};
  126         129509  
  126         8256  
20 126     126   979 use Sys::Hostname;
  126         486  
  126         7385  
21 126     126   820 use Time::HiRes qw{ ualarm gettimeofday };
  126         4650  
  126         1591  
22              
23              
24             our $TestResolver; # For Testing
25              
26              
27             sub new {
28 1018     1018 1 3814 my ( $class, $thischild ) = @_;
29 1018         3818 my $self = {
30             'thischild' => $thischild,
31             };
32 1018         4413 bless $self, $class;
33 1018         3331 return $self;
34             }
35              
36              
37             sub get_version {
38 234     234 1 696 my ( $self ) = @_;
39             {
40 126     126   30243 no strict 'refs'; ## no critic;
  126         385  
  126         1896001  
  234         488  
41 234   50     519 return ${ ref( $self ) . "::VERSION" } // 'unknown'; # no critic;
  234         5316  
42             }
43             }
44              
45              
46             sub get_json {
47 6     6 1 26 my ( $self, $file ) = @_;
48 6         19 my $basefile = __FILE__;
49 6         72 $basefile =~ s/Handler\.pm$/Handler\/$file/;
50 6         20 $basefile .= '.json';
51 6 50       204 if ( ! -e $basefile ) {
52 0         0 die 'json file ' . $file . ' not found';
53             }
54 6         349 open my $InF, '<', $basefile;
55 6         927 my @Content = <$InF>;
56 6         108 close $InF;
57 6         332 return join( q{}, @Content );
58             }
59              
60              
61             sub metric_register {
62 0     0 1 0 my ( $self, $id, $help ) = @_;
63 0         0 $self->{'thischild'}->{'metric'}->register( $id, $help, $self->{'thischild'} );
64             }
65              
66              
67             sub metric_count {
68 11322     11322 1 29493 my ( $self, $count_id, $labels, $count ) = @_;
69 11322 100       28613 $labels = {} if ! defined $labels;
70 11322 100       24892 $count = 1 if ! defined $count;
71              
72 11322         25970 my $metric = $self->{'thischild'}->{'metric'};
73 11322         48973 $metric->set_handler( $self );
74             $metric->count({
75             'count_id' => $count_id,
76             'labels' => $labels,
77 11322         67108 'server' => $self->{'thischild'},
78             'count' => $count,
79             });
80 11322         45243 $metric->set_handler( undef );
81             }
82              
83              
84             sub metric_set {
85 29     29 1 237 my ( $self, $gauge_id, $labels, $value ) = @_;
86 29 50       142 $labels = {} if ! defined $labels;
87 29 50       119 die 'Must set value in metric_set call' if ! defined $value;
88              
89 29         132 my $metric = $self->{'thischild'}->{'metric'};
90 29         240 $metric->set_handler( $self );
91             $metric->set({
92             'gauge_id' => $gauge_id,
93             'labels' => $labels,
94 29         400 'server' => $self->{'thischild'},
95             'value' => $value,
96             });
97 29         174 $metric->set_handler( undef );
98             }
99              
100              
101             sub metric_send {
102 0     0 1 0 my ( $self ) = @_;
103             # NOOP
104             # TODO Deprecate and remove
105             }
106              
107              
108             sub rbl_check_ip {
109 4     4 1 7710 my ( $self, $ip, $list ) = @_;
110              
111 4         8 my $lookup_ip;
112              
113             # Reverse the IP
114 4 100       12 if ( $ip->version() == 4 ) {
    50          
115 2         17 $lookup_ip = join( '.', reverse( split( /\./, $ip->ip() ) ) );
116             }
117             elsif ( $ip->version() == 6 ) {
118 2         27 my $ip_string = $ip->ip();
119 2         26 $ip_string =~ s/://g;
120 2         25 $lookup_ip = join( '.', reverse( split( '', $ip_string ) ) );
121             }
122              
123 4 50       29 return 0 if ! $lookup_ip;
124 4         12 return $self->rbl_check_domain( $lookup_ip, $list );
125             }
126              
127              
128             sub rbl_check_domain {
129 6     6 1 793 my ( $self, $domain, $list ) = @_;
130 6         69 my $resolver = $self->get_object( 'resolver' );
131 6         22 my $lookup = join( '.', $domain, $list );
132 6         56 my $packet = $resolver->query( $lookup, 'A' );
133              
134 6 100       1790 if ($packet) {
135 3         11 foreach my $rr ( $packet->answer ) {
136 3 50       27 if ( lc $rr->type eq 'a' ) {
137 3         44 return $rr->address();
138             }
139             }
140             }
141 3         19 return 0;
142             }
143              
144              
145             sub get_microseconds {
146 34929     34929 1 65531 my ( $self ) = @_;
147 34929         103448 my ($seconds, $microseconds) = gettimeofday;
148 34929         107366 return ( ( $seconds * 1000000 ) + $microseconds );
149             }
150              
151              
152             sub get_microseconds_since {
153 9953     9953 1 22027 my ( $self, $since ) = @_;
154 9953         22118 my $now = $self->get_microseconds();
155 9953         18833 my $elapsed = $now - $since;
156 9953 50       22895 $elapsed = 1 if $elapsed == 0; # Always return at least 1
157 9953         34847 return $elapsed;
158             }
159              
160             # Top Level Callbacks
161              
162              
163             sub register_metrics {
164             return {
165 43     43 1 528 'connect_total' => 'The number of connections made to authentication milter',
166             'callback_error_total' => 'The number of errors in callbacks',
167             'time_microseconds_total' => 'The time in microseconds spent in various handlers',
168             };
169             }
170              
171              
172             sub top_dequeue_callback {
173 3     3 1 15 my ( $self ) = @_;
174              
175 3         60 $self->status('dequeue');
176 3         283 $self->set_symbol('C','i','DEQUEUE.'.substr( uc md5_hex( "Authentication Milter Client $PID " . time() . rand(100) ) , -11 ));
177 3         22 $self->dbgout( 'CALLBACK', 'Dequeue', LOG_DEBUG );
178 3         27 my $config = $self->config();
179 3         7 eval {
180 3     0   196 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Dequeue callback timeout' }) };
  0         0  
181 3 50       45 if ( my $timeout = $self->get_type_timeout( 'dequeue' ) ) {
182 3         58 $self->set_alarm( $timeout );
183             }
184 3         40 my $callbacks = $self->get_callbacks( 'dequeue' );
185 3         50 foreach my $handler ( @$callbacks ) {
186 3         51 $self->dbgout( 'CALLBACK', 'Dequeue ' . $handler, LOG_DEBUG );
187 3         38 my $start_time = $self->get_microseconds();
188 3         61 $self->get_handler($handler)->dequeue_callback();
189 3         2355 $self->dbgoutwrite();
190 3         105 $self->metric_count( 'time_microseconds_total', { 'callback' => 'dequeue', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
191             }
192 3         22 $self->set_alarm(0);
193             };
194 3 50       31 if ( my $error = $@ ) {
195 0 0       0 if ( my $type = $self->is_exception_type( $error ) ) {
196 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'dequeue', 'type' => $type } );
197             }
198             else {
199 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'dequeue' } );
200             }
201             }
202 3         17 $self->dbgoutwrite();
203 3         22 $self->status('postdequeue');
204             }
205              
206              
207             sub top_setup_callback {
208              
209 88     88 1 441 my ( $self ) = @_;
210 88         1012 $self->status('setup');
211 88         911 $self->dbgout( 'CALLBACK', 'Setup', LOG_DEBUG );
212 88         1271 $self->set_return( $self->smfis_continue() );
213              
214 88         635 my $callbacks = $self->get_callbacks( 'setup' );
215 88         637 foreach my $handler ( @$callbacks ) {
216 143         1109 $self->dbgout( 'CALLBACK', 'Setup ' . $handler, LOG_DEBUG );
217 143         1303 my $start_time = $self->get_microseconds();
218 143         987 $self->get_handler($handler)->setup_callback();
219 143         2848 $self->metric_count( 'time_microseconds_total', { 'callback' => 'setup', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
220             }
221 88         642 $self->status('postsetup');
222             }
223              
224              
225             sub is_exception_type {
226 11029     11029 1 24602 my ( $self, $exception ) = @_;
227 11029 50       31491 return if ! defined $exception;
228 11029 100       33504 return if ! $exception;
229 171 100       916 return if ref $exception ne 'Mail::Milter::Authentication::Exception';
230 48   50     366 my $Type = $exception->{ 'Type' } || 'Unknown';
231 48         430 return $Type;
232             }
233              
234              
235             sub handle_exception {
236 11005     11005 1 28584 my ( $self, $exception ) = @_;
237 11005 50       32820 return if ! defined $exception;
238 11005         33084 my $Type = $self->is_exception_type( $exception );
239 11005 100       49081 return if ! $Type;
240 24 50       1144 die $exception if $Type eq 'Timeout';
241             #my $Text = $exception->{ 'Text' } || 'Unknown';
242             }
243              
244              
245             sub get_time_remaining {
246 242     242 1 604 my ( $self ) = @_;
247 242         793 my $top_handler = $self->get_top_handler();
248 242 50       991 return if ! exists $top_handler->{ 'timeout_at' };
249 242         795 my $now = $self->get_microseconds();
250 242         734 my $remaining = $top_handler->{ 'timeout_at' } - $now;
251             # can be zero or -ve
252 242         645 return $remaining;
253             }
254              
255              
256             sub set_alarm {
257 7582     7582 1 16604 my ( $self, $microseconds ) = @_;
258 7582         16118 my $top_handler = $self->get_top_handler();
259 7582         25106 $self->dbgout( 'Timeout set', $microseconds, LOG_DEBUG );
260 7582         71639 ualarm( $microseconds );
261 7582 100       27951 if ( $microseconds == 0 ) {
262 3974         82905 delete $top_handler->{ 'timeout_at' };
263             }
264             else {
265 3608         12083 $top_handler->{ 'timeout_at' } = $self->get_microseconds() + ( $microseconds );
266             }
267             }
268              
269              
270             sub set_handler_alarm {
271             # Call this in a handler to set a local alarm, will take the lower value
272             # of the microseconds passed in, or what is left of a higher level timeout.
273 126     126 1 548 my ( $self, $microseconds ) = @_;
274 126         882 my $remaining = $self->get_time_remaining();
275 126 50       631 if ( $remaining < $microseconds ) {
276             # This should already be set of course, but for clarity...
277 0         0 $self->dbgout( 'Handler timeout set (remaining used)', $remaining, LOG_DEBUG );
278 0         0 ualarm( $remaining );
279             }
280             else {
281 126         639 $self->dbgout( 'Handler timeout set', $microseconds, LOG_DEBUG );
282 126         1637 ualarm( $microseconds );
283             }
284             }
285              
286              
287             sub reset_alarm {
288             # Call this after any local handler timeouts to reset to the overall value remaining
289 116     116 1 441 my ( $self ) = @_;
290 116         438 my $remaining = $self->get_time_remaining();
291 116         895 $self->dbgout( 'Timeout reset', $remaining, LOG_DEBUG );
292 116 50       905 if ( $remaining < 1 ) {
293             # We have already timed out!
294 0         0 die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Reset check timeout' });
295             }
296 116         1602 ualarm( $remaining );
297             }
298              
299              
300             sub clear_overall_timeout {
301 40     40 1 202 my ( $self ) = @_;
302 40         200 $self->dbgout( 'Overall timeout', 'Clear', LOG_DEBUG );
303 40         279 my $top_handler = $self->get_top_handler();
304 40         258 delete $top_handler->{ 'overall_timeout' };
305             }
306              
307              
308             sub set_overall_timeout {
309 82     82 1 453 my ( $self, $microseconds ) = @_;
310 82         415 my $top_handler = $self->get_top_handler();
311 82         871 $self->dbgout( 'Overall timeout', $microseconds, LOG_DEBUG );
312 82         546 $top_handler->{ 'overall_timeout' } = $self->get_microseconds() + $microseconds;
313             }
314              
315              
316             sub get_type_timeout {
317 3998     3998 1 12470 my ( $self, $type ) = @_;
318              
319 3998         7498 my @log;
320 3998         11606 push @log, "Type: $type";
321              
322 3998         7419 my $effective;
323              
324             my $timeout;
325 3998         9870 my $config = $self->config();
326 3998 100       16536 if ( $config->{ $type . '_timeout' } ) {
327 3563         9230 $timeout = $config->{ $type . '_timeout' } * 1000000;
328 3563         6754 $effective = $timeout;
329 3563         9079 push @log, "Section: $timeout";
330             }
331              
332 3998         6674 my $remaining;
333 3998         9484 my $top_handler = $self->get_top_handler();
334 3998 100       11965 if ( my $overall_timeout = $top_handler->{ 'overall_timeout' } ) {
335 878         2246 my $now = $self->get_microseconds();
336 878         1710 $remaining = $overall_timeout - $now;
337 878         2688 push @log, "Overall: $remaining";
338 878 50       2857 if ( $remaining < 1 ) {
339 0         0 push @log, "Overall Timedout";
340 0         0 $remaining = 10; # arb low value;
341             }
342             }
343              
344 3998 100       9639 if ( $remaining ) {
345 878 100       2150 if ( $timeout ) {
346 833 100       1971 if ( $remaining < $timeout ) {
347 617         1328 $effective = $remaining;
348             }
349             }
350             else {
351 45         161 $effective = $remaining;
352             }
353             }
354              
355 3998 100       12423 push @log, "Effective: $effective" if $effective;
356              
357 3998         20524 $self->dbgout( 'Timeout set', join( ', ', @log ), LOG_DEBUG );
358              
359 3998         26404 return $effective;
360             }
361              
362              
363             sub check_timeout {
364 10321     10321 1 21300 my ( $self ) = @_;
365 10321         25031 my $top_handler = $self->get_top_handler();
366 10321 100       28516 return if ! exists $top_handler->{ 'timeout_at' };
367 10189 50       23443 return if $top_handler->{ 'timeout_at' } >= $self->get_microseconds();
368 0         0 delete $top_handler->{ 'timeout_at' };
369 0         0 ualarm( 0 );
370 0         0 die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Manual check timeout' });
371             }
372              
373             sub _remap_ip_and_helo {
374 530     530   1397 my ( $self ) = @_;
375              
376 530         1553 my $config = $self->config();
377 530 100       3200 if ( exists ( $config->{ 'ip_map' } ) ) {
378 140         452 my $ip_object = $self->{ 'raw_ip_object' };
379 140         397 my $helo_host = $self->{'raw_helo_name'};
380 140         306 foreach my $ip_map ( sort keys %{ $config->{ 'ip_map' } } ) {
  140         1049  
381 264         1179 my $map_obj = Net::IP->new( $ip_map );
382 264 50       240716 if ( !$map_obj ) {
383 0         0 $self->log_error( 'Core: Could not parse IP '.$ip_map );
384             }
385             else {
386 264   100     1977 my $is_overlap = $ip_object->overlaps($map_obj) || 0;
387 264 100 66     48903 if (
      66        
      66        
388             $is_overlap == $IP_A_IN_B_OVERLAP
389             || $is_overlap == $IP_B_IN_A_OVERLAP # Should never happen
390             || $is_overlap == $IP_PARTIAL_OVERLAP # Should never happen
391             || $is_overlap == $IP_IDENTICAL
392             )
393             {
394 16         72 my $mapped_to = $config->{ 'ip_map' }->{ $ip_map };
395 16 100 100     310 if ( $helo_host && exists $mapped_to->{helo_map} && exists $mapped_to->{helo_map}->{ $helo_host } ) {
      100        
396             # We have a specific HELO mapping for this!
397 2         10 $mapped_to = $mapped_to->{helo_map}->{ $helo_host };
398             return {
399             ip => Net::IP->new( $mapped_to->{ip} ),
400             helo => $mapped_to->{helo},
401 2         10 };
402             }
403             else {
404             # Remap based on IP Only
405             return {
406             ip => Net::IP->new( $mapped_to->{ip} ),
407             helo => $mapped_to->{helo},
408 14         83 };
409             }
410             }
411             }
412             }
413             }
414             }
415              
416              
417             sub remap_connect_callback {
418 268     268 1 94830 my ( $self, $hostname, $ip ) = @_;
419 268         1110 $self->{'raw_ip_object'} = $ip;
420 268         1403 my $ip_remap = $self->_remap_ip_and_helo();
421 268 100       7322 if ( $ip_remap ) {
422 8 50       38 if ( !$ip_remap->{ip} ) {
423 0         0 $self->log_error( 'Core: Ignored bad IP in remapping' );
424             }
425             else {
426 8         71 $ip = $ip_remap->{ip};
427 8         69 $self->dbgout( 'RemappedConnect', $self->{'raw_ip_object'}->ip() . ' > ' . $ip->ip(), LOG_DEBUG );
428             }
429             }
430 268         1399 $self->{'ip_object'} = $ip;
431             }
432              
433              
434             sub top_metrics_callback {
435 11     11 1 74 my ( $self ) = @_;
436 11         190 my $callbacks = $self->get_callbacks( 'metrics' );
437 11         219 foreach my $handler ( @$callbacks ) {
438 10         168 $self->dbgout( 'CALLBACK', 'Metrics ' . $handler, LOG_DEBUG );
439 10         35 eval{ $self->get_handler($handler)->metrics_callback(); };
  10         99  
440 10 50       7762 if ( my $error = $@ ) {
441 0         0 $self->handle_exception( $error );
442 0         0 $self->log_error( 'Metrics callback error ' . $error );
443             }
444             };
445             }
446              
447              
448             sub top_connect_callback {
449              
450             # On Connect
451 268     268 1 1126 my ( $self, $hostname, $ip ) = @_;
452 268         1329 $self->metric_count( 'connect_total' );
453 268         1876 $self->status('connect');
454 268         2121 $self->dbgout( 'CALLBACK', 'Connect', LOG_DEBUG );
455 268         1870 $self->set_return( $self->smfis_continue() );
456 268         1912 $self->clear_reject_mail();
457 268         1275 $self->clear_defer_mail();
458 268         1309 $self->clear_quarantine_mail();
459 268         987 my $config = $self->config();
460 268         774 eval {
461 268     1   7268 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Connect callback timeout' }) };
  1         158  
462 268 50       2150 if ( my $timeout = $self->get_type_timeout( 'connect' ) ) {
463 268         1316 $self->set_alarm( $timeout );
464             }
465              
466 268         1642 $self->dbgout( 'ConnectFrom', $ip->ip(), LOG_DEBUG );
467              
468 268         1718 my $callbacks = $self->get_callbacks( 'connect' );
469 268         1326 foreach my $handler ( @$callbacks ) {
470 351         1789 $self->dbgout( 'CALLBACK', 'Connect ' . $handler, LOG_DEBUG );
471 351         1383 my $start_time = $self->get_microseconds();
472 351         895 eval{ $self->get_handler($handler)->connect_callback( $hostname, $ip ); };
  351         1135  
473 351 100       1327 if ( my $error = $@ ) {
474 1         11 $self->handle_exception( $error );
475 0         0 $self->exit_on_close( 'Connect callback error ' . $error );
476 0         0 $self->tempfail_on_error();
477 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'connect', 'handler' => $handler } );
478             }
479 350         1987 $self->metric_count( 'time_microseconds_total', { 'callback' => 'connect', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
480 350         1612 $self->check_timeout();
481             }
482 267         1382 $self->set_alarm(0);
483             };
484 268 100       1868 if ( my $error = $@ ) {
485 1 50       5 if ( my $type = $self->is_exception_type( $error ) ) {
486 1         24 $self->metric_count( 'callback_error_total', { 'stage' => 'connect', 'type' => $type } );
487 1         46 $self->exit_on_close( 'Connect callback error ' . $type . ' - ' . $error->{ 'Text' } );
488             }
489             else {
490 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'connect' } );
491 0         0 $self->exit_on_close( 'Connect callback error ' . $error );
492             }
493 1         8 $self->tempfail_on_error();
494             }
495 268         1220 $self->status('postconnect');
496 268         1910 return $self->get_return();
497             }
498              
499              
500             sub remap_helo_callback {
501 262     262 1 1000 my ( $self, $helo_host ) = @_;
502 262 50       1306 if ( !( $self->{'helo_name'} ) ) {
503              
504 262         1015 $self->{'raw_helo_name'} = $helo_host;
505 262         877 my $ip_remap = $self->_remap_ip_and_helo();
506 262 100       7228 if ( $ip_remap ) {
507 8         31 my $ip = $ip_remap->{ip};
508 8 100       39 if ( $self->{'ip_object'}->ip() ne $ip_remap->{ip}->ip() ) {
509             # The mapped IP has been changed based on the HELO host received
510 2         32 $self->{'ip_object'} = $ip;
511 2         9 $self->dbgout( 'RemappedConnectHELO', $self->{'ip_object'}->ip() . ' > ' . $ip->ip(), LOG_DEBUG );
512             }
513 8         100 $helo_host = $ip_remap->{helo};
514 8         58 $self->dbgout( 'RemappedHELO', $self->{'raw_helo_name'} . ' > ' . $helo_host, LOG_DEBUG );
515             }
516              
517 262         1429 $self->{'helo_name'} = $helo_host;
518             }
519             }
520              
521              
522             sub top_helo_callback {
523              
524             # On HELO
525 262     262 1 1117 my ( $self, $helo_host ) = @_;
526 262         991 $self->status('helo');
527 262         2127 $self->dbgout( 'CALLBACK', 'Helo', LOG_DEBUG );
528 262         1665 $self->set_return( $self->smfis_continue() );
529 262 50       1452 $helo_host = q{} if ! defined $helo_host;
530 262         1054 my $config = $self->config();
531 262         797 eval {
532 262     1   5752 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'HELO callback timeout' }) };
  1         488  
533 262 50       2030 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
534 262         1155 $self->set_alarm( $timeout );
535             }
536              
537             # Take only the first HELO from a connection
538 262 50       2018 if ( !( $self->{'seen_helo_name'} ) ) {
539 262         1308 $self->{'seen_helo_name'} = $helo_host;
540              
541 262         1041 my $callbacks = $self->get_callbacks( 'helo' );
542 262         1258 foreach my $handler ( @$callbacks ) {
543 386         1923 $self->dbgout( 'CALLBACK', 'Helo ' . $handler, LOG_DEBUG );
544 386         1667 my $start_time = $self->get_microseconds();
545 386         967 eval{ $self->get_handler($handler)->helo_callback($helo_host); };
  386         1366  
546 386 100       1551 if ( my $error = $@ ) {
547 1         16 $self->handle_exception( $error );
548 0         0 $self->exit_on_close( 'HELO callback error ' . $error );
549 0         0 $self->tempfail_on_error();
550 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'helo', 'handler' => $handler } );
551             }
552 385         2149 $self->metric_count( 'time_microseconds_total', { 'callback' => 'helo', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
553 385         1704 $self->check_timeout();
554             }
555             }
556             else {
557 0         0 $self->dbgout('Multiple HELO callbacks detected and ignored', $self->{'seen_helo_name'} . ' / ' . $helo_host, LOG_DEBUG );
558             }
559              
560 261         1416 $self->set_alarm(0);
561             };
562 262 100       1859 if ( my $error = $@ ) {
563 1 50       10 if ( my $type = $self->is_exception_type( $error ) ) {
564 1         19 $self->metric_count( 'callback_error_total', { 'stage' => 'helo', 'type' => $type } );
565 1         16 $self->exit_on_close( 'HELO error ' . $type . ' - ' . $error->{ 'Text' } );
566             }
567             else {
568 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'helo' } );
569 0         0 $self->exit_on_close( 'HELO callback error ' . $error );
570             }
571 1         9 $self->tempfail_on_error();
572             }
573 262         1163 $self->status('posthelo');
574 262         1705 return $self->get_return();
575             }
576              
577              
578             sub top_envfrom_callback {
579              
580             # On MAILFROM
581             #...
582 261     261 1 1054 my ( $self, $env_from, @params ) = @_;
583 261         1050 $self->status('envfrom');
584 261         2125 $self->dbgout( 'CALLBACK', 'EnvFrom', LOG_DEBUG );
585 261         1663 $self->set_return( $self->smfis_continue() );
586 261 100       1495 $env_from = q{} if ! defined $env_from;
587 261         959 my $config = $self->config();
588 261         895 eval {
589 261     1   5559 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EnvFrom callback timeout' }) };
  1         39  
590 261 50       2049 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
591 261         1132 $self->set_alarm( $timeout );
592             }
593              
594             # Reset private data for this MAIL transaction
595 261         1512 delete $self->{'auth_headers'};
596 261         1042 delete $self->{'pre_headers'};
597 261         668 delete $self->{'add_headers'};
598 261         803 delete $self->{'suppress_error_emails'};
599              
600 261         1036 my $callbacks = $self->get_callbacks( 'envfrom' );
601 261         1292 foreach my $handler ( @$callbacks ) {
602 828         4015 $self->dbgout( 'CALLBACK', 'EnvFrom ' . $handler, LOG_DEBUG );
603 828         3100 my $start_time = $self->get_microseconds();
604 828         1889 eval { $self->get_handler($handler)->envfrom_callback($env_from, @params); };
  828         2613  
605 828 100       2836 if ( my $error = $@ ) {
606 1         15 $self->handle_exception( $error );
607 0         0 $self->exit_on_close( 'Env From callback error ' . $error );
608 0         0 $self->tempfail_on_error();
609 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'envfrom', 'handler' => $handler } );
610             }
611 827         4260 $self->metric_count( 'time_microseconds_total', { 'callback' => 'envfrom', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
612 827         3473 $self->check_timeout();
613             }
614 260         1560 $self->set_alarm(0);
615             };
616 261 100       1699 if ( my $error = $@ ) {
617 1 50       15 if ( my $type = $self->is_exception_type( $error ) ) {
618 1         155 $self->metric_count( 'callback_error_total', { 'stage' => 'envfrom', 'type' => $type } );
619 1         27 $self->exit_on_close( 'Env From error ' . $type . ' - ' . $error->{ 'Text' } );
620             }
621             else {
622 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'envfrom' } );
623 0         0 $self->exit_on_close( 'Env From callback error ' . $error );
624             }
625 1         12 $self->tempfail_on_error();
626             }
627 261         1664 $self->status('postenvfrom');
628 261         1317 return $self->get_return();
629             }
630              
631              
632             sub top_envrcpt_callback {
633              
634             # On RCPTTO
635             #...
636 260     260 1 1094 my ( $self, $env_to, @params ) = @_;
637 260         921 $self->status('envrcpt');
638 260         1915 $self->dbgout( 'CALLBACK', 'EnvRcpt', LOG_DEBUG );
639 260         1860 $self->set_return( $self->smfis_continue() );
640 260 50       1415 $env_to = q{} if ! defined $env_to;
641 260         916 my $config = $self->config();
642 260         922 eval {
643 260     1   5572 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EnvRcpt callback timeout' }) };
  1         91  
644 260 50       1911 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
645 260         1126 $self->set_alarm( $timeout );
646             }
647              
648 260         1567 my $callbacks = $self->get_callbacks( 'envrcpt' );
649 260         1230 foreach my $handler ( @$callbacks ) {
650 122         802 $self->dbgout( 'CALLBACK', 'EnvRcpt ' . $handler, LOG_DEBUG );
651 122         755 my $start_time = $self->get_microseconds();
652 122         517 eval{ $self->get_handler($handler)->envrcpt_callback($env_to, @params); };
  122         530  
653 122 100       638 if ( my $error = $@ ) {
654 1         16 $self->handle_exception( $error );
655 0         0 $self->exit_on_close( 'Env Rcpt callback error ' . $error );
656 0         0 $self->tempfail_on_error();
657 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'rcptto', 'handler' => $handler } );
658             }
659 121         1156 $self->metric_count( 'time_microseconds_total', { 'callback' => 'rcptto', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
660 121         848 $self->check_timeout();
661             }
662 259         1347 $self->set_alarm(0);
663             };
664 260 100       1882 if ( my $error = $@ ) {
665 1 50       33 if ( my $type = $self->is_exception_type( $error ) ) {
666 1         20 $self->metric_count( 'callback_error_total', { 'stage' => 'rcptto', 'type' => $type } );
667 1         29 $self->exit_on_close( 'Env Rcpt callback error ' . $type . ' - ' . $error->{ 'Text' } );
668             }
669             else {
670 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'rcptto' } );
671 0         0 $self->exit_on_close( 'Env Rcpt callback error ' . $error );
672             }
673 1         12 $self->tempfail_on_error();
674             }
675 260         1304 $self->status('postenvrcpt');
676 260         1666 return $self->get_return();
677             }
678              
679              
680             sub top_header_callback {
681              
682             # On Each Header
683 1577     1577 1 4902 my ( $self, $header, $value, $original ) = @_;
684 1577         5046 $self->status('header');
685 1577         8812 $self->dbgout( 'CALLBACK', 'Header', LOG_DEBUG );
686 1577         5825 $self->set_return( $self->smfis_continue() );
687 1577 50       4382 $value = q{} if ! defined $value;
688 1577         3896 my $config = $self->config();
689              
690 1577 50 33     5488 if ( $header eq 'X-Authentication-Milter-Error' && $value eq 'Generated Error Report' ) {
691 0         0 $self->{'suppress_error_emails'} = 1;
692             }
693              
694 1577         3294 eval {
695 1577     15   30473 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Header callback timeout' }) };
  15         570  
696 1577 50       7591 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
697 1577         4687 $self->set_alarm( $timeout );
698             }
699 1577 50       6035 if ( my $error = $@ ) {
700 0         0 $self->dbgout( 'inline error $error', '', LOG_DEBUG );
701             }
702              
703 1577         5217 my $callbacks = $self->get_callbacks( 'header' );
704 1577         4824 foreach my $handler ( @$callbacks ) {
705 5617         23650 $self->dbgout( 'CALLBACK', 'Header ' . $handler, LOG_DEBUG );
706 5617         16912 my $start_time = $self->get_microseconds();
707 5617         11107 eval{ $self->get_handler($handler)->header_callback( $header, $value, $original ); };
  5617         14213  
708 5617 100       16081 if ( my $error = $@ ) {
709 15         240 $self->handle_exception( $error );
710 0         0 $self->exit_on_close( 'Header callback error ' . $error );
711 0         0 $self->tempfail_on_error();
712 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'header', 'handler' => $handler } );
713             }
714 5602         25307 $self->metric_count( 'time_microseconds_total', { 'callback' => 'header', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
715 5602         19780 $self->check_timeout();
716             }
717 1562         4545 $self->set_alarm(0);
718             };
719 1577 100       6991 if ( my $error = $@ ) {
720 15 50       116 if ( my $type = $self->is_exception_type( $error ) ) {
721 15         338 $self->metric_count( 'callback_error_total', { 'stage' => 'header', 'type' => $type } );
722 15         399 $self->exit_on_close( 'Header error ' . $type . ' - ' . $error->{ 'Text' } );
723             }
724             else {
725 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'header' } );
726 0         0 $self->exit_on_close( 'Header callback error ' . $error );
727             }
728 15         108 $self->tempfail_on_error();
729             }
730 1577         6088 $self->status('postheader');
731 1577         6645 return $self->get_return();
732             }
733              
734              
735             sub top_eoh_callback {
736              
737             # On End of headers
738 258     258 1 1044 my ($self) = @_;
739 258         1055 $self->status('eoh');
740 258         2271 $self->dbgout( 'CALLBACK', 'EOH', LOG_DEBUG );
741 258         1756 $self->set_return( $self->smfis_continue() );
742 258         1211 my $config = $self->config();
743 258         813 eval {
744 258     1   5858 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EOH callback timeout' }) };
  1         35  
745 258 50       1773 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
746 258         1205 $self->set_alarm( $timeout );
747             }
748              
749 258         1527 my $callbacks = $self->get_callbacks( 'eoh' );
750 258         1362 foreach my $handler ( @$callbacks ) {
751 390         2117 $self->dbgout( 'CALLBACK', 'EOH ' . $handler, LOG_DEBUG );
752 390         1897 my $start_time = $self->get_microseconds();
753 390         957 eval{ $self->get_handler($handler)->eoh_callback(); };
  390         1418  
754 390 100       6361 if ( my $error = $@ ) {
755 1         18 $self->handle_exception( $error );
756 0         0 $self->exit_on_close( 'EOH callback error ' . $error );
757 0         0 $self->tempfail_on_error();
758 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'eoh', 'handler' => $handler } );
759             }
760 389         2354 $self->metric_count( 'time_microseconds_total', { 'callback' => 'eoh', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
761 389         1894 $self->check_timeout();
762             }
763 257         1391 $self->set_alarm(0);
764             };
765 258 100       1846 if ( my $error = $@ ) {
766 1 50       47 if ( my $type = $self->is_exception_type( $error ) ) {
767 1         41 $self->metric_count( 'callback_error_total', { 'stage' => 'eoh', 'type' => $type } );
768 1         17 $self->exit_on_close( 'EOH error ' . $type . ' - ' . $error->{ 'Text' } );
769             }
770             else {
771 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'eoh' } );
772 0         0 $self->exit_on_close( 'EOH callback error ' . $error );
773             }
774 1         7 $self->tempfail_on_error();
775             }
776 258         1605 $self->dbgoutwrite();
777 258         1415 $self->status('posteoh');
778 258         1772 return $self->get_return();
779             }
780              
781              
782             sub top_body_callback {
783              
784             # On each body chunk
785 256     256 1 1074 my ( $self, $body_chunk ) = @_;
786 256         1067 $self->status('body');
787 256         1971 $self->dbgout( 'CALLBACK', 'Body', LOG_DEBUG );
788 256         1908 $self->set_return( $self->smfis_continue() );
789 256         1022 my $config = $self->config();
790 256         779 eval {
791 256     1   6102 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Body callback timeout' }) };
  1         36  
792 256 50       2115 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
793 256         1328 $self->set_alarm( $timeout );
794             }
795              
796 256         1578 my $callbacks = $self->get_callbacks( 'body' );
797 256         1424 foreach my $handler ( @$callbacks ) {
798 267         1574 $self->dbgout( 'CALLBACK', 'Body ' . $handler, LOG_DEBUG );
799 267         1378 my $start_time = $self->get_microseconds();
800 267         878 eval{ $self->get_handler($handler)->body_callback( $body_chunk ); };
  267         1165  
801 267 100       1573 if ( my $error = $@ ) {
802 1         18 $self->handle_exception( $error );
803 0         0 $self->exit_on_close( 'Body callback error ' . $error );
804 0         0 $self->tempfail_on_error();
805 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'body', 'handler' => $handler } );
806             }
807 266         1993 $self->metric_count( 'time_microseconds_total', { 'callback' => 'body', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
808 266         1215 $self->check_timeout();
809             }
810 255         1299 $self->set_alarm(0);
811             };
812 256 100       1897 if ( my $error = $@ ) {
813 1 50       15 if ( my $type = $self->is_exception_type( $error ) ) {
814 1         107 $self->metric_count( 'callback_error_total', { 'stage' => 'body', 'type' => $type } );
815 1         22 $self->exit_on_close( 'Body error ' . $type . ' - ' . $error->{ 'Text' } );
816             }
817             else {
818 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'body' } );
819 0         0 $self->exit_on_close( 'Body callback error ' . $error );
820             }
821 1         12 $self->tempfail_on_error();
822             }
823 256         1244 $self->dbgoutwrite();
824 256         1695 $self->status('postbody');
825 256         1724 return $self->get_return();
826             }
827              
828              
829             sub top_eom_callback {
830              
831             # On End of Message
832 258     258 1 994 my ($self) = @_;
833 258         1020 $self->status('eom');
834 258         2140 $self->dbgout( 'CALLBACK', 'EOM', LOG_DEBUG );
835 258         1771 $self->set_return( $self->smfis_continue() );
836 258         1220 my $config = $self->config();
837 258         851 eval {
838 258     1   5718 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EOM callback timeout' }) };
  1         32  
839 258 50       1895 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
840 258         1243 $self->set_alarm( $timeout );
841             }
842              
843 258         1741 my $callbacks = $self->get_callbacks( 'eom' );
844 258         1435 foreach my $handler ( @$callbacks ) {
845 513         2864 $self->dbgout( 'CALLBACK', 'EOM ' . $handler, LOG_DEBUG );
846 513         2230 my $start_time = $self->get_microseconds();
847 513         1341 eval{ $self->get_handler($handler)->eom_callback(); };
  513         1769  
848 513 100       2205 if ( my $error = $@ ) {
849 1         14 $self->handle_exception( $error );
850 0         0 $self->exit_on_close( 'EOM callback error ' . $error );
851 0         0 $self->tempfail_on_error();
852 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'eom', 'handler' => $handler } );
853             }
854 512         2894 $self->metric_count( 'time_microseconds_total', { 'callback' => 'eom', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
855 512         2466 $self->check_timeout();
856             }
857 257         1487 $self->set_alarm(0);
858             };
859 258 100       1798 if ( my $error = $@ ) {
860 1 50       58 if ( my $type = $self->is_exception_type( $error ) ) {
861 1         41 $self->metric_count( 'callback_error_total', { 'stage' => 'eom', 'type' => $type } );
862 1         11 $self->exit_on_close( 'EOM error ' . $type . ' - ' . $error->{ 'Text' } );
863             }
864             else {
865 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'eom' } );
866 0         0 $self->exit_on_close( 'EOM callback error ' . $error );
867             }
868 1         6 $self->tempfail_on_error();
869             }
870             #$self->apply_policy();
871 258         1656 $self->add_headers();
872 258         1200 $self->dbgoutwrite();
873 258         1588 $self->status('posteom');
874 258         1690 return $self->get_return();
875             }
876              
877              
878       0 1   sub apply_policy {
879             #my ($self) = @_;
880              
881             #my @auth_headers;
882             #my $top_handler = $self->get_top_handler();
883             #if ( exists( $top_handler->{'c_auth_headers'} ) ) {
884             # @auth_headers = @{ $top_handler->{'c_auth_headers'} };
885             #}
886             #if ( exists( $top_handler->{'auth_headers'} ) ) {
887             # @auth_headers = ( @auth_headers, @{ $top_handler->{'auth_headers'} } );
888             #}
889              
890             #my $parsed_headers = Mail::AuthenticationResults::Parser->new( \@auth_headers );
891              
892             #use Data::Dumper;
893             #print Dumper \@structured_headers;
894             }
895              
896              
897             sub top_abort_callback {
898              
899             # On any out of our control abort
900 33     33 1 123 my ($self) = @_;
901 33         140 $self->status('abort');
902 33         231 $self->dbgout( 'CALLBACK', 'Abort', LOG_DEBUG );
903 33         208 $self->set_return( $self->smfis_continue() );
904 33         114 my $config = $self->config();
905 33         83 eval {
906 33     1   848 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Abord callback timeout' }) };
  1         25  
907 33 50       335 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
908 33         150 $self->set_alarm( $timeout );
909             }
910              
911 33         275 my $callbacks = $self->get_callbacks( 'abort' );
912 33         177 foreach my $handler ( @$callbacks ) {
913 1         8 $self->dbgout( 'CALLBACK', 'Abort ' . $handler, LOG_DEBUG );
914 1         4 my $start_time = $self->get_microseconds();
915 1         5 eval{ $self->get_handler($handler)->abort_callback(); };
  1         6  
916 1 50       14 if ( my $error = $@ ) {
917 1         11 $self->handle_exception( $error );
918 0         0 $self->exit_on_close( 'Abort callback error ' . $error );
919 0         0 $self->tempfail_on_error();
920 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'abort', 'handler' => $handler } );
921             }
922 0         0 $self->metric_count( 'time_microseconds_total', { 'callback' => 'abort', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
923 0         0 $self->check_timeout();
924             }
925 32         108 $self->set_alarm(0);
926             };
927 33 100       200 if ( my $error = $@ ) {
928 1 50       6 if ( my $type = $self->is_exception_type( $error ) ) {
929 1         31 $self->metric_count( 'callback_error_total', { 'stage' => 'abort', 'type' => $type } );
930 1         13 $self->exit_on_close( 'Abort error ' . $type . ' - ' . $error->{ 'Text' } );
931             }
932             else {
933 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'abort' } );
934 0         0 $self->exit_on_close( 'Abort callback error ' . $error );
935             }
936 1         7 $self->tempfail_on_error();
937             }
938 33         164 $self->status('postabort');
939 33         193 return $self->get_return();
940             }
941              
942              
943             sub top_close_callback {
944              
945             # On end of connection
946 130     130 1 424 my ($self) = @_;
947 130         577 $self->status('close');
948 130         835 $self->dbgout( 'CALLBACK', 'Close', LOG_DEBUG );
949 130         765 $self->set_return( $self->smfis_continue() );
950 130         713 $self->clear_reject_mail();
951 130         573 $self->clear_defer_mail();
952 130         520 $self->clear_quarantine_mail();
953 130         429 my $config = $self->config();
954 130         408 eval {
955 130     1   2766 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Close callback timeout' }) };
  1         21  
956 130 50       902 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
957 130         506 $self->set_alarm( $timeout );
958             }
959              
960 130         736 my $callbacks = $self->get_callbacks( 'close' );
961 130         711 foreach my $handler ( @$callbacks ) {
962 1182         5311 $self->dbgout( 'CALLBACK', 'Close ' . $handler, LOG_DEBUG );
963 1182         3509 my $start_time = $self->get_microseconds();
964 1182         2380 eval{ $self->get_handler($handler)->close_callback(); };
  1182         3199  
965 1182 100       3667 if ( my $error = $@ ) {
966 1         9 $self->handle_exception( $error );
967 0         0 $self->exit_on_close( 'Close callback error ' . $error );
968 0         0 $self->tempfail_on_error();
969 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'close', 'handler' => $handler } );
970             }
971 1181         5687 $self->metric_count( 'time_microseconds_total', { 'callback' => 'close', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
972 1181         5026 $self->check_timeout();
973              
974 1181         2915 my $handler_object = $self->get_handler($handler);
975 1181         4633 foreach my $key ( sort keys $handler_object->%* ) {
976 1181 50       4930 next if $key eq 'thischild';
977 0         0 $self->exit_on_close( 'Handler '.$handler.' did not clean up data for key '.$key.' in close callback' );
978             }
979             }
980 129         666 $self->set_alarm(0);
981             };
982 130 100       813 if ( my $error = $@ ) {
983 1 50       5 if ( my $type = $self->is_exception_type( $error ) ) {
984 1         11 $self->metric_count( 'callback_error_total', { 'stage' => 'close', 'type' => $type } );
985 1         13 $self->exit_on_close( 'Close error ' . $type . ' - ' . $error->{ 'Text' } );
986             }
987             else {
988 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'close' } );
989 0         0 $self->exit_on_close( 'Close callback error ' . $error );
990             }
991 1         10 $self->tempfail_on_error();
992             }
993 130         508 delete $self->{'helo_name'};
994 130         418 delete $self->{'seen_helo_name'};
995 130         384 delete $self->{'raw_helo_name'};
996 130         1475 delete $self->{'c_auth_headers'};
997 130         2135 delete $self->{'auth_headers'};
998 130         574 delete $self->{'pre_headers'};
999 130         335 delete $self->{'add_headers'};
1000 130         404 delete $self->{'ip_object'};
1001 130         790 delete $self->{'raw_ip_object'};
1002 130         684 $self->dbgoutwrite();
1003 130         1039 $self->clear_all_symbols();
1004 130         559 $self->status('postclose');
1005 130         1008 return $self->get_return();
1006             }
1007              
1008              
1009             sub top_addheader_callback {
1010 432     432 1 1210 my ( $self ) = @_;
1011 432         1308 my $config = $self->config();
1012              
1013 432         1067 eval {
1014 432     0   9085 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'AddHeader callback timeout' }) };
  0         0  
1015 432 100       2612 if ( my $timeout = $self->get_type_timeout( 'addheader' ) ) {
1016 42         171 $self->set_alarm( $timeout );
1017             }
1018              
1019 432         1743 my $callbacks = $self->get_callbacks( 'addheader' );
1020 432         1386 foreach my $handler ( @$callbacks ) {
1021 174         561 my $start_time = $self->get_microseconds();
1022 174         759 $self->get_handler($handler)->addheader_callback($self);
1023 174         1166 $self->metric_count( 'time_microseconds_total', { 'callback' => 'addheader', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
1024 174         989 $self->check_timeout();
1025             }
1026 432         1732 $self->set_alarm(0);
1027             };
1028 432 50       4071 if ( my $error = $@ ) {
1029 0 0       0 if ( my $type = $self->is_exception_type( $error ) ) {
1030 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'addheader', 'type' => $type } );
1031 0         0 $self->exit_on_close( 'AddHeader error ' . $type . ' - ' . $error->{ 'Text' } );
1032             }
1033             else {
1034 0         0 $self->metric_count( 'callback_error_total', { 'stage' => 'addheader' } );
1035 0         0 $self->exit_on_close( 'AddHeader callback error ' . $error );
1036             }
1037 0         0 $self->tempfail_on_error();
1038             }
1039             }
1040              
1041              
1042             # Other methods
1043              
1044              
1045             sub status {
1046 7308     7308 1 19419 my ($self, $status) = @_;
1047 7308         18049 my $count = $self->{'thischild'}->{'count'};
1048 7308 100       19305 if ( exists ( $self->{'thischild'}->{'smtp'} ) ) {
1049 1666 50       4847 if ( $self->{'thischild'}->{'smtp'}->{'count'} ) {
1050 1666         4390 $count .= '.' . $self->{'thischild'}->{'smtp'}->{'count'};
1051             }
1052             }
1053 7308 50       16499 if ( $status ) {
1054 7308         72275 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':processing:' . $status . '(' . $count . ')';
1055             }
1056             else {
1057 0         0 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':processing(' . $count . ')';
1058             }
1059             }
1060              
1061              
1062             sub config {
1063 68102     68102 1 139491 my ($self) = @_;
1064 68102         179123 return $self->{'thischild'}->{'config'};
1065             }
1066              
1067              
1068             sub handler_config {
1069 3317     3317 1 7936 my ($self) = @_;
1070 3317         9972 my $type = $self->handler_type();
1071 3317 50       10725 return if ! $type;
1072 3317 50       10690 if ( $self->is_handler_loaded( $type ) ) {
1073 3317         7988 my $config = $self->config();
1074 3317         8650 my $handler_config = $config->{'handlers'}->{$type};
1075              
1076 3317 50       10082 if ( exists( $config->{ '_external_callback_processor' } ) ) {
1077 0 0       0 if ( $config->{ '_external_callback_processor' }->can( 'handler_config' ) ) {
1078 0         0 $handler_config = clone $handler_config;
1079 0         0 $config->{ '_external_callback_processor' }->handler_config( $type, $handler_config );
1080             }
1081             }
1082              
1083 3317         13184 return $handler_config;
1084             }
1085             }
1086              
1087              
1088             sub handler_type {
1089 3317     3317 1 7273 my ($self) = @_;
1090 3317         8485 my $type = ref $self;
1091 3317 50       25515 if ( $type eq 'Mail::Milter::Authentication::Handler' ) {
    50          
1092 0         0 return 'Handler';
1093             }
1094             elsif ( $type =~ /^Mail::Milter::Authentication::Handler::(.*)/ ) {
1095 3317         11690 my $handler_type = $1;
1096 3317         11390 return $handler_type;
1097             }
1098             else {
1099 0         0 return undef; ## no critic
1100             }
1101             }
1102              
1103              
1104             sub set_return {
1105 3675     3675 1 8264 my ( $self, $return ) = @_;
1106 3675         8807 my $top_handler = $self->get_top_handler();
1107 3675         8950 $top_handler->{'return_code'} = $return;
1108             }
1109              
1110              
1111             sub get_return {
1112 3563     3563 1 8558 my ( $self ) = @_;
1113 3563         8758 my $top_handler = $self->get_top_handler();
1114 3563 100       9866 if ( defined $self->get_reject_mail() ) {
    100          
    100          
1115 3         39 return $self->smfis_reject();
1116             }
1117             elsif ( defined $self->get_defer_mail() ) {
1118 9         40 return $self->smfis_tempfail();
1119             }
1120             elsif ( defined $self->get_quarantine_mail() ) {
1121             ## TODO Implement this.
1122             }
1123 3551         19075 return $top_handler->{'return_code'};
1124             }
1125              
1126              
1127             sub get_reject_mail {
1128 4454     4454 1 8923 my ( $self ) = @_;
1129 4454         9134 my $top_handler = $self->get_top_handler();
1130 4454         18112 return $top_handler->{'reject_mail'};
1131             }
1132              
1133              
1134             sub clear_reject_mail {
1135 400     400 1 1402 my ( $self ) = @_;
1136 400         1224 my $top_handler = $self->get_top_handler();
1137 400         1459 delete $top_handler->{'reject_mail'};
1138             }
1139              
1140              
1141             sub get_defer_mail {
1142 4449     4449 1 9209 my ( $self ) = @_;
1143 4449         9066 my $top_handler = $self->get_top_handler();
1144 4449         15499 return $top_handler->{'defer_mail'};
1145             }
1146              
1147              
1148             sub clear_defer_mail {
1149 398     398 1 1148 my ( $self ) = @_;
1150 398         1014 my $top_handler = $self->get_top_handler();
1151 398         1099 delete $top_handler->{'defer_mail'};
1152             }
1153              
1154              
1155              
1156             sub get_quarantine_mail {
1157 4739     4739 1 9461 my ( $self ) = @_;
1158 4739         9302 my $top_handler = $self->get_top_handler();
1159 4739         15290 return $top_handler->{'quarantine_mail'};
1160             }
1161              
1162              
1163             sub clear_quarantine_mail {
1164 398     398 1 1118 my ( $self ) = @_;
1165 398         949 my $top_handler = $self->get_top_handler();
1166 398         1087 delete $top_handler->{'quarantine_mail'};
1167             }
1168              
1169              
1170             sub get_top_handler {
1171 121732     121732 1 223593 my ($self) = @_;
1172 121732         216527 my $thischild = $self->{'thischild'};
1173 121732         219612 my $object = $thischild->{'handler'}->{'_Handler'};
1174 121732         223683 return $object;
1175             }
1176              
1177              
1178             sub is_handler_loaded {
1179 12139     12139 1 25845 my ( $self, $name ) = @_;
1180 12139         26885 my $config = $self->config();
1181 12139 100       35721 if ( exists ( $config->{'handlers'}->{$name} ) ) {
1182 9136         29716 return 1;
1183             }
1184 3003         14693 return 0;
1185             }
1186              
1187              
1188             sub get_handler {
1189 17082     17082 1 36770 my ( $self, $name ) = @_;
1190 17082         30470 my $thischild = $self->{'thischild'};
1191 17082         37336 my $object = $thischild->{'handler'}->{$name};
1192 17082         101343 return $object;
1193             }
1194              
1195              
1196             sub get_callbacks {
1197 4097     4097 1 10511 my ( $self, $callback ) = @_;
1198 4097         8560 my $thischild = $self->{'thischild'};
1199 4097         14184 return $thischild->{'callbacks_list'}->{$callback};
1200             }
1201              
1202              
1203             sub set_object_maker {
1204 142     142 1 755 my ( $self, $name, $ref ) = @_;
1205 142         828 my $thischild = $self->{'thischild'};
1206 142 100       999 return if $thischild->{'object_maker'}->{$name};
1207 20         214 $thischild->{'object_maker'}->{$name} = $ref;
1208             }
1209              
1210              
1211             sub get_object {
1212 1827     1827 1 5545 my ( $self, $name ) = @_;
1213              
1214 1827         4144 my $thischild = $self->{'thischild'};
1215 1827         4984 my $object = $thischild->{'object'}->{$name};
1216 1827 100       4946 if ( ! $object ) {
1217              
1218 526 100       3145 if ( exists( $thischild->{'object_maker'}->{$name} ) ) {
    100          
1219 56         239 my $maker = $thischild->{'object_maker'}->{$name};
1220 56         311 &$maker( $self, $name );
1221             }
1222              
1223             elsif ( $name eq 'resolver' ) {
1224 154         981 $self->dbgout( 'Object created', $name, LOG_DEBUG );
1225 154 50       1329 if ( defined $TestResolver ) {
1226 154         1218 $object = $TestResolver;
1227 154         5036 warn "Using FAKE TEST DNS Resolver - I Hope this isn't production!";
1228             # If it is you better know what you're doing!
1229             }
1230             else {
1231 0         0 my $config = $self->config();
1232 0         0 my %args;
1233 0         0 $args{_handler} = $self;
1234 0   0     0 $args{udp_timeout} = $config->{'dns_timeout'} || 8;
1235 0   0     0 $args{tcp_timeout} = $config->{'dns_timeout'} || 8;
1236 0   0     0 $args{retry} = $config->{'dns_retry'} || 2;
1237 0 0 0     0 $args{nameservers} = $config->{'dns_resolvers'} if $config->{'dns_resolvers'} && $config->{'dns_resolvers'}->@*;
1238 0         0 $object = Mail::Milter::Authentication::Resolver->new(%args);
1239 0         0 $object->udppacketsize(1240);
1240 0         0 $object->persistent_udp(1);
1241             }
1242 154         2392 $thischild->{'object'}->{$name} = {
1243             'object' => $object,
1244             'destroy' => 0,
1245             };
1246             }
1247              
1248             }
1249 1827         7673 return $thischild->{'object'}->{$name}->{'object'};
1250             }
1251              
1252              
1253             sub set_object {
1254 826     826 1 3183 my ( $self, $name, $object, $destroy ) = @_;
1255 826         1935 my $thischild = $self->{'thischild'};
1256 826         3240 $self->dbgout( 'Object set', $name, LOG_DEBUG );
1257 826         6623 $thischild->{'object'}->{$name} = {
1258             'object' => $object,
1259             'destroy' => $destroy,
1260             };
1261             }
1262              
1263              
1264             sub destroy_object {
1265 1108     1108 1 3665 my ( $self, $name ) = @_;
1266 1108         2478 my $thischild = $self->{'thischild'};
1267              
1268             # Objects may be set to not be destroyed,
1269             # eg. resolver and spf_server are not
1270             # destroyed for performance reasons
1271             # Resolver, however, has its error cache cleared, as this should only
1272             # cache errors within a single transaction.
1273 1108 100       5317 return if ! $thischild->{'object'}->{$name};
1274 490 100       1671 if ($name eq 'resolver' ) {
1275 19 50       1188 if ( $thischild->{'object'}->{'resolver'}->{'object'}->can( 'clear_error_cache' ) ) {
1276 0         0 $thischild->{'object'}->{'resolver'}->{'object'}->clear_error_cache();
1277             }
1278             }
1279 490 100       1985 return if ! $thischild->{'object'}->{$name}->{'destroy'};
1280 455         1778 $self->dbgout( 'Object destroyed', $name, LOG_DEBUG );
1281 455         14268 delete $thischild->{'object'}->{$name};
1282             }
1283              
1284              
1285             sub destroy_all_objects {
1286             # Unused!
1287 25     25 1 150 my ( $self ) = @_;
1288 25         150 my $thischild = $self->{'thischild'};
1289 25         98 foreach my $name ( keys %{ $thischild->{'object'} } )
  25         503  
1290             {
1291 35         215 $self->destroy_object( $name );
1292             }
1293             }
1294              
1295              
1296             sub exit_on_close {
1297 24     24 1 209 my ( $self, $error ) = @_;
1298 24 50       148 $error = 'Generic exit_on_close requested' if ! $error;
1299 24         189 $self->log_error( $error );
1300 24         202 my $top_handler = $self->get_top_handler();
1301 24         179 $top_handler->{'exit_on_close'} = 1;
1302 24 100       164 $top_handler->{'exit_on_close_error'} = 'Exit on close requested' if ! exists $top_handler->{'exit_on_close_error'};
1303 24         189 $top_handler->{'exit_on_close_error'} .= "\n$error";
1304             }
1305              
1306              
1307             sub reject_mail {
1308 3     3 1 22 my ( $self, $reason ) = @_;
1309 3         27 my ( $rcode, $xcode, $message ) = split( ' ', $reason, 3 );
1310 3 50 33     114 if ($rcode !~ /^[5]\d\d$/ || $xcode !~ /^[5]\.\d+\.\d+$/ || substr($rcode, 0, 1) ne substr($xcode, 0, 1)) {
      33        
1311 0         0 $self->loginfo ( "Invalid reject message $reason - setting to default" );
1312 0         0 $reason = '550 5.0.0 Message rejected';
1313             }
1314 3         26 my $top_handler = $self->get_top_handler();
1315 3         21 $top_handler->{'reject_mail'} = $reason;
1316             }
1317              
1318              
1319             sub quarantine_mail {
1320 12     12 1 97 my ( $self, $reason ) = @_;
1321 12         54 my $top_handler = $self->get_top_handler();
1322 12         64 $top_handler->{'quarantine_mail'} = $reason;
1323             }
1324              
1325              
1326             sub defer_mail {
1327 9     9 1 27 my ( $self, $reason ) = @_;
1328 9         40 my ( $rcode, $xcode, $message ) = split( ' ', $reason, 3 );
1329 9 50 33     128 if ($rcode !~ /^[4]\d\d$/ || $xcode !~ /^[4]\.\d+\.\d+$/ || substr($rcode, 0, 1) ne substr($xcode, 0, 1)) {
      33        
1330 0         0 $self->loginfo ( "Invalid defer message $reason - setting to default" );
1331 0         0 $reason = '450 4.0.0 Message deferred';
1332             }
1333 9         30 my $top_handler = $self->get_top_handler();
1334 9         52 $top_handler->{'defer_mail'} = $reason;
1335             }
1336              
1337              
1338             sub clear_all_symbols {
1339 130     130 1 436 my ( $self ) = @_;
1340 130         370 my $top_handler = $self->get_top_handler();
1341 130         676 delete $top_handler->{'symbols'};
1342             }
1343              
1344              
1345             sub clear_symbols {
1346 0     0 1 0 my ( $self ) = @_;
1347 0         0 my $top_handler = $self->get_top_handler();
1348              
1349 0         0 my $connect_symbols;
1350 0 0       0 if ( exists ( $top_handler->{'symbols'} ) ) {
1351 0 0       0 if ( exists ( $top_handler->{'symbols'}->{'C'} ) ) {
1352 0         0 $connect_symbols = $top_handler->{'symbols'}->{'C'};
1353             }
1354             }
1355              
1356 0         0 delete $top_handler->{'symbols'};
1357              
1358 0 0       0 if ( $connect_symbols ) {
1359 0         0 $top_handler->{'symbols'} = {
1360             'C' => $connect_symbols,
1361             };
1362             }
1363             }
1364              
1365              
1366             sub set_symbol {
1367 461     461 1 1866 my ( $self, $code, $key, $value ) = @_;
1368 461         2637 $self->dbgout( 'SetSymbol', "$code: $key: $value", LOG_DEBUG );
1369 461         2244 my $top_handler = $self->get_top_handler();
1370 461 100       1796 if ( ! exists ( $top_handler->{'symbols'} ) ) {
1371 80         710 $top_handler->{'symbols'} = {};
1372             }
1373 461 100       1870 if ( ! exists ( $top_handler->{'symbols'}->{$code} ) ) {
1374 146         1061 $top_handler->{'symbols'}->{$code} = {};
1375             }
1376 461         2974 $top_handler->{'symbols'}->{$code}->{$key} = $value;;
1377             }
1378              
1379              
1380             sub get_symbol {
1381 60118     60118 1 123169 my ( $self, $searchkey ) = @_;
1382 60118         128447 my $top_handler = $self->get_top_handler();
1383 60118   100     200544 my $symbols = $top_handler->{'symbols'} || {};
1384 60118         104911 foreach my $code ( keys %{$symbols} ) {
  60118         279155  
1385 48600         88426 my $subsymbols = $symbols->{$code};
1386 48600         74081 foreach my $key ( keys %{$subsymbols} ) {
  48600         110960  
1387 106293 100       265148 if ( $searchkey eq $key ) {
1388 19656         78017 return $subsymbols->{$key};
1389             }
1390             }
1391             }
1392             }
1393              
1394              
1395             sub tempfail_on_error {
1396 24     24 1 154 my ( $self ) = @_;
1397 24         176 my $config = $self->config();
1398 24 50       235 if ( $self->is_authenticated() ) {
    50          
    50          
1399 0 0       0 if ( $config->{'tempfail_on_error_authenticated'} ) {
1400 0         0 $self->log_error('TempFail set');
1401 0         0 $self->set_return( $self->smfis_tempfail() );
1402             }
1403             }
1404             elsif ( $self->is_local_ip_address() ) {
1405 0 0       0 if ( $config->{'tempfail_on_error_local'} ) {
1406 0         0 $self->log_error('TempFail set');
1407 0         0 $self->set_return( $self->smfis_tempfail() );
1408             }
1409             }
1410             elsif ( $self->is_trusted_ip_address() ) {
1411 0 0       0 if ( $config->{'tempfail_on_error_trusted'} ) {
1412 0         0 $self->log_error('TempFail set');
1413 0         0 $self->set_return( $self->smfis_tempfail() );
1414             }
1415             }
1416             else {
1417 24 50       236 if ( $config->{'tempfail_on_error'} ) {
1418 24         155 $self->log_error('TempFail set');
1419 24         324 $self->set_return( $self->smfis_tempfail() );
1420             }
1421             }
1422             }
1423              
1424              
1425              
1426             # Common calls into other Handlers
1427              
1428 147     147   307 sub _dequeue_dir($self) {
  147         358  
  147         328  
1429 147         458 my $config = $self->config();
1430 147         695 my $dir = $config->{spool_dir}.'/dequeue';
1431 147 100       3284 mkdir $dir if ! -d $dir;
1432 147         783 return $dir;
1433             }
1434              
1435              
1436             {
1437             my $queue_index = 1;
1438 62     62 1 173 sub add_dequeue($self,$key,$data) {
  62         192  
  62         247  
  62         179  
  62         161  
1439 62         442 my $dir = $self->_dequeue_dir;
1440 62         190 my $fullpath;
1441 62         523 my $timestamp = join( '.',gettimeofday);
1442 62         535 my $filename = join( '.',$key,$PID,$timestamp,$queue_index++,'dequeue');
1443 62         387 $fullpath = "$dir/$filename";
1444 62         4424 my $serialised_data = encode_sereal($data);
1445 62         1339 write_file($fullpath,{atomic=>1},$serialised_data);
1446             }
1447             }
1448              
1449              
1450 3     3 1 21 sub get_dequeue_list($self,$key) {
  3         38  
  3         44  
  3         10  
1451 3         103 my $dir = $self->_dequeue_dir;
1452 3         31 my $dequeue_index_file = $dir.'/dequeue.index';
1453 3         77 my $dequeue_lock_file = $dir.'/dequeue.lock';
1454              
1455 3         227 my $lock = Lock::File->new( $dequeue_lock_file, {} );
1456 3         1377 my $count_new = 0;
1457 3         37 my $count_allocated = 0;
1458 3         33 my $count_stale = 0;
1459              
1460 3         13 my $dequeue_index = {};
1461 3         619 my $j = JSON->new->pretty->canonical->utf8;
1462              
1463             # Build a list of Process IDs
1464 3         13 my $process_ids = {};
1465 3         159 my $process_table = Proc::ProcessTable->new();
1466 3         6834 foreach my $process ( @{$process_table->table} ) {
  3         14981  
1467 45         1194 $process_ids->{$process->pid} = 1;
1468             }
1469              
1470             # Read the last state from the index file
1471 3 100       126 if ( -e $dequeue_index_file ) {
1472 2         15 eval {
1473 2         97 my $body = scalar read_file($dequeue_index_file);
1474 2         661 $dequeue_index = $j->decode($body);
1475             };
1476             }
1477              
1478 3         16 my @dequeue_list;
1479 3 50       115 opendir(my $dh, $dir) || die "Failed to open dequeue directory: $!";
1480             FILE:
1481 3         162 while (my $file = readdir $dh) {
1482 52 100       428 if ( $file =~ /^$key\..*\.dequeue$/ ) {
1483 41 50       150 if ( exists ( $dequeue_index->{ $file } ) ) {
1484 0 0       0 if ( exists $process_ids->{ $dequeue_index->{$file}->{pid} } ) {
1485             # File exists in the index, and is associated with a currently valid PID
1486 0         0 $count_allocated++;
1487 0         0 next FILE;
1488             }
1489             else {
1490 0         0 $count_stale++;
1491             }
1492             }
1493 41         331 $dequeue_index->{$file} = {
1494             pid => $PID,
1495             };
1496 41         110 $count_new++;
1497 41         194 push @dequeue_list, $file;
1498             }
1499             }
1500 3         52 closedir $dh;
1501              
1502             # Remove deleted files from the dequeue index
1503 3         72 foreach my $id ( sort keys $dequeue_index->%* ) {
1504 73         241 my $filepath = join('/',$dir,$id);
1505 73 100       1258 delete $dequeue_index->{$id} unless -e $filepath;
1506             }
1507 3         352 write_file($dequeue_index_file,{atomic=>1},$j->encode($dequeue_index));
1508              
1509 3         4807 $lock->unlock;
1510              
1511 3         338 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'new' }, $count_new - $count_stale );
1512 3         54 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'allocated' }, $count_allocated );
1513 3         90 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'stale' }, $count_stale );
1514              
1515 3         384 return \@dequeue_list;
1516             }
1517              
1518              
1519 41     41 1 131 sub get_dequeue($self,$id) {
  41         122  
  41         101  
  41         79  
1520 41         128 my $dir = $self->_dequeue_dir;
1521 41         198 my $filepath = join('/',$dir,$id);
1522 41 50       687 return if ! -e $filepath;
1523 41 50       514 return if ! -f $filepath;
1524 41         326 my $serialized = scalar read_file($filepath);
1525 41         12963 my $data = decode_sereal($serialized);
1526 41         276 return $data;
1527             }
1528              
1529              
1530 41     41 1 88 sub delete_dequeue($self,$id) {
  41         107  
  41         96  
  41         111  
1531 41         157 my $dir = $self->_dequeue_dir;
1532 41         184 my $filepath = join('/',$dir,$id);
1533 41 50       565 return if ! -e $filepath;
1534 41 50       554 return if ! -f $filepath;
1535 41         5340 unlink $filepath;
1536             }
1537              
1538              
1539 0     0 1 0 sub error_dequeue($self,$id) {
  0         0  
  0         0  
  0         0  
1540 0         0 my $dir = $self->_dequeue_dir;
1541 0         0 my $filepath = join('/',$dir,$id);
1542 0 0       0 return if ! -e $filepath;
1543 0 0       0 return if ! -f $filepath;
1544 0         0 rename $filepath, $filepath . '.err';
1545             }
1546              
1547              
1548             sub add_header_to_sanitize_list {
1549 2     2 1 6 my($self,$header,$silent) = @_;
1550 2 50       9 return 0 if ! $self->is_handler_loaded('Sanitize');
1551 0         0 return $self->get_handler('Sanitize')->add_header_to_sanitize_list($header,$silent);
1552             }
1553              
1554              
1555             sub is_local_ip_address {
1556 2754     2754 1 6257 my ($self) = @_;
1557 2754 100       6923 return 0 if ! $self->is_handler_loaded('LocalIP');
1558 1868         5360 return $self->get_handler('LocalIP')->{'is_local_ip_address'};
1559             }
1560              
1561              
1562             sub is_trusted_ip_address {
1563 3323     3323 1 7741 my ($self) = @_;
1564 3323 100       7146 return 0 if ! $self->is_handler_loaded('TrustedIP');
1565 2229         4887 return $self->get_handler('TrustedIP')->{'is_trusted_ip_address'};
1566             }
1567              
1568              
1569             sub is_encrypted {
1570 4     4 1 13 my ($self) = @_;
1571 4 50       24 return undef if ! $self->is_handler_loaded('TLS'); ## no critic
1572 4         16 return $self->get_handler('TLS')->{'is_encrypted'};
1573             }
1574              
1575              
1576             sub is_authenticated {
1577 2318     2318 1 4716 my ($self) = @_;
1578 2318 100       5208 return 0 if ! $self->is_handler_loaded('Auth');
1579 1430         3156 return $self->get_handler('Auth')->{'is_authenticated'};
1580             }
1581              
1582              
1583             sub ip_address {
1584 352     352 1 1095 my ($self) = @_;
1585 352         1177 my $top_handler = $self->get_top_handler();
1586 352         2619 return $top_handler->{'ip_object'}->ip();
1587             }
1588              
1589              
1590              
1591             # Header formatting and data methods
1592              
1593              
1594             sub format_ctext {
1595              
1596             # Return ctext (but with spaces intact)
1597 702     702 1 1381 my ( $self, $text ) = @_;
1598 702 50       1617 $text = q{} if ! defined $text;
1599 702         1413 $text =~ s/\t/ /g;
1600 702         1228 $text =~ s/\n/ /g;
1601 702         1162 $text =~ s/\r/ /g;
1602 702         1183 $text =~ s/\(/ /g;
1603 702         1133 $text =~ s/\)/ /g;
1604 702         1254 $text =~ s/\\/ /g;
1605 702         1533 return $text;
1606             }
1607              
1608              
1609             sub format_ctext_no_space {
1610 702     702 1 1547 my ( $self, $text ) = @_;
1611 702         1600 $text = $self->format_ctext($text);
1612 702         1351 $text =~ s/ //g;
1613 702         1200 $text =~ s/;/_/g;
1614 702         1454 return $text;
1615             }
1616              
1617              
1618             sub format_header_comment {
1619 0     0 1 0 my ( $self, $comment ) = @_;
1620 0         0 $comment = $self->format_ctext($comment);
1621 0         0 return $comment;
1622             }
1623              
1624              
1625             sub format_header_entry {
1626 351     351 1 1538 my ( $self, $key, $value ) = @_;
1627 351         1299 $key = $self->format_ctext_no_space($key);
1628 351         855 $value = $self->format_ctext_no_space($value);
1629 351         1340 my $string = "$key=$value";
1630 351         1493 return $string;
1631             }
1632              
1633              
1634             sub get_domain_from {
1635 437     437 1 10817 my ( $self, $address ) = @_;
1636 437 50       1295 $address = q{} if ! defined $address;
1637 437         1572 $address = $self->get_address_from($address);
1638 437         1330 my $domain = 'localhost.localdomain';
1639 437         1430 $address =~ s/<//g;
1640 437         1142 $address =~ s/>//g;
1641 437 100       1703 if ( $address =~ /\@/ ) {
1642 434         2284 ($domain) = $address =~ /.*\@(.*)/;
1643             }
1644 437         1363 $domain =~ s/\s//g;
1645 437         3281 return lc $domain;
1646             }
1647              
1648              
1649             sub get_domains_from {
1650 211     211 1 1430 my ( $self, $addresstxt ) = @_;
1651 211 50       689 $addresstxt = q{} if ! defined $addresstxt;
1652 211         726 my $addresses = $self->get_addresses_from($addresstxt);
1653 211         693 my $domains = [];
1654 211         931 foreach my $address ( @$addresses ) {
1655 221         562 my $domain;
1656 221         572 $address =~ s/<//g;
1657 221         516 $address =~ s/>//g;
1658 221 100       984 if ( $address =~ /\@/ ) {
1659 215         1187 ($domain) = $address =~ /.*\@(.*)/;
1660             }
1661 221 100       812 next if ! defined $domain;
1662 215         594 $domain =~ s/\s//g;
1663 215         770 push @$domains, lc $domain;
1664             }
1665 211         1134 return $domains;
1666             }
1667              
1668 126     126   1263 use constant IsSep => 0;
  126         315  
  126         8453  
1669 126     126   1692 use constant IsPhrase => 1;
  126         394  
  126         7245  
1670 126     126   886 use constant IsEmail => 2;
  126         375  
  126         7567  
1671 126     126   863 use constant IsComment => 3;
  126         467  
  126         672798  
1672              
1673              
1674             sub get_address_from {
1675 819     819 1 13421 my ( $self, $Str ) = @_;
1676 819         2913 my $addresses = $self->get_addresses_from( $Str );
1677 819         3740 return $addresses->[0];
1678             }
1679              
1680              
1681             sub get_addresses_from {
1682 1119     1119 1 3393 my ( $self, $Str ) = @_;
1683 1119 50       2836 $Str = q{} if ! defined $Str;
1684              
1685 1119 100       2864 if ( $Str eq q{} ) {
1686 22         158 $self->log_error( 'Could not parse empty address' );
1687 22         111 return [ $Str ];
1688             }
1689              
1690 1097         5226 my $IDNComponentRE = qr/[^\x20-\x2c\x2e\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/;
1691 1097         7970 my $IDNRE = qr/(?:$IDNComponentRE\.)+$IDNComponentRE/;
1692 1097         3489 my $RFC_atom = qr/[a-z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+/i;
1693 1097         6698 my $RFC_dotatom = qr/${RFC_atom}(?:\.${RFC_atom})*/;
1694              
1695             # Break everything into Tokens
1696 1097         2472 my ( @Tokens, @Types );
1697             TOKEN_LOOP:
1698 1097         2168 while (1) {
1699 2564 100       19611 if ($Str =~ m/\G\"(.*?)(?<!\\)(?:\"|\z)\s*/sgc) {
    100          
    100          
    100          
    100          
    50          
1700             # String " ... "
1701 19         109 push @Tokens, $1;
1702 19         60 push @Types, IsPhrase;
1703             }
1704             elsif ( $Str =~ m/\G\<(.*?)(?<!\\)(?:[>,;]|\z)\s*/sgc) {
1705             # String < ... >
1706 237         847 push @Tokens, $1;
1707 237         553 push @Types, IsEmail;
1708             }
1709             elsif ($Str =~ m/\G\((.*?)(?<!\\)\)\s*/sgc) {
1710             # String ( ... )
1711 2         6 push @Tokens, $1;
1712 2         6 push @Types, IsComment;
1713             }
1714             elsif ($Str =~ m/\G[,;]\s*/gc) {
1715             # Comma or semi-colon
1716 28         78 push @Tokens, undef;
1717 28         69 push @Types, IsSep;
1718             }
1719             elsif ($Str =~ m/\G$/gc) {
1720             # End of line
1721 1097         2718 last TOKEN_LOOP;
1722             }
1723             elsif ($Str =~ m/\G([^\s,;"<]*)\s*/gc) {
1724             # Anything else
1725 1181 50       4211 if (length $1) {
1726 1181         3672 push @Tokens, $1;
1727 1181         2569 push @Types, IsPhrase;
1728             }
1729             }
1730             else {
1731             # Incomplete line. We'd like to die, but we'll return what we can
1732 0         0 $self->log_error('Could not parse address ' . $Str . ' : Unknown line remainder : ' . substr( $Str, pos() ) );
1733 0         0 push @Tokens, substr($Str, pos($Str));
1734 0         0 push @Types, IsComment;
1735 0         0 last TOKEN_LOOP;
1736             }
1737             }
1738              
1739             # Now massage Tokens into [ "phrase", "emailaddress", "comment" ]
1740 1097         2125 my @Addrs;
1741 1097         2332 my ($Phrase, $Email, $Comment, $Type);
1742 1097         3574 for (my $i = 0; $i < scalar(@Tokens); $i++) {
1743 1467         4004 my ($Type, $Token) = ($Types[$i], $Tokens[$i]);
1744              
1745             # If - a separator OR
1746             # - email address and already got one OR
1747             # - phrase and already got email address
1748             # then add current data as token
1749 1467 100 100     11090 if (($Type == IsSep) ||
      100        
      100        
      100        
1750             ($Type == IsEmail && defined($Email)) ||
1751             ($Type == IsPhrase && defined($Email)) ) {
1752 55 100       238 push @Addrs, $Email if defined $Email;
1753 55         162 ($Phrase, $Email, $Comment) = (undef, undef, undef);
1754             }
1755              
1756             # A phrase...
1757 1467 100       3965 if ($Type == IsPhrase) {
    100          
    100          
1758             # Strip '...' around token
1759 1200         2785 $Token =~ s/^'(.*)'$/$1/;
1760             # Strip any newlines assuming folded headers
1761 1200         2750 $Token =~ s/\r?\n//g;
1762              
1763             # Email like token?
1764 1200 100       8133 if ($Token =~ /^$RFC_dotatom\@$IDNRE$/o) {
1765 883         2517 $Token =~ s/^\s+//;
1766 883         2087 $Token =~ s/\s+$//;
1767 883         1888 $Token =~ s/\s+\@/\@/;
1768 883         2218 $Token =~ s/\@\s+/\@/;
1769             # Yes, check if next token is definitely email. If yes,
1770             # make this a phrase, otherwise make it an email item
1771 883 50 66     3234 if ($i+1 < scalar(@Tokens) && $Types[$i+1] == IsEmail) {
1772 0 0       0 $Phrase = defined($Phrase) ? $Phrase . " " . $Token : $Token;
1773             }
1774             else {
1775             # If we've already got an email address, add current address
1776 883 50       2139 if (defined($Email)) {
1777 0         0 push @Addrs, $Email;
1778 0         0 ($Phrase, $Email, $Comment) = (undef, undef, undef);
1779             }
1780 883         2957 $Email = $Token;
1781             }
1782             }
1783             else {
1784             # No, just add as phrase
1785 317 100       1496 $Phrase = defined($Phrase) ? $Phrase . " " . $Token : $Token;
1786             }
1787             }
1788             elsif ($Type == IsEmail) {
1789             # If an email, set email addr. Should be empty
1790 237         954 $Email = $Token;
1791             }
1792             elsif ($Type == IsComment) {
1793 2 50       8 $Comment = defined($Comment) ? $Comment . ", " . $Token : $Token;
1794             }
1795             # Must be separator, do nothing
1796             }
1797              
1798             # Add any remaining addresses
1799 1097 100       3400 push @Addrs, $Email if defined($Email);
1800              
1801 1097 100       2693 if ( ! @Addrs ) {
1802             # We couldn't parse, so just run with it and hope for the best
1803 15         42 push @Addrs, $Str;
1804 15         159 $self->log_error( 'Could not parse address ' . $Str );
1805             }
1806              
1807 1097         2037 my @TidyAddresses;
1808 1097         2689 foreach my $Address ( @Addrs ) {
1809              
1810 1135 50       3059 next if ( $Address =~ /\@unspecified-domain$/ );
1811              
1812 1135 50       3116 if ( $Address =~ /^mailto:(.*)$/ ) {
1813 0         0 $Address = $1;
1814             }
1815              
1816             # Trim whitelist that's possible, but not useful and
1817             # almost certainly a copy/paste issue
1818             # e.g. < foo @ bar.com >
1819              
1820 1135         2798 $Address =~ s/^\s+//;
1821 1135         2433 $Address =~ s/\s+$//;
1822 1135         2374 $Address =~ s/\s+\@/\@/;
1823 1135         2858 $Address =~ s/\@\s+/\@/;
1824              
1825 1135         3731 push @TidyAddresses, $Address;
1826             }
1827              
1828 1097 50       2907 if ( ! @TidyAddresses ) {
1829             # We really couldn't parse, so just run with it and hope for the best
1830 0         0 push @TidyAddresses, $Str;
1831             }
1832              
1833 1097         6507 return \@TidyAddresses;
1834              
1835             }
1836              
1837              
1838             sub get_my_hostname {
1839 332     332 1 940 my ($self) = @_;
1840 332         1080 my $hostname = $self->get_symbol('j');
1841 332 100       1861 if ( ! $hostname ) {
1842 206         973 $hostname = $self->get_symbol('{rcpt_host}');
1843             }
1844 332 100       1738 if ( ! $hostname ) { # Fallback
1845 206         1417 $hostname = hostname;
1846             }
1847 332         4039 return $hostname;
1848             }
1849              
1850              
1851             sub get_my_authserv_id {
1852 254     254 1 3236 my ($self) = @_;
1853 254         907 my $config = $self->config();
1854 254 50 66     1375 if ( exists( $config->{'authserv_id'} ) && $config->{'authserv_id'} ) {
1855 20         111 return $config->{'authserv_id'};
1856             }
1857 234         1180 return $self->get_my_hostname();
1858             }
1859              
1860              
1861              
1862             # Logging
1863              
1864              
1865             sub dbgout {
1866 40350     40350 1 114096 my ( $self, $key, $value, $priority ) = @_;
1867 40350   100     99130 my $queue_id = $self->get_symbol('i') || q{--};
1868 40350 50       103202 $key = q{--} if ! defined $key;
1869 40350 50       81850 $value = q{--} if ! defined $value;
1870              
1871 40350         70888 my $thischild = $self->{'thischild'};
1872 40350 100       91172 if ( exists $thischild->{'tracelog'} ) {
1873 27004         101496 push $thischild->{'tracelog'}->@*, time2str('%Y:%m:%d %X %z',time) . " $queue_id: $key: $value";
1874             }
1875              
1876 40350         6333127 my $config = $self->config();
1877 40350 100 100     194252 if (
1878             $priority == LOG_DEBUG
1879             &&
1880             ! $config->{'debug'}
1881             ) {
1882 26885         81223 return;
1883             }
1884              
1885             # Sys::Syslog and Log::Dispatchouli have different priority models
1886 13465 0       33103 my $log_priority = $priority == LOG_DEBUG ? 'debug'
    0          
    0          
    50          
    50          
    50          
    100          
    100          
1887             : $priority == LOG_INFO ? 'info'
1888             : $priority == LOG_NOTICE ? 'notice'
1889             : $priority == LOG_WARNING ? 'warning'
1890             : $priority == LOG_ERR ? 'error'
1891             : $priority == LOG_CRIT ? 'critical'
1892             : $priority == LOG_ALERT ? 'alert'
1893             : $priority == LOG_EMERG ? 'emergency'
1894             : 'info';
1895              
1896 13465 50       30458 if ( $config->{'logtoerr'} ) {
1897 13465         57391 Mail::Milter::Authentication::_warn( "$queue_id: $key: $value" );
1898             }
1899              
1900 13465         69429 my $top_handler = $self->get_top_handler();
1901 13465 100       38409 if ( !exists( $top_handler->{'dbgout'} ) ) {
1902 856         3283 $top_handler->{'dbgout'} = [];
1903             }
1904 13465   50     23529 push @{ $top_handler->{'dbgout'} },
  13465   100     92836  
1905             {
1906             'priority' => $log_priority,
1907             'key' => $key || q{},
1908             'value' => $value || q{},
1909             };
1910              
1911             # Write now if we can.
1912 13465 100       39951 if ( $self->get_symbol('i') ) {
1913 282         1659 $self->dbgoutwrite();
1914             }
1915             }
1916              
1917              
1918             sub log_error {
1919 92     92 1 383 my ( $self, $error ) = @_;
1920 92         422 $self->dbgout( 'ERROR', $error, LOG_ERR );
1921             }
1922              
1923              
1924             sub dbgoutwrite {
1925 1190     1190 1 3260 my ($self) = @_;
1926 1190         2492 eval {
1927 1190         3266 my $config = $self->config();
1928 1190   66     3425 my $queue_id = $self->get_symbol('i') ||
1929             'NOQUEUE.' . substr( uc md5_hex( "Authentication Milter Client $PID " . time() . rand(100) ) , -11 );
1930 1190         3889 my $top_handler = $self->get_top_handler();
1931 1190 100       4202 if ( exists( $top_handler->{'dbgout'} ) ) {
1932             LOGENTRY:
1933 845         2028 foreach my $entry ( @{ $top_handler->{'dbgout'} } ) {
  845         2638  
1934 13198         7286567 my $key = $entry->{'key'};
1935 13198         28808 my $value = $entry->{'value'};
1936 13198         23824 my $priority = $entry->{'priority'};
1937 13198         37619 my $line = "$queue_id: $key: $value";
1938 13198 50 66     62326 if (
1939             $priority eq 'debug'
1940             &&
1941             ! $config->{'debug'}
1942             ) {
1943 0         0 next LOGENTRY;
1944             }
1945 13198         43555 Mail::Milter::Authentication::logger()->log( { 'level' => $priority }, $line );
1946             }
1947             }
1948 1190         573401 delete $top_handler->{'dbgout'};
1949             };
1950 1190         4857 $self->handle_exception( $@ ); # Not usually called within an eval, however we shouldn't
1951             # ever get a Timeout (for example) here, so it is safe to
1952             # pass to handle_exception anyway.
1953             }
1954              
1955              
1956              
1957             # Header handling
1958              
1959              
1960             sub can_sort_header {
1961 56     56 1 185 my ( $self, $header ) = @_;
1962 56         233 return 0;
1963             }
1964              
1965              
1966             sub header_sort {
1967 949     949 1 2281 my ( $self, $sa, $sb ) = @_;
1968              
1969 949         2486 my $config = $self->config();
1970              
1971 949         2643 my $string_a;
1972             my $string_b;
1973              
1974 949         0 my $handler_a;
1975 949 50       2922 if ( ref $sa eq 'Mail::AuthenticationResults::Header::Entry' ) {
1976 949         2969 $handler_a = $sa->key();
1977 949         11667 $string_a = $sa->as_string();
1978             }
1979             else {
1980 0         0 ( $handler_a ) = split( '=', $sa, 2 );
1981 0         0 $string_a = $sa;
1982             }
1983 949         750134 my $handler_b;
1984 949 50       3055 if ( ref $sb eq 'Mail::AuthenticationResults::Header::Entry' ) {
1985 949         2769 $handler_b = $sb->key();
1986 949         11391 $string_b = $sb->as_string();
1987             }
1988             else {
1989 0         0 ( $handler_b ) = split( '=', $sb, 2 );
1990 0         0 $string_b = $sb;
1991             }
1992              
1993 949 100       930510 if ( $handler_a eq $handler_b ) {
1994             # Check for a handler specific sort method
1995 40         109 foreach my $name ( @{$config->{'load_handlers'}} ) {
  40         208  
1996 64         192 my $handler = $self->get_handler($name);
1997 64 100       500 if ( $handler->can_sort_header( lc $handler_a ) ) {
1998 8 50       51 if ( $handler->can( 'handler_header_sort' ) ) {
1999 8         36 return $handler->handler_header_sort( $sa, $sb );
2000             }
2001             }
2002             }
2003             }
2004              
2005 941         3624 return $string_a cmp $string_b;
2006             }
2007              
2008             sub _stringify_header {
2009 0     0   0 my ( $self, $header ) = @_;
2010 0 0       0 if ( ref $header eq 'Mail::AuthenticationResults::Header::Entry' ) {
2011 0         0 return $header->as_string();
2012             }
2013 0         0 return $header;
2014             }
2015              
2016              
2017             sub add_headers {
2018 258     258 1 865 my ($self) = @_;
2019 258         966 my $config = $self->config();
2020 258         986 my $top_handler = $self->get_top_handler();
2021 258         662 my @types;
2022 258 100       1458 push @types, keys $top_handler->{'c_auth_headers'}->%* if exists $top_handler->{'c_auth_headers'};
2023 258 100       1854 push @types, keys $top_handler->{'auth_headers'}->%* if exists $top_handler->{'auth_headers'};
2024              
2025 258   100     888 my $queue_id = $self->get_symbol('i') || q{--};
2026 258         2536 $self->{extended_log} = {ar => [], queue_id => $queue_id};
2027              
2028 258         2589 for my $type (uniq sort @types) {
2029 238         1215 $self->add_auth_headers_of_type($type);
2030             }
2031              
2032 258 50       1519 if ($config->{extended_log}) {
2033 0         0 my $j = JSON->new->canonical->utf8;
2034 0         0 $self->dbgout( 'ARex',$j->encode($self->{extended_log}), LOG_INFO );
2035             }
2036              
2037 258 100       1056 if ( my $reason = $self->get_quarantine_mail() ) {
2038 11         42 $self->prepend_header( 'X-Disposition-Quarantine', $reason );
2039             }
2040              
2041 258         1783 $top_handler->top_addheader_callback();
2042              
2043 258 100       1309 if ( exists( $top_handler->{'pre_headers'} ) ) {
2044 238         630 foreach my $header ( @{ $top_handler->{'pre_headers'} } ) {
  238         949  
2045             $self->dbgout( 'PreHeader',
2046 365         3028 $header->{'field'} . ': ' . $header->{'value'}, LOG_DEBUG );
2047 365         3344 $self->insert_header( 1, $header->{'field'}, $header->{'value'} );
2048             }
2049             }
2050              
2051 258 100       2294 if ( exists( $top_handler->{'add_headers'} ) ) {
2052 4         14 foreach my $header ( @{ $top_handler->{'add_headers'} } ) {
  4         21  
2053             $self->dbgout( 'AddHeader',
2054 18         285 $header->{'field'} . ': ' . $header->{'value'}, LOG_DEBUG );
2055 18         100 $self->add_header( $header->{'field'}, $header->{'value'} );
2056             }
2057             }
2058             }
2059              
2060              
2061 238     238 0 535 sub add_auth_headers_of_type($self,$type) {
  238         568  
  238         540  
  238         496  
2062 238         784 my $config = $self->config();
2063 238         1040 my $top_handler = $self->get_top_handler();
2064              
2065 238         723 my @auth_headers;
2066 238 100       1176 if ( exists( $top_handler->{'c_auth_headers'}->{$type} ) ) {
2067 81         197 @auth_headers = @{ $top_handler->{'c_auth_headers'}->{$type} };
  81         399  
2068             }
2069 238 100       983 if ( exists( $top_handler->{'auth_headers'}->{$type} ) ) {
2070 217         600 @auth_headers = ( @auth_headers, @{ $top_handler->{'auth_headers'}->{$type} } );
  217         885  
2071             }
2072 238 50       912 if (@auth_headers) {
    0          
2073              
2074 238         1434 @auth_headers = sort { $self->header_sort( $a, $b ) } @auth_headers;
  949         2823  
2075              
2076             # Do we have any legacy type headers?
2077 238         959 my $are_string_headers = 0;
2078 238         2562 my $header_obj = Mail::AuthenticationResults::Header->new();
2079 238         3003 foreach my $header ( @auth_headers ) {
2080 737 50       24192 if ( ref $header ne 'Mail::AuthenticationResults::Header::Entry' ) {
2081 0         0 $are_string_headers = 1;
2082 0         0 last;
2083             }
2084 737 50       2006 $header->orphan() if exists $header->{parent};
2085 737         2850 $header_obj->add_child( $header );
2086             }
2087              
2088 238         11508 my $header_text;
2089 238 50       1109 if ( $are_string_headers ) {
2090             # We have legacy headers, add in a legacy way
2091 0         0 $header_text = $self->get_my_authserv_id();
2092 0         0 $header_text .= ";\n ";
2093 0         0 $header_text .= join( ";\n ", map { $self->_stringify_header( $_ ) } @auth_headers );
  0         0  
2094 0         0 $self->dbgout( 'auth header added: $type: ',$header_text, LOG_INFO );
2095             }
2096             else {
2097 238         2097 $header_obj->set_value( Mail::AuthenticationResults::Header::AuthServID->new()->safe_set_value( $self->get_my_authserv_id() ) );
2098 238         17990 $header_obj->set_eol( "\n" );
2099 238 50       4187 if ( exists( $config->{'header_indent_style'} ) ) {
2100 0         0 $header_obj->set_indent_style( $config->{'header_indent_style'} );
2101             }
2102             else {
2103 238         1159 $header_obj->set_indent_style( 'entry' );
2104             }
2105 238 50       11985 if ( exists( $config->{'header_indent_by'} ) ) {
2106 0         0 $header_obj->set_indent_by( $config->{'header_indent_by'} );
2107             }
2108             else {
2109 238         763 $header_obj->set_indent_by( 4 );
2110             }
2111 238 50       2087 if ( exists( $config->{'header_fold_at'} ) ) {
2112 0         0 $header_obj->set_fold_at( $config->{'header_fold_at'} );
2113             }
2114 238         1365 $header_text = $header_obj->as_string();
2115              
2116             # Log a single line version of the added auth header
2117 238         1214009 $header_obj->set_indent_style('none');
2118 238         7317 $header_obj->set_fold_at(9999);
2119 238         2956 my $header_log_text = $header_obj->as_string();
2120 238 50 33     1093188 $self->dbgout( "A-R: $type",$header_log_text, LOG_INFO ) unless $config->{extended_log} && ! $config->{legacy_log};
2121 238 50       1680 push $self->{extended_log}->{ar}->@*, {type => $type, payload =>$header_obj->_as_hashref} if $config->{extended_log};
2122             }
2123              
2124 238         1506 my ($header_type,$header_type_postfix) = split /:/, $type;
2125 238         1360 $self->prepend_header( $header_type, $header_text );
2126             }
2127             elsif ( !$config->{'hide_none'} ) {
2128 0         0 my $header_text = $self->get_my_authserv_id();
2129 0         0 $header_text .= '; none';
2130 0         0 my ($header_type,$header_type_postfix) = split /:/, $type;
2131 0         0 $self->prepend_header( $header_type, $header_text );
2132             } else {
2133             # the result is none and hide_none is set, so we do not add an AR header
2134             }
2135             }
2136              
2137              
2138             sub prepend_header {
2139 365     365 1 1561 my ( $self, $field, $value ) = @_;
2140 365         1125 my $top_handler = $self->get_top_handler();
2141 365 100       1864 if ( !exists( $top_handler->{'pre_headers'} ) ) {
2142 238         1084 $top_handler->{'pre_headers'} = [];
2143             }
2144 365         945 push @{ $top_handler->{'pre_headers'} },
  365         4196  
2145             {
2146             'field' => $field,
2147             'value' => $value,
2148             };
2149             }
2150              
2151              
2152 598     598 1 1496 sub add_auth_header($self,$value) {
  598         1302  
  598         1128  
  598         1180  
2153 598         2042 my $config = $self->handler_config();
2154 598   50     4512 my $header_name = $config->{auth_header_name} // 'Authentication-Results';
2155 598         1798 my $top_handler = $self->get_top_handler();
2156 598 100       2982 $top_handler->{auth_headers} = {} unless exists $top_handler->{auth_headers};
2157 598 100       3002 $top_handler->{auth_headers}->{$header_name} = [] unless exists $top_handler->{auth_headers}->{$header_name};
2158 598         3252 push $top_handler->{auth_headers}->{$header_name}->@*, $value;
2159             }
2160              
2161              
2162 139     139 1 419 sub add_c_auth_header($self,$value) {
  139         308  
  139         332  
  139         335  
2163             # Connection wide auth headers
2164 139         644 my $config = $self->handler_config();
2165 139   50     1175 my $header_name = $config->{auth_header_name} // 'Authentication-Results';
2166 139         490 my $top_handler = $self->get_top_handler();
2167 139 100       835 $top_handler->{c_auth_headers} = {} unless exists $top_handler->{c_auth_headers};
2168 139 100       756 $top_handler->{c_auth_headers}->{$header_name} = [] unless exists $top_handler->{c_auth_headers}->{$header_name};
2169 139         632 push $top_handler->{c_auth_headers}->{$header_name}->@*, $value;
2170             }
2171              
2172              
2173             sub append_header {
2174 18     18 1 63 my ( $self, $field, $value ) = @_;
2175 18         46 my $top_handler = $self->get_top_handler();
2176 18 100       62 if ( !exists( $top_handler->{'add_headers'} ) ) {
2177 4         43 $top_handler->{'add_headers'} = [];
2178             }
2179 18         41 push @{ $top_handler->{'add_headers'} },
  18         116  
2180             {
2181             'field' => $field,
2182             'value' => $value,
2183             };
2184             }
2185              
2186              
2187              
2188             # Lower level methods
2189              
2190              
2191             sub smfis_continue {
2192 3651     3651 1 14405 return SMFIS_CONTINUE;
2193             }
2194              
2195              
2196             sub smfis_tempfail {
2197 33     33 1 234 return SMFIS_TEMPFAIL;
2198             }
2199              
2200              
2201             sub smfis_reject {
2202 3     3 1 22 return SMFIS_REJECT;
2203             }
2204              
2205              
2206             sub smfis_discard {
2207 0     0 1 0 return SMFIS_DISCARD;
2208             }
2209              
2210              
2211             sub smfis_accept {
2212 0     0 1 0 return SMFIS_ACCEPT;
2213             }
2214              
2215              
2216              
2217              
2218             sub write_packet {
2219 0     0 1 0 my ( $self, $type, $data ) = @_;
2220 0         0 my $thischild = $self->{'thischild'};
2221 0         0 $thischild->write_packet( $type, $data );
2222             }
2223              
2224              
2225             sub add_header {
2226 18     18 1 53 my ( $self, $key, $value ) = @_;
2227 18         40 my $thischild = $self->{'thischild'};
2228 18         41 my $config = $self->config();
2229 18 50       515 return if $config->{'dryrun'};
2230 18         112 $thischild->add_header( $key, $value );
2231             }
2232              
2233              
2234             sub insert_header {
2235 365     365 1 1281 my ( $self, $index, $key, $value ) = @_;
2236 365         856 my $thischild = $self->{'thischild'};
2237 365         1138 my $config = $self->config();
2238 365 100       1490 return if $config->{'dryrun'};
2239 361         3424 $thischild->insert_header( $index, $key, $value );
2240             }
2241              
2242              
2243             sub change_header {
2244 26     26 1 99 my ( $self, $key, $index, $value ) = @_;
2245 26         60 my $thischild = $self->{'thischild'};
2246 26         62 my $config = $self->config();
2247 26 50       161 return if $config->{'dryrun'};
2248 26         218 $thischild->change_header( $key, $index, $value );
2249             }
2250              
2251             1;
2252              
2253             __END__
2254              
2255             =pod
2256              
2257             =encoding UTF-8
2258              
2259             =head1 NAME
2260              
2261             Mail::Milter::Authentication::Handler - Handler superclass
2262              
2263             =head1 VERSION
2264              
2265             version 3.20230629
2266              
2267             =head1 DESCRIPTION
2268              
2269             Handle the milter requests and pass off to individual handlers
2270              
2271             =head1 CONSTRUCTOR
2272              
2273             =head2 I<new( $thischild )>
2274              
2275             my $object = Mail::Milter::Authentication::Handler->new( $thischild );
2276              
2277             Takes the argument of the current Mail::Milter::Authentication object
2278             and creates a new handler object.
2279              
2280             =head1 METHODS
2281              
2282             =head2 I<get_version()>
2283              
2284             Return the version of this handler
2285              
2286             =head2 I<status( $status )>
2287              
2288             Set the status of the current child as visible by ps.
2289              
2290             =head2 I<config()>
2291              
2292             Return the configuration hashref.
2293              
2294             =head2 I<handler_config( $type )>
2295              
2296             Return the configuration for the current handler.
2297              
2298             =head2 I<handler_type()>
2299              
2300             Return the current handler type.
2301              
2302             =head2 I<set_return( $code )>
2303              
2304             Set the return code to be passed back to the MTA.
2305              
2306             =head2 I<get_return()>
2307              
2308             Get the current return code.
2309              
2310             =head2 I<get_reject_mail()>
2311              
2312             Get the reject mail reason (or undef)
2313              
2314             =head2 I<clear_reject_mail()>
2315              
2316             Clear the reject mail reason
2317              
2318             =head2 I<get_defer_mail()>
2319              
2320             Get the defer mail reason (or undef)
2321              
2322             =head2 I<clear_defer_mail()>
2323              
2324             Clear the defer mail reason
2325              
2326             =head2 I<get_quarantine_mail()>
2327              
2328             Get the quarantine mail reason (or undef)
2329              
2330             =head2 I<clear_quarantine_mail()>
2331              
2332             Clear the quarantine mail reason
2333              
2334             =head2 I<get_top_handler()>
2335              
2336             Return the current top Handler object.
2337              
2338             =head2 I<is_handler_loaded( $name )>
2339              
2340             Check if the named handler is loaded.
2341              
2342             =head2 I<get_handler( $name )>
2343              
2344             Return the named handler object.
2345              
2346             =head2 I<get_callbacks( $callback )>
2347              
2348             Return the list of handlers which have callbacks for the given event in the order they must be called in.
2349              
2350             =head2 I<set_object_maker( $name, $ref )>
2351              
2352             Register an object maker for type 'name'
2353              
2354             =head2 I<get_object( $name )>
2355              
2356             Return the named object from the object store.
2357              
2358             Object 'resolver' will be created if it does not already exist.
2359              
2360             Object 'spf_server' will be created by the SPF handler if it does not already exist.
2361              
2362             Handlers may register makers for other types as required.
2363              
2364             =head2 I<set_object( $name, $object, $destroy )>
2365              
2366             Store the given object in the object store with the given name.
2367              
2368             If $destroy then the object will be destroyed when the connection to the child closes
2369              
2370             =head2 I<destroy_object( $name )>
2371              
2372             Remove the reference to the named object from the object store.
2373              
2374             =head2 I<destroy_all_objects()>
2375              
2376             Remove the references to all objects currently stored in the object store.
2377              
2378             Certain objects (resolver and spf_server) are not destroyed for performance reasons.
2379              
2380             =head2 I<exit_on_close( $error )>
2381              
2382             Exit this child once it has completed, do not process further requests with this child.
2383              
2384             =head2 I<reject_mail( $reason )>
2385              
2386             Reject mail with the given reason
2387              
2388             =head2 I<quarantine_mail( $reason )>
2389              
2390             Request quarantine mail with the given reason
2391              
2392             =head2 I<defer_mail( $reason )>
2393              
2394             Defer mail with the given reason
2395              
2396             =head2 I<clear_all_symbols()>
2397              
2398             Clear the symbol store.
2399              
2400             =head2 I<clear_symbols()>
2401              
2402             Clear the symbol store but do not remove the Connect symbols.
2403              
2404             =head2 I<set_symbol( $code, $key, $value )>
2405              
2406             Store the key value pair in the symbol store with the given code (event stage).
2407              
2408             =head2 I<get_symbol( $searchkey )>
2409              
2410             Return a value from the symbol store, searches all codes for the given key.
2411              
2412             =head2 I<tempfail_on_error()>
2413              
2414             Returns a TEMP FAIL to the calling MTA if the configuration is set to do so.
2415              
2416             Config can be set for all, authenticated, local, and trusted connections.
2417              
2418             =head2 I<can_sort_header( $header )>
2419              
2420             Returns 1 is this handler has a header_sort method capable or sorting entries for $header
2421             Returns 0 otherwise
2422              
2423             =head2 I<header_sort()>
2424              
2425             Sorting function for sorting the Authentication-Results headers
2426             Calls out to __HANDLER__->header_sort() to sort headers of a particular type if available,
2427             otherwise sorts alphabetically.
2428              
2429             =head2 I<add_headers()>
2430              
2431             Send the header changes to the MTA.
2432              
2433             =head2 I<add_headers_of_type( $type )>
2434              
2435             Find and add all Authentication-Results style headers of given type
2436              
2437             =head2 I<prepend_header( $field, $value )>
2438              
2439             Add a trace header to the email.
2440              
2441             =head2 I<add_auth_header( $value )>
2442              
2443             Add a section to the authentication header for this email.
2444              
2445             =head2 I<add_c_auth_header( $value )>
2446              
2447             Add a section to the authentication header for this email, and to any subsequent emails for this connection.
2448              
2449             =head2 I<append_header( $field, $value )>
2450              
2451             Add a normal header to the email.
2452              
2453             =head1 METRICS METHODS
2454              
2455             =head2 I<get_json( $file )>
2456              
2457             Return json data from external file
2458              
2459             =head2 I<metric_register( $id, $help )>
2460              
2461             Register a metric type
2462              
2463             =head2 I<metric_count( $id, $labels, $count )>
2464              
2465             Increment a metrics counter by $count (defaults to 1 if undef)
2466              
2467             =head2 I<metric_set( $id, $labels, $count )>
2468              
2469             Set a metrics counter to $count
2470              
2471             =head2 I<metric_send()>
2472              
2473             Send metrics to the parent
2474              
2475             =head2 I<register_metrics()>
2476              
2477             Return details of the metrics this module exports.
2478              
2479             =head1 RBL METHODS
2480              
2481             =head2 I<rbl_check_ip( $ip, $list )>
2482              
2483             Check the given IP address against an rbl list.
2484              
2485             Returns true is listed.
2486              
2487             =head2 I<rbl_check_domain( $domain, $list )>
2488              
2489             Check the given domain against an rbl list.
2490              
2491             Returns true is listed.
2492              
2493             =head1 TIMEOUT METHODS
2494              
2495             =head2 I<get_microseconds()>
2496              
2497             Return the current time in microseconds
2498              
2499             =head2 I<get_microseconds_since( $time )>
2500              
2501             Return the number of microseconds since the given time (in microseconds)
2502              
2503             =head2 I<is_exception_type( $exception )>
2504              
2505             Given a Mail::Milter::Authentication::Exception object, this return
2506             the exception object type.
2507             Otherwise returns undef.
2508              
2509             =head2 I<handle_exception( $exception )>
2510              
2511             Handle exceptions thrown, this method currently handles the
2512             timeout type, by re-throwing the exception.
2513              
2514             Should be called in Handlers when handling local exceptions, such that the
2515             higher level timeout exceptions are properly handled.
2516              
2517             =head2 I<get_time_remaining()>
2518              
2519             Return the time remaining (in microseconds) for the current Handler section level
2520             callback timeout.
2521              
2522             =head2 I<set_alarm( $microseconds )>
2523              
2524             Set a timeout alarm for $microseconds, and set the time remaining
2525             in the top level handler object.
2526              
2527             =head2 I<set_handler_alarm( $microseconds )>
2528              
2529             Set an alarm for $microseconds, or the current time remaining for the section callback, whichever
2530             is the lower. This should be used in Handler timeouts to ensure that a local timeout never goes for
2531             longer than the current handler section, or protocol section level timeout.
2532              
2533             =head2 I<reset_alarm()>
2534              
2535             Reset the alarm to the current time remaining in the section or protocol level timeouts.
2536              
2537             This should be called in Handlers after local timeouts have completed, to reset the higher level
2538             timeout alarm value.
2539              
2540             =head2 I<clear_overall_timeout()>
2541              
2542             Clear the current Handler level timeout, should be called from the Protocol layer, never from the Handler layer.
2543              
2544             =head2 I<set_overall_timeout( $microseconds )>
2545              
2546             Set the time in microseconds after which the Handler layer should timeout, called from the Protocol later, never from the Handler layer.
2547              
2548             =head2 I<get_type_timeout( $type )>
2549              
2550             For a given timeout type, return the configured timeout value, or the current handler level timeout, whichever is lower.
2551              
2552             =head2 I<check_timeout()>
2553              
2554             Manually check the current timeout, and throw if it has passed.
2555              
2556             =head1 CALLBACK METHODS
2557              
2558             =head2 I<top_dequeue_callback()>
2559              
2560             Top level handler for dequeue.
2561              
2562             =head2 I<top_setup_callback()>
2563              
2564             Top level handler for handler setup.
2565              
2566             =head2 I<remap_connect_callback( $hostname, $ip )>
2567              
2568             Top level handler for the connect event for remapping only.
2569              
2570             =head2 I<top_metrics_callback()>
2571              
2572             Top level handler for the metrics event.
2573              
2574             =head2 I<top_connect_callback( $hostname, $ip )>
2575              
2576             Top level handler for the connect event.
2577              
2578             =head2 I<remap_helo_callback( $helo_host )>
2579              
2580             Top level handler for the HELO event for remapping only.
2581              
2582             =head2 I<top_helo_callback( $helo_host )>
2583              
2584             Top level handler for the HELO event.
2585              
2586             =head2 I<top_envfrom_callback( $env_from )>
2587              
2588             Top level handler for the MAIL FROM event.
2589              
2590             =head2 I<top_envrcpt_callback( $env_to )>
2591              
2592             Top level handler for the RCPT TO event.
2593              
2594             =head2 I<top_header_callback( $header, $value, $original )>
2595              
2596             Top level handler for the BODY header event.
2597              
2598             =head2 I<top_eoh_callback()>
2599              
2600             Top level handler for the BODY end of headers event.
2601              
2602             =head2 I<top_body_callback( $body_chunk )>
2603              
2604             Top level handler for the BODY body chunk event.
2605              
2606             =head2 I<top_eom_callback()>
2607              
2608             Top level handler for the BODY end of message event.
2609              
2610             =head2 I<apply_policy()>
2611              
2612             Apply policy to the message, currently a nop.
2613              
2614             =head2 I<top_abort_callback()>
2615              
2616             Top level handler for the abort event.
2617              
2618             =head2 I<top_close_callback()>
2619              
2620             Top level handler for the close event.
2621              
2622             =head2 I<top_addheader_callback()>
2623              
2624             Top level handler for the add header event.
2625              
2626             Called after the Authentication-Results header has been added, but before any other headers.
2627              
2628             =head1 HELPER METHODS
2629              
2630             =head2 I<add_dequeue($key,$data)>
2631              
2632             Write serialized $data into the queue for later dequeueing
2633              
2634             =head2 I<get_dequeue_list($key)>
2635              
2636             Return an ArrayRef of all queued items for $key
2637              
2638             This may be a list of filenames, or may be a list of some
2639             other ID, it should not be assumed that this value is
2640             useful outside of the dequeue methods.
2641              
2642             Used in get_dequeue_object and delete_dequeue_object
2643              
2644             =head2 I<get_dequeue($id)>
2645              
2646             Return a previously queued item
2647              
2648             =head2 I<delete_dequeue($id)>
2649              
2650             Delete a previously queued item
2651              
2652             =head2 I<error_dequeue($id)>
2653              
2654             Mark a previously queued item as errored
2655              
2656             =head2 I<add_header_to_sanitize_list($header,$silent)>
2657              
2658             Add the given to the list of headers removed by the Sanitize handler if loaded
2659              
2660             =head2 I<is_local_ip_address()>
2661              
2662             Is the current connection from a local ip address?
2663              
2664             Requires the LocalIP Handler to be loaded.
2665              
2666             =head2 I<is_trusted_ip_address()>
2667              
2668             Is the current connection from a trusted ip address?
2669              
2670             Requires the TrustedIP Handler to be loaded.
2671              
2672             =head2 I<is_encrypted()>
2673              
2674             Is the current connection encrypted?
2675              
2676             Requires the TLS Handler to be loaded.
2677              
2678             In SMTP mode this is only available AFTER the eoh call.
2679              
2680             Returns undef if the state is not yet known.
2681              
2682             =head2 I<is_authenticated()>
2683              
2684             Is the current connection authenticated?
2685              
2686             Requires the Auth Handler to be loaded.
2687              
2688             =head2 I<ip_address()>
2689              
2690             Return the ip address of the current connection.
2691              
2692             =head2 I<format_ctext( $text )>
2693              
2694             Format text as ctext for use in headers.
2695              
2696             Deprecated.
2697              
2698             =head2 I<format_ctext_no_space( $text )>
2699              
2700             Format text as ctext with no spaces for use in headers.
2701              
2702             Deprecated.
2703              
2704             =head2 I<format_header_comment( $comment )>
2705              
2706             Format text as a comment for use in headers.
2707              
2708             Deprecated.
2709              
2710             =head2 I<format_header_entry( $key, $value )>
2711              
2712             Format text as a key value pair for use in authentication header.
2713              
2714             Deprecated.
2715              
2716             =head2 I<get_domain_from( $address )>
2717              
2718             Extract a single domain from an email address.
2719              
2720             =head2 I<get_domains_from( $address )>
2721              
2722             Extract the domains from an email address as an arrayref.
2723              
2724             =head2 I<get_address_from( $text )>
2725              
2726             Extract a single email address from a string.
2727              
2728             =head2 I<get_addresses_from( $text )>
2729              
2730             Extract all email address from a string as an arrayref.
2731              
2732             =head2 I<get_my_hostname()>
2733              
2734             Return the effective hostname of the MTA.
2735              
2736             =head2 I<get_my_authserv_id()>
2737              
2738             Return the effective authserv-id. Defaults to hostname if not explicitly set.
2739              
2740             =head1 LOGGING METHODS
2741              
2742             =head2 I<dbgout( $key, $value, $priority )>
2743              
2744             Send output to debug and/or Mail Log.
2745              
2746             priority is a standard Syslog priority.
2747              
2748             =head2 I<log_error( $error )>
2749              
2750             Log an error.
2751              
2752             =head2 I<dbgoutwrite()>
2753              
2754             Write out logs to disc.
2755              
2756             Logs are not written immediately, they are written at the end of a connection so we can
2757             include a queue id. This is not available at the start of the process.
2758              
2759             =head1 LOW LEVEL METHODS
2760              
2761             =head2 I<smfis_continue()>
2762              
2763             Return Continue code.
2764              
2765             =head2 I<smfis_tempfail()>
2766              
2767             Return TempFail code.
2768              
2769             =head2 I<smfis_reject()>
2770              
2771             Return Reject code.
2772              
2773             =head2 I<smfis_discard()>
2774              
2775             Return Discard code.
2776              
2777             =head2 I<smfis_accept()>
2778              
2779             Return Accept code.
2780              
2781             =head2 I<write_packet( $type, $data )>
2782              
2783             Write a packet to the MTA (calls Protocol object)
2784              
2785             =head2 I<add_header( $key, $value )>
2786              
2787             Write an Add Header packet to the MTA (calls Protocol object)
2788              
2789             =head2 I<insert_header( $index, $key, $value )>
2790              
2791             Write an Insert Header packet to the MTA (calls Protocol object)
2792              
2793             =head2 I<change_header( $key, $index, $value )>
2794              
2795             Write a Change Header packet to the MTA (calls Protocol object)
2796              
2797             =head1 WRITING HANDLERS
2798              
2799             tbc
2800              
2801             =head1 AUTHOR
2802              
2803             Marc Bradshaw <marc@marcbradshaw.net>
2804              
2805             =head1 COPYRIGHT AND LICENSE
2806              
2807             This software is copyright (c) 2020 by Marc Bradshaw.
2808              
2809             This is free software; you can redistribute it and/or modify it under
2810             the same terms as the Perl 5 programming language system itself.
2811              
2812             =cut