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

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

  procmail과 perl로 메일수신로그를 DB로 (v2) 작성일 : 2004/09/10 15:53
 
  • 글쓴이 : 좋은진호 ( http://coffeenix.net/ )
  • 조회수 : 6961
          [ 이전화면 / 수정 ]   비밀번호 :     인쇄용 화면
      제  목 : procmail과 perl로 메일수신로그를 DB로. v2
    작성자 : 좋은진호(truefeel, http://coffeenix.net/ )
    작성일 : 2004.1.15(목)
    수정일 : 2004.1.18(일) DB 스키마 수정, mail_log.pl에서 작은따옴표(')처리
             http://coffeenix.net/board_view.php?bd_code=172
    업데이트 : 2004.9.10(금) 메일 필터링 여부 체크 필드 추가

    메일 쿼터(파일시스템 쿼터나 milterAPI를 이용하지 않고 순수 procmail+perl로만으로
    구현할려는 진보적인(?) 쿼터)를 위해 만드는 과정에서 수신 정보가 필요했고, 이
    수신정보를 DB로 남겨도 좋겠다는 생각을 하게되었다.
    즉, 단순히 곁다리로 나온 것이지만 쓸만하다 싶어(?) 정리하여 소개한다.

    1. 들어가기

    1) DB로 남기면 뭐가 좋은가?

      - 통계처리가 쉽게 가능하다.
        월 몇통의 메일을 받는 서버인지 COUNT(*)만으로 쉽게 확인할 수 있다.
      - 수신자별로 메일 수신 메일 통수 통계를 볼 수 있다. (수신자별 GROUP BY 로 가능)
      - 메일 제목을 통해 필터링할 스팸 메일 설정을 쉽게 도와준다.
      -  SUM(MAIL_SIZE)를 이용하면 월별 메일 수신용량(헤더 제외)을 확인할 수 있다.
      - 메일 필터링 여부를 DB에 저장하여 필터링 비율을 확인할 수 있다.

    2) 어떤 로그를 남기는가?

      - 메일 송신자 메일주소와 이름
      - 메일 수신자 ID
      - 메일 제목
      - 본문 길이 (단위 byte)
      - 송신한 일시 (정확히는 DB에 로그를 남긴 일시이나 시간상의 차이는 거의 없다.)
      - 필터링 여부 (값이 0이면 필터링되지 않은 메일이다.)

    3) 과정을 이해해보자.

      sendmail, qmail 등에서 메일을 수신하면 MDA인 procmail로 넘겨준다.
      -> /etc/procmailrc 에서 메일 제목 디코딩을 한다.  (procmail에서)
      -> 송신자, 수신자, 제목, 길이 등을 얻어내어 변수에 저장한다. (procmail에서)
      -> 얻어낸 값을 mail_log.pl 로 넘겨준다. (procmail에서)
      -> DB로 저장한다. (mail_log.pl에서)
      -> 필터링 여부를 체크한다. (mail_filterchk.pl에서)

    2. 요구 사항

    1) DB는 MySQL을 사용한다.
       오라클도 상관없다. 그게 바로 Perl DBI모듈의 장점이다.

    2) Perl과 Perl DBI, DBD 모듈이 필요하다.
       펄의 저장창고라 불리는 CPAN( http://www.cpan.org/modules/ )에서
       DBI, DBD 모듈을 구할 수 있다.
       참고로 레드햇 9에서는 rpm으로 제공된다.

       http://www.cpan.org/authors/id/T/TI/TIMB/DBI-1.43.tar.gz
       http://www.cpan.org/authors/id/J/JW/JWIED/DBD-mysql-2.1028.tar.gz

       먼저 DBI을 다음과 같은 과정으로 설치하고 똑깥이 DBD-mysql도 설치하면 된다.
       기존에 설치된 것을 사용했으므로, 위에 링크한 소스로 컴파일했을 때 문제가
       발생하는지에 대해서는 확인해줄 수 없다.
        
     
       # perl Makefile.PL
       # make
       # make test
        (꼭 할 필요는 없다. 정상 동작하는 것인지 확인하기 위한 용도.
         예전에 설치했을 때 몇 개 오류가 발생했어도 실제 사용에는 문제없었다.)
       # make install
     


    3) 메일 제목의 한글 디코딩을 위해서는 hcode 프로그램이 필요하다. (옵션)
       ftp://ftp.kaist.ac.kr/pub/hangul/code/hcode/
       ftp://ftp.kreonet.re.kr/pub/hangul/cair-archive/code/hcode/
       에서 구할 수 있으며, make 만으로 컴파일할 수 있다.

    3. procmail 설정

    [ /etc/procmailrc 설정 중 디코딩 부분만 ]
     
    # 메일 헤더 디코딩
    :0 fhw
    *^(Subject|From|Cc):.*=\?EUC-KR\?(B|Q)\?
      |formail -c | /usr/bin/hcode -dk -m

    :0 Efhw
    *^(Subject|From|Cc):.*=\?ks_c_5601-1987\?(B|Q)\?
      |formail -c | /usr/bin/hcode -dk -m

    :0 Efhw
    *^(Subject|From|Cc):.*=\?KSC5601\?(B|Q)\?
      |formail -c | /usr/bin/hcode -dk -m

    :0 Efhw
    *^(Subject|From|Cc):.*=\?ISO-8859-1\?(b|B|Q)\?
      |formail -c | /usr/bin/hcode -dk -m

    # 메일 수신로그를 DB로 저장
    INCLUDERC=/etc/procmail/mail_log.rc

    # 이부분에 필터링 내용을 나열한다.
    #
    # 예)
    #
    # SPAM_LOG=/var/log/SPAM.log
    # :0 :
    # * ^Subject:.*(무료.*(교재|샘플|증정|홍삼)|샘플.*무료.*(배송|배포|제공)|자선전안내|기적.*영문법|명품.*(최저
    # * *가|시계))
    # $SPAM_LOG

    # 필터링 여부를 체크한다. (필터링이 안된 메일만 mail_filterchk.rc가 실행된다.)
    INCLUDERC=/etc/procmail/mail_filterchk.rc
     


    : 는 처리할 조건의 시작을 의미하며 recipes라 불린다.
    위에서 헤더에서 각각의 조건을 찾아 맞지 않으면 다음 조건(E = else if로 이해하면 됨)을
    처리하는 형태로 되어 있다.
    이런 과정을 거쳐 Base64나 QP로 인코딩된 메일 헤더를 디코딩하게 된다.

    이제 include된 mail_log.rc과 mail_filterchk.rc 를 살펴보자.

    [ /etc/procmail/mail_log.rc ]
     
    # 송신자 메일주소
    :0
    * ^From: \/.*
    {
            FROM = "$MATCH"
    }
    # 수신자 메일주소
    :0
    * ^To: \/.*
    {
            TO = "$MATCH"
    }
    # 메일제목
    :0
    * ^Subject: \/.*
    {
            SUBJECT = "$MATCH"
    }

    # 메일 본문 byte수
    :0
    * 1^1 B ?? > 1
    { }

    LENGTH = $=

    RESULT=`/etc/procmail/mail_log.pl "$FROM" "TO" $LOGNAME "$SUBJECT" $LENGTH`
     

    * 다운로드 : http://coffeenix.net/truefeel/files/mail_log_v2/mail_log.rc

    각각의 조건에 의해 수신자, 송신자, 메일제목, 본문 길이를 얻어낸다.
    그 얻어진 값은 변수에 저장되어 mail_log.pl 프로그램에 인수로 넘겨주게 된다.

    어떻게 매칭이 되어 FROM, TO, SUBJECT, LENGTH 변수에 값이 들어가는지 궁금하면
    procmailrc 에 VERBOSE=yes 로 하면 쉽게 확인할 수 있을 것이다.

     
    LOGFILE=/var/log/procmail
    VERBOSE=yes
     


    [ /etc/procmail/mail_log.rc ]
     
    # 메일 필터링 여부 체크
    #
    # 메일 필터링이 되지 않은 경우는 DB에서 필터링 유무 체크용 필드를 0 으로
    # update합니다.
    # 이 파일은 /etc/procmailrc 의 제일 마지막에 INCLUDE해야 합니다.
    RESULT2=`/etc/procmail/mail_filterchk.pl "$RESULT"`
     

    * 다운로드 : http://coffeenix.net/truefeel/files/mail_log_v2/mail_filterchk.rc

    4. DB 스키마

    MAIL_LOG DB 스키마이다.
     
    /* 메일 수신 로그 */
    CREATE TABLE MAIL_LOG (
      MAIL_SEQ              int unsigned not null auto_increment,    /* 로그 SEQ.  */
      MAIL_FROM             varchar(255),                   /* 송신자(From) */
      MAIL_FROMNAME         varchar(255),                   /* 송신자 이름 */
      MAIL_FROMMAIL         varchar(255),                   /* 송신자 메일주소 */
      MAIL_TO               varchar(255),                   /* 수신자(To)  */
      MAIL_LOGNAME          varchar(255),                   /* 수신 ID   */
      MAIL_SUBJ             varchar(255),                   /* 제목      */
      MAIL_SIZE             int unsigned default 0,         /* 메일 크기 */
      MAIL_FILTERCHK        int unsigned default 0,         /* 필터링 유무 (0=필터링 안됨) */
      MAIL_DATE             datetime,                       /* 메일 날짜 */
      PRIMARY KEY (MAIL_SEQ),
      INDEX key_filterchk(MAIL_FILTERCHK)
    );
     

    * 다운로드 http://coffeenix.net/truefeel/files/mail_log_v2/mail_log.sql

    5. 로깅 및 필터링 여부 체크 프로그램

    다음은 로그를 DB에 저장하는 펄 소스이다.

    [ /etc/procmail/db_lib.pl ]
     
    #!/usr/bin/perl
    #
    # DB 함수
    #
    # Made By 좋은진호(truefeel, http://coffeenix.net/ )

    use DBI;

    # DB 연결
    sub db_connect {
       $szDBName  = "DB지정";
       $szDBUser  = "DB USER ID";
       $szDBPasswd= "DB 비밀번호";

       $dbh = DBI->connect ( "DBI:mysql:$szDBName", $szDBUser, $szDBPasswd)
    || die "$DBI::errstr";
    }

    # DB 접속을 끊음
    sub db_disconnect {
       $dbh->disconnect();
    }

    # SQL문 실행
    sub db_do_sql {
       my ( $szSQL ) = @_;
       my ( $sth );

       $sth = $dbh->prepare($szSQL);

       # 오류가 발생했는지 검사 --------
       if ( $@ ) {
            &db_disconnect;
            print " 오류 발생 : $@\n";
       } else {
            $sth->execute;
       }
       $sth->finish();
    }

    $temp="1";
     


    [ /etc/procmail/mail_log.pl ]
     
    #!/usr/bin/perl
    #
    # procmail을 통해 넘겨온 메일 수신 정보를 DB로.
    #
    # Made By 좋은진호(truefeel, http://coffeenix.net/ )
    #
    # 2004.1.13(화)
    # 2004.9.10(금) 필터링 여부 체크용 필드 추가
    #
    # - Perl DBI, DBD 모듈 필요
    # - DB : MySQL
    # - 넘겨오는 값 : 순서대로 From, To, 수신ID, 메일제목, 본문크기(byte)

    require '/etc/procmail/db_lib.pl';

    # $DEBUG = 1;
    # 정보를 넘겨 받음
    if ( $#ARGV < 4 ) {
       print "실행방법이 틀렸습니다. procmail을 통해서 실행하세요.\n";
       exit 1;
    }
    ($FROM, $TO, $LOGNAME, $SUBJECT, $SIZE ) = @ARGV;

    # DB저장을 위한 작은 따옴표 처리
    $FROM    =~ s/'/''/g;
    $TO      =~ s/'/''/g;
    $SUBJECT =~ s/'/''/g;

    # From: 에서 이름과 메일주소를 분리
    # 예 1) $FROM = '"truefeel"<true____@coffee___.___>';
    # 예 2) $FROM = 'true____@coffee___.___';
    # 예 3) $FROM = '<true____@coffee___.___>';
    if ( $FROM =~ /"{0,}([^"|.]*)"{0,}\s{0,}<(.*)>/g ) {
       $FROMNAME = $1;
       $FROMMAIL = $2;
    } else {
       $FROMMAIL = $FROM;
    }

    # 필터링 여부 체크를 위한 Uniq한 키(9자리) 만들기
    srand();
    $FILTERCHK = sprintf("%09d", int(rand(999999999)) );

    # -------------------------------------------------
    # DB 처리
    # -------------------------------------------------
    # DB 접속
    &db_connect;

    # 로그 저장
    $szSQLMailLog = qq {
       INSERT INTO MAIL_LOG
       VALUES ('', '$FROM', '$FROMNAME', '$FROMMAIL', '$TO', '$LOGNAME', '$SUBJECT', '$SIZE', '$FILTERCHK', now() ) };
    &db_do_sql($szSQLMailLog);
    &db_disconnect;

    # 디버깅
    if ( defined($DEBUG) ) {
       $szMailLog = sprintf("송신= %s\n수신= %s, %s\n제목= %s\n크기= %dBytes\n", $FROM, $TO, $LOGNAME, $SUBJECT, $SIZE);
       open(FILE, ">/tmp/maillog.debug");
          print FILE $szMailLog;
          print FILE "$szSQLMailLog \n";
       close(FILE);
    }

    print $FILTERCHK;      # 키값을 procmail 로 넘김
    exit;
     

    * Syntax Highlight된 소스 보기 :
      http://coffeenix.net/truefeel/files/mail_log_v2/mail_log.pl.html
      http://coffeenix.net/truefeel/files/mail_log_v2/db_lib.pl.html
    * 다운로드
      http://coffeenix.net/truefeel/files/mail_log_v2/mail_log.pl.txt
      http://coffeenix.net/truefeel/files/mail_log_v2/db_lib.pl.txt

    간단히 살펴보자.

    넘겨온 인수중에서 송신자 정보는 이름과 메일주소로 나눈다. 물론 이름이 없어도 문제없이
    처리한다. 그리고 DB에 저장하고 종료한다.
    $DEBUG = 1 으로 지정하면 디버깅에 유용하다. 넘겨받은 인수를 /tmp/maillog.debug에 저장 한다.

    db_connect() 함수에서 $szDBName, $szDBUser, $szDBPasswd을 설정해주어야 한다.
    만약 Oracle DB이라면 'DBI:mysql' 대신 'DBI:Oracle'을 써주면 된다.

    주의할 것은 DB 비밀번호도 있으니 파일 퍼미션을 700(rwx------)으로 해야한다.

     
    # chmod 700 /etc/procmail/db_lib.pl
     


    [ /etc/procmail/mail_log.pl ]
     
    #!/usr/bin/perl
    #
    # procmail을 통해 넘어온 키로 필터링 여부를 DB에 표시
    #
    # Made By 좋은진호(truefeel, http://coffeenix.net/ )
    #
    # 2004.9.10(금)
    #
    # - Perl DBI, DBD 모듈 필요
    # - DB : MySQL
    # - 필터링 안된 것은 MAIL_FILTERCHK 필드를 0 으로 함

    require '/etc/procmail/db_lib.pl';

    # $DEBUG = 1;
    # 정보를 넘겨 받음
    if ( $#ARGV < 0 ) {
       print "실행방법이 틀렸습니다. procmail을 통해서 실행하세요.\n";
       exit 1;
    }
    ($FILTERCHK ) = @ARGV;

    # -------------------------------------------------
    # DB 처리
    # -------------------------------------------------
    # DB 접속
    &db_connect;

    # 로그 저장
    $szSQLMailLog = qq {
       UPDATE MAIL_LOG SET MAIL_FILTERCHK = 0 WHERE MAIL_FILTERCHK = '$FILTERCHK' };
    &db_do_sql($szSQLMailLog);
    &db_disconnect;

    # 디버깅
    if ( defined($DEBUG) ) {
       $szMailLog = sprintf("송신= %s\n수신= %s, %s\n제목= %s\n크기= %dBytes\n", $FROM, $TO, $LOGNAME, $SUBJECT, $SIZE);
       open(FILE, ">/tmp/maillog.debug");
          print FILE $szMailLog;
          print FILE "$szSQLMailLog \n";
       close(FILE);
    }

    exit;
     

    * Syntax Highlight된 소스 보기 :
      http://coffeenix.net/truefeel/files/mail_log_v2/mail_filterchk.pl.html
    * 다운로드
      http://coffeenix.net/truefeel/files/mail_log_v2/mail_filterchk.pl.txt

    수신 메일에 대한 유일한 키값($FILTERCHK)을 넘겨받아서 필터링 되지 않은 메일임을
    표시한다. (UPDATE문, MAIL_FILTERCHK = 0)

    로그가 제대로 남았는지 확인해보자.


    로그를 DB로 남겼을 때 어떻게 활용할 것인지 생각했는가?
    그럼 지금 당장 시작해라!

    6. 참고 자료

    * Procmail Tips
      http://pm-doc.sourceforge.net/pm-tips.html
    * procmail에 관하여 (글 이상로)
      http://trade.chonbuk.ac.kr/~leesl/procmail/index.html
    * Short guide to DBI (The Perl Database Interface Module)
      http://www.perl.com/pub/a/1999/10/DBI.html
      커피닉스 카페 최근 글
    [07/14] SSL АО
    [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 개발 팀장을 모십니다.
      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일~