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