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