blob: 447bfcd30be381a0566a7f5bc1add1c24bea16df [file] [log] [blame]
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +05301/**
2 * Copyright © 2017 IBM 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 */
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053016#include <cstring>
17#include <unistd.h>
18#include <sys/types.h>
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053019#include <sys/stat.h>
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053020#include <shadow.h>
21#include <array>
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053022#include <random>
23#include <errno.h>
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +053024#include <xyz/openbmc_project/Common/error.hpp>
25#include <phosphor-logging/log.hpp>
26#include <phosphor-logging/elog.hpp>
27#include <phosphor-logging/elog-errors.hpp>
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +053028#include "user.hpp"
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053029#include "file.hpp"
30#include "shadowlock.hpp"
31#include "config.h"
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +053032namespace phosphor
33{
34namespace user
35{
36
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053037constexpr auto SHADOW_FILE = "/etc/shadow";
38
39// See crypt(3)
40constexpr int SALT_LENGTH = 16;
41
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +053042using namespace phosphor::logging;
43using InsufficientPermission = sdbusplus::xyz::openbmc_project::Common::
44 Error::InsufficientPermission;
45using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
46 Error::InternalFailure;
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053047// Sets or updates the password
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +053048void User::setPassword(std::string newPassword)
49{
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053050 // Gate any access to /etc/shadow
51 phosphor::user::shadow::Lock lock();
52
53 // rewind to the start of shadow entry
54 setspent();
55
56 // Generate a random string from set [A-Za-z0-9./]
57 std::string salt{};
58 salt.resize(SALT_LENGTH);
59 salt = randomString(SALT_LENGTH);
60
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053061 // Apply the change. Updates could be directly made to shadow
62 // but then any update must be contained within the boundary
63 // of that user, else it would run into next entry and thus
64 // corrupting it. Classic example is when a password is set on
65 // a user ID that does not have a prior password
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +053066 applyPassword(SHADOW_FILE, newPassword, salt);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053067 return;
68}
69
70void User::applyPassword(const std::string& shadowFile,
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053071 const std::string& password,
72 const std::string& salt)
73{
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053074 // Needed by getspnam_r
75 struct spwd shdp;
76 struct spwd* pshdp;
77
78 // This should be fine even if SHA512 is used.
79 std::array<char,1024> buffer{};
80
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053081 // Open the shadow file for reading
82 phosphor::user::File shadow(shadowFile, "r");
83 if ((shadow)() == NULL)
84 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +053085 return raiseException(errno, "Error opening shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053086 }
87
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +053088 // open temp shadow file, by suffixing random name in shadow file name.
89 std::vector<char> tempFileName(shadowFile.begin(), shadowFile.end());
90 std::vector<char> fileTemplate = {
91 '_', '_', 'X', 'X', 'X', 'X', 'X', 'X', '\0' };
92 tempFileName.insert(
93 tempFileName.end(), fileTemplate.begin(), fileTemplate.end());
94
95 int fd = mkstemp(tempFileName.data());
96 if (fd == -1)
97 {
98 return raiseException(errno, "Error creating temp shadow file");
99 }
100
101 std::string strTempFileName(tempFileName.data());
102 // Open the temp shadow file for writing from provided fd
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530103 // By "true", remove it at exit if still there.
104 // This is needed to cleanup the temp file at exception
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530105 phosphor::user::File temp(fd, strTempFileName, "w", true);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530106 if ((temp)() == NULL)
107 {
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530108 close(fd);
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530109 return raiseException(errno, "Error opening temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530110 }
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530111 fd = -1; // don't use fd anymore, as the File object owns it
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530112
113 // Change the permission of this new temp file
114 // to be same as shadow so that it's secure
115 struct stat st{};
116 auto r = fstat(fileno((shadow)()), &st);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530117 if (r < 0)
118 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530119 return raiseException(errno, "Error reading shadow file mode");
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530120 }
121
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530122 r = fchmod(fileno((temp)()), st.st_mode);
123 if (r < 0)
124 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530125 return raiseException(errno, "Error setting temp file mode");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530126 }
127
128 // Read shadow file and process
129 while (true)
130 {
131 auto r = fgetspent_r((shadow)(), &shdp, buffer.data(),
132 buffer.max_size(), &pshdp);
133 if (r)
134 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530135 if (errno == EACCES || errno == ERANGE)
136 {
137 return raiseException(errno, "Error reading shadow file");
138 }
139 else
140 {
141 // Seem to have run over all
142 break;
143 }
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530144 }
145
146 // Hash of password if the user matches
147 std::string hash{};
148
149 // Matched user
150 if (user == shdp.sp_namp)
151 {
152 // Update with new hashed password
153 hash = hashPassword(shdp.sp_pwdp, password, salt);
154 shdp.sp_pwdp = const_cast<char*>(hash.c_str());
155 }
156
157 // Apply
158 r = putspent(&shdp, (temp)());
159 if (r < 0)
160 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530161 return raiseException(errno, "Error updating temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530162 }
163 } // All entries
164
165 // Done
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530166 endspent();
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530167 // flush contents to file first, before renaming to avoid
168 // corruption during power failure
169 fflush((temp)());
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530170
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530171 // Everything must be fine at this point
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530172 fs::rename(strTempFileName, shadowFile);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530173 return;
174}
175
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530176void User::raiseException(int errNo, const std::string& errMsg)
177{
178 using namespace std::string_literals;
179 if (errNo == EACCES)
180 {
181 auto message = "Access denied "s + errMsg;
182 log<level::ERR>(message.c_str());
183 elog<InsufficientPermission>();
184 }
185 else
186 {
187 log<level::ERR>(errMsg.c_str(),
188 entry("USER=%s",user.c_str()),
189 entry("ERRNO=%d", errNo));
190 elog<InternalFailure>();
191 }
192}
193
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530194std::string User::hashPassword(char* spPwdp,
195 const std::string& password,
196 const std::string& salt)
197{
198 // Parse and get crypt algo
199 auto cryptAlgo = getCryptField(spPwdp);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530200 if (cryptAlgo.empty())
201 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530202 log<level::ERR>("Error finding crypt algo",
203 entry("USER=%s",user.c_str()));
204 elog<InternalFailure>();
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530205 }
206
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530207 // Update shadow password pointer with hash
208 auto saltString = getSaltString(cryptAlgo, salt);
209 return generateHash(password, saltString);
210}
211
212// Returns a random string in set [A-Za-z0-9./]
213// of size numChars
214const std::string User::randomString(int length)
215{
216 // Populated random string
217 std::string random{};
218
219 // Needed per crypt(3)
220 std::string set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
221 "lmnopqrstuvwxyz0123456789./";
222
223 // Will be used to obtain a seed for the random number engine
224 std::random_device rd;
225
226 // Standard mersenne_twister_engine seeded with rd()
227 std::mt19937 gen(rd());
228
229 std::uniform_int_distribution<> dis(0, set.size()-1);
230 for (int count = 0; count < length; count++)
231 {
232 // Use dis to transform the random unsigned int generated by
233 // gen into a int in [1, SALT_LENGTH]
234 random.push_back(set.at(dis(gen)));
235 }
236 return random;
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530237}
238
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530239// Extract crypto algorithm field
240CryptAlgo User::getCryptField(char* spPwdp)
241{
242 char* savePtr{};
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530243 if (std::string{spPwdp}.front() != '$')
244 {
245 return DEFAULT_CRYPT_ALGO;
246 }
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530247 return strtok_r(spPwdp, "$", &savePtr);
248}
249
250// Returns specific format of salt string
251std::string User::getSaltString(const std::string& crypt,
252 const std::string& salt)
253{
254 return '$' + crypt + '$' + salt + '$';
255}
256
257// Given a password and salt, generates hash
258std::string User::generateHash(const std::string& password,
259 const std::string& salt)
260{
261 return crypt(password.c_str(), salt.c_str());
262}
263
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530264} // namespace user
265} // namespace phosphor