メイルの誤送信防止などの目的のため,メイル・プロキシ (SMTP プロキシ) が重要になっている. Perl によって書かれたメイル・プロキシとしては Qsmtpd があるが,自分で単純なプロキシをつくりたいとおもったときには,このプログラムは複雑すぎて,あまり参考にはならない. そこで,できるだけ単純なプロキシのプログラムを書いてみた.
以下のプログラムはメイル・クライアントからメイルを SMTP によってうけとって SMTP サーバにリレーするが,"viagra" というキーワードがみつかると転送しない. 動作させるためには 'smtp.server.address' のかわりに実際に使用する SMTP サーバのアドレス / ドメイン名を指定する. (ポートが既定の 25 でなければ,それもかきかえる必要がある.)
############################################################################
#
# Simple Mail Proxy
#
############################################################################
use Carp;
use Net::SMTP;
use Net::SMTP::Server;
use Mail::Message;
use Sys::Hostname;
use strict;
my $debug = 0;
## SMTP server address and port ##
#
my $SMTP_Server_Address = 'smtp.server.address';
my $SMTP_Server_Port = 25;
### Mail proxy (this server) address and port ##
#
my $Proxy_Port = 25;
#===========================================================================
# Mail client connection service
#===========================================================================
### Constants ###
my $SUCCEEDED = 0;
my %commands = (DATA => \&cmd_data,
EXPN => \&cmd_dummy,
HELO => \&cmd_helo,
HELP => \&cmd_help,
MAIL => \&cmd_mail,
NOOP => \&cmd_noop,
QUIT => \&cmd_quit,
RCPT => \&cmd_rcpt,
RSET => \&cmd_rset,
VRFY => \&cmd_dummy);
### Variables ###
my $client_socket;
my $from;
my @to;
my $message;
sub client_put ($) {
my ($message) = @_;
print "Sent: $message\n" if ($debug);
print $client_socket $message, "\r\n";
}
sub cmd_data () {
if (!defined($from)) {
client_put("503 5.5.1 Sender address not yet specified");
return 1;
};
if (!@to) {
client_put("503 5.5.1 Recepient address not yet specified");
return 1;
};
client_put("354 Start mail input; end with .");
my $done = 0;
while (<$client_socket>) {
# print "Received: $_" if ($debug);
if (/^\.\r\n$/) {
$done = 1;
last;
};
s/^\.\./\./;
$message .= $_;
};
if (!$done) {
client_put("451 5.6.0 Message input failed");
return 1;
};
return 0;
}
sub cmd_helo () {
client_put("250-Action completed, okay");
client_put("250 ENHANCEDSTATUSCODES");
}
sub cmd_help () {
my $out = "214-Commands\r\n";
my $total = keys %commands;
my $i = 0;
foreach my $cmd (keys %commands) {
$out .= "\r\n214";
if ($i++ % 5 != 0) {
$out .= $total - $i < 5 ? " " : "-";
} else {
$out .= " ";
};
};
client_put($out);
}
sub cmd_noop () {
client_put("252 Unknown status, but attempting delivery");
}
sub cmd_quit () {
client_put("221 Service closing");
$client_socket->close();
return 0;
}
sub cmd_mail ($) {
my ($arg) = @_;
$arg =~ /FROM:\s*(\S+)$/i;
$from = $1;
client_put("250 Mail sender okay");
}
sub cmd_rcpt ($) {
my ($arg) = @_;
$arg =~ /TO:\s*(\S+)$/i;
my $to = $1;
push(@to, $to);
client_put("250 Mail recepient okay");
}
sub cmd_rset () {
$from = undef;
@to = ();
client_put("250 Reset action okay");
}
sub cmd_dummy () {
}
#===========================================================================
# SMTP server connection service
#===========================================================================
### relay ($from, @to, $msg) ###
# forward a mail to specified SMTP server
#
sub relay ($\@$) {
my ($from, $to, $msg) = @_;
$from =~ /<.*@(.*)>/;
my $domain = $1;
print "Domain: $domain\n" if ($debug);
my $client = new Net::SMTP($SMTP_Server_Address, Port => $SMTP_Server_Port,
Hello => $domain, Timeout => 30, Debug => $debug) ||
croak "Unable to connect to mail server: $!\n";
if ($client) {
$client->mail($from);
foreach my $recipient (@$to) {
$client->to($recipient);
};
$client->data($msg);
$client->quit() || croak "Relay failed: $!\n";
};
}
#===========================================================================
# Main
#===========================================================================
my $server = new Net::SMTP::Server(hostname(), $Proxy_Port) ||
croak "Unable to create a new mail proxy: $!\n";
while ($client_socket = $server->accept()) {
$from = undef;
@to = ();
$message = undef;
my $accepted;
client_put("220 Service ready");
while (<$client_socket>) {
print "Received: $_" if ($debug);
chomp;
my ($cmd, $arg);
/^\s*(\S+)(\s+(.*\S))?\s*$/;
$cmd = $1;
$arg = $3;
$cmd =~ tr/a-z/A-Z/;
if (!defined($commands{$cmd})) {
client_put("500 5.5.2 Syntax error, command unrecognized");
next;
};
&{$commands{$cmd}}($arg);
if ($cmd eq 'DATA') {
my $msg = Mail::Message->read($message);
my $body = $msg->body;
if ($body =~ /viagra/i) {
client_put("554 5.6.0 Invalid keyword included: viagra");
$accepted = 0;
} else {
client_put("250 2.0.0 Message accepted for delivery");
$accepted = 1;
};
};
};
$client_socket->close();
if ($accepted) {
relay($from, @to, $message);
};
}
