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 #include "cipher_mgmt.hpp"
17 
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22 
23 #include <phosphor-logging/log.hpp>
24 
25 #include <filesystem>
26 #include <fstream>
27 
28 namespace ipmi
29 {
30 
31 using namespace phosphor::logging;
32 using Json = nlohmann::json;
33 namespace fs = std::filesystem;
34 
35 CipherConfig& getCipherConfigObject(const std::string& csFileName,
36                                     const std::string& csDefaultFileName)
37 {
38     static CipherConfig cipherConfig(csFileName, csDefaultFileName);
39     return cipherConfig;
40 }
41 
42 CipherConfig::CipherConfig(const std::string& csFileName,
43                            const std::string& csDefaultFileName) :
44     cipherSuitePrivFileName(csFileName),
45     cipherSuiteDefaultPrivFileName(csDefaultFileName)
46 {
47     loadCSPrivilegesToMap();
48 }
49 
50 void CipherConfig::loadCSPrivilegesToMap()
51 {
52     if (!fs::exists(cipherSuiteDefaultPrivFileName))
53     {
54         log<level::ERR>("CS privilege levels default file does not exist...");
55     }
56     else
57     {
58         // read default privileges
59         Json data = readCSPrivilegeLevels(cipherSuiteDefaultPrivFileName);
60 
61         // load default privileges
62         updateCSPrivilegesMap(data);
63 
64         // check for user-saved privileges
65         if (fs::exists(cipherSuitePrivFileName))
66         {
67             data = readCSPrivilegeLevels(cipherSuitePrivFileName);
68             if (data != nullptr)
69             {
70                 // update map with user-saved privileges by merging (overriding)
71                 // values from the defaults
72                 updateCSPrivilegesMap(data);
73             }
74         }
75     }
76 }
77 
78 void CipherConfig::updateCSPrivilegesMap(const Json& jsonData)
79 {
80     for (uint8_t chNum = 0; chNum < ipmi::maxIpmiChannels; chNum++)
81     {
82         std::string chKey = "Channel" + std::to_string(chNum);
83         for (uint8_t csNum = 0; csNum < maxCSRecords; csNum++)
84         {
85             auto csKey = "CipherID" + std::to_string(csNum);
86 
87             if (jsonData.find(chKey) != jsonData.end())
88             {
89                 csPrivilegeMap[{chNum, csNum}] = convertToPrivLimitIndex(
90                     static_cast<std::string>(jsonData[chKey][csKey]));
91             }
92         }
93     }
94 }
95 
96 Json CipherConfig::readCSPrivilegeLevels(const std::string& csFileName)
97 {
98     std::ifstream jsonFile(csFileName);
99     if (!jsonFile.good())
100     {
101         log<level::ERR>("JSON file not found");
102         return nullptr;
103     }
104 
105     Json data = nullptr;
106     try
107     {
108         data = Json::parse(jsonFile, nullptr, false);
109     }
110     catch (const Json::parse_error& e)
111     {
112         log<level::ERR>("Corrupted cipher suite privilege levels config file.",
113                         entry("MSG: %s", e.what()));
114     }
115 
116     return data;
117 }
118 
119 int CipherConfig::writeCSPrivilegeLevels(const Json& jsonData)
120 {
121     std::string tmpFile = static_cast<std::string>(cipherSuitePrivFileName) +
122                           "_tmpXXXXXX";
123 
124     std::vector<char> tmpRandomFile(tmpFile.length() + 1);
125     strncpy(tmpRandomFile.data(), tmpFile.c_str(), tmpFile.length() + 1);
126 
127     int fd = mkstemp(tmpRandomFile.data());
128     fchmod(fd, 0644);
129 
130     if (fd < 0)
131     {
132         log<level::ERR>("Error opening CS privilege level config file",
133                         entry("FILE_NAME=%s", tmpFile.c_str()));
134         return -EIO;
135     }
136     const auto& writeData = jsonData.dump();
137     if (write(fd, writeData.c_str(), writeData.size()) !=
138         static_cast<ssize_t>(writeData.size()))
139     {
140         close(fd);
141         log<level::ERR>("Error writing CS privilege level config file",
142                         entry("FILE_NAME=%s", tmpFile.c_str()));
143         unlink(tmpRandomFile.data());
144         return -EIO;
145     }
146     close(fd);
147 
148     if (std::rename(tmpRandomFile.data(), cipherSuitePrivFileName.c_str()))
149     {
150         log<level::ERR>("Error renaming CS privilege level config file",
151                         entry("FILE_NAME=%s", tmpFile.c_str()));
152         unlink(tmpRandomFile.data());
153         return -EIO;
154     }
155 
156     return 0;
157 }
158 
159 uint4_t CipherConfig::convertToPrivLimitIndex(const std::string& value)
160 {
161     auto iter = std::find(ipmi::privList.begin(), ipmi::privList.end(), value);
162     if (iter == privList.end())
163     {
164         log<level::ERR>("Invalid privilege.",
165                         entry("PRIV_STR=%s", value.c_str()));
166         return ccUnspecifiedError;
167     }
168 
169     return static_cast<uint4_t>(std::distance(ipmi::privList.begin(), iter));
170 }
171 
172 std::string CipherConfig::convertToPrivLimitString(const uint4_t& value)
173 {
174     return ipmi::privList.at(static_cast<size_t>(value));
175 }
176 
177 ipmi::Cc CipherConfig::getCSPrivilegeLevels(
178     uint8_t chNum, std::array<uint4_t, maxCSRecords>& csPrivilegeLevels)
179 {
180     if (!isValidChannel(chNum))
181     {
182         log<level::ERR>("Invalid channel number", entry("CHANNEL=%u", chNum));
183         return ccInvalidFieldRequest;
184     }
185 
186     for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
187     {
188         csPrivilegeLevels[csNum] = csPrivilegeMap[{chNum, csNum}];
189     }
190     return ccSuccess;
191 }
192 
193 ipmi::Cc CipherConfig::setCSPrivilegeLevels(
194     uint8_t chNum, const std::array<uint4_t, maxCSRecords>& requestData)
195 {
196     if (!isValidChannel(chNum))
197     {
198         log<level::ERR>("Invalid channel number", entry("CHANNEL=%u", chNum));
199         return ccInvalidFieldRequest;
200     }
201 
202     Json jsonData;
203     if (!fs::exists(cipherSuitePrivFileName))
204     {
205         log<level::INFO>("CS privilege levels user settings file does not "
206                          "exist. Creating...");
207     }
208     else
209     {
210         jsonData = readCSPrivilegeLevels(cipherSuitePrivFileName);
211         if (jsonData == nullptr)
212         {
213             return ccUnspecifiedError;
214         }
215     }
216 
217     Json privData;
218     std::string csKey;
219     constexpr auto privMaxValue = static_cast<uint8_t>(ipmi::Privilege::Oem);
220     for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
221     {
222         csKey = "CipherID" + std::to_string(csNum);
223         auto priv = static_cast<uint8_t>(requestData[csNum]);
224 
225         if (priv > privMaxValue)
226         {
227             return ccInvalidFieldRequest;
228         }
229         privData[csKey] = convertToPrivLimitString(priv);
230     }
231 
232     std::string chKey = "Channel" + std::to_string(chNum);
233     jsonData[chKey] = privData;
234 
235     if (writeCSPrivilegeLevels(jsonData))
236     {
237         log<level::ERR>("Error in setting CS Privilege Levels.");
238         return ccUnspecifiedError;
239     }
240 
241     updateCSPrivilegesMap(jsonData);
242     return ccSuccess;
243 }
244 
245 } // namespace ipmi
246