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