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