blob: f92fe10bdcec9ce0caf4bc7d33643ce43b00049c [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
61 auto tempShadowFile = std::string("/etc/__") +
62 randomString(SALT_LENGTH) +
63 std::string("__");
64
65 // Apply the change. Updates could be directly made to shadow
66 // but then any update must be contained within the boundary
67 // of that user, else it would run into next entry and thus
68 // corrupting it. Classic example is when a password is set on
69 // a user ID that does not have a prior password
70 applyPassword(SHADOW_FILE, tempShadowFile,
71 newPassword, salt);
72 return;
73}
74
75void User::applyPassword(const std::string& shadowFile,
76 const std::string& tempFile,
77 const std::string& password,
78 const std::string& salt)
79{
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053080 // Needed by getspnam_r
81 struct spwd shdp;
82 struct spwd* pshdp;
83
84 // This should be fine even if SHA512 is used.
85 std::array<char,1024> buffer{};
86
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053087 // Open the shadow file for reading
88 phosphor::user::File shadow(shadowFile, "r");
89 if ((shadow)() == NULL)
90 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +053091 return raiseException(errno, "Error opening shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053092 }
93
94 // Open the temp shadow file for writing
95 // By "true", remove it at exit if still there.
96 // This is needed to cleanup the temp file at exception
97 phosphor::user::File temp(tempFile, "w", true);
98 if ((temp)() == NULL)
99 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530100 return raiseException(errno, "Error opening temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530101 }
102
103 // Change the permission of this new temp file
104 // to be same as shadow so that it's secure
105 struct stat st{};
106 auto r = fstat(fileno((shadow)()), &st);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530107 if (r < 0)
108 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530109 return raiseException(errno, "Error reading shadow file mode");
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530110 }
111
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530112 r = fchmod(fileno((temp)()), st.st_mode);
113 if (r < 0)
114 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530115 return raiseException(errno, "Error setting temp file mode");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530116 }
117
118 // Read shadow file and process
119 while (true)
120 {
121 auto r = fgetspent_r((shadow)(), &shdp, buffer.data(),
122 buffer.max_size(), &pshdp);
123 if (r)
124 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530125 if (errno == EACCES || errno == ERANGE)
126 {
127 return raiseException(errno, "Error reading shadow file");
128 }
129 else
130 {
131 // Seem to have run over all
132 break;
133 }
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530134 }
135
136 // Hash of password if the user matches
137 std::string hash{};
138
139 // Matched user
140 if (user == shdp.sp_namp)
141 {
142 // Update with new hashed password
143 hash = hashPassword(shdp.sp_pwdp, password, salt);
144 shdp.sp_pwdp = const_cast<char*>(hash.c_str());
145 }
146
147 // Apply
148 r = putspent(&shdp, (temp)());
149 if (r < 0)
150 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530151 return raiseException(errno, "Error updating temp shadow file");
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530152 }
153 } // All entries
154
155 // Done
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530156 endspent();
157
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530158 // Everything must be fine at this point
159 fs::rename(tempFile, shadowFile);
160 return;
161}
162
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530163void User::raiseException(int errNo, const std::string& errMsg)
164{
165 using namespace std::string_literals;
166 if (errNo == EACCES)
167 {
168 auto message = "Access denied "s + errMsg;
169 log<level::ERR>(message.c_str());
170 elog<InsufficientPermission>();
171 }
172 else
173 {
174 log<level::ERR>(errMsg.c_str(),
175 entry("USER=%s",user.c_str()),
176 entry("ERRNO=%d", errNo));
177 elog<InternalFailure>();
178 }
179}
180
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530181std::string User::hashPassword(char* spPwdp,
182 const std::string& password,
183 const std::string& salt)
184{
185 // Parse and get crypt algo
186 auto cryptAlgo = getCryptField(spPwdp);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530187 if (cryptAlgo.empty())
188 {
Vishwanatha Subbanna36218e62017-09-06 17:19:56 +0530189 log<level::ERR>("Error finding crypt algo",
190 entry("USER=%s",user.c_str()));
191 elog<InternalFailure>();
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530192 }
193
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530194 // Update shadow password pointer with hash
195 auto saltString = getSaltString(cryptAlgo, salt);
196 return generateHash(password, saltString);
197}
198
199// Returns a random string in set [A-Za-z0-9./]
200// of size numChars
201const std::string User::randomString(int length)
202{
203 // Populated random string
204 std::string random{};
205
206 // Needed per crypt(3)
207 std::string set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
208 "lmnopqrstuvwxyz0123456789./";
209
210 // Will be used to obtain a seed for the random number engine
211 std::random_device rd;
212
213 // Standard mersenne_twister_engine seeded with rd()
214 std::mt19937 gen(rd());
215
216 std::uniform_int_distribution<> dis(0, set.size()-1);
217 for (int count = 0; count < length; count++)
218 {
219 // Use dis to transform the random unsigned int generated by
220 // gen into a int in [1, SALT_LENGTH]
221 random.push_back(set.at(dis(gen)));
222 }
223 return random;
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530224}
225
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530226// Extract crypto algorithm field
227CryptAlgo User::getCryptField(char* spPwdp)
228{
229 char* savePtr{};
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530230 if (std::string{spPwdp}.front() != '$')
231 {
232 return DEFAULT_CRYPT_ALGO;
233 }
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530234 return strtok_r(spPwdp, "$", &savePtr);
235}
236
237// Returns specific format of salt string
238std::string User::getSaltString(const std::string& crypt,
239 const std::string& salt)
240{
241 return '$' + crypt + '$' + salt + '$';
242}
243
244// Given a password and salt, generates hash
245std::string User::generateHash(const std::string& password,
246 const std::string& salt)
247{
248 return crypt(password.c_str(), salt.c_str());
249}
250
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530251} // namespace user
252} // namespace phosphor