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 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 void handleHostnamePatch(const std::string& hostName, 305 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 306 { 307 crow::connections::systemBus->async_method_call( 308 [asyncResp](const boost::system::error_code ec) { 309 if (ec) 310 { 311 messages::internalError(asyncResp->res); 312 return; 313 } 314 }, 315 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 316 "org.freedesktop.DBus.Properties", "Set", 317 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 318 std::variant<std::string>(hostName)); 319 } 320 #endif 321 322 void handleNTPProtocolEnabled( 323 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 324 { 325 std::string timeSyncMethod; 326 if (ntpEnabled) 327 { 328 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 329 } 330 else 331 { 332 timeSyncMethod = 333 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 334 } 335 336 crow::connections::systemBus->async_method_call( 337 [asyncResp](const boost::system::error_code errorCode) { 338 if (errorCode) 339 { 340 messages::internalError(asyncResp->res); 341 } 342 }, 343 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 344 "org.freedesktop.DBus.Properties", "Set", 345 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 346 std::variant<std::string>{timeSyncMethod}); 347 } 348 349 void handleNTPServersPatch(const std::vector<std::string>& ntpServers, 350 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 351 { 352 crow::connections::systemBus->async_method_call( 353 [asyncResp](const boost::system::error_code ec) { 354 if (ec) 355 { 356 messages::internalError(asyncResp->res); 357 return; 358 } 359 }, 360 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 361 "org.freedesktop.DBus.Properties", "Set", 362 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 363 std::variant<std::vector<std::string>>{ntpServers}); 364 } 365 366 void handleProtocolEnabled(const bool protocolEnabled, 367 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 368 const std::string_view netBasePath) 369 { 370 crow::connections::systemBus->async_method_call( 371 [protocolEnabled, asyncResp, 372 netBasePath](const boost::system::error_code ec, 373 const crow::openbmc_mapper::GetSubTreeType& subtree) { 374 if (ec) 375 { 376 messages::internalError(asyncResp->res); 377 return; 378 } 379 380 for (const auto& entry : subtree) 381 { 382 if (boost::algorithm::starts_with(entry.first, netBasePath)) 383 { 384 crow::connections::systemBus->async_method_call( 385 [asyncResp](const boost::system::error_code ec2) { 386 if (ec2) 387 { 388 messages::internalError(asyncResp->res); 389 return; 390 } 391 }, 392 entry.second.begin()->first, entry.first, 393 "org.freedesktop.DBus.Properties", "Set", 394 "xyz.openbmc_project.Control.Service.Attributes", 395 "Running", std::variant<bool>{protocolEnabled}); 396 397 crow::connections::systemBus->async_method_call( 398 [asyncResp](const boost::system::error_code ec2) { 399 if (ec2) 400 { 401 messages::internalError(asyncResp->res); 402 return; 403 } 404 }, 405 entry.second.begin()->first, entry.first, 406 "org.freedesktop.DBus.Properties", "Set", 407 "xyz.openbmc_project.Control.Service.Attributes", 408 "Enabled", std::variant<bool>{protocolEnabled}); 409 } 410 } 411 }, 412 "xyz.openbmc_project.ObjectMapper", 413 "/xyz/openbmc_project/object_mapper", 414 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 415 "/xyz/openbmc_project/control/service", 0, 416 std::array<const char*, 1>{ 417 "xyz.openbmc_project.Control.Service.Attributes"}); 418 } 419 420 std::string getHostName() 421 { 422 std::string hostName; 423 424 std::array<char, HOST_NAME_MAX> hostNameCStr; 425 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 426 { 427 hostName = hostNameCStr.data(); 428 } 429 return hostName; 430 } 431 432 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 433 { 434 crow::connections::systemBus->async_method_call( 435 [asyncResp](const boost::system::error_code errorCode, 436 const std::variant<std::string>& timeSyncMethod) { 437 if (errorCode) 438 { 439 return; 440 } 441 442 const std::string* s = std::get_if<std::string>(&timeSyncMethod); 443 444 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP") 445 { 446 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 447 } 448 else if (*s == "xyz.openbmc_project.Time.Synchronization." 449 "Method.Manual") 450 { 451 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 452 } 453 }, 454 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 455 "org.freedesktop.DBus.Properties", "Get", 456 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod"); 457 } 458 459 inline void requestRoutesNetworkProtocol(App& app) 460 { 461 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 462 .privileges(redfish::privileges::patchManagerNetworkProtocol) 463 .methods(boost::beast::http::verb::patch)( 464 [](const crow::Request& req, 465 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 466 std::optional<std::string> newHostName; 467 std::optional<nlohmann::json> ntp; 468 std::optional<nlohmann::json> ipmi; 469 std::optional<nlohmann::json> ssh; 470 471 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp, 472 "HostName", newHostName, "IPMI", ipmi, 473 "SSH", ssh)) 474 { 475 return; 476 } 477 478 asyncResp->res.result(boost::beast::http::status::no_content); 479 if (newHostName) 480 { 481 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH 482 handleHostnamePatch(*newHostName, asyncResp); 483 #else 484 messages::propertyNotWritable(asyncResp->res, "HostName"); 485 #endif 486 } 487 488 if (ntp) 489 { 490 std::optional<std::vector<std::string>> ntpServers; 491 std::optional<bool> ntpEnabled; 492 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers", 493 ntpServers, "ProtocolEnabled", 494 ntpEnabled)) 495 { 496 return; 497 } 498 499 if (ntpEnabled) 500 { 501 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 502 } 503 504 if (ntpServers) 505 { 506 std::sort((*ntpServers).begin(), (*ntpServers).end()); 507 (*ntpServers) 508 .erase(std::unique((*ntpServers).begin(), 509 (*ntpServers).end()), 510 (*ntpServers).end()); 511 handleNTPServersPatch(*ntpServers, asyncResp); 512 } 513 } 514 515 if (ipmi) 516 { 517 std::optional<bool> ipmiProtocolEnabled; 518 if (!json_util::readJson(*ipmi, asyncResp->res, 519 "ProtocolEnabled", 520 ipmiProtocolEnabled)) 521 { 522 return; 523 } 524 525 if (ipmiProtocolEnabled) 526 { 527 handleProtocolEnabled( 528 *ipmiProtocolEnabled, asyncResp, 529 "/xyz/openbmc_project/control/service/" 530 "phosphor_2dipmi_2dnet_40"); 531 } 532 } 533 534 if (ssh) 535 { 536 std::optional<bool> sshProtocolEnabled; 537 if (!json_util::readJson(*ssh, asyncResp->res, 538 "ProtocolEnabled", 539 sshProtocolEnabled)) 540 { 541 return; 542 } 543 544 if (sshProtocolEnabled) 545 { 546 handleProtocolEnabled( 547 *sshProtocolEnabled, asyncResp, 548 "/xyz/openbmc_project/control/service/dropbear"); 549 } 550 } 551 }); 552 553 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 554 .privileges(redfish::privileges::getManagerNetworkProtocol) 555 .methods(boost::beast::http::verb::get)( 556 [](const crow::Request& req, 557 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 558 getNetworkData(asyncResp, req); 559 }); 560 } 561 562 } // namespace redfish 563