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