File indexing completed on 2024-04-14 03:52:54

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 // $Id: kacl.cpp 424977 2005-06-13 15:13:22Z tilladam $
0008 
0009 #include <config-kiocore.h>
0010 
0011 #include "kacl.h"
0012 
0013 #if HAVE_POSIX_ACL
0014 #include "../aclhelpers_p.h"
0015 #endif
0016 
0017 #include <QDataStream>
0018 #include <QHash>
0019 #include <QString>
0020 
0021 #if HAVE_POSIX_ACL
0022 using namespace KIO;
0023 #endif
0024 
0025 class Q_DECL_HIDDEN KACL::KACLPrivate
0026 {
0027 public:
0028     KACLPrivate()
0029 #if HAVE_POSIX_ACL
0030         : m_acl(nullptr)
0031 #endif
0032     {
0033     }
0034 #if HAVE_POSIX_ACL
0035     explicit KACLPrivate(acl_t acl)
0036         : m_acl(acl)
0037     {
0038     }
0039 #endif
0040 #if HAVE_POSIX_ACL
0041     ~KACLPrivate()
0042     {
0043         if (m_acl) {
0044             acl_free(m_acl);
0045         }
0046     }
0047 #endif
0048     // helpers
0049 #if HAVE_POSIX_ACL
0050     bool setMaskPermissions(unsigned short v);
0051     QString getUserName(uid_t uid) const;
0052     QString getGroupName(gid_t gid) const;
0053     bool setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type);
0054     bool setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type);
0055 
0056     acl_t m_acl;
0057     mutable QHash<uid_t, QString> m_usercache;
0058     mutable QHash<gid_t, QString> m_groupcache;
0059 #endif
0060 };
0061 
0062 KACL::KACL(const QString &aclString)
0063     : d(new KACLPrivate)
0064 {
0065     setACL(aclString);
0066 }
0067 
0068 KACL::KACL(mode_t basePermissions)
0069 #if HAVE_POSIX_ACL
0070     : d(new KACLPrivate(ACLPortability::acl_from_mode(basePermissions)))
0071 #else
0072     : d(new KACLPrivate)
0073 #endif
0074 {
0075 #if !HAVE_POSIX_ACL
0076     Q_UNUSED(basePermissions);
0077 #endif
0078 }
0079 
0080 KACL::KACL()
0081     : d(new KACLPrivate)
0082 {
0083 }
0084 
0085 KACL::KACL(const KACL &rhs)
0086     : d(new KACLPrivate)
0087 {
0088     setACL(rhs.asString());
0089 }
0090 
0091 KACL::~KACL() = default;
0092 
0093 KACL &KACL::operator=(const KACL &rhs)
0094 {
0095     if (this != &rhs) {
0096         setACL(rhs.asString());
0097     }
0098     return *this;
0099 }
0100 
0101 bool KACL::operator==(const KACL &rhs) const
0102 {
0103 #if HAVE_POSIX_ACL
0104     return (ACLPortability::acl_cmp(d->m_acl, rhs.d->m_acl) == 0);
0105 #else
0106     Q_UNUSED(rhs);
0107     return true;
0108 #endif
0109 }
0110 
0111 bool KACL::operator!=(const KACL &rhs) const
0112 {
0113     return !operator==(rhs);
0114 }
0115 
0116 bool KACL::isValid() const
0117 {
0118     bool valid = false;
0119 #if HAVE_POSIX_ACL
0120     if (d->m_acl) {
0121         valid = (acl_valid(d->m_acl) == 0);
0122     }
0123 #endif
0124     return valid;
0125 }
0126 
0127 bool KACL::isExtended() const
0128 {
0129 #if HAVE_POSIX_ACL
0130     return (ACLPortability::acl_equiv_mode(d->m_acl, nullptr) != 0);
0131 #else
0132     return false;
0133 #endif
0134 }
0135 
0136 #if HAVE_POSIX_ACL
0137 static acl_entry_t entryForTag(acl_t acl, acl_tag_t tag)
0138 {
0139     acl_entry_t entry;
0140     int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
0141     while (ret == 1) {
0142         acl_tag_t currentTag;
0143         acl_get_tag_type(entry, &currentTag);
0144         if (currentTag == tag) {
0145             return entry;
0146         }
0147         ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
0148     }
0149     return nullptr;
0150 }
0151 
0152 static unsigned short entryToPermissions(acl_entry_t entry)
0153 {
0154     if (entry == nullptr) {
0155         return 0;
0156     }
0157     acl_permset_t permset;
0158     if (acl_get_permset(entry, &permset) != 0) {
0159         return 0;
0160     }
0161     return (ACLPortability::acl_get_perm(permset, ACL_READ) << 2 | ACLPortability::acl_get_perm(permset, ACL_WRITE) << 1
0162             | ACLPortability::acl_get_perm(permset, ACL_EXECUTE));
0163 }
0164 
0165 static void permissionsToEntry(acl_entry_t entry, unsigned short v)
0166 {
0167     if (entry == nullptr) {
0168         return;
0169     }
0170     acl_permset_t permset;
0171     if (acl_get_permset(entry, &permset) != 0) {
0172         return;
0173     }
0174     acl_clear_perms(permset);
0175     if (v & 4) {
0176         acl_add_perm(permset, ACL_READ);
0177     }
0178     if (v & 2) {
0179         acl_add_perm(permset, ACL_WRITE);
0180     }
0181     if (v & 1) {
0182         acl_add_perm(permset, ACL_EXECUTE);
0183     }
0184 }
0185 
0186 static int getUidForName(const QString &name)
0187 {
0188     struct passwd *user = getpwnam(name.toLocal8Bit().constData());
0189     if (user) {
0190         return user->pw_uid;
0191     } else {
0192         return -1;
0193     }
0194 }
0195 
0196 static int getGidForName(const QString &name)
0197 {
0198     struct group *group = getgrnam(name.toLocal8Bit().constData());
0199     if (group) {
0200         return group->gr_gid;
0201     } else {
0202         return -1;
0203     }
0204 }
0205 #endif
0206 // ------------------ begin API implementation ------------
0207 
0208 unsigned short KACL::ownerPermissions() const
0209 {
0210 #if HAVE_POSIX_ACL
0211     return entryToPermissions(entryForTag(d->m_acl, ACL_USER_OBJ));
0212 #else
0213     return 0;
0214 #endif
0215 }
0216 
0217 bool KACL::setOwnerPermissions(unsigned short v)
0218 {
0219 #if HAVE_POSIX_ACL
0220     permissionsToEntry(entryForTag(d->m_acl, ACL_USER_OBJ), v);
0221 #else
0222     Q_UNUSED(v);
0223 #endif
0224     return true;
0225 }
0226 
0227 unsigned short KACL::owningGroupPermissions() const
0228 {
0229 #if HAVE_POSIX_ACL
0230     return entryToPermissions(entryForTag(d->m_acl, ACL_GROUP_OBJ));
0231 #else
0232     return 0;
0233 #endif
0234 }
0235 
0236 bool KACL::setOwningGroupPermissions(unsigned short v)
0237 {
0238 #if HAVE_POSIX_ACL
0239     permissionsToEntry(entryForTag(d->m_acl, ACL_GROUP_OBJ), v);
0240 #else
0241     Q_UNUSED(v);
0242 #endif
0243     return true;
0244 }
0245 
0246 unsigned short KACL::othersPermissions() const
0247 {
0248 #if HAVE_POSIX_ACL
0249     return entryToPermissions(entryForTag(d->m_acl, ACL_OTHER));
0250 #else
0251     return 0;
0252 #endif
0253 }
0254 
0255 bool KACL::setOthersPermissions(unsigned short v)
0256 {
0257 #if HAVE_POSIX_ACL
0258     permissionsToEntry(entryForTag(d->m_acl, ACL_OTHER), v);
0259 #else
0260     Q_UNUSED(v);
0261 #endif
0262     return true;
0263 }
0264 
0265 mode_t KACL::basePermissions() const
0266 {
0267     mode_t perms(0);
0268 #if HAVE_POSIX_ACL
0269     if (ownerPermissions() & ACL_READ) {
0270         perms |= S_IRUSR;
0271     }
0272     if (ownerPermissions() & ACL_WRITE) {
0273         perms |= S_IWUSR;
0274     }
0275     if (ownerPermissions() & ACL_EXECUTE) {
0276         perms |= S_IXUSR;
0277     }
0278     if (owningGroupPermissions() & ACL_READ) {
0279         perms |= S_IRGRP;
0280     }
0281     if (owningGroupPermissions() & ACL_WRITE) {
0282         perms |= S_IWGRP;
0283     }
0284     if (owningGroupPermissions() & ACL_EXECUTE) {
0285         perms |= S_IXGRP;
0286     }
0287     if (othersPermissions() & ACL_READ) {
0288         perms |= S_IROTH;
0289     }
0290     if (othersPermissions() & ACL_WRITE) {
0291         perms |= S_IWOTH;
0292     }
0293     if (othersPermissions() & ACL_EXECUTE) {
0294         perms |= S_IXOTH;
0295     }
0296 #endif
0297     return perms;
0298 }
0299 
0300 unsigned short KACL::maskPermissions(bool &exists) const
0301 {
0302     exists = true;
0303 #if HAVE_POSIX_ACL
0304     acl_entry_t entry = entryForTag(d->m_acl, ACL_MASK);
0305     if (entry == nullptr) {
0306         exists = false;
0307         return 0;
0308     }
0309     return entryToPermissions(entry);
0310 #else
0311     return 0;
0312 #endif
0313 }
0314 
0315 #if HAVE_POSIX_ACL
0316 bool KACL::KACLPrivate::setMaskPermissions(unsigned short v)
0317 {
0318     acl_entry_t entry = entryForTag(m_acl, ACL_MASK);
0319     if (entry == nullptr) {
0320         acl_create_entry(&m_acl, &entry);
0321         acl_set_tag_type(entry, ACL_MASK);
0322     }
0323     permissionsToEntry(entry, v);
0324     return true;
0325 }
0326 #endif
0327 
0328 bool KACL::setMaskPermissions(unsigned short v)
0329 {
0330 #if HAVE_POSIX_ACL
0331     return d->setMaskPermissions(v);
0332 #else
0333     Q_UNUSED(v);
0334     return true;
0335 #endif
0336 }
0337 
0338 #if HAVE_POSIX_ACL
0339 using unique_ptr_acl_free = std::unique_ptr<void, int (*)(void *)>;
0340 #endif
0341 
0342 /**************************
0343  * Deal with named users  *
0344  **************************/
0345 unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const
0346 {
0347 #if HAVE_POSIX_ACL
0348     acl_entry_t entry;
0349     *exists = false;
0350     int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
0351     while (ret == 1) {
0352         acl_tag_t currentTag;
0353         acl_get_tag_type(entry, &currentTag);
0354         if (currentTag == ACL_USER) {
0355             const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
0356             const uid_t id = *(static_cast<uid_t *>(idptr.get()));
0357             if (d->getUserName(id) == name) {
0358                 *exists = true;
0359                 return entryToPermissions(entry);
0360             }
0361         }
0362         ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
0363     }
0364 #else
0365     Q_UNUSED(name);
0366     Q_UNUSED(exists);
0367 #endif
0368     return 0;
0369 }
0370 
0371 #if HAVE_POSIX_ACL
0372 bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type)
0373 {
0374     bool allIsWell = true;
0375     acl_t newACL = acl_dup(m_acl);
0376     acl_entry_t entry;
0377     bool createdNewEntry = false;
0378     bool found = false;
0379     int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
0380     while (ret == 1) {
0381         acl_tag_t currentTag;
0382         acl_get_tag_type(entry, &currentTag);
0383         if (currentTag == type) {
0384             const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
0385             const int id = *(static_cast<int *>(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t)
0386             const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id);
0387             if (entryName == name) {
0388                 // found him, update
0389                 permissionsToEntry(entry, permissions);
0390                 found = true;
0391                 break;
0392             }
0393         }
0394         ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
0395     }
0396     if (!found) {
0397         acl_create_entry(&newACL, &entry);
0398         acl_set_tag_type(entry, type);
0399         int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
0400         if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
0401             acl_delete_entry(newACL, entry);
0402             allIsWell = false;
0403         } else {
0404             permissionsToEntry(entry, permissions);
0405             createdNewEntry = true;
0406         }
0407     }
0408     if (allIsWell && createdNewEntry) {
0409         // 23.1.1 of 1003.1e states that as soon as there is a named user or
0410         // named group entry, there needs to be a mask entry as well, so add
0411         // one, if the user hasn't explicitly set one.
0412         if (entryForTag(newACL, ACL_MASK) == nullptr) {
0413             acl_calc_mask(&newACL);
0414         }
0415     }
0416 
0417     if (!allIsWell || acl_valid(newACL) != 0) {
0418         acl_free(newACL);
0419         allIsWell = false;
0420     } else {
0421         acl_free(m_acl);
0422         m_acl = newACL;
0423     }
0424     return allIsWell;
0425 }
0426 #endif
0427 
0428 bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions)
0429 {
0430 #if HAVE_POSIX_ACL
0431     return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER);
0432 #else
0433     Q_UNUSED(name);
0434     Q_UNUSED(permissions);
0435     return true;
0436 #endif
0437 }
0438 
0439 ACLUserPermissionsList KACL::allUserPermissions() const
0440 {
0441     ACLUserPermissionsList list;
0442 #if HAVE_POSIX_ACL
0443     acl_entry_t entry;
0444     int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
0445     while (ret == 1) {
0446         acl_tag_t currentTag;
0447         acl_get_tag_type(entry, &currentTag);
0448         if (currentTag == ACL_USER) {
0449             const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
0450             const uid_t id = *(static_cast<uid_t *>(idptr.get()));
0451             QString name = d->getUserName(id);
0452             unsigned short permissions = entryToPermissions(entry);
0453             ACLUserPermissions pair = qMakePair(name, permissions);
0454             list.append(pair);
0455         }
0456         ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
0457     }
0458 #endif
0459     return list;
0460 }
0461 
0462 #if HAVE_POSIX_ACL
0463 bool KACL::KACLPrivate::setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type)
0464 {
0465     bool allIsWell = true;
0466     bool atLeastOneUserOrGroup = false;
0467 
0468     // make working copy, in case something goes wrong
0469     acl_t newACL = acl_dup(m_acl);
0470     acl_entry_t entry;
0471 
0472     // clear user entries
0473     int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
0474     while (ret == 1) {
0475         acl_tag_t currentTag;
0476         acl_get_tag_type(entry, &currentTag);
0477         if (currentTag == type) {
0478             acl_delete_entry(newACL, entry);
0479             // we have to start from the beginning, the iterator is
0480             // invalidated, on deletion
0481             ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
0482         } else {
0483             ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
0484         }
0485     }
0486 
0487     // now add the entries from the list
0488     for (const auto &[name, userId] : list) {
0489         acl_create_entry(&newACL, &entry);
0490         acl_set_tag_type(entry, type);
0491         int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
0492         if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
0493             // user or group doesn't exist => error
0494             acl_delete_entry(newACL, entry);
0495             allIsWell = false;
0496             break;
0497         } else {
0498             permissionsToEntry(entry, userId);
0499             atLeastOneUserOrGroup = true;
0500         }
0501     }
0502 
0503     if (allIsWell && atLeastOneUserOrGroup) {
0504         // 23.1.1 of 1003.1e states that as soon as there is a named user or
0505         // named group entry, there needs to be a mask entry as well, so add
0506         // one, if the user hasn't explicitly set one.
0507         if (entryForTag(newACL, ACL_MASK) == nullptr) {
0508             acl_calc_mask(&newACL);
0509         }
0510     }
0511     if (allIsWell && (acl_valid(newACL) == 0)) {
0512         acl_free(m_acl);
0513         m_acl = newACL;
0514     } else {
0515         acl_free(newACL);
0516     }
0517     return allIsWell;
0518 }
0519 #endif
0520 
0521 bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users)
0522 {
0523 #if HAVE_POSIX_ACL
0524     return d->setAllUsersOrGroups(users, ACL_USER);
0525 #else
0526     Q_UNUSED(users);
0527     return true;
0528 #endif
0529 }
0530 
0531 /**************************
0532  * Deal with named groups  *
0533  **************************/
0534 
0535 unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const
0536 {
0537     *exists = false;
0538 #if HAVE_POSIX_ACL
0539     acl_entry_t entry;
0540     int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
0541     while (ret == 1) {
0542         acl_tag_t currentTag;
0543         acl_get_tag_type(entry, &currentTag);
0544         if (currentTag == ACL_GROUP) {
0545             const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
0546             const gid_t id = *(static_cast<gid_t *>(idptr.get()));
0547             if (d->getGroupName(id) == name) {
0548                 *exists = true;
0549                 return entryToPermissions(entry);
0550             }
0551         }
0552         ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
0553     }
0554 #else
0555     Q_UNUSED(name);
0556 #endif
0557     return 0;
0558 }
0559 
0560 bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions)
0561 {
0562 #if HAVE_POSIX_ACL
0563     return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP);
0564 #else
0565     Q_UNUSED(name);
0566     Q_UNUSED(permissions);
0567     return true;
0568 #endif
0569 }
0570 
0571 ACLGroupPermissionsList KACL::allGroupPermissions() const
0572 {
0573     ACLGroupPermissionsList list;
0574 #if HAVE_POSIX_ACL
0575     acl_entry_t entry;
0576     int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
0577     while (ret == 1) {
0578         acl_tag_t currentTag;
0579         acl_get_tag_type(entry, &currentTag);
0580         if (currentTag == ACL_GROUP) {
0581             const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
0582             const gid_t id = *(static_cast<gid_t *>(idptr.get()));
0583             QString name = d->getGroupName(id);
0584             unsigned short permissions = entryToPermissions(entry);
0585             ACLGroupPermissions pair = qMakePair(name, permissions);
0586             list.append(pair);
0587         }
0588         ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
0589     }
0590 #endif
0591     return list;
0592 }
0593 
0594 bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups)
0595 {
0596 #if HAVE_POSIX_ACL
0597     return d->setAllUsersOrGroups(groups, ACL_GROUP);
0598 #else
0599     Q_UNUSED(groups);
0600     return true;
0601 #endif
0602 }
0603 
0604 /**************************
0605  * from and to string     *
0606  **************************/
0607 
0608 bool KACL::setACL(const QString &aclStr)
0609 {
0610     bool ret = false;
0611 #if HAVE_POSIX_ACL
0612     acl_t temp = acl_from_text(aclStr.toLatin1().constData());
0613     if (acl_valid(temp) != 0) {
0614         // TODO errno is set, what to do with it here?
0615         acl_free(temp);
0616     } else {
0617         if (d->m_acl) {
0618             acl_free(d->m_acl);
0619         }
0620         d->m_acl = temp;
0621         ret = true;
0622     }
0623 #else
0624     Q_UNUSED(aclStr);
0625 #endif
0626     return ret;
0627 }
0628 
0629 QString KACL::asString() const
0630 {
0631 #if HAVE_POSIX_ACL
0632     ssize_t size = 0;
0633     char *txt = acl_to_text(d->m_acl, &size);
0634     const QString ret = QString::fromLatin1(txt, size);
0635     acl_free(txt);
0636     return ret;
0637 #else
0638     return QString();
0639 #endif
0640 }
0641 
0642 // helpers
0643 
0644 #if HAVE_POSIX_ACL
0645 QString KACL::KACLPrivate::getUserName(uid_t uid) const
0646 {
0647     auto it = m_usercache.find(uid);
0648     if (it == m_usercache.end()) {
0649         struct passwd *user = getpwuid(uid);
0650         if (user) {
0651             it = m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
0652         } else {
0653             return QString::number(uid);
0654         }
0655     }
0656     return *it;
0657 }
0658 
0659 QString KACL::KACLPrivate::getGroupName(gid_t gid) const
0660 {
0661     auto it = m_groupcache.find(gid);
0662     if (it == m_groupcache.end()) {
0663         struct group *grp = getgrgid(gid);
0664         if (grp) {
0665             it = m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
0666         } else {
0667             return QString::number(gid);
0668         }
0669     }
0670     return *it;
0671 }
0672 #endif
0673 
0674 void KACL::virtual_hook(int, void *)
0675 {
0676     /*BASE::virtual_hook( id, data );*/
0677 }
0678 
0679 QDataStream &operator<<(QDataStream &s, const KACL &a)
0680 {
0681     s << a.asString();
0682     return s;
0683 }
0684 
0685 QDataStream &operator>>(QDataStream &s, KACL &a)
0686 {
0687     QString str;
0688     s >> str;
0689     a.setACL(str);
0690     return s;
0691 }