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 #include "openbmc_dbus_rest.hpp"
21 
22 #include <utils/json_utils.hpp>
23 
24 #include <optional>
25 #include <variant>
26 namespace redfish
27 {
28 
29 enum NetworkProtocolUnitStructFields
30 {
31     NET_PROTO_UNIT_NAME,
32     NET_PROTO_UNIT_DESC,
33     NET_PROTO_UNIT_LOAD_STATE,
34     NET_PROTO_UNIT_ACTIVE_STATE,
35     NET_PROTO_UNIT_SUB_STATE,
36     NET_PROTO_UNIT_DEVICE,
37     NET_PROTO_UNIT_OBJ_PATH,
38     NET_PROTO_UNIT_ALWAYS_0,
39     NET_PROTO_UNIT_ALWAYS_EMPTY,
40     NET_PROTO_UNIT_ALWAYS_ROOT_PATH
41 };
42 
43 enum NetworkProtocolListenResponseElements
44 {
45     NET_PROTO_LISTEN_TYPE,
46     NET_PROTO_LISTEN_STREAM
47 };
48 
49 /**
50  * @brief D-Bus Unit structure returned in array from ListUnits Method
51  */
52 using UnitStruct =
53     std::tuple<std::string, std::string, std::string, std::string, std::string,
54                std::string, sdbusplus::message::object_path, uint32_t,
55                std::string, sdbusplus::message::object_path>;
56 
57 const static std::array<std::pair<const char*, const char*>, 3> protocolToDBus{
58     {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}};
59 
60 inline void
61     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData,
62                                         std::vector<std::string>& ntpData,
63                                         std::vector<std::string>& dnData)
64 {
65     for (const auto& obj : dbusData)
66     {
67         for (const auto& ifacePair : obj.second)
68         {
69             if (obj.first == "/xyz/openbmc_project/network/eth0")
70             {
71                 if (ifacePair.first ==
72                     "xyz.openbmc_project.Network.EthernetInterface")
73                 {
74                     for (const auto& propertyPair : ifacePair.second)
75                     {
76                         if (propertyPair.first == "NTPServers")
77                         {
78                             const std::vector<std::string>* ntpServers =
79                                 std::get_if<std::vector<std::string>>(
80                                     &propertyPair.second);
81                             if (ntpServers != nullptr)
82                             {
83                                 ntpData = *ntpServers;
84                             }
85                         }
86                         else if (propertyPair.first == "DomainName")
87                         {
88                             const std::vector<std::string>* domainNames =
89                                 std::get_if<std::vector<std::string>>(
90                                     &propertyPair.second);
91                             if (domainNames != nullptr)
92                             {
93                                 dnData = *domainNames;
94                             }
95                         }
96                     }
97                 }
98             }
99         }
100     }
101 }
102 
103 template <typename CallbackFunc>
104 void getEthernetIfaceData(CallbackFunc&& callback)
105 {
106     crow::connections::systemBus->async_method_call(
107         [callback{std::move(callback)}](
108             const boost::system::error_code errorCode,
109             const GetManagedObjects& dbusData) {
110             std::vector<std::string> ntpServers;
111             std::vector<std::string> domainNames;
112 
113             if (errorCode)
114             {
115                 callback(false, ntpServers, domainNames);
116                 return;
117             }
118 
119             extractNTPServersAndDomainNamesData(dbusData, ntpServers,
120                                                 domainNames);
121 
122             callback(true, ntpServers, domainNames);
123         },
124         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
125         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
126 }
127 
128 class NetworkProtocol : public Node
129 {
130   public:
131     NetworkProtocol(App& app) :
132         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
133     {
134         entityPrivileges = {
135             {boost::beast::http::verb::get, {{"Login"}}},
136             {boost::beast::http::verb::head, {{"Login"}}},
137             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
138             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
139             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
140             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
141     }
142 
143   private:
144     void doGet(crow::Response& res, const crow::Request&,
145                const std::vector<std::string>&) override
146     {
147         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
148 
149         getData(asyncResp);
150     }
151 
152     std::string getHostName() const
153     {
154         std::string hostName;
155 
156         std::array<char, HOST_NAME_MAX> hostNameCStr;
157         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
158         {
159             hostName = hostNameCStr.data();
160         }
161         return hostName;
162     }
163 
164     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
165     {
166         crow::connections::systemBus->async_method_call(
167             [asyncResp](const boost::system::error_code errorCode,
168                         const std::variant<std::string>& timeSyncMethod) {
169                 if (errorCode)
170                 {
171                     return;
172                 }
173 
174                 const std::string* s =
175                     std::get_if<std::string>(&timeSyncMethod);
176 
177                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
178                 {
179                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
180                 }
181                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
182                                "Method.Manual")
183                 {
184                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
185                 }
186             },
187             "xyz.openbmc_project.Settings",
188             "/xyz/openbmc_project/time/sync_method",
189             "org.freedesktop.DBus.Properties", "Get",
190             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
191     }
192 
193     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
194     {
195         asyncResp->res.jsonValue["@odata.type"] =
196             "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
197         asyncResp->res.jsonValue["@odata.id"] =
198             "/redfish/v1/Managers/bmc/NetworkProtocol";
199         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
200         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
201         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
202         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
203         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
204         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
205 
206         // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
207         // but from security perspective it is not recommended to use.
208         // Hence using protocolEnabled as false to make it OCP and security-wise
209         // compliant
210         asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
211         asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
212 
213         for (auto& protocol : protocolToDBus)
214         {
215             asyncResp->res.jsonValue[protocol.first]["Port"] =
216                 nlohmann::detail::value_t::null;
217             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
218         }
219 
220         std::string hostName = getHostName();
221 
222         asyncResp->res.jsonValue["HostName"] = hostName;
223 
224         getNTPProtocolEnabled(asyncResp);
225 
226         // TODO Get eth0 interface data, and call the below callback for JSON
227         // preparation
228         getEthernetIfaceData(
229             [hostName, asyncResp](const bool& success,
230                                   const std::vector<std::string>& ntpServers,
231                                   const std::vector<std::string>& domainNames) {
232                 if (!success)
233                 {
234                     messages::resourceNotFound(asyncResp->res,
235                                                "EthernetInterface", "eth0");
236                     return;
237                 }
238                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
239                 if (hostName.empty() == false)
240                 {
241                     std::string fqdn = hostName;
242                     if (domainNames.empty() == false)
243                     {
244                         fqdn += ".";
245                         fqdn += domainNames[0];
246                     }
247                     asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
248                 }
249             });
250 
251         crow::connections::systemBus->async_method_call(
252             [asyncResp](const boost::system::error_code e,
253                         const std::vector<UnitStruct>& r) {
254                 if (e)
255                 {
256                     asyncResp->res.jsonValue = nlohmann::json::object();
257                     messages::internalError(asyncResp->res);
258                     return;
259                 }
260                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
261                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
262                                   "HTTPS/Certificates"}};
263 
264                 for (auto& unit : r)
265                 {
266                     /* Only traverse through <xyz>.socket units */
267                     const std::string& unitName =
268                         std::get<NET_PROTO_UNIT_NAME>(unit);
269                     if (!boost::ends_with(unitName, ".socket"))
270                     {
271                         continue;
272                     }
273 
274                     for (auto& kv : protocolToDBus)
275                     {
276                         // We are interested in services, which starts with
277                         // mapped service name
278                         if (!boost::starts_with(unitName, kv.second))
279                         {
280                             continue;
281                         }
282                         const char* rfServiceKey = kv.first;
283                         const std::string& socketPath =
284                             std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
285                         const std::string& unitState =
286                             std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
287 
288                         asyncResp->res
289                             .jsonValue[rfServiceKey]["ProtocolEnabled"] =
290                             (unitState == "running") ||
291                             (unitState == "listening");
292 
293                         crow::connections::systemBus->async_method_call(
294                             [asyncResp,
295                              rfServiceKey{std::string(rfServiceKey)}](
296                                 const boost::system::error_code ec,
297                                 const std::variant<std::vector<std::tuple<
298                                     std::string, std::string>>>& resp) {
299                                 if (ec)
300                                 {
301                                     messages::internalError(asyncResp->res);
302                                     return;
303                                 }
304                                 const std::vector<
305                                     std::tuple<std::string, std::string>>*
306                                     responsePtr = std::get_if<std::vector<
307                                         std::tuple<std::string, std::string>>>(
308                                         &resp);
309                                 if (responsePtr == nullptr ||
310                                     responsePtr->size() < 1)
311                                 {
312                                     return;
313                                 }
314 
315                                 const std::string& listenStream =
316                                     std::get<NET_PROTO_LISTEN_STREAM>(
317                                         (*responsePtr)[0]);
318                                 std::size_t lastColonPos =
319                                     listenStream.rfind(':');
320                                 if (lastColonPos == std::string::npos)
321                                 {
322                                     // Not a port
323                                     return;
324                                 }
325                                 std::string portStr =
326                                     listenStream.substr(lastColonPos + 1);
327                                 if (portStr.empty())
328                                 {
329                                     return;
330                                 }
331                                 char* endPtr = nullptr;
332                                 errno = 0;
333                                 // Use strtol instead of stroi to avoid
334                                 // exceptions
335                                 long port =
336                                     std::strtol(portStr.c_str(), &endPtr, 10);
337                                 if ((errno == 0) && (*endPtr == '\0'))
338                                 {
339                                     asyncResp->res
340                                         .jsonValue[rfServiceKey]["Port"] = port;
341                                 }
342                                 return;
343                             },
344                             "org.freedesktop.systemd1", socketPath,
345                             "org.freedesktop.DBus.Properties", "Get",
346                             "org.freedesktop.systemd1.Socket", "Listen");
347 
348                         // We found service, break the inner loop.
349                         break;
350                     }
351                 }
352             },
353             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
354             "org.freedesktop.systemd1.Manager", "ListUnits");
355     }
356 
357     void handleHostnamePatch(const std::string& hostName,
358                              const std::shared_ptr<AsyncResp>& asyncResp)
359     {
360         crow::connections::systemBus->async_method_call(
361             [asyncResp](const boost::system::error_code ec) {
362                 if (ec)
363                 {
364                     messages::internalError(asyncResp->res);
365                     return;
366                 }
367             },
368             "xyz.openbmc_project.Network",
369             "/xyz/openbmc_project/network/config",
370             "org.freedesktop.DBus.Properties", "Set",
371             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
372             std::variant<std::string>(hostName));
373     }
374 
375     void handleNTPProtocolEnabled(const bool& ntpEnabled,
376                                   const std::shared_ptr<AsyncResp>& asyncResp)
377     {
378         std::string timeSyncMethod;
379         if (ntpEnabled)
380         {
381             timeSyncMethod =
382                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
383         }
384         else
385         {
386             timeSyncMethod =
387                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
388         }
389 
390         crow::connections::systemBus->async_method_call(
391             [asyncResp](const boost::system::error_code errorCode) {
392                 if (errorCode)
393                 {
394                     messages::internalError(asyncResp->res);
395                 }
396             },
397             "xyz.openbmc_project.Settings",
398             "/xyz/openbmc_project/time/sync_method",
399             "org.freedesktop.DBus.Properties", "Set",
400             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
401             std::variant<std::string>{timeSyncMethod});
402     }
403 
404     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
405                                const std::shared_ptr<AsyncResp>& asyncResp)
406     {
407         crow::connections::systemBus->async_method_call(
408             [asyncResp](const boost::system::error_code ec) {
409                 if (ec)
410                 {
411                     messages::internalError(asyncResp->res);
412                     return;
413                 }
414             },
415             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
416             "org.freedesktop.DBus.Properties", "Set",
417             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
418             std::variant<std::vector<std::string>>{ntpServers});
419     }
420 
421     void handleIpmiProtocolEnabled(const bool ipmiProtocolEnabled,
422                                    const std::shared_ptr<AsyncResp>& asyncResp)
423     {
424         crow::connections::systemBus->async_method_call(
425             [ipmiProtocolEnabled,
426              asyncResp](const boost::system::error_code ec,
427                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
428                 if (ec)
429                 {
430                     messages::internalError(asyncResp->res);
431                     return;
432                 }
433 
434                 constexpr char const* netipmidBasePath =
435                     "/xyz/openbmc_project/control/service/"
436                     "phosphor_2dipmi_2dnet_40";
437 
438                 for (const auto& entry : subtree)
439                 {
440                     if (boost::algorithm::starts_with(entry.first,
441                                                       netipmidBasePath))
442                     {
443                         crow::connections::systemBus->async_method_call(
444                             [asyncResp](const boost::system::error_code ec2) {
445                                 if (ec2)
446                                 {
447                                     messages::internalError(asyncResp->res);
448                                     return;
449                                 }
450                             },
451                             entry.second.begin()->first, entry.first,
452                             "org.freedesktop.DBus.Properties", "Set",
453                             "xyz.openbmc_project.Control.Service.Attributes",
454                             "Running", std::variant<bool>{ipmiProtocolEnabled});
455 
456                         crow::connections::systemBus->async_method_call(
457                             [asyncResp](const boost::system::error_code ec2) {
458                                 if (ec2)
459                                 {
460                                     messages::internalError(asyncResp->res);
461                                     return;
462                                 }
463                             },
464                             entry.second.begin()->first, entry.first,
465                             "org.freedesktop.DBus.Properties", "Set",
466                             "xyz.openbmc_project.Control.Service.Attributes",
467                             "Enabled", std::variant<bool>{ipmiProtocolEnabled});
468                     }
469                 }
470             },
471             "xyz.openbmc_project.ObjectMapper",
472             "/xyz/openbmc_project/object_mapper",
473             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
474             "/xyz/openbmc_project/control/service", 0,
475             std::array<const char*, 1>{
476                 "xyz.openbmc_project.Control.Service.Attributes"});
477     }
478 
479     void doPatch(crow::Response& res, const crow::Request& req,
480                  const std::vector<std::string>&) override
481     {
482         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
483         std::optional<std::string> newHostName;
484         std::optional<nlohmann::json> ntp;
485         std::optional<nlohmann::json> ipmi;
486 
487         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp,
488                                  "IPMI", ipmi))
489         {
490             return;
491         }
492 
493         res.result(boost::beast::http::status::no_content);
494         if (newHostName)
495         {
496             handleHostnamePatch(*newHostName, asyncResp);
497         }
498 
499         if (ntp)
500         {
501             std::optional<std::vector<std::string>> ntpServers;
502             std::optional<bool> ntpEnabled;
503             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
504                                      "ProtocolEnabled", ntpEnabled))
505             {
506                 return;
507             }
508 
509             if (ntpEnabled)
510             {
511                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
512             }
513 
514             if (ntpServers)
515             {
516                 std::sort((*ntpServers).begin(), (*ntpServers).end());
517                 (*ntpServers)
518                     .erase(
519                         std::unique((*ntpServers).begin(), (*ntpServers).end()),
520                         (*ntpServers).end());
521                 handleNTPServersPatch(*ntpServers, asyncResp);
522             }
523         }
524 
525         if (ipmi)
526         {
527             std::optional<bool> ipmiProtocolEnabled;
528             if (!json_util::readJson(*ipmi, res, "ProtocolEnabled",
529                                      ipmiProtocolEnabled))
530             {
531                 return;
532             }
533 
534             if (ipmiProtocolEnabled)
535             {
536                 handleIpmiProtocolEnabled(*ipmiProtocolEnabled, asyncResp);
537             }
538         }
539     }
540 };
541 
542 } // namespace redfish
543