OpenBSD httpd

いつもの日課で農道を散歩していると、遅い稲刈りに出くわした。

暫く前までは、イナゴを採取する中国人だかが、バッタいやイナゴのごとく溢れていたけど、 もうそんな騒ぎは無い。

ほとんどの田が稲刈りを終了して、田に物干し竿の親分みたいなのを設置して、そこで穂を天日干ししてる。昔ながらの光景なんだけど、夜間にその穂を盗みに来る連中がいるそうだ。イナゴなんかより、よっぽど質が悪い。何とか窃盗団だな。

稲刈りも大型機械でやっちゃうのね。稲刈り、脱穀、藁の裁断をまとめてやっちゃう。これも、天日で干して盗まれるのを防止する為?

田にコンバインが入るのにちと苦労してた。大型機械ですからねぇ。どうしても、一部の穂を踏み潰さないとだめ。思い切りよく、踏み潰して田に入っちゃったよ。踏み潰された穂は寝てしまうので、機械にかけられない。どうするか見てたら、お付きの人がやってきて、鎌で刈ってた。

機械の稲刈りは、見てて気持ちがいいねぇ。さーと撫でるだけで、刈り取られていく。 一度動き出すと速いものだ。見る間に、田が裸になっていく。

なんか、コンバインがバリカンに思えてきたぞ。オイラーの頭は高校球児。女房にバリカンを 入れてもらってんだけど、気持ちいいぐらいにさっぱりと刈ってくれる。

そんな訳で、一句浮かんだ

稲刈りの
 手際のよさや
  秋の空

TVでやってる一流芸人認定番組で、偉そうに人の句を添削してるおばちゃんに、何と言われるだろう?

OpenBSD httpd

apacheのソースを追う旅をしようと思ったけど、#ifdef の嵐に見舞われて、沈没しそうなので、河岸を変えます。雑音の無い、OpenBSDです。ソースが最初から付いてます。

ob6$ ps awxl | egrep '(PID|httpd)'
  UID   PID  PPID CPU PRI  NI   VSZ   RSS WCHAN   STAT  TT       TIME COMMAND
   67 12433     1   0   2   0   696  2576 kqread  Isp   ??    0:00.01 httpd: logger (httpd)
   67 32260     1   0   2   0   888  2908 kqread  Isp   ??    0:00.01 httpd: server (httpd)
    0 29940     1   0   2   0   892  2052 kqread  Isp   ??    0:00.01 /usr/sbin/httpd
ob6$ id 67
uid=67(www) gid=67(www) groups=67(www)

起動すると、ちゃんと働くやつは、wwwの権限を持ったやつになるのね。apacheだとnobodyさんだったかな。サーバー数を減らしているのは、下記の設定による。

how to use cgi

apacheのCGIは昔、散々やったので(Perl/ruby)、今回はOpenBSDのhttpdで試してみたい。

設定の基本は、下記のようにするみたい。

Guide to Unix/BSD/OpenBSD/As a Webserver

/etc/httpd.conf

ext_ip = "127.0.0.1"

prefork 1

server "default" {
    listen on $ext_ip port 80

    root "/htdocs/my.domain"

    location "/cgi-bin/*" {
        fastcgi socket "/run/slowcgi.sock"
        root "/"
    }
}

types {
    include "/usr/share/misc/mime.types"
}

slowcgiを動かしておく必要が有るそうな。これ、AF_UNIX なパケットを、cgiアプリ用のstdin/stdout/stderrに、相互変換するものらしい。

slowcgiとhttpdを、OS起動時から有効にするには、rcctl enable slowcgi とかすればよい。それが、/etc/rc.conf.localに

httpd_flags=
slowcgi_flags=

反映される。これが有ると、ダエモン君が動くよ。

そして、お試しのcgiをC語で書いた。(微妙に進歩してるのか)だって、perlとかでやろうとすると、監獄の中へごっそりとperl関連を連れ込まないといけないからね。

#include <stdio.h>
#include <sys/types.h>
#include <time.h>

int main(){
        time_t x;

        x = time(NULL);
        puts(ctime(&x));
        return (0);
}

何の事はない。今何時?って答えてくれるやつである(注: BUG included)。普通にコンパイルしちゃうと、libcとかld.soとかが必要になる。コンパイル時に、-static オプションを付けて、内包したものにする。ああ、内包と言えば、便利なgolangが有るな。

ob6# chroot -u www /var/www/ /cgi-bin/now
Wed Oct 10 06:56:31 2018

出来上がったアプリが、監獄内でちゃんと動くか確認。ああ、アプリ名はnowね。それを一つだけ、/var/www/cgi-bin に放り込んでおく。

ob6$ curl -v http://localhost/cgi-bin/now
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /cgi-bin/now HTTP/1.1
> Host: localhost
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Date: Wed, 10 Oct 2018 07:23:57 GMT
< Server: OpenBSD httpd
< Transfer-Encoding: chunked
< Wed Oct 10 07: 23:57 2018
<
* Connection #0 to host localhost left intact

OK ステータスが返ってきてるけど、付随するデータが来てない。普通は、

ob6$ curl -v http://localhost/
 :
< Content-Length: 33
< Content-Type: text/html
 :
Hello, world. from OpenBSD httpd

こんな風に、返答のサイズと共に、サーバーからレスポンスが有るんだけどね。

ここで少し反省しとく。未知なやつに対して、未知、未知してどうすると。最初の未知は、cgiを動かす為の設定の事ね。後の未知は、オイラーが適当にでっちあげたnowなるアプリ。どちらかに不都合が有っても動かない。

ならば、未知のものを既知のものに変えよう。後の未知をapacheから移植(コピペですな)してみる。そう、test-cgiっていうshell-scriptね。

こいつを、/var/www/cgi-binの中に放り込んでから、実行属性を付ける。こいつを駆動するには、/bin/shが必要になるのは分かっているけど、まずはそれが無い状態で試す。何事も経験ですから。

ob6$ curl -sv http://localhost/cgi-bin/test-cgi
  :
> GET /cgi-bin/test-cgi HTTP/1.1
  :
* HTTP 1.0, assume close after body
< HTTP/1.0 500 Internal Server Error
  :
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>500 Internal Server Error</title>
<style type="text/css"><!--
body { background-color: white; color: black; font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }
hr { border: 0; border-bottom: 1px dashed; }
  :

確かに、サーバー側の内部エラーだわな。エラーページも、cssで装飾してくれるのかと、変な事に感心してます。

この時、/var/www/logs/error.log にはエラーの痕跡は無し。その代わりに、/var/log/messagesに下記のエラーが出てたぞ。

Oct 11 06:31:53 ob6 slowcgi[52922]: execve //cgi-bin/test-cgi: No such file or directory

httpdとslowcgiは別物ですから、当然の事よ。slowcgi側で、execveしてるのか。そして、あたかも、test-cgiが無いという(嘘)の報告。騙されてはいけませんぜ。 test-cgiは、こんなやつですから、

#!/bin/sh
# disable filename globbing
set -f
echo Content-type: text/plain
echo
echo CGI/1.0 test script report:
echo
echo argc is $#. argv is "$*".
echo
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
 :

ちゃんと静的に作られた、/bin/sh を、/var/www/binにコピーしてあげます。これで、test-cgiを実行する環境が出来たはず。試してみる。

ob6$ curl -sv http://localhost/cgi-bin/test-cgi
  :
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-type: text/plain
< Date: Wed, 10 Oct 2018 21:47:37 GMT
< Server: OpenBSD httpd
< Transfer-Encoding: chunked
<
CGI/1.0 test script report:

argc is 0. argv is .

SERVER_SOFTWARE = OpenBSD httpd
SERVER_NAME = default
GATEWAY_INTERFACE = CGI/1.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_PORT = 80
REQUEST_METHOD = GET
HTTP_ACCEPT = */*
PATH_INFO =
PATH_TRANSLATED =
SCRIPT_NAME = /cgi-bin/test-cgi
QUERY_STRING =
REMOTE_HOST =
REMOTE_ADDR = 127.0.0.1
  :

レスポンスヘッダーにbody部の長さは載ってこないのか。これが普通の挙動? どんな長さのbodyになるかなんて、サーバー側では予想もつかないから当然よ。

SERVER_NAMEの所は、httpd.confのあれが反映されるのか。それはいいけど、もろもろのenvデータが、環境変数としてcgiのアプリに渡されるんだな。(こんな事は、昔散々やったろうに、もう忘れているのか)

差分法

上のように、未知数のうちの一つを潰した所、移植組(test-cgi)では、正常動作した。と言う事は、最初の未知、サーバー設定は正常に成されていると考えていいだろう。

するとオイラーが即席ででっち上げたnowに不具合が内在してる事になる。勝手知ったるshell-scriptなんて、差分法の適用も容易。で、結論をいきなり出す事にする。

ob6$ cat now.c
#include <stdio.h>
#include <sys/types.h>
#include <time.h>

int main(){
        time_t ept;

        puts("Content-type: text/plain"); // Must be need for CGI
        puts("");                         // Need null line

        ept = time(NULL);
        puts(ctime(&ept));
        return (0);
}

コメントを入れた行が重要。CGIするなら、キャッシュカードの暗証番号を忘れようと、この2行は忘れるな! 昔やってたのに、すっかり抜け落ちていたわい。まだらボケですかね?

ob6$ curl -sv http://localhost/cgi-bin/now
  :
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-type: text/plain
< Date: Wed, 10 Oct 2018 22:34:21 GMT
< Server: OpenBSD httpd
< Transfer-Encoding: chunked
<
Wed Oct 10 22:34:21 2018

ちゃんと、Content-typeが引き継がれている。良かった、よかった、正しい科学の方法が、身を助くってのを、身をもってまた経験したな。

fastcgi/slowcgi

上で出て来たslowcgiなんだけど、これってUnixのコマンドで言う、more/lessみたいな物? 最初にfastcgiってのが有ったんで、ならばその反対語だなってんで名付けた?

slowcgi(8)

NAME
     slowcgi - a FastCGI to CGI wrapper server

からすると、そういう類の物では無さそう。fastcgiって機能が有ってそれを普通のCGIに変換するみたいだ。なんで、そんな面倒が必要?

cgiは任意のアプリを起動するんだった。forkしてexecしてっていう取っても重い処理。それを 頻繁にやられたんでは、遅くなっちゃう。

だったら、アプリをあらかじめ起動させておいて、Webサーバーから要求が有ったら、fastcgi経由で、アプリに接続すればいいじゃん。

でも、古いCGI仕様しか知らない(すっかり忘れている事が露呈したけど)オイラーみたいなのを救済する為、slowcgiを作ってあげたよってのが真実なんだろうね。

fastcgiの切口は、

ob6$ ls -l /var/www/run/
total 0
srw-rw----  1 www  www  0 Oct 11 14:38 slowcgi.sock=

この正体は、AF_UNIX

ob6$ netstat -f unix | egrep '(Address|slow)'
Address            Type   Recv-Q Send-Q              Inode               Conn               Refs            Nextref Addr
               0x0 stream      0      0                0x0                0x0                0x0                0x0 /var/www/run/slowcgi.sock

この切口は、slowcgiが起動すると用意される。そこにhttpd側のfastcgiが接続しに行くって寸法だな。 念のため、参考文献を探してみた。

FastCGI Specification

CGIとFastCGIの仕様について

そして、こちらは、現代風大好きなPython(AIって言ったらPython一択ですからね)と、組み合わせて、便利に使えるようにした仕掛けっぽい。

Using OpenBSD httpd as proxy

多分、こいつら(って、Pythonを使って自由気ままな事をしたい連中、こう書くと、Python屋は、軽い連中と誤解されそう。そこの人、石投げないでね)、OpenBSDが用意した監獄(chroot)には入りたく無いんだろう。

オイラーだって、入りたく無いぞ。だって、Python(rubyでもperlでもいいけど)を、監獄の中で動かそうとすると、関連するライブラリーとかを一切喝采持ち込まなければいけませんからね。

その点、Fastcgiを使えば、便利なpythonは塀の外に置いておける。塀の中のWebサーバーとのやり取りは、ソケット一本で事足りますから。セキュリティーにも貢献しますよってのが、大きなメリットになるな。

bgplg

付録で付いていたbgplgとは、何者? manしたら

DESCRIPTION
     The bgplg CGI program is a looking glass for the bgpd(8) Border Gateway
     Protocol daemon.  The looking glass will provide a simple web interface
     with read-only access to a restricted set of bgpd(8) and system status
     information, which is typically used on route servers by Internet Service
     Providers (ISPs) and Internet eXchange points (IXs).  It is intended to
     be used in a chroot(2) environment in /var/www.

とかと書かれていたから、AS番号なんちゃらと常にぶつくさ言っている、交通整理のおじさん御用達なやつだな。使うには、

                # chmod 0555 /var/www/cgi-bin/bgplg
                # chmod 0555 /var/www/bin/bgpctl

                # mkdir /var/www/etc
                # cp /etc/resolv.conf /var/www/etc

          For example, add the following to /etc/bgpd.conf to have bgpd(8)
          open a second, restricted, control socket:

                socket "/var/www/run/bgpd.rsock" restricted

     4.   Start the httpd(8) and slowcgi(8) servers after configuring the
          related server section in httpd.conf(5).  For example:

                ext_addr="0.0.0.0"

                server "lg.example.net" {
                        listen on $ext_addr port 80
                        location "/cgi-bin/*" {
                                fastcgi
                                root ""
                        }
                }

     The following statically linked executables have been installed into the
     chroot(2) environment of the httpd(8) server.  To enable the
     corresponding functionality, use the chmod(1) utility to manually set the
     file permission mode to 0555 or anything appropriate.  Some of these
     executables need the set-user-ID bit, so they should be mounted on a
     filesystem without the nosuid option.

どうせサンプルを載せるなら、もっと一般的なやつにして下さい。>親分殿

ob6$ cat htdocs/bgplg/index.html
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>bgplg...</title>
    <meta http-equiv="refresh" content="1; URL=/cgi-bin/bgplg"/>
  </head>
  <body>
    <h1><a href="/cgi-bin/bgplg">bgplg...</a></h1>
  </body>
</html>

でも、このindex.html中の、metaなタグは昔を思い出させてくれて、なかなか良いぞ。 Javascript全盛のご時世で、こういうのに出会うと、昔の職人さんをおもいだす。

chroot

フランスの刑務所から囚人がヘリコプターで脱獄したそうな。捜査の末に居所を特定、逮捕しようとしたらカーチェイスの末、逃げられたとか。

日本でも、大阪方面で接見室のパネルを破って、逃げ出した輩が居ましたなあ。よく逃亡者続けられたな。

で、フランス当局に避難殺到。最近ようやく捕まえた。憤懣やるかた無い当局は、ググルに、施設の写真はボカしてくれってお願い。モザイクじゃ駄目なんですかって言ってみたい。モザイクを取り込んだ国ですから。(前回のネタの再利用、スマソ)

んな事で、chrootの監獄がどうなってるか見ておくか。監獄内のアプリを、囚人に成りすまして rootが実行する例は、上でやったな。

        if (chroot(argv[0]) != 0 || chdir("/") != 0)
                err(1, "%s", argv[0]);

        if (argv[1]) {
                execvp(argv[1], &argv[1]);
                err(1, "%s", argv[1]);
        }

これが、アプリケーションの骨子か。核は、chroot(2)だ。核の方をmanしたら、

     The following example changes the root directory to newroot, sets the
     current directory to the new root, and drops some setuid privileges.
     There may be other privileges which need to be dropped as well.

           #include <err.h>
           #include <unistd.h>

           if (chroot(newroot) != 0 || chdir("/") != 0)
                   err(1, "%s", newroot);
           setresuid(getuid(), getuid(), getuid());

こんな例が出てた。そうよな、収監される時は、ケツの穴まで調べられるって言うけど、この例では、世間での特権も、はく奪されてるよ。ここから更にchrootの現場へ行ってもいいけど、 今回は、もう少し穏やかに、httpdでの使い方を見てみるか。

ob6$ grep chroot *.[ch]
httpd.c:        if (env->sc_chroot == NULL)
httpd.c:                env->sc_chroot = ps->ps_pw->pw_dir;
httpd.c:                procs[proc].p_chroot = env->sc_chroot;
httpd.c:                if (asprintf(&env->sc_logdir, "%s%s", env->sc_chroot,
httpd.h:        const char              *p_chroot;
httpd.h:        char                    *sc_chroot;
proc.c: if (p->p_chroot != NULL)
proc.c:         root = p->p_chroot;
proc.c: if (chroot(root) == -1)
proc.c:         fatal("%s: chroot", __func__);

あれ、それっぽい名前は出てくるけど、移動先がrootってどゆ事? 長くなりそうなので、次回に続く。

libevent

そして、こちらが予習資料です。使ってますからねぇ。

libeventの使い方

C言語 libeventの使い方 tailを作ってみる

複数の event_base を使う場合

これが有ったから、node.jsが進歩したんかな。オリジナルの考え方はUnixに実装されてるシグナルか。更に遡ると、CPUの割り込み処理系か。 って、事は分かったぞ。