File Coverage

blib/lib/POE/Component/Server/SimpleSMTP.pm
Criterion Covered Total %
statement 425 613 69.3
branch 134 262 51.1
condition 43 108 39.8
subroutine 55 73 75.3
pod 18 29 62.0
total 675 1085 62.2


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