blob: bd63aac6bc23499279c94544edd30097d6fdb755 [file] [log] [blame]
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301/*
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
Patrick Williams9638afb2021-02-22 17:16:24 -060017#include "config.h"
18
19#include "user_mgr.hpp"
20
21#include "file.hpp"
22#include "shadowlock.hpp"
23#include "users.hpp"
24
25#include <grp.h>
26#include <pwd.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053027#include <shadow.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053028#include <sys/types.h>
29#include <sys/wait.h>
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -060030#include <time.h>
Patrick Williams9638afb2021-02-22 17:16:24 -060031#include <unistd.h>
32
33#include <boost/algorithm/string/split.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060034#include <phosphor-logging/elog-errors.hpp>
35#include <phosphor-logging/elog.hpp>
36#include <phosphor-logging/log.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053037#include <xyz/openbmc_project/Common/error.hpp>
38#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060039
40#include <algorithm>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080041#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060042#include <fstream>
43#include <numeric>
44#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000045#include <span>
46#include <string>
47#include <string_view>
48#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053049
50namespace phosphor
51{
52namespace user
53{
54
Patrick Williams9638afb2021-02-22 17:16:24 -060055static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053056static constexpr size_t ipmiMaxUserNameLen = 16;
57static constexpr size_t systemMaxUserNameLen = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060058static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053059static constexpr int success = 0;
60static constexpr int failure = -1;
61
62// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060063static constexpr const char* pamTally2 = "pam_tally2.so";
64static constexpr const char* pamCrackLib = "pam_cracklib.so";
65static constexpr const char* pamPWHistory = "pam_pwhistory.so";
66static constexpr const char* minPasswdLenProp = "minlen";
67static constexpr const char* remOldPasswdCount = "remember";
68static constexpr const char* maxFailedAttempt = "deny";
69static constexpr const char* unlockTimeout = "unlock_time";
Nan Zhoue48085d2022-10-25 00:07:04 +000070static constexpr const char* defaultPamPasswdConfigFile =
71 "/etc/pam.d/common-password";
72static constexpr const char* defaultPamAuthConfigFile =
73 "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053074
Ratan Guptaaeaf9412019-02-11 04:41:52 -060075// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060076static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060077 "/xyz/openbmc_project/user/ldap";
78
79// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060080static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060081 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060082static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060083 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060084static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060085 "xyz.openbmc_project.ObjectMapper";
86
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053087using namespace phosphor::logging;
88using InsufficientPermission =
89 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
90using InternalFailure =
91 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
92using InvalidArgument =
93 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
94using UserNameExists =
95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
96using UserNameDoesNotExist =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
98using UserNameGroupFail =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530100using NoResource =
101 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
102
103using Argument = xyz::openbmc_project::Common::InvalidArgument;
104
Nan Zhoue47c09d2022-10-25 00:06:41 +0000105std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530106{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000107 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530108 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000109 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530110 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000111 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
112 [](std::string&& val, std::string_view element) {
113 val += ',';
114 val += element;
115 return val;
116 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530117}
118
Nan Zhou332fb9d2022-10-25 00:07:03 +0000119bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530120{
121 std::string::size_type delStrPos = csvStr.find(delStr);
122 if (delStrPos != std::string::npos)
123 {
124 // need to also delete the comma char
125 if (delStrPos == 0)
126 {
127 csvStr.erase(delStrPos, delStr.size() + 1);
128 }
129 else
130 {
131 csvStr.erase(delStrPos - 1, delStr.size() + 1);
132 }
133 return true;
134 }
135 return false;
136}
137
Patrick Williams9638afb2021-02-22 17:16:24 -0600138bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530139{
140 if (userName.empty())
141 {
142 log<level::ERR>("User name is empty");
143 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
144 Argument::ARGUMENT_VALUE("Null"));
145 }
146 if (usersList.find(userName) == usersList.end())
147 {
148 return false;
149 }
150 return true;
151}
152
Patrick Williams9638afb2021-02-22 17:16:24 -0600153void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530154{
Nan Zhou8a11d992022-10-25 00:07:06 +0000155 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530156 {
157 log<level::ERR>("User does not exist",
158 entry("USER_NAME=%s", userName.c_str()));
159 elog<UserNameDoesNotExist>();
160 }
161}
162
Patrick Williams9638afb2021-02-22 17:16:24 -0600163void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530164{
Nan Zhou8a11d992022-10-25 00:07:06 +0000165 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530166 {
167 log<level::ERR>("User already exists",
168 entry("USER_NAME=%s", userName.c_str()));
169 elog<UserNameExists>();
170 }
171}
172
173void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600174 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530175{
176 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
177 groupNames.end())
178 {
179 if (userName.length() > ipmiMaxUserNameLen)
180 {
181 log<level::ERR>("IPMI user name length limitation",
182 entry("SIZE=%d", userName.length()));
183 elog<UserNameGroupFail>(
184 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
185 "IPMI length"));
186 }
187 }
188 if (userName.length() > systemMaxUserNameLen)
189 {
190 log<level::ERR>("User name length limitation",
191 entry("SIZE=%d", userName.length()));
192 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
193 Argument::ARGUMENT_VALUE("Invalid length"));
194 }
195 if (!std::regex_match(userName.c_str(),
196 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
197 {
198 log<level::ERR>("Invalid user name",
199 entry("USER_NAME=%s", userName.c_str()));
200 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
201 Argument::ARGUMENT_VALUE("Invalid data"));
202 }
203}
204
205void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600206 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530207{
208 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
209 groupNames.end())
210 {
211 if (getIpmiUsersCount() >= ipmiMaxUsers)
212 {
213 log<level::ERR>("IPMI user limit reached");
214 elog<NoResource>(
215 xyz::openbmc_project::User::Common::NoResource::REASON(
216 "ipmi user count reached"));
217 }
218 }
219 else
220 {
221 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
222 (maxSystemUsers - ipmiMaxUsers))
223 {
224 log<level::ERR>("Non-ipmi User limit reached");
225 elog<NoResource>(
226 xyz::openbmc_project::User::Common::NoResource::REASON(
227 "Non-ipmi user count reached"));
228 }
229 }
230 return;
231}
232
Patrick Williams9638afb2021-02-22 17:16:24 -0600233void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530234{
235 if (!priv.empty() &&
236 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
237 {
238 log<level::ERR>("Invalid privilege");
239 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
240 Argument::ARGUMENT_VALUE(priv.c_str()));
241 }
242}
243
Patrick Williams9638afb2021-02-22 17:16:24 -0600244void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530245{
Patrick Williams9638afb2021-02-22 17:16:24 -0600246 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530247 {
248 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
249 groupsMgr.end())
250 {
251 log<level::ERR>("Invalid Group Name listed");
252 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
253 Argument::ARGUMENT_VALUE(group.c_str()));
254 }
255 }
256}
257
258void UserMgr::createUser(std::string userName,
259 std::vector<std::string> groupNames, std::string priv,
260 bool enabled)
261{
262 throwForInvalidPrivilege(priv);
263 throwForInvalidGroups(groupNames);
264 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500265 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530266 throwForUserExists(userName);
267 throwForUserNameConstraints(userName, groupNames);
268 throwForMaxGrpUserCount(groupNames);
269
270 std::string groups = getCSVFromVector(groupNames);
271 bool sshRequested = removeStringFromCSV(groups, grpSsh);
272
273 // treat privilege as a group - This is to avoid using different file to
274 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530275 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530276 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530277 if (groups.size() != 0)
278 {
279 groups += ",";
280 }
281 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530282 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530283 try
284 {
Nan Zhou49c81362022-10-25 00:07:08 +0000285 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530286 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600287 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 {
289 log<level::ERR>("Unable to create new user");
290 elog<InternalFailure>();
291 }
292
293 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530294 sdbusplus::message::object_path tempObjPath(usersObjPath);
295 tempObjPath /= userName;
296 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530297 std::sort(groupNames.begin(), groupNames.end());
298 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000299 userName, std::make_unique<phosphor::user::Users>(
300 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530301
302 log<level::INFO>("User created successfully",
303 entry("USER_NAME=%s", userName.c_str()));
304 return;
305}
306
307void UserMgr::deleteUser(std::string userName)
308{
309 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500310 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530311 throwForUserDoesNotExist(userName);
312 try
313 {
Nan Zhou49c81362022-10-25 00:07:08 +0000314 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530315 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600316 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530317 {
318 log<level::ERR>("User delete failed",
319 entry("USER_NAME=%s", userName.c_str()));
320 elog<InternalFailure>();
321 }
322
323 usersList.erase(userName);
324
325 log<level::INFO>("User deleted successfully",
326 entry("USER_NAME=%s", userName.c_str()));
327 return;
328}
329
330void UserMgr::renameUser(std::string userName, std::string newUserName)
331{
332 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500333 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530334 throwForUserDoesNotExist(userName);
335 throwForUserExists(newUserName);
336 throwForUserNameConstraints(newUserName,
337 usersList[userName].get()->userGroups());
338 try
339 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000340 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530341 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600342 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530343 {
344 log<level::ERR>("User rename failed",
345 entry("USER_NAME=%s", userName.c_str()));
346 elog<InternalFailure>();
347 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600348 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530349 std::string priv = user.get()->userPrivilege();
350 std::vector<std::string> groupNames = user.get()->userGroups();
351 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530352 sdbusplus::message::object_path tempObjPath(usersObjPath);
353 tempObjPath /= newUserName;
354 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530355 // Special group 'ipmi' needs a way to identify user renamed, in order to
356 // update encrypted password. It can't rely only on InterfacesRemoved &
357 // InterfacesAdded. So first send out userRenamed signal.
358 this->userRenamed(userName, newUserName);
359 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000360 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
361 bus, newUserObj.c_str(), groupNames,
362 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530363 return;
364}
365
Patrick Williams9638afb2021-02-22 17:16:24 -0600366void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000367 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600368 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530369{
370 throwForInvalidPrivilege(priv);
371 throwForInvalidGroups(groupNames);
372 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500373 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530374 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600375 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530376 usersList[userName].get()->userGroups();
377 std::vector<std::string> groupDiff;
378 // Note: already dealing with sorted group lists.
379 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
380 groupNames.begin(), groupNames.end(),
381 std::back_inserter(groupDiff));
382 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
383 groupDiff.end())
384 {
385 throwForUserNameConstraints(userName, groupNames);
386 throwForMaxGrpUserCount(groupNames);
387 }
388
389 std::string groups = getCSVFromVector(groupNames);
390 bool sshRequested = removeStringFromCSV(groups, grpSsh);
391
392 // treat privilege as a group - This is to avoid using different file to
393 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530394 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530396 if (groups.size() != 0)
397 {
398 groups += ",";
399 }
400 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 try
403 {
Nan Zhoufef63032022-10-25 00:07:12 +0000404 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530405 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600406 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530407 {
408 log<level::ERR>("Unable to modify user privilege / groups");
409 elog<InternalFailure>();
410 }
411
412 log<level::INFO>("User groups / privilege updated successfully",
413 entry("USER_NAME=%s", userName.c_str()));
Nan Zhoufef63032022-10-25 00:07:12 +0000414 std::sort(groupNames.begin(), groupNames.end());
415 usersList[userName]->setUserGroups(groupNames);
416 usersList[userName]->setUserPrivilege(priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530417 return;
418}
419
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530420uint8_t UserMgr::minPasswordLength(uint8_t value)
421{
422 if (value == AccountPolicyIface::minPasswordLength())
423 {
424 return value;
425 }
426 if (value < minPasswdLength)
427 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000428 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
429 std::to_string(minPasswdLength))
430 .c_str(),
431 entry("SIZE=%d", value));
432 elog<InvalidArgument>(
433 Argument::ARGUMENT_NAME("minPasswordLength"),
434 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530435 }
436 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
437 std::to_string(value)) != success)
438 {
439 log<level::ERR>("Unable to set minPasswordLength");
440 elog<InternalFailure>();
441 }
442 return AccountPolicyIface::minPasswordLength(value);
443}
444
445uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
446{
447 if (value == AccountPolicyIface::rememberOldPasswordTimes())
448 {
449 return value;
450 }
451 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
452 std::to_string(value)) != success)
453 {
454 log<level::ERR>("Unable to set rememberOldPasswordTimes");
455 elog<InternalFailure>();
456 }
457 return AccountPolicyIface::rememberOldPasswordTimes(value);
458}
459
460uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
461{
462 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
463 {
464 return value;
465 }
466 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
467 std::to_string(value)) != success)
468 {
469 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
470 elog<InternalFailure>();
471 }
472 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
473}
474
475uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
476{
477 if (value == AccountPolicyIface::accountUnlockTimeout())
478 {
479 return value;
480 }
481 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
482 success)
483 {
484 log<level::ERR>("Unable to set accountUnlockTimeout");
485 elog<InternalFailure>();
486 }
487 return AccountPolicyIface::accountUnlockTimeout(value);
488}
489
Patrick Williams9638afb2021-02-22 17:16:24 -0600490int UserMgr::getPamModuleArgValue(const std::string& moduleName,
491 const std::string& argName,
492 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530493{
494 std::string fileName;
495 if (moduleName == pamTally2)
496 {
497 fileName = pamAuthConfigFile;
498 }
499 else
500 {
501 fileName = pamPasswdConfigFile;
502 }
503 std::ifstream fileToRead(fileName, std::ios::in);
504 if (!fileToRead.is_open())
505 {
506 log<level::ERR>("Failed to open pam configuration file",
507 entry("FILE_NAME=%s", fileName.c_str()));
508 return failure;
509 }
510 std::string line;
511 auto argSearch = argName + "=";
512 size_t startPos = 0;
513 size_t endPos = 0;
514 while (getline(fileToRead, line))
515 {
516 // skip comments section starting with #
517 if ((startPos = line.find('#')) != std::string::npos)
518 {
519 if (startPos == 0)
520 {
521 continue;
522 }
523 // skip comments after meaningful section and process those
524 line = line.substr(0, startPos);
525 }
526 if (line.find(moduleName) != std::string::npos)
527 {
528 if ((startPos = line.find(argSearch)) != std::string::npos)
529 {
530 if ((endPos = line.find(' ', startPos)) == std::string::npos)
531 {
532 endPos = line.size();
533 }
534 startPos += argSearch.size();
535 argValue = line.substr(startPos, endPos - startPos);
536 return success;
537 }
538 }
539 }
540 return failure;
541}
542
Patrick Williams9638afb2021-02-22 17:16:24 -0600543int UserMgr::setPamModuleArgValue(const std::string& moduleName,
544 const std::string& argName,
545 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530546{
547 std::string fileName;
548 if (moduleName == pamTally2)
549 {
550 fileName = pamAuthConfigFile;
551 }
552 else
553 {
554 fileName = pamPasswdConfigFile;
555 }
556 std::string tmpFileName = fileName + "_tmp";
557 std::ifstream fileToRead(fileName, std::ios::in);
558 std::ofstream fileToWrite(tmpFileName, std::ios::out);
559 if (!fileToRead.is_open() || !fileToWrite.is_open())
560 {
561 log<level::ERR>("Failed to open pam configuration /tmp file",
562 entry("FILE_NAME=%s", fileName.c_str()));
563 return failure;
564 }
565 std::string line;
566 auto argSearch = argName + "=";
567 size_t startPos = 0;
568 size_t endPos = 0;
569 bool found = false;
570 while (getline(fileToRead, line))
571 {
572 // skip comments section starting with #
573 if ((startPos = line.find('#')) != std::string::npos)
574 {
575 if (startPos == 0)
576 {
577 fileToWrite << line << std::endl;
578 continue;
579 }
580 // skip comments after meaningful section and process those
581 line = line.substr(0, startPos);
582 }
583 if (line.find(moduleName) != std::string::npos)
584 {
585 if ((startPos = line.find(argSearch)) != std::string::npos)
586 {
587 if ((endPos = line.find(' ', startPos)) == std::string::npos)
588 {
589 endPos = line.size();
590 }
591 startPos += argSearch.size();
592 fileToWrite << line.substr(0, startPos) << argValue
593 << line.substr(endPos, line.size() - endPos)
594 << std::endl;
595 found = true;
596 continue;
597 }
598 }
599 fileToWrite << line << std::endl;
600 }
601 fileToWrite.close();
602 fileToRead.close();
603 if (found)
604 {
605 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
606 {
607 return success;
608 }
609 }
610 return failure;
611}
612
Patrick Williams9638afb2021-02-22 17:16:24 -0600613void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530614{
615 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500616 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530617 throwForUserDoesNotExist(userName);
618 try
619 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000620 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530621 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600622 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530623 {
624 log<level::ERR>("Unable to modify user enabled state");
625 elog<InternalFailure>();
626 }
627
628 log<level::INFO>("User enabled/disabled state updated successfully",
629 entry("USER_NAME=%s", userName.c_str()),
630 entry("ENABLED=%d", enabled));
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000631 usersList[userName]->setUserEnabled(enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530632 return;
633}
634
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530635/**
636 * pam_tally2 app will provide the user failure count and failure status
637 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800638 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530639 * [4] - failure app
640 **/
641
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530642static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800643static constexpr size_t t2FailDateIdx = 2;
644static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530645static constexpr size_t t2OutputIndex = 1;
646
Patrick Williams9638afb2021-02-22 17:16:24 -0600647bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530648{
649 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500650 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800651 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
652 {
653 return false;
654 }
655
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530656 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800657 try
658 {
Nan Zhoua2953032022-11-11 21:50:32 +0000659 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800660 }
661 catch (const InternalFailure& e)
662 {
663 log<level::ERR>("Unable to read login failure counter");
664 elog<InternalFailure>();
665 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530666
667 std::vector<std::string> splitWords;
668 boost::algorithm::split(splitWords, output[t2OutputIndex],
669 boost::algorithm::is_any_of("\t "),
670 boost::token_compress_on);
671
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800672 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530673 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530674 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530675 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800676 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530677 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530678 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530679 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800680 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600682 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530683 {
684 log<level::ERR>("Exception for userLockedForFailedAttempt",
685 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800686 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530687 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800688
689 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
690 {
691 return false;
692 }
693
694 // When failedAttempts is not 0, Latest failure date/time should be
695 // available
696 if (splitWords.size() < 4)
697 {
698 log<level::ERR>("Unable to read latest failure date/time");
699 elog<InternalFailure>();
700 }
701
702 const std::string failDateTime =
703 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
704
705 // NOTE: Cannot use std::get_time() here as the implementation of %y in
706 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
707 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
708 std::tm tmStruct = {};
709 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
710 {
711 log<level::ERR>("Failed to parse latest failure date/time");
712 elog<InternalFailure>();
713 }
714
715 time_t failTimestamp = std::mktime(&tmStruct);
716 if (failTimestamp +
717 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
718 std::time(NULL))
719 {
720 return false;
721 }
722
723 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530724}
725
Patrick Williams9638afb2021-02-22 17:16:24 -0600726bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
727 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530728{
729 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500730 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530731 if (value == true)
732 {
733 return userLockedForFailedAttempt(userName);
734 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530735
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800736 try
737 {
738 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
739 }
740 catch (const InternalFailure& e)
741 {
742 log<level::ERR>("Unable to reset login failure counter");
743 elog<InternalFailure>();
744 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530745
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530746 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530747}
748
Patrick Williams9638afb2021-02-22 17:16:24 -0600749bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600750{
751 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500752 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600753
754 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600755 {};
756 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600757 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
758 if (buflen < -1)
759 {
760 // Use a default size if there is no hard limit suggested by sysconf()
761 buflen = 1024;
762 }
763 std::vector<char> buffer(buflen);
764 auto status =
765 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
766 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
767 // If no matching password record was found, these functions return 0
768 // and store NULL in *spwdPtr
769 if ((status == 0) && (&spwd == spwdPtr))
770 {
771 // Determine password validity per "chage" docs, where:
772 // spwd.sp_lstchg == 0 means password is expired, and
773 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000774 constexpr long secondsPerDay = 60 * 60 * 24;
775 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600776 if ((spwd.sp_lstchg == 0) ||
777 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
778 {
779 return true;
780 }
781 }
782 else
783 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000784 // User entry is missing in /etc/shadow, indicating no SHA password.
785 // Treat this as new user without password entry in /etc/shadow
786 // TODO: Add property to indicate user password was not set yet
787 // https://github.com/openbmc/phosphor-user-manager/issues/8
788 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600789 }
790
791 return false;
792}
793
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530794UserSSHLists UserMgr::getUserAndSshGrpList()
795{
796 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500797 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530798
799 std::vector<std::string> userList;
800 std::vector<std::string> sshUsersList;
801 struct passwd pw, *pwp = nullptr;
802 std::array<char, 1024> buffer{};
803
804 phosphor::user::File passwd(passwdFileName, "r");
805 if ((passwd)() == NULL)
806 {
807 log<level::ERR>("Error opening the passwd file");
808 elog<InternalFailure>();
809 }
810
811 while (true)
812 {
813 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
814 &pwp);
815 if ((r != 0) || (pwp == NULL))
816 {
817 // Any error, break the loop.
818 break;
819 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530820#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530821 // Add all users whose UID >= 1000 and < 65534
822 // and special UID 0.
823 if ((pwp->pw_uid == 0) ||
824 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530825#else
826 // Add all users whose UID >=1000 and < 65534
827 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
828#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530829 {
830 std::string userName(pwp->pw_name);
831 userList.emplace_back(userName);
832
833 // ssh doesn't have separate group. Check login shell entry to
834 // get all users list which are member of ssh group.
835 std::string loginShell(pwp->pw_shell);
836 if (loginShell == "/bin/sh")
837 {
838 sshUsersList.emplace_back(userName);
839 }
840 }
841 }
842 endpwent();
843 return std::make_pair(std::move(userList), std::move(sshUsersList));
844}
845
846size_t UserMgr::getIpmiUsersCount()
847{
848 std::vector<std::string> userList = getUsersInGroup("ipmi");
849 return userList.size();
850}
851
Nan Zhou49c81362022-10-25 00:07:08 +0000852size_t UserMgr::getNonIpmiUsersCount()
853{
854 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
855 return usersList.size() - ipmiUsers.size();
856}
857
Patrick Williams9638afb2021-02-22 17:16:24 -0600858bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530859{
860 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500861 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530862 std::array<char, 4096> buffer{};
863 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600864 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530865 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
866 buffer.max_size(), &resultPtr);
867 if (!status && (&spwd == resultPtr))
868 {
869 if (resultPtr->sp_expire >= 0)
870 {
871 return false; // user locked out
872 }
873 return true;
874 }
875 return false; // assume user is disabled for any error.
876}
877
Patrick Williams9638afb2021-02-22 17:16:24 -0600878std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530879{
880 std::vector<std::string> usersInGroup;
881 // Should be more than enough to get the pwd structure.
882 std::array<char, 4096> buffer{};
883 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600884 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530885
886 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
887 buffer.max_size(), &resultPtr);
888
889 if (!status && (&grp == resultPtr))
890 {
891 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
892 {
893 usersInGroup.emplace_back(*(grp.gr_mem));
894 }
895 }
896 else
897 {
898 log<level::ERR>("Group not found",
899 entry("GROUP=%s", groupName.c_str()));
900 // Don't throw error, just return empty userList - fallback
901 }
902 return usersInGroup;
903}
904
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600905DbusUserObj UserMgr::getPrivilegeMapperObject(void)
906{
907 DbusUserObj objects;
908 try
909 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500910 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
911 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600912
913 auto ldapMgmtService =
914 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600915 auto method = bus.new_method_call(
916 ldapMgmtService.c_str(), ldapMgrObjBasePath,
917 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
918
919 auto reply = bus.call(method);
920 reply.read(objects);
921 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600922 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600923 {
924 log<level::ERR>("Unable to get the User Service",
925 entry("WHAT=%s", e.what()));
926 throw;
927 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500928 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600929 {
930 log<level::ERR>(
931 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
932 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
933 throw;
934 }
935 return objects;
936}
937
Patrick Williams9638afb2021-02-22 17:16:24 -0600938std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600939{
940 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
941 objMapperInterface, "GetObject");
942
943 mapperCall.append(std::move(path));
944 mapperCall.append(std::vector<std::string>({std::move(intf)}));
945
946 auto mapperResponseMsg = bus.call(mapperCall);
947
948 if (mapperResponseMsg.is_method_error())
949 {
950 log<level::ERR>("Error in mapper call");
951 elog<InternalFailure>();
952 }
953
954 std::map<std::string, std::vector<std::string>> mapperResponse;
955 mapperResponseMsg.read(mapperResponse);
956
957 if (mapperResponse.begin() == mapperResponse.end())
958 {
959 log<level::ERR>("Invalid response from mapper");
960 elog<InternalFailure>();
961 }
962
963 return mapperResponse.begin()->first;
964}
965
Alexander Filippov75626582022-02-09 18:42:37 +0300966gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
967{
968 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
969 if (buflen <= 0)
970 {
971 // Use a default size if there is no hard limit suggested by sysconf()
972 buflen = 1024;
973 }
974
975 struct passwd pwd;
976 struct passwd* pwdPtr = nullptr;
977 std::vector<char> buffer(buflen);
978
979 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
980 buffer.size(), &pwdPtr);
981 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
982 // If no matching password record was found, these functions return 0
983 // and store NULL in *pwdPtr
984 if (!status && (&pwd == pwdPtr))
985 {
986 return pwd.pw_gid;
987 }
988
989 log<level::ERR>("User noes not exist",
990 entry("USER_NAME=%s", userName.c_str()));
991 elog<UserNameDoesNotExist>();
992}
993
994bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
995 const std::string& groupName) const
996{
997 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
998 if (buflen <= 0)
999 {
1000 // Use a default size if there is no hard limit suggested by sysconf()
1001 buflen = 1024;
1002 }
1003
1004 struct group grp;
1005 struct group* grpPtr = nullptr;
1006 std::vector<char> buffer(buflen);
1007
1008 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1009 buffer.size(), &grpPtr);
1010
1011 // Groups with a lot of members may require a buffer of bigger size than
1012 // suggested by _SC_GETGR_R_SIZE_MAX.
1013 // 32K should be enough for about 2K members.
1014 constexpr auto maxBufferLength = 32 * 1024;
1015 while (status == ERANGE && buflen < maxBufferLength)
1016 {
1017 buflen *= 2;
1018 buffer.resize(buflen);
1019
1020 log<level::DEBUG>("Increase buffer for getgrnam_r()",
1021 entry("BUFFER_LENGTH=%zu", buflen));
1022
1023 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1024 buffer.size(), &grpPtr);
1025 }
1026
1027 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1028 // If no matching group record was found, these functions return 0
1029 // and store NULL in *grpPtr
1030 if (!status && (&grp == grpPtr))
1031 {
1032 if (primaryGid == grp.gr_gid)
1033 {
1034 return true;
1035 }
1036
1037 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1038 {
1039 if (userName == grp.gr_mem[i])
1040 {
1041 return true;
1042 }
1043 }
1044 }
1045 else if (status == ERANGE)
1046 {
1047 log<level::ERR>("Group info requires too much memory",
1048 entry("GROUP_NAME=%s", groupName.c_str()));
1049 }
1050 else
1051 {
1052 log<level::ERR>("Group does not exist",
1053 entry("GROUP_NAME=%s", groupName.c_str()));
1054 }
1055
1056 return false;
1057}
1058
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001059UserInfoMap UserMgr::getUserInfo(std::string userName)
1060{
1061 UserInfoMap userInfo;
1062 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001063 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001064 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001065 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001066 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1067 userInfo.emplace("UserGroups", user.get()->userGroups());
1068 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1069 userInfo.emplace("UserLockedForFailedAttempt",
1070 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001071 userInfo.emplace("UserPasswordExpired",
1072 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001073 userInfo.emplace("RemoteUser", false);
1074 }
1075 else
1076 {
Alexander Filippov75626582022-02-09 18:42:37 +03001077 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001078
1079 DbusUserObj objects = getPrivilegeMapperObject();
1080
Ravi Teja5fe724a2019-05-07 05:14:42 -05001081 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001082 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001083
1084 try
1085 {
Alexander Filippov75626582022-02-09 18:42:37 +03001086 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001087 {
Alexander Filippov75626582022-02-09 18:42:37 +03001088 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1089 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001090 {
Alexander Filippov75626582022-02-09 18:42:37 +03001091 auto propIt = it->second.find("Enabled");
1092 if (propIt != it->second.end() &&
1093 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001094 {
Alexander Filippov75626582022-02-09 18:42:37 +03001095 ldapConfigPath = path.str + '/';
1096 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001097 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001098 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001099 }
1100
1101 if (ldapConfigPath.empty())
1102 {
1103 return userInfo;
1104 }
1105
Alexander Filippov75626582022-02-09 18:42:37 +03001106 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001107 {
Alexander Filippov75626582022-02-09 18:42:37 +03001108 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001109 {
Alexander Filippov75626582022-02-09 18:42:37 +03001110 continue;
1111 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001112
Alexander Filippov75626582022-02-09 18:42:37 +03001113 auto it = interfaces.find(
1114 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1115 if (it != interfaces.end())
1116 {
1117 std::string privilege;
1118 std::string groupName;
1119
1120 for (const auto& [propName, propValue] : it->second)
1121 {
1122 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001123 {
Alexander Filippov75626582022-02-09 18:42:37 +03001124 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001125 }
Alexander Filippov75626582022-02-09 18:42:37 +03001126 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001127 {
Alexander Filippov75626582022-02-09 18:42:37 +03001128 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001129 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001130 }
Alexander Filippov75626582022-02-09 18:42:37 +03001131
1132 if (!groupName.empty() && !privilege.empty() &&
1133 isGroupMember(userName, primaryGid, groupName))
1134 {
1135 userPrivilege = privilege;
1136 break;
1137 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001138 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001139 if (!userPrivilege.empty())
1140 {
1141 break;
1142 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001143 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001144
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001145 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001146 {
1147 log<level::ERR>("LDAP group privilege mapping does not exist");
1148 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001149 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001150 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001151 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001152 {
1153 log<level::ERR>("Error while accessing variant",
1154 entry("WHAT=%s", e.what()));
1155 elog<InternalFailure>();
1156 }
1157 userInfo.emplace("RemoteUser", true);
1158 }
1159
1160 return userInfo;
1161}
1162
Nan Zhou4bc69812022-10-25 00:07:13 +00001163void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301164{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301165 std::string valueStr;
1166 auto value = minPasswdLength;
1167 unsigned long tmp = 0;
1168 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1169 success)
1170 {
1171 AccountPolicyIface::minPasswordLength(minPasswdLength);
1172 }
1173 else
1174 {
1175 try
1176 {
1177 tmp = std::stoul(valueStr, nullptr);
1178 if (tmp > std::numeric_limits<decltype(value)>::max())
1179 {
1180 throw std::out_of_range("Out of range");
1181 }
1182 value = static_cast<decltype(value)>(tmp);
1183 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001184 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301185 {
1186 log<level::ERR>("Exception for MinPasswordLength",
1187 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001188 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301189 }
1190 AccountPolicyIface::minPasswordLength(value);
1191 }
1192 valueStr.clear();
1193 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1194 success)
1195 {
1196 AccountPolicyIface::rememberOldPasswordTimes(0);
1197 }
1198 else
1199 {
1200 value = 0;
1201 try
1202 {
1203 tmp = std::stoul(valueStr, nullptr);
1204 if (tmp > std::numeric_limits<decltype(value)>::max())
1205 {
1206 throw std::out_of_range("Out of range");
1207 }
1208 value = static_cast<decltype(value)>(tmp);
1209 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001210 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301211 {
1212 log<level::ERR>("Exception for RememberOldPasswordTimes",
1213 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001214 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301215 }
1216 AccountPolicyIface::rememberOldPasswordTimes(value);
1217 }
1218 valueStr.clear();
1219 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1220 {
1221 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1222 }
1223 else
1224 {
1225 uint16_t value16 = 0;
1226 try
1227 {
1228 tmp = std::stoul(valueStr, nullptr);
1229 if (tmp > std::numeric_limits<decltype(value16)>::max())
1230 {
1231 throw std::out_of_range("Out of range");
1232 }
1233 value16 = static_cast<decltype(value16)>(tmp);
1234 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001235 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301236 {
1237 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1238 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001239 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301240 }
1241 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1242 }
1243 valueStr.clear();
1244 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1245 {
1246 AccountPolicyIface::accountUnlockTimeout(0);
1247 }
1248 else
1249 {
1250 uint32_t value32 = 0;
1251 try
1252 {
1253 tmp = std::stoul(valueStr, nullptr);
1254 if (tmp > std::numeric_limits<decltype(value32)>::max())
1255 {
1256 throw std::out_of_range("Out of range");
1257 }
1258 value32 = static_cast<decltype(value32)>(tmp);
1259 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001260 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301261 {
1262 log<level::ERR>("Exception for AccountUnlockTimeout",
1263 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001264 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301265 }
1266 AccountPolicyIface::accountUnlockTimeout(value32);
1267 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001268}
1269
1270void UserMgr::initUserObjects(void)
1271{
1272 // All user management lock has to be based on /etc/shadow
1273 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1274 std::vector<std::string> userNameList;
1275 std::vector<std::string> sshGrpUsersList;
1276 UserSSHLists userSSHLists = getUserAndSshGrpList();
1277 userNameList = std::move(userSSHLists.first);
1278 sshGrpUsersList = std::move(userSSHLists.second);
1279
1280 if (!userNameList.empty())
1281 {
1282 std::map<std::string, std::vector<std::string>> groupLists;
1283 for (auto& grp : groupsMgr)
1284 {
1285 if (grp == grpSsh)
1286 {
1287 groupLists.emplace(grp, sshGrpUsersList);
1288 }
1289 else
1290 {
1291 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1292 groupLists.emplace(grp, grpUsersList);
1293 }
1294 }
1295 for (auto& grp : privMgr)
1296 {
1297 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1298 groupLists.emplace(grp, grpUsersList);
1299 }
1300
1301 for (auto& user : userNameList)
1302 {
1303 std::vector<std::string> userGroups;
1304 std::string userPriv;
1305 for (const auto& grp : groupLists)
1306 {
1307 std::vector<std::string> tempGrp = grp.second;
1308 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1309 tempGrp.end())
1310 {
1311 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1312 privMgr.end())
1313 {
1314 userPriv = grp.first;
1315 }
1316 else
1317 {
1318 userGroups.emplace_back(grp.first);
1319 }
1320 }
1321 }
1322 // Add user objects to the Users path.
1323 sdbusplus::message::object_path tempObjPath(usersObjPath);
1324 tempObjPath /= user;
1325 std::string objPath(tempObjPath);
1326 std::sort(userGroups.begin(), userGroups.end());
1327 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1328 bus, objPath.c_str(), userGroups,
1329 userPriv, isUserEnabled(user), *this));
1330 }
1331 }
1332}
1333
1334UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1335 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1336 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1337 pamAuthConfigFile(defaultPamAuthConfigFile)
1338{
1339 UserMgrIface::allPrivileges(privMgr);
1340 std::sort(groupsMgr.begin(), groupsMgr.end());
1341 UserMgrIface::allGroups(groupsMgr);
1342 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301343 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301344
1345 // emit the signal
1346 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301347}
1348
Nan Zhou49c81362022-10-25 00:07:08 +00001349void UserMgr::executeUserAdd(const char* userName, const char* groups,
1350 bool sshRequested, bool enabled)
1351{
1352 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1353 // 1970-01-01, that's an implementation-defined behavior
1354 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001355 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001356 (enabled ? "" : "1970-01-01"));
1357}
1358
1359void UserMgr::executeUserDelete(const char* userName)
1360{
1361 executeCmd("/usr/sbin/userdel", userName, "-r");
1362}
1363
Nan Zhouf25443e2022-10-25 00:07:11 +00001364void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1365{
1366 std::string newHomeDir = "/home/";
1367 newHomeDir += newUserName;
1368 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1369 newHomeDir.c_str(), "-m");
1370}
1371
Nan Zhoufef63032022-10-25 00:07:12 +00001372void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1373 bool sshRequested)
1374{
1375 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001376 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001377}
1378
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001379void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1380{
1381 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1382 // 1970-01-01, that's an implementation-defined behavior
1383 executeCmd("/usr/sbin/usermod", userName, "-e",
1384 (enabled ? "" : "1970-01-01"));
1385}
1386
Nan Zhoua2953032022-11-11 21:50:32 +00001387std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1388{
1389 return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1390}
1391
Nan Zhou86040c22022-11-17 02:08:24 +00001392void UserMgr::createGroup(std::string /*groupName*/)
1393{
1394 log<level::ERR>("Not implemented yet");
1395 elog<InternalFailure>();
1396}
1397
1398void UserMgr::deleteGroup(std::string /*groupName*/)
1399{
1400 log<level::ERR>("Not implemented yet");
1401 elog<InternalFailure>();
1402}
1403
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301404} // namespace user
1405} // namespace phosphor