Programming/Other

procmail과 perl로 메일수신로그를 DB에 남기기

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

1. 들어가기

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

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

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

 - 메일 송신자 메일주소와 이름
 - 메일 수신자 ID
 - 메일 제목
 - 본문 길이 (단위 byte)
 - 송신한 일시 (정확히는 DB에 로그를 남긴 일시이나 시간상의 차이는 거의 없다.)

3) 과정을 이해해보자.

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

2. 요구 사항

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

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

  http://www.perl.com/CPAN-local/authors/id/T/TI/TIMB/DBI-1.40.tar.gz
  http://www.perl.com/CPAN-local/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/ 에서 구할 수 있으며,
  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
 


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

이제 include된 mail_log.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.rc

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

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

 
LOGFILE=/var/log/procmail
VERBOSE=yes
 


4. DB 스키마와 로깅 프로그램

MAIL_LOG DB 스키마이다.
 
/* 메일 수신 로그 */
CREATE TABLE MAIL_LOG (
 MAIL_SEQ       int not null auto_increment,  /* 로그 SEQ. */
 MAIL_FROM       varchar(255),          /* 송신자 */
 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 default 0,         /* 메일 크기 */
 MAIL_DATE       datetime,            /* 메일 날짜 */
 PRIMARY KEY (MAIL_SEQ)
);
 

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

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

[ /etc/procmail/mail_log.pl ]
 
#!/usr/bin/perl
#
# procmail을 통해 넘겨온 메일 수신 정보를 DB로.
#
# Made By Jinho Hwangbo (좋은진호)
#
# 2004.1.13(화)
#
# - Perl DBI, DBD 모듈 필요
# - DB : MySQL
# - 넘겨오는 값 : 순서대로 From, To, 수신ID, 메일제목, 본문크기(byte)

use DBI;

# $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;
}

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

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

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

# -------------------------------------------------
# DB 연결
sub db_connect {
   $database = "DB지정";
   $db_user = "DB USER ID";
   $db_passwd= "DB 비밀번호";

   $dbh = DBI->connect ( "DBI:mysql:$database", $db_user, $db_passwd) || die "$DBI::errstr";
}

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

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

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

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

 

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

간단히 살펴보자.

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

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

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

 
# chmod 700 /etc/procmail/mail_log.pl
 


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


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

5. 참고 자료

* 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