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