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