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 <ipmid/utils.hpp>
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/message/types.hpp>
16 
17 #include <chrono>
18 #include <cmath>
19 
20 constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
21 constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
22 constexpr const char* PROP_INTF = "org.freedesktop.DBus.Properties";
23 
24 namespace sol
25 {
26 
27 std::unique_ptr<sdbusplus::bus::match_t> matchPtrSOL(nullptr);
28 std::unique_ptr<sdbusplus::bus::match_t> solConfPropertiesSignal(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         lg2::error(
53             "Reading ready count from host console socket failed: {ERROR}",
54             "ERROR", ec.value());
55         return;
56     }
57     std::vector<uint8_t> buffer(readSize);
58     ec.clear();
59     size_t readDataLen =
60         consoleSocket->read_some(boost::asio::buffer(buffer), ec);
61     if (ec)
62     {
63         lg2::error("Reading from host console socket failed: {ERROR}", "ERROR",
64                    ec.value());
65         return;
66     }
67 
68     // Update the Console buffer with data read from the socket
69     buffer.resize(readDataLen);
70     dataBuffer.write(buffer);
71 }
72 
73 int Manager::writeConsoleSocket(const std::vector<uint8_t>& input,
74                                 bool breakFlag) const
75 {
76     boost::system::error_code ec;
77     if (breakFlag)
78     {
79         consoleSocket->send(boost::asio::buffer(input), MSG_OOB, ec);
80     }
81     else
82     {
83         consoleSocket->send(boost::asio::buffer(input), 0, ec);
84     }
85 
86     return ec.value();
87 }
88 
89 void Manager::startHostConsole()
90 {
91     if (!consoleSocket)
92     {
93         initConsoleSocket();
94     }
95 
96     // Register callback to close SOL session for disable SSH SOL
97     if (matchPtrSOL == nullptr)
98     {
99         registerSOLServiceChangeCallback();
100     }
101 
102     consoleSocket->async_wait(boost::asio::socket_base::wait_read,
103                               [this](const boost::system::error_code& ec) {
104                                   if (!ec)
105                                   {
106                                       consoleInputHandler();
107                                       startHostConsole();
108                                   }
109                               });
110 } // namespace sol
111 
112 void Manager::stopHostConsole()
113 {
114     if (consoleSocket)
115     {
116         consoleSocket->cancel();
117         consoleSocket.reset();
118     }
119 }
120 
121 void Manager::updateSOLParameter(uint8_t channelNum)
122 {
123     std::variant<uint8_t, bool> value;
124     sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
125     static std::string solService{};
126     ipmi::PropertyMap properties;
127     std::string ethdevice = ipmi::getChannelName(channelNum);
128     std::string solPathWitheEthName = solPath + ethdevice;
129     if (solService.empty())
130     {
131         try
132         {
133             solService =
134                 ipmi::getService(dbus, solInterface, solPathWitheEthName);
135         }
136         catch (const std::runtime_error& e)
137         {
138             solService.clear();
139             lg2::error("Get SOL service failed: {ERROR}", "ERROR", e);
140             return;
141         }
142     }
143     try
144     {
145         properties = ipmi::getAllDbusProperties(
146             dbus, solService, solPathWitheEthName, solInterface);
147     }
148     catch (const std::runtime_error& e)
149     {
150         lg2::error("Setting sol parameter: {ERROR}", "ERROR", e);
151         return;
152     }
153 
154     progress = std::get<uint8_t>(properties["Progress"]);
155 
156     enable = std::get<bool>(properties["Enable"]);
157 
158     forceEncrypt = std::get<bool>(properties["ForceEncryption"]);
159 
160     forceAuth = std::get<bool>(properties["ForceAuthentication"]);
161 
162     solMinPrivilege = static_cast<session::Privilege>(
163         std::get<uint8_t>(properties["Privilege"]));
164 
165     accumulateInterval =
166         std::get<uint8_t>((properties["AccumulateIntervalMS"])) *
167         sol::accIntervalFactor * 1ms;
168 
169     sendThreshold = std::get<uint8_t>(properties["Threshold"]);
170 
171     retryCount = std::get<uint8_t>(properties["RetryCount"]);
172 
173     retryInterval = std::get<uint8_t>(properties["RetryIntervalMS"]) *
174                     sol::retryIntervalFactor * 1ms;
175 
176     return;
177 }
178 
179 void Manager::startPayloadInstance(uint8_t payloadInstance,
180                                    session::SessionID sessionID)
181 {
182     if (payloadMap.empty())
183     {
184         try
185         {
186             startHostConsole();
187         }
188         catch (const std::exception& e)
189         {
190             lg2::error(
191                 "Encountered exception when starting host console. Hence stopping host console: {ERROR}",
192                 "ERROR", e);
193             stopHostConsole();
194             throw;
195         }
196     }
197 
198     // Create the SOL Context data for payload instance
199     std::shared_ptr<Context> context = Context::makeContext(
200         io, retryCount, sendThreshold, payloadInstance, sessionID);
201 
202     payloadMap.emplace(payloadInstance, std::move(context));
203 }
204 
205 void Manager::stopPayloadInstance(uint8_t payloadInstance)
206 {
207     auto iter = payloadMap.find(payloadInstance);
208     if (iter == payloadMap.end())
209     {
210         throw std::runtime_error("SOL Payload instance not found ");
211     }
212 
213     payloadMap.erase(iter);
214 
215     if (payloadMap.empty())
216     {
217         stopHostConsole();
218 
219         dataBuffer.erase(dataBuffer.size());
220     }
221 }
222 
223 void Manager::stopAllPayloadInstance()
224 {
225     // Erase all payload instance
226     payloadMap.erase(payloadMap.begin(), payloadMap.end());
227 
228     stopHostConsole();
229 
230     dataBuffer.erase(dataBuffer.size());
231 }
232 
233 void registerSOLServiceChangeCallback()
234 {
235     using namespace sdbusplus::bus::match::rules;
236     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
237     try
238     {
239         auto servicePath = ipmi::getDbusObject(
240             bus, "xyz.openbmc_project.Control.Service.Attributes",
241             "/xyz/openbmc_project/control/service", "_6fbmc_2dconsole");
242 
243         if (!std::empty(servicePath.first))
244         {
245             matchPtrSOL = std::make_unique<sdbusplus::bus::match_t>(
246                 bus,
247                 path_namespace(servicePath.first) +
248                     "arg0namespace='xyz.openbmc_project.Control.Service."
249                     "Attributes'"
250                     ", " +
251                     type::signal() + member("PropertiesChanged") +
252                     interface("org.freedesktop.DBus.Properties"),
253                 [](sdbusplus::message_t& msg) {
254                     std::string intfName;
255                     std::map<std::string, std::variant<bool>> properties;
256                     msg.read(intfName, properties);
257 
258                     const auto it = properties.find("Enabled");
259                     if (it != properties.end())
260                     {
261                         const bool* state = std::get_if<bool>(&it->second);
262 
263                         if (state != nullptr && *state == false)
264                         {
265                             // Stop all the payload session.
266                             sol::Manager::get().stopAllPayloadInstance();
267                         }
268                     }
269                 });
270         }
271     }
272     catch (const sdbusplus::exception_t& e)
273     {
274         lg2::error(
275             "Failed to get service path in registerSOLServiceChangeCallback: {ERROR}",
276             "ERROR", e);
277     }
278 }
279 
280 void procSolConfChange(sdbusplus::message_t& msg)
281 {
282     using SolConfVariant = std::variant<bool, uint8_t>;
283     using SolConfProperties =
284         std::vector<std::pair<std::string, SolConfVariant>>;
285 
286     std::string iface;
287     SolConfProperties properties;
288 
289     try
290     {
291         msg.read(iface, properties);
292     }
293     catch (const std::exception& e)
294     {
295         lg2::error("procSolConfChange get properties FAIL: {ERROR}", "ERROR",
296                    e);
297         return;
298     }
299 
300     for (const auto& prop : properties)
301     {
302         if (prop.first == "Progress")
303         {
304             sol::Manager::get().progress = std::get<uint8_t>(prop.second);
305         }
306         else if (prop.first == "Enable")
307         {
308             sol::Manager::get().enable = std::get<bool>(prop.second);
309         }
310         else if (prop.first == "ForceEncryption")
311         {
312             sol::Manager::get().forceEncrypt = std::get<bool>(prop.second);
313         }
314         else if (prop.first == "ForceAuthentication")
315         {
316             sol::Manager::get().forceAuth = std::get<bool>(prop.second);
317         }
318         else if (prop.first == "Privilege")
319         {
320             sol::Manager::get().solMinPrivilege =
321                 static_cast<session::Privilege>(std::get<uint8_t>(prop.second));
322         }
323         else if (prop.first == "AccumulateIntervalMS")
324         {
325             sol::Manager::get().accumulateInterval =
326                 std::get<uint8_t>(prop.second) * sol::accIntervalFactor * 1ms;
327         }
328         else if (prop.first == "Threshold")
329         {
330             sol::Manager::get().sendThreshold = std::get<uint8_t>(prop.second);
331         }
332         else if (prop.first == "RetryCount")
333         {
334             sol::Manager::get().retryCount = std::get<uint8_t>(prop.second);
335         }
336         else if (prop.first == "RetryIntervalMS")
337         {
338             sol::Manager::get().retryInterval =
339                 std::get<uint8_t>(prop.second) * sol::retryIntervalFactor * 1ms;
340         }
341     }
342 }
343 
344 void registerSolConfChangeCallbackHandler(std::string channel)
345 {
346     if (solConfPropertiesSignal == nullptr)
347     {
348         using namespace sdbusplus::bus::match::rules;
349         sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
350         try
351         {
352             auto servicePath = solPath + channel;
353 
354             solConfPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
355                 bus, propertiesChangedNamespace(servicePath, solInterface),
356                 procSolConfChange);
357         }
358         catch (const sdbusplus::exception_t& e)
359         {
360             lg2::error(
361                 "Failed to get service path in registerSolConfChangeCallbackHandler, channel: {CHANNEL}, error: {ERROR}",
362                 "CHANNEL", channel, "ERROR", e);
363         }
364     }
365     return;
366 }
367 
368 } // namespace sol
369