1 #include "channel.hpp"
2 
3 #include "user_channel/channel_layer.hpp"
4 
5 #include <arpa/inet.h>
6 
7 #include <boost/process/child.hpp>
8 #include <fstream>
9 #include <ipmid/types.hpp>
10 #include <ipmid/utils.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/log.hpp>
13 #include <set>
14 #include <string>
15 #include <xyz/openbmc_project/Common/error.hpp>
16 
17 using namespace phosphor::logging;
18 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
19 
20 namespace cipher
21 {
22 
23 /** @brief Get the supported Cipher records
24  *
25  * The cipher records are read from the JSON file and converted into
26  * 1. cipher suite record format mentioned in the IPMI specification. The
27  * records can be either OEM or standard cipher. Each json entry is parsed and
28  * converted into the cipher record format and pushed into the vector.
29  * 2. Algorithms listed in vector format
30  *
31  * @return pair of vector containing 1. all the cipher suite records. 2.
32  * Algorithms supported
33  *
34  */
35 std::pair<std::vector<uint8_t>, std::vector<uint8_t>> getCipherRecords()
36 {
37     std::vector<uint8_t> cipherRecords;
38     std::vector<uint8_t> supportedAlgorithmRecords;
39     // create set to get the unique supported algorithms
40     std::set<uint8_t> supportedAlgorithmSet;
41 
42     std::ifstream jsonFile(configFile);
43     if (!jsonFile.is_open())
44     {
45         log<level::ERR>("Channel Cipher suites file not found");
46         elog<InternalFailure>();
47     }
48 
49     auto data = Json::parse(jsonFile, nullptr, false);
50     if (data.is_discarded())
51     {
52         log<level::ERR>("Parsing channel cipher suites JSON failed");
53         elog<InternalFailure>();
54     }
55 
56     for (const auto& record : data)
57     {
58         if (record.find(oem) != record.end())
59         {
60             // OEM cipher suite - 0xC1
61             cipherRecords.push_back(oemCipherSuite);
62             // Cipher Suite ID
63             cipherRecords.push_back(record.value(cipher, 0));
64             // OEM IANA - 3 bytes
65             cipherRecords.push_back(record.value(oem, 0));
66             cipherRecords.push_back(record.value(oem, 0) >> 8);
67             cipherRecords.push_back(record.value(oem, 0) >> 16);
68         }
69         else
70         {
71             // Standard cipher suite - 0xC0
72             cipherRecords.push_back(stdCipherSuite);
73             // Cipher Suite ID
74             cipherRecords.push_back(record.value(cipher, 0));
75         }
76 
77         // Authentication algorithm number
78         cipherRecords.push_back(record.value(auth, 0));
79         supportedAlgorithmSet.insert(record.value(auth, 0));
80 
81         // Integrity algorithm number
82         cipherRecords.push_back(record.value(integrity, 0) | integrityTag);
83         supportedAlgorithmSet.insert(record.value(integrity, 0) | integrityTag);
84 
85         // Confidentiality algorithm number
86         cipherRecords.push_back(record.value(conf, 0) | confTag);
87         supportedAlgorithmSet.insert(record.value(conf, 0) | confTag);
88     }
89 
90     // copy the set to supportedAlgorithmRecord which is vector based.
91     std::copy(supportedAlgorithmSet.begin(), supportedAlgorithmSet.end(),
92               std::back_inserter(supportedAlgorithmRecords));
93 
94     return std::make_pair(cipherRecords, supportedAlgorithmRecords);
95 }
96 
97 } // namespace cipher
98 
99 /** @brief this command is used to look up what authentication, integrity,
100  *  confidentiality algorithms are supported.
101  *
102  *  @ param ctx - context pointer
103  *  @ param channelNumber - channel number
104  *  @ param payloadType - payload type
105  *  @ param listIndex - list index
106  *  @ param algoSelectBit - list algorithms
107  *
108  *  @returns ipmi completion code plus response data
109  *  - rspChannel - channel number for authentication algorithm.
110  *  - rspRecords - cipher suite records.
111  **/
112 ipmi::RspType<uint8_t,             // Channel Number
113               std::vector<uint8_t> // Cipher Records
114               >
115     getChannelCipherSuites(ipmi::Context::ptr ctx, uint4_t channelNumber,
116                            uint4_t reserved1, uint8_t payloadType,
117                            uint6_t listIndex, uint1_t reserved2,
118                            uint1_t algoSelectBit)
119 {
120     static std::vector<uint8_t> cipherRecords;
121     static std::vector<uint8_t> supportedAlgorithms;
122     static auto recordInit = false;
123 
124     uint8_t rspChannel = ipmi::convertCurrentChannelNum(
125         static_cast<uint8_t>(channelNumber), ctx->channel);
126 
127     if (!ipmi::isValidChannel(rspChannel) || reserved1 != 0 || reserved2 != 0)
128     {
129         return ipmi::responseInvalidFieldRequest();
130     }
131 
132     if (!recordInit)
133     {
134         try
135         {
136             std::tie(cipherRecords, supportedAlgorithms) =
137                 cipher::getCipherRecords();
138             recordInit = true;
139         }
140         catch (const std::exception& e)
141         {
142             return ipmi::responseUnspecifiedError();
143         }
144     }
145 
146     const std::vector<uint8_t>& records =
147         algoSelectBit ? cipherRecords : supportedAlgorithms;
148     static constexpr auto respSize = 16;
149 
150     // Session support is available in active LAN channels.
151     if ((ipmi::getChannelSessionSupport(rspChannel) ==
152          ipmi::EChannelSessSupported::none) ||
153         !(ipmi::doesDeviceExist(rspChannel)))
154     {
155         log<level::DEBUG>("Get channel cipher suites - Device does not exist");
156         return ipmi::responseInvalidFieldRequest();
157     }
158 
159     // List index(00h-3Fh), 0h selects the first set of 16, 1h selects the next
160     // set of 16 and so on.
161 
162     // Calculate the number of record data bytes to be returned.
163     auto start =
164         std::min(static_cast<size_t>(listIndex) * respSize, records.size());
165     auto end = std::min((static_cast<size_t>(listIndex) * respSize) + respSize,
166                         records.size());
167     auto size = end - start;
168 
169     std::vector<uint8_t> rspRecords;
170     std::copy_n(records.data() + start, size, std::back_inserter(rspRecords));
171 
172     return ipmi::responseSuccess(rspChannel, rspRecords);
173 }
174 
175 template <typename... ArgTypes>
176 static int executeCmd(const char* path, ArgTypes&&... tArgs)
177 {
178     boost::process::child execProg(path, const_cast<char*>(tArgs)...);
179     execProg.wait();
180     return execProg.exit_code();
181 }
182 
183 /** @brief Enable the network IPMI service on the specified ethernet interface.
184  *
185  *  @param[in] intf - ethernet interface on which to enable IPMI
186  */
187 void enableNetworkIPMI(const std::string& intf)
188 {
189     // Check if there is a iptable filter to drop IPMI packets for the
190     // interface.
191     auto retCode =
192         executeCmd("/usr/sbin/iptables", "-C", "INPUT", "-p", "udp", "-i",
193                    intf.c_str(), "--dport", "623", "-j", "DROP");
194 
195     // If the iptable filter exists, delete the filter.
196     if (!retCode)
197     {
198         auto response =
199             executeCmd("/usr/sbin/iptables", "-D", "INPUT", "-p", "udp", "-i",
200                        intf.c_str(), "--dport", "623", "-j", "DROP");
201 
202         if (response)
203         {
204             log<level::ERR>("Dropping the iptables filter failed",
205                             entry("INTF=%s", intf.c_str()),
206                             entry("RETURN_CODE:%d", response));
207         }
208     }
209 }
210 
211 /** @brief Disable the network IPMI service on the specified ethernet interface.
212  *
213  *  @param[in] intf - ethernet interface on which to disable IPMI
214  */
215 void disableNetworkIPMI(const std::string& intf)
216 {
217     // Check if there is a iptable filter to drop IPMI packets for the
218     // interface.
219     auto retCode =
220         executeCmd("/usr/sbin/iptables", "-C", "INPUT", "-p", "udp", "-i",
221                    intf.c_str(), "--dport", "623", "-j", "DROP");
222 
223     // If the iptable filter does not exist, add filter to drop network IPMI
224     // packets
225     if (retCode)
226     {
227         auto response =
228             executeCmd("/usr/sbin/iptables", "-I", "INPUT", "-p", "udp", "-i",
229                        intf.c_str(), "--dport", "623", "-j", "DROP");
230 
231         if (response)
232         {
233             log<level::ERR>("Inserting iptables filter failed",
234                             entry("INTF=%s", intf.c_str()),
235                             entry("RETURN_CODE:%d", response));
236         }
237     }
238 }
239