FreeBSDの最大の長所であるports。/usr/ports/ports-mgmt/portaudit をインストールしておくと、portsでインストールしたソフトウェアにセキュリティホールなどの問題がある場合に、こんな具合に、報告してくれます。
# portaudit
Affected package: fetchmail-6.3.8_6
Type of problem: fetchmail -- potential crash in -v -v verbose mode (revised patch).
Reference: <http://www.FreeBSD.org/ports/portaudit/1e8e63c0-478a-11dd-a88d-000ea69a5213.html>
Affected package: php5-posix-5.2.6
Type of problem: php -- input validation error in posix_access function.
Reference: <http://www.FreeBSD.org/ports/portaudit/ee6fa2bd-406a-11dd-936a-0015af872849.html>
2 problem(s) in your installed packages found.
You are advised to update or deinstall the affected package(s) immediately.
この、portsでインストールしたソフトウェアに関するセキュリティホールのレポートは、毎日、自動的にメールで送られてきます。
これは、こういう仕掛けになっているからです。
- /etc/crontab に書いてあるperiodic dailyというコマンドが、毎日午前3時(デフォルトの設定)に実行される
 - periodic dailyは、/etc/periodic/daily/ など、特定のディレクトリ以下にあるファイルを、順番に実行していく
 - /etc/periodic/daily/450.status-security を実行したとき、そこから、periodic securityが実行される
 - periodic securityも同様にして、/etc/periodic/securityとか/usr/local/etc/periodic/securityなど特定のディレクトリ以下のファイルを実行していく
 - portauditをインストールしていると、/usr/local/etc/periodic/security/410.portaudit というファイルもインストールされている。この中で、portauditを実行している。
 - 実行ログが、メールでrootあてに送られる
 
☆
というわけで、portauditは、毎日、勝手に実行されています。
portauditは、セキュリティホール情報をどこから入手しているかというと、まあ当たり前ですが、ネットワーク経由でダウンロードしてきています。ダウンロードしたファイルは、デフォルトでは、/var/db/portaudit/auditfile.tbz という名前で保存されています。
/usr/local/etc/portaudit.conf.sample というファイルがありますが、これを参考に、/usr/local/etc/portaudit.confという名前の設定ファイルを作れば、portauditの挙動をいろいろとカスタマイズできます。
たとえばauditfile.tbzというファイルをどこからダウンロードするかも、指定できるようになっています。
たとえば私の場合、たくさんのFreeBSDマシンを動かしているので、
- 1台だけ、http://www.FreeBSD.org/ports/ からダウンロードするようにして、
 - ダウンロードしたファイルを、ローカル環境のWebサーバにおいておき、
 - そのほかのFreeBSDマシンは、ローカル環境のWebサーバからダウンロードする
 
ようにしています(http proxyのキャッシュ機能がどうも腐ってて、何日たってもキャッシュのデータを返してくるとか、そういう理由もあったり・・・)。
ちなみに、portaudit.confにどんなことを書けるかは、実は、/usr/local/sbin/portauditを解読したほうが手っ取り早いかもしれません・・・portauditは、ただのシェルスクリプトなので。
さらに余談ですが、/usr/sbin/periodicもシェルスクリプトなんですねぇ。
なお、portauditを実行するたびに、毎回auditfile.tbzをダウンロードしているわけではなく、ある程度日付が経過したら、改めて、サーバから最新版をダウンロードするようになっています。man portauditして、「-X」オプションの説明に書いてあります。
☆
あるとき、/tmpに巨大なファイルが作られて、file system full、ディスクの空き容量がなくなってしまいました。
その巨大なファイルの中身をのぞいたら、
Checking for a current audit database:
Downloading fresh database.
fetch: http://ローカル環境のWebサーバ/auditfile.tbz: Operation timed out
fetch: http://ローカル環境のWebサーバ/auditfile.tbz: Host is down
fetch: http://ローカル環境のWebサーバ/auditfile.tbz: Host is down
fetch: http://ローカル環境のWebサーバ/auditfile.tbz: Host is down
(以下、ディスクの空き容量を食いつぶすまで繰り返し)
ということになってました。
どうやら、
- periodic securityの実行がはじまって、
 - その実行ログが/tmp以下に一時ファイルとして作られていて、
 - ログが永遠に出力されけてしまった挙句の、file system full
 
ということだったようです。
たまたまそのとき、ローカル環境のWebサーバを停止させていたのですが、portauditは、auditfile.tbzをダウンロードしようとして、無限ループしていました。
ログメッセージには「fetch」と書いてあるように、portauditは、/usr/bin/fetchというコマンドを使って、ファイルauditfile.tbzをダウンロードしようとしています。
ダウンロードするときにどんなコマンドを使うかは、やはりportaudit.confで指定できます。
たまたまなんですが、portaudit.confでは
portaudit_fetch_cmd="fetch -1amp"
と記述していました。このため、ファイルをダウンロードするとき、
fetch -1amp fetch: http://ローカル環境のWebサーバ/auditfile.tbz
が実行されることになります。
ためしに、手で上記のコマンドを入力して、実行してみました。
すると、Webサーバが落ちている場合、こんな実行シーケンスが起きてしまうことがわかりました
- (Webサーバは同じサブネット上に存在しているため)、ARPでMACアドレス解決をしようとして
 - しばらくたってタイムアウトしてエラー
 - fetchに-aオプションを指定していると、一時的なエラーの場合は(ファイルが存在しない、といったエラーじゃない場合)、成功するまで繰り返すことになっている
 - 今回出た「Host is down」の場合は、繰り返すことになっている
 - というわけで、もう一回、サーバに接続を試みる
 - 今度は、ARPのキャッシュが有効で、といってもARPが解決できない、という結果になるだけですが(arp -aしたときincompleteと表示される)
 - というわけで、即座に、Host is downというエラーになる
 - -aオプションのせいで、また繰り返し・・・
 - ARPのキャッシュの有効期限内は、そりゃーもー、猛烈な勢いで、エラーが出続けます
 - ARPのキャッシュの有効期限が過ぎても、しばらくたって、やっぱりARP解決できないエラーになり、また同じことの繰り返し
 
以上のようなシーケンスで、
fetch: http://ローカル環境のWebサーバ/auditfile.tbz: Host is down
というエラーが、永遠に吐き出され続けるのでした。うぐぅ・・・
☆
さて、これは、何が悪いのでしょうか。
(1) fetchに-aオプションをつけるのが悪い
/usr/local/sbin/portauditを見るとわかるのですが、実は、デフォルトでは-aオプションは、ついてないのです。portaudit.conf.sampleに「-1amp」って書いてあるのは、まずいですねぇ。
(2) fetchが、全速力でリトライを繰り返すのはまずい
昔、ダイアルアップでインターネット接続していたころの話ですが、電話では、リダイアル規制ってのがあって、お話中でCONNECTできなかった場合、リダイアルをするんですが、あんまり立て続けにリダイアルしちゃダメ、というルールがありました。
そういうルールって、やっぱり必要ですよね。というわけで、fetchには、-wオプションで、待ち時間が指定できるようになっています。
でも、これって、filesystem fullになるまでの、時間稼ぎにしかならないですね。
(3) fetchが、永遠にリトライしつづけるのが悪い
まあ、これは無限ループしちゃうケースがあるってのは、一般的な話としても、プログラムとして非常に問題なわけです。
というわけで、思いつきで、-eオプションというのを追加して、リトライ回数の上限を指定できるようにしてみました。
# diff -u fetch.c.ORG fetch.c
--- fetch.c.ORG 2006-12-27 09:50:57.000000000 +0900
+++ fetch.c     2008-07-28 16:15:40.000000000 +0900
@@ -57,6 +57,7 @@
 int     b_flag;        /*!   -b: workaround TCP bug */
 char    *c_dirname;    /*    -c: remote directory */
 int     d_flag;        /*    -d: direct connection */
+int     e_retry;       /*    -e: max retry */
 int     F_flag;        /*    -F: restart without checking mtime  */
 char   *f_filename;    /*    -f: file to fetch */
 char   *h_hostname;    /*    -h: host to fetch from */
@@ -730,7 +731,7 @@
        int c, e, r;
        while ((c = getopt(argc, argv,
-           "146AaB:bc:dFf:Hh:lMmN:nPpo:qRrS:sT:tUvw:")) != -1)
+           "146AaB:bc:de:Ff:Hh:lMmN:nPpo:qRrS:sT:tUvw:")) != -1)
                switch (c) {
                case '1':
                        once_flag = 1;
@@ -762,6 +763,9 @@
                case 'd':
                        d_flag = 1;
                        break;
+               case 'e':
+                       e_retry = strtol(optarg, &end, 10);
+                       break;
                case 'F':
                        F_flag = 1;
                        break;
@@ -973,8 +977,12 @@
                                            "before retrying\n", w_secs);
                                if (w_secs)
                                        sleep(w_secs);
-                               if (a_flag)
+                               if (a_flag) {
+                                   static int retry = 0;
+                                   if ( ++retry < e_retry ) {
                                        continue;
+                                   }
+                               }
                        }
                }
fetchは、引数で、複数のURLを指定できるみたいなんですが、それだと、上のパッチ、正しくないですね。2つめのURLのときに、retryを初期化していないので。まあ、トータルでのリトライ回数、というこじつけにしておきます(笑)
(4) それにしても/usr/bin/fetchの出来は悪い
個人的に帰着した結論ですが、やはり、これなんです。
以前、ここのブログで、portsnapでトラブルが起きたことを書いたような気がしますが、そのときも、fetchって出来が悪いなぁ、と思ったのでした。
そういえば、fetchって、URLにユーザー名とパスワードを埋め込んだ、「ftp://ユーザー名:パスワード@サーバーアドレス/パス」という形式のURLが、使えないですよね。


 







 
 



 












 


 
 
 
 

 

 






