迷惑メール対策の一環で、面倒なのでスパマーの多い国ごとブロックしてしまおうという内容です。
現在ARIN、RIPE NCC、APNIC、LACNIC、AfriNICという5団体が世界中のIPv4アドレスを管理していて、どの国にどのIPアドレスが割り当てられているのかリストが公開されているようです。
ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest
ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
しかしこのリスト、[どの国に][どのIPアドレスから][何個連続で]割り当てているか。という記述の仕方になっています。
つまり生データだとiptablesやfirewalld、またはPostfixでは利用できない形式です。
この問題とその解決についてはnamiさんの記事が非常に参考になります。
そこでこのリストをサーバで利用できるようCIDRの記述に変換し、利用したいと思います。
firewallやTCPWrapperで弾いてしまうのも手ですが、今回はREJECTログをmaillogに残したかったため、Postfixでのフィルターを作成します。
とはいえPostfixではなくもっとその手前で弾きたい方にも簡単に応用していただけるかと思います。
まずREJECT用のCIDR作成スクリプトを作成します。
以下が今回作成したPHPスクリプトです。
1 2 |
mkdir /root/scripts vi /root/scripts/cidr-smtp-client.php |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php define('REJECT_COUNTRIES', 'KR|KP|VN|NL|IN|RU|BA|PH|CO|BO|PK|IR|AR'); define('TEMP_PATH', '/tmp'); define('CIDR_FILTER_PATH', '/etc/postfix/cidr_client'); passthru('wget -O '.TEMP_PATH.'/delegated-arin-extended-latest ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest'); passthru('wget -O '.TEMP_PATH.'/delegated-ripencc-extended-latest ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest'); passthru('wget -O '.TEMP_PATH.'/delegated-apnic-extended-latest ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest'); passthru('wget -O '.TEMP_PATH.'/delegated-lacnic-extended-latest ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest'); passthru('wget -O '.TEMP_PATH.'/delegated-afrinic-extended-latest ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest'); if(!file_exists(CIDR_FILTER_PATH) || is_writable(CIDR_FILTER_PATH)) { $wfp = fopen(CIDR_FILTER_PATH, 'w'); foreach (glob(TEMP_PATH.'/delegated-*-extended-latest') as $filename) { $rfp = fopen($filename, 'r'); if($rfp){ while ($line = fgets($rfp)) { if(preg_match('/(arin|ripencc|apnic|lacnic|afrinic)\|('.REJECT_COUNTRIES.')\|ipv4/', $line)) { $rows = explode('|', $line); $ipMin = ip2long($rows[3]); $ipMax = $ipMin + $rows[4] - 1; $cidrs = PlageVersCIDRs($ipMin, $ipMax); foreach ($cidrs as $row) { fwrite($wfp, $row." REJECT\n"); } } } } fclose($rfp); } fclose($wfp); } else { echo '*** ERROR: Cannot write to file '.CIDR_FILTER_PATH.' ***'; } function PlageVersCIDRs($ip_min, $ip_max) { $cidrs = array(); $ip_min_bin = sprintf('%032b', $ip_min); $ip_max_bin = sprintf('%032b', $ip_max); $ip_cour_bin = $ip_min_bin; while (strcmp($ip_cour_bin, $ip_max_bin) <= 0) { $lng_reseau = 32; $ip_reseau_bin = $ip_cour_bin; while (($ip_cour_bin[$lng_reseau - 1] == '0') && (strcmp(substr_replace($ip_reseau_bin, '1', $lng_reseau - 1, 1), $ip_max_bin) <= 0)) { $ip_reseau_bin[$lng_reseau - 1] = '1'; $lng_reseau--; } $cidrs[] = long2ip(bindec($ip_cour_bin)).'/'.$lng_reseau; $ip_cour_bin = sprintf('%032b', bindec($ip_reseau_bin) + 1); } return $cidrs; } |
IPアドレスの範囲をCIDRに変換するロジックが思い浮かばなくて、PlageVersCIDRs関数はこちらから拝借しました。
同僚にも頭を捻ってもらったのに、結局良いソースが転がってたというオチ(´・ω・`)
冒頭でdefineされてるREJECT_COUNTRIESの中にrejectしたい国コードを記入してください。
正規表現で判定しているので、”|”区切りで記入すればOKです。
ちなみに表記法はISO 3166-1 alpha-2です。
こちらのPHPを1日に1回実行します。
1 2 |
crontab -e 15 03 * * * /usr/bin/php /root/scripts/cidr-smtp-client.php |
あとは作成されたファイルを利用してクライアントのIPをチェックすればOK!
Postfixのmain.cfを編集します。
1 2 3 4 5 |
vi /etc/postfix/main.cf # main.cfの、smtpd_client_restrictionsのpermit_mynetworksの次でcheck_client_accessする。 smtpd_client_restrictions = permit_mynetworks, check_client_access cidr:/etc/postfix/cidr_client, |
まだ検証中のコードのため、ご自身の責任のもとでご利用ください。