File indexing completed on 2024-04-28 11:40:57

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