1 /*
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
17 #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>
27 #include <shadow.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include <phosphor-logging/elog-errors.hpp>
34 #include <phosphor-logging/elog.hpp>
35 #include <phosphor-logging/lg2.hpp>
36 #include <xyz/openbmc_project/Common/error.hpp>
37 #include <xyz/openbmc_project/User/Common/error.hpp>
38
39 #include <algorithm>
40 #include <array>
41 #include <chrono>
42 #include <ctime>
43 #include <filesystem>
44 #include <fstream>
45 #include <numeric>
46 #include <regex>
47 #include <span>
48 #include <string>
49 #include <string_view>
50 #include <vector>
51 namespace phosphor
52 {
53 namespace user
54 {
55
56 static constexpr const char* passwdFileName = "/etc/passwd";
57 static constexpr size_t ipmiMaxUserNameLen = 16;
58 static constexpr size_t systemMaxUserNameLen = 100;
59 static constexpr const char* grpSsh = "ssh";
60 static constexpr int success = 0;
61 static constexpr int failure = -1;
62 static constexpr long secondsPerDay = 60 * 60 * 24;
63
64 uint8_t maxPasswdLength = MAX_PASSWORD_LENGTH;
65 // pam modules related
66 static constexpr const char* minPasswdLenProp = "minlen";
67 static constexpr const char* remOldPasswdCount = "remember";
68 static constexpr const char* maxFailedAttempt = "deny";
69 static constexpr const char* unlockTimeout = "unlock_time";
70 static constexpr const char* defaultFaillockConfigFile =
71 "/etc/security/faillock.conf";
72 static constexpr const char* defaultPWHistoryConfigFile =
73 "/etc/security/pwhistory.conf";
74 static constexpr const char* defaultPWQualityConfigFile =
75 "/etc/security/pwquality.conf";
76
77 // Object Manager related
78 static constexpr const char* ldapMgrObjBasePath =
79 "/xyz/openbmc_project/user/ldap";
80
81 // Object Mapper related
82 static constexpr const char* objMapperService =
83 "xyz.openbmc_project.ObjectMapper";
84 static constexpr const char* objMapperPath =
85 "/xyz/openbmc_project/object_mapper";
86 static constexpr const char* objMapperInterface =
87 "xyz.openbmc_project.ObjectMapper";
88
89 using namespace phosphor::logging;
90 using InsufficientPermission =
91 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
92 using InternalFailure =
93 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
94 using InvalidArgument =
95 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
96 using UserNameExists =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
98 using UserNameDoesNotExist =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
100 using UserNameGroupFail =
101 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
102 using NoResource =
103 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
104 using Argument = xyz::openbmc_project::Common::InvalidArgument;
105 using GroupNameExists =
106 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
107 using GroupNameDoesNotExists =
108 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
109 using UserProperty =
110 sdbusplus::common::xyz::openbmc_project::user::Manager::UserProperty;
111
112 namespace
113 {
114 constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
115 // The hardcoded groups in OpenBMC projects
116 constexpr std::array<const char*, 4> predefinedGroups = {
117 "redfish", "ipmi", "ssh", "hostconsole"};
118
119 // These prefixes are for Dynamic Redfish authorization. See
120 // https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
121
122 // Base role and base privileges are added by Redfish implementation (e.g.,
123 // BMCWeb) at compile time
124 constexpr std::array<const char*, 4> allowedGroupPrefix = {
125 "openbmc_rfr_", // OpenBMC Redfish Base Role
126 "openbmc_rfp_", // OpenBMC Redfish Base Privileges
127 "openbmc_orfr_", // OpenBMC Redfish OEM Role
128 "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
129 };
130
131 struct SystemUserInfo
132 {
133 struct passwd pwd;
134 std::vector<char> buffer;
135 };
136
checkAndThrowsForGroupChangeAllowed(const std::string & groupName)137 void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
138 {
139 bool allowed = false;
140 for (std::string_view prefix : allowedGroupPrefix)
141 {
142 if (groupName.starts_with(prefix))
143 {
144 allowed = true;
145 break;
146 }
147 }
148 if (!allowed)
149 {
150 lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
151 groupName);
152 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
153 Argument::ARGUMENT_VALUE(groupName.c_str()));
154 }
155 }
156
currentDate()157 long currentDate()
158 {
159 const auto date = std::chrono::duration_cast<std::chrono::days>(
160 std::chrono::system_clock::now().time_since_epoch())
161 .count();
162
163 if (date > std::numeric_limits<long>::max())
164 {
165 return std::numeric_limits<long>::max();
166 }
167
168 if (date < std::numeric_limits<long>::min())
169 {
170 return std::numeric_limits<long>::min();
171 }
172
173 return date;
174 }
175
daysToSeconds(const uint64_t days)176 uint64_t daysToSeconds(const uint64_t days)
177 {
178 const uint64_t dateSeconds =
179 std::chrono::duration_cast<std::chrono::seconds>(
180 std::chrono::days{days})
181 .count();
182
183 return dateSeconds;
184 }
185
secondsToDays(const uint64_t seconds)186 uint64_t secondsToDays(const uint64_t seconds)
187 {
188 const uint64_t dateDays = seconds / secondsPerDay;
189
190 return dateDays;
191 }
192
getSystemUser(const std::string & userName)193 std::unique_ptr<struct SystemUserInfo> getSystemUser(
194 const std::string& userName)
195 {
196 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
197 if (buflen <= 0)
198 {
199 // Use a default size if there is no hard limit suggested by sysconf()
200 buflen = 1024;
201 }
202
203 auto res = std::make_unique<struct SystemUserInfo>();
204 res->buffer = std::vector<char>(buflen);
205
206 struct passwd* pwdPtr = nullptr;
207
208 auto status = getpwnam_r(userName.c_str(), &res->pwd, res->buffer.data(),
209 res->buffer.size(), &pwdPtr);
210 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
211 // If no matching password record was found, these functions return 0
212 // and store NULL in *pwdPtr
213 if (!status && (&res->pwd == pwdPtr))
214 {
215 return res;
216 }
217
218 return nullptr;
219 }
220
221 } // namespace
222
getCSVFromVector(std::span<const std::string> vec)223 std::string getCSVFromVector(std::span<const std::string> vec)
224 {
225 if (vec.empty())
226 {
227 return "";
228 }
229 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
230 [](std::string&& val, std::string_view element) {
231 val += ',';
232 val += element;
233 return val;
234 });
235 }
236
removeStringFromCSV(std::string & csvStr,const std::string & delStr)237 bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
238 {
239 std::string::size_type delStrPos = csvStr.find(delStr);
240 if (delStrPos != std::string::npos)
241 {
242 // need to also delete the comma char
243 if (delStrPos == 0)
244 {
245 csvStr.erase(delStrPos, delStr.size() + 1);
246 }
247 else
248 {
249 csvStr.erase(delStrPos - 1, delStr.size() + 1);
250 }
251 return true;
252 }
253 return false;
254 }
255
isUserExist(const std::string & userName) const256 bool UserMgr::isUserExist(const std::string& userName) const
257 {
258 if (userName.empty())
259 {
260 lg2::error("User name is empty");
261 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
262 Argument::ARGUMENT_VALUE("Null"));
263 }
264 if (usersList.find(userName) == usersList.end())
265 {
266 return false;
267 }
268 return true;
269 }
270
isUserExistSystem(const std::string & userName)271 bool UserMgr::isUserExistSystem(const std::string& userName)
272 {
273 if (userName.empty())
274 {
275 lg2::error("User name is empty");
276 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
277 Argument::ARGUMENT_VALUE("Null"));
278 }
279
280 return getSystemUser(userName) != nullptr;
281 }
282
throwForUserDoesNotExist(const std::string & userName) const283 void UserMgr::throwForUserDoesNotExist(const std::string& userName) const
284 {
285 if (!isUserExist(userName))
286 {
287 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
288 elog<UserNameDoesNotExist>();
289 }
290 }
291
checkAndThrowForDisallowedGroupCreation(const std::string & groupName)292 void UserMgr::checkAndThrowForDisallowedGroupCreation(
293 const std::string& groupName)
294 {
295 if (groupName.size() > maxSystemGroupNameLength ||
296 !std::regex_match(groupName.c_str(),
297 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
298 {
299 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
300 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
301 Argument::ARGUMENT_VALUE(groupName.c_str()));
302 }
303 checkAndThrowsForGroupChangeAllowed(groupName);
304 }
305
throwForUserExists(const std::string & userName)306 void UserMgr::throwForUserExists(const std::string& userName)
307 {
308 if (isUserExist(userName))
309 {
310 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
311 elog<UserNameExists>();
312 }
313 }
314
throwForUserNameConstraints(const std::string & userName,const std::vector<std::string> & groupNames)315 void UserMgr::throwForUserNameConstraints(
316 const std::string& userName, const std::vector<std::string>& groupNames)
317 {
318 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
319 groupNames.end())
320 {
321 if (userName.length() > ipmiMaxUserNameLen)
322 {
323 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
324 "({LENGTH} > {LIMIT})",
325 "USERNAME", userName, "LENGTH", userName.length(),
326 "LIMIT", ipmiMaxUserNameLen);
327 elog<UserNameGroupFail>(
328 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
329 "IPMI length"));
330 }
331 }
332 if (userName.length() > systemMaxUserNameLen)
333 {
334 lg2::error("User '{USERNAME}' exceeds system username length limit "
335 "({LENGTH} > {LIMIT})",
336 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
337 systemMaxUserNameLen);
338 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
339 Argument::ARGUMENT_VALUE("Invalid length"));
340 }
341 if (!std::regex_match(userName.c_str(),
342 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
343 {
344 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
345 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
346 Argument::ARGUMENT_VALUE("Invalid data"));
347 }
348 }
349
throwForMaxGrpUserCount(const std::vector<std::string> & groupNames)350 void UserMgr::throwForMaxGrpUserCount(
351 const std::vector<std::string>& groupNames)
352 {
353 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
354 groupNames.end())
355 {
356 if (getIpmiUsersCount() >= ipmiMaxUsers)
357 {
358 lg2::error("IPMI user limit reached");
359 elog<NoResource>(
360 xyz::openbmc_project::User::Common::NoResource::REASON(
361 "IPMI user limit reached"));
362 }
363 }
364 else
365 {
366 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
367 (maxSystemUsers - ipmiMaxUsers))
368 {
369 lg2::error("Non-ipmi User limit reached");
370 elog<NoResource>(
371 xyz::openbmc_project::User::Common::NoResource::REASON(
372 "Non-ipmi user limit reached"));
373 }
374 }
375 return;
376 }
377
throwForInvalidPrivilege(const std::string & priv)378 void UserMgr::throwForInvalidPrivilege(const std::string& priv)
379 {
380 if (!priv.empty() &&
381 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
382 {
383 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
384 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
385 Argument::ARGUMENT_VALUE(priv.c_str()));
386 }
387 }
388
throwForInvalidGroups(const std::vector<std::string> & groupNames)389 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
390 {
391 for (auto& group : groupNames)
392 {
393 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
394 groupsMgr.end())
395 {
396 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
397 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
398 Argument::ARGUMENT_VALUE(group.c_str()));
399 }
400 }
401 }
402
readAllGroupsOnSystem()403 std::vector<std::string> UserMgr::readAllGroupsOnSystem()
404 {
405 std::vector<std::string> allGroups = {predefinedGroups.begin(),
406 predefinedGroups.end()};
407 // rewinds to the beginning of the group database
408 setgrent();
409 struct group* gr = getgrent();
410 while (gr != nullptr)
411 {
412 std::string group(gr->gr_name);
413 for (std::string_view prefix : allowedGroupPrefix)
414 {
415 if (group.starts_with(prefix))
416 {
417 allGroups.push_back(gr->gr_name);
418 }
419 }
420 gr = getgrent();
421 }
422 // close the group database
423 endgrent();
424 return allGroups;
425 }
426
createUserImpl(const std::string & userName,UserCreateMap props)427 void UserMgr::createUserImpl(const std::string& userName, UserCreateMap props)
428 {
429 auto priv = std::get<std::string>(props[UserProperty::Privilege]);
430 auto enabled = std::get<bool>(props[UserProperty::Enabled]);
431 auto groupNames =
432 std::get<std::vector<std::string>>(props[UserProperty::GroupNames]);
433
434 auto passwordExpiration = getDefaultPasswordExpiration();
435 if (props.contains(UserProperty::PasswordExpiration))
436 passwordExpiration =
437 std::get<uint64_t>(props[UserProperty::PasswordExpiration]);
438
439 throwForInvalidPrivilege(priv);
440 throwForInvalidGroups(groupNames);
441 // All user management lock has to be based on /etc/shadow
442 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
443 throwForUserExists(userName);
444 throwForUserNameConstraints(userName, groupNames);
445 throwForMaxGrpUserCount(groupNames);
446
447 std::string groups = getCSVFromVector(groupNames);
448 bool sshRequested = removeStringFromCSV(groups, grpSsh);
449
450 // treat privilege as a group - This is to avoid using different file to
451 // store the same.
452 if (!priv.empty())
453 {
454 if (groups.size() != 0)
455 {
456 groups += ",";
457 }
458 groups += priv;
459 }
460 try
461 {
462 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
463 }
464 catch (const InternalFailure& e)
465 {
466 if (isUserExistSystem(userName))
467 {
468 lg2::warning(
469 "User created despite error, attempting to delete user",
470 "USERNAME", userName);
471 executeUserDelete(userName.c_str());
472 }
473 else
474 {
475 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
476 userName);
477 }
478 elog<InternalFailure>();
479 }
480
481 // Add the users object before sending out the signal
482 sdbusplus::message::object_path tempObjPath(usersObjPath);
483 tempObjPath /= userName;
484 std::string userObj(tempObjPath);
485 std::sort(groupNames.begin(), groupNames.end());
486 usersList.emplace(userName, std::make_unique<phosphor::user::Users>(
487 bus, userObj.c_str(), groupNames, priv,
488 enabled, passwordExpiration, *this));
489 serializer.store();
490 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
491
492 return;
493 }
494
createUser(std::string userName,std::vector<std::string> groupNames,std::string priv,bool enabled)495 void UserMgr::createUser(std::string userName,
496 std::vector<std::string> groupNames, std::string priv,
497 bool enabled)
498 {
499 UserCreateMap props;
500 props[UserProperty::GroupNames] = std::move(groupNames);
501 props[UserProperty::Privilege] = std::move(priv);
502 props[UserProperty::Enabled] = enabled;
503
504 createUserImpl(userName, props);
505 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
506 }
507
deleteUserImpl(const std::string & userName)508 void UserMgr::deleteUserImpl(const std::string& userName)
509 {
510 // All user management lock has to be based on /etc/shadow
511 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
512 try
513 {
514 // Clear user fail records
515 executeUserClearFailRecords(userName.c_str());
516
517 executeUserDelete(userName.c_str());
518 }
519 catch (const InternalFailure& e)
520 {
521 if (!isUserExistSystem(userName))
522 {
523 lg2::warning(
524 "Delete User '{USERNAME}' failed, and user is no longer present, treating as success",
525 "USERNAME", userName);
526 }
527 else
528 {
529 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
530 elog<InternalFailure>();
531 }
532 }
533
534 usersList.erase(userName);
535 serializer.store();
536 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
537 return;
538 }
539
deleteUser(std::string userName)540 void UserMgr::deleteUser(std::string userName)
541 {
542 throwForUserDoesNotExist(userName);
543 deleteUserImpl(userName);
544 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
545 }
546
checkDeleteGroupConstraints(const std::string & groupName)547 void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
548 {
549 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
550 groupsMgr.end())
551 {
552 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
553 elog<GroupNameDoesNotExists>();
554 }
555 checkAndThrowsForGroupChangeAllowed(groupName);
556 }
557
deleteGroup(std::string groupName)558 void UserMgr::deleteGroup(std::string groupName)
559 {
560 checkDeleteGroupConstraints(groupName);
561 try
562 {
563 executeGroupDeletion(groupName.c_str());
564 }
565 catch (const InternalFailure& e)
566 {
567 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
568 elog<InternalFailure>();
569 }
570
571 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
572 UserMgrIface::allGroups(groupsMgr);
573 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
574 }
575
checkCreateGroupConstraints(const std::string & groupName)576 void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
577 {
578 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
579 groupsMgr.end())
580 {
581 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
582 elog<GroupNameExists>();
583 }
584 checkAndThrowForDisallowedGroupCreation(groupName);
585 if (groupsMgr.size() >= maxSystemGroupCount)
586 {
587 lg2::error("Group limit reached");
588 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
589 "Group limit reached"));
590 }
591 }
592
createGroup(std::string groupName)593 void UserMgr::createGroup(std::string groupName)
594 {
595 checkCreateGroupConstraints(groupName);
596 try
597 {
598 executeGroupCreation(groupName.c_str());
599 }
600 catch (const InternalFailure& e)
601 {
602 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
603 elog<InternalFailure>();
604 }
605 groupsMgr.push_back(groupName);
606 UserMgrIface::allGroups(groupsMgr);
607 }
608
renameUser(std::string userName,std::string newUserName)609 void UserMgr::renameUser(std::string userName, std::string newUserName)
610 {
611 bool err = false;
612 // All user management lock has to be based on /etc/shadow
613 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
614 throwForUserDoesNotExist(userName);
615 throwForUserExists(newUserName);
616 throwForUserNameConstraints(newUserName,
617 usersList[userName].get()->userGroups());
618 try
619 {
620 executeUserRename(userName.c_str(), newUserName.c_str());
621 }
622 catch (const InternalFailure& e)
623 {
624 if (isUserExistSystem(newUserName))
625 {
626 lg2::error(
627 "Rename '{USERNAME}' to '{NEWUSERNAME}' partially failed",
628 "USERNAME", userName, "NEWUSERNAME", newUserName);
629 err = true;
630 }
631 else
632 {
633 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed",
634 "USERNAME", userName, "NEWUSERNAME", newUserName);
635 elog<InternalFailure>();
636 }
637 }
638 const auto& user = usersList[userName];
639 std::string priv = user.get()->userPrivilege();
640 std::vector<std::string> groupNames = user.get()->userGroups();
641 bool enabled = user.get()->userEnabled();
642 uint64_t passwordExpiration = user.get()->passwordExpiration();
643 sdbusplus::message::object_path tempObjPath(usersObjPath);
644 tempObjPath /= newUserName;
645 std::string newUserObj(tempObjPath);
646 // Special group 'ipmi' needs a way to identify user renamed, in order to
647 // update encrypted password. It can't rely only on InterfacesRemoved &
648 // InterfacesAdded. So first send out userRenamed signal.
649 this->userRenamed(userName, newUserName);
650 usersList.erase(userName);
651 usersList.emplace(newUserName,
652 std::make_unique<phosphor::user::Users>(
653 bus, newUserObj.c_str(), groupNames, priv, enabled,
654 passwordExpiration, *this));
655
656 if (err)
657 {
658 elog<InternalFailure>();
659 }
660 return;
661 }
662
updateGroupsAndPriv(const std::string & userName,std::vector<std::string> groupNames,const std::string & priv)663 void UserMgr::updateGroupsAndPriv(const std::string& userName,
664 std::vector<std::string> groupNames,
665 const std::string& priv)
666 {
667 throwForInvalidPrivilege(priv);
668 throwForInvalidGroups(groupNames);
669 // All user management lock has to be based on /etc/shadow
670 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
671 throwForUserDoesNotExist(userName);
672 const std::vector<std::string>& oldGroupNames =
673 usersList[userName].get()->userGroups();
674 std::vector<std::string> groupDiff;
675 // Note: already dealing with sorted group lists.
676 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
677 groupNames.begin(), groupNames.end(),
678 std::back_inserter(groupDiff));
679 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
680 groupDiff.end())
681 {
682 throwForUserNameConstraints(userName, groupNames);
683 throwForMaxGrpUserCount(groupNames);
684 }
685
686 std::string groups = getCSVFromVector(groupNames);
687 bool sshRequested = removeStringFromCSV(groups, grpSsh);
688
689 // treat privilege as a group - This is to avoid using different file to
690 // store the same.
691 if (!priv.empty())
692 {
693 if (groups.size() != 0)
694 {
695 groups += ",";
696 }
697 groups += priv;
698 }
699 try
700 {
701 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
702 }
703 catch (const InternalFailure& e)
704 {
705 lg2::error(
706 "Unable to modify user privilege / groups for user '{USERNAME}'",
707 "USERNAME", userName);
708 elog<InternalFailure>();
709 }
710
711 std::sort(groupNames.begin(), groupNames.end());
712 usersList[userName]->setUserGroups(groupNames);
713 usersList[userName]->setUserPrivilege(priv);
714 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
715 "USERNAME", userName);
716 }
717
minPasswordLength(uint8_t value)718 uint8_t UserMgr::minPasswordLength(uint8_t value)
719 {
720 if (value == AccountPolicyIface::minPasswordLength())
721 {
722 return value;
723 }
724 if (value < minPasswdLength || value > maxPasswdLength)
725 {
726 std::string valueStr = std::to_string(value);
727 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
728 "{MINPASSWORDLENGTH} or greater than {MAXPASSWORDLENGTH}",
729 "VALUE", value, "MINPASSWORDLENGTH", minPasswdLength,
730 "MAXPASSWORDLENGTH", maxPasswdLength);
731 elog<InvalidArgument>(Argument::ARGUMENT_NAME("minPasswordLength"),
732 Argument::ARGUMENT_VALUE(valueStr.data()));
733 }
734 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
735 std::to_string(value)) != success)
736 {
737 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
738 value);
739 elog<InternalFailure>();
740 }
741 return AccountPolicyIface::minPasswordLength(value);
742 }
743
rememberOldPasswordTimes(uint8_t value)744 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
745 {
746 if (value == AccountPolicyIface::rememberOldPasswordTimes())
747 {
748 return value;
749 }
750 if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
751 std::to_string(value)) != success)
752 {
753 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
754 value);
755 elog<InternalFailure>();
756 }
757 return AccountPolicyIface::rememberOldPasswordTimes(value);
758 }
759
maxLoginAttemptBeforeLockout(uint16_t value)760 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
761 {
762 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
763 {
764 return value;
765 }
766 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
767 std::to_string(value)) != success)
768 {
769 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
770 "VALUE", value);
771 elog<InternalFailure>();
772 }
773 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
774 }
775
accountUnlockTimeout(uint32_t value)776 uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
777 {
778 if (value == AccountPolicyIface::accountUnlockTimeout())
779 {
780 return value;
781 }
782 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
783 std::to_string(value)) != success)
784 {
785 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
786 value);
787 elog<InternalFailure>();
788 }
789 return AccountPolicyIface::accountUnlockTimeout(value);
790 }
791
getPamModuleConfValue(const std::string & confFile,const std::string & argName,std::string & argValue)792 int UserMgr::getPamModuleConfValue(const std::string& confFile,
793 const std::string& argName,
794 std::string& argValue)
795 {
796 std::ifstream fileToRead(confFile, std::ios::in);
797 if (!fileToRead.is_open())
798 {
799 lg2::error("Failed to open pam configuration file {FILENAME}",
800 "FILENAME", confFile);
801 return failure;
802 }
803 std::string line;
804 auto argSearch = argName + "=";
805 size_t startPos = 0;
806 size_t endPos = 0;
807 while (getline(fileToRead, line))
808 {
809 // skip comments section starting with #
810 if ((startPos = line.find('#')) != std::string::npos)
811 {
812 if (startPos == 0)
813 {
814 continue;
815 }
816 // skip comments after meaningful section and process those
817 line = line.substr(0, startPos);
818 }
819 if ((startPos = line.find(argSearch)) != std::string::npos)
820 {
821 if ((endPos = line.find(' ', startPos)) == std::string::npos)
822 {
823 endPos = line.size();
824 }
825 startPos += argSearch.size();
826 argValue = line.substr(startPos, endPos - startPos);
827 return success;
828 }
829 }
830 return failure;
831 }
832
setPamModuleConfValue(const std::string & confFile,const std::string & argName,const std::string & argValue)833 int UserMgr::setPamModuleConfValue(const std::string& confFile,
834 const std::string& argName,
835 const std::string& argValue)
836 {
837 std::string tmpConfFile = confFile + "_tmp";
838 std::ifstream fileToRead(confFile, std::ios::in);
839 std::ofstream fileToWrite(tmpConfFile, std::ios::out);
840 if (!fileToRead.is_open() || !fileToWrite.is_open())
841 {
842 lg2::error("Failed to open pam configuration file {FILENAME}",
843 "FILENAME", confFile);
844 // Delete the unused tmp file
845 std::remove(tmpConfFile.c_str());
846 return failure;
847 }
848 std::string line;
849 auto argSearch = argName + "=";
850 size_t startPos = 0;
851 size_t endPos = 0;
852 bool found = false;
853 while (getline(fileToRead, line))
854 {
855 // skip comments section starting with #
856 if ((startPos = line.find('#')) != std::string::npos)
857 {
858 if (startPos == 0)
859 {
860 fileToWrite << line << std::endl;
861 continue;
862 }
863 // skip comments after meaningful section and process those
864 line = line.substr(0, startPos);
865 }
866 if ((startPos = line.find(argSearch)) != std::string::npos)
867 {
868 if ((endPos = line.find(' ', startPos)) == std::string::npos)
869 {
870 endPos = line.size();
871 }
872 startPos += argSearch.size();
873 fileToWrite << line.substr(0, startPos) << argValue
874 << line.substr(endPos, line.size() - endPos)
875 << std::endl;
876 found = true;
877 continue;
878 }
879 fileToWrite << line << std::endl;
880 }
881 fileToWrite.close();
882 fileToRead.close();
883 if (found)
884 {
885 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
886 {
887 return success;
888 }
889 }
890 // No changes, so delete the unused tmp file
891 std::remove(tmpConfFile.c_str());
892 return failure;
893 }
894
userEnable(const std::string & userName,bool enabled)895 void UserMgr::userEnable(const std::string& userName, bool enabled)
896 {
897 // All user management lock has to be based on /etc/shadow
898 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
899 throwForUserDoesNotExist(userName);
900 try
901 {
902 executeUserModifyUserEnable(userName.c_str(), enabled);
903 }
904 catch (const InternalFailure& e)
905 {
906 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
907 "USERNAME", userName);
908 elog<InternalFailure>();
909 }
910
911 usersList[userName]->setUserEnabled(enabled);
912 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
913 "STATUS", enabled ? "Enabled" : "Disabled");
914 }
915
916 /**
917 * faillock app will provide the user failed login list with when the attempt
918 * was made, the type, the source, and if it's valid.
919 *
920 * Valid in this case means that the attempt was made within the fail_interval
921 * time. So, we can check this list for the number of valid entries (lines
922 * ending with 'V') compared to the maximum allowed to determine if the user is
923 * locked out.
924 *
925 * This data is only refreshed when an attempt is made, so if the user appears
926 * to be locked out, we must also check if the most recent attempt was older
927 * than the unlock_time to know if the user has since been unlocked.
928 **/
parseFaillockForLockout(const std::vector<std::string> & faillockOutput)929 bool UserMgr::parseFaillockForLockout(
930 const std::vector<std::string>& faillockOutput)
931 {
932 uint16_t failAttempts = 0;
933 time_t lastFailedAttempt{};
934 for (const std::string& line : faillockOutput)
935 {
936 if (!line.ends_with("V"))
937 {
938 continue;
939 }
940
941 // Count this failed attempt
942 failAttempts++;
943
944 // Update the last attempt time
945 // First get the "when" which is the first two words (date and time)
946 size_t pos = line.find(" ");
947 if (pos == std::string::npos)
948 {
949 continue;
950 }
951 pos = line.find(" ", pos + 1);
952 if (pos == std::string::npos)
953 {
954 continue;
955 }
956 std::string failDateTime = line.substr(0, pos);
957
958 // NOTE: Cannot use std::get_time() here as the implementation of %y in
959 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
960 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
961 std::tm tmStruct = {};
962 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
963 {
964 lg2::error("Failed to parse latest failure date/time");
965 elog<InternalFailure>();
966 }
967
968 time_t failTimestamp = std::mktime(&tmStruct);
969 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
970 }
971
972 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
973 {
974 return false;
975 }
976 uint32_t unlockTimeout = AccountPolicyIface::accountUnlockTimeout();
977 if (unlockTimeout == 0)
978 {
979 return true;
980 }
981 if (lastFailedAttempt + static_cast<time_t>(unlockTimeout) <=
982 std::time(NULL))
983 {
984 return false;
985 }
986
987 return true;
988 }
989
userLockedForFailedAttempt(const std::string & userName)990 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
991 {
992 // All user management lock has to be based on /etc/shadow
993 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
994 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
995 {
996 return false;
997 }
998
999 std::vector<std::string> output;
1000 try
1001 {
1002 output = getFailedAttempt(userName.c_str());
1003 }
1004 catch (const InternalFailure& e)
1005 {
1006 lg2::error("Unable to read login failure counter");
1007 elog<InternalFailure>();
1008 }
1009
1010 return parseFaillockForLockout(output);
1011 }
1012
userLockedForFailedAttempt(const std::string & userName,const bool & value)1013 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
1014 const bool& value)
1015 {
1016 // All user management lock has to be based on /etc/shadow
1017 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1018 if (value == true)
1019 {
1020 return userLockedForFailedAttempt(userName);
1021 }
1022
1023 try
1024 {
1025 // Clear user fail records
1026 executeUserClearFailRecords(userName.c_str());
1027 }
1028 catch (const InternalFailure& e)
1029 {
1030 lg2::error("Unable to reset login failure counter");
1031 elog<InternalFailure>();
1032 }
1033
1034 return userLockedForFailedAttempt(userName);
1035 }
1036
userPasswordExpired(const std::string & userName)1037 bool UserMgr::userPasswordExpired(const std::string& userName)
1038 {
1039 // All user management lock has to be based on /etc/shadow
1040 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1041
1042 struct spwd spwd{};
1043 struct spwd* spwdPtr = nullptr;
1044 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1045 if (buflen <= 0)
1046 {
1047 // Use a default size if there is no hard limit suggested by sysconf()
1048 buflen = 1024;
1049 }
1050 std::vector<char> buffer(buflen);
1051 auto status =
1052 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
1053 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
1054 // If no matching password record was found, these functions return 0
1055 // and store NULL in *spwdPtr
1056 if ((status == 0) && (&spwd == spwdPtr))
1057 {
1058 // Determine password validity per "chage" docs, where:
1059 // spwd.sp_lstchg == 0 means password is expired, and
1060 // spwd.sp_max == -1 means the password does not expire.
1061 long today = static_cast<long>(time(NULL)) / secondsPerDay;
1062 if ((spwd.sp_lstchg == 0) ||
1063 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
1064 {
1065 return true;
1066 }
1067 }
1068 else
1069 {
1070 // User entry is missing in /etc/shadow, indicating no SHA password.
1071 // Treat this as new user without password entry in /etc/shadow
1072 // TODO: Add property to indicate user password was not set yet
1073 // https://github.com/openbmc/phosphor-user-manager/issues/8
1074 return false;
1075 }
1076
1077 return false;
1078 }
1079
getUserAndSshGrpList()1080 UserSSHLists UserMgr::getUserAndSshGrpList()
1081 {
1082 // All user management lock has to be based on /etc/shadow
1083 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1084
1085 std::vector<std::string> userList;
1086 std::vector<std::string> sshUsersList;
1087 struct passwd pw, *pwp = nullptr;
1088 std::array<char, 1024> buffer{};
1089
1090 phosphor::user::File passwd(passwdFileName, "r");
1091 if ((passwd)() == NULL)
1092 {
1093 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
1094 elog<InternalFailure>();
1095 }
1096
1097 while (true)
1098 {
1099 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
1100 &pwp);
1101 if ((r != 0) || (pwp == NULL))
1102 {
1103 // Any error, break the loop.
1104 break;
1105 }
1106 #ifdef ENABLE_ROOT_USER_MGMT
1107 // Add all users whose UID >= 1000 and < 65534
1108 // and special UID 0.
1109 if ((pwp->pw_uid == 0) ||
1110 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
1111 #else
1112 // Add all users whose UID >=1000 and < 65534
1113 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
1114 #endif
1115 {
1116 std::string userName(pwp->pw_name);
1117 userList.emplace_back(userName);
1118
1119 // ssh doesn't have separate group. Check login shell entry to
1120 // get all users list which are member of ssh group.
1121 std::string loginShell(pwp->pw_shell);
1122 if (loginShell == "/bin/sh")
1123 {
1124 sshUsersList.emplace_back(userName);
1125 }
1126 }
1127 }
1128 endpwent();
1129 return std::make_pair(std::move(userList), std::move(sshUsersList));
1130 }
1131
getIpmiUsersCount()1132 size_t UserMgr::getIpmiUsersCount()
1133 {
1134 std::vector<std::string> userList = getUsersInGroup("ipmi");
1135 return userList.size();
1136 }
1137
getNonIpmiUsersCount()1138 size_t UserMgr::getNonIpmiUsersCount()
1139 {
1140 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1141 return usersList.size() - ipmiUsers.size();
1142 }
1143
isUserEnabled(const std::string & userName)1144 bool UserMgr::isUserEnabled(const std::string& userName)
1145 {
1146 // All user management lock has to be based on /etc/shadow
1147 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1148 std::array<char, 4096> buffer{};
1149 struct spwd spwd;
1150 struct spwd* resultPtr = nullptr;
1151 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1152 buffer.max_size(), &resultPtr);
1153 if (!status && (&spwd == resultPtr))
1154 {
1155 // according to chage/usermod code -1 means that account does not expire
1156 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c
1157 if (resultPtr->sp_expire < 0)
1158 {
1159 return true;
1160 }
1161
1162 // check account expiration date against current date
1163 if (resultPtr->sp_expire > currentDate())
1164 {
1165 return true;
1166 }
1167
1168 return false;
1169 }
1170 return false; // assume user is disabled for any error.
1171 }
1172
getUsersInGroup(const std::string & groupName)1173 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
1174 {
1175 std::vector<std::string> usersInGroup;
1176 // Should be more than enough to get the pwd structure.
1177 std::array<char, 4096> buffer{};
1178 struct group grp;
1179 struct group* resultPtr = nullptr;
1180
1181 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1182 buffer.max_size(), &resultPtr);
1183
1184 if (!status && (&grp == resultPtr))
1185 {
1186 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1187 {
1188 usersInGroup.emplace_back(*(grp.gr_mem));
1189 }
1190 }
1191 else
1192 {
1193 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
1194 // Don't throw error, just return empty userList - fallback
1195 }
1196 return usersInGroup;
1197 }
1198
getPrivilegeMapperObject(void)1199 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1200 {
1201 DbusUserObj objects;
1202 try
1203 {
1204 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1205 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
1206
1207 auto ldapMgmtService =
1208 getServiceName(std::move(basePath), std::move(interface));
1209 auto method = bus.new_method_call(
1210 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1211 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1212
1213 auto reply = bus.call(method);
1214 reply.read(objects);
1215 }
1216 catch (const InternalFailure& e)
1217 {
1218 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
1219 throw;
1220 }
1221 catch (const sdbusplus::exception_t& e)
1222 {
1223 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
1224 "PATH", ldapMgrObjBasePath, "ERR", e);
1225 throw;
1226 }
1227 return objects;
1228 }
1229
getServiceName(std::string && path,std::string && intf)1230 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1231 {
1232 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1233 objMapperInterface, "GetObject");
1234
1235 mapperCall.append(std::move(path));
1236 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1237
1238 std::map<std::string, std::vector<std::string>> mapperResponse;
1239 try
1240 {
1241 auto mapperResponseMsg = bus.call(mapperCall);
1242 mapperResponseMsg.read(mapperResponse);
1243 }
1244 catch (const sdbusplus::exception_t& e)
1245 {
1246 lg2::error("Error in mapper call: {ERROR}", "ERROR", e.what());
1247 elog<InternalFailure>();
1248 }
1249
1250 if (mapperResponse.begin() == mapperResponse.end())
1251 {
1252 lg2::error("Invalid response from mapper");
1253 elog<InternalFailure>();
1254 }
1255
1256 return mapperResponse.begin()->first;
1257 }
1258
getPrimaryGroup(const std::string & userName) const1259 gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1260 {
1261 auto systemUser = getSystemUser(userName);
1262 if (systemUser)
1263 {
1264 return systemUser->pwd.pw_gid;
1265 }
1266
1267 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
1268 elog<UserNameDoesNotExist>();
1269 }
1270
isGroupMember(const std::string & userName,gid_t primaryGid,const std::string & groupName) const1271 bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1272 const std::string& groupName) const
1273 {
1274 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1275 if (buflen <= 0)
1276 {
1277 // Use a default size if there is no hard limit suggested by sysconf()
1278 buflen = 1024;
1279 }
1280
1281 struct group grp;
1282 struct group* grpPtr = nullptr;
1283 std::vector<char> buffer(buflen);
1284
1285 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1286 buffer.size(), &grpPtr);
1287
1288 // Groups with a lot of members may require a buffer of bigger size than
1289 // suggested by _SC_GETGR_R_SIZE_MAX.
1290 // 32K should be enough for about 2K members.
1291 constexpr auto maxBufferLength = 32 * 1024;
1292 while (status == ERANGE && buflen < maxBufferLength)
1293 {
1294 buflen *= 2;
1295 buffer.resize(buflen);
1296
1297 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1298 buflen);
1299
1300 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1301 buffer.size(), &grpPtr);
1302 }
1303
1304 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1305 // If no matching group record was found, these functions return 0
1306 // and store NULL in *grpPtr
1307 if (!status && (&grp == grpPtr))
1308 {
1309 if (primaryGid == grp.gr_gid)
1310 {
1311 return true;
1312 }
1313
1314 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1315 {
1316 if (userName == grp.gr_mem[i])
1317 {
1318 return true;
1319 }
1320 }
1321 }
1322 else if (status == ERANGE)
1323 {
1324 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1325 groupName);
1326 }
1327 else
1328 {
1329 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
1330 }
1331
1332 return false;
1333 }
1334
executeGroupCreation(const char * groupName)1335 void UserMgr::executeGroupCreation(const char* groupName)
1336 {
1337 executeCmd("/usr/sbin/groupadd", groupName);
1338 }
1339
executeGroupDeletion(const char * groupName)1340 void UserMgr::executeGroupDeletion(const char* groupName)
1341 {
1342 executeCmd("/usr/sbin/groupdel", groupName);
1343 }
1344
getUserInfo(std::string userName)1345 UserInfoMap UserMgr::getUserInfo(std::string userName)
1346 {
1347 UserInfoMap userInfo;
1348 // Check whether the given user is local user or not.
1349 if (isUserExist(userName))
1350 {
1351 const auto& user = usersList[userName];
1352 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1353 userInfo.emplace("UserGroups", user.get()->userGroups());
1354 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1355 userInfo.emplace("UserLockedForFailedAttempt",
1356 user.get()->userLockedForFailedAttempt());
1357 userInfo.emplace("UserPasswordExpired",
1358 user.get()->userPasswordExpired());
1359 userInfo.emplace("TOTPSecretkeyRequired",
1360 user.get()->secretKeyGenerationRequired());
1361 userInfo.emplace("PasswordExpiration",
1362 user.get()->passwordExpiration());
1363 userInfo.emplace("RemoteUser", false);
1364 }
1365 else
1366 {
1367 auto primaryGid = getPrimaryGroup(userName);
1368
1369 DbusUserObj objects = getPrivilegeMapperObject();
1370
1371 std::string ldapConfigPath;
1372 std::string userPrivilege;
1373
1374 try
1375 {
1376 for (const auto& [path, interfaces] : objects)
1377 {
1378 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1379 if (it != interfaces.end())
1380 {
1381 auto propIt = it->second.find("Enabled");
1382 if (propIt != it->second.end() &&
1383 std::get<bool>(propIt->second))
1384 {
1385 ldapConfigPath = path.str + '/';
1386 break;
1387 }
1388 }
1389 }
1390
1391 if (ldapConfigPath.empty())
1392 {
1393 return userInfo;
1394 }
1395
1396 for (const auto& [path, interfaces] : objects)
1397 {
1398 if (!path.str.starts_with(ldapConfigPath))
1399 {
1400 continue;
1401 }
1402
1403 auto it = interfaces.find(
1404 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1405 if (it != interfaces.end())
1406 {
1407 std::string privilege;
1408 std::string groupName;
1409
1410 for (const auto& [propName, propValue] : it->second)
1411 {
1412 if (propName == "GroupName")
1413 {
1414 groupName = std::get<std::string>(propValue);
1415 }
1416 else if (propName == "Privilege")
1417 {
1418 privilege = std::get<std::string>(propValue);
1419 }
1420 }
1421
1422 if (!groupName.empty() && !privilege.empty() &&
1423 isGroupMember(userName, primaryGid, groupName))
1424 {
1425 userPrivilege = privilege;
1426 break;
1427 }
1428 }
1429 if (!userPrivilege.empty())
1430 {
1431 break;
1432 }
1433 }
1434
1435 if (userPrivilege.empty())
1436 {
1437 lg2::warning("LDAP group privilege mapping does not exist");
1438 }
1439 userInfo.emplace("UserPrivilege", userPrivilege);
1440 }
1441 catch (const std::bad_variant_access& e)
1442 {
1443 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
1444 elog<InternalFailure>();
1445 }
1446 userInfo.emplace("RemoteUser", true);
1447 }
1448
1449 return userInfo;
1450 }
1451
initializeAccountPolicy()1452 void UserMgr::initializeAccountPolicy()
1453 {
1454 std::string valueStr;
1455 auto value = minPasswdLength;
1456 unsigned long tmp = 0;
1457 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1458 valueStr) != success)
1459 {
1460 AccountPolicyIface::minPasswordLength(minPasswdLength);
1461 }
1462 else
1463 {
1464 try
1465 {
1466 tmp = std::stoul(valueStr, nullptr);
1467 if (tmp > std::numeric_limits<decltype(value)>::max())
1468 {
1469 throw std::out_of_range("Out of range");
1470 }
1471 value = static_cast<decltype(value)>(tmp);
1472 }
1473 catch (const std::exception& e)
1474 {
1475 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
1476 throw;
1477 }
1478 AccountPolicyIface::minPasswordLength(value);
1479 }
1480 valueStr.clear();
1481 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1482 valueStr) != success)
1483 {
1484 AccountPolicyIface::rememberOldPasswordTimes(0);
1485 }
1486 else
1487 {
1488 value = 0;
1489 try
1490 {
1491 tmp = std::stoul(valueStr, nullptr);
1492 if (tmp > std::numeric_limits<decltype(value)>::max())
1493 {
1494 throw std::out_of_range("Out of range");
1495 }
1496 value = static_cast<decltype(value)>(tmp);
1497 }
1498 catch (const std::exception& e)
1499 {
1500 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1501 e);
1502 throw;
1503 }
1504 AccountPolicyIface::rememberOldPasswordTimes(value);
1505 }
1506 valueStr.clear();
1507 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1508 success)
1509 {
1510 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1511 }
1512 else
1513 {
1514 uint16_t value16 = 0;
1515 try
1516 {
1517 tmp = std::stoul(valueStr, nullptr);
1518 if (tmp > std::numeric_limits<decltype(value16)>::max())
1519 {
1520 throw std::out_of_range("Out of range");
1521 }
1522 value16 = static_cast<decltype(value16)>(tmp);
1523 }
1524 catch (const std::exception& e)
1525 {
1526 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1527 "ERR", e);
1528 throw;
1529 }
1530 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1531 }
1532 valueStr.clear();
1533 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1534 success)
1535 {
1536 AccountPolicyIface::accountUnlockTimeout(0);
1537 }
1538 else
1539 {
1540 uint32_t value32 = 0;
1541 try
1542 {
1543 tmp = std::stoul(valueStr, nullptr);
1544 if (tmp > std::numeric_limits<decltype(value32)>::max())
1545 {
1546 throw std::out_of_range("Out of range");
1547 }
1548 value32 = static_cast<decltype(value32)>(tmp);
1549 }
1550 catch (const std::exception& e)
1551 {
1552 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
1553 throw;
1554 }
1555 AccountPolicyIface::accountUnlockTimeout(value32);
1556 }
1557 }
1558
initUserObjects(void)1559 void UserMgr::initUserObjects(void)
1560 {
1561 // All user management lock has to be based on /etc/shadow
1562 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1563 std::vector<std::string> userNameList;
1564 std::vector<std::string> sshGrpUsersList;
1565 UserSSHLists userSSHLists = getUserAndSshGrpList();
1566 userNameList = std::move(userSSHLists.first);
1567 sshGrpUsersList = std::move(userSSHLists.second);
1568
1569 if (!userNameList.empty())
1570 {
1571 std::map<std::string, std::vector<std::string>> groupLists;
1572 // We only track users that are in the |predefinedGroups|
1573 // The other groups don't contain real BMC users.
1574 for (const char* grp : predefinedGroups)
1575 {
1576 if (grp == grpSsh)
1577 {
1578 groupLists.emplace(grp, sshGrpUsersList);
1579 }
1580 else
1581 {
1582 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1583 groupLists.emplace(grp, grpUsersList);
1584 }
1585 }
1586 for (auto& grp : privMgr)
1587 {
1588 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1589 groupLists.emplace(grp, grpUsersList);
1590 }
1591
1592 for (auto& user : userNameList)
1593 {
1594 std::vector<std::string> userGroups;
1595 std::string userPriv;
1596 for (const auto& grp : groupLists)
1597 {
1598 std::vector<std::string> tempGrp = grp.second;
1599 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1600 tempGrp.end())
1601 {
1602 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1603 privMgr.end())
1604 {
1605 userPriv = grp.first;
1606 }
1607 else
1608 {
1609 userGroups.emplace_back(grp.first);
1610 }
1611 }
1612 }
1613 // Add user objects to the Users path.
1614 sdbusplus::message::object_path tempObjPath(usersObjPath);
1615 tempObjPath /= user;
1616 std::string objPath(tempObjPath);
1617 std::sort(userGroups.begin(), userGroups.end());
1618
1619 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1620 bus, objPath.c_str(), userGroups,
1621 userPriv, isUserEnabled(user),
1622 getPasswordExpiration(user), *this));
1623 }
1624 }
1625 }
1626
load()1627 void UserMgr::load()
1628 {
1629 std::optional<std::string> authTypeStr;
1630 if (std::filesystem::exists(mfaConfPath) && serializer.load())
1631 {
1632 serializer.deserialize("authtype", authTypeStr);
1633 }
1634 auto authType =
1635 authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
1636 .value_or(std::optional(MultiFactorAuthType::None));
1637 if (authType)
1638 {
1639 enabled(*authType, true);
1640 }
1641 }
1642
UserMgr(sdbusplus::bus_t & bus,const char * path)1643 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1644 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1645 serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
1646 pwHistoryConfigFile(defaultPWHistoryConfigFile),
1647 pwQualityConfigFile(defaultPWQualityConfigFile)
1648
1649 {
1650 UserMgrIface::allPrivileges(privMgr);
1651 groupsMgr = readAllGroupsOnSystem();
1652 std::sort(groupsMgr.begin(), groupsMgr.end());
1653 UserMgrIface::allGroups(groupsMgr);
1654 initializeAccountPolicy();
1655 load();
1656 initUserObjects();
1657 // emit the signal
1658 this->emit_object_added();
1659 }
1660
executeUserAdd(const char * userName,const char * groups,bool sshRequested,bool enabled)1661 void UserMgr::executeUserAdd(const char* userName, const char* groups,
1662 bool sshRequested, bool enabled)
1663 {
1664 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1665 // 1970-01-01, that's an implementation-defined behavior
1666 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1667 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
1668 (enabled ? "" : "1970-01-01"));
1669 }
1670
executeUserDelete(const char * userName)1671 void UserMgr::executeUserDelete(const char* userName)
1672 {
1673 executeCmd("/usr/sbin/userdel", userName, "-r", "-f");
1674 }
1675
executeUserClearFailRecords(const char * userName)1676 void UserMgr::executeUserClearFailRecords(const char* userName)
1677 {
1678 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1679 }
1680
executeUserRename(const char * userName,const char * newUserName)1681 void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1682 {
1683 std::string newHomeDir = "/home/";
1684 newHomeDir += newUserName;
1685 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1686 newHomeDir.c_str(), "-m");
1687 }
1688
executeUserModify(const char * userName,const char * newGroups,bool sshRequested)1689 void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1690 bool sshRequested)
1691 {
1692 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1693 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
1694 }
1695
executeUserModifyUserEnable(const char * userName,bool enabled)1696 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1697 {
1698 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1699 // 1970-01-01, that's an implementation-defined behavior
1700 executeCmd("/usr/sbin/usermod", userName, "-e",
1701 (enabled ? "" : "1970-01-01"));
1702 }
1703
getFailedAttempt(const char * userName)1704 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1705 {
1706 return executeCmd("/usr/sbin/faillock", "--user", userName);
1707 }
1708
enabled(MultiFactorAuthType value,bool skipSignal)1709 MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1710 {
1711 if (value == enabled())
1712 {
1713 return value;
1714 }
1715 switch (value)
1716 {
1717 case MultiFactorAuthType::None:
1718 for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1719 {
1720 for (auto& u : usersList)
1721 {
1722 u.second->enableMultiFactorAuth(type, false);
1723 }
1724 }
1725 break;
1726 default:
1727 for (auto& u : usersList)
1728 {
1729 u.second->enableMultiFactorAuth(value, true);
1730 }
1731 break;
1732 }
1733 serializer.serialize(
1734 "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
1735 serializer.store();
1736 return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1737 }
1738
secretKeyRequired(std::string userName)1739 bool UserMgr::secretKeyRequired(std::string userName)
1740 {
1741 if (usersList.contains(userName))
1742 {
1743 return usersList[userName]->secretKeyGenerationRequired();
1744 }
1745 return false;
1746 }
1747
executeUserPasswordExpiration(const char * userName,const long int passwordLastChange,const long int passwordAge) const1748 void UserMgr::executeUserPasswordExpiration(const char* userName,
1749 const long int passwordLastChange,
1750 const long int passwordAge) const
1751 {
1752 executeCmd("/usr/bin/chage", userName, "--lastday",
1753 std::to_string(passwordLastChange).c_str(), "--maxdays",
1754 std::to_string(passwordAge).c_str());
1755 }
1756
getShadowData(const std::string & userName,struct spwd & spwd) const1757 void UserMgr::getShadowData(const std::string& userName,
1758 struct spwd& spwd) const
1759 {
1760 struct spwd* p = nullptr;
1761
1762 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1763 if (buflen <= 0)
1764 buflen = 1024;
1765
1766 std::vector<char> buffer(buflen);
1767 auto status =
1768 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &p);
1769 if (status)
1770 {
1771 lg2::warning("Failed to get shadow entry for the user {USER_NAME}",
1772 "USER_NAME", userName.c_str());
1773 elog<InternalFailure>();
1774 }
1775
1776 spwd.sp_namp = nullptr;
1777 spwd.sp_pwdp = nullptr;
1778 }
1779
getPasswordExpiration(const std::string & userName) const1780 uint64_t UserMgr::getPasswordExpiration(const std::string& userName) const
1781 {
1782 // All user management lock has to be based on /etc/shadow
1783 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1784 struct spwd spwd{};
1785 getShadowData(userName, spwd);
1786
1787 // use default value for maximum password age to check that password
1788 // expiration was not specified
1789 // TODO: this default value might be changed, so it should be obtain
1790 // properly instead of hardcoding
1791 if (spwd.sp_max == 99999)
1792 {
1793 return getDefaultPasswordExpiration();
1794 }
1795
1796 // process last change date and maximum password age according to
1797 // list_fields() in
1798 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c#L266
1799
1800 // if last change is negative, then password does not exprire
1801 // if last change is positive and maximum password age is negative, then
1802 // password does not expire
1803 if (spwd.sp_lstchg < 0 || (spwd.sp_lstchg > 0 && spwd.sp_max < 0))
1804 {
1805 return getUnexpiringPasswordTime();
1806 }
1807
1808 // if last change is 0, then password must be changed
1809 // https://linux.die.net/man/5/shadow assume its now
1810 if (spwd.sp_lstchg == 0)
1811 {
1812 using namespace std::chrono;
1813 return duration_cast<seconds>(system_clock::now().time_since_epoch())
1814 .count();
1815 }
1816
1817 return daysToSeconds(static_cast<uint64_t>(spwd.sp_lstchg) + spwd.sp_max);
1818 }
1819
setPasswordExpiration(const std::string & userName,const uint64_t value)1820 void UserMgr::setPasswordExpiration(const std::string& userName,
1821 const uint64_t value)
1822 {
1823 setPasswordExpirationImpl(userName, value);
1824
1825 lg2::info("User's '{USER_NAME}' password expiration updated successfully",
1826 "USER_NAME", userName.c_str());
1827 }
1828
setPasswordExpirationImpl(const std::string & userName,const uint64_t value)1829 void UserMgr::setPasswordExpirationImpl(const std::string& userName,
1830 const uint64_t value)
1831 {
1832 // All user management lock has to be based on /etc/shadow
1833 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1834 const bool resetPasswordExpiration = (value == getUnexpiringPasswordTime());
1835
1836 struct spwd spwd{};
1837 getShadowData(userName, spwd);
1838
1839 // process last change date according to list_fields() in
1840 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c#L266
1841
1842 long int lastChangeDate = spwd.sp_lstchg;
1843 if (lastChangeDate <= 0 && !resetPasswordExpiration)
1844 {
1845 // if last change is 0, then password must be changed
1846 // https://linux.die.net/man/5/shadow make last change value valid,
1847 // update it to today
1848 // if last change is negative, then password does not expire, update it
1849 // to today as well
1850 using namespace std::chrono;
1851 lastChangeDate =
1852 duration_cast<days>(system_clock::now().time_since_epoch()).count();
1853 }
1854
1855 long int passwordAgeDays = spwd.sp_max;
1856 if (resetPasswordExpiration)
1857 {
1858 // if password expiration must be reset, do it via last negative maximum
1859 // password age
1860 passwordAgeDays = getUnexpiringPasswordAge();
1861 }
1862 else
1863 {
1864 const uint64_t date = secondsToDays(value);
1865 const long int expirationDate =
1866 (date > std::numeric_limits<long int>::max())
1867 ? std::numeric_limits<long int>::max()
1868 : date;
1869
1870 // if password expiration date is less than last change date, then this
1871 // leads to the situation when password age is negative, which in turn
1872 // is treated by system as password does not expire, hence treat such a
1873 // value of password expiration as invalid
1874 if (expirationDate < lastChangeDate)
1875 {
1876 lg2::error(
1877 "Password expiration date specified is less than password last change date for user '{USER_NAME}'",
1878 "USER_NAME", userName.c_str());
1879 elog<InvalidArgument>(
1880 Argument::ARGUMENT_NAME("User's password expiration date"),
1881 Argument::ARGUMENT_VALUE("less then last change date"));
1882 }
1883
1884 // set password expiration via maximum password age
1885 passwordAgeDays = expirationDate - lastChangeDate;
1886 }
1887
1888 try
1889 {
1890 executeUserPasswordExpiration(userName.c_str(), lastChangeDate,
1891 passwordAgeDays);
1892 }
1893 catch (const std::exception& e)
1894 {
1895 lg2::error("Unable to update user's '{USER_NAME}' password expiration",
1896 "USER_NAME", userName.c_str());
1897 elog<InternalFailure>();
1898 }
1899 }
1900
createUser2(std::string userName,UserCreateMap createProps)1901 void UserMgr::createUser2(std::string userName, UserCreateMap createProps)
1902 {
1903 createUserImpl(userName, createProps);
1904
1905 auto passwordExpiration = getDefaultPasswordExpiration();
1906 if (createProps.contains(UserProperty::PasswordExpiration))
1907 passwordExpiration =
1908 std::get<uint64_t>(createProps[UserProperty::PasswordExpiration]);
1909
1910 // maximum value (default value of password expiration) means not to set
1911 // password expiration
1912 if (passwordExpiration != getDefaultPasswordExpiration())
1913 {
1914 try
1915 {
1916 setPasswordExpirationImpl(userName, passwordExpiration);
1917 }
1918 catch (const sdbusplus::exception::generated_exception& e2)
1919 {
1920 // delete user created by createUserImpl
1921 deleteUserImpl(userName);
1922 throw;
1923 }
1924 catch (const std::exception& e2)
1925 {
1926 // delete user created by createUserImpl
1927 deleteUserImpl(userName);
1928 lg2::error(
1929 "User's password expiration value is incorrect for user '{USER_NAME}'",
1930 "USER_NAME", userName.c_str());
1931
1932 elog<InvalidArgument>(
1933 Argument::ARGUMENT_NAME("Password Expiration"),
1934 Argument::ARGUMENT_VALUE(
1935 std::to_string(passwordExpiration).c_str()));
1936 }
1937 }
1938
1939 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
1940 }
1941
1942 } // namespace user
1943 } // namespace phosphor
1944