File Coverage

blib/lib/Mail/Milter/Authentication/Protocol/SMTP.pm
Criterion Covered Total %
statement 463 659 70.2
branch 136 260 52.3
condition 21 40 52.5
subroutine 33 38 86.8
pod 23 25 92.0
total 676 1022 66.1


line stmt bran cond sub pod time code
1             package Mail::Milter::Authentication::Protocol::SMTP;
2 126     126   5198 use 5.20.0;
  126         405  
3 126     126   768 use strict;
  126         537  
  126         2736  
4 126     126   637 use warnings;
  126         282  
  126         3208  
5 126     126   763 use Mail::Milter::Authentication::Pragmas;
  126         209  
  126         1394  
6             # ABSTRACT: SMTP protocol handling
7             our $VERSION = '3.20230629'; # VERSION
8 126     126   34324 use Digest::MD5 qw{ md5_hex };
  126         322  
  126         5881  
9 126     126   80469 use Email::Date::Format qw{ email_date };
  126         78050  
  126         8276  
10 126     126   1136 use IO::Socket::INET;
  126         275  
  126         6866  
11 126     126   83104 use IO::Socket::UNIX;
  126         374  
  126         3980  
12 126     126   224199 use IO::Socket;
  126         377  
  126         600  
13 126     126   134211 use Net::IP;
  126         350  
  126         1247303  
14              
15             sub register_metrics {
16             return {
17 17     17 1 404 'mail_processed_total' => 'Number of emails processed',
18             };
19             }
20              
21             sub get_smtp_config {
22 163     163 1 588 my ( $self ) = @_;
23 163         1042 my $client_details = $self->get_client_details();
24 163         7900 my $smtp_config;
25              
26 163 50       1143 if ( exists( $self->{'config'}->{'smtp'}->{ $client_details } ) ) {
27 0         0 $smtp_config = $self->{'config'}->{'smtp'}->{ $client_details };
28             }
29             else {
30 163         617 $smtp_config = $self->{'config'}->{'smtp'};
31             }
32              
33 163         569 return $smtp_config;
34             }
35              
36             sub queue_type {
37 37     37 1 132 my ( $self ) = @_;
38 37         179 my $smtp_config = $self->get_smtp_config();
39 37 50       1251 return $smtp_config->{'queue_type'} eq 'before' ? 'before' : 'after';
40             }
41              
42             sub smtp_status {
43 1787     1787 1 4779 my ( $self, $status ) = @_;
44 1787         4162 my $smtp = $self->{'smtp'};
45 1787         18075 $PROGRAM_NAME = $Mail::Milter::Authentication::Config::IDENT . ':' . $status . '(' . $self->{'count'} . '.' . $smtp->{'count'} . ')';
46             }
47              
48             sub smtp_init {
49 211     211 1 684 my ( $self ) = @_;
50              
51 211 100       1004 return if $self->{'smtp'}->{'init_required'} == 0;
52              
53 43         251 my $handler = $self->{'handler'}->{'_Handler'};
54 43         170 my $smtp = $self->{'smtp'};
55              
56 43         412 $handler->set_symbol( 'C', 'j', $smtp->{'server_name'} );
57 43         291 $handler->set_symbol( 'C', '{rcpt_host}', $smtp->{'server_name'} );
58              
59 43         1828 $smtp->{'queue_id'} = substr( uc md5_hex( "Authentication Milter Client $PID " . time() . rand(100) ) , -11 );
60 43         433 $handler->set_symbol( 'C', 'i', $self->smtp_queue_id() );
61              
62 43         274 $smtp->{'count'}++ ;
63 43         448 $handler->dbgout( 'SMTP Transaction count', $self->{'count'} . '.' . $smtp->{'count'} , LOG_DEBUG );
64              
65 43         345 $smtp->{'init_required'} = 0;
66             }
67              
68             sub protocol_process_request {
69 37     37 1 316 my ( $self ) = @_;
70              
71 37         212 my $handler = $self->{'handler'}->{'_Handler'};
72 37         503 $handler->top_setup_callback();
73              
74 37         307 my $config = $self->{ 'config' };
75 37   50     292 my $seconds = $config->{'content_timeout'} // 300;
76 37         570 $handler->set_overall_timeout( $seconds * 1000000 );
77              
78             $self->{'smtp'} = {
79             'fwd_helo_host' => undef,
80             'fwd_connect_ip' => undef,
81             'fwd_connect_host' => undef,
82             'fwd_ident' => undef,
83             'helo_host' => q{},
84             'mail_from' => q{},
85             'rcpt_to' => [],
86             'has_mail_from' => 0,
87             'has_data' => 0,
88             'connect_ip' => $self->{'server'}->{'peeraddr'},
89 37         2753 'connect_host' => $self->{'server'}->{'peeraddr'},
90             'last_command' => 0,
91             'headers' => [],
92             'body' => q{},
93             'using_lmtp' => 0,
94             'lmtp_rcpt' => [],
95             'init_required' => 1,
96             'string' => q{},
97             'count' => 0,
98             };
99              
100             # If we have a UNIX connection then these will be undef,
101             # Set them to localhost to avoid warnings later.
102 37 50       324 if ( ! $self->{'smtp'}->{'connect_ip'} ) { $self->{'smtp'}->{'connect_ip'} = '127.0.0.1'; }
  37         183  
103              
104 37 50       249 if ( $self->{'smtp'}->{'connect_ip'} eq '127.0.0.1' ) {
105 37         141 $self->{'smtp'}->{'connect_host'} = 'localhost';
106             }
107             else {
108             # TODO do a reverse lookup here!
109             }
110              
111 37         207 my $smtp = $self->{'smtp'};
112 37         149 my $socket = $self->{'socket'};
113              
114 37         665 my $smtp_config = $self->get_smtp_config();
115 37   50     486 $smtp->{'server_name'} = $smtp_config->{'server_name'} || 'server.example.com';
116 37   50     318 $smtp->{'smtp_timeout_in'} = $smtp_config->{'timeout_in'} || 60;
117 37   50     415 $smtp->{'smtp_timeout_out'} = $smtp_config->{'timeout_out'} || 60;
118              
119 37         4203 print $socket "220 " . $smtp->{'server_name'} . " ESMTP AuthenticationMilter\r\n";
120              
121 37         619 $self->smtp_init();
122              
123             COMMAND:
124 37         271 while ( ! $smtp->{'last_command'} ) {
125              
126 331         1275 my $command;
127 331     0   6957 local $SIG{'ALRM'} = sub{ die "Timeout\n" };
  0         0  
128 331         3112 alarm( $smtp->{'smtp_timeout_in'} );
129 331         1060 eval {
130 331         93259 $command = <$socket>;
131             };
132 331 50       2716 if ( my $error = $@ ) {
133 0         0 $self->logerror( "Read Error: $error" );
134 0         0 last COMMAND;
135             }
136 331         2464 alarm( 0 );
137              
138 331 50       1493 if ( ! $command ) {
139 0         0 $self->logdebug( "receive NULL command" );
140 0         0 last COMMAND;
141             }
142              
143 331         4152 $command =~ s/\r?\n$//;
144 331         1524 my $uccommand = uc $command;
145              
146 331         2775 $self->logdebug( "receive command $command" );
147              
148 331 50       1503 if ( exists ( $smtp_config->{ 'debug_triggers' } ) ) {
149 0         0 my $triggers = $smtp_config->{ 'debug_triggers' };
150 0         0 foreach my $trigger ( @$triggers ) {
151 0 0       0 if ( $command =~ /$trigger/ ) {
152 0         0 $self->enable_extra_debugging();
153             }
154             }
155             }
156              
157 331         876 my $returncode = SMFIS_CONTINUE;
158              
159 331 100       4616 if ( $uccommand =~ /^EHLO/ ) {
    50          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
160 37         465 $self->smtp_command_ehlo( $command );
161             }
162             elsif ( $uccommand =~ /^LHLO/ ) {
163 0         0 $self->smtp_command_lhlo( $command );
164             }
165             elsif ( $uccommand =~ /^HELO/ ) {
166 0         0 $self->smtp_command_helo( $command );
167             }
168             elsif ( $uccommand =~ /^XFORWARD/ ) {
169 126         1132 $self->smtp_command_xforward( $command );
170             }
171             elsif ( $uccommand =~ /^MAIL FROM:/ ) {
172 42         301 $self->smtp_init();
173 42         945 $self->smtp_command_mailfrom( $command );
174             }
175             elsif ( $uccommand =~ /^RCPT TO:/ ) {
176 42         467 $self->smtp_command_rcptto( $command );
177             }
178             elsif ( $uccommand =~ /^RSET/ ) {
179 6         52 $self->smtp_command_rset( $command );
180             }
181             elsif ( $uccommand =~ /^DATA/ ) {
182 42         362 $handler->set_overall_timeout( $seconds * 1000000 );
183 42         557 $self->smtp_command_data( $command );
184             }
185             elsif ( $uccommand =~ /^QUIT/ ){
186 36         197 $self->smtp_status('smtp.i.quit');
187 36         1454 print $socket "221 2.0.0 Bye\n";
188 36         813 last COMMAND;
189             }
190             else {
191 0         0 $self->smtp_status('smtp.i.unknown');
192 0         0 $self->logerror( "Unknown SMTP command: $command" );
193 0         0 print $socket "502 5.5.2 I don't understand\r\n";
194             }
195              
196             }
197 37         340 $handler->clear_overall_timeout();
198              
199 37         207 $self->smtp_status('smtp.close');
200              
201 37         505 $self->close_destination_socket();
202              
203 37         455 delete $self->{'smtp'};
204             }
205              
206             sub smtp_queue_id {
207 125     125 1 500 my ( $self ) = @_;
208 125         435 my $smtp = $self->{'smtp'};
209 125         369 my $queue_id = $smtp->{'queue_id'};
210 125 50 33     601 if ( $smtp->{'fwd_ident'} && $smtp->{'fwd_ident'} ne '[UNAVAILABLE]' ) {
211 0         0 $queue_id .= '.' . $smtp->{'fwd_ident'};
212             }
213 125         3108 return $queue_id;
214             }
215              
216             sub command_param {
217 247     247 1 719 my ( $command, $index ) = @_;
218 247         722 my $p = q{};
219 247 50       811 if ( length( $command ) >= $index ) {
220 247         872 $p = substr( $command, $index );
221             }
222 247         816 return $p;
223             }
224              
225             # Splits the given command parameter line into a list of separate
226             # path (<foo@example.com>) and esmtp-param (KEYWORD=VALUE) strings.
227             sub split_command_params {
228 84     84 0 302 my ( $line ) = @_;
229 84         452 $line =~ s/^\s+//;
230 84   66     341 my $path_end = find_path_end( $line ) || length( $line );
231 84         322 my $path = substr( $line, 0, $path_end );
232 84         216 $line = substr( $line, $path_end );
233 84         234 $line =~ s/^\s+//;
234 84         865 return ( $path, split( /\s+/, $line ) );
235             }
236              
237             sub find_path_end {
238 84     84 0 221 my ( $input ) = @_;
239 84         188 my $len = length( $input );
240 84         185 my @qstack;
241 84         387 for ( my $i = 0; $i < $len; $i++ ) {
242 1567         2643 my $c = substr( $input, $i, 1 );
243 1567 100 100     8970 if ( @qstack && $c eq $qstack[-1] ) {
    50 33        
    100 66        
    50 33        
    100 100        
244             # end of a quoted string
245 16         87 pop @qstack;
246             } elsif ( $c eq '"' ) {
247             # start of a "" quoted string
248 0         0 push @qstack, '"';
249             } elsif ( $c eq '<' && ( !@qstack || $qstack[-1] eq '>' ) ) {
250             # start of a <> quoted string (possibly inside of another <>)
251 16         110 push @qstack, '>';
252             } elsif ( $c eq '\\' && @qstack ) {
253             # escaped character within a quoted string
254 0         0 $i++;
255             } elsif ( $c eq ' ' && !@qstack ) {
256             # found the end
257 1         16 return $i;
258             }
259             }
260 83 50       504 return ( @qstack ? undef : $len );
261             }
262              
263             sub smtp_command_lhlo {
264 0     0 1 0 my ( $self, $command ) = @_;
265 0         0 my $smtp = $self->{'smtp'};
266 0         0 my $socket = $self->{'socket'};
267 0         0 my $handler = $self->{'handler'}->{'_Handler'};
268 0         0 $self->smtp_status('smtp.i.lhlo');
269              
270 0         0 $smtp->{'using_lmtp'} = 1;
271              
272 0 0       0 if ( $smtp->{'has_data'} ) {
273 0         0 $self->logerror( "Out of Order SMTP command: $command" );
274 0         0 print $socket "501 5.5.2 Out of Order\r\n";
275 0         0 return;
276             }
277 0         0 $smtp->{'helo_host'} = command_param( $command,5 );
278 0         0 print $socket "250-" . $smtp->{'server_name'} . "\r\n";
279 0 0       0 if ( $self->queue_type() eq 'before' ) {
280 0         0 print $socket "250-XFORWARD NAME ADDR HELO \r\n";
281             }
282             else {
283 0         0 print $socket "250-XFORWARD NAME ADDR HELO IDENT \r\n";
284             }
285 0         0 print $socket "250-PIPELINING\r\n";
286 0         0 print $socket "250-ENHANCEDSTATUSCODES\r\n";
287 0         0 print $socket "250 8BITMIME\r\n";
288             }
289              
290             sub smtp_command_ehlo {
291 37     37 1 240 my ( $self, $command ) = @_;
292 37         170 my $smtp = $self->{'smtp'};
293 37         139 my $socket = $self->{'socket'};
294 37         142 my $handler = $self->{'handler'}->{'_Handler'};
295 37         328 $self->smtp_status('smtp.i.ehlo');
296              
297 37 50       310 if ( $smtp->{'has_data'} ) {
298 0         0 $self->logerror( "Out of Order SMTP command: $command" );
299 0         0 print $socket "501 5.5.2 Out of Order\r\n";
300 0         0 return;
301             }
302 37         245 $smtp->{'helo_host'} = command_param( $command,5 );
303 37         1708 print $socket "250-" . $smtp->{'server_name'} . "\r\n";
304 37 50       514 if ( $self->queue_type() eq 'before' ) {
305 0         0 print $socket "250-XFORWARD NAME ADDR HELO \r\n";
306             }
307             else {
308 37         1783 print $socket "250-XFORWARD NAME ADDR HELO IDENT \r\n";
309             }
310 37         686 print $socket "250-PIPELINING\r\n";
311 37         535 print $socket "250-ENHANCEDSTATUSCODES\r\n";
312 37         1222 print $socket "250 8BITMIME\r\n";
313             }
314              
315             sub smtp_command_helo {
316 0     0 1 0 my ( $self, $command ) = @_;
317 0         0 my $smtp = $self->{'smtp'};
318 0         0 my $socket = $self->{'socket'};
319 0         0 my $handler = $self->{'handler'}->{'_Handler'};
320 0         0 $self->smtp_status('smtp.i.helo');
321              
322 0 0       0 if ( $smtp->{'has_data'} ) {
323 0         0 $self->logerror( "Out of Order SMTP command: $command" );
324 0         0 print $socket "501 5.5.2 Out of Order\r\n";
325 0         0 return;
326             }
327 0         0 $smtp->{'helo_host'} = command_param( $command,5 );
328 0         0 print $socket "250 " . $smtp->{'server_name'} . " Hi " . $smtp->{'helo_host'} . "\r\n";
329             }
330              
331             sub smtp_command_xforward {
332 126     126 1 559 my ( $self, $command ) = @_;
333 126         328 my $smtp = $self->{'smtp'};
334 126         336 my $socket = $self->{'socket'};
335 126         391 my $handler = $self->{'handler'}->{'_Handler'};
336 126         473 $self->smtp_status('smtp.i.xforward');
337              
338 126         883 $self->smtp_init();
339              
340 126 50       571 if ( $smtp->{'has_data'} ) {
341 0         0 $self->logerror( "Out of Order SMTP command: $command" );
342 0         0 print $socket "503 5.5.2 Out of Order\r\n";
343 0         0 return;
344             }
345 126         390 my $xdata = command_param( $command,9 );
346 126         751 foreach my $entry ( split( q{ }, $xdata ) ) {
347 126         764 my ( $key, $value ) = split( '=', $entry, 2 );
348 126 100       1084 if ( $key eq 'NAME' ) {
    100          
    50          
    0          
349 42         252 $smtp->{'fwd_connect_host'} = $value;
350             }
351             elsif ( $key eq 'ADDR' ) {
352 42         198 $smtp->{'fwd_connect_ip'} = $value;
353             }
354             elsif ( $key eq 'HELO' ) {
355 42         224 $smtp->{'fwd_helo_host'} = $value;
356             }
357             elsif ( $key eq 'IDENT' ) {
358 0 0       0 if ( $self->queue_type() eq 'before' ) {
359 0         0 $self->logerror( "XForward IDENT received in before queue mode: $key=$value" );
360             }
361             else {
362 0         0 $smtp->{'fwd_ident'} = $value;
363 0         0 $handler->set_symbol( 'C', 'i', $self->smtp_queue_id() );
364 0         0 $handler->dbgout( 'Upstream ID', $value, LOG_INFO );
365             }
366             }
367             else {
368             # NOP
369 0         0 $self->logerror( "Unknown XForward Entry: $key=$value" );
370             ### log it here though
371             }
372             }
373 126         8369 print $socket "250 2.0.0 Ok\r\n";
374             }
375              
376             sub smtp_command_rset {
377 6     6 1 27 my ( $self, $command ) = @_;
378 6         22 my $smtp = $self->{'smtp'};
379 6         27 my $socket = $self->{'socket'};
380 6         31 $self->smtp_status('smtp.i.rset');
381 6         41 $smtp->{'mail_from'} = q{};
382 6         33 $smtp->{'rcpt_to'} = [];
383 6         24 $smtp->{'headers'} = [];
384 6         22 $smtp->{'body'} = q{};
385 6         24 $smtp->{'has_data'} = 0;
386 6         20 $smtp->{'has_mail_from'} = 0;
387 6         19 $smtp->{'fwd_connect_host'} = undef;
388 6         14 $smtp->{'fwd_connect_ip'} = undef;
389 6         15 $smtp->{'fwd_helo_host'} = undef;
390 6         20 $smtp->{'fwd_ident'} = undef;
391 6         18 $smtp->{'lmtp_rcpt'} = [];
392 6         19 $smtp->{'string'} = q{};
393 6         37 $self->{'handler'}->{'_Handler'}->top_close_callback();
394              
395 6         18 $smtp->{'init_required'} = 1;
396 6         30 $self->smtp_init();
397              
398 6         37 my $smtp_conf = $self->get_smtp_config();
399              
400 6         26 my $handler = $self->{'handler'}->{'_Handler'};
401 6 50       29 if ( $handler->{'exit_on_close'} ) {
402 0         0 $smtp->{'last_command'} = 1;
403 0         0 $handler->dbgout( 'EXIT ON CLOSE REQUESTED', 'closing on RSET', LOG_INFO );
404 0         0 print $socket "421 4.3.2 Pipeline limit reached\r\n";
405 0         0 return;
406             }
407 6 50       25 if ( $smtp_conf->{'pipeline_limit'} ) {
408 6         17 my $count = $smtp->{'count'};
409 6         16 my $limit = $smtp_conf->{'pipeline_limit'};
410 6 100       30 if ( $count > $limit ) {
411 1         3 $smtp->{'last_command'} = 1;
412 1         7 $handler->dbgout( 'SMTP Pipeline limit reached', 'closing on RSET', LOG_INFO );
413 1         77 print $socket "421 4.3.2 Pipeline limit reached\r\n";
414 1         27 return;
415             }
416             }
417 5         331 print $socket "250 2.0.0 Ok\r\n";
418             }
419              
420             sub smtp_command_mailfrom {
421 42     42 1 268 my ( $self, $command ) = @_;
422 42         164 my $smtp = $self->{'smtp'};
423 42         141 my $socket = $self->{'socket'};
424 42         195 my $handler = $self->{'handler'}->{'_Handler'};
425 42         189 $self->smtp_status('smtp.i.mailfrom');
426              
427 42         217 my $returncode;
428 42 50       450 if ( $smtp->{'has_data'} ) {
429 0         0 $self->logerror( "Out of Order SMTP command: $command" );
430 0         0 print $socket "503 5.5.2 Out of Order\r\n";
431 0         0 return;
432             }
433 42 50       293 if ( $smtp->{'has_mail_from'} ) {
434 0         0 $self->logerror( "Out of Order SMTP command: $command" );
435 0         0 print $socket "503 5.5.1 Nested MAIL Command\r\n";
436 0         0 return;
437             }
438              
439             # Do connect callback here, because of XFORWARD
440 42   33     325 my $host = $smtp->{'fwd_connect_host'} || $smtp->{'connect_host'};
441 42   33     303 my $ip = $smtp->{'fwd_connect_ip'} || $smtp->{'connect_ip'};
442 42   33     231 my $helo = $smtp->{'fwd_helo_host'} || $smtp->{'helo_host'};
443              
444 42 50       353 if ( substr( $ip, 0, 5 ) eq 'IPv6:' ) {
445 0         0 $ip = substr( $ip, 5 );
446             }
447              
448             # Do connection remapping first
449 42         1195 $handler->remap_connect_callback( $host, Net::IP->new( $ip ) );
450 42         422 $handler->remap_helo_callback( $helo );
451              
452 42         222 $self->logdebug( "Inbound IP Address " . $handler->{'ip_object'}->ip() );
453 42         427 $returncode = $handler->top_connect_callback( $host, $handler->{'ip_object'} );
454 42 50       260 if ( $returncode == SMFIS_CONTINUE ) {
    0          
    0          
455 42         259 $returncode = $handler->top_helo_callback( $handler->{'helo_name'} );
456 42 50       396 if ( $returncode == SMFIS_CONTINUE ) {
    0          
    0          
457 42         317 my $envfrom = command_param( $command,10 );
458 42         219 $smtp->{'mail_from'} = $envfrom;
459 42         239 $returncode = $handler->top_envfrom_callback( split_command_params( $envfrom ) );
460 42 50       220 if ( $returncode == SMFIS_CONTINUE ) {
    0          
    0          
461 42         164 $smtp->{'has_mail_from'} = 1;
462 42         3509 print $socket "250 2.0.0 Ok\r\n";
463             }
464             elsif ( my $reject_reason = $handler->get_reject_mail() ) {
465 0         0 $handler->clear_reject_mail();
466 0         0 $self->loginfo ( "SMTPReject: $reject_reason" );
467 0         0 print $socket $reject_reason . "\r\n";
468             }
469             elsif ( my $defer_reason = $handler->get_defer_mail() ) {
470 0         0 $handler->clear_defer_mail();
471 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
472 0         0 print $socket $defer_reason . "\r\n";
473             }
474             else {
475 0         0 print $socket "451 4.0.0 MAIL - That's not right\r\n";
476             }
477             }
478             elsif ( my $reject_reason = $handler->get_reject_mail() ) {
479 0         0 $handler->clear_reject_mail();
480 0         0 $self->loginfo ( "SMTPReject: $reject_reason" );
481 0         0 print $socket $reject_reason . "\r\n";
482             }
483             elsif ( my $defer_reason = $handler->get_defer_mail() ) {
484 0         0 $handler->clear_defer_mail();
485 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
486 0         0 print $socket $defer_reason . "\r\n";
487             }
488             else {
489 0         0 print $socket "451 4.0.1 HELO - That's not right\r\n";
490             }
491             }
492             elsif ( my $reject_reason = $handler->get_reject_mail() ) {
493 0         0 $handler->clear_reject_mail();
494 0         0 $self->loginfo ( "SMTPReject: $reject_reason" );
495 0         0 print $socket $reject_reason . "\r\n";
496             }
497             elsif ( my $defer_reason = $handler->get_defer_mail() ) {
498 0         0 $handler->clear_defer_mail();
499 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
500 0         0 print $socket $defer_reason . "\r\n";
501             }
502             else {
503 0         0 print $socket "451 4.0.2 Connection - That's not right\r\n";
504             }
505             }
506              
507             sub smtp_command_rcptto {
508 42     42 1 218 my ( $self, $command ) = @_;
509 42         176 my $smtp = $self->{'smtp'};
510 42         126 my $socket = $self->{'socket'};
511 42         145 my $handler = $self->{'handler'}->{'_Handler'};
512 42         238 $self->smtp_status('smtp.i.rcptto');
513              
514 42 50       401 if ( $smtp->{'has_data'} ) {
515 0         0 $self->logerror( "Out of Order SMTP command: $command" );
516 0         0 print $socket "503 5.5.2 Out of Order\r\n";
517 0         0 return;
518             }
519 42         233 my $envrcpt = command_param( $command,8 );
520 42         136 push @{ $smtp->{'rcpt_to'} }, $envrcpt;
  42         219  
521 42         196 my $returncode = $handler->top_envrcpt_callback( split_command_params( $envrcpt ) );
522 42 50       246 if ( $returncode == SMFIS_CONTINUE ) {
    0          
    0          
523 42         138 push @{ $smtp->{'lmtp_rcpt'} }, $envrcpt;
  42         192  
524 42         2635 print $socket "250 2.0.0 Ok\r\n";
525             }
526             elsif ( my $reject_reason = $handler->get_reject_mail() ) {
527 0         0 $handler->clear_reject_mail();
528 0         0 $self->loginfo ( "SMTPReject: $reject_reason" );
529 0         0 print $socket $reject_reason . "\r\n";
530             }
531             elsif ( my $defer_reason = $handler->get_defer_mail() ) {
532 0         0 $handler->clear_defer_mail();
533 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
534 0         0 print $socket $defer_reason . "\r\n";
535             }
536             else {
537 0         0 print $socket "451 4.0.3 That's not right\r\n";
538             }
539             }
540              
541             sub smtp_command_data {
542 42     42 1 239 my ( $self, $command ) = @_;
543 42         192 my $smtp = $self->{'smtp'};
544 42         124 my $socket = $self->{'socket'};
545 42         187 my $handler = $self->{'handler'}->{'_Handler'};
546 42         182 $self->smtp_status('smtp.i.data');
547              
548 42         282 my $body = q{};
549 42         102 my $done = 0;
550 42         155 my $fail = 0;
551 42         153 my @header_split;
552             my $returncode;
553              
554 42 50       210 if ( $smtp->{'has_data'} ) {
555 0         0 $self->logerror( "Repeated SMTP DATA command: $command" );
556 0         0 print $socket "503 5.5.2 One at a time please\r\n";
557 0         0 return;
558             }
559 42         1777 print $socket "354 2.0.0 Send body\r\n";
560              
561 42     0   1365 local $SIG{'ALRM'} = sub{ die "Timeout\n" };
  0         0  
562 42         289 eval{
563 42         341 alarm( $smtp->{'smtp_timeout_in'} );
564             HEADERS:
565 42         10504 while ( my $dataline = <$socket> ) {
566 1518         8434 $self->extra_debugging( "RAW DEBUG: ". $dataline );
567 1518         7554 alarm( 0 );
568 1518         9493 $dataline =~ s/\r?\n$//;
569 1518 50       4060 if ( $dataline eq '.' ) {
570 0         0 $done = 1;
571 0         0 last HEADERS;
572             }
573             # Handle transparency
574 1518 50       3337 if ( $dataline =~ /^\./ ) {
575 0         0 $dataline = substr( $dataline, 1 );
576             }
577 1518 100       2919 if ( $dataline eq q{} ) {
578 42         367 last HEADERS;
579             }
580 1476         3355 push @header_split, $dataline;
581 1476         12087 alarm( $smtp->{'smtp_timeout_in'} );
582             }
583             };
584 42 50       340 if ( my $error = $@ ) {
585 0         0 $self->logerror( "Read Error: $error" );
586 0         0 $done = 1;
587 0         0 $fail = 1;
588             }
589 42         280 alarm( 0 );
590              
591 42         329 $self->smtp_status('smtp.i.data.process');
592 42         154 my $value = q{};
593 42         366 foreach my $header_line ( @header_split ) {
594 1476 100       4977 if ( $header_line =~ /^\s/ ) {
595 985         2712 $value .= "\r\n" . $header_line;
596             }
597             else {
598 491 100       1226 if ( $value ) {
599 449         747 push @{ $smtp->{'headers'} } , $value;
  449         1583  
600 449         2121 my ( $hkey, $hvalue ) = split ( ':', $value, 2 );
601 449         2360 $hvalue =~ s/^\s+//;
602 449         1190 $hkey =~ s/^\s+//;
603 449         1254 $hkey =~ s/\s+$//;
604 449 50       1186 if ( ! $fail ) {
605 449         1762 my $returncode = $handler->top_header_callback( $hkey, $hvalue, $value );
606 449 50       1466 if ( $returncode != SMFIS_CONTINUE ) {
607 0         0 $fail = 1;
608             }
609             }
610             }
611 491         1473 $value = $header_line;
612             }
613             }
614 42 50       320 if ( $value ) {
615 42         128 push @{ $smtp->{'headers'} } , $value;
  42         208  
616 42         274 my ( $hkey, $hvalue ) = split ( ':', $value, 2 );
617 42         483 $hvalue =~ s/^\s+//;
618 42         189 $hkey =~ s/^\s+//;
619 42         205 $hkey =~ s/\s+$//;
620 42 50       166 if ( ! $fail ) {
621 42         233 my $returncode = $handler->top_header_callback( $hkey, $hvalue, $value );
622 42 50       323 if ( $returncode != SMFIS_CONTINUE ) {
623 0         0 $fail = 1;
624             }
625             }
626             }
627 42 50       178 if ( ! $fail ) {
628 42         321 $returncode = $handler->top_eoh_callback();
629 42 50       314 if ( $returncode != SMFIS_CONTINUE ) {
630 0         0 $fail = 1;
631             }
632             }
633              
634 42         267 my $smtp_conf = $self->get_smtp_config();
635              
636 42         143 my $chunk_limit = 1048576; # Process in chunks no larger than...
637 42 50       225 if ( exists ( $smtp_conf->{ 'chunk_limit' } ) ) {
638 0         0 $chunk_limit = $smtp_conf->{ 'chunk_limit' };
639             }
640              
641 42         97 my $temp_file;
642 42 50       240 if ( exists ( $smtp_conf->{ 'temp_dir' } ) ) {
643             # TODO use spool_dir to create this
644             # add config option to en/disable this
645             # allow dir to be overridden
646 0         0 $temp_file = File::Temp->new( DIR => $smtp_conf->{ 'temp_dir' } );
647             }
648              
649 42         265 $self->smtp_status('smtp.i.body');
650 42         177 my $body_chunk = q{};
651 42 50       264 if ( ! $done ) {
652 42         103 eval {
653 42         426 alarm( $smtp->{'smtp_timeout_in'} );
654             DATA:
655 42         495 while ( my $dataline = <$socket> ) {
656 716         3905 $self->extra_debugging( "RAW DEBUG: ". $dataline );
657 716         3701 alarm( 0 );
658 716 100       2598 last DATA if $dataline =~ /^\.\r\n/;
659             # Handle transparency
660 674 100       1533 if ( $dataline =~ /^\./ ) {
661 16         46 $dataline = substr( $dataline, 1 );
662             }
663              
664 674 50       1403 if ( $temp_file ) {
665 0         0 print $temp_file $dataline;
666             }
667             else {
668 674         1544 $body .= $dataline;
669             }
670              
671 674 50       1614 if ( length( $body_chunk ) + length( $dataline ) > $chunk_limit ) {
672 0         0 $returncode = $handler->top_body_callback( $body_chunk );
673 0 0       0 if ( $returncode != SMFIS_CONTINUE ) {
674 0         0 $fail = 1;
675             }
676 0         0 $body_chunk = q{};
677             }
678              
679 674         1251 $body_chunk .= $dataline;
680              
681 674         6749 alarm( $smtp->{'smtp_timeout_in'} );
682             }
683 42 50       301 if ( ! $fail ) {
684 42         411 $returncode = $handler->top_body_callback( $body_chunk );
685 42 50       360 if ( $returncode != SMFIS_CONTINUE ) {
686 0         0 $fail = 1;
687             }
688 42         253 $body_chunk = q{};
689             }
690             };
691 42 50       192 if ( my $error = $@ ) {
692 0         0 $self->logerror( "Read Error: $error" );
693 0         0 $done = 1;
694 0         0 $fail = 1;
695             }
696 42         326 alarm( 0 );
697             }
698              
699 42 50       236 if ( ! $fail ) {
700 42         288 $returncode = $handler->top_eom_callback();
701 42 100       285 if ( $returncode != SMFIS_CONTINUE ) {
702 1         5 $fail = 1;
703             }
704             }
705              
706 42         366 $self->smtp_status('smtp.i.data.received');
707              
708 42 100       290 if ( ! $fail ) {
    50          
    0          
709              
710 41 50       164 if ( $temp_file ) {
711 0         0 $smtp->{'spool'} = $temp_file;
712             }
713             else {
714 41         275 $smtp->{'body'} = $body;
715             }
716              
717 41 50       419 if ( $self->smtp_forward_to_destination() ) {
718              
719 41 100       292 if ( my $reject_reason = $handler->get_quarantine_mail() ) {
720 2         20 $handler->metric_count( 'mail_processed_total', { 'result' => 'quarantined' } );
721             }
722             else {
723 39         455 $handler->metric_count( 'mail_processed_total', { 'result' => 'accepted' } );
724             }
725              
726 41         473 $handler->dbgout( 'Accept string', $smtp->{'string'}, LOG_INFO );
727 41         338 $smtp->{'has_data'} = 1;
728              
729 41 50       298 if ( $smtp->{'using_lmtp'} ) {
730 0         0 foreach my $rcpt_to ( @{ $smtp->{'lmtp_rcpt'} } ) {
  0         0  
731 0         0 print $socket "250 2.0.0 Queued as " . $self->smtp_queue_id() . "\r\n";
732             }
733             }
734             else {
735 41         256 print $socket "250 2.0.0 Queued as " . $self->smtp_queue_id() . "\r\n";
736             }
737             }
738             else {
739 0         0 $self->logerror( "SMTP Mail Rejected" );
740 0         0 my $error = '451 4.0.4 That\'s not right';
741 0         0 my $upstream_error = $smtp->{'string'};
742 0 0       0 if ( $upstream_error =~ /^4\d\d / ) {
    0          
743 0         0 $handler->metric_count( 'mail_processed_total', { 'result' => 'deferred' } );
744 0         0 $error = $upstream_error;
745             }
746             elsif ( $upstream_error =~ /^5\d\d / ) {
747             # Also pass back rejects
748 0         0 $handler->metric_count( 'mail_processed_total', { 'result' => 'rejected' } );
749 0         0 $error = $upstream_error;
750             }
751             else {
752 0         0 $handler->metric_count( 'mail_processed_total', { 'result' => 'deferred_error' } );
753 0         0 $error .= ': ' . $upstream_error;
754             }
755 0 0       0 if ( $smtp->{'using_lmtp'} ) {
756 0         0 foreach my $rcpt_to ( @{ $smtp->{'lmtp_rcpt'} } ) {
  0         0  
757 0         0 print $socket "$error\r\n";
758             }
759             }
760             else {
761 0         0 print $socket "$error\r\n";
762             }
763             }
764             }
765             elsif ( my $reject_reason = $handler->get_reject_mail() ) {
766 1         21 $handler->metric_count( 'mail_processed_total', { 'result' => 'rejected' } );
767 1         16 $handler->clear_reject_mail();
768 1 50       7 if ( $smtp->{'using_lmtp'} ) {
769 0         0 foreach my $rcpt_to ( @{ $smtp->{'lmtp_rcpt'} } ) {
  0         0  
770 0         0 $self->loginfo ( "SMTPReject: $reject_reason" );
771 0         0 print $socket $reject_reason . "\r\n";
772             }
773             }
774             else {
775 1         12 $self->loginfo ( "SMTPReject: $reject_reason" );
776 1         673 print $socket $reject_reason . "\r\n";
777             }
778             }
779             elsif ( my $defer_reason = $handler->get_defer_mail() ) {
780 0         0 $handler->metric_count( 'mail_processed_total', { 'result' => 'defered' } );
781 0         0 $handler->clear_defer_mail();
782 0 0       0 if ( $smtp->{'using_lmtp'} ) {
783 0         0 foreach my $rcpt_to ( @{ $smtp->{'lmtp_rcpt'} } ) {
  0         0  
784 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
785 0         0 print $socket $defer_reason . "\r\n";
786             }
787             }
788             else {
789 0         0 $self->loginfo ( "SMTPDefer: $defer_reason" );
790 0         0 print $socket $defer_reason . "\r\n";
791             }
792             }
793             else {
794 0         0 $handler->metric_count( 'mail_processed_total', { 'result' => 'deferred_error' } );
795 0 0       0 if ( $smtp->{'using_lmtp'} ) {
796 0         0 foreach my $rcpt_to ( @{ $smtp->{'lmtp_rcpt'} } ) {
  0         0  
797 0         0 print $socket "451 4.0.5 That's not right\r\n";
798             }
799             }
800             else {
801 0         0 print $socket "451 4.0.6 That's not right\r\n";
802             }
803             }
804 42         411 $self->smtp_status('smtp.i.data.done');
805              
806             # Reset
807 42         276 $smtp->{'mail_from'} = q{};
808 42         235 $smtp->{'rcpt_to'} = [];
809 42         318 $smtp->{'headers'} = [];
810 42         147 $smtp->{'body'} = q{};
811 42         113 $smtp->{'has_data'} = 0;
812 42         124 $smtp->{'fwd_connect_host'} = undef;
813 42         147 $smtp->{'fwd_connect_ip'} = undef;
814 42         125 $smtp->{'fwd_helo_host'} = undef;
815 42         197 $smtp->{'lmtp_rcpt'} = [];
816 42         148 $smtp->{'string'} = q{};
817 42         332 $self->{'handler'}->{'_Handler'}->top_close_callback();
818 42         293 $smtp->{'init_required'} = 1;
819              
820 42 50       2531 if ( $handler->{'exit_on_close'} ) {
821 0         0 $smtp->{'last_command'} = 1;
822 0         0 $handler->dbgout( 'EXIT ON CLOSE REQUESTED', 'NO MORE SMTP', LOG_INFO );
823             }
824             }
825              
826             sub smtp_insert_received_header {
827 41     41 1 205 my ( $self ) = @_;
828 41         158 my $smtp = $self->{'smtp'};
829              
830             my $value = join ( q{},
831              
832             'from ',
833             $smtp->{'helo_host'},
834             ' (',
835             $smtp->{'connect_host'}
836             ,
837             ' [',
838             $smtp->{'connect_ip'},
839             '])',
840             "\r\n",
841              
842             ' by ',
843 41         367 $smtp->{'server_name'},
844             ' (Authentication Milter)',
845             ' with ESMTP',
846             "\r\n",
847              
848             ' id ',
849             $self->smtp_queue_id(),
850             ';',
851             "\r\n",
852              
853             ' ',
854             email_date(),
855              
856             );
857              
858 41         7710 splice @{ $smtp->{'headers'} }, 0, 0, 'Received: '. $value;
  41         365  
859             }
860              
861             sub smtp_forward_to_destination {
862 41     41 1 225 my ( $self ) = @_;
863 41         200 $self->smtp_status('smtp.o');
864              
865 41         259 my $smtp = $self->{'smtp'};
866              
867 41         459 $self->smtp_insert_received_header();
868              
869 41         236 my $smtp_conf = $self->get_smtp_config();
870              
871 41         203 my $sock = $smtp->{'destination_sock'};
872              
873 41         121 my $new_sock = 0;
874              
875 41         106 my $line;
876              
877 41 100       214 if ( $sock ) {
878 5 50       61 if ( ! $sock->connected() ) {
879 0         0 $self->logerror( "Outbound SMTP socket was disconnected by remote end" );
880 0         0 undef $sock;
881             }
882             }
883              
884 41 100       297 if ( ! $sock ) {
885 36         89 $new_sock = 1;
886 36         178 $self->smtp_status('smtp.o.open');
887              
888 36 50       459 if ( $smtp_conf->{'sock_type'} eq 'inet' ) {
    50          
889             $sock = IO::Socket::INET->new(
890             'Proto' => 'tcp',
891             'PeerAddr' => $smtp_conf->{'sock_host'},
892 0         0 'PeerPort' => $smtp_conf->{'sock_port'},
893             );
894             }
895             elsif ( $smtp_conf->{'sock_type'} eq 'unix' ) {
896             $sock = IO::Socket::UNIX->new(
897 36         494 'Peer' => $smtp_conf->{'sock_path'},
898             );
899             }
900             else {
901 0         0 $self->logerror( 'Outbound SMTP Socket type unknown or undefined: ' . $smtp_conf->{'sock_type'} );
902 0         0 return 0;
903             }
904              
905 36 50       14261 if ( ! $sock ) {
906 0         0 $self->logerror( "Could not open outbound SMTP socket: $!" );
907 0         0 return 0;
908             }
909 36         139 eval {
910 36         17196 $line = <$sock>;
911             };
912 36 50       379 if ( my $error = $@ ) {
913 0         0 $self->logerror( "Outbound SMTP Read Error: $error" );
914 0         0 return 0;
915             }
916 36         295 alarm( 0 );
917              
918 36 50       226 if ( ! $line =~ /250/ ) {
919 0         0 $self->logerror( "Unexpected SMTP response $line" );
920 0         0 return 0;
921             }
922              
923 36         325 $smtp->{'destination_sock'} = $sock;
924             }
925              
926 41         403 $self->logdebug( 'Sending envelope to destination' );
927              
928 41 100       149 if ( $new_sock ) {
929 36 50       406 $self->send_smtp_packet( $sock, 'EHLO ' . $smtp->{'server_name'}, '250' ) || return;
930             }
931             else {
932 5 50       28 $self->send_smtp_packet( $sock, 'RSET', '250' ) || return;
933             }
934              
935 41 50       328 if ( $smtp->{'fwd_helo_host'} ) {
936 41 50       320 $self->send_smtp_packet( $sock, 'XFORWARD HELO=' . $smtp->{'fwd_helo_host'}, '250' ) || return;
937             }
938 41 50       328 if ( $smtp->{'fwd_connect_ip'} ) {
939 41 50       318 $self->send_smtp_packet( $sock, 'XFORWARD ADDR=' . $smtp->{'fwd_connect_ip'}, '250' ) || return;
940             }
941 41 50       340 if ( $smtp->{'fwd_connect_host'} ) {
942 41 50       257 $self->send_smtp_packet( $sock, 'XFORWARD NAME=' . $smtp->{'fwd_connect_host'}, '250' ) || return;
943             }
944 41 50       322 if ( $smtp->{'fwd_ident'} ) {
945 0 0       0 $self->send_smtp_packet( $sock, 'XFORWARD IDENT=' . $smtp->{'fwd_ident'}, '250' ) || return;
946             }
947              
948 41 50       334 $self->send_smtp_packet( $sock, 'MAIL FROM:' . $smtp->{'mail_from'}, '250' ) || return;
949 41         204 foreach my $rcpt_to ( @{ $smtp->{'rcpt_to'} } ) {
  41         381  
950 41 50       282 $self->send_smtp_packet( $sock, 'RCPT TO:' . $rcpt_to, '250' ) || return;
951             }
952              
953 41         576 $self->logdebug( 'Sending data to destination' );
954 41 50       269 $self->send_smtp_packet( $sock, 'DATA', '354' ) || return;
955              
956 41         308 $self->smtp_status('smtp.o.body');
957 41         281 my $email = q{};
958 41         165 foreach my $header ( @{ $smtp->{'headers'} } ) {
  41         216  
959 597         1696 $email .= "$header\r\n";
960             }
961 41         146 $email .= "\r\n";
962              
963 41         179 my $spool = $smtp->{'spool'};
964 41 50       186 if ( $spool ) {
965              
966             # Handle transparency - should not be any in headers, but for completeness
967 0         0 $email =~ s/\015\012\./\015\012\.\./g;
968              
969 0         0 print $sock $email;
970              
971 0         0 seek( $spool, 0, 0 );
972 0         0 while ( my $line = <$spool> ) {
973 0         0 $line =~ s/\015?\012/\015\012/g;
974 0         0 $line =~ s/^\./\.\./g;
975 0         0 print $sock $line;
976             }
977              
978             }
979             else {
980 41         144 my $body = $smtp->{'body'};
981 41         1072 $body =~ s/\015?\012/\015\012/g;
982 41         276 $email .= $body;
983              
984             # Handle transparency
985 41         361 $email =~ s/\015\012\./\015\012\.\./g;
986              
987 41         4274 print $sock $email;
988             }
989              
990 41         474 $self->logdebug( 'Sending end to destination' );
991 41 50       228 $self->send_smtp_packet( $sock, '.', '250' ) || return;
992 41         431 $self->logdebug( 'Sent to destination' );
993 41         248 $self->smtp_status('smtp.o.done');
994              
995 41         414 return 1;
996             }
997              
998             sub close_destination_socket {
999 37     37 1 136 my ( $self ) = @_;
1000 37         184 my $smtp = $self->{'smtp'};
1001 37         157 my $sock = $smtp->{'destination_sock'};
1002 37 100       170 return if ! $sock;
1003 36 50       160 $self->send_smtp_packet( $sock, 'QUIT', '221' ) || return;
1004 36         439 $sock->close();
1005 36         2135 delete $smtp->{'destination_sock'};
1006             }
1007              
1008             sub send_smtp_packet {
1009 364     364 1 1663 my ( $self, $socket, $send, $expect ) = @_;
1010 364         886 my $smtp = $self->{'smtp'};
1011              
1012 364         1027 my $status = lc $send;
1013 364         2339 $status =~ s/^([^ ]+) .*$/$1/;
1014 364 100       1096 $status = 'dot' if $status eq '.';
1015 364         1507 $self->smtp_status('smtp.o.' . $status);
1016              
1017 364         18822 print $socket "$send\r\n";
1018              
1019 364         2601 $self->smtp_status('smtp.o.' . $status . '.wait');
1020              
1021 364     0   7683 local $SIG{'ALRM'} = sub{ die "Timeout\n" };
  0         0  
1022 364         2808 alarm( $smtp->{'smtp_timeout_out'} );
1023 364         948 my $recv;
1024 364         726 eval {
1025 364         129870 $recv = <$socket>;
1026 364         4275 $self->extra_debugging( "RAW DEBUG: ". $recv );
1027 364         1806 while ( $recv =~ /^\d\d\d\-/ ) {
1028 0         0 $self->smtp_status('smtp.o.' . $status . '.waitext');
1029 0         0 $recv = <$socket>;
1030 0         0 $self->extra_debugging( "RAW DEBUG: ". $recv );
1031             }
1032             };
1033 364 50       1081 if ( my $error = $@ ) {
1034 0         0 $self->logerror( "Outbound SMTP Read Error: $error" );
1035 0         0 $smtp->{'string'} = $error;
1036 0         0 return 0;
1037             }
1038 364         2418 alarm( 0 );
1039 364         1892 $self->smtp_status('smtp.o');
1040              
1041 364   50     1763 $smtp->{'string'} = $recv || q{};
1042 364         2332 $smtp->{'string'} =~ s/\r//g;
1043 364         1363 $smtp->{'string'} =~ s/\n//g;
1044              
1045 364 50       5605 if ( $recv =~ /^$expect/ ) {
1046 364         8016 return 1;
1047             }
1048             else {
1049 0         0 $self->logerror( "SMTP Send expected $expect received $recv when sending $send" );
1050 0         0 return 0;
1051             }
1052             }
1053              
1054             sub add_header {
1055 9     9 1 29 my ( $self, $header, $value ) = @_;
1056 9         22 my $smtp = $self->{'smtp'};
1057 9         156 $value =~ s/\015?\012/\015\012/g;
1058 9         28 push @{ $smtp->{'headers'} } , "$header: $value";
  9         60  
1059             }
1060              
1061             sub change_header {
1062 13     13 1 37 my ( $self, $header, $index, $value ) = @_;
1063 13         32 my $smtp = $self->{'smtp'};
1064              
1065 13         25 my $header_i = 0;
1066 13         20 my $search_i = 0;
1067 13         22 my $result_i;
1068              
1069             HEADER:
1070 13         23 foreach my $header_v ( @{ $smtp->{'headers'} } ) {
  13         37  
1071 137 100       377 if ( substr( lc $header_v, 0, length($header) + 1 ) eq lc "$header:" ) {
1072 49         71 $search_i ++;
1073 49 100       101 if ( $search_i == $index ) {
1074 13         24 $result_i = $header_i;
1075 13         32 last HEADER;
1076             }
1077             }
1078 124         787 $header_i ++;
1079             }
1080              
1081 13 50       40 if ( $result_i ) {
1082 13 50       33 if ( $value eq q{} ) {
1083 13         26 splice @{ $smtp->{'headers'} }, $result_i, 1;
  13         86  
1084             }
1085             else {
1086 0         0 $value =~ s/\015?\012/\015\012/g;
1087 0         0 $smtp->{'headers'}->[ $result_i ] = "$header: $value";
1088             #untested.
1089             }
1090             }
1091             }
1092              
1093             sub insert_header {
1094 301     301 1 1077 my ( $self, $index, $key, $value ) = @_;
1095 301         801 my $smtp = $self->{'smtp'};
1096 301         3925 $value =~ s/\015?\012/\015\012/g;
1097 301         939 splice @{ $smtp->{'headers'} }, $index - 1, 0, "$key: $value";
  301         2968  
1098             }
1099              
1100             1;
1101              
1102             __END__
1103              
1104             =pod
1105              
1106             =encoding UTF-8
1107              
1108             =head1 NAME
1109              
1110             Mail::Milter::Authentication::Protocol::SMTP - SMTP protocol handling
1111              
1112             =head1 VERSION
1113              
1114             version 3.20230629
1115              
1116             =head1 SYNOPSIS
1117              
1118             Subclass of Net::Server::PreFork for bringing up the main server process for authentication_milter.
1119              
1120             Please see Net::Server docs for more detail of the server code.
1121              
1122             =head1 DESCRIPTION
1123              
1124             A Perl implenmetation of email authentication standards rolled up into a single easy to use milter.
1125              
1126             =head1 FUNCTIONS
1127              
1128             =over
1129              
1130             =item I<command_param( $command, $index )>
1131              
1132             Extract parameters from a SMTP command line.
1133              
1134             =back
1135              
1136             =head1 METHODS
1137              
1138             =over
1139              
1140             =item register_metrics
1141              
1142             Return details of the metrics this module exports.
1143              
1144             =item I<protocol_process_request( $command, $buffer )>
1145              
1146             Process the command from the SMTP protocol stream.
1147              
1148             =item I<get_smtp_config()>
1149              
1150             Return the SMTP config for the given connection, or
1151             the default config if no connection specific config
1152             exists.
1153              
1154             =item I<queue_type()>
1155              
1156             Return the smtp queue type, either before or after
1157             A before queue will not have an upstream queue id, an
1158             after queue will.
1159              
1160             =item I<send_smtp_packet( $socket, $send, $expect )>
1161              
1162             Send an SMTP command to the protocol stream.
1163             Expecting a response $expect.
1164              
1165             =item I<smtp_command_data( $command )>
1166              
1167             Process the SMTP DATA command.
1168              
1169             =item I<smtp_command_ehlo( $command )>
1170              
1171             Process the SMTP EHLO command.
1172              
1173             =item I<smtp_command_helo( $command )>
1174              
1175             Process the SMTP HELO command.
1176              
1177             =item I<smtp_command_lhlo( $command )>
1178              
1179             Process the LMTP LHLO command.
1180              
1181             =item I<smtp_command_mailfrom( $command )>
1182              
1183             Process the SMTP MAIL FROM command.
1184              
1185             =item I<smtp_command_rcptto( $command )>
1186              
1187             Process the SMTP RCPT TO command.
1188              
1189             =item I<smtp_command_rset( $command )>
1190              
1191             Process the SMTP RSET command.
1192              
1193             =item I<smtp_command_xforward( $command )>
1194              
1195             Process the SMTP XFORWARD command.
1196              
1197             =item I<smtp_forward_to_destination()>
1198              
1199             Send the received SMTP transaction on to its destination
1200             with authentication results headers (etc) added.
1201              
1202             =item I<close_destination_socket()>
1203              
1204             QUIT and close the destination socket if open.
1205              
1206             =item I<smtp_init()>
1207              
1208             Initialise transaction data as/when required.
1209              
1210             =item I<smtp_insert_received_header()>
1211              
1212             Insert a SMTP Received header into the email.
1213              
1214             =item I<smtp_queue_id()>
1215              
1216             Return a generated Queue ID for the email.
1217             This can include the received ID from XFORWARD.
1218              
1219             =item I<add_header( $header, $value )>
1220              
1221             Add a header
1222              
1223             =item I<change_header( $header, $index, $value )>
1224              
1225             Change a header
1226              
1227             =item I<insert_header( $index, $key, $value )>
1228              
1229             Insert a header
1230              
1231             =item I<smtp_status( $status )>
1232              
1233             Update the process name status line
1234              
1235             =back
1236              
1237             =head1 AUTHOR
1238              
1239             Marc Bradshaw <marc@marcbradshaw.net>
1240              
1241             =head1 COPYRIGHT AND LICENSE
1242              
1243             This software is copyright (c) 2020 by Marc Bradshaw.
1244              
1245             This is free software; you can redistribute it and/or modify it under
1246             the same terms as the Perl 5 programming language system itself.
1247              
1248             =cut