AnyEvent-SMTP
view release on metacpan or search on metacpan
lib/AnyEvent/SMTP/Server.pm view on Meta::CPAN
package AnyEvent::SMTP::Server;
=head1 NAME
AnyEvent::SMTP::Server - Simple asyncronous SMTP Server
=cut
use Carp;
use AnyEvent;
use common::sense;
m{# trying to cheat with cpants game ;)
use strict;
use warnings;
}x;
use base 'Object::Event';
use AnyEvent::Handle;
use AnyEvent::Socket;
use AnyEvent::Util;
use Sys::Hostname;
use Mail::Address;
use AnyEvent::SMTP::Conn;
our $VERSION = $AnyEvent::SMTP::VERSION;use AnyEvent::SMTP ();
our %CMD = map { $_ => 1 } qw( HELO EHLO MAIL RCPT QUIT DATA EXPN VRFY NOOP HELP RSET );
=head1 SYNOPSIS
use AnyEvent::SMTP::Server 'smtp_server';
smtp_server undef, 2525, sub {
my $mail = shift;
warn "Received mail from $mail->{from} to $mail->{to}\n$mail->{data}\n";
};
# or
use AnyEvent::SMTP::Server;
my $server = AnyEvent::SMTP::Server->new(
port => 2525,
mail_validate => sub {
my ($m,$addr) = @_;
if ($good) { return 1 } else { return 0, 513, 'Bad sender.' }
},
rcpt_validate => sub {
my ($m,$addr) = @_;
if ($good) { return 1 } else { return 0, 513, 'Bad recipient.' }
},
data_validate => sub {
my ($m,$data) = @_;
my $size = length $data;
if ($size > $max_email_size) {
return 0, 552, 'REJECTED: message size limit exceeded';
} else {
return 1;
}
},
);
$server->reg_cb(
client => sub {
my ($s,$con) = @_;
warn "Client from $con->{host}:$con->{port} connected\n";
},
disconnect => sub {
my ($s,$con) = @_;
warn "Client from $con->{host}:$con->{port} gone\n";
},
mail => sub {
my ($s,$mail) = @_;
warn "Received mail from ($mail->{host}:$mail->{port}) $mail->{from} to $mail->{to}\n$mail->{data}\n";
},
);
$server->start;
AnyEvent->condvar->recv;
=head1 DESCRIPTION
Simple asyncronous SMTP server. Authorization not implemented yet. Patches are welcome
=head1 FUNCTIONS
=head2 smtp_server $host, $port, $cb->(MAIL)
=head1 METHODS
=head2 new %args;
=over 4
=item hosthame
Server FQDN
=item host
Address to listen on. by default - undef (0.0.0.0)
=item port
Port to listen on
=back
=head2 start
Creates tcp server and starts to listen
lib/AnyEvent/SMTP/Server.pm view on Meta::CPAN
$con = $::con;
}
if ($con) {
my $msg = "500 INTERNAL ERROR";
if ($self->{devel}) {
$ex =~ s{(?:\r?\n)+}{ }sg;
$ex =~ s{\s+$}{}s;
$msg .= ": ".$ex;
}
$con->reply($msg);
}
warn "exception during $event : $ex";
} );
$self->reg_cb(
command => sub {
my ($s,$con,$com) = @_;
my ($cmd, @args);
for ($com) {
s/^\s+//;s/\s+$//;
length or last;
($cmd, @args) = split /\s+/;
$cmd = uc $cmd;
}
if (exists $CMD{$cmd}) {
$s->handle( $con, $cmd, @args );
} else {
warn "$cmd @args";
$con->reply("500 Learn to type!");
}
#warn "Got command @_";
},
HELO => sub {
my ($s,$con,@args) = @_;
$con->{helo} = "@args";
$con->new_m();
$con->ok("I'm ready.");
},
EHLO => sub {
my ($s,$con,@args) = @_;
$con->{helo} = "@args";
$con->new_m();
$con->ok("Go on.");
},
RSET => sub {
my ($s,$con,@args) = @_;
$con->new_m();
$con->ok;
},
MAIL => sub {
my ($s,$con,@args) = @_;
my $from = join ' ',@args;
$from =~ s{^from:}{}i or return $con->reply('501 Usage: MAIL FROM:<mail addr>');
$con->{helo} or return $con->reply("503 Error: send HELO/EHLO first");
my @addrs;
if ($from !~ /^\s*<>\s*$/) {
@addrs = map { $_->address } Mail::Address->parse($from);
@addrs == 1 or return $con->reply('501 Usage: MAIL FROM:<mail addr>');
} else {
@addrs = ('');
}
if ($self->{mail_validate}) {
my ($res,$err,$errstr) = $self->{mail_validate}->($con->{m}, $addrs[0]);
$res or return $con->reply("$err $errstr");
}
$con->{m}{from} = $addrs[0];
$con->ok;
},
RCPT => sub {
my ($s,$con,@args) = @_;
my $to = join ' ',@args;
$to =~ s{^to:}{}i or return $con->reply('501 Usage: RCPT TO:<mail addr>');
defined $con->{m}{from} or return $con->reply("503 Error: need MAIL command");
my @addrs = map { $_->address } Mail::Address->parse($to);
@addrs or return $con->reply('501 Usage: RCPT TO:<mail addr>');
if ($self->{rcpt_validate}) {
my ($res,$err,$errstr) = $self->{rcpt_validate}->($con->{m}, $addrs[0]);
$res or return $con->reply("$err $errstr");
}
push @{ $con->{m}{to} ||= [] }, $addrs[0];
$con->ok;
},
DATA => sub {
my ($s,$con) = @_;
defined $con->{m}{from} or return $con->reply("503 Error: need MAIL command");
$con->{m}{to} or return $con->reply("554 Error: need RCPT command");
$con->reply("354 End data with <CR><LF>.<CR><LF>");
$con->data(cb => sub {
my $data = shift;
if ($self->{data_validate}) {
my ($res,$err,$errstr) = $self->{data_validate}->($con->{m}, $data);
$res or return $con->reply("$err $errstr");
}
$con->{m}{data} = $data;
local $s->{event_failed} = 0;
local $s->{current_con} = $con;
$s->event( mail => delete $con->{m} );
if ($s->{event_failed}) {
$con->reply("500 Internal Server Error");
} else {
$con->ok("I'll take it");
}
});
},
QUIT => sub {
my ($s,$con,$to,@args) = @_;
$con->reply("221 Bye.");
$con->close;
return;
},
HELP => sub { $_[1]->reply("214 No help available.") },
NOOP => sub { $_[1]->reply("252 Ok.") },
EXPN => sub { $_[1]->reply("252 Nice try.") },
VRFY => sub { $_[1]->reply("252 Nice try.") },
);
$self;
}
sub stop {
my $self = shift;
for (keys %{ $self->{c} }) {
$self->{c}{$_} and $self->{c}{$_}->close;
}
delete $self->{c};
delete $self->{s};
return;
}
sub start {
my $self = shift;
$self->eventcan('command') or croak "Server implementation $self doesn't parses commands";
#$self->{engine} or croak "Server implementation $self doesn't have engine";
$self->{s} = tcp_server $self->{host}, $self->{port}, sub {
my ($fh,$host,$port) = @_;
unless ($fh) {
$self->event( error => "couldn't accept client: $!" );
return;
}
$self->accept_connection(@_);
}, sub {
my ($sock,$host,$port) = @_;
#$self->{sock} = $sock;
$self->{host} = $host unless defined $self->{host};
$self->{port} = $port unless defined $self->{port};
warn "Server started on port $self->{port}\n" if $self->{debug};
$self->event(ready => ());
return undef;
};
}
( run in 1.609 second using v1.01-cache-2.11-cpan-5b529ec07f3 )