1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7// $Id: kacl.cpp 424977 2005-06-13 15:13:22Z tilladam $
8
9#include <config-kiocore.h>
10
11#include "kacl.h"
12
13#if HAVE_POSIX_ACL
14#include "../aclhelpers_p.h"
15#endif
16
17#include <QDataStream>
18#include <QHash>
19#include <QString>
20
21#if HAVE_POSIX_ACL
22using namespace KIO;
23#endif
24
25class Q_DECL_HIDDEN KACL::KACLPrivate
26{
27public:
28 KACLPrivate()
29#if HAVE_POSIX_ACL
30 : m_acl(nullptr)
31#endif
32 {
33 }
34#if HAVE_POSIX_ACL
35 explicit KACLPrivate(acl_t acl)
36 : m_acl(acl)
37 {
38 }
39#endif
40#if HAVE_POSIX_ACL
41 ~KACLPrivate()
42 {
43 if (m_acl) {
44 acl_free(m_acl);
45 }
46 }
47#endif
48 // helpers
49#if HAVE_POSIX_ACL
50 bool setMaskPermissions(unsigned short v);
51 QString getUserName(uid_t uid) const;
52 QString getGroupName(gid_t gid) const;
53 bool setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type);
54 bool setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type);
55
56 acl_t m_acl;
57 mutable QHash<uid_t, QString> m_usercache;
58 mutable QHash<gid_t, QString> m_groupcache;
59#endif
60};
61
62KACL::KACL(const QString &aclString)
63 : d(new KACLPrivate)
64{
65 setACL(aclString);
66}
67
68KACL::KACL(mode_t basePermissions)
69#if HAVE_POSIX_ACL
70 : d(new KACLPrivate(ACLPortability::acl_from_mode(basePermissions)))
71#else
72 : d(new KACLPrivate)
73#endif
74{
75#if !HAVE_POSIX_ACL
76 Q_UNUSED(basePermissions);
77#endif
78}
79
80KACL::KACL()
81 : d(new KACLPrivate)
82{
83}
84
85KACL::KACL(const KACL &rhs)
86 : d(new KACLPrivate)
87{
88 setACL(rhs.asString());
89}
90
91KACL::~KACL() = default;
92
93KACL &KACL::operator=(const KACL &rhs)
94{
95 if (this != &rhs) {
96 setACL(rhs.asString());
97 }
98 return *this;
99}
100
101bool KACL::operator==(const KACL &rhs) const
102{
103#if HAVE_POSIX_ACL
104 return (ACLPortability::acl_cmp(d->m_acl, rhs.d->m_acl) == 0);
105#else
106 Q_UNUSED(rhs);
107 return true;
108#endif
109}
110
111bool KACL::operator!=(const KACL &rhs) const
112{
113 return !operator==(rhs);
114}
115
116bool KACL::isValid() const
117{
118 bool valid = false;
119#if HAVE_POSIX_ACL
120 if (d->m_acl) {
121 valid = (acl_valid(d->m_acl) == 0);
122 }
123#endif
124 return valid;
125}
126
127bool KACL::isExtended() const
128{
129#if HAVE_POSIX_ACL
130 return (ACLPortability::acl_equiv_mode(d->m_acl, nullptr) != 0);
131#else
132 return false;
133#endif
134}
135
136#if HAVE_POSIX_ACL
137static acl_entry_t entryForTag(acl_t acl, acl_tag_t tag)
138{
139 acl_entry_t entry;
140 int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
141 while (ret == 1) {
142 acl_tag_t currentTag;
143 acl_get_tag_type(entry, &currentTag);
144 if (currentTag == tag) {
145 return entry;
146 }
147 ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
148 }
149 return nullptr;
150}
151
152static unsigned short entryToPermissions(acl_entry_t entry)
153{
154 if (entry == nullptr) {
155 return 0;
156 }
157 acl_permset_t permset;
158 if (acl_get_permset(entry, &permset) != 0) {
159 return 0;
160 }
161 return (ACLPortability::acl_get_perm(permset, ACL_READ) << 2 | ACLPortability::acl_get_perm(permset, ACL_WRITE) << 1
162 | ACLPortability::acl_get_perm(permset, ACL_EXECUTE));
163}
164
165static void permissionsToEntry(acl_entry_t entry, unsigned short v)
166{
167 if (entry == nullptr) {
168 return;
169 }
170 acl_permset_t permset;
171 if (acl_get_permset(entry, &permset) != 0) {
172 return;
173 }
174 acl_clear_perms(permset);
175 if (v & 4) {
176 acl_add_perm(permset, ACL_READ);
177 }
178 if (v & 2) {
179 acl_add_perm(permset, ACL_WRITE);
180 }
181 if (v & 1) {
182 acl_add_perm(permset, ACL_EXECUTE);
183 }
184}
185
186static int getUidForName(const QString &name)
187{
188 struct passwd *user = getpwnam(name.toLocal8Bit().constData());
189 if (user) {
190 return user->pw_uid;
191 } else {
192 return -1;
193 }
194}
195
196static int getGidForName(const QString &name)
197{
198 struct group *group = getgrnam(name.toLocal8Bit().constData());
199 if (group) {
200 return group->gr_gid;
201 } else {
202 return -1;
203 }
204}
205#endif
206// ------------------ begin API implementation ------------
207
208unsigned short KACL::ownerPermissions() const
209{
210#if HAVE_POSIX_ACL
211 return entryToPermissions(entryForTag(d->m_acl, ACL_USER_OBJ));
212#else
213 return 0;
214#endif
215}
216
217bool KACL::setOwnerPermissions(unsigned short v)
218{
219#if HAVE_POSIX_ACL
220 permissionsToEntry(entryForTag(d->m_acl, ACL_USER_OBJ), v);
221#else
222 Q_UNUSED(v);
223#endif
224 return true;
225}
226
227unsigned short KACL::owningGroupPermissions() const
228{
229#if HAVE_POSIX_ACL
230 return entryToPermissions(entryForTag(d->m_acl, ACL_GROUP_OBJ));
231#else
232 return 0;
233#endif
234}
235
236bool KACL::setOwningGroupPermissions(unsigned short v)
237{
238#if HAVE_POSIX_ACL
239 permissionsToEntry(entryForTag(d->m_acl, ACL_GROUP_OBJ), v);
240#else
241 Q_UNUSED(v);
242#endif
243 return true;
244}
245
246unsigned short KACL::othersPermissions() const
247{
248#if HAVE_POSIX_ACL
249 return entryToPermissions(entryForTag(d->m_acl, ACL_OTHER));
250#else
251 return 0;
252#endif
253}
254
255bool KACL::setOthersPermissions(unsigned short v)
256{
257#if HAVE_POSIX_ACL
258 permissionsToEntry(entryForTag(d->m_acl, ACL_OTHER), v);
259#else
260 Q_UNUSED(v);
261#endif
262 return true;
263}
264
265mode_t KACL::basePermissions() const
266{
267 mode_t perms(0);
268#if HAVE_POSIX_ACL
269 if (ownerPermissions() & ACL_READ) {
270 perms |= S_IRUSR;
271 }
272 if (ownerPermissions() & ACL_WRITE) {
273 perms |= S_IWUSR;
274 }
275 if (ownerPermissions() & ACL_EXECUTE) {
276 perms |= S_IXUSR;
277 }
278 if (owningGroupPermissions() & ACL_READ) {
279 perms |= S_IRGRP;
280 }
281 if (owningGroupPermissions() & ACL_WRITE) {
282 perms |= S_IWGRP;
283 }
284 if (owningGroupPermissions() & ACL_EXECUTE) {
285 perms |= S_IXGRP;
286 }
287 if (othersPermissions() & ACL_READ) {
288 perms |= S_IROTH;
289 }
290 if (othersPermissions() & ACL_WRITE) {
291 perms |= S_IWOTH;
292 }
293 if (othersPermissions() & ACL_EXECUTE) {
294 perms |= S_IXOTH;
295 }
296#endif
297 return perms;
298}
299
300unsigned short KACL::maskPermissions(bool &exists) const
301{
302 exists = true;
303#if HAVE_POSIX_ACL
304 acl_entry_t entry = entryForTag(d->m_acl, ACL_MASK);
305 if (entry == nullptr) {
306 exists = false;
307 return 0;
308 }
309 return entryToPermissions(entry);
310#else
311 return 0;
312#endif
313}
314
315#if HAVE_POSIX_ACL
316bool KACL::KACLPrivate::setMaskPermissions(unsigned short v)
317{
318 acl_entry_t entry = entryForTag(m_acl, ACL_MASK);
319 if (entry == nullptr) {
320 acl_create_entry(&m_acl, &entry);
321 acl_set_tag_type(entry, ACL_MASK);
322 }
323 permissionsToEntry(entry, v);
324 return true;
325}
326#endif
327
328bool KACL::setMaskPermissions(unsigned short v)
329{
330#if HAVE_POSIX_ACL
331 return d->setMaskPermissions(v);
332#else
333 Q_UNUSED(v);
334 return true;
335#endif
336}
337
338#if HAVE_POSIX_ACL
339using unique_ptr_acl_free = std::unique_ptr<void, int (*)(void *)>;
340#endif
341
342// Deal with named users
343unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const
344{
345#if HAVE_POSIX_ACL
346 acl_entry_t entry;
347 *exists = false;
348 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
349 while (ret == 1) {
350 acl_tag_t currentTag;
351 acl_get_tag_type(entry, &currentTag);
352 if (currentTag == ACL_USER) {
353 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
354 const uid_t id = *(static_cast<uid_t *>(idptr.get()));
355 if (d->getUserName(id) == name) {
356 *exists = true;
357 return entryToPermissions(entry);
358 }
359 }
360 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
361 }
362#else
363 Q_UNUSED(name);
364 Q_UNUSED(exists);
365#endif
366 return 0;
367}
368
369#if HAVE_POSIX_ACL
370bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type)
371{
372 bool allIsWell = true;
373 acl_t newACL = acl_dup(m_acl);
374 acl_entry_t entry;
375 bool createdNewEntry = false;
376 bool found = false;
377 int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
378 while (ret == 1) {
379 acl_tag_t currentTag;
380 acl_get_tag_type(entry, &currentTag);
381 if (currentTag == type) {
382 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
383 const int id = *(static_cast<int *>(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t)
384 const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id);
385 if (entryName == name) {
386 // found him, update
387 permissionsToEntry(entry, permissions);
388 found = true;
389 break;
390 }
391 }
392 ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
393 }
394 if (!found) {
395 acl_create_entry(&newACL, &entry);
396 acl_set_tag_type(entry, type);
397 int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
398 if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
399 acl_delete_entry(newACL, entry);
400 allIsWell = false;
401 } else {
402 permissionsToEntry(entry, permissions);
403 createdNewEntry = true;
404 }
405 }
406 if (allIsWell && createdNewEntry) {
407 // 23.1.1 of 1003.1e states that as soon as there is a named user or
408 // named group entry, there needs to be a mask entry as well, so add
409 // one, if the user hasn't explicitly set one.
410 if (entryForTag(newACL, ACL_MASK) == nullptr) {
411 acl_calc_mask(&newACL);
412 }
413 }
414
415 if (!allIsWell || acl_valid(newACL) != 0) {
416 acl_free(newACL);
417 allIsWell = false;
418 } else {
419 acl_free(m_acl);
420 m_acl = newACL;
421 }
422 return allIsWell;
423}
424#endif
425
426bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions)
427{
428#if HAVE_POSIX_ACL
429 return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER);
430#else
431 Q_UNUSED(name);
432 Q_UNUSED(permissions);
433 return true;
434#endif
435}
436
437ACLUserPermissionsList KACL::allUserPermissions() const
438{
439 ACLUserPermissionsList list;
440#if HAVE_POSIX_ACL
441 acl_entry_t entry;
442 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
443 while (ret == 1) {
444 acl_tag_t currentTag;
445 acl_get_tag_type(entry, &currentTag);
446 if (currentTag == ACL_USER) {
447 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
448 const uid_t id = *(static_cast<uid_t *>(idptr.get()));
449 QString name = d->getUserName(id);
450 unsigned short permissions = entryToPermissions(entry);
451 ACLUserPermissions pair = qMakePair(name, permissions);
452 list.append(pair);
453 }
454 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
455 }
456#endif
457 return list;
458}
459
460#if HAVE_POSIX_ACL
461bool KACL::KACLPrivate::setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type)
462{
463 bool allIsWell = true;
464 bool atLeastOneUserOrGroup = false;
465
466 // make working copy, in case something goes wrong
467 acl_t newACL = acl_dup(m_acl);
468 acl_entry_t entry;
469
470 // clear user entries
471 int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
472 while (ret == 1) {
473 acl_tag_t currentTag;
474 acl_get_tag_type(entry, &currentTag);
475 if (currentTag == type) {
476 acl_delete_entry(newACL, entry);
477 // we have to start from the beginning, the iterator is
478 // invalidated, on deletion
479 ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
480 } else {
481 ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
482 }
483 }
484
485 // now add the entries from the list
486 for (const auto &[name, userId] : list) {
487 acl_create_entry(&newACL, &entry);
488 acl_set_tag_type(entry, type);
489 int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
490 if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
491 // user or group doesn't exist => error
492 acl_delete_entry(newACL, entry);
493 allIsWell = false;
494 break;
495 } else {
496 permissionsToEntry(entry, userId);
497 atLeastOneUserOrGroup = true;
498 }
499 }
500
501 if (allIsWell && atLeastOneUserOrGroup) {
502 // 23.1.1 of 1003.1e states that as soon as there is a named user or
503 // named group entry, there needs to be a mask entry as well, so add
504 // one, if the user hasn't explicitly set one.
505 if (entryForTag(newACL, ACL_MASK) == nullptr) {
506 acl_calc_mask(&newACL);
507 }
508 }
509 if (allIsWell && (acl_valid(newACL) == 0)) {
510 acl_free(m_acl);
511 m_acl = newACL;
512 } else {
513 acl_free(newACL);
514 }
515 return allIsWell;
516}
517#endif
518
519bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users)
520{
521#if HAVE_POSIX_ACL
522 return d->setAllUsersOrGroups(users, ACL_USER);
523#else
524 Q_UNUSED(users);
525 return true;
526#endif
527}
528
529// Deal with named groups
530
531unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const
532{
533 *exists = false;
534#if HAVE_POSIX_ACL
535 acl_entry_t entry;
536 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
537 while (ret == 1) {
538 acl_tag_t currentTag;
539 acl_get_tag_type(entry, &currentTag);
540 if (currentTag == ACL_GROUP) {
541 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
542 const gid_t id = *(static_cast<gid_t *>(idptr.get()));
543 if (d->getGroupName(id) == name) {
544 *exists = true;
545 return entryToPermissions(entry);
546 }
547 }
548 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
549 }
550#else
551 Q_UNUSED(name);
552#endif
553 return 0;
554}
555
556bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions)
557{
558#if HAVE_POSIX_ACL
559 return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP);
560#else
561 Q_UNUSED(name);
562 Q_UNUSED(permissions);
563 return true;
564#endif
565}
566
567ACLGroupPermissionsList KACL::allGroupPermissions() const
568{
569 ACLGroupPermissionsList list;
570#if HAVE_POSIX_ACL
571 acl_entry_t entry;
572 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
573 while (ret == 1) {
574 acl_tag_t currentTag;
575 acl_get_tag_type(entry, &currentTag);
576 if (currentTag == ACL_GROUP) {
577 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
578 const gid_t id = *(static_cast<gid_t *>(idptr.get()));
579 QString name = d->getGroupName(id);
580 unsigned short permissions = entryToPermissions(entry);
581 ACLGroupPermissions pair = qMakePair(name, permissions);
582 list.append(pair);
583 }
584 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
585 }
586#endif
587 return list;
588}
589
590bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups)
591{
592#if HAVE_POSIX_ACL
593 return d->setAllUsersOrGroups(groups, ACL_GROUP);
594#else
595 Q_UNUSED(groups);
596 return true;
597#endif
598}
599
600// from and to string
601
602bool KACL::setACL(const QString &aclStr)
603{
604 bool ret = false;
605#if HAVE_POSIX_ACL
606 acl_t temp = acl_from_text(aclStr.toLatin1().constData());
607 if (acl_valid(temp) != 0) {
608 // TODO errno is set, what to do with it here?
609 acl_free(temp);
610 } else {
611 if (d->m_acl) {
612 acl_free(d->m_acl);
613 }
614 d->m_acl = temp;
615 ret = true;
616 }
617#else
618 Q_UNUSED(aclStr);
619#endif
620 return ret;
621}
622
623QString KACL::asString() const
624{
625#if HAVE_POSIX_ACL
626 ssize_t size = 0;
627 char *txt = acl_to_text(d->m_acl, &size);
628 const QString ret = QString::fromLatin1(txt, size);
629 acl_free(txt);
630 return ret;
631#else
632 return QString();
633#endif
634}
635
636// helpers
637
638#if HAVE_POSIX_ACL
639QString KACL::KACLPrivate::getUserName(uid_t uid) const
640{
641 auto it = m_usercache.find(uid);
642 if (it == m_usercache.end()) {
643 struct passwd *user = getpwuid(uid);
644 if (user) {
645 it = m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
646 } else {
647 return QString::number(uid);
648 }
649 }
650 return *it;
651}
652
653QString KACL::KACLPrivate::getGroupName(gid_t gid) const
654{
655 auto it = m_groupcache.find(gid);
656 if (it == m_groupcache.end()) {
657 struct group *grp = getgrgid(gid);
658 if (grp) {
659 it = m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
660 } else {
661 return QString::number(gid);
662 }
663 }
664 return *it;
665}
666#endif
667
668void KACL::virtual_hook(int, void *)
669{
670 /*BASE::virtual_hook( id, data );*/
671}
672
673QDataStream &operator<<(QDataStream &s, const KACL &a)
674{
675 s << a.asString();
676 return s;
677}
678
679QDataStream &operator>>(QDataStream &s, KACL &a)
680{
681 QString str;
682 s >> str;
683 a.setACL(str);
684 return s;
685}
686

source code of kio/src/core/kacl.cpp