OpenBSD httpd (3)

先週の半ば、女房からユーチューブが映らないと主訴を受けた。オイラーに聞かれたって、 そんなん知らんわと言うと、悶着が起きそうなので試してみた。

依頼は受け取りましたが、返事出来ませんとか言われたかな。動画が表示されるべく所が、真っ暗。古いipadなんで、新種の動画は見れなくなったか? それとも、動画表示機構がいかれたか。

自分で出来る対策は、ipadのコールドスタートぐらいだな。やってみたけど、やはり動画だけが 表示されない。こういう場合は、ツイターを見るのが一番だろうけど、生憎オペレーションを知らない原人です。yahooのリアルタイム検索で、youtube down って検索したら、阿鼻叫喚な状態になってたよ。ユーチューブが映らないってね。

これで一安心。おもむろに威厳をもって女房に宣告。ユーチューブのサーバーがおかしくなって ますから、時間をおいてからアクセスしてみてください。

ユーチューブが映らないと、 YouTubeの世界的ダウンにより人気ポルノサイト「Pornhub」へのトラフィックが急増していたことが判明したそうですよ。みんな、ネット中毒だなあ。

稀有な現場に居合わせた人よりでした。ああ、国民放送局へ投稿するの忘れてた。 確か、スクープBOXとか言うやつだったな。

tcsh

前回の最後に出来心で、tcshの動きをfstatしちゃった。あの時は、tcshの入力にしか注目しなかったけど、出力はどう扱われるのだろう? 標準出力を次の段のコマンドが受け取ってくれて、入力が有るまでブロックさせたい。

俄かに都合の良いコマンドを思い付かなかったので、下記をでっちあげた。

#include <stdio.h>

int main(){
           char buf[1024];

           for (;;){
               fgets(buf, sizeof(buf), stdin);
               printf("From stdin: %s", buf);
           }
           return (0);
}

最近はC語でものを考えるようになったので、実験プログラムもC語で書く大胆な事をしてます。 なんせ、C語は透き通った言語ですからね。

sakae@fb:/tmp % cat - | tcsh | ./a.out
pwd
From stdin: /tmp
ls
From stdin: a.out
From stdin: rpl.c
From stdin: tmux-1001

まずは、動作確認。作ったアプリは、ちゃんと動いています。これ、replのeが抜けた版です。 evalは、tcshに押し付けてます。

sakae@fb:~ % fstat | grep pipe
 :
sakae    a.out        729    0* pipe fffff80003af2be0 <-> fffff80003af2d48 0 rw
sakae    tcsh         728   16* pipe fffff80003af7000 <-> fffff80003af7168 0 rw
sakae    tcsh         728   17* pipe fffff80003af2d48 <-> fffff80003af2be0 0 rw
sakae    tcsh         728   19* pipe fffff80003af7000 <-> fffff80003af7168 0 rw
sakae    cat          727    1* pipe fffff80003af7168 <-> fffff80003af7000 0 rw

FreeBSDのfstatは、情報過多過ぎるきらいがあるので(lsofは情報FAT過ぎて、横にはみ出してる)、pipeだけを抜き出しました。何段もパイプ接続した場合、最終段からプロセスが起動されます。それから、stdin/outと言っても、OS的にはR/W出来る代物なんだな。これ、以前、login_tty()だかで、調べたよ。別の確度から再確認出来ましたな。

sakae@fb:~ % fstat -p 728
USER     CMD          PID   FD MOUNT      INUM MODE         SZ|DV R/W
sakae    tcsh         728 text /        321059 -r-xr-xr-x  427888  r
sakae    tcsh         728 ctty /dev         84 crw--w----   pts/1 rw
sakae    tcsh         728   wd /tmp          2 drwxrwxrwt     448  r
sakae    tcsh         728 root /             2 drwxr-xr-x    1024  r
sakae    tcsh         728   16* pipe fffff80003af7000 <-> fffff80003af7168 0 rw
sakae    tcsh         728   17* pipe fffff80003af2d48 <-> fffff80003af2be0 0 rw
sakae    tcsh         728   18 /dev         84 crw--w----   pts/1 rw
sakae    tcsh         728   19* pipe fffff80003af7000 <-> fffff80003af7168 0 rw

そしてこちらは、tcshだけをzoomしたもの。fd=18が、stderrでしょう。fd=19って、非常用の入力なのかなあ? 4000行を超えるtcshのマニュアルを精査する元気は有りません。ご存知の方がいらしたら、こっそり教えてください。

OpenBSDだと下記のようになった。

ob6$ cat - | sh | ./a.out
pwd
From stdin: /tmp
USER     CMD          PID   FD MOUNT        INUM  MODE         R/W    SZ|DV
sakae    a.out      62584   wd /tmp            2  drwxrwxrwt     r      512
sakae    a.out      62584    0 pipe 0x0 state: R
sakae    a.out      62584    1 /          889403  crw--w----    rw    ttyp1
sakae    a.out      62584    2 /          889403  crw--w----    rw    ttyp1
sakae    sh         37063   wd /tmp            2  drwxrwxrwt     r      512
sakae    sh         37063    0 pipe 0x0 state: R
sakae    sh         37063    1 pipe 0x0 state:
sakae    sh         37063    2 /          889403  crw--w----    rw    ttyp1
sakae    cat         7554   wd /tmp            2  drwxrwxrwt     r      512
sakae    cat         7554    0 /          889403  crw--w----    rw    ttyp1
sakae    cat         7554    1 pipe 0x0 state:
sakae    cat         7554    2 /          889403  crw--w----    rw    ttyp1

fstatは、BSD系OSに搭載されてる、節度あるlsofだ。fstatのお仲間に、fuserってコマンドがある。fstatのソースツリーに同梱されてて知った。(いや、忘れていたのが思い出されたのだ)

このfuserは、リナにも有る。主として、リソースを使ってるプロセスを見つけ出す(連携して、プロセスを殺す)のに使う。 世間一般的に言うと、地上げ屋だな。その土地誰の持ち物だ? マンション建てるに邪魔なんで、持ち主をヒットしてこい。こういう時に便利です。

[sakae@cent tmp]$ ps -ejH
  :
 1689  1689  1689 pts/1    00:00:00     bash
 1851  1851  1689 pts/1    00:00:00       cat
 1852  1851  1689 pts/1    00:00:00       sh
 1853  1851  1689 pts/1    00:00:00       a.out

上の実験で使ったパイプラインをCentOSにも施設してみました。この状態で、/tmp/a.outを どのプロセスが使用しているか確認します。

[sakae@cent tmp]$ fuser /tmp/a.out
/tmp/a.out:           1853e

コマンドぐらいなら、psで検出出来るけど、ライブラリーとかになると、普通はお手上げ。 そんな時、fuserは威力を発揮します。

[sakae@cent tmp]$ ldd a.out
        linux-vdso.so.1 =>  (0x00007ffe419ab000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f5f0a8da000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f5f0aca7000)
[sakae@cent tmp]$ fuser /lib64/ld-linux-x86-64.so.2
/usr/lib64/ld-2.17.so:  1620m  1686m  1688m  1689m  1851m  1852m  1853m  1854m

a.outに内蔵されてるライブラリーをリストアップ。ld-linux-x86-64.so.2をどのプロセスが使ってるか、確認。ちゃんと、a.outのプロセスが出てきました。正に、地の果てまで追い詰める、すごい(怖い)ツールです。

なお、プロセス名の末尾に付いてる、eとかmの意味は、

              c      current directory.
              e      executable being run.
              f      open file.  f is omitted in default display mode.
              F      open file for writing.  F is omitted in  default  display
                     mode.
              r      root directory.
              m      mmap'ed file or shared library.

だよと、fuser(1)に、説明がありました。fuserを使って、プロセスをどうやって殺すかは、取り扱い説明書を熟読されたし。きっと、役に立つ場面があるはずです。ご用命をお待ち申し上げております。最近のヤーさんは、礼儀正しいです。

WebDAV

随分と道草を喰ってしまったので、本道へ戻る。OpenBSDのhttpd。ソースが随分あるので、まずはヘッダーファイルを眺めるか。http.hってのが、サポートしてるプロトコルとかを定義してるんだろう。最低限のWebサーバーと言うけど、どんなの?

enum httpmethod {
        HTTP_METHOD_NONE        = 0,

        /* HTTP/1.1, RFC 7231 */
        HTTP_METHOD_GET,
        HTTP_METHOD_HEAD,
        HTTP_METHOD_POST,
         :
        /* WebDAV, RFC 4918 */
        HTTP_METHOD_PROPFIND,
        HTTP_METHOD_PROPPATCH,
        HTTP_METHOD_MKCOL,
        HTTP_METHOD_COPY,
        HTTP_METHOD_MOVE,
        HTTP_METHOD_LOCK,
        HTTP_METHOD_UNLOCK,

        /* WebDAV Versioning Extension, RFC 3253 */
        HTTP_METHOD_VERSION_CONTROL,
        HTTP_METHOD_REPORT,
         :

HTTP/1.1に混じって、WebDAVなんてのが顔を出しているぞ。なんじゃいWebDAVって? 名前は聞いた記憶があるけど。。。

OpenBSDでのwebdavは基本的なものだけをサポートしたらしい。正式には、こんなに有る。

sakae@fb:~ % rfc -k webdav | egrep '^[0-9]'
2518 HTTP Extensions for Distributed Authoring -- WEBDAV. Y. Goland, E.
3253 Versioning Extensions to WebDAV (Web Distributed Authoring and
3648 Web Distributed Authoring and Versioning (WebDAV) Ordered
3744 Web Distributed Authoring and Versioning (WebDAV) Access Control
4316 Datatypes for Web Distributed Authoring and Versioning (WebDAV)
4437 Web Distributed Authoring and Versioning (WebDAV) Redirect Reference
4709 Mounting Web Distributed Authoring and Versioning (WebDAV) Servers.
4791 Calendaring Extensions to WebDAV (CalDAV). C. Daboo, B.
4918 HTTP Extensions for Web Distributed Authoring and Versioning
5323 Web Distributed Authoring and Versioning (WebDAV) SEARCH. J.
5397 WebDAV Current Principal Extension. W. Sanchez, C. Daboo. December
5689 Extended MKCOL for Web Distributed Authoring and Versioning
5842 Binding Extensions to Web Distributed Authoring and Versioning
5995 Using POST to Add Members to Web Distributed Authoring and
6352 CardDAV: vCard Extensions to Web Distributed Authoring and
6578 Collection Synchronization for Web Distributed Authoring and
6764 Locating Services for Calendaring Extensions to WebDAV (CalDAV) and
7809 Calendaring Extensions to WebDAV (CalDAV): Time Zones by Reference.
8144 Use of the Prefer Header Field in Web Distributed Authoring and

WebDAV on wiki ftpの代替プロトコルとな。ただ、http(s)に載せてるので、大容量の転送時は、不安定かつパフォーマンスが出ない事が有るとな。提唱したのがM$って、産婆だけじゃ喰っていけないから、Web業界にも、くさびを打ち込んでおきたいって魂胆なんだな。

WebDAVとは?その機能と使い方 で、より実用的な実例が出てた。オペレーションはWindows10で出来ればよい。何とかドライブの代替品と考えると、対向するサーバーの容量は無限大が望ましい。まてまて、オイラーのテキストだけのページだと、年間3Mにも満たないぞ。仮に10G使えるとしても、ほぼ無限大だ。余った容量を、各種バックアップに使えばいいのか。

レンタルサーバーの選び方と基礎知識 そして、こちらもWebDAVについて解説してたけど、もっと一般的にサーバーの選び方が出てた。 ロリポップって所でWebDAVしてるのか。Pageの最後の所にレンタルサーバーの評価表が有るな。

このhttpdシリーズを始めた理由が、無料の長屋を追い出されるって危機感からだった。 ようやく、ここに来て具体的な所と結びつき出したな。

WebDAV 技術仕様徹底解説 こういうのを見ると、OpenBSD httpdで、気軽に試してみたくなる。 telnetで叩けばいいかと思ってたら、現代風に curlコマンドでWebDAVサーバアクセスをやれ、とな。

ftpの代わりって事は、ユーザー認証も必要だろうし、特別なエリアを割り当てる必要がありそう。httpd.conf(5)とかを見ても、それらしい記述は無い。

みんなどうしてる?って聞いてみても、Apacheでなんたらかんたらの記事ばかり。唯一、WebDAVで引っかかったのは、 www/nextcloud and httpd.conf subdomain configなんだけど、なんだかなあの記事である。

エラーになるのを承知で、

ob6$ curl -sv --url http://localhost/index.html -X PROPFIND
  :
> PROPFIND /index.html HTTP/1.1
> Host: localhost
> User-Agent: curl/7.59.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 405 Method Not Allowed

httpd.confに書いてなくても、ソースにヒントが隠されているかも知れないと思って、 parse.yを調べてみたけど、何も無かったぞ。(parse.yって、あの人が大好きだったな。) 大体、apacheだとDAVって言うキーワードで、WebDAV機能がonになるんだけどな。かすりもしないよ。

PROPFINDで、ソースファイルを検索すると、server_http.cがヒットした。 POST/PUTと同列に扱われている。このあたりにBPを置いてみるかな。これが有るのは、server_read_httpの中。

ob6# ps awx
89417 ??  Isp     0:00.01 a.out: server (a.out)
30733 ??  Isp     0:00.01 ./a.out
79151 ??  Isp     0:00.01 a.out: logger (a.out)
ob6# cgdb -q a.out 89417

こんな具合に、働き蜂の方を指定してgdbを起動し、BPを置いたよ。

(gdb) bt
#0  server_read_http (bev=0x1effc4f44200, arg=0x1effb4675800) at server_http.c:300
#1  0x00001effc343df50 in bufferevent_readcb (fd=<optimized out>, event=<optimized out>, arg=0x1effc4f44200) at /usr/src/lib/libevent/evbuffer.c:140
#2  0x00001effc343b361 in event_process_active (base=0x1effb467a400) at /usr/src/lib/libevent/event.c:350
#3  event_base_loop (base=0x1effb467a400, flags=0) at /usr/src/lib/libevent/event.c:502
#4  0x00001efd09d154b7 in proc_run (ps=0x1effb4675000, p=0x1efd09f34020 <procs>, procs=0x1efd09f34140 <procs>, nproc=2, run=0x1efd09d15af0 <server_init>, arg=0x0) at proc.c:594
#5  0x00001efd09d15ad8 in server (ps=0x1effb4675000, p=0x1efd09f34020 <procs>) at server.c:87
#6  0x00001efd09d14047 in proc_init (ps=0x1effb4675000, procs=0x1efd09f34020 <procs>, nproc=2, argc=5, argv=0x7f7ffffed668, proc_id=PROC_SERVER) at proc.c:249
#7  0x00001efd09d0b4ca in main (argc=0, argv=0x7f7ffffed668) at httpd.c:218

クライアントから送られてきたヘッダーの最初のワードを見て、それが有効なメソッドがどうか判定してる。 ここを無事に通過出来ると、ヘッダーの解析が始まる。いきなりgdbで追うと、流れがわからんわ。gdbで流れの目星をつけてから、ソースを見るのが良さそうです。

responseは、server_fileの中で、server_fcgi を呼ぶか(cgiの場合)、そのまま、そこで処理するか決定してる。server_fcgiは、httpd.confの設定で、socketの冒頭に :1234とか設定すると、AF_INET が使えるみたい。

WebDAVで、エラーになるのは、やっぱりサーバー側の設定がきちんと出来ていないせいだろうな。認証だと、htpasswd(1)で、認証ファイルを作って、それを/var/www/htdocs/my.domain/に置く。

/etc/httpd.conf に追加

    location "/safe/*" {
        authenticate MyRealm with "/htdocs/my.domain/.htpasswd"
    }
    location "/.htpasswd" {
        # don't let people download the .htpasswd file
        block return 403
    }
ob6$ w3m http://localhost/safe/index.html
HTTP/1.0 401 Unauthorized
Username for MyRealm: sakae
Password for MyRealm: ************

これで、ダイジェスト認証付の金庫が出来たな。apacheの時代にベーシック認証で、同じ事をやった覚えがあるぞ。なんだか、それより簡単に思えるな。

WebDAVどう動くはず?

ネットを検索しても、WebDAVの為のOpenBSD httpdの例は出てこない。でも、上でちょい見した、/cgi-bin/now をアクセスする例を追っていたら、server_file.c/server_file(...)の冒頭付近に、

        if (srv_conf->flags & SRVFLAG_FCGI)
                return (server_fcgi(env, clt));

こんなコードが有って、fcgiの起動へと導かれていた。これの判定で、sev_conf->flagsが、現在サーバーがサポート出来る機能(リクエストヘッダーにより、動的に変わる)。右辺は、それを抽出するフラグだと分かる。ならば、この右辺に定義されてる群れに、WebDAVを許す定義が有るんではなかろうか?

話はちょっと逸れるけど、Webサーバーへのリクエストヘッダーとか良く聞く言葉だ。今までは素直にヘッダーって理解してたけど、違う見方も出来るね。

リクエストヘッダーの冒頭には、GET /path ... って並んでる。htdocs内のファイルをgetするのも、/cgi-bin の下のコマンドを起動するのも、同じメソッドのgetが使われる。それの区別をしてるのは、pathの違いしかない。

これって、コマンドの引数じゃん。そう思って次の行とかを見ると、xxx: yyyって形式が並んでいて、最後は何も有りませんの空行だ。そう、これって、コマンドに対するオプションを、名前: 値って具合に並べた、引数行の連なりだ。C語で言う、argv[]か。いや、名前: 値って事からすれば、env配列だな。よーく分かりましたよ。

で、話は戻って、SRVFLAG_FCGIは、httpd.hに定義されてる。

#define SRVFLAG_INDEX           0x00000001
#define SRVFLAG_NO_INDEX        0x00000002
#define SRVFLAG_AUTO_INDEX      0x00000004
#define SRVFLAG_NO_AUTO_INDEX   0x00000008
#define SRVFLAG_ROOT            0x00000010
#define SRVFLAG_LOCATION        0x00000020
#define SRVFLAG_FCGI            0x00000040
#define SRVFLAG_NO_FCGI         0x00000080
  :

ここにDAVとかが出てこなければ、WevDAV関係は、未実装って事だ。かすりしなかったね。 未実装間違い無し、残念無念でした。OpenBSD 6.4 が、つい最近リリースされたけど、ちゃんとサポートされてるかなあ? 6.4の更新情報には、

httpd(8) now supports client certificate authentication.

こんな案内があったけど、正直期待薄。6.4に上げるのは、急がなくてもいいか。

今思い付いた。WebDAV用のエラーコードがちゃんとソースに散りばめられているか? ヘッダーだけの張りぼてじゃ駄目よ。どんなエラーがDAV用に新設されてるかと言うと、 102,207.422.423.424,507とからしい。これも、かすらなかったぞ。

でも、最後の悪あがきをしておく。

nginx

どうもOpenBSD httpdのconfは、nginx流っぽいので、参考に調べてみた。

nginxでWebDAV (nginx-dav-ext-moduleはなし)

nginx で WebDAVを使ってファイル共有してみる

nginx webdavの使い方をまとめる

コマンドラインツールでアップロードする場合はcurlが便利。

    $ curl --upload somefile https://example.com/foo/

パスワードをファイルに保管しておきたいので、こんな感じ。

    $ touch upload_url.private
    $ chmod 600 upload_url.private
    $ vi upload_url.private
    https://user:password@example.com/foo/
    $

    $ curl --upload somefile `cat upload_url.private`

--verboseオプションを付けると分かりやすい。

    $ curl --verbose --upload somefile `cat upload_url.private`

ブラウザーで認証付のURLへアクセスする方法かな。これも、まだら模様に覚えている。

ob6$ w3m http://sakae:*********@localhost/safe/index.html
ob6$ curl -sv http://sakae:*********@localhost/safe/index.html
  :
* Connected to localhost (127.0.0.1) port 80 (#0)
* Server auth using Basic with user 'sakae'
> GET /safe/index.html HTTP/1.1
> Host: localhost
> Authorization: Basic ccccccaaaaaa3333333111111==

あれ? すっかりダイジェスト認証かと思ったら、昔ながらの方法だった。危ないな。 bcrypt(Blowfish crypt)を、htpasswdが採用してるから大丈夫と思っていたんだけどな。

ob6$ cat /var/www/htdocs/my.domain/.htpasswd
sakae:$2b$10$dm5tB.VltiiJHN4sssssqugWwQtinAhvZCoXtfffffy4pu69k/HZm

httpd at golang

C語に疲れたら、現代風C語である、golangに足を延ばすのが良いと思われる。http関連がどうなっているか、覗いてみよう。まあ、母体がググルさんですから、充実してるとは思いますがね。

Go言語でhttpサーバーを立ち上げてHello Worldをする

Go言語によるwebアプリの作り方

nginx + Go-FCGI で Web アプリを動かす

GO で WebDAV の PROPFIND を使ってファイルの URL 一覧をとってみる

ほーら、簡単そう。

httpd at gauche

Gauche-makiki