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