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