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 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 stl_utils::removeDuplicate(ntpServers); 144 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 145 if (!hostName.empty()) 146 { 147 std::string fqdn = hostName; 148 if (!domainNames.empty()) 149 { 150 fqdn += "."; 151 fqdn += domainNames[0]; 152 } 153 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 154 } 155 }); 156 157 Privileges effectiveUserPrivileges = 158 redfish::getUserPrivileges(req.userRole); 159 160 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 161 // something only ConfigureManager can access then only display when 162 // the user has permissions ConfigureManager 163 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 164 effectiveUserPrivileges)) 165 { 166 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 167 {"@odata.id", 168 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}}; 169 } 170 171 for (const auto& protocol : protocolToDBus) 172 { 173 const std::string& protocolName = protocol.first; 174 const std::string& serviceName = protocol.second; 175 getPortStatusAndPath( 176 serviceName, 177 [asyncResp, protocolName](const boost::system::error_code ec, 178 const std::string& socketPath, 179 bool isProtocolEnabled) { 180 // If the service is not installed, that is not an error 181 if (ec == boost::system::errc::no_such_process) 182 { 183 asyncResp->res.jsonValue[protocolName]["Port"] = 184 nlohmann::detail::value_t::null; 185 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 186 false; 187 return; 188 } 189 if (ec) 190 { 191 messages::internalError(asyncResp->res); 192 return; 193 } 194 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 195 isProtocolEnabled; 196 getPortNumber( 197 socketPath, 198 [asyncResp, protocolName]( 199 const boost::system::error_code ec, int portNumber) { 200 if (ec) 201 { 202 messages::internalError(asyncResp->res); 203 return; 204 } 205 asyncResp->res.jsonValue[protocolName]["Port"] = 206 portNumber; 207 }); 208 }); 209 } 210 } // namespace redfish 211 212 inline void handleNTPProtocolEnabled( 213 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 214 { 215 std::string timeSyncMethod; 216 if (ntpEnabled) 217 { 218 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 219 } 220 else 221 { 222 timeSyncMethod = 223 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 224 } 225 226 crow::connections::systemBus->async_method_call( 227 [asyncResp](const boost::system::error_code errorCode) { 228 if (errorCode) 229 { 230 messages::internalError(asyncResp->res); 231 } 232 }, 233 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 234 "org.freedesktop.DBus.Properties", "Set", 235 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 236 dbus::utility::DbusVariantType{timeSyncMethod}); 237 } 238 239 inline void 240 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 241 std::vector<std::string>& ntpServers) 242 { 243 auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end()); 244 if (iter != ntpServers.end()) 245 { 246 std::string pointer = 247 "NTPServers/" + 248 std::to_string(std::distance(ntpServers.begin(), iter)); 249 messages::propertyValueIncorrect(asyncResp->res, pointer, *iter); 250 return; 251 } 252 253 crow::connections::systemBus->async_method_call( 254 [asyncResp, 255 ntpServers](boost::system::error_code ec, 256 const dbus::utility::MapperGetSubTreeResponse& subtree) { 257 if (ec) 258 { 259 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " 260 << ec.message(); 261 messages::internalError(asyncResp->res); 262 return; 263 } 264 265 for (const auto& [objectPath, serviceMap] : subtree) 266 { 267 for (const auto& [service, interfaces] : serviceMap) 268 { 269 for (const auto& interface : interfaces) 270 { 271 if (interface != 272 "xyz.openbmc_project.Network.EthernetInterface") 273 { 274 continue; 275 } 276 277 crow::connections::systemBus->async_method_call( 278 [asyncResp](const boost::system::error_code ec) { 279 if (ec) 280 { 281 messages::internalError(asyncResp->res); 282 return; 283 } 284 }, 285 service, objectPath, 286 "org.freedesktop.DBus.Properties", "Set", interface, 287 "NTPServers", 288 dbus::utility::DbusVariantType{ntpServers}); 289 } 290 } 291 } 292 }, 293 "xyz.openbmc_project.ObjectMapper", 294 "/xyz/openbmc_project/object_mapper", 295 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 296 "/xyz/openbmc_project", 0, 297 std::array<const char*, 1>{ 298 "xyz.openbmc_project.Network.EthernetInterface"}); 299 } 300 301 inline void 302 handleProtocolEnabled(const bool protocolEnabled, 303 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 304 const std::string_view netBasePath) 305 { 306 crow::connections::systemBus->async_method_call( 307 [protocolEnabled, asyncResp, 308 netBasePath](const boost::system::error_code ec, 309 const dbus::utility::MapperGetSubTreeResponse& subtree) { 310 if (ec) 311 { 312 messages::internalError(asyncResp->res); 313 return; 314 } 315 316 for (const auto& entry : subtree) 317 { 318 if (boost::algorithm::starts_with(entry.first, netBasePath)) 319 { 320 crow::connections::systemBus->async_method_call( 321 [asyncResp](const boost::system::error_code ec2) { 322 if (ec2) 323 { 324 messages::internalError(asyncResp->res); 325 return; 326 } 327 }, 328 entry.second.begin()->first, entry.first, 329 "org.freedesktop.DBus.Properties", "Set", 330 "xyz.openbmc_project.Control.Service.Attributes", 331 "Running", 332 dbus::utility::DbusVariantType{protocolEnabled}); 333 334 crow::connections::systemBus->async_method_call( 335 [asyncResp](const boost::system::error_code ec2) { 336 if (ec2) 337 { 338 messages::internalError(asyncResp->res); 339 return; 340 } 341 }, 342 entry.second.begin()->first, entry.first, 343 "org.freedesktop.DBus.Properties", "Set", 344 "xyz.openbmc_project.Control.Service.Attributes", 345 "Enabled", 346 dbus::utility::DbusVariantType{protocolEnabled}); 347 } 348 } 349 }, 350 "xyz.openbmc_project.ObjectMapper", 351 "/xyz/openbmc_project/object_mapper", 352 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 353 "/xyz/openbmc_project/control/service", 0, 354 std::array<const char*, 1>{ 355 "xyz.openbmc_project.Control.Service.Attributes"}); 356 } 357 358 inline std::string getHostName() 359 { 360 std::string hostName; 361 362 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 363 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 364 { 365 hostName = hostNameCStr.data(); 366 } 367 return hostName; 368 } 369 370 inline void 371 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 372 { 373 sdbusplus::asio::getProperty<std::string>( 374 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 375 "/xyz/openbmc_project/time/sync_method", 376 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 377 [asyncResp](const boost::system::error_code errorCode, 378 const std::string& timeSyncMethod) { 379 if (errorCode) 380 { 381 return; 382 } 383 384 if (timeSyncMethod == 385 "xyz.openbmc_project.Time.Synchronization.Method.NTP") 386 { 387 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 388 } 389 else if (timeSyncMethod == 390 "xyz.openbmc_project.Time.Synchronization." 391 "Method.Manual") 392 { 393 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 394 } 395 }); 396 } 397 398 inline void requestRoutesNetworkProtocol(App& app) 399 { 400 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 401 .privileges(redfish::privileges::patchManagerNetworkProtocol) 402 .methods( 403 boost::beast::http::verb:: 404 patch)([](const crow::Request& req, 405 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 406 std::optional<std::string> newHostName; 407 std::optional<nlohmann::json> ntp; 408 std::optional<nlohmann::json> ipmi; 409 std::optional<nlohmann::json> ssh; 410 411 if (!json_util::readJsonPatch(req, asyncResp->res, "NTP", ntp, 412 "HostName", newHostName, "IPMI", ipmi, 413 "SSH", ssh)) 414 { 415 return; 416 } 417 418 asyncResp->res.result(boost::beast::http::status::no_content); 419 if (newHostName) 420 { 421 messages::propertyNotWritable(asyncResp->res, "HostName"); 422 return; 423 } 424 425 if (ntp) 426 { 427 std::optional<std::vector<std::string>> ntpServers; 428 std::optional<bool> ntpEnabled; 429 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers", 430 ntpServers, "ProtocolEnabled", 431 ntpEnabled)) 432 { 433 return; 434 } 435 436 if (ntpEnabled) 437 { 438 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 439 } 440 441 if (ntpServers) 442 { 443 stl_utils::removeDuplicate(*ntpServers); 444 handleNTPServersPatch(asyncResp, *ntpServers); 445 } 446 } 447 448 if (ipmi) 449 { 450 std::optional<bool> ipmiProtocolEnabled; 451 if (!json_util::readJson(*ipmi, asyncResp->res, 452 "ProtocolEnabled", 453 ipmiProtocolEnabled)) 454 { 455 return; 456 } 457 458 if (ipmiProtocolEnabled) 459 { 460 handleProtocolEnabled( 461 *ipmiProtocolEnabled, asyncResp, 462 "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet_40"); 463 } 464 } 465 466 if (ssh) 467 { 468 std::optional<bool> sshProtocolEnabled; 469 if (!json_util::readJson(*ssh, asyncResp->res, 470 "ProtocolEnabled", sshProtocolEnabled)) 471 { 472 return; 473 } 474 475 if (sshProtocolEnabled) 476 { 477 handleProtocolEnabled( 478 *sshProtocolEnabled, asyncResp, 479 "/xyz/openbmc_project/control/service/dropbear"); 480 } 481 } 482 }); 483 484 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 485 .privileges(redfish::privileges::getManagerNetworkProtocol) 486 .methods(boost::beast::http::verb::get)( 487 [](const crow::Request& req, 488 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 489 getNetworkData(asyncResp, req); 490 }); 491 } 492 493 } // namespace redfish 494