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;
79 QMap<KUser::UserProperty, QVariant> properties;
80
81 KUserPrivate()
82 {
83 }
84 KUserPrivate(const char *name)
85 {
86 if (!name) {
87 fillPasswd(p: 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(p: 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(p: 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
151KUser::KUser(UIDMode mode)
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
173KUser::KUser(KUserId _uid)
174 : d(new KUserPrivate(_uid.nativeId()))
175{
176}
177
178KUser::KUser(const QString &name)
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
198KUser &KUser::operator=(const KUser &user)
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
214KUserId KUser::userId() const
215{
216 return KUserId(d->uid);
217}
218
219KGroupId KUser::groupId() const
220{
221 return KGroupId(d->gid);
222}
223
224bool KUser::isSuperUser() const
225{
226 return d->uid == 0;
227}
228
229QString KUser::loginName() const
230{
231 return d->loginName;
232}
233
234QString KUser::homeDir() const
235{
236 return d->homeDir;
237}
238
239QString KUser::faceIconPath() const
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
259QString KUser::shell() const
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
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
328QList<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
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
351QList<KUser> KUser::allUsers(uint maxCount)
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
367QStringList KUser::allUserNames(uint maxCount)
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
382KUser::~KUser()
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(p: _name ? ::getgrnam(name: _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(p: 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
434KUserGroup::KUserGroup(KUser::UIDMode mode)
435 : d(new KUserGroupPrivate(KUser(mode).groupId().nativeId()))
436{
437}
438
439KUserGroup::KUserGroup(K_GID _gid)
440 : d(new KUserGroupPrivate(_gid))
441{
442}
443
444KUserGroup::KUserGroup(KGroupId _gid)
445 : d(new KUserGroupPrivate(_gid.nativeId()))
446{
447}
448
449KUserGroup::KUserGroup(const QString &_name)
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
464KUserGroup::KUserGroup(const KUserGroup &group)
465 : d(group.d)
466{
467}
468
469KUserGroup &KUserGroup::operator=(const KUserGroup &group)
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
480bool KUserGroup::isValid() const
481{
482 return d->gid != gid_t(-1);
483}
484
485KGroupId KUserGroup::groupId() const
486{
487 return KGroupId(d->gid);
488}
489
490QString KUserGroup::name() const
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: 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(name: *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
542QStringList 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
551QList<KUserGroup> KUserGroup::allGroups(uint maxCount)
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
567QStringList KUserGroup::allGroupNames(uint maxCount)
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
583KUserGroup::~KUserGroup()
584{
585}
586
587KUserId KUserId::fromName(const QString &name)
588{
589 if (name.isEmpty()) {
590 return KUserId();
591 }
592 QByteArray name8Bit = name.toLocal8Bit();
593 struct passwd *p = ::getpwnam(name: 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
601KGroupId KGroupId::fromName(const QString &name)
602{
603 if (name.isEmpty()) {
604 return KGroupId();
605 }
606 QByteArray name8Bit = name.toLocal8Bit();
607 struct group *g = ::getgrnam(name: 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
615KUserId KUserId::currentUserId()
616{
617 return KUserId(getuid());
618}
619
620KUserId KUserId::currentEffectiveUserId()
621{
622 return KUserId(geteuid());
623}
624
625KGroupId KGroupId::currentGroupId()
626{
627 return KGroupId(getgid());
628}
629
630KGroupId KGroupId::currentEffectiveGroupId()
631{
632 return KGroupId(getegid());
633}
634

source code of kcoreaddons/src/lib/util/kuser_unix.cpp