File Coverage

lib/AnyEvent/SMTP/Server.pm
Criterion Covered Total %
statement 140 203 68.9
branch 30 76 39.4
condition 1 8 12.5
subroutine 30 39 76.9
pod 4 8 50.0
total 205 334 61.3


line stmt bran cond sub pod time code
1             package AnyEvent::SMTP::Server;
2              
3             =head1 NAME
4              
5             AnyEvent::SMTP::Server - Simple asyncronous SMTP Server
6              
7             =cut
8              
9 12     12   65370 use Carp;
  12         19  
  12         1032  
10 12     12   68 use AnyEvent;
  12         22  
  12         286  
11 12     12   783 use common::sense;
  12         25  
  12         226  
12             m{# trying to cheat with cpants game ;)
13             use strict;
14             use warnings;
15             }x;
16              
17 12     12   968 use base 'Object::Event';
  12         25  
  12         13670  
18              
19 12     12   117527 use AnyEvent::Handle;
  12         111727  
  12         577  
20 12     12   130 use AnyEvent::Socket;
  12         26  
  12         2228  
21 12     12   99 use AnyEvent::Util;
  12         28  
  12         1956  
22              
23 12     12   11794 use Sys::Hostname;
  12         15495  
  12         1182  
24 12     12   10339 use Mail::Address;
  12         31423  
  12         448  
25              
26 12     12   7324 use AnyEvent::SMTP::Conn;
  12         30  
  12         2153  
27              
28 12     12   79 our $VERSION = $AnyEvent::SMTP::VERSION;use AnyEvent::SMTP ();
  12         20  
  12         58479  
29              
30             our %CMD = map { $_ => 1 } qw( HELO EHLO MAIL RCPT QUIT DATA EXPN VRFY NOOP HELP RSET );
31              
32             =head1 SYNOPSIS
33              
34             use AnyEvent::SMTP::Server 'smtp_server';
35              
36             smtp_server undef, 2525, sub {
37             my $mail = shift;
38             warn "Received mail from $mail->{from} to $mail->{to}\n$mail->{data}\n";
39             };
40            
41             # or
42            
43             use AnyEvent::SMTP::Server;
44            
45             my $server = AnyEvent::SMTP::Server->new(
46             port => 2525,
47             mail_validate => sub {
48             my ($m,$addr) = @_;
49             if ($good) { return 1 } else { return 0, 513, 'Bad sender.' }
50             },
51             rcpt_validate => sub {
52             my ($m,$addr) = @_;
53             if ($good) { return 1 } else { return 0, 513, 'Bad recipient.' }
54             },
55             data_validate => sub {
56             my ($m,$data) = @_;
57             my $size = length $data;
58             if ($size > $max_email_size) {
59             return 0, 552, 'REJECTED: message size limit exceeded';
60             } else {
61             return 1;
62             }
63             },
64             );
65              
66             $server->reg_cb(
67             client => sub {
68             my ($s,$con) = @_;
69             warn "Client from $con->{host}:$con->{port} connected\n";
70             },
71             disconnect => sub {
72             my ($s,$con) = @_;
73             warn "Client from $con->{host}:$con->{port} gone\n";
74             },
75             mail => sub {
76             my ($s,$mail) = @_;
77             warn "Received mail from ($mail->{host}:$mail->{port}) $mail->{from} to $mail->{to}\n$mail->{data}\n";
78             },
79             );
80              
81             $server->start;
82             AnyEvent->condvar->recv;
83              
84             =head1 DESCRIPTION
85              
86             Simple asyncronous SMTP server. Authorization not implemented yet. Patches are welcome
87              
88             =head1 FUNCTIONS
89              
90             =head2 smtp_server $host, $port, $cb->(MAIL)
91              
92             =head1 METHODS
93              
94             =head2 new %args;
95              
96             =over 4
97              
98             =item hosthame
99              
100             Server FQDN
101              
102             =item host
103              
104             Address to listen on. by default - undef (0.0.0.0)
105              
106             =item port
107              
108             Port to listen on
109              
110             =back
111              
112             =head2 start
113              
114             Creates tcp server and starts to listen
115              
116             =head2 stop
117              
118             Closes all opened connections and shutdown server
119              
120             =head1 EVENTS
121              
122             =over 4
123              
124             =item ready()
125              
126             Invoked when server is ready
127              
128             =item client($connection)
129              
130             Invoked when client connects
131              
132             =item disconnect($connection)
133              
134             Invoked when client disconnects
135              
136             =item mail($mail)
137              
138             Invoked when server received complete mail message
139              
140             $mail = {
141             from => ...,
142             to => [ ... ],
143             data => '...',
144             host => 'remote addr',
145             port => 'remote port',
146             helo => 'HELO/EHLO string',
147             };
148              
149             =back
150              
151             =cut
152              
153             sub import {
154 2     2   20 my $me = shift;
155 2         7 my $pkg = caller;
156            
157 2 50       47 @_ or return;
158 0         0 for (@_) {
159 0 0       0 if ( $_ eq 'smtp_server') {
160 0         0 *{$pkg.'::'.$_} = \&$_;
  0         0  
161             } else {
162 0         0 croak "$_ is not exported by $me";
163             }
164             }
165             }
166              
167             sub smtp_server {
168 5     5 1 17482 my ($host,$port,$cb) = @_;
169 5         437 my $server = AnyEvent::SMTP::Server->new(
170             host => $host,
171             port => $port,
172             );
173             $server->reg_cb(
174             mail => sub {
175 41     41   1645 $cb->($_[1]);
176             },
177 5         112 );
178 5         599 $server->start;
179             defined wantarray
180 0     0   0 ? AnyEvent::Util::guard { $server->stop; %$server = (); }
  0         0  
181             : ()
182 5 50       955 }
183              
184             sub new {
185 7     7 1 524 my $pkg = shift;
186 7         332 my $self = bless { @_ }, $pkg;
187 7 50       448 $self->{hostname} = hostname() unless defined $self->{hostname};
188             $self->set_exception_cb( sub {
189 0     0   0 my ($e, $event, @args) = @_;
190 0         0 my $ex = $@;
191 0 0       0 if (exists $self->{event_failed}) {
192 0         0 $self->{event_failed} = $ex;
193 0         0 return;
194             }
195             #warn "exception: $self, $self->{current_con} (@args) [$@]";
196 0         0 my $con = $self->{current_con};
197 0 0       0 if (!$con) {
198 0         0 local $::self = $self;
199 0         0 local $::con;
200 0         0 local $::event = $event;
201             {
202 0         0 package DB;
203 0         0 my $i = 0;
204 0         0 while (my @c = caller(++$i)) {
205 0         0 warn "$i. [@DB::args]";
206 0 0       0 next if @DB::args < 2;
207 0 0 0     0 last if $DB::args[0] == $::self and $DB::args[1] eq $::event and UNIVERSAL::isa($DB::args[2], 'AnyEvent::SMTP::Conn');
      0        
208             }
209 0         0 $::con = $DB::args[2];
210             }
211 0         0 $con = $::con;
212             }
213 0 0       0 if ($con) {
214 0         0 my $msg = "500 INTERNAL ERROR";
215 0 0       0 if ($self->{devel}) {
216 0         0 $ex =~ s{(?:\r?\n)+}{ }sg;
217 0         0 $ex =~ s{\s+$}{}s;
218 0         0 $msg .= ": ".$ex;
219             }
220 0         0 $con->reply($msg);
221             }
222 0         0 warn "exception during $event : $ex";
223 7         773 } );
224             $self->reg_cb(
225             command => sub {
226 164     164   3515 my ($s,$con,$com) = @_;
227 164         211 my ($cmd, @args);
228 164         290 for ($com) {
229 164         658 s/^\s+//;s/\s+$//;
  164         821  
230 164 50       394 length or last;
231 164         795 ($cmd, @args) = split /\s+/;
232 164         672 $cmd = uc $cmd;
233             }
234 164 50       836 if (exists $CMD{$cmd}) {
235 164         3935 $s->handle( $con, $cmd, @args );
236             } else {
237 0         0 warn "$cmd @args";
238 0         0 $con->reply("500 Learn to type!");
239             }
240             #warn "Got command @_";
241             },
242             HELO => sub {
243 41     41   795 my ($s,$con,@args) = @_;
244 41         153 $con->{helo} = "@args";
245 41         206 $con->new_m();
246 41         174 $con->ok("I'm ready.");
247             },
248             EHLO => sub {
249 0     0   0 my ($s,$con,@args) = @_;
250 0         0 $con->{helo} = "@args";
251 0         0 $con->new_m();
252 0         0 $con->ok("Go on.");
253             },
254             RSET => sub {
255 0     0   0 my ($s,$con,@args) = @_;
256 0         0 $con->new_m();
257 0         0 $con->ok;
258             },
259             MAIL => sub {
260 41     41   930 my ($s,$con,@args) = @_;
261 41         113 my $from = join ' ',@args;
262 41 50       346 $from =~ s{^from:}{}i or return $con->reply('501 Usage: MAIL FROM:');
263 41 50       5817 $con->{helo} or return $con->reply("503 Error: send HELO/EHLO first");
264 41         68 my @addrs;
265 41 50       298 if ($from !~ /^\s*<>\s*$/) {
266 41         375 @addrs = map { $_->address } Mail::Address->parse($from);
  41         8579  
267 41 50       654 @addrs == 1 or return $con->reply('501 Usage: MAIL FROM:');
268             } else {
269 0         0 @addrs = ('');
270             }
271 41 50       142 if ($self->{mail_validate}) {
272 0         0 my ($res,$err,$errstr) = $self->{mail_validate}->($con->{m}, $addrs[0]);
273 0 0       0 $res or return $con->reply("$err $errstr");
274             }
275 41         128 $con->{m}{from} = $addrs[0];
276 41         151 $con->ok;
277             },
278             RCPT => sub {
279 41     41   9740 my ($s,$con,@args) = @_;
280 41         286 my $to = join ' ',@args;
281 41 50       249 $to =~ s{^to:}{}i or return $con->reply('501 Usage: RCPT TO:');
282 41 50       361 defined $con->{m}{from} or return $con->reply("503 Error: need MAIL command");
283 41         358 my @addrs = map { $_->address } Mail::Address->parse($to);
  41         7587  
284 41 50       477 @addrs or return $con->reply('501 Usage: RCPT TO:');
285 41 50       134 if ($self->{rcpt_validate}) {
286 0         0 my ($res,$err,$errstr) = $self->{rcpt_validate}->($con->{m}, $addrs[0]);
287 0 0       0 $res or return $con->reply("$err $errstr");
288             }
289 41   50     232 push @{ $con->{m}{to} ||= [] }, $addrs[0];
  41         1203  
290 41         161 $con->ok;
291             },
292             DATA => sub {
293 41     41   695 my ($s,$con) = @_;
294 41 50       324 defined $con->{m}{from} or return $con->reply("503 Error: need MAIL command");
295 41 50       134 $con->{m}{to} or return $con->reply("554 Error: need RCPT command");
296 41         152 $con->reply("354 End data with .");
297             $con->data(cb => sub {
298 41         150 my $data = shift;
299 41 50       223 if ($self->{data_validate}) {
300 0         0 my ($res,$err,$errstr) = $self->{data_validate}->($con->{m}, $data);
301 0 0       0 $res or return $con->reply("$err $errstr");
302             }
303 41         211 $con->{m}{data} = $data;
304 41         143 local $s->{event_failed} = 0;
305 41         323 local $s->{current_con} = $con;
306 41         321 $s->event( mail => delete $con->{m} );
307 41 50       922 if ($s->{event_failed}) {
308 0         0 $con->reply("500 Internal Server Error");
309             } else {
310 41         4800 $con->ok("I'll take it");
311             }
312 41         394 });
313             },
314             QUIT => sub {
315 0     0   0 my ($s,$con,$to,@args) = @_;
316 0         0 $con->reply("221 Bye.");
317 0         0 $con->close;
318 0         0 return;
319             },
320 0     0   0 HELP => sub { $_[1]->reply("214 No help available.") },
321 0     0   0 NOOP => sub { $_[1]->reply("252 Ok.") },
322 0     0   0 EXPN => sub { $_[1]->reply("252 Nice try.") },
323 0     0   0 VRFY => sub { $_[1]->reply("252 Nice try.") },
324 7         1694 );
325 7         8397 $self;
326             }
327              
328             sub stop {
329 2     2 1 529 my $self = shift;
330 2         3 for (keys %{ $self->{c} }) {
  2         206  
331 1 50       12 $self->{c}{$_} and $self->{c}{$_}->close;
332             }
333 2         6 delete $self->{c};
334 2         11 delete $self->{s};
335 2         77 return;
336             }
337              
338             sub start {
339 7     7 1 94 my $self = shift;
340 7 50       61 $self->eventcan('command') or croak "Server implementation $self doesn't parses commands";
341             #$self->{engine} or croak "Server implementation $self doesn't have engine";
342             $self->{s} = tcp_server $self->{host}, $self->{port}, sub {
343 47     47   309833 my ($fh,$host,$port) = @_;
344 47 50       324 unless ($fh) {
345 0         0 $self->event( error => "couldn't accept client: $!" );
346 0         0 return;
347             }
348 47         274 $self->accept_connection(@_);
349             }, sub {
350 7     7   3234 my ($sock,$host,$port) = @_;
351             #$self->{sock} = $sock;
352 7 50       54 $self->{host} = $host unless defined $self->{host};
353 7 50       327 $self->{port} = $port unless defined $self->{port};
354 7 50       39 warn "Server started on port $self->{port}\n" if $self->{debug};
355 7         172 $self->event(ready => ());
356 7         261 return undef;
357 7         319 };
358            
359             }
360              
361             sub accept_connection {
362 47     47 0 123 my ($self,$fh,$host,$port) = @_;
363 47 50       194 warn "Client connected $host:$port \n" if $self->{debug};
364 47         918 my $con = AnyEvent::SMTP::Conn->new(
365             fh => $fh,
366             host => $host,
367             port => $port,
368             debug => $self->{debug},
369             );
370 47         278 $self->{c}{int $con} = $con;
371             $con->reg_cb(
372             disconnect => sub {
373 46     46   1978 delete $self->{c}{int $_[0]};
374 46         411 $self->event( disconnect => $_[0], $_[1] );
375             },
376             command => sub {
377 164     164   4305 $self->event( command => @_ )
378             },
379 47         1172 );
380 47         17110 $self->eventif( client => $con );
381 47         403 $con->reply("220 $self->{hostname} AnyEvent::SMTP Ready.");
382 47         1370 $con->want_command;
383             }
384              
385             sub eventif {
386             #my ($self,$name) = @_;
387 211     211 0 326 my $self = shift;my $name = shift;
  211         362  
388 211 100       1219 return 0 unless $self->eventcan($name);
389 165         769 $self->event($name => @_);
390 165         4821 return 1;
391             #goto &{ $self->can('event') };
392             }
393              
394             sub eventcan {
395 218     218 0 491 my $self = shift;
396 218         337 my $name = shift;
397 218 100       975 return undef unless exists $self->{__oe_events}{$name};
398 172         397 return scalar @{ $self->{__oe_events}{$name} };
  172         719  
399             }
400              
401             sub handle {
402 164     164 0 405 my ($self,$con, $cmd, @args ) = @_;
403             $self->eventif( $cmd => $con, @args )
404 164 50       623 or do {
405 0           $con->reply("500 Not Supported");
406 0           warn "$cmd event not handled ($cmd @args)";
407 0           0;
408             };
409             }
410              
411             =head1 BUGS
412              
413             Bug reports are welcome in CPAN's request tracker L
414              
415             =head1 AUTHOR
416              
417             Mons Anderson, C<< >>
418              
419             =head1 COPYRIGHT & LICENSE
420              
421             Copyright 2009 Mons Anderson, all rights reserved.
422              
423             This program is free software; you can redistribute it and/or modify it
424             under the same terms as Perl itself.
425              
426             =cut
427              
428             1;