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