1 #include "sol_manager.hpp"
2 
3 #include "main.hpp"
4 #include "sol_context.hpp"
5 
6 #include <sys/socket.h>
7 #include <sys/un.h>
8 
9 #include <boost/asio/basic_stream_socket.hpp>
10 #include <boost/asio/io_context.hpp>
11 #include <boost/asio/local/stream_protocol.hpp>
12 #include <boost/asio/write.hpp>
13 #include <chrono>
14 #include <cmath>
15 #include <ipmid/utils.hpp>
16 #include <phosphor-logging/log.hpp>
17 #include <sdbusplus/message/types.hpp>
18 
19 constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
20 constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
21 constexpr const char* PROP_INTF = "org.freedesktop.DBus.Properties";
22 
23 namespace sol
24 {
25 
26 using namespace phosphor::logging;
27 
28 std::unique_ptr<sdbusplus::bus::match_t> matchPtrSOL(nullptr);
29 
30 void Manager::initConsoleSocket()
31 {
32     // explicit length constructor for NUL-prefixed abstract path
33     std::string path(CONSOLE_SOCKET_PATH, CONSOLE_SOCKET_PATH_LEN);
34     boost::asio::local::stream_protocol::endpoint ep(path);
35     consoleSocket =
36         std::make_unique<boost::asio::local::stream_protocol::socket>(*io);
37     consoleSocket->connect(ep);
38 }
39 
40 void Manager::consoleInputHandler()
41 {
42     boost::system::error_code ec;
43     boost::asio::socket_base::bytes_readable cmd(true);
44     consoleSocket->io_control(cmd, ec);
45     size_t readSize;
46     if (!ec)
47     {
48         readSize = cmd.get();
49     }
50     else
51     {
52         log<level::ERR>("Reading ready count from host console socket failed:",
53                         entry("EXCEPTION=%s", ec.message().c_str()));
54         return;
55     }
56     std::vector<uint8_t> buffer(readSize);
57     ec.clear();
58     size_t readDataLen =
59         consoleSocket->read_some(boost::asio::buffer(buffer), ec);
60     if (ec)
61     {
62         log<level::ERR>("Reading from host console socket failed:",
63                         entry("EXCEPTION=%s", ec.message().c_str()));
64         return;
65     }
66 
67     // Update the Console buffer with data read from the socket
68     buffer.resize(readDataLen);
69     dataBuffer.write(buffer);
70 }
71 
72 int Manager::writeConsoleSocket(const std::vector<uint8_t>& input) const
73 {
74     boost::system::error_code ec;
75     boost::asio::write(*consoleSocket, boost::asio::buffer(input), ec);
76     return ec.value();
77 }
78 
79 void Manager::startHostConsole()
80 {
81     if (!consoleSocket)
82     {
83         initConsoleSocket();
84     }
85 
86     // Register callback to close SOL session for disable SSH SOL
87     if (matchPtrSOL == nullptr)
88     {
89         registerSOLServiceChangeCallback();
90     }
91 
92     consoleSocket->async_wait(boost::asio::socket_base::wait_read,
93                               [this](const boost::system::error_code& ec) {
94                                   if (!ec)
95                                   {
96                                       consoleInputHandler();
97                                       startHostConsole();
98                                   }
99                               });
100 } // namespace sol
101 
102 void Manager::stopHostConsole()
103 {
104     if (consoleSocket)
105     {
106         consoleSocket->cancel();
107         consoleSocket.reset();
108     }
109 }
110 
111 void Manager::updateSOLParameter(uint8_t channelNum)
112 {
113     std::variant<uint8_t, bool> value;
114     sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
115     static std::string solService{};
116     ipmi::PropertyMap properties;
117     std::string ethdevice = ipmi::getChannelName(channelNum);
118     std::string solPathWitheEthName = solPath + ethdevice;
119     if (solService.empty())
120     {
121         try
122         {
123             solService =
124                 ipmi::getService(dbus, solInterface, solPathWitheEthName);
125         }
126         catch (const std::runtime_error& e)
127         {
128             solService.clear();
129             phosphor::logging::log<phosphor::logging::level::ERR>(
130                 "Error: get SOL service failed");
131             return;
132         }
133     }
134     try
135     {
136         properties = ipmi::getAllDbusProperties(
137             dbus, solService, solPathWitheEthName, solInterface);
138     }
139     catch (const std::runtime_error&)
140     {
141         phosphor::logging::log<phosphor::logging::level::ERR>(
142             "Error setting sol parameter");
143         return;
144     }
145 
146     progress = std::get<uint8_t>(properties["Progress"]);
147 
148     enable = std::get<bool>(properties["Enable"]);
149 
150     forceEncrypt = std::get<bool>(properties["ForceEncryption"]);
151 
152     forceAuth = std::get<bool>(properties["ForceAuthentication"]);
153 
154     solMinPrivilege = static_cast<session::Privilege>(
155         std::get<uint8_t>(properties["Privilege"]));
156 
157     accumulateInterval =
158         std::get<uint8_t>((properties["AccumulateIntervalMS"])) *
159         sol::accIntervalFactor * 1ms;
160 
161     sendThreshold = std::get<uint8_t>(properties["Threshold"]);
162 
163     retryCount = std::get<uint8_t>(properties["RetryCount"]);
164 
165     retryInterval = std::get<uint8_t>(properties["RetryIntervalMS"]) *
166                     sol::retryIntervalFactor * 1ms;
167 
168     return;
169 }
170 
171 void Manager::startPayloadInstance(uint8_t payloadInstance,
172                                    session::SessionID sessionID)
173 {
174     if (payloadMap.empty())
175     {
176         try
177         {
178             startHostConsole();
179         }
180         catch (const std::exception& e)
181         {
182             log<level::ERR>("Encountered exception when starting host console. "
183                             "Hence stopping host console.",
184                             entry("EXCEPTION=%s", e.what()));
185             stopHostConsole();
186             throw;
187         }
188     }
189 
190     // Create the SOL Context data for payload instance
191     std::shared_ptr<Context> context = Context::makeContext(
192         io, retryCount, sendThreshold, payloadInstance, sessionID);
193 
194     payloadMap.emplace(payloadInstance, std::move(context));
195 }
196 
197 void Manager::stopPayloadInstance(uint8_t payloadInstance)
198 {
199     auto iter = payloadMap.find(payloadInstance);
200     if (iter == payloadMap.end())
201     {
202         throw std::runtime_error("SOL Payload instance not found ");
203     }
204 
205     payloadMap.erase(iter);
206 
207     if (payloadMap.empty())
208     {
209         stopHostConsole();
210 
211         dataBuffer.erase(dataBuffer.size());
212     }
213 }
214 
215 void Manager::stopAllPayloadInstance()
216 {
217     // Erase all payload instance
218     payloadMap.erase(payloadMap.begin(), payloadMap.end());
219 
220     stopHostConsole();
221 
222     dataBuffer.erase(dataBuffer.size());
223 }
224 
225 void registerSOLServiceChangeCallback()
226 {
227     using namespace sdbusplus::bus::match::rules;
228     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
229     try
230     {
231         auto servicePath = ipmi::getDbusObject(
232             bus, "xyz.openbmc_project.Control.Service.Attributes",
233             "/xyz/openbmc_project/control/service", "obmc_2dconsole");
234 
235         if (!std::empty(servicePath.first))
236         {
237             matchPtrSOL = std::make_unique<sdbusplus::bus::match_t>(
238                 bus,
239                 path_namespace(servicePath.first) +
240                     "arg0namespace='xyz.openbmc_project.Control.Service."
241                     "Attributes'"
242                     ", " +
243                     type::signal() + member("PropertiesChanged") +
244                     interface("org.freedesktop.DBus.Properties"),
245                 [](sdbusplus::message::message& msg) {
246                     std::string intfName;
247                     std::map<std::string, std::variant<bool>> properties;
248                     msg.read(intfName, properties);
249 
250                     const auto it = properties.find("Enabled");
251                     if (it != properties.end())
252                     {
253                         const bool* state = std::get_if<bool>(&it->second);
254 
255                         if (state != nullptr && *state == false)
256                         {
257                             // Stop all the payload session.
258                             sol::Manager::get().stopAllPayloadInstance();
259                         }
260                     }
261                 });
262         }
263     }
264     catch (const sdbusplus::exception_t& e)
265     {
266         log<level::ERR>(
267             "Failed to get service path in registerSOLServiceChangeCallback");
268     }
269 }
270 
271 } // namespace sol
272