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/**************************
343 * Deal with named users *
344 **************************/
345unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const
346{
347#if HAVE_POSIX_ACL
348 acl_entry_t entry;
349 *exists = false;
350 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
351 while (ret == 1) {
352 acl_tag_t currentTag;
353 acl_get_tag_type(entry, &currentTag);
354 if (currentTag == ACL_USER) {
355 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
356 const uid_t id = *(static_cast<uid_t *>(idptr.get()));
357 if (d->getUserName(id) == name) {
358 *exists = true;
359 return entryToPermissions(entry);
360 }
361 }
362 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
363 }
364#else
365 Q_UNUSED(name);
366 Q_UNUSED(exists);
367#endif
368 return 0;
369}
370
371#if HAVE_POSIX_ACL
372bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type)
373{
374 bool allIsWell = true;
375 acl_t newACL = acl_dup(m_acl);
376 acl_entry_t entry;
377 bool createdNewEntry = false;
378 bool found = false;
379 int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
380 while (ret == 1) {
381 acl_tag_t currentTag;
382 acl_get_tag_type(entry, &currentTag);
383 if (currentTag == type) {
384 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
385 const int id = *(static_cast<int *>(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t)
386 const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id);
387 if (entryName == name) {
388 // found him, update
389 permissionsToEntry(entry, permissions);
390 found = true;
391 break;
392 }
393 }
394 ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
395 }
396 if (!found) {
397 acl_create_entry(&newACL, &entry);
398 acl_set_tag_type(entry, type);
399 int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
400 if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
401 acl_delete_entry(newACL, entry);
402 allIsWell = false;
403 } else {
404 permissionsToEntry(entry, permissions);
405 createdNewEntry = true;
406 }
407 }
408 if (allIsWell && createdNewEntry) {
409 // 23.1.1 of 1003.1e states that as soon as there is a named user or
410 // named group entry, there needs to be a mask entry as well, so add
411 // one, if the user hasn't explicitly set one.
412 if (entryForTag(newACL, ACL_MASK) == nullptr) {
413 acl_calc_mask(&newACL);
414 }
415 }
416
417 if (!allIsWell || acl_valid(newACL) != 0) {
418 acl_free(newACL);
419 allIsWell = false;
420 } else {
421 acl_free(m_acl);
422 m_acl = newACL;
423 }
424 return allIsWell;
425}
426#endif
427
428bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions)
429{
430#if HAVE_POSIX_ACL
431 return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER);
432#else
433 Q_UNUSED(name);
434 Q_UNUSED(permissions);
435 return true;
436#endif
437}
438
439ACLUserPermissionsList KACL::allUserPermissions() const
440{
441 ACLUserPermissionsList list;
442#if HAVE_POSIX_ACL
443 acl_entry_t entry;
444 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
445 while (ret == 1) {
446 acl_tag_t currentTag;
447 acl_get_tag_type(entry, &currentTag);
448 if (currentTag == ACL_USER) {
449 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
450 const uid_t id = *(static_cast<uid_t *>(idptr.get()));
451 QString name = d->getUserName(id);
452 unsigned short permissions = entryToPermissions(entry);
453 ACLUserPermissions pair = qMakePair(name, permissions);
454 list.append(pair);
455 }
456 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
457 }
458#endif
459 return list;
460}
461
462#if HAVE_POSIX_ACL
463bool KACL::KACLPrivate::setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type)
464{
465 bool allIsWell = true;
466 bool atLeastOneUserOrGroup = false;
467
468 // make working copy, in case something goes wrong
469 acl_t newACL = acl_dup(m_acl);
470 acl_entry_t entry;
471
472 // clear user entries
473 int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
474 while (ret == 1) {
475 acl_tag_t currentTag;
476 acl_get_tag_type(entry, &currentTag);
477 if (currentTag == type) {
478 acl_delete_entry(newACL, entry);
479 // we have to start from the beginning, the iterator is
480 // invalidated, on deletion
481 ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry);
482 } else {
483 ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry);
484 }
485 }
486
487 // now add the entries from the list
488 for (const auto &[name, userId] : list) {
489 acl_create_entry(&newACL, &entry);
490 acl_set_tag_type(entry, type);
491 int id = type == ACL_USER ? getUidForName(name) : getGidForName(name);
492 if (id == -1 || acl_set_qualifier(entry, &id) != 0) {
493 // user or group doesn't exist => error
494 acl_delete_entry(newACL, entry);
495 allIsWell = false;
496 break;
497 } else {
498 permissionsToEntry(entry, userId);
499 atLeastOneUserOrGroup = true;
500 }
501 }
502
503 if (allIsWell && atLeastOneUserOrGroup) {
504 // 23.1.1 of 1003.1e states that as soon as there is a named user or
505 // named group entry, there needs to be a mask entry as well, so add
506 // one, if the user hasn't explicitly set one.
507 if (entryForTag(newACL, ACL_MASK) == nullptr) {
508 acl_calc_mask(&newACL);
509 }
510 }
511 if (allIsWell && (acl_valid(newACL) == 0)) {
512 acl_free(m_acl);
513 m_acl = newACL;
514 } else {
515 acl_free(newACL);
516 }
517 return allIsWell;
518}
519#endif
520
521bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users)
522{
523#if HAVE_POSIX_ACL
524 return d->setAllUsersOrGroups(users, ACL_USER);
525#else
526 Q_UNUSED(users);
527 return true;
528#endif
529}
530
531/**************************
532 * Deal with named groups *
533 **************************/
534
535unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const
536{
537 *exists = false;
538#if HAVE_POSIX_ACL
539 acl_entry_t entry;
540 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
541 while (ret == 1) {
542 acl_tag_t currentTag;
543 acl_get_tag_type(entry, &currentTag);
544 if (currentTag == ACL_GROUP) {
545 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
546 const gid_t id = *(static_cast<gid_t *>(idptr.get()));
547 if (d->getGroupName(id) == name) {
548 *exists = true;
549 return entryToPermissions(entry);
550 }
551 }
552 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
553 }
554#else
555 Q_UNUSED(name);
556#endif
557 return 0;
558}
559
560bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions)
561{
562#if HAVE_POSIX_ACL
563 return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP);
564#else
565 Q_UNUSED(name);
566 Q_UNUSED(permissions);
567 return true;
568#endif
569}
570
571ACLGroupPermissionsList KACL::allGroupPermissions() const
572{
573 ACLGroupPermissionsList list;
574#if HAVE_POSIX_ACL
575 acl_entry_t entry;
576 int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry);
577 while (ret == 1) {
578 acl_tag_t currentTag;
579 acl_get_tag_type(entry, &currentTag);
580 if (currentTag == ACL_GROUP) {
581 const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free);
582 const gid_t id = *(static_cast<gid_t *>(idptr.get()));
583 QString name = d->getGroupName(id);
584 unsigned short permissions = entryToPermissions(entry);
585 ACLGroupPermissions pair = qMakePair(name, permissions);
586 list.append(pair);
587 }
588 ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry);
589 }
590#endif
591 return list;
592}
593
594bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups)
595{
596#if HAVE_POSIX_ACL
597 return d->setAllUsersOrGroups(groups, ACL_GROUP);
598#else
599 Q_UNUSED(groups);
600 return true;
601#endif
602}
603
604/**************************
605 * from and to string *
606 **************************/
607
608bool KACL::setACL(const QString &aclStr)
609{
610 bool ret = false;
611#if HAVE_POSIX_ACL
612 acl_t temp = acl_from_text(aclStr.toLatin1().constData());
613 if (acl_valid(temp) != 0) {
614 // TODO errno is set, what to do with it here?
615 acl_free(temp);
616 } else {
617 if (d->m_acl) {
618 acl_free(d->m_acl);
619 }
620 d->m_acl = temp;
621 ret = true;
622 }
623#else
624 Q_UNUSED(aclStr);
625#endif
626 return ret;
627}
628
629QString KACL::asString() const
630{
631#if HAVE_POSIX_ACL
632 ssize_t size = 0;
633 char *txt = acl_to_text(d->m_acl, &size);
634 const QString ret = QString::fromLatin1(txt, size);
635 acl_free(txt);
636 return ret;
637#else
638 return QString();
639#endif
640}
641
642// helpers
643
644#if HAVE_POSIX_ACL
645QString KACL::KACLPrivate::getUserName(uid_t uid) const
646{
647 auto it = m_usercache.find(uid);
648 if (it == m_usercache.end()) {
649 struct passwd *user = getpwuid(uid);
650 if (user) {
651 it = m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
652 } else {
653 return QString::number(uid);
654 }
655 }
656 return *it;
657}
658
659QString KACL::KACLPrivate::getGroupName(gid_t gid) const
660{
661 auto it = m_groupcache.find(gid);
662 if (it == m_groupcache.end()) {
663 struct group *grp = getgrgid(gid);
664 if (grp) {
665 it = m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
666 } else {
667 return QString::number(gid);
668 }
669 }
670 return *it;
671}
672#endif
673
674void KACL::virtual_hook(int, void *)
675{
676 /*BASE::virtual_hook( id, data );*/
677}
678
679QDataStream &operator<<(QDataStream &s, const KACL &a)
680{
681 s << a.asString();
682 return s;
683}
684
685QDataStream &operator>>(QDataStream &s, KACL &a)
686{
687 QString str;
688 s >> str;
689 a.setACL(str);
690 return s;
691}
692

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