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