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