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 208 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 209 // but from security perspective it is not recommended to use. 210 // Hence using protocolEnabled as false to make it OCP and security-wise 211 // compliant 212 asyncResp->res.jsonValue["HTTP"]["Port"] = 0; 213 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 214 215 for (auto& protocol : protocolToDBus) 216 { 217 asyncResp->res.jsonValue[protocol.first]["Port"] = 218 nlohmann::detail::value_t::null; 219 asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false; 220 } 221 222 std::string hostName = getHostName(); 223 224 asyncResp->res.jsonValue["HostName"] = hostName; 225 226 getNTPProtocolEnabled(asyncResp); 227 228 // TODO Get eth0 interface data, and call the below callback for JSON 229 // preparation 230 getEthernetIfaceData( 231 [hostName, asyncResp](const bool& success, 232 const std::vector<std::string>& ntpServers, 233 const std::vector<std::string>& domainNames) { 234 if (!success) 235 { 236 messages::resourceNotFound(asyncResp->res, 237 "EthernetInterface", "eth0"); 238 return; 239 } 240 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 241 if (hostName.empty() == false) 242 { 243 std::string fqdn = std::move(hostName); 244 if (domainNames.empty() == false) 245 { 246 fqdn += "." + domainNames[0]; 247 } 248 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 249 } 250 }); 251 252 crow::connections::systemBus->async_method_call( 253 [asyncResp](const boost::system::error_code e, 254 const std::vector<UnitStruct>& r) { 255 if (e) 256 { 257 asyncResp->res.jsonValue = nlohmann::json::object(); 258 messages::internalError(asyncResp->res); 259 return; 260 } 261 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 262 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/" 263 "HTTPS/Certificates"}}; 264 265 for (auto& unit : r) 266 { 267 /* Only traverse through <xyz>.socket units */ 268 const std::string& unitName = 269 std::get<NET_PROTO_UNIT_NAME>(unit); 270 if (!boost::ends_with(unitName, ".socket")) 271 { 272 continue; 273 } 274 275 for (auto& kv : protocolToDBus) 276 { 277 // We are interested in services, which starts with 278 // mapped service name 279 if (!boost::starts_with(unitName, kv.second)) 280 { 281 continue; 282 } 283 const char* rfServiceKey = kv.first; 284 const std::string& socketPath = 285 std::get<NET_PROTO_UNIT_OBJ_PATH>(unit); 286 const std::string& unitState = 287 std::get<NET_PROTO_UNIT_SUB_STATE>(unit); 288 289 asyncResp->res 290 .jsonValue[rfServiceKey]["ProtocolEnabled"] = 291 (unitState == "running") || 292 (unitState == "listening"); 293 294 crow::connections::systemBus->async_method_call( 295 [asyncResp, 296 rfServiceKey{std::string(rfServiceKey)}]( 297 const boost::system::error_code ec, 298 const std::variant<std::vector<std::tuple< 299 std::string, std::string>>>& resp) { 300 if (ec) 301 { 302 messages::internalError(asyncResp->res); 303 return; 304 } 305 const std::vector< 306 std::tuple<std::string, std::string>>* 307 responsePtr = std::get_if<std::vector< 308 std::tuple<std::string, std::string>>>( 309 &resp); 310 if (responsePtr == nullptr || 311 responsePtr->size() < 1) 312 { 313 return; 314 } 315 316 const std::string& listenStream = 317 std::get<NET_PROTO_LISTEN_STREAM>( 318 (*responsePtr)[0]); 319 std::size_t lastColonPos = 320 listenStream.rfind(":"); 321 if (lastColonPos == std::string::npos) 322 { 323 // Not a port 324 return; 325 } 326 std::string portStr = 327 listenStream.substr(lastColonPos + 1); 328 if (portStr.empty()) 329 { 330 return; 331 } 332 char* endPtr = nullptr; 333 errno = 0; 334 // Use strtol instead of stroi to avoid 335 // exceptions 336 long port = 337 std::strtol(portStr.c_str(), &endPtr, 10); 338 if ((errno == 0) && (*endPtr == '\0')) 339 { 340 asyncResp->res 341 .jsonValue[rfServiceKey]["Port"] = port; 342 } 343 return; 344 }, 345 "org.freedesktop.systemd1", socketPath, 346 "org.freedesktop.DBus.Properties", "Get", 347 "org.freedesktop.systemd1.Socket", "Listen"); 348 349 // We found service, break the inner loop. 350 break; 351 } 352 } 353 }, 354 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 355 "org.freedesktop.systemd1.Manager", "ListUnits"); 356 } 357 358 void handleHostnamePatch(const std::string& hostName, 359 const std::shared_ptr<AsyncResp>& asyncResp) 360 { 361 crow::connections::systemBus->async_method_call( 362 [asyncResp](const boost::system::error_code ec) { 363 if (ec) 364 { 365 messages::internalError(asyncResp->res); 366 return; 367 } 368 }, 369 "xyz.openbmc_project.Network", 370 "/xyz/openbmc_project/network/config", 371 "org.freedesktop.DBus.Properties", "Set", 372 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 373 std::variant<std::string>(hostName)); 374 } 375 376 void handleNTPProtocolEnabled(const bool& ntpEnabled, 377 const std::shared_ptr<AsyncResp>& asyncResp) 378 { 379 std::string timeSyncMethod; 380 if (ntpEnabled) 381 { 382 timeSyncMethod = 383 "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 384 } 385 else 386 { 387 timeSyncMethod = 388 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 389 } 390 391 crow::connections::systemBus->async_method_call( 392 [asyncResp](const boost::system::error_code error_code) { 393 if (error_code) 394 { 395 messages::internalError(asyncResp->res); 396 } 397 }, 398 "xyz.openbmc_project.Settings", 399 "/xyz/openbmc_project/time/sync_method", 400 "org.freedesktop.DBus.Properties", "Set", 401 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 402 std::variant<std::string>{timeSyncMethod}); 403 } 404 405 void handleNTPServersPatch(const std::vector<std::string>& ntpServers, 406 const std::shared_ptr<AsyncResp>& asyncResp) 407 { 408 crow::connections::systemBus->async_method_call( 409 [asyncResp](const boost::system::error_code ec) { 410 if (ec) 411 { 412 messages::internalError(asyncResp->res); 413 return; 414 } 415 }, 416 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 417 "org.freedesktop.DBus.Properties", "Set", 418 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 419 std::variant<std::vector<std::string>>{ntpServers}); 420 } 421 422 void handleIpmiProtocolEnabled(const bool ipmiProtocolEnabled, 423 const std::shared_ptr<AsyncResp>& asyncResp) 424 { 425 crow::connections::systemBus->async_method_call( 426 [ipmiProtocolEnabled, 427 asyncResp](const boost::system::error_code ec, 428 const crow::openbmc_mapper::GetSubTreeType& subtree) { 429 if (ec) 430 { 431 messages::internalError(asyncResp->res); 432 return; 433 } 434 435 constexpr char const* netipmidBasePath = 436 "/xyz/openbmc_project/control/service/" 437 "phosphor_2dipmi_2dnet_40"; 438 439 for (const auto& entry : subtree) 440 { 441 if (boost::algorithm::starts_with(entry.first, 442 netipmidBasePath)) 443 { 444 crow::connections::systemBus->async_method_call( 445 [asyncResp](const boost::system::error_code ec2) { 446 if (ec2) 447 { 448 messages::internalError(asyncResp->res); 449 return; 450 } 451 }, 452 entry.second.begin()->first, entry.first, 453 "org.freedesktop.DBus.Properties", "Set", 454 "xyz.openbmc_project.Control.Service.Attributes", 455 "Running", std::variant<bool>{ipmiProtocolEnabled}); 456 457 crow::connections::systemBus->async_method_call( 458 [asyncResp](const boost::system::error_code ec2) { 459 if (ec2) 460 { 461 messages::internalError(asyncResp->res); 462 return; 463 } 464 }, 465 entry.second.begin()->first, entry.first, 466 "org.freedesktop.DBus.Properties", "Set", 467 "xyz.openbmc_project.Control.Service.Attributes", 468 "Enabled", std::variant<bool>{ipmiProtocolEnabled}); 469 } 470 } 471 }, 472 "xyz.openbmc_project.ObjectMapper", 473 "/xyz/openbmc_project/object_mapper", 474 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 475 "/xyz/openbmc_project/control/service", 0, 476 std::array<const char*, 1>{ 477 "xyz.openbmc_project.Control.Service.Attributes"}); 478 } 479 480 void doPatch(crow::Response& res, const crow::Request& req, 481 const std::vector<std::string>&) override 482 { 483 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 484 std::optional<std::string> newHostName; 485 std::optional<nlohmann::json> ntp; 486 std::optional<nlohmann::json> ipmi; 487 488 if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp, 489 "IPMI", ipmi)) 490 { 491 return; 492 } 493 494 res.result(boost::beast::http::status::no_content); 495 if (newHostName) 496 { 497 handleHostnamePatch(*newHostName, asyncResp); 498 } 499 500 if (ntp) 501 { 502 std::optional<std::vector<std::string>> ntpServers; 503 std::optional<bool> ntpEnabled; 504 if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers, 505 "ProtocolEnabled", ntpEnabled)) 506 { 507 return; 508 } 509 510 if (ntpEnabled) 511 { 512 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 513 } 514 515 if (ntpServers) 516 { 517 std::sort((*ntpServers).begin(), (*ntpServers).end()); 518 (*ntpServers) 519 .erase( 520 std::unique((*ntpServers).begin(), (*ntpServers).end()), 521 (*ntpServers).end()); 522 handleNTPServersPatch(*ntpServers, asyncResp); 523 } 524 } 525 526 if (ipmi) 527 { 528 std::optional<bool> ipmiProtocolEnabled; 529 if (!json_util::readJson(*ipmi, res, "ProtocolEnabled", 530 ipmiProtocolEnabled)) 531 { 532 return; 533 } 534 535 if (ipmiProtocolEnabled) 536 { 537 handleIpmiProtocolEnabled(*ipmiProtocolEnabled, asyncResp); 538 } 539 } 540 } 541 }; 542 543 } // namespace redfish 544