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