せっかく(10年くらい前に)作ったのでここで公開しておく。
ログを監視して IP アドレスごとバンするやつ
こういうスクリプトを root で走らせておく。
#!/bin/sh detect() { ip=`echo "$1" | egrep -o -e "\[([[:digit:]]+\.){3}[[:digit:]]+\]" | sed -e "s/[][]//g"` if [ ! -z "$ip" ]; then /sbin/ipfw add 8`date +%H` deny all from $ip to any 2>&1 | /usr/bin/sed 's/^/add ipfw rule: /' | /usr/bin/logger -p auth.info -t ${0##*/} -s fi } exit_me() { kill `jobs -p` exit 1 } PID=$$ trap "exit_me" TERM INT tail -n 0 -F /var/log/maillog | (trap "kill $PID" TERM INT && while read line; do if echo "$line" | egrep -e 'Password mismatch ' > /dev/null 2>&1; then detect "$line" fi if echo "$line" | egrep -e 'SASL LOGIN authentication failed:' > /dev/null 2>&1; then detect "$line" fi done) & wait $! exit_me
内容を要約すると、 tail -f /var/log/maillog
を走らせっぱなしにして、
何か出てくるたびに grep
でフィルタして、認証エラーから IP アドレスを抽出したら
ipfw
のルールにつっこむ。
でっ、その時に 800 番台の番号を振っておく。800 + 現在時刻の hour の値。
例えば、10:34 に流れてきたログから抽出した IP アドレスなら 810 って。
800 番台にしたのは、うちの ipfw
ルールが偶然あいてたから。別に何でもいいんだけど。
マッチングルールはまぁいろいろお好みで。 他のコードは、あとで daemon にする時にシグナル周りをいい感じに何とかするためのヤツ。
ほとぼりが冷めたころに ban を解除するやつ
で、あとはこういうスクリプトを cron で毎時 30 分に回す。
#!/bin/sh export LC_ALL=C id=`printf "8%02d" \`expr \( \\\`date +%H\\\` + 20 \) % 24\`` echo "delete ipfw rule: $id" | /usr/bin/logger -p auth.info -t ${0##*/} /sbin/ipfw show | grep "^00$id" | /usr/bin/logger -p auth.info -t ${0##*/} /sbin/ipfw delete $id > /dev/null 2>&1
20:30 に実行されたら、816 番のルールを全部消す。 だいたい 3 時間くらいで解除する感じかな?別にそんなに厳密にする必要もないので、 書きやすい感じで書いただけ。この辺の数字もお好みで。
最初のスクリプトを daemon にする
/usr/local/etc/rc.d/watch_mx
とか適当な名前で下のスクリプトを置いとく。
パスとかは適宜直して使う。
#!/bin/sh # # PROVIDE: watch_mx # REQUIRE: ezjail . /etc/rc.subr name=watch_mx rcvar=watch_mx_enable command=/usr/sbin/daemon load_rc_config $name : ${watch_mx_enable:="NO"} pidfile=${watch_mx_pidfile:="/var/run/watch_mx.pid"} command_args="-cfr -P ${pidfile} -t ${name} /usr/local/bin/${name}.sh" run_rc_command "$1"
こっからメモ書き(ポエム)
/usr/sbin/daemon
って初めて使ったけど案外便利ね。
基本的なノリとしては
$ sudo daemon -cfr -P /var/run/watch_mx.pid -t watch_mx sh ~/bin/watch_mx.sh
こんな感じで起動すると、 sh
以降のコマンドを子プロセスとして起動して、
死んでたら再起動したり、シグナルを中継したりしてくれるみたい。
実際、 tail
の方に SIGKILL
とか送ると勝手に再起動してくれる。
sh
の方に送ってもちゃんと再起動してもらうために、
パイプの接続先は子プロセスでも trap
してから while read
とかしてる。
あんま見かけない書き方だけど、なんか動いてるから多分大丈夫(?)。
LISTEN から accept
して fork
するだけの、サーバプログラムの共通になりがちなコードの
親プロセス部分の実装が inetd
だとするなら、daemon の実実装部分の管理をするだけの
親プロセス部分が daemon
といったところか?
ただ、PGID の親が daemon
さんになってしまうせいで、
kill -$$
がちゃんと動かないのはハマった。
子プロセスからみた自 PID、 $$
と同じ PGID は存在しないので。
おかげでシグナルトラップをちゃんと動かすのに大変手間取った……
解決策は
$ kill `jobs -p`
こんな感じ。
素直に perl
とかで書けばよかった気がしないでもない。
なんでもかんでも root
で動かすとセキュリティホールになるしね。
いずれ作り直したいところではある。
2022-09-29 注:IPv6 対応がバグっているまま10年以上運用していたらしい。まぁ別に害がある訳でもないか……