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 static constexpr const char* sshServiceName = "dropbear"; 40 static constexpr const char* httpsServiceName = "bmcweb"; 41 static constexpr const char* ipmiServiceName = "phosphor-ipmi-net"; 42 static constexpr std::array<std::pair<const char*, const char*>, 3> 43 protocolToService = {{{"SSH", sshServiceName}, 44 {"HTTPS", httpsServiceName}, 45 {"IPMI", ipmiServiceName}}}; 46 47 inline void extractNTPServersAndDomainNamesData( 48 const dbus::utility::ManagedObjectType& dbusData, 49 std::vector<std::string>& ntpData, std::vector<std::string>& dnData) 50 { 51 for (const auto& obj : dbusData) 52 { 53 for (const auto& ifacePair : obj.second) 54 { 55 if (ifacePair.first != 56 "xyz.openbmc_project.Network.EthernetInterface") 57 { 58 continue; 59 } 60 61 for (const auto& propertyPair : ifacePair.second) 62 { 63 if (propertyPair.first == "NTPServers") 64 { 65 const std::vector<std::string>* ntpServers = 66 std::get_if<std::vector<std::string>>( 67 &propertyPair.second); 68 if (ntpServers != nullptr) 69 { 70 ntpData = *ntpServers; 71 } 72 } 73 else if (propertyPair.first == "DomainName") 74 { 75 const std::vector<std::string>* domainNames = 76 std::get_if<std::vector<std::string>>( 77 &propertyPair.second); 78 if (domainNames != nullptr) 79 { 80 dnData = *domainNames; 81 } 82 } 83 } 84 } 85 } 86 } 87 88 template <typename CallbackFunc> 89 void getEthernetIfaceData(CallbackFunc&& callback) 90 { 91 crow::connections::systemBus->async_method_call( 92 [callback{std::forward<CallbackFunc>(callback)}]( 93 const boost::system::error_code errorCode, 94 const dbus::utility::ManagedObjectType& dbusData) { 95 std::vector<std::string> ntpServers; 96 std::vector<std::string> domainNames; 97 98 if (errorCode) 99 { 100 callback(false, ntpServers, domainNames); 101 return; 102 } 103 104 extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames); 105 106 callback(true, ntpServers, domainNames); 107 }, 108 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 109 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 110 } 111 112 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 113 const crow::Request& req) 114 { 115 asyncResp->res.jsonValue["@odata.type"] = 116 "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol"; 117 asyncResp->res.jsonValue["@odata.id"] = 118 "/redfish/v1/Managers/bmc/NetworkProtocol"; 119 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 120 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 121 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 122 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 123 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 124 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 125 126 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 127 // but from security perspective it is not recommended to use. 128 // Hence using protocolEnabled as false to make it OCP and security-wise 129 // compliant 130 asyncResp->res.jsonValue["HTTP"]["Port"] = 0; 131 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 132 133 std::string hostName = getHostName(); 134 135 asyncResp->res.jsonValue["HostName"] = hostName; 136 137 getNTPProtocolEnabled(asyncResp); 138 139 getEthernetIfaceData( 140 [hostName, asyncResp](const bool& success, 141 std::vector<std::string>& ntpServers, 142 const std::vector<std::string>& domainNames) { 143 if (!success) 144 { 145 messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol", 146 "NetworkProtocol"); 147 return; 148 } 149 stl_utils::removeDuplicate(ntpServers); 150 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 151 if (!hostName.empty()) 152 { 153 std::string fqdn = hostName; 154 if (!domainNames.empty()) 155 { 156 fqdn += "."; 157 fqdn += domainNames[0]; 158 } 159 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 160 } 161 }); 162 163 Privileges effectiveUserPrivileges = 164 redfish::getUserPrivileges(req.userRole); 165 166 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 167 // something only ConfigureManager can access then only display when 168 // the user has permissions ConfigureManager 169 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 170 effectiveUserPrivileges)) 171 { 172 asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] = 173 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 174 } 175 176 for (const auto& protocol : protocolToService) 177 { 178 const std::string& protocolName = protocol.first; 179 const std::string& serviceName = protocol.second; 180 getPortStatusAndPath( 181 serviceName, 182 [asyncResp, protocolName](const boost::system::error_code ec, 183 const std::string& socketPath, 184 bool isProtocolEnabled) { 185 // If the service is not installed, that is not an error 186 if (ec == boost::system::errc::no_such_process) 187 { 188 asyncResp->res.jsonValue[protocolName]["Port"] = 189 nlohmann::detail::value_t::null; 190 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 191 false; 192 return; 193 } 194 if (ec) 195 { 196 messages::internalError(asyncResp->res); 197 return; 198 } 199 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 200 isProtocolEnabled; 201 getPortNumber(socketPath, [asyncResp, protocolName]( 202 const boost::system::error_code ec2, 203 int portNumber) { 204 if (ec2) 205 { 206 messages::internalError(asyncResp->res); 207 return; 208 } 209 asyncResp->res.jsonValue[protocolName]["Port"] = portNumber; 210 }); 211 }); 212 } 213 } // namespace redfish 214 215 inline void handleNTPProtocolEnabled( 216 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 217 { 218 std::string timeSyncMethod; 219 if (ntpEnabled) 220 { 221 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 222 } 223 else 224 { 225 timeSyncMethod = 226 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 227 } 228 229 crow::connections::systemBus->async_method_call( 230 [asyncResp](const boost::system::error_code errorCode) { 231 if (errorCode) 232 { 233 messages::internalError(asyncResp->res); 234 } 235 }, 236 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 237 "org.freedesktop.DBus.Properties", "Set", 238 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 239 dbus::utility::DbusVariantType{timeSyncMethod}); 240 } 241 242 inline void 243 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 244 std::vector<std::string>& ntpServers) 245 { 246 auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end()); 247 if (iter != ntpServers.end()) 248 { 249 std::string pointer = 250 "NTPServers/" + 251 std::to_string(std::distance(ntpServers.begin(), iter)); 252 messages::propertyValueIncorrect(asyncResp->res, pointer, *iter); 253 return; 254 } 255 256 crow::connections::systemBus->async_method_call( 257 [asyncResp, 258 ntpServers](boost::system::error_code ec, 259 const dbus::utility::MapperGetSubTreeResponse& subtree) { 260 if (ec) 261 { 262 BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message(); 263 messages::internalError(asyncResp->res); 264 return; 265 } 266 267 for (const auto& [objectPath, serviceMap] : subtree) 268 { 269 for (const auto& [service, interfaces] : serviceMap) 270 { 271 for (const auto& interface : interfaces) 272 { 273 if (interface != 274 "xyz.openbmc_project.Network.EthernetInterface") 275 { 276 continue; 277 } 278 279 crow::connections::systemBus->async_method_call( 280 [asyncResp](const boost::system::error_code ec2) { 281 if (ec2) 282 { 283 messages::internalError(asyncResp->res); 284 return; 285 } 286 }, 287 service, objectPath, "org.freedesktop.DBus.Properties", 288 "Set", interface, "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& 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", "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", "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 == "xyz.openbmc_project.Time.Synchronization." 389 "Method.Manual") 390 { 391 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 392 } 393 }); 394 } 395 396 inline std::string encodeServiceObjectPath(const std::string& serviceName) 397 { 398 sdbusplus::message::object_path objPath( 399 "/xyz/openbmc_project/control/service"); 400 objPath /= serviceName; 401 return objPath.str; 402 } 403 404 inline void requestRoutesNetworkProtocol(App& app) 405 { 406 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 407 .privileges(redfish::privileges::patchManagerNetworkProtocol) 408 .methods(boost::beast::http::verb::patch)( 409 [&app](const crow::Request& req, 410 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 411 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 412 { 413 return; 414 } 415 std::optional<std::string> newHostName; 416 std::optional<std::vector<std::string>> ntpServers; 417 std::optional<bool> ntpEnabled; 418 std::optional<bool> ipmiEnabled; 419 std::optional<bool> sshEnabled; 420 421 // clang-format off 422 if (!json_util::readJsonPatch( 423 req, asyncResp->res, 424 "HostName", newHostName, 425 "NTP/NTPServers", ntpServers, 426 "NTP/ProtocolEnabled", ntpEnabled, 427 "IPMI/ProtocolEnabled", ipmiEnabled, 428 "SSH/ProtocolEnabled", sshEnabled)) 429 { 430 return; 431 } 432 // clang-format on 433 434 asyncResp->res.result(boost::beast::http::status::no_content); 435 if (newHostName) 436 { 437 messages::propertyNotWritable(asyncResp->res, "HostName"); 438 return; 439 } 440 441 if (ntpEnabled) 442 { 443 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 444 } 445 if (ntpServers) 446 { 447 stl_utils::removeDuplicate(*ntpServers); 448 handleNTPServersPatch(asyncResp, *ntpServers); 449 } 450 451 if (ipmiEnabled) 452 { 453 handleProtocolEnabled( 454 *ipmiEnabled, asyncResp, 455 encodeServiceObjectPath(std::string(ipmiServiceName) + '@')); 456 } 457 458 if (sshEnabled) 459 { 460 handleProtocolEnabled(*sshEnabled, asyncResp, 461 encodeServiceObjectPath(sshServiceName)); 462 } 463 }); 464 465 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 466 .privileges(redfish::privileges::getManagerNetworkProtocol) 467 .methods(boost::beast::http::verb::get)( 468 [&app](const crow::Request& req, 469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 470 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 471 { 472 return; 473 } 474 getNetworkData(asyncResp, req); 475 }); 476 } 477 478 } // namespace redfish 479