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 "openbmc_dbus_rest.hpp" 20 #include "redfish_util.hpp" 21 22 #include <app.hpp> 23 #include <registries/privilege_registry.hpp> 24 #include <utils/json_utils.hpp> 25 #include <utils/stl_utils.hpp> 26 27 #include <optional> 28 #include <variant> 29 namespace redfish 30 { 31 32 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp); 33 std::string getHostName(); 34 35 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{ 36 {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}}; 37 38 inline void 39 extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData, 40 std::vector<std::string>& ntpData, 41 std::vector<std::string>& dnData) 42 { 43 for (const auto& obj : dbusData) 44 { 45 for (const auto& ifacePair : obj.second) 46 { 47 if (obj.first == "/xyz/openbmc_project/network/eth0") 48 { 49 if (ifacePair.first == 50 "xyz.openbmc_project.Network.EthernetInterface") 51 { 52 for (const auto& propertyPair : ifacePair.second) 53 { 54 if (propertyPair.first == "NTPServers") 55 { 56 const std::vector<std::string>* ntpServers = 57 std::get_if<std::vector<std::string>>( 58 &propertyPair.second); 59 if (ntpServers != nullptr) 60 { 61 ntpData = *ntpServers; 62 } 63 } 64 else if (propertyPair.first == "DomainName") 65 { 66 const std::vector<std::string>* domainNames = 67 std::get_if<std::vector<std::string>>( 68 &propertyPair.second); 69 if (domainNames != nullptr) 70 { 71 dnData = *domainNames; 72 } 73 } 74 } 75 } 76 } 77 } 78 } 79 } 80 81 template <typename CallbackFunc> 82 void getEthernetIfaceData(CallbackFunc&& callback) 83 { 84 crow::connections::systemBus->async_method_call( 85 [callback{std::move(callback)}]( 86 const boost::system::error_code errorCode, 87 const GetManagedObjects& dbusData) { 88 std::vector<std::string> ntpServers; 89 std::vector<std::string> domainNames; 90 91 if (errorCode) 92 { 93 callback(false, ntpServers, domainNames); 94 return; 95 } 96 97 extractNTPServersAndDomainNamesData(dbusData, ntpServers, 98 domainNames); 99 100 callback(true, ntpServers, domainNames); 101 }, 102 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 103 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 104 } 105 106 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 107 const crow::Request& req) 108 { 109 asyncResp->res.jsonValue["@odata.type"] = 110 "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol"; 111 asyncResp->res.jsonValue["@odata.id"] = 112 "/redfish/v1/Managers/bmc/NetworkProtocol"; 113 asyncResp->res.jsonValue["Id"] = "NetworkProtocol"; 114 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol"; 115 asyncResp->res.jsonValue["Description"] = "Manager Network Service"; 116 asyncResp->res.jsonValue["Status"]["Health"] = "OK"; 117 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 118 asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; 119 120 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0, 121 // but from security perspective it is not recommended to use. 122 // Hence using protocolEnabled as false to make it OCP and security-wise 123 // compliant 124 asyncResp->res.jsonValue["HTTP"]["Port"] = 0; 125 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false; 126 127 std::string hostName = getHostName(); 128 129 asyncResp->res.jsonValue["HostName"] = hostName; 130 131 getNTPProtocolEnabled(asyncResp); 132 133 // TODO Get eth0 interface data, and call the below callback for JSON 134 // preparation 135 getEthernetIfaceData( 136 [hostName, asyncResp](const bool& success, 137 const std::vector<std::string>& ntpServers, 138 const std::vector<std::string>& domainNames) { 139 if (!success) 140 { 141 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 142 "eth0"); 143 return; 144 } 145 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers; 146 if (hostName.empty() == false) 147 { 148 std::string fqdn = hostName; 149 if (domainNames.empty() == false) 150 { 151 fqdn += "."; 152 fqdn += domainNames[0]; 153 } 154 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn); 155 } 156 }); 157 158 Privileges effectiveUserPrivileges = 159 redfish::getUserPrivileges(req.userRole); 160 161 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is 162 // something only ConfigureManager can access then only display when 163 // the user has permissions ConfigureManager 164 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 165 effectiveUserPrivileges)) 166 { 167 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = { 168 {"@odata.id", 169 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}}; 170 } 171 172 for (const auto& protocol : protocolToDBus) 173 { 174 const std::string& protocolName = protocol.first; 175 const std::string& serviceName = protocol.second; 176 getPortStatusAndPath( 177 serviceName, 178 [asyncResp, protocolName](const boost::system::error_code ec, 179 const std::string& socketPath, 180 bool isProtocolEnabled) { 181 // If the service is not installed, that is not an error 182 if (ec == boost::system::errc::no_such_process) 183 { 184 asyncResp->res.jsonValue[protocolName]["Port"] = 185 nlohmann::detail::value_t::null; 186 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 187 false; 188 return; 189 } 190 if (ec) 191 { 192 messages::internalError(asyncResp->res); 193 return; 194 } 195 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] = 196 isProtocolEnabled; 197 getPortNumber( 198 socketPath, 199 [asyncResp, protocolName]( 200 const boost::system::error_code ec, int portNumber) { 201 if (ec) 202 { 203 messages::internalError(asyncResp->res); 204 return; 205 } 206 asyncResp->res.jsonValue[protocolName]["Port"] = 207 portNumber; 208 }); 209 }); 210 } 211 } // namespace redfish 212 213 inline void handleNTPProtocolEnabled( 214 const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 215 { 216 std::string timeSyncMethod; 217 if (ntpEnabled) 218 { 219 timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP"; 220 } 221 else 222 { 223 timeSyncMethod = 224 "xyz.openbmc_project.Time.Synchronization.Method.Manual"; 225 } 226 227 crow::connections::systemBus->async_method_call( 228 [asyncResp](const boost::system::error_code errorCode) { 229 if (errorCode) 230 { 231 messages::internalError(asyncResp->res); 232 } 233 }, 234 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 235 "org.freedesktop.DBus.Properties", "Set", 236 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod", 237 std::variant<std::string>{timeSyncMethod}); 238 } 239 240 inline void 241 handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 242 std::vector<std::string>& ntpServers) 243 { 244 auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end()); 245 if (iter != ntpServers.end()) 246 { 247 std::string pointer = 248 "NTPServers/" + 249 std::to_string(std::distance(ntpServers.begin(), iter)); 250 messages::propertyValueIncorrect(asyncResp->res, pointer, *iter); 251 return; 252 } 253 254 crow::connections::systemBus->async_method_call( 255 [asyncResp](const boost::system::error_code ec) { 256 if (ec) 257 { 258 messages::internalError(asyncResp->res); 259 return; 260 } 261 }, 262 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0", 263 "org.freedesktop.DBus.Properties", "Set", 264 "xyz.openbmc_project.Network.EthernetInterface", "NTPServers", 265 std::variant<std::vector<std::string>>{ntpServers}); 266 } 267 268 inline void 269 handleProtocolEnabled(const bool protocolEnabled, 270 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const std::string_view netBasePath) 272 { 273 crow::connections::systemBus->async_method_call( 274 [protocolEnabled, asyncResp, 275 netBasePath](const boost::system::error_code ec, 276 const crow::openbmc_mapper::GetSubTreeType& subtree) { 277 if (ec) 278 { 279 messages::internalError(asyncResp->res); 280 return; 281 } 282 283 for (const auto& entry : subtree) 284 { 285 if (boost::algorithm::starts_with(entry.first, netBasePath)) 286 { 287 crow::connections::systemBus->async_method_call( 288 [asyncResp](const boost::system::error_code ec2) { 289 if (ec2) 290 { 291 messages::internalError(asyncResp->res); 292 return; 293 } 294 }, 295 entry.second.begin()->first, entry.first, 296 "org.freedesktop.DBus.Properties", "Set", 297 "xyz.openbmc_project.Control.Service.Attributes", 298 "Running", std::variant<bool>{protocolEnabled}); 299 300 crow::connections::systemBus->async_method_call( 301 [asyncResp](const boost::system::error_code ec2) { 302 if (ec2) 303 { 304 messages::internalError(asyncResp->res); 305 return; 306 } 307 }, 308 entry.second.begin()->first, entry.first, 309 "org.freedesktop.DBus.Properties", "Set", 310 "xyz.openbmc_project.Control.Service.Attributes", 311 "Enabled", std::variant<bool>{protocolEnabled}); 312 } 313 } 314 }, 315 "xyz.openbmc_project.ObjectMapper", 316 "/xyz/openbmc_project/object_mapper", 317 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 318 "/xyz/openbmc_project/control/service", 0, 319 std::array<const char*, 1>{ 320 "xyz.openbmc_project.Control.Service.Attributes"}); 321 } 322 323 inline std::string getHostName() 324 { 325 std::string hostName; 326 327 std::array<char, HOST_NAME_MAX> hostNameCStr; 328 if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0) 329 { 330 hostName = hostNameCStr.data(); 331 } 332 return hostName; 333 } 334 335 inline void 336 getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 337 { 338 crow::connections::systemBus->async_method_call( 339 [asyncResp](const boost::system::error_code errorCode, 340 const std::variant<std::string>& timeSyncMethod) { 341 if (errorCode) 342 { 343 return; 344 } 345 346 const std::string* s = std::get_if<std::string>(&timeSyncMethod); 347 348 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP") 349 { 350 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true; 351 } 352 else if (*s == "xyz.openbmc_project.Time.Synchronization." 353 "Method.Manual") 354 { 355 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false; 356 } 357 }, 358 "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method", 359 "org.freedesktop.DBus.Properties", "Get", 360 "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod"); 361 } 362 363 inline void requestRoutesNetworkProtocol(App& app) 364 { 365 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 366 .privileges(redfish::privileges::patchManagerNetworkProtocol) 367 .methods(boost::beast::http::verb::patch)( 368 [](const crow::Request& req, 369 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 370 std::optional<std::string> newHostName; 371 std::optional<nlohmann::json> ntp; 372 std::optional<nlohmann::json> ipmi; 373 std::optional<nlohmann::json> ssh; 374 375 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp, 376 "HostName", newHostName, "IPMI", ipmi, 377 "SSH", ssh)) 378 { 379 return; 380 } 381 382 asyncResp->res.result(boost::beast::http::status::no_content); 383 if (newHostName) 384 { 385 messages::propertyNotWritable(asyncResp->res, "HostName"); 386 return; 387 } 388 389 if (ntp) 390 { 391 std::optional<std::vector<std::string>> ntpServers; 392 std::optional<bool> ntpEnabled; 393 if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers", 394 ntpServers, "ProtocolEnabled", 395 ntpEnabled)) 396 { 397 return; 398 } 399 400 if (ntpEnabled) 401 { 402 handleNTPProtocolEnabled(*ntpEnabled, asyncResp); 403 } 404 405 if (ntpServers) 406 { 407 stl_utils::removeDuplicate(*ntpServers); 408 handleNTPServersPatch(asyncResp, *ntpServers); 409 } 410 } 411 412 if (ipmi) 413 { 414 std::optional<bool> ipmiProtocolEnabled; 415 if (!json_util::readJson(*ipmi, asyncResp->res, 416 "ProtocolEnabled", 417 ipmiProtocolEnabled)) 418 { 419 return; 420 } 421 422 if (ipmiProtocolEnabled) 423 { 424 handleProtocolEnabled( 425 *ipmiProtocolEnabled, asyncResp, 426 "/xyz/openbmc_project/control/service/" 427 "phosphor_2dipmi_2dnet_40"); 428 } 429 } 430 431 if (ssh) 432 { 433 std::optional<bool> sshProtocolEnabled; 434 if (!json_util::readJson(*ssh, asyncResp->res, 435 "ProtocolEnabled", 436 sshProtocolEnabled)) 437 { 438 return; 439 } 440 441 if (sshProtocolEnabled) 442 { 443 handleProtocolEnabled( 444 *sshProtocolEnabled, asyncResp, 445 "/xyz/openbmc_project/control/service/dropbear"); 446 } 447 } 448 }); 449 450 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/") 451 .privileges(redfish::privileges::getManagerNetworkProtocol) 452 .methods(boost::beast::http::verb::get)( 453 [](const crow::Request& req, 454 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 455 getNetworkData(asyncResp, req); 456 }); 457 } 458 459 } // namespace redfish 460