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 127     127   1773 use 5.20.0;
  127         454  
3 127     127   656 use strict;
  127         321  
  127         2560  
4 127     127   592 use warnings;
  127         319  
  127         2978  
5 127     127   658 use Mail::Milter::Authentication::Pragmas;
  127         274  
  127         885  
6             # ABSTRACT: Handler superclass
7             our $VERSION = '3.20230911'; # VERSION
8 127     127   86049 use Mail::Milter::Authentication::Exception;
  127         442  
  127         4440  
9 127     127   64196 use Mail::Milter::Authentication::Resolver;
  127         473  
  127         5995  
10 127     127   61747 use Date::Format qw{ time2str };
  127         1001757  
  127         13050  
11 127     127   1326 use Digest::MD5 qw{ md5_hex };
  127         422  
  127         9116  
12 127     127   81025 use List::MoreUtils qw{ uniq };
  127         1785391  
  127         1088  
13 127     127   206094 use Lock::File;
  127         1959558  
  127         6910  
14 127     127   64834 use MIME::Base64;
  127         80525  
  127         9516  
15 127     127   54918 use Mail::SPF;
  127         10202410  
  127         4441  
16 127     127   1164 use Net::DNS::Resolver;
  127         333  
  127         3558  
17 127     127   95948 use Net::IP;
  127         7490267  
  127         20147  
18 127     127   65103 use Proc::ProcessTable;
  127         416520  
  127         10566  
19 127     127   85621 use Sereal qw{encode_sereal decode_sereal};
  127         130455  
  127         8401  
20 127     127   1036 use Sys::Hostname;
  127         355  
  127         8295  
21 127     127   1225 use Time::HiRes qw{ ualarm gettimeofday };
  127         4591  
  127         1589  
22              
23              
24             our $TestResolver; # For Testing
25              
26              
27             sub new {
28 1039     1039 1 3856 my ( $class, $thischild ) = @_;
29 1039         3598 my $self = {
30             'thischild' => $thischild,
31             };
32 1039         4325 bless $self, $class;
33 1039         3575 return $self;
34             }
35              
36              
37             sub get_version {
38 234     234 1 729 my ( $self ) = @_;
39             {
40 127     127   28736 no strict 'refs'; ## no critic;
  127         376  
  127         1894495  
  234         578  
41 234   50     467 return ${ ref( $self ) . "::VERSION" } // 'unknown'; # no critic;
  234         5147  
42             }
43             }
44              
45              
46             sub get_json {
47 6     6 1 28 my ( $self, $file ) = @_;
48 6         18 my $basefile = __FILE__;
49 6         77 $basefile =~ s/Handler\.pm$/Handler\/$file/;
50 6         20 $basefile .= '.json';
51 6 50       182 if ( ! -e $basefile ) {
52 0         0 die 'json file ' . $file . ' not found';
53             }
54 6         313 open my $InF, '<', $basefile;
55 6         877 my @Content = <$InF>;
56 6         91 close $InF;
57 6         324 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 12785     12785 1 29631 my ( $self, $count_id, $labels, $count ) = @_;
69 12785 100       31052 $labels = {} if ! defined $labels;
70 12785 100       27313 $count = 1 if ! defined $count;
71              
72 12785         28006 my $metric = $self->{'thischild'}->{'metric'};
73 12785         52892 $metric->set_handler( $self );
74             $metric->count({
75             'count_id' => $count_id,
76             'labels' => $labels,
77 12785         73213 'server' => $self->{'thischild'},
78             'count' => $count,
79             });
80 12785         50483 $metric->set_handler( undef );
81             }
82              
83              
84             sub metric_set {
85 29     29 1 252 my ( $self, $gauge_id, $labels, $value ) = @_;
86 29 50       141 $labels = {} if ! defined $labels;
87 29 50       103 die 'Must set value in metric_set call' if ! defined $value;
88              
89 29         160 my $metric = $self->{'thischild'}->{'metric'};
90 29         249 $metric->set_handler( $self );
91             $metric->set({
92             'gauge_id' => $gauge_id,
93             'labels' => $labels,
94 29         396 'server' => $self->{'thischild'},
95             'value' => $value,
96             });
97 29         160 $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 8191 my ( $self, $ip, $list ) = @_;
110              
111 4         8 my $lookup_ip;
112              
113             # Reverse the IP
114 4 100       13 if ( $ip->version() == 4 ) {
    50          
115 2         17 $lookup_ip = join( '.', reverse( split( /\./, $ip->ip() ) ) );
116             }
117             elsif ( $ip->version() == 6 ) {
118 2         29 my $ip_string = $ip->ip();
119 2         27 $ip_string =~ s/://g;
120 2         22 $lookup_ip = join( '.', reverse( split( '', $ip_string ) ) );
121             }
122              
123 4 50       27 return 0 if ! $lookup_ip;
124 4         11 return $self->rbl_check_domain( $lookup_ip, $list );
125             }
126              
127              
128             sub rbl_check_domain {
129 6     6 1 1035 my ( $self, $domain, $list ) = @_;
130 6         27 my $resolver = $self->get_object( 'resolver' );
131 6         27 my $lookup = join( '.', $domain, $list );
132 6         47 my $packet = $resolver->query( $lookup, 'A' );
133              
134 6 100       2042 if ($packet) {
135 3         12 foreach my $rr ( $packet->answer ) {
136 3 50       61 if ( lc $rr->type eq 'a' ) {
137 3         49 return $rr->address();
138             }
139             }
140             }
141 3         35 return 0;
142             }
143              
144              
145             sub get_microseconds {
146 39321     39321 1 72004 my ( $self ) = @_;
147 39321         114398 my ($seconds, $microseconds) = gettimeofday;
148 39321         114907 return ( ( $seconds * 1000000 ) + $microseconds );
149             }
150              
151              
152             sub get_microseconds_since {
153 11380     11380 1 24394 my ( $self, $since ) = @_;
154 11380         23524 my $now = $self->get_microseconds();
155 11380         21151 my $elapsed = $now - $since;
156 11380 50       25218 $elapsed = 1 if $elapsed == 0; # Always return at least 1
157 11380         35384 return $elapsed;
158             }
159              
160             # Top Level Callbacks
161              
162              
163             sub register_metrics {
164             return {
165 43     43 1 724 '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 30 my ( $self ) = @_;
174              
175 3         86 $self->status('dequeue');
176 3         279 $self->set_symbol('C','i','DEQUEUE.'.substr( uc md5_hex( "Authentication Milter Client $PID " . time() . rand(100) ) , -11 ));
177 3         38 $self->dbgout( 'CALLBACK', 'Dequeue', LOG_DEBUG );
178 3         64 my $config = $self->config();
179 3         25 eval {
180 3     0   234 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Dequeue callback timeout' }) };
  0         0  
181 3 50       46 if ( my $timeout = $self->get_type_timeout( 'dequeue' ) ) {
182 3         56 $self->set_alarm( $timeout );
183             }
184 3         31 my $callbacks = $self->get_callbacks( 'dequeue' );
185 3         43 foreach my $handler ( @$callbacks ) {
186 3         41 $self->dbgout( 'CALLBACK', 'Dequeue ' . $handler, LOG_DEBUG );
187 3         14 my $start_time = $self->get_microseconds();
188 3         25 $self->get_handler($handler)->dequeue_callback();
189 3         2267 $self->dbgoutwrite();
190 3         120 $self->metric_count( 'time_microseconds_total', { 'callback' => 'dequeue', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
191             }
192 3         19 $self->set_alarm(0);
193             };
194 3 50       22 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         18 $self->dbgoutwrite();
203 3         20 $self->status('postdequeue');
204             }
205              
206              
207             sub top_setup_callback {
208              
209 91     91 1 418 my ( $self ) = @_;
210 91         1372 $self->status('setup');
211 91         1155 $self->dbgout( 'CALLBACK', 'Setup', LOG_DEBUG );
212 91         1280 $self->set_return( $self->smfis_continue() );
213              
214 91         677 my $callbacks = $self->get_callbacks( 'setup' );
215 91         606 foreach my $handler ( @$callbacks ) {
216 146         1402 $self->dbgout( 'CALLBACK', 'Setup ' . $handler, LOG_DEBUG );
217 146         1491 my $start_time = $self->get_microseconds();
218 146         764 $self->get_handler($handler)->setup_callback();
219 146         2804 $self->metric_count( 'time_microseconds_total', { 'callback' => 'setup', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
220             }
221 91         681 $self->status('postsetup');
222             }
223              
224              
225             sub is_exception_type {
226 12317     12317 1 28313 my ( $self, $exception ) = @_;
227 12317 50       33844 return if ! defined $exception;
228 12317 100       37297 return if ! $exception;
229 173 100       866 return if ref $exception ne 'Mail::Milter::Authentication::Exception';
230 48   50     301 my $Type = $exception->{ 'Type' } || 'Unknown';
231 48         578 return $Type;
232             }
233              
234              
235             sub handle_exception {
236 12293     12293 1 33286 my ( $self, $exception ) = @_;
237 12293 50       33864 return if ! defined $exception;
238 12293         36834 my $Type = $self->is_exception_type( $exception );
239 12293 100       48610 return if ! $Type;
240 24 50       1267 die $exception if $Type eq 'Timeout';
241             #my $Text = $exception->{ 'Text' } || 'Unknown';
242             }
243              
244              
245             sub get_time_remaining {
246 242     242 1 662 my ( $self ) = @_;
247 242         825 my $top_handler = $self->get_top_handler();
248 242 50       917 return if ! exists $top_handler->{ 'timeout_at' };
249 242         816 my $now = $self->get_microseconds();
250 242         688 my $remaining = $top_handler->{ 'timeout_at' } - $now;
251             # can be zero or -ve
252 242         636 return $remaining;
253             }
254              
255              
256             sub set_alarm {
257 7846     7846 1 16858 my ( $self, $microseconds ) = @_;
258 7846         15592 my $top_handler = $self->get_top_handler();
259 7846         23724 $self->dbgout( 'Timeout set', $microseconds, LOG_DEBUG );
260 7846         70494 ualarm( $microseconds );
261 7846 100       28170 if ( $microseconds == 0 ) {
262 4115         83935 delete $top_handler->{ 'timeout_at' };
263             }
264             else {
265 3731         11760 $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 471 my ( $self, $microseconds ) = @_;
274 126         832 my $remaining = $self->get_time_remaining();
275 126 50       543 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         603 $self->dbgout( 'Handler timeout set', $microseconds, LOG_DEBUG );
282 126         1702 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 415 my ( $self ) = @_;
290 116         437 my $remaining = $self->get_time_remaining();
291 116         566 $self->dbgout( 'Timeout reset', $remaining, LOG_DEBUG );
292 116 50       583 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         1613 ualarm( $remaining );
297             }
298              
299              
300             sub clear_overall_timeout {
301 40     40 1 162 my ( $self ) = @_;
302 40         242 $self->dbgout( 'Overall timeout', 'Clear', LOG_DEBUG );
303 40         712 my $top_handler = $self->get_top_handler();
304 40         179 delete $top_handler->{ 'overall_timeout' };
305             }
306              
307              
308             sub set_overall_timeout {
309 82     82 1 550 my ( $self, $microseconds ) = @_;
310 82         404 my $top_handler = $self->get_top_handler();
311 82         841 $self->dbgout( 'Overall timeout', $microseconds, LOG_DEBUG );
312 82         559 $top_handler->{ 'overall_timeout' } = $self->get_microseconds() + $microseconds;
313             }
314              
315              
316             sub get_type_timeout {
317 4139     4139 1 11819 my ( $self, $type ) = @_;
318              
319 4139         7704 my @log;
320 4139         11821 push @log, "Type: $type";
321              
322 4139         7482 my $effective;
323              
324             my $timeout;
325 4139         9983 my $config = $self->config();
326 4139 100       15459 if ( $config->{ $type . '_timeout' } ) {
327 3686         8935 $timeout = $config->{ $type . '_timeout' } * 1000000;
328 3686         6481 $effective = $timeout;
329 3686         9050 push @log, "Section: $timeout";
330             }
331              
332 4139         6869 my $remaining;
333 4139         9365 my $top_handler = $self->get_top_handler();
334 4139 100       11601 if ( my $overall_timeout = $top_handler->{ 'overall_timeout' } ) {
335 878         2582 my $now = $self->get_microseconds();
336 878         1805 $remaining = $overall_timeout - $now;
337 878         2958 push @log, "Overall: $remaining";
338 878 50       2656 if ( $remaining < 1 ) {
339 0         0 push @log, "Overall Timedout";
340 0         0 $remaining = 10; # arb low value;
341             }
342             }
343              
344 4139 100       9521 if ( $remaining ) {
345 878 100       2208 if ( $timeout ) {
346 833 100       2360 if ( $remaining < $timeout ) {
347 617         1204 $effective = $remaining;
348             }
349             }
350             else {
351 45         149 $effective = $remaining;
352             }
353             }
354              
355 4139 100       12708 push @log, "Effective: $effective" if $effective;
356              
357 4139         20450 $self->dbgout( 'Timeout set', join( ', ', @log ), LOG_DEBUG );
358              
359 4139         24995 return $effective;
360             }
361              
362              
363             sub check_timeout {
364 11754     11754 1 24710 my ( $self ) = @_;
365 11754         27652 my $top_handler = $self->get_top_handler();
366 11754 100       30649 return if ! exists $top_handler->{ 'timeout_at' };
367 11604 50       27014 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 548     548   1437 my ( $self ) = @_;
375              
376 548         1645 my $config = $self->config();
377 548 100       3008 if ( exists ( $config->{ 'ip_map' } ) ) {
378 140         386 my $ip_object = $self->{ 'raw_ip_object' };
379 140         361 my $helo_host = $self->{'raw_helo_name'};
380 140         318 foreach my $ip_map ( sort keys %{ $config->{ 'ip_map' } } ) {
  140         1071  
381 264         1088 my $map_obj = Net::IP->new( $ip_map );
382 264 50       231114 if ( !$map_obj ) {
383 0         0 $self->log_error( 'Core: Could not parse IP '.$ip_map );
384             }
385             else {
386 264   100     1691 my $is_overlap = $ip_object->overlaps($map_obj) || 0;
387 264 100 66     45726 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         57 my $mapped_to = $config->{ 'ip_map' }->{ $ip_map };
395 16 100 100     222 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         14 };
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         72 };
409             }
410             }
411             }
412             }
413             }
414             }
415              
416              
417             sub remap_connect_callback {
418 277     277 1 89396 my ( $self, $hostname, $ip ) = @_;
419 277         1219 $self->{'raw_ip_object'} = $ip;
420 277         1344 my $ip_remap = $self->_remap_ip_and_helo();
421 277 100       6478 if ( $ip_remap ) {
422 8 50       39 if ( !$ip_remap->{ip} ) {
423 0         0 $self->log_error( 'Core: Ignored bad IP in remapping' );
424             }
425             else {
426 8         70 $ip = $ip_remap->{ip};
427 8         42 $self->dbgout( 'RemappedConnect', $self->{'raw_ip_object'}->ip() . ' > ' . $ip->ip(), LOG_DEBUG );
428             }
429             }
430 277         1440 $self->{'ip_object'} = $ip;
431             }
432              
433              
434             sub top_metrics_callback {
435 11     11 1 126 my ( $self ) = @_;
436 11         125 my $callbacks = $self->get_callbacks( 'metrics' );
437 11         204 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         95  
440 10 50       7629 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 277     277 1 1225 my ( $self, $hostname, $ip ) = @_;
452 277         1409 $self->metric_count( 'connect_total' );
453 277         1424 $self->status('connect');
454 277         2326 $self->dbgout( 'CALLBACK', 'Connect', LOG_DEBUG );
455 277         1877 $self->set_return( $self->smfis_continue() );
456 277         1705 $self->clear_reject_mail();
457 277         1358 $self->clear_defer_mail();
458 277         1319 $self->clear_quarantine_mail();
459 277         870 my $config = $self->config();
460 277         818 eval {
461 277     1   7335 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Connect callback timeout' }) };
  1         195  
462 277 50       2070 if ( my $timeout = $self->get_type_timeout( 'connect' ) ) {
463 277         1351 $self->set_alarm( $timeout );
464             }
465              
466 277         1701 $self->dbgout( 'ConnectFrom', $ip->ip(), LOG_DEBUG );
467              
468 277         1768 my $callbacks = $self->get_callbacks( 'connect' );
469 277         1224 foreach my $handler ( @$callbacks ) {
470 351         1750 $self->dbgout( 'CALLBACK', 'Connect ' . $handler, LOG_DEBUG );
471 351         1231 my $start_time = $self->get_microseconds();
472 351         906 eval{ $self->get_handler($handler)->connect_callback( $hostname, $ip ); };
  351         1073  
473 351 100       1308 if ( my $error = $@ ) {
474 1         20 $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         1894 $self->metric_count( 'time_microseconds_total', { 'callback' => 'connect', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
480 350         1413 $self->check_timeout();
481             }
482 276         1126 $self->set_alarm(0);
483             };
484 277 100       1655 if ( my $error = $@ ) {
485 1 50       38 if ( my $type = $self->is_exception_type( $error ) ) {
486 1         36 $self->metric_count( 'callback_error_total', { 'stage' => 'connect', 'type' => $type } );
487 1         32 $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         13 $self->tempfail_on_error();
494             }
495 277         1247 $self->status('postconnect');
496 277         1762 return $self->get_return();
497             }
498              
499              
500             sub remap_helo_callback {
501 271     271 1 1094 my ( $self, $helo_host ) = @_;
502 271 50       1151 if ( !( $self->{'helo_name'} ) ) {
503              
504 271         1143 $self->{'raw_helo_name'} = $helo_host;
505 271         950 my $ip_remap = $self->_remap_ip_and_helo();
506 271 100       6495 if ( $ip_remap ) {
507 8         32 my $ip = $ip_remap->{ip};
508 8 100       52 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         31 $self->{'ip_object'} = $ip;
511 2         11 $self->dbgout( 'RemappedConnectHELO', $self->{'ip_object'}->ip() . ' > ' . $ip->ip(), LOG_DEBUG );
512             }
513 8         108 $helo_host = $ip_remap->{helo};
514 8         66 $self->dbgout( 'RemappedHELO', $self->{'raw_helo_name'} . ' > ' . $helo_host, LOG_DEBUG );
515             }
516              
517 271         1239 $self->{'helo_name'} = $helo_host;
518             }
519             }
520              
521              
522             sub top_helo_callback {
523              
524             # On HELO
525 271     271 1 949 my ( $self, $helo_host ) = @_;
526 271         1086 $self->status('helo');
527 271         2374 $self->dbgout( 'CALLBACK', 'Helo', LOG_DEBUG );
528 271         1588 $self->set_return( $self->smfis_continue() );
529 271 50       1347 $helo_host = q{} if ! defined $helo_host;
530 271         1037 my $config = $self->config();
531 271         724 eval {
532 271     1   5765 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'HELO callback timeout' }) };
  1         94  
533 271 50       1856 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
534 271         1115 $self->set_alarm( $timeout );
535             }
536              
537             # Take only the first HELO from a connection
538 271 50       1879 if ( !( $self->{'seen_helo_name'} ) ) {
539 271         1078 $self->{'seen_helo_name'} = $helo_host;
540              
541 271         993 my $callbacks = $self->get_callbacks( 'helo' );
542 271         1436 foreach my $handler ( @$callbacks ) {
543 404         1988 $self->dbgout( 'CALLBACK', 'Helo ' . $handler, LOG_DEBUG );
544 404         1704 my $start_time = $self->get_microseconds();
545 404         1006 eval{ $self->get_handler($handler)->helo_callback($helo_host); };
  404         1212  
546 404 100       1404 if ( my $error = $@ ) {
547 1         17 $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 403         1974 $self->metric_count( 'time_microseconds_total', { 'callback' => 'helo', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
553 403         1592 $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 270         1296 $self->set_alarm(0);
561             };
562 271 100       1882 if ( my $error = $@ ) {
563 1 50       9 if ( my $type = $self->is_exception_type( $error ) ) {
564 1         20 $self->metric_count( 'callback_error_total', { 'stage' => 'helo', 'type' => $type } );
565 1         25 $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         13 $self->tempfail_on_error();
572             }
573 271         1236 $self->status('posthelo');
574 271         1539 return $self->get_return();
575             }
576              
577              
578             sub top_envfrom_callback {
579              
580             # On MAILFROM
581             #...
582 270     270 1 1121 my ( $self, $env_from, @params ) = @_;
583 270         958 $self->status('envfrom');
584 270         1818 $self->dbgout( 'CALLBACK', 'EnvFrom', LOG_DEBUG );
585 270         1520 $self->set_return( $self->smfis_continue() );
586 270 100       1334 $env_from = q{} if ! defined $env_from;
587 270         1018 my $config = $self->config();
588 270         783 eval {
589 270     1   5632 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EnvFrom callback timeout' }) };
  1         41  
590 270 50       1735 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
591 270         1009 $self->set_alarm( $timeout );
592             }
593              
594             # Reset private data for this MAIL transaction
595 270         1299 delete $self->{'auth_headers'};
596 270         872 delete $self->{'pre_headers'};
597 270         700 delete $self->{'add_headers'};
598 270         744 delete $self->{'suppress_error_emails'};
599              
600 270         966 my $callbacks = $self->get_callbacks( 'envfrom' );
601 270         1258 foreach my $handler ( @$callbacks ) {
602 855         4070 $self->dbgout( 'CALLBACK', 'EnvFrom ' . $handler, LOG_DEBUG );
603 855         2908 my $start_time = $self->get_microseconds();
604 855         1931 eval { $self->get_handler($handler)->envfrom_callback($env_from, @params); };
  855         2505  
605 855 100       2767 if ( my $error = $@ ) {
606 1         22 $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 854         4155 $self->metric_count( 'time_microseconds_total', { 'callback' => 'envfrom', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
612 854         3344 $self->check_timeout();
613             }
614 269         1463 $self->set_alarm(0);
615             };
616 270 100       1775 if ( my $error = $@ ) {
617 1 50       13 if ( my $type = $self->is_exception_type( $error ) ) {
618 1         44 $self->metric_count( 'callback_error_total', { 'stage' => 'envfrom', 'type' => $type } );
619 1         22 $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         11 $self->tempfail_on_error();
626             }
627 270         1307 $self->status('postenvfrom');
628 270         1423 return $self->get_return();
629             }
630              
631              
632             sub top_envrcpt_callback {
633              
634             # On RCPTTO
635             #...
636 269     269 1 1073 my ( $self, $env_to, @params ) = @_;
637 269         955 $self->status('envrcpt');
638 269         1863 $self->dbgout( 'CALLBACK', 'EnvRcpt', LOG_DEBUG );
639 269         1631 $self->set_return( $self->smfis_continue() );
640 269 50       1339 $env_to = q{} if ! defined $env_to;
641 269         953 my $config = $self->config();
642 269         810 eval {
643 269     1   5757 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EnvRcpt callback timeout' }) };
  1         42  
644 269 50       1951 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
645 269         1124 $self->set_alarm( $timeout );
646             }
647              
648 269         1616 my $callbacks = $self->get_callbacks( 'envrcpt' );
649 269         1302 foreach my $handler ( @$callbacks ) {
650 131         861 $self->dbgout( 'CALLBACK', 'EnvRcpt ' . $handler, LOG_DEBUG );
651 131         703 my $start_time = $self->get_microseconds();
652 131         469 eval{ $self->get_handler($handler)->envrcpt_callback($env_to, @params); };
  131         505  
653 131 100       644 if ( my $error = $@ ) {
654 1         13 $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 130         918 $self->metric_count( 'time_microseconds_total', { 'callback' => 'rcptto', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
660 130         727 $self->check_timeout();
661             }
662 268         1252 $self->set_alarm(0);
663             };
664 269 100       1824 if ( my $error = $@ ) {
665 1 50       12 if ( my $type = $self->is_exception_type( $error ) ) {
666 1         57 $self->metric_count( 'callback_error_total', { 'stage' => 'rcptto', 'type' => $type } );
667 1         21 $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         13 $self->tempfail_on_error();
674             }
675 269         1177 $self->status('postenvrcpt');
676 269         1367 return $self->get_return();
677             }
678              
679              
680             sub top_header_callback {
681              
682             # On Each Header
683 1637     1637 1 4660 my ( $self, $header, $value, $original ) = @_;
684 1637         5087 $self->status('header');
685 1637         8795 $self->dbgout( 'CALLBACK', 'Header', LOG_DEBUG );
686 1637         5906 $self->set_return( $self->smfis_continue() );
687 1637 50       4684 $value = q{} if ! defined $value;
688 1637         4073 my $config = $self->config();
689              
690 1637 50 33     5194 if ( $header eq 'X-Authentication-Milter-Error' && $value eq 'Generated Error Report' ) {
691 0         0 $self->{'suppress_error_emails'} = 1;
692             }
693              
694 1637         3177 eval {
695 1637     15   31493 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Header callback timeout' }) };
  15         610  
696 1637 50       7762 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
697 1637         4875 $self->set_alarm( $timeout );
698             }
699 1637 50       6092 if ( my $error = $@ ) {
700 0         0 $self->dbgout( 'inline error $error', '', LOG_DEBUG );
701             }
702              
703 1637         4916 my $callbacks = $self->get_callbacks( 'header' );
704 1637         4767 foreach my $handler ( @$callbacks ) {
705 6810         28865 $self->dbgout( 'CALLBACK', 'Header ' . $handler, LOG_DEBUG );
706 6810         19954 my $start_time = $self->get_microseconds();
707 6810         13128 eval{ $self->get_handler($handler)->header_callback( $header, $value, $original ); };
  6810         16293  
708 6810 100       18308 if ( my $error = $@ ) {
709 15         281 $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 6795         28530 $self->metric_count( 'time_microseconds_total', { 'callback' => 'header', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
715 6795         22600 $self->check_timeout();
716             }
717 1622         4847 $self->set_alarm(0);
718             };
719 1637 100       7114 if ( my $error = $@ ) {
720 15 50       139 if ( my $type = $self->is_exception_type( $error ) ) {
721 15         233 $self->metric_count( 'callback_error_total', { 'stage' => 'header', 'type' => $type } );
722 15         381 $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         142 $self->tempfail_on_error();
729             }
730 1637         6139 $self->status('postheader');
731 1637         6671 return $self->get_return();
732             }
733              
734              
735             sub top_eoh_callback {
736              
737             # On End of headers
738 267     267 1 975 my ($self) = @_;
739 267         1020 $self->status('eoh');
740 267         1892 $self->dbgout( 'CALLBACK', 'EOH', LOG_DEBUG );
741 267         1442 $self->set_return( $self->smfis_continue() );
742 267         1019 my $config = $self->config();
743 267         769 eval {
744 267     1   5671 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EOH callback timeout' }) };
  1         76  
745 267 50       1780 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
746 267         1138 $self->set_alarm( $timeout );
747             }
748              
749 267         1624 my $callbacks = $self->get_callbacks( 'eoh' );
750 267         1333 foreach my $handler ( @$callbacks ) {
751 522         2602 $self->dbgout( 'CALLBACK', 'EOH ' . $handler, LOG_DEBUG );
752 522         2026 my $start_time = $self->get_microseconds();
753 522         1257 eval{ $self->get_handler($handler)->eoh_callback(); };
  522         1456  
754 522 100       5144 if ( my $error = $@ ) {
755 1         20 $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 521         2827 $self->metric_count( 'time_microseconds_total', { 'callback' => 'eoh', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
761 521         2121 $self->check_timeout();
762             }
763 266         1184 $self->set_alarm(0);
764             };
765 267 100       1873 if ( my $error = $@ ) {
766 1 50       9 if ( my $type = $self->is_exception_type( $error ) ) {
767 1         24 $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 267         1607 $self->dbgoutwrite();
777 267         1452 $self->status('posteoh');
778 267         1553 return $self->get_return();
779             }
780              
781              
782             sub top_body_callback {
783              
784             # On each body chunk
785 265     265 1 1105 my ( $self, $body_chunk ) = @_;
786 265         1017 $self->status('body');
787 265         2118 $self->dbgout( 'CALLBACK', 'Body', LOG_DEBUG );
788 265         1770 $self->set_return( $self->smfis_continue() );
789 265         986 my $config = $self->config();
790 265         771 eval {
791 265     1   6007 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Body callback timeout' }) };
  1         41  
792 265 50       1873 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
793 265         1244 $self->set_alarm( $timeout );
794             }
795              
796 265         1394 my $callbacks = $self->get_callbacks( 'body' );
797 265         1248 foreach my $handler ( @$callbacks ) {
798 276         1583 $self->dbgout( 'CALLBACK', 'Body ' . $handler, LOG_DEBUG );
799 276         1422 my $start_time = $self->get_microseconds();
800 276         832 eval{ $self->get_handler($handler)->body_callback( $body_chunk ); };
  276         1164  
801 276 100       1245 if ( my $error = $@ ) {
802 1         30 $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 275         1640 $self->metric_count( 'time_microseconds_total', { 'callback' => 'body', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
808 275         1266 $self->check_timeout();
809             }
810 264         1307 $self->set_alarm(0);
811             };
812 265 100       1916 if ( my $error = $@ ) {
813 1 50       35 if ( my $type = $self->is_exception_type( $error ) ) {
814 1         64 $self->metric_count( 'callback_error_total', { 'stage' => 'body', 'type' => $type } );
815 1         21 $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         16 $self->tempfail_on_error();
822             }
823 265         1291 $self->dbgoutwrite();
824 265         1611 $self->status('postbody');
825 265         1780 return $self->get_return();
826             }
827              
828              
829             sub top_eom_callback {
830              
831             # On End of Message
832 267     267 1 862 my ($self) = @_;
833 267         999 $self->status('eom');
834 267         2197 $self->dbgout( 'CALLBACK', 'EOM', LOG_DEBUG );
835 267         1861 $self->set_return( $self->smfis_continue() );
836 267         1082 my $config = $self->config();
837 267         1003 eval {
838 267     1   5773 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'EOM callback timeout' }) };
  1         46  
839 267 50       1845 if ( my $timeout = $self->get_type_timeout( 'content' ) ) {
840 267         1122 $self->set_alarm( $timeout );
841             }
842              
843 267         1758 my $callbacks = $self->get_callbacks( 'eom' );
844 267         1362 foreach my $handler ( @$callbacks ) {
845 531         2880 $self->dbgout( 'CALLBACK', 'EOM ' . $handler, LOG_DEBUG );
846 531         2313 my $start_time = $self->get_microseconds();
847 531         1429 eval{ $self->get_handler($handler)->eom_callback(); };
  531         1801  
848 531 100       2110 if ( my $error = $@ ) {
849 1         42 $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 530         2956 $self->metric_count( 'time_microseconds_total', { 'callback' => 'eom', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
855 530         2345 $self->check_timeout();
856             }
857 266         1514 $self->set_alarm(0);
858             };
859 267 100       1771 if ( my $error = $@ ) {
860 1 50       9 if ( my $type = $self->is_exception_type( $error ) ) {
861 1         46 $self->metric_count( 'callback_error_total', { 'stage' => 'eom', 'type' => $type } );
862 1         18 $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         19 $self->tempfail_on_error();
869             }
870             #$self->apply_policy();
871 267         1777 $self->add_headers();
872 267         1293 $self->dbgoutwrite();
873 267         1761 $self->status('posteom');
874 267         1717 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 117 my ($self) = @_;
901 33         139 $self->status('abort');
902 33         247 $self->dbgout( 'CALLBACK', 'Abort', LOG_DEBUG );
903 33         230 $self->set_return( $self->smfis_continue() );
904 33         150 my $config = $self->config();
905 33         92 eval {
906 33     1   890 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Abord callback timeout' }) };
  1         59  
907 33 50       233 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
908 33         146 $self->set_alarm( $timeout );
909             }
910              
911 33         202 my $callbacks = $self->get_callbacks( 'abort' );
912 33         263 foreach my $handler ( @$callbacks ) {
913 1         24 $self->dbgout( 'CALLBACK', 'Abort ' . $handler, LOG_DEBUG );
914 1         8 my $start_time = $self->get_microseconds();
915 1         10 eval{ $self->get_handler($handler)->abort_callback(); };
  1         10  
916 1 50       16 if ( my $error = $@ ) {
917 1         13 $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         136 $self->set_alarm(0);
926             };
927 33 100       202 if ( my $error = $@ ) {
928 1 50       6 if ( my $type = $self->is_exception_type( $error ) ) {
929 1         27 $self->metric_count( 'callback_error_total', { 'stage' => 'abort', 'type' => $type } );
930 1         11 $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         156 $self->status('postabort');
939 33         167 return $self->get_return();
940             }
941              
942              
943             sub top_close_callback {
944              
945             # On end of connection
946 130     130 1 411 my ($self) = @_;
947 130         518 $self->status('close');
948 130         744 $self->dbgout( 'CALLBACK', 'Close', LOG_DEBUG );
949 130         772 $self->set_return( $self->smfis_continue() );
950 130         660 $self->clear_reject_mail();
951 130         641 $self->clear_defer_mail();
952 130         542 $self->clear_quarantine_mail();
953 130         436 my $config = $self->config();
954 130         356 eval {
955 130     1   2729 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'Close callback timeout' }) };
  1         23  
956 130 50       906 if ( my $timeout = $self->get_type_timeout( 'command' ) ) {
957 130         549 $self->set_alarm( $timeout );
958             }
959              
960 130         726 my $callbacks = $self->get_callbacks( 'close' );
961 130         760 foreach my $handler ( @$callbacks ) {
962 1182         5109 $self->dbgout( 'CALLBACK', 'Close ' . $handler, LOG_DEBUG );
963 1182         3436 my $start_time = $self->get_microseconds();
964 1182         2322 eval{ $self->get_handler($handler)->close_callback(); };
  1182         2968  
965 1182 100       3639 if ( my $error = $@ ) {
966 1         12 $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         5170 $self->metric_count( 'time_microseconds_total', { 'callback' => 'close', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
972 1181         4838 $self->check_timeout();
973              
974 1181         2926 my $handler_object = $self->get_handler($handler);
975 1181         4379 foreach my $key ( sort keys $handler_object->%* ) {
976 1181 50       4546 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         641 $self->set_alarm(0);
981             };
982 130 100       825 if ( my $error = $@ ) {
983 1 50       6 if ( my $type = $self->is_exception_type( $error ) ) {
984 1         32 $self->metric_count( 'callback_error_total', { 'stage' => 'close', 'type' => $type } );
985 1         12 $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         7 $self->tempfail_on_error();
992             }
993 130         418 delete $self->{'helo_name'};
994 130         421 delete $self->{'seen_helo_name'};
995 130         384 delete $self->{'raw_helo_name'};
996 130         1365 delete $self->{'c_auth_headers'};
997 130         1919 delete $self->{'auth_headers'};
998 130         519 delete $self->{'pre_headers'};
999 130         390 delete $self->{'add_headers'};
1000 130         410 delete $self->{'ip_object'};
1001 130         784 delete $self->{'raw_ip_object'};
1002 130         638 $self->dbgoutwrite();
1003 130         1329 $self->clear_all_symbols();
1004 130         553 $self->status('postclose');
1005 130         896 return $self->get_return();
1006             }
1007              
1008              
1009             sub top_addheader_callback {
1010 450     450 1 1080 my ( $self ) = @_;
1011 450         1221 my $config = $self->config();
1012              
1013 450         921 eval {
1014 450     0   9192 local $SIG{'ALRM'} = sub{ die Mail::Milter::Authentication::Exception->new({ 'Type' => 'Timeout', 'Text' => 'AddHeader callback timeout' }) };
  0         0  
1015 450 100       2983 if ( my $timeout = $self->get_type_timeout( 'addheader' ) ) {
1016 42         195 $self->set_alarm( $timeout );
1017             }
1018              
1019 450         1733 my $callbacks = $self->get_callbacks( 'addheader' );
1020 450         1286 foreach my $handler ( @$callbacks ) {
1021 192         554 my $start_time = $self->get_microseconds();
1022 192         708 $self->get_handler($handler)->addheader_callback($self);
1023 192         1106 $self->metric_count( 'time_microseconds_total', { 'callback' => 'addheader', 'handler' => $handler }, $self->get_microseconds_since( $start_time ) );
1024 192         851 $self->check_timeout();
1025             }
1026 450         1506 $self->set_alarm(0);
1027             };
1028 450 50       3829 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 7560     7560 1 17689 my ($self, $status) = @_;
1047 7560         18024 my $count = $self->{'thischild'}->{'count'};
1048 7560 100       18476 if ( exists ( $self->{'thischild'}->{'smtp'} ) ) {
1049 1666 50       4870 if ( $self->{'thischild'}->{'smtp'}->{'count'} ) {
1050 1666         4102 $count .= '.' . $self->{'thischild'}->{'smtp'}->{'count'};
1051             }
1052             }
1053 7560 50       16067 if ( $status ) {
1054 7560         72749 $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 72209     72209 1 141760 my ($self) = @_;
1064 72209         186056 return $self->{'thischild'}->{'config'};
1065             }
1066              
1067              
1068             sub handler_config {
1069 3432     3432 1 7728 my ($self) = @_;
1070 3432         10526 my $type = $self->handler_type();
1071 3432 50       10645 return if ! $type;
1072 3432 50       11097 if ( $self->is_handler_loaded( $type ) ) {
1073 3432         8451 my $config = $self->config();
1074 3432         8247 my $handler_config = $config->{'handlers'}->{$type};
1075              
1076 3432 50       10603 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 3432         11624 return $handler_config;
1084             }
1085             }
1086              
1087              
1088             sub handler_type {
1089 3432     3432 1 7056 my ($self) = @_;
1090 3432         8686 my $type = ref $self;
1091 3432 50       25502 if ( $type eq 'Mail::Milter::Authentication::Handler' ) {
    50          
1092 0         0 return 'Handler';
1093             }
1094             elsif ( $type =~ /^Mail::Milter::Authentication::Handler::(.*)/ ) {
1095 3432         11847 my $handler_type = $1;
1096 3432         11189 return $handler_type;
1097             }
1098             else {
1099 0         0 return undef; ## no critic
1100             }
1101             }
1102              
1103              
1104             sub set_return {
1105 3801     3801 1 8398 my ( $self, $return ) = @_;
1106 3801         8456 my $top_handler = $self->get_top_handler();
1107 3801         9959 $top_handler->{'return_code'} = $return;
1108             }
1109              
1110              
1111             sub get_return {
1112 3686     3686 1 8382 my ( $self ) = @_;
1113 3686         8699 my $top_handler = $self->get_top_handler();
1114 3686 100       9968 if ( defined $self->get_reject_mail() ) {
    100          
    100          
1115 3         25 return $self->smfis_reject();
1116             }
1117             elsif ( defined $self->get_defer_mail() ) {
1118 9         32 return $self->smfis_tempfail();
1119             }
1120             elsif ( defined $self->get_quarantine_mail() ) {
1121             ## TODO Implement this.
1122             }
1123 3674         19780 return $top_handler->{'return_code'};
1124             }
1125              
1126              
1127             sub get_reject_mail {
1128 4577     4577 1 9090 my ( $self ) = @_;
1129 4577         9546 my $top_handler = $self->get_top_handler();
1130 4577         17490 return $top_handler->{'reject_mail'};
1131             }
1132              
1133              
1134             sub clear_reject_mail {
1135 409     409 1 1410 my ( $self ) = @_;
1136 409         1212 my $top_handler = $self->get_top_handler();
1137 409         1218 delete $top_handler->{'reject_mail'};
1138             }
1139              
1140              
1141             sub get_defer_mail {
1142 4572     4572 1 8676 my ( $self ) = @_;
1143 4572         9354 my $top_handler = $self->get_top_handler();
1144 4572         15841 return $top_handler->{'defer_mail'};
1145             }
1146              
1147              
1148             sub clear_defer_mail {
1149 407     407 1 1061 my ( $self ) = @_;
1150 407         1151 my $top_handler = $self->get_top_handler();
1151 407         1119 delete $top_handler->{'defer_mail'};
1152             }
1153              
1154              
1155              
1156             sub get_quarantine_mail {
1157 4871     4871 1 9143 my ( $self ) = @_;
1158 4871         9528 my $top_handler = $self->get_top_handler();
1159 4871         15029 return $top_handler->{'quarantine_mail'};
1160             }
1161              
1162              
1163             sub clear_quarantine_mail {
1164 407     407 1 1151 my ( $self ) = @_;
1165 407         1063 my $top_handler = $self->get_top_handler();
1166 407         1208 delete $top_handler->{'quarantine_mail'};
1167             }
1168              
1169              
1170             sub get_top_handler {
1171 130067     130067 1 227469 my ($self) = @_;
1172 130067         219396 my $thischild = $self->{'thischild'};
1173 130067         235265 my $object = $thischild->{'handler'}->{'_Handler'};
1174 130067         233455 return $object;
1175             }
1176              
1177              
1178             sub is_handler_loaded {
1179 12580     12580 1 25401 my ( $self, $name ) = @_;
1180 12580         27648 my $config = $self->config();
1181 12580 100       35257 if ( exists ( $config->{'handlers'}->{$name} ) ) {
1182 9277         29518 return 1;
1183             }
1184 3303         14590 return 0;
1185             }
1186              
1187              
1188             sub get_handler {
1189 18647     18647 1 38368 my ( $self, $name ) = @_;
1190 18647         33129 my $thischild = $self->{'thischild'};
1191 18647         40013 my $object = $thischild->{'handler'}->{$name};
1192 18647         101249 return $object;
1193             }
1194              
1195              
1196             sub get_callbacks {
1197 4241     4241 1 10360 my ( $self, $callback ) = @_;
1198 4241         8256 my $thischild = $self->{'thischild'};
1199 4241         13083 return $thischild->{'callbacks_list'}->{$callback};
1200             }
1201              
1202              
1203             sub set_object_maker {
1204 145     145 1 778 my ( $self, $name, $ref ) = @_;
1205 145         782 my $thischild = $self->{'thischild'};
1206 145 100       956 return if $thischild->{'object_maker'}->{$name};
1207 23         201 $thischild->{'object_maker'}->{$name} = $ref;
1208             }
1209              
1210              
1211             sub get_object {
1212 1928     1928 1 5658 my ( $self, $name ) = @_;
1213              
1214 1928         4172 my $thischild = $self->{'thischild'};
1215 1928         4970 my $object = $thischild->{'object'}->{$name};
1216 1928 100       5037 if ( ! $object ) {
1217              
1218 571 100       3640 if ( exists( $thischild->{'object_maker'}->{$name} ) ) {
    100          
1219 65         213 my $maker = $thischild->{'object_maker'}->{$name};
1220 65         319 &$maker( $self, $name );
1221             }
1222              
1223             elsif ( $name eq 'resolver' ) {
1224 163         876 $self->dbgout( 'Object created', $name, LOG_DEBUG );
1225 163 50       1033 if ( defined $TestResolver ) {
1226 163         750 $object = $TestResolver;
1227 163         5793 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 163         2270 $thischild->{'object'}->{$name} = {
1243             'object' => $object,
1244             'destroy' => 0,
1245             };
1246             }
1247              
1248             }
1249 1928         7805 return $thischild->{'object'}->{$name}->{'object'};
1250             }
1251              
1252              
1253             sub set_object {
1254 892     892 1 3078 my ( $self, $name, $object, $destroy ) = @_;
1255 892         2002 my $thischild = $self->{'thischild'};
1256 892         3193 $self->dbgout( 'Object set', $name, LOG_DEBUG );
1257 892         6981 $thischild->{'object'}->{$name} = {
1258             'object' => $object,
1259             'destroy' => $destroy,
1260             };
1261             }
1262              
1263              
1264             sub destroy_object {
1265 1126     1126 1 3796 my ( $self, $name ) = @_;
1266 1126         2501 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 1126 100       4446 return if ! $thischild->{'object'}->{$name};
1274 499 100       1631 if ($name eq 'resolver' ) {
1275 19 50       1003 if ( $thischild->{'object'}->{'resolver'}->{'object'}->can( 'clear_error_cache' ) ) {
1276 0         0 $thischild->{'object'}->{'resolver'}->{'object'}->clear_error_cache();
1277             }
1278             }
1279 499 100       1781 return if ! $thischild->{'object'}->{$name}->{'destroy'};
1280 464         1775 $self->dbgout( 'Object destroyed', $name, LOG_DEBUG );
1281 464         13445 delete $thischild->{'object'}->{$name};
1282             }
1283              
1284              
1285             sub destroy_all_objects {
1286             # Unused!
1287 25     25 1 254 my ( $self ) = @_;
1288 25         98 my $thischild = $self->{'thischild'};
1289 25         89 foreach my $name ( keys %{ $thischild->{'object'} } )
  25         221  
1290             {
1291 35         174 $self->destroy_object( $name );
1292             }
1293             }
1294              
1295              
1296             sub exit_on_close {
1297 24     24 1 189 my ( $self, $error ) = @_;
1298 24 50       167 $error = 'Generic exit_on_close requested' if ! $error;
1299 24         265 $self->log_error( $error );
1300 24         179 my $top_handler = $self->get_top_handler();
1301 24         157 $top_handler->{'exit_on_close'} = 1;
1302 24 100       190 $top_handler->{'exit_on_close_error'} = 'Exit on close requested' if ! exists $top_handler->{'exit_on_close_error'};
1303 24         201 $top_handler->{'exit_on_close_error'} .= "\n$error";
1304             }
1305              
1306              
1307             sub reject_mail {
1308 3     3 1 26 my ( $self, $reason ) = @_;
1309 3         34 my ( $rcode, $xcode, $message ) = split( ' ', $reason, 3 );
1310 3 50 33     107 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         14 my $top_handler = $self->get_top_handler();
1315 3         19 $top_handler->{'reject_mail'} = $reason;
1316             }
1317              
1318              
1319             sub quarantine_mail {
1320 12     12 1 70 my ( $self, $reason ) = @_;
1321 12         50 my $top_handler = $self->get_top_handler();
1322 12         97 $top_handler->{'quarantine_mail'} = $reason;
1323             }
1324              
1325              
1326             sub defer_mail {
1327 9     9 1 25 my ( $self, $reason ) = @_;
1328 9         40 my ( $rcode, $xcode, $message ) = split( ' ', $reason, 3 );
1329 9 50 33     118 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         28 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 439 my ( $self ) = @_;
1340 130         390 my $top_handler = $self->get_top_handler();
1341 130         599 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 1772 my ( $self, $code, $key, $value ) = @_;
1368 461         2671 $self->dbgout( 'SetSymbol', "$code: $key: $value", LOG_DEBUG );
1369 461         2060 my $top_handler = $self->get_top_handler();
1370 461 100       1967 if ( ! exists ( $top_handler->{'symbols'} ) ) {
1371 80         659 $top_handler->{'symbols'} = {};
1372             }
1373 461 100       1758 if ( ! exists ( $top_handler->{'symbols'}->{$code} ) ) {
1374 146         872 $top_handler->{'symbols'}->{$code} = {};
1375             }
1376 461         3048 $top_handler->{'symbols'}->{$code}->{$key} = $value;;
1377             }
1378              
1379              
1380             sub get_symbol {
1381 64636     64636 1 126435 my ( $self, $searchkey ) = @_;
1382 64636         135663 my $top_handler = $self->get_top_handler();
1383 64636   100     217912 my $symbols = $top_handler->{'symbols'} || {};
1384 64636         105518 foreach my $code ( keys %{$symbols} ) {
  64636         283253  
1385 53593         96673 my $subsymbols = $symbols->{$code};
1386 53593         80499 foreach my $key ( keys %{$subsymbols} ) {
  53593         121802  
1387 115024 100       281745 if ( $searchkey eq $key ) {
1388 20815         74768 return $subsymbols->{$key};
1389             }
1390             }
1391             }
1392             }
1393              
1394              
1395             sub tempfail_on_error {
1396 24     24 1 145 my ( $self ) = @_;
1397 24         181 my $config = $self->config();
1398 24 50       291 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       162 if ( $config->{'tempfail_on_error'} ) {
1418 24         170 $self->log_error('TempFail set');
1419 24         365 $self->set_return( $self->smfis_tempfail() );
1420             }
1421             }
1422             }
1423              
1424              
1425              
1426             # Common calls into other Handlers
1427              
1428 147     147   341 sub _dequeue_dir($self) {
  147         323  
  147         282  
1429 147         544 my $config = $self->config();
1430 147         660 my $dir = $config->{spool_dir}.'/dequeue';
1431 147 100       3426 mkdir $dir if ! -d $dir;
1432 147         760 return $dir;
1433             }
1434              
1435              
1436             {
1437             my $queue_index = 1;
1438 62     62 1 198 sub add_dequeue($self,$key,$data) {
  62         157  
  62         214  
  62         167  
  62         141  
1439 62         380 my $dir = $self->_dequeue_dir;
1440 62         162 my $fullpath;
1441 62         508 my $timestamp = join( '.',gettimeofday);
1442 62         516 my $filename = join( '.',$key,$PID,$timestamp,$queue_index++,'dequeue');
1443 62         284 $fullpath = "$dir/$filename";
1444 62         4396 my $serialised_data = encode_sereal($data);
1445 62         1201 write_file($fullpath,{atomic=>1},$serialised_data);
1446             }
1447             }
1448              
1449              
1450 3     3 1 12 sub get_dequeue_list($self,$key) {
  3         20  
  3         40  
  3         25  
1451 3         76 my $dir = $self->_dequeue_dir;
1452 3         39 my $dequeue_index_file = $dir.'/dequeue.index';
1453 3         17 my $dequeue_lock_file = $dir.'/dequeue.lock';
1454              
1455 3         250 my $lock = Lock::File->new( $dequeue_lock_file, {} );
1456 3         1534 my $count_new = 0;
1457 3         13 my $count_allocated = 0;
1458 3         8 my $count_stale = 0;
1459              
1460 3         21 my $dequeue_index = {};
1461 3         662 my $j = JSON->new->pretty->canonical->utf8;
1462              
1463             # Build a list of Process IDs
1464 3         51 my $process_ids = {};
1465 3         167 my $process_table = Proc::ProcessTable->new();
1466 3         7000 foreach my $process ( @{$process_table->table} ) {
  3         18086  
1467 45         1295 $process_ids->{$process->pid} = 1;
1468             }
1469              
1470             # Read the last state from the index file
1471 3 100       111 if ( -e $dequeue_index_file ) {
1472 2         7 eval {
1473 2         77 my $body = scalar read_file($dequeue_index_file);
1474 2         635 $dequeue_index = $j->decode($body);
1475             };
1476             }
1477              
1478 3         50 my @dequeue_list;
1479 3 50       107 opendir(my $dh, $dir) || die "Failed to open dequeue directory: $!";
1480             FILE:
1481 3         165 while (my $file = readdir $dh) {
1482 52 100       426 if ( $file =~ /^$key\..*\.dequeue$/ ) {
1483 41 50       111 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         469 $dequeue_index->{$file} = {
1494             pid => $PID,
1495             };
1496 41         111 $count_new++;
1497 41         196 push @dequeue_list, $file;
1498             }
1499             }
1500 3         44 closedir $dh;
1501              
1502             # Remove deleted files from the dequeue index
1503 3         62 foreach my $id ( sort keys $dequeue_index->%* ) {
1504 73         228 my $filepath = join('/',$dir,$id);
1505 73 100       1129 delete $dequeue_index->{$id} unless -e $filepath;
1506             }
1507 3         275 write_file($dequeue_index_file,{atomic=>1},$j->encode($dequeue_index));
1508              
1509 3         4863 $lock->unlock;
1510              
1511 3         359 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'new' }, $count_new - $count_stale );
1512 3         51 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'allocated' }, $count_allocated );
1513 3         26 $self->metric_set( 'dequeue_files_total', { 'key' => $key, 'state' => 'stale' }, $count_stale );
1514              
1515 3         395 return \@dequeue_list;
1516             }
1517              
1518              
1519 41     41 1 106 sub get_dequeue($self,$id) {
  41         78  
  41         95  
  41         71  
1520 41         138 my $dir = $self->_dequeue_dir;
1521 41         184 my $filepath = join('/',$dir,$id);
1522 41 50       700 return if ! -e $filepath;
1523 41 50       522 return if ! -f $filepath;
1524 41         280 my $serialized = scalar read_file($filepath);
1525 41         12585 my $data = decode_sereal($serialized);
1526 41         263 return $data;
1527             }
1528              
1529              
1530 41     41 1 98 sub delete_dequeue($self,$id) {
  41         82  
  41         86  
  41         93  
1531 41         124 my $dir = $self->_dequeue_dir;
1532 41         149 my $filepath = join('/',$dir,$id);
1533 41 50       564 return if ! -e $filepath;
1534 41 50       517 return if ! -f $filepath;
1535 41         3502 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 7 my($self,$header,$silent) = @_;
1550 2 50       7 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 2850     2850 1 6437 my ($self) = @_;
1557 2850 100       8006 return 0 if ! $self->is_handler_loaded('LocalIP');
1558 1868         5163 return $self->get_handler('LocalIP')->{'is_local_ip_address'};
1559             }
1560              
1561              
1562             sub is_trusted_ip_address {
1563 3419     3419 1 7209 my ($self) = @_;
1564 3419 100       6919 return 0 if ! $self->is_handler_loaded('TrustedIP');
1565 2229         5127 return $self->get_handler('TrustedIP')->{'is_trusted_ip_address'};
1566             }
1567              
1568              
1569             sub is_encrypted {
1570 4     4 1 18 my ($self) = @_;
1571 4 50       9 return undef if ! $self->is_handler_loaded('TLS'); ## no critic
1572 4         10 return $self->get_handler('TLS')->{'is_encrypted'};
1573             }
1574              
1575              
1576             sub is_authenticated {
1577 2414     2414 1 5147 my ($self) = @_;
1578 2414 100       5256 return 0 if ! $self->is_handler_loaded('Auth');
1579 1430         3174 return $self->get_handler('Auth')->{'is_authenticated'};
1580             }
1581              
1582              
1583             sub ip_address {
1584 376     376 1 1082 my ($self) = @_;
1585 376         1271 my $top_handler = $self->get_top_handler();
1586 376         2602 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 768     768 1 1370 my ( $self, $text ) = @_;
1598 768 50       1610 $text = q{} if ! defined $text;
1599 768         1492 $text =~ s/\t/ /g;
1600 768         1196 $text =~ s/\n/ /g;
1601 768         1266 $text =~ s/\r/ /g;
1602 768         1236 $text =~ s/\(/ /g;
1603 768         1212 $text =~ s/\)/ /g;
1604 768         1206 $text =~ s/\\/ /g;
1605 768         1557 return $text;
1606             }
1607              
1608              
1609             sub format_ctext_no_space {
1610 768     768 1 1521 my ( $self, $text ) = @_;
1611 768         1761 $text = $self->format_ctext($text);
1612 768         1402 $text =~ s/ //g;
1613 768         1304 $text =~ s/;/_/g;
1614 768         1469 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 384     384 1 1171 my ( $self, $key, $value ) = @_;
1627 384         1386 $key = $self->format_ctext_no_space($key);
1628 384         925 $value = $self->format_ctext_no_space($value);
1629 384         1179 my $string = "$key=$value";
1630 384         1215 return $string;
1631             }
1632              
1633              
1634             sub get_domain_from {
1635 472     472 1 9193 my ( $self, $address ) = @_;
1636 472 50       1382 $address = q{} if ! defined $address;
1637 472         1491 $address = $self->get_address_from($address);
1638 472         1260 my $domain = 'localhost.localdomain';
1639 472         1385 $address =~ s/<//g;
1640 472         1075 $address =~ s/>//g;
1641 472 100       1691 if ( $address =~ /\@/ ) {
1642 469         2366 ($domain) = $address =~ /.*\@(.*)/;
1643             }
1644 472         1456 $domain =~ s/\s//g;
1645 472         3316 return lc $domain;
1646             }
1647              
1648              
1649             sub get_domains_from {
1650 229     229 1 1407 my ( $self, $addresstxt ) = @_;
1651 229 50       643 $addresstxt = q{} if ! defined $addresstxt;
1652 229         732 my $addresses = $self->get_addresses_from($addresstxt);
1653 229         692 my $domains = [];
1654 229         873 foreach my $address ( @$addresses ) {
1655 239         459 my $domain;
1656 239         625 $address =~ s/<//g;
1657 239         529 $address =~ s/>//g;
1658 239 100       1011 if ( $address =~ /\@/ ) {
1659 233         1250 ($domain) = $address =~ /.*\@(.*)/;
1660             }
1661 239 100       757 next if ! defined $domain;
1662 233         566 $domain =~ s/\s//g;
1663 233         792 push @$domains, lc $domain;
1664             }
1665 229         1060 return $domains;
1666             }
1667              
1668 127     127   1395 use constant IsSep => 0;
  127         316  
  127         8565  
1669 127     127   1015 use constant IsPhrase => 1;
  127         935  
  127         7443  
1670 127     127   956 use constant IsEmail => 2;
  127         282  
  127         6486  
1671 127     127   810 use constant IsComment => 3;
  127         280  
  127         646859  
1672              
1673              
1674             sub get_address_from {
1675 893     893 1 12113 my ( $self, $Str ) = @_;
1676 893         2896 my $addresses = $self->get_addresses_from( $Str );
1677 893         3730 return $addresses->[0];
1678             }
1679              
1680              
1681             sub get_addresses_from {
1682 1211     1211 1 3296 my ( $self, $Str ) = @_;
1683 1211 50       2936 $Str = q{} if ! defined $Str;
1684              
1685 1211 100       2872 if ( $Str eq q{} ) {
1686 22         126 $self->log_error( 'Could not parse empty address' );
1687 22         120 return [ $Str ];
1688             }
1689              
1690 1189         5381 my $IDNComponentRE = qr/[^\x20-\x2c\x2e\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/;
1691 1189         7844 my $IDNRE = qr/(?:$IDNComponentRE\.)+$IDNComponentRE/;
1692 1189         3360 my $RFC_atom = qr/[a-z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+/i;
1693 1189         6541 my $RFC_dotatom = qr/${RFC_atom}(?:\.${RFC_atom})*/;
1694              
1695             # Break everything into Tokens
1696 1189         2500 my ( @Tokens, @Types );
1697             TOKEN_LOOP:
1698 1189         2042 while (1) {
1699 2774 100       19982 if ($Str =~ m/\G\"(.*?)(?<!\\)(?:\"|\z)\s*/sgc) {
    100          
    100          
    100          
    100          
    50          
1700             # String " ... "
1701 19         91 push @Tokens, $1;
1702 19         50 push @Types, IsPhrase;
1703             }
1704             elsif ( $Str =~ m/\G\<(.*?)(?<!\\)(?:[>,;]|\z)\s*/sgc) {
1705             # String < ... >
1706 263         939 push @Tokens, $1;
1707 263         598 push @Types, IsEmail;
1708             }
1709             elsif ($Str =~ m/\G\((.*?)(?<!\\)\)\s*/sgc) {
1710             # String ( ... )
1711 2         6 push @Tokens, $1;
1712 2         4 push @Types, IsComment;
1713             }
1714             elsif ($Str =~ m/\G[,;]\s*/gc) {
1715             # Comma or semi-colon
1716 28         87 push @Tokens, undef;
1717 28         65 push @Types, IsSep;
1718             }
1719             elsif ($Str =~ m/\G$/gc) {
1720             # End of line
1721 1189         2907 last TOKEN_LOOP;
1722             }
1723             elsif ($Str =~ m/\G([^\s,;"<]*)\s*/gc) {
1724             # Anything else
1725 1273 50       4300 if (length $1) {
1726 1273         3390 push @Tokens, $1;
1727 1273         2522 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 1189         1987 my @Addrs;
1741 1189         2189 my ($Phrase, $Email, $Comment, $Type);
1742 1189         3544 for (my $i = 0; $i < scalar(@Tokens); $i++) {
1743 1585         4057 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 1585 100 100     11210 if (($Type == IsSep) ||
      100        
      100        
      100        
1750             ($Type == IsEmail && defined($Email)) ||
1751             ($Type == IsPhrase && defined($Email)) ) {
1752 55 100       174 push @Addrs, $Email if defined $Email;
1753 55         144 ($Phrase, $Email, $Comment) = (undef, undef, undef);
1754             }
1755              
1756             # A phrase...
1757 1585 100       4130 if ($Type == IsPhrase) {
    100          
    100          
1758             # Strip '...' around token
1759 1292         2835 $Token =~ s/^'(.*)'$/$1/;
1760             # Strip any newlines assuming folded headers
1761 1292         2943 $Token =~ s/\r?\n//g;
1762              
1763             # Email like token?
1764 1292 100       8393 if ($Token =~ /^$RFC_dotatom\@$IDNRE$/o) {
1765 945         2196 $Token =~ s/^\s+//;
1766 945         2197 $Token =~ s/\s+$//;
1767 945         1975 $Token =~ s/\s+\@/\@/;
1768 945         2235 $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 945 50 66     3163 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 945 50       2106 if (defined($Email)) {
1777 0         0 push @Addrs, $Email;
1778 0         0 ($Phrase, $Email, $Comment) = (undef, undef, undef);
1779             }
1780 945         2863 $Email = $Token;
1781             }
1782             }
1783             else {
1784             # No, just add as phrase
1785 347 100       1670 $Phrase = defined($Phrase) ? $Phrase . " " . $Token : $Token;
1786             }
1787             }
1788             elsif ($Type == IsEmail) {
1789             # If an email, set email addr. Should be empty
1790 263         803 $Email = $Token;
1791             }
1792             elsif ($Type == IsComment) {
1793 2 50       11 $Comment = defined($Comment) ? $Comment . ", " . $Token : $Token;
1794             }
1795             # Must be separator, do nothing
1796             }
1797              
1798             # Add any remaining addresses
1799 1189 100       3574 push @Addrs, $Email if defined($Email);
1800              
1801 1189 100       2737 if ( ! @Addrs ) {
1802             # We couldn't parse, so just run with it and hope for the best
1803 19         46 push @Addrs, $Str;
1804 19         133 $self->log_error( 'Could not parse address ' . $Str );
1805             }
1806              
1807 1189         2023 my @TidyAddresses;
1808 1189         2654 foreach my $Address ( @Addrs ) {
1809              
1810 1227 50       3236 next if ( $Address =~ /\@unspecified-domain$/ );
1811              
1812 1227 50       2913 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 1227         2689 $Address =~ s/^\s+//;
1821 1227         2525 $Address =~ s/\s+$//;
1822 1227         2332 $Address =~ s/\s+\@/\@/;
1823 1227         2747 $Address =~ s/\@\s+/\@/;
1824              
1825 1227         3485 push @TidyAddresses, $Address;
1826             }
1827              
1828 1189 50       2856 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 1189         6879 return \@TidyAddresses;
1834              
1835             }
1836              
1837              
1838             sub get_my_hostname {
1839 350     350 1 936 my ($self) = @_;
1840 350         1121 my $hostname = $self->get_symbol('j');
1841 350 100       1760 if ( ! $hostname ) {
1842 224         766 $hostname = $self->get_symbol('{rcpt_host}');
1843             }
1844 350 100       1622 if ( ! $hostname ) { # Fallback
1845 224         1364 $hostname = hostname;
1846             }
1847 350         3926 return $hostname;
1848             }
1849              
1850              
1851             sub get_my_authserv_id {
1852 263     263 1 3419 my ($self) = @_;
1853 263         994 my $config = $self->config();
1854 263 50 66     1346 if ( exists( $config->{'authserv_id'} ) && $config->{'authserv_id'} ) {
1855 20         136 return $config->{'authserv_id'};
1856             }
1857 243         934 return $self->get_my_hostname();
1858             }
1859              
1860              
1861              
1862             # Logging
1863              
1864              
1865             sub dbgout {
1866 43459     43459 1 118393 my ( $self, $key, $value, $priority ) = @_;
1867 43459   100     108238 my $queue_id = $self->get_symbol('i') || q{--};
1868 43459 50       107682 $key = q{--} if ! defined $key;
1869 43459 50       88418 $value = q{--} if ! defined $value;
1870              
1871 43459         77233 my $thischild = $self->{'thischild'};
1872 43459 100       98064 if ( exists $thischild->{'tracelog'} ) {
1873 28902         104214 push $thischild->{'tracelog'}->@*, time2str('%Y:%m:%d %X %z',time) . " $queue_id: $key: $value";
1874             }
1875              
1876 43459         6660848 my $config = $self->config();
1877 43459 100 100     197122 if (
1878             $priority == LOG_DEBUG
1879             &&
1880             ! $config->{'debug'}
1881             ) {
1882 28783         85516 return;
1883             }
1884              
1885             # Sys::Syslog and Log::Dispatchouli have different priority models
1886 14676 0       34609 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 14676 50       30661 if ( $config->{'logtoerr'} ) {
1897 14676         58186 Mail::Milter::Authentication::_warn( "$queue_id: $key: $value" );
1898             }
1899              
1900 14676         71298 my $top_handler = $self->get_top_handler();
1901 14676 100       42232 if ( !exists( $top_handler->{'dbgout'} ) ) {
1902 886         3469 $top_handler->{'dbgout'} = [];
1903             }
1904 14676   50     25788 push @{ $top_handler->{'dbgout'} },
  14676   100     95080  
1905             {
1906             'priority' => $log_priority,
1907             'key' => $key || q{},
1908             'value' => $value || q{},
1909             };
1910              
1911             # Write now if we can.
1912 14676 100       40656 if ( $self->get_symbol('i') ) {
1913 282         1727 $self->dbgoutwrite();
1914             }
1915             }
1916              
1917              
1918             sub log_error {
1919 96     96 1 360 my ( $self, $error ) = @_;
1920 96         487 $self->dbgout( 'ERROR', $error, LOG_ERR );
1921             }
1922              
1923              
1924             sub dbgoutwrite {
1925 1217     1217 1 3181 my ($self) = @_;
1926 1217         2470 eval {
1927 1217         3323 my $config = $self->config();
1928 1217   66     3462 my $queue_id = $self->get_symbol('i') ||
1929             'NOQUEUE.' . substr( uc md5_hex( "Authentication Milter Client $PID " . time() . rand(100) ) , -11 );
1930 1217         3672 my $top_handler = $self->get_top_handler();
1931 1217 100       3978 if ( exists( $top_handler->{'dbgout'} ) ) {
1932             LOGENTRY:
1933 872         1627 foreach my $entry ( @{ $top_handler->{'dbgout'} } ) {
  872         2706  
1934 14403         7554888 my $key = $entry->{'key'};
1935 14403         30579 my $value = $entry->{'value'};
1936 14403         26909 my $priority = $entry->{'priority'};
1937 14403         38642 my $line = "$queue_id: $key: $value";
1938 14403 50 66     61646 if (
1939             $priority eq 'debug'
1940             &&
1941             ! $config->{'debug'}
1942             ) {
1943 0         0 next LOGENTRY;
1944             }
1945 14403         46201 Mail::Milter::Authentication::logger()->log( { 'level' => $priority }, $line );
1946             }
1947             }
1948 1217         564242 delete $top_handler->{'dbgout'};
1949             };
1950 1217         4900 $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 42     42 1 168 my ( $self, $header ) = @_;
1962 42         190 return 0;
1963             }
1964              
1965              
1966             sub header_sort {
1967 971     971 1 2306 my ( $self, $sa, $sb ) = @_;
1968              
1969 971         2635 my $config = $self->config();
1970              
1971 971         2718 my $string_a;
1972             my $string_b;
1973              
1974 971         0 my $handler_a;
1975 971 50       2728 if ( ref $sa eq 'Mail::AuthenticationResults::Header::Entry' ) {
1976 971         2942 $handler_a = $sa->key();
1977 971         11832 $string_a = $sa->as_string();
1978             }
1979             else {
1980 0         0 ( $handler_a ) = split( '=', $sa, 2 );
1981 0         0 $string_a = $sa;
1982             }
1983 971         733738 my $handler_b;
1984 971 50       2981 if ( ref $sb eq 'Mail::AuthenticationResults::Header::Entry' ) {
1985 971         2626 $handler_b = $sb->key();
1986 971         11158 $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 971 100       944663 if ( $handler_a eq $handler_b ) {
1994             # Check for a handler specific sort method
1995 40         119 foreach my $name ( @{$config->{'load_handlers'}} ) {
  40         191  
1996 50         222 my $handler = $self->get_handler($name);
1997 50 100       403 if ( $handler->can_sort_header( lc $handler_a ) ) {
1998 8 50       45 if ( $handler->can( 'handler_header_sort' ) ) {
1999 8         37 return $handler->handler_header_sort( $sa, $sb );
2000             }
2001             }
2002             }
2003             }
2004              
2005 963         3570 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 267     267 1 842 my ($self) = @_;
2019 267         927 my $config = $self->config();
2020 267         968 my $top_handler = $self->get_top_handler();
2021 267         665 my @types;
2022 267 100       1638 push @types, keys $top_handler->{'c_auth_headers'}->%* if exists $top_handler->{'c_auth_headers'};
2023 267 100       1708 push @types, keys $top_handler->{'auth_headers'}->%* if exists $top_handler->{'auth_headers'};
2024              
2025 267   100     1045 my $queue_id = $self->get_symbol('i') || q{--};
2026 267         2488 $self->{extended_log} = {ar => [], queue_id => $queue_id};
2027              
2028 267         2442 for my $type (uniq sort @types) {
2029 247         1216 $self->add_auth_headers_of_type($type);
2030             }
2031              
2032 267 50       1565 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 267 100       1053 if ( my $reason = $self->get_quarantine_mail() ) {
2038 11         48 $self->prepend_header( 'X-Disposition-Quarantine', $reason );
2039             }
2040              
2041 267         1627 $top_handler->top_addheader_callback();
2042              
2043 267 100       1184 if ( exists( $top_handler->{'pre_headers'} ) ) {
2044 247         606 foreach my $header ( @{ $top_handler->{'pre_headers'} } ) {
  247         932  
2045             $self->dbgout( 'PreHeader',
2046 383         3083 $header->{'field'} . ': ' . $header->{'value'}, LOG_DEBUG );
2047 383         3077 $self->insert_header( 1, $header->{'field'}, $header->{'value'} );
2048             }
2049             }
2050              
2051 267 100       2033 if ( exists( $top_handler->{'add_headers'} ) ) {
2052 4         15 foreach my $header ( @{ $top_handler->{'add_headers'} } ) {
  4         21  
2053             $self->dbgout( 'AddHeader',
2054 18         252 $header->{'field'} . ': ' . $header->{'value'}, LOG_DEBUG );
2055 18         84 $self->add_header( $header->{'field'}, $header->{'value'} );
2056             }
2057             }
2058             }
2059              
2060              
2061 247     247 0 633 sub add_auth_headers_of_type($self,$type) {
  247         530  
  247         584  
  247         875  
2062 247         703 my $config = $self->config();
2063 247         1017 my $top_handler = $self->get_top_handler();
2064              
2065 247         800 my @auth_headers;
2066 247 100       1184 if ( exists( $top_handler->{'c_auth_headers'}->{$type} ) ) {
2067 81         192 @auth_headers = @{ $top_handler->{'c_auth_headers'}->{$type} };
  81         395  
2068             }
2069 247 100       1083 if ( exists( $top_handler->{'auth_headers'}->{$type} ) ) {
2070 226         566 @auth_headers = ( @auth_headers, @{ $top_handler->{'auth_headers'}->{$type} } );
  226         864  
2071             }
2072 247 50       1111 if (@auth_headers) {
    0          
2073              
2074 247         1559 @auth_headers = sort { $self->header_sort( $a, $b ) } @auth_headers;
  971         2912  
2075              
2076             # Do we have any legacy type headers?
2077 247         909 my $are_string_headers = 0;
2078 247         2666 my $header_obj = Mail::AuthenticationResults::Header->new();
2079 247         2928 foreach my $header ( @auth_headers ) {
2080 764 50       23870 if ( ref $header ne 'Mail::AuthenticationResults::Header::Entry' ) {
2081 0         0 $are_string_headers = 1;
2082 0         0 last;
2083             }
2084 764 50       2067 $header->orphan() if exists $header->{parent};
2085 764         2623 $header_obj->add_child( $header );
2086             }
2087              
2088 247         11190 my $header_text;
2089 247 50       1056 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 247         1943 $header_obj->set_value( Mail::AuthenticationResults::Header::AuthServID->new()->safe_set_value( $self->get_my_authserv_id() ) );
2098 247         17311 $header_obj->set_eol( "\n" );
2099 247 50       4225 if ( exists( $config->{'header_indent_style'} ) ) {
2100 0         0 $header_obj->set_indent_style( $config->{'header_indent_style'} );
2101             }
2102             else {
2103 247         1173 $header_obj->set_indent_style( 'entry' );
2104             }
2105 247 50       11750 if ( exists( $config->{'header_indent_by'} ) ) {
2106 0         0 $header_obj->set_indent_by( $config->{'header_indent_by'} );
2107             }
2108             else {
2109 247         813 $header_obj->set_indent_by( 4 );
2110             }
2111 247 50       2182 if ( exists( $config->{'header_fold_at'} ) ) {
2112 0         0 $header_obj->set_fold_at( $config->{'header_fold_at'} );
2113             }
2114 247         1140 $header_text = $header_obj->as_string();
2115              
2116             # Log a single line version of the added auth header
2117 247         1216164 $header_obj->set_indent_style('none');
2118 247         7455 $header_obj->set_fold_at(9999);
2119 247         3023 my $header_log_text = $header_obj->as_string();
2120 247 50 33     1103268 $self->dbgout( "A-R: $type",$header_log_text, LOG_INFO ) unless $config->{extended_log} && ! $config->{legacy_log};
2121 247 50       1616 push $self->{extended_log}->{ar}->@*, {type => $type, payload =>$header_obj->_as_hashref} if $config->{extended_log};
2122             }
2123              
2124 247         1580 my ($header_type,$header_type_postfix) = split /:/, $type;
2125 247         1256 $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 383     383 1 1424 my ( $self, $field, $value ) = @_;
2140 383         1437 my $top_handler = $self->get_top_handler();
2141 383 100       1901 if ( !exists( $top_handler->{'pre_headers'} ) ) {
2142 247         925 $top_handler->{'pre_headers'} = [];
2143             }
2144 383         881 push @{ $top_handler->{'pre_headers'} },
  383         3914  
2145             {
2146             'field' => $field,
2147             'value' => $value,
2148             };
2149             }
2150              
2151              
2152 625     625 1 1589 sub add_auth_header($self,$value) {
  625         1251  
  625         1118  
  625         1117  
2153 625         2003 my $config = $self->handler_config();
2154 625   50     4342 my $header_name = $config->{auth_header_name} // 'Authentication-Results';
2155 625         1703 my $top_handler = $self->get_top_handler();
2156 625 100       2672 $top_handler->{auth_headers} = {} unless exists $top_handler->{auth_headers};
2157 625 100       2586 $top_handler->{auth_headers}->{$header_name} = [] unless exists $top_handler->{auth_headers}->{$header_name};
2158 625         3131 push $top_handler->{auth_headers}->{$header_name}->@*, $value;
2159             }
2160              
2161              
2162 139     139 1 377 sub add_c_auth_header($self,$value) {
  139         434  
  139         304  
  139         249  
2163             # Connection wide auth headers
2164 139         716 my $config = $self->handler_config();
2165 139   50     1070 my $header_name = $config->{auth_header_name} // 'Authentication-Results';
2166 139         457 my $top_handler = $self->get_top_handler();
2167 139 100       789 $top_handler->{c_auth_headers} = {} unless exists $top_handler->{c_auth_headers};
2168 139 100       672 $top_handler->{c_auth_headers}->{$header_name} = [] unless exists $top_handler->{c_auth_headers}->{$header_name};
2169 139         651 push $top_handler->{c_auth_headers}->{$header_name}->@*, $value;
2170             }
2171              
2172              
2173             sub append_header {
2174 18     18 1 51 my ( $self, $field, $value ) = @_;
2175 18         51 my $top_handler = $self->get_top_handler();
2176 18 100       63 if ( !exists( $top_handler->{'add_headers'} ) ) {
2177 4         23 $top_handler->{'add_headers'} = [];
2178             }
2179 18         588 push @{ $top_handler->{'add_headers'} },
  18         118  
2180             {
2181             'field' => $field,
2182             'value' => $value,
2183             };
2184             }
2185              
2186              
2187              
2188             # Lower level methods
2189              
2190              
2191             sub smfis_continue {
2192 3777     3777 1 14187 return SMFIS_CONTINUE;
2193             }
2194              
2195              
2196             sub smfis_tempfail {
2197 33     33 1 364 return SMFIS_TEMPFAIL;
2198             }
2199              
2200              
2201             sub smfis_reject {
2202 3     3 1 21 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 51 my ( $self, $key, $value ) = @_;
2227 18         44 my $thischild = $self->{'thischild'};
2228 18         45 my $config = $self->config();
2229 18 50       65 return if $config->{'dryrun'};
2230 18         117 $thischild->add_header( $key, $value );
2231             }
2232              
2233              
2234             sub insert_header {
2235 383     383 1 1299 my ( $self, $index, $key, $value ) = @_;
2236 383         928 my $thischild = $self->{'thischild'};
2237 383         1191 my $config = $self->config();
2238 383 100       1597 return if $config->{'dryrun'};
2239 379         3109 $thischild->insert_header( $index, $key, $value );
2240             }
2241              
2242              
2243             sub change_header {
2244 26     26 1 135 my ( $self, $key, $index, $value ) = @_;
2245 26         61 my $thischild = $self->{'thischild'};
2246 26         67 my $config = $self->config();
2247 26 50       84 return if $config->{'dryrun'};
2248 26         209 $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.20230911
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