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