blob: 95fb42123da208f80cec4c05ec18779c0e3ed2d4 [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>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053034#include <boost/process/child.hpp>
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053035#include <boost/process/io.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060036#include <phosphor-logging/elog-errors.hpp>
37#include <phosphor-logging/elog.hpp>
38#include <phosphor-logging/log.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053039#include <xyz/openbmc_project/Common/error.hpp>
40#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060041
42#include <algorithm>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080043#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060044#include <fstream>
45#include <numeric>
46#include <regex>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053047
48namespace phosphor
49{
50namespace user
51{
52
Patrick Williams9638afb2021-02-22 17:16:24 -060053static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053054static constexpr size_t ipmiMaxUsers = 15;
55static constexpr size_t ipmiMaxUserNameLen = 16;
56static constexpr size_t systemMaxUserNameLen = 30;
57static constexpr size_t maxSystemUsers = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060058static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053059static constexpr uint8_t minPasswdLength = 8;
60static constexpr int success = 0;
61static constexpr int failure = -1;
62
63// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060064static constexpr const char* pamTally2 = "pam_tally2.so";
65static constexpr const char* pamCrackLib = "pam_cracklib.so";
66static constexpr const char* pamPWHistory = "pam_pwhistory.so";
67static constexpr const char* minPasswdLenProp = "minlen";
68static constexpr const char* remOldPasswdCount = "remember";
69static constexpr const char* maxFailedAttempt = "deny";
70static constexpr const char* unlockTimeout = "unlock_time";
71static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
72static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053073
Ratan Guptaaeaf9412019-02-11 04:41:52 -060074// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060075static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060076 "/xyz/openbmc_project/user/ldap";
77
78// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060079static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060080 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060081static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060082 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060083static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060084 "xyz.openbmc_project.ObjectMapper";
85
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053086using namespace phosphor::logging;
87using InsufficientPermission =
88 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
89using InternalFailure =
90 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
91using InvalidArgument =
92 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
93using UserNameExists =
94 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
95using UserNameDoesNotExist =
96 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
97using UserNameGroupFail =
98 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053099using NoResource =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
101
102using Argument = xyz::openbmc_project::Common::InvalidArgument;
103
104template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600105static std::vector<std::string> executeCmd(const char* path,
106 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530107{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530108 std::vector<std::string> stdOutput;
109 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600110 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530111 boost::process::std_out > stdOutStream);
112 std::string stdOutLine;
113
114 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
115 !stdOutLine.empty())
116 {
117 stdOutput.emplace_back(stdOutLine);
118 }
119
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530120 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530121
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530122 int retCode = execProg.exit_code();
123 if (retCode)
124 {
Jonathan Domanccd28892021-10-14 16:43:33 -0700125 log<level::ERR>("Command execution failed", entry("PATH=%s", path),
126 entry("RETURN_CODE=%d", retCode));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530127 elog<InternalFailure>();
128 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530129
130 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530131}
132
133static std::string getCSVFromVector(std::vector<std::string> vec)
134{
135 switch (vec.size())
136 {
137 case 0:
138 {
139 return "";
140 }
141 break;
142
143 case 1:
144 {
145 return std::string{vec[0]};
146 }
147 break;
148
149 default:
150 {
151 return std::accumulate(
152 std::next(vec.begin()), vec.end(), vec[0],
153 [](std::string a, std::string b) { return a + ',' + b; });
154 }
155 }
156}
157
Patrick Williams9638afb2021-02-22 17:16:24 -0600158static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530159{
160 std::string::size_type delStrPos = csvStr.find(delStr);
161 if (delStrPos != std::string::npos)
162 {
163 // need to also delete the comma char
164 if (delStrPos == 0)
165 {
166 csvStr.erase(delStrPos, delStr.size() + 1);
167 }
168 else
169 {
170 csvStr.erase(delStrPos - 1, delStr.size() + 1);
171 }
172 return true;
173 }
174 return false;
175}
176
Patrick Williams9638afb2021-02-22 17:16:24 -0600177bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530178{
179 if (userName.empty())
180 {
181 log<level::ERR>("User name is empty");
182 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
183 Argument::ARGUMENT_VALUE("Null"));
184 }
185 if (usersList.find(userName) == usersList.end())
186 {
187 return false;
188 }
189 return true;
190}
191
Patrick Williams9638afb2021-02-22 17:16:24 -0600192void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530193{
194 if (isUserExist(userName) == false)
195 {
196 log<level::ERR>("User does not exist",
197 entry("USER_NAME=%s", userName.c_str()));
198 elog<UserNameDoesNotExist>();
199 }
200}
201
Patrick Williams9638afb2021-02-22 17:16:24 -0600202void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530203{
204 if (isUserExist(userName) == true)
205 {
206 log<level::ERR>("User already exists",
207 entry("USER_NAME=%s", userName.c_str()));
208 elog<UserNameExists>();
209 }
210}
211
212void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600213 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530214{
215 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
216 groupNames.end())
217 {
218 if (userName.length() > ipmiMaxUserNameLen)
219 {
220 log<level::ERR>("IPMI user name length limitation",
221 entry("SIZE=%d", userName.length()));
222 elog<UserNameGroupFail>(
223 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
224 "IPMI length"));
225 }
226 }
227 if (userName.length() > systemMaxUserNameLen)
228 {
229 log<level::ERR>("User name length limitation",
230 entry("SIZE=%d", userName.length()));
231 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
232 Argument::ARGUMENT_VALUE("Invalid length"));
233 }
234 if (!std::regex_match(userName.c_str(),
235 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
236 {
237 log<level::ERR>("Invalid user name",
238 entry("USER_NAME=%s", userName.c_str()));
239 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
240 Argument::ARGUMENT_VALUE("Invalid data"));
241 }
242}
243
244void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600245 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530246{
247 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
248 groupNames.end())
249 {
250 if (getIpmiUsersCount() >= ipmiMaxUsers)
251 {
252 log<level::ERR>("IPMI user limit reached");
253 elog<NoResource>(
254 xyz::openbmc_project::User::Common::NoResource::REASON(
255 "ipmi user count reached"));
256 }
257 }
258 else
259 {
260 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
261 (maxSystemUsers - ipmiMaxUsers))
262 {
263 log<level::ERR>("Non-ipmi User limit reached");
264 elog<NoResource>(
265 xyz::openbmc_project::User::Common::NoResource::REASON(
266 "Non-ipmi user count reached"));
267 }
268 }
269 return;
270}
271
Patrick Williams9638afb2021-02-22 17:16:24 -0600272void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530273{
274 if (!priv.empty() &&
275 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
276 {
277 log<level::ERR>("Invalid privilege");
278 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
279 Argument::ARGUMENT_VALUE(priv.c_str()));
280 }
281}
282
Patrick Williams9638afb2021-02-22 17:16:24 -0600283void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530284{
Patrick Williams9638afb2021-02-22 17:16:24 -0600285 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530286 {
287 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
288 groupsMgr.end())
289 {
290 log<level::ERR>("Invalid Group Name listed");
291 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
292 Argument::ARGUMENT_VALUE(group.c_str()));
293 }
294 }
295}
296
297void UserMgr::createUser(std::string userName,
298 std::vector<std::string> groupNames, std::string priv,
299 bool enabled)
300{
301 throwForInvalidPrivilege(priv);
302 throwForInvalidGroups(groupNames);
303 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500304 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530305 throwForUserExists(userName);
306 throwForUserNameConstraints(userName, groupNames);
307 throwForMaxGrpUserCount(groupNames);
308
309 std::string groups = getCSVFromVector(groupNames);
310 bool sshRequested = removeStringFromCSV(groups, grpSsh);
311
312 // treat privilege as a group - This is to avoid using different file to
313 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530314 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530315 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530316 if (groups.size() != 0)
317 {
318 groups += ",";
319 }
320 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530322 try
323 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800324 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
325 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530326 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530327 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530328 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800329 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530330 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600331 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530332 {
333 log<level::ERR>("Unable to create new user");
334 elog<InternalFailure>();
335 }
336
337 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530338 sdbusplus::message::object_path tempObjPath(usersObjPath);
339 tempObjPath /= userName;
340 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530341 std::sort(groupNames.begin(), groupNames.end());
342 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000343 userName, std::make_unique<phosphor::user::Users>(
344 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530345
346 log<level::INFO>("User created successfully",
347 entry("USER_NAME=%s", userName.c_str()));
348 return;
349}
350
351void UserMgr::deleteUser(std::string userName)
352{
353 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500354 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530355 throwForUserDoesNotExist(userName);
356 try
357 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530358 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530359 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600360 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530361 {
362 log<level::ERR>("User delete failed",
363 entry("USER_NAME=%s", userName.c_str()));
364 elog<InternalFailure>();
365 }
366
367 usersList.erase(userName);
368
369 log<level::INFO>("User deleted successfully",
370 entry("USER_NAME=%s", userName.c_str()));
371 return;
372}
373
374void UserMgr::renameUser(std::string userName, std::string newUserName)
375{
376 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500377 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530378 throwForUserDoesNotExist(userName);
379 throwForUserExists(newUserName);
380 throwForUserNameConstraints(newUserName,
381 usersList[userName].get()->userGroups());
382 try
383 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530384 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530385 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530386 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530387 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600388 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530389 {
390 log<level::ERR>("User rename failed",
391 entry("USER_NAME=%s", userName.c_str()));
392 elog<InternalFailure>();
393 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600394 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 std::string priv = user.get()->userPrivilege();
396 std::vector<std::string> groupNames = user.get()->userGroups();
397 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530398 sdbusplus::message::object_path tempObjPath(usersObjPath);
399 tempObjPath /= newUserName;
400 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 // Special group 'ipmi' needs a way to identify user renamed, in order to
402 // update encrypted password. It can't rely only on InterfacesRemoved &
403 // InterfacesAdded. So first send out userRenamed signal.
404 this->userRenamed(userName, newUserName);
405 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000406 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
407 bus, newUserObj.c_str(), groupNames,
408 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530409 return;
410}
411
Patrick Williams9638afb2021-02-22 17:16:24 -0600412void UserMgr::updateGroupsAndPriv(const std::string& userName,
413 const std::vector<std::string>& groupNames,
414 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530415{
416 throwForInvalidPrivilege(priv);
417 throwForInvalidGroups(groupNames);
418 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500419 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530420 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600421 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530422 usersList[userName].get()->userGroups();
423 std::vector<std::string> groupDiff;
424 // Note: already dealing with sorted group lists.
425 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
426 groupNames.begin(), groupNames.end(),
427 std::back_inserter(groupDiff));
428 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
429 groupDiff.end())
430 {
431 throwForUserNameConstraints(userName, groupNames);
432 throwForMaxGrpUserCount(groupNames);
433 }
434
435 std::string groups = getCSVFromVector(groupNames);
436 bool sshRequested = removeStringFromCSV(groups, grpSsh);
437
438 // treat privilege as a group - This is to avoid using different file to
439 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530440 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530441 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530442 if (groups.size() != 0)
443 {
444 groups += ",";
445 }
446 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530447 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530448 try
449 {
450 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
451 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
452 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600453 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530454 {
455 log<level::ERR>("Unable to modify user privilege / groups");
456 elog<InternalFailure>();
457 }
458
459 log<level::INFO>("User groups / privilege updated successfully",
460 entry("USER_NAME=%s", userName.c_str()));
461 return;
462}
463
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530464uint8_t UserMgr::minPasswordLength(uint8_t value)
465{
466 if (value == AccountPolicyIface::minPasswordLength())
467 {
468 return value;
469 }
470 if (value < minPasswdLength)
471 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000472 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
473 std::to_string(minPasswdLength))
474 .c_str(),
475 entry("SIZE=%d", value));
476 elog<InvalidArgument>(
477 Argument::ARGUMENT_NAME("minPasswordLength"),
478 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530479 }
480 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
481 std::to_string(value)) != success)
482 {
483 log<level::ERR>("Unable to set minPasswordLength");
484 elog<InternalFailure>();
485 }
486 return AccountPolicyIface::minPasswordLength(value);
487}
488
489uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
490{
491 if (value == AccountPolicyIface::rememberOldPasswordTimes())
492 {
493 return value;
494 }
495 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
496 std::to_string(value)) != success)
497 {
498 log<level::ERR>("Unable to set rememberOldPasswordTimes");
499 elog<InternalFailure>();
500 }
501 return AccountPolicyIface::rememberOldPasswordTimes(value);
502}
503
504uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
505{
506 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
507 {
508 return value;
509 }
510 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
511 std::to_string(value)) != success)
512 {
513 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
514 elog<InternalFailure>();
515 }
516 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
517}
518
519uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
520{
521 if (value == AccountPolicyIface::accountUnlockTimeout())
522 {
523 return value;
524 }
525 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
526 success)
527 {
528 log<level::ERR>("Unable to set accountUnlockTimeout");
529 elog<InternalFailure>();
530 }
531 return AccountPolicyIface::accountUnlockTimeout(value);
532}
533
Patrick Williams9638afb2021-02-22 17:16:24 -0600534int UserMgr::getPamModuleArgValue(const std::string& moduleName,
535 const std::string& argName,
536 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530537{
538 std::string fileName;
539 if (moduleName == pamTally2)
540 {
541 fileName = pamAuthConfigFile;
542 }
543 else
544 {
545 fileName = pamPasswdConfigFile;
546 }
547 std::ifstream fileToRead(fileName, std::ios::in);
548 if (!fileToRead.is_open())
549 {
550 log<level::ERR>("Failed to open pam configuration file",
551 entry("FILE_NAME=%s", fileName.c_str()));
552 return failure;
553 }
554 std::string line;
555 auto argSearch = argName + "=";
556 size_t startPos = 0;
557 size_t endPos = 0;
558 while (getline(fileToRead, line))
559 {
560 // skip comments section starting with #
561 if ((startPos = line.find('#')) != std::string::npos)
562 {
563 if (startPos == 0)
564 {
565 continue;
566 }
567 // skip comments after meaningful section and process those
568 line = line.substr(0, startPos);
569 }
570 if (line.find(moduleName) != std::string::npos)
571 {
572 if ((startPos = line.find(argSearch)) != std::string::npos)
573 {
574 if ((endPos = line.find(' ', startPos)) == std::string::npos)
575 {
576 endPos = line.size();
577 }
578 startPos += argSearch.size();
579 argValue = line.substr(startPos, endPos - startPos);
580 return success;
581 }
582 }
583 }
584 return failure;
585}
586
Patrick Williams9638afb2021-02-22 17:16:24 -0600587int UserMgr::setPamModuleArgValue(const std::string& moduleName,
588 const std::string& argName,
589 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530590{
591 std::string fileName;
592 if (moduleName == pamTally2)
593 {
594 fileName = pamAuthConfigFile;
595 }
596 else
597 {
598 fileName = pamPasswdConfigFile;
599 }
600 std::string tmpFileName = fileName + "_tmp";
601 std::ifstream fileToRead(fileName, std::ios::in);
602 std::ofstream fileToWrite(tmpFileName, std::ios::out);
603 if (!fileToRead.is_open() || !fileToWrite.is_open())
604 {
605 log<level::ERR>("Failed to open pam configuration /tmp file",
606 entry("FILE_NAME=%s", fileName.c_str()));
607 return failure;
608 }
609 std::string line;
610 auto argSearch = argName + "=";
611 size_t startPos = 0;
612 size_t endPos = 0;
613 bool found = false;
614 while (getline(fileToRead, line))
615 {
616 // skip comments section starting with #
617 if ((startPos = line.find('#')) != std::string::npos)
618 {
619 if (startPos == 0)
620 {
621 fileToWrite << line << std::endl;
622 continue;
623 }
624 // skip comments after meaningful section and process those
625 line = line.substr(0, startPos);
626 }
627 if (line.find(moduleName) != std::string::npos)
628 {
629 if ((startPos = line.find(argSearch)) != std::string::npos)
630 {
631 if ((endPos = line.find(' ', startPos)) == std::string::npos)
632 {
633 endPos = line.size();
634 }
635 startPos += argSearch.size();
636 fileToWrite << line.substr(0, startPos) << argValue
637 << line.substr(endPos, line.size() - endPos)
638 << std::endl;
639 found = true;
640 continue;
641 }
642 }
643 fileToWrite << line << std::endl;
644 }
645 fileToWrite.close();
646 fileToRead.close();
647 if (found)
648 {
649 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
650 {
651 return success;
652 }
653 }
654 return failure;
655}
656
Patrick Williams9638afb2021-02-22 17:16:24 -0600657void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530658{
659 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500660 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530661 throwForUserDoesNotExist(userName);
662 try
663 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800664 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
665 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530666 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800667 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530668 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600669 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530670 {
671 log<level::ERR>("Unable to modify user enabled state");
672 elog<InternalFailure>();
673 }
674
675 log<level::INFO>("User enabled/disabled state updated successfully",
676 entry("USER_NAME=%s", userName.c_str()),
677 entry("ENABLED=%d", enabled));
678 return;
679}
680
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681/**
682 * pam_tally2 app will provide the user failure count and failure status
683 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800684 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530685 * [4] - failure app
686 **/
687
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530688static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800689static constexpr size_t t2FailDateIdx = 2;
690static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530691static constexpr size_t t2OutputIndex = 1;
692
Patrick Williams9638afb2021-02-22 17:16:24 -0600693bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530694{
695 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500696 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800697 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
698 {
699 return false;
700 }
701
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530702 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800703 try
704 {
705 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
706 }
707 catch (const InternalFailure& e)
708 {
709 log<level::ERR>("Unable to read login failure counter");
710 elog<InternalFailure>();
711 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530712
713 std::vector<std::string> splitWords;
714 boost::algorithm::split(splitWords, output[t2OutputIndex],
715 boost::algorithm::is_any_of("\t "),
716 boost::token_compress_on);
717
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800718 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530719 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530720 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530721 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800722 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530723 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530724 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530725 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800726 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530727 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600728 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530729 {
730 log<level::ERR>("Exception for userLockedForFailedAttempt",
731 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800732 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530733 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800734
735 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
736 {
737 return false;
738 }
739
740 // When failedAttempts is not 0, Latest failure date/time should be
741 // available
742 if (splitWords.size() < 4)
743 {
744 log<level::ERR>("Unable to read latest failure date/time");
745 elog<InternalFailure>();
746 }
747
748 const std::string failDateTime =
749 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
750
751 // NOTE: Cannot use std::get_time() here as the implementation of %y in
752 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
753 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
754 std::tm tmStruct = {};
755 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
756 {
757 log<level::ERR>("Failed to parse latest failure date/time");
758 elog<InternalFailure>();
759 }
760
761 time_t failTimestamp = std::mktime(&tmStruct);
762 if (failTimestamp +
763 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
764 std::time(NULL))
765 {
766 return false;
767 }
768
769 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530770}
771
Patrick Williams9638afb2021-02-22 17:16:24 -0600772bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
773 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530774{
775 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500776 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530777 if (value == true)
778 {
779 return userLockedForFailedAttempt(userName);
780 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530781
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800782 try
783 {
784 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
785 }
786 catch (const InternalFailure& e)
787 {
788 log<level::ERR>("Unable to reset login failure counter");
789 elog<InternalFailure>();
790 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530791
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530792 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530793}
794
Patrick Williams9638afb2021-02-22 17:16:24 -0600795bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600796{
797 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500798 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600799
800 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600801 {};
802 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600803 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
804 if (buflen < -1)
805 {
806 // Use a default size if there is no hard limit suggested by sysconf()
807 buflen = 1024;
808 }
809 std::vector<char> buffer(buflen);
810 auto status =
811 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
812 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
813 // If no matching password record was found, these functions return 0
814 // and store NULL in *spwdPtr
815 if ((status == 0) && (&spwd == spwdPtr))
816 {
817 // Determine password validity per "chage" docs, where:
818 // spwd.sp_lstchg == 0 means password is expired, and
819 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000820 constexpr long secondsPerDay = 60 * 60 * 24;
821 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600822 if ((spwd.sp_lstchg == 0) ||
823 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
824 {
825 return true;
826 }
827 }
828 else
829 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000830 // User entry is missing in /etc/shadow, indicating no SHA password.
831 // Treat this as new user without password entry in /etc/shadow
832 // TODO: Add property to indicate user password was not set yet
833 // https://github.com/openbmc/phosphor-user-manager/issues/8
834 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600835 }
836
837 return false;
838}
839
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530840UserSSHLists UserMgr::getUserAndSshGrpList()
841{
842 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500843 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530844
845 std::vector<std::string> userList;
846 std::vector<std::string> sshUsersList;
847 struct passwd pw, *pwp = nullptr;
848 std::array<char, 1024> buffer{};
849
850 phosphor::user::File passwd(passwdFileName, "r");
851 if ((passwd)() == NULL)
852 {
853 log<level::ERR>("Error opening the passwd file");
854 elog<InternalFailure>();
855 }
856
857 while (true)
858 {
859 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
860 &pwp);
861 if ((r != 0) || (pwp == NULL))
862 {
863 // Any error, break the loop.
864 break;
865 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530866#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530867 // Add all users whose UID >= 1000 and < 65534
868 // and special UID 0.
869 if ((pwp->pw_uid == 0) ||
870 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530871#else
872 // Add all users whose UID >=1000 and < 65534
873 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
874#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530875 {
876 std::string userName(pwp->pw_name);
877 userList.emplace_back(userName);
878
879 // ssh doesn't have separate group. Check login shell entry to
880 // get all users list which are member of ssh group.
881 std::string loginShell(pwp->pw_shell);
882 if (loginShell == "/bin/sh")
883 {
884 sshUsersList.emplace_back(userName);
885 }
886 }
887 }
888 endpwent();
889 return std::make_pair(std::move(userList), std::move(sshUsersList));
890}
891
892size_t UserMgr::getIpmiUsersCount()
893{
894 std::vector<std::string> userList = getUsersInGroup("ipmi");
895 return userList.size();
896}
897
Patrick Williams9638afb2021-02-22 17:16:24 -0600898bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530899{
900 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500901 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530902 std::array<char, 4096> buffer{};
903 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600904 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530905 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
906 buffer.max_size(), &resultPtr);
907 if (!status && (&spwd == resultPtr))
908 {
909 if (resultPtr->sp_expire >= 0)
910 {
911 return false; // user locked out
912 }
913 return true;
914 }
915 return false; // assume user is disabled for any error.
916}
917
Patrick Williams9638afb2021-02-22 17:16:24 -0600918std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530919{
920 std::vector<std::string> usersInGroup;
921 // Should be more than enough to get the pwd structure.
922 std::array<char, 4096> buffer{};
923 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600924 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530925
926 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
927 buffer.max_size(), &resultPtr);
928
929 if (!status && (&grp == resultPtr))
930 {
931 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
932 {
933 usersInGroup.emplace_back(*(grp.gr_mem));
934 }
935 }
936 else
937 {
938 log<level::ERR>("Group not found",
939 entry("GROUP=%s", groupName.c_str()));
940 // Don't throw error, just return empty userList - fallback
941 }
942 return usersInGroup;
943}
944
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600945DbusUserObj UserMgr::getPrivilegeMapperObject(void)
946{
947 DbusUserObj objects;
948 try
949 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500950 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
951 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600952
953 auto ldapMgmtService =
954 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600955 auto method = bus.new_method_call(
956 ldapMgmtService.c_str(), ldapMgrObjBasePath,
957 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
958
959 auto reply = bus.call(method);
960 reply.read(objects);
961 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600962 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600963 {
964 log<level::ERR>("Unable to get the User Service",
965 entry("WHAT=%s", e.what()));
966 throw;
967 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500968 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600969 {
970 log<level::ERR>(
971 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
972 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
973 throw;
974 }
975 return objects;
976}
977
Patrick Williams9638afb2021-02-22 17:16:24 -0600978std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600979{
980 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600981 {};
982 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600983 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
984 if (buflen < -1)
985 {
986 // Use a default size if there is no hard limit suggested by sysconf()
987 buflen = 1024;
988 }
989 std::vector<char> buffer(buflen);
990 gid_t gid = 0;
991
992 auto status =
993 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
994 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
995 // If no matching password record was found, these functions return 0
996 // and store NULL in *pwdPtr
997 if (!status && (&pwd == pwdPtr))
998 {
999 gid = pwd.pw_gid;
1000 }
1001 else
1002 {
1003 log<level::ERR>("User does not exist",
1004 entry("USER_NAME=%s", userName.c_str()));
1005 elog<UserNameDoesNotExist>();
1006 }
1007
Patrick Williams9638afb2021-02-22 17:16:24 -06001008 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001009 std::string ldapGroupName;
1010
1011 while ((groups = getgrent()) != NULL)
1012 {
1013 if (groups->gr_gid == gid)
1014 {
1015 ldapGroupName = groups->gr_name;
1016 break;
1017 }
1018 }
1019 // Call endgrent() to close the group database.
1020 endgrent();
1021
1022 return ldapGroupName;
1023}
1024
Patrick Williams9638afb2021-02-22 17:16:24 -06001025std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001026{
1027 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1028 objMapperInterface, "GetObject");
1029
1030 mapperCall.append(std::move(path));
1031 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1032
1033 auto mapperResponseMsg = bus.call(mapperCall);
1034
1035 if (mapperResponseMsg.is_method_error())
1036 {
1037 log<level::ERR>("Error in mapper call");
1038 elog<InternalFailure>();
1039 }
1040
1041 std::map<std::string, std::vector<std::string>> mapperResponse;
1042 mapperResponseMsg.read(mapperResponse);
1043
1044 if (mapperResponse.begin() == mapperResponse.end())
1045 {
1046 log<level::ERR>("Invalid response from mapper");
1047 elog<InternalFailure>();
1048 }
1049
1050 return mapperResponse.begin()->first;
1051}
1052
1053UserInfoMap UserMgr::getUserInfo(std::string userName)
1054{
1055 UserInfoMap userInfo;
1056 // Check whether the given user is local user or not.
1057 if (isUserExist(userName) == true)
1058 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001059 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001060 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1061 userInfo.emplace("UserGroups", user.get()->userGroups());
1062 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1063 userInfo.emplace("UserLockedForFailedAttempt",
1064 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001065 userInfo.emplace("UserPasswordExpired",
1066 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001067 userInfo.emplace("RemoteUser", false);
1068 }
1069 else
1070 {
1071 std::string ldapGroupName = getLdapGroupName(userName);
1072 if (ldapGroupName.empty())
1073 {
1074 log<level::ERR>("Unable to get group name",
1075 entry("USER_NAME=%s", userName.c_str()));
1076 elog<InternalFailure>();
1077 }
1078
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 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001086 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001087 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001088 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001089 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001090 if ((interface.first ==
1091 "xyz.openbmc_project.Object.Enable"))
1092 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001093 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001094 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001095 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001096 if ((property.first == "Enabled") &&
1097 (value == true))
1098 {
1099 ldapConfigPath = obj.first;
1100 break;
1101 }
1102 }
1103 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001104 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001105 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001106 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001107 break;
1108 }
1109 }
1110
1111 if (ldapConfigPath.empty())
1112 {
1113 return userInfo;
1114 }
1115
Patrick Williams9638afb2021-02-22 17:16:24 -06001116 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001117 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001118 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001119 {
1120 if ((interface.first ==
1121 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1122 (obj.first.str.find(ldapConfigPath) !=
1123 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001124 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001125 std::string privilege;
1126 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001127
Patrick Williams9638afb2021-02-22 17:16:24 -06001128 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001129 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001130 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001131 if (property.first == "GroupName")
1132 {
1133 groupName = value;
1134 }
1135 else if (property.first == "Privilege")
1136 {
1137 privilege = value;
1138 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001139 }
1140 if (groupName == ldapGroupName)
1141 {
1142 userPrivilege = privilege;
1143 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001144 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001145 }
1146 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001147 if (!userPrivilege.empty())
1148 {
1149 break;
1150 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001151 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001152
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001153 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001154 {
1155 log<level::ERR>("LDAP group privilege mapping does not exist");
1156 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001157 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001158 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001159 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001160 {
1161 log<level::ERR>("Error while accessing variant",
1162 entry("WHAT=%s", e.what()));
1163 elog<InternalFailure>();
1164 }
1165 userInfo.emplace("RemoteUser", true);
1166 }
1167
1168 return userInfo;
1169}
1170
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301171void UserMgr::initUserObjects(void)
1172{
1173 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001174 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301175 std::vector<std::string> userNameList;
1176 std::vector<std::string> sshGrpUsersList;
1177 UserSSHLists userSSHLists = getUserAndSshGrpList();
1178 userNameList = std::move(userSSHLists.first);
1179 sshGrpUsersList = std::move(userSSHLists.second);
1180
1181 if (!userNameList.empty())
1182 {
1183 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001184 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301185 {
1186 if (grp == grpSsh)
1187 {
1188 groupLists.emplace(grp, sshGrpUsersList);
1189 }
1190 else
1191 {
1192 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1193 groupLists.emplace(grp, grpUsersList);
1194 }
1195 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001196 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301197 {
1198 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1199 groupLists.emplace(grp, grpUsersList);
1200 }
1201
Patrick Williams9638afb2021-02-22 17:16:24 -06001202 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301203 {
1204 std::vector<std::string> userGroups;
1205 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001206 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301207 {
1208 std::vector<std::string> tempGrp = grp.second;
1209 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1210 tempGrp.end())
1211 {
1212 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1213 privMgr.end())
1214 {
1215 userPriv = grp.first;
1216 }
1217 else
1218 {
1219 userGroups.emplace_back(grp.first);
1220 }
1221 }
1222 }
1223 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301224 sdbusplus::message::object_path tempObjPath(usersObjPath);
1225 tempObjPath /= user;
1226 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301227 std::sort(userGroups.begin(), userGroups.end());
Nan Zhou78d85042022-08-29 17:50:22 +00001228 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1229 bus, objPath.c_str(), userGroups,
1230 userPriv, isUserEnabled(user), *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301231 }
1232 }
1233}
1234
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001235UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Patrick Williams224559b2022-04-05 16:10:39 -05001236 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301237{
1238 UserMgrIface::allPrivileges(privMgr);
1239 std::sort(groupsMgr.begin(), groupsMgr.end());
1240 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301241 std::string valueStr;
1242 auto value = minPasswdLength;
1243 unsigned long tmp = 0;
1244 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1245 success)
1246 {
1247 AccountPolicyIface::minPasswordLength(minPasswdLength);
1248 }
1249 else
1250 {
1251 try
1252 {
1253 tmp = std::stoul(valueStr, nullptr);
1254 if (tmp > std::numeric_limits<decltype(value)>::max())
1255 {
1256 throw std::out_of_range("Out of range");
1257 }
1258 value = static_cast<decltype(value)>(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 MinPasswordLength",
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::minPasswordLength(value);
1267 }
1268 valueStr.clear();
1269 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1270 success)
1271 {
1272 AccountPolicyIface::rememberOldPasswordTimes(0);
1273 }
1274 else
1275 {
1276 value = 0;
1277 try
1278 {
1279 tmp = std::stoul(valueStr, nullptr);
1280 if (tmp > std::numeric_limits<decltype(value)>::max())
1281 {
1282 throw std::out_of_range("Out of range");
1283 }
1284 value = static_cast<decltype(value)>(tmp);
1285 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001286 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301287 {
1288 log<level::ERR>("Exception for RememberOldPasswordTimes",
1289 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001290 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301291 }
1292 AccountPolicyIface::rememberOldPasswordTimes(value);
1293 }
1294 valueStr.clear();
1295 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1296 {
1297 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1298 }
1299 else
1300 {
1301 uint16_t value16 = 0;
1302 try
1303 {
1304 tmp = std::stoul(valueStr, nullptr);
1305 if (tmp > std::numeric_limits<decltype(value16)>::max())
1306 {
1307 throw std::out_of_range("Out of range");
1308 }
1309 value16 = static_cast<decltype(value16)>(tmp);
1310 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001311 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301312 {
1313 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1314 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001315 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301316 }
1317 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1318 }
1319 valueStr.clear();
1320 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1321 {
1322 AccountPolicyIface::accountUnlockTimeout(0);
1323 }
1324 else
1325 {
1326 uint32_t value32 = 0;
1327 try
1328 {
1329 tmp = std::stoul(valueStr, nullptr);
1330 if (tmp > std::numeric_limits<decltype(value32)>::max())
1331 {
1332 throw std::out_of_range("Out of range");
1333 }
1334 value32 = static_cast<decltype(value32)>(tmp);
1335 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001336 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301337 {
1338 log<level::ERR>("Exception for AccountUnlockTimeout",
1339 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001340 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301341 }
1342 AccountPolicyIface::accountUnlockTimeout(value32);
1343 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301344 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301345
1346 // emit the signal
1347 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301348}
1349
1350} // namespace user
1351} // namespace phosphor