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