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