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 extractNTPServersData(const GetManagedObjects& dbus_data, 75 std::vector<std::string>& ntpData) 76 { 77 for (const auto& obj : dbus_data) 78 { 79 for (const auto& ifacePair : obj.second) 80 { 81 if (obj.first == "/xyz/openbmc_project/network/eth0") 82 { 83 if (ifacePair.first == 84 "xyz.openbmc_project.Network.EthernetInterface") 85 { 86 for (const auto& propertyPair : ifacePair.second) 87 { 88 if (propertyPair.first == "NTPServers") 89 { 90 const std::vector<std::string>* ntpServers = 91 sdbusplus::message::variant_ns::get_if< 92 std::vector<std::string>>( 93 &propertyPair.second); 94 if (ntpServers != nullptr) 95 { 96 ntpData = std::move(*ntpServers); 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 error_code, 112 const GetManagedObjects& dbus_data) { 113 std::vector<std::string> ntpServers; 114 115 if (error_code) 116 { 117 callback(false, ntpServers); 118 return; 119 } 120 121 extractNTPServersData(dbus_data, ntpServers); 122 123 callback(true, ntpServers); 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_4_0.ManagerNetworkProtocol"; 193 asyncResp->res.jsonValue["@odata.id"] = 194 "/redfish/v1/Managers/bmc/NetworkProtocol"; 195 asyncResp->res.jsonValue["@odata.context"] = 196 "/redfish/v1/" 197 "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol"; 198 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 199 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 200 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 201 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 202 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 203 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 204 205 for (auto& protocol : protocolToDBus) 206 { 207 asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false; 208 } 209 210 asyncResp->res.jsonValue["HostName"] = getHostName(); 211 212 getNTPProtocolEnabled(asyncResp); 213 214 // TODO Get eth0 interface data, and call the below callback for JSON 215 // preparation 216 getEthernetIfaceData( 217 [this, asyncResp](const bool& success, 218 const std::vector<std::string>& ntpServers) { 219 if (!success) 220 { 221 messages::resourceNotFound(asyncResp->res, 222 "EthernetInterface", "eth0"); 223 return; 224 } 225 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 226 }); 227 228 crow::connections::systemBus->async_method_call( 229 [asyncResp](const boost::system::error_code ec, 230 const std::vector<UnitStruct>& resp) { 231 if (ec) 232 { 233 asyncResp->res.jsonValue = nlohmann::json::object(); 234 messages::internalError(asyncResp->res); 235 return; 236 } 237 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 238 {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/" 239 "HTTPS/Certificates/"}}; 240 241 for (auto& unit : resp) 242 { 243 for (auto& kv : protocolToDBus) 244 { 245 if (kv.second.serviceName != 246 std::get<NET_PROTO_UNIT_NAME>(unit)) 247 { 248 continue; 249 } 250 const char* service = kv.first; 251 const char* socketPath = kv.second.socketPath; 252 253 asyncResp->res.jsonValue[service]["ProtocolEnabled"] = 254 (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) == 255 "running") || 256 (std::get<NET_PROTO_UNIT_SUB_STATE>(unit) == 257 "listening"); 258 crow::connections::systemBus->async_method_call( 259 [asyncResp, service{std::string(service)}]( 260 const boost::system::error_code ec, 261 const std::variant<std::vector<std::tuple< 262 std::string, std::string>>>& resp) { 263 if (ec) 264 { 265 messages::internalError(asyncResp->res); 266 return; 267 } 268 const std::vector< 269 std::tuple<std::string, std::string>>* 270 responsePtr = std::get_if<std::vector< 271 std::tuple<std::string, std::string>>>( 272 &resp); 273 if (responsePtr == nullptr || 274 responsePtr->size() < 1) 275 { 276 return; 277 } 278 279 const std::string& listenStream = 280 std::get<NET_PROTO_LISTEN_STREAM>( 281 (*responsePtr)[0]); 282 std::size_t lastColonPos = 283 listenStream.rfind(":"); 284 if (lastColonPos == std::string::npos) 285 { 286 // Not a port 287 return; 288 } 289 std::string portStr = 290 listenStream.substr(lastColonPos + 1); 291 char* endPtr = nullptr; 292 // Use strtol instead of stroi to avoid 293 // exceptions 294 long port = 295 std::strtol(portStr.c_str(), &endPtr, 10); 296 297 if (*endPtr != '\0' || portStr.empty()) 298 { 299 // Invalid value 300 asyncResp->res.jsonValue[service]["Port"] = 301 nullptr; 302 } 303 else 304 { 305 // Everything OK 306 asyncResp->res.jsonValue[service]["Port"] = 307 port; 308 } 309 }, 310 "org.freedesktop.systemd1", socketPath, 311 "org.freedesktop.DBus.Properties", "Get", 312 "org.freedesktop.systemd1.Socket", "Listen"); 313 } 314 } 315 }, 316 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 317 "org.freedesktop.systemd1.Manager", "ListUnits"); 318 } 319 320 void handleHostnamePatch(const std::string& hostName, 321 const std::shared_ptr<AsyncResp>& asyncResp) 322 { 323 crow::connections::systemBus->async_method_call( 324 [asyncResp](const boost::system::error_code ec) { 325 if (ec) 326 { 327 messages::internalError(asyncResp->res); 328 return; 329 } 330 messages::success(asyncResp->res); 331 }, 332 "xyz.openbmc_project.Network", 333 "/xyz/openbmc_project/network/config", 334 "org.freedesktop.DBus.Properties", "Set", 335 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 336 std::variant<std::string>(hostName)); 337 } 338 339 void handleNTPProtocolEnabled(const bool& ntpEnabled, 340 const std::shared_ptr<AsyncResp>& asyncResp) 341 { 342 std::string timeSyncMethod; 343 if (ntpEnabled) 344 { 345 timeSyncMethod = 346 "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 347 } 348 else 349 { 350 timeSyncMethod = 351 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 352 } 353 354 crow::connections::systemBus->async_method_call( 355 [asyncResp, 356 ntpEnabled](const boost::system::error_code error_code) { 357 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = ntpEnabled; 358 }, 359 "xyz.openbmc_project.Settings", 360 "/xyz/openbmc_project/time/sync_method", 361 "org.freedesktop.DBus.Properties", "Set", 362 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 363 std::variant<std::string>{timeSyncMethod}); 364 } 365 366 void handleNTPServersPatch(const std::vector<std::string>& ntpServers, 367 const std::shared_ptr<AsyncResp>& asyncResp) 368 { 369 crow::connections::systemBus->async_method_call( 370 [asyncResp, ntpServers](const boost::system::error_code ec) { 371 if (ec) 372 { 373 messages::internalError(asyncResp->res); 374 return; 375 } 376 asyncResp->res.jsonValue["NTP"]["NTPServers"] = 377 std::move(ntpServers); 378 }, 379 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 380 "org.freedesktop.DBus.Properties", "Set", 381 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 382 std::variant<std::vector<std::string>>{ntpServers}); 383 } 384 385 void doPatch(crow::Response& res, const crow::Request& req, 386 const std::vector<std::string>& params) override 387 { 388 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 389 std::optional<std::string> newHostName; 390 std::optional<std::vector<std::string>> ntpServers; 391 std::optional<bool> ntpEnabled; 392 393 if (!json_util::readJson(req, res, "HostName", newHostName, 394 "NTPServers", ntpServers, "NTPEnabled", 395 ntpEnabled)) 396 { 397 return; 398 } 399 if (newHostName) 400 { 401 handleHostnamePatch(*newHostName, asyncResp); 402 return; 403 } 404 if (ntpEnabled) 405 { 406 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 407 } 408 if (ntpServers) 409 { 410 handleNTPServersPatch(*ntpServers, asyncResp); 411 } 412 } 413 }; 414 415 } // namespace redfish 416