File Coverage

blib/lib/Net/Server/Mail.pm
Criterion Covered Total %
statement 178 229 77.7
branch 59 108 54.6
condition 25 56 44.6
subroutine 30 37 81.0
pod 6 28 21.4
total 298 458 65.0


line stmt bran cond sub pod time code
1             package Net::Server::Mail;
2              
3 4     4   66 use 5.006;
  4         16  
4 4     4   20 use strict;
  4         8  
  4         70  
5 4     4   17 use warnings;
  4         7  
  4         148  
6 4     4   1696 use Sys::Hostname;
  4         3964  
  4         203  
7 4     4   1857 use IO::Select;
  4         6379  
  4         174  
8 4     4   28 use IO::Handle;
  4         8  
  4         152  
9 4     4   22 use Carp;
  4         16  
  4         189  
10 4     4   21 use Errno;
  4         15  
  4         168  
11              
12 4     4   25 use constant HOSTNAME => hostname();
  4         5  
  4         15  
13              
14             $Net::Server::Mail::VERSION = '0.26';
15              
16             =pod
17              
18             =head1 NAME
19              
20             Net::Server::Mail - Class to easily create a mail server
21              
22             =head1 SYNOPSIS
23              
24             use Net::Server::Mail::SMTP;
25              
26             my @local_domains = qw(example.com example.org);
27             my $server = IO::Socket::INET->new( Listen => 1, LocalPort => 25 );
28            
29             my $conn;
30             while($conn = $server->accept)
31             {
32             my $smtp = Net::Server::Mail::SMTP->new( socket => $conn );
33             $smtp->set_callback(RCPT => \&validate_recipient);
34             $smtp->set_callback(DATA => \&queue_message);
35             $smtp->process();
36             $conn->close();
37             }
38              
39             sub validate_recipient
40             {
41             my($session, $recipient) = @_;
42            
43             my $domain;
44             if($recipient =~ /\@(.*)>\s*$/)
45             {
46             $domain = $1;
47             }
48              
49             if(not defined $domain)
50             {
51             return(0, 513, 'Syntax error.');
52             }
53             elsif(not(grep $domain eq $_, @local_domains))
54             {
55             return(0, 554, "$recipient: Recipient address rejected: Relay access denied");
56             }
57              
58             return(1);
59             }
60              
61             sub queue_message
62             {
63             my($session, $data) = @_;
64              
65             my $sender = $session->get_sender();
66             my @recipients = $session->get_recipients();
67              
68             return(0, 554, 'Error: no valid recipients')
69             unless(@recipients);
70            
71             my $msgid = add_queue($sender, \@recipients, $data)
72             or return(0);
73              
74             return(1, 250, "message queued $msgid");
75             }
76              
77             =head1 DESCRIPTION
78              
79             This module is a versatile and extensible implementation of the SMTP
80             protocol and its different evolutions like ESMTP and LMTP. The event
81             driven object-oriented API makes easy to incorporate the SMTP protocol
82             to your programs.
83              
84             Other SMTPd implementations don't support useful ESMTP extensions and
85             the LMTP protocol. Their interface design precludes adding them
86             later. So I've decided to rewrite a complete implementation with
87             extensibility in mind.
88              
89             It provides mechanism to easy addition future or not yet implemented
90             ESMTP extensions. Developers can hook code at each SMTP session state
91             and change the module's behaviors by registering event call-backs. The
92             class is designed to be easily inherited from.
93              
94             This class is the base class for mail service protocols such as
95             B, B and
96             B. Refer to the documentation provided with
97             each of these modules.
98              
99             =head1 METHODS
100              
101             =head2 new
102              
103             $instance = Net::Server::Mail->new( [option => 'value', ...] )
104              
105             options:
106              
107             =over 4
108              
109             =item handle_in
110              
111             Sets the input handle, from which the server reads data. Defaults to
112             STDIN.
113              
114             =item handle_out
115              
116             Sets the output handle, to which the server writes data. Defaults to
117             STDOUT.
118              
119             =item socket
120              
121             Sets a socket to be used for server reads and writes instead of
122             handles.
123              
124             =item error_sleep_time
125              
126             Number of seconds to wait for before printing an error message. This
127             avoids some DoS attacks that attempt to flood the server with bogus
128             commands. A value of 0 turns this feature off. Defaults to 0.
129              
130             =item idle_timeout
131              
132             Number of seconds a connection must remain idle before it is closed.
133             A value of 0 turns this feature off. Defaults to 0.
134              
135             =back
136              
137             =cut
138              
139             sub new {
140 7     7 1 1011024 my ( $proto, @args ) = @_;
141 7   33     90 my $class = ref $proto || $proto;
142 7         36 my $self = {};
143 7         30 bless( $self, $class );
144 7         72 return $self->init(@args);
145             }
146              
147             sub init {
148 7     7 0 20 my $self = shift;
149 7 50       38 confess("odd number of arguments") if ( @_ % 2 );
150             my $options = $self->{options} = {
151 7         94 handle_in => undef,
152             handle_out => undef,
153             socket => undef,
154             error_sleep_time => 0,
155             idle_timeout => 0,
156             };
157 7         35 for ( my $i = 0 ; $i < @_ ; $i += 2 ) {
158 21         98 $options->{ lc( $_[$i] ) } = $_[ $i + 1 ];
159             }
160              
161 7 50 33     51 if ( defined $options->{handle_in} && defined $options->{handle_out} ) {
    50          
162 0 0       0 if ( UNIVERSAL::isa( $options->{handle_in}, 'IO::Handle' ) ) {
163 0         0 $self->{in} = $options->{handle_in};
164             }
165             else {
166             $self->{in} =
167 0         0 IO::Handle->new->fdopen( fileno( $options->{handle_in} ), "r" );
168             }
169 0 0       0 if ( UNIVERSAL::isa( $options->{handle_out}, 'IO::Handle' ) ) {
170 0         0 $self->{out} = $options->{handle_out};
171             }
172             else {
173             $self->{out} =
174 0         0 IO::Handle->new->fdopen( fileno( $options->{handle_out} ), "w" );
175             }
176             }
177             elsif ( defined $options->{'socket'} ) {
178 7         30 $self->{in} = $options->{'socket'};
179 7         70 $self->{out} = $options->{'socket'};
180             }
181             else {
182 0         0 $self->{in} = IO::Handle->new->fdopen( fileno(STDIN), "r" );
183 0         0 $self->{out} = IO::Handle->new->fdopen( fileno(STDOUT), "w" );
184             }
185              
186 7         91 $self->{out}->autoflush(1);
187 7         322 $self->{process_operation} = \&process_operation;
188              
189 7         21 return $self;
190             }
191              
192             =pod
193              
194             =head2 dojob
195              
196             Some commands need to do a job after the handler call. The handler
197             may want to override this to prevent the job from being executed.
198              
199             By calling this method with a (defined) false value as an argument,
200             the expected job isn't executed. Defaults to true.
201              
202             =cut
203              
204 33     33 0 68 sub init_dojob { shift->{_dojob} = 1; }
205              
206             sub dojob {
207 33     33 1 69 my ( $self, $bool ) = @_;
208 33 50       63 $self->{_dojob} = $bool if ( defined $bool );
209 33         86 return $self->{_dojob};
210             }
211              
212             sub make_event {
213 33     33 0 76 my $self = shift;
214 33 50       90 confess('odd number of arguments') if ( @_ % 2 );
215 33         169 my %args = @_;
216              
217 33   33     100 my $name = $args{'name'} || confess('missing argument: \'name\'');
218             my $args = defined $args{'arguments'}
219 33 100 66     152 && ref $args{'arguments'} eq 'ARRAY' ? $args{'arguments'} : [];
220              
221 33         104 $self->init_dojob();
222 33         58 my ( $success, $code, $msg ) = $self->callback( $name, @{$args} );
  33         93  
223              
224             # we have to take a proper decision if successness is undefined
225 33 50       81 if ( not defined $success ) {
226 0 0       0 if ( exists $args{'default_reply'} ) {
227 0 0       0 if ( ref $args{'default_reply'} eq 'ARRAY' ) {
228 0         0 ( $success, $code, $msg ) = $args{'default_reply'};
229 0 0       0 $success = 0 unless defined $success;
230             }
231             else {
232 0         0 $success = $args{'default_reply'};
233             }
234             }
235             else {
236 0         0 $success = 1; # default
237             }
238             }
239              
240             # command may have some job to do regarding to the result. handler
241             # can avoid it by calling dojob() method with a false value.
242 33 50       92 if ( $self->dojob() ) {
243 33 50       61 if ($success) {
244 33 100 66     132 if ( defined $args{'on_success'}
245             and ref $args{'on_success'} eq 'CODE' )
246             {
247 19         27 &{ $args{'on_success'} };
  19         54  
248             }
249             }
250             else {
251 0 0 0     0 if ( defined $args{'on_failure'}
252             and ref $args{'on_failure'} eq 'CODE' )
253             {
254 0         0 &{ $args{'on_failure'} };
  0         0  
255             }
256             }
257             }
258              
259             # ensure that a reply is sent, all SMTP command need at most 1 reply.
260             # some events such as 'stop_session' don't require sending reply.
261 33 100 66     97 unless ( defined $code && !$args{'no_reply'} ) {
262 31 50 33     127 if ( defined $success && $success ) {
263             ( $code, $msg ) =
264 31         85 $self->get_default_reply( $args{'success_reply'}, 250 );
265             }
266             else {
267             ( $code, $msg ) =
268 0         0 $self->get_default_reply( $args{'failure_reply'}, 550 );
269             }
270             }
271              
272 33 50 33     194 die "return code `$code' isn't numeric"
273             if ( defined $code && $code =~ /\D/ );
274              
275 33 100 66     229 $self->handle_reply( $name, $success, $code, $msg )
276             if defined $code and length $code;
277              
278 33         1424 return $success;
279             }
280              
281             sub get_default_reply {
282 31     31 0 80 my ( $self, $config, $default ) = @_;
283              
284 31         50 my ( $code, $msg );
285 31 100       62 if ( defined $config ) {
286 30 100       77 if ( ref $config eq 'ARRAY' ) {
    50          
287 28         65 ( $code, $msg ) = @$config;
288             }
289             elsif ( not ref $config ) {
290 2         6 $code = $config;
291             }
292             else {
293 0         0 confess("unexpected format for reply");
294             }
295             }
296             else {
297 1         4 $code = $default;
298             }
299              
300 31         83 return ( $code, $msg );
301             }
302              
303             sub handle_reply {
304 0     0 0 0 my ( $self, $verb, $success, $code, $msg ) = @_;
305              
306             # don't reply anything if code is empty
307 0 0       0 $self->reply( $code, $msg ) if ( length $code );
308             }
309              
310             sub callback {
311 33     33 0 78 my ( $self, $name, @args ) = @_;
312              
313 33 100       101 if ( defined $self->{callback}->{$name} ) {
314 2         5 my @rv;
315 2         4 eval {
316 2         5 my ( $code, $context ) = @{ $self->{callback}->{$name} };
  2         7  
317 2         26 $self->set_context($context);
318 2         5 @rv = &{$code}( $self, @args );
  2         11  
319             };
320 2 50       1812 if ($@) {
321 0         0 confess $@;
322             }
323 2         10 return @rv;
324             }
325              
326 31         86 return 1;
327             }
328              
329             sub set_context {
330 2     2 0 5 my ( $self, $context ) = @_;
331 2         13 $self->{_context} = $context;
332             }
333              
334             sub get_context {
335 0     0 0 0 my ($self) = @_;
336 0         0 return $self->{_context};
337             }
338              
339             =pod
340              
341             =head2 set_callback
342              
343             ($success, $code, $msg) = $obj->set_callback(VERB, \&function, $context)>
344              
345             Sets the callback code to be called on a particular event. The function should
346             return 1 to 3 values: (success, [return_code, ["message"]]).
347              
348             $mailserver->set_callback
349             (
350             'RCPT', sub
351             {
352             my($address) = @_;
353             if(is_relayed($address))
354             {
355             # default success code/message will be used
356             return 1;
357             }
358             else
359             {
360             return(0, 513, 'Relaying denied.');
361             }
362             }
363             );
364              
365             =cut
366              
367             sub set_callback {
368 7     7 1 98 my ( $self, $name, $code, $context ) = @_;
369 7 50 33     46 confess('bad callback() invocation')
370             unless defined $code && ref $code eq 'CODE';
371 7         39 $self->{callback}->{$name} = [ $code, $context ];
372             }
373              
374             sub def_verb {
375 112     112 0 254 my ( $self, $verb, $coderef ) = @_;
376 112         342 $self->{verb}->{ uc $verb } = $coderef;
377             }
378              
379             sub undef_verb {
380 0     0 0 0 my ( $self, $verb ) = @_;
381             delete $self->{verb}->{$verb}
382 0 0       0 if defined $self->{verb};
383             }
384              
385             sub list_verb {
386 0     0 0 0 my ($self) = @_;
387 0         0 return keys %{ $self->{verb} };
  0         0  
388             }
389              
390             sub next_input_to {
391 35     35 0 88 my ( $self, $method_ref ) = @_;
392 35 100       125 $self->{next_input} = $method_ref
393             if ( defined $method_ref );
394 35         111 return $self->{next_input};
395             }
396              
397             sub tell_next_input_method {
398              
399 4     4 0 14 my ( $self, $input ) = @_;
400              
401             # calling the method and reinitialize. Note: we have to reinit
402             # before calling the code, because code can resetup this variable.
403 4         9 my $code = $self->{next_input};
404 4         8 undef $self->{next_input};
405 4         10 my $rv = &{$code}( $self, $input );
  4         25  
406 4         10 return $rv;
407             }
408              
409             =pod
410              
411             =head2 process
412              
413             $mailserver->process;
414              
415             Start a new session.
416              
417             =cut
418              
419             sub process {
420 7     7 1 40 my ($self) = @_;
421              
422 7         19 my $in = $self->{in};
423 7         23 my $sel = IO::Select->new;
424 7         85 $sel->add($in);
425              
426 7         331 $self->banner;
427              
428             # switch to non-blocking socket to handle PIPELINING
429             # ESMTP extension. See RFC 2920 for more details.
430 7 50       41 if ( $^O eq 'MSWin32' ) {
431              
432             # win32 platforms don't support nonblocking IO
433 0         0 ioctl( $in, 2147772030, 1 );
434             }
435             else {
436 7 50       62 defined( $in->blocking(0) ) or die "Couldn't set nonblocking: $^E";
437             }
438              
439 7         138 my $buffer = "";
440 7         13 while (1) {
441              
442             # wait for data and read it
443 32         48 my $rv = undef;
444              
445 32 50 50     168 if ( $sel->can_read( $self->{options}->{idle_timeout} || undef ) ) {
446 32 50       442962 if ( $^O eq 'MSWin32' ) {
447              
448             # see how much data is available to read
449 0         0 my $size = pack( "L", 0 );
450 0         0 ioctl( $in, 1074030207, $size );
451 0         0 $size = unpack( "L", $size );
452              
453             # read the data to $buffer
454 0         0 $rv = sysread( $in, $buffer, $size, length($buffer) );
455             }
456             else {
457 32         498 $rv = sysread( $in, $buffer, 512 * 1024, length($buffer) );
458             }
459             }
460             else {
461             # timeout
462 0         0 return $self->timeout;
463             }
464              
465             # No data available at the moment
466             next
467             if ( not defined $rv
468 32 0 0     993 and ( $! =~ /Resource temporarily unavailable/ or $!{'EAGAIN'} ) );
      33        
469 32 100 66     206 if ( ( not defined $rv ) or ( $rv == 0 ) ) {
470              
471             # read error or connection closed
472 1 50       35 return $self->stop_session( ( not defined $rv ) ? ($!) : () );
473             }
474              
475             # process all terminated lines
476             # Note: Should accept only CRLF according to RFC. We accept
477             # plain LFs anyway because its more liberal and works as well.
478 31         91 my $newline_idx = rindex( $buffer, "\n" );
479 31 50       81 if ( $newline_idx >= 0 ) {
480              
481             # one or more lines, terminated with \r?\n
482 31         123 my $chunk = substr( $buffer, 0, $newline_idx + 1 );
483              
484             # remaining buffer
485 31         86 $buffer = substr( $buffer, $newline_idx + 1 );
486              
487 31         50 my $rv;
488 31 100       134 if ( defined $self->next_input_to() ) {
489 4         21 $rv = $self->tell_next_input_method($chunk);
490             }
491             else {
492 27         80 $rv = $self->{process_operation}( $self, $chunk );
493             }
494              
495             # if $rv is defined, we have to close the connection
496 31 100       85 if ( defined $rv ) {
497 6         37 return $rv;
498             }
499             }
500              
501             # limit the size of lines to protect from excessive memory consumption
502             # (RFC specifies 1000 bytes including \r\n)
503 25 50       65 if ( length($buffer) > 1000 ) {
504 0         0 $self->make_event(
505             name => 'linetobig',
506             success_reply => [ 552, 'line too long' ]
507             );
508 0         0 return 1;
509             }
510             }
511              
512 0         0 return 1;
513             }
514              
515             sub process_once {
516 0     0 0 0 my ( $self, $operation ) = @_;
517 0 0       0 if ( $self->next_input_to() ) {
518 0         0 return $self->tell_next_input_method($operation);
519             }
520             else {
521 0         0 return $self->{process_operation}( $self, $operation );
522             }
523             }
524              
525             sub process_operation {
526 27     27 0 69 my ( $self, $operation ) = @_;
527 27         103 my ( $verb, $params ) = $self->tokenize_command($operation);
528 27 50 66     151 if ( defined $params && $params =~ /[\r\n]/ ) {
529              
530             # doesn't support grouping of operations
531 0         0 $self->reply( 453,
532             "Command received prior to completion of"
533             . " previous command sequence" );
534 0         0 return;
535             }
536 27         84 $self->process_command( $verb, $params );
537             }
538              
539             sub process_command {
540 27     27 0 73 my ( $self, $verb, $params ) = @_;
541              
542 27 50       86 if ( exists $self->{verb}->{$verb} ) {
543 27         59 my $action = $self->{verb}->{$verb};
544 27         49 my $rv;
545 27 100       72 if ( ref $action eq 'CODE' ) {
546 6         16 $rv = &{ $self->{verb}->{$verb} }( $self, $params );
  6         56  
547             }
548             else {
549 21         209 $rv = $self->$action($params);
550             }
551 27         94 return $rv;
552             }
553             else {
554 0         0 $self->reply( 500, 'Syntax error: unrecognized command' );
555 0         0 return;
556             }
557             }
558              
559             sub tokenize_command {
560 27     27 0 68 my ( $self, $line ) = @_;
561 27         327 $line =~ s/\r?\n$//s;
562 27         168 $line =~ s/^\s+|\s+$//g;
563 27         111 my ( $verb, $params ) = split ' ', $line, 2;
564 27         110 return ( uc($verb), $params );
565             }
566              
567             sub reply {
568 39     39 0 132 my ( $self, $code, $msg ) = @_;
569 39         95 my $out = $self->{out};
570              
571             # tempo on error
572             sleep $self->{options}->{error_sleep_time}
573 39 50 66     128 if ( $code >= 400 && $self->{options}->{error_sleep_time} );
574              
575             # default message
576 39 50       92 $msg = $code >= 400 ? 'Failure' : 'Ok'
    100          
577             unless defined $msg;
578              
579             # handle multiple lines
580 39         55 my @lines;
581              
582 39 100       78 if ( ref $msg ) {
583 9 50       40 confess "bad argument" unless ref $msg eq 'ARRAY';
584 9         21 @lines = @$msg;
585             }
586             else {
587 30         130 @lines = split( /\r?\n/, $msg );
588             }
589 39         140 for ( my $i = 0 ; $i < @lines ; $i++ ) {
590              
591             # RFC says that all lines but the last must
592             # split the code and the message with a dash (-)
593 48 100       415 my $sep = $i == $#lines ? ' ' : '-';
594 48         2446 print $out "$code$sep$lines[$i]\r\n";
595             }
596             }
597              
598             sub get_hostname {
599 20     20 0 44 my ($self) = @_;
600 20         82 return HOSTNAME;
601             }
602              
603             sub get_protoname {
604 0     0 0 0 my ($self) = @_;
605 0         0 return 'NOPROTO';
606             }
607              
608             sub get_appname {
609 7     7 0 22 my ($self) = @_;
610 7         23 return 'Net::Server::Mail (Perl)';
611             }
612              
613             ###########################################################
614              
615             =pod
616              
617             =head2 banner
618              
619             Send the introduction banner. You have to call it manually when are
620             using process_once() method. Don't use it with process() method.
621              
622             =head1 EVENTS
623              
624             =head2 banner
625              
626             Append at the opening of a new connection.
627              
628             Handler takes no argument.
629              
630             =cut
631              
632             sub banner {
633 7     7 1 19 my ($self) = @_;
634              
635 7 50       26 unless ( defined $self->{banner_string} ) {
636 7   50     37 my $hostname = $self->get_hostname || '';
637 7   50     25 my $protoname = $self->get_protoname || '';
638 7   50     36 my $appname = $self->get_appname || '';
639              
640 7         13 my $str;
641 7 50       48 $str = $hostname . ' ' if length $hostname;
642 7 50       25 $str .= $protoname . ' ' if length $protoname;
643 7 50       38 $str .= $appname . ' ' if length $appname;
644 7         15 $str .= 'Service ready';
645 7         23 $self->{banner_string} = $str;
646             }
647              
648             $self->make_event(
649             name => 'banner',
650 7         56 success_reply => [ 220, $self->{banner_string} ],
651             failure_reply => [ '', '' ],
652             );
653             }
654              
655             =pod
656              
657             =head2 timeout
658              
659             This event append where timeout is exceeded.
660              
661             Handler takes no argument.
662              
663             =cut
664              
665             sub timeout {
666 0     0 1 0 my ($self) = @_;
667              
668 0         0 $self->make_event(
669             name => 'timeout',
670             success_reply => [
671             421,
672             $self->get_hostname
673             . ' Timeout exceeded, closing transmission channel'
674             ],
675             );
676              
677 0         0 return 1;
678             }
679              
680             =pod
681              
682             =head2 timeout
683              
684             This event append where connection is closed or an error occurs during reading from socket.
685              
686             Takes the error description as an argument if an error occurred and the argument is undefined if the session was closed by peer.
687              
688             $mailserver->set_callback
689             (
690             'stop_session', sub
691             {
692             my($session, $err) = @_;
693             if( defined $err )
694             {
695             print "Error occurred during processing: $err\n";
696             }
697             else
698             {
699             print "Session closed py peer\n";
700             }
701             return 1;
702             }
703             );
704              
705             =cut
706              
707             sub stop_session {
708 1     1 0 6 my ( $self, $err ) = @_;
709              
710 1         7 $self->make_event(
711             name => 'stop_session',
712             arguments => [$err],
713             no_reply => 1,
714             );
715              
716 1         7 return 1;
717             }
718              
719             =pod
720              
721             =head1 SEE ALSO
722              
723             Please, see L, L
724             and L.
725              
726             =head1 AUTHOR
727              
728             Olivier Poitrey Ers@rhapsodyk.netE
729              
730             =head1 AVAILABILITY
731              
732             Available on CPAN.
733              
734             anonymous Git repository:
735              
736             git clone git://github.com/rs/net-server-mail.git
737              
738             Git repository on the web:
739              
740             L
741              
742             =head1 BUGS
743              
744             Please use CPAN system to report a bug (http://rt.cpan.org/).
745              
746             =head1 LICENCE
747              
748             This library is free software; you can redistribute it and/or modify
749             it under the terms of the GNU Lesser General Public License as
750             published by the Free Software Foundation; either version 2.1 of the
751             License, or (at your option) any later version.
752              
753             This library is distributed in the hope that it will be useful, but
754             WITHOUT ANY WARRANTY; without even the implied warranty of
755             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
756             Lesser General Public License for more details.
757              
758             You should have received a copy of the GNU Lesser General Public
759             License along with this library; if not, write to the Free Software
760             Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
761             USA
762              
763             =head1 COPYRIGHT
764              
765             =over
766              
767             =item Copyright (C) 2002 - Olivier Poitrey
768              
769             =item Copyright (C) 2007-2013 - Xavier Guimard
770              
771             =back
772              
773             =head2 STARTTLS
774              
775             =over
776              
777             =item Copyright (C) 2009 - Dan Moore
778              
779             =item Copyright (C) 2013 - Mytram
780              
781             =item Copyright (C) 2013 - Xavier Guimard
782              
783             =back
784              
785             =head2 Contributors
786              
787             =over
788              
789             =item 2012 - Georg Hoesch (patch to reduce memory consumption)
790              
791             =back
792              
793             =cut
794              
795             1;