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