sshd_configのデフォルト値がいったいどこから来ているのかが知りたくてOpenSSHのソースを読んでみた

# 調べてみようと思ったきっかけ

最近、[SSHエージェントの転送機能](https://man.openbsd.org/ssh-agent)を利用している際に気になったことがありました。
SSHエージェントの転送というのは、以下のようなコマンドの実行によりローカルにある秘密鍵を接続先のサーバでも利用可能にするための機能のことです。

```
$ eval `ssh-agent`
Agent pid プロセスID
$ ssh-add 秘密鍵
$ ssh -A ユーザ名@接続先サーバ
```

このSSHエージェントの転送を可能にするには接続先サーバの `/etc/ssh/sshd_config` で「AllowAgentForwarding」を許可しておく必要があります。

```:/etc/ssh/sshd_config
AllowAgentForwarding yes
```

なるほど、と思い。接続先サーバの `/etc/ssh/sshd_config` を確認してみると、「AllowAgentForwarding」の設定項目はコメントアウトされていました。

```:/etc/ssh/sshd_config
$ sudo cat /etc/ssh/sshd_config |grep 'AllowAgentForwarding'
#AllowAgentForwarding yes
```

sshdコマンドの「-T」オプション（拡張テストモード）を利用すると、各設定項目の値が表示できます。

```:SSHD(8)
     -T      Extended test mode.  Check the validity of the configuration file, output the effective configuration
             to stdout and then exit.  Optionally, Match rules may be applied by specifying the connection parame‐
             ters using one or more -C options.
```

なので、sshdコマンドの「-T」オプションを利用して「AllowAgentForwarding」を確認してみると「yes」になっていました。
恐らくこの設定値が「yes」なので `/etc/ssh/sshd_config` 内でコメントアウトされていてもSSH鍵の転送が問題なく実行できるのだと思います。

```
$ sudo sshd -T |grep -i 'AllowAgentForwarding'
allowagentforwarding yes
```

しかし、この値はどこからやって来たのだろう？という疑問が湧き、sshdの中身が気になったのでソースを読んでみることにしました。（普段はPHPerなのでC言語わかんないけど）

# ソースを読む、その前に

sshdのソースを読む前に、sshdがどのパッケージに含まれているコマンドなのかを知る必要があるでしょう。

まず、sshdコマンドのフルパスを確認します。

```
$ which sshd
/usr/sbin/sshd
```

sshdコマンドのフルパスが `/usr/sbin/sshd` だとわかったので、この情報を元にどのパッケージに含まれているかを確認します。

```:Redhat系OSの場合
$ rpm -qf /usr/sbin/sshd
openssh-server-8.0p1-6.el8_4.2.x86_64
```

```:Debian系OSの場合
$ dpkg -S /usr/sbin/sshd
openssh-server: /usr/sbin/sshd
$ dpkg -l |grep openssh-server
ii  openssh-server                 1:8.2p1-4ubuntu0.2                amd64        secure shell (SSH) server, for secure access from remote machines
```

`/usr/sbin/sshd` は「openssh-server」というパッケージに含まれるコマンドであるということがわかりました。
つまり、sshdコマンドを知るにはOpenSSHのソースを解析していくとよさそうです。

# OpenSSHのソースファイルを取得

[OpenSSHのwebサイト](https://www.openssh.com/)にアクセスし、左メニューの「For other systems」の「[Linux](https://www.openssh.com/portable.html)」をクリックすると、ページ内にダウンロードリンクが表示されます。

`rpm -qf` で確認した内容と同じバージョンの「openssh-8.0p1.tar.gz」をダウンロードします。
※今回利用した接続先のサーバはCentOS8なのでRPMコマンドを前提として説明を進めます。

「openssh-8.0p1.tar.gz」のリンクをクリックしてもよいですし、curlコマンドでもよいでしょう。

```
$ curl -LO https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-8.0p1.tar.gz 
```

ダウンロードが完了したら展開しておきます。

```
$ tar zxvf openssh-8.0p1.tar.gz
$ cd openssh-8.0p1
```

これでソースファイルを読む準備が整いました。

# OpenSSHのソースファイルを読んでみる

まずは今回のキーワードである「AllowAgentForwarding」を `grep` で検索してみると、いきなりそれっぽいのが表示されました。

```
$ grep -n 'AllowAgentForwarding' * -r
servconf.c:503:	sUsePrivilegeSeparation, sAllowAgentForwarding,
servconf.c:609:	{ "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL },
servconf.c:1658:	case sAllowAgentForwarding:
servconf.c:2603:	dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
sshd_config:84:#AllowAgentForwarding yes
sshd_config.0:36:     AllowAgentForwarding
sshd_config.0:656:             AllowAgentForwarding, AllowGroups, AllowStreamLocalForwarding,
sshd_config.5:100:.It Cm AllowAgentForwarding
sshd_config.5:1110:.Cm AllowAgentForwarding ,
```

`sshd_config` の検索結果を見てみると84行目の「#AllowAgentForwarding yes」という内容からわかるように、設定ファイルの `/etc/ssh/sshd_config` そのもののようです。
今回知りたいことは `sshd_config` についてではありません。このことから `sshd_config.0` 、 `sshd_config.5` についても一旦無視することとし、まずは `servconf.c` の中身を見ていきます。

`servconf.c` に対して「AllowAgentForwarding」のgrep検索を実行すると、以下の行が該当しました。

```
$ grep -n 'AllowAgentForwarding' servconf.c
503:	sUsePrivilegeSeparation, sAllowAgentForwarding,
609:	{ "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL },
1658:	case sAllowAgentForwarding:
2603:	dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
```

[`servconf.c` の2603行目の「dump_cfg_fmtint」](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2603)を見てピンと来ました。前後の行を見ると以下のようになっています。

```:servconf.c#L2601-L2605
2601         dump_cfg_fmtint(sUseDNS, o->use_dns);
2602         dump_cfg_fmtint(sAllowTcpForwarding, o->allow_tcp_forwarding);
2603         dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
2604         dump_cfg_fmtint(sDisableForwarding, o->disable_forwarding);
2605         dump_cfg_fmtint(sAllowStreamLocalForwarding, o->allow_streamlocal_forwarding);
```

この表示順は `sshd -T` を実行した際に表示される設定項目の順番と一致しているのです。

```
$ sudo sshd -T
port 22
addressfamily any
・
・
usedns no
allowtcpforwarding yes
allowagentforwarding yes
disableforwarding no
allowstreamlocalforwarding yes
・
・
```

つまり、「dump_cfg_fmtint」の周辺を見ていくと「allowagentforwarding yes」の謎が解けそうな気がしてきました。

次に「dump_cfg_fmtint」の箇所が `sshd -T` の実行時に実際に呼び出されているのかを確認してみます。

「AllowAgentForwarding」に関係してそうな[2603行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2603)は[「dump_config」という関数](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2535-L2708)の中で実行されている内容だとわかりました。

```:servconf.c#L2534-L2604
2534 void
2535 dump_config(ServerOptions *o)
2536 {
2537         char *s;
2538         u_int i;
・
・
2602         dump_cfg_fmtint(sAllowTcpForwarding, o->allow_tcp_forwarding);
2603         dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
2604         dump_cfg_fmtint(sDisableForwarding, o->disable_forwarding);
```

`servconf.c` の中で「dump_config」を検索してみると該当箇所は見つかりませんでした。
つまり、「dump_config」は別のファイルから呼び出される関数だということが予想されます。
ということは、「dump_config」はsshdのソースから呼び出されているのではないかという仮説が立てられます。

なので、今度は「dump_config」というキーワードを元に `grep` で検索してみます。
すると、[本命の `sshd.c` の1847行目が該当しました](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1847)ので、今度は `sshd.c` を見ていきましょう。

```
$ grep -n 'dump_config' * -r
servconf.c:2535:dump_config(ServerOptions *o)
servconf.h:274:void	 dump_config(ServerOptions *);
sshd.c:1847:		dump_config(&options);
```

「dump_config」が呼び出されている前後を見ると、[「test_flag」という変数が1より大きい場合に「dump_config」が呼び出される](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1839-L1848)ようです。

```:sshd.c#L1839-L1848
1839         if (test_flag > 1) {
1840                 /*
1841                  * If no connection info was provided by -C then use
1842                  * use a blank one that will cause no predicate to match.
1843                  */
1844                 if (connection_info == NULL)
1845                         connection_info = get_connection_info(ssh, 0, 0);
1846                 parse_server_match_config(&options, connection_info);
1847                 dump_config(&options);
1848         }
```

変数「test_flag」の定義元を見てみると、予想が当たっていたことがわかる説明が書いてありました。
[「if (test_flag > 1)」というのはsshdコマンドの「-T」オプションのことを指していました。](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L148-L154)

```:sshd.c#L148-L154
 148 /*
 149  * Indicating that the daemon should only test the configuration and keys.
 150  * If test_flag > 1 ("-T" flag), then sshd will also dump the effective
 151  * configuration, optionally using connection information provided by the
 152  * "-C" flag.
 153  */
 154 static int test_flag = 0;
```

ちなみに、 `sshd -T` を実行した場合、変数「test_flag」に何がセットされるかについては、[引数毎のswitch文](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1468-L1576)が記述されていて、[その中で「2」がセットされています。](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1548-L1550)これにより、 `if (test_flag > 1)` の判定文が真になり、「dump_config」関数が呼び出されるという仕組みのようです。

```sshd.c#L1468-L1550
1468         /* Parse command-line arguments. */
1469         while ((opt = getopt(ac, av,
1470             "C:E:b:c:f:g:h:k:o:p:u:46DQRTdeiqrt")) != -1) {
1471                 switch (opt) {
1472                 case '4':
1473                         options.address_family = AF_INET;
1474                         break;
1475                 case '6':
1476                         options.address_family = AF_INET6;
1477                         break;
・
・
1548                 case 'T':
1549                         test_flag = 2;
1550                         break;
```

次に `sshd -T` を実行した際に `servconf.c` の「dump_config」関数で、どのように「allowagentforwarding yes」と出力しているかを見ていきます。

先程見ていた[`servconf.c` の2603行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2603)に再び着目してみます。

```:servconf.c#L2603
2603         dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
```

`sshd -T` の実行時に2603行目に到達し、その出力結果が恐らく以下のような結果になっているはずですので、「dump_cfg_fmtint」関数の定義を見てみます。

```
allowagentforwarding yes
```

[「dump_cfg_fmtint」関数](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2458-L2462)の中の　`printf("%s %s\n"` の部分で2つの文字列を出力してるようです。 `lookup_opcode_name(code), fmt_intarg(code, val));` の部分でキー名と値を取得していることが予想できます。

```:servconf.c#L2458-L2462
2458 static void
2459 dump_cfg_fmtint(ServerOpCodes code, int val)
2460 {
2461         printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val));
2462 }
```

[「lookup_opcode_name」関数](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L677-L686)の定義を見てみると、 `keywords` という配列のようなものを走査して、 `keywords[i].opcode` と引数の `code` が一致したら `keywords[i].name` を返却しています。

```:servconf.c#L677-L686
 677 static const char *
 678 lookup_opcode_name(ServerOpCodes code)
 679 {
 680         u_int i;
 681 
 682         for (i = 0; keywords[i].name != NULL; i++)
 683                 if (keywords[i].opcode == code)
 684                         return(keywords[i].name);
 685         return "UNKNOWN";
 686 }
```

次は `keywords` の定義を見ていきましょう。
`struct` という記述があるので `keywords` はどうやら構造体と呼ばれるもののようです。
この構造体の定義の中に事前に調べておいた[609行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L609)の内容が含まれていました。

```:servconf.c#L520-L662
 520 /* Textual representation of the tokens. */
 521 static struct {
 522         const char *name;
 523         ServerOpCodes opcode;
 524         u_int flags;
 525 } keywords[] = {
 ・
 ・
 608         { "allowtcpforwarding", sAllowTcpForwarding, SSHCFG_ALL },
 609         { "allowagentforwarding", sAllowAgentForwarding, SSHCFG_ALL },
 610         { "allowusers", sAllowUsers, SSHCFG_ALL },
 ・
 ・
 662 };
```

ということは[2603行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2603)で「dump_cfg_fmtint」関数の第一引数に「sAllowAgentForwarding」を渡していますが、これにより「lookup_opcode_name」関数を経由して、「"allowagentforwarding"」が出力されることがわかりました。

```:servconf.c#L2603
2603         dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
```

残るは `sshd -T` の実行時に出力される「allowagentforwarding yes」の「yes」の部分がどのように出力されるかを突き止めるだけです。
[2603行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2603)の「dump_cfg_fmtint」関数の第2引数は「o->allow_agent_forwarding」となっていました。
第2引数の「o->allow_agent_forwarding」の元となっている変数「o」の出どころですが、実はちょっと前に登場していました。
`sshd.c` から呼び出されている「dump_config」関数の第1引数です。

```:servconf.c#L2535
2535 dump_config(ServerOptions *o)
```

`sshd.c` の中で「dump_config」関数を呼び出している箇所は[「main」関数](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1413-L2198)に含まれています。
「dump_config」関数の引数で渡している変数optionsは、「main」関数の[1466行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1466)の「initialize_server_options」関数で初期化されています。

```:sshd.c#L1413-L2198
1413 /*
1414  * Main program for the daemon.
1415  */
1416 int
1417 main(int ac, char **av)
1418 {
・
・
1465         /* Initialize configuration options to their default values. */
1466         initialize_server_options(&options);
・
・
1635         parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
1636             cfg, NULL);
1637 
1638         /* Fill in default values for those options not explicitly set. */
1639         fill_default_server_options(&options);
・
・
1839         if (test_flag > 1) {
1840                 /*
1841                  * If no connection info was provided by -C then use
1842                  * use a blank one that will cause no predicate to match.
1843                  */
1844                 if (connection_info == NULL)
1845                         connection_info = get_connection_info(ssh, 0, 0);
1846                 parse_server_match_config(&options, connection_info);
1847                 dump_config(&options);
1848         }
・
・
2197         exit(0);
2198 }
```

`servconf.c` の[「initialize_server_options」関数](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L79-L183)を見ると、[140行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L140)で「options->allow_agent_forwarding」に「-1」がセットされています。

```servconf.c#L7７-L183
  77 /* Initializes the server options to their default values. */
  78 
  79 void
  80 initialize_server_options(ServerOptions *options)
  81 {
  ・
  ・
 140         options->allow_agent_forwarding = -1;
  ・
  ・
 183 }
```

「initialize_server_options」関数を通ったあとに、[1635行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1635-L1636)の「parse_server_config」関数が呼ばれているんですが、ここでは「options->allow_agent_forwarding」の値に変化はないようなので、[1639行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/sshd.c#L1639)の「fill_default_server_options」関数を見ていきます。
「Fill in default values」というコメントから察するに、いかにもデフォルト値を設定してそうです。

```:sshd.c#L1465-L1639
1465         /* Initialize configuration options to their default values. */
1466         initialize_server_options(&options);
・
・
1635         parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
1636             cfg, NULL);
1637 
1638         /* Fill in default values for those options not explicitly set. */
1639         fill_default_server_options(&options);
```

「initialize_server_options」関数では「-1」をセットしていましたが、 `options->allow_agent_forwarding` が「-1」の場合は「1」で上書きしているようです。

```:servconf.c#L273-L473
 273 fill_default_server_options(ServerOptions *options)
 274 {
 ・
 ・
 380         if (options->allow_agent_forwarding == -1)
 381                 options->allow_agent_forwarding = 1;
 ・
 ・
 473 }
```

そして、ようやく `sshd.c` の「dump_config」関数を呼び出しているところに戻ってきました。
「dump_config」関数を呼び出す直前で「parse_server_match_config」を呼んでいますが、引数のoptionsの値に変化はなさそうだったのでスキップします。（parse_server_match_configのコードは少々複雑で読むのが若干つらかったというのが正直なところ）

```:sshd.c#L1839-L1848
1839         if (test_flag > 1) {
1840                 /*
1841                  * If no connection info was provided by -C then use
1842                  * use a blank one that will cause no predicate to match.
1843                  */
1844                 if (connection_info == NULL)
1845                         connection_info = get_connection_info(ssh, 0, 0);
1846                 parse_server_match_config(&options, connection_info);
1847                 dump_config(&options);
1848         }
```

「dump_config」関数の中で「AllowAgentForwarding」のデフォルト値を出力している処理の中身をもう一度見ていきましょう。

```:servconf.c#L2534-L2604
2534 void
2535 dump_config(ServerOptions *o)
2536 {
2537         char *s;
2538         u_int i;
・
・
2602         dump_cfg_fmtint(sAllowTcpForwarding, o->allow_tcp_forwarding);
2603         dump_cfg_fmtint(sAllowAgentForwarding, o->allow_agent_forwarding);
2604         dump_cfg_fmtint(sDisableForwarding, o->disable_forwarding);
```

「dump_cfg_fmtint」関数は、このような構成になっており、「lookup_opcode_name」関数で「"allowagentforwarding"」が出力されることまではわかっています。
なので、「fmt_intarg」関数を見ていきます。

```:servconf.c#L2458-L2462
2458 static void
2459 dump_cfg_fmtint(ServerOpCodes code, int val)
2460 {
2461         printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val));
2462 }
```

「fmt_intarg」関数の中の[2439行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2439)についに見つけました。「yes」の文字列です。
ここまでの処理で「o->allow_agent_forwarding」には「1」がセットされています。
[2419行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2419)のswitch〜caseを見ると、「sAllowAgentForwarding」の条件は定義されていませんので、defaultを通ります。
ということは、[2438行目](https://github.com/openssh/openssh-portable/blob/V_8_0_P1/servconf.c#L2438)の「case 1:」に該当し、「yes」の文字列がreturnされます。

```:servconf.c#L2414-L2444
2414 static const char *
2415 fmt_intarg(ServerOpCodes code, int val)
2416 {
2417         if (val == -1)
2418                 return "unset";
2419         switch (code) {
2420         case sAddressFamily:
2421                 return fmt_multistate_int(val, multistate_addressfamily);
2422         case sPermitRootLogin:
2423                 return fmt_multistate_int(val, multistate_permitrootlogin);
2424         case sGatewayPorts:
2425                 return fmt_multistate_int(val, multistate_gatewayports);
2426         case sCompression:
2427                 return fmt_multistate_int(val, multistate_compression);
2428         case sAllowTcpForwarding:
2429                 return fmt_multistate_int(val, multistate_tcpfwd);
2430         case sAllowStreamLocalForwarding:
2431                 return fmt_multistate_int(val, multistate_tcpfwd);
2432         case sFingerprintHash:
2433                 return ssh_digest_alg_name(val);
2434         default:
2435                 switch (val) {
2436                 case 0:
2437                         return "no";
2438                 case 1:
2439                         return "yes";
2440                 default:
2441                         return "UNKNOWN";
2442                 }
2443         }
2444 }
```

ここまで長い道のりでしたが、 `sshd -T` が実行されると `sshd.c` の「dump_config」関数が呼ばれ、それにより `servconf.c` の「dump_cfg_fmtint」関数を通り、以下のような出力結果が得られることがわかりました。

```
allowagentforwarding yes
```

# まとめ
ほんのちょっとした疑問からOpenSSHのソースを読みはじめてみましたが、慣れてない言語ということでわからないことも多く大変でした。
ですが、「この値はどこからやって来たのだろう？」という疑問の答えに到達することができたので非常に満足感が得られました。

アプリケーションエンジニアをやっていると、フレームワークやライブラリ、DBやwebサーバなどのミドルウェアなど様々なツールを利用することがあります。
そういった際にはドキュメントを見ることになると思います。利用方法について詳しく説明が書かれている場合もあれば、概要っぽく書かれていることもあり、時には誤りが書かれていたり、バージョンアップに追従していなくて内容が古くなっていることもあるでしょう。

そんな時に「よーし、いっちょソースを読んでやるか」という気持ちになるのか、「うぇー、ソース読まないとダメなのかー」という気持ちになるのか、問題の解決に向かうには前者の方が好ましいでしょう。

ということで、ふとしたきっかけで仕事では使ってない言語を読んでみるとなかなか楽しかったので、これからも気軽に知らない言語を読むクセをつけていきたいなと思いました。ちなみに、次にC言語を読むときは[gdb](https://ja.wikipedia.org/wiki/GNU%E3%83%87%E3%83%90%E3%83%83%E3%82%AC)の使い方を学ぼうと思います。

# おまけ

「`/etc/ssh/sshd_config`」でコメントアウトされている値について話してたら、「`man sshd_config`を実行して、知りたいキーワードを検索したらデフォルト値がどうなってるかはすぐにわかるんじゃないですか？」と言われちゃいました。

まず真っ先に「ドキュメントを読む」ということをやりましょうね。はい。ごめんなさい。
（いや、違うんだ。今回はそのデフォルト値がどこから来てるのかが知りたかったんだ。）

```:SSHD_CONFIG(5)
$ man sshd_config
SSHD_CONFIG(5)              BSD File Formats Manual             SSHD_CONFIG(5)

NAME
     sshd_config — OpenSSH SSH daemon configuration file
・
・
・
     AllowAgentForwarding
             Specifies whether ssh-agent(1) forwarding is permitted.  The
             default is yes.  Note that disabling agent forwarding does not
             improve security unless users are also denied shell access, as
             they can always install their own forwarders.
```