xref: /openbmc/bmcweb/http/http_server.hpp (revision a3b9eb98)
1 #pragma once
2 
3 #include "http_connection.hpp"
4 #include "logging.hpp"
5 #include "ssl_key_handler.hpp"
6 
7 #include <boost/asio/ip/address.hpp>
8 #include <boost/asio/ip/tcp.hpp>
9 #include <boost/asio/signal_set.hpp>
10 #include <boost/asio/ssl/context.hpp>
11 #include <boost/asio/ssl/stream.hpp>
12 #include <boost/asio/steady_timer.hpp>
13 
14 #include <atomic>
15 #include <chrono>
16 #include <cstdint>
17 #include <filesystem>
18 #include <future>
19 #include <memory>
20 #include <utility>
21 #include <vector>
22 
23 namespace crow
24 {
25 
26 template <typename Handler, typename Adaptor = boost::asio::ip::tcp::socket>
27 class Server
28 {
29   public:
30     Server(Handler* handlerIn, boost::asio::ip::tcp::acceptor&& acceptorIn,
31            std::shared_ptr<boost::asio::ssl::context> adaptorCtxIn,
32            std::shared_ptr<boost::asio::io_context> io) :
33         ioService(std::move(io)),
34         acceptor(std::move(acceptorIn)),
35         signals(*ioService, SIGINT, SIGTERM, SIGHUP), handler(handlerIn),
36         adaptorCtx(std::move(adaptorCtxIn))
37     {}
38 
39     void updateDateStr()
40     {
41         time_t lastTimeT = time(nullptr);
42         tm myTm{};
43 
44         gmtime_r(&lastTimeT, &myTm);
45 
46         dateStr.resize(100);
47         size_t dateStrSz = strftime(dateStr.data(), dateStr.size() - 1,
48                                     "%a, %d %b %Y %H:%M:%S GMT", &myTm);
49         dateStr.resize(dateStrSz);
50     }
51 
52     void run()
53     {
54         loadCertificate();
55         updateDateStr();
56 
57         getCachedDateStr = [this]() -> std::string {
58             static std::chrono::time_point<std::chrono::steady_clock>
59                 lastDateUpdate = std::chrono::steady_clock::now();
60             if (std::chrono::steady_clock::now() - lastDateUpdate >=
61                 std::chrono::seconds(10))
62             {
63                 lastDateUpdate = std::chrono::steady_clock::now();
64                 updateDateStr();
65             }
66             return dateStr;
67         };
68 
69         BMCWEB_LOG_INFO("bmcweb server is running, local endpoint {}",
70                         acceptor.local_endpoint().address().to_string());
71         startAsyncWaitForSignal();
72         doAccept();
73     }
74 
75     void loadCertificate()
76     {
77         if constexpr (BMCWEB_INSECURE_DISABLE_SSL)
78         {
79             return;
80         }
81         namespace fs = std::filesystem;
82         // Cleanup older certificate file existing in the system
83         fs::path oldCert = "/home/root/server.pem";
84         if (fs::exists(oldCert))
85         {
86             fs::remove("/home/root/server.pem");
87         }
88         fs::path certPath = "/etc/ssl/certs/https/";
89         // if path does not exist create the path so that
90         // self signed certificate can be created in the
91         // path
92         if (!fs::exists(certPath))
93         {
94             fs::create_directories(certPath);
95         }
96         fs::path certFile = certPath / "server.pem";
97         BMCWEB_LOG_INFO("Building SSL Context file={}", certFile.string());
98         std::string sslPemFile(certFile);
99         ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
100         std::shared_ptr<boost::asio::ssl::context> sslContext =
101             ensuressl::getSslContext(sslPemFile);
102         adaptorCtx = sslContext;
103         handler->ssl(std::move(sslContext));
104     }
105 
106     void startAsyncWaitForSignal()
107     {
108         signals.async_wait(
109             [this](const boost::system::error_code& ec, int signalNo) {
110             if (ec)
111             {
112                 BMCWEB_LOG_INFO("Error in signal handler{}", ec.message());
113             }
114             else
115             {
116                 if (signalNo == SIGHUP)
117                 {
118                     BMCWEB_LOG_INFO("Receivied reload signal");
119                     loadCertificate();
120                     boost::system::error_code ec2;
121                     acceptor.cancel(ec2);
122                     if (ec2)
123                     {
124                         BMCWEB_LOG_ERROR(
125                             "Error while canceling async operations:{}",
126                             ec2.message());
127                     }
128                     startAsyncWaitForSignal();
129                 }
130                 else
131                 {
132                     stop();
133                 }
134             }
135         });
136     }
137 
138     void stop()
139     {
140         ioService->stop();
141     }
142 
143     void doAccept()
144     {
145         if (ioService == nullptr)
146         {
147             BMCWEB_LOG_CRITICAL("IoService was null");
148             return;
149         }
150         boost::asio::steady_timer timer(*ioService);
151         std::shared_ptr<Connection<Adaptor, Handler>> connection;
152         if constexpr (std::is_same<Adaptor,
153                                    boost::asio::ssl::stream<
154                                        boost::asio::ip::tcp::socket>>::value)
155         {
156             if (adaptorCtx == nullptr)
157             {
158                 BMCWEB_LOG_CRITICAL(
159                     "Asked to lauch TLS socket but no context available");
160                 return;
161             }
162             connection = std::make_shared<Connection<Adaptor, Handler>>(
163                 handler, std::move(timer), getCachedDateStr,
164                 Adaptor(*ioService, *adaptorCtx));
165         }
166         else
167         {
168             connection = std::make_shared<Connection<Adaptor, Handler>>(
169                 handler, std::move(timer), getCachedDateStr,
170                 Adaptor(*ioService));
171         }
172         acceptor.async_accept(
173             boost::beast::get_lowest_layer(connection->socket()),
174             [this, connection](const boost::system::error_code& ec) {
175             if (!ec)
176             {
177                 boost::asio::post(*ioService,
178                                   [connection] { connection->start(); });
179             }
180             doAccept();
181         });
182     }
183 
184   private:
185     std::shared_ptr<boost::asio::io_context> ioService;
186     std::function<std::string()> getCachedDateStr;
187     boost::asio::ip::tcp::acceptor acceptor;
188     boost::asio::signal_set signals;
189 
190     std::string dateStr;
191 
192     Handler* handler;
193 
194     std::shared_ptr<boost::asio::ssl::context> adaptorCtx;
195 };
196 } // namespace crow
197