File Coverage

blib/lib/POE/Component/Server/SimpleSMTP.pm
Criterion Covered Total %
statement 422 596 70.8
branch 132 254 51.9
condition 42 105 40.0
subroutine 56 74 75.6
pod 18 29 62.0
total 670 1058 63.3


line stmt bran cond sub pod time code
1             package POE::Component::Server::SimpleSMTP;
2             BEGIN {
3 13     13   3659218 $POE::Component::Server::SimpleSMTP::VERSION = '1.50';
4             }
5              
6             #ABSTRACT: A simple to use POE SMTP Server.
7              
8 13     13   128 use strict;
  13         30  
  13         448  
9 13     13   74 use warnings;
  13         28  
  13         503  
10 13     13   89 use POSIX;
  13         28  
  13         116  
11 13     13   38572 use POE qw(Component::Client::SMTP Component::Client::DNS Wheel::SocketFactory Wheel::ReadWrite Filter::Transparent::SMTP);
  13         31  
  13         162  
12 13     13   1877266 use POE::Component::Client::DNSBL;
  13         72455  
  13         502  
13 13     13   112 use base qw(POE::Component::Pluggable);
  13         33  
  13         31560  
14 13     13   94585 use POE::Component::Pluggable::Constants qw(:ALL);
  13         40  
  13         1707  
15 13     13   88 use Email::MessageID;
  13         27  
  13         399  
16 13     13   83 use Email::Simple;
  13         38  
  13         416  
17 13     13   14162 use Email::Address;
  13         503726  
  13         1173  
18 13     13   183 use Carp;
  13         29  
  13         1779  
19 13     13   92 use Socket;
  13         28  
  13         10408  
20 13     13   21565 use Storable;
  13         60739  
  13         124363  
21              
22             sub spawn {
23 13     13 1 100931 my $package = shift;
24 13         107 my %opts = @_;
25 13         168 $opts{lc $_} = delete $opts{$_} for keys %opts;
26 13         48 my $options = delete $opts{options};
27 13 100       106 _massage_handlers( $opts{handlers} ) if $opts{handlers};
28 13 100 66     121 $opts{handlers} = [ ] unless $opts{handlers} and ref $opts{handlers} eq 'ARRAY';
29 13 100 66     90 $opts{domains} = [ ] unless $opts{domains} and ref $opts{domains} eq 'ARRAY';
30 13 100 66     118 $opts{simple} = 1 unless defined $opts{simple} and !$opts{simple};
31 13 50 33     117 $opts{handle_connects} = 1 unless defined $opts{handle_connects} and !$opts{handle_connects};
32 13 50       93 $opts{hostname} = 'localhost' unless defined $opts{hostname};
33 13 50       75 $opts{relay} = 0 unless $opts{relay};
34 13 100       69 $opts{origin} = 0 unless $opts{origin};
35 13 50       59 $opts{maxrelay} = 5 unless $opts{maxrelay};
36 13 50       54 $opts{relay_auth} = 'PLAIN' if $opts{relay_auth};
37 13 50       122 $opts{version} = join('-', __PACKAGE__, $POE::Component::Server::SimpleSMTP::VERSION ) unless $opts{version};
38 13         64 my $self = bless \%opts, $package;
39 13         237 $self->_pluggable_init( prefix => 'smtpd_', types => [ 'SMTPD', 'SMTPC' ], debug => 1 );
40 13 50       1562 $self->{session_id} = POE::Session->create(
41             object_states => [
42             $self => { shutdown => '_shutdown',
43             send_event => '__send_event',
44             send_to_client => '_send_to_client',
45             start_listener => '_start_listener',
46             },
47             $self => [ qw(_start register unregister _accept_client _accept_failed _conn_input _conn_error _conn_flushed _conn_alarm _send_to_client __send_event _process_queue _smtp_send_relay _smtp_send_mx _smtp_send_success _smtp_send_failure _process_dns_mx _fh_buffer _buffer_error _buffer_flush _dnsbl _sender_verify) ],
48             ],
49             heap => $self,
50             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
51             )->ID();
52 13         3608 return $self;
53             }
54              
55             sub session_id {
56 13     13 1 66 return $_[0]->{session_id};
57             }
58              
59             sub mail_queue {
60 3     3 1 6874 my $self = shift;
61 3         9 return map { { %$_ } } @{ $self->{_mail_queue} };
  2         32  
  3         12  
62             }
63              
64             sub pause_queue {
65 0     0 1 0 my $self = shift;
66 0         0 $self->{paused} = 1;
67             }
68              
69             sub resume_queue {
70 2     2 1 535 my $self = shift;
71 2         201 my $pause = delete $self->{paused};
72 2 50       25 $poe_kernel->post( $self->{session_id}, '_process_queue' ) if $pause;
73             }
74              
75             sub paused {
76 2     2 1 4562 return $_[0]->{paused};
77             }
78              
79             sub cancel_message {
80 1     1 1 630 my $self = shift;
81 1   50     9 my $uid = shift || return;
82 1 50       2 return unless scalar @{ $self->{_mail_queue} };
  1         6  
83 1         4 my $i = 0;
84 1         33 for ( @{ $self->{_mail_queue} } ) {
  1         3  
85 1 50       7 splice( @{ $self->{_mail_queue} }, $i, 1 ), last
  1         4  
86             if $_->{uid} eq $uid;
87 0         0 ++$i;
88             }
89 1         6 return 1;
90             }
91              
92             sub data_mode {
93 3     3 1 2357 my $self = shift;
94 3   50     19 my $id = shift || return;
95 3 50       12 return unless $self->_conn_exists( $id );
96 3         10 my $handle = shift;
97 3 100 66     28 if ( $handle and $^O ne 'MSWin32' ) {
98 1         7 $poe_kernel->call( $self->{session_id}, '_fh_buffer', $id, $handle );
99             }
100             else {
101 2         10 $self->{clients}->{ $id }->{buffer} = [ ];
102             }
103 3         43 return 1;
104             }
105              
106             sub getsockname {
107 12 50   12 1 23938 return unless $_[0]->{listener};
108 12         123 return $_[0]->{listener}->getsockname();
109             }
110              
111             sub get_handlers {
112 0     0 1 0 my $self = shift;
113 0         0 my $handlers = Storable::dclone( $self->{handlers} );
114 0         0 delete $_->{RE} for @{ $handlers };
  0         0  
115 0         0 return $handlers;
116             }
117              
118             sub set_handlers {
119 0     0 1 0 my $self = shift;
120 0   0     0 my $handlers = shift || return;
121 0         0 _massage_handlers( $handlers );
122 0         0 $self->{handlers} = $handlers;
123 0         0 return 1;
124             }
125              
126             sub _conn_exists {
127 409     409   682 my ($self,$wheel_id) = @_;
128 409 50 33     2814 return 0 unless $wheel_id and defined $self->{clients}->{ $wheel_id };
129 409         6374 return 1;
130             }
131              
132             sub _valid_cmd {
133 70     70   233 my $self = shift;
134 70   50     336 my $cmd = shift || return;
135 70         160 $cmd = lc $cmd;
136 70 50       109 return 0 unless grep { $_ eq $cmd } @{ $self->{cmds} };
  770         1407  
  70         209  
137 70         277 return 1;
138             }
139              
140             sub _massage_handlers {
141 6   50 6   32 my $handler = shift || return;
142 6 50 33     80 croak( "HANDLERS is not a ref to an array!" )
143             unless ref $handler and ref $handler eq 'ARRAY';
144 6         15 my $count = 0;
145 6         24 while ( $count < scalar( @$handler ) ) {
146 6 50 33     58 if ( ref $handler->[ $count ] and ref( $handler->[ $count ] ) eq 'HASH' ) {
147 6         61 $handler->[ $count ]->{ uc $_ } = delete $handler->[ $count ]->{ $_ }
148 6         13 for keys %{ $handler->[ $count ] };
149 6 50       33 croak( "HANDLER number $count does not have a SESSION argument!" )
150             unless $handler->[ $count ]->{'SESSION'};
151 6 50       26 croak( "HANDLER number $count does not have an EVENT argument!" )
152             unless $handler->[ $count ]->{'EVENT'};
153 6 50       71 croak( "HANDLER number $count does not have a MATCH argument!" )
154             unless $handler->[ $count ]->{'MATCH'};
155 6 50       87 $handler->[ $count ]->{'SESSION'} = $handler->[ $count ]->{'SESSION'}->ID()
156             if UNIVERSAL::isa( $handler->[ $count ]->{'SESSION'}, 'POE::Session' );
157 6         25 my $regex;
158 6         155 eval { $regex = qr/$handler->[ $count ]->{'MATCH'}/ };
  6         148  
159 6 50       31 if ( $@ ) {
160 0         0 croak( "HANDLER number $count has a malformed MATCH -> $@" );
161             }
162             else {
163 6         23 $handler->[ $count ]->{'RE'} = $regex;
164             }
165             }
166             else {
167 0         0 croak( "HANDLER number $count is not a reference to a HASH!" );
168             }
169 6         17 $count++;
170             }
171 6         14 return 1;
172             }
173              
174             sub shutdown {
175 13     13 1 24810 my $self = shift;
176 13         80 $poe_kernel->post( $self->{session_id}, 'shutdown' );
177             }
178              
179             sub _start {
180 13     13   6692 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
181 13         85 $self->{session_id} = $_[SESSION]->ID();
182 13 50       133 if ( $self->{alias} ) {
183 0         0 $kernel->alias_set( $self->{alias} );
184             }
185             else {
186 13         132 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
187             }
188 13 50       532 if ( $kernel != $sender ) {
189 13         93 my $sender_id = $sender->ID;
190 13         99 $self->{events}->{'smtpd_all'}->{$sender_id} = $sender_id;
191 13         73 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
192 13         79 $kernel->refcount_increment($sender_id, __PACKAGE__);
193 13         491 $kernel->post( $sender, 'smtpd_registered', $self );
194             }
195              
196             #$self->{filter} = POE::Filter::Line->new( Literal => "\015\012" );
197 13         1788 $self->{filter} = POE::Filter::Transparent::SMTP->new(
198             InputLiteral => qq{\015\012},
199             OutputLiteral => qq{\015\012},
200             );
201              
202 13         1561 $self->{cmds} = [ qw(ehlo helo mail rcpt data noop vrfy rset expn help quit) ];
203              
204 13         89 $kernel->call( $self->{session_id}, 'start_listener' );
205              
206 13 50 33     334 $self->{resolver} = POE::Component::Client::DNS->spawn()
207             unless $self->{resolver} and $self->{resolver}->isa('POE::Component::Client::DNS');
208              
209 13 100       15743 $self->{_dnsbl} = POE::Component::Client::DNSBL->spawn(
210             resolver => $self->{resolver},
211             dnsbl => $self->{dnsbl},
212             ) if $self->{dnsbl_enable};
213              
214              
215 13         436 return;
216             }
217              
218             sub start_listener {
219 0     0 1 0 my $self = shift;
220 0         0 $poe_kernel->post( $self->{session_id}, 'start_listener', @_ );
221             }
222              
223             sub _start_listener {
224 13     13   742 my ($kernel,$self) = @_[KERNEL,OBJECT];
225 13 50       67 if ( $self->{listener} ) {
226 0         0 warn "Listener already started\n";
227 0         0 return;
228             }
229 13 50       266 $self->{listener} = POE::Wheel::SocketFactory->new(
    50          
230             ( defined $self->{address} ? ( BindAddress => $self->{address} ) : () ),
231             ( defined $self->{port} ? ( BindPort => $self->{port} ) : ( BindPort => 25 ) ),
232             SuccessEvent => '_accept_client',
233             FailureEvent => '_accept_failed',
234             SocketDomain => AF_INET, # Sets the socket() domain
235             SocketType => SOCK_STREAM, # Sets the socket() type
236             SocketProtocol => 'tcp', # Sets the socket() protocol
237             Reuse => 'on', # Lets the port be reused
238             );
239 13         18671 return;
240             }
241              
242             sub _accept_client {
243 12     12   18416 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
244 12         2529 my $sockaddr = eval "inet_ntoa( ( unpack_sockaddr_in ( CORE::getsockname $socket ) )[1] )";
245 12         1130 my $sockport = eval "( unpack_sockaddr_in ( CORE::getsockname $socket ) )[0]";
246 12         211 $peeraddr = inet_ntoa( $peeraddr );
247              
248 12         173 my $wheel = POE::Wheel::ReadWrite->new(
249             Handle => $socket,
250             Filter => $self->{filter},
251             InputEvent => '_conn_input',
252             ErrorEvent => '_conn_error',
253             FlushedEvent => '_conn_flushed',
254             );
255              
256 12 50       4396 return unless $wheel;
257            
258 12         79 my $id = $wheel->ID();
259 12         175 $self->{clients}->{ $id } =
260             {
261             wheel => $wheel,
262             peeraddr => $peeraddr,
263             peerport => $peerport,
264             sockaddr => $sockaddr,
265             sockport => $sockport,
266             };
267 12         124 $self->_send_event( 'smtpd_connection', $id, $peeraddr, $peerport, $sockaddr, $sockport );
268              
269 12   100     163 $self->{clients}->{ $id }->{alarm} = $kernel->delay_set( '_conn_alarm', $self->{client_time_out} || 300, $id );
270 12         931 return;
271             }
272              
273              
274             sub _accept_failed {
275 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
276 0         0 warn "Wheel $wheel_id generated $operation error $errnum: $errstr\n";
277 0         0 delete $self->{listener};
278 0         0 $self->_send_event( 'smtpd_listener_failed', $operation, $errnum, $errstr );
279 0         0 return;
280             }
281              
282             sub _conn_input {
283 134     134   350402 my ($kernel,$self,$input,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
284 134 50       415 return unless $self->_conn_exists( $id );
285 134   100     1146 $kernel->delay_adjust( $self->{clients}->{ $id }->{alarm}, $self->{client_time_out} || 300 );
286 134 100       12800 if ( $self->{clients}->{ $id }->{buffer} ) {
287 64 100 100     489 if ( $input eq '.' and $self->{simple} ) {
    100 100        
    100          
288 5         34 my $mail = delete $self->{clients}->{ $id }->{mail};
289 5         41 my $rcpt = delete $self->{clients}->{ $id }->{rcpt};
290 5         44 my $buffer = delete $self->{clients}->{ $id }->{buffer};
291 5         59 $self->_send_event( 'smtpd_message', $id, $mail, $rcpt, $buffer );
292 5         408 return;
293             }
294             elsif ( $input eq '.' and ref( $self->{clients}->{ $id }->{buffer} ) eq 'ARRAY' ) {
295 2         9 my $buffer = delete $self->{clients}->{ $id }->{buffer};
296 2         8 $self->_send_event( 'smtpd_data', $id, $buffer );
297 2         7 return;
298             }
299             elsif ( $input eq '.' ) {
300 1         5 my $wheel_id = delete $self->{clients}->{ $id }->{buffer};
301 1         6 $self->{buffers}->{ $wheel_id }->{shutdown} = 1;
302 1         5 return;
303             }
304 56 100       182 if ( ref( $self->{clients}->{ $id }->{buffer} ) eq 'ARRAY' ) {
305 50         65 push @{ $self->{clients}->{ $id }->{buffer} }, $input;
  50         153  
306             }
307             else {
308 6         14 my $buffer = $self->{clients}->{ $id }->{buffer};
309 6         25 $self->{buffers}->{ $buffer }->{wheel}->put( $input );
310             }
311 56         761 return;
312             }
313 70         271 $input =~ s/^\s+//g;
314 70         225 $input =~ s/\s+$//g;
315 70         297 my @args = split /\s+/, $input, 2;
316 70         156 my $cmd = shift @args;
317 70 50       224 return unless $cmd;
318 70 50       235 unless ( $self->_valid_cmd( $cmd ) ) {
319 0         0 $self->send_to_client( $id, "500 Syntax error, command unrecognized" );
320 0         0 return;
321             }
322 70         137 $cmd = lc $cmd;
323 70 100       221 if ( $cmd eq 'quit' ) {
324 8         42 $self->{clients}->{ $id }->{quit} = 1;
325 8         83 $self->send_to_client( $id, '221 closing connection - goodbye!' );
326 8         560 return;
327             }
328 62         282 $self->_send_event( 'smtpd_cmd_' . $cmd, $id, @args );
329 62         1173 return;
330             }
331              
332             sub _conn_error {
333 0     0   0 my ($self,$errstr,$id) = @_[OBJECT,ARG2,ARG3];
334 0 0       0 return unless $self->_conn_exists( $id );
335 0         0 delete $self->{clients}->{ $id };
336 0         0 $self->_send_event( 'smtpd_disconnected', $id );
337 0         0 return;
338             }
339              
340             sub _conn_flushed {
341 90     90   45552 my ($self,$id) = @_[OBJECT,ARG0];
342 90 50       266 return unless $self->_conn_exists( $id );
343 90 100       493 return unless $self->{clients}->{ $id }->{quit};
344 8         95 delete $self->{clients}->{ $id };
345 8         2802 $self->_send_event( 'smtpd_disconnected', $id );
346 8         31 return;
347             }
348              
349             sub _conn_alarm {
350 1     1   10008793 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
351 1 50       9 return unless $self->_conn_exists( $id );
352 1         16 delete $self->{clients}->{ $id };
353 1         436 $self->_send_event( 'smtpd_disconnected', $id );
354 1         4 return;
355             }
356              
357             sub _shutdown {
358 13     13   5460 my ($kernel,$self) = @_[KERNEL,OBJECT];
359 13         251 delete $self->{listener};
360 13         18121 delete $self->{clients};
361 13         1109 delete $self->{buffers};
362 13         98 $kernel->alarm_remove_all();
363 13         1460 $kernel->alias_remove( $_ ) for $kernel->alias_list();
364 13 50       665 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
365 13         1021 $self->_pluggable_destroy();
366 13         549 $self->_unregister_sessions();
367 13 100       786 $self->{_dnsbl}->shutdown() if $self->{dnsbl_enable};
368 13         212 $self->{resolver}->shutdown();
369 13         3090 undef;
370             }
371              
372             sub _fh_buffer {
373 1     1   790 my ($kernel,$self,$id,$handle) = @_[KERNEL,OBJECT,ARG0,ARG1];
374 1 50       4 return unless $self->_conn_exists( $id );
375 1         13 my $wheel = POE::Wheel::ReadWrite->new(
376             Handle => $handle,
377             FlushedEvent => '_buffer_flush',
378             ErrorEvent => '_buffer_error',
379             );
380 1         353 my $wheel_id = $wheel->ID();
381 1         9 $self->{clients}->{ $id }->{buffer} = $wheel_id;
382 1         8 $self->{buffers}->{ $wheel_id } = { wheel => $wheel, id => $id };
383 1         6 return;
384             }
385              
386             sub _buffer_flush {
387 1     1   804 my ($self,$wheel_id) = @_[OBJECT,ARG0];
388 1 50       23 return unless $self->{buffers}->{ $wheel_id }->{shutdown};
389 1         3 my $data = delete $self->{buffers}->{ $wheel_id };
390 1         3 my $id = delete $data->{id};
391 1         55 $self->send_event( 'smtpd_data_fh', $id );
392 1         305 return;
393             }
394              
395             sub _buffer_error {
396 0     0   0 my ($kernel,$self,$error,$wheel_id) = @_[KERNEL,OBJECT,ARG1,ARG3];
397 0         0 return;
398             }
399              
400             sub register {
401 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
402             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
403              
404 0 0       0 unless (@events) {
405 0         0 warn "register: Not enough arguments";
406 0         0 return;
407             }
408              
409 0         0 my $sender_id = $sender->ID();
410              
411 0         0 foreach (@events) {
412 0 0       0 $_ = "smtpd_" . $_ unless /^_/;
413 0         0 $self->{events}->{$_}->{$sender_id} = $sender_id;
414 0         0 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
415 0 0 0     0 unless ($self->{sessions}->{$sender_id}->{refcnt}++ or $session == $sender) {
416 0         0 $kernel->refcount_increment($sender_id, __PACKAGE__);
417             }
418             }
419              
420 0         0 $kernel->post( $sender, 'smtpd_registered', $self );
421 0         0 return;
422             }
423              
424             sub unregister {
425 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
426             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
427              
428 0 0       0 unless (@events) {
429 0         0 warn "unregister: Not enough arguments";
430 0         0 return;
431             }
432              
433 0         0 $self->_unregister($session,$sender,@events);
434 0         0 undef;
435             }
436              
437             sub _unregister {
438 0     0   0 my ($self,$session,$sender) = splice @_,0,3;
439 0         0 my $sender_id = $sender->ID();
440              
441 0         0 foreach (@_) {
442 0 0       0 $_ = "smtpd_" . $_ unless /^_/;
443 0         0 my $blah = delete $self->{events}->{$_}->{$sender_id};
444 0 0       0 unless ( $blah ) {
445 0         0 warn "$sender_id hasn't registered for '$_' events\n";
446 0         0 next;
447             }
448 0 0       0 if (--$self->{sessions}->{$sender_id}->{refcnt} <= 0) {
449 0         0 delete $self->{sessions}->{$sender_id};
450 0 0       0 unless ($session == $sender) {
451 0         0 $poe_kernel->refcount_decrement($sender_id, __PACKAGE__);
452             }
453             }
454             }
455 0         0 undef;
456             }
457              
458             sub _unregister_sessions {
459 13     13   122 my $self = shift;
460 13         77 my $smtpd_id = $self->session_id();
461 13         33 foreach my $session_id ( keys %{ $self->{sessions} } ) {
  13         67  
462 13 50       87 if (--$self->{sessions}->{$session_id}->{refcnt} <= 0) {
463 13         60 delete $self->{sessions}->{$session_id};
464 13 50       258 $poe_kernel->refcount_decrement($session_id, __PACKAGE__)
465             unless ( $session_id eq $smtpd_id );
466             }
467             }
468             }
469              
470             sub __send_event {
471 6     6   812 my( $self, $event, @args ) = @_[ OBJECT, ARG0, ARG1 .. $#_ ];
472 6         29 $self->_send_event( $event, @args );
473 6         28 return;
474             }
475              
476             sub _pluggable_event {
477 0     0   0 my $self = shift;
478 0         0 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
479             }
480              
481             sub send_event {
482 6     6 1 15 my $self = shift;
483 6         35 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
484             }
485              
486             sub _send_event {
487 97     97   179 my $self = shift;
488 97         315 my ($event, @args) = @_;
489 97         152 my $kernel = $POE::Kernel::poe_kernel;
490 97         408 my $session = $kernel->get_active_session()->ID();
491 97         800 my %sessions;
492              
493             my @extra_args;
494              
495 97 100       591 return 1 if $self->_pluggable_process( 'SMTPD', $event, \( @args ), \@extra_args ) == PLUGIN_EAT_ALL;
496              
497 42 50       1846 push @args, @extra_args if scalar @extra_args;
498              
499 42         78 $sessions{$_} = $_ for (values %{$self->{events}->{'smtpd_all'}}, values %{$self->{events}->{$event}});
  42         167  
  42         300  
500              
501 42         385 $kernel->post( $_ => $event => @args ) for values %sessions;
502 42         5230 undef;
503             }
504              
505             sub send_to_client {
506 90     90 1 19768 my $self = shift;
507 90         492 $poe_kernel->call( $self->{session_id}, '_send_to_client', @_ );
508             }
509              
510             sub _send_to_client {
511 90     90   4834 my ($kernel,$self,$id,$output) = @_[KERNEL,OBJECT,ARG0..ARG1];
512 90 50       319 return unless $self->_conn_exists( $id );
513 90 50       230 return unless $output;
514              
515 90 50       418 return 1 if $self->_pluggable_process( 'SMTPC', 'response', $id, \$output ) == PLUGIN_EAT_ALL;
516              
517 90 50 33     3921 return unless $self->_conn_exists( $id ) and defined $self->{clients}->{ $id }->{wheel};
518 90         476 $self->{clients}->{ $id }->{wheel}->put($output);
519 90         9065 return 1;
520             }
521              
522             sub _check_recipient {
523 5     5   12 my $self = shift;
524 5   50     29 my $recipient = shift || return;
525 5         11 foreach my $handler ( @{ $self->{handlers} } ) {
  5         25  
526 5 50       79 return $handler if $recipient =~ $handler->{RE};
527             }
528 0         0 return;
529             }
530              
531             sub _process_queue {
532 8     8   3417 my ($kernel,$self) = @_[KERNEL,OBJECT];
533 8 100       48 return if $self->{paused};
534 6 50 33     39 return if $self->{_smtp_clients} and $self->{_smtp_clients} >= $self->{maxrelay};
535 6         13 my $item = shift @{ $self->{_mail_queue} };
  6         43  
536 6         37 $kernel->delay_set( '_process_queue', 120 );
537 6 100       436 return unless $item;
538 5         22 $item->{attempt}++;
539             # Process Recipient Handlers here
540 5 50       27 if ( $self->{relay} ) {
541 0         0 $kernel->yield( '_smtp_send_relay', $item );
542 0         0 return;
543             }
544 5         9 my %domains;
545 5         10 foreach my $recipient ( @{ $item->{rcpt} } ) {
  5         18  
546 5 50       27 if ( my $handler = $self->_check_recipient( $recipient ) ) {
547 5         43 $kernel->post( $handler->{'SESSION'}, $handler->{'EVENT'}, $item );
548 5         514 next;
549             }
550 0         0 my $host = Email::Address->new(undef,$recipient,undef)->host();
551 0         0 push @{ $domains{ $host } }, $recipient;
  0         0  
552             }
553 5         20 foreach my $domain ( keys %domains ) {
554 0         0 my $copy = { %{ $item } };
  0         0  
555 0         0 $copy->{rcpt} = $domains{ $domain };
556 0         0 my $response = $self->{resolver}->resolve(
557             event => '_process_dns_mx',
558             type => 'MX',
559             host => $domain,
560             context => $copy,
561             );
562 0 0       0 $kernel->yield( '_process_dns_mx', $response ) if $response;
563             }
564 5         32 return;
565             }
566              
567             sub _process_dns_mx {
568 0     0   0 my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
569 0         0 my $item = $response->{context};
570 0 0       0 unless ( $response->{response} ) {
571 0 0       0 if ( time() - $item->{ts} > 345600 ) {
572 0         0 return;
573             }
574 0         0 push @{ $self->{_mail_queue} }, $item;
  0         0  
575 0         0 return;
576             }
577 0         0 my @answers = $response->{response}->answer();
578 0         0 my %mx = map { ( $_->exchange(), $_->preference() ) }
  0         0  
579 0         0 grep { $_->type() eq 'MX' } @answers;
580 0         0 my @mx = sort { $mx{$a} <=> $mx{$b} } keys %mx;
  0         0  
581 0 0       0 push @mx, $response->{host} unless scalar @mx;
582 0         0 $item->{mx} = \@mx;
583 0         0 $kernel->yield( '_smtp_send_mx', $item );
584 0         0 return;
585             }
586              
587             sub _smtp_send_mx {
588 0     0   0 my ($kernel,$self,$item) = @_[KERNEL,OBJECT,ARG0];
589 0         0 $item->{count}++;
590 0         0 my $exchange = shift @{ $item->{mx} };
  0         0  
591 0         0 push @{ $item->{mx} }, $exchange;
  0         0  
592 0         0 $self->{_smtp_clients}++;
593 0   0     0 POE::Component::Client::SMTP->send(
594             From => $item->{from},
595             To => $item->{rcpt},
596             Body => $item->{msg},
597             Server => $exchange,
598             Context => $item,
599             Debug => $self->{smtpc_debug},
600             Timeout => $self->{time_out} || 300,
601             MyHostname => $self->{hostname},
602             SMTP_Success => '_smtp_send_success',
603             SMTP_Failure => '_smtp_send_failure',
604             );
605 0         0 return;
606             }
607              
608             sub _smtp_send_relay {
609 0     0   0 my ($kernel,$self,$item) = @_[KERNEL,OBJECT,ARG0];
610 0         0 $item->{count}++;
611 0         0 my %auth;
612 0 0 0     0 if ( $self->{relay_user} and $self->{relay_pass} ) {
613 0   0     0 $auth{mechanism} = $self->{relay_auth} || 'PLAIN',
614             $auth{user} = $self->{relay_user},
615             $auth{pass} = $self->{relay_pass},
616             }
617 0         0 $self->{_smtp_clients}++;
618 0 0 0     0 POE::Component::Client::SMTP->send(
619             From => $item->{from},
620             To => $item->{rcpt},
621             Body => $item->{msg},
622             Server => $self->{relay},
623             Context => $item,
624             Timeout => $self->{time_out} || 300,
625             MyHostname => $self->{hostname},
626             SMTP_Success => '_smtp_send_success',
627             SMTP_Failure => '_smtp_send_failure',
628             ( scalar keys %auth ? ( Auth => \%auth ) : () ),
629             );
630 0         0 return;
631             }
632              
633             sub _smtp_send_success {
634 0     0   0 my ($kernel,$self,$item) = @_[KERNEL,OBJECT,ARG0];
635 0         0 $self->send_event( 'smtpd_send_success', $item->{uid} );
636 0         0 $kernel->delay_set( '_process_queue', 20 );
637 0         0 $self->{_smtp_clients}--;
638 0         0 return;
639             }
640              
641             sub _smtp_send_failure {
642 0     0   0 my ($kernel,$self,$item,$error) = @_[KERNEL,OBJECT,ARG0,ARG1];
643 0         0 $self->send_event( 'smtpd_send_failed', $item->{uid}, $error );
644 0         0 $self->{_smtp_clients}--;
645 0 0 0     0 if ( $error->{SMTP_Server_Error} and $error->{SMTP_Server_Error} =~ /^5/ ) {
646 0         0 return;
647             }
648 0 0       0 if ( time() - $item->{ts} > 345600 ) {
649 0         0 return;
650             }
651 0         0 push @{ $self->{_mail_queue} }, $item;
  0         0  
652 0         0 $kernel->delay_set( '_process_queue', 20 );
653 0         0 return;
654             }
655              
656             sub SMTPD_connection {
657 12     12 0 1415 my ($self,$smtpd) = splice @_, 0, 2;
658 12         32 my $id = ${ $_[0] };
  12         40  
659 12         26 my $peeraddr = ${ $_[1] };
  12         31  
660 12 50       77 return PLUGIN_EAT_NONE unless $self->{handle_connects};
661 12 100       82 unless ( $self->{dnsbl_enable} ) {
662 11         95 $self->send_to_client( $id, join( ' ', '220', $self->{hostname}, $self->{version}, 'ready' ) );
663             }
664             else {
665 1         8 $self->{_dnsbl}->lookup( session => $self->{session_id}, event => '_dnsbl', address => $peeraddr, _id => $id );
666             }
667 12         228 return PLUGIN_EAT_NONE;
668             }
669              
670             sub _dnsbl {
671 1     1   33936 my ($kernel,$self,$data) = @_[KERNEL,OBJECT,ARG0];
672 1         4 my $id = delete $data->{_id};
673 1         8 delete $data->{$_} for qw(event session);
674 1         6 my $to_client = join ' ', '220', $self->{hostname}, $self->{version}, 'ready';
675 1 50       7 if ( $data->{error} ) {
676 0         0 $self->{clients}->{ $id }->{dnsbl} = 'NXDOMAIN';
677             }
678             else {
679 1         5 $self->{clients}->{ $id }->{dnsbl} = $data->{response};
680 1 50       8 $to_client = '554 No SMTP service here' if $data->{response} ne 'NXDOMAIN';
681             }
682 1         7 $self->send_to_client( $id, $to_client );
683 1         14 $self->_send_event( 'smtpd_dnsbl', $id, $to_client, $data );
684 1         4 return;
685             }
686              
687             sub _sender_verify {
688 0     0   0 my ($kernel,$self,$data) = @_[KERNEL,OBJECT,ARG0];
689 0 0 0     0 return if $data->{error} and $data->{error} eq 'NOERROR';
690 0         0 my $id = delete $data->{context};
691 0         0 $self->{clients}->{ $id }->{fverify} = $data->{error};
692 0         0 return;
693             }
694              
695             sub SMTPD_cmd_helo {
696 4     4 0 173 my ($self,$smtpd) = splice @_, 0, 2;
697 4 100       45 return PLUGIN_EAT_NONE unless $self->{simple};
698 1         6 my $id = ${ $_[0] };
  1         2  
699 1         14 $self->send_to_client( $id, '250 OK' );
700 1         8 return PLUGIN_EAT_ALL;
701             }
702              
703             sub SMTPD_cmd_ehlo {
704 0     0 0 0 my ($self,$smtpd) = splice @_, 0, 2;
705 0 0       0 return PLUGIN_EAT_NONE unless $self->{simple};
706 0         0 my $id = ${ $_[0] };
  0         0  
707 0         0 $self->send_to_client( $id, '250 ' . $self->{hostname} . ' Hello [' . $self->{clients}->{ $id }->{peeraddr} . '], pleased to meet you' );
708 0         0 return PLUGIN_EAT_ALL;
709             }
710              
711             sub SMTPD_cmd_mail {
712 11     11 0 428 my ($self,$smtpd) = splice @_, 0, 2;
713 11 100       71 return PLUGIN_EAT_NONE unless $self->{simple};
714 8         17 my $id = ${ $_[0] };
  8         41  
715 8         18 my $args = ${ $_[1] };
  8         21  
716 8         85 my $response;
717 8 50 33     248 if ( $self->{dnsbl_enable} and ( !$self->{clients}->{ $id }->{dnsbl} or $self->{clients}->{ $id }->{dnsbl} ne 'NXDOMAIN' ) ) {
    50 66        
    50          
718 0         0 $response = '503 bad sequence of commands';
719             }
720             elsif ( $self->{clients}->{ $id }->{mail} ) {
721 0         0 $response = '503 Sender already specified';
722             }
723             elsif ( my ($from) = $args =~ /^from:\s*<(.+)>/i ) {
724 8         30 $response = "250 <$from>... Sender OK";
725 8         42 $self->{clients}->{ $id }->{mail} = $from;
726 8 50       56 if ( $self->{sender_verify} ) {
727 0         0 my $host = Email::Address->new(undef,$from,undef)->host();
728 0         0 my $response = $self->{resolver}->resolve(
729             event => '_sender_verify',
730             type => 'MX',
731             host => $host,
732             context => $id,
733             );
734 0 0       0 $poe_kernel->post( $self->{session_id}, '_sender_verify', $response ) if $response;
735             }
736             }
737             else {
738 0 0       0 $args = '' unless $args;
739 0         0 $response = "501 Syntax error in parameters scanning '$args'";
740             }
741 8         36 $self->send_to_client( $id, $response );
742 8         70 return PLUGIN_EAT_ALL;
743             }
744              
745             sub SMTPD_cmd_rcpt {
746 15     15 0 593 my ($self,$smtpd) = splice @_, 0, 2;
747 15 100       84 return PLUGIN_EAT_NONE unless $self->{simple};
748 12         24 my $id = ${ $_[0] };
  12         85  
749 12         26 my $args = ${ $_[1] };
  12         29  
750 12         23 my $response;
751 12 100 33     212 if ( !$self->{clients}->{ $id }->{mail} ) {
    50          
    50          
752 2         7 $response = '503 Need MAIL before RCPT';
753             }
754             elsif ( $self->{sender_verify} and defined $self->{clients}->{ $id }->{fverify} ) {
755 0         0 my $fverify = uc $self->{clients}->{ $id }->{fverify};
756 0 0       0 if ( $fverify eq 'NXDOMAIN' ) {
757 0         0 $response = '550 Sender verify failed';
758             }
759             else {
760 0         0 $response = '451 Temporary local problem - please try later';
761             }
762 0         0 delete $self->{clients}->{ $id }->{mail};
763 0         0 delete $self->{clients}->{ $id }->{rcpt};
764 0         0 delete $self->{clients}->{ $id }->{buffer};
765 0         0 delete $self->{clients}->{ $id }->{fverify};
766 0         0 $self->_send_event( 'smtpd_fverify', $id, $response, $fverify );
767             }
768             elsif ( my ($to) = $args =~ /^to:\s*<(.+)>/i ) {
769             # TODO scan through $self->{domains} and reject as necessary.
770 10 100       52 unless ( $self->_recipient_domain( $to ) ) {
771 1         3 $response = "550 #5.1.0 Address rejected $to";
772             }
773             else {
774 9         38 $response = "250 <$to>... Recipient OK";
775 9         18 push @{ $self->{clients}->{ $id }->{rcpt} }, $to;
  9         68  
776             }
777             }
778             else {
779 0 0       0 $args = '' unless $args;
780 0         0 $response = "501 Syntax error in parameters scanning '$args'";
781             }
782 12         46 $self->send_to_client( $id, $response );
783 12         102 return PLUGIN_EAT_ALL;
784             }
785              
786             sub _recipient_domain {
787 10     10   36 my $self = shift;
788 10 100       20 return 1 unless scalar @{ $self->{domains} };
  10         87  
789 3   50     15 my $address = shift || return;
790 3         13 my $hostpart = ( split /\@/, $address )[-1];
791 3 50       8 return unless $hostpart;
792 3 100       52 return 1 if grep { uc $_ eq uc $hostpart } @{ $self->{domains} };
  6         27  
  3         8  
793 1         5 return 0;
794             }
795              
796             sub SMTPD_cmd_data {
797 8     8 0 336 my ($self,$smtpd) = splice @_, 0, 2;
798 8 100       55 return PLUGIN_EAT_NONE unless $self->{simple};
799 5         10 my $id = ${ $_[0] };
  5         17  
800 5         12 my $response;
801 5 50 33     84 if ( !$self->{clients}->{ $id }->{mail} ) {
    50          
    50          
802 0         0 $response = '503 Need MAIL command';
803             }
804             elsif ( !$self->{clients}->{ $id }->{rcpt} ) {
805 0         0 $response = '503 Need RCPT (recipient)';
806             }
807             elsif ( $self->{sender_verify} and defined $self->{clients}->{ $id }->{fverify} ) {
808 0         0 my $fverify = uc $self->{clients}->{ $id }->{fverify};
809 0 0       0 if ( $fverify eq 'NXDOMAIN' ) {
810 0         0 $response = '550 Sender verify failed';
811             }
812             else {
813 0         0 $response = '451 Temporary local problem - please try later';
814             }
815 0         0 delete $self->{clients}->{ $id }->{mail};
816 0         0 delete $self->{clients}->{ $id }->{rcpt};
817 0         0 delete $self->{clients}->{ $id }->{buffer};
818 0         0 delete $self->{clients}->{ $id }->{fverify};
819 0         0 $self->_send_event( 'smtpd_fverify', $id, $response, $fverify );
820             }
821             else {
822 5         13 $response = '354 Enter mail, end with "." on a line by itself';
823 5         22 $self->{clients}->{ $id }->{buffer} = [ ];
824             }
825 5         43 $self->send_to_client( $id, $response );
826 5         48 return PLUGIN_EAT_ALL;
827             }
828              
829             sub SMTPD_cmd_noop {
830 8     8 0 389 my ($self,$smtpd) = splice @_, 0, 2;
831 8 50       41 return PLUGIN_EAT_NONE unless $self->{simple};
832 8         25 my $id = ${ $_[0] };
  8         24  
833 8         48 $self->send_to_client( $id, '250 OK' );
834 8         80 return PLUGIN_EAT_ALL;
835             }
836              
837             sub SMTPD_cmd_expn {
838 7     7 0 249 my ($self,$smtpd) = splice @_, 0, 2;
839 7 50       38 return PLUGIN_EAT_NONE unless $self->{simple};
840 7         37 my $id = ${ $_[0] };
  7         20  
841 7         70 $self->send_to_client( $id, '502 Command not implemented; unsupported operation (EXPN)' );
842 7         57 return PLUGIN_EAT_ALL;
843             }
844              
845             sub SMTPD_cmd_vrfy {
846 7     7 0 257 my ($self,$smtpd) = splice @_, 0, 2;
847 7 50       41 return PLUGIN_EAT_NONE unless $self->{simple};
848 7         123 my $id = ${ $_[0] };
  7         22  
849 7         184 $self->send_to_client( $id, '252 Cannot VRFY user, but will accept message for delivery' );
850 7         55 return PLUGIN_EAT_ALL;
851             }
852              
853             sub SMTPD_cmd_rset {
854 2     2 0 87 my ($self,$smtpd) = splice @_, 0, 2;
855 2 50       11 return PLUGIN_EAT_NONE unless $self->{simple};
856 2         4 my $id = ${ $_[0] };
  2         7  
857 2         20 delete $self->{clients}->{$id}->{$_} for qw(mail rcpt buffer);
858 2         9 $self->send_to_client( $id, '250 Reset state' );
859 2         16 return PLUGIN_EAT_ALL;
860             }
861              
862             sub SMTPD_message {
863 5     5 0 301 my ($self,$smtpd) = splice @_, 0, 2;
864 5 50       48 return PLUGIN_EAT_NONE unless $self->{simple};
865 5         13 my $id = ${ $_[0] };
  5         18  
866 5         19 my $from = ${ $_[1] };
  5         15  
867 5         48 my $rcpt = ${ $_[2] };
  5         15  
868 5         11 my $buf = ${ $_[3] };
  5         14  
869 5         79 my $msg_id = Email::MessageID->new( host => $self->{hostname} );
870 5         667 my $uid = $msg_id->user();
871 3         32 unshift @{ $buf }, "Message-ID: " . $msg_id->in_brackets()
  38         118  
872 5 100       257 unless grep { /^Message-ID:/i } @{ $buf };
  5         19  
873 5 100       52 unshift @{ $buf }, "Received: from Unknown [" . $self->{clients}->{ $id }->{peeraddr} . "] by " . $self->{hostname} . " " . $self->{version} . " with SMTP id $uid; " . strftime("%a, %d %b %Y %H:%M:%S %z", localtime)
  4         458  
874             unless $self->{origin};
875 5         53 $self->send_to_client( $id, "250 $uid Message accepted for delivery" );
876 5         96 my $email = Email::Simple->new( join "\r\n", @{ $buf } );
  5         90  
877 5 100       1305 $email->header_set('Received') if $self->{origin};
878 5   50     78 my $subject = $email->header('Subject') || '';
879 5         214 push @{ $self->{_mail_queue} }, { uid => $uid, from => $from, rcpt => $rcpt, msg => $email->as_string, ts => time(), subject => $subject };
  5         45  
880 5         465 $poe_kernel->post( $self->{session_id}, '_process_queue' );
881 5   50     509 $self->send_event( 'smtpd_message_queued', $id, $from, $rcpt, $uid, scalar @{ $buf }, $subject || '' );
  5         44  
882 5         406 delete $self->{clients}->{$id}->{$_} for qw(mail rcpt buffer);
883 5         150 return PLUGIN_EAT_ALL;
884             }
885              
886             sub enqueue {
887 1     1 1 1425 my $self = shift;
888 1         2 my %item;
889 1 50 33     12 if ( ref $_[0] and ref $_[0] eq 'HASH' ) {
    0 0        
890 1         2 %item = %{ $_[0] };
  1         5  
891             }
892             elsif ( ref $_[0] and ref $_[0] eq 'ARRAY' ) {
893 0         0 %item = @{ $_[0] };
  0         0  
894             }
895             else {
896 0         0 %item = @_;
897             }
898 1         9 $item{lc $_} = delete $item{$_} for keys %item;
899 1 50       5 return unless $item{from};
900 1 50       4 return unless $item{msg};
901 1 50 33     17 return unless $item{rcpt} and ref $item{rcpt} eq 'ARRAY' and scalar @{ $item{rcpt} };
  1   50     4  
902 1 50 33     7 $item{ts} = time() unless $item{ts} and $item{ts} =~ /^\d+$/;
903 1 50       13 $item{uid} = Email::MessageID->new( host => $self->{hostname} )->user() unless $item{uid};
904 1 50       51 $item{subject} = '' unless $item{subject};
905 1         2 push @{ $self->{_mail_queue} }, \%item;
  1         4  
906 1         5 $poe_kernel->post( $self->{session_id}, '_process_queue' );
907 1         90 return 1;
908             }
909              
910             1;
911              
912              
913             __END__