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