xref: /openbmc/bmcweb/http/http_server.hpp (revision 262d7d4b)
1 #pragma once
2 
3 #include "http_connection.hpp"
4 #include "logging.hpp"
5 #include "timer_queue.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 #include <boost/date_time/posix_time/posix_time.hpp>
14 #include <ssl_key_handler.hpp>
15 
16 #include <atomic>
17 #include <chrono>
18 #include <cstdint>
19 #include <filesystem>
20 #include <future>
21 #include <memory>
22 #include <utility>
23 #include <vector>
24 
25 namespace crow
26 {
27 
28 template <typename Handler, typename Adaptor = boost::asio::ip::tcp::socket>
29 class Server
30 {
31   public:
32     Server(Handler* handlerIn,
33            std::unique_ptr<boost::asio::ip::tcp::acceptor>&& acceptorIn,
34            std::shared_ptr<boost::asio::ssl::context> adaptorCtx,
35            std::shared_ptr<boost::asio::io_context> io =
36                std::make_shared<boost::asio::io_context>()) :
37         ioService(std::move(io)),
38         acceptor(std::move(acceptorIn)),
39         signals(*ioService, SIGINT, SIGTERM, SIGHUP), timer(*ioService),
40         handler(handlerIn), adaptorCtx(std::move(adaptorCtx))
41     {}
42 
43     Server(Handler* handlerIn, const std::string& bindaddr, uint16_t port,
44            const std::shared_ptr<boost::asio::ssl::context>& adaptorCtx,
45            const std::shared_ptr<boost::asio::io_context>& io =
46                std::make_shared<boost::asio::io_context>()) :
47         Server(handlerIn,
48                std::make_unique<boost::asio::ip::tcp::acceptor>(
49                    *io, boost::asio::ip::tcp::endpoint(
50                             boost::asio::ip::make_address(bindaddr), port)),
51                adaptorCtx, io)
52     {}
53 
54     Server(Handler* handlerIn, int existingSocket,
55            const std::shared_ptr<boost::asio::ssl::context>& adaptorCtx,
56            const std::shared_ptr<boost::asio::io_context>& io =
57                std::make_shared<boost::asio::io_context>()) :
58         Server(handlerIn,
59                std::make_unique<boost::asio::ip::tcp::acceptor>(
60                    *io, boost::asio::ip::tcp::v6(), existingSocket),
61                adaptorCtx, io)
62     {}
63 
64     void updateDateStr()
65     {
66         time_t lastTimeT = time(nullptr);
67         tm myTm{};
68 
69         gmtime_r(&lastTimeT, &myTm);
70 
71         dateStr.resize(100);
72         size_t dateStrSz =
73             strftime(&dateStr[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &myTm);
74         dateStr.resize(dateStrSz);
75     }
76 
77     void run()
78     {
79         loadCertificate();
80         updateDateStr();
81 
82         getCachedDateStr = [this]() -> std::string {
83             static std::chrono::time_point<std::chrono::steady_clock>
84                 lastDateUpdate = std::chrono::steady_clock::now();
85             if (std::chrono::steady_clock::now() - lastDateUpdate >=
86                 std::chrono::seconds(10))
87             {
88                 lastDateUpdate = std::chrono::steady_clock::now();
89                 updateDateStr();
90             }
91             return this->dateStr;
92         };
93 
94         timer.expires_after(std::chrono::seconds(1));
95 
96         timerHandler = [this](const boost::system::error_code& ec) {
97             if (ec)
98             {
99                 return;
100             }
101             timerQueue.process();
102             timer.expires_after(std::chrono::seconds(1));
103             timer.async_wait(timerHandler);
104         };
105         timer.async_wait(timerHandler);
106 
107         BMCWEB_LOG_INFO << "bmcweb server is running, local endpoint "
108                         << acceptor->local_endpoint();
109         startAsyncWaitForSignal();
110         doAccept();
111     }
112 
113     void loadCertificate()
114     {
115 #ifdef BMCWEB_ENABLE_SSL
116         namespace fs = std::filesystem;
117         // Cleanup older certificate file existing in the system
118         fs::path oldCert = "/home/root/server.pem";
119         if (fs::exists(oldCert))
120         {
121             fs::remove("/home/root/server.pem");
122         }
123         fs::path certPath = "/etc/ssl/certs/https/";
124         // if path does not exist create the path so that
125         // self signed certificate can be created in the
126         // path
127         if (!fs::exists(certPath))
128         {
129             fs::create_directories(certPath);
130         }
131         fs::path certFile = certPath / "server.pem";
132         BMCWEB_LOG_INFO << "Building SSL Context file=" << certFile;
133         std::string sslPemFile(certFile);
134         ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
135         std::shared_ptr<boost::asio::ssl::context> sslContext =
136             ensuressl::getSslContext(sslPemFile);
137         adaptorCtx = sslContext;
138         handler->ssl(std::move(sslContext));
139 #endif
140     }
141 
142     void startAsyncWaitForSignal()
143     {
144         signals.async_wait([this](const boost::system::error_code& ec,
145                                   int signalNo) {
146             if (ec)
147             {
148                 BMCWEB_LOG_INFO << "Error in signal handler" << ec.message();
149             }
150             else
151             {
152                 if (signalNo == SIGHUP)
153                 {
154                     BMCWEB_LOG_INFO << "Receivied reload signal";
155                     loadCertificate();
156                     boost::system::error_code ec2;
157                     acceptor->cancel(ec2);
158                     if (ec2)
159                     {
160                         BMCWEB_LOG_ERROR
161                             << "Error while canceling async operations:"
162                             << ec2.message();
163                     }
164                     this->startAsyncWaitForSignal();
165                 }
166                 else
167                 {
168                     stop();
169                 }
170             }
171         });
172     }
173 
174     void stop()
175     {
176         ioService->stop();
177     }
178 
179     void doAccept()
180     {
181         std::optional<Adaptor> adaptorTemp;
182         if constexpr (std::is_same<Adaptor,
183                                    boost::beast::ssl_stream<
184                                        boost::asio::ip::tcp::socket>>::value)
185         {
186             adaptorTemp = Adaptor(*ioService, *adaptorCtx);
187             auto p = std::make_shared<Connection<Adaptor, Handler>>(
188                 handler, getCachedDateStr, timerQueue,
189                 std::move(adaptorTemp.value()));
190 
191             acceptor->async_accept(p->socket().next_layer(),
192                                    [this, p](boost::system::error_code ec) {
193                                        if (!ec)
194                                        {
195                                            boost::asio::post(
196                                                *this->ioService,
197                                                [p] { p->start(); });
198                                        }
199                                        doAccept();
200                                    });
201         }
202         else
203         {
204             adaptorTemp = Adaptor(*ioService);
205             auto p = std::make_shared<Connection<Adaptor, Handler>>(
206                 handler, getCachedDateStr, timerQueue,
207                 std::move(adaptorTemp.value()));
208 
209             acceptor->async_accept(
210                 p->socket(), [this, p](boost::system::error_code ec) {
211                     if (!ec)
212                     {
213                         boost::asio::post(*this->ioService,
214                                           [p] { p->start(); });
215                     }
216                     doAccept();
217                 });
218         }
219     }
220 
221   private:
222     std::shared_ptr<boost::asio::io_context> ioService;
223     detail::TimerQueue timerQueue;
224     std::function<std::string()> getCachedDateStr;
225     std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
226     boost::asio::signal_set signals;
227     boost::asio::steady_timer timer;
228 
229     std::string dateStr;
230 
231     Handler* handler;
232 
233     std::function<void(const boost::system::error_code& ec)> timerHandler;
234 
235 #ifdef BMCWEB_ENABLE_SSL
236     bool useSsl{false};
237 #endif
238     std::shared_ptr<boost::asio::ssl::context> adaptorCtx;
239 }; // namespace crow
240 } // namespace crow
241