作者 主題: 百年問題之一 -- procmail 檔信通知作法  (閱讀 11728 次)

0 會員 與 1 訪客 正在閱讀本文。

abelyang

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 1097
    • 檢視個人資料
原來文章我貼在一大篇的後面,我想可能很多人沒有注意到
所以把他拉出來獨立寫成一篇,不過有些雜亂,
不過這本來就是工程師的本性不是嗎  :D

代碼: [選擇]
# file /etc/procmailrc
# 前面這幾個變數很重要尤其是 USER/HOME 等這些系統預設
# INCLUDERC 也是一個重要的用法,只是這裏我沒有列出來
# 我記得 man procmail 裏面有寫
# log 在初學時是一定要開的,並且也可以用 mailstat 來看到統計

# 以下的這些變數 man procmailrc 會說明的更多更詳細
PATH=$PATH
SHELL=/bin/sh
MAILDIR=/var/spool/mail

#表示要開啟 procmail 的記錄
VERBOSE=yes
SENDMAIL=/usr/sbin/sendmail

#Log 到什麼檔案
LOGFILE=/home1/MAIL/procmail.log

#收件人的 home 目錄
#如果這封信是寄給 abelyang, 那他的 $HOME 就是 /home/abelyang
USER=$HOME

#收件人, ${USER##*/}  結果就 abelyang, man bash
USER=`echo ${USER##*/}@xxx.net.tw`

#主旨 , 但主指可能編碼過 (qp or mine), 請看最後的一隻 perl 程式,幫你
#解碼,同樣的事也可使用 mmencode 做到
#formail 的用法請 man 就有夠看了, formail -zxMAIL_HEAD_FIELD 可以取出
#MAIL HEADER 的欄位意義
SUBJ=`formail -zxSubject: `

# ppp <ppp@ppp.com.tw> paser to ppp@ppp.com.tw
# 如果你要什麼 Return-Path 或其他的參考 formail
# 至於 ${FROM##*<} 這種變數的 paser 你 man bash
#或參考一些 Shell Script 講變數控制的章節
FROM=`formail -zxFrom:`
FROM=`echo ${FROM##*<}`
FROM=`echo ${FROM%%>}`

# sed 用法請自己去查,這段主要在取出多個收件者
# 如果這封信給 a b c 三個人,在 to 的地方是寫成 a,b,c
# tr 在於將 ',' 換成斷行 , sed 在於取出 abelyang <abelyang@xxx.net.tw>
# 中的 <abelyang@xxx.net.tw> 的值,要先斷行的原因則是在於 sed 的處理需要
TO=`formail -zxTo:|tr ',' '\n'| sed 's/\(.*\) \(.*\)/\2/'`

# 定義另存新檔的檔名, .eml 是 outlook 用的附檔名
FILE=/home1/MAIL/mail-`echo $FROM-$SUBJ| sed -e s/' '/''/g`-`date +%Y%m%d-%H:%I:%S`.eml

# 只要收到 mail 就直接先另存新檔, chown 是為了 Apache 處理用
tmp=`formail > $FILE;chown apache.apache $FILE`

#這段較容易誤判...抓 Attach file 的檔案名稱
attach=` formail -i "" | grep -i 'Name=' | cut -f 2 -d'=' | tail -1`

# 以上的處理,重點在於對 procmail 的環境變數的了解, 及 formail 的應用是不是看懂了
# 所以我們有了 寄件人(FROM)/收件人(TO)/實際收件人才(USER)/主旨(SUBJ)/附件(attach)
# 這封信的備份(FILE)
#-------------------------------------------------------

#很多了不了解 H B 或其他如 w h b f ..等棋標的意義, 可以 man procmailrc , 每一項都有
#解釋, 僅就我用到的做一些解釋好了
# H Header 信件的表頭段
# B Body 信件的本文段
#   這裏用 HB 是因為 attachment 的資訊有時會在 header, 有時會在 body 內
#   大寫 HB 和小寫 hb 其實是差不多的,只不過大寫用 egrep 處理而以
# f Filter 是一個很重要的用法,做二次處理用,這裏的義意在於符合這個過慮條件的
#   要有兩個處理動作(看 exitcode),一個放資料庫, 一個放 crash
# w Wait 的意思,要等 那一段( script) 處理完後才可以做存到 crash 的動作
# h Header    ,參考 H

# 如果你要檔一封就告訴 User 說你被檔了....那你的信對 User 來說只是增加
# 困擾而以,所以這種作法個人覺得是不夠實際的,應該是一個 Summary 的 report


:0 HB
* ^.*Content-Type:.*
# 夾帶附件名稱 ,這裏沒有換行
* ^.*[nN]ame=.*\.(wav|pif|bat|vbs|js|exe|com|hta|dll|scr|js|asd|chm| lnk|nws|ocx|reg|shb|shs|vb|vbe|ws[cfh]|EXE|PIF|BAT|VBS|JS|COM|HTA|SCR|JS|ASD|CHM|LNK|NWS|OCX|RE|G|SHB|SHS|VB|VBE|WS[CFH])
# 寄件人不為 apache, 因為我的 Web 是用 apache 的身份在跑的,
# 沒有這一條取回原信時又被檔了
* ! ^From: apache.*
{
# fwh 下的 command 沒有斷行
# 句尾沒有 \ 的就是沒有換行哦,每一行要用 \ 接起來讓它成為一個 command
# 一定要加 \ , 不然就寫成一行, 切記 !!
# email = TO 的值 <abelyang@xxx.net.tw> 要去掉 '<' '>' 符號
# delete 那一行的目的在於這封信寄給一個 group 時 (aliases) , 這個 group
# 如果有 10 個人,沒有經過這個時間值 500 (五分鐘,用 DB 的 data type 有關)
# 的判斷,你會存了 100 (10^2) 筆(每人十筆)記錄在資料庫
# 原因和 procmail 的運作有關,你可自身去體會囉
:0 fwh
|( for email in $TO;\
   do \
    email=`echo ${email##*<}`;\
    email=`echo ${email%%>}`;\
    echo "delete from SPAM where MAIL_FROM='$FROM' and SUBJ='$SUBJ' and USERNAME ='$USER' and CREATE_TIME+500>now()"|mysql procmail -u username;\
    echo "INSERT IN TO SPAM(MAIL_FROM, RCPT_TO,SUBJ, FILE_NAME,USERNAME, MAIL_TYPE ) VALUES('$FROM','$email','$SUBJ','$FILE','$USER',2)"| mysql -u username procmail ;\
   done );
# 無條件存入 crash ...雖然我的 rule 很多,但沒有一條是存入 /dev/null 的
:0
/var/spool/mail/crash
}





DB 的內容:
代碼: [選擇]
CREATE TABLE SPAM (
SN int(20) NOT NULL auto_increment,
MAIL_FROM varchar(255) default NULL, --寄件人
RCPT_TO varchar(255) default NULL, --收件人, 可能是 aliases
SUBJ varchar(255) default NULL, --主旨
FILE_NAME varchar(255) NOT NULL default '',--這封信另存新檔的名稱
CREATE_TIME timestamp(14) NOT NULL, --收件時間,為 yyyymmddhhiiss
MAIL_TYPE tinyint(2) default '1', --是廣告信還是附件為危險檔案
USERNAME varchar(64) NOT NULL default '',--實際收件人
PRIMARY KEY (SN)
) TYPE=MyISAM;




如果你怕在 /home1/MAIL 的檔案太多可以用 crontab 去刪掉過期的(例:三天)如:
代碼: [選擇]
00 1,13 * * 3,4,5,6 find /home1/MAIL -atime +3 -exec rm -f {} \;

好了,信可以另存新檔,也可統一進垃圾桶,並有一份清單存到資料庫 ~~
這時候你就可以寫一個寄信程式 (shell,php,perl...),去查資料庫,
並將每天的檔件 Summary Report 寄給 USER 看,並在 Mail 中讓他以CGI 的
方式取回信件,我個人是用 php 寫的,在 mail 中以 html 程現 report
並以 GET 的方式連結
代碼: [選擇]
http://webmail.xxx.net.tw/action.php?to=".urlencode($RCPT_TO)."&subject=".urlencode($SUBJ)
就可以取回信了
這裏有一點你要特別注意的就是不要直接 用像 cat $FILE| sendmail -oi -t $USER 這種方式
因為有些病毒可能會直接在 MUA 上發作,而是將這封信 Attachment , 並附上一段警語
可能較恰當


為了這些東西我記得當時我看了這些 man page (還有一大堆不相甘的就不列了)
procmail
procmailrc
procmailex
formail

常看到網路上有人在問這個問題~~可是我從來沒有看過人解過,
我是幾乎都看 man page 學的

有人可能想知道中文怎麼檔吧 ~~ 等我有時間再來分享囉
(提示: 善用 :0 fwh 那段就可以了)


解 mail encoding 程式,如果程式名叫做 mail_parser 好了
./mail_parser 編碼過的字串
mail 有兩種編碼方式,一種是 QP , 一種是 Mine64,
因為當初做這個時並不知道有 mmencode 這種指令
 還讓我看了半天的標準 XD

代碼: [選擇]
#!/usr/bin/perl
    $sub=$ARGV[0];
    if ($sub=~ /=\?\S+\?\l(\S)\?/) {
      if ($1 =~ /[Qq]/) {
        $sub=decode_qp($sub);
      }
      elsif ($1 =~ /[Bb]/) {
        $sub=decode_base64($sub);
      }
    }
    elsif ($sub=~ /=[a-fA-F0-9][a-fA-F0-9]/) {
      $sub=decode_qp($sub);
    }
    $subject.=$sub;
print $subject;    
sub decode_qp {
        my($string) = @_;
        @buffer=split(/\?/,$string);
        $string = $buffer[3] if ($buffer[3] ne "");
        $string =~ s/=([\da-fA-F]{2})/pack("C", hex($1))/ge;
        $string =~ /\?=(.*)/;
        $string =~ tr/_/ /;
        $buffer[4]=~s/^=//;
        $buffer[0]=~s/=$//;
        $string="$buffer[0]$string$buffer[4]";
        return($string);
}

sub decode_base64 {
        my($string) = @_;
        my($string2);
        @buffer=split(/\?/,$string);
        $string = $buffer[3] if ($buffer[3] ne "");
        $string =~ s/=+$//;
        $string =~ tr|A-Za-z0-9+/| -_|;
        while($string =~ /(.{1,60})/gs) {
                my($string3) = chr(32+length($1)*3/4);
                $string2 .= unpack("u",$string3 . $1 );
        }
        $buffer[4]=~s/^=//;
        $buffer[0]=~s/=$//;
        $string2="$buffer[0]$string2$buffer[4]";
        return($string2);
}

abelyang

  • 酷!學園 學長們
  • 俺是博士!
  • *****
  • 文章數: 1097
    • 檢視個人資料
百年問題之一 -- procmail 檔信通知作法
« 回覆 #1 於: 2003-07-01 13:29 »
解中文的問題請參考這裏
http://phorum.study-area.org/viewtopic.php?t=17308
最近在趕東西 ~~去他的 ISMS

wilson

  • 俺是博士!
  • *****
  • 文章數: 1821
  • 帥氣柴老大
    • 檢視個人資料
百年問題之一 -- procmail 檔信通知作法
« 回覆 #2 於: 2005-10-10 13:18 »
感謝abelyang兄的大作~

弟實作時發現一個問題,屬SQL Injection吧?
簡單說就是當Subject為”wilson's PC is good”時~因為帶了一個單引號,
所以procmail在執行SQL時會出錯~且該信件會進到mailq裡頭,
依mailq的程序~當然是一段時間後會再跑一次該信件囉~直到drop掉~
這樣會浪費資源且原收件者不一定會收到該信件~

建議把
代碼: [選擇]

SUBJ=`${FORMAIL} -zxSubject: `

改成
代碼: [選擇]

SUBJ=`${FORMAIL} -zxSubject: | sed -e "s/['|\"|\$]//g"`


(上述是對單引號而言~如有其它字符再自行調整)
(原先寫錯了~感謝abel兄指導)