blob: 3987088684389ae7944a3fa49ad9a7d30a0e140d [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.
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
297void 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
320void 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
356void 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
405void 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
427UserSSHLists 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
472size_t UserMgr::getIpmiUsersCount()
473{
474 std::vector<std::string> userList = getUsersInGroup("ipmi");
475 return userList.size();
476}
477
478bool 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
498std::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
525void 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
588UserMgr::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