blob: 6999a98221224981290df342bbccc91a23ba9258 [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;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053043using InsufficientPermission =
44 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
45using InternalFailure =
46 sdbusplus::xyz::openbmc_project::Common::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,
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053071 const std::string& password, const std::string& salt)
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053072{
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053073 // Needed by getspnam_r
74 struct spwd shdp;
75 struct spwd* pshdp;
76
77 // This should be fine even if SHA512 is used.
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053078 std::array<char, 1024> buffer{};
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053079
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053080 // Open the shadow file for reading
81 phosphor::user::File shadow(shadowFile, "r");
82 if ((shadow)() == NULL)
83 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +053084 return raiseException(errno, "Error opening shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053085 }
86
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +053087 // open temp shadow file, by suffixing random name in shadow file name.
88 std::vector<char> tempFileName(shadowFile.begin(), shadowFile.end());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053089 std::vector<char> fileTemplate = {'_', '_', 'X', 'X', 'X',
90 'X', 'X', 'X', '\0'};
91 tempFileName.insert(tempFileName.end(), fileTemplate.begin(),
92 fileTemplate.end());
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +053093
94 int fd = mkstemp(tempFileName.data());
95 if (fd == -1)
96 {
97 return raiseException(errno, "Error creating temp shadow file");
98 }
99
100 std::string strTempFileName(tempFileName.data());
101 // Open the temp shadow file for writing from provided fd
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530102 // By "true", remove it at exit if still there.
103 // This is needed to cleanup the temp file at exception
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530104 phosphor::user::File temp(fd, strTempFileName, "w", true);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530105 if ((temp)() == NULL)
106 {
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530107 close(fd);
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530108 return raiseException(errno, "Error opening temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530109 }
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530110 fd = -1; // don't use fd anymore, as the File object owns it
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530111
112 // Change the permission of this new temp file
113 // to be same as shadow so that it's secure
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530114 struct stat st
115 {
116 };
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530117 auto r = fstat(fileno((shadow)()), &st);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530118 if (r < 0)
119 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530120 return raiseException(errno, "Error reading shadow file mode");
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530121 }
122
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530123 r = fchmod(fileno((temp)()), st.st_mode);
124 if (r < 0)
125 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530126 return raiseException(errno, "Error setting temp file mode");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530127 }
128
129 // Read shadow file and process
130 while (true)
131 {
132 auto r = fgetspent_r((shadow)(), &shdp, buffer.data(),
133 buffer.max_size(), &pshdp);
134 if (r)
135 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530136 if (errno == EACCES || errno == ERANGE)
137 {
138 return raiseException(errno, "Error reading shadow file");
139 }
140 else
141 {
142 // Seem to have run over all
143 break;
144 }
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530145 }
146
147 // Hash of password if the user matches
148 std::string hash{};
149
150 // Matched user
151 if (user == shdp.sp_namp)
152 {
153 // Update with new hashed password
154 hash = hashPassword(shdp.sp_pwdp, password, salt);
155 shdp.sp_pwdp = const_cast<char*>(hash.c_str());
156 }
157
158 // Apply
159 r = putspent(&shdp, (temp)());
160 if (r < 0)
161 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530162 return raiseException(errno, "Error updating temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530163 }
164 } // All entries
165
166 // Done
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530167 endspent();
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530168 // flush contents to file first, before renaming to avoid
169 // corruption during power failure
170 fflush((temp)());
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530171
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530172 // Everything must be fine at this point
Richard Marian Thomaiyar1f5a0022017-12-16 15:11:47 +0530173 fs::rename(strTempFileName, shadowFile);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530174 return;
175}
176
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530177void User::raiseException(int errNo, const std::string& errMsg)
178{
179 using namespace std::string_literals;
180 if (errNo == EACCES)
181 {
182 auto message = "Access denied "s + errMsg;
183 log<level::ERR>(message.c_str());
184 elog<InsufficientPermission>();
185 }
186 else
187 {
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530188 log<level::ERR>(errMsg.c_str(), entry("USER=%s", user.c_str()),
189 entry("ERRNO=%d", errNo));
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530190 elog<InternalFailure>();
191 }
192}
193
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530194std::string User::hashPassword(char* spPwdp, const std::string& password,
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530195 const std::string& salt)
196{
197 // Parse and get crypt algo
198 auto cryptAlgo = getCryptField(spPwdp);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530199 if (cryptAlgo.empty())
200 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530201 log<level::ERR>("Error finding crypt algo",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530202 entry("USER=%s", user.c_str()));
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530203 elog<InternalFailure>();
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530204 }
205
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530206 // Update shadow password pointer with hash
207 auto saltString = getSaltString(cryptAlgo, salt);
208 return generateHash(password, saltString);
209}
210
211// Returns a random string in set [A-Za-z0-9./]
212// of size numChars
213const std::string User::randomString(int length)
214{
215 // Populated random string
216 std::string random{};
217
218 // Needed per crypt(3)
219 std::string set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
220 "lmnopqrstuvwxyz0123456789./";
221
222 // Will be used to obtain a seed for the random number engine
223 std::random_device rd;
224
225 // Standard mersenne_twister_engine seeded with rd()
226 std::mt19937 gen(rd());
227
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530228 std::uniform_int_distribution<> dis(0, set.size() - 1);
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530229 for (int count = 0; count < length; count++)
230 {
231 // Use dis to transform the random unsigned int generated by
232 // gen into a int in [1, SALT_LENGTH]
233 random.push_back(set.at(dis(gen)));
234 }
235 return random;
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530236}
237
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530238// Extract crypto algorithm field
239CryptAlgo User::getCryptField(char* spPwdp)
240{
241 char* savePtr{};
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530242 if (std::string{spPwdp}.front() != '$')
243 {
244 return DEFAULT_CRYPT_ALGO;
245 }
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530246 return strtok_r(spPwdp, "$", &savePtr);
247}
248
249// Returns specific format of salt string
250std::string User::getSaltString(const std::string& crypt,
251 const std::string& salt)
252{
253 return '$' + crypt + '$' + salt + '$';
254}
255
256// Given a password and salt, generates hash
257std::string User::generateHash(const std::string& password,
258 const std::string& salt)
259{
260 return crypt(password.c_str(), salt.c_str());
261}
262
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530263} // namespace user
264} // namespace phosphor