LibreSSL と Boost.ASIO.SSL を組み合わせて使う事になった場合に ::SSL_CTX_get_default_passwd_cb_userdata 等複数箇所で翻訳時エラーが生じてしまう場合の応急対処法

# 問題

LibreSSL と Boost.ASIO.SSL を組み合わせて使う事になった場合に `::SSL_CTX_get_default_passwd_cb_userdata` 等複数箇所で翻訳時エラーが生じてしまう。

## 発生条件

以下の組み合わせにより発生する。

1. [LibreSSL](http://www.libressl.org/) を使用したい
    - 執筆時点の最新版は 2.5.0, 2.4.4 、あるいはそれ以降も対応されない可能性あり。
2. [Boost.ASIO.SSL](http://think-async.com/Asio/boost_asio_1_10_6/doc/html/boost_asio/overview/ssl.html) を使用したい。
    - Boost-1.61以降執筆現在最新1.63まで、あるいはそれ以降も対応されない可能性あり。

## 現象

翻訳時エラーが発生する。Boost.ASIO.SSL

```
...include/boost/asio/ssl/impl/context.ipp: In destructor 'boost::asio::ssl::context::~context()':
.../include/boost/asio/ssl/impl/context.ipp:232:25: error: '::SSL_CTX_get_default_passwd_cb_userdata' has not been declared
     void* cb_userdata = ::SSL_CTX_get_default_passwd_cb_userdata(handle_);
                         ^~
```

類似のエラーが大量に発生する。

# 原因

Boost.ASIO.SSL の以下のコミットから `asio/ssl/impl/context.hpp` などのソースコード複数について `OPENSSL_VERSION_NUMBER` によるプリプロセッサーレベルの分岐処理が導入され、 OpenSSL-1.1 系の API への対応が追加された。

- asio/ssl/impl/context.hpp
    - https://github.com/chriskohlhoff/asio/commit/062b19c97bb85f4625b46f93ee19b234948ff235

```cpp
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    void* cb_userdata = ::SSL_CTX_get_default_passwd_cb_userdata(handle_);
#else // (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    void* cb_userdata = handle_->default_passwd_callback_userdata;
#endif // (OPENSSL_VERSION_NUMBER >= 0x10100000L)
```

- asio/ssl/detail/impl/openssl_init.hpp
    - https://github.com/chriskohlhoff/asio/commit/2cde22623ca0fd9571d8d57c5a8965082d815e1c#diff-c7c37be381e2eed5c4edeaa54b07cb99

```cpp
#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) \
    && (OPENSSL_VERSION_NUMBER < 0x10100000L)
    ::SSL_COMP_free_compression_methods();
#endif // (OPENSSL_VERSION_NUMBER >= 0x10002000L)
```

OpenSSL では `OPENSSL_VERSION_NUMBER` は `0x10100000L` 未満か否かによって OpenSSL-1.1 系の API を使用可能か判別できる。

- https://github.com/openssl/openssl/blob/OpenSSL_1_1_0-stable/include/openssl/opensslv.h#L42

```cpp
# define OPENSSL_VERSION_NUMBER  0x10100040L
# ifdef OPENSSL_FIPS
#  define OPENSSL_VERSION_TEXT    "OpenSSL 1.1.0d-fips-dev  xx XXX xxxx"
# else
#  define OPENSSL_VERSION_TEXT    "OpenSSL 1.1.0d-dev  xx XXX xxxx"
# endif
```

一方、 LibreSSL-2.4/2.5 系では `openssl/opensslv.h` で `OPENSSL_VERSION_NUMBER` を `0x20000000L` と LibreSSL または互換の OpenSSL の API とは連動せず固定値で定義している。

- https://github.com/libressl-portable/openbsd/blob/42a3543c4c4b602ec3468c9bd2be4486e5f0deba/src/lib/libcrypto/opensslv.h#L10

```cpp
/* These will never change */
#define OPENSSL_VERSION_NUMBER	0x20000000L
#define OPENSSL_VERSION_TEXT	LIBRESSL_VERSION_TEXT
#define OPENSSL_VERSION_PTEXT	" part of " OPENSSL_VERSION_TEXT
```

また、 LibreSSL-2.4/2.5 は OpenSSL-1.1 系の API に対応していない。

よって、 LibreSSL-2.4/2.5 と Boost-1.61/1.62/1.63.ASIO.SSL を組み合わせると、 Boost.ASIO.SSL のソースコード中から OpenSSL-1.1 系の API を使用するコードがプリプロセッサーレベルで有効となり、 OpenSSL-1.1 系の API に未対応の LibreSSL-2.4/2.5 ではその API が未定義で使用できないため、翻訳時エラーとなる。

## 余談: LibreSSL のバージョン情報

原因と直接は関係しないが、 LibreSSL のバージョン情報をプリプロセッサーで取得したい場合は、 `openssl/opensslv.h` に次の LibreSSL 独自の定義が追加されているのでこちらを使用できる。

```cpp
#define LIBRESSL_VERSION_NUMBER       0x2030200fL
#define LIBRESSL_VERSION_TEXT "LibreSSL 2.3.2"
```

# 応急対処法

Boost.ASIO.SSL のヘッダーインクルード前後で `OPENSSL_VERSION_NUMBER` を偽装する事で比較的簡単に応急対処できる。

```cpp
/// @brief LibreSSL と Boost.ASIO.SSL の OpenSSL-1.1 API 対応判定の偽装処理
/// @ref http://qiita.com/usagi/items/acee410849ca8f4f9c11
/// @{
#if LIBRESSL_VERSION_NUMBER <= 0x2050100fL
  #include <openssl/opensslv.h>
  #undef OPENSSL_VERSION_NUMBER
  #define OPENSSL_VERSION_NUMBER 0x10001fffL
#endif
/// @}

#include <boost/asio/ssl.hpp>
```



## 注意

- この記事で紹介する対処方法は「応急」である事を理解して施し、適切な保守を行う必要がある。