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 == "StaticNTPServers") 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 auto respHandler = 324 [asyncResp, currentNtpServers]( 325 const 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, "StaticNTPServers", 356 dbus::utility::DbusVariantType{currentNtpServers}); 357 } 358 } 359 } 360 }; 361 362 std::vector<std::string> interfaces = { 363 "xyz.openbmc_project.Network.EthernetInterface"}; 364 dbus::utility::getSubTree("/xyz/openbmc_project", interfaces, 365 std::move(respHandler)); 366 } 367 368 inline void 369 handleProtocolEnabled(const bool protocolEnabled, 370 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 371 const std::string& netBasePath) 372 { 373 auto respHandler = 374 [protocolEnabled, asyncResp, 375 netBasePath](const boost::system::error_code& ec, 376 const dbus::utility::MapperGetSubTreeResponse& subtree) { 377 if (ec) 378 { 379 messages::internalError(asyncResp->res); 380 return; 381 } 382 383 for (const auto& entry : subtree) 384 { 385 if (boost::algorithm::starts_with(entry.first, netBasePath)) 386 { 387 crow::connections::systemBus->async_method_call( 388 [asyncResp](const boost::system::error_code ec2) { 389 if (ec2) 390 { 391 messages::internalError(asyncResp->res); 392 return; 393 } 394 }, 395 entry.second.begin()->first, entry.first, 396 "org.freedesktop.DBus.Properties", "Set", 397 "xyz.openbmc_project.Control.Service.Attributes", "Running", 398 dbus::utility::DbusVariantType{protocolEnabled}); 399 400 crow::connections::systemBus->async_method_call( 401 [asyncResp](const boost::system::error_code ec2) { 402 if (ec2) 403 { 404 messages::internalError(asyncResp->res); 405 return; 406 } 407 }, 408 entry.second.begin()->first, entry.first, 409 "org.freedesktop.DBus.Properties", "Set", 410 "xyz.openbmc_project.Control.Service.Attributes", "Enabled", 411 dbus::utility::DbusVariantType{protocolEnabled}); 412 } 413 } 414 }; 415 416 std::vector<std::string> interfaces = { 417 "xyz.openbmc_project.Control.Service.Attributes"}; 418 dbus::utility::getSubTree("/xyz/openbmc_project/control/service", 419 interfaces, std::move(respHandler)); 420 } 421 422 inline std::string getHostName() 423 { 424 std::string hostName; 425 426 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 427 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 428 { 429 hostName = hostNameCStr.data(); 430 } 431 return hostName; 432 } 433 434 inline void 435 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 436 { 437 sdbusplus::asio::getProperty<std::string>( 438 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 439 "/xyz/openbmc_project/time/sync_method", 440 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 441 [asyncResp](const boost::system::error_code errorCode, 442 const std::string& timeSyncMethod) { 443 if (errorCode) 444 { 445 return; 446 } 447 448 if (timeSyncMethod == 449 "xyz.openbmc_project.Time.Synchronization.Method.NTP") 450 { 451 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 452 } 453 else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization." 454 "Method.Manual") 455 { 456 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 457 } 458 }); 459 } 460 461 inline std::string encodeServiceObjectPath(const std::string& serviceName) 462 { 463 sdbusplus::message::object_path objPath( 464 "/xyz/openbmc_project/control/service"); 465 objPath /= serviceName; 466 return objPath.str; 467 } 468 469 void handleBmcNetworkProtocolHead( 470 crow::App& app, const crow::Request& req, 471 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 472 { 473 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 474 { 475 return; 476 } 477 asyncResp->res.addHeader( 478 boost::beast::http::field::link, 479 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby"); 480 } 481 482 inline void requestRoutesNetworkProtocol(App& app) 483 { 484 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 485 .privileges(redfish::privileges::patchManagerNetworkProtocol) 486 .methods(boost::beast::http::verb::patch)( 487 [&app](const crow::Request& req, 488 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 489 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 490 { 491 return; 492 } 493 std::optional<std::string> newHostName; 494 std::optional<std::vector<nlohmann::json>> ntpServerObjects; 495 std::optional<bool> ntpEnabled; 496 std::optional<bool> ipmiEnabled; 497 std::optional<bool> sshEnabled; 498 499 // clang-format off 500 if (!json_util::readJsonPatch( 501 req, asyncResp->res, 502 "HostName", newHostName, 503 "NTP/NTPServers", ntpServerObjects, 504 "NTP/ProtocolEnabled", ntpEnabled, 505 "IPMI/ProtocolEnabled", ipmiEnabled, 506 "SSH/ProtocolEnabled", sshEnabled)) 507 { 508 return; 509 } 510 // clang-format on 511 512 asyncResp->res.result(boost::beast::http::status::no_content); 513 if (newHostName) 514 { 515 messages::propertyNotWritable(asyncResp->res, "HostName"); 516 return; 517 } 518 519 if (ntpEnabled) 520 { 521 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 522 } 523 if (ntpServerObjects) 524 { 525 getEthernetIfaceData( 526 [asyncResp, ntpServerObjects]( 527 const bool success, 528 std::vector<std::string>& currentNtpServers, 529 const std::vector<std::string>& /*domainNames*/) { 530 if (!success) 531 { 532 messages::internalError(asyncResp->res); 533 return; 534 } 535 handleNTPServersPatch(asyncResp, *ntpServerObjects, 536 std::move(currentNtpServers)); 537 }); 538 } 539 540 if (ipmiEnabled) 541 { 542 handleProtocolEnabled( 543 *ipmiEnabled, asyncResp, 544 encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); 545 } 546 547 if (sshEnabled) 548 { 549 handleProtocolEnabled(*sshEnabled, asyncResp, 550 encodeServiceObjectPath(sshServiceName)); 551 } 552 }); 553 554 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 555 .privileges(redfish::privileges::headManagerNetworkProtocol) 556 .methods(boost::beast::http::verb::head)( 557 std::bind_front(handleBmcNetworkProtocolHead, std::ref(app))); 558 559 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 560 .privileges(redfish::privileges::getManagerNetworkProtocol) 561 .methods(boost::beast::http::verb::get)( 562 [&app](const crow::Request& req, 563 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 564 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 565 { 566 return; 567 } 568 getNetworkData(asyncResp, req); 569 }); 570 } 571 572 } // namespace redfish 573