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 26 #include <optional> 27 #include <variant> 28 namespace redfish 29 { 30 31 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp); 32 std::string getHostName(); 33 34 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{ 35 {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}}; 36 37 inline void 38 extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData, 39 std::vector<std::string>& ntpData, 40 std::vector<std::string>& dnData) 41 { 42 for (const auto& obj : dbusData) 43 { 44 for (const auto& ifacePair : obj.second) 45 { 46 if (obj.first == "/xyz/openbmc_project/network/eth0") 47 { 48 if (ifacePair.first == 49 "xyz.openbmc_project.Network.EthernetInterface") 50 { 51 for (const auto& propertyPair : ifacePair.second) 52 { 53 if (propertyPair.first == "NTPServers") 54 { 55 const std::vector<std::string>* ntpServers = 56 std::get_if<std::vector<std::string>>( 57 &propertyPair.second); 58 if (ntpServers != nullptr) 59 { 60 ntpData = *ntpServers; 61 } 62 } 63 else if (propertyPair.first == "DomainName") 64 { 65 const std::vector<std::string>* domainNames = 66 std::get_if<std::vector<std::string>>( 67 &propertyPair.second); 68 if (domainNames != nullptr) 69 { 70 dnData = *domainNames; 71 } 72 } 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 for (auto& protocol : protocolToDBus) 127 { 128 asyncResp->res.jsonValue[protocol.first]["Port"] = 129 nlohmann::detail::value_t::null; 130 asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false; 131 } 132 133 std::string hostName = getHostName(); 134 135 asyncResp->res.jsonValue["HostName"] = hostName; 136 137 getNTPProtocolEnabled(asyncResp); 138 139 // TODO Get eth0 interface data, and call the below callback for JSON 140 // preparation 141 getEthernetIfaceData( 142 [hostName, asyncResp](const bool& success, 143 const std::vector<std::string>& ntpServers, 144 const std::vector<std::string>& domainNames) { 145 if (!success) 146 { 147 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 148 "eth0"); 149 return; 150 } 151 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 152 if (hostName.empty() == false) 153 { 154 std::string fqdn = hostName; 155 if (domainNames.empty() == false) 156 { 157 fqdn += "."; 158 fqdn += domainNames[0]; 159 } 160 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 161 } 162 }); 163 164 Privileges effectiveUserPrivileges = 165 redfish::getUserPrivileges(req.userRole); 166 167 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 168 // something only ConfigureManager can access then only display when 169 // the user has permissions ConfigureManager 170 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 171 effectiveUserPrivileges)) 172 { 173 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 174 {"@odata.id", 175 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}}; 176 } 177 178 for (const auto& protocol : protocolToDBus) 179 { 180 const std::string& protocolName = protocol.first; 181 const std::string& serviceName = protocol.second; 182 getPortStatusAndPath( 183 serviceName, 184 [asyncResp, protocolName](const boost::system::error_code ec, 185 const std::string& socketPath, 186 bool isProtocolEnabled) { 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 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH 211 inline void 212 handleHostnamePatch(const std::string& hostName, 213 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 214 { 215 crow::connections::systemBus->async_method_call( 216 [asyncResp](const boost::system::error_code ec) { 217 if (ec) 218 { 219 messages::internalError(asyncResp->res); 220 return; 221 } 222 }, 223 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 224 "org.freedesktop.DBus.Properties", "Set", 225 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 226 std::variant<std::string>(hostName)); 227 } 228 #endif 229 230 inline void handleNTPProtocolEnabled( 231 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 232 { 233 std::string timeSyncMethod; 234 if (ntpEnabled) 235 { 236 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 237 } 238 else 239 { 240 timeSyncMethod = 241 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 242 } 243 244 crow::connections::systemBus->async_method_call( 245 [asyncResp](const boost::system::error_code errorCode) { 246 if (errorCode) 247 { 248 messages::internalError(asyncResp->res); 249 } 250 }, 251 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 252 "org.freedesktop.DBus.Properties", "Set", 253 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 254 std::variant<std::string>{timeSyncMethod}); 255 } 256 257 inline void 258 handleNTPServersPatch(const std::vector<std::string>& ntpServers, 259 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 260 { 261 crow::connections::systemBus->async_method_call( 262 [asyncResp](const boost::system::error_code ec) { 263 if (ec) 264 { 265 messages::internalError(asyncResp->res); 266 return; 267 } 268 }, 269 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 270 "org.freedesktop.DBus.Properties", "Set", 271 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 272 std::variant<std::vector<std::string>>{ntpServers}); 273 } 274 275 inline void 276 handleProtocolEnabled(const bool protocolEnabled, 277 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 278 const std::string_view netBasePath) 279 { 280 crow::connections::systemBus->async_method_call( 281 [protocolEnabled, asyncResp, 282 netBasePath](const boost::system::error_code ec, 283 const crow::openbmc_mapper::GetSubTreeType& subtree) { 284 if (ec) 285 { 286 messages::internalError(asyncResp->res); 287 return; 288 } 289 290 for (const auto& entry : subtree) 291 { 292 if (boost::algorithm::starts_with(entry.first, netBasePath)) 293 { 294 crow::connections::systemBus->async_method_call( 295 [asyncResp](const boost::system::error_code ec2) { 296 if (ec2) 297 { 298 messages::internalError(asyncResp->res); 299 return; 300 } 301 }, 302 entry.second.begin()->first, entry.first, 303 "org.freedesktop.DBus.Properties", "Set", 304 "xyz.openbmc_project.Control.Service.Attributes", 305 "Running", std::variant<bool>{protocolEnabled}); 306 307 crow::connections::systemBus->async_method_call( 308 [asyncResp](const boost::system::error_code ec2) { 309 if (ec2) 310 { 311 messages::internalError(asyncResp->res); 312 return; 313 } 314 }, 315 entry.second.begin()->first, entry.first, 316 "org.freedesktop.DBus.Properties", "Set", 317 "xyz.openbmc_project.Control.Service.Attributes", 318 "Enabled", std::variant<bool>{protocolEnabled}); 319 } 320 } 321 }, 322 "xyz.openbmc_project.ObjectMapper", 323 "/xyz/openbmc_project/object_mapper", 324 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 325 "/xyz/openbmc_project/control/service", 0, 326 std::array<const char*, 1>{ 327 "xyz.openbmc_project.Control.Service.Attributes"}); 328 } 329 330 inline std::string getHostName() 331 { 332 std::string hostName; 333 334 std::array<char, HOST_NAME_MAX> hostNameCStr; 335 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 336 { 337 hostName = hostNameCStr.data(); 338 } 339 return hostName; 340 } 341 342 inline void 343 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 344 { 345 crow::connections::systemBus->async_method_call( 346 [asyncResp](const boost::system::error_code errorCode, 347 const std::variant<std::string>& timeSyncMethod) { 348 if (errorCode) 349 { 350 return; 351 } 352 353 const std::string* s = std::get_if<std::string>(&timeSyncMethod); 354 355 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP") 356 { 357 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 358 } 359 else if (*s == "xyz.openbmc_project.Time.Synchronization." 360 "Method.Manual") 361 { 362 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 363 } 364 }, 365 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 366 "org.freedesktop.DBus.Properties", "Get", 367 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod"); 368 } 369 370 inline void requestRoutesNetworkProtocol(App& app) 371 { 372 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 373 .privileges(redfish::privileges::patchManagerNetworkProtocol) 374 .methods(boost::beast::http::verb::patch)( 375 [](const crow::Request& req, 376 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 377 std::optional<std::string> newHostName; 378 std::optional<nlohmann::json> ntp; 379 std::optional<nlohmann::json> ipmi; 380 std::optional<nlohmann::json> ssh; 381 382 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp, 383 "HostName", newHostName, "IPMI", ipmi, 384 "SSH", ssh)) 385 { 386 return; 387 } 388 389 asyncResp->res.result(boost::beast::http::status::no_content); 390 if (newHostName) 391 { 392 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH 393 handleHostnamePatch(*newHostName, asyncResp); 394 #else 395 messages::propertyNotWritable(asyncResp->res, "HostName"); 396 #endif 397 } 398 399 if (ntp) 400 { 401 std::optional<std::vector<std::string>> ntpServers; 402 std::optional<bool> ntpEnabled; 403 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers", 404 ntpServers, "ProtocolEnabled", 405 ntpEnabled)) 406 { 407 return; 408 } 409 410 if (ntpEnabled) 411 { 412 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 413 } 414 415 if (ntpServers) 416 { 417 std::sort((*ntpServers).begin(), (*ntpServers).end()); 418 (*ntpServers) 419 .erase(std::unique((*ntpServers).begin(), 420 (*ntpServers).end()), 421 (*ntpServers).end()); 422 handleNTPServersPatch(*ntpServers, asyncResp); 423 } 424 } 425 426 if (ipmi) 427 { 428 std::optional<bool> ipmiProtocolEnabled; 429 if (!json_util::readJson(*ipmi, asyncResp->res, 430 "ProtocolEnabled", 431 ipmiProtocolEnabled)) 432 { 433 return; 434 } 435 436 if (ipmiProtocolEnabled) 437 { 438 handleProtocolEnabled( 439 *ipmiProtocolEnabled, asyncResp, 440 "/xyz/openbmc_project/control/service/" 441 "phosphor_2dipmi_2dnet_40"); 442 } 443 } 444 445 if (ssh) 446 { 447 std::optional<bool> sshProtocolEnabled; 448 if (!json_util::readJson(*ssh, asyncResp->res, 449 "ProtocolEnabled", 450 sshProtocolEnabled)) 451 { 452 return; 453 } 454 455 if (sshProtocolEnabled) 456 { 457 handleProtocolEnabled( 458 *sshProtocolEnabled, asyncResp, 459 "/xyz/openbmc_project/control/service/dropbear"); 460 } 461 } 462 }); 463 464 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 465 .privileges(redfish::privileges::getManagerNetworkProtocol) 466 .methods(boost::beast::http::verb::get)( 467 [](const crow::Request& req, 468 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 469 getNetworkData(asyncResp, req); 470 }); 471 } 472 473 } // namespace redfish 474