連載 / Linux でリラックス Debian GNU/Linux の /etc/inittab ファイル 図 2 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 # The default runlevel . id: 2 : initdefault : ←デフォルトの実行レベルは 2 # Boot—time system configuration/initialization script . # This is run first except when booting in emergency (—b) mode. /etc/init . d executes the S and K scripts upon change : S:wait : /sbin/sulogin ←シングノレユーザー・モードのときに実彳予 What tO dO in single—user mode . SI : : sysinit : /etc/init . d/rcS ←起動時に 1 度だけ実行 # Run1eve1s 2 ー 5 are multi—user . # Run1eve1 1 is single—user. # Run1eve1 0 is halt . # of runlevel . # Run1eve1 6 is reboot . 10. 11 12. 13. 14. 15. 16. ・ 5 : ・ 4 : wait ・ 2 : wait : 1 : wait ・ 0 :wait : /etc/init . d/rc :/etc/init . d/rc :/etc/init .d/rc ・ 3 :wait : /etc/init . d/rc :/etc/init . d/rc 4 : /etc/init . d/rc ・ 6 :wait :/etc/init . d/rc 0 1 2 3 5 6 ←システムの停止時に実行 ←シングルューサー・モードのときに実行 ←実行レベルが 2 のときに実行 ←実行レベルが 3 のときに実行 ←実行レベルが 4 のときに実行 ←実行レベルが 5 のときに実行 ←システムの再起動時に実行 # Norma11y not reached , but fallthrough in case of emergency ・ ←システムの再起動に失敗したときに実行 z6 : 6 :respawn: /sbin/sulogin # What tO dO when CTRL—ALT—DEL is pressed. po : :powerokwait : /etc/init. d/powerfail stop pn : : powerfailnow : /etc/init . d/powerfail now pf : :powerwait : /etc/init . d/powerfail start # What to do when the power fails/returns . ↑ Alt 十↑キーが押されたときに実行 kb: :kbrequest : /bin/echo "Keyboard Request——edit /etc/inittab tO let this work. # Action on special keypress (ALT-UpArrow) . ↑ CtrI 十 AIt 十 DeI キーが押されたときに実行 ca: 12345 : ctrlaltdel : /sbin/shutdown —tl —a —r now # The " id" field MUST be the same as the last /sbin/getty invocations for the runlevels . ↑ UPS デーモンと組み合わせて使用される characters 0f the device (after "tty") . Format : く id> : く runlevels> : く action>: く process> 1 : 2345 :respawn: /sbin/getty 38400 ttyl 2 :23:respawn:/sbin/getty 38400 tty2 3 : 23 : respawn : /sbin/getty 38400 tty3 4 : 23:respawn: /sbin/getty 38400 tty4 5 :23:respawn:/sbin/getty 38400 tty5 6:23:respawn:/sbin/getty 38400 tty6 108 ←実行レベルが 2 ~ 5 なら getty を起動 ←実行レベルが 2 または 3 なら、 仮想コンソール 2 ~ 6 でも getty を起動 UNIX MAGAZINE 1999.10
の起重加に、どのような初期化スクリプトが、どのような 順番で実行されるのかをすこし詳しく説明します。 そのあと、 Debian GNU/Linux パッケージの使用法 に関するドキュメントの探し方を説明します。 dselect コ マンドなどを利用してさまざまなパッケージをインストー ルしてみたものの、パッケージの使い方がよく分からない ために、使いこなせない人も多いのではないでしようか。 そこで、新た ( ンヾッケージをインストールしたときに、ど うやってドキュメントを探せばいいのかを紹介します。さ らに、パッケージの成疋ファイルやログファイルがどのよ うなディレクトリにオタされているのかもあわせて説明し ます。 Debian の初期化スクリプト init テーモン Debian GNU/Linux にかぎらず、一殳の UNIX オ ペレーティング・システムでは、ハードウェアの初期化 やデーモンの起動などをおこなう初期化スクリプトはⅲ it デーモンによって起動されます。これは、カーネルが最初 ( プロセス番号 1 ) のプロセスとして起重丿けるデーモンであ り、他のすべてのプロセスの親プロセスとなります。 他の多くの Linux 配布パッケージと同様、 Debian GNU/Linux では、 init デーモンとして MiqueI van Smoorenburg が作成した SystemV 互換の sysvinit を用いています。 sysvinit の詳細は、 Slackware の初期 化スクリプトをとりあげたとき ( 2 月号 ) に解説したの こでは sysvinit そのものについては説明しません。 で、 sysvinit における実行レベル (runlevel) の定義や、 init デーモンの成疋ファイルである /etc/inittab ファイルの 書式などについては、 2 月号を参照してください。 /etc/init. d ディレクトリ Debian GNU/Linux の初期化スクリプトは、すべて /etc/init. d というディレクトリ内にオ内されています。 これらの初期化スクリプトは、システムの起重加 ( 正確に は init デーモンの起重加 ) や、システムの夫行レベルか変 化するときに、 init デーモンによって実行されます。これ らのネ月化スクリプトにより、さまざまなサービスの開始 連載 / 凵 nux でリラックスー 104 や終了などがおこなわれます。 ただし、実行する初期化スクリプトの不頁は、実行レベ ルに応して変化します。また、初期化スクリプトを実行す る順番も重要です。たとえば、ネットワークの設定をお こなう初期化スクリプトや、ノヾックグラウンドでデーモン を起動する初期化スクリプトなどは、一殳にシングルユー サー・モードでは実行する必要はありません。また、ネット ワークの設定をおこなうネノ期化スクリプトは、ネットワー クを利用するデーモンを起動する初期化スクリプトよりも 前に実行しておく必要があります。 Debian GNU/Linux では、基本的に SystemV と同 じ方式 2 で、どのようなスクリプトをどのような順番で起 重丿庁るのかを決定しています。つまり、それぞれの実行レ ベルに対して、 /etc/rcrunlevel. d というディレクトリを用意し、 こから /etc/init. d ディ レクトリ内のネノ期化スクリプトにシンポリック・リンクを 作成しています。このシンポリック・リンクにより、実 行する初期化スクリプトや、その実行順序を決定していま す。ここで、 runlevel は現在の実行レベル ( 0 ~ 6 ) 、また はシステムの起重丿時を表す S のいすれかです。 シンポリック・リンクの名前は、 S れれスクリプト名 もしくは、 K れれスクリプト名 という形式になります。れれは、初期化スクリプトの実行 順を表す 2 桁の数字です。、、スクリプト名 " は初期化スク リプト名で、通常はリンク先のファイル名 (/etc/init. d ディレクトリ内の初期化スクリプトのファイル名 ) です。 init デーモンは、夫行レベルが襯 e 眦 I に変化したと きに、ます /etc/rcrunlevel.d ディレクトリ内のネ月化ス クリプトのうち、、、 K " て始まるもの ( サービスの停止ス クリプト ) をれれの小さい順 ( れれか 1 司番の場合はアルファ べッい印に実行します。このとき、ネ加月化スクリプトに stop" という引数を与えて実行します。正確には、 init デーモンか初期化スクリプトを直接実行するのではなく、 init デーモンから起動される / etc / ⅲ it. d / rc スクリプト 2 ただし、 init デーモンから実行されるスクリプト名 (/etc/init. d/rc など ) は一緇 ; 異なります。 UNIX MAGAZINE 1999.10
連載 / Linux でリラックスー /etc/rcrunlevel. d ディレクトリから、、スクリプト名 " て指定した初期化スクリプトへのシンポリック・リンク をすべて削除します。 update-rc. d コマンドは、 /etc/ init. d ディレクトリに、、スクリプト名 " の琪月化スクリ プトが残っている場合は、シンポリック・リンクを削 除しません。強制的にシンポリック・リンクを削除する 場合には、 update-rc. d コマンドに一 f オプションを指 定します。 たとえば、 /etc/init. d/exim へのシンポリック・リン クを削除するには以下のようにします。 # update—rc . d —f eX1m remove update—rc. d: /etc/init . d/exim exists during rc . d purge (continuing) Removing any system startup links for /etc/rc6. d/K20exim /etc/rc5. d/S20exim /etc/rc4. d/S20exim /etc/rc3. d/S20exim /etc/rc2. d/S20exim /etc/rcl . d/K20exim /etc/rcO . d/K20exim /etc/init . d/exim す。これにより、 exim パッケージをシステムから削除 update-rc. d コマンドに一 f オフションを指定していま /etc/init. d/exim スクリプトが存在しているため、 /etc/inittab ファイ ) レ ンクを標準出力に表示します。 ンクの削除はおこなわす、削除されるシンポリック・リ なお、 -n オプションを指定すると、シンポリック・リ きます。 することなしに、 exim デーモンか起動しないように UNIX MAGAZINE 1999.10 デーモンの起重加に、 /etc/init. d/rcS というプログラ また、 Debian GNU/Linux の起重加 ( 正確には init 行レベルは 2 であることが分かります。 5 行目から、 Debian GNU/Linux のデフォルトの実 います ) 。 の内容を図 2 に示します ( 説明のために行番号をイ寸加して のノヾージョン 2.76-3 に含まれる ) /etc/inittab ファイル Linux 2.1 に含まれる ( 正確には、 sysvinit パッケージ うな順番で実行されるかを説明します。 Debian GNU/ イルをとりあげ、どのような初期化スクリプトが、どのよ 以下では、 Debian GNU/Linux の /etc/inittab ファ ム ( シェル・スクリプト ) か実行されることカ吩かります ( 9 行目 ) 。工ントリの不頁が、、 sys ⅲ it " であるため、この スクリプトはシステムの起重加寺に 1 度だけ実行されます。 init デーモンは、このスクリプトカ鮗了するまで他の処理 をおこないません。 こで、 /etc/init. d/rcS スクリプトの中身も見てみま しよう ( 図 3 ) 。 /etc/init. d/rcS スクリプトは環境変数 などの設定をおこなったあと、図 3 の 26 行目で /etc/ default/rcS ファイルを言売み込んでいます。このファイル では、初期化スクリプトのためのデフォルト値がいくつか 設定されています。たとえば、 CMOS クロックが GMT ( グリニッジ標 ~ おを示しているかどうかなどの設定も こにあります。そのあと、 /etc/rcS. d ディレクトリ内 に存在するスクリプトのうち、ファイル名が S て始まる ものを順番に実行します ( 37 ~ 56 行目 ) 。さらに、 /etc/ rc. boot ディレクトリ内のすべての砌化スクリプトを順 番に実行します ( 61 行目 ) 。 さて、 /etc/inittab ファイルに戻りましよう。 /etc/ init. d/rcS スクリプトの実行カ鮗ったあと、現在の実行レ ベルを引数として、 /etc/init. d/rc スクリプトを実行して います ( 図 2 の 22 ~ 28 行目 ) 。工ントリの不頁が、、 wait であるため、ⅲ it デーモンはこのスクリプトカ冬了するま ・で他の処理をおこないません。 こで、 /etc/init. d/rc スクリプトの中身も覗いてみ ましよう ( 図 4 ) 。ます、現在の実行レベルと直前の実行 レベルを調べています ( 図 4 の 43 ~ 47 行目 ) 。そのあ と、直前の実行レベルが、、 N " でなければ、、、 K " で始まる すべての初期化スクリプトを順番に実行します ( 57 ~ 64 行目 ) 。シェルのワイルドカードを使用しているため、前 述の 2 桁の数字 ( れれ ) の小さいスクリプトから順番に実行 S て始まるすべての開始スクリプ されます。さらに、 こでは、直前の トを順番に実行します ( 67 ~ 95 行目 ) 。 実行レベルが、、 N " ではなく、なおかっ直前の実行レベル で同じ初期化スクリプトかま行されていれば、ふたたひ初 期化スクリプトを実行しない点に注意してください ( 71 ~ 86 行目 ) 。 以 - ヒのように初期化スクリプトの実行か完了したあと、 init デーモンは getty プロセスを起動します ( 図 2 の 50 ~ 55 行目 ) 。この点で、ようやくューザーのシステムへ のログインが可能になります。 107
連載 / Linux でリラックスー 図 4 Debian GNU/Linux の /etc/init. d/rc スクリプト # Now find out what the current and what the previous run1eve1 are . ←現在の実行レベル runIeve1=$RUNLEVEL # Get first argument . Set new runlevel tO this argument . ] & & runlevel=$l ←引数があれは、これをもとに実行レベルを決定 ←直則の実行レベノレ previous=$PREVLEVEL export runlevel previous # ls there an rc directory for this new runlevel? if [ -d /etc/rc$runlevel. d ] ← /etc/rcrunlevel.d ディレクトリが存在するか ? then # First, run the KILL scripts ・ ←直前の実行レベルが N でなけれは・・・ if [ $previous ! = N ] then for i in /etc/rc$runlevel . d / K [ 0 ー 9 ] [ 0 ー 9 ] * ↑停止スクリプトを順番に実行 do # Check if the script is there . [ ! —f $i ] & & continue ↑リンク先が存在するかどうかをチェック # Stop the service . startup $i stop ←初期化スクリプトの引数に st 。 p を指定 done # Now run the START scripts for this runlevel . for i in /etc/rc$runlevel . d/S* ↑開始スクリプトを順番に実行 do [ ! —f $i ] & & continue ↑リンク先が存在するかどうかをチェック if [ $previous ! = N ] then 1 っ 3 -4 5 一ト一 0 1 っつけ一 4 一り一 8 9 0 一 1 っつけ 4 一り冖ー 8 0 ー 1 っ 1 一ト り【ー一 8 一一 0 11 っ 1 っ -4 一り ( ー 4 -4 4 -4 4 4 -4 一 4 4 5 - り 5 - り”り 5 一り 1 り 5 6 6 6 ー「ー冖ー suffix=$i#/etc/rc$runlevel . d/S [ 0 ー 9 ] [ 0 ー 9 ] stop=/etc/rc$runlevel. d/K [ 0 ー 9 ] [ 0 ー 9 ] $suffix previous-start=/etc/rc$previous . d/S [ 0 ー 9 ] [ 0 ー 9 ] $suffix [ —f $previous—start ] & & [ ! —f $stop ] & & continue ↑直前の実行レベルでサービスが開始されていればスキップする in 0 ) ←実行レベルが 0 または 6 なら、 startup $i stop 引数に st 叩を指定 startup $i start ←実行レベルがそれ以外なら、 引数に start を指定 # eof /etc/init . d/rc ノヾッケージの制罹卩ファイノレの Description フィールド ( 7 次々とインストールすることも容易です。 月号参照 ) にパッケージの簡単な説明か書かれており、 しかし、、、パッケージをインストールしてみたものの、 れを見川ま、それぞれのパッケージの概要を知ることがで どのようにして使えばいいのか分からない " という場合も きます。 dselect コマンドを使用すれは、パッケージの説 あるでしよう。 Debian のポリシー・マニュアルでは、パ 明にざっと目をとおして、おもしろそうなパッケージを ッケージのドキュメントに関しても日寉なルールを定めて 110 UNIX MAGAZINE 1999.10
図 4 通常の一里 図 5 プログラミング・テクニック クライアント 逆向きの一里 socket() PASV 要求 クライアント socket() bind() listen() acce pt() 要求 socket() connect() initconn() socket() bind() listen() PASV 応答 要求 connect() 接続の確立 す。このときクライアントが dataconn 関数で accept を 実行することにより、接続か確立されます ( 図 4 ) 。 逆向き ( 制御用の接続と同じ向き ) に接続を張る場合に は、もうすこし工夫が必要です。さきはどの図で、たんに クライアントとサーバーの彳難リを逆にすることはできませ ん。サーバー側で bind や listen をいつ実行すればよいか カ吩からないからです。要求がきてから準備するガ去も考 えられますが、その場合、クライアント側は要求を出した あとしばらく待ってから connect を実行する必喫があり ます。、、しばらく " とはどの程度でしようか。これはマシ ンの能などに左右されるので、このガ去ではうまくいか ない場合があります。 実際の FTP では、逆向きの接続をサポートするために 新たな要求か加えられています。これを用いた処理の様子 を図 5 に示します。クライアントは、 initconn 関数のな かで socket によりソケットを作成したあと、 PASV 要求 をサーバーに送ります。 PASV 要求を受けたサーバーは、 socket 、 bind 、 listen を実行して接続を受け入れる準備 をします。そのあとで PASV 要求に対する応答を返しま す。応答を受け取ったクライアントは connect を実行し て実際に接続を試みます。 則回、 connect システムコーノレはサーノヾーカゞ accept シ ステムコールを実行するまでプロックすると書きました。 正確には、 listen システムコールを実行していれば con- nect システムコールはプロックしません。ごめんなさい。 ということで、クライアントが connect システムコー ルを実行しても、処理は続けられます。ここでサーバーに 対して実際のファイルの要求を出します。するとサー 側では、 select システムコールて接続要求を尉見しながら UNIX MAGAZINE 1999 ユ 0 接続の確立 dataconn() select() accept() accept システムコールを実行します。これで無事接続が 確立することになります。クライアント側の datac 。Ⅱ n 関 数ではすることがなくなってしまい、 initconn 関数で作 成したソケットを返すだけになります。 レジューム機能 最近の FTP て粳利だなと思うのは、レジューム機能 です。ネットワークの或は以前にくらべれは齧瞿的に太 くなっていますが、転送されるデータ量やネットワーク の利用者数も増えています。場合によっては、目的のファ イルの転送がうまくいかないこともあります。このような とき、途中まで取得したデータを捨てて、最初からやりな おすのはもったいない話です。そこで、すでに取得したも のを活かしながら本を得ようというのがレジューム機能 です。 この機能を寒見しているプログラムをみる前に、プロト コルとしてどのような仕組みが用意されているのカ召介し ておきましよう。しつは、これはごく簡単な仕組みです。 最初にどこから先か欲しいのかをサーバーに伝えてからフ ァイルの中幻医を開始します。すると、サーバーは指定され た場戸励、らの残りをクライアントに転送します。クライア ントでは、以偂弭え得したファイルと今回渡されたデータを 連結してファイル本を構成します。 レジューム機能を利用するには、サーバーとクライアン トがともにレジューム機能に対応していなけれは・なりませ 129
連載 / 遠隔オフィスとの接続ー return ERROR; / * コマンドを実行する * / int exec—command() FILE *fp; int C , i ; C ommand , / * 実行結果を tmp パッフアに読み込む * / system(tmp) ; / * コマンドの実行 * / sprintf (tmp, "%s >%s 2 & > 1 " / * 実行するコマンド文字列を作る * / tmpf) ; if ( (fp=fopen(tmpf , "r")) = = NULL) { *tmp } else { for (i = 0 ; (c=getc(fp)) ! = EOF & & i く BUFSIZE; tmp[i] unlink (tmpf) ; void message(char *msg) printf ( "Content—type : text/html\n\n" ) ; printf(" く html> く head><tit1e>Run Command Message く /title> く /head>\n") ; 66 message(tmp) ; exec—command() ; } else { message("Permission denied く p>\n") ; } else if (setuid-process() = = ERROR) { message("lnvalid input\n") ; = ERROR) { if (parse() init() ; char *argv ロ ; int argc ; main(argc , argv) printf ( " く body> く pre>%s く /pre> く /body> く /html>\n" ,msg) ; UNIX MAGAZINE 1999.10
連載 / Linux でリラックスー 図 1 /etc/rcO. d ディレクトリ内のキ琪手化スクリプト列 K11cron K12kerne1d K20exim K20gpm K20samba K20skkserv K25nfs—server K30netstd_misc K50net at alk K89atd K90sysk10gd S20sendsigs S25h clock . sh S30urandom ・ restart S40umountfs S90ha1t K15netstd-init K2010goutd K18netbase K20autofs K201pd K20ppp によって実行されます。なお、直前の実行レベルが、、 N " の場合は、これらの、、 K " から始まる初期化スクリプトは 実行されません。 次に、 init デーモンは /etc/rcrunlevel.d ディレクト リの初期化スクリプトのうち、 S て始まるもの ( サービ スの開始スクリプト ) を同しく数字十アルファベット順に 実行します。このとき、ネ川月化スクリプトに、、 start" とい う引数を与えます。ただし、実行レベルが 0 または 6 の ときは、初期化スクリプト名が S から始まっていても 、 stop" という引数をケえて実行します。 たとえば、 /etc/rcO. d ディレクトリに図 1 のような初 期化スクリプト ( 実体はシンポリック・リンク ) があったと します。この場合、実行レベルが 0 に変化したときには、 サービスをいったん中止し、その後あらためてサービス を開始する。 ・ reload サービスを中断することなく を再度読み込む。 ・ force— 、サービスの成疋ファイノレ → → → → → → → K11cro 取 stop K12kerne1d stop K89atd stop K90sysk10gd stop S20sendsigs stop S25hwc10ck. sh stop S40umountfs stop S90haIt stop 可能ならば、サービスを中断することなく、サーピスの 故疋ファイルを再度読み込む。これができない場合は、 サーピスをいったん中止し、その後あらためてサービス を開始する。 Debian GNU/Linux では、デーモンとして重川乍する ようなサーピスに対してはかならす初期化スクリプトが用 意されています。また、ほとんどの匆月化スクリプトでは、 start 、 stop 、 restart 、 force-reload といった引数を使 用することができます。 このため、サービスの起動や終了などは、サービスの種 類によらすすべて統一されたガ去でおこなうことができま す。たとえば、 exim ( メール中幻医工ージェント ) デーモン を終了したいとしましよう。多くの UNIX オペレーティ ング・システムでは、 # ps ax ー grep exim の順でネノ琪月化スクリプトか実行されることになります。実 行レベルが 0 なので、 S から始まる初期化スクリプト であっても、、 stop" という引数が与えられることに注意し てください。 初期化スクリプトの本髄 このように、初期化スクリプトには、、 start" や、、 stop などの引数が与・えられて実行されます。 Debian GNU/ Linux では、初期化スクリプトの引数として、以下のよう UNIX MAGAZINE 1999.10 ・ stop ・ start サービスを中止する。 サービスを開始する。 な値か定義されています。 582 aO S 169 ? S 502 al S # k 土 11 169 0 : 00 grep exim 0 : 00 /usr/sbin/exim —bd —q30m 0 : 00 vi /etc/init . d/exim のように いったん exim デーモンのプロセス番号を調 ペそのあとにシグナルを送ってプロセスを終了させる必 要があります。一方、 Debian GNU/Linux では、 exim デーモン用に初期化スクリプトか用意されているため、 # /etc/init . d/exim stop のようにしてデーモンを終了させることかて、きます。もち ろん、 exim デーモンをふたたひ開始するには、 # /etc/init . d/exim start 105
連載 / 遠隔オフィスとの接続ー parse() の最後の、 if (*user ! = *command ! = return OK; } else { return ERROR; 図 4 CGI の実行結果 河井集表示 ( ) ジャンプ喞 Oøm 取 0 ヘルプ ( 印 : Run 00 宿衂聞 d M 鱸 3 e - 権 0 夘を & & *pass イの 再読み込み耒ーム検幸 ガイド 場所 : い / 帚 0 ゴト bin/AP runcgi 印 セキュリティ ' 関連サイト では、 Web プラウザからすべての情報か渡されているか どうかを石薩忍し、不足があれば工ラーにします。この処理 は、 JavaScript などを使って Web プラウザ側でおこな うようにすることもできます。 setuid-process() 関数は、この CGI のなかでもっと も重要な部分です。ここでは、 UNIX のアカウント情報 とユーサーが入力した情報をもとに認証をおこない、成功 したらプロセスの実イ限を変更します。 UNIX MAGAZINE 1999.10 定します。たとえは、ユーザーカ甘旨定したコマンドが、 ます、 tmp 変数には実行するコマンド列をそのまま設 system(tmp) ; command , tmpf) ; sprintf (tmp, "%s >%s 2 & > 1 " command() 関数はかなり簡略化しています。 Web プラウサから渡されたコマンドを実行する exec- でプロセスの権限を変更しています。 setuid(pw—>pw—uid) ューザー名とパスワードの組合で認証か成功したら、 OK & & pw¯>pw—passwd) if (strcmp (crypt (pass ,pw—>pw-passwd) , スワードであるかどうかを判定しています。 があります。以下のコードでは、これを利用して正しいパ て暗号化すると、もとの暗号化されたものと一致する性質 暗号化されたものをもう一度クリアテキストのパスワード います。 UNIX で使われているパスワードは、 crypt() で に登録されている暗号化されたパスワード文字列が入って ます。 pw->pw-passwd には、 /etc/passwd ファイル て、入力されたパスワードと一致しているかどうかを卩串ヾ 次に、エントリからパスワードのフィールドを抜き出し pw = getpwnam (user) ; ポインタである pw 変数に設定しています。 たユーサー名のエントリを探し出し、 passwd 構造体への 以下のコードは、 /etc/passwd ファイルから指定され ・カクーク Is /etc LGTOuscsi TI MEZONE を 00 を 引こ色 &pache apache. Old asppp . cf au [ 0 ー hO を をしを 0- 用を ster autopush 0 を t を b 尾 5 chroot 0 「 0 n cron. d 035. conf だったとすると、 tmp 変数には次のようなコマンドが設 定されます。 ls /etc >/usr/tmp/runPT 、 ocessID 2 & > 1 C のプログラムからコマンドを実行するガ去には何通り かありますが、 こでは system() 関数を使っています。 system() は、シェルを起動して引数で受け取った文字列 をそのまま実行します。このプログラムでは、ユーザーが 指定したコマンドを実行し、標準出力と標準ェラー出力に 出力されるメッセージを /usr/tmp/runProcessID に保 存しています。 最後の処理は、出力したメッセージを読み込んで web プラウサに返すことです。ここでは message() 関数を流 用するためにすこし手抜きをしていて、 tmp 変数に保存で きる長さ ( 1 , 024 バイト ) だけメッセージを読み込み、そ れを送り返しています。したがって、返された結果の最後 の 1 文字だけか文字化けすることがあります。これは、 2 バイト文字 ( 全角のかなや漢字など ) の前半の 1 バイトだ けが送られたからだと捉えてください。 図 4 の画面は、 Web プラウザから正しいユーサー情 報とパスワードを入力し、 "ls /etc" コマンドを実行させ たときの結果です。そっけない表示ではありますが、 /etc ディレクトリのファイルの一覧が表示されています。 ☆ 今回は、ユーザー認証や権限設定時の安全性に重点をお いて、 CGI を作る際の注意点を説明しました。 CGI を安 全なものにするには、プログラムの安全性を考えるだけで はなく、システム本でセキュリティ・ホールを作らない ように注意することも必要です。 63
連載 / 遠隔オフィスとの接続ー 図 3 フォームにユーサー名やパスワードを入力 フを新第第町を : 0 明純イ 0 ーへせ はう第、 3 み ~ を一 ~ はは 当ノトンブド ! 0 / 宿、ッ′を ! 0 : っ 11 ・冢・行′を ユーサー名 : パスワード : 実行するコマント : C で作った CGI プログラム ・「エ関直りイト それでは、任意の UNIX のコマンドを実行する CGI プログラムの C 言語版を紹介しましよう。末尾のリスト 2 にこのプログラム run. c を示しました。 run. c は、ユー サー認証をおこない、指定されたユーサーの権限て指定さ れたコマンドを実行します。 なお、このプログラムは、理解しやすさを第一に考え てエラー処理や入力値の判定をはとんどおこなっていませ ん。実用化するには、もうすこし書き足す必喫があるでし よう。また、デバイスを制御する一にのコマンドや、イン タラクテイプなコマンドに対応する仕組みもありません。 このようなコマンドを使いたいのであれは、仕組みそのも のを根底から変更する必があります。 さっそく、ソースコードを見てみましよう。 ストの最後の main() 関数を見てください。 main(argc , argv) int argc ; char *argv ロ ; init ( ) ; if (parse ( ) = = ERROR) { message("lnvalid input\n") ; } else if (setuid-process() = = ERROR) message("Permission denied く p>\n") ; } else { exec—command() ; message(tmp) ; まず、リ こでは、実際に処理をおこなっている関数を呼び出 し、エラーメッセージや処理結果を message() 関数を使 って表示します。全体の流れは次のようになっています。 62 1. init() 関数で初期化 2. parse() 関数で Web プラウサから受け取ったデータを 角財斤し、変数に保存 UNIX MAGAZINE 1999.10 ます。 ている decode() 関数は、変換された文字をもとに戻し という処理をおこないます。 parse() 関数から呼び出され ( は 16 進 ・そのはかの使用できない文字は、、 % " の形式に変換 ・空白文字は、、十 " に変換 に使えない文字列について、 pass 、 command に保存します。 Web プラウサは URL ウザからのデータを読み込み、内容を角斤して変数 user 、 parse() 関数は、標準入力へ送られてきた Web プラ 使われます。 ァイルは、コマンドの実行結果を一印判勺に保存するために というファイルのパス名を保存している変数てす。このフ /usr/tmp/runProcessID 最後の tmpf は、 ・ tmpf ・ command ・ pass ・ user 数をネ川月化しています。 init() 関数では、プログラム中て利用する次の 4 つの変 Web プラウサ ( 標準出力 ) に出力するだけの関数です。 これは、引数で渡された文字列を HTML の形式にし、 く /html>\n" ,msg) ; printf ( " く body> く pre>%s く /pre> く /body> Message く /title> く /head>\n") ; printf (" く html> く head> く tit1e>Run Command printf ( "Content—type : text/html\n\n" ) ; void message(char *msg) 面の都合上、で折り返しています。以羽司様 ) message() 関数の部分は以下のようになっています ( 誌 5. 実行結果を Web プラウザに返送 4. コマンドを実行 ロセスの権限を変更 3. setuid-process() 関数でユーザー認証をおこない、プ
連載 / 遠隔オフィスとの接続ー ーム ・ファイルの所有者を root に変更したときに、 set user- bit をクリアする。 また、ネットワーク上の言算機どうしのアクセス権を設 定する、 ・ hosts. equiv ファイノレ ・ . rhosts ファイ / レ などにも注意してください。 NFS で公開しているファイ ルの権限も適切に管理する必要があります。 システムに負荷を加える攻撃 Web サーバーや FTP サーバーに対して、ま可間に何 度もアクセスをしたり、巨大なファイルを繰り返しダウン ロードするような攻撃もあります。 その結果、回線の容量カ坏要なアクセスでパンクした り、過負荷のためにユーザーに対するサービスカ歸ったり します。 このような攻撃の景些を軽減する手段として、 攻撃をしかけてくるシステムよりも能力か高いシステム を用意する ・攻撃をしかけてくるシステムからのアクセスをネットワ ークの入口 ( ルータなど ) で拒否する などのガ去があります。 しかし、前者はシステムの導入のためにコストと時間が かかるので、あまり現実的とはいえません。 後者は迷惑なアクセスから身を守るための孝第斗書的な対 去ですが、攻撃者がいくつものシステムを使い分けてい たり、複数の攻撃者がいる場合には、つねに不正アクセス を見張ってルータなどの設定を変更する必要があるのでイ タチごっこになってしまうかもしれません。 いずれにしても、このような攻撃に対しては根本的な回 避策がないのか伏です。 UNIX コマンド実行用の CGI 前回は、遠隔オフィスから Web インターフェイスを使 い、本社のホストの任意のコマンドを実行する CGI を紹 介しました。 UNIX MAGAZINE 1999.10 リスト 1 UNIX のコマンド列を実行するフォ く html> く head> く tit1e>Run Command く /title> く /head> く body> く form method="POST" action= ューサー名 : く br> く blockquote> く input type="text" name= く /blockquote> / くスワード . く br> く blockquote> "/cgi-bin/run ・ cgi"> く br> く input type= password" name= password"> く br> く /blockquote> 実行するコマンド : く br> く blockquote> く input type="text" name=ttcommand"> く /blockquote> く input type=" submit " value=" 送信 " > く input type="reset" value=" クリア” > く /form> く /body> く /html> この CGI は、 Web サーバーの権限で実行されます。で すから、各ューザーの権限でコマンドを実行するには、所 有者が root の setuid プログラムとして重川させ、コマ ンドを指定されたユーサーの権限に変更する必要がありま した。ところが、紹介した CGI はシェル・スクリプトで 入力フォーム た CGI プログラムを紹介します。 そこで、今回はコンパイル言語である C 言語で作成し を奪わ書き換えられる危険があります。 作られていたので、前述のようにスーパーユーサーの権限 図 3 は、このフォームにユーザー名やパスワードを入 ん。 としていること以外は、とくに注意すべき点はありませ ・フォームのデータの受け渡し方式に POST を使う ・パスワードの入力には input タグの password 属性を づき、 サから呼び出すためのフォームです。前述の注意点にもと リスト 1 は、 UNIX の任意のコマンドを Web プラウ 力した状態です。 61