blob: 61ffc0598420a8ff64137b8adf2ed31dcad89a6e [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(
343 userName, std::move(std::make_unique<phosphor::user::Users>(
344 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
345
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);
406 usersList.emplace(
407 newUserName,
408 std::move(std::make_unique<phosphor::user::Users>(
409 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
410 return;
411}
412
Patrick Williams9638afb2021-02-22 17:16:24 -0600413void UserMgr::updateGroupsAndPriv(const std::string& userName,
414 const std::vector<std::string>& groupNames,
415 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530416{
417 throwForInvalidPrivilege(priv);
418 throwForInvalidGroups(groupNames);
419 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500420 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530421 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600422 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530423 usersList[userName].get()->userGroups();
424 std::vector<std::string> groupDiff;
425 // Note: already dealing with sorted group lists.
426 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
427 groupNames.begin(), groupNames.end(),
428 std::back_inserter(groupDiff));
429 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
430 groupDiff.end())
431 {
432 throwForUserNameConstraints(userName, groupNames);
433 throwForMaxGrpUserCount(groupNames);
434 }
435
436 std::string groups = getCSVFromVector(groupNames);
437 bool sshRequested = removeStringFromCSV(groups, grpSsh);
438
439 // treat privilege as a group - This is to avoid using different file to
440 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530441 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530442 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530443 if (groups.size() != 0)
444 {
445 groups += ",";
446 }
447 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530448 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530449 try
450 {
451 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
452 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
453 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600454 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530455 {
456 log<level::ERR>("Unable to modify user privilege / groups");
457 elog<InternalFailure>();
458 }
459
460 log<level::INFO>("User groups / privilege updated successfully",
461 entry("USER_NAME=%s", userName.c_str()));
462 return;
463}
464
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530465uint8_t UserMgr::minPasswordLength(uint8_t value)
466{
467 if (value == AccountPolicyIface::minPasswordLength())
468 {
469 return value;
470 }
471 if (value < minPasswdLength)
472 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000473 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
474 std::to_string(minPasswdLength))
475 .c_str(),
476 entry("SIZE=%d", value));
477 elog<InvalidArgument>(
478 Argument::ARGUMENT_NAME("minPasswordLength"),
479 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530480 }
481 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
482 std::to_string(value)) != success)
483 {
484 log<level::ERR>("Unable to set minPasswordLength");
485 elog<InternalFailure>();
486 }
487 return AccountPolicyIface::minPasswordLength(value);
488}
489
490uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
491{
492 if (value == AccountPolicyIface::rememberOldPasswordTimes())
493 {
494 return value;
495 }
496 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
497 std::to_string(value)) != success)
498 {
499 log<level::ERR>("Unable to set rememberOldPasswordTimes");
500 elog<InternalFailure>();
501 }
502 return AccountPolicyIface::rememberOldPasswordTimes(value);
503}
504
505uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
506{
507 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
508 {
509 return value;
510 }
511 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
512 std::to_string(value)) != success)
513 {
514 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
515 elog<InternalFailure>();
516 }
517 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
518}
519
520uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
521{
522 if (value == AccountPolicyIface::accountUnlockTimeout())
523 {
524 return value;
525 }
526 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
527 success)
528 {
529 log<level::ERR>("Unable to set accountUnlockTimeout");
530 elog<InternalFailure>();
531 }
532 return AccountPolicyIface::accountUnlockTimeout(value);
533}
534
Patrick Williams9638afb2021-02-22 17:16:24 -0600535int UserMgr::getPamModuleArgValue(const std::string& moduleName,
536 const std::string& argName,
537 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530538{
539 std::string fileName;
540 if (moduleName == pamTally2)
541 {
542 fileName = pamAuthConfigFile;
543 }
544 else
545 {
546 fileName = pamPasswdConfigFile;
547 }
548 std::ifstream fileToRead(fileName, std::ios::in);
549 if (!fileToRead.is_open())
550 {
551 log<level::ERR>("Failed to open pam configuration file",
552 entry("FILE_NAME=%s", fileName.c_str()));
553 return failure;
554 }
555 std::string line;
556 auto argSearch = argName + "=";
557 size_t startPos = 0;
558 size_t endPos = 0;
559 while (getline(fileToRead, line))
560 {
561 // skip comments section starting with #
562 if ((startPos = line.find('#')) != std::string::npos)
563 {
564 if (startPos == 0)
565 {
566 continue;
567 }
568 // skip comments after meaningful section and process those
569 line = line.substr(0, startPos);
570 }
571 if (line.find(moduleName) != std::string::npos)
572 {
573 if ((startPos = line.find(argSearch)) != std::string::npos)
574 {
575 if ((endPos = line.find(' ', startPos)) == std::string::npos)
576 {
577 endPos = line.size();
578 }
579 startPos += argSearch.size();
580 argValue = line.substr(startPos, endPos - startPos);
581 return success;
582 }
583 }
584 }
585 return failure;
586}
587
Patrick Williams9638afb2021-02-22 17:16:24 -0600588int UserMgr::setPamModuleArgValue(const std::string& moduleName,
589 const std::string& argName,
590 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530591{
592 std::string fileName;
593 if (moduleName == pamTally2)
594 {
595 fileName = pamAuthConfigFile;
596 }
597 else
598 {
599 fileName = pamPasswdConfigFile;
600 }
601 std::string tmpFileName = fileName + "_tmp";
602 std::ifstream fileToRead(fileName, std::ios::in);
603 std::ofstream fileToWrite(tmpFileName, std::ios::out);
604 if (!fileToRead.is_open() || !fileToWrite.is_open())
605 {
606 log<level::ERR>("Failed to open pam configuration /tmp file",
607 entry("FILE_NAME=%s", fileName.c_str()));
608 return failure;
609 }
610 std::string line;
611 auto argSearch = argName + "=";
612 size_t startPos = 0;
613 size_t endPos = 0;
614 bool found = false;
615 while (getline(fileToRead, line))
616 {
617 // skip comments section starting with #
618 if ((startPos = line.find('#')) != std::string::npos)
619 {
620 if (startPos == 0)
621 {
622 fileToWrite << line << std::endl;
623 continue;
624 }
625 // skip comments after meaningful section and process those
626 line = line.substr(0, startPos);
627 }
628 if (line.find(moduleName) != std::string::npos)
629 {
630 if ((startPos = line.find(argSearch)) != std::string::npos)
631 {
632 if ((endPos = line.find(' ', startPos)) == std::string::npos)
633 {
634 endPos = line.size();
635 }
636 startPos += argSearch.size();
637 fileToWrite << line.substr(0, startPos) << argValue
638 << line.substr(endPos, line.size() - endPos)
639 << std::endl;
640 found = true;
641 continue;
642 }
643 }
644 fileToWrite << line << std::endl;
645 }
646 fileToWrite.close();
647 fileToRead.close();
648 if (found)
649 {
650 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
651 {
652 return success;
653 }
654 }
655 return failure;
656}
657
Patrick Williams9638afb2021-02-22 17:16:24 -0600658void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530659{
660 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500661 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530662 throwForUserDoesNotExist(userName);
663 try
664 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800665 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
666 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530667 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800668 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530669 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600670 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530671 {
672 log<level::ERR>("Unable to modify user enabled state");
673 elog<InternalFailure>();
674 }
675
676 log<level::INFO>("User enabled/disabled state updated successfully",
677 entry("USER_NAME=%s", userName.c_str()),
678 entry("ENABLED=%d", enabled));
679 return;
680}
681
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530682/**
683 * pam_tally2 app will provide the user failure count and failure status
684 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800685 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530686 * [4] - failure app
687 **/
688
689static constexpr size_t t2UserIdx = 0;
690static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800691static constexpr size_t t2FailDateIdx = 2;
692static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530693static constexpr size_t t2OutputIndex = 1;
694
Patrick Williams9638afb2021-02-22 17:16:24 -0600695bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530696{
697 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500698 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800699 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
700 {
701 return false;
702 }
703
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530704 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800705 try
706 {
707 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
708 }
709 catch (const InternalFailure& e)
710 {
711 log<level::ERR>("Unable to read login failure counter");
712 elog<InternalFailure>();
713 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530714
715 std::vector<std::string> splitWords;
716 boost::algorithm::split(splitWords, output[t2OutputIndex],
717 boost::algorithm::is_any_of("\t "),
718 boost::token_compress_on);
719
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800720 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530721 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530722 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530723 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800724 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530725 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530726 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530727 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800728 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530729 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600730 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530731 {
732 log<level::ERR>("Exception for userLockedForFailedAttempt",
733 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800734 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530735 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800736
737 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
738 {
739 return false;
740 }
741
742 // When failedAttempts is not 0, Latest failure date/time should be
743 // available
744 if (splitWords.size() < 4)
745 {
746 log<level::ERR>("Unable to read latest failure date/time");
747 elog<InternalFailure>();
748 }
749
750 const std::string failDateTime =
751 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
752
753 // NOTE: Cannot use std::get_time() here as the implementation of %y in
754 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
755 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
756 std::tm tmStruct = {};
757 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
758 {
759 log<level::ERR>("Failed to parse latest failure date/time");
760 elog<InternalFailure>();
761 }
762
763 time_t failTimestamp = std::mktime(&tmStruct);
764 if (failTimestamp +
765 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
766 std::time(NULL))
767 {
768 return false;
769 }
770
771 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530772}
773
Patrick Williams9638afb2021-02-22 17:16:24 -0600774bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
775 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530776{
777 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500778 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530779 if (value == true)
780 {
781 return userLockedForFailedAttempt(userName);
782 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530783
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800784 try
785 {
786 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
787 }
788 catch (const InternalFailure& e)
789 {
790 log<level::ERR>("Unable to reset login failure counter");
791 elog<InternalFailure>();
792 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530793
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530794 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530795}
796
Patrick Williams9638afb2021-02-22 17:16:24 -0600797bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600798{
799 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500800 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600801
802 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600803 {};
804 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600805 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
806 if (buflen < -1)
807 {
808 // Use a default size if there is no hard limit suggested by sysconf()
809 buflen = 1024;
810 }
811 std::vector<char> buffer(buflen);
812 auto status =
813 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
814 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
815 // If no matching password record was found, these functions return 0
816 // and store NULL in *spwdPtr
817 if ((status == 0) && (&spwd == spwdPtr))
818 {
819 // Determine password validity per "chage" docs, where:
820 // spwd.sp_lstchg == 0 means password is expired, and
821 // spwd.sp_max == -1 means the password does not expire.
822 constexpr long seconds_per_day = 60 * 60 * 24;
823 long today = static_cast<long>(time(NULL)) / seconds_per_day;
824 if ((spwd.sp_lstchg == 0) ||
825 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
826 {
827 return true;
828 }
829 }
830 else
831 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000832 // User entry is missing in /etc/shadow, indicating no SHA password.
833 // Treat this as new user without password entry in /etc/shadow
834 // TODO: Add property to indicate user password was not set yet
835 // https://github.com/openbmc/phosphor-user-manager/issues/8
836 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600837 }
838
839 return false;
840}
841
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530842UserSSHLists UserMgr::getUserAndSshGrpList()
843{
844 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500845 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530846
847 std::vector<std::string> userList;
848 std::vector<std::string> sshUsersList;
849 struct passwd pw, *pwp = nullptr;
850 std::array<char, 1024> buffer{};
851
852 phosphor::user::File passwd(passwdFileName, "r");
853 if ((passwd)() == NULL)
854 {
855 log<level::ERR>("Error opening the passwd file");
856 elog<InternalFailure>();
857 }
858
859 while (true)
860 {
861 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
862 &pwp);
863 if ((r != 0) || (pwp == NULL))
864 {
865 // Any error, break the loop.
866 break;
867 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530868#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530869 // Add all users whose UID >= 1000 and < 65534
870 // and special UID 0.
871 if ((pwp->pw_uid == 0) ||
872 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530873#else
874 // Add all users whose UID >=1000 and < 65534
875 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
876#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530877 {
878 std::string userName(pwp->pw_name);
879 userList.emplace_back(userName);
880
881 // ssh doesn't have separate group. Check login shell entry to
882 // get all users list which are member of ssh group.
883 std::string loginShell(pwp->pw_shell);
884 if (loginShell == "/bin/sh")
885 {
886 sshUsersList.emplace_back(userName);
887 }
888 }
889 }
890 endpwent();
891 return std::make_pair(std::move(userList), std::move(sshUsersList));
892}
893
894size_t UserMgr::getIpmiUsersCount()
895{
896 std::vector<std::string> userList = getUsersInGroup("ipmi");
897 return userList.size();
898}
899
Patrick Williams9638afb2021-02-22 17:16:24 -0600900bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530901{
902 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500903 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530904 std::array<char, 4096> buffer{};
905 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600906 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530907 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
908 buffer.max_size(), &resultPtr);
909 if (!status && (&spwd == resultPtr))
910 {
911 if (resultPtr->sp_expire >= 0)
912 {
913 return false; // user locked out
914 }
915 return true;
916 }
917 return false; // assume user is disabled for any error.
918}
919
Patrick Williams9638afb2021-02-22 17:16:24 -0600920std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530921{
922 std::vector<std::string> usersInGroup;
923 // Should be more than enough to get the pwd structure.
924 std::array<char, 4096> buffer{};
925 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600926 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530927
928 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
929 buffer.max_size(), &resultPtr);
930
931 if (!status && (&grp == resultPtr))
932 {
933 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
934 {
935 usersInGroup.emplace_back(*(grp.gr_mem));
936 }
937 }
938 else
939 {
940 log<level::ERR>("Group not found",
941 entry("GROUP=%s", groupName.c_str()));
942 // Don't throw error, just return empty userList - fallback
943 }
944 return usersInGroup;
945}
946
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600947DbusUserObj UserMgr::getPrivilegeMapperObject(void)
948{
949 DbusUserObj objects;
950 try
951 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500952 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
953 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600954
955 auto ldapMgmtService =
956 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600957 auto method = bus.new_method_call(
958 ldapMgmtService.c_str(), ldapMgrObjBasePath,
959 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
960
961 auto reply = bus.call(method);
962 reply.read(objects);
963 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600964 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600965 {
966 log<level::ERR>("Unable to get the User Service",
967 entry("WHAT=%s", e.what()));
968 throw;
969 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500970 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600971 {
972 log<level::ERR>(
973 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
974 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
975 throw;
976 }
977 return objects;
978}
979
Patrick Williams9638afb2021-02-22 17:16:24 -0600980std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600981{
982 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600983 {};
984 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600985 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
986 if (buflen < -1)
987 {
988 // Use a default size if there is no hard limit suggested by sysconf()
989 buflen = 1024;
990 }
991 std::vector<char> buffer(buflen);
992 gid_t gid = 0;
993
994 auto status =
995 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
996 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
997 // If no matching password record was found, these functions return 0
998 // and store NULL in *pwdPtr
999 if (!status && (&pwd == pwdPtr))
1000 {
1001 gid = pwd.pw_gid;
1002 }
1003 else
1004 {
1005 log<level::ERR>("User does not exist",
1006 entry("USER_NAME=%s", userName.c_str()));
1007 elog<UserNameDoesNotExist>();
1008 }
1009
Patrick Williams9638afb2021-02-22 17:16:24 -06001010 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001011 std::string ldapGroupName;
1012
1013 while ((groups = getgrent()) != NULL)
1014 {
1015 if (groups->gr_gid == gid)
1016 {
1017 ldapGroupName = groups->gr_name;
1018 break;
1019 }
1020 }
1021 // Call endgrent() to close the group database.
1022 endgrent();
1023
1024 return ldapGroupName;
1025}
1026
Patrick Williams9638afb2021-02-22 17:16:24 -06001027std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001028{
1029 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1030 objMapperInterface, "GetObject");
1031
1032 mapperCall.append(std::move(path));
1033 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1034
1035 auto mapperResponseMsg = bus.call(mapperCall);
1036
1037 if (mapperResponseMsg.is_method_error())
1038 {
1039 log<level::ERR>("Error in mapper call");
1040 elog<InternalFailure>();
1041 }
1042
1043 std::map<std::string, std::vector<std::string>> mapperResponse;
1044 mapperResponseMsg.read(mapperResponse);
1045
1046 if (mapperResponse.begin() == mapperResponse.end())
1047 {
1048 log<level::ERR>("Invalid response from mapper");
1049 elog<InternalFailure>();
1050 }
1051
1052 return mapperResponse.begin()->first;
1053}
1054
1055UserInfoMap UserMgr::getUserInfo(std::string userName)
1056{
1057 UserInfoMap userInfo;
1058 // Check whether the given user is local user or not.
1059 if (isUserExist(userName) == true)
1060 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001061 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001062 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1063 userInfo.emplace("UserGroups", user.get()->userGroups());
1064 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1065 userInfo.emplace("UserLockedForFailedAttempt",
1066 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001067 userInfo.emplace("UserPasswordExpired",
1068 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001069 userInfo.emplace("RemoteUser", false);
1070 }
1071 else
1072 {
1073 std::string ldapGroupName = getLdapGroupName(userName);
1074 if (ldapGroupName.empty())
1075 {
1076 log<level::ERR>("Unable to get group name",
1077 entry("USER_NAME=%s", userName.c_str()));
1078 elog<InternalFailure>();
1079 }
1080
1081 DbusUserObj objects = getPrivilegeMapperObject();
1082
1083 std::string privilege;
1084 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001085 std::string ldapConfigPath;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001086
1087 try
1088 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001089 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001090 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001091 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001092 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001093 if ((interface.first ==
1094 "xyz.openbmc_project.Object.Enable"))
1095 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001096 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001097 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001098 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001099 if ((property.first == "Enabled") &&
1100 (value == true))
1101 {
1102 ldapConfigPath = obj.first;
1103 break;
1104 }
1105 }
1106 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001107 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001108 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001109 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001110 break;
1111 }
1112 }
1113
1114 if (ldapConfigPath.empty())
1115 {
1116 return userInfo;
1117 }
1118
Patrick Williams9638afb2021-02-22 17:16:24 -06001119 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001120 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001121 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001122 {
1123 if ((interface.first ==
1124 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1125 (obj.first.str.find(ldapConfigPath) !=
1126 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001127 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001128
Patrick Williams9638afb2021-02-22 17:16:24 -06001129 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001130 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001131 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001132 if (property.first == "GroupName")
1133 {
1134 groupName = value;
1135 }
1136 else if (property.first == "Privilege")
1137 {
1138 privilege = value;
1139 }
1140 if (groupName == ldapGroupName)
1141 {
1142 userInfo["UserPrivilege"] = privilege;
1143 }
1144 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001145 }
1146 }
1147 }
1148 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001149
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001150 if (priv.empty())
1151 {
1152 log<level::ERR>("LDAP group privilege mapping does not exist");
1153 }
1154 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001155 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001156 {
1157 log<level::ERR>("Error while accessing variant",
1158 entry("WHAT=%s", e.what()));
1159 elog<InternalFailure>();
1160 }
1161 userInfo.emplace("RemoteUser", true);
1162 }
1163
1164 return userInfo;
1165}
1166
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301167void UserMgr::initUserObjects(void)
1168{
1169 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001170 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301171 std::vector<std::string> userNameList;
1172 std::vector<std::string> sshGrpUsersList;
1173 UserSSHLists userSSHLists = getUserAndSshGrpList();
1174 userNameList = std::move(userSSHLists.first);
1175 sshGrpUsersList = std::move(userSSHLists.second);
1176
1177 if (!userNameList.empty())
1178 {
1179 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001180 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301181 {
1182 if (grp == grpSsh)
1183 {
1184 groupLists.emplace(grp, sshGrpUsersList);
1185 }
1186 else
1187 {
1188 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1189 groupLists.emplace(grp, grpUsersList);
1190 }
1191 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001192 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301193 {
1194 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1195 groupLists.emplace(grp, grpUsersList);
1196 }
1197
Patrick Williams9638afb2021-02-22 17:16:24 -06001198 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301199 {
1200 std::vector<std::string> userGroups;
1201 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001202 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301203 {
1204 std::vector<std::string> tempGrp = grp.second;
1205 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1206 tempGrp.end())
1207 {
1208 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1209 privMgr.end())
1210 {
1211 userPriv = grp.first;
1212 }
1213 else
1214 {
1215 userGroups.emplace_back(grp.first);
1216 }
1217 }
1218 }
1219 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301220 sdbusplus::message::object_path tempObjPath(usersObjPath);
1221 tempObjPath /= user;
1222 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301223 std::sort(userGroups.begin(), userGroups.end());
1224 usersList.emplace(user,
1225 std::move(std::make_unique<phosphor::user::Users>(
1226 bus, objPath.c_str(), userGroups, userPriv,
1227 isUserEnabled(user), *this)));
1228 }
1229 }
1230}
1231
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001232UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Patrick Williams224559b2022-04-05 16:10:39 -05001233 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301234{
1235 UserMgrIface::allPrivileges(privMgr);
1236 std::sort(groupsMgr.begin(), groupsMgr.end());
1237 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301238 std::string valueStr;
1239 auto value = minPasswdLength;
1240 unsigned long tmp = 0;
1241 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1242 success)
1243 {
1244 AccountPolicyIface::minPasswordLength(minPasswdLength);
1245 }
1246 else
1247 {
1248 try
1249 {
1250 tmp = std::stoul(valueStr, nullptr);
1251 if (tmp > std::numeric_limits<decltype(value)>::max())
1252 {
1253 throw std::out_of_range("Out of range");
1254 }
1255 value = static_cast<decltype(value)>(tmp);
1256 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001257 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301258 {
1259 log<level::ERR>("Exception for MinPasswordLength",
1260 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001261 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301262 }
1263 AccountPolicyIface::minPasswordLength(value);
1264 }
1265 valueStr.clear();
1266 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1267 success)
1268 {
1269 AccountPolicyIface::rememberOldPasswordTimes(0);
1270 }
1271 else
1272 {
1273 value = 0;
1274 try
1275 {
1276 tmp = std::stoul(valueStr, nullptr);
1277 if (tmp > std::numeric_limits<decltype(value)>::max())
1278 {
1279 throw std::out_of_range("Out of range");
1280 }
1281 value = static_cast<decltype(value)>(tmp);
1282 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001283 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301284 {
1285 log<level::ERR>("Exception for RememberOldPasswordTimes",
1286 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001287 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301288 }
1289 AccountPolicyIface::rememberOldPasswordTimes(value);
1290 }
1291 valueStr.clear();
1292 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1293 {
1294 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1295 }
1296 else
1297 {
1298 uint16_t value16 = 0;
1299 try
1300 {
1301 tmp = std::stoul(valueStr, nullptr);
1302 if (tmp > std::numeric_limits<decltype(value16)>::max())
1303 {
1304 throw std::out_of_range("Out of range");
1305 }
1306 value16 = static_cast<decltype(value16)>(tmp);
1307 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001308 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301309 {
1310 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1311 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001312 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301313 }
1314 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1315 }
1316 valueStr.clear();
1317 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1318 {
1319 AccountPolicyIface::accountUnlockTimeout(0);
1320 }
1321 else
1322 {
1323 uint32_t value32 = 0;
1324 try
1325 {
1326 tmp = std::stoul(valueStr, nullptr);
1327 if (tmp > std::numeric_limits<decltype(value32)>::max())
1328 {
1329 throw std::out_of_range("Out of range");
1330 }
1331 value32 = static_cast<decltype(value32)>(tmp);
1332 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001333 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301334 {
1335 log<level::ERR>("Exception for AccountUnlockTimeout",
1336 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001337 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301338 }
1339 AccountPolicyIface::accountUnlockTimeout(value32);
1340 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301341 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301342
1343 // emit the signal
1344 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301345}
1346
1347} // namespace user
1348} // namespace phosphor