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