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