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 "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "error_messages.hpp"
21 #include "openbmc_dbus_rest.hpp"
22 #include "query.hpp"
23 #include "redfish_util.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "utils/json_utils.hpp"
26 #include "utils/stl_utils.hpp"
27 
28 #include <boost/system/error_code.hpp>
29 #include <sdbusplus/asio/property.hpp>
30 
31 #include <array>
32 #include <optional>
33 #include <string_view>
34 #include <variant>
35 
36 namespace redfish
37 {
38 
39 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
40 std::string getHostName();
41 
42 static constexpr std::string_view sshServiceName = "dropbear";
43 static constexpr std::string_view httpsServiceName = "bmcweb";
44 static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
45 
46 // Mapping from Redfish NetworkProtocol key name to backend service that hosts
47 // that protocol.
48 static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
49     networkProtocolToDbus = {{{"SSH", sshServiceName},
50                               {"HTTPS", httpsServiceName},
51                               {"IPMI", ipmiServiceName}}};
52 
53 inline void extractNTPServersAndDomainNamesData(
54     const dbus::utility::ManagedObjectType& dbusData,
55     std::vector<std::string>& ntpData, std::vector<std::string>& dnData)
56 {
57     for (const auto& obj : dbusData)
58     {
59         for (const auto& ifacePair : obj.second)
60         {
61             if (ifacePair.first !=
62                 "xyz.openbmc_project.Network.EthernetInterface")
63             {
64                 continue;
65             }
66 
67             for (const auto& propertyPair : ifacePair.second)
68             {
69                 if (propertyPair.first == "StaticNTPServers")
70                 {
71                     const std::vector<std::string>* ntpServers =
72                         std::get_if<std::vector<std::string>>(
73                             &propertyPair.second);
74                     if (ntpServers != nullptr)
75                     {
76                         ntpData = *ntpServers;
77                     }
78                 }
79                 else if (propertyPair.first == "DomainName")
80                 {
81                     const std::vector<std::string>* domainNames =
82                         std::get_if<std::vector<std::string>>(
83                             &propertyPair.second);
84                     if (domainNames != nullptr)
85                     {
86                         dnData = *domainNames;
87                     }
88                 }
89             }
90         }
91     }
92     stl_utils::removeDuplicate(ntpData);
93 }
94 
95 template <typename CallbackFunc>
96 void getEthernetIfaceData(CallbackFunc&& callback)
97 {
98     crow::connections::systemBus->async_method_call(
99         [callback{std::forward<CallbackFunc>(callback)}](
100             const boost::system::error_code& errorCode,
101             const dbus::utility::ManagedObjectType& dbusData) {
102         std::vector<std::string> ntpServers;
103         std::vector<std::string> domainNames;
104 
105         if (errorCode)
106         {
107             callback(false, ntpServers, domainNames);
108             return;
109         }
110 
111         extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames);
112 
113         callback(true, ntpServers, domainNames);
114         },
115         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
116         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
117 }
118 
119 inline void afterNetworkPortRequest(
120     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
121     const boost::system::error_code& ec,
122     const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
123 {
124     if (ec)
125     {
126         messages::internalError(asyncResp->res);
127         return;
128     }
129     for (const auto& data : socketData)
130     {
131         const std::string& socketPath = get<0>(data);
132         const std::string& protocolName = get<1>(data);
133         bool isProtocolEnabled = get<2>(data);
134 
135         asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
136             isProtocolEnabled;
137         asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
138         getPortNumber(socketPath, [asyncResp, protocolName](
139                                       const boost::system::error_code& ec2,
140                                       int portNumber) {
141             if (ec2)
142             {
143                 messages::internalError(asyncResp->res);
144                 return;
145             }
146             asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
147         });
148     }
149 }
150 
151 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
152                            const crow::Request& req)
153 {
154     asyncResp->res.addHeader(
155         boost::beast::http::field::link,
156         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
157     asyncResp->res.jsonValue["@odata.type"] =
158         "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
159     asyncResp->res.jsonValue["@odata.id"] =
160         "/redfish/v1/Managers/bmc/NetworkProtocol";
161     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
162     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
163     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
164     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
165     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
166     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
167 
168     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
169     // but from security perspective it is not recommended to use.
170     // Hence using protocolEnabled as false to make it OCP and security-wise
171     // compliant
172     asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
173     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
174 
175     std::string hostName = getHostName();
176 
177     asyncResp->res.jsonValue["HostName"] = hostName;
178 
179     getNTPProtocolEnabled(asyncResp);
180 
181     getEthernetIfaceData(
182         [hostName, asyncResp](const bool& success,
183                               const std::vector<std::string>& ntpServers,
184                               const std::vector<std::string>& domainNames) {
185         if (!success)
186         {
187             messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
188                                        "NetworkProtocol");
189             return;
190         }
191         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
192         if (!hostName.empty())
193         {
194             std::string fqdn = hostName;
195             if (!domainNames.empty())
196             {
197                 fqdn += ".";
198                 fqdn += domainNames[0];
199             }
200             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
201         }
202     });
203 
204     Privileges effectiveUserPrivileges =
205         redfish::getUserPrivileges(req.userRole);
206 
207     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
208     // something only ConfigureManager can access then only display when
209     // the user has permissions ConfigureManager
210     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
211                                          effectiveUserPrivileges))
212     {
213         asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
214             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
215     }
216 
217     getPortStatusAndPath(std::span(networkProtocolToDbus),
218                          std::bind_front(afterNetworkPortRequest, asyncResp));
219 } // namespace redfish
220 
221 inline void handleNTPProtocolEnabled(
222     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
223 {
224     std::string timeSyncMethod;
225     if (ntpEnabled)
226     {
227         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
228     }
229     else
230     {
231         timeSyncMethod =
232             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
233     }
234 
235     crow::connections::systemBus->async_method_call(
236         [asyncResp](const boost::system::error_code& errorCode) {
237         if (errorCode)
238         {
239             messages::internalError(asyncResp->res);
240         }
241         },
242         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
243         "org.freedesktop.DBus.Properties", "Set",
244         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
245         dbus::utility::DbusVariantType{timeSyncMethod});
246 }
247 
248 inline void
249     handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
250                           const std::vector<nlohmann::json>& ntpServerObjects,
251                           std::vector<std::string> currentNtpServers)
252 {
253     std::vector<std::string>::iterator currentNtpServer =
254         currentNtpServers.begin();
255     for (size_t index = 0; index < ntpServerObjects.size(); index++)
256     {
257         const nlohmann::json& ntpServer = ntpServerObjects[index];
258         if (ntpServer.is_null())
259         {
260             // Can't delete an item that doesn't exist
261             if (currentNtpServer == currentNtpServers.end())
262             {
263                 messages::propertyValueNotInList(asyncResp->res, "null",
264                                                  "NTP/NTPServers/" +
265                                                      std::to_string(index));
266 
267                 return;
268             }
269             currentNtpServer = currentNtpServers.erase(currentNtpServer);
270             continue;
271         }
272         const nlohmann::json::object_t* ntpServerObject =
273             ntpServer.get_ptr<const nlohmann::json::object_t*>();
274         if (ntpServerObject != nullptr)
275         {
276             if (!ntpServerObject->empty())
277             {
278                 messages::propertyValueNotInList(
279                     asyncResp->res,
280                     ntpServer.dump(2, ' ', true,
281                                    nlohmann::json::error_handler_t::replace),
282                     "NTP/NTPServers/" + std::to_string(index));
283                 return;
284             }
285             // Can't retain an item that doesn't exist
286             if (currentNtpServer == currentNtpServers.end())
287             {
288                 messages::propertyValueOutOfRange(
289                     asyncResp->res,
290                     ntpServer.dump(2, ' ', true,
291                                    nlohmann::json::error_handler_t::replace),
292                     "NTP/NTPServers/" + std::to_string(index));
293 
294                 return;
295             }
296             // empty objects should leave the NtpServer unmodified
297             currentNtpServer++;
298             continue;
299         }
300 
301         const std::string* ntpServerStr =
302             ntpServer.get_ptr<const std::string*>();
303         if (ntpServerStr == nullptr)
304         {
305             messages::propertyValueTypeError(
306                 asyncResp->res,
307                 ntpServer.dump(2, ' ', true,
308                                nlohmann::json::error_handler_t::replace),
309                 "NTP/NTPServers/" + std::to_string(index));
310             return;
311         }
312         if (currentNtpServer == currentNtpServers.end())
313         {
314             // if we're at the end of the list, append to the end
315             currentNtpServers.push_back(*ntpServerStr);
316             currentNtpServer = currentNtpServers.end();
317             continue;
318         }
319         *currentNtpServer = *ntpServerStr;
320         currentNtpServer++;
321     }
322 
323     // Any remaining array elements should be removed
324     currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
325 
326     constexpr std::array<std::string_view, 1> ethInterfaces = {
327         "xyz.openbmc_project.Network.EthernetInterface"};
328     dbus::utility::getSubTree(
329         "/xyz/openbmc_project", 0, ethInterfaces,
330         [asyncResp, currentNtpServers](
331             const boost::system::error_code& ec,
332             const dbus::utility::MapperGetSubTreeResponse& subtree) {
333         if (ec)
334         {
335             BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message();
336             messages::internalError(asyncResp->res);
337             return;
338         }
339 
340         for (const auto& [objectPath, serviceMap] : subtree)
341         {
342             for (const auto& [service, interfaces] : serviceMap)
343             {
344                 for (const auto& interface : interfaces)
345                 {
346                     if (interface !=
347                         "xyz.openbmc_project.Network.EthernetInterface")
348                     {
349                         continue;
350                     }
351 
352                     crow::connections::systemBus->async_method_call(
353                         [asyncResp](const boost::system::error_code& ec2) {
354                         if (ec2)
355                         {
356                             messages::internalError(asyncResp->res);
357                             return;
358                         }
359                         },
360                         service, objectPath, "org.freedesktop.DBus.Properties",
361                         "Set", interface, "StaticNTPServers",
362                         dbus::utility::DbusVariantType{currentNtpServers});
363                 }
364             }
365         }
366         });
367 }
368 
369 inline void
370     handleProtocolEnabled(const bool protocolEnabled,
371                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
372                           const std::string& netBasePath)
373 {
374     constexpr std::array<std::string_view, 1> interfaces = {
375         "xyz.openbmc_project.Control.Service.Attributes"};
376     dbus::utility::getSubTree(
377         "/xyz/openbmc_project/control/service", 0, interfaces,
378         [protocolEnabled, asyncResp,
379          netBasePath](const boost::system::error_code& ec,
380                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
381         if (ec)
382         {
383             messages::internalError(asyncResp->res);
384             return;
385         }
386 
387         for (const auto& entry : subtree)
388         {
389             if (boost::algorithm::starts_with(entry.first, netBasePath))
390             {
391                 crow::connections::systemBus->async_method_call(
392                     [asyncResp](const boost::system::error_code& ec2) {
393                     if (ec2)
394                     {
395                         messages::internalError(asyncResp->res);
396                         return;
397                     }
398                     },
399                     entry.second.begin()->first, entry.first,
400                     "org.freedesktop.DBus.Properties", "Set",
401                     "xyz.openbmc_project.Control.Service.Attributes", "Running",
402                     dbus::utility::DbusVariantType{protocolEnabled});
403 
404                 crow::connections::systemBus->async_method_call(
405                     [asyncResp](const boost::system::error_code& ec2) {
406                     if (ec2)
407                     {
408                         messages::internalError(asyncResp->res);
409                         return;
410                     }
411                     },
412                     entry.second.begin()->first, entry.first,
413                     "org.freedesktop.DBus.Properties", "Set",
414                     "xyz.openbmc_project.Control.Service.Attributes", "Enabled",
415                     dbus::utility::DbusVariantType{protocolEnabled});
416             }
417         }
418         });
419 }
420 
421 inline std::string getHostName()
422 {
423     std::string hostName;
424 
425     std::array<char, HOST_NAME_MAX> hostNameCStr{};
426     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
427     {
428         hostName = hostNameCStr.data();
429     }
430     return hostName;
431 }
432 
433 inline void
434     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
435 {
436     sdbusplus::asio::getProperty<std::string>(
437         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
438         "/xyz/openbmc_project/time/sync_method",
439         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
440         [asyncResp](const boost::system::error_code& errorCode,
441                     const std::string& timeSyncMethod) {
442         if (errorCode)
443         {
444             return;
445         }
446 
447         if (timeSyncMethod ==
448             "xyz.openbmc_project.Time.Synchronization.Method.NTP")
449         {
450             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
451         }
452         else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization."
453                                    "Method.Manual")
454         {
455             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
456         }
457         });
458 }
459 
460 inline std::string encodeServiceObjectPath(std::string_view serviceName)
461 {
462     sdbusplus::message::object_path objPath(
463         "/xyz/openbmc_project/control/service");
464     objPath /= serviceName;
465     return objPath.str;
466 }
467 
468 inline void handleBmcNetworkProtocolHead(
469     crow::App& app, const crow::Request& req,
470     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
471 {
472     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
473     {
474         return;
475     }
476     asyncResp->res.addHeader(
477         boost::beast::http::field::link,
478         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
479 }
480 
481 inline void handleManagersNetworkProtocolPatch(
482     App& app, const crow::Request& req,
483     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
484 {
485 
486     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
487     {
488         return;
489     }
490     std::optional<std::string> newHostName;
491     std::optional<std::vector<nlohmann::json>> ntpServerObjects;
492     std::optional<bool> ntpEnabled;
493     std::optional<bool> ipmiEnabled;
494     std::optional<bool> sshEnabled;
495 
496     // clang-format off
497         if (!json_util::readJsonPatch(
498                 req, asyncResp->res,
499                 "HostName", newHostName,
500                 "NTP/NTPServers", ntpServerObjects,
501                 "NTP/ProtocolEnabled", ntpEnabled,
502                 "IPMI/ProtocolEnabled", ipmiEnabled,
503                 "SSH/ProtocolEnabled", sshEnabled))
504         {
505             return;
506         }
507     // clang-format on
508 
509     asyncResp->res.result(boost::beast::http::status::no_content);
510     if (newHostName)
511     {
512         messages::propertyNotWritable(asyncResp->res, "HostName");
513         return;
514     }
515 
516     if (ntpEnabled)
517     {
518         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
519     }
520     if (ntpServerObjects)
521     {
522         getEthernetIfaceData(
523             [asyncResp, ntpServerObjects](
524                 const bool success, std::vector<std::string>& currentNtpServers,
525                 const std::vector<std::string>& /*domainNames*/) {
526             if (!success)
527             {
528                 messages::internalError(asyncResp->res);
529                 return;
530             }
531             handleNTPServersPatch(asyncResp, *ntpServerObjects,
532                                   std::move(currentNtpServers));
533         });
534     }
535 
536     if (ipmiEnabled)
537     {
538         handleProtocolEnabled(
539             *ipmiEnabled, asyncResp,
540             encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
541     }
542 
543     if (sshEnabled)
544     {
545         handleProtocolEnabled(*sshEnabled, asyncResp,
546                               encodeServiceObjectPath(sshServiceName));
547     }
548 }
549 
550 inline void handleManagersNetworkProtocolHead(
551     App& app, const crow::Request& req,
552     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
553 {
554     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
555     {
556         return;
557     }
558     asyncResp->res.addHeader(
559         boost::beast::http::field::link,
560         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
561 }
562 
563 inline void handleManagersNetworkProtocolGet(
564     App& app, const crow::Request& req,
565     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
566 {
567     handleManagersNetworkProtocolHead(app, req, asyncResp);
568     getNetworkData(asyncResp, req);
569 }
570 
571 inline void requestRoutesNetworkProtocol(App& app)
572 {
573     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
574         .privileges(redfish::privileges::patchManagerNetworkProtocol)
575         .methods(boost::beast::http::verb::patch)(
576             std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
577 
578     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
579         .privileges(redfish::privileges::headManagerNetworkProtocol)
580         .methods(boost::beast::http::verb::head)(
581             std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
582 
583     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
584         .privileges(redfish::privileges::getManagerNetworkProtocol)
585         .methods(boost::beast::http::verb::get)(
586             std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
587 }
588 
589 } // namespace redfish
590