| Новости | FAQ | Авторы | Документация | В действии | Библиотека |
| Инструменты | Полезные ссылки | Хостинги | Скачать | Примеры | Форум |
Sanja v.2 12.04.2004 12:27
Вот, держите. С поправленными багами и сохранением полученного от клиента в EML-файл Outlook.############################################################################
#
# Эмулятор работы SMTP-сервера - ничего не отправляет, но аккуратно
# журналирует все переговоры с клиентами, сохраняя копию в EML-файле Outlook.
#
############################################################################
#
# Copyright (c) 2004 Alexander Bougakov, http://www.bougakov.com/go/cityblog/
# Вы вольны пользоваться этим кодом на условиях Creative Commons License:
# http://creativecommons.org/licenses/by-nc-sa/1.0/
#
# Основано на коде модулей Net::SMTP::Server, Net::SMTP::Server::Client и
# Net::SMTP::Server::Client2 авторства Habeeb J. Dihu и David Nicole,
# соответственно.
#
############################################################################
unshift @INC, './'; # указывает Perl искать модули
# в текущей папке, а не только в /lib
use Carp;
use smtpserver;
use smtpclient;
# Сообщения больше этого размера (в байтах) не будут приниматься эмулятором
# (клиент получит ошибку 552 по RfC 821):
my $limit = 9900000;
# Куда подцепляется эмулятор (хост, порт):
my $host = "localhost";
my $port = 25;
# Логфайл:
my $log = "SMTP_log.txt";
# Также пишется логфайл с именем $log.eml - в него сохраняется последнее
# из отправленных писем. Запуск этого файла откроет его в Outlook Express
# - это даст вам проверить, такой ли результат вы ожидаете (что гораздо
# веселее отправки его самому себе через "настоящий" почтовый сервер с
# дальнейшей выкачкой его по POP3, не правда ли?)
# Логфайл пишется по принципу "открыть файл, записать строку, закрыть файл,
# повторить для каждой следующей строки" - работает медленнее, чем могло бы
# быть, зато в случае падения сервера или клиента будет видно, на каком месте
# проблема случилась.
# Старый логфайл, если есть на диске, затирается. Если в одном сеансе работы
# эмулятора отправляется нескольких писем, все сессии фиксируются в логе
# (а не только самая последняя)
## Ну поехали: #############################################################
# Welcome-screen:
print "___________________________________________________________________\n\n";
print " SMTP server emulator is now working on \"" . $host .":" . $port ."\"\n\n";
print " Conversations with clients will be logged to \"" . $log ."\" file\n";
print " (overwrites older one, if present). Outlook-readable copy will be\n";
print " saved to \"" . $log .".eml\" (If several messages will be sent in\n";
print " the batch, only the last one will be there!) Server's responses\n";
print " will be echoed to this window.\n\n";
print " Press Ctrl+C to close this window and stop the emulator.\n";
print "___________________________________________________________________\n\n";
# Удаляем прежний логфайл:
unlink $log;
unlink $log . ".eml";
# Цепляемся к указанным локальным хосту и порту:
my $server = new smtpserver($host => $port) || croak("Unable to launch or bind server: $!\n");
while($conn = $server->accept()) {
fork and last;
$conn->close;
};
# Ждём подключений:
my $count = '';
my $client = new smtpclient($conn, $log) || croak("Unable to start interface: $!\n");
$client->greet;
while($client->get_message){
if (length($client->{MSG}) > $limit){
$client->too_long;
} else {
$count++;
$client->okay("Message accepted");
$client->closelog;
}
}Файл smtpserver.pm package smtpserver;
use strict;
use vars qw(@ISA @EXPORT);
require Exporter;
require AutoLoader;
use Carp;
use IO::Socket;
use Sys::Hostname;
@ISA = qw(Exporter AutoLoader);
@EXPORT = qw();
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {};
$self->{HOST} = shift;
$self->{PORT} = shift;
bless($self, $class);
$self->{HOST} = hostname unless defined($self->{HOST});
$self->{PORT} = 25 unless defined($self->{PORT});
$self->{SOCK} = IO::Socket::INET->new(
Proto => 'tcp',
LocalAddr => $self->{HOST},
LocalPort => $self->{PORT},
Listen => SOMAXCONN,
Reuse => 1
);
return defined($self->{SOCK}) ? $self : undef;
}
sub accept {
my $self = shift;
my $client;
if($client = $self->{SOCK}->accept()) {
$self->{SOCK}->autoflush(1);
return $client;
}
return undef;
}
sub DESTROY {
shift->{SOCK}->close;
}
1;Файл smtpclient.pm package smtpclient;
use strict;
use vars qw($LOGF);
use Carp;
use IO::Socket;
# Значение по умолчанию:
my $LOGF = "SMTP_log.txt";
my %_cmds = (
DATA => \&_data,
EXPN => \&_noway,
HELO => \&_hello,
HELP => \&_help,
MAIL => \&_mail,
NOOP => \&_noop,
QUIT => \&_quit,
RCPT => \&_receipt,
RSET => \&_reset,
VRFY => \&_noway
);
sub _reset0 {
my $self = shift;
$self->{FROM} = undef;
$self->{TO} = [];
$self->{MSG} = undef;
$self->{faults} = 0;
}
sub _reset {
my $self = shift;
$self->_reset0;
$self->_put("250 Mail transaction aborted");
}
sub new {
my($this, $sock, $log) = @_;
my $class = ref($this) || $this;
my $self = {};
$LOGF = $log;
bless($self, $class);
$self->_reset0;
$self->{SOCK} = $sock;
croak("No client connection specified.") unless defined($self->{SOCK});
open(LOG, ">>$LOGF");
print LOG "### Эмулятор SMTP-сервера ### " . scalar(localtime) . " ###\n\n";
close(LOG);
unlink $log . ".eml";
return $self;
}
sub greet {
$_[0]->_put("220 SMTP Emulator ready.");
}
sub basta{
my $self = shift;
$self -> _put("421 closing transmission channel");
$self->{SOCK}->close;
1;
}
sub get_message {
my $self = shift;
my($cmd, @args);
my $sock = $self->{SOCK};
$self->_reset0;
while(<$sock>) {
chomp;
open(LOG, ">>$LOGF");
print LOG $_ , "";
close(LOG);
$$self{faults} > 15 and $self->basta and last;
s/^\s+//;
s/\s+$//;
unless(length $_){
++$$self{faults};
$self->greet;
next;
};
($cmd, @args) = split(/\s+/);
$cmd =~ tr/a-z/A-Z/;
if(!defined($_cmds{$cmd})) {
sleep ++$$self{faults};
$self->_put("500 Server doesn't know how to $cmd");
next;
};
&{$_cmds{$cmd}}($self, \@args) or
return(defined($self->{MSG}));
}
return undef;
}
sub find_addresses {
return map { /([^<|;]+\@[^>|;&,\s]+)/ ? $1 : () } @_;
};
sub okay {
my $self = shift;
$self -> _put("250 OK @_");
}
sub fail {
my $self = shift;
$self -> _put("554 @_");
}
sub too_long {
$_[0] -> _put("552 Requested mail action aborted: exceeded storage allocation");
};
sub _mail {
my $self = $_[0];
my @who = find_addresses(@{$_[1]});
my $who = shift @who;
if ($who){
$self->{FROM} = $who;
return $self->okay("Envelope sender set to <$who> ")
}else{
$self->{faults}++;
return $self-> _put("501 could not find name\@postoffice in <@{$_[1]}>")
};
}
sub rcpt_syntax{
$_[0] -> _put("553 no user\@host addresses found in <@{$_[1]}>");
}
sub _receipt {
my $self = $_[0];
my @recipients = find_addresses(@{$_[1]});
@recipients or return $self->rcpt_syntax($_[1]);
push @{ $self->{TO} }, @recipients;
$self->okay("sending to @{$self->{TO}}");
}
sub _put {
print {shift->{SOCK}} @_, "\r\n";
print "### ", @_, "\n";
open(LOG, ">>$LOGF");
print LOG "", @_ , "\n";
close(LOG);
}
sub _data {
my $self = shift;
my @msg;
if(!$self->{FROM}) {
$self-> _put("503 start with 'mail from: ...'");
$self->{faults}++;
return 1;
}
if(!@{$self->{TO}}) {
$self->_put("503 specify recipients with 'RCPT TO: ...'");
$self->{faults}++;
return 1;
}
$self->_put("354 Start mail input, end with CRLF-dot-CRLF");
my $sock = $self->{SOCK};
while(<$sock>) {
chomp;
s/\x0D/\x0A/sg;
s/\x0D\x0D/\x0D/sg;
if(/^\.\r*\n*$/) {
$self->{MSG} = join ('', @msg);
return 0;
}
# Совместимость с RFC 821.
s/^\.\./\./;
push @msg, $_;
open(LOG, ">>$LOGF");
print LOG $_;
close(LOG);
open(EML, ">>$LOGF.eml");
print EML $_;
close(EML);
}
return 0;
}
sub closelog {
open(LOG, ">>$LOGF");
print LOG "\n\n### Конец журнала ###\n\n\n\n";
close(LOG);
}
sub _noway {
shift->_put("252 Nice try.");
}
sub _noop {
shift->_put("250 OK, server will wait.");
}
sub _help {
my $self = shift;
my $i = 0;
my $str = "214-Commands\r\n";
my $total = keys(%_cmds);
foreach(sort(keys(%_cmds))) {
if(!($i++ % 5)) {
if(($total - $i) < 5) {
$str .= "\r\n214 ";
} else {
$str .= "\r\n214-";
}
} else {
$str .= ' ';
}
$str .= $_;
}
$self->_put($str);
}
sub _quit {
my $self = shift;
$self->_put("221 Closing communication");
$self->{SOCK}->close;
return 0;
}
sub _hello {
shift->okay( "Welcome" );
}
1;