커피닉스, 시스템 엔지니어의 쉼터 커피향이 나는 *NIX
커피닉스
시스템/네트웍/보안을 다루는 곳
* HanIRC의 #coffeenix 방
[ 장비 및 회선 후원 ]
HOME > 네트워크(network) > 메일 서버(mail) 도움말
검색 : 사이트 WHOIS 웹서버 종류

메일 필터링(스팸메일) / procmail (14, 글 18, 자료 8)

  sendmail에서 milter 사용해서 약간의 스팸과 바이러스 막기.. 작성일 : 2004/02/07 16:48
 
  • 글쓴이 : TinyWolf
  • 조회수 : 6648
          [ 이전화면 / 수정 ]   비밀번호 :     인쇄용 화면
      얼마전 마이둠으로 인해서 제가 관리하는 조그만 메일서버가
    엉망이된 적이 있어서 여기저기 알아보다가 알게된 방법입니다.

    qmail을 사용하라는 얘기를 많이 들었는데요.
    처음부터 새로 설치한다는데에 겁이 나서 sendmail을
    사용해서 하는 법을 여기저기 찾아보다가 알게된 방법이죠.

    RedHat9를 기준으로 설명합니다.


    1. milter 사용을 위해 sendmail-devel패키지와 sendmail소스를 설치
      (perl의 milter 모듈을 설치할 때 소스를 필요로 합니다)

    [sendmail-devel rpm]
      RedHat9 cd3 RPMS
      sendmail-devel-8.12.8-4.i386.rpm

    [sendmail srpm]
      http://rpmfind.net/에서 sendmail로 검색 후 RedHat9용 같은 버전을 찾는다.
      sendmail-8.12.8-4.src.rpm

    [설치]
      # rpm -Uvh sendmail-devel-8.12.8-4.i386.rpm
      # rpm -Uvh sendmail-8.12.8-4.src.rpm

    [sendmail 소스 컴파일]
      # cd /usr/src/redhat/SPECS/
      # rpmbuild -bc sendmail.spec


    2. perl용 milter 모듈을 설치
      (base64 인코딩된 메일도 검사하기 위하여 base64 모듈도 설치합니다)

    [모듈 다운로드]
      http://www.cpan.org/authors/id/G/GA/GAAS/MIME-Base64-3.00.tar.gz
      http://www.cpan.org/authors/id/C/CY/CYING/Sendmail-Milter-0.18.tar.gz

    [Base64 모듈 설치]
      # tar xzvf MIME-Base64-3.00.tar.gz
      # cd MIME-Base64-3.00
      # perl Makefile.PL
      # make
      # make install

    [Milter 모듈 설치] 반드시 sendmail소스 컴파일이 먼저 되어 있어야 합니다.
      # tar xzvf Sendmail-Milter-0.18.tar.gz
      # cd Sendmail-Milter-0.18
      # perl Makefile.PL /usr/src/redhat/BUILD/sendmail-8.12.8/ /usr/src/redhat/BUILD/sendmail-8.12.8/obj.Linux.2.4.20-8smp.i686/
      # make
      # make install


    3. 이제 자신만의 밀터를 perl로 작성하겠습니다.

    [perl 스크립트 생성]
      # cd /etc/mail/
      # vi MyMilter.pl
    --------------------------8<------------------------------code : MyMilter.pl----
    #!/usr/bin/perl
    
    use Sendmail::Milter;
    use Socket;
    use MIME::Base64;
    
    sub connect_callback {
        my ($ctx, $hostname, $sockaddr_in) = @_;
    
        print "[connect] hostname: '$hostname'\n";
    
        return SMFIS_CONTINUE;
    }
    
    sub helo_callback {
        my ($ctx, $helohost) = @_;
    
        print "    [helo] helohost: '$helohost'\n";
    
        return SMFIS_CONTINUE;
    }
    
    sub envfrom_callback {
        my ($ctx, $from, @args) = @_;
    
        print "    [envfrom] args: '$from','" . join("','", @args) . "'\n";
    
        # 익명 송신자 제거
        if ($from !~ /.*\@.*/) {
            print("    ! from nobody: $from\n");
            return SMFIS_REJECT;
        }
    
        return SMFIS_CONTINUE;
    }
    
    sub envrcpt_callback {
        my ($ctx, $rcpt, @args) = @_;
    
        print "    [envrcpt] args: '$rcpt','" . join("','", @args) . "'\n";
    
        # 익명 수신자 제거
        if ($rcpt !~ /.*\@.*/) {
            print("    ! rcpt nobody: $rcpt\n");
            return SMFIS_REJECT;
        }
    
        return SMFIS_CONTINUE;
    }
    
    sub check_subject {
        my ($subject) = @_;
    
        # 광고 및 홍보 제거
        if ($subject =~ /[[(< ({].*(광.*고|홍.*보).*[])>) }]/) {
            print("    ! ad: $subject\n");
            return "bad";
        }
        # 포르노 제거
        if ($subject =~ /.*(포르노|porno).*/) {
            print("    ! porno: $subject\n");
            return "bad";
        }
        return "good";
    }
    
    sub header_callback {
        my ($ctx, $headerf, $headerv) = @_;
    
        # Subject: 헤더인 경우.
        if ($headerf eq "Subject") {
            $chk = &check_subject($headerv);
            if ($chk eq "bad") {
                return SMFIS_REJECT;
            }
    
            # base64 디코딩을 수행
            @decoded = split(/[\?+\n+ +\t+]/, $headerv);
            $headerv = "";
            foreach (@decoded) {
                $headerv .= decode_base64($_);
            }
    
            $chk = &check_subject($headerv);
            if ($chk eq "bad") {
                return SMFIS_REJECT;
            }
        }
    
        return SMFIS_CONTINUE;
    }
    
    sub eoh_callback {
        my ($ctx) = @_;
    
        return SMFIS_CONTINUE;
    }
    
    sub check_body {
        my ($body) = @_;
    
        # 본문 광고 체크 : 정보통신부
        if ($body =~ /정보통신부 *권고 *사항에 *의거/) {
            print("    ! ad: 정보통신부 권고 사항에 의거한 내용\n");
            return "bad";
        }
        return "good";
    }
    
    sub body_callback {
        my ($ctx, $body_chunk, $len) = @_;
    
        print("    [body] length: $len\n");
    
        # 적당하지 않은 첨부파일이 있을시 거부 *.src, *.pif, *.bat, *.com
        @decoded = split(/[\r+\n+]/, $body_chunk);
        foreach (@decoded) {
            if ($_ =~ /name=\"?.*\.(scr|pif|bat|com)\"?/) {
                print("    ! virus?: $_\n");
                return SMFIS_REJECT;
            }
        }
    
        $chk = &check_body($body_chunk);
        if ($chk eq "bad") {
            return SMFIS_REJECT;
        }
    
        # base64 디코딩을 수행
        @decoded = split(/[\?+\r+\n+ +\t+]/, $body_chunk);
        $body_chunk = "";
        foreach (@decoded) {
            $body_chunk .= decode_base64($_);
        }
    
        $chk = &check_body($body_chunk);
        if ($chk eq "bad") {
            return SMFIS_REJECT;
        }
    
        return SMFIS_CONTINUE;
    }
    
    sub eom_callback {
        my ($ctx) = @_;
    
        $ctx->addheader("X-MyMilter", "Spam,Virus Chk 1.01beta [Perl version]");
    
        return SMFIS_CONTINUE;
    }
    
    sub abort_callback {
        my ($ctx) = @_;
    
        return SMFIS_CONTINUE;
    }
    
    sub close_callback {
        my ($ctx) = @_;
    
        return SMFIS_CONTINUE;
    }
    
    ############################################
    # 이하의 코드는 밀터 기본 코드로 수정 불가 #
    ############################################
    
    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,
    );
    
    BEGIN:
    {
        if (scalar(@ARGV) < 2) {
            print "Usage: perl $0  \n";
            exit;
        }
    
        my $conn = Sendmail::Milter::auto_getconn($ARGV[0], $ARGV[1]);
    
        print "Found connection info for '$ARGV[0]': $conn\n";
    
        if ($conn =~ /^local:(.+)$/) {
            my $unix_socket = $1;
    
            if (-e $unix_socket) {
                print "Attempting to UNIX socket '$conn' ...";
    
                if (unlink($unix_socket) == 0) {
                    print "failed.\n";
                    exit;
                }
                print "successful.\n";
            }
        }
    
        if (!Sendmail::Milter::auto_setconn($ARGV[0], $ARGV[1])) {
            print "Failed to detect connection information.\n";
            exit;
        }
    
        if (!Sendmail::Milter::register($ARGV[0], \%my_callbacks, SMFI_CURR_ACTS)) {
            print "Failed to register callbacks for $ARGV[0].\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";
        }
    }
    --------------------------8<------------------------------code : MyMilter.pl----


    위의 코드는 어느분이 milter에 대해 설명해주신 곳에서
    그대로 가져다가 몇가지만 수정한 것입니다. (정확히 어딘진 기억이 가물..)


    4. sendmail에서 방금 만든 밀터를 사용하도록 설정합니다.

    [sendmail.mc 수정]
      # vi /etc/mail/sendmail.mc
        -다음을 마지막에 추가-----------
        INPUT_MAIL_FILTER(`MyMilter', `S=local:/var/run/mymilter.sock')dnl
        --------------------------------
      # m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf

    [sendmail 재시작]
      # /etc/init.d/sendmail restart


    5. 테스트를 위해 밀터를 실행시켜 봅니다.

    [perl로 밀터 실행]
      # perl /etc/mail/MyMilter.pl MyMilter /etc/mail/sendmail.cf

    [백그라운드 실행시 프로세스 확인] : 특별히 실행하는 perl이 없다면 하나만 보일껍니다.
      # ps -Al | grep perl

    샐행된 상태에서 이런 저런 메일을 보내 봅니다.
    그러면 메일 하나마다 화면에 찍힐껍니다.

    Outlook Express라면 받은 메일에 '속성'->'자세히'해보면
    제일 끝에 'X-MyMilter'라는 내용이 붙어 있는 것이 보일것입니다.

    그러면 정상적으로 실행된 것입니다.


    6. 재부팅하면 바로 백그라운드로 실행되게 합시다.

    [rc.local 수정]
      # vi /etc/rc.local
        -다음을 마지막에 추가-----------
        perl /etc/mail/MyMilter.pl MyMilter /etc/mail/sendmail.cf &
        --------------------------------


    다음부터는 밀터가 동작하면서 내용에 대한 것을 tty1에 찍게됩니다.
    화면에 찍히는게 싫으면 print문을 전부 #으로 주석처리 하시면 되구요.
    위의 소스만 살펴보시면 아주 다양하게 활용할 수 있을 것같습니다.

    여러개의 밀터도 사용이 가능하구요..
    sendmail.mc에 등록시킨 순서대로 작동하게 됩니다.
    밀터가 여러개일땐 'mymilter.sock' <- 요부분의 이름이 다 달라야 합니다.

    메일이 일단 도착하면 제일 먼저 milter에게 수신 여부를 묻는다고 하니 저장되기도 전에 제일 먼저 보고 차단하는 것이라고 볼 수 있습니다.

    제가 관리하는 서버는 이용자가 20명 좀 안되기 때문에 충분한 효과를 보고 있습니다.
    대규모에서까지 효율적일지는 모르겠군요.
      커피닉스 카페 최근 글
    [04/26] Re: 도스화면 원격조종 여부
    [04/25] 도스화면 원격조종 여부
    [10/30] Cshell에서 난수 설정
    [10/23] 공항철도주식회사 SE 구인 件
    [01/26] Re: wget으로 다른서버에있는 디렉토리를 가져오려고합니다.
    [01/25] wget으로 다른서버에있는 디렉토리를 가져오려고합니다.
    [01/11] 특정 안드로이드 WebView 버전에서 SSL 문제 (WebView 버그)
    [08/01] DNS forwarder (전달자) 서버를 통해서 쿼리하면 역방향을 받아오질 못합니다.
    [05/16] (주)후이즈 시스템엔지니어 (경력자) 모집
    [02/15] [AWS] Cloudfront edge 확인하기
    [01/20] Mobile Service/eCommerce 기업에서 Server / Java / PHP 개발자 구인
    [01/11] 탄탄한 퍼블리싱 모바일기업에서 Mobile 개발자를 모십니다.
    [01/11] 탄탄한 퍼블리싱 모바일기업에서 Web Front 개발자를 모십니다.
    [01/11] 탄탄한 퍼블리싱 모바일기업에서 Server 개발 팀장을 모십니다.
    [12/21] 브라우저별 SHA1 퇴출 시점
      New!   최근에 등록한 페이지
      KiCad EDA Suite project (Free/Libre/Open-Source EDA Suite) (CAD)
      오픈캐스케이드 캐드 (OpenCASCADE CAD)
      QCad for Windows --- GNU GPL (Free Software)
      The Hello World Collection
      IPMI를 활용한 리눅스 서버관리
      DNS 설정 검사
      nagiosgraph 설치 방법
      Slony-I 설치 방법 (postgresql replication tool)
      Qmail기반의 Anti spam 시스템 구축하기
      clusterssh

    [ 함께하는 사이트 ]




    운영진 : 좋은진호(truefeel), 야수(yasu), 범냉이, sCag
    2003년 8월 4일~