#!/bin/bash ## ## 450 Retry access check and registering script. ## Original is from http://www.gabacho-net.jp/anti-spam/log-sorting-script.html ## Reformed by kiyoshi@kuragane.jp 2011.05.10 V1.0.0 - 2013.12.08 V4.0.1 ## ... http://kuragane.jp/anti-spam.htm ## ## ## Postfix 450再試行アクセスを検出し、アクセスを自動的に登録、通知します。 ## これによりスパムの多い逆引き設定されていないクライアントからの配送を一時遅延し、 ## 再試行してくる正常なメールを受信することによりスパム防止に寄与することができます。 ## ## このスクリプトを /etc/cron.hourly に置きます。 ## postfixのmain.cfでsmtpd_recipient_restrictionsを設定。 ## 他のアクセス条件を適用した後、最後のほうにでも、 ## check_client_access hash:/etc/postfix/access、 ## check_client_access hash:/etc/postfix/access_retryと、そして ## 450 rejectを返すアクセス制限。ここでは、 ## reject_unknown_client_hostname ## あるいは reject_unknown_reverse_client_hostname ...(安全を期するならこちら。) ## をセットで置きます。 ## (例:) ## smtpd_recipient_restrictions = ## permit_mynetworks, ## check_client_access btree:/etc/mail/dracd, ...(pop-before-smtp認証) ## permit_sasl_authenticated, ...(SASL認証) ## reject_non_fqdn_recipient, ## reject_unknown_recipient_domain, ## reject_unauth_destination, ## check_recipient_access hash:/etc/postfix/access, ...(以前からのアクセス制限) ## check_client_access hash:/etc/postfix/access, ...(追加) ## check_client_access hash:/etc/postfix/access_retry, ...(追加) ## reject_unknown_client_hostname, ...(追加) ## permit ## (註:順番はけっこう重要。決してreject_unauth_destinationの前に置いたりしてはいけません。) ## その他450rejectを返す制限を使っているところがあれば、そのrestrictionにも置くと救済ができます。 ## CentOS5.6+Postfix2.3.3で動作していますが、多少の書き換えで他のLinuxでもいけると思います。 ## ## デスクトップ使用を前提に、外部プログラムとしてzenity(ダイアログ), gedit(エディタ)を使っています。 ## 無い場合にはインストールするか、あるいは好みのを使うように書き換えてください。 ## その他は標準的なツールなので問題はないと思います。 ## ## ## 最初に以下の設定を確認、あるいは編集してください。 ## # # 環境の指定。 # # 日本語環境変数の設定。CentOS6 GNOME用。システム環境によって異なります。 # (cronで実行する場合これらの値がセットされないため。ダイアログを使わないなら不要です。) # # export 等で確認してデスクトップと言語関係の環境変数をお使いのシステムに合わせます。 # CentOS6-GNOME export DISPLAY=":0.0" export LANG="ja_JP.UTF-8" export QT_IM_MODULE="ibus" export XMODIFIERS="@im=ibus" # CentOS5-KDE #export DISPLAY=":0.0" #export LANG="ja_JP.UTF-8" #export GTK_IM_MODULE="scim-bridge" #export QT_IM_MODULE="scim" #export XMODIFIERS="@im=SCIM" # チェックの対象とするメールログ。順序は関係ありません。 # 再試行期間(postfixでディフォルト5日)を含むログを指定。ログローテート期間が1週間なら2つ必要。 # 最近は日付でrotateされるので、数字になるよう /etc/logrotate.d/syslog の maillog について # nodateext を指定し、maillog.1, maillog.2, ... となるようにします。 CHECK_LOGS="/var/log/maillog /var/log/maillog.1" # さらに rotate 7(5以上) を指定し、maillogからmaillog.5までの6個を指定するのがベストではあります。 # (応答が早く無駄な登録が減り管理が楽です)。 #CHECK_LOGS="/var/log/maillog /var/log/maillog.1 /var/log/maillog.2 /var/log/maillog.3 /var/log/maillog.4 /var/log/maillog.5" # めんどうだったら #CHECK_LOGS="/var/log/maillog /var/log/maillog.*" # アクセス定義リストファイルと作業用ファイルの指定。 # いったん結果を作業用ファイル ACCESS_LIST_NEW に書き出し、変更があればACCESS_LISTを書き換えて適用します。 ACCESS_LIST="/etc/postfix/access_retry" ACCESS_LIST_NEW=$ACCESS_LIST"_NEW" # ログを記録したければログファイルを指定すればアクセス設定の変更を記録します。 # ログのローテーションなどはしないので、記録するなら自分でセットしてね。 #LOGFILE="" LOGFILE="/var/log/check450retry.log" # # 基本動作の設定。 # # 再試行検出の感度を調整。再試行の回数(COUNT:回)と継続時間(PERIOD:秒)で。 # まずSTAGE1でDUNNNOの設定で再試行があったことが通知されますが、まだ許可はされません。 # アクセスかDUNNOのまま再試行がSTAGE2の設定を越えると、自動的に*PRESETのアクセスが設定されます。 # 誤検出が多い場合はその内容に応じて設定を変更してください。 # ただしSTAGE2の設定をむやみに大きくはしないこと。正常なメールが受信されなくなります。 # 特にメールマガジン等のサーバーは再試行をあまりしないようです。(30分3回くらいでやめることもある。) # 極端にしつこいスパムとちゃんとしたメールサーバーから発信されるスパムにはいずれ効果はありません。 # # まず以下の回数と時間(秒)を越える再試行があればDUNNOが設定され、通知されます。 # 値が小さければ早めに検知されますが、よけいな検出も増えます。大きければその逆。 STAGE1_RETRY_COUNT=1 STAGE1_RETRY_PERIOD=100 # DUNNOのまま以下の回数と時間(秒)を越える再試行があれば*PRESETに設定したアクセスが適用され、通知されます。 STAGE2_RETRY_COUNT=2 STAGE2_RETRY_PERIOD=600 # アクセスの変更がなくてもアクセスリストをアップデートする間隔。 # cron実行時にこの期間を過ぎていればアップデートされます。 # これによりリストの時間情報その他がアップデートされ、この後に設定される自動消去等も実行されます。 # 同時にpostmap, postfix reloadも行われます。 # 時間:秒 1時間:3600, 3時間:10800, 6時間:21600, 12時間:43200, 1日:86400程度で。 # 0だと定期アップデートはやりません。また自動消去もやりません。 ACCESS_LIST_UPDATE_PERIOD=10600 # めでたくログローテートによってログから消え去ったDUNNOとREJECTアクセスを消去、あるいは無効化。 # OKのアクセスについては何もせず放置されます。 # 0で何もしない、1で消去。2で単に無効化(頭に#{^_^}をマーク)。運用は1をお薦め。 # 0を指定した場合は、必要に応じて手動で削除してください。 # 2を指定した場合は、次の設定で自動消去するか、あるいは必要に応じて手動で削除してください。 DELETE_PAST_DUNNO=1 DELETE_PAST_REJECT=2 # さらに上記で2を指定して無効化されたDUNNOとREJECTアクセスをおよそ指定日数後に自動消去します。 # (アップデート間隔時間程度のずれは生じます。) # ここの単位は日。変更は自動アップデート時に反映されます。0を指定するとやりません。 # 実質アクセスそのものに変更はないので特に通知はされません。 FINAL_DELETE_PAST_DUNNO=3 FINAL_DELETE_PAST_REJECT=7 # アクセスが登録、変更されたらメールやダイアログでおしらせ。少なくとも一方は有効に! # diffで検出しているので、順位が変わっただけの時も通知されてしまうことはあります。 # メールでお知らせ。(メールサーバーの設定も考慮のこと。) REPORT_BY_MAIL=1 # メールの差し出し人と宛先。通常postmasterまたはroot。 MAIL_FROM="root" MAIL_TO="root" # ダイアログでお知らせ。 # 常時デスクトップを起動していないのなら0に。 REPORT_BY_DIALOG=1 # 消去または無効化も通知してほしければ 1に。 REPORT_DELETE_PAST_ACCESS=1 # # 以下、リストの設定。リストは基本的にアクセスの新しい順に表示されます。 # まれに同じIPから複数のfrom,toでの試行が行われる場合があり、複数のアクセスが表示されます。 # この場合どれが最初に有効になるかはわかりませんのですべての*PRESETを同じに設定しておいてください。 # あるいは、明らかにスパムならさっさとREJECTを設定してしまってください。 # # 0ならIPアドレス順。1でアクセスリストを状態やアクセス時刻等で(概ね新しいものが上に)並び替え。 # IPアドレス順は現在の状況がとてもわかりにくく、お薦めしません。 ORDER_BY_STAGE=1 # 拒絶(450 reject)の内容を記録するなら1に。 # main.cfに 450 rejectを返す制限を複数設置したときに、どれだかわかるようにします。 RECORD_REJECTION_INFORMATION=1 # アクセス設定の記録を残しておく。 # 1でアクセス(DUNNO, OK, REJECT)を最初に設定した時の記録を残す。0でやらない。 # タイミングとしてはアクセスファイルが自動更新された時に記録されます。 RECORD_ACCESS_INFORMATION=1 # コメント行作成 # 1で新規アクセス作成の際、あらかじめコメント用の行を用意しておきます。すごくこまめな人向き。 # コメントは1行のみ。{}の中にコメント等を記入してください。 RECORD_COMMENT_PREPARE=1 # アクセス情報の状態表示のタイトル。 # 変えたら、いったん access_retryを捨てて、再度起動して作成のこと。 # 制限は半角6文字以内。3つはダブっていてはいけません。誤動作します。 # STAGE1として検出されるアクセスの情報表示タイトル。"READY","NOTICE","IDLE","WATCH"など。 STAGE1_ACCESS_INFOSTR="READY" # STAGE2として検出されたアクセスの情報表示タイトル。"ACTIVE","VALID","FIXED","SET"など。 STAGE2_ACCESS_INFOSTR="ACTIVE" # ログファイルから消滅したアクセスの情報表示タイトル。"PAST","OUTLOG","NULL","QUIET"など。 STAGE3_ACCESS_INFOSTR="PAST!!" ## ## 設定の確認と編集、ここまで。 ## MESSAGE="再試行回数${STAGE2_RETRY_COUNT}回、継続時間${STAGE2_RETRY_PERIOD}秒を越えたDUNNOアクセスに*PRESETのアクセスが設定されます。" case $DELETE_PAST_DUNNO in 0) MESSAGE=$MESSAGE"\n現在めでたくアクセスログから消えたDUNNOアクセスも、 なぜかそのまま残る設定になっています。";; 1) MESSAGE=$MESSAGE"\nめでたくアクセスログから消えたDUNNOアクセスは、 自動的に消去されます。";; 2) MESSAGE=$MESSAGE"\nめでたくアクセスログから消えたDUNNOアクセスは、 無効化(#{^_^}でマーク)されます。";; esac if [ $DELETE_PAST_DUNNO -eq 2 ]; then if [ $FINAL_DELETE_PAST_DUNNO -eq 0 ]; then MESSAGE=$MESSAGE"\n無効化されたDUNNOアクセスは、なぜかそのまま残る設定になっています。" else MESSAGE=$MESSAGE"\n無効化されたDUNNOアクセスは、さらに${FINAL_DELETE_PAST_DUNNO}日後に自動的に消去されます。" fi fi case $DELETE_PAST_REJECT in 0) MESSAGE=$MESSAGE"\nめでたくアクセスログから消えたREJECTアクセスは、そのまま残る設定になっています。";; 1) MESSAGE=$MESSAGE"\nめでたくアクセスログから消えたREJECTアクセスは、自動的に消去されます。";; 2) MESSAGE=$MESSAGE"\nめでたくアクセスログから消えたREJECTアクセスは、無効化(#{^_^}でマーク)されます。";; esac if [ $DELETE_PAST_REJECT -eq 2 ]; then if [ $FINAL_DELETE_PAST_REJECT -eq 0 ]; then MESSAGE=$MESSAGE"\n無効化されたREJECTアクセスは、なぜかそのまま残る設定になっています。" else MESSAGE=$MESSAGE"\n無効化されたREJECTアクセスは、さらに${FINAL_DELETE_PAST_REJECT}日後に自動的に消去されます。" fi fi # アクセスリストがなかったらつくる。 if [ ! -e $ACCESS_LIST ]; then touch $ACCESS_LIST cat <> $ACCESS_LIST # 0. 以下の説明は、読んで理解して頂けましたら消去していただいてかまいません。 # 1. 450 retry requestに対し実際に再試行があったとき、まずアクセス'DUNNO'が自動的に設定されます。 # 2. 'DUNNNO'のまま放置しさらに設定以上の再試行があったら、*PRESETのアクセスが自動的に設定されます。 # 3. もしアクセスの拒否が決められましたら*PRESETのアクセス'OK'を'REJECT'に変更しておいてください。 # 4. あるいは、せっかちでしたら、アクセス'DUNNO'を直接'OK'または'REJECT'に変更してもかまいません。 # 5. 直接変更しましたら postmap ${ACCESS_LIST} と postfix reload をお忘れなく! # 6. 可否判断が確実にできない場合等は、基本的には'DUNNO'のままで放置しておくのがいいかと思われます。 # 7. (あるいはいったん'OK'に変更しておいて、メールが来たらば内容を確認して決めるのが最も確実です。) # 8. コメント等はIPブロックの最後に#IP {}の形で{}の中に記入してください。1行のみ。IPの後ろに要空白。 # 9. その他の#IP ... 欄には各種情報が表示されていますが自動で処理をしますので編集はしないでください。 # A. 最終アクセスからの経過日数+時間、再試行回数、継続時間などが#IP @....欄の項目に表示されています。 # B. ただしログがローテートされて該当アクセスの記録がなくなれば、'@${STAGE3_ACCESS_INFOSTR}'が表示され更新はされません。 # C. '@${STAGE3_ACCESS_INFOSTR}'と表示された'DUNNO'アクセスについては#IP ... 欄ごと早めに消去するようお薦めします。 # D. 同様に'OK'または'REJECT'としたアクセスについても本来のaccessのほうへ移動するようお薦めします。 # END. EOM fi # # アクセスの変更や編集の情報を抽出し、おしらせやログ用にわかりやすく整形したデータを作成。 # function get_changes() { if [ ! -f $ACCESS_LIST -o ! -f $ACCESS_LIST_NEW ]; then return fi DIFF=`diff -ubB $ACCESS_LIST $ACCESS_LIST_NEW` while getopts Rr OPT do case $OPT in [Rr] ) DIFF=`diff -ubB $ACCESS_LIST_NEW $ACCESS_LIST`;; esac done CHANGEACCESS=`echo "$DIFF" | egrep "^[\+-][1-9][0-9\.]+"` UPDATEINFO=`echo "$DIFF" | egrep "^[ \+]# *UPDATED:"` CHANGEINFO=`echo "$DIFF" | \ egrep -A 2 "^[\+\-](#{\^_\^})?[1-9][0-9\.]+ " | \ gawk '{sub(/^ /, "#");print;}' | \ sort -k 1,1d -k 1.2,1.2r -k 1.1,1.1r -k 2.1b,2.1r | uniq | \ gawk '{sub(/^#/, " ");print;}' | \ gawk '{if($1!~/^-[\-#]/)print;}' | \ gawk '{if($1~/^[\+\-][1-9][0-9\.]+/){\ if($1!~ad)print"";ad=$1;gsub(/[^0-9\.]/,"",ad);}print;}'` } # もしアクセスリストが編集されていたらログに記録。 get_changes -R if [ -n "$LOGFILE" -a -n "$CHANGEACCESS" ];then echo `date`$' (編集検知)\n\n'"$CHANGEINFO"$'\n-------------------' >> $LOGFILE fi # # 作業用ファイル作成。データ処理とデータ置き換えの準備。 # 処理が確実、容易に行えるように改行追加、グループ分けと順番用マーカーを付加。 # あとの処理を確実に行うために項目の頭の位置を再設定。(編集されないと思うけど念のため。) # データがまだ存在するかはわからないので一旦無しのマークを付けデータがあれば更新されるようにしている。 # 古いメッセージ等は削除 # cat "$ACCESS_LIST" | \ # 区切りの改行を整形 gawk ' BEGIN { newline="#" } { if($0 == "") next if($1 ~ /^(#|#{\^_\^})?[1-9][0-9\.]+$/ && $2 ~ /^[DRO]/ && $3 ~ /^(\(.*)?$/) { if(newline!="0") { print "" newline="0" } print } else if($1 ~ /#[1-9][0-9\.]+/) { if($2 ~ /^(@|&)/) newline="1" print } else if($1 ~ /^#/) { if(newline!="#") { print "" newline="#" } if($2 !~ /^(MESSAGE:|UPDATED:|LISTING:)/) print } } ' | \ # データのグルーピングマーカーを作成。IPとfrom、to。 gawk ' BEGIN { RS="" } { if($0 ~ /^(#|#{\^_\^})?[1-9][0-9\.]+ /) { address=substr($0, match($0, /[1-9][0-9\.]+/), RLENGTH) from=substr($0, match($0, /from=<[^>]*>/), RLENGTH) to=substr($0, match($0, /to=<[^>]*>/), RLENGTH) ident=address":"from":"to printf "#ident= %s\n%s\n", ident, $0 } else printf "#ident= %s\n%s\n", "information", $0 } ' | \ # 各データにグルーピングマーカーと優先順位を付加し整形。古いメッセージ等は削除。 gawk ' BEGIN { ident="?" this_ip="" this_access="" } { if($1 == "#ident=") { ident=$2 print "" next } if($1 ~ /^(#|#{\^_\^})?[1-9][0-9\.]+/ &&\ $2 ~ /^(DUNNO|OK|REJECT)$/ && $3 ~ /^(\(.*)?$/) { ip=$1 gsub(/[^0-9\.]/, "", ip) if(this_ip != ip) { this_ip=ip this_access=$2 } } if($1 ~ /^[^0-9\.]*[1-9][0-9\.]+$/ && (!this_ip || !this_access)) next if($1 ~ /^[1-9][0-9\.]+$/) { if(this_access ~ /^(OK|REJECT)$/) printf "%s %-24s %s\n", ident":A1", $1, this_access else if(this_access ~ /^DUNNO$/) printf "%s %-24s %s\n", ident":A7", $1, this_access } else if($1 ~ /^#{\^_\^}[1-9][0-9\.]+$/) { if(this_access ~ /^(OK|REJECT)$/) { printf "%s %-24s %s\n", ident":A2", this_ip, this_access printf "%s %s\n", ident":A9", $0 } else if(this_access ~ /^DUNNO$/) { printf "%s %-24s %s\n", ident":A8", this_ip, this_access printf "%s %s\n", ident":A9", $0 } } else if($1 ~ /^#[1-9][0-9\.]+$/) { if($2=="@'$STAGE1_ACCESS_INFOSTR'" || $2=="@'$STAGE2_ACCESS_INFOSTR'") { if('$RECORD_ACCESS_INFORMATION' && this_access ~ /^(DUNNO|OK|REJECT)$/) { printf "%s %-16s %-7s", ident":F2", $1, this_access for(fn=3; $(fn)!=""; fn++) { fv=length($(fn))==1 ? " "$(fn) : " "$(fn) printf "%s", fv } print "" } sub(/@'$STAGE1_ACCESS_INFOSTR'/, "@'$STAGE3_ACCESS_INFOSTR'", $2) sub(/@'$STAGE2_ACCESS_INFOSTR'/, "@'$STAGE3_ACCESS_INFOSTR'", $2) } if($2 ~ /^(DUNNO|OK|REJECT)$/) { if($3 ~ /^\(/) { if(this_access ~ /^(OK|REJECT)$/) printf "%s %-24s %s\n", ident":A2", this_ip, this_access else if(this_access ~ /^DUNNO$/) printf "%s %-24s %s\n", ident":A7", this_ip, this_access next } else if ($3 ~ /^[0-9]/) printf "%s %-16s %-7s", ident":F1", $1, $2 } else if($2 ~ /^@/) printf "%s %-16s %-7s", ident":B3", $1, $2 else if($2 ~ /^&/) printf "%s %-16s %s", ident":C2", $1, $2 else if($2 ~ /^450/) printf "%s %-16s %s", ident":D2", $1, $2 else if($2 ~ /^*PRESET/) { printf "%s %-24s %s\n", ident":A5", this_ip, $3 printf "%s %-16s %-7s", ident":E1", $1, $2 } else if($2 ~ /^{/) printf "%s %-16s %s", ident":G1", $1, $2 else printf "%s %-16s %s", ident":H1", $1, $2 for(fn=3; $(fn)!=""; fn++) { fv=length($(fn))==1 ? " "$(fn) : " "$(fn) printf "%s", fv } print "" } else if($1 ~ /^#/) { if($2 ~ /(MESSAGE:|UPDATED:|LISTING:)/) next printf "%s %s\n", ident":", $0 } } ' > $ACCESS_LIST_NEW"_TMP" # # Input mail log. # ログの読込。 # 最初まだログのないときエラーを吐くのでエラーは抑制しておく。 # cat $CHECK_LOGS 2>/dev/null | \ # # Extract records indicating 450 .... rejected:". # ログから450 .... rejected:アクセス情報を取得。 # egrep 'reject: .+ 450 4\.[0-9]\.[0-9] ' | \ # # Extract essential items. # 必要な情報を選別。 # gawk ' { client=substr($0, match($0, /from [^]]+\]/)+5, RLENGTH-5) sub(/[^[]*\[/, "[", client) rejection=substr($0, match($0, /450 .*; from=/), RLENGTH-6) gsub(/ /, "\037", rejection); sender=substr($0, match($0, /from=<[^>]*>/), RLENGTH) rcpt=substr($0, match($0, /to=<[^>]*>/), RLENGTH) helo=substr($0, match($0, /helo=<[^>]*>/), RLENGTH) printf "%s %2d %s %s %s %s %s %s\n", $1, $2, $3, client, sender, rcpt, helo, rejection } ' | \ # # Convert month names into month numbers. # 時間処理のためいったん月名を数値に変換。 # gawk ' BEGIN { month_num["Jan"]=1 month_num["Feb"]=2 month_num["Mar"]=3 month_num["Apr"]=4 month_num["May"]=5 month_num["Jun"]=6 month_num["Jul"]=7 month_num["Aug"]=8 month_num["Sep"]=9 month_num["Oct"]=10 month_num["Nov"]=11 month_num["Dec"]=12 max_month_num=0 } { $1=month_num[$1] if ($1>max_month_num) max_month_num=$1 else if ($1=minimum_retry_count) { print print "" } } ' | \ # # Add timestamp # 再試行時間を判定するためにタイムスタンプを付加する。 # maillogには年の記載がないため現在の年からログの年を推定。年末年始にちゃんと動くかどうかは未確認。 # gawk ' BEGIN { year=strftime("%Y") } { if ($0) { time=$3 gsub(/:/, " ", time) timestamp=mktime(year" "$1" "$2" "time) if(timestamp>systime()) { year-=1 timestamp=mktime(year" "$1" "$2" "time) } printf "%s %2d %s %s %s %s %s %s %s", $1, $2, $3, $4, $5, $6, $7, $8, timestamp } print "" } ' | \ # # Reconvert month numbers into month names. # 月表示を数字から元の月名表示に戻しておく。 # gawk ' BEGIN { month_name[1]="Jan" month_name[2]="Feb" month_name[3]="Mar" month_name[4]="Apr" month_name[5]="May" month_name[6]="Jun" month_name[7]="Jul" month_name[8]="Aug" month_name[9]="Sep" month_name[10]="Oct" month_name[11]="Nov" month_name[12]="Dec" } { if ($0!="") { $1=month_name[($1-1)%12+1] printf "%s %2d %s %s %s %s %s %s %s", $1, $2, $3, $4, $5, $6, $7, $8, $9 } print "" } ' | \ # # Select by timestump and Output Access OK # 指定回数と指定時間以上継続して再試行してきたアクセスを抽出。 # gawk ' BEGIN { RS="" stage1_retry_count='$STAGE1_RETRY_COUNT' stage1_retry_period='$STAGE1_RETRY_PERIOD' stage2_retry_count='$STAGE2_RETRY_COUNT' stage2_retry_period='$STAGE2_RETRY_PERIOD' now_timestr=strftime("%Y.%m.%d %H:%M") listup_count=0 print "" } { retry_count=gsub(/\n/, "\n") retry_period=$NF-$9 retry_days=int(retry_period/(60*60*24)) retry_hrs=int(retry_period/(60*60*24)%1*24) retry_min=int(retry_period/(60*60)%1*60) retry_timestr=sprintf("%02d+%02d:%02d", retry_days, retry_hrs, retry_min) pass_period=systime()-$NF pass_days=int(pass_period/(60*60*24)) pass_hrs=int(pass_period/(60*60*24)%1*24) pass_min=int(pass_period/(60*60)%1*60) pass_timestr=sprintf("%02d+%02d:%02d", pass_days, pass_hrs, pass_min) rejection=$(NF-1) gsub(/\037/, " ", rejection) gsub(/[\[\]]/, "") address=$4 from=substr($0, match($0, /from=<[^>]*>/), RLENGTH) to=substr($0, match($0, /to=<[^>]*>/), RLENGTH) ident=address":"from":"to if(retry_count>=stage2_retry_count && retry_period>=stage2_retry_period) { printf "%s %-24s OK\n", ident":A6", $4 printf "%s #%-15s %-7s %s pass, %2d retry in %s, %s %2d %s to %s %2d %s; %s\n",\ ident":B2", $4, "@'$STAGE2_ACCESS_INFOSTR'", pass_timestr, retry_count, retry_timestr,\ $1, $2, $3, $(NF-8), $(NF-7), $(NF-6), now_timestr printf "%s #%-15s &%s %s %s\n", ident":C1", $4, $5, $6, $7 if('$RECORD_REJECTION_INFORMATION'==1) printf "%s #%-15s %s\n", ident":D1", $4, rejection printf "%s #%-15s *PRESET OK\n", ident":E2", $4 if('$RECORD_COMMENT_PREPARE'==1) printf "%s #%-15s %s\n", ident":G2", $4, "{}" ++listup_count print "" } else if(retry_count>=stage1_retry_count && retry_period>=stage1_retry_period) { printf "%s %-24s DUNNO\n", ident":A3", $4 printf "%s #%-15s %-7s %s pass, %2d retry in %s, %s %2d %s to %s %2d %s; %s\n",\ ident":B1", $4, "@'$STAGE1_ACCESS_INFOSTR'", pass_timestr, retry_count, retry_timestr,\ $1, $2, $3, $(NF-8), $(NF-7), $(NF-6), now_timestr printf "%s #%-15s &%s %s %s\n", ident":C1", $4, $5, $6, $7 if('$RECORD_REJECTION_INFORMATION'==1) printf "%s #%-15s %s\n", ident":D1", $4, rejection printf "%s #%-15s *PRESET OK\n", ident":E2", $4 if('$RECORD_COMMENT_PREPARE'==1) printf "%s #%-15s %s\n", ident":G2", $4, "{}" ++listup_count print "" } } END { printf "%s # %s\n", "confirmation:",\ "LISTING: "listup_count" entries detected; "strftime("%Y.%m.%d %H:%M:%S") } ' >> $ACCESS_LIST_NEW"_TMP" # # Set PRESET data. # STAGE2のアクセスに入ったときに*PRESETで指定されたアクセスを適用。 # cat $ACCESS_LIST_NEW"_TMP" | sort -r | \ gawk ' BEGIN { need_preset="" } { if($1 ~ /^[1-9][0-9\.]+:.*:A6/) need_preset=$2 if($1 ~ /^[1-9][0-9\.]+:.*:A5/) { if($2 == need_preset) { print need_preset="" } } else if($0) print } ' | sort | \ # # Remove duplicated and invalid data. # 処理に使った頭のマーカーを消去し、優先したデータに重複する余分なデータを削除、再度データを分離。 gawk ' { gsub(/^[^ ]*[ ]*/, "") print } ' | uniq -w 18 | \ gawk ' BEGIN { data_block="" } { if($1 ~ /^[1-9][0-9\.]+/) { data_block=$1 print "" print } else if($1 ~ /^#{\^_\^}[1-9][0-9\.]+/) { ip=$1 gsub(/[^0-9\.]/, "", ip); if(ip!=data_block) { data_block=ip print "" } print } else if($1 ~ /#[1-9][0-9\.]+/) { if($2 ~ /^(@|&)/) data_block="" print } else if($1 ~ /^#/) { if($2 ~ /^LISTING:/) print "" else if(data_block!="#") { data_block="#" print "" } print } } ' | \ # # IP、有効無効、経過時間等を抽出し、データを付加して並び替えの準備と並び替え。 # gawk ' BEGIN { RS="" } { if ($1 ~ /^(#{\^_\^})?[1-9][0-9\.]+$/ && $2 ~ /^(DUNNO|OK|REJECT)$/) { ip=$1 gsub(/[^0-9\.]/, "", ip) if($0 ~ / @'$STAGE3_ACCESS_INFOSTR' /) { stage_class=3 if($2=="OK") access_class=3 else if($2=="REJECT") access_class=2 else access_class=1 } else { stage_class=1 if($2=="REJECT") access_class=3 else access_class=1 } pass_time=substr($0, match($0, /[0-9][0-9]\+[0-9][0-9]:[0-9][0-9]/), 8) printf "%s %s %s %s \n%s\n", ip, stage_class, access_class, pass_time, $0 } else if($0 ~ /# LISTING:/) { printf "%s %s %s %s \n%s\n", "0", "2", "0", "0", $0 } else { printf "%s %s %s %s \n%s\n", "X", "X", "X", "X", $0 } print "" } ' | \ gawk ' BEGIN { RS="" } { gsub(/\n/, "\037") print } ' | sort -k 1,1 -k 3,3r -k 2,2 -k 4,4 | \ gawk ' BEGIN { ip="" access="" } { if(ip!=$1) { ip=$1 access=substr($0, match($0, / (DUNNO|OK|REJECT)\037/)+1, RLENGTH-2) print "" } else { sub(/^[^ ]+ [^ ]+ [^ ]+ [^ ]+ \037/, "") # IPの重複を調し、修正。 if($0 ~ /^(#{\^_\^})?[1-9][0-9\.]+ +(DUNNO|OK|REJECT)\037/) { sub(/^(#|#{\^_\^})?/, "#") sub(/ (DUNNO|OK|REJECT)\037/, access" (複数アクセス検知。\ 今後どれが優先になるかわかりません。アクセスや*PRESETを変更するなら皆同じに。)\037") } } print } ' | \ if [ $ORDER_BY_STAGE -eq 1 ]; then gawk ' BEGIN { RS="" } { gsub(/\n/, "\037") print } ' | sort -k 2,4 else cat - fi | \ gawk ' { sub(/^[^ ]+ [^ ]+ [^ ]+ [^ ]+ \037/, "") gsub(/\037/, "\n") print print "" } ' | \ # # めでたくログから消え去ったDUNNOアクセスを消去または無効化。 # if [ $DELETE_PAST_DUNNO -ge 1 ]; then gawk ' BEGIN { RS="" } { if($0 ~ /^[1-9][0-9\.]+ +DUNNO[\n][^@]*#[1-9][0-9\.]+ +@'$STAGE3_ACCESS_INFOSTR' /) { if('$DELETE_PAST_DUNNO'==2) { if($1 != /^#/) printf "\n#{^_^}%s\n", $0 else printf "\n%s\n", $0 } } else printf "\n%s\n", $0 } ' else cat - fi | uniq | \ # # 無効化されたDUNNOアクセスを指定日数後に消去。 # if [ $FINAL_DELETE_PAST_DUNNO -ge 1 -a $ACCESS_LIST_UPDATE_PERIOD -gt 0 ]; then gawk ' BEGIN { RS="" } { if($0 ~ /^#{\^_\^}[1-9][0-9\.]+ +DUNNO[\n]#[1-9][0-9\.]+ +@'$STAGE3_ACCESS_INFOSTR' /) { past_time=$18" "$19" 00" gsub(/[\.:]/, " ", past_time) past_period=systime()-mktime(past_time) if(past_period < '$FINAL_DELETE_PAST_DUNNO'*60*60*24+'$ACCESS_LIST_UPDATE_PERIOD') printf "\n%s\n", $0 } else printf "\n%s\n", $0 } ' else cat - fi | \ # # めでたくログから消え去ったREJECTアクセスを無効化または消去。 # if [ $DELETE_PAST_REJECT -ge 1 ]; then gawk ' BEGIN { RS="" } { if($0 ~ /^[1-9][0-9\.]+ +REJECT[\n][^@]*#[1-9][0-9\.]+ +@'$STAGE3_ACCESS_INFOSTR' /) { if('$DELETE_PAST_REJECT'==2) { if($1 != /^#/) printf "\n#{^_^}%s\n", $0 else printf "\n%s\n", $0 } } else printf "\n%s\n", $0 } ' else cat - fi | uniq | \ # # 無効化されたREJECTアクセスを指定日数後に消去。 # if [ $FINAL_DELETE_PAST_REJECT -ge 1 -a $ACCESS_LIST_UPDATE_PERIOD -gt 0 ]; then gawk ' BEGIN { RS="" } { if($0 ~ /^#{\^_\^}[1-9][0-9\.]+ +REJECT[\n]#[1-9][0-9\.]+ +@'$STAGE3_ACCESS_INFOSTR' /) { past_time=$18" "$19" 00" gsub(/[\.:]/, " ", past_time) past_period=systime()-mktime(past_time) if(past_period < '$FINAL_DELETE_PAST_REJECT'*60*60*24+'$ACCESS_LIST_UPDATE_PERIOD') printf "\n%s\n", $0 } else printf "\n%s\n", $0 } ' else cat - fi | \ # # # 新しいメッセージ、UPDATE情報等を追加し、見やすいように改行しなおし。 # gawk ' BEGIN { message="'"$MESSAGE"'" if(message!="") { gsub(/^|\n/, "\n# MESSAGE: ", message) printf "%s\n", message } datetime=strftime("%x(%a) %H時%M分") printf "# UPDATED: %s\n", datetime group_ip="" } { if($0) { if($1 ~ /^(#{\^_\^})?[1-9][0-9\.]+/) { ip=$1 gsub(/[^0-9\.]/, "", ip); if(ip!=group_ip) { group_ip=ip print "" } } else if($0 ~ /^(# 0\. |# LISTING:)/) print "" print } } ' | uniq > $ACCESS_LIST_NEW rm -f $ACCESS_LIST_NEW"_TMP" # # Apply and sendmail/dialog if list is changed. # アクセスに変更があれば、アクセスリストに書込み、設定を適用。 # get_changes if [ -n "$CHANGEACCESS" ]; then cp -f $ACCESS_LIST_NEW $ACCESS_LIST /usr/sbin/postmap $ACCESS_LIST /sbin/service postfix reload 2>&1 > /dev/null # アクセスの変更がなくても定期的にアクセスリストをアップデートする。 # 時間情報や自動消去の情報が更新される。 elif [ $ACCESS_LIST_UPDATE_PERIOD -gt 0 ]; then TIMESTAMP_ACCESS=`stat --printf="%Y" $ACCESS_LIST` TIMESTAMP_ACCESS_NEW=`stat --printf="%Y" $ACCESS_LIST_NEW` PERIOD=`expr $TIMESTAMP_ACCESS_NEW - $TIMESTAMP_ACCESS` if [ $PERIOD -ge $ACCESS_LIST_UPDATE_PERIOD ]; then cp -f $ACCESS_LIST_NEW $ACCESS_LIST /usr/sbin/postmap $ACCESS_LIST /sbin/service postfix reload 2>&1 > /dev/null fi fi # # ログを記録 # if [ -n "$LOGFILE" -a -n "$CHANGEACCESS" ];then echo "$UPDATEINFO"$'\n\n'"$CHANGEINFO"$'\n-------------------' >> $LOGFILE fi # # メールやダイアログでおしらせ。 # if [ $REPORT_DELETE_PAST_ACCESS -eq 0 ]; then CHANGEACCESS=`echo "$CHANGEACCESS" | egrep "^[\+][1-9][0-9\.]+"` fi if [ -n "$CHANGEACCESS" ]; then # メールでおしらせ。 if [ $REPORT_BY_MAIL -eq 1 ]; then SENDMAIL="/usr/sbin/sendmail -it" SUBJECT="Postfix 450 retry detected !!" SUBJECT=`/bin/echo "$SUBJECT" | /usr/bin/nkf -jMB` MESSAGE="$ACCESS_LIST is changed. Change is : $UPDATEINFO $CHANGEINFO Please confirm or edit it, and do if necessary: # postmap $ACCESS_LIST # postfix reload " MESSAGE=`echo "$MESSAGE" | /usr/bin/nkf -j` $SENDMAIL <&1 > /dev/null get_changes -R if [ -n "$LOGFILE" -a -n "$CHANGEACCESS" ];then echo `date`$' (編集検出)\n\n'"$CHANGEINFO"$'\n-------------------' >> $LOGFILE fi cat $ACCESS_LIST > $ACCESS_LIST_NEW /usr/bin/zenity --info --width=333 --title="Postfix reloaded."\ --text="アクセスファイルの更新が検出されましたので、\npostmapとpostfix reloadを実行しました。" fi fi ) & fi fi exit 0