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

KDE's Doxygen guidelines are available online.