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