KCoreAddons

kuser_unix.cpp
1/*
2 KUser - represent a user/account
3
4 SPDX-FileCopyrightText: 2002 Tim Jansen <tim@tjansen.de>
5 SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
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
26static inline struct passwd *getpwent()
27{
28 return nullptr;
29}
30inline void setpwent()
31{
32}
33static inline void setgrent()
34{
35}
36static inline struct group *getgrent()
37{
38 return nullptr;
39}
40inline void endpwent()
41{
42}
43static 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)
50static 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))
62static 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
72class KUserPrivate : public QSharedData
73{
74public:
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
168KUser::KUser(K_UID _uid)
169 : d(new KUserPrivate(_uid))
170{
171}
172
174 : d(new KUserPrivate(_uid.nativeId()))
175{
176}
177
179 : d(new KUserPrivate(name.toLocal8Bit().data()))
180{
181}
182
183KUser::KUser(const char *name)
184 : d(new KUserPrivate(name))
185{
186}
187
188KUser::KUser(const passwd *p)
189 : d(new KUserPrivate(p))
190{
191}
192
193KUser::KUser(const KUser &user)
194 : d(user.d)
195{
196}
197
199{
200 d = user.d;
201 return *this;
202}
203
204bool KUser::operator==(const KUser &user) const
205{
206 return isValid() && (d->uid == user.d->uid);
207}
208
209bool 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
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
264template<class Func>
265static 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
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
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
337QStringList 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
346QVariant 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
386class KUserGroupPrivate : public QSharedData
387{
388public:
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
454KUserGroup::KUserGroup(const char *_name)
455 : d(new KUserGroupPrivate(_name))
456{
457}
458
459KUserGroup::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
475bool 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
495static 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
533QList<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
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
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}
Represents a group on your system.
Definition kuser.h:424
QStringList userNames(uint maxCount=KCOREADDONS_UINT_MAX) const
static QList< KUserGroup > allGroups(uint maxCount=KCOREADDONS_UINT_MAX)
QString name() const
The name of the group.
KUserGroup(const QString &name)
Create an object from a group name.
bool isValid() const
Returns whether the group is valid.
KGroupId groupId() const
QList< KUser > users(uint maxCount=KCOREADDONS_UINT_MAX) const
bool operator==(const KUserGroup &group) const
Two KUserGroup objects are equal if their gid()s are identical.
static QStringList allGroupNames(uint maxCount=KCOREADDONS_UINT_MAX)
~KUserGroup()
Destructor.
KUserGroup & operator=(const KUserGroup &group)
Copies a group.
Represents a user on your system.
Definition kuser.h:226
QString faceIconPath() const
The path to the user's face file.
KUser(UIDMode mode=UseEffectiveUID)
Creates an object that contains information about the current user.
KGroupId groupId() const
QString homeDir() const
The path to the user's home directory.
QVariant property(UserProperty which) const
Returns an extended property.
QList< KUserGroup > groups(uint maxCount=KCOREADDONS_UINT_MAX) const
QString shell() const
The path to the user's login shell.
~KUser()
Destructor.
bool operator==(const KUser &user) const
Two KUser objects are equal if the userId() are identical.
QStringList groupNames(uint maxCount=KCOREADDONS_UINT_MAX) const
KUserId userId() const
QString loginName() const
The login name of the user.
bool isValid() const
Returns true if the user is valid.
static QList< KUser > allUsers(uint maxCount=KCOREADDONS_UINT_MAX)
bool isSuperUser() const
Checks whether the user is the super user (root).
KUser & operator=(const KUser &user)
Copies a user.
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
UIDMode
Definition kuser.h:228
@ UseEffectiveUID
Use the effective user id.
Definition kuser.h:229
KGuiItem properties()
QString name(StandardShortcut id)
const char * constData() const const
QString decodeName(const QByteArray &localFileName)
bool exists() const const
void append(QList< T > &&value)
qsizetype size() const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toLocal8Bit() const const
void append(T &&t)
const_iterator cbegin() const const
const_iterator cend() const const
void resize(qsizetype size)
qsizetype size() const const
A platform independent group ID.
Definition kuser.h:168
static KGroupId fromName(const QString &name)
KGroupId()
Creates an invalid KGroupId.
Definition kuser.h:170
static KGroupId currentGroupId()
static KGroupId currentEffectiveGroupId()
A platform independent user ID.
Definition kuser.h:133
static KUserId currentUserId()
KUserId()
Creates an invalid KUserId.
Definition kuser.h:135
static KUserId fromName(const QString &name)
static KUserId currentEffectiveUserId()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.