File Coverage

blib/lib/Mail/Milter/Authentication/Protocol/SMTP.pm
Criterion Covered Total %
statement 469 659 71.1
branch 125 244 51.2
condition 9 22 40.9
subroutine 34 39 87.1
pod 23 23 100.0
total 660 987 66.8


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