1 | /* |
2 | KUser - represent a user/account |
3 | |
4 | SPDX-FileCopyrightText: 2002 Tim Jansen <tim@tjansen.de> |
5 | SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | #include "config-util.h" |
11 | #include "kcoreaddons_debug.h" |
12 | #include "kuser.h" |
13 | |
14 | #include <QFileInfo> |
15 | |
16 | #include <cerrno> |
17 | #include <grp.h> |
18 | #include <pwd.h> |
19 | #include <stdlib.h> |
20 | #include <unistd.h> |
21 | |
22 | #include <algorithm> // std::find |
23 | #include <functional> // std::function |
24 | |
25 | #if defined(__BIONIC__) && __ANDROID_API__ < 26 |
26 | static inline struct passwd *getpwent() |
27 | { |
28 | return nullptr; |
29 | } |
30 | inline void setpwent() |
31 | { |
32 | } |
33 | static inline void setgrent() |
34 | { |
35 | } |
36 | static inline struct group *getgrent() |
37 | { |
38 | return nullptr; |
39 | } |
40 | inline void endpwent() |
41 | { |
42 | } |
43 | static inline void endgrent() |
44 | { |
45 | } |
46 | #endif |
47 | |
48 | // Only define os_pw_size() if it's going to be used |
49 | #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) |
50 | static int os_pw_size() // hint for size of passwd struct |
51 | { |
52 | const int size_max = sysconf(_SC_GETPW_R_SIZE_MAX); |
53 | if (size_max != -1) { |
54 | return size_max; |
55 | } |
56 | return 1024; |
57 | } |
58 | #endif |
59 | |
60 | // Only define os_gr_size() if it's going to be used |
61 | #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24)) |
62 | static int os_gr_size() // hint for size of group struct |
63 | { |
64 | const int size_max = sysconf(_SC_GETGR_R_SIZE_MAX); |
65 | if (size_max != -1) { |
66 | return size_max; |
67 | } |
68 | return 1024; |
69 | } |
70 | #endif |
71 | |
72 | class KUserPrivate : public QSharedData |
73 | { |
74 | public: |
75 | uid_t uid = uid_t(-1); |
76 | gid_t gid = gid_t(-1); |
77 | QString loginName; |
78 | QString homeDir, shell; |
79 | QMap<KUser::UserProperty, QVariant> properties; |
80 | |
81 | KUserPrivate() |
82 | { |
83 | } |
84 | KUserPrivate(const char *name) |
85 | { |
86 | if (!name) { |
87 | fillPasswd(p: nullptr); |
88 | } else { |
89 | struct passwd *pw = nullptr; |
90 | #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) |
91 | static const int bufsize = os_pw_size(); |
92 | QVarLengthArray<char, 1024> buf(bufsize); |
93 | struct passwd entry; |
94 | getpwnam_r(name: name, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &pw); |
95 | #else |
96 | pw = getpwnam(name); // not thread-safe! |
97 | #endif |
98 | fillPasswd(p: pw); |
99 | } |
100 | } |
101 | KUserPrivate(K_UID uid) |
102 | { |
103 | struct passwd *pw = nullptr; |
104 | #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) |
105 | static const int bufsize = os_pw_size(); |
106 | QVarLengthArray<char, 1024> buf(bufsize); |
107 | struct passwd entry; |
108 | getpwuid_r(uid: uid, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &pw); |
109 | #else |
110 | pw = getpwuid(uid); // not thread-safe! |
111 | #endif |
112 | fillPasswd(p: pw); |
113 | } |
114 | KUserPrivate(const passwd *p) |
115 | { |
116 | fillPasswd(p); |
117 | } |
118 | |
119 | void fillPasswd(const passwd *p) |
120 | { |
121 | if (p) { |
122 | #ifndef __BIONIC__ |
123 | QString gecos = QString::fromLocal8Bit(ba: p->pw_gecos); |
124 | #else |
125 | QString gecos = QString(); |
126 | #endif |
127 | QStringList gecosList = gecos.split(sep: QLatin1Char(',')); |
128 | // fill up the list, should be at least 4 entries |
129 | while (gecosList.size() < 4) { |
130 | gecosList << QString(); |
131 | } |
132 | |
133 | uid = p->pw_uid; |
134 | gid = p->pw_gid; |
135 | loginName = QString::fromLocal8Bit(ba: p->pw_name); |
136 | properties[KUser::FullName] = QVariant(gecosList[0]); |
137 | properties[KUser::RoomNumber] = QVariant(gecosList[1]); |
138 | properties[KUser::WorkPhone] = QVariant(gecosList[2]); |
139 | properties[KUser::HomePhone] = QVariant(gecosList[3]); |
140 | if (uid == ::getuid() && uid == ::geteuid()) { |
141 | homeDir = QFile::decodeName(localFileName: qgetenv(varName: "HOME" )); |
142 | } |
143 | if (homeDir.isEmpty()) { |
144 | homeDir = QFile::decodeName(localFileName: p->pw_dir); |
145 | } |
146 | shell = QString::fromLocal8Bit(ba: p->pw_shell); |
147 | } |
148 | } |
149 | }; |
150 | |
151 | KUser::KUser(UIDMode mode) |
152 | { |
153 | uid_t _uid = ::getuid(); |
154 | uid_t _euid; |
155 | if (mode == UseEffectiveUID && (_euid = ::geteuid()) != _uid) { |
156 | d = new KUserPrivate(_euid); |
157 | } else { |
158 | d = new KUserPrivate(qgetenv(varName: "LOGNAME" ).constData()); |
159 | if (d->uid != _uid) { |
160 | d = new KUserPrivate(qgetenv(varName: "USER" ).constData()); |
161 | if (d->uid != _uid) { |
162 | d = new KUserPrivate(_uid); |
163 | } |
164 | } |
165 | } |
166 | } |
167 | |
168 | KUser::KUser(K_UID _uid) |
169 | : d(new KUserPrivate(_uid)) |
170 | { |
171 | } |
172 | |
173 | KUser::KUser(KUserId _uid) |
174 | : d(new KUserPrivate(_uid.nativeId())) |
175 | { |
176 | } |
177 | |
178 | KUser::KUser(const QString &name) |
179 | : d(new KUserPrivate(name.toLocal8Bit().data())) |
180 | { |
181 | } |
182 | |
183 | KUser::KUser(const char *name) |
184 | : d(new KUserPrivate(name)) |
185 | { |
186 | } |
187 | |
188 | KUser::KUser(const passwd *p) |
189 | : d(new KUserPrivate(p)) |
190 | { |
191 | } |
192 | |
193 | KUser::KUser(const KUser &user) |
194 | : d(user.d) |
195 | { |
196 | } |
197 | |
198 | KUser &KUser::operator=(const KUser &user) |
199 | { |
200 | d = user.d; |
201 | return *this; |
202 | } |
203 | |
204 | bool KUser::operator==(const KUser &user) const |
205 | { |
206 | return isValid() && (d->uid == user.d->uid); |
207 | } |
208 | |
209 | bool KUser::isValid() const |
210 | { |
211 | return d->uid != uid_t(-1); |
212 | } |
213 | |
214 | KUserId KUser::userId() const |
215 | { |
216 | return KUserId(d->uid); |
217 | } |
218 | |
219 | KGroupId KUser::groupId() const |
220 | { |
221 | return KGroupId(d->gid); |
222 | } |
223 | |
224 | bool KUser::isSuperUser() const |
225 | { |
226 | return d->uid == 0; |
227 | } |
228 | |
229 | QString KUser::loginName() const |
230 | { |
231 | return d->loginName; |
232 | } |
233 | |
234 | QString KUser::homeDir() const |
235 | { |
236 | return d->homeDir; |
237 | } |
238 | |
239 | QString KUser::faceIconPath() const |
240 | { |
241 | QString pathToFaceIcon; |
242 | if (!d->loginName.isEmpty()) { |
243 | pathToFaceIcon = QStringLiteral(ACCOUNTS_SERVICE_ICON_DIR) + QLatin1Char('/') + d->loginName; |
244 | } |
245 | |
246 | if (QFile::exists(fileName: pathToFaceIcon)) { |
247 | return pathToFaceIcon; |
248 | } |
249 | |
250 | pathToFaceIcon = homeDir() + QLatin1Char('/') + QLatin1String(".face.icon" ); |
251 | |
252 | if (QFileInfo(pathToFaceIcon).isReadable()) { |
253 | return pathToFaceIcon; |
254 | } |
255 | |
256 | return QString(); |
257 | } |
258 | |
259 | QString KUser::shell() const |
260 | { |
261 | return d->shell; |
262 | } |
263 | |
264 | template<class Func> |
265 | static void listGroupsForUser(const char *name, gid_t gid, uint maxCount, Func handleNextGroup) |
266 | { |
267 | if (Q_UNLIKELY(maxCount == 0)) { |
268 | return; |
269 | } |
270 | uint found = 0; |
271 | #if HAVE_GETGROUPLIST |
272 | QVarLengthArray<gid_t, 100> gid_buffer; |
273 | gid_buffer.resize(sz: 100); |
274 | int numGroups = gid_buffer.size(); |
275 | int result = getgrouplist(user: name, group: gid, groups: gid_buffer.data(), ngroups: &numGroups); |
276 | if (result < 0 && uint(numGroups) < maxCount) { |
277 | // getgrouplist returns -1 if the buffer was too small to store all entries, the required size is in numGroups |
278 | qCDebug(KCOREADDONS_DEBUG) << "Buffer was too small: " << gid_buffer.size() << ", need" << numGroups; |
279 | gid_buffer.resize(sz: numGroups); |
280 | numGroups = gid_buffer.size(); |
281 | getgrouplist(user: name, group: gid, groups: gid_buffer.data(), ngroups: &numGroups); |
282 | } |
283 | for (int i = 0; i < numGroups && found < maxCount; ++i) { |
284 | struct group *g = getgrgid(gid: gid_buffer[i]); // ### not threadsafe |
285 | // should never be null, but better be safe than crash |
286 | if (g) { |
287 | found++; |
288 | handleNextGroup(g); |
289 | } |
290 | } |
291 | #else |
292 | // fall back to getgrent() and reading gr->gr_mem |
293 | // This is slower than getgrouplist, but works as well |
294 | // add the current gid, this is often not part of g->gr_mem (e.g. build.kde.org or my openSuSE 13.1 system) |
295 | struct group *g = getgrgid(gid); // ### not threadsafe |
296 | if (g) { |
297 | handleNextGroup(g); |
298 | found++; |
299 | if (found >= maxCount) { |
300 | return; |
301 | } |
302 | } |
303 | |
304 | static const auto groupContainsUser = [](struct group *g, const char *name) -> bool { |
305 | for (char **user = g->gr_mem; *user; user++) { |
306 | if (strcmp(name, *user) == 0) { |
307 | return true; |
308 | } |
309 | } |
310 | return false; |
311 | }; |
312 | |
313 | setgrent(); |
314 | while ((g = getgrent())) { |
315 | // don't add the current gid again |
316 | if (g->gr_gid != gid && groupContainsUser(g, name)) { |
317 | handleNextGroup(g); |
318 | found++; |
319 | if (found >= maxCount) { |
320 | break; |
321 | } |
322 | } |
323 | } |
324 | endgrent(); |
325 | #endif |
326 | } |
327 | |
328 | QList<KUserGroup> KUser::groups(uint maxCount) const |
329 | { |
330 | QList<KUserGroup> result; |
331 | listGroupsForUser(name: d->loginName.toLocal8Bit().constData(), gid: d->gid, maxCount, handleNextGroup: [&](const group *g) { |
332 | result.append(t: KUserGroup(g)); |
333 | }); |
334 | return result; |
335 | } |
336 | |
337 | QStringList KUser::groupNames(uint maxCount) const |
338 | { |
339 | QStringList result; |
340 | listGroupsForUser(name: d->loginName.toLocal8Bit().constData(), gid: d->gid, maxCount, handleNextGroup: [&](const group *g) { |
341 | result.append(t: QString::fromLocal8Bit(ba: g->gr_name)); |
342 | }); |
343 | return result; |
344 | } |
345 | |
346 | QVariant KUser::property(UserProperty which) const |
347 | { |
348 | return d->properties.value(key: which); |
349 | } |
350 | |
351 | QList<KUser> KUser::allUsers(uint maxCount) |
352 | { |
353 | QList<KUser> result; |
354 | |
355 | passwd *p; |
356 | setpwent(); |
357 | |
358 | for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { |
359 | result.append(t: KUser(p)); |
360 | } |
361 | |
362 | endpwent(); |
363 | |
364 | return result; |
365 | } |
366 | |
367 | QStringList KUser::allUserNames(uint maxCount) |
368 | { |
369 | QStringList result; |
370 | |
371 | passwd *p; |
372 | setpwent(); |
373 | |
374 | for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { |
375 | result.append(t: QString::fromLocal8Bit(ba: p->pw_name)); |
376 | } |
377 | |
378 | endpwent(); |
379 | return result; |
380 | } |
381 | |
382 | KUser::~KUser() |
383 | { |
384 | } |
385 | |
386 | class KUserGroupPrivate : public QSharedData |
387 | { |
388 | public: |
389 | gid_t gid = gid_t(-1); |
390 | QString name; |
391 | |
392 | KUserGroupPrivate() |
393 | { |
394 | } |
395 | KUserGroupPrivate(const char *_name) |
396 | { |
397 | fillGroup(p: _name ? ::getgrnam(name: _name) : nullptr); |
398 | } |
399 | KUserGroupPrivate(K_GID gid) |
400 | { |
401 | struct group *gr = nullptr; |
402 | #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24)) |
403 | static const int bufsize = os_gr_size(); |
404 | QVarLengthArray<char, 1024> buf(bufsize); |
405 | struct group entry; |
406 | // Some large systems have more members than the POSIX max size |
407 | // Loop over by doubling the buffer size (upper limit 250k) |
408 | for (int size = bufsize; size < 256000; size += size) { |
409 | buf.resize(sz: size); |
410 | // ERANGE indicates that the buffer was too small |
411 | if (!getgrgid_r(gid: gid, resultbuf: &entry, buffer: buf.data(), buflen: buf.size(), result: &gr) || errno != ERANGE) { |
412 | break; |
413 | } |
414 | } |
415 | #else |
416 | gr = getgrgid(gid); // not thread-safe! |
417 | #endif |
418 | fillGroup(p: gr); |
419 | } |
420 | KUserGroupPrivate(const ::group *p) |
421 | { |
422 | fillGroup(p); |
423 | } |
424 | |
425 | void fillGroup(const ::group *p) |
426 | { |
427 | if (p) { |
428 | gid = p->gr_gid; |
429 | name = QString::fromLocal8Bit(ba: p->gr_name); |
430 | } |
431 | } |
432 | }; |
433 | |
434 | KUserGroup::KUserGroup(KUser::UIDMode mode) |
435 | : d(new KUserGroupPrivate(KUser(mode).groupId().nativeId())) |
436 | { |
437 | } |
438 | |
439 | KUserGroup::KUserGroup(K_GID _gid) |
440 | : d(new KUserGroupPrivate(_gid)) |
441 | { |
442 | } |
443 | |
444 | KUserGroup::KUserGroup(KGroupId _gid) |
445 | : d(new KUserGroupPrivate(_gid.nativeId())) |
446 | { |
447 | } |
448 | |
449 | KUserGroup::KUserGroup(const QString &_name) |
450 | : d(new KUserGroupPrivate(_name.toLocal8Bit().data())) |
451 | { |
452 | } |
453 | |
454 | KUserGroup::KUserGroup(const char *_name) |
455 | : d(new KUserGroupPrivate(_name)) |
456 | { |
457 | } |
458 | |
459 | KUserGroup::KUserGroup(const ::group *g) |
460 | : d(new KUserGroupPrivate(g)) |
461 | { |
462 | } |
463 | |
464 | KUserGroup::KUserGroup(const KUserGroup &group) |
465 | : d(group.d) |
466 | { |
467 | } |
468 | |
469 | KUserGroup &KUserGroup::operator=(const KUserGroup &group) |
470 | { |
471 | d = group.d; |
472 | return *this; |
473 | } |
474 | |
475 | bool KUserGroup::operator==(const KUserGroup &group) const |
476 | { |
477 | return isValid() && (d->gid == group.d->gid); |
478 | } |
479 | |
480 | bool KUserGroup::isValid() const |
481 | { |
482 | return d->gid != gid_t(-1); |
483 | } |
484 | |
485 | KGroupId KUserGroup::groupId() const |
486 | { |
487 | return KGroupId(d->gid); |
488 | } |
489 | |
490 | QString KUserGroup::name() const |
491 | { |
492 | return d->name; |
493 | } |
494 | |
495 | static void listGroupMembers(gid_t gid, uint maxCount, std::function<void(passwd *)> handleNextGroupUser) |
496 | { |
497 | if (maxCount == 0) { |
498 | return; |
499 | } |
500 | struct group *g = getgrgid(gid: gid); // ### not threadsafe |
501 | if (!g) { |
502 | return; |
503 | } |
504 | uint found = 0; |
505 | QVarLengthArray<uid_t> addedUsers; |
506 | struct passwd *p = nullptr; |
507 | for (char **user = g->gr_mem; *user; user++) { |
508 | if ((p = getpwnam(name: *user))) { // ### not threadsafe |
509 | addedUsers.append(t: p->pw_uid); |
510 | handleNextGroupUser(p); |
511 | found++; |
512 | if (found >= maxCount) { |
513 | break; |
514 | } |
515 | } |
516 | } |
517 | |
518 | // gr_mem doesn't contain users where the primary group == gid -> we have to iterate over all users |
519 | setpwent(); |
520 | while ((p = getpwent()) && found < maxCount) { |
521 | if (p->pw_gid != gid) { |
522 | continue; // only need primary gid since otherwise gr_mem already contains this user |
523 | } |
524 | // make sure we don't list a user twice |
525 | if (std::find(first: addedUsers.cbegin(), last: addedUsers.cend(), val: p->pw_uid) == addedUsers.cend()) { |
526 | handleNextGroupUser(p); |
527 | found++; |
528 | } |
529 | } |
530 | endpwent(); |
531 | } |
532 | |
533 | QList<KUser> KUserGroup::users(uint maxCount) const |
534 | { |
535 | QList<KUser> result; |
536 | listGroupMembers(gid: d->gid, maxCount, handleNextGroupUser: [&](const passwd *p) { |
537 | result.append(t: KUser(p)); |
538 | }); |
539 | return result; |
540 | } |
541 | |
542 | QStringList KUserGroup::userNames(uint maxCount) const |
543 | { |
544 | QStringList result; |
545 | listGroupMembers(gid: d->gid, maxCount, handleNextGroupUser: [&](const passwd *p) { |
546 | result.append(t: QString::fromLocal8Bit(ba: p->pw_name)); |
547 | }); |
548 | return result; |
549 | } |
550 | |
551 | QList<KUserGroup> KUserGroup::allGroups(uint maxCount) |
552 | { |
553 | QList<KUserGroup> result; |
554 | |
555 | ::group *g; |
556 | setgrent(); |
557 | |
558 | for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { |
559 | result.append(t: KUserGroup(g)); |
560 | } |
561 | |
562 | endgrent(); |
563 | |
564 | return result; |
565 | } |
566 | |
567 | QStringList KUserGroup::allGroupNames(uint maxCount) |
568 | { |
569 | QStringList result; |
570 | |
571 | ::group *g; |
572 | setgrent(); |
573 | |
574 | for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { |
575 | result.append(t: QString::fromLocal8Bit(ba: g->gr_name)); |
576 | } |
577 | |
578 | endgrent(); |
579 | |
580 | return result; |
581 | } |
582 | |
583 | KUserGroup::~KUserGroup() |
584 | { |
585 | } |
586 | |
587 | KUserId KUserId::fromName(const QString &name) |
588 | { |
589 | if (name.isEmpty()) { |
590 | return KUserId(); |
591 | } |
592 | QByteArray name8Bit = name.toLocal8Bit(); |
593 | struct passwd *p = ::getpwnam(name: name8Bit.constData()); |
594 | if (!p) { |
595 | qCWarning(KCOREADDONS_DEBUG, "Failed to lookup user %s: %s" , name8Bit.constData(), strerror(errno)); |
596 | return KUserId(); |
597 | } |
598 | return KUserId(p->pw_uid); |
599 | } |
600 | |
601 | KGroupId KGroupId::fromName(const QString &name) |
602 | { |
603 | if (name.isEmpty()) { |
604 | return KGroupId(); |
605 | } |
606 | QByteArray name8Bit = name.toLocal8Bit(); |
607 | struct group *g = ::getgrnam(name: name8Bit.constData()); |
608 | if (!g) { |
609 | qCWarning(KCOREADDONS_DEBUG, "Failed to lookup group %s: %s" , name8Bit.constData(), strerror(errno)); |
610 | return KGroupId(); |
611 | } |
612 | return KGroupId(g->gr_gid); |
613 | } |
614 | |
615 | KUserId KUserId::currentUserId() |
616 | { |
617 | return KUserId(getuid()); |
618 | } |
619 | |
620 | KUserId KUserId::currentEffectiveUserId() |
621 | { |
622 | return KUserId(geteuid()); |
623 | } |
624 | |
625 | KGroupId KGroupId::currentGroupId() |
626 | { |
627 | return KGroupId(getgid()); |
628 | } |
629 | |
630 | KGroupId KGroupId::currentEffectiveGroupId() |
631 | { |
632 | return KGroupId(getegid()); |
633 | } |
634 | |