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 <shadow.h>
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <fstream>
22 #include <grp.h>
23 #include <pwd.h>
24 #include <regex>
25 #include <algorithm>
26 #include <numeric>
27 #include <boost/process/child.hpp>
28 #include <xyz/openbmc_project/Common/error.hpp>
29 #include <xyz/openbmc_project/User/Common/error.hpp>
30 #include <phosphor-logging/log.hpp>
31 #include <phosphor-logging/elog.hpp>
32 #include <phosphor-logging/elog-errors.hpp>
33 #include "shadowlock.hpp"
34 #include "file.hpp"
35 #include "user_mgr.hpp"
36 #include "users.hpp"
37 #include "config.h"
38 
39 namespace phosphor
40 {
41 namespace user
42 {
43 
44 static constexpr const char *passwdFileName = "/etc/passwd";
45 static constexpr size_t ipmiMaxUsers = 15;
46 static constexpr size_t ipmiMaxUserNameLen = 16;
47 static constexpr size_t systemMaxUserNameLen = 30;
48 static constexpr size_t maxSystemUsers = 30;
49 static constexpr const char *grpSsh = "ssh";
50 
51 using namespace phosphor::logging;
52 using InsufficientPermission =
53     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
54 using InternalFailure =
55     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
56 using InvalidArgument =
57     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
58 using UserNameExists =
59     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
60 using UserNameDoesNotExist =
61     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
62 using UserNameGroupFail =
63     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
64 
65 using NoResource =
66     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
67 
68 using Argument = xyz::openbmc_project::Common::InvalidArgument;
69 
70 template <typename... ArgTypes>
71 static void executeCmd(const char *path, ArgTypes &&... tArgs)
72 {
73     boost::process::child execProg(path, const_cast<char *>(tArgs)...);
74     execProg.wait();
75     int retCode = execProg.exit_code();
76     if (retCode)
77     {
78         log<level::ERR>("Command execution failed", entry("PATH=%s", path),
79                         entry("RETURN_CODE:%d", retCode));
80         elog<InternalFailure>();
81     }
82     return;
83 }
84 
85 static std::string getCSVFromVector(std::vector<std::string> vec)
86 {
87     switch (vec.size())
88     {
89         case 0:
90         {
91             return "";
92         }
93         break;
94 
95         case 1:
96         {
97             return std::string{vec[0]};
98         }
99         break;
100 
101         default:
102         {
103             return std::accumulate(
104                 std::next(vec.begin()), vec.end(), vec[0],
105                 [](std::string a, std::string b) { return a + ',' + b; });
106         }
107     }
108 }
109 
110 static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
111 {
112     std::string::size_type delStrPos = csvStr.find(delStr);
113     if (delStrPos != std::string::npos)
114     {
115         // need to also delete the comma char
116         if (delStrPos == 0)
117         {
118             csvStr.erase(delStrPos, delStr.size() + 1);
119         }
120         else
121         {
122             csvStr.erase(delStrPos - 1, delStr.size() + 1);
123         }
124         return true;
125     }
126     return false;
127 }
128 
129 bool UserMgr::isUserExist(const std::string &userName)
130 {
131     if (userName.empty())
132     {
133         log<level::ERR>("User name is empty");
134         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
135                               Argument::ARGUMENT_VALUE("Null"));
136     }
137     if (usersList.find(userName) == usersList.end())
138     {
139         return false;
140     }
141     return true;
142 }
143 
144 void UserMgr::throwForUserDoesNotExist(const std::string &userName)
145 {
146     if (isUserExist(userName) == false)
147     {
148         log<level::ERR>("User does not exist",
149                         entry("USER_NAME=%s", userName.c_str()));
150         elog<UserNameDoesNotExist>();
151     }
152 }
153 
154 void UserMgr::throwForUserExists(const std::string &userName)
155 {
156     if (isUserExist(userName) == true)
157     {
158         log<level::ERR>("User already exists",
159                         entry("USER_NAME=%s", userName.c_str()));
160         elog<UserNameExists>();
161     }
162 }
163 
164 void UserMgr::throwForUserNameConstraints(
165     const std::string &userName, const std::vector<std::string> &groupNames)
166 {
167     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
168         groupNames.end())
169     {
170         if (userName.length() > ipmiMaxUserNameLen)
171         {
172             log<level::ERR>("IPMI user name length limitation",
173                             entry("SIZE=%d", userName.length()));
174             elog<UserNameGroupFail>(
175                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
176                     "IPMI length"));
177         }
178     }
179     if (userName.length() > systemMaxUserNameLen)
180     {
181         log<level::ERR>("User name length limitation",
182                         entry("SIZE=%d", userName.length()));
183         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
184                               Argument::ARGUMENT_VALUE("Invalid length"));
185     }
186     if (!std::regex_match(userName.c_str(),
187                           std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
188     {
189         log<level::ERR>("Invalid user name",
190                         entry("USER_NAME=%s", userName.c_str()));
191         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
192                               Argument::ARGUMENT_VALUE("Invalid data"));
193     }
194 }
195 
196 void UserMgr::throwForMaxGrpUserCount(
197     const std::vector<std::string> &groupNames)
198 {
199     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
200         groupNames.end())
201     {
202         if (getIpmiUsersCount() >= ipmiMaxUsers)
203         {
204             log<level::ERR>("IPMI user limit reached");
205             elog<NoResource>(
206                 xyz::openbmc_project::User::Common::NoResource::REASON(
207                     "ipmi user count reached"));
208         }
209     }
210     else
211     {
212         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
213                                         (maxSystemUsers - ipmiMaxUsers))
214         {
215             log<level::ERR>("Non-ipmi User limit reached");
216             elog<NoResource>(
217                 xyz::openbmc_project::User::Common::NoResource::REASON(
218                     "Non-ipmi user count reached"));
219         }
220     }
221     return;
222 }
223 
224 void UserMgr::throwForInvalidPrivilege(const std::string &priv)
225 {
226     if (!priv.empty() &&
227         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
228     {
229         log<level::ERR>("Invalid privilege");
230         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
231                               Argument::ARGUMENT_VALUE(priv.c_str()));
232     }
233 }
234 
235 void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
236 {
237     for (auto &group : groupNames)
238     {
239         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
240             groupsMgr.end())
241         {
242             log<level::ERR>("Invalid Group Name listed");
243             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
244                                   Argument::ARGUMENT_VALUE(group.c_str()));
245         }
246     }
247 }
248 
249 void UserMgr::createUser(std::string userName,
250                          std::vector<std::string> groupNames, std::string priv,
251                          bool enabled)
252 {
253     throwForInvalidPrivilege(priv);
254     throwForInvalidGroups(groupNames);
255     // All user management lock has to be based on /etc/shadow
256     phosphor::user::shadow::Lock lock();
257     throwForUserExists(userName);
258     throwForUserNameConstraints(userName, groupNames);
259     throwForMaxGrpUserCount(groupNames);
260 
261     std::string groups = getCSVFromVector(groupNames);
262     bool sshRequested = removeStringFromCSV(groups, grpSsh);
263 
264     // treat privilege as a group - This is to avoid using different file to
265     // store the same.
266     if (groups.size() != 0)
267     {
268         groups += ",";
269     }
270     groups += priv;
271 
272     try
273     {
274         executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
275                    "-M", "-N", "-s",
276                    (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
277                    (enabled ? "" : "1970-01-02"));
278     }
279     catch (const InternalFailure &e)
280     {
281         log<level::ERR>("Unable to create new user");
282         elog<InternalFailure>();
283     }
284 
285     // Add the users object before sending out the signal
286     std::string userObj = std::string(usersObjPath) + "/" + userName;
287     std::sort(groupNames.begin(), groupNames.end());
288     usersList.emplace(
289         userName, std::move(std::make_unique<phosphor::user::Users>(
290                       bus, userObj.c_str(), groupNames, priv, enabled, *this)));
291 
292     log<level::INFO>("User created successfully",
293                      entry("USER_NAME=%s", userName.c_str()));
294     return;
295 }
296 
297 void UserMgr::deleteUser(std::string userName)
298 {
299     // All user management lock has to be based on /etc/shadow
300     phosphor::user::shadow::Lock lock();
301     throwForUserDoesNotExist(userName);
302     try
303     {
304         executeCmd("/usr/sbin/userdel", userName.c_str());
305     }
306     catch (const InternalFailure &e)
307     {
308         log<level::ERR>("User delete failed",
309                         entry("USER_NAME=%s", userName.c_str()));
310         elog<InternalFailure>();
311     }
312 
313     usersList.erase(userName);
314 
315     log<level::INFO>("User deleted successfully",
316                      entry("USER_NAME=%s", userName.c_str()));
317     return;
318 }
319 
320 void UserMgr::renameUser(std::string userName, std::string newUserName)
321 {
322     // All user management lock has to be based on /etc/shadow
323     phosphor::user::shadow::Lock lock();
324     throwForUserDoesNotExist(userName);
325     throwForUserExists(newUserName);
326     throwForUserNameConstraints(newUserName,
327                                 usersList[userName].get()->userGroups());
328     try
329     {
330         executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
331                    userName.c_str());
332     }
333     catch (const InternalFailure &e)
334     {
335         log<level::ERR>("User rename failed",
336                         entry("USER_NAME=%s", userName.c_str()));
337         elog<InternalFailure>();
338     }
339     const auto &user = usersList[userName];
340     std::string priv = user.get()->userPrivilege();
341     std::vector<std::string> groupNames = user.get()->userGroups();
342     bool enabled = user.get()->userEnabled();
343     std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
344     // Special group 'ipmi' needs a way to identify user renamed, in order to
345     // update encrypted password. It can't rely only on InterfacesRemoved &
346     // InterfacesAdded. So first send out userRenamed signal.
347     this->userRenamed(userName, newUserName);
348     usersList.erase(userName);
349     usersList.emplace(
350         newUserName,
351         std::move(std::make_unique<phosphor::user::Users>(
352             bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
353     return;
354 }
355 
356 void UserMgr::updateGroupsAndPriv(const std::string &userName,
357                                   const std::vector<std::string> &groupNames,
358                                   const std::string &priv)
359 {
360     throwForInvalidPrivilege(priv);
361     throwForInvalidGroups(groupNames);
362     // All user management lock has to be based on /etc/shadow
363     phosphor::user::shadow::Lock lock();
364     throwForUserDoesNotExist(userName);
365     const std::vector<std::string> &oldGroupNames =
366         usersList[userName].get()->userGroups();
367     std::vector<std::string> groupDiff;
368     // Note: already dealing with sorted group lists.
369     std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
370                                   groupNames.begin(), groupNames.end(),
371                                   std::back_inserter(groupDiff));
372     if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
373         groupDiff.end())
374     {
375         throwForUserNameConstraints(userName, groupNames);
376         throwForMaxGrpUserCount(groupNames);
377     }
378 
379     std::string groups = getCSVFromVector(groupNames);
380     bool sshRequested = removeStringFromCSV(groups, grpSsh);
381 
382     // treat privilege as a group - This is to avoid using different file to
383     // store the same.
384     if (groups.size() != 0)
385     {
386         groups += ",";
387     }
388     groups += priv;
389     try
390     {
391         executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
392                    "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
393     }
394     catch (const InternalFailure &e)
395     {
396         log<level::ERR>("Unable to modify user privilege / groups");
397         elog<InternalFailure>();
398     }
399 
400     log<level::INFO>("User groups / privilege updated successfully",
401                      entry("USER_NAME=%s", userName.c_str()));
402     return;
403 }
404 
405 void UserMgr::userEnable(const std::string &userName, bool enabled)
406 {
407     // All user management lock has to be based on /etc/shadow
408     phosphor::user::shadow::Lock lock();
409     throwForUserDoesNotExist(userName);
410     try
411     {
412         executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
413                    (enabled ? "" : "1970-01-02"));
414     }
415     catch (const InternalFailure &e)
416     {
417         log<level::ERR>("Unable to modify user enabled state");
418         elog<InternalFailure>();
419     }
420 
421     log<level::INFO>("User enabled/disabled state updated successfully",
422                      entry("USER_NAME=%s", userName.c_str()),
423                      entry("ENABLED=%d", enabled));
424     return;
425 }
426 
427 UserSSHLists UserMgr::getUserAndSshGrpList()
428 {
429     // All user management lock has to be based on /etc/shadow
430     phosphor::user::shadow::Lock lock();
431 
432     std::vector<std::string> userList;
433     std::vector<std::string> sshUsersList;
434     struct passwd pw, *pwp = nullptr;
435     std::array<char, 1024> buffer{};
436 
437     phosphor::user::File passwd(passwdFileName, "r");
438     if ((passwd)() == NULL)
439     {
440         log<level::ERR>("Error opening the passwd file");
441         elog<InternalFailure>();
442     }
443 
444     while (true)
445     {
446         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
447                              &pwp);
448         if ((r != 0) || (pwp == NULL))
449         {
450             // Any error, break the loop.
451             break;
452         }
453         // All users whose UID >= 1000 and < 65534
454         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
455         {
456             std::string userName(pwp->pw_name);
457             userList.emplace_back(userName);
458 
459             // ssh doesn't have separate group. Check login shell entry to
460             // get all users list which are member of ssh group.
461             std::string loginShell(pwp->pw_shell);
462             if (loginShell == "/bin/sh")
463             {
464                 sshUsersList.emplace_back(userName);
465             }
466         }
467     }
468     endpwent();
469     return std::make_pair(std::move(userList), std::move(sshUsersList));
470 }
471 
472 size_t UserMgr::getIpmiUsersCount()
473 {
474     std::vector<std::string> userList = getUsersInGroup("ipmi");
475     return userList.size();
476 }
477 
478 bool UserMgr::isUserEnabled(const std::string &userName)
479 {
480     // All user management lock has to be based on /etc/shadow
481     phosphor::user::shadow::Lock lock();
482     std::array<char, 4096> buffer{};
483     struct spwd spwd;
484     struct spwd *resultPtr = nullptr;
485     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
486                             buffer.max_size(), &resultPtr);
487     if (!status && (&spwd == resultPtr))
488     {
489         if (resultPtr->sp_expire >= 0)
490         {
491             return false; // user locked out
492         }
493         return true;
494     }
495     return false; // assume user is disabled for any error.
496 }
497 
498 std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
499 {
500     std::vector<std::string> usersInGroup;
501     // Should be more than enough to get the pwd structure.
502     std::array<char, 4096> buffer{};
503     struct group grp;
504     struct group *resultPtr = nullptr;
505 
506     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
507                             buffer.max_size(), &resultPtr);
508 
509     if (!status && (&grp == resultPtr))
510     {
511         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
512         {
513             usersInGroup.emplace_back(*(grp.gr_mem));
514         }
515     }
516     else
517     {
518         log<level::ERR>("Group not found",
519                         entry("GROUP=%s", groupName.c_str()));
520         // Don't throw error, just return empty userList - fallback
521     }
522     return usersInGroup;
523 }
524 
525 void UserMgr::initUserObjects(void)
526 {
527     // All user management lock has to be based on /etc/shadow
528     phosphor::user::shadow::Lock lock();
529     std::vector<std::string> userNameList;
530     std::vector<std::string> sshGrpUsersList;
531     UserSSHLists userSSHLists = getUserAndSshGrpList();
532     userNameList = std::move(userSSHLists.first);
533     sshGrpUsersList = std::move(userSSHLists.second);
534 
535     if (!userNameList.empty())
536     {
537         std::map<std::string, std::vector<std::string>> groupLists;
538         for (auto &grp : groupsMgr)
539         {
540             if (grp == grpSsh)
541             {
542                 groupLists.emplace(grp, sshGrpUsersList);
543             }
544             else
545             {
546                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
547                 groupLists.emplace(grp, grpUsersList);
548             }
549         }
550         for (auto &grp : privMgr)
551         {
552             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
553             groupLists.emplace(grp, grpUsersList);
554         }
555 
556         for (auto &user : userNameList)
557         {
558             std::vector<std::string> userGroups;
559             std::string userPriv;
560             for (const auto &grp : groupLists)
561             {
562                 std::vector<std::string> tempGrp = grp.second;
563                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
564                     tempGrp.end())
565                 {
566                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
567                         privMgr.end())
568                     {
569                         userPriv = grp.first;
570                     }
571                     else
572                     {
573                         userGroups.emplace_back(grp.first);
574                     }
575                 }
576             }
577             // Add user objects to the Users path.
578             auto objPath = std::string(usersObjPath) + "/" + user;
579             std::sort(userGroups.begin(), userGroups.end());
580             usersList.emplace(user,
581                               std::move(std::make_unique<phosphor::user::Users>(
582                                   bus, objPath.c_str(), userGroups, userPriv,
583                                   isUserEnabled(user), *this)));
584         }
585     }
586 }
587 
588 UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
589     UserMgrIface(bus, path), bus(bus), path(path)
590 {
591     UserMgrIface::allPrivileges(privMgr);
592     std::sort(groupsMgr.begin(), groupsMgr.end());
593     UserMgrIface::allGroups(groupsMgr);
594     initUserObjects();
595 }
596 
597 } // namespace user
598 } // namespace phosphor
599