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 21 #include <app.hpp> 22 #include <registries/privilege_registry.hpp> 23 #include <utils/json_utils.hpp> 24 25 #include <optional> 26 #include <variant> 27 namespace redfish 28 { 29 30 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp); 31 std::string getHostName(); 32 33 enum NetworkProtocolUnitStructFields 34 { 35 NET_PROTO_UNIT_NAME, 36 NET_PROTO_UNIT_DESC, 37 NET_PROTO_UNIT_LOAD_STATE, 38 NET_PROTO_UNIT_ACTIVE_STATE, 39 NET_PROTO_UNIT_SUB_STATE, 40 NET_PROTO_UNIT_DEVICE, 41 NET_PROTO_UNIT_OBJ_PATH, 42 NET_PROTO_UNIT_ALWAYS_0, 43 NET_PROTO_UNIT_ALWAYS_EMPTY, 44 NET_PROTO_UNIT_ALWAYS_ROOT_PATH 45 }; 46 47 enum NetworkProtocolListenResponseElements 48 { 49 NET_PROTO_LISTEN_TYPE, 50 NET_PROTO_LISTEN_STREAM 51 }; 52 53 /** 54 * @brief D-Bus Unit structure returned in array from ListUnits Method 55 */ 56 using UnitStruct = 57 std::tuple<std::string, std::string, std::string, std::string, std::string, 58 std::string, sdbusplus::message::object_path, uint32_t, 59 std::string, sdbusplus::message::object_path>; 60 61 const static std::array<std::pair<const char*, const char*>, 3> protocolToDBus{ 62 {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}}; 63 64 inline void 65 extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData, 66 std::vector<std::string>& ntpData, 67 std::vector<std::string>& dnData) 68 { 69 for (const auto& obj : dbusData) 70 { 71 for (const auto& ifacePair : obj.second) 72 { 73 if (obj.first == "/xyz/openbmc_project/network/eth0") 74 { 75 if (ifacePair.first == 76 "xyz.openbmc_project.Network.EthernetInterface") 77 { 78 for (const auto& propertyPair : ifacePair.second) 79 { 80 if (propertyPair.first == "NTPServers") 81 { 82 const std::vector<std::string>* ntpServers = 83 std::get_if<std::vector<std::string>>( 84 &propertyPair.second); 85 if (ntpServers != nullptr) 86 { 87 ntpData = *ntpServers; 88 } 89 } 90 else if (propertyPair.first == "DomainName") 91 { 92 const std::vector<std::string>* domainNames = 93 std::get_if<std::vector<std::string>>( 94 &propertyPair.second); 95 if (domainNames != nullptr) 96 { 97 dnData = *domainNames; 98 } 99 } 100 } 101 } 102 } 103 } 104 } 105 } 106 107 template <typename CallbackFunc> 108 void getEthernetIfaceData(CallbackFunc&& callback) 109 { 110 crow::connections::systemBus->async_method_call( 111 [callback{std::move(callback)}]( 112 const boost::system::error_code errorCode, 113 const GetManagedObjects& dbusData) { 114 std::vector<std::string> ntpServers; 115 std::vector<std::string> domainNames; 116 117 if (errorCode) 118 { 119 callback(false, ntpServers, domainNames); 120 return; 121 } 122 123 extractNTPServersAndDomainNamesData(dbusData, ntpServers, 124 domainNames); 125 126 callback(true, ntpServers, domainNames); 127 }, 128 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 129 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 130 } 131 132 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 133 const crow::Request& req) 134 { 135 asyncResp->res.jsonValue["@odata.type"] = 136 "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol"; 137 asyncResp->res.jsonValue["@odata.id"] = 138 "/redfish/v1/Managers/bmc/NetworkProtocol"; 139 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 140 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 141 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 142 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 143 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 144 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 145 146 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 147 // but from security perspective it is not recommended to use. 148 // Hence using protocolEnabled as false to make it OCP and security-wise 149 // compliant 150 asyncResp->res.jsonValue["HTTP"]["Port"] = 0; 151 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 152 153 for (auto& protocol : protocolToDBus) 154 { 155 asyncResp->res.jsonValue[protocol.first]["Port"] = 156 nlohmann::detail::value_t::null; 157 asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false; 158 } 159 160 std::string hostName = getHostName(); 161 162 asyncResp->res.jsonValue["HostName"] = hostName; 163 164 getNTPProtocolEnabled(asyncResp); 165 166 // TODO Get eth0 interface data, and call the below callback for JSON 167 // preparation 168 getEthernetIfaceData( 169 [hostName, asyncResp](const bool& success, 170 const std::vector<std::string>& ntpServers, 171 const std::vector<std::string>& domainNames) { 172 if (!success) 173 { 174 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 175 "eth0"); 176 return; 177 } 178 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 179 if (hostName.empty() == false) 180 { 181 std::string fqdn = hostName; 182 if (domainNames.empty() == false) 183 { 184 fqdn += "."; 185 fqdn += domainNames[0]; 186 } 187 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 188 } 189 }); 190 191 Privileges effectiveUserPrivileges = 192 redfish::getUserPrivileges(req.userRole); 193 194 crow::connections::systemBus->async_method_call( 195 [asyncResp, 196 &effectiveUserPrivileges](const boost::system::error_code e, 197 const std::vector<UnitStruct>& r) { 198 if (e) 199 { 200 asyncResp->res.jsonValue = nlohmann::json::object(); 201 messages::internalError(asyncResp->res); 202 return; 203 } 204 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 205 // something only ConfigureManager can access then only display when 206 // the user has permissions ConfigureManager 207 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 208 effectiveUserPrivileges)) 209 { 210 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 211 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/" 212 "HTTPS/Certificates"}}; 213 } 214 for (auto& unit : r) 215 { 216 /* Only traverse through <xyz>.socket units */ 217 const std::string& unitName = 218 std::get<NET_PROTO_UNIT_NAME>(unit); 219 if (!boost::ends_with(unitName, ".socket")) 220 { 221 continue; 222 } 223 224 for (auto& kv : protocolToDBus) 225 { 226 // We are interested in services, which starts with 227 // mapped service name 228 if (!boost::starts_with(unitName, kv.second)) 229 { 230 continue; 231 } 232 const char* rfServiceKey = kv.first; 233 const std::string& socketPath = 234 std::get<NET_PROTO_UNIT_OBJ_PATH>(unit); 235 const std::string& unitState = 236 std::get<NET_PROTO_UNIT_SUB_STATE>(unit); 237 238 asyncResp->res.jsonValue[rfServiceKey]["ProtocolEnabled"] = 239 (unitState == "running") || (unitState == "listening"); 240 241 crow::connections::systemBus->async_method_call( 242 [asyncResp, rfServiceKey{std::string(rfServiceKey)}]( 243 const boost::system::error_code ec, 244 const std::variant<std::vector< 245 std::tuple<std::string, std::string>>>& resp) { 246 if (ec) 247 { 248 messages::internalError(asyncResp->res); 249 return; 250 } 251 const std::vector< 252 std::tuple<std::string, std::string>>* 253 responsePtr = std::get_if<std::vector< 254 std::tuple<std::string, std::string>>>( 255 &resp); 256 if (responsePtr == nullptr || 257 responsePtr->size() < 1) 258 { 259 return; 260 } 261 262 const std::string& listenStream = 263 std::get<NET_PROTO_LISTEN_STREAM>( 264 (*responsePtr)[0]); 265 std::size_t lastColonPos = listenStream.rfind(':'); 266 if (lastColonPos == std::string::npos) 267 { 268 // Not a port 269 return; 270 } 271 std::string portStr = 272 listenStream.substr(lastColonPos + 1); 273 if (portStr.empty()) 274 { 275 return; 276 } 277 char* endPtr = nullptr; 278 errno = 0; 279 // Use strtol instead of stroi to avoid 280 // exceptions 281 long port = 282 std::strtol(portStr.c_str(), &endPtr, 10); 283 if ((errno == 0) && (*endPtr == '\0')) 284 { 285 asyncResp->res.jsonValue[rfServiceKey]["Port"] = 286 port; 287 } 288 return; 289 }, 290 "org.freedesktop.systemd1", socketPath, 291 "org.freedesktop.DBus.Properties", "Get", 292 "org.freedesktop.systemd1.Socket", "Listen"); 293 294 // We found service, break the inner loop. 295 break; 296 } 297 } 298 }, 299 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 300 "org.freedesktop.systemd1.Manager", "ListUnits"); 301 } 302 303 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH 304 inline void 305 handleHostnamePatch(const std::string& hostName, 306 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 307 { 308 crow::connections::systemBus->async_method_call( 309 [asyncResp](const boost::system::error_code ec) { 310 if (ec) 311 { 312 messages::internalError(asyncResp->res); 313 return; 314 } 315 }, 316 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 317 "org.freedesktop.DBus.Properties", "Set", 318 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 319 std::variant<std::string>(hostName)); 320 } 321 #endif 322 323 inline void handleNTPProtocolEnabled( 324 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 325 { 326 std::string timeSyncMethod; 327 if (ntpEnabled) 328 { 329 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 330 } 331 else 332 { 333 timeSyncMethod = 334 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 335 } 336 337 crow::connections::systemBus->async_method_call( 338 [asyncResp](const boost::system::error_code errorCode) { 339 if (errorCode) 340 { 341 messages::internalError(asyncResp->res); 342 } 343 }, 344 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 345 "org.freedesktop.DBus.Properties", "Set", 346 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 347 std::variant<std::string>{timeSyncMethod}); 348 } 349 350 inline void 351 handleNTPServersPatch(const std::vector<std::string>& ntpServers, 352 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 353 { 354 crow::connections::systemBus->async_method_call( 355 [asyncResp](const boost::system::error_code ec) { 356 if (ec) 357 { 358 messages::internalError(asyncResp->res); 359 return; 360 } 361 }, 362 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 363 "org.freedesktop.DBus.Properties", "Set", 364 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 365 std::variant<std::vector<std::string>>{ntpServers}); 366 } 367 368 inline void 369 handleProtocolEnabled(const bool protocolEnabled, 370 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 371 const std::string_view netBasePath) 372 { 373 crow::connections::systemBus->async_method_call( 374 [protocolEnabled, asyncResp, 375 netBasePath](const boost::system::error_code ec, 376 const crow::openbmc_mapper::GetSubTreeType& subtree) { 377 if (ec) 378 { 379 messages::internalError(asyncResp->res); 380 return; 381 } 382 383 for (const auto& entry : subtree) 384 { 385 if (boost::algorithm::starts_with(entry.first, netBasePath)) 386 { 387 crow::connections::systemBus->async_method_call( 388 [asyncResp](const boost::system::error_code ec2) { 389 if (ec2) 390 { 391 messages::internalError(asyncResp->res); 392 return; 393 } 394 }, 395 entry.second.begin()->first, entry.first, 396 "org.freedesktop.DBus.Properties", "Set", 397 "xyz.openbmc_project.Control.Service.Attributes", 398 "Running", std::variant<bool>{protocolEnabled}); 399 400 crow::connections::systemBus->async_method_call( 401 [asyncResp](const boost::system::error_code ec2) { 402 if (ec2) 403 { 404 messages::internalError(asyncResp->res); 405 return; 406 } 407 }, 408 entry.second.begin()->first, entry.first, 409 "org.freedesktop.DBus.Properties", "Set", 410 "xyz.openbmc_project.Control.Service.Attributes", 411 "Enabled", std::variant<bool>{protocolEnabled}); 412 } 413 } 414 }, 415 "xyz.openbmc_project.ObjectMapper", 416 "/xyz/openbmc_project/object_mapper", 417 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 418 "/xyz/openbmc_project/control/service", 0, 419 std::array<const char*, 1>{ 420 "xyz.openbmc_project.Control.Service.Attributes"}); 421 } 422 423 inline std::string getHostName() 424 { 425 std::string hostName; 426 427 std::array<char, HOST_NAME_MAX> hostNameCStr; 428 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 429 { 430 hostName = hostNameCStr.data(); 431 } 432 return hostName; 433 } 434 435 inline void 436 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 437 { 438 crow::connections::systemBus->async_method_call( 439 [asyncResp](const boost::system::error_code errorCode, 440 const std::variant<std::string>& timeSyncMethod) { 441 if (errorCode) 442 { 443 return; 444 } 445 446 const std::string* s = std::get_if<std::string>(&timeSyncMethod); 447 448 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP") 449 { 450 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 451 } 452 else if (*s == "xyz.openbmc_project.Time.Synchronization." 453 "Method.Manual") 454 { 455 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 456 } 457 }, 458 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 459 "org.freedesktop.DBus.Properties", "Get", 460 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod"); 461 } 462 463 inline void requestRoutesNetworkProtocol(App& app) 464 { 465 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 466 .privileges(redfish::privileges::patchManagerNetworkProtocol) 467 .methods(boost::beast::http::verb::patch)( 468 [](const crow::Request& req, 469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 470 std::optional<std::string> newHostName; 471 std::optional<nlohmann::json> ntp; 472 std::optional<nlohmann::json> ipmi; 473 std::optional<nlohmann::json> ssh; 474 475 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp, 476 "HostName", newHostName, "IPMI", ipmi, 477 "SSH", ssh)) 478 { 479 return; 480 } 481 482 asyncResp->res.result(boost::beast::http::status::no_content); 483 if (newHostName) 484 { 485 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH 486 handleHostnamePatch(*newHostName, asyncResp); 487 #else 488 messages::propertyNotWritable(asyncResp->res, "HostName"); 489 #endif 490 } 491 492 if (ntp) 493 { 494 std::optional<std::vector<std::string>> ntpServers; 495 std::optional<bool> ntpEnabled; 496 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers", 497 ntpServers, "ProtocolEnabled", 498 ntpEnabled)) 499 { 500 return; 501 } 502 503 if (ntpEnabled) 504 { 505 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 506 } 507 508 if (ntpServers) 509 { 510 std::sort((*ntpServers).begin(), (*ntpServers).end()); 511 (*ntpServers) 512 .erase(std::unique((*ntpServers).begin(), 513 (*ntpServers).end()), 514 (*ntpServers).end()); 515 handleNTPServersPatch(*ntpServers, asyncResp); 516 } 517 } 518 519 if (ipmi) 520 { 521 std::optional<bool> ipmiProtocolEnabled; 522 if (!json_util::readJson(*ipmi, asyncResp->res, 523 "ProtocolEnabled", 524 ipmiProtocolEnabled)) 525 { 526 return; 527 } 528 529 if (ipmiProtocolEnabled) 530 { 531 handleProtocolEnabled( 532 *ipmiProtocolEnabled, asyncResp, 533 "/xyz/openbmc_project/control/service/" 534 "phosphor_2dipmi_2dnet_40"); 535 } 536 } 537 538 if (ssh) 539 { 540 std::optional<bool> sshProtocolEnabled; 541 if (!json_util::readJson(*ssh, asyncResp->res, 542 "ProtocolEnabled", 543 sshProtocolEnabled)) 544 { 545 return; 546 } 547 548 if (sshProtocolEnabled) 549 { 550 handleProtocolEnabled( 551 *sshProtocolEnabled, asyncResp, 552 "/xyz/openbmc_project/control/service/dropbear"); 553 } 554 } 555 }); 556 557 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 558 .privileges(redfish::privileges::getManagerNetworkProtocol) 559 .methods(boost::beast::http::verb::get)( 560 [](const crow::Request& req, 561 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 562 getNetworkData(asyncResp, req); 563 }); 564 } 565 566 } // namespace redfish 567