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