blob: ebed493bab07deb40ad765433cfa2bc2569ffa9a [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(),
277 "-M", "-N", "-s",
278 (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 {
306 executeCmd("/usr/sbin/userdel", userName.c_str());
307 }
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 {
332 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
333 userName.c_str());
334 }
335 catch (const InternalFailure &e)
336 {
337 log<level::ERR>("User rename failed",
338 entry("USER_NAME=%s", userName.c_str()));
339 elog<InternalFailure>();
340 }
341 const auto &user = usersList[userName];
342 std::string priv = user.get()->userPrivilege();
343 std::vector<std::string> groupNames = user.get()->userGroups();
344 bool enabled = user.get()->userEnabled();
345 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
346 // Special group 'ipmi' needs a way to identify user renamed, in order to
347 // update encrypted password. It can't rely only on InterfacesRemoved &
348 // InterfacesAdded. So first send out userRenamed signal.
349 this->userRenamed(userName, newUserName);
350 usersList.erase(userName);
351 usersList.emplace(
352 newUserName,
353 std::move(std::make_unique<phosphor::user::Users>(
354 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
355 return;
356}
357
358void UserMgr::updateGroupsAndPriv(const std::string &userName,
359 const std::vector<std::string> &groupNames,
360 const std::string &priv)
361{
362 throwForInvalidPrivilege(priv);
363 throwForInvalidGroups(groupNames);
364 // All user management lock has to be based on /etc/shadow
365 phosphor::user::shadow::Lock lock();
366 throwForUserDoesNotExist(userName);
367 const std::vector<std::string> &oldGroupNames =
368 usersList[userName].get()->userGroups();
369 std::vector<std::string> groupDiff;
370 // Note: already dealing with sorted group lists.
371 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
372 groupNames.begin(), groupNames.end(),
373 std::back_inserter(groupDiff));
374 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
375 groupDiff.end())
376 {
377 throwForUserNameConstraints(userName, groupNames);
378 throwForMaxGrpUserCount(groupNames);
379 }
380
381 std::string groups = getCSVFromVector(groupNames);
382 bool sshRequested = removeStringFromCSV(groups, grpSsh);
383
384 // treat privilege as a group - This is to avoid using different file to
385 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530386 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530387 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530388 if (groups.size() != 0)
389 {
390 groups += ",";
391 }
392 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530393 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 try
395 {
396 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
397 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
398 }
399 catch (const InternalFailure &e)
400 {
401 log<level::ERR>("Unable to modify user privilege / groups");
402 elog<InternalFailure>();
403 }
404
405 log<level::INFO>("User groups / privilege updated successfully",
406 entry("USER_NAME=%s", userName.c_str()));
407 return;
408}
409
410void UserMgr::userEnable(const std::string &userName, bool enabled)
411{
412 // All user management lock has to be based on /etc/shadow
413 phosphor::user::shadow::Lock lock();
414 throwForUserDoesNotExist(userName);
415 try
416 {
417 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
418 (enabled ? "" : "1970-01-02"));
419 }
420 catch (const InternalFailure &e)
421 {
422 log<level::ERR>("Unable to modify user enabled state");
423 elog<InternalFailure>();
424 }
425
426 log<level::INFO>("User enabled/disabled state updated successfully",
427 entry("USER_NAME=%s", userName.c_str()),
428 entry("ENABLED=%d", enabled));
429 return;
430}
431
432UserSSHLists UserMgr::getUserAndSshGrpList()
433{
434 // All user management lock has to be based on /etc/shadow
435 phosphor::user::shadow::Lock lock();
436
437 std::vector<std::string> userList;
438 std::vector<std::string> sshUsersList;
439 struct passwd pw, *pwp = nullptr;
440 std::array<char, 1024> buffer{};
441
442 phosphor::user::File passwd(passwdFileName, "r");
443 if ((passwd)() == NULL)
444 {
445 log<level::ERR>("Error opening the passwd file");
446 elog<InternalFailure>();
447 }
448
449 while (true)
450 {
451 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
452 &pwp);
453 if ((r != 0) || (pwp == NULL))
454 {
455 // Any error, break the loop.
456 break;
457 }
458 // All users whose UID >= 1000 and < 65534
459 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
460 {
461 std::string userName(pwp->pw_name);
462 userList.emplace_back(userName);
463
464 // ssh doesn't have separate group. Check login shell entry to
465 // get all users list which are member of ssh group.
466 std::string loginShell(pwp->pw_shell);
467 if (loginShell == "/bin/sh")
468 {
469 sshUsersList.emplace_back(userName);
470 }
471 }
472 }
473 endpwent();
474 return std::make_pair(std::move(userList), std::move(sshUsersList));
475}
476
477size_t UserMgr::getIpmiUsersCount()
478{
479 std::vector<std::string> userList = getUsersInGroup("ipmi");
480 return userList.size();
481}
482
483bool UserMgr::isUserEnabled(const std::string &userName)
484{
485 // All user management lock has to be based on /etc/shadow
486 phosphor::user::shadow::Lock lock();
487 std::array<char, 4096> buffer{};
488 struct spwd spwd;
489 struct spwd *resultPtr = nullptr;
490 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
491 buffer.max_size(), &resultPtr);
492 if (!status && (&spwd == resultPtr))
493 {
494 if (resultPtr->sp_expire >= 0)
495 {
496 return false; // user locked out
497 }
498 return true;
499 }
500 return false; // assume user is disabled for any error.
501}
502
503std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
504{
505 std::vector<std::string> usersInGroup;
506 // Should be more than enough to get the pwd structure.
507 std::array<char, 4096> buffer{};
508 struct group grp;
509 struct group *resultPtr = nullptr;
510
511 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
512 buffer.max_size(), &resultPtr);
513
514 if (!status && (&grp == resultPtr))
515 {
516 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
517 {
518 usersInGroup.emplace_back(*(grp.gr_mem));
519 }
520 }
521 else
522 {
523 log<level::ERR>("Group not found",
524 entry("GROUP=%s", groupName.c_str()));
525 // Don't throw error, just return empty userList - fallback
526 }
527 return usersInGroup;
528}
529
530void UserMgr::initUserObjects(void)
531{
532 // All user management lock has to be based on /etc/shadow
533 phosphor::user::shadow::Lock lock();
534 std::vector<std::string> userNameList;
535 std::vector<std::string> sshGrpUsersList;
536 UserSSHLists userSSHLists = getUserAndSshGrpList();
537 userNameList = std::move(userSSHLists.first);
538 sshGrpUsersList = std::move(userSSHLists.second);
539
540 if (!userNameList.empty())
541 {
542 std::map<std::string, std::vector<std::string>> groupLists;
543 for (auto &grp : groupsMgr)
544 {
545 if (grp == grpSsh)
546 {
547 groupLists.emplace(grp, sshGrpUsersList);
548 }
549 else
550 {
551 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
552 groupLists.emplace(grp, grpUsersList);
553 }
554 }
555 for (auto &grp : privMgr)
556 {
557 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
558 groupLists.emplace(grp, grpUsersList);
559 }
560
561 for (auto &user : userNameList)
562 {
563 std::vector<std::string> userGroups;
564 std::string userPriv;
565 for (const auto &grp : groupLists)
566 {
567 std::vector<std::string> tempGrp = grp.second;
568 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
569 tempGrp.end())
570 {
571 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
572 privMgr.end())
573 {
574 userPriv = grp.first;
575 }
576 else
577 {
578 userGroups.emplace_back(grp.first);
579 }
580 }
581 }
582 // Add user objects to the Users path.
583 auto objPath = std::string(usersObjPath) + "/" + user;
584 std::sort(userGroups.begin(), userGroups.end());
585 usersList.emplace(user,
586 std::move(std::make_unique<phosphor::user::Users>(
587 bus, objPath.c_str(), userGroups, userPriv,
588 isUserEnabled(user), *this)));
589 }
590 }
591}
592
593UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
594 UserMgrIface(bus, path), bus(bus), path(path)
595{
596 UserMgrIface::allPrivileges(privMgr);
597 std::sort(groupsMgr.begin(), groupsMgr.end());
598 UserMgrIface::allGroups(groupsMgr);
599 initUserObjects();
600}
601
602} // namespace user
603} // namespace phosphor