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 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{ 40 {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}}; 41 42 inline void extractNTPServersAndDomainNamesData( 43 const dbus::utility::ManagedObjectType& dbusData, 44 std::vector<std::string>& ntpData, std::vector<std::string>& dnData) 45 { 46 for (const auto& obj : dbusData) 47 { 48 for (const auto& ifacePair : obj.second) 49 { 50 if (ifacePair.first != 51 "xyz.openbmc_project.Network.EthernetInterface") 52 { 53 continue; 54 } 55 56 for (const auto& propertyPair : ifacePair.second) 57 { 58 if (propertyPair.first == "NTPServers") 59 { 60 const std::vector<std::string>* ntpServers = 61 std::get_if<std::vector<std::string>>( 62 &propertyPair.second); 63 if (ntpServers != nullptr) 64 { 65 ntpData = *ntpServers; 66 } 67 } 68 else if (propertyPair.first == "DomainName") 69 { 70 const std::vector<std::string>* domainNames = 71 std::get_if<std::vector<std::string>>( 72 &propertyPair.second); 73 if (domainNames != nullptr) 74 { 75 dnData = *domainNames; 76 } 77 } 78 } 79 } 80 } 81 } 82 83 template <typename CallbackFunc> 84 void getEthernetIfaceData(CallbackFunc&& callback) 85 { 86 crow::connections::systemBus->async_method_call( 87 [callback{std::forward<CallbackFunc>(callback)}]( 88 const boost::system::error_code errorCode, 89 const dbus::utility::ManagedObjectType& dbusData) { 90 std::vector<std::string> ntpServers; 91 std::vector<std::string> domainNames; 92 93 if (errorCode) 94 { 95 callback(false, ntpServers, domainNames); 96 return; 97 } 98 99 extractNTPServersAndDomainNamesData(dbusData, ntpServers, 100 domainNames); 101 102 callback(true, ntpServers, domainNames); 103 }, 104 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 105 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 106 } 107 108 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 109 const crow::Request& req) 110 { 111 asyncResp->res.jsonValue["@odata.type"] = 112 "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol"; 113 asyncResp->res.jsonValue["@odata.id"] = 114 "/redfish/v1/Managers/bmc/NetworkProtocol"; 115 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 116 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 117 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 118 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 119 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 120 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 121 122 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 123 // but from security perspective it is not recommended to use. 124 // Hence using protocolEnabled as false to make it OCP and security-wise 125 // compliant 126 asyncResp->res.jsonValue["HTTP"]["Port"] = 0; 127 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 128 129 std::string hostName = getHostName(); 130 131 asyncResp->res.jsonValue["HostName"] = hostName; 132 133 getNTPProtocolEnabled(asyncResp); 134 135 getEthernetIfaceData([hostName, asyncResp]( 136 const bool& success, 137 std::vector<std::string>& ntpServers, 138 const std::vector<std::string>& domainNames) { 139 if (!success) 140 { 141 messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol", 142 "NetworkProtocol"); 143 return; 144 } 145 stl_utils::removeDuplicate(ntpServers); 146 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 147 if (!hostName.empty()) 148 { 149 std::string fqdn = hostName; 150 if (!domainNames.empty()) 151 { 152 fqdn += "."; 153 fqdn += domainNames[0]; 154 } 155 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 156 } 157 }); 158 159 Privileges effectiveUserPrivileges = 160 redfish::getUserPrivileges(req.userRole); 161 162 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 163 // something only ConfigureManager can access then only display when 164 // the user has permissions ConfigureManager 165 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 166 effectiveUserPrivileges)) 167 { 168 asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@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<std::vector<std::string>> ntpServers; 414 std::optional<bool> ntpEnabled; 415 std::optional<bool> ipmiEnabled; 416 std::optional<bool> sshEnabled; 417 418 // clang-format off 419 if (!json_util::readJsonPatch( 420 req, asyncResp->res, 421 "HostName", newHostName, 422 "NTP/NTPServers", ntpServers, 423 "NTP/ProtocolEnabled", ntpEnabled, 424 "IPMI/ProtocolEnabled", ipmiEnabled, 425 "SSH/ProtocolEnabled", sshEnabled)) 426 { 427 return; 428 } 429 // clang-format on 430 431 asyncResp->res.result(boost::beast::http::status::no_content); 432 if (newHostName) 433 { 434 messages::propertyNotWritable(asyncResp->res, "HostName"); 435 return; 436 } 437 438 if (ntpEnabled) 439 { 440 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 441 } 442 if (ntpServers) 443 { 444 stl_utils::removeDuplicate(*ntpServers); 445 handleNTPServersPatch(asyncResp, *ntpServers); 446 } 447 448 if (ipmiEnabled) 449 { 450 handleProtocolEnabled( 451 *ipmiEnabled, asyncResp, 452 "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet_40"); 453 } 454 455 if (sshEnabled) 456 { 457 handleProtocolEnabled( 458 *sshEnabled, asyncResp, 459 "/xyz/openbmc_project/control/service/dropbear"); 460 } 461 }); 462 463 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 464 .privileges(redfish::privileges::getManagerNetworkProtocol) 465 .methods(boost::beast::http::verb::get)( 466 [&app](const crow::Request& req, 467 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 468 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 469 { 470 return; 471 } 472 getNetworkData(asyncResp, req); 473 }); 474 } 475 476 } // namespace redfish 477