xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision cda9bc622b92933e9a6bbebfb3509d8b0f5bd481)
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 <boost/algorithm/string/split.hpp>
34 #include <boost/process/child.hpp>
35 #include <boost/process/io.hpp>
36 #include <phosphor-logging/elog-errors.hpp>
37 #include <phosphor-logging/elog.hpp>
38 #include <phosphor-logging/log.hpp>
39 #include <xyz/openbmc_project/Common/error.hpp>
40 #include <xyz/openbmc_project/User/Common/error.hpp>
41 
42 #include <algorithm>
43 #include <fstream>
44 #include <numeric>
45 #include <regex>
46 
47 namespace phosphor
48 {
49 namespace user
50 {
51 
52 static constexpr const char* passwdFileName = "/etc/passwd";
53 static constexpr size_t ipmiMaxUsers = 15;
54 static constexpr size_t ipmiMaxUserNameLen = 16;
55 static constexpr size_t systemMaxUserNameLen = 30;
56 static constexpr size_t maxSystemUsers = 30;
57 static constexpr const char* grpSsh = "ssh";
58 static constexpr uint8_t minPasswdLength = 8;
59 static constexpr int success = 0;
60 static constexpr int failure = -1;
61 
62 // pam modules related
63 static constexpr const char* pamTally2 = "pam_tally2.so";
64 static constexpr const char* pamCrackLib = "pam_cracklib.so";
65 static constexpr const char* pamPWHistory = "pam_pwhistory.so";
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* pamPasswdConfigFile = "/etc/pam.d/common-password";
71 static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
72 
73 // Object Manager related
74 static constexpr const char* ldapMgrObjBasePath =
75     "/xyz/openbmc_project/user/ldap";
76 
77 // Object Mapper related
78 static constexpr const char* objMapperService =
79     "xyz.openbmc_project.ObjectMapper";
80 static constexpr const char* objMapperPath =
81     "/xyz/openbmc_project/object_mapper";
82 static constexpr const char* objMapperInterface =
83     "xyz.openbmc_project.ObjectMapper";
84 
85 using namespace phosphor::logging;
86 using InsufficientPermission =
87     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
88 using InternalFailure =
89     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
90 using InvalidArgument =
91     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
92 using UserNameExists =
93     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
94 using UserNameDoesNotExist =
95     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
96 using UserNameGroupFail =
97     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
98 using NoResource =
99     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
100 
101 using Argument = xyz::openbmc_project::Common::InvalidArgument;
102 
103 template <typename... ArgTypes>
104 static std::vector<std::string> executeCmd(const char* path,
105                                            ArgTypes&&... tArgs)
106 {
107     std::vector<std::string> stdOutput;
108     boost::process::ipstream stdOutStream;
109     boost::process::child execProg(path, const_cast<char*>(tArgs)...,
110                                    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 
119     execProg.wait();
120 
121     int retCode = execProg.exit_code();
122     if (retCode)
123     {
124         log<level::ERR>("Command execution failed", entry("PATH=%d", path),
125                         entry("RETURN_CODE:%d", retCode));
126         elog<InternalFailure>();
127     }
128 
129     return stdOutput;
130 }
131 
132 static 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 
157 static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
158 {
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 
176 bool UserMgr::isUserExist(const std::string& userName)
177 {
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 
191 void UserMgr::throwForUserDoesNotExist(const std::string& userName)
192 {
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 
201 void UserMgr::throwForUserExists(const std::string& userName)
202 {
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 
211 void UserMgr::throwForUserNameConstraints(
212     const std::string& userName, const std::vector<std::string>& groupNames)
213 {
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 
243 void UserMgr::throwForMaxGrpUserCount(
244     const std::vector<std::string>& groupNames)
245 {
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 
271 void UserMgr::throwForInvalidPrivilege(const std::string& priv)
272 {
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 
282 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
283 {
284     for (auto& group : groupNames)
285     {
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 
296 void 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
303     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
304     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.
313     if (!priv.empty())
314     {
315         if (groups.size() != 0)
316         {
317             groups += ",";
318         }
319         groups += priv;
320     }
321     try
322     {
323         executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
324                    "-m", "-N", "-s",
325                    (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
326                    (enabled ? "" : "1970-01-02"));
327     }
328     catch (const InternalFailure& e)
329     {
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 
346 void UserMgr::deleteUser(std::string userName)
347 {
348     // All user management lock has to be based on /etc/shadow
349     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
350     throwForUserDoesNotExist(userName);
351     try
352     {
353         executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
354     }
355     catch (const InternalFailure& e)
356     {
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 
369 void UserMgr::renameUser(std::string userName, std::string newUserName)
370 {
371     // All user management lock has to be based on /etc/shadow
372     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
373     throwForUserDoesNotExist(userName);
374     throwForUserExists(newUserName);
375     throwForUserNameConstraints(newUserName,
376                                 usersList[userName].get()->userGroups());
377     try
378     {
379         std::string newHomeDir = "/home/" + newUserName;
380         executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
381                    userName.c_str(), "-d", newHomeDir.c_str(), "-m");
382     }
383     catch (const InternalFailure& e)
384     {
385         log<level::ERR>("User rename failed",
386                         entry("USER_NAME=%s", userName.c_str()));
387         elog<InternalFailure>();
388     }
389     const auto& user = usersList[userName];
390     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 
406 void UserMgr::updateGroupsAndPriv(const std::string& userName,
407                                   const std::vector<std::string>& groupNames,
408                                   const std::string& priv)
409 {
410     throwForInvalidPrivilege(priv);
411     throwForInvalidGroups(groupNames);
412     // All user management lock has to be based on /etc/shadow
413     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
414     throwForUserDoesNotExist(userName);
415     const std::vector<std::string>& oldGroupNames =
416         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.
434     if (!priv.empty())
435     {
436         if (groups.size() != 0)
437         {
438             groups += ",";
439         }
440         groups += priv;
441     }
442     try
443     {
444         executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
445                    "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
446     }
447     catch (const InternalFailure& e)
448     {
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 
458 uint8_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 
477 uint8_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 
492 uint16_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 
507 uint32_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 
522 int UserMgr::getPamModuleArgValue(const std::string& moduleName,
523                                   const std::string& argName,
524                                   std::string& argValue)
525 {
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 
575 int UserMgr::setPamModuleArgValue(const std::string& moduleName,
576                                   const std::string& argName,
577                                   const std::string& argValue)
578 {
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 
645 void UserMgr::userEnable(const std::string& userName, bool enabled)
646 {
647     // All user management lock has to be based on /etc/shadow
648     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
649     throwForUserDoesNotExist(userName);
650     try
651     {
652         executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
653                    (enabled ? "" : "1970-01-02"));
654     }
655     catch (const InternalFailure& e)
656     {
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 
667 /**
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 
674 static constexpr size_t t2UserIdx = 0;
675 static constexpr size_t t2FailCntIdx = 1;
676 static constexpr size_t t2OutputIndex = 1;
677 
678 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
679 {
680     // All user management lock has to be based on /etc/shadow
681     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
682     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 
691     try
692     {
693         unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
694         uint16_t value16 = 0;
695         if (tmp > std::numeric_limits<decltype(value16)>::max())
696         {
697             throw std::out_of_range("Out of range");
698         }
699         value16 = static_cast<decltype(value16)>(tmp);
700         if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
701             value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
702         {
703             return true; // User account is locked out
704         }
705         return false; // User account is un-locked
706     }
707     catch (const std::exception& e)
708     {
709         log<level::ERR>("Exception for userLockedForFailedAttempt",
710                         entry("WHAT=%s", e.what()));
711         throw;
712     }
713 }
714 
715 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
716                                          const bool& value)
717 {
718     // All user management lock has to be based on /etc/shadow
719     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
720     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 
732     return userLockedForFailedAttempt(userName);
733 }
734 
735 bool UserMgr::userPasswordExpired(const std::string& userName)
736 {
737     // All user management lock has to be based on /etc/shadow
738     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
739 
740     struct spwd spwd
741     {};
742     struct spwd* spwdPtr = nullptr;
743     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     {
770         // 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;
775     }
776 
777     return false;
778 }
779 
780 UserSSHLists UserMgr::getUserAndSshGrpList()
781 {
782     // All user management lock has to be based on /etc/shadow
783     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
784 
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         }
806 #ifdef ENABLE_ROOT_USER_MGMT
807         // 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)))
811 #else
812         // Add all users whose UID >=1000 and < 65534
813         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
814 #endif
815         {
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 
832 size_t UserMgr::getIpmiUsersCount()
833 {
834     std::vector<std::string> userList = getUsersInGroup("ipmi");
835     return userList.size();
836 }
837 
838 bool UserMgr::isUserEnabled(const std::string& userName)
839 {
840     // All user management lock has to be based on /etc/shadow
841     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
842     std::array<char, 4096> buffer{};
843     struct spwd spwd;
844     struct spwd* resultPtr = nullptr;
845     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 
858 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
859 {
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;
864     struct group* resultPtr = nullptr;
865 
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 
885 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
886 {
887     DbusUserObj objects;
888     try
889     {
890         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
891         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
892 
893         auto ldapMgmtService =
894             getServiceName(std::move(basePath), std::move(interface));
895         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     }
902     catch (const InternalFailure& e)
903     {
904         log<level::ERR>("Unable to get the User Service",
905                         entry("WHAT=%s", e.what()));
906         throw;
907     }
908     catch (const sdbusplus::exception::SdBusError& e)
909     {
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 
918 std::string UserMgr::getLdapGroupName(const std::string& userName)
919 {
920     struct passwd pwd
921     {};
922     struct passwd* pwdPtr = nullptr;
923     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 
948     struct group* groups = nullptr;
949     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 
965 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
966 {
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 
993 UserInfoMap 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     {
999         const auto& user = usersList[userName];
1000         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());
1005         userInfo.emplace("UserPasswordExpired",
1006                          user.get()->userPasswordExpired());
1007         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;
1023         std::string ldapConfigPath;
1024 
1025         try
1026         {
1027             for (const auto& obj : objects)
1028             {
1029                 for (const auto& interface : obj.second)
1030                 {
1031                     if ((interface.first ==
1032                          "xyz.openbmc_project.Object.Enable"))
1033                     {
1034                         for (const auto& property : interface.second)
1035                         {
1036                             auto value = std::get<bool>(property.second);
1037                             if ((property.first == "Enabled") &&
1038                                 (value == true))
1039                             {
1040                                 ldapConfigPath = obj.first;
1041                                 break;
1042                             }
1043                         }
1044                     }
1045                 }
1046                 if (!ldapConfigPath.empty())
1047                 {
1048                     break;
1049                 }
1050             }
1051 
1052             if (ldapConfigPath.empty())
1053             {
1054                 return userInfo;
1055             }
1056 
1057             for (const auto& obj : objects)
1058             {
1059                 for (const auto& interface : obj.second)
1060                 {
1061                     if ((interface.first ==
1062                          "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1063                         (obj.first.str.find(ldapConfigPath) !=
1064                          std::string::npos))
1065                     {
1066 
1067                         for (const auto& property : interface.second)
1068                         {
1069                             auto value = std::get<std::string>(property.second);
1070                             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                         }
1083                     }
1084                 }
1085             }
1086             auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
1087 
1088             if (priv.empty())
1089             {
1090                 log<level::ERR>("LDAP group privilege mapping does not exist");
1091             }
1092         }
1093         catch (const std::bad_variant_access& e)
1094         {
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 
1105 void UserMgr::initUserObjects(void)
1106 {
1107     // All user management lock has to be based on /etc/shadow
1108     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1109     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;
1118         for (auto& grp : groupsMgr)
1119         {
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         }
1130         for (auto& grp : privMgr)
1131         {
1132             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1133             groupLists.emplace(grp, grpUsersList);
1134         }
1135 
1136         for (auto& user : userNameList)
1137         {
1138             std::vector<std::string> userGroups;
1139             std::string userPriv;
1140             for (const auto& grp : groupLists)
1141             {
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 
1168 UserMgr::UserMgr(sdbusplus::bus::bus& bus, const char* path) :
1169     Ifaces(bus, path, true), bus(bus), path(path)
1170 {
1171     UserMgrIface::allPrivileges(privMgr);
1172     std::sort(groupsMgr.begin(), groupsMgr.end());
1173     UserMgrIface::allGroups(groupsMgr);
1174     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         }
1193         catch (const std::exception& e)
1194         {
1195             log<level::ERR>("Exception for MinPasswordLength",
1196                             entry("WHAT=%s", e.what()));
1197             throw;
1198         }
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         }
1219         catch (const std::exception& e)
1220         {
1221             log<level::ERR>("Exception for RememberOldPasswordTimes",
1222                             entry("WHAT=%s", e.what()));
1223             throw;
1224         }
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         }
1244         catch (const std::exception& e)
1245         {
1246             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1247                             entry("WHAT=%s", e.what()));
1248             throw;
1249         }
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         }
1269         catch (const std::exception& e)
1270         {
1271             log<level::ERR>("Exception for AccountUnlockTimeout",
1272                             entry("WHAT=%s", e.what()));
1273             throw;
1274         }
1275         AccountPolicyIface::accountUnlockTimeout(value32);
1276     }
1277     initUserObjects();
1278 
1279     // emit the signal
1280     this->emit_object_added();
1281 }
1282 
1283 } // namespace user
1284 } // namespace phosphor
1285