Enable HTTP additional sockets

This commit attempts to add the concept of an SSL detector from beast,
and add the capability into bmcweb.  This allows directing multiple
socket files to the bmcweb instance, and bmcweb will automatically sort
out whether or not they're SSL, and give the correct response.  This
allows users to plug in erroneous urls like "https://mybmc:80" and they
will forward and work correctly.

Some key design points:
The HTTP side of bmcweb implements the exact same http headers as the
HTTPS side, with the exception of HSTS, which is explicitly disallowed.
This is for consistency and security.

The above allows bmcweb builds to "select" the appropriate security
posture (http, https, or both) for a given channel using the
FileDescriptorName field within a socket file.  Items ending in:
both: Will support both HTTPS and HTTP redirect to HTTPS
https: Will support HTTPS only
http: will support HTTP only

Given the flexibility in bind statements, this allows administrators to
support essentially any security posture they like.  The openbmc
defaults are:
HTTPS + Redirect on both ports 443 and port 80 if http-redirect is
enabled

And HTTPS only if http-redirect is disabled.

This commit adds the following meson options that each take an array of
strings, indexex on the port.
additional-ports
  Adds additional ports that bmcweb should listen to.  This is always
  required when adding new ports.

additional-protocol
  Specifies 'http', 'https', or 'both' for whether or not tls is enfoced
  on this socket.  'both' allows bmcweb to detect whether a user has
  specified tls or not on a given connection and give the correct
  response.

additional-bind-to-device
  Accepts values that fill the SO_BINDTODEVICE flag in systemd/linux,
  and allows binding to a specific device

additional-auth
  Accepts values of 'auth' or 'noauth' that determines whether this
  socket should apply the normal authentication routines, or treat the
  socket as unauthenticated.

Tested:
Previous commits ran the below tests.
Ran the server with options enabled.  Tried:
```
curl -vvvv --insecure --user root:0penBmc http://192.168.7.2/redfish/v1/Managers/bmc
*   Trying 192.168.7.2:80...
* Connected to 192.168.7.2 (192.168.7.2) port 80 (#0)
* Server auth using Basic with user 'root'
> GET /redfish/v1/Managers/bmc HTTP/1.1
> Host: 192.168.7.2
> Authorization: Basic cm9vdDowcGVuQm1j
> User-Agent: curl/7.72.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: https://192.168.7.2
< X-Frame-Options: DENY
< Pragma: no-cache
< Cache-Control: no-Store,no-Cache
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Content-Security-Policy: default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:
< Date: Fri, 08 Jan 2021 01:43:49 GMT
< Connection: close
< Content-Length: 0
<
* Closing connection 0
```

Observe above:
webserver returned 301 redirect.
webserver returned the appropriate security headers
webserver immediately closed the connection.

The same test above over https:// returns the values as expected

Loaded the webui to test static file hosting.  Webui logs in and works
as expected.

Used the scripts/websocket_test.py to verify that websockets work.
Sensors report as expected.

Change-Id: Ib5733bbe5473fed6e0e27c56cdead0bffedf2993
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index a6773bc..c9eb2ae 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -9,6 +9,7 @@
 #include "forward_unauthorized.hpp"
 #include "http2_connection.hpp"
 #include "http_body.hpp"
+#include "http_connect_types.hpp"
 #include "http_request.hpp"
 #include "http_response.hpp"
 #include "http_utility.hpp"
@@ -26,6 +27,8 @@
 #include <boost/asio/steady_timer.hpp>
 #include <boost/beast/_experimental/test/stream.hpp>
 #include <boost/beast/core/buffers_generator.hpp>
+#include <boost/beast/core/detect_ssl.hpp>
+#include <boost/beast/core/error.hpp>
 #include <boost/beast/core/flat_static_buffer.hpp>
 #include <boost/beast/http/error.hpp>
 #include <boost/beast/http/field.hpp>
@@ -64,14 +67,6 @@
 
 constexpr uint32_t httpHeaderLimit = 8192U;
 
-template <typename>
-struct IsTls : std::false_type
-{};
-
-template <typename T>
-struct IsTls<boost::asio::ssl::stream<T>> : std::true_type
-{};
-
 template <typename Adaptor, typename Handler>
 class Connection :
     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
@@ -79,10 +74,11 @@
     using self_type = Connection<Adaptor, Handler>;
 
   public:
-    Connection(Handler* handlerIn, boost::asio::steady_timer&& timerIn,
+    Connection(Handler* handlerIn, HttpType httpTypeIn,
+               boost::asio::steady_timer&& timerIn,
                std::function<std::string()>& getCachedDateStrF,
-               Adaptor&& adaptorIn) :
-        adaptor(std::move(adaptorIn)), handler(handlerIn),
+               boost::asio::ssl::stream<Adaptor>&& adaptorIn) :
+        httpType(httpTypeIn), adaptor(std::move(adaptorIn)), handler(handlerIn),
         timer(std::move(timerIn)), getCachedDateStr(getCachedDateStrF)
     {
         initParser();
@@ -139,38 +135,75 @@
 
     bool prepareMutualTls()
     {
-        if constexpr (IsTls<Adaptor>::value)
+        BMCWEB_LOG_DEBUG("prepareMutualTls");
+
+        constexpr std::string_view id = "bmcweb";
+
+        const char* idPtr = id.data();
+        const auto* idCPtr = std::bit_cast<const unsigned char*>(idPtr);
+        auto idLen = static_cast<unsigned int>(id.length());
+        int ret =
+            SSL_set_session_id_context(adaptor.native_handle(), idCPtr, idLen);
+        if (ret == 0)
         {
-            BMCWEB_LOG_DEBUG("prepareMutualTls");
+            BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
+            return false;
+        }
 
-            constexpr std::string_view id = "bmcweb";
+        BMCWEB_LOG_DEBUG("set_verify_callback");
 
-            const char* idPtr = id.data();
-            const auto* idCPtr = std::bit_cast<const unsigned char*>(idPtr);
-            auto idLen = static_cast<unsigned int>(id.length());
-            int ret = SSL_set_session_id_context(adaptor.native_handle(),
-                                                 idCPtr, idLen);
-            if (ret == 0)
-            {
-                BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
-                return false;
-            }
-
-            BMCWEB_LOG_DEBUG("set_verify_callback");
-
-            boost::system::error_code ec;
-            adaptor.set_verify_callback(
-                std::bind_front(&self_type::tlsVerifyCallback, this), ec);
-            if (ec)
-            {
-                BMCWEB_LOG_ERROR("Failed to set verify callback {}", ec);
-                return false;
-            }
+        boost::system::error_code ec;
+        adaptor.set_verify_callback(
+            std::bind_front(&self_type::tlsVerifyCallback, this), ec);
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("Failed to set verify callback {}", ec);
+            return false;
         }
 
         return true;
     }
 
+    void afterDetectSsl(const std::shared_ptr<self_type>& /*self*/,
+                        boost::beast::error_code ec, bool isTls)
+    {
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("Couldn't detect ssl ", ec);
+            return;
+        }
+        BMCWEB_LOG_DEBUG("{} TLS was detected as {}", logPtr(this), isTls);
+        if (isTls)
+        {
+            if (httpType != HttpType::HTTPS && httpType != HttpType::BOTH)
+            {
+                BMCWEB_LOG_WARNING(
+                    "{} Connection closed due to incompatible type",
+                    logPtr(this));
+                return;
+            }
+            httpType = HttpType::HTTPS;
+            adaptor.async_handshake(
+                boost::asio::ssl::stream_base::server, buffer.data(),
+                std::bind_front(&self_type::afterSslHandshake, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            if (httpType != HttpType::HTTP && httpType != HttpType::BOTH)
+            {
+                BMCWEB_LOG_WARNING(
+                    "{} Connection closed due to incompatible type",
+                    logPtr(this));
+                return;
+            }
+
+            httpType = HttpType::HTTP;
+            BMCWEB_LOG_INFO("Starting non-SSL session");
+            doReadHeaders();
+        }
+    }
+
     void start()
     {
         BMCWEB_LOG_DEBUG("{} Connection started, total {}", logPtr(this),
@@ -194,29 +227,23 @@
         startDeadline();
 
         readClientIp();
-
-        // TODO(ed) Abstract this to a more clever class with the idea of an
-        // asynchronous "start"
-        if constexpr (IsTls<Adaptor>::value)
-        {
-            adaptor.async_handshake(boost::asio::ssl::stream_base::server,
-                                    [this, self(shared_from_this())](
-                                        const boost::system::error_code& ec) {
-                                        if (ec)
-                                        {
-                                            return;
-                                        }
-                                        afterSslHandshake();
-                                    });
-        }
-        else
-        {
-            doReadHeaders();
-        }
+        boost::beast::async_detect_ssl(
+            adaptor.next_layer(), buffer,
+            std::bind_front(&self_type::afterDetectSsl, this,
+                            shared_from_this()));
     }
 
-    void afterSslHandshake()
+    void afterSslHandshake(const std::shared_ptr<self_type>& /*self*/,
+                           const boost::system::error_code& ec,
+                           size_t bytesParsed)
     {
+        buffer.consume(bytesParsed);
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("{} SSL handshake failed", logPtr(this));
+            return;
+        }
+        BMCWEB_LOG_DEBUG("{} SSL handshake succeeded", logPtr(this));
         // If http2 is enabled, negotiate the protocol
         if constexpr (BMCWEB_EXPERIMENTAL_HTTP2)
         {
@@ -231,10 +258,7 @@
                                  selectedProtocol, alpnlen);
                 if (selectedProtocol == "h2")
                 {
-                    auto http2 =
-                        std::make_shared<HTTP2Connection<Adaptor, Handler>>(
-                            std::move(adaptor), handler, getCachedDateStr);
-                    http2->start();
+                    upgradeToHttp2();
                     return;
                 }
             }
@@ -256,6 +280,39 @@
         instance.body_limit(boost::none);
     }
 
+    void upgradeToHttp2()
+    {
+        // TODO HTTP2Connection needs adaptor moved to a similar abstraction as
+        // HTTPConnection
+        if (httpType == HttpType::HTTP)
+        {
+            auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
+                std::move(adaptor.next_layer()), handler, getCachedDateStr);
+            if (http2settings.empty())
+            {
+                http2->start();
+            }
+            else
+            {
+                http2->startFromSettings(http2settings);
+            }
+        }
+        else
+        {
+            auto http2 = std::make_shared<
+                HTTP2Connection<boost::asio::ssl::stream<Adaptor>, Handler>>(
+                std::move(adaptor), handler, getCachedDateStr);
+            if (http2settings.empty())
+            {
+                http2->start();
+            }
+            else
+            {
+                http2->startFromSettings(http2settings);
+            }
+        }
+    }
+
     // returns whether connection was upgraded
     bool doUpgrade(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
     {
@@ -312,7 +369,16 @@
                         }
                     });
                 BMCWEB_LOG_INFO("{} Upgrading socket", logPtr(this));
-                handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+                if (httpType == HttpType::HTTP)
+                {
+                    handler->handleUpgrade(req, asyncResp,
+                                           std::move(adaptor.next_layer()));
+                }
+                else
+                {
+                    handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+                }
+
                 return true;
             }
         }
@@ -364,24 +430,23 @@
             return;
         }
         keepAlive = req->keepAlive();
-        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
+
+        if (authenticationEnabled)
         {
-            if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
+            if (!crow::authentication::isOnAllowlist(req->url().path(),
+                                                     req->method()) &&
+                req->session == nullptr)
             {
-                if (!crow::authentication::isOnAllowlist(req->url().path(),
-                                                         req->method()) &&
-                    req->session == nullptr)
-                {
-                    BMCWEB_LOG_WARNING("Authentication failed");
-                    forward_unauthorized::sendUnauthorized(
-                        req->url().encoded_path(),
-                        req->getHeaderValue("X-Requested-With"),
-                        req->getHeaderValue("Accept"), res);
-                    completeRequest(res);
-                    return;
-                }
+                BMCWEB_LOG_WARNING("Authentication failed");
+                forward_unauthorized::sendUnauthorized(
+                    req->url().encoded_path(),
+                    req->getHeaderValue("X-Requested-With"),
+                    req->getHeaderValue("Accept"), res);
+                completeRequest(res);
+                return;
             }
         }
+
         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
         BMCWEB_LOG_DEBUG("Setting completion handler");
         asyncResp->res.setCompleteRequestHandler(
@@ -403,15 +468,8 @@
 
     void hardClose()
     {
-        if (mtlsSession != nullptr)
-        {
-            BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this),
-                             mtlsSession->uniqueId);
-            persistent_data::SessionStore::getInstance().removeSession(
-                mtlsSession);
-        }
         BMCWEB_LOG_DEBUG("{} Closing socket", logPtr(this));
-        boost::beast::get_lowest_layer(adaptor).close();
+        adaptor.next_layer().close();
     }
 
     void tlsShutdownComplete(const std::shared_ptr<self_type>& self,
@@ -429,8 +487,16 @@
     {
         BMCWEB_LOG_DEBUG("{} Socket close requested", logPtr(this));
 
-        if constexpr (IsTls<Adaptor>::value)
+        if (httpType == HttpType::HTTPS)
         {
+            if (mtlsSession != nullptr)
+            {
+                BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this),
+                                 mtlsSession->uniqueId);
+                persistent_data::SessionStore::getInstance().removeSession(
+                    mtlsSession);
+            }
+
             adaptor.async_shutdown(std::bind_front(
                 &self_type::tlsShutdownComplete, this, shared_from_this()));
         }
@@ -459,21 +525,23 @@
     {
         boost::system::error_code ec;
 
-        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
-        {
-            boost::asio::ip::tcp::endpoint endpoint =
-                boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
+        boost::asio::ip::tcp::endpoint endpoint =
+            boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
 
-            if (ec)
-            {
-                // If remote endpoint fails keep going. "ClientOriginIPAddress"
-                // will be empty.
-                BMCWEB_LOG_ERROR(
-                    "Failed to get the client's IP Address. ec : {}", ec);
-                return;
-            }
-            ip = endpoint.address();
+        if (ec)
+        {
+            // If remote endpoint fails keep going. "ClientOriginIPAddress"
+            // will be empty.
+            BMCWEB_LOG_ERROR("Failed to get the client's IP Address. ec : {}",
+                             ec);
+            return;
         }
+        ip = endpoint.address();
+    }
+
+    void disableAuth()
+    {
+        authenticationEnabled = false;
     }
 
   private:
@@ -585,13 +653,10 @@
             return;
         }
 
-        constexpr bool isTest =
-            std::is_same_v<Adaptor, boost::beast::test::stream>;
-
-        if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH && !isTest)
+        if (authenticationEnabled)
         {
             boost::beast::http::verb method = parser->get().method();
-            userSession = crow::authentication::authenticate(
+            userSession = authentication::authenticate(
                 ip, res, method, parser->get().base(), mtlsSession);
         }
 
@@ -628,11 +693,21 @@
             BMCWEB_LOG_CRITICAL("Parser was not initialized.");
             return;
         }
-        // Clean up any previous Connection.
-        boost::beast::http::async_read_header(
-            adaptor, buffer, *parser,
-            std::bind_front(&self_type::afterReadHeaders, this,
-                            shared_from_this()));
+
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::http::async_read_header(
+                adaptor.next_layer(), buffer, *parser,
+                std::bind_front(&self_type::afterReadHeaders, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::http::async_read_header(
+                adaptor, buffer, *parser,
+                std::bind_front(&self_type::afterReadHeaders, this,
+                                shared_from_this()));
+        }
     }
 
     void afterRead(const std::shared_ptr<self_type>& /*self*/,
@@ -697,9 +772,20 @@
             return;
         }
         startDeadline();
-        boost::beast::http::async_read_some(
-            adaptor, buffer, *parser,
-            std::bind_front(&self_type::afterRead, this, shared_from_this()));
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::http::async_read_some(
+                adaptor.next_layer(), buffer, *parser,
+                std::bind_front(&self_type::afterRead, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::http::async_read_some(
+                adaptor, buffer, *parser,
+                std::bind_front(&self_type::afterRead, this,
+                                shared_from_this()));
+        }
     }
 
     void afterDoWrite(const std::shared_ptr<self_type>& /*self*/,
@@ -725,9 +811,7 @@
 
         if (res.result() == boost::beast::http::status::switching_protocols)
         {
-            auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
-                std::move(adaptor), handler, getCachedDateStr);
-            http2->startFromSettings(http2settings);
+            upgradeToHttp2();
             return;
         }
 
@@ -764,11 +848,22 @@
         res.preparePayload();
 
         startDeadline();
-        boost::beast::async_write(
-            adaptor,
-            boost::beast::http::message_generator(std::move(res.response)),
-            std::bind_front(&self_type::afterDoWrite, this,
-                            shared_from_this()));
+        if (httpType == HttpType::HTTP)
+        {
+            boost::beast::async_write(
+                adaptor.next_layer(),
+                boost::beast::http::message_generator(std::move(res.response)),
+                std::bind_front(&self_type::afterDoWrite, this,
+                                shared_from_this()));
+        }
+        else
+        {
+            boost::beast::async_write(
+                adaptor,
+                boost::beast::http::message_generator(std::move(res.response)),
+                std::bind_front(&self_type::afterDoWrite, this,
+                                shared_from_this()));
+        }
     }
 
     void cancelDeadlineTimer()
@@ -835,7 +930,10 @@
         BMCWEB_LOG_DEBUG("{} timer started", logPtr(this));
     }
 
-    Adaptor adaptor;
+    bool authenticationEnabled = !BMCWEB_INSECURE_DISABLE_AUTH;
+    HttpType httpType = HttpType::BOTH;
+
+    boost::asio::ssl::stream<Adaptor> adaptor;
     Handler* handler;
 
     boost::asio::ip::address ip;