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, 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( 135 [hostName, asyncResp](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"]["@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(socketPath, [asyncResp, protocolName]( 197 const boost::system::error_code ec, 198 int portNumber) { 199 if (ec) 200 { 201 messages::internalError(asyncResp->res); 202 return; 203 } 204 asyncResp->res.jsonValue[protocolName]["Port"] = portNumber; 205 }); 206 }); 207 } 208 } // namespace redfish 209 210 inline void handleNTPProtocolEnabled( 211 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 212 { 213 std::string timeSyncMethod; 214 if (ntpEnabled) 215 { 216 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 217 } 218 else 219 { 220 timeSyncMethod = 221 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 222 } 223 224 crow::connections::systemBus->async_method_call( 225 [asyncResp](const boost::system::error_code errorCode) { 226 if (errorCode) 227 { 228 messages::internalError(asyncResp->res); 229 } 230 }, 231 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 232 "org.freedesktop.DBus.Properties", "Set", 233 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 234 dbus::utility::DbusVariantType{timeSyncMethod}); 235 } 236 237 inline void 238 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 239 std::vector<std::string>& ntpServers) 240 { 241 auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end()); 242 if (iter != ntpServers.end()) 243 { 244 std::string pointer = 245 "NTPServers/" + 246 std::to_string(std::distance(ntpServers.begin(), iter)); 247 messages::propertyValueIncorrect(asyncResp->res, pointer, *iter); 248 return; 249 } 250 251 crow::connections::systemBus->async_method_call( 252 [asyncResp, 253 ntpServers](boost::system::error_code ec, 254 const dbus::utility::MapperGetSubTreeResponse& subtree) { 255 if (ec) 256 { 257 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 258 messages::internalError(asyncResp->res); 259 return; 260 } 261 262 for (const auto& [objectPath, serviceMap] : subtree) 263 { 264 for (const auto& [service, interfaces] : serviceMap) 265 { 266 for (const auto& interface : interfaces) 267 { 268 if (interface != 269 "xyz.openbmc_project.Network.EthernetInterface") 270 { 271 continue; 272 } 273 274 crow::connections::systemBus->async_method_call( 275 [asyncResp](const boost::system::error_code ec) { 276 if (ec) 277 { 278 messages::internalError(asyncResp->res); 279 return; 280 } 281 }, 282 service, objectPath, "org.freedesktop.DBus.Properties", 283 "Set", interface, "NTPServers", 284 dbus::utility::DbusVariantType{ntpServers}); 285 } 286 } 287 } 288 }, 289 "xyz.openbmc_project.ObjectMapper", 290 "/xyz/openbmc_project/object_mapper", 291 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 292 "/xyz/openbmc_project", 0, 293 std::array<const char*, 1>{ 294 "xyz.openbmc_project.Network.EthernetInterface"}); 295 } 296 297 inline void 298 handleProtocolEnabled(const bool protocolEnabled, 299 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 300 const std::string_view netBasePath) 301 { 302 crow::connections::systemBus->async_method_call( 303 [protocolEnabled, asyncResp, 304 netBasePath](const boost::system::error_code ec, 305 const dbus::utility::MapperGetSubTreeResponse& subtree) { 306 if (ec) 307 { 308 messages::internalError(asyncResp->res); 309 return; 310 } 311 312 for (const auto& entry : subtree) 313 { 314 if (boost::algorithm::starts_with(entry.first, netBasePath)) 315 { 316 crow::connections::systemBus->async_method_call( 317 [asyncResp](const boost::system::error_code ec2) { 318 if (ec2) 319 { 320 messages::internalError(asyncResp->res); 321 return; 322 } 323 }, 324 entry.second.begin()->first, entry.first, 325 "org.freedesktop.DBus.Properties", "Set", 326 "xyz.openbmc_project.Control.Service.Attributes", "Running", 327 dbus::utility::DbusVariantType{protocolEnabled}); 328 329 crow::connections::systemBus->async_method_call( 330 [asyncResp](const boost::system::error_code ec2) { 331 if (ec2) 332 { 333 messages::internalError(asyncResp->res); 334 return; 335 } 336 }, 337 entry.second.begin()->first, entry.first, 338 "org.freedesktop.DBus.Properties", "Set", 339 "xyz.openbmc_project.Control.Service.Attributes", "Enabled", 340 dbus::utility::DbusVariantType{protocolEnabled}); 341 } 342 } 343 }, 344 "xyz.openbmc_project.ObjectMapper", 345 "/xyz/openbmc_project/object_mapper", 346 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 347 "/xyz/openbmc_project/control/service", 0, 348 std::array<const char*, 1>{ 349 "xyz.openbmc_project.Control.Service.Attributes"}); 350 } 351 352 inline std::string getHostName() 353 { 354 std::string hostName; 355 356 std::array<char, HOST_NAME_MAX> hostNameCStr{}; 357 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 358 { 359 hostName = hostNameCStr.data(); 360 } 361 return hostName; 362 } 363 364 inline void 365 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 366 { 367 sdbusplus::asio::getProperty<std::string>( 368 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 369 "/xyz/openbmc_project/time/sync_method", 370 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 371 [asyncResp](const boost::system::error_code errorCode, 372 const std::string& timeSyncMethod) { 373 if (errorCode) 374 { 375 return; 376 } 377 378 if (timeSyncMethod == 379 "xyz.openbmc_project.Time.Synchronization.Method.NTP") 380 { 381 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 382 } 383 else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization." 384 "Method.Manual") 385 { 386 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 387 } 388 }); 389 } 390 391 inline void requestRoutesNetworkProtocol(App& app) 392 { 393 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 394 .privileges(redfish::privileges::patchManagerNetworkProtocol) 395 .methods(boost::beast::http::verb::patch)( 396 [&app](const crow::Request& req, 397 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 398 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 399 { 400 return; 401 } 402 std::optional<std::string> newHostName; 403 std::optional<std::vector<std::string>> ntpServers; 404 std::optional<bool> ntpEnabled; 405 std::optional<bool> ipmiEnabled; 406 std::optional<bool> sshEnabled; 407 408 // clang-format off 409 if (!json_util::readJsonPatch( 410 req, asyncResp->res, 411 "HostName", newHostName, 412 "NTP/NTPServers", ntpServers, 413 "NTP/ProtocolEnabled", ntpEnabled, 414 "IPMI/ProtocolEnabled", ipmiEnabled, 415 "SSH/ProtocolEnabled", sshEnabled)) 416 { 417 return; 418 } 419 // clang-format on 420 421 asyncResp->res.result(boost::beast::http::status::no_content); 422 if (newHostName) 423 { 424 messages::propertyNotWritable(asyncResp->res, "HostName"); 425 return; 426 } 427 428 if (ntpEnabled) 429 { 430 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 431 } 432 if (ntpServers) 433 { 434 stl_utils::removeDuplicate(*ntpServers); 435 handleNTPServersPatch(asyncResp, *ntpServers); 436 } 437 438 if (ipmiEnabled) 439 { 440 handleProtocolEnabled( 441 *ipmiEnabled, asyncResp, 442 "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet_40"); 443 } 444 445 if (sshEnabled) 446 { 447 handleProtocolEnabled( 448 *sshEnabled, asyncResp, 449 "/xyz/openbmc_project/control/service/dropbear"); 450 } 451 }); 452 453 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 454 .privileges(redfish::privileges::getManagerNetworkProtocol) 455 .methods(boost::beast::http::verb::get)( 456 [&app](const crow::Request& req, 457 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 458 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 459 { 460 return; 461 } 462 getNetworkData(asyncResp, req); 463 }); 464 } 465 466 } // namespace redfish 467