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