KCoreAddons

kuser_unix.cpp
1 /*
2  KUser - represent a user/account
3 
4  SPDX-FileCopyrightText: 2002 Tim Jansen <[email protected]>
5  SPDX-FileCopyrightText: 2014 Alex Richardson <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "config-util.h"
11 #include "kcoreaddons_debug.h"
12 #include "kuser.h"
13 
14 #include <QFileInfo>
15 
16 #include <cerrno>
17 #include <grp.h>
18 #include <pwd.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 
22 #include <algorithm> // std::find
23 #include <functional> // std::function
24 
25 #if defined(__BIONIC__) && __ANDROID_API__ < 26
26 static inline struct passwd *getpwent()
27 {
28  return nullptr;
29 }
30 inline void setpwent()
31 {
32 }
33 static inline void setgrent()
34 {
35 }
36 static inline struct group *getgrent()
37 {
38  return nullptr;
39 }
40 inline void endpwent()
41 {
42 }
43 static inline void endgrent()
44 {
45 }
46 #endif
47 
48 // Only define os_pw_size() if it's going to be used
49 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
50 static int os_pw_size() // hint for size of passwd struct
51 {
52  const int size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
53  if (size_max != -1) {
54  return size_max;
55  }
56  return 1024;
57 }
58 #endif
59 
60 // Only define os_gr_size() if it's going to be used
61 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24))
62 static int os_gr_size() // hint for size of group struct
63 {
64  const int size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
65  if (size_max != -1) {
66  return size_max;
67  }
68  return 1024;
69 }
70 #endif
71 
72 class KUserPrivate : public QSharedData
73 {
74 public:
75  uid_t uid = uid_t(-1);
76  gid_t gid = gid_t(-1);
77  QString loginName;
78  QString homeDir, shell;
80 
81  KUserPrivate()
82  {
83  }
84  KUserPrivate(const char *name)
85  {
86  if (!name) {
87  fillPasswd(nullptr);
88  } else {
89  struct passwd *pw = nullptr;
90 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
91  static const int bufsize = os_pw_size();
92  QVarLengthArray<char, 1024> buf(bufsize);
93  struct passwd entry;
94  getpwnam_r(name, &entry, buf.data(), buf.size(), &pw);
95 #else
96  pw = getpwnam(name); // not thread-safe!
97 #endif
98  fillPasswd(pw);
99  }
100  }
101  KUserPrivate(K_UID uid)
102  {
103  struct passwd *pw = nullptr;
104 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
105  static const int bufsize = os_pw_size();
106  QVarLengthArray<char, 1024> buf(bufsize);
107  struct passwd entry;
108  getpwuid_r(uid, &entry, buf.data(), buf.size(), &pw);
109 #else
110  pw = getpwuid(uid); // not thread-safe!
111 #endif
112  fillPasswd(pw);
113  }
114  KUserPrivate(const passwd *p)
115  {
116  fillPasswd(p);
117  }
118 
119  void fillPasswd(const passwd *p)
120  {
121  if (p) {
122 #ifndef __BIONIC__
123  QString gecos = QString::fromLocal8Bit(p->pw_gecos);
124 #else
125  QString gecos = QString();
126 #endif
127  QStringList gecosList = gecos.split(QLatin1Char(','));
128  // fill up the list, should be at least 4 entries
129  while (gecosList.size() < 4) {
130  gecosList << QString();
131  }
132 
133  uid = p->pw_uid;
134  gid = p->pw_gid;
135  loginName = QString::fromLocal8Bit(p->pw_name);
136  properties[KUser::FullName] = QVariant(gecosList[0]);
137  properties[KUser::RoomNumber] = QVariant(gecosList[1]);
138  properties[KUser::WorkPhone] = QVariant(gecosList[2]);
139  properties[KUser::HomePhone] = QVariant(gecosList[3]);
140  if (uid == ::getuid() && uid == ::geteuid()) {
141  homeDir = QFile::decodeName(qgetenv("HOME"));
142  }
143  if (homeDir.isEmpty()) {
144  homeDir = QFile::decodeName(p->pw_dir);
145  }
146  shell = QString::fromLocal8Bit(p->pw_shell);
147  }
148  }
149 };
150 
152 {
153  uid_t _uid = ::getuid();
154  uid_t _euid;
155  if (mode == UseEffectiveUID && (_euid = ::geteuid()) != _uid) {
156  d = new KUserPrivate(_euid);
157  } else {
158  d = new KUserPrivate(qgetenv("LOGNAME").constData());
159  if (d->uid != _uid) {
160  d = new KUserPrivate(qgetenv("USER").constData());
161  if (d->uid != _uid) {
162  d = new KUserPrivate(_uid);
163  }
164  }
165  }
166 }
167 
168 KUser::KUser(K_UID _uid)
169  : d(new KUserPrivate(_uid))
170 {
171 }
172 
174  : d(new KUserPrivate(_uid.nativeId()))
175 {
176 }
177 
178 KUser::KUser(const QString &name)
179  : d(new KUserPrivate(name.toLocal8Bit().data()))
180 {
181 }
182 
183 KUser::KUser(const char *name)
184  : d(new KUserPrivate(name))
185 {
186 }
187 
188 KUser::KUser(const passwd *p)
189  : d(new KUserPrivate(p))
190 {
191 }
192 
193 KUser::KUser(const KUser &user)
194  : d(user.d)
195 {
196 }
197 
199 {
200  d = user.d;
201  return *this;
202 }
203 
204 bool KUser::operator==(const KUser &user) const
205 {
206  return isValid() && (d->uid == user.d->uid);
207 }
208 
209 bool KUser::isValid() const
210 {
211  return d->uid != uid_t(-1);
212 }
213 
215 {
216  return KUserId(d->uid);
217 }
218 
220 {
221  return KGroupId(d->gid);
222 }
223 
224 bool KUser::isSuperUser() const
225 {
226  return d->uid == 0;
227 }
228 
230 {
231  return d->loginName;
232 }
233 
235 {
236  return d->homeDir;
237 }
238 
240 {
241  QString pathToFaceIcon;
242  if (!d->loginName.isEmpty()) {
243  pathToFaceIcon = QStringLiteral(ACCOUNTS_SERVICE_ICON_DIR) + QLatin1Char('/') + d->loginName;
244  }
245 
246  if (QFile::exists(pathToFaceIcon)) {
247  return pathToFaceIcon;
248  }
249 
250  pathToFaceIcon = homeDir() + QLatin1Char('/') + QLatin1String(".face.icon");
251 
252  if (QFileInfo(pathToFaceIcon).isReadable()) {
253  return pathToFaceIcon;
254  }
255 
256  return QString();
257 }
258 
260 {
261  return d->shell;
262 }
263 
264 template<class Func>
265 static void listGroupsForUser(const char *name, gid_t gid, uint maxCount, Func handleNextGroup)
266 {
267  if (Q_UNLIKELY(maxCount == 0)) {
268  return;
269  }
270  uint found = 0;
271 #if HAVE_GETGROUPLIST
272  QVarLengthArray<gid_t, 100> gid_buffer;
273  gid_buffer.resize(100);
274  int numGroups = gid_buffer.size();
275  int result = getgrouplist(name, gid, gid_buffer.data(), &numGroups);
276  if (result < 0 && uint(numGroups) < maxCount) {
277  // getgrouplist returns -1 if the buffer was too small to store all entries, the required size is in numGroups
278  qCDebug(KCOREADDONS_DEBUG) << "Buffer was too small: " << gid_buffer.size() << ", need" << numGroups;
279  gid_buffer.resize(numGroups);
280  numGroups = gid_buffer.size();
281  getgrouplist(name, gid, gid_buffer.data(), &numGroups);
282  }
283  for (int i = 0; i < numGroups && found < maxCount; ++i) {
284  struct group *g = getgrgid(gid_buffer[i]); // ### not threadsafe
285  // should never be null, but better be safe than crash
286  if (g) {
287  found++;
288  handleNextGroup(g);
289  }
290  }
291 #else
292  // fall back to getgrent() and reading gr->gr_mem
293  // This is slower than getgrouplist, but works as well
294  // add the current gid, this is often not part of g->gr_mem (e.g. build.kde.org or my openSuSE 13.1 system)
295  struct group *g = getgrgid(gid); // ### not threadsafe
296  if (g) {
297  handleNextGroup(g);
298  found++;
299  if (found >= maxCount) {
300  return;
301  }
302  }
303 
304  static const auto groupContainsUser = [](struct group *g, const char *name) -> bool {
305  for (char **user = g->gr_mem; *user; user++) {
306  if (strcmp(name, *user) == 0) {
307  return true;
308  }
309  }
310  return false;
311  };
312 
313  setgrent();
314  while ((g = getgrent())) {
315  // don't add the current gid again
316  if (g->gr_gid != gid && groupContainsUser(g, name)) {
317  handleNextGroup(g);
318  found++;
319  if (found >= maxCount) {
320  break;
321  }
322  }
323  }
324  endgrent();
325 #endif
326 }
327 
328 QList<KUserGroup> KUser::groups(uint maxCount) const
329 {
330  QList<KUserGroup> result;
331  listGroupsForUser(d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group *g) {
332  result.append(KUserGroup(g));
333  });
334  return result;
335 }
336 
337 QStringList KUser::groupNames(uint maxCount) const
338 {
339  QStringList result;
340  listGroupsForUser(d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group *g) {
341  result.append(QString::fromLocal8Bit(g->gr_name));
342  });
343  return result;
344 }
345 
346 QVariant KUser::property(UserProperty which) const
347 {
348  return d->properties.value(which);
349 }
350 
352 {
353  QList<KUser> result;
354 
355  passwd *p;
356  setpwent();
357 
358  for (uint i = 0; i < maxCount && (p = getpwent()); ++i) {
359  result.append(KUser(p));
360  }
361 
362  endpwent();
363 
364  return result;
365 }
366 
368 {
369  QStringList result;
370 
371  passwd *p;
372  setpwent();
373 
374  for (uint i = 0; i < maxCount && (p = getpwent()); ++i) {
375  result.append(QString::fromLocal8Bit(p->pw_name));
376  }
377 
378  endpwent();
379  return result;
380 }
381 
383 {
384 }
385 
386 class KUserGroupPrivate : public QSharedData
387 {
388 public:
389  gid_t gid = gid_t(-1);
390  QString name;
391 
392  KUserGroupPrivate()
393  {
394  }
395  KUserGroupPrivate(const char *_name)
396  {
397  fillGroup(_name ? ::getgrnam(_name) : nullptr);
398  }
399  KUserGroupPrivate(K_GID gid)
400  {
401  struct group *gr = nullptr;
402 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24))
403  static const int bufsize = os_gr_size();
404  QVarLengthArray<char, 1024> buf(bufsize);
405  struct group entry;
406  // Some large systems have more members than the POSIX max size
407  // Loop over by doubling the buffer size (upper limit 250k)
408  for (int size = bufsize; size < 256000; size += size) {
409  buf.resize(size);
410  // ERANGE indicates that the buffer was too small
411  if (!getgrgid_r(gid, &entry, buf.data(), buf.size(), &gr) || errno != ERANGE) {
412  break;
413  }
414  }
415 #else
416  gr = getgrgid(gid); // not thread-safe!
417 #endif
418  fillGroup(gr);
419  }
420  KUserGroupPrivate(const ::group *p)
421  {
422  fillGroup(p);
423  }
424 
425  void fillGroup(const ::group *p)
426  {
427  if (p) {
428  gid = p->gr_gid;
429  name = QString::fromLocal8Bit(p->gr_name);
430  }
431  }
432 };
433 
435  : d(new KUserGroupPrivate(KUser(mode).groupId().nativeId()))
436 {
437 }
438 
440  : d(new KUserGroupPrivate(_gid))
441 {
442 }
443 
445  : d(new KUserGroupPrivate(_gid.nativeId()))
446 {
447 }
448 
450  : d(new KUserGroupPrivate(_name.toLocal8Bit().data()))
451 {
452 }
453 
454 KUserGroup::KUserGroup(const char *_name)
455  : d(new KUserGroupPrivate(_name))
456 {
457 }
458 
459 KUserGroup::KUserGroup(const ::group *g)
460  : d(new KUserGroupPrivate(g))
461 {
462 }
463 
465  : d(group.d)
466 {
467 }
468 
470 {
471  d = group.d;
472  return *this;
473 }
474 
475 bool KUserGroup::operator==(const KUserGroup &group) const
476 {
477  return isValid() && (d->gid == group.d->gid);
478 }
479 
481 {
482  return d->gid != gid_t(-1);
483 }
484 
486 {
487  return KGroupId(d->gid);
488 }
489 
491 {
492  return d->name;
493 }
494 
495 static void listGroupMembers(gid_t gid, uint maxCount, std::function<void(passwd *)> handleNextGroupUser)
496 {
497  if (maxCount == 0) {
498  return;
499  }
500  struct group *g = getgrgid(gid); // ### not threadsafe
501  if (!g) {
502  return;
503  }
504  uint found = 0;
505  QVarLengthArray<uid_t> addedUsers;
506  struct passwd *p = nullptr;
507  for (char **user = g->gr_mem; *user; user++) {
508  if ((p = getpwnam(*user))) { // ### not threadsafe
509  addedUsers.append(p->pw_uid);
510  handleNextGroupUser(p);
511  found++;
512  if (found >= maxCount) {
513  break;
514  }
515  }
516  }
517 
518  // gr_mem doesn't contain users where the primary group == gid -> we have to iterate over all users
519  setpwent();
520  while ((p = getpwent()) && found < maxCount) {
521  if (p->pw_gid != gid) {
522  continue; // only need primary gid since otherwise gr_mem already contains this user
523  }
524  // make sure we don't list a user twice
525  if (std::find(addedUsers.cbegin(), addedUsers.cend(), p->pw_uid) == addedUsers.cend()) {
526  handleNextGroupUser(p);
527  found++;
528  }
529  }
530  endpwent();
531 }
532 
533 QList<KUser> KUserGroup::users(uint maxCount) const
534 {
535  QList<KUser> result;
536  listGroupMembers(d->gid, maxCount, [&](const passwd *p) {
537  result.append(KUser(p));
538  });
539  return result;
540 }
541 
542 QStringList KUserGroup::userNames(uint maxCount) const
543 {
544  QStringList result;
545  listGroupMembers(d->gid, maxCount, [&](const passwd *p) {
546  result.append(QString::fromLocal8Bit(p->pw_name));
547  });
548  return result;
549 }
550 
552 {
553  QList<KUserGroup> result;
554 
555  ::group *g;
556  setgrent();
557 
558  for (uint i = 0; i < maxCount && (g = getgrent()); ++i) {
559  result.append(KUserGroup(g));
560  }
561 
562  endgrent();
563 
564  return result;
565 }
566 
568 {
569  QStringList result;
570 
571  ::group *g;
572  setgrent();
573 
574  for (uint i = 0; i < maxCount && (g = getgrent()); ++i) {
575  result.append(QString::fromLocal8Bit(g->gr_name));
576  }
577 
578  endgrent();
579 
580  return result;
581 }
582 
584 {
585 }
586 
588 {
589  if (name.isEmpty()) {
590  return KUserId();
591  }
592  QByteArray name8Bit = name.toLocal8Bit();
593  struct passwd *p = ::getpwnam(name8Bit.constData());
594  if (!p) {
595  qCWarning(KCOREADDONS_DEBUG, "Failed to lookup user %s: %s", name8Bit.constData(), strerror(errno));
596  return KUserId();
597  }
598  return KUserId(p->pw_uid);
599 }
600 
602 {
603  if (name.isEmpty()) {
604  return KGroupId();
605  }
606  QByteArray name8Bit = name.toLocal8Bit();
607  struct group *g = ::getgrnam(name8Bit.constData());
608  if (!g) {
609  qCWarning(KCOREADDONS_DEBUG, "Failed to lookup group %s: %s", name8Bit.constData(), strerror(errno));
610  return KGroupId();
611  }
612  return KGroupId(g->gr_gid);
613 }
614 
616 {
617  return KUserId(getuid());
618 }
619 
621 {
622  return KUserId(geteuid());
623 }
624 
626 {
627  return KGroupId(getgid());
628 }
629 
631 {
632  return KGroupId(getegid());
633 }
void append(const T &value)
KGroupId()
Creates an invalid KGroupId.
Definition: kuser.h:170
static KUserId fromName(const QString &name)
Definition: kuser_unix.cpp:587
KGroupId groupId() const
Definition: kuser_unix.cpp:485
KUserId()
Creates an invalid KUserId.
Definition: kuser.h:135
KUserGroup(const QString &name)
Create an object from a group name.
Definition: kuser_unix.cpp:449
static QList< KUserGroup > allGroups(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:551
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void resize(int size)
KUserId userId() const
Definition: kuser_unix.cpp:214
bool isValid() const
Returns whether the group is valid.
Definition: kuser_unix.cpp:480
static QList< KUser > allUsers(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:351
KGuiItem properties()
void append(const T &t)
@ UseEffectiveUID
Use the effective user id.
Definition: kuser.h:229
bool exists() const const
QList< KUserGroup > groups(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:328
UIDMode
Definition: kuser.h:228
int size() const const
static KUserId currentUserId()
Definition: kuser_unix.cpp:615
QVarLengthArray::const_iterator cbegin() const const
static KGroupId currentGroupId()
Definition: kuser_unix.cpp:625
A platform independent user ID.
Definition: kuser.h:133
QString fromLocal8Bit(const char *str, int size)
Represents a group on your system.
Definition: kuser.h:457
QList< KUser > users(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:533
bool isEmpty() const const
bool isValid() const
Returns true if the user is valid.
Definition: kuser_unix.cpp:209
~KUserGroup()
Destructor.
Definition: kuser_unix.cpp:583
QString name() const
The name of the group.
Definition: kuser_unix.cpp:490
bool isSuperUser() const
Checks whether the user is the super user (root).
Definition: kuser_unix.cpp:224
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:367
QString loginName() const
The login name of the user.
Definition: kuser_unix.cpp:229
static KGroupId fromName(const QString &name)
Definition: kuser_unix.cpp:601
A platform independent group ID.
Definition: kuser.h:168
Represents a user on your system.
Definition: kuser.h:225
~KUser()
Destructor.
Definition: kuser_unix.cpp:382
QVariant property(UserProperty which) const
Returns an extended property.
Definition: kuser_unix.cpp:346
bool operator==(const KUserGroup &group) const
Two KUserGroup objects are equal if their gid()s are identical.
Definition: kuser_unix.cpp:475
static KUserId currentEffectiveUserId()
Definition: kuser_unix.cpp:620
const char * constData() const const
KUser & operator=(const KUser &user)
Copies a user.
Definition: kuser_unix.cpp:198
QString name(StandardShortcut id)
KUser(UIDMode mode=UseEffectiveUID)
Creates an object that contains information about the current user.
Definition: kuser_unix.cpp:151
QString faceIconPath() const
The path to the user's face file.
Definition: kuser_unix.cpp:239
bool operator==(const KUser &user) const
Two KUser objects are equal if the userId() are identical.
Definition: kuser_unix.cpp:204
static KGroupId currentEffectiveGroupId()
Definition: kuser_unix.cpp:630
QStringList userNames(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:542
QByteArray toLocal8Bit() const const
int size() const const
QString homeDir() const
The path to the user's home directory.
Definition: kuser_unix.cpp:234
QStringList groupNames(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:337
const T * constData() const const
KUserGroup & operator=(const KUserGroup &group)
Copies a group.
Definition: kuser_unix.cpp:469
QVarLengthArray::const_iterator cend() const const
static QStringList allGroupNames(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:567
KGroupId groupId() const
Definition: kuser_unix.cpp:219
QString shell() const
The path to the user's login shell.
Definition: kuser_unix.cpp:259
QString decodeName(const QByteArray &localFileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:04:52 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.