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