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