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