1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "error_messages.hpp"
19 #include "node.hpp"
20 
21 #include <optional>
22 #include <utils/json_utils.hpp>
23 #include <variant>
24 namespace redfish
25 {
26 
27 enum NetworkProtocolUnitStructFields
28 {
29     NET_PROTO_UNIT_NAME,
30     NET_PROTO_UNIT_DESC,
31     NET_PROTO_UNIT_LOAD_STATE,
32     NET_PROTO_UNIT_ACTIVE_STATE,
33     NET_PROTO_UNIT_SUB_STATE,
34     NET_PROTO_UNIT_DEVICE,
35     NET_PROTO_UNIT_OBJ_PATH,
36     NET_PROTO_UNIT_ALWAYS_0,
37     NET_PROTO_UNIT_ALWAYS_EMPTY,
38     NET_PROTO_UNIT_ALWAYS_ROOT_PATH
39 };
40 
41 enum NetworkProtocolListenResponseElements
42 {
43     NET_PROTO_LISTEN_TYPE,
44     NET_PROTO_LISTEN_STREAM
45 };
46 
47 /**
48  * @brief D-Bus Unit structure returned in array from ListUnits Method
49  */
50 using UnitStruct =
51     std::tuple<std::string, std::string, std::string, std::string, std::string,
52                std::string, sdbusplus::message::object_path, uint32_t,
53                std::string, sdbusplus::message::object_path>;
54 
55 struct ServiceConfiguration
56 {
57     const char* serviceName;
58     const char* socketPath;
59 };
60 
61 const static boost::container::flat_map<const char*, ServiceConfiguration>
62     protocolToDBus{
63         {"SSH",
64          {"dropbear.socket",
65           "/org/freedesktop/systemd1/unit/dropbear_2esocket"}},
66         {"HTTPS",
67          {"bmcweb.service",
68           "/org/freedesktop/systemd1/unit/"
69           "bmcweb_2esocket"}}, //"/org/freedesktop/systemd1/unit/phosphor_2dgevent_2esocket"}},
70         {"IPMI",
71          {"phosphor-ipmi-net.socket", "/org/freedesktop/systemd1/unit/"
72                                       "phosphor_2dipmi_2dnet_2esocket"}}};
73 
74 inline void
75     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbus_data,
76                                         std::vector<std::string>& ntpData,
77                                         std::vector<std::string>& dnData)
78 {
79     for (const auto& obj : dbus_data)
80     {
81         for (const auto& ifacePair : obj.second)
82         {
83             if (obj.first == "/xyz/openbmc_project/network/eth0")
84             {
85                 if (ifacePair.first ==
86                     "xyz.openbmc_project.Network.EthernetInterface")
87                 {
88                     for (const auto& propertyPair : ifacePair.second)
89                     {
90                         if (propertyPair.first == "NTPServers")
91                         {
92                             const std::vector<std::string>* ntpServers =
93                                 sdbusplus::message::variant_ns::get_if<
94                                     std::vector<std::string>>(
95                                     &propertyPair.second);
96                             if (ntpServers != nullptr)
97                             {
98                                 ntpData = std::move(*ntpServers);
99                             }
100                         }
101                         else if (propertyPair.first == "DomainName")
102                         {
103                             const std::vector<std::string>* domainNames =
104                                 sdbusplus::message::variant_ns::get_if<
105                                     std::vector<std::string>>(
106                                     &propertyPair.second);
107                             if (domainNames != nullptr)
108                             {
109                                 dnData = std::move(*domainNames);
110                             }
111                         }
112                     }
113                 }
114             }
115         }
116     }
117 }
118 
119 template <typename CallbackFunc>
120 void getEthernetIfaceData(CallbackFunc&& callback)
121 {
122     crow::connections::systemBus->async_method_call(
123         [callback{std::move(callback)}](
124             const boost::system::error_code error_code,
125             const GetManagedObjects& dbus_data) {
126             std::vector<std::string> ntpServers;
127             std::vector<std::string> domainNames;
128 
129             if (error_code)
130             {
131                 callback(false, ntpServers, domainNames);
132                 return;
133             }
134 
135             extractNTPServersAndDomainNamesData(dbus_data, ntpServers,
136                                                 domainNames);
137 
138             callback(true, ntpServers, domainNames);
139         },
140         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
141         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
142 }
143 
144 class NetworkProtocol : public Node
145 {
146   public:
147     NetworkProtocol(CrowApp& app) :
148         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol")
149     {
150         entityPrivileges = {
151             {boost::beast::http::verb::get, {{"Login"}}},
152             {boost::beast::http::verb::head, {{"Login"}}},
153             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
154             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
155             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
156             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
157     }
158 
159   private:
160     void doGet(crow::Response& res, const crow::Request& req,
161                const std::vector<std::string>& params) override
162     {
163         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
164 
165         getData(asyncResp);
166     }
167 
168     std::string getHostName() const
169     {
170         std::string hostName;
171 
172         std::array<char, HOST_NAME_MAX> hostNameCStr;
173         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
174         {
175             hostName = hostNameCStr.data();
176         }
177         return hostName;
178     }
179 
180     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
181     {
182         crow::connections::systemBus->async_method_call(
183             [asyncResp](const boost::system::error_code error_code,
184                         const std::variant<std::string>& timeSyncMethod) {
185                 const std::string* s =
186                     std::get_if<std::string>(&timeSyncMethod);
187 
188                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
189                 {
190                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
191                 }
192                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
193                                "Method.Manual")
194                 {
195                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
196                 }
197             },
198             "xyz.openbmc_project.Settings",
199             "/xyz/openbmc_project/time/sync_method",
200             "org.freedesktop.DBus.Properties", "Get",
201             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
202     }
203 
204     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
205     {
206         asyncResp->res.jsonValue["@odata.type"] =
207             "#ManagerNetworkProtocol.v1_4_0.ManagerNetworkProtocol";
208         asyncResp->res.jsonValue["@odata.id"] =
209             "/redfish/v1/Managers/bmc/NetworkProtocol";
210         asyncResp->res.jsonValue["@odata.context"] =
211             "/redfish/v1/"
212             "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol";
213         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
214         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
215         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
216         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
217         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
218         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
219 
220         for (auto& protocol : protocolToDBus)
221         {
222             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
223         }
224 
225         std::string hostName = getHostName();
226 
227         asyncResp->res.jsonValue["HostName"] = hostName;
228 
229         getNTPProtocolEnabled(asyncResp);
230 
231         // TODO Get eth0 interface data, and call the below callback for JSON
232         // preparation
233         getEthernetIfaceData(
234             [hostName, asyncResp](const bool& success,
235                                   const std::vector<std::string>& ntpServers,
236                                   const std::vector<std::string>& domainNames) {
237                 if (!success)
238                 {
239                     messages::resourceNotFound(asyncResp->res,
240                                                "EthernetInterface", "eth0");
241                     return;
242                 }
243                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
244                 if (hostName.empty() == false)
245                 {
246                     std::string FQDN = std::move(hostName);
247                     if (domainNames.empty() == false)
248                     {
249                         FQDN += "." + domainNames[0];
250                     }
251                     asyncResp->res.jsonValue["FQDN"] = std::move(FQDN);
252                 }
253             });
254 
255         crow::connections::systemBus->async_method_call(
256             [asyncResp](const boost::system::error_code e,
257                         const std::vector<UnitStruct>& r) {
258                 if (e)
259                 {
260                     asyncResp->res.jsonValue = nlohmann::json::object();
261                     messages::internalError(asyncResp->res);
262                     return;
263                 }
264                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
265                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
266                                   "HTTPS/Certificates/"}};
267 
268                 for (auto& unit : r)
269                 {
270                     for (auto& kv : protocolToDBus)
271                     {
272                         if (kv.second.serviceName !=
273                             std::get<NET_PROTO_UNIT_NAME>(unit))
274                         {
275                             continue;
276                         }
277                         const char* service = kv.first;
278                         const char* socketPath = kv.second.socketPath;
279 
280                         asyncResp->res.jsonValue[service]["ProtocolEnabled"] =
281                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
282                              "running") ||
283                             (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
284                              "listening");
285                         crow::connections::systemBus->async_method_call(
286                             [asyncResp, service{std::string(service)}](
287                                 const boost::system::error_code ec,
288                                 const std::variant<std::vector<std::tuple<
289                                     std::string, std::string>>>& resp) {
290                                 if (ec)
291                                 {
292                                     messages::internalError(asyncResp->res);
293                                     return;
294                                 }
295                                 const std::vector<
296                                     std::tuple<std::string, std::string>>*
297                                     responsePtr = std::get_if<std::vector<
298                                         std::tuple<std::string, std::string>>>(
299                                         &resp);
300                                 if (responsePtr == nullptr ||
301                                     responsePtr->size() < 1)
302                                 {
303                                     return;
304                                 }
305 
306                                 const std::string& listenStream =
307                                     std::get<NET_PROTO_LISTEN_STREAM>(
308                                         (*responsePtr)[0]);
309                                 std::size_t lastColonPos =
310                                     listenStream.rfind(":");
311                                 if (lastColonPos == std::string::npos)
312                                 {
313                                     // Not a port
314                                     return;
315                                 }
316                                 std::string portStr =
317                                     listenStream.substr(lastColonPos + 1);
318                                 char* endPtr = nullptr;
319                                 // Use strtol instead of stroi to avoid
320                                 // exceptions
321                                 long port =
322                                     std::strtol(portStr.c_str(), &endPtr, 10);
323                                 if (*endPtr != '\0' || portStr.empty())
324                                 {
325                                     // Invalid value
326                                     asyncResp->res.jsonValue[service]["Port"] =
327                                         nullptr;
328                                 }
329                                 else
330                                 {
331                                     // Everything OK
332                                     asyncResp->res.jsonValue[service]["Port"] =
333                                         port;
334                                 }
335                             },
336                             "org.freedesktop.systemd1", socketPath,
337                             "org.freedesktop.DBus.Properties", "Get",
338                             "org.freedesktop.systemd1.Socket", "Listen");
339                     }
340                 }
341             },
342             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
343             "org.freedesktop.systemd1.Manager", "ListUnits");
344     }
345 
346     void handleHostnamePatch(const std::string& hostName,
347                              const std::shared_ptr<AsyncResp>& asyncResp)
348     {
349         crow::connections::systemBus->async_method_call(
350             [asyncResp](const boost::system::error_code ec) {
351                 if (ec)
352                 {
353                     messages::internalError(asyncResp->res);
354                     return;
355                 }
356             },
357             "xyz.openbmc_project.Network",
358             "/xyz/openbmc_project/network/config",
359             "org.freedesktop.DBus.Properties", "Set",
360             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
361             std::variant<std::string>(hostName));
362     }
363 
364     void handleNTPProtocolEnabled(const bool& ntpEnabled,
365                                   const std::shared_ptr<AsyncResp>& asyncResp)
366     {
367         std::string timeSyncMethod;
368         if (ntpEnabled)
369         {
370             timeSyncMethod =
371                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
372         }
373         else
374         {
375             timeSyncMethod =
376                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
377         }
378 
379         crow::connections::systemBus->async_method_call(
380             [asyncResp](const boost::system::error_code error_code) {},
381             "xyz.openbmc_project.Settings",
382             "/xyz/openbmc_project/time/sync_method",
383             "org.freedesktop.DBus.Properties", "Set",
384             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
385             std::variant<std::string>{timeSyncMethod});
386     }
387 
388     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
389                                const std::shared_ptr<AsyncResp>& asyncResp)
390     {
391         crow::connections::systemBus->async_method_call(
392             [asyncResp](const boost::system::error_code ec) {
393                 if (ec)
394                 {
395                     messages::internalError(asyncResp->res);
396                     return;
397                 }
398             },
399             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
400             "org.freedesktop.DBus.Properties", "Set",
401             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
402             std::variant<std::vector<std::string>>{ntpServers});
403     }
404 
405     void doPatch(crow::Response& res, const crow::Request& req,
406                  const std::vector<std::string>& params) override
407     {
408         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
409         std::optional<std::string> newHostName;
410         std::optional<nlohmann::json> ntp;
411 
412         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp))
413         {
414             return;
415         }
416 
417         res.result(boost::beast::http::status::no_content);
418         if (newHostName)
419         {
420             handleHostnamePatch(*newHostName, asyncResp);
421         }
422 
423         if (ntp)
424         {
425             std::optional<std::vector<std::string>> ntpServers;
426             std::optional<bool> ntpEnabled;
427             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
428                                      "ProtocolEnabled", ntpEnabled))
429             {
430                 return;
431             }
432 
433             if (ntpEnabled)
434             {
435                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
436             }
437 
438             if (ntpServers)
439             {
440                 handleNTPServersPatch(*ntpServers, asyncResp);
441             }
442         }
443     }
444 };
445 
446 } // namespace redfish
447