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