KCoreAddons

kuser_win.cpp
1/*
2 KUser - represent a user/account (Windows)
3
4 SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.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 "kuser.h"
11
12#include "kcoreaddons_debug.h"
13#include <QDir>
14#include <QStandardPaths>
15
16#include <memory> // unique_ptr
17#include <type_traits>
18
19#include <qt_windows.h> // Must be included before lm.h
20
21#include <lm.h> //Net*
22
23#include <sddl.h> //ConvertSidToStringSidW
24#include <shlobj.h>
25#include <userenv.h> //GetProfilesDirectoryW
26
27// this can't be a lambda due to a MSVC2012 bug
28// (works fine in 2010 and 2013)
29struct netApiBufferDeleter {
30 void operator()(void *buffer)
31 {
32 if (buffer) {
33 NetApiBufferFree(buffer);
34 }
35 }
36};
37
38template<typename T>
39class ScopedNetApiBuffer : public std::unique_ptr<T, netApiBufferDeleter>
40{
41public:
42 // explicit scope resolution operator needed in ::netApiBufferDeleter
43 // because of *another* MSVC bug :(
44 inline explicit ScopedNetApiBuffer(T *data)
45 : std::unique_ptr<T, ::netApiBufferDeleter>(data, ::netApiBufferDeleter())
46 {
47 }
48};
49
50const auto handleCloser = [](HANDLE h) {
51 if (h != INVALID_HANDLE_VALUE) {
52 CloseHandle(h);
53 }
54};
55typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(handleCloser)> ScopedHANDLE;
56
57/** Make sure the NetApi functions are called with the correct level argument (for template functions)
58 * This argument can be retrieved by using NetApiTypeInfo<T>::level. In order to do so the type must be
59 * registered by writing e.g. NETAPI_TYPE_INFO(GROUP_INFO, 0) for GROUP_INFO_0
60 */
61template<typename T>
62struct NetApiTypeInfo {
63};
64#define NETAPI_TYPE_INFO(prefix, n) \
65 template<> \
66 struct NetApiTypeInfo<prefix##_##n> { \
67 enum { \
68 level = n \
69 }; \
70 };
71NETAPI_TYPE_INFO(GROUP_INFO, 0)
72NETAPI_TYPE_INFO(GROUP_INFO, 3)
73NETAPI_TYPE_INFO(USER_INFO, 0)
74NETAPI_TYPE_INFO(USER_INFO, 4)
75NETAPI_TYPE_INFO(USER_INFO, 11)
76NETAPI_TYPE_INFO(GROUP_USERS_INFO, 0)
77
78// T must be a USER_INFO_* structure
79template<typename T>
80ScopedNetApiBuffer<T> getUserInfo(LPCWSTR server, const QString &userName, NET_API_STATUS *errCode)
81{
82 LPBYTE userInfoTmp = nullptr;
83 // if level = 11 a USER_INFO_11 structure gets filled in and allocated by NetUserGetInfo(), etc.
84 NET_API_STATUS status = NetUserGetInfo(server, (LPCWSTR)userName.utf16(), NetApiTypeInfo<T>::level, &userInfoTmp);
85 if (status != NERR_Success) {
86 userInfoTmp = nullptr;
87 }
88 if (errCode) {
89 *errCode = status;
90 }
91 return ScopedNetApiBuffer<T>((T *)userInfoTmp);
92}
93
94// enumeration functions
95/** simplify calling the Net*Enum functions to prevent copy and paste for allUsers(), allUserNames(), allGroups(), allGroupNames()
96 * @tparam T The type that is enumerated (e.g. USER_INFO_11) Must be registered using NETAPI_TYPE_INFO.
97 * @param callback Callback for each listed object. Signature: void(const T&)
98 * @param enumFunc This function enumerates the data using a Net* function.
99 * It will be called in a loop as long as it returns ERROR_MORE_DATA.
100 *
101 */
102template<class T, class Callback, class EnumFunction>
103static void netApiEnumerate(uint maxCount, Callback callback, EnumFunction enumFunc)
104{
105 NET_API_STATUS nStatus = NERR_Success;
106 DWORD_PTR resumeHandle = 0;
107 uint total = 0;
108 int level = NetApiTypeInfo<T>::level;
109 do {
110 LPBYTE buffer = nullptr;
111 DWORD entriesRead = 0;
112 DWORD totalEntries = 0;
113 nStatus = enumFunc(level, &buffer, &entriesRead, &totalEntries, &resumeHandle);
114 // qDebug("Net*Enum(level = %d) returned %d entries, total was (%d), status = %d, resume handle = %llx",
115 // level, entriesRead, totalEntries, nStatus, resumeHandle);
116
117 // buffer must always be freed, even if Net*Enum fails
118 ScopedNetApiBuffer<T> groupInfo((T *)buffer);
119 if (nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) {
120 for (DWORD i = 0; total < maxCount && i < entriesRead; i++, total++) {
121 callback(groupInfo.get()[i]);
122 }
123 } else {
124 qCWarning(KCOREADDONS_DEBUG, "NetApi enumerate function failed: status = %d", (int)nStatus);
125 }
126 } while (nStatus == ERROR_MORE_DATA);
127}
128
129template<class T, class Callback>
130void enumerateAllUsers(uint maxCount, Callback callback)
131{
132 netApiEnumerate<T>(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) {
133 // pass 0 as filter -> get all users
134 // Why does this function take a DWORD* as resume handle and NetUserEnum/NetGroupGetUsers a UINT64*
135 // Great API design by Microsoft...
136 // casting the uint64* to uint32* is fine, it just writes to the first 32 bits
137 return NetUserEnum(nullptr, level, 0, buffer, MAX_PREFERRED_LENGTH, count, total, (PDWORD)resumeHandle);
138 });
139}
140
141template<typename T, class Callback>
142void enumerateAllGroups(uint maxCount, Callback callback)
143{
144 netApiEnumerate<T>(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) {
145 return NetGroupEnum(nullptr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle);
146 });
147}
148
149template<typename T, class Callback>
150void enumerateGroupsForUser(uint maxCount, const QString &name, Callback callback)
151{
152 LPCWSTR nameStr = (LPCWSTR)name.utf16();
153 netApiEnumerate<T>(maxCount, callback, [&](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) -> NET_API_STATUS {
154 Q_UNUSED(resumeHandle);
155 NET_API_STATUS ret = NetUserGetGroups(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total);
156 // if we return ERROR_MORE_DATA here it will result in an endless loop
157 if (ret == ERROR_MORE_DATA) {
158 qCWarning(KCOREADDONS_DEBUG) << "NetUserGetGroups for user" << name << "returned ERROR_MORE_DATA. This should not happen!";
159 ret = NERR_Success;
160 }
161 return ret;
162 });
163}
164
165template<typename T, class Callback>
166void enumerateUsersForGroup(const QString &name, uint maxCount, Callback callback)
167{
168 LPCWSTR nameStr = (LPCWSTR)name.utf16();
169 netApiEnumerate<T>(maxCount, callback, [nameStr](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) {
170 return NetGroupGetUsers(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle);
171 });
172}
173
174class KUserPrivate : public QSharedData
175{
177 KUserPrivate()
178 : isAdmin(false)
179 {
180 }
181 // takes ownership over userInfo_
182 KUserPrivate(KUserId uid, KGroupId gid, const QString &loginName, const QString &fullName, const QString &domain, const QString &homeDir, bool isAdmin)
183 : uid(uid)
184 , gid(gid)
185 , loginName(loginName)
187 , domain(domain)
188 , homeDir(homeDir)
189 , isAdmin(isAdmin)
190 {
191 Q_ASSERT(uid.isValid());
192 }
193 static QString guessHomeDir(const QString &username, KUserId uid)
194 {
195 // usri11_home_dir/usri4_home_dir is often empty
196 // check whether it is the homedir for the current user and if not then fall back to "<user profiles dir><user name>"
197 if (uid == KUserId::currentUserId()) {
198 return QDir::homePath();
199 }
200 QString homeDir;
201 WCHAR profileDirPath[MAX_PATH];
202 DWORD bufSize = MAX_PATH;
203 BOOL result = GetProfilesDirectoryW(profileDirPath, &bufSize);
204 if (result) {
205 // This might not be correct: e.g. with local user and domain user with same
206 // In that case it could be C:\Users\Foo (local user) vs C:\Users\Foo.DOMAIN (domain user)
207 // However it is still much better than the previous code which just returned the current users home dir
208 homeDir = QString::fromWCharArray(profileDirPath) + QLatin1Char('\\') + username;
209 }
210 return homeDir;
211 }
212
213public:
214 static Ptr sharedNull;
215 KUserId uid;
216 KGroupId gid;
217 QString loginName;
218 QString fullName;
219 QString domain;
220 QString homeDir;
221 bool isAdmin;
222
223 /** Creates a user info from a SID (never returns null) */
224 static Ptr create(KUserId uid)
225 {
226 if (!uid.isValid()) {
227 return sharedNull;
228 }
229 // now find the fully qualified name for the user
230 DWORD nameBufferLen = UNLEN + 1;
231 WCHAR nameBuffer[UNLEN + 1];
232 DWORD domainBufferLen = UNLEN + 1;
233 WCHAR domainBuffer[UNLEN + 1];
234 SID_NAME_USE use;
235 if (!LookupAccountSidW(nullptr, uid.nativeId(), nameBuffer, &nameBufferLen, domainBuffer, &domainBufferLen, &use)) {
236 qCWarning(KCOREADDONS_DEBUG) << "Could not lookup user " << uid.toString() << "error =" << GetLastError();
237 return sharedNull;
238 }
239 QString loginName = QString::fromWCharArray(nameBuffer);
240 QString domainName = QString::fromWCharArray(domainBuffer);
241 if (use != SidTypeUser && use != SidTypeDeletedAccount) {
242 qCWarning(KCOREADDONS_DEBUG).nospace() << "SID for " << domainName << "\\" << loginName << " (" << uid.toString() << ") is not of type user ("
243 << SidTypeUser << " or " << SidTypeDeletedAccount << "). Got type " << use << " instead.";
244 return sharedNull;
245 }
246 // now get the server name to query (could be null for local machine)
247 LPWSTR servernameTmp = nullptr;
248 NET_API_STATUS status = NetGetAnyDCName(nullptr, nullptr, (LPBYTE *)&servernameTmp);
249 if (status != NERR_Success) {
250 // this always fails on my desktop system, don't spam the output
251 // qDebug("NetGetAnyDCName failed with error %d", status);
252 }
253 ScopedNetApiBuffer<WCHAR> servername(servernameTmp);
254
256 QString homeDir;
257 KGroupId group;
258 bool isAdmin = false;
259 // must NOT pass the qualified name ("domain\user") here or lookup fails -> just the name
260 // try USER_INFO_4 first, MSDN says it is valid only on servers (whatever that means), it works on my desktop system
261 // If it fails fall back to USER_INFO11, which has all the needed information except primary group
262 if (auto userInfo4 = getUserInfo<USER_INFO_4>(servername.get(), loginName, &status)) {
263 Q_ASSERT(KUserId(userInfo4->usri4_user_sid) == uid); // if this is not the same we have a logic error
264 fullName = QString::fromWCharArray(userInfo4->usri4_full_name);
265 homeDir = QString::fromWCharArray(userInfo4->usri4_home_dir);
266 isAdmin = userInfo4->usri4_priv == USER_PRIV_ADMIN;
267 // now determine the primary group:
268 const DWORD primaryGroup = userInfo4->usri4_primary_group_id;
269 // primary group is a relative identifier, i.e. in order to get the SID for that group
270 // we have to take the user SID and replace the last subauthority value with the relative identifier
271 group = KGroupId(uid.nativeId()); // constructor does not check whether the sid refers to a group
272 Q_ASSERT(group.isValid());
273 UCHAR numSubauthorities = *GetSidSubAuthorityCount(group.nativeId());
274 PDWORD lastSubAutority = GetSidSubAuthority(group.nativeId(), numSubauthorities - 1);
275 *lastSubAutority = primaryGroup;
276 } else if (auto userInfo11 = getUserInfo<USER_INFO_11>(servername.get(), loginName, &status)) {
277 fullName = QString::fromWCharArray(userInfo11->usri11_full_name);
278 homeDir = QString::fromWCharArray(userInfo11->usri11_home_dir);
279 isAdmin = userInfo11->usri11_priv == USER_PRIV_ADMIN;
280 } else {
281 qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not get information for user " << domainName << "\\" << loginName << ": error code = " << status;
282 return sharedNull;
283 }
284 if (homeDir.isEmpty()) {
285 homeDir = guessHomeDir(loginName, uid);
286 }
287 // if we couldn't find a primary group just take the first group found for this user
288 if (!group.isValid()) {
289 enumerateGroupsForUser<GROUP_USERS_INFO_0>(1, loginName, [&](const GROUP_USERS_INFO_0 &info) {
290 group = KGroupId::fromName(QString::fromWCharArray(info.grui0_name));
291 });
292 }
293 return Ptr(new KUserPrivate(uid, group, loginName, fullName, domainName, homeDir, isAdmin));
294 }
295};
296
297KUserPrivate::Ptr KUserPrivate::sharedNull(new KUserPrivate());
298
299KUser::KUser(UIDMode mode)
300{
301 if (mode == UseEffectiveUID) {
302 d = KUserPrivate::create(KUserId::currentEffectiveUserId());
303 } else if (mode == UseRealUserID) {
304 d = KUserPrivate::create(KUserId::currentUserId());
305 } else {
306 d = KUserPrivate::sharedNull;
307 }
308}
309
310KUser::KUser(K_UID uid)
311 : d(KUserPrivate::create(KUserId(uid)))
312{
313}
314
316 : d(KUserPrivate::create(uid))
317{
318}
319
320KUser::KUser(const QString &name)
321 : d(KUserPrivate::create(KUserId::fromName(name)))
322{
323}
324
325KUser::KUser(const char *name)
326 : d(KUserPrivate::create(KUserId::fromName(QString::fromLocal8Bit(name))))
327{
328}
329
330KUser::KUser(const KUser &user)
331 : d(user.d)
332{
333}
334
335KUser &KUser::operator=(const KUser &user)
336{
337 d = user.d;
338 return *this;
339}
340
341bool KUser::operator==(const KUser &user) const
342{
343 return isValid() && d->uid == user.d->uid;
344}
345
346bool KUser::isValid() const
347{
348 return d->uid.isValid();
349}
350
351bool KUser::isSuperUser() const
352{
353 return d->isAdmin;
354}
355
357{
358 return d->loginName;
359}
360
362{
363 return d->homeDir;
364}
365
366// Some RAII objects to help uninitializing/destroying WinAPI stuff
367// used in faceIconPath.
368class COMInitializer
369{
370public:
371 COMInitializer()
372 : result(CoInitialize(nullptr))
373 {
374 }
375 ~COMInitializer()
376 {
377 if (SUCCEEDED(result)) {
378 CoUninitialize();
379 }
380 }
381 HRESULT result;
382};
383class W32Library
384{
385public:
386 W32Library(HMODULE h)
387 : h(h)
388 {
389 }
390 ~W32Library()
391 {
392 if (h) {
393 FreeLibrary(h);
394 }
395 }
396 operator HMODULE()
397 {
398 return h;
399 }
400 HMODULE h;
401};
402
403// faceIconPath uses undocumented Windows API known as SHGetUserPicturePath,
404// only accessible by ordinal, unofficially documented at
405// http://undoc.airesoft.co.uk/shell32.dll/SHGetUserPicturePath.php
406
407// The function has a different ordinal and parameters on Windows XP and Vista/7.
408// These structs encapsulate the differences.
409
410struct FaceIconPath_XP {
411 typedef HRESULT(WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR);
412 static const int ordinal = 233;
413 static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathXP, LPCWSTR username, LPWSTR buf, UINT bufsize)
414 {
415 Q_UNUSED(bufsize);
416 // assumes the buffer is MAX_PATH in size
417 return SHGetUserPicturePathXP(username, 0, buf);
418 }
419};
420struct FaceIconPath_Vista {
421 typedef HRESULT(WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR, UINT);
422 static const int ordinal = 261;
423 static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathV, LPCWSTR username, LPWSTR buf, UINT bufsize)
424 {
425 return SHGetUserPicturePathV(username, 0, buf, bufsize);
426 }
427};
428
429template<typename Platform>
430static QString faceIconPathImpl(LPCWSTR username)
431{
432 static COMInitializer COMinit;
433
434 static W32Library shellMod = LoadLibraryA("shell32.dll");
435 if (!shellMod) {
436 return QString();
437 }
438 static typename Platform::funcptr_t sgupp_ptr =
439 reinterpret_cast<typename Platform::funcptr_t>(GetProcAddress(shellMod, MAKEINTRESOURCEA(Platform::ordinal)));
440 if (!sgupp_ptr) {
441 return QString();
442 }
443
444 WCHAR pathBuf[MAX_PATH];
445
446 HRESULT res = Platform::getPicturePath(sgupp_ptr, username, pathBuf, MAX_PATH);
447 if (res != S_OK) {
448 return QString();
449 }
450 return QString::fromWCharArray(pathBuf);
451}
452
454{
455 if (!isValid()) {
456 return QString();
457 }
458
459 LPCWSTR username = reinterpret_cast<const WCHAR *>(d->loginName.utf16());
460 return faceIconPathImpl<FaceIconPath_Vista>(username);
461}
462
463QString KUser::shell() const
464{
465 return isValid() ? QStringLiteral("cmd.exe") : QString();
466}
467
469{
470 return d->uid;
471}
472
474{
475 return d->gid;
476}
477
478QVariant KUser::property(UserProperty which) const
479{
480 if (which == FullName) {
481 return QVariant(d->fullName);
482 }
483
484 return QVariant();
485}
486
488{
489}
490
491class KUserGroupPrivate : public QSharedData
492{
493public:
495 KGroupId gid;
496 KUserGroupPrivate()
497 {
498 }
499 KUserGroupPrivate(const QString &name, KGroupId id)
500 : name(name)
501 , gid(id)
502 {
503 if (!name.isEmpty()) {
504 PBYTE groupInfoTmp = nullptr;
505 NET_API_STATUS status = NetGroupGetInfo(nullptr, (LPCWSTR)name.utf16(), 0, &groupInfoTmp);
506 // must always be freed, even on error
507 ScopedNetApiBuffer<GROUP_INFO_0> groupInfo((GROUP_INFO_0 *)groupInfoTmp);
508 if (status != NERR_Success) {
509 qCWarning(KCOREADDONS_DEBUG) << "Failed to find group with name" << name << "error =" << status;
510 groupInfo.reset();
511 }
512 if (!id.isValid()) {
513 gid = KGroupId::fromName(name);
514 }
515 }
516 }
517};
518
520 : d(new KUserGroupPrivate(_name, KGroupId()))
521{
522}
523
524KUserGroup::KUserGroup(const char *_name)
525 : d(new KUserGroupPrivate(QLatin1String(_name), KGroupId()))
526{
527}
528
529static QString nameFromGroupId(KGroupId gid)
530{
531 if (!gid.isValid()) {
532 return QString();
533 }
534
535 DWORD bufferLen = UNLEN + 1;
536 WCHAR buffer[UNLEN + 1];
537 DWORD domainBufferLen = UNLEN + 1;
538 WCHAR domainBuffer[UNLEN + 1];
539 SID_NAME_USE eUse;
541 if (LookupAccountSidW(nullptr, gid.nativeId(), buffer, &bufferLen, domainBuffer, &domainBufferLen, &eUse)) {
542 if (eUse == SidTypeGroup || eUse == SidTypeWellKnownGroup) {
544 } else {
545 qCWarning(KCOREADDONS_DEBUG) << QString::fromWCharArray(buffer) << "is not a group, SID type is" << eUse;
546 }
547 }
548 return name;
549}
550
552 : d(new KUserGroupPrivate(nameFromGroupId(gid), gid))
553{
554}
555
556KUserGroup::KUserGroup(K_GID gid)
557{
558 KGroupId groupId(gid);
559 d = new KUserGroupPrivate(nameFromGroupId(groupId), groupId);
560}
561
563{
564 KGroupId gid;
565 if (mode == KUser::UseEffectiveUID) {
567 } else if (mode == KUser::UseRealUserID) {
569 }
570 d = new KUserGroupPrivate(nameFromGroupId(gid), gid);
571}
572
574 : d(group.d)
575{
576}
577
579{
580 d = group.d;
581 return *this;
582}
583
584bool KUserGroup::operator==(const KUserGroup &group) const
585{
586 return isValid() && d->gid == group.d->gid && d->name == group.d->name;
587}
588
589bool KUserGroup::isValid() const
590{
591 return d->gid.isValid() && !d->name.isEmpty();
592}
593
595{
596 return d->name;
597}
598
600{
601 return d->gid;
602}
603
605{
606}
607
608QList<KUser> KUser::allUsers(uint maxCount)
609{
610 QList<KUser> result;
611 // No advantage if we take a USER_INFO_11, since there is no way of copying it
612 // and it is not owned by this function!
613 // -> get a USER_INFO_0 instead and then use KUser(QString)
614 // USER_INFO_23 or USER_INFO_23 would be ideal here since they contains a SID,
615 // but that fails with error code 0x7c (bad level)
616 enumerateAllUsers<USER_INFO_0>(maxCount, [&result](const USER_INFO_0 &info) {
617 result.append(KUser(QString::fromWCharArray(info.usri0_name)));
618 });
619 return result;
620}
621
622QStringList KUser::allUserNames(uint maxCount)
623{
624 QStringList result;
625 enumerateAllUsers<USER_INFO_0>(maxCount, [&result](const USER_INFO_0 &info) {
626 result.append(QString::fromWCharArray(info.usri0_name));
627 });
628 return result;
629}
630
632{
633 QList<KUserGroup> result;
634 // MSDN documentation say 3 is a valid level, however the function fails with invalid level!!!
635 // User GROUP_INFO_0 instead...
636 enumerateAllGroups<GROUP_INFO_0>(maxCount, [&result](const GROUP_INFO_0 &groupInfo) {
637 result.append(KUserGroup(QString::fromWCharArray(groupInfo.grpi0_name)));
638 });
639 return result;
640}
641
643{
644 QStringList result;
645 enumerateAllGroups<GROUP_INFO_0>(maxCount, [&result](const GROUP_INFO_0 &groupInfo) {
646 result.append(QString::fromWCharArray(groupInfo.grpi0_name));
647 });
648 return result;
649}
650
651QList<KUserGroup> KUser::groups(uint maxCount) const
652{
653 QList<KUserGroup> result;
654 if (!isValid()) {
655 return result;
656 }
657 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) {
658 result.append(KUserGroup(QString::fromWCharArray(info.grui0_name)));
659 });
660 return result;
661}
662
663QStringList KUser::groupNames(uint maxCount) const
664{
665 QStringList result;
666 if (!isValid()) {
667 return result;
668 }
669 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) {
670 result.append(QString::fromWCharArray(info.grui0_name));
671 });
672 return result;
673}
674
675QList<KUser> KUserGroup::users(uint maxCount) const
676{
677 QList<KUser> result;
678 if (!isValid()) {
679 return result;
680 }
681 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) {
682 result.append(KUser(QString::fromWCharArray(info.grui0_name)));
683 });
684 return result;
685}
686
687QStringList KUserGroup::userNames(uint maxCount) const
688{
689 QStringList result;
690 if (!isValid()) {
691 return result;
692 }
693 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) {
694 result.append(QString::fromWCharArray(info.grui0_name));
695 });
696 return result;
697}
698
699static const auto invalidSidString = QStringLiteral("<invalid SID>");
700
701static QString sidToString(void *sid)
702{
703 if (!sid || !IsValidSid(sid)) {
704 return invalidSidString;
705 }
706 WCHAR *sidStr; // allocated by ConvertStringSidToSidW, must be freed using LocalFree()
707 if (!ConvertSidToStringSidW(sid, &sidStr)) {
708 return invalidSidString;
709 }
710 QString ret = QString::fromWCharArray(sidStr);
711 LocalFree(sidStr);
712 return ret;
713}
714
715struct WindowsSIDWrapper : public QSharedData {
716 char sidBuffer[SECURITY_MAX_SID_SIZE];
717 /** @return a copy of @p sid or null if sid is not valid or an error occurs */
718 static WindowsSIDWrapper *copySid(PSID sid)
719 {
720 if (!sid || !IsValidSid(sid)) {
721 return nullptr;
722 }
723 // create a copy of sid
724 WindowsSIDWrapper *copy = new WindowsSIDWrapper();
725 bool success = CopySid(SECURITY_MAX_SID_SIZE, copy->sidBuffer, sid);
726 if (!success) {
727 QString sidString = sidToString(sid);
728 qCWarning(KCOREADDONS_DEBUG, "Failed to copy SID %s, error = %d", qPrintable(sidString), (int)GetLastError());
729 delete copy;
730 return nullptr;
731 }
732 return copy;
733 }
734};
735
736template<>
738{
739}
740
741template<>
743{
744}
745
746template<>
748 : data(other.data)
749{
750}
751
752template<>
754{
755 data = other.data;
756 return *this;
757}
758
759template<>
761 : data(WindowsSIDWrapper::copySid(nativeId))
762{
763}
764
765template<>
767{
768 return data;
769}
770
771template<>
773{
774 if (!data) {
775 return nullptr;
776 }
777 return data->sidBuffer;
778}
779
780template<>
782{
783 if (data) {
784 if (!other.data) {
785 return false;
786 }
787 return EqualSid(data->sidBuffer, other.data->sidBuffer);
788 }
789 return !other.data; // only equal if other data is also invalid
790}
791
792template<>
794{
795 return !(*this == other);
796}
797
798template<>
800{
801 return sidToString(data ? data->sidBuffer : nullptr);
802}
803
804/** T must be either KUserId or KGroupId, Callback has signature T(PSID, SID_NAME_USE) */
805template<class T, class Callback>
806static T sidFromName(const QString &name, Callback callback)
807{
808 if (name.isEmpty()) {
809 // for some reason empty string will always return S-1-5-32 which is of type domain
810 // we only want users or groups -> return invalid
811 return T();
812 }
813 char buffer[SECURITY_MAX_SID_SIZE];
814 DWORD sidLength = SECURITY_MAX_SID_SIZE;
815 // ReferencedDomainName must be passed or LookupAccountNameW fails
816 // Documentation says it is optional, however if not passed the function fails and returns the required size
817 WCHAR domainBuffer[1024];
818 DWORD domainBufferSize = 1024;
819 SID_NAME_USE sidType;
820 bool ok = LookupAccountNameW(nullptr, (LPCWSTR)name.utf16(), buffer, &sidLength, domainBuffer, &domainBufferSize, &sidType);
821 if (!ok) {
822 qCWarning(KCOREADDONS_DEBUG) << "Failed to lookup account" << name << "error code =" << GetLastError();
823 return T();
824 }
825 return callback(buffer, sidType);
826}
827
829{
830 return sidFromName<KUserId>(name, [&](PSID sid, SID_NAME_USE sidType) -> KUserId {
831 if (sidType != SidTypeUser && sidType != SidTypeDeletedAccount) {
832 qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid)
833 << " is not a user."
834 " Got SID type "
835 << sidType << " instead.";
836 return KUserId();
837 }
838 return KUserId(sid);
839 });
840}
841
843{
844 return sidFromName<KGroupId>(name, [&](PSID sid, SID_NAME_USE sidType) -> KGroupId {
845 if (sidType != SidTypeGroup && sidType != SidTypeWellKnownGroup) {
846 qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid)
847 << " is not a group."
848 " Got SID type "
849 << sidType << " instead.";
850 return KGroupId();
851 }
852 return KGroupId(sid);
853 });
854}
855
856static std::unique_ptr<char[]> queryProcessInformation(TOKEN_INFORMATION_CLASS type)
857{
858 HANDLE _token;
859 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &_token)) {
860 qCWarning(KCOREADDONS_DEBUG, "Failed to get the token for the current process: %d", (int)GetLastError());
861 return nullptr;
862 }
863 ScopedHANDLE token(_token, handleCloser);
864 // query required size
865 DWORD requiredSize;
866 if (!GetTokenInformation(token.get(), type, nullptr, 0, &requiredSize)) {
867 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
868 qCWarning(KCOREADDONS_DEBUG, "Failed to get the required size for the token information %d: %d", type, (int)GetLastError());
869 return nullptr;
870 }
871 }
872 std::unique_ptr<char[]> buffer(new char[requiredSize]);
873 if (!GetTokenInformation(token.get(), type, buffer.get(), requiredSize, &requiredSize)) {
874 qCWarning(KCOREADDONS_DEBUG, "Failed to get token information %d from current process: %d", type, (int)GetLastError());
875 return nullptr;
876 }
877 return buffer;
878}
879
881{
882 std::unique_ptr<char[]> userTokenBuffer = queryProcessInformation(TokenUser);
883 TOKEN_USER *userToken = (TOKEN_USER *)userTokenBuffer.get();
884 return KUserId(userToken->User.Sid);
885}
886
888{
889 std::unique_ptr<char[]> primaryGroupBuffer = queryProcessInformation(TokenPrimaryGroup);
890 TOKEN_PRIMARY_GROUP *primaryGroup = (TOKEN_PRIMARY_GROUP *)primaryGroupBuffer.get();
891 return KGroupId(primaryGroup->PrimaryGroup);
892}
893
895{
896 return currentUserId();
897}
898
900{
901 return currentGroupId();
902}
903
904KCOREADDONS_EXPORT size_t qHash(const KUserId &id, size_t seed)
905{
906 if (!id.isValid()) {
907 return seed;
908 }
909 // we can't just hash the pointer since equal object must have the same hash -> hash contents
910 char *sid = (char *)id.nativeId();
911 return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed);
912}
913
914KCOREADDONS_EXPORT size_t qHash(const KGroupId &id, size_t seed)
915{
916 if (!id.isValid()) {
917 return seed;
918 }
919 // we can't just hash the pointer since equal object must have the same hash -> hash contents
920 char *sid = (char *)id.nativeId();
921 return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed);
922}
Represents a group on your system.
Definition kuser.h:424
QStringList userNames(uint maxCount=KCOREADDONS_UINT_MAX) const
static QList< KUserGroup > allGroups(uint maxCount=KCOREADDONS_UINT_MAX)
QString name() const
The name of the group.
KUserGroup(const QString &name)
Create an object from a group name.
bool isValid() const
Returns whether the group is valid.
KGroupId groupId() const
QList< KUser > users(uint maxCount=KCOREADDONS_UINT_MAX) const
bool operator==(const KUserGroup &group) const
Two KUserGroup objects are equal if their gid()s are identical.
static QStringList allGroupNames(uint maxCount=KCOREADDONS_UINT_MAX)
~KUserGroup()
Destructor.
KUserGroup & operator=(const KUserGroup &group)
Copies a group.
Represents a user on your system.
Definition kuser.h:226
QString faceIconPath() const
The path to the user's face file.
KUser(UIDMode mode=UseEffectiveUID)
Creates an object that contains information about the current user.
KGroupId groupId() const
QString homeDir() const
The path to the user's home directory.
QVariant property(UserProperty which) const
Returns an extended property.
QList< KUserGroup > groups(uint maxCount=KCOREADDONS_UINT_MAX) const
QString shell() const
The path to the user's login shell.
~KUser()
Destructor.
bool operator==(const KUser &user) const
Two KUser objects are equal if the userId() are identical.
QStringList groupNames(uint maxCount=KCOREADDONS_UINT_MAX) const
KUserId userId() const
QString loginName() const
The login name of the user.
bool isValid() const
Returns true if the user is valid.
static QList< KUser > allUsers(uint maxCount=KCOREADDONS_UINT_MAX)
bool isSuperUser() const
Checks whether the user is the super user (root).
KUser & operator=(const KUser &user)
Copies a user.
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
UIDMode
Definition kuser.h:228
@ UseEffectiveUID
Use the effective user id.
Definition kuser.h:229
@ UseRealUserID
Use the real user id.
Definition kuser.h:230
Q_SCRIPTABLE CaptureState status()
QString fullName(const PartType &type)
bool isValid(QStringView ifopt)
QStringView level(QStringView ifopt)
QString name(StandardAction id)
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
const QList< QKeySequence > & copy()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
QByteArray fromRawData(const char *data, qsizetype size)
QString homePath()
void append(QList< T > &&value)
QString fromWCharArray(const wchar_t *string, qsizetype size)
bool isEmpty() const const
const ushort * utf16() const const
typedef HANDLE
A platform independent group ID.
Definition kuser.h:168
static KGroupId fromName(const QString &name)
KGroupId()
Creates an invalid KGroupId.
Definition kuser.h:170
static KGroupId currentGroupId()
static KGroupId currentEffectiveGroupId()
A platform independent user ID.
Definition kuser.h:133
static KUserId currentUserId()
KUserId()
Creates an invalid KUserId.
Definition kuser.h:135
static KUserId fromName(const QString &name)
static KUserId currentEffectiveUserId()
A platform independent user or group ID.
Definition kuser.h:59
bool operator==(const KUserOrGroupId &other) const
Definition kuser.h:562
bool isValid() const
Definition kuser.h:557
NativeType nativeId() const
Definition kuser.h:572
QString toString() const
Definition kuser.h:577
bool operator!=(const KUserOrGroupId &other) const
Definition kuser.h:567
KUserOrGroupId()
Creates an invalid KUserOrGroupId.
Definition kuser.h:582
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:22 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.