blob: 24cd9888a6b475d4136dcafd0b901628e990d2dc [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 Subbannad20225f2017-09-06 11:36:04 +053024#include "user.hpp"
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053025#include "file.hpp"
26#include "shadowlock.hpp"
27#include "config.h"
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +053028namespace phosphor
29{
30namespace user
31{
32
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053033constexpr auto SHADOW_FILE = "/etc/shadow";
34
35// See crypt(3)
36constexpr int SALT_LENGTH = 16;
37
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053038// Sets or updates the password
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +053039void User::setPassword(std::string newPassword)
40{
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053041 // Gate any access to /etc/shadow
42 phosphor::user::shadow::Lock lock();
43
44 // rewind to the start of shadow entry
45 setspent();
46
47 // Generate a random string from set [A-Za-z0-9./]
48 std::string salt{};
49 salt.resize(SALT_LENGTH);
50 salt = randomString(SALT_LENGTH);
51
52 auto tempShadowFile = std::string("/etc/__") +
53 randomString(SALT_LENGTH) +
54 std::string("__");
55
56 // Apply the change. Updates could be directly made to shadow
57 // but then any update must be contained within the boundary
58 // of that user, else it would run into next entry and thus
59 // corrupting it. Classic example is when a password is set on
60 // a user ID that does not have a prior password
61 applyPassword(SHADOW_FILE, tempShadowFile,
62 newPassword, salt);
63 return;
64}
65
66void User::applyPassword(const std::string& shadowFile,
67 const std::string& tempFile,
68 const std::string& password,
69 const std::string& salt)
70{
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +053071 // Needed by getspnam_r
72 struct spwd shdp;
73 struct spwd* pshdp;
74
75 // This should be fine even if SHA512 is used.
76 std::array<char,1024> buffer{};
77
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +053078 // Open the shadow file for reading
79 phosphor::user::File shadow(shadowFile, "r");
80 if ((shadow)() == NULL)
81 {
82 throw std::runtime_error("Error opening shadow file");
83 // TODO: Throw error
84 }
85
86 // Open the temp shadow file for writing
87 // By "true", remove it at exit if still there.
88 // This is needed to cleanup the temp file at exception
89 phosphor::user::File temp(tempFile, "w", true);
90 if ((temp)() == NULL)
91 {
92 throw std::runtime_error("Error opening temp shadow file");
93 // TODO: Throw error
94 }
95
96 // Change the permission of this new temp file
97 // to be same as shadow so that it's secure
98 struct stat st{};
99 auto r = fstat(fileno((shadow)()), &st);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530100 if (r < 0)
101 {
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530102 throw std::runtime_error("Error reading permission of shadow");
103 // TODO: Throw error
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530104 }
105
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530106 r = fchmod(fileno((temp)()), st.st_mode);
107 if (r < 0)
108 {
109 throw std::runtime_error("Error setting permission on temp file");
110 // TODO: Throw error
111 }
112
113 // Read shadow file and process
114 while (true)
115 {
116 auto r = fgetspent_r((shadow)(), &shdp, buffer.data(),
117 buffer.max_size(), &pshdp);
118 if (r)
119 {
120 // Done with all entries
121 break;
122 }
123
124 // Hash of password if the user matches
125 std::string hash{};
126
127 // Matched user
128 if (user == shdp.sp_namp)
129 {
130 // Update with new hashed password
131 hash = hashPassword(shdp.sp_pwdp, password, salt);
132 shdp.sp_pwdp = const_cast<char*>(hash.c_str());
133 }
134
135 // Apply
136 r = putspent(&shdp, (temp)());
137 if (r < 0)
138 {
139 throw std::runtime_error("Error updating temp shadow entry");
140 // TODO: Throw exception
141 }
142 } // All entries
143
144 // Done
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530145 endspent();
146
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530147 // Everything must be fine at this point
148 fs::rename(tempFile, shadowFile);
149 return;
150}
151
152std::string User::hashPassword(char* spPwdp,
153 const std::string& password,
154 const std::string& salt)
155{
156 // Parse and get crypt algo
157 auto cryptAlgo = getCryptField(spPwdp);
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530158 if (cryptAlgo.empty())
159 {
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530160 throw std::runtime_error("Error finding crypt algo");
161 // TODO: Throw error
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530162 }
163
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530164 // Update shadow password pointer with hash
165 auto saltString = getSaltString(cryptAlgo, salt);
166 return generateHash(password, saltString);
167}
168
169// Returns a random string in set [A-Za-z0-9./]
170// of size numChars
171const std::string User::randomString(int length)
172{
173 // Populated random string
174 std::string random{};
175
176 // Needed per crypt(3)
177 std::string set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
178 "lmnopqrstuvwxyz0123456789./";
179
180 // Will be used to obtain a seed for the random number engine
181 std::random_device rd;
182
183 // Standard mersenne_twister_engine seeded with rd()
184 std::mt19937 gen(rd());
185
186 std::uniform_int_distribution<> dis(0, set.size()-1);
187 for (int count = 0; count < length; count++)
188 {
189 // Use dis to transform the random unsigned int generated by
190 // gen into a int in [1, SALT_LENGTH]
191 random.push_back(set.at(dis(gen)));
192 }
193 return random;
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530194}
195
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530196// Extract crypto algorithm field
197CryptAlgo User::getCryptField(char* spPwdp)
198{
199 char* savePtr{};
Vishwanatha Subbanna070a3e42017-09-06 11:40:45 +0530200 if (std::string{spPwdp}.front() != '$')
201 {
202 return DEFAULT_CRYPT_ALGO;
203 }
Vishwanatha Subbannabdb298f2017-09-06 11:39:22 +0530204 return strtok_r(spPwdp, "$", &savePtr);
205}
206
207// Returns specific format of salt string
208std::string User::getSaltString(const std::string& crypt,
209 const std::string& salt)
210{
211 return '$' + crypt + '$' + salt + '$';
212}
213
214// Given a password and salt, generates hash
215std::string User::generateHash(const std::string& password,
216 const std::string& salt)
217{
218 return crypt(password.c_str(), salt.c_str());
219}
220
Vishwanatha Subbannad20225f2017-09-06 11:36:04 +0530221} // namespace user
222} // namespace phosphor