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