일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- perltidy
- 리눅스
- Replication
- clustering
- 펄 코딩스타일
- PERL
- connection tunning
- pgsql
- ext3
- php-oracle 연동
- ext4
- pgpool-ii
- 오라클
- inotify
- CVSROOT 세팅
- 포기해버린꿈
- Openfiler
- pgbench
- postfix
- OCFS2
- 파일시스템
- tomcat
- 펄
- ZFS
- LVS
- 시그널
- mailfiler
- pvfs
- 가상파일시스템
- Nexenta
Archives
- Today
- Total
avicom의 신변잡기
Mail Filter 본문
Mail Filter
libmilter 설치sendmail 컴파일
- 소스 다운로드
rpm버전을 설치해도 되고, 소스 컴파일 설치해도 되지만, perl에서 milter 모듈을 컴파일하기 위해선 컴파일된 sendmail 소스가 필요하다.
wget ftp://ftp.sendmail.org/pub/sendmail/sendmail.8.14.4.tar.gz
- 빌드
64bit linux에서 sendmail을 컴파일하기 위해선 gcc -fPIC 옵션을 붙여야한다. -fPIC(position independent code)옵션 없이 컴파일할 경우 메모리의 지정된 주소에서 함수 및 심볼을 찾기 때문에 해당 라이브러리를 링크해서 컴파일할 때 필요한 함수를 못찾는 에러가 발생할 수 있다. ~sendmail source/devtools/M4/header.m4에 보면 cc 설정을 정의한 부분이 있다. 여기에 -fPIC 옵션을 추가한다.
define(`confCC', `cc -fPIC')
여기서는 MTA로 postfix를 사용하고, sendmail의 libmilter 를 가져와서 사용할 것이기 때문에, sendmail 컴파일까지만 하고 설치할 필요는 없다. (libmilter.a라는 정적 라이브러리가 필요하다)
top level 디렉토리의 Makefile을 열어서 SUBDIRS 항목에 libmilter를 추가하고 Build한다.
# ./Build
obj.Linux.{kernel_version}.x86_64 디렉토리가 생성되고 그 밑에 libmilter 디렉토리에 milter관련 라이브러리가 컴파일되어있다.
perl Milter 모듈 컴파일- 소스 다운로드
wget http://search.cpan.org/CPAN/authors/id/C/CY/CYING/Sendmail-Milter-0.18.tar.gz
- 컴파일 및 설치
perl Makefile.PL /root/src/sendmail-8.14.4 /root/src/sendmail-8.14.4/obj.Linux.2.6.18-92.el5xen.x86_64
make
make install
main.cf 수정
postfix는 가능하면 최근 버전을 사용하도록 한다. milter가 postfix에 완전하게 포팅되지 않아 postfix 버전별로 지원할 수 있는 기능이 다르다.
/etc/postfix/main.cf에 다음 항목을 추가한다.
milter_default_action = tempfail
smtpd_milters = unix:/var/run/milter.sock
milter_protocol = 2
이제 postfix과 milter.pl을 재시작하면 postfix로 들어오는 모든 메일은 /var/run/milter.sock을 통해 milter 모듈로 보내지고 필터링된 결과에 따라 반환값을 받게된다
Milter.pl 작성
Sendmail::Milter 모듈을 사용하여 milter 라이브러리를 제어한다.
milter는 몇가지 콜백함수를 정의해놓고 단계에 따라 필요할 때 호출하는 구조다. 콜백함수의 종류와 반환값은 아래 표를 참조. 여기서는 메일 헤더를 기준으로 필터링할 것이므로 header_callback 에만 동작을 지정해놓았다.
- milter.pl
#!/usr/bin/perl
use strict ('vars');;
use warnings;
use Sendmail::Milter;
use Daemon::Simple;
use Socket;
my %my_callbacks = (
'connect' => \&connect_callback,
'helo' => \&helo_callback,
'envfrom' => \&envfrom_callback,
'envrcpt' => \&envrcpt_callback,
'header' => \&header_callback,
'eoh' => \&eoh_callback,
'body' => \&body_callback,
'eom' => \&eom_callback,
'abort' => \&abort_callback,
'close' => \&close_callback,
);
my $hash = {};
my $homedir = "/root/admin_tools";
my $pidfile = "/var/run/milter.pid";
my $command = $ARGV[0];
sub Log {
my ($sec,$min,$hour,$mday,$mon,$year) = localtime;;
$year = $year + 1900; $mon = $mon + 1;
my $date = sprintf("%d-%02d-%02d %02d:%02d:%02d",$year, $mon, $mday, $hour, $min, $sec);
my $msg = $_[0];
my $LOG = "/var/log/milter.log";
open FILE,'>>',$LOG;
print FILE "$date : $msg \n";
close FILE;
}
Daemon::Simple::init($command,$homedir,$pidfile);
while(1) {
BEGIN:
{
my $filter = "SpamMailFilter";
my $unix_socket = '/var/run/milter.sock';
print "Found connection info for '$filter': $unix_socket\n";
if (-e $unix_socket) {
print "Attempting to unlink UNIX socket '$unix_socket' ... ";
if (unlink($unix_socket) == 0) {
print "failed.\n";
exit;
}
print "successful.\n";
}
if (!Sendmail::Milter::setconn('local:/var/run/milter.sock')) {
#if (!Sendmail::Milter::auto_setconn($filter, './milter.cf')) {
print "Failed to detect connection information.\n";
exit;
}
if (!Sendmail::Milter::register($filter, \%my_callbacks, SMFI_CURR_ACTS)) {
print "Failed to register callbacks for $filter.\n";
exit;
}
print("Starting Sendmail::Milter $Sendmail::Milter::VERSION engine.\n");
if (Sendmail::Milter::main()) {
print "Successful exit from the Sendmail::Milter engine.\n";
}
else {
print "Unsuccessful exit from the Sendmail::Milter engine.\n";
}
}
sleep 5;
}
sub connect_callback {
my ($ctx, $hostname, $sockaddr_in) = @_;
return SMFIS_CONTINUE;
}
sub helo_callback {
my ($ctx, $helohost) = @_;
return SMFIS_CONTINUE;
}
sub envfrom_callback {
my ($ctx, @args) = @_;
return SMFIS_CONTINUE;
}
sub envrcpt_callback {
my ($ctx, @args) = @_;
return SMFIS_CONTINUE;
}
sub header_callback {
my ($ctx, $headerf, $headerv) = @_;
if ($headerf eq "From") {
if ($headerv =~ /Returned mail/ || $headerv =~ /^Postmaster notify/ || $headerv =~ /^Mail Delivery/ ) { return SMFIS_CONTINUE; }
my $from_address;
if ($headerv =~ m/^[\W|\"| +]/) {
$headerv =~ m/.*<(\S+\@\S+)>/;
$from_address = $1;
}
else {
$headerv =~ m/(\S+\@\S+)/;
$from_address = $1;
}
my ($id, $addr) = split(/\@/, $from_address, 2);
$hash->{$id}->{id} = $id;
$hash->{$id}->{addr} = $addr;
my $cur_time = $hash->{$id}->{cur_time} = time();
my $blocked = $hash->{$id}->{blocked};
my $blocked_time = $hash->{$id}->{blocked_time};
if ( defined($blocked) && defined($blocked_time) && ($cur_time - $blocked_time) <= 30 ) {
my $gap = $cur_time - $blocked_time;
print "$headerv - time : $gap\n";
return SMFIS_REJECT;
#return SMFIS_DISCARD;
}
my $base_time = $hash->{$id}->{base_time};
if (!defined($base_time)) {
$hash->{$id}->{base_time} = time();
$hash->{$id}->{count} = 1;
}
elsif ( time() - $hash->{$id}->{base_time} <= 30) {
$hash->{$id}->{count}++;
}
else {
$hash->{$id}->{count} = 1;
}
#$cur_time = time();
#$base_time = $hash->{$id}->{base_time};
my $count = $hash->{$id}->{count};
my $time_gap = $cur_time - $base_time;
my $trans_rate;
if ($count > 0 && $time_gap > 0) {
$trans_rate = sprintf("%.2f",$count / $time_gap);
}
else {
$trans_rate = 0;
}
my $sizeofhash += keys %$hash;
if ($trans_rate > 30) {
#printf ("reject : %30s count %d trans_rate = %.2f \n", $headerv, $count, $trans_rate);
my $msg = sprintf("reject : %30s total count in 30s = %2d trans_rate = %.2f addr : %30s hashsize : %5d", $from_address, $count, $trans_rate, $headerv, $sizeofhash);
Log($msg);
$hash->{$id}->{blocked} = 1;
$hash->{$id}->{blockeb_time} = time();
return SMFIS_REJECT;
#return SMFIS_DISCARD;
}
elsif ( $time_gap > 30 ) {
$hash->{$id}->{base_time} = time();
$hash->{$id}->{cur_time} = time();
$hash->{$id}->{count} = 0;
}
else {
#print "addr : $id, basetime : $base_time, curtime : $cur_time, count : $count, trans_rate = $trans_rate \n";
my $msg = sprintf("accept : %30s total count in 30s = %2d trans_rate = %.2f addr : %30s hashsize : %5d", $from_address, $count, $trans_rate, $headerv, $sizeofhash) ;
Log($msg);
}
if ($sizeofhash > 5000) {
$hash = {};
}
}
return SMFIS_CONTINUE;
}
sub eoh_callback {
my ($ctx) = @_;
return SMFIS_CONTINUE;
}
sub body_callback {
my ($ctx, $body_chunk, $len) = @_;
# print("▒▒▒▒ ▒▒▒▒: $len\n");
#print "$body_chunk \n";
return SMFIS_CONTINUE;
}
sub eom_callback {
my ($ctx) = @_;
$ctx->addheader("X-Simplexi-MailFilter", "1.0");
return SMFIS_CONTINUE;
}
sub abort_callback {
my ($ctx) = @_;
return SMFIS_CONTINUE;
}
sub close_callback {
my ($ctx) = @_;
return SMFIS_CONTINUE;
}
- start script 작성
cat milter.sh
#!/bin/sh
function start() {
/root/admin_tools/milter.pl start
chown postfix. /var/run/milter.sock
}
function stop() {
/root/admin_tools/milter.pl stop
}
case "$1" in
start)
start
;;
stop)
stop
;;
*)
echo $"Usage: $0 {start|stop}"
RETVAL=1
esac
- 기동
milter.pl을 root로 띄울 경우 메일 프로세스가 milter.sock을 엑세스하지 못하므로 메일 데몬과 같은 owner로 띄워야한다.
./milter.sh start
/root/admin/script/postfix_policy/milter_hash.pl is Starting.
Filtering Test
- milter.pl 설정을 30초 동안 초당 1 건을 초과하는 메일을 필터링하는 경우
2010-04-14 18:21:23 : accept : emerson@simplexi.com total count in 30s = 1 trans_rate = 1.00 addr : emerson@simplexi.com hashsize : 4
2010-04-14 18:21:23 : accept : dsdhks@simplexi.com total count in 30s = 1 trans_rate = 0.00 addr : dsdhks@simplexi.com hashsize : 4
2010-04-14 18:21:23 : accept : kim@simplexi.com total count in 30s = 1 trans_rate = 0.00 addr : kim@simplexi.com hashsize : 4
2010-04-14 18:21:23 : accept : ht@simplexi.com total count in 30s = 1 trans_rate = 0.00 addr : ht@simplexi.com hashsize : 4
2010-04-14 18:21:23 : reject : emerson@simplexi.com total count in 30s = 2 trans_rate = 2.00 addr : emerson@simplexi.com hashsize : 4
2010-04-14 18:21:24 : reject : dsdhks@simplexi.com total count in 30s = 2 trans_rate = 2.00 addr : dsdhks@simplexi.com hashsize : 4
2010-04-14 18:21:24 : reject : kim@simplexi.com total count in 30s = 2 trans_rate = 2.00 addr : kim@simplexi.com hashsize : 4
2010-04-14 18:21:24 : reject : ht@simplexi.com total count in 30s = 2 trans_rate = 2.00 addr : ht@simplexi.com hashsize : 4
2010-04-14 18:21:26 : accept : dsdhks@simplexi.com total count in 30s = 3 trans_rate = 1.00 addr : dsdhks@simplexi.com hashsize : 4
2010-04-14 18:21:26 : accept : kim@simplexi.com total count in 30s = 3 trans_rate = 1.00 addr : kim@simplexi.com hashsize : 4
2010-04-14 18:21:26 : accept : ht@simplexi.com total count in 30s = 3 trans_rate = 1.00 addr : ht@simplexi.com hashsize : 4
2010-04-14 18:21:26 : accept : emerson@simplexi.com total count in 30s = 3 trans_rate = 0.75 addr : emerson@simplexi.com hashsize : 4
2010-04-14 18:21:26 : reject : dsdhks@simplexi.com total count in 30s = 4 trans_rate = 1.33 addr : dsdhks@simplexi.com hashsize : 4
2010-04-14 18:21:26 : reject : kim@simplexi.com total count in 30s = 4 trans_rate = 1.33 addr : kim@simplexi.com hashsize : 4
2010-04-14 18:21:26 : reject : ht@simplexi.com total count in 30s = 4 trans_rate = 1.33 addr : ht@simplexi.com hashsize : 4
2010-04-14 18:21:26 : accept : emerson@simplexi.com total count in 30s = 4 trans_rate = 1.00 addr : emerson@simplexi.com hashsize : 4
2010-04-14 18:21:26 : reject : dsdhks@simplexi.com total count in 30s = 5 trans_rate = 1.67 addr : dsdhks@simplexi.com hashsize : 4
Milter API
- Milter API callback function
함수명 | 호출 시점 | ||
connect_callback | 클라이언트가 연결되었을 때 호출됨. | ||
helo_callback | HELO/EHLO 명령이 입력되었을 때 호출됨. | ||
envfrom_callback |
MAIL FROM: 명령이 입력되었을 때 호출됨. | ||
envrcpt_callback | RCPT TO: 명령이 입력되었을 때 호출됨. | ||
header_callback | 헤더가 입력될 때, 각 헤더에 대해 한번씩 호출됨. | ||
eoh_callback | End Of Header, 즉, 모든 헤더가 다 받아졌을 때 한번 호출됨. | ||
body_callback | 메일 본문이 입력되고 나서 호출됨. | ||
eom_callback | End Of Message, 즉 모든 메시지가 다 받아졌을 때 호출됨. | ||
abort_callback | 메일 트랜잭션이 중간에 취소 될때 호출됨. | ||
close_callback | 클라이언트 연결이 종료될 때 호출됨. |
- Milter API Return Value
콜백 함수의 반환값 | 의미 |
SMFIS_CONTINUE | 메일을 계속 정상적으로 처리하라. |
SMFIS_REJECT | 메일을 더 이상 진행하지 말고 명시적으로 거부하라. |
SMFIS_DISCARD | 현재 메일을 정상적으로 받아들이되, 아무말없이 그냥 버려라. 즉 송신측은 메일이 정상적으로 보내졌다고 생각하지만, 실제로 메일은 수신자에게 전달되지 않고 그냥 이 순간에 버려져버린다. 다른 밀터에게도 더 이상 메일이 전달되지 않는다. |
SMFIS_ACCEPT | 추후의 다른 필터링 조건을 무시하고 무조건 받아들여라. |
SMFIS_TEMPFAIL | 일시적 서비스 거부 메세지를 내보내라. 즉, 클라이언트에게 5xx 등의 정상적 메시지가 아닌 4xx 메시지를 내보낸다. 이것은 클라이언트에게 서버가 일시적으로 사용 불가능하므로, 추후 다시 접속할 것을 요구하는 것이다. |