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