ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

認証エラーログを見て ipfw で ban するやつ

せっかく(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年以上運用していたらしい。まぁ別に害がある訳でもないか……