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