プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Roundcube の立ち上げ: docker イメージを作成(2/3)

前回の記事の続きです。今回は、今まで試した設定を含めた dockerfile を作成して、イメージを作ることで簡単に起動できるようにしたいと思います。

docker イメージを作成

いろいろと試した設定を毎回手作業でやるのは面倒なので、 docker イメージを作成したいと思います。なお、作業は NAS に SSH でログインして、適当な作業ディレクトリを作ってそこで行っています。

dockerfile の作成

いままで検討した設定を反映するように、 dockerfile を作成します。

FROM roundcube/roundcubemail:1.5.3-apache

RUN set -ex; \
    sed -i "s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/" /etc/ssl/openssl.cnf \
    ;
 
COPY ./config.docker.add_imap_conn_options.inc.php /var/roundcube/config/

config.docker.add_imap_conn_options.inc.php は、前の記事で書いた、 imap 接続時のオプション設定を書いたファイルです。

<?php

// IMAP socket context options
$config['imap_conn_options'] = [
  'ssl'         => [
    'verify_peer'  => true,
    'verify_depth' => 3,
  ],
];

// SMTP socket context options
$config['smtp_conn_options'] = [
  'ssl'         => [
    'verify_peer'  => true,
    'verify_depth' => 3,
  ],
];

/var/roundcube/config に設定ファイルを置いておくと読み込んでくれるということなので、ここに設定することにしました。

イメージを作成

作成した dockerfile を使ってイメージを作成してみます。

[ユーザ名@NAS名 myroundcube]$ docker build -t myroundcube .
Sending build context to Docker daemon  4.608kB
Step 1/3 : FROM roundcube/roundcubemail:1.5.3-apache
 ---> d2eac54d0366
Step 2/3 : RUN set -ex;     sed -i "s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/" /etc/ssl/openssl.cnf     ;
 ---> Running in 4176d8859682
+ sed -i s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/ /etc/ssl/openssl.cnf
Removing intermediate container 4176d8859682
 ---> 73ef52fbc915
Step 3/3 : COPY ./config.docker.add_imap_conn_options.inc.php /var/roundcube/config/
 ---> 664cbf63c985
Successfully built 664cbf63c985
Successfully tagged myroundcube:latest
[ユーザ名@NAS名 myroundcube]$ 

エラーなく、イメージが作成できました。

このイメージを使って、ちゃんと Container Station で起動するのか確認して、問題なく起動できればOKです。

(参考1)

roundcubemail-docker の github には、自分で使うプラグインをあらかじめインストールした docker image の作成方法が載っているので、ident_switch もこのようにしたいと思って下記のようにしてみました。

FROM roundcube/roundcubemail:1.5.3-apache

ENV COMPOSER_ALLOW_SUPERUSER=1

RUN set -ex; \
    apt update; \
    apt install -y --no-install-recommends \
        unzip \
    ; \
    sed -i "s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/" /etc/ssl/openssl.cnf \
    ; \
    composer \
      --working-dir=/usr/src/roundcubemail/ \
      --prefer-dist \
      --prefer-stable \
      --update-no-dev \
      --no-interaction \
      --optimize-autoloader \
      require \
        boressoft/ident_switch \
    ;
 
COPY ./config.docker.add_imap_conn_options.inc.php /var/roundcube/config/

しかし、このファイルで docker build を行うと、

[ユーザ名@NAS名 myroundcube]$ docker build -f ./dockerfile.ident_switch -t jmtest .
Sending build context to Docker daemon  4.608kB
Step 1/4 : FROM roundcube/roundcubemail:1.5.3-apache
 ---> d2eac54d0366
(略)
Setting up unzip (6.0-26+deb11u1) ...
Processing triggers for mailcap (3.69) ...
+ sed -i s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/ /etc/ssl/openssl.cnf
+ composer --working-dir=/usr/src/roundcubemail/ --prefer-dist --prefer-stable --update-no-dev --no-interaction --optimize-autoloader require boressoft/ident_switch
Info from https://repo.packagist.org: #StandWithUkraine
Using version ^4.4 for boressoft/ident_switch
./composer.json has been updated
Running composer update boressoft/ident_switch
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking boressoft/ident_switch (4.4.2)
Writing lock file
Installing dependencies from lock file
Package operations: 1 install, 0 updates, 0 removals
  - Downloading boressoft/ident_switch (4.4.2)
  - Installing boressoft/ident_switch (4.4.2): Extracting archive
Creating package config file
Running database initialization script for ident_switch
ERROR: SQLSTATE[HY000] [2002] No such file or directory
ERROR: Failed to connect to database
The command '/bin/sh -c set -ex;     apt update;     apt install -y --no-install-recommends         unzip     ;     sed -i "s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/" /etc/ssl/openssl.cnf     ;     composer       --working-dir=/usr/src/roundcubemail/       --prefer-dist       --prefer-stable       --update-no-dev       --no-interaction       --optimize-autoloader       require         boressoft/ident_switch     ;' returned a non-zero code: 1
[ユーザ名@NAS名 myroundcube]$ 

のように、エラーが出てしまいます。どうも、インストール時に、データベースにテーブルを作るようで、docker イメージを作るタイミングだと、当然データベースがないので、インストールしても失敗してしまいます。

なので、この部分は起動後に後から手作業でインストールすることにしました。

(参考2)

Roundcube 試用の際の記事でも触れましたが、最新版 Roundcube (1.6.2) を使おうとすると、 php-zip に関するエラーが出てきます。例えば、ログインに成功した際に、コンソールに下記のようなエラーが出てきています。

errors: <73b8bb66> PHP Error: php-zip extension is required for the zipdownload plugin in /var/www/html/plugins/zipdownload/zipdownload.php on line 36 (GET /?_task=mail&_token=EDMRG9or8UJXOvuYH2p7bFOjkqOQEeGP)

いろいろとネットを見てみると、 roundcube が元にしている php のイメージ(多分 8.1.21)が持っているバグっぽいような印象です。おおもとの debian bookworm でライブラリパスがいくつか変更になったのが遠因のようです。

これの解消方法がわからなかったのと、そこまで、最新版にこだわってないので、今回は、 1.5.3-apache を元にイメージを作成することにしました。

まとめ

次は、 この作成した docker イメージを元に、 Roundcube を実際に稼働させたいと思います。

Roundcube の立ち上げ:暗号化接続を有効化 (1/3)

あれこれ検討しましたが、こちらの記事でまとめたように、ブラウザベースのメールクライアントとして、 Roundcube を使うことにしました。

試用の際は、問題があったら避けるようにしたのですが、今度は試用ではないので、もうちょっとちゃんと対応して、しっかりと使えるようにしたいと思います。

まずは、メールサーバーに接続する際に暗号化を使うような設定をしたので、それについてメモっておきます。

なお、最終的に立ち上げる際の docker-compose.yml は、 Roundcube を試した際のものと基本的には同じです(コンテナ名、ポート番号が少し異なる)。また、 ident_switch を入れて、複数アカウントを登録して、複数の IMAP サーバーに接続できるようにします。

暗号化接続を有効化

まず、 IMAP や SMTP サーバーとの接続時に暗号化が有効にならないのを何とかしたいと思います(というか、メールサーバーによっては、 SSL での接続のみが許可されているので、これを有効にしないと話になりません)。

メールサーバー情報を設定する際に、サーバー名の先頭に

ssl://

を追加すると、 SSL/TLS での接続になるようです(デフォルトのサーバー設定および ident_switch での設定時とも)。

いまのところ、この状態でメールサーバー(mail.xxx.sakura.ne.jp)につないでみると

errors: <1494e41f> IMAP Error: Login failed for test@ドメイン名 against ssl://mail.xxx.sakura.ne.jp from 192.168.0.16. Could not connect to ssl://mail.xxx.sakura.ne.jp:993: Unknown reason in /var/www/html/program/lib/Roundcube/rcube_imap.php on line 211 (GET /?_task=mail&_action=list&_refresh=1&_layout=widescreen&_mbox=INBOX&_page=&_remote=1&_unlock=loading1690772721943&_=1690772721911)

のようなエラーが表示されます。で、これをネットをググってみると、どうも証明書関連の問題が原因のようです。

サーバー証明書の設定を確認

最初に見つけた、下記の記事を参考にいろいろと試してみました。

Could not connect to ssl://mail.ingentar.com:993: Unknown reason in /var/www/html/program/lib/Roundcube/rcube_imap.php on line 200 · Issue #7408 · roundcube/roundcubemail · GitHub

これを読むと、クライアント側でサーバー証明書を検証する際に、サーバー証明書が見つからないことが原因かもしれないと思われます。

なので、まずは現状を確認してみます。

[ユーザ名@NAS名 application]$ docker exec -it roundcubemailtest /bin/bash

として、コンテナに入って、証明書があることを確認します。

root@0c74afe74dd1:/var/www/html# apt list --installed                   
Listing... Done
(略)
ca-certificates/stable,now 20210119 all [installed]
(略)

ありますね。 php の設定も確認します。

root@0c74afe74dd1:/var/www/html# php --info
phpinfo()
PHP Version => 7.4.30

System => Linux 0c74afe74dd1 5.10.60-qnap #1 SMP Fri Jun 9 01:48:30 CST 2023 x86_64
Build Date => Jun 23 2022 08:44:46
(中略)
openssl

OpenSSL support => enabled
OpenSSL Library Version => OpenSSL 1.1.1n  15 Mar 2022
OpenSSL Header Version => OpenSSL 1.1.1n  15 Mar 2022
Openssl default config => /usr/lib/ssl/openssl.cnf

Directive => Local Value => Master Value
openssl.cafile => no value => no value
openssl.capath => no value => no value
(後略)

openssl.cafile および openssl.capath の設定が無いようです。設定が無い場合はどうなるのかな? さきほどの php --info の先頭に読み込んでいる php.ini に関する情報があるので、確認すると

root@0c74afe74dd1:/var/www/html# php --info
phpinfo()
PHP Version => 7.4.30

System => Linux 0c74afe74dd1 5.10.60-qnap #1 SMP Fri Jun 9 01:48:30 CST 2023 x86_64
Build Date => Jun 23 2022 08:44:46
Configure Command =>  './configure'  '--build=x86_64-linux-gnu' '--with-config-file-path=/usr/local/etc/php' '--with-config-file-scan-dir=/usr/local/etc/php/conf.d' '--enable-option-checking=fatal' '--with-mhash' '--with-pic' '--enable-ftp' '--enable-mbstring' '--enable-mysqlnd' '--with-password-argon2' '--with-sodium=shared' '--with-pdo-sqlite=/usr' '--with-sqlite3=/usr' '--with-curl' '--with-iconv' '--with-openssl' '--with-readline' '--with-zlib' '--disable-phpdbg' '--with-pear' '--with-libdir=lib/x86_64-linux-gnu' '--disable-cgi' '--with-apxs2' 'build_alias=x86_64-linux-gnu'
Server API => Command Line Interface
Virtual Directory Support => disabled
Configuration File (php.ini) Path => /usr/local/etc/php
Loaded Configuration File => (none)
Scan this dir for additional .ini files => /usr/local/etc/php/conf.d
Additional .ini files parsed => /usr/local/etc/php/conf.d/docker-php-ext-exif.ini,
/usr/local/etc/php/conf.d/docker-php-ext-gd.ini,
/usr/local/etc/php/conf.d/docker-php-ext-imagick.ini,
/usr/local/etc/php/conf.d/docker-php-ext-intl.ini,
/usr/local/etc/php/conf.d/docker-php-ext-ldap.ini,
/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini,
/usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini,
/usr/local/etc/php/conf.d/docker-php-ext-pdo_pgsql.ini,
/usr/local/etc/php/conf.d/docker-php-ext-pspell.ini,
/usr/local/etc/php/conf.d/docker-php-ext-redis.ini,
/usr/local/etc/php/conf.d/docker-php-ext-sodium.ini,
/usr/local/etc/php/conf.d/docker-php-ext-zip.ini,
/usr/local/etc/php/conf.d/roundcube-defaults.ini

PHP API => 20190902
(略)

/usr/local/etc/php およびその下の conf.d にある php.ini などの設定ファイルを読み込んでいるようです。

このコンテナの場合 php.in-production と php.ini-development があるのですが、 openssl に関する記述は共通で

[openssl]
; The location of a Certificate Authority (CA) file on the local filesystem
; to use when verifying the identity of SSL/TLS peers. Most users should
; not specify a value for this directive as PHP will attempt to use the
; OS-managed cert stores in its absence. If specified, this value may still
; be overridden on a per-stream basis via the "cafile" SSL stream context
; option.
;openssl.cafile=

; If openssl.cafile is not specified or if the CA file is not found, the
; directory pointed to by openssl.capath is searched for a suitable
; certificate. This value must be a correctly hashed certificate directory.
; Most users should not specify a value for this directive as PHP will
; attempt to use the OS-managed cert stores in its absence. If specified,
; this value may still be overridden on a per-stream basis via the "capath"
; SSL stream context option.
;openssl.capath=

となっています。なので、この説明によるとパスが無い場合は、システムデフォルトが使われるとのことです。

じゃあ、システムデフォルトがどうなっているのか、下記の記事に従って確認してみると、

python - what is my openssl and ssl Default CA Certs Path? - Stack Overflow

root@0c74afe74dd1:/var/www/html# openssl version -a
OpenSSL 1.1.1n  15 Mar 2022
built on: Tue May 10 18:37:36 2022 UTC
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(int) blowfish(ptr) 
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -ffile-prefix-map=/build/openssl-iy042u/openssl-1.1.1n=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-1.1"
Seeding source: os-specific
root@0c74afe74dd1:/var/www/html# 

となっています。この OPENSSLDIR がデフォルトのパスになるらしい。

で、ここを見てみると

root@0c74afe74dd1:/var/www/html# ls -laF /usr/lib/ssl/
total 16
drwxr-xr-x 3 root root 4096 Jun 23  2022 ./
drwxr-xr-x 1 root root 4096 Jun 30  2022 ../
lrwxrwxrwx 1 root root   14 May 10  2022 certs -> /etc/ssl/certs/
drwxr-xr-x 2 root root 4096 Jun 23  2022 misc/
lrwxrwxrwx 1 root root   20 May 10  2022 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx 1 root root   16 May 10  2022 private -> /etc/ssl/private/
root@0c74afe74dd1:/var/www/html# 

/etc/ssl 以下を見に行っているようです。ここをみると、各種証明書が入っています。なので、証明書そのものは正しく検索できるんじゃないかな?と思われます。

(参考)

上記が正しいとすると、今回使っているコンテナの場合は証明書のパスを設定するのは不要と思われます。

ですが、もし、最初の記事にあるように、 certificateファイルへのパスを設定する場合は、 roundcube-add-cert-path.ini のようなファイル名を下記の内容で作成して、

[openssl]
; The location of a Certificate Authority (CA) file on the local filesystem
; to use when verifying the identity of SSL/TLS peers. Most users should
; not specify a value for this directive as PHP will attempt to use the
; OS-managed cert stores in its absence. If specified, this value may still
; be overridden on a per-stream basis via the "cafile" SSL stream context
; option.
openssl.cafile=/etc/ssl/certs/ca-certificates.crt

; If openssl.cafile is not specified or if the CA file is not found, the
; directory pointed to by openssl.capath is searched for a suitable
; certificate. This value must be a correctly hashed certificate directory.
; Most users should not specify a value for this directive as PHP will
; attempt to use the OS-managed cert stores in its absence. If specified,
; this value may still be overridden on a per-stream basis via the "capath"
; SSL stream context option.
openssl.capath=/etc/ssl/certs

このファイルを、さきほどの 設定ファイル用のディレクトリに保存しておけばOKです。

root@387123b8511b:/usr/local/etc/php/conf.d# ls
docker-php-ext-exif.ini  docker-php-ext-imagick.ini  docker-php-ext-ldap.ini     docker-php-ext-pdo_mysql.ini  docker-php-ext-pspell.ini  docker-php-ext-sodium.ini  roundcube-add-cert-path.ini
docker-php-ext-gd.ini    docker-php-ext-intl.ini     docker-php-ext-opcache.ini  docker-php-ext-pdo_pgsql.ini  docker-php-ext-redis.ini   docker-php-ext-zip.ini     roundcube-defaults.ini
root@387123b8511b:/usr/local/etc/php/conf.d# 

php --info を見てみると、

openssl

OpenSSL support => enabled
OpenSSL Library Version => OpenSSL 1.1.1n  15 Mar 2022
OpenSSL Header Version => OpenSSL 1.1.1n  15 Mar 2022
Openssl default config => /usr/lib/ssl/openssl.cnf

Directive => Local Value => Master Value
openssl.cafile => /etc/ssl/certs/ca-certificates.crt => /etc/ssl/certs/ca-certificates.crt
openssl.capath => /etc/ssl/certs => /etc/ssl/certs

ちゃんと設定できてますね。

IMAP 接続時のオプション

次に、下記の記事などを見ると、証明書の検証自体を無効にするようなことが書かれている。

Roundcube SSL connection IMAP Error: Login failed - Stack Overflow

Howto Get Roundcube To Use SSL/TLS To Connect @ ;; MYRTANA.SK ;;

証明書のパスに問題が無さそうだったので、これも試してみます。

現状の /var/www/html/config/config.inc.php および config.docker.inc.php を確認してみると、どこにも imap_conn_options の設定がありません。も少し調べると、同じフォルダにある defaults.inc.php 内に

// IMAP socket context options
// See http://php.net/manual/en/context.ssl.php
// The example below enables server certificate validation
//$config['imap_conn_options'] = [
//  'ssl'         => [
//     'verify_peer'  => true,
//     'verify_depth' => 3,
//     'cafile'       => '/etc/openssl/certs/ca.crt',
//   ],
// ];
// Note: These can be also specified as an array of options indexed by hostname
$config['imap_conn_options'] = null;

という記述があります(SMTPについても同様の記述があります)。まずは試しに一度、これを有効にしてみます。

まず、 config.docker.add_options.inc.php を下記の内容で作成します。

<?php

// IMAP socket context options
$config['imap_conn_options'] = [
  'ssl'         => [
    'verify_peer'  => true,
    'verify_depth' => 3,
  ],
];

// SMTP socket context options
$config['smtp_conn_options'] = [
  'ssl'         => [
    'verify_peer'  => true,
    'verify_depth' => 3,
  ],
];

/var/www/html はコンテナのボリュームとして、 NAS のフォルダに接続されているので、作成したファイルを NAS 上の File Station を使って当該フォルダにアップロードしてやります(コンテナ内に vi とかのエディタがないため)。

次に、設定ファイルを追加で読み込むように config.inc.php の最後に一行追加します。

root@0c74afe74dd1:/var/www/html/config# echo "    include(__DIR__ . '/config.docker.add_options.inc.php');" >> config.inc.php
root@0c74afe74dd1:/var/www/html/config# cat config.inc.php
<?php
    $config['plugins'] = [
        'archive',
        'zipdownload',
        'ident_switch',
];
    $config['log_driver'] = 'stdout';
    $config['zipdownload_selection'] = true;
    $config['des_key'] = 'mcmwPOc6YPMtTZs8quIcNaaW';
    $config['enable_spellcheck'] = true;
    $config['spellcheck_engine'] = 'pspell';
    include(__DIR__ . '/config.docker.inc.php');
    
    include(__DIR__ . '/config.docker.add_options.inc.php');
root@0c74afe74dd1:/var/www/html/config# 

この状態で、IMAPサーバーへの接続を試してみると、

[Mon Jul 31 06:10:04.626685 2023] [php7:warn] [pid 50] [client 192.168.0.16:57207] PHP Warning:  stream_socket_client(): Peer certificate CN=`*.sakura.ne.jp' did not match expected CN=`mail.xxx.sakura.ne.jp' in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
[Mon Jul 31 06:10:04.626890 2023] [php7:warn] [pid 50] [client 192.168.0.16:57207] PHP Warning:  stream_socket_client(): Failed to enable crypto in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
[Mon Jul 31 06:10:04.627211 2023] [php7:warn] [pid 50] [client 192.168.0.16:57207] PHP Warning:  stream_socket_client(): unable to connect to ssl://mail.xxx.sakura.ne.jp:993 (Unknown error) in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
errors: <1494e41f> IMAP Error: Login failed for test@ドメイン名 against ssl://mail.xxx.sakura.ne.jp from 192.168.0.16. Could not connect to ssl://mail.xxx.sakura.ne.jp:993: Unknown reason in /var/www/html/program/lib/Roundcube/rcube_imap.php on line 211 (GET /?_task=mail&_action=getunread&_page=1&_remote=1&_unlock=0&_=1690783804509)

のように Warning が出るようになりました。

で、この Warning の内容をよく見てみると、

Peer certificate CN=*.sakura.ne.jp' did not match expected CN=mail.xxx.sakura.ne.jp'

とあります。 CN が証明書と異なる?

実は、今回試している IMAP サーバーはさくらインターネットのレンタルサーバーで、 SSL は共有 SSL を使っています。改めて、さくらインターネットのメールの設定を調べてみると、例えば、下記のページにあるように

メール(Windows 10・11)の設定をしたい | さくらのサポート情報

メールサーバーとして mail.xxx.sakura.ne.jp ではなく、 xxx.sakura.ne.jp (初期ドメイン)を使えとあります。あ、これですね。

ということで、メールサーバーの設定を xxx.sakura.ne.jp に変更してもう一度試してみると、問題なく接続できました!

(参考)

共有 SSL は初期ドメインの部分を * にしたワイルドカード証明書になっているようです。このため、 mail.xxx.sakura.ne.jp は階層が異なるため、サーバー証明書の検証に失敗していたようです。

一方、独自ドメインに対して設定できる無料 SSL (Let's Encrypt)もありますが、下記の説明からすると、こちらはメールでは使えないとのことです。

無料SSL(Let’s Encrypt)を設定したい | さくらのサポート情報

別のメールサーバーへ接続

これで、問題ないかと思ったのですが、別のメールサーバーへの接続を試してみると

[Mon Jul 31 06:17:12.468482 2023] [php7:warn] [pid 58] [client 192.168.0.16:57252] PHP Warning:  stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:\nerror:1414D172:SSL routines:tls12_check_peer_sigalg:wrong signature type in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
[Mon Jul 31 06:17:12.469031 2023] [php7:warn] [pid 58] [client 192.168.0.16:57252] PHP Warning:  stream_socket_client(): Failed to enable crypto in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
[Mon Jul 31 06:17:12.469673 2023] [php7:warn] [pid 58] [client 192.168.0.16:57252] PHP Warning:  stream_socket_client(): unable to connect to ssl://imap.xxx.xxx.jp:993 (Unknown error) in /var/www/html/program/lib/Roundcube/rcube_imap_generic.php on line 1060, referer: http://nas01:9004/?_task=mail&_mbox=INBOX
errors: <1494e41f> IMAP Error: Login failed for test2@別のドメイン名 against ssl://imap.xxx.xxx.jp from 192.168.0.16. Could not connect to ssl://imap.xxx.xxx.jp:993: Unknown reason in /var/www/html/program/lib/Roundcube/rcube_imap.php on line 211 (GET /?_task=mail&_action=getunread&_page=1&_remote=1&_unlock=0&_=1690784232338)

のように先ほどとは内容が違いますが Warning が出ます。

コマンドラインで検証

IMAP 接続時のオプションを試した際のさきほどの記事に書いてあった openssl s_client を試してみます。

root@0c74afe74dd1:/var/www/html/config# openssl s_client -connect imap.xxx.xxx.jp:993
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
verify return:1
depth=0 C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp
verify return:1
139802833319232:error:1414D172:SSL routines:tls12_check_peer_sigalg:wrong signature type:../ssl/t1_lib.c:1145:
---
Certificate chain
 0 s:C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp
   i:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
 1 s:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
 2 s:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
   i:C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
---
Server certificate
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE-----
subject=C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp

issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1

---
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 4573 bytes and written 313 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID: 4128E371AF8CFCDFCC3C079195546F513D3A1C5AACC87CB5CF8BD000A31E56AE
    Session-ID-ctx: 
    Master-Key: 
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1690784373
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---
root@0c74afe74dd1:/var/www/html/config# 

とあります。ここでも、表示されるメッセージをよく見ると、

tls12_check_peer_sigalg:wrong signature type

とあります。これってなんだろうか?と思い、ググると

OpenSSL SECLEVELによるwrong signature typeエラーの概要 - designetwork

セキュリティ レベル

openssl のセキュリティレベルというのがあって、それがあってないときに出るエラーのようです。

ためしに、変更してみます。既存の /etc/ssl/openssl.cnf の最後の部分が

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=2

となっているので、これを

root@0c74afe74dd1:/etc/ssl# sed -i "s/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/" openssl.cnf

のように編集しておきます。

root@0c74afe74dd1:/etc/ssl# cat openssl.cnf
#
# OpenSSL example configuration file.
# This is mostly being used for generation of certificate requests.
#
(略)
[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=1
root@0c74afe74dd1:/etc/ssl# 

変更されてますね。

この状態で一度、 roundcube を立ち上げなおして(コンテナを再起動して)アクセスしてみると、問題なくアクセスできました!

(参考)

セキュリティレベル変更後に openssl s_client を試すと

root@0c74afe74dd1:/var/www/html/config# openssl s_client -connect imap.xxx.xxx.jp:993
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
verify return:1
depth=0 C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp
verify return:1
---
Certificate chain
 0 s:C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp
   i:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
 1 s:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
 2 s:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
   i:C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
---
Server certificate
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE-----
subject=C = JP, ST = Tokyo, L = xxx 区, O = 会社名, CN = imap.xxx.xxx.jp

issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1

---
No client certificate CA names sent
Peer signing digest: SHA1
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 4633 bytes and written 438 bytes
Verification: OK
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: BBC91ACC2D9F8838296555800F1670C7474EEC1DEDEBA315DE4635F7B9585238
    Session-ID-ctx: 
    Master-Key: 79531CB64488D4CC67211712D6491E1D8B4ACC5FFECB2C5C050BADAFDEBD387720E276E0C374191FFEEC6E768B9F0D26
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1690785252
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN] IMAP4 ready

となり、 Cipher のところが表示されるようになってました。なるほどね、 SSL の最初にある暗号化方式を決める、という部分で失敗していたんですね。

(おまけ)ポート番号の指定

さて、これで送受信とも暗号化されて通信できたのですが、いろいろと試すと、メールの送信ができないケースがありました。

というのは、 docker-compose.yml で

  roundcubemail:
    environment:
      - ROUNDCUBEMAIL_DB_TYPE=sqlite
      - ROUNDCUBEMAIL_SKIN=elastic
      - ROUNDCUBEMAIL_DEFAULT_HOST=ssl://メールサーバー名
      - ROUNDCUBEMAIL_SMTP_SERVER=ssl://メールサーバー名

としたときに、ログイン時に用いるメールアドレスについて、 IMAP はポート番号として 143 ではなく 993 を使っているようなのですが、 SMTP についてはデフォルトの値(587)をそのまま使っており、 SMTPS でよくある 465 には切り替わっていないようでした。

なので、 ident_switch で設定したメールアドレスではこの現象が起きずに、デフォルトのメールサーバーを使うログイン時のメールアカウントの場合にのみ、送信エラーが起きていました。

ということで、余計なトラブルを避けるため、

  roundcubemail:
    environment:
      - ROUNDCUBEMAIL_DB_TYPE=sqlite
      - ROUNDCUBEMAIL_SKIN=elastic
      - ROUNDCUBEMAIL_DEFAULT_HOST=ssl://メールサーバー名
      - ROUNDCUBEMAIL_DEFAULT_PORT=993
      - ROUNDCUBEMAIL_SMTP_SERVER=ssl://メールサーバー名
      - ROUNDCUBEMAIL_SMTP_PORT=465

のように、明示的にポート番号を指定するようにしておきます。

まとめ

実際は、ここに書いたようにスムーズに解決法が見つかったわけではなく、結構紆余曲折がありました。

特に、 imap_conn_options を設定するまでは、Warning メッセージも出てなくて、何を調べればいいのかわからず、行き当たりばっりでした。この Warning メッセージが出力されるようになってからは、比較的解決策を見つけるのが早くなりました。

メッセージ大事ですね。

にしても、imap_conn_options の設定がない場合に、 Warning が出ないのは何とかしてほしいものです。

Roundcube も試しました

こちらの記事の実際に試したものになります。

Cypht を試した次は、 Roundcube も試してみます。なお、 GitHub はこちらです。

起動

試用であれば、 Roundcube も docker compose を使って立ち上げるのが一番簡単です。 Roundcube の docker compose に関しては、docker hub には情報がなかったのですが、 GitHub のリポジトリを見るとサンプルがあるので、それを用いました。

実際に試した docker-compose.yml はこんな感じです。

version: '3'

services:
  roundcubemail:
#    image: roundcube/roundcubemail:latest
    image: roundcube/roundcubemail:1.5.3-apache
#    container_name: roundcubemail
    container_name: roundcubemail3
#    restart: unless-stopped
    volumes:
      - ./www:/var/www/html
      - ./db/sqlite:/var/roundcube/db
    ports:
#      - 9002:80
      - 9003:80
    environment:
      - ROUNDCUBEMAIL_DB_TYPE=sqlite
      - ROUNDCUBEMAIL_SKIN=elastic
#      - ROUNDCUBEMAIL_DEFAULT_HOST=ssl://メールサーバー名
#      - ROUNDCUBEMAIL_SMTP_SERVER=ssl://メールサーバー名
      - ROUNDCUBEMAIL_DEFAULT_HOST=メールサーバー名
      - ROUNDCUBEMAIL_SMTP_SERVER=メールサーバー名

### Optional: add a full mail server stack to use with Roundcube like https://hub.docker.com/r/tvial/docker-mailserver
#   mailserver:
#     image: tvial/docker-mailserver:latest
#     hostname: mail.example.org
#     ...  # for more options see https://github.com/tomav/docker-mailserver#examples

サンプルとは異なり、 latest を使わずに 1.5.3 と少し前のものを使っています。これは、最新版(1.6.2)だと、ログイン成功後に下記のような php-zip 関連のエラーが吐かれていたので、避けました。

errors: <73b8bb66> PHP Error: php-zip extension is required for the zipdownload plugin in /var/www/html/plugins/zipdownload/zipdownload.php on line 36 (GET /?_task=mail&_token=EDMRG9or8UJXOvuYH2p7bFOjkqOQEeGP)

また、 IMAP でメールサーバーへ接続する際に、暗号化が有効な場合(メールサーバー名の先頭に ssl:// をつけた場合)はうまく認証できなかったので、とりあえず暗号化なしで試しています。

使い勝手

Roundcube が立ち上がったら、早速使ってみます。NASのアドレスを指定すると、下記のようなログイン画面が表示されます。

ここで、 docker-compose.yml で指定した IMAP サーバー上の有効なメールアドレスとパスワードを入力すると、ログインできます。

読み込み

ログインすると、そのメールアドレスでのフォルダ一番左に表示され、中央に選択中のフォルダにあるメールの一覧が表示されます。で、メールを選択すると、一番右にメールの内容が表示されます。

メールクライアントの動作として素直に受け入れられますね。また、個人的には配色や表示している内容、フォントサイズ等も好みの感じです。

添付ファイルの表示

添付ファイルもブラウザ表示できるもの(例えば、画像ファイルやPDF)であれば、別ウィンドウが立ち上がって、表示されます。

複数アカウント

Roundcube はそのままでも、『設定』->『識別情報』を選択して、『追加』ボタンを押すと、

このような形で、識別情報を追加することができます。これにより、複数の送信者情報を選択することができます(リンク先の Multiple sender Identities という機能になります)。とはいえ、送信者情報(From 欄)や署名を切り替えるだけなので、複数のメールボックスを見ることはできません。

複数アカウントを切り替えて、それぞれのメールボックスをみたり操作したりするためには、別途プラグインが必要になります。これは、 Roundcube の機能紹介ページにもリンクが張られている ident_switch というものになります。

boressoft/ident_switch - Packagist

こちらをインストールしてやれば、問題なく使えるようになります。

インストールはコンテナ内で行う必要があるので、NAS に SSH でログインして、下記のように行います。

[ユーザ名@NAS名 application]$ docker exec -it roundcubemail3 composer require boressoft/ident_switch --update-no-dev

すると下記のようなメッセージが表示されて、

Using version ^4.4 for boressoft/ident_switch
./composer.json has been updated
Running composer update boressoft/ident_switch
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Installing dependencies from lock file
Package operations: 1 install, 0 updates, 0 removals
As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.
This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.
Installing 'unzip' or '7z' (21.01+) may remediate them.
  - Installing boressoft/ident_switch (4.4.2): Extracting archive
Do you want to activate the plugin ident_switch? [Y|n] y
Updated local config at /var/www/html/config/config.inc.php
Creating package config file
Running database initialization script for ident_switch
Creating database schema... [OK]
Package phpunit/php-token-stream is abandoned, you should avoid using it. No replacement was suggested.
Package phpunit/phpunit-mock-objects is abandoned, you should avoid using it. No replacement was suggested.
Generating autoload files
7 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
[ユーザ名@NAS名 application]$ 

unzip がないとか、若干、警告が出てますが、問題なくインストールできたようです。

Roundcube の画面に戻り、『設定』->『識別情報』と進みます(プラグインのインストール後、ログアウトしてなくても大丈夫でした)。『作成』で識別情報を追加すると、今度は、

のように、先ほどの識別情報の追加画面の下側に、 ident_switch を有効にするボタンが表示されます。こちらを有効にすると、

のように、 IMAP および SMTP サーバーの情報を入力できるようになります。

ここで、サーバーおよびメールアドレスやパスワード等の情報を入力して、保存ボタンを押すと、

のように、2つのアカウントを見ることができます。

アカウント追加後、左側のメニューから『電子メール』を選択すると、メールアドレスが表示されている部分に、ドロップダウンリストっぽい記号が表示され、

これをクリックすると、

のように、アカウントを選択することができるようなります。

で、アカウントを切り替えると、切り替えた先のアカウントのメールを見ることができます。

いい感じですね。

メールの移動

メールを選択して、同じアカウント内のフォルダに移動させることは問題なくできます(ドラッグアンドドロップでもできますし、メニューから移動先を選ぶ形でもできます)。

でも、アカウントをまたいだメールの移動については、残念ながら、 Cypht とは異なりできませんでした。

送信

画面左の『新規作成』ボタンを押したら、受け取ったメールに返信をすると、メールの作成画面が表示されます。デフォルトの発信者は、選択中のアカウントですが、これを切り替えることもできます。

まとめ

ということで、 Roundcube はメールクライアントとして使い勝手がよさそうです。また、プラグインを入れればマルチアカウントにも対応できます。

希望することでできなさそうなのは、アカウントをまたいだメールの移動ぐらいになりそうです。まあ、メールのインポート・エクスポートはできるので、少なければ、それらを使っても対応できそうです。

であれば、今の Rainloop の替わりとしては、これが一番妥当っぽいですね。