blob: cba9366ddfcec7035a401759cd7334ed0a6951c6 [file] [log] [blame]
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
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
39namespace phosphor
40{
41namespace user
42{
43
44static constexpr const char *passwdFileName = "/etc/passwd";
45static constexpr size_t ipmiMaxUsers = 15;
46static constexpr size_t ipmiMaxUserNameLen = 16;
47static constexpr size_t systemMaxUserNameLen = 30;
48static constexpr size_t maxSystemUsers = 30;
49static constexpr const char *grpSsh = "ssh";
50
51using namespace phosphor::logging;
52using InsufficientPermission =
53 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
54using InternalFailure =
55 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
56using InvalidArgument =
57 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
58using UserNameExists =
59 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
60using UserNameDoesNotExist =
61 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
62using UserNameGroupFail =
63 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
64
65using NoResource =
66 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
67
68using Argument = xyz::openbmc_project::Common::InvalidArgument;
69
70template <typename... ArgTypes>
71static 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
85static 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
110static 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
129bool 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
144void 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
154void 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
164void 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
196void 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
224void 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
235void 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
249void 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.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530266 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530267 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530268 if (groups.size() != 0)
269 {
270 groups += ",";
271 }
272 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530273 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530274 try
275 {
276 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530277 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530278 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
279 (enabled ? "" : "1970-01-02"));
280 }
281 catch (const InternalFailure &e)
282 {
283 log<level::ERR>("Unable to create new user");
284 elog<InternalFailure>();
285 }
286
287 // Add the users object before sending out the signal
288 std::string userObj = std::string(usersObjPath) + "/" + userName;
289 std::sort(groupNames.begin(), groupNames.end());
290 usersList.emplace(
291 userName, std::move(std::make_unique<phosphor::user::Users>(
292 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
293
294 log<level::INFO>("User created successfully",
295 entry("USER_NAME=%s", userName.c_str()));
296 return;
297}
298
299void UserMgr::deleteUser(std::string userName)
300{
301 // All user management lock has to be based on /etc/shadow
302 phosphor::user::shadow::Lock lock();
303 throwForUserDoesNotExist(userName);
304 try
305 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530306 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530307 }
308 catch (const InternalFailure &e)
309 {
310 log<level::ERR>("User delete failed",
311 entry("USER_NAME=%s", userName.c_str()));
312 elog<InternalFailure>();
313 }
314
315 usersList.erase(userName);
316
317 log<level::INFO>("User deleted successfully",
318 entry("USER_NAME=%s", userName.c_str()));
319 return;
320}
321
322void UserMgr::renameUser(std::string userName, std::string newUserName)
323{
324 // All user management lock has to be based on /etc/shadow
325 phosphor::user::shadow::Lock lock();
326 throwForUserDoesNotExist(userName);
327 throwForUserExists(newUserName);
328 throwForUserNameConstraints(newUserName,
329 usersList[userName].get()->userGroups());
330 try
331 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530332 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530333 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530334 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530335 }
336 catch (const InternalFailure &e)
337 {
338 log<level::ERR>("User rename failed",
339 entry("USER_NAME=%s", userName.c_str()));
340 elog<InternalFailure>();
341 }
342 const auto &user = usersList[userName];
343 std::string priv = user.get()->userPrivilege();
344 std::vector<std::string> groupNames = user.get()->userGroups();
345 bool enabled = user.get()->userEnabled();
346 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
347 // Special group 'ipmi' needs a way to identify user renamed, in order to
348 // update encrypted password. It can't rely only on InterfacesRemoved &
349 // InterfacesAdded. So first send out userRenamed signal.
350 this->userRenamed(userName, newUserName);
351 usersList.erase(userName);
352 usersList.emplace(
353 newUserName,
354 std::move(std::make_unique<phosphor::user::Users>(
355 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
356 return;
357}
358
359void UserMgr::updateGroupsAndPriv(const std::string &userName,
360 const std::vector<std::string> &groupNames,
361 const std::string &priv)
362{
363 throwForInvalidPrivilege(priv);
364 throwForInvalidGroups(groupNames);
365 // All user management lock has to be based on /etc/shadow
366 phosphor::user::shadow::Lock lock();
367 throwForUserDoesNotExist(userName);
368 const std::vector<std::string> &oldGroupNames =
369 usersList[userName].get()->userGroups();
370 std::vector<std::string> groupDiff;
371 // Note: already dealing with sorted group lists.
372 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
373 groupNames.begin(), groupNames.end(),
374 std::back_inserter(groupDiff));
375 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
376 groupDiff.end())
377 {
378 throwForUserNameConstraints(userName, groupNames);
379 throwForMaxGrpUserCount(groupNames);
380 }
381
382 std::string groups = getCSVFromVector(groupNames);
383 bool sshRequested = removeStringFromCSV(groups, grpSsh);
384
385 // treat privilege as a group - This is to avoid using different file to
386 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530387 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530389 if (groups.size() != 0)
390 {
391 groups += ",";
392 }
393 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 try
396 {
397 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
398 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
399 }
400 catch (const InternalFailure &e)
401 {
402 log<level::ERR>("Unable to modify user privilege / groups");
403 elog<InternalFailure>();
404 }
405
406 log<level::INFO>("User groups / privilege updated successfully",
407 entry("USER_NAME=%s", userName.c_str()));
408 return;
409}
410
411void UserMgr::userEnable(const std::string &userName, bool enabled)
412{
413 // All user management lock has to be based on /etc/shadow
414 phosphor::user::shadow::Lock lock();
415 throwForUserDoesNotExist(userName);
416 try
417 {
418 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
419 (enabled ? "" : "1970-01-02"));
420 }
421 catch (const InternalFailure &e)
422 {
423 log<level::ERR>("Unable to modify user enabled state");
424 elog<InternalFailure>();
425 }
426
427 log<level::INFO>("User enabled/disabled state updated successfully",
428 entry("USER_NAME=%s", userName.c_str()),
429 entry("ENABLED=%d", enabled));
430 return;
431}
432
433UserSSHLists UserMgr::getUserAndSshGrpList()
434{
435 // All user management lock has to be based on /etc/shadow
436 phosphor::user::shadow::Lock lock();
437
438 std::vector<std::string> userList;
439 std::vector<std::string> sshUsersList;
440 struct passwd pw, *pwp = nullptr;
441 std::array<char, 1024> buffer{};
442
443 phosphor::user::File passwd(passwdFileName, "r");
444 if ((passwd)() == NULL)
445 {
446 log<level::ERR>("Error opening the passwd file");
447 elog<InternalFailure>();
448 }
449
450 while (true)
451 {
452 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
453 &pwp);
454 if ((r != 0) || (pwp == NULL))
455 {
456 // Any error, break the loop.
457 break;
458 }
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530459 // Add all users whose UID >= 1000 and < 65534
460 // and special UID 0.
461 if ((pwp->pw_uid == 0) ||
462 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530463 {
464 std::string userName(pwp->pw_name);
465 userList.emplace_back(userName);
466
467 // ssh doesn't have separate group. Check login shell entry to
468 // get all users list which are member of ssh group.
469 std::string loginShell(pwp->pw_shell);
470 if (loginShell == "/bin/sh")
471 {
472 sshUsersList.emplace_back(userName);
473 }
474 }
475 }
476 endpwent();
477 return std::make_pair(std::move(userList), std::move(sshUsersList));
478}
479
480size_t UserMgr::getIpmiUsersCount()
481{
482 std::vector<std::string> userList = getUsersInGroup("ipmi");
483 return userList.size();
484}
485
486bool UserMgr::isUserEnabled(const std::string &userName)
487{
488 // All user management lock has to be based on /etc/shadow
489 phosphor::user::shadow::Lock lock();
490 std::array<char, 4096> buffer{};
491 struct spwd spwd;
492 struct spwd *resultPtr = nullptr;
493 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
494 buffer.max_size(), &resultPtr);
495 if (!status && (&spwd == resultPtr))
496 {
497 if (resultPtr->sp_expire >= 0)
498 {
499 return false; // user locked out
500 }
501 return true;
502 }
503 return false; // assume user is disabled for any error.
504}
505
506std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
507{
508 std::vector<std::string> usersInGroup;
509 // Should be more than enough to get the pwd structure.
510 std::array<char, 4096> buffer{};
511 struct group grp;
512 struct group *resultPtr = nullptr;
513
514 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
515 buffer.max_size(), &resultPtr);
516
517 if (!status && (&grp == resultPtr))
518 {
519 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
520 {
521 usersInGroup.emplace_back(*(grp.gr_mem));
522 }
523 }
524 else
525 {
526 log<level::ERR>("Group not found",
527 entry("GROUP=%s", groupName.c_str()));
528 // Don't throw error, just return empty userList - fallback
529 }
530 return usersInGroup;
531}
532
533void UserMgr::initUserObjects(void)
534{
535 // All user management lock has to be based on /etc/shadow
536 phosphor::user::shadow::Lock lock();
537 std::vector<std::string> userNameList;
538 std::vector<std::string> sshGrpUsersList;
539 UserSSHLists userSSHLists = getUserAndSshGrpList();
540 userNameList = std::move(userSSHLists.first);
541 sshGrpUsersList = std::move(userSSHLists.second);
542
543 if (!userNameList.empty())
544 {
545 std::map<std::string, std::vector<std::string>> groupLists;
546 for (auto &grp : groupsMgr)
547 {
548 if (grp == grpSsh)
549 {
550 groupLists.emplace(grp, sshGrpUsersList);
551 }
552 else
553 {
554 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
555 groupLists.emplace(grp, grpUsersList);
556 }
557 }
558 for (auto &grp : privMgr)
559 {
560 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
561 groupLists.emplace(grp, grpUsersList);
562 }
563
564 for (auto &user : userNameList)
565 {
566 std::vector<std::string> userGroups;
567 std::string userPriv;
568 for (const auto &grp : groupLists)
569 {
570 std::vector<std::string> tempGrp = grp.second;
571 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
572 tempGrp.end())
573 {
574 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
575 privMgr.end())
576 {
577 userPriv = grp.first;
578 }
579 else
580 {
581 userGroups.emplace_back(grp.first);
582 }
583 }
584 }
585 // Add user objects to the Users path.
586 auto objPath = std::string(usersObjPath) + "/" + user;
587 std::sort(userGroups.begin(), userGroups.end());
588 usersList.emplace(user,
589 std::move(std::make_unique<phosphor::user::Users>(
590 bus, objPath.c_str(), userGroups, userPriv,
591 isUserEnabled(user), *this)));
592 }
593 }
594}
595
596UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
597 UserMgrIface(bus, path), bus(bus), path(path)
598{
599 UserMgrIface::allPrivileges(privMgr);
600 std::sort(groupsMgr.begin(), groupsMgr.end());
601 UserMgrIface::allGroups(groupsMgr);
602 initUserObjects();
603}
604
605} // namespace user
606} // namespace phosphor