File indexing completed on 2024-05-12 03:55:01

0001 /*
0002     KUser - represent a user/account
0003 
0004     SPDX-FileCopyrightText: 2002 Tim Jansen <tim@tjansen.de>
0005     SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "config-util.h"
0011 #include "kcoreaddons_debug.h"
0012 #include "kuser.h"
0013 
0014 #include <QFileInfo>
0015 
0016 #include <cerrno>
0017 #include <grp.h>
0018 #include <pwd.h>
0019 #include <stdlib.h>
0020 #include <unistd.h>
0021 
0022 #include <algorithm> // std::find
0023 #include <functional> // std::function
0024 
0025 #if defined(__BIONIC__) && __ANDROID_API__ < 26
0026 static inline struct passwd *getpwent()
0027 {
0028     return nullptr;
0029 }
0030 inline void setpwent()
0031 {
0032 }
0033 static inline void setgrent()
0034 {
0035 }
0036 static inline struct group *getgrent()
0037 {
0038     return nullptr;
0039 }
0040 inline void endpwent()
0041 {
0042 }
0043 static inline void endgrent()
0044 {
0045 }
0046 #endif
0047 
0048 // Only define os_pw_size() if it's going to be used
0049 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
0050 static int os_pw_size() // hint for size of passwd struct
0051 {
0052     const int size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
0053     if (size_max != -1) {
0054         return size_max;
0055     }
0056     return 1024;
0057 }
0058 #endif
0059 
0060 // Only define os_gr_size() if it's going to be used
0061 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24))
0062 static int os_gr_size() // hint for size of group struct
0063 {
0064     const int size_max = sysconf(_SC_GETGR_R_SIZE_MAX);
0065     if (size_max != -1) {
0066         return size_max;
0067     }
0068     return 1024;
0069 }
0070 #endif
0071 
0072 class KUserPrivate : public QSharedData
0073 {
0074 public:
0075     uid_t uid = uid_t(-1);
0076     gid_t gid = gid_t(-1);
0077     QString loginName;
0078     QString homeDir, shell;
0079     QMap<KUser::UserProperty, QVariant> properties;
0080 
0081     KUserPrivate()
0082     {
0083     }
0084     KUserPrivate(const char *name)
0085     {
0086         if (!name) {
0087             fillPasswd(nullptr);
0088         } else {
0089             struct passwd *pw = nullptr;
0090 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
0091             static const int bufsize = os_pw_size();
0092             QVarLengthArray<char, 1024> buf(bufsize);
0093             struct passwd entry;
0094             getpwnam_r(name, &entry, buf.data(), buf.size(), &pw);
0095 #else
0096             pw = getpwnam(name); // not thread-safe!
0097 #endif
0098             fillPasswd(pw);
0099         }
0100     }
0101     KUserPrivate(K_UID uid)
0102     {
0103         struct passwd *pw = nullptr;
0104 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
0105         static const int bufsize = os_pw_size();
0106         QVarLengthArray<char, 1024> buf(bufsize);
0107         struct passwd entry;
0108         getpwuid_r(uid, &entry, buf.data(), buf.size(), &pw);
0109 #else
0110         pw = getpwuid(uid); // not thread-safe!
0111 #endif
0112         fillPasswd(pw);
0113     }
0114     KUserPrivate(const passwd *p)
0115     {
0116         fillPasswd(p);
0117     }
0118 
0119     void fillPasswd(const passwd *p)
0120     {
0121         if (p) {
0122 #ifndef __BIONIC__
0123             QString gecos = QString::fromLocal8Bit(p->pw_gecos);
0124 #else
0125             QString gecos = QString();
0126 #endif
0127             QStringList gecosList = gecos.split(QLatin1Char(','));
0128             // fill up the list, should be at least 4 entries
0129             while (gecosList.size() < 4) {
0130                 gecosList << QString();
0131             }
0132 
0133             uid = p->pw_uid;
0134             gid = p->pw_gid;
0135             loginName = QString::fromLocal8Bit(p->pw_name);
0136             properties[KUser::FullName] = QVariant(gecosList[0]);
0137             properties[KUser::RoomNumber] = QVariant(gecosList[1]);
0138             properties[KUser::WorkPhone] = QVariant(gecosList[2]);
0139             properties[KUser::HomePhone] = QVariant(gecosList[3]);
0140             if (uid == ::getuid() && uid == ::geteuid()) {
0141                 homeDir = QFile::decodeName(qgetenv("HOME"));
0142             }
0143             if (homeDir.isEmpty()) {
0144                 homeDir = QFile::decodeName(p->pw_dir);
0145             }
0146             shell = QString::fromLocal8Bit(p->pw_shell);
0147         }
0148     }
0149 };
0150 
0151 KUser::KUser(UIDMode mode)
0152 {
0153     uid_t _uid = ::getuid();
0154     uid_t _euid;
0155     if (mode == UseEffectiveUID && (_euid = ::geteuid()) != _uid) {
0156         d = new KUserPrivate(_euid);
0157     } else {
0158         d = new KUserPrivate(qgetenv("LOGNAME").constData());
0159         if (d->uid != _uid) {
0160             d = new KUserPrivate(qgetenv("USER").constData());
0161             if (d->uid != _uid) {
0162                 d = new KUserPrivate(_uid);
0163             }
0164         }
0165     }
0166 }
0167 
0168 KUser::KUser(K_UID _uid)
0169     : d(new KUserPrivate(_uid))
0170 {
0171 }
0172 
0173 KUser::KUser(KUserId _uid)
0174     : d(new KUserPrivate(_uid.nativeId()))
0175 {
0176 }
0177 
0178 KUser::KUser(const QString &name)
0179     : d(new KUserPrivate(name.toLocal8Bit().data()))
0180 {
0181 }
0182 
0183 KUser::KUser(const char *name)
0184     : d(new KUserPrivate(name))
0185 {
0186 }
0187 
0188 KUser::KUser(const passwd *p)
0189     : d(new KUserPrivate(p))
0190 {
0191 }
0192 
0193 KUser::KUser(const KUser &user)
0194     : d(user.d)
0195 {
0196 }
0197 
0198 KUser &KUser::operator=(const KUser &user)
0199 {
0200     d = user.d;
0201     return *this;
0202 }
0203 
0204 bool KUser::operator==(const KUser &user) const
0205 {
0206     return isValid() && (d->uid == user.d->uid);
0207 }
0208 
0209 bool KUser::isValid() const
0210 {
0211     return d->uid != uid_t(-1);
0212 }
0213 
0214 KUserId KUser::userId() const
0215 {
0216     return KUserId(d->uid);
0217 }
0218 
0219 KGroupId KUser::groupId() const
0220 {
0221     return KGroupId(d->gid);
0222 }
0223 
0224 bool KUser::isSuperUser() const
0225 {
0226     return d->uid == 0;
0227 }
0228 
0229 QString KUser::loginName() const
0230 {
0231     return d->loginName;
0232 }
0233 
0234 QString KUser::homeDir() const
0235 {
0236     return d->homeDir;
0237 }
0238 
0239 QString KUser::faceIconPath() const
0240 {
0241     QString pathToFaceIcon;
0242     if (!d->loginName.isEmpty()) {
0243         pathToFaceIcon = QStringLiteral(ACCOUNTS_SERVICE_ICON_DIR) + QLatin1Char('/') + d->loginName;
0244     }
0245 
0246     if (QFile::exists(pathToFaceIcon)) {
0247         return pathToFaceIcon;
0248     }
0249 
0250     pathToFaceIcon = homeDir() + QLatin1Char('/') + QLatin1String(".face.icon");
0251 
0252     if (QFileInfo(pathToFaceIcon).isReadable()) {
0253         return pathToFaceIcon;
0254     }
0255 
0256     return QString();
0257 }
0258 
0259 QString KUser::shell() const
0260 {
0261     return d->shell;
0262 }
0263 
0264 template<class Func>
0265 static void listGroupsForUser(const char *name, gid_t gid, uint maxCount, Func handleNextGroup)
0266 {
0267     if (Q_UNLIKELY(maxCount == 0)) {
0268         return;
0269     }
0270     uint found = 0;
0271 #if HAVE_GETGROUPLIST
0272     QVarLengthArray<gid_t, 100> gid_buffer;
0273     gid_buffer.resize(100);
0274     int numGroups = gid_buffer.size();
0275     int result = getgrouplist(name, gid, gid_buffer.data(), &numGroups);
0276     if (result < 0 && uint(numGroups) < maxCount) {
0277         // getgrouplist returns -1 if the buffer was too small to store all entries, the required size is in numGroups
0278         qCDebug(KCOREADDONS_DEBUG) << "Buffer was too small: " << gid_buffer.size() << ", need" << numGroups;
0279         gid_buffer.resize(numGroups);
0280         numGroups = gid_buffer.size();
0281         getgrouplist(name, gid, gid_buffer.data(), &numGroups);
0282     }
0283     for (int i = 0; i < numGroups && found < maxCount; ++i) {
0284         struct group *g = getgrgid(gid_buffer[i]); // ### not threadsafe
0285         // should never be null, but better be safe than crash
0286         if (g) {
0287             found++;
0288             handleNextGroup(g);
0289         }
0290     }
0291 #else
0292     // fall back to getgrent() and reading gr->gr_mem
0293     // This is slower than getgrouplist, but works as well
0294     // add the current gid, this is often not part of g->gr_mem (e.g. build.kde.org or my openSuSE 13.1 system)
0295     struct group *g = getgrgid(gid); // ### not threadsafe
0296     if (g) {
0297         handleNextGroup(g);
0298         found++;
0299         if (found >= maxCount) {
0300             return;
0301         }
0302     }
0303 
0304     static const auto groupContainsUser = [](struct group *g, const char *name) -> bool {
0305         for (char **user = g->gr_mem; *user; user++) {
0306             if (strcmp(name, *user) == 0) {
0307                 return true;
0308             }
0309         }
0310         return false;
0311     };
0312 
0313     setgrent();
0314     while ((g = getgrent())) {
0315         // don't add the current gid again
0316         if (g->gr_gid != gid && groupContainsUser(g, name)) {
0317             handleNextGroup(g);
0318             found++;
0319             if (found >= maxCount) {
0320                 break;
0321             }
0322         }
0323     }
0324     endgrent();
0325 #endif
0326 }
0327 
0328 QList<KUserGroup> KUser::groups(uint maxCount) const
0329 {
0330     QList<KUserGroup> result;
0331     listGroupsForUser(d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group *g) {
0332         result.append(KUserGroup(g));
0333     });
0334     return result;
0335 }
0336 
0337 QStringList KUser::groupNames(uint maxCount) const
0338 {
0339     QStringList result;
0340     listGroupsForUser(d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group *g) {
0341         result.append(QString::fromLocal8Bit(g->gr_name));
0342     });
0343     return result;
0344 }
0345 
0346 QVariant KUser::property(UserProperty which) const
0347 {
0348     return d->properties.value(which);
0349 }
0350 
0351 QList<KUser> KUser::allUsers(uint maxCount)
0352 {
0353     QList<KUser> result;
0354 
0355     passwd *p;
0356     setpwent();
0357 
0358     for (uint i = 0; i < maxCount && (p = getpwent()); ++i) {
0359         result.append(KUser(p));
0360     }
0361 
0362     endpwent();
0363 
0364     return result;
0365 }
0366 
0367 QStringList KUser::allUserNames(uint maxCount)
0368 {
0369     QStringList result;
0370 
0371     passwd *p;
0372     setpwent();
0373 
0374     for (uint i = 0; i < maxCount && (p = getpwent()); ++i) {
0375         result.append(QString::fromLocal8Bit(p->pw_name));
0376     }
0377 
0378     endpwent();
0379     return result;
0380 }
0381 
0382 KUser::~KUser()
0383 {
0384 }
0385 
0386 class KUserGroupPrivate : public QSharedData
0387 {
0388 public:
0389     gid_t gid = gid_t(-1);
0390     QString name;
0391 
0392     KUserGroupPrivate()
0393     {
0394     }
0395     KUserGroupPrivate(const char *_name)
0396     {
0397         fillGroup(_name ? ::getgrnam(_name) : nullptr);
0398     }
0399     KUserGroupPrivate(K_GID gid)
0400     {
0401         struct group *gr = nullptr;
0402 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24))
0403         static const int bufsize = os_gr_size();
0404         QVarLengthArray<char, 1024> buf(bufsize);
0405         struct group entry;
0406         // Some large systems have more members than the POSIX max size
0407         // Loop over by doubling the buffer size (upper limit 250k)
0408         for (int size = bufsize; size < 256000; size += size) {
0409             buf.resize(size);
0410             // ERANGE indicates that the buffer was too small
0411             if (!getgrgid_r(gid, &entry, buf.data(), buf.size(), &gr) || errno != ERANGE) {
0412                 break;
0413             }
0414         }
0415 #else
0416         gr = getgrgid(gid); // not thread-safe!
0417 #endif
0418         fillGroup(gr);
0419     }
0420     KUserGroupPrivate(const ::group *p)
0421     {
0422         fillGroup(p);
0423     }
0424 
0425     void fillGroup(const ::group *p)
0426     {
0427         if (p) {
0428             gid = p->gr_gid;
0429             name = QString::fromLocal8Bit(p->gr_name);
0430         }
0431     }
0432 };
0433 
0434 KUserGroup::KUserGroup(KUser::UIDMode mode)
0435     : d(new KUserGroupPrivate(KUser(mode).groupId().nativeId()))
0436 {
0437 }
0438 
0439 KUserGroup::KUserGroup(K_GID _gid)
0440     : d(new KUserGroupPrivate(_gid))
0441 {
0442 }
0443 
0444 KUserGroup::KUserGroup(KGroupId _gid)
0445     : d(new KUserGroupPrivate(_gid.nativeId()))
0446 {
0447 }
0448 
0449 KUserGroup::KUserGroup(const QString &_name)
0450     : d(new KUserGroupPrivate(_name.toLocal8Bit().data()))
0451 {
0452 }
0453 
0454 KUserGroup::KUserGroup(const char *_name)
0455     : d(new KUserGroupPrivate(_name))
0456 {
0457 }
0458 
0459 KUserGroup::KUserGroup(const ::group *g)
0460     : d(new KUserGroupPrivate(g))
0461 {
0462 }
0463 
0464 KUserGroup::KUserGroup(const KUserGroup &group)
0465     : d(group.d)
0466 {
0467 }
0468 
0469 KUserGroup &KUserGroup::operator=(const KUserGroup &group)
0470 {
0471     d = group.d;
0472     return *this;
0473 }
0474 
0475 bool KUserGroup::operator==(const KUserGroup &group) const
0476 {
0477     return isValid() && (d->gid == group.d->gid);
0478 }
0479 
0480 bool KUserGroup::isValid() const
0481 {
0482     return d->gid != gid_t(-1);
0483 }
0484 
0485 KGroupId KUserGroup::groupId() const
0486 {
0487     return KGroupId(d->gid);
0488 }
0489 
0490 QString KUserGroup::name() const
0491 {
0492     return d->name;
0493 }
0494 
0495 static void listGroupMembers(gid_t gid, uint maxCount, std::function<void(passwd *)> handleNextGroupUser)
0496 {
0497     if (maxCount == 0) {
0498         return;
0499     }
0500     struct group *g = getgrgid(gid); // ### not threadsafe
0501     if (!g) {
0502         return;
0503     }
0504     uint found = 0;
0505     QVarLengthArray<uid_t> addedUsers;
0506     struct passwd *p = nullptr;
0507     for (char **user = g->gr_mem; *user; user++) {
0508         if ((p = getpwnam(*user))) { // ### not threadsafe
0509             addedUsers.append(p->pw_uid);
0510             handleNextGroupUser(p);
0511             found++;
0512             if (found >= maxCount) {
0513                 break;
0514             }
0515         }
0516     }
0517 
0518     // gr_mem doesn't contain users where the primary group == gid -> we have to iterate over all users
0519     setpwent();
0520     while ((p = getpwent()) && found < maxCount) {
0521         if (p->pw_gid != gid) {
0522             continue; // only need primary gid since otherwise gr_mem already contains this user
0523         }
0524         // make sure we don't list a user twice
0525         if (std::find(addedUsers.cbegin(), addedUsers.cend(), p->pw_uid) == addedUsers.cend()) {
0526             handleNextGroupUser(p);
0527             found++;
0528         }
0529     }
0530     endpwent();
0531 }
0532 
0533 QList<KUser> KUserGroup::users(uint maxCount) const
0534 {
0535     QList<KUser> result;
0536     listGroupMembers(d->gid, maxCount, [&](const passwd *p) {
0537         result.append(KUser(p));
0538     });
0539     return result;
0540 }
0541 
0542 QStringList KUserGroup::userNames(uint maxCount) const
0543 {
0544     QStringList result;
0545     listGroupMembers(d->gid, maxCount, [&](const passwd *p) {
0546         result.append(QString::fromLocal8Bit(p->pw_name));
0547     });
0548     return result;
0549 }
0550 
0551 QList<KUserGroup> KUserGroup::allGroups(uint maxCount)
0552 {
0553     QList<KUserGroup> result;
0554 
0555     ::group *g;
0556     setgrent();
0557 
0558     for (uint i = 0; i < maxCount && (g = getgrent()); ++i) {
0559         result.append(KUserGroup(g));
0560     }
0561 
0562     endgrent();
0563 
0564     return result;
0565 }
0566 
0567 QStringList KUserGroup::allGroupNames(uint maxCount)
0568 {
0569     QStringList result;
0570 
0571     ::group *g;
0572     setgrent();
0573 
0574     for (uint i = 0; i < maxCount && (g = getgrent()); ++i) {
0575         result.append(QString::fromLocal8Bit(g->gr_name));
0576     }
0577 
0578     endgrent();
0579 
0580     return result;
0581 }
0582 
0583 KUserGroup::~KUserGroup()
0584 {
0585 }
0586 
0587 KUserId KUserId::fromName(const QString &name)
0588 {
0589     if (name.isEmpty()) {
0590         return KUserId();
0591     }
0592     QByteArray name8Bit = name.toLocal8Bit();
0593     struct passwd *p = ::getpwnam(name8Bit.constData());
0594     if (!p) {
0595         qCWarning(KCOREADDONS_DEBUG, "Failed to lookup user %s: %s", name8Bit.constData(), strerror(errno));
0596         return KUserId();
0597     }
0598     return KUserId(p->pw_uid);
0599 }
0600 
0601 KGroupId KGroupId::fromName(const QString &name)
0602 {
0603     if (name.isEmpty()) {
0604         return KGroupId();
0605     }
0606     QByteArray name8Bit = name.toLocal8Bit();
0607     struct group *g = ::getgrnam(name8Bit.constData());
0608     if (!g) {
0609         qCWarning(KCOREADDONS_DEBUG, "Failed to lookup group %s: %s", name8Bit.constData(), strerror(errno));
0610         return KGroupId();
0611     }
0612     return KGroupId(g->gr_gid);
0613 }
0614 
0615 KUserId KUserId::currentUserId()
0616 {
0617     return KUserId(getuid());
0618 }
0619 
0620 KUserId KUserId::currentEffectiveUserId()
0621 {
0622     return KUserId(geteuid());
0623 }
0624 
0625 KGroupId KGroupId::currentGroupId()
0626 {
0627     return KGroupId(getgid());
0628 }
0629 
0630 KGroupId KGroupId::currentEffectiveGroupId()
0631 {
0632     return KGroupId(getegid());
0633 }