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