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 <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 crow::connections::systemBus->async_method_call( 102 [callback{std::forward<CallbackFunc>(callback)}]( 103 const boost::system::error_code& errorCode, 104 const dbus::utility::ManagedObjectType& dbusData) { 105 std::vector<std::string> ntpServers; 106 std::vector<std::string> domainNames; 107 108 if (errorCode) 109 { 110 callback(false, ntpServers, domainNames); 111 return; 112 } 113 114 extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames); 115 116 callback(true, ntpServers, domainNames); 117 }, 118 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 119 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 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 "/redfish/v1/Managers/bmc/NetworkProtocol"; 170 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 171 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 172 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 173 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 174 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 175 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 176 177 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 178 // but from security perspective it is not recommended to use. 179 // Hence using protocolEnabled as false to make it OCP and security-wise 180 // compliant 181 asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr; 182 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 183 184 // The ProtocolEnabled of the following protocols is determined by 185 // inspecting the state of associated systemd sockets. If these protocols 186 // have been disabled, then the systemd socket unit files will not be found 187 // and the protocols will not be returned in this Redfish query. Set some 188 // defaults to ensure something is always returned. 189 for (const auto& nwkProtocol : networkProtocolToDbus) 190 { 191 asyncResp->res.jsonValue[nwkProtocol.first]["Port"] = nullptr; 192 asyncResp->res.jsonValue[nwkProtocol.first]["ProtocolEnabled"] = false; 193 } 194 195 std::string hostName = getHostName(); 196 197 asyncResp->res.jsonValue["HostName"] = hostName; 198 199 getNTPProtocolEnabled(asyncResp); 200 201 getEthernetIfaceData( 202 [hostName, asyncResp](const bool& success, 203 const std::vector<std::string>& ntpServers, 204 const std::vector<std::string>& domainNames) { 205 if (!success) 206 { 207 messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol", 208 "NetworkProtocol"); 209 return; 210 } 211 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 212 if (!hostName.empty()) 213 { 214 std::string fqdn = hostName; 215 if (!domainNames.empty()) 216 { 217 fqdn += "."; 218 fqdn += domainNames[0]; 219 } 220 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 221 } 222 }); 223 224 Privileges effectiveUserPrivileges = 225 redfish::getUserPrivileges(*req.session); 226 227 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 228 // something only ConfigureManager can access then only display when 229 // the user has permissions ConfigureManager 230 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 231 effectiveUserPrivileges)) 232 { 233 asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] = 234 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 235 } 236 237 getPortStatusAndPath(std::span(networkProtocolToDbus), 238 std::bind_front(afterNetworkPortRequest, asyncResp)); 239 } // namespace redfish 240 241 inline void handleNTPProtocolEnabled( 242 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 243 { 244 std::string timeSyncMethod; 245 if (ntpEnabled) 246 { 247 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 248 } 249 else 250 { 251 timeSyncMethod = 252 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 253 } 254 255 crow::connections::systemBus->async_method_call( 256 [asyncResp](const boost::system::error_code& errorCode) { 257 if (errorCode) 258 { 259 messages::internalError(asyncResp->res); 260 } 261 }, 262 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 263 "org.freedesktop.DBus.Properties", "Set", 264 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 265 dbus::utility::DbusVariantType{timeSyncMethod}); 266 } 267 268 inline void 269 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 270 const std::vector<nlohmann::json>& ntpServerObjects, 271 std::vector<std::string> currentNtpServers) 272 { 273 std::vector<std::string>::iterator currentNtpServer = 274 currentNtpServers.begin(); 275 for (size_t index = 0; index < ntpServerObjects.size(); index++) 276 { 277 const nlohmann::json& ntpServer = ntpServerObjects[index]; 278 if (ntpServer.is_null()) 279 { 280 // Can't delete an item that doesn't exist 281 if (currentNtpServer == currentNtpServers.end()) 282 { 283 messages::propertyValueNotInList(asyncResp->res, "null", 284 "NTP/NTPServers/" + 285 std::to_string(index)); 286 287 return; 288 } 289 currentNtpServer = currentNtpServers.erase(currentNtpServer); 290 continue; 291 } 292 const nlohmann::json::object_t* ntpServerObject = 293 ntpServer.get_ptr<const nlohmann::json::object_t*>(); 294 if (ntpServerObject != nullptr) 295 { 296 if (!ntpServerObject->empty()) 297 { 298 messages::propertyValueNotInList( 299 asyncResp->res, 300 ntpServer.dump(2, ' ', true, 301 nlohmann::json::error_handler_t::replace), 302 "NTP/NTPServers/" + std::to_string(index)); 303 return; 304 } 305 // Can't retain an item that doesn't exist 306 if (currentNtpServer == currentNtpServers.end()) 307 { 308 messages::propertyValueOutOfRange( 309 asyncResp->res, 310 ntpServer.dump(2, ' ', true, 311 nlohmann::json::error_handler_t::replace), 312 "NTP/NTPServers/" + std::to_string(index)); 313 314 return; 315 } 316 // empty objects should leave the NtpServer unmodified 317 currentNtpServer++; 318 continue; 319 } 320 321 const std::string* ntpServerStr = 322 ntpServer.get_ptr<const std::string*>(); 323 if (ntpServerStr == nullptr) 324 { 325 messages::propertyValueTypeError( 326 asyncResp->res, 327 ntpServer.dump(2, ' ', true, 328 nlohmann::json::error_handler_t::replace), 329 "NTP/NTPServers/" + std::to_string(index)); 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 crow::connections::systemBus->async_method_call( 373 [asyncResp](const boost::system::error_code& ec2) { 374 if (ec2) 375 { 376 messages::internalError(asyncResp->res); 377 return; 378 } 379 }, 380 service, objectPath, "org.freedesktop.DBus.Properties", 381 "Set", interface, "StaticNTPServers", 382 dbus::utility::DbusVariantType{currentNtpServers}); 383 } 384 } 385 } 386 }); 387 } 388 389 inline void 390 handleProtocolEnabled(const bool protocolEnabled, 391 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 392 const std::string& netBasePath) 393 { 394 constexpr std::array<std::string_view, 1> interfaces = { 395 "xyz.openbmc_project.Control.Service.Attributes"}; 396 dbus::utility::getSubTree( 397 "/xyz/openbmc_project/control/service", 0, interfaces, 398 [protocolEnabled, asyncResp, 399 netBasePath](const boost::system::error_code& ec, 400 const dbus::utility::MapperGetSubTreeResponse& subtree) { 401 if (ec) 402 { 403 messages::internalError(asyncResp->res); 404 return; 405 } 406 407 for (const auto& entry : subtree) 408 { 409 if (boost::algorithm::starts_with(entry.first, netBasePath)) 410 { 411 crow::connections::systemBus->async_method_call( 412 [asyncResp](const boost::system::error_code& ec2) { 413 if (ec2) 414 { 415 messages::internalError(asyncResp->res); 416 return; 417 } 418 }, 419 entry.second.begin()->first, entry.first, 420 "org.freedesktop.DBus.Properties", "Set", 421 "xyz.openbmc_project.Control.Service.Attributes", "Running", 422 dbus::utility::DbusVariantType{protocolEnabled}); 423 424 crow::connections::systemBus->async_method_call( 425 [asyncResp](const boost::system::error_code& ec2) { 426 if (ec2) 427 { 428 messages::internalError(asyncResp->res); 429 return; 430 } 431 }, 432 entry.second.begin()->first, entry.first, 433 "org.freedesktop.DBus.Properties", "Set", 434 "xyz.openbmc_project.Control.Service.Attributes", "Enabled", 435 dbus::utility::DbusVariantType{protocolEnabled}); 436 } 437 } 438 }); 439 } 440 441 inline std::string getHostName() 442 { 443 std::string hostName; 444 445 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 446 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 447 { 448 hostName = hostNameCStr.data(); 449 } 450 return hostName; 451 } 452 453 inline void 454 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 455 { 456 sdbusplus::asio::getProperty<std::string>( 457 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 458 "/xyz/openbmc_project/time/sync_method", 459 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 460 [asyncResp](const boost::system::error_code& errorCode, 461 const std::string& timeSyncMethod) { 462 if (errorCode) 463 { 464 return; 465 } 466 467 if (timeSyncMethod == 468 "xyz.openbmc_project.Time.Synchronization.Method.NTP") 469 { 470 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 471 } 472 else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization." 473 "Method.Manual") 474 { 475 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 476 } 477 }); 478 } 479 480 inline std::string encodeServiceObjectPath(std::string_view serviceName) 481 { 482 sdbusplus::message::object_path objPath( 483 "/xyz/openbmc_project/control/service"); 484 objPath /= serviceName; 485 return objPath.str; 486 } 487 488 inline void handleBmcNetworkProtocolHead( 489 crow::App& app, const crow::Request& req, 490 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 491 { 492 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 493 { 494 return; 495 } 496 asyncResp->res.addHeader( 497 boost::beast::http::field::link, 498 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 499 } 500 501 inline void handleManagersNetworkProtocolPatch( 502 App& app, const crow::Request& req, 503 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 504 { 505 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 506 { 507 return; 508 } 509 std::optional<std::string> newHostName; 510 std::optional<std::vector<nlohmann::json>> ntpServerObjects; 511 std::optional<bool> ntpEnabled; 512 std::optional<bool> ipmiEnabled; 513 std::optional<bool> sshEnabled; 514 515 // clang-format off 516 if (!json_util::readJsonPatch( 517 req, asyncResp->res, 518 "HostName", newHostName, 519 "NTP/NTPServers", ntpServerObjects, 520 "NTP/ProtocolEnabled", ntpEnabled, 521 "IPMI/ProtocolEnabled", ipmiEnabled, 522 "SSH/ProtocolEnabled", sshEnabled)) 523 { 524 return; 525 } 526 // clang-format on 527 528 asyncResp->res.result(boost::beast::http::status::no_content); 529 if (newHostName) 530 { 531 messages::propertyNotWritable(asyncResp->res, "HostName"); 532 return; 533 } 534 535 if (ntpEnabled) 536 { 537 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 538 } 539 if (ntpServerObjects) 540 { 541 getEthernetIfaceData( 542 [asyncResp, ntpServerObjects]( 543 const bool success, std::vector<std::string>& currentNtpServers, 544 const std::vector<std::string>& /*domainNames*/) { 545 if (!success) 546 { 547 messages::internalError(asyncResp->res); 548 return; 549 } 550 handleNTPServersPatch(asyncResp, *ntpServerObjects, 551 std::move(currentNtpServers)); 552 }); 553 } 554 555 if (ipmiEnabled) 556 { 557 handleProtocolEnabled( 558 *ipmiEnabled, asyncResp, 559 encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); 560 } 561 562 if (sshEnabled) 563 { 564 handleProtocolEnabled(*sshEnabled, asyncResp, 565 encodeServiceObjectPath(sshServiceName)); 566 } 567 } 568 569 inline void handleManagersNetworkProtocolHead( 570 App& app, const crow::Request& req, 571 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 572 { 573 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 574 { 575 return; 576 } 577 asyncResp->res.addHeader( 578 boost::beast::http::field::link, 579 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 580 } 581 582 inline void handleManagersNetworkProtocolGet( 583 App& app, const crow::Request& req, 584 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 585 { 586 handleManagersNetworkProtocolHead(app, req, asyncResp); 587 getNetworkData(asyncResp, req); 588 } 589 590 inline void requestRoutesNetworkProtocol(App& app) 591 { 592 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 593 .privileges(redfish::privileges::patchManagerNetworkProtocol) 594 .methods(boost::beast::http::verb::patch)( 595 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app))); 596 597 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 598 .privileges(redfish::privileges::headManagerNetworkProtocol) 599 .methods(boost::beast::http::verb::head)( 600 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app))); 601 602 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 603 .privileges(redfish::privileges::getManagerNetworkProtocol) 604 .methods(boost::beast::http::verb::get)( 605 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app))); 606 } 607 608 } // namespace redfish 609