blob: 05aa6e73d5a37c9097340f9b911b3cbd2be4beee [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>
43#include <fstream>
44#include <numeric>
45#include <regex>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053046
47namespace phosphor
48{
49namespace user
50{
51
Patrick Williams9638afb2021-02-22 17:16:24 -060052static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053053static constexpr size_t ipmiMaxUsers = 15;
54static constexpr size_t ipmiMaxUserNameLen = 16;
55static constexpr size_t systemMaxUserNameLen = 30;
56static constexpr size_t maxSystemUsers = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060057static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053058static constexpr uint8_t minPasswdLength = 8;
59static constexpr int success = 0;
60static constexpr int failure = -1;
61
62// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060063static constexpr const char* pamTally2 = "pam_tally2.so";
64static constexpr const char* pamCrackLib = "pam_cracklib.so";
65static constexpr const char* pamPWHistory = "pam_pwhistory.so";
66static constexpr const char* minPasswdLenProp = "minlen";
67static constexpr const char* remOldPasswdCount = "remember";
68static constexpr const char* maxFailedAttempt = "deny";
69static constexpr const char* unlockTimeout = "unlock_time";
70static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
71static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053072
Ratan Guptaaeaf9412019-02-11 04:41:52 -060073// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060074static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060075 "/xyz/openbmc_project/user/ldap";
76
77// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060078static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060079 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060080static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060081 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060082static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060083 "xyz.openbmc_project.ObjectMapper";
84
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053085using namespace phosphor::logging;
86using InsufficientPermission =
87 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
88using InternalFailure =
89 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
90using InvalidArgument =
91 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
92using UserNameExists =
93 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
94using UserNameDoesNotExist =
95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
96using UserNameGroupFail =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053098using NoResource =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
100
101using Argument = xyz::openbmc_project::Common::InvalidArgument;
102
103template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600104static std::vector<std::string> executeCmd(const char* path,
105 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530106{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530107 std::vector<std::string> stdOutput;
108 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600109 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530110 boost::process::std_out > stdOutStream);
111 std::string stdOutLine;
112
113 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
114 !stdOutLine.empty())
115 {
116 stdOutput.emplace_back(stdOutLine);
117 }
118
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530119 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530120
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530121 int retCode = execProg.exit_code();
122 if (retCode)
123 {
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530124 log<level::ERR>("Command execution failed", entry("PATH=%d", path),
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530125 entry("RETURN_CODE:%d", retCode));
126 elog<InternalFailure>();
127 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530128
129 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530130}
131
132static std::string getCSVFromVector(std::vector<std::string> vec)
133{
134 switch (vec.size())
135 {
136 case 0:
137 {
138 return "";
139 }
140 break;
141
142 case 1:
143 {
144 return std::string{vec[0]};
145 }
146 break;
147
148 default:
149 {
150 return std::accumulate(
151 std::next(vec.begin()), vec.end(), vec[0],
152 [](std::string a, std::string b) { return a + ',' + b; });
153 }
154 }
155}
156
Patrick Williams9638afb2021-02-22 17:16:24 -0600157static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530158{
159 std::string::size_type delStrPos = csvStr.find(delStr);
160 if (delStrPos != std::string::npos)
161 {
162 // need to also delete the comma char
163 if (delStrPos == 0)
164 {
165 csvStr.erase(delStrPos, delStr.size() + 1);
166 }
167 else
168 {
169 csvStr.erase(delStrPos - 1, delStr.size() + 1);
170 }
171 return true;
172 }
173 return false;
174}
175
Patrick Williams9638afb2021-02-22 17:16:24 -0600176bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530177{
178 if (userName.empty())
179 {
180 log<level::ERR>("User name is empty");
181 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
182 Argument::ARGUMENT_VALUE("Null"));
183 }
184 if (usersList.find(userName) == usersList.end())
185 {
186 return false;
187 }
188 return true;
189}
190
Patrick Williams9638afb2021-02-22 17:16:24 -0600191void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530192{
193 if (isUserExist(userName) == false)
194 {
195 log<level::ERR>("User does not exist",
196 entry("USER_NAME=%s", userName.c_str()));
197 elog<UserNameDoesNotExist>();
198 }
199}
200
Patrick Williams9638afb2021-02-22 17:16:24 -0600201void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530202{
203 if (isUserExist(userName) == true)
204 {
205 log<level::ERR>("User already exists",
206 entry("USER_NAME=%s", userName.c_str()));
207 elog<UserNameExists>();
208 }
209}
210
211void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600212 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530213{
214 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
215 groupNames.end())
216 {
217 if (userName.length() > ipmiMaxUserNameLen)
218 {
219 log<level::ERR>("IPMI user name length limitation",
220 entry("SIZE=%d", userName.length()));
221 elog<UserNameGroupFail>(
222 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
223 "IPMI length"));
224 }
225 }
226 if (userName.length() > systemMaxUserNameLen)
227 {
228 log<level::ERR>("User name length limitation",
229 entry("SIZE=%d", userName.length()));
230 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
231 Argument::ARGUMENT_VALUE("Invalid length"));
232 }
233 if (!std::regex_match(userName.c_str(),
234 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
235 {
236 log<level::ERR>("Invalid user name",
237 entry("USER_NAME=%s", userName.c_str()));
238 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
239 Argument::ARGUMENT_VALUE("Invalid data"));
240 }
241}
242
243void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600244 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530245{
246 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
247 groupNames.end())
248 {
249 if (getIpmiUsersCount() >= ipmiMaxUsers)
250 {
251 log<level::ERR>("IPMI user limit reached");
252 elog<NoResource>(
253 xyz::openbmc_project::User::Common::NoResource::REASON(
254 "ipmi user count reached"));
255 }
256 }
257 else
258 {
259 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
260 (maxSystemUsers - ipmiMaxUsers))
261 {
262 log<level::ERR>("Non-ipmi User limit reached");
263 elog<NoResource>(
264 xyz::openbmc_project::User::Common::NoResource::REASON(
265 "Non-ipmi user count reached"));
266 }
267 }
268 return;
269}
270
Patrick Williams9638afb2021-02-22 17:16:24 -0600271void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530272{
273 if (!priv.empty() &&
274 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
275 {
276 log<level::ERR>("Invalid privilege");
277 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
278 Argument::ARGUMENT_VALUE(priv.c_str()));
279 }
280}
281
Patrick Williams9638afb2021-02-22 17:16:24 -0600282void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530283{
Patrick Williams9638afb2021-02-22 17:16:24 -0600284 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530285 {
286 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
287 groupsMgr.end())
288 {
289 log<level::ERR>("Invalid Group Name listed");
290 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
291 Argument::ARGUMENT_VALUE(group.c_str()));
292 }
293 }
294}
295
296void UserMgr::createUser(std::string userName,
297 std::vector<std::string> groupNames, std::string priv,
298 bool enabled)
299{
300 throwForInvalidPrivilege(priv);
301 throwForInvalidGroups(groupNames);
302 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500303 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530304 throwForUserExists(userName);
305 throwForUserNameConstraints(userName, groupNames);
306 throwForMaxGrpUserCount(groupNames);
307
308 std::string groups = getCSVFromVector(groupNames);
309 bool sshRequested = removeStringFromCSV(groups, grpSsh);
310
311 // treat privilege as a group - This is to avoid using different file to
312 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530313 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530314 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530315 if (groups.size() != 0)
316 {
317 groups += ",";
318 }
319 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530320 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 try
322 {
323 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530324 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530325 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
326 (enabled ? "" : "1970-01-02"));
327 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600328 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530329 {
330 log<level::ERR>("Unable to create new user");
331 elog<InternalFailure>();
332 }
333
334 // Add the users object before sending out the signal
335 std::string userObj = std::string(usersObjPath) + "/" + userName;
336 std::sort(groupNames.begin(), groupNames.end());
337 usersList.emplace(
338 userName, std::move(std::make_unique<phosphor::user::Users>(
339 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
340
341 log<level::INFO>("User created successfully",
342 entry("USER_NAME=%s", userName.c_str()));
343 return;
344}
345
346void UserMgr::deleteUser(std::string userName)
347{
348 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500349 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530350 throwForUserDoesNotExist(userName);
351 try
352 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530353 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530354 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600355 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530356 {
357 log<level::ERR>("User delete failed",
358 entry("USER_NAME=%s", userName.c_str()));
359 elog<InternalFailure>();
360 }
361
362 usersList.erase(userName);
363
364 log<level::INFO>("User deleted successfully",
365 entry("USER_NAME=%s", userName.c_str()));
366 return;
367}
368
369void UserMgr::renameUser(std::string userName, std::string newUserName)
370{
371 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500372 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530373 throwForUserDoesNotExist(userName);
374 throwForUserExists(newUserName);
375 throwForUserNameConstraints(newUserName,
376 usersList[userName].get()->userGroups());
377 try
378 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530379 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530380 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530381 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530382 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600383 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530384 {
385 log<level::ERR>("User rename failed",
386 entry("USER_NAME=%s", userName.c_str()));
387 elog<InternalFailure>();
388 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600389 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530390 std::string priv = user.get()->userPrivilege();
391 std::vector<std::string> groupNames = user.get()->userGroups();
392 bool enabled = user.get()->userEnabled();
393 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
394 // Special group 'ipmi' needs a way to identify user renamed, in order to
395 // update encrypted password. It can't rely only on InterfacesRemoved &
396 // InterfacesAdded. So first send out userRenamed signal.
397 this->userRenamed(userName, newUserName);
398 usersList.erase(userName);
399 usersList.emplace(
400 newUserName,
401 std::move(std::make_unique<phosphor::user::Users>(
402 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
403 return;
404}
405
Patrick Williams9638afb2021-02-22 17:16:24 -0600406void UserMgr::updateGroupsAndPriv(const std::string& userName,
407 const std::vector<std::string>& groupNames,
408 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530409{
410 throwForInvalidPrivilege(priv);
411 throwForInvalidGroups(groupNames);
412 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500413 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530414 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600415 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530416 usersList[userName].get()->userGroups();
417 std::vector<std::string> groupDiff;
418 // Note: already dealing with sorted group lists.
419 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
420 groupNames.begin(), groupNames.end(),
421 std::back_inserter(groupDiff));
422 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
423 groupDiff.end())
424 {
425 throwForUserNameConstraints(userName, groupNames);
426 throwForMaxGrpUserCount(groupNames);
427 }
428
429 std::string groups = getCSVFromVector(groupNames);
430 bool sshRequested = removeStringFromCSV(groups, grpSsh);
431
432 // treat privilege as a group - This is to avoid using different file to
433 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530434 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530435 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530436 if (groups.size() != 0)
437 {
438 groups += ",";
439 }
440 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530441 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530442 try
443 {
444 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
445 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
446 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600447 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530448 {
449 log<level::ERR>("Unable to modify user privilege / groups");
450 elog<InternalFailure>();
451 }
452
453 log<level::INFO>("User groups / privilege updated successfully",
454 entry("USER_NAME=%s", userName.c_str()));
455 return;
456}
457
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530458uint8_t UserMgr::minPasswordLength(uint8_t value)
459{
460 if (value == AccountPolicyIface::minPasswordLength())
461 {
462 return value;
463 }
464 if (value < minPasswdLength)
465 {
466 return value;
467 }
468 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
469 std::to_string(value)) != success)
470 {
471 log<level::ERR>("Unable to set minPasswordLength");
472 elog<InternalFailure>();
473 }
474 return AccountPolicyIface::minPasswordLength(value);
475}
476
477uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
478{
479 if (value == AccountPolicyIface::rememberOldPasswordTimes())
480 {
481 return value;
482 }
483 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
484 std::to_string(value)) != success)
485 {
486 log<level::ERR>("Unable to set rememberOldPasswordTimes");
487 elog<InternalFailure>();
488 }
489 return AccountPolicyIface::rememberOldPasswordTimes(value);
490}
491
492uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
493{
494 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
495 {
496 return value;
497 }
498 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
499 std::to_string(value)) != success)
500 {
501 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
502 elog<InternalFailure>();
503 }
504 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
505}
506
507uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
508{
509 if (value == AccountPolicyIface::accountUnlockTimeout())
510 {
511 return value;
512 }
513 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
514 success)
515 {
516 log<level::ERR>("Unable to set accountUnlockTimeout");
517 elog<InternalFailure>();
518 }
519 return AccountPolicyIface::accountUnlockTimeout(value);
520}
521
Patrick Williams9638afb2021-02-22 17:16:24 -0600522int UserMgr::getPamModuleArgValue(const std::string& moduleName,
523 const std::string& argName,
524 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530525{
526 std::string fileName;
527 if (moduleName == pamTally2)
528 {
529 fileName = pamAuthConfigFile;
530 }
531 else
532 {
533 fileName = pamPasswdConfigFile;
534 }
535 std::ifstream fileToRead(fileName, std::ios::in);
536 if (!fileToRead.is_open())
537 {
538 log<level::ERR>("Failed to open pam configuration file",
539 entry("FILE_NAME=%s", fileName.c_str()));
540 return failure;
541 }
542 std::string line;
543 auto argSearch = argName + "=";
544 size_t startPos = 0;
545 size_t endPos = 0;
546 while (getline(fileToRead, line))
547 {
548 // skip comments section starting with #
549 if ((startPos = line.find('#')) != std::string::npos)
550 {
551 if (startPos == 0)
552 {
553 continue;
554 }
555 // skip comments after meaningful section and process those
556 line = line.substr(0, startPos);
557 }
558 if (line.find(moduleName) != std::string::npos)
559 {
560 if ((startPos = line.find(argSearch)) != std::string::npos)
561 {
562 if ((endPos = line.find(' ', startPos)) == std::string::npos)
563 {
564 endPos = line.size();
565 }
566 startPos += argSearch.size();
567 argValue = line.substr(startPos, endPos - startPos);
568 return success;
569 }
570 }
571 }
572 return failure;
573}
574
Patrick Williams9638afb2021-02-22 17:16:24 -0600575int UserMgr::setPamModuleArgValue(const std::string& moduleName,
576 const std::string& argName,
577 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530578{
579 std::string fileName;
580 if (moduleName == pamTally2)
581 {
582 fileName = pamAuthConfigFile;
583 }
584 else
585 {
586 fileName = pamPasswdConfigFile;
587 }
588 std::string tmpFileName = fileName + "_tmp";
589 std::ifstream fileToRead(fileName, std::ios::in);
590 std::ofstream fileToWrite(tmpFileName, std::ios::out);
591 if (!fileToRead.is_open() || !fileToWrite.is_open())
592 {
593 log<level::ERR>("Failed to open pam configuration /tmp file",
594 entry("FILE_NAME=%s", fileName.c_str()));
595 return failure;
596 }
597 std::string line;
598 auto argSearch = argName + "=";
599 size_t startPos = 0;
600 size_t endPos = 0;
601 bool found = false;
602 while (getline(fileToRead, line))
603 {
604 // skip comments section starting with #
605 if ((startPos = line.find('#')) != std::string::npos)
606 {
607 if (startPos == 0)
608 {
609 fileToWrite << line << std::endl;
610 continue;
611 }
612 // skip comments after meaningful section and process those
613 line = line.substr(0, startPos);
614 }
615 if (line.find(moduleName) != std::string::npos)
616 {
617 if ((startPos = line.find(argSearch)) != std::string::npos)
618 {
619 if ((endPos = line.find(' ', startPos)) == std::string::npos)
620 {
621 endPos = line.size();
622 }
623 startPos += argSearch.size();
624 fileToWrite << line.substr(0, startPos) << argValue
625 << line.substr(endPos, line.size() - endPos)
626 << std::endl;
627 found = true;
628 continue;
629 }
630 }
631 fileToWrite << line << std::endl;
632 }
633 fileToWrite.close();
634 fileToRead.close();
635 if (found)
636 {
637 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
638 {
639 return success;
640 }
641 }
642 return failure;
643}
644
Patrick Williams9638afb2021-02-22 17:16:24 -0600645void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530646{
647 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500648 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530649 throwForUserDoesNotExist(userName);
650 try
651 {
652 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
653 (enabled ? "" : "1970-01-02"));
654 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600655 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530656 {
657 log<level::ERR>("Unable to modify user enabled state");
658 elog<InternalFailure>();
659 }
660
661 log<level::INFO>("User enabled/disabled state updated successfully",
662 entry("USER_NAME=%s", userName.c_str()),
663 entry("ENABLED=%d", enabled));
664 return;
665}
666
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530667/**
668 * pam_tally2 app will provide the user failure count and failure status
669 * in second line of output with words position [0] - user name,
670 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
671 * [4] - failure app
672 **/
673
674static constexpr size_t t2UserIdx = 0;
675static constexpr size_t t2FailCntIdx = 1;
676static constexpr size_t t2OutputIndex = 1;
677
Patrick Williams9638afb2021-02-22 17:16:24 -0600678bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530679{
680 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500681 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530682 std::vector<std::string> output;
683
684 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
685
686 std::vector<std::string> splitWords;
687 boost::algorithm::split(splitWords, output[t2OutputIndex],
688 boost::algorithm::is_any_of("\t "),
689 boost::token_compress_on);
690
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530691 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530692 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530693 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
694 uint16_t value16 = 0;
695 if (tmp > std::numeric_limits<decltype(value16)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530696 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530697 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530698 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530699 value16 = static_cast<decltype(value16)>(tmp);
700 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
701 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530702 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530703 return true; // User account is locked out
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530704 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530705 return false; // User account is un-locked
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530706 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600707 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530708 {
709 log<level::ERR>("Exception for userLockedForFailedAttempt",
710 entry("WHAT=%s", e.what()));
711 throw;
712 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530713}
714
Patrick Williams9638afb2021-02-22 17:16:24 -0600715bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
716 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530717{
718 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500719 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530720 std::vector<std::string> output;
721 if (value == true)
722 {
723 return userLockedForFailedAttempt(userName);
724 }
725 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
726
727 std::vector<std::string> splitWords;
728 boost::algorithm::split(splitWords, output[t2OutputIndex],
729 boost::algorithm::is_any_of("\t "),
730 boost::token_compress_on);
731
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530732 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530733}
734
Patrick Williams9638afb2021-02-22 17:16:24 -0600735bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600736{
737 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500738 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600739
740 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600741 {};
742 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600743 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
744 if (buflen < -1)
745 {
746 // Use a default size if there is no hard limit suggested by sysconf()
747 buflen = 1024;
748 }
749 std::vector<char> buffer(buflen);
750 auto status =
751 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
752 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
753 // If no matching password record was found, these functions return 0
754 // and store NULL in *spwdPtr
755 if ((status == 0) && (&spwd == spwdPtr))
756 {
757 // Determine password validity per "chage" docs, where:
758 // spwd.sp_lstchg == 0 means password is expired, and
759 // spwd.sp_max == -1 means the password does not expire.
760 constexpr long seconds_per_day = 60 * 60 * 24;
761 long today = static_cast<long>(time(NULL)) / seconds_per_day;
762 if ((spwd.sp_lstchg == 0) ||
763 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
764 {
765 return true;
766 }
767 }
768 else
769 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000770 // User entry is missing in /etc/shadow, indicating no SHA password.
771 // Treat this as new user without password entry in /etc/shadow
772 // TODO: Add property to indicate user password was not set yet
773 // https://github.com/openbmc/phosphor-user-manager/issues/8
774 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600775 }
776
777 return false;
778}
779
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530780UserSSHLists UserMgr::getUserAndSshGrpList()
781{
782 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500783 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530784
785 std::vector<std::string> userList;
786 std::vector<std::string> sshUsersList;
787 struct passwd pw, *pwp = nullptr;
788 std::array<char, 1024> buffer{};
789
790 phosphor::user::File passwd(passwdFileName, "r");
791 if ((passwd)() == NULL)
792 {
793 log<level::ERR>("Error opening the passwd file");
794 elog<InternalFailure>();
795 }
796
797 while (true)
798 {
799 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
800 &pwp);
801 if ((r != 0) || (pwp == NULL))
802 {
803 // Any error, break the loop.
804 break;
805 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530806#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530807 // Add all users whose UID >= 1000 and < 65534
808 // and special UID 0.
809 if ((pwp->pw_uid == 0) ||
810 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530811#else
812 // Add all users whose UID >=1000 and < 65534
813 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
814#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530815 {
816 std::string userName(pwp->pw_name);
817 userList.emplace_back(userName);
818
819 // ssh doesn't have separate group. Check login shell entry to
820 // get all users list which are member of ssh group.
821 std::string loginShell(pwp->pw_shell);
822 if (loginShell == "/bin/sh")
823 {
824 sshUsersList.emplace_back(userName);
825 }
826 }
827 }
828 endpwent();
829 return std::make_pair(std::move(userList), std::move(sshUsersList));
830}
831
832size_t UserMgr::getIpmiUsersCount()
833{
834 std::vector<std::string> userList = getUsersInGroup("ipmi");
835 return userList.size();
836}
837
Patrick Williams9638afb2021-02-22 17:16:24 -0600838bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530839{
840 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500841 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530842 std::array<char, 4096> buffer{};
843 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600844 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530845 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
846 buffer.max_size(), &resultPtr);
847 if (!status && (&spwd == resultPtr))
848 {
849 if (resultPtr->sp_expire >= 0)
850 {
851 return false; // user locked out
852 }
853 return true;
854 }
855 return false; // assume user is disabled for any error.
856}
857
Patrick Williams9638afb2021-02-22 17:16:24 -0600858std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530859{
860 std::vector<std::string> usersInGroup;
861 // Should be more than enough to get the pwd structure.
862 std::array<char, 4096> buffer{};
863 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600864 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530865
866 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
867 buffer.max_size(), &resultPtr);
868
869 if (!status && (&grp == resultPtr))
870 {
871 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
872 {
873 usersInGroup.emplace_back(*(grp.gr_mem));
874 }
875 }
876 else
877 {
878 log<level::ERR>("Group not found",
879 entry("GROUP=%s", groupName.c_str()));
880 // Don't throw error, just return empty userList - fallback
881 }
882 return usersInGroup;
883}
884
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600885DbusUserObj UserMgr::getPrivilegeMapperObject(void)
886{
887 DbusUserObj objects;
888 try
889 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500890 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
891 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600892
893 auto ldapMgmtService =
894 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600895 auto method = bus.new_method_call(
896 ldapMgmtService.c_str(), ldapMgrObjBasePath,
897 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
898
899 auto reply = bus.call(method);
900 reply.read(objects);
901 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600902 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600903 {
904 log<level::ERR>("Unable to get the User Service",
905 entry("WHAT=%s", e.what()));
906 throw;
907 }
Patrick Williams178c3f62021-09-02 09:50:31 -0500908 catch (const sdbusplus::exception::exception& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600909 {
910 log<level::ERR>(
911 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
912 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
913 throw;
914 }
915 return objects;
916}
917
Patrick Williams9638afb2021-02-22 17:16:24 -0600918std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600919{
920 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600921 {};
922 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600923 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
924 if (buflen < -1)
925 {
926 // Use a default size if there is no hard limit suggested by sysconf()
927 buflen = 1024;
928 }
929 std::vector<char> buffer(buflen);
930 gid_t gid = 0;
931
932 auto status =
933 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
934 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
935 // If no matching password record was found, these functions return 0
936 // and store NULL in *pwdPtr
937 if (!status && (&pwd == pwdPtr))
938 {
939 gid = pwd.pw_gid;
940 }
941 else
942 {
943 log<level::ERR>("User does not exist",
944 entry("USER_NAME=%s", userName.c_str()));
945 elog<UserNameDoesNotExist>();
946 }
947
Patrick Williams9638afb2021-02-22 17:16:24 -0600948 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600949 std::string ldapGroupName;
950
951 while ((groups = getgrent()) != NULL)
952 {
953 if (groups->gr_gid == gid)
954 {
955 ldapGroupName = groups->gr_name;
956 break;
957 }
958 }
959 // Call endgrent() to close the group database.
960 endgrent();
961
962 return ldapGroupName;
963}
964
Patrick Williams9638afb2021-02-22 17:16:24 -0600965std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600966{
967 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
968 objMapperInterface, "GetObject");
969
970 mapperCall.append(std::move(path));
971 mapperCall.append(std::vector<std::string>({std::move(intf)}));
972
973 auto mapperResponseMsg = bus.call(mapperCall);
974
975 if (mapperResponseMsg.is_method_error())
976 {
977 log<level::ERR>("Error in mapper call");
978 elog<InternalFailure>();
979 }
980
981 std::map<std::string, std::vector<std::string>> mapperResponse;
982 mapperResponseMsg.read(mapperResponse);
983
984 if (mapperResponse.begin() == mapperResponse.end())
985 {
986 log<level::ERR>("Invalid response from mapper");
987 elog<InternalFailure>();
988 }
989
990 return mapperResponse.begin()->first;
991}
992
993UserInfoMap UserMgr::getUserInfo(std::string userName)
994{
995 UserInfoMap userInfo;
996 // Check whether the given user is local user or not.
997 if (isUserExist(userName) == true)
998 {
Patrick Williams9638afb2021-02-22 17:16:24 -0600999 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001000 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1001 userInfo.emplace("UserGroups", user.get()->userGroups());
1002 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1003 userInfo.emplace("UserLockedForFailedAttempt",
1004 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001005 userInfo.emplace("UserPasswordExpired",
1006 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001007 userInfo.emplace("RemoteUser", false);
1008 }
1009 else
1010 {
1011 std::string ldapGroupName = getLdapGroupName(userName);
1012 if (ldapGroupName.empty())
1013 {
1014 log<level::ERR>("Unable to get group name",
1015 entry("USER_NAME=%s", userName.c_str()));
1016 elog<InternalFailure>();
1017 }
1018
1019 DbusUserObj objects = getPrivilegeMapperObject();
1020
1021 std::string privilege;
1022 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001023 std::string ldapConfigPath;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001024
1025 try
1026 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001027 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001028 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001029 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001030 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001031 if ((interface.first ==
1032 "xyz.openbmc_project.Object.Enable"))
1033 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001034 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001035 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001036 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001037 if ((property.first == "Enabled") &&
1038 (value == true))
1039 {
1040 ldapConfigPath = obj.first;
1041 break;
1042 }
1043 }
1044 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001045 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001046 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001047 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001048 break;
1049 }
1050 }
1051
1052 if (ldapConfigPath.empty())
1053 {
1054 return userInfo;
1055 }
1056
Patrick Williams9638afb2021-02-22 17:16:24 -06001057 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001058 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001059 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001060 {
1061 if ((interface.first ==
1062 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1063 (obj.first.str.find(ldapConfigPath) !=
1064 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001065 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001066
Patrick Williams9638afb2021-02-22 17:16:24 -06001067 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001068 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001069 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001070 if (property.first == "GroupName")
1071 {
1072 groupName = value;
1073 }
1074 else if (property.first == "Privilege")
1075 {
1076 privilege = value;
1077 }
1078 if (groupName == ldapGroupName)
1079 {
1080 userInfo["UserPrivilege"] = privilege;
1081 }
1082 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001083 }
1084 }
1085 }
1086 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001087
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001088 if (priv.empty())
1089 {
1090 log<level::ERR>("LDAP group privilege mapping does not exist");
1091 }
1092 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001093 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001094 {
1095 log<level::ERR>("Error while accessing variant",
1096 entry("WHAT=%s", e.what()));
1097 elog<InternalFailure>();
1098 }
1099 userInfo.emplace("RemoteUser", true);
1100 }
1101
1102 return userInfo;
1103}
1104
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301105void UserMgr::initUserObjects(void)
1106{
1107 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001108 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301109 std::vector<std::string> userNameList;
1110 std::vector<std::string> sshGrpUsersList;
1111 UserSSHLists userSSHLists = getUserAndSshGrpList();
1112 userNameList = std::move(userSSHLists.first);
1113 sshGrpUsersList = std::move(userSSHLists.second);
1114
1115 if (!userNameList.empty())
1116 {
1117 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001118 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301119 {
1120 if (grp == grpSsh)
1121 {
1122 groupLists.emplace(grp, sshGrpUsersList);
1123 }
1124 else
1125 {
1126 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1127 groupLists.emplace(grp, grpUsersList);
1128 }
1129 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001130 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301131 {
1132 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1133 groupLists.emplace(grp, grpUsersList);
1134 }
1135
Patrick Williams9638afb2021-02-22 17:16:24 -06001136 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301137 {
1138 std::vector<std::string> userGroups;
1139 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001140 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301141 {
1142 std::vector<std::string> tempGrp = grp.second;
1143 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1144 tempGrp.end())
1145 {
1146 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1147 privMgr.end())
1148 {
1149 userPriv = grp.first;
1150 }
1151 else
1152 {
1153 userGroups.emplace_back(grp.first);
1154 }
1155 }
1156 }
1157 // Add user objects to the Users path.
1158 auto objPath = std::string(usersObjPath) + "/" + user;
1159 std::sort(userGroups.begin(), userGroups.end());
1160 usersList.emplace(user,
1161 std::move(std::make_unique<phosphor::user::Users>(
1162 bus, objPath.c_str(), userGroups, userPriv,
1163 isUserEnabled(user), *this)));
1164 }
1165 }
1166}
1167
Patrick Williams9638afb2021-02-22 17:16:24 -06001168UserMgr::UserMgr(sdbusplus::bus::bus& bus, const char* path) :
Ratan Gupta1af12232018-11-03 00:35:38 +05301169 Ifaces(bus, path, true), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301170{
1171 UserMgrIface::allPrivileges(privMgr);
1172 std::sort(groupsMgr.begin(), groupsMgr.end());
1173 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301174 std::string valueStr;
1175 auto value = minPasswdLength;
1176 unsigned long tmp = 0;
1177 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1178 success)
1179 {
1180 AccountPolicyIface::minPasswordLength(minPasswdLength);
1181 }
1182 else
1183 {
1184 try
1185 {
1186 tmp = std::stoul(valueStr, nullptr);
1187 if (tmp > std::numeric_limits<decltype(value)>::max())
1188 {
1189 throw std::out_of_range("Out of range");
1190 }
1191 value = static_cast<decltype(value)>(tmp);
1192 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001193 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301194 {
1195 log<level::ERR>("Exception for MinPasswordLength",
1196 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001197 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301198 }
1199 AccountPolicyIface::minPasswordLength(value);
1200 }
1201 valueStr.clear();
1202 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1203 success)
1204 {
1205 AccountPolicyIface::rememberOldPasswordTimes(0);
1206 }
1207 else
1208 {
1209 value = 0;
1210 try
1211 {
1212 tmp = std::stoul(valueStr, nullptr);
1213 if (tmp > std::numeric_limits<decltype(value)>::max())
1214 {
1215 throw std::out_of_range("Out of range");
1216 }
1217 value = static_cast<decltype(value)>(tmp);
1218 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001219 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301220 {
1221 log<level::ERR>("Exception for RememberOldPasswordTimes",
1222 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001223 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301224 }
1225 AccountPolicyIface::rememberOldPasswordTimes(value);
1226 }
1227 valueStr.clear();
1228 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1229 {
1230 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1231 }
1232 else
1233 {
1234 uint16_t value16 = 0;
1235 try
1236 {
1237 tmp = std::stoul(valueStr, nullptr);
1238 if (tmp > std::numeric_limits<decltype(value16)>::max())
1239 {
1240 throw std::out_of_range("Out of range");
1241 }
1242 value16 = static_cast<decltype(value16)>(tmp);
1243 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001244 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301245 {
1246 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1247 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001248 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301249 }
1250 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1251 }
1252 valueStr.clear();
1253 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1254 {
1255 AccountPolicyIface::accountUnlockTimeout(0);
1256 }
1257 else
1258 {
1259 uint32_t value32 = 0;
1260 try
1261 {
1262 tmp = std::stoul(valueStr, nullptr);
1263 if (tmp > std::numeric_limits<decltype(value32)>::max())
1264 {
1265 throw std::out_of_range("Out of range");
1266 }
1267 value32 = static_cast<decltype(value32)>(tmp);
1268 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001269 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301270 {
1271 log<level::ERR>("Exception for AccountUnlockTimeout",
1272 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001273 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301274 }
1275 AccountPolicyIface::accountUnlockTimeout(value32);
1276 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301277 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301278
1279 // emit the signal
1280 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301281}
1282
1283} // namespace user
1284} // namespace phosphor