File Coverage

blib/lib/POE/Component/Client/SMTP.pm
Criterion Covered Total %
statement 330 363 90.9
branch 109 172 63.3
condition 12 15 80.0
subroutine 44 48 91.6
pod 1 10 10.0
total 496 608 81.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2005 - 2009 George Nistorica
2             # All rights reserved.
3             # This program is free software; you can redistribute it and/or
4             # modify it under the same terms as Perl itself. See the LICENSE
5             # file that comes with this distribution for more details.
6              
7             # $Id: SMTP.pm,v 1.25 2009/09/02 08:23:37 UltraDM Exp $
8              
9             package POE::Component::Client::SMTP;
10              
11             # TODO:
12             # * more nice output in debug mode
13              
14 17     17   4798832 use warnings;
  17         47  
  17         876  
15 17     17   103 use strict;
  17         40  
  17         849  
16              
17             our $VERSION = q{0.22};
18              
19 17     17   20545 use Data::Dumper;
  17         48479  
  17         1272  
20 17     17   116 use Carp;
  17         33  
  17         1259  
21 17     17   2025 use Socket;
  17         4967  
  17         13704  
22 17     17   1253 use Symbol qw( gensym );
  17         940  
  17         944  
23 17     17   1111 use POE;
  17         54788  
  17         127  
24 17     17   206626 use POE::Wheel::SocketFactory;
  17         96751  
  17         545  
25 17     17   11137 use POE::Wheel::ReadWrite;
  17         137542  
  17         610  
26 17     17   360 use POE::Filter::Line;
  17         39  
  17         341  
27 17     17   18387 use POE::Filter::Stream;
  17         7630  
  17         482  
28 17     17   18314 use POE::Filter::Transparent::SMTP;
  17         31337  
  17         107139  
29              
30             # End of line as specified by SMTP RFC
31             my $EOL = qq{\015\012};
32              
33             # an alias to _create currently
34             # perhaps it could have dual behaviour in the future
35             sub send {
36 26     26 1 108184 _create(@_);
37 26         90 return 1;
38             }
39              
40             # Create session
41             sub _create {
42 26     26   57 my $class = shift;
43 26         248 my %parameters = @_;
44              
45             # some checking
46 26 50       127 croak q{not an object method} if ( ref $class );
47              
48             # The actual Object;
49             # fill the PoCo parameter from the parameters we got and add the
50             # defaults where the parameters are not supplied
51 26         114 my $self = bless _fill_data( \%parameters ), $class;
52              
53             # store the caller
54 26         156 $self->parameter( q{Caller_Session}, $poe_kernel->get_active_session() );
55              
56             # did we sent the callback event back?
57             # used so that the poco won't send multiple events for the same
58             # SMTP session
59 26         78 $self->parameter( q{message_sent}, 0 );
60              
61             # Spawn the PoCoClient::SMTP session
62 26         523 POE::Session->create(
63             q{object_states} => [
64             $self => {
65             q{_start} => q{_pococlsmtp_start},
66             q{_stop} => q{_pococlsmtp_stop},
67             q{_default} => q{_pococlsmtp_default},
68              
69             # public available events
70             q{smtp_shutdown} => q{_pococlsmtp_shutdown},
71             q{smtp_progress} => q{_pococlsmtp_progress},
72              
73             # internal events SMTP codes and stuff
74             q{smtp_send} => q{_pococlsmtp_send},
75              
76             # network related
77             q{connection_established} => q{_pococlsmtp_conn_est},
78             q{connection_error} => q{_pococlsmtp_conn_err},
79             q{smtp_session_input} => q{_pococlsmtp_input},
80             q{smtp_session_error} => q{_pococlsmtp_error},
81             q{smtp_timeout_event} => q{_smtp_timeout_handler},
82              
83             q{_build_commands} => q{_build_commands},
84             q{_build_expected_states} => q{_build_expected_states},
85              
86             # return events
87             q{return_failure} => q{_pococlsmtp_return_error_event},
88              
89             # file slurping
90             q{_get_file} => q{_get_file},
91             q{_slurp_file_input_event} => q{_slurp_file_input_event},
92             q{_slurp_file_error_event} => q{_slurp_file_error_event},
93             },
94             ],
95             );
96 26         3127 return 1;
97             }
98              
99             # EVENT HANDLERS
100              
101             # event: _start
102             sub _pococlsmtp_start {
103 26     26   8518 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
104              
105 26 50       112 carp q{CURRENT STATE: _pococlsmtp_start} if $self->debug;
106              
107             # in case there's no alias, use refcount
108 26 100       75 if ( $self->parameter(q{Alias}) ) {
109 10         32 $kernel->alias_set( $self->parameter(q{Alias}) );
110             }
111             else {
112 16         62 $kernel->refcount_increment(
113             $kernel->get_active_session()->ID() => __PACKAGE__ );
114             }
115              
116             # build expected states
117 26         1047 $kernel->yield(q{_build_expected_states});
118              
119 26         1674 return 1;
120             }
121              
122             # event: _stop
123             sub _pococlsmtp_stop {
124 26     26   17365 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
125 26 50       97 carp q{CURRENT STATE _pococlsmtp_stop} if $self->debug;
126 26         108 return 1;
127             }
128              
129             # event: _default
130             sub _pococlsmtp_default {
131 5     5   372 my ($self) = $_[OBJECT];
132 5 50       12 carp q{CURRENT STATE _pococlsmtp_default} if $self->debug;
133 5         18 return 1;
134             }
135              
136             # this takes care of wheel creation and initial handshake with the SMTP server
137             # event: smtp_send
138             sub _pococlsmtp_send {
139 21     21   2505 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
140 21         51 my ( %options, $wheel, $alarm );
141              
142 21 50       208 carp q{CURRENT STATE: _pococlsmtp_send} if $self->debug;
143              
144 21         86 %options = (
145             q{RemoteAddress} => $self->parameter(q{Server}),
146             q{RemotePort} => $self->parameter(q{Port}),
147             q{SocketDomain} => AF_INET,
148             q{SocketType} => SOCK_STREAM,
149             q{SocketProtocol} => q{tcp},
150             q{Reuse} => q{yes},
151             q{SuccessEvent} => q{connection_established},
152             q{FailureEvent} => q{connection_error},
153             );
154              
155             # set BindAddress and BindPort if any.
156 21         63 for my $opt ( q{BindAddress}, q{BindPort} ) {
157 42 50       100 if ( defined $self->parameter($opt) ) {
158 0         0 $options{$opt} = $self->parameter($opt);
159             }
160             }
161              
162             # set the alarm for preventing timeouts
163             $alarm =
164 21         78 $kernel->delay_set( q{smtp_timeout_event}, $self->parameter(q{Timeout}) );
165              
166             # store the alarm to be used when talking to the SMTP server
167 21         1729 $self->_alarm($alarm);
168              
169 21         294 $wheel = POE::Wheel::SocketFactory->new( %options, );
170              
171             # store the wheel
172 21         20098 $self->store_sf_wheel($wheel);
173              
174 21         122 return 1;
175             }
176              
177             # event: connection_established
178             # event: SuccessEvent
179             sub _pococlsmtp_conn_est {
180 18     18   29078 my ( $kernel, $self, $socket ) = @_[ KERNEL, OBJECT, ARG0 ];
181 18         35 my ( $wheel, $alarm );
182              
183 18 50       69 carp q{CURRENT STATE: _pococlsmtp_conn_est} if $self->debug;
184              
185 18         216 $wheel = POE::Wheel::ReadWrite->new(
186             q{Handle} => $socket,
187             q{InputFilter} => POE::Filter::Transparent::SMTP->new(),
188             q{OutputFilter} => POE::Filter::Transparent::SMTP->new(),
189             q{InputEvent} => q{smtp_session_input},
190             q{ErrorEvent} => q{smtp_session_error},
191             );
192              
193             # set the alarm for preventing timeouts
194 18         7266 $alarm =
195             $kernel->delay_set( q{smtp_timeout_event}, $self->parameter(q{Timeout}) );
196              
197             # store the wheel
198 18         1253 $self->store_rw_wheel($wheel);
199              
200             # store the alarm; this is used while 'talking' with the SMTP server, during
201             # _pococlsmtp_input
202 18         61 $self->_alarm($alarm);
203              
204 18         57 return 1;
205             }
206              
207             # event: connection_error
208             # event: FailureEvent
209             sub _pococlsmtp_conn_err {
210 3     3   1192 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
211              
212 3 50       12 carp q{CURRENT STATE: _pococlsmtp_conn_err} if $self->debug;
213              
214             # send back to the caller the error which is generated during the
215             # connection establishing
216 3         27 $kernel->yield( q{return_failure},
217             { q{POE::Wheel::SocketFactory} => [ @_[ ARG0 .. ARG3 ] ] } );
218              
219 3         216 return 1;
220             }
221              
222             # we've got our connection established, now we're processing input
223             # event: InputEvent
224             # event: smtp_session_input
225             sub _pococlsmtp_input {
226 194     194   139983 my ( $kernel, $self, $input, $wheel_id ) = @_[ KERNEL, OBJECT, ARG0, ARG1 ];
227 194         246 my ( $smtp_code, $smtp_string, $to_send );
228              
229 194 50       441 carp q{CURRENT STATE: _pococlsmtp_input} if $self->debug;
230              
231             # reset alarm
232 194         632 $kernel->delay_adjust( $self->_alarm, $self->parameter(q{Timeout}), );
233              
234 194 100       17983 if ( $self->parameter(q{TransactionLog}) ) {
235              
236             # add to the transaction log
237 45         160 $self->_transaction_log( q{<- } . $input );
238             }
239              
240             # allright, received something in the form XXX text
241 194 100 100     1674 if (
    50          
242             $input =~ /
243             ^(\d{3}) # first 3 digits
244             \s+
245             (.*)$ # SMTP message corresponding to the SMTP code
246             /xo
247             or $input =~ /
248             ^(\d{3})
249             \s*$ # in case there's no status message ...
250             /xo
251             )
252             {
253              
254             # is the SMTP server letting us know there's a problem?
255 123         414 ( $smtp_code, $smtp_string ) = ( $1, $2 );
256 123 100       294 if ( not defined $smtp_string ) {
257              
258             # in case there's no status message from server
259 17         27 $smtp_string = q{Inserted by PoCoClSMTP: }
260             . q{Server didn't sent any status message along with the status code!};
261             }
262 123 50       255 if ( not defined $smtp_code ) {
263 0         0 $smtp_code = q{Inserted by PoCoClSMTP: }
264             . q{Server didn't replied with a status code as expected!};
265             }
266 123 100       491 if ( $smtp_code =~ /^(1|2|3)\d{2}$/ ) {
    100          
267              
268             # we're ok
269             # and also stupid, don't know estmp, don't know 1XY codes
270 118         343 $to_send = $self->command;
271              
272 118 100       290 if ( not defined $to_send ) {
273              
274             # this is the end of the 'commands' we had stored for sending to
275             # the server
276 22 100       72 if ( not $self->parameter(q{message_sent}) ) {
277 12 50       60 carp q{Sending SUCCESS event to the caller session}
278             if $self->debug;
279              
280             # send callback only once
281 12         200 $kernel->post(
282             $self->parameter(q{Caller_Session}),
283             $self->parameter(q{SMTP_Success}),
284             $self->parameter(q{Context}),
285             $self->_transaction_log(),
286             );
287             }
288              
289 22         1500 $self->parameter( q{message_sent}, 1 );
290 22         69 $self->_smtp_component_destroy;
291             }
292             else {
293 96 50       252 carp qq{PoCoClSMTP TO SEND: "$to_send"} if $self->debug;
294 96 100       212 if ( $self->parameter(q{TransactionLog}) ) {
295 21         69 $self->_transaction_log( q{-> } . $to_send );
296             }
297 96         249 $self->store_rw_wheel->put($to_send);
298             }
299             }
300             elsif (
301             $smtp_code =~ /
302             ^(4|5)\d{2}$ # look for error codes (starting with 4 or 5)
303             /xo
304             )
305             {
306 4 50       16 carp qq{Server Error! $input } if $self->debug;
307              
308             # the server responded with 4XY or 5XY code;
309             # while 4XY is temporary failure, 5XY is permanent
310             # it's unclear to me whether PoCoClientSMTP should retry in case of
311             # 4XY or the user should. In case is PoCoClientSMTP's job, then I
312             # should define for how many times and what interval
313 4         29 $kernel->yield( q{return_failure},
314             { q{SMTP_Server_Error} => $input } );
315             }
316             else {
317              
318             # oops! we shouldn't end-up here unless the server is buggy
319 1 50       22 carp qq{Error! I don't know the SMTP Code! "$input"}
320             if $self->debug;
321 1         9 $kernel->yield( q{return_failure},
322             { q{SMTP_Server_Error} => $input } );
323             }
324             }
325             elsif (
326             $input =~ /
327             # these lines are advertising SMTP capabilities
328             ^(\d{3}) # 3 digits
329             \- # separator
330             (.*)$ # capability
331             /xo
332             )
333             {
334 71 50       168 if ( $self->parameter(q{Debug}) > 1 ) {
335 0         0 carp qq{ESMTP Server capability: "$input"};
336             }
337             }
338             else {
339 0 0       0 carp qq{Received unknown string type from SMTP server, "$input"}
340             if $self->debug;
341 0         0 $kernel->yield( q{return_failure}, { q{SMTP_Server_Error} => $input } );
342             }
343              
344 194         9971 return 1;
345             }
346              
347             # event: smtp_session_error
348             # event: ErrorEvent
349             sub _pococlsmtp_error {
350 0     0   0 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
351 0 0       0 carp q{CURRENT STATE: _pococlsmtp_error} if $self->debug;
352 0         0 $kernel->yield( q{return_failure},
353             { q{POE::Wheel::ReadWrite} => [ @_[ ARG0 .. ARG3 ] ] } );
354 0         0 return 1;
355             }
356              
357             # event: smtp_timeout_event
358             sub _smtp_timeout_handler {
359 2     2   3000025 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
360 2 50       10 carp q{CURRENT STATE: _smtp_timeout_handler} if $self->debug;
361 2         9 $kernel->yield( q{return_failure},
362             { q{Timeout} => $self->parameter(q{Timeout}) } );
363 2         520 return 1;
364             }
365              
366             # event: return_failure
367             sub _pococlsmtp_return_error_event {
368 15     15   3081 my ( $kernel, $self, $arg, $session ) = @_[ KERNEL, OBJECT, ARG0, SESSION ];
369 15 50       50 carp q{CURRENT STATE: _pococlsmtp_return_error_event} if $self->debug;
370 15         28 my $error = $arg;
371              
372 15         27 my $error_line = q{};
373 15         25 foreach my $key ( keys %{$error} ) {
  15         293  
374 15         72 $error_line .= $key . q{: };
375 15         36 my $value = $error->{$key};
376 15 100       50 if ( ref $value eq q{ARRAY} ) {
377 3         9 $error_line .= join( q{, }, @{$value} );
  3         93  
378             }
379             else {
380 12         35 $error_line .= $value;
381             }
382             }
383              
384             # send the callback just once
385 15 100       50 if ( not $self->parameter(q{message_sent}) ) {
386 14 50       37 carp q{Sending FAIL event to the caller session} if $self->debug;
387 14         39 $kernel->post(
388             $self->parameter(q{Caller_Session}),
389             $self->parameter(q{SMTP_Failure}),
390             $self->parameter(q{Context}),
391             $arg,
392             $self->_transaction_log,
393             $error_line,
394             );
395             }
396 15         1510 $self->parameter( q{message_sent}, 1 );
397 15         68 $self->_smtp_component_destroy;
398 15         59 return 1;
399             }
400              
401             sub _smtp_component_destroy {
402 37     37   65 my $self = shift;
403              
404 37 50       99 carp q{CURRENT STATE: _smtp_component_destroy} if $self->debug;
405              
406             # remove alarms set for the Timeout
407 37         163 $poe_kernel->alarm_remove_all();
408              
409             # in case there's no alias, use refcount
410 37 100       4423 if ( $self->parameter(q{Alias}) ) {
411 21         47 $poe_kernel->alias_remove( $self->parameter(q{Alias}) );
412             }
413             else {
414 16         214 $poe_kernel->refcount_decrement(
415             $poe_kernel->get_active_session()->ID() => __PACKAGE__ );
416             }
417              
418             # delete all wheels
419 37         1476 $self->delete_rw_wheel;
420 37         5620 $self->delete_sf_wheel;
421 37         201 $self->delete_file_wheel;
422              
423 37         954 return 1;
424             }
425              
426             # place holder for future closing shutdown of the component
427             # useful in case the component will be sending multiple messages
428             # event: smtp_shutdown
429 0     0   0 sub _pococlsmtp_shutdown {
430             }
431              
432             # place holder for future sending back "progress events" in case
433             # sending multiple messages
434             # event: smtp_progress
435 0     0   0 sub _pococlsmtp_progress {
436             }
437              
438             # END OF EVENT HANDLERS
439              
440             # UNDER THE HOOD
441              
442             # take parameters, do checks on them and fill the object with data
443             sub _fill_data {
444 26     26   42 my $parameters = shift;
445 26         41 my $smtp_hash;
446              
447             # defaults
448 26         614 my %default = (
449             q{To} => q{root@localhost},
450             q{From} => q{root@localhost},
451             q{Body} => q{},
452             q{Server} => q{localhost},
453             q{Port} => 25,
454             q{Timeout} => 30,
455             q{MyHostname} => q{localhost},
456             q{BindAddress} => undef,
457             q{BindPort} => undef,
458             q{Debug} => 0,
459             q{Alias} => undef,
460             q{Context} => undef,
461             q{SMTP_Success} => undef,
462             q{SMTP_Failure} => undef,
463             q{Auth} => {
464             q{mechanism} => undef,
465             q{user} => undef,
466             q{pass} => undef,
467             },
468             q{MessageFile} => undef,
469             q{FileHandle} => undef,
470             q{TransactionLog} => undef,
471             );
472              
473             #check parameters and set them to defaults if they don't exist
474 26         178 for my $parameter ( keys %default ) {
475 468 100       841 if ( exists $parameters->{$parameter} ) {
476 251         685 $smtp_hash->{q{Parameter}}->{$parameter} =
477             $parameters->{$parameter};
478             }
479             else {
480 217         467 $smtp_hash->{q{Parameter}}->{$parameter} = $default{$parameter};
481             }
482             }
483              
484             # add supported auth methods
485             # for this poco
486 26         115 $smtp_hash->{q{Auth_Mechanism}} = [q{PLAIN}];
487              
488 26         172 return $smtp_hash;
489             }
490              
491             # accessor/mutator
492             sub parameter {
493 1879     1879 0 3169 my ( $self, $parameter, $value ) = ( shift, shift, shift );
494              
495 1879 50       4022 croak q{This is an object method only} if ( not ref $self );
496 1879 50       3332 croak q{need a parameter!} if ( not defined $parameter );
497              
498 1879 100       3346 if ( defined $value ) {
499 101         676 $self->{q{Parameter}}->{qq{$parameter}} = $value;
500             }
501              
502 1879         7888 return $self->{q{Parameter}}->{qq{$parameter}};
503             }
504              
505             # accessor/mutator
506             # stores and returns the SocketFactory wheel
507             sub store_sf_wheel {
508 21     21 0 56 my ( $self, $wheel ) = ( shift, shift );
509 21 50       97 croak q{not a class method} if ( not ref $self );
510 21 50       130 if ( defined $wheel ) {
511 21         122 $self->{q{Wheel}}->{q{SF}}->{$wheel} = $wheel;
512             }
513 21         67 return $self->{q{Wheel}}->{q{SF}};
514             }
515              
516             # if specified which SocketFactory wheel to delete it deletes it;
517             # if not specified, it deletes ALL SocketFactory wheels
518             sub delete_sf_wheel {
519 37     37 0 97 my ( $self, $wheel ) = ( shift, shift );
520 37 50       120 croak q{not a class method} if ( not ref $self );
521 37 50       108 if ( defined $wheel ) {
522 0         0 return delete $self->{q{Wheel}}->{q{SF}}->{$wheel};
523             }
524             else {
525 37         112 my $sf_wheel = delete $self->{q{Wheel}}->{q{SF}};
526             }
527             }
528              
529             sub store_rw_wheel {
530 114     114 0 185 my ( $self, $wheel ) = ( shift, shift );
531 114         135 my $ret;
532              
533 114 50       267 croak q{not a class method} if ( not ref $self );
534              
535 114 100       319 if ( defined $wheel ) {
536 18         144 $self->{q{Wheel}}->{q{RW}}->{$wheel} = $wheel;
537 18         92 $ret = $self->{q{Wheel}}->{q{RW}}->{$wheel};
538             }
539             else {
540 96         118 foreach my $key ( keys %{ $self->{q{Wheel}}->{q{RW}} } ) {
  96         356  
541 96         192 $ret = $self->{q{Wheel}}->{q{RW}}->{$key};
542 96         152 last;
543             }
544             }
545              
546 114 50       300 if ( not defined $ret ) {
547 0         0 $ret = $self->{q{Wheel}}->{q{RW}};
548             }
549              
550 114         462 return $ret;
551              
552             }
553              
554             sub delete_rw_wheel {
555 37     37 0 80 my ( $self, $wheel ) = ( shift, shift );
556 37 50       136 croak q{not a class method} if ( not ref $self );
557 37 50       100 if ( defined $wheel ) {
558 0         0 return delete $self->{q{Wheel}}->{$wheel};
559             }
560             else {
561 37         237 return delete $self->{q{Wheel}}->{q{RW}};
562             }
563             }
564              
565             # accessor/mutator
566             sub store_file_wheel {
567 4     4 0 8 my $self = shift;
568 4         6 my $wheel = shift;
569 4 50       17 croak 'not a class method' if ( not ref $self );
570 4 50       12 if ( defined $wheel ) {
571 4         29 $self->{'Wheel'}->{'FileWheel'}->{$wheel} = $wheel;
572             }
573 4         9 return $self->{'Wheel'}->{'FileWheel'};
574             }
575              
576             sub delete_file_wheel {
577 37     37 0 1122 my ( $self, $wheel ) = ( shift, shift );
578 37 50       122 croak q{not a class method} if ( not ref $self );
579 37 50       160 if ( defined $wheel ) {
580 0         0 return delete $self->{q{Wheel}}->{q{FileWheel}}->{$wheel};
581             }
582             else {
583 37         126 return delete $self->{q{Wheel}}->{q{FileWheel}};
584             }
585             }
586              
587             # accessor/mutator for the alarm
588             sub _alarm {
589 233     233   384 my ( $self, $alarm ) = ( shift, shift );
590 233 50       527 croak q{not a class method} if ( not ref $self );
591 233 100       481 if ( defined $alarm ) {
592 39         86 $self->{q{session_alarm}} = $alarm;
593 39         77 return $self;
594             }
595             else {
596 194         667 return $self->{q{session_alarm}};
597             }
598             }
599              
600             # return the current expected state
601             # return value is a list of expected values
602             sub _state {
603 0     0   0 my $self = shift;
604 0 0       0 croak q{not a class method} if ( not ref $self );
605 0         0 return shift @{ $self->{q{State}} };
  0         0  
606             }
607              
608             # build the expected list of states for every SMTP command we will be sending
609             # event: _build_expected_states
610             sub _build_expected_states {
611 26     26   3853 my ( $kernel, $self ) = @_[ KERNEL, OBJECT ];
612 26         40 my ( @states, $rcpt_to );
613              
614 26 50       90 croak q{not a class method} if ( not ref $self );
615              
616 26 50       70 carp q{CURRENT STATE: _build_expected_states} if $self->debug;
617              
618             # initial state, the SMTP server greeting
619 26         70 push @states, [ 220, 221 ];
620              
621             # "ehlo" command
622 26         62 push @states, [ 250, 251 ];
623              
624             # TODO: check if server only supports HELO (is this sane nowadays?)
625              
626 26 100       72 if ( defined $self->parameter(q{Auth})->{q{mechanism}} ) {
627              
628             # "auth" command
629 7         18 push @states, [235];
630             }
631              
632             # "mail from" command
633 26         11052 push @states, [ 250, 251 ], $rcpt_to = \$self->parameter(q{To});
634              
635             # "rcpt to" command
636 26 50       51 if ( ref( ${$rcpt_to} ) =~ /SCALAR/io ) {
  26 100       121  
  26         127  
637 0         0 push @states, [ 250, 251 ];
638             }
639             elsif ( ref( ${$rcpt_to} ) =~ /ARRAY/io ) {
640 16         34 for ( 0 .. $#{ ${$rcpt_to} } ) {
  16         21  
  16         50  
641 29         83 push @states, [ 250, 251 ];
642             }
643             }
644             else {
645 10         44 push @states, [ 250, 251 ];
646             }
647              
648             # "data" command:
649 26         82 push @states, [ 354, ];
650              
651             # dot command
652 26         63 push @states, [ 250, ];
653              
654             # "quit" command
655 26         93 push @states, [ 221, ];
656              
657 26         69 $self->{q{State}} = @states;
658              
659 26 100 100     122 if ( defined $self->parameter(q{MessageFile})
660             or defined $self->parameter(q{FileHandle}) )
661             {
662 4         12 $kernel->yield(q{_get_file});
663             }
664             else {
665 22         100 $kernel->yield(q{_build_commands});
666             }
667 26         1677 return $self;
668             }
669              
670             # event: _get_file
671             # in case MessageFile is set, slurp the contents of the file into Body
672             sub _get_file {
673 4     4   555 my ( $self, $kernel ) = @_[ OBJECT, KERNEL ];
674 4         8 my ( $handle, $wheel );
675 4 50       11 carp q{CURRENT STATE: _get_file} if $self->debug;
676              
677 4 100       12 if ( not defined $self->parameter(q{FileHandle}) ) {
678 3         9 $handle = _open_file( $self->parameter(q{MessageFile}) );
679             }
680             else {
681 1         3 $handle = $self->parameter(q{FileHandle});
682             }
683              
684 4 50       16 if ( not defined $handle ) {
685              
686             # no file handle
687 0 0       0 carp q{File not found!} if $self->debug;
688 0         0 $kernel->yield(
689             q{return_failure},
690             {
691             q{MessageFile_Error} =>
692             [ $self->parameter(q{MessageFile}) . q{ not found!} ],
693             },
694             );
695 0         0 return;
696             }
697              
698 4 50       19 if ( defined $self->parameter(q{Body}) ) {
699 4         26 $self->parameter( q{Body}, q{} );
700             }
701              
702 4         39 $wheel = POE::Wheel::ReadWrite->new(
703             q{Handle} => $handle,
704             q{Filter} => POE::Filter::Stream->new,
705             q{InputEvent} => q{_slurp_file_input_event},
706             q{ErrorEvent} => q{_slurp_file_error_event},
707             );
708 4         1375 $self->store_file_wheel($wheel);
709 4         13 return 1;
710              
711             }
712              
713             # event: InputEvent
714             # event: _slurp_file_input_event
715             sub _slurp_file_input_event {
716 4     4   884 my ( $self, $kernel, $input ) = @_[ OBJECT, KERNEL, ARG0 ];
717 4 50       12 carp q{CURRENT STATE: _slurp_file_input_event} if $self->debug;
718 4         16 $self->parameter( q{Body}, $self->parameter(q{Body}) . $input );
719 4         14 return 1;
720             }
721              
722             # event: ErrorEvent
723             # event: _slurp_file_error_event
724             sub _slurp_file_error_event {
725 4     4   765 my ( $self, $kernel ) = @_[ OBJECT, KERNEL ];
726 4         16 my ( $operation, $errnum, $errstr, $wheel_id ) = @_[ ARG0 .. ARG3 ];
727 4 50       18 carp q{CURRENT STATE: _slurp_file_error_event} if $self->debug;
728 4 50       14 if ( $self->debug > 1 ) {
729 0         0 carp <<"EOER";
730             Operation: $operation
731             ERRNUM: $errnum
732             ERRSTR: $errstr
733             WHEELID: $wheel_id
734             EOER
735             }
736 4 50       12 if ( $errnum == 0 ) {
737              
738             # go to the next step, building the commands, now that we have
739             # the Body filled with the file contents
740             # Escape single dots on a line
741 4         41 my $filter =
742             POE::Filter::Transparent::SMTP->new( q{EscapeSingleInputDot} => 1, );
743 4         318 my $lines = $self->parameter(q{Body});
744 4         20 $lines = $filter->get( [$lines] );
745 4         1342 $lines = $filter->put($lines);
746 4         571 $self->parameter( q{Body}, join( q{}, @{$lines} ) );
  4         22  
747 4         29 $kernel->yield(q{_build_commands});
748             }
749             else {
750              
751             # we've got an wheel error!
752 0         0 $kernel->yield(
753             q{return_failure},
754             {
755             q{POE::Wheel::ReadWrite} =>
756             [ $operation, $errnum, $errstr, $wheel_id ]
757             }
758             );
759             }
760              
761 4         314 return 1;
762             }
763              
764             # return the next command
765             sub command {
766 118     118 0 2590 my $self = shift;
767              
768 118 50       282 croak q{not a class method} if ( not ref $self );
769              
770 118         141 return shift @{ $self->{q{Command}} };
  118         353  
771              
772             }
773              
774             # build the list of commands
775             # event: _build_commands
776             sub _build_commands {
777 26     26   3406 my ( $kernel, $self, $session ) = @_[ KERNEL, OBJECT, SESSION ];
778 26         57 my ( @commands, $mechanism, $user, $pass, $rcpt_to, $body );
779              
780 26 50       115 croak q{not a class method} if ( not ref $self );
781              
782 26 50       88 carp q{CURRENT STATE: _build_commands} if $self->debug;
783              
784 26         109 $mechanism = $self->parameter('Auth')->{'mechanism'};
785 26         91 $user = $self->parameter('Auth')->{'user'};
786 26         93 $pass = $self->parameter('Auth')->{'pass'};
787 26 100       87 if ( defined $mechanism ) {
788 7 100       15 if ( $self->_is_auth_supported_by_poco($mechanism) ) {
789              
790             # here we start ESMTP ...
791 5 100 66     23 if ( defined $user and defined $pass ) {
792 2         7 my $encoded_data =
793             $self->_encode_auth( $mechanism, $user, $pass );
794 2         6 push @commands, q{EHLO } . $self->parameter(q{MyHostname});
795 2         5 push @commands, q{AUTH PLAIN } . $encoded_data;
796             }
797             else {
798              
799             # ERROR: user data not complete
800             # remove the next event which is smtp_send
801 3         12 $kernel->state(q{smtp_send});
802 3         57 $kernel->yield(
803             q{return_failure},
804             {
805             q{Configure} =>
806             q{ERROR: You want AUTH but no USER/PASS given!}
807             }
808             );
809             }
810             }
811             else {
812              
813             # ERROR: method unsupported by Component!
814             # remove the next event which is smtp_send
815 2         7 $kernel->state(q{smtp_send});
816 2         46 $kernel->yield(
817             q{return_failure},
818             {
819             q{Configure} =>
820             qq{ERROR: Method unsupported by Component version: $VERSION}
821             }
822             );
823             }
824             }
825             else {
826 19         57 push @commands, q{HELO } . $self->parameter(q{MyHostname});
827             }
828              
829 26         426 push @commands, q{MAIL FROM: <} . $self->parameter(q{From}) . q{>};
830 26         74 $rcpt_to = \$self->parameter(q{To});
831 26 100       42 if ( ref( ${$rcpt_to} ) =~ /ARRAY/io ) {
  26 50       154  
  10         45  
832 16         32 for my $recipient ( @{ ${$rcpt_to} } ) {
  16         18  
  16         35  
833 29         73 push @commands, q{RCPT TO: <} . $recipient . q{>};
834             }
835             }
836             elsif ( ref( ${$rcpt_to} ) =~ /SCALAR/io ) {
837 0         0 push @commands, q{RCPT TO: <} . ${ ${$rcpt_to} } . q{>};
  0         0  
  0         0  
838             }
839             else {
840              
841             # no ref, just a scalar ;-)
842 10         21 push @commands, q{RCPT TO: <} . ${$rcpt_to} . q{>};
  10         59  
843             }
844 26         103 $body = $self->parameter(q{Body});
845 26 100 100     810 if ( not $self->parameter(q{MessageFile})
846             and not $self->parameter(q{FileHandle}) )
847             {
848              
849             # GRR
850             # create the body
851             # escape single dots on a line
852 22         187 my $filter =
853             POE::Filter::Transparent::SMTP->new( q{EscapeSingleInputDot} => 1, );
854 22         12378 my $data = $filter->get( [$body] );
855 22         5284 $data = $filter->put($data);
856 22         1461 $body = join( q{}, @{$data} );
  22         72  
857 22         325 $body .= q{.};
858             }
859             else {
860 4         33 $body .= qq{$EOL.};
861             }
862              
863 26         85 push @commands, q{DATA}, $body, q{QUIT};
864              
865 26         85 $self->{q{Command}} = \@commands;
866 26         131 $kernel->yield(q{smtp_send});
867              
868 26         1871 return $self;
869             }
870              
871             sub debug {
872 542     542 0 1039 my ( $self, $debug_level ) = ( shift, shift );
873 542 50       1232 croak q{not a class method} if ( not ref $self );
874 542 50       1121 if ( defined $debug_level ) {
875 0         0 $self->parameter(q{Debug}) = $debug_level;
876             }
877 542         1198 return $self->parameter(q{Debug});
878             }
879              
880             sub _is_auth_supported_by_poco {
881 7     7   11 my ( $self, $requested_mehtod ) = ( shift, shift );
882 7         9 for my $mechanism ( @{ $self->{q{Auth_Mechanism}} } ) {
  7         16  
883 7 100       23 if ( uc($requested_mehtod) eq $mechanism ) {
884 5         14 return 1;
885             }
886             }
887 2         6 return 0;
888             }
889              
890             # encode the authentication string
891             sub _encode_auth {
892 2     2   6 my ( $self, $mechanism, $user, $pass ) = ( shift, shift, shift, shift );
893 2         4 my $encoded_data;
894              
895 2 50       5 if ( $mechanism eq q{PLAIN} ) {
896 2         4 eval { require MIME::Base64 };
  2         1123  
897 2 50       829 if ($@) {
898 0         0 carp q{You need to install MIME::Base64 to use AUTH PLAIN!};
899 0         0 $encoded_data = q{I don't have MIME::Base64 installed};
900             }
901             else {
902 2         14 $encoded_data =
903             MIME::Base64::encode_base64( qq{\0} . $user . qq{\0} . $pass,
904             q{} );
905             }
906             }
907             else {
908 0         0 croak q{ There's a bug in PoCoClSMTP, we really shouldn't get here!};
909             }
910              
911 2         5 return $encoded_data;
912             }
913              
914             sub _open_file {
915 3     3   6 my $filename = shift;
916 3         4 my $handle;
917              
918             # does file exist and is readable?
919 3 50 33     136 if ( -e $filename and -r $filename ) {
920 3         21 $handle = gensym();
921 3         150 open $handle, q{<}, qq{$filename};
922             }
923             else {
924 0         0 $handle = undef;
925             }
926              
927 3         9 return $handle;
928             }
929              
930             # accessor/mutator
931             sub _transaction_log {
932 92     92   177 my ( $self, $log ) = ( shift, shift );
933              
934 92 50       237 croak q{not a class method} if ( not ref $self );
935              
936 92 100       227 if ( defined $log ) {
937 66         71 push @{ $self->{q{transaction_log}} }, $log;
  66         166  
938 66         126 return $self;
939             }
940             else {
941 26         214 return $self->{q{transaction_log}};
942             }
943             }
944              
945             # END UNDER THE HOOD
946              
947             1; # End of POE::Component::Client::SMTP
948              
949             __END__