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, 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"] = resource::Health::OK; 176 asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK; 177 asyncResp->res.jsonValue["Status"]["State"] = resource::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, "NTP/NTPServers/", service, 374 objectPath, interface, "StaticNTPServers", 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, "IPMI/ProtocolEnabled", 406 entry.second.begin()->first, entry.first, 407 "xyz.openbmc_project.Control.Service.Attributes", "Running", 408 protocolEnabled); 409 setDbusProperty( 410 asyncResp, "IPMI/ProtocolEnabled", 411 entry.second.begin()->first, entry.first, 412 "xyz.openbmc_project.Control.Service.Attributes", "Enabled", 413 protocolEnabled); 414 } 415 } 416 }); 417 } 418 419 inline std::string getHostName() 420 { 421 std::string hostName; 422 423 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 424 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 425 { 426 hostName = hostNameCStr.data(); 427 } 428 return hostName; 429 } 430 431 inline void 432 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 433 { 434 sdbusplus::asio::getProperty<bool>( 435 *crow::connections::systemBus, "org.freedesktop.timedate1", 436 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", 437 [asyncResp](const boost::system::error_code& ec, bool enabled) { 438 if (ec) 439 { 440 BMCWEB_LOG_WARNING( 441 "Failed to get NTP status, assuming not supported"); 442 return; 443 } 444 445 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled; 446 }); 447 } 448 449 inline std::string encodeServiceObjectPath(std::string_view serviceName) 450 { 451 sdbusplus::message::object_path objPath( 452 "/xyz/openbmc_project/control/service"); 453 objPath /= serviceName; 454 return objPath.str; 455 } 456 457 inline void handleBmcNetworkProtocolHead( 458 crow::App& app, const crow::Request& req, 459 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 460 { 461 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 462 { 463 return; 464 } 465 asyncResp->res.addHeader( 466 boost::beast::http::field::link, 467 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 468 } 469 470 inline void handleManagersNetworkProtocolPatch( 471 App& app, const crow::Request& req, 472 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 473 const std::string& managerId) 474 { 475 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 476 { 477 return; 478 } 479 480 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 481 { 482 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 483 return; 484 } 485 486 std::optional<std::string> newHostName; 487 488 std::optional<std::vector<IpAddress>> ntpServerObjects; 489 std::optional<bool> ntpEnabled; 490 std::optional<bool> ipmiEnabled; 491 std::optional<bool> sshEnabled; 492 493 // clang-format off 494 if (!json_util::readJsonPatch( 495 req, asyncResp->res, 496 "HostName", newHostName, 497 "NTP/NTPServers", ntpServerObjects, 498 "NTP/ProtocolEnabled", ntpEnabled, 499 "IPMI/ProtocolEnabled", ipmiEnabled, 500 "SSH/ProtocolEnabled", sshEnabled)) 501 { 502 return; 503 } 504 // clang-format on 505 506 asyncResp->res.result(boost::beast::http::status::no_content); 507 if (newHostName) 508 { 509 messages::propertyNotWritable(asyncResp->res, "HostName"); 510 return; 511 } 512 513 if (ntpEnabled) 514 { 515 handleNTPProtocolEnabled(asyncResp, *ntpEnabled); 516 } 517 if (ntpServerObjects) 518 { 519 getEthernetIfaceData( 520 [asyncResp, ntpServerObjects]( 521 const bool success, std::vector<std::string>& currentNtpServers, 522 const std::vector<std::string>& /*domainNames*/) { 523 if (!success) 524 { 525 messages::internalError(asyncResp->res); 526 return; 527 } 528 handleNTPServersPatch(asyncResp, *ntpServerObjects, 529 std::move(currentNtpServers)); 530 }); 531 } 532 533 if (ipmiEnabled) 534 { 535 handleProtocolEnabled( 536 *ipmiEnabled, asyncResp, 537 encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); 538 } 539 540 if (sshEnabled) 541 { 542 handleProtocolEnabled(*sshEnabled, asyncResp, 543 encodeServiceObjectPath(sshServiceName)); 544 } 545 } 546 547 inline void handleManagersNetworkProtocolHead( 548 App& app, const crow::Request& req, 549 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 550 const std::string& managerId) 551 { 552 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 553 { 554 return; 555 } 556 asyncResp->res.addHeader( 557 boost::beast::http::field::link, 558 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 559 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 560 { 561 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 562 return; 563 } 564 } 565 566 inline void handleManagersNetworkProtocolGet( 567 App& app, const crow::Request& req, 568 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 569 const std::string& managerId) 570 { 571 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 572 { 573 return; 574 } 575 asyncResp->res.addHeader( 576 boost::beast::http::field::link, 577 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 578 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 579 { 580 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 581 return; 582 } 583 584 getNetworkData(asyncResp, req); 585 } 586 587 inline void requestRoutesNetworkProtocol(App& app) 588 { 589 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 590 .privileges(redfish::privileges::patchManagerNetworkProtocol) 591 .methods(boost::beast::http::verb::patch)( 592 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app))); 593 594 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 595 .privileges(redfish::privileges::headManagerNetworkProtocol) 596 .methods(boost::beast::http::verb::head)( 597 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app))); 598 599 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/") 600 .privileges(redfish::privileges::getManagerNetworkProtocol) 601 .methods(boost::beast::http::verb::get)( 602 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app))); 603 } 604 605 } // namespace redfish 606