File indexing completed on 2024-04-28 03:53:51
0001 /* 0002 KUser - represent a user/account (Windows) 0003 0004 SPDX-FileCopyrightText: 2007 Bernhard Loos <nhuh.put@web.de> 0005 SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kuser.h" 0011 0012 #include "kcoreaddons_debug.h" 0013 #include <QDir> 0014 #include <QStandardPaths> 0015 0016 #include <memory> // unique_ptr 0017 #include <type_traits> 0018 0019 #include <qt_windows.h> // Must be included before lm.h 0020 0021 #include <lm.h> //Net* 0022 0023 #include <sddl.h> //ConvertSidToStringSidW 0024 #include <shlobj.h> 0025 #include <userenv.h> //GetProfilesDirectoryW 0026 0027 // this can't be a lambda due to a MSVC2012 bug 0028 // (works fine in 2010 and 2013) 0029 struct netApiBufferDeleter { 0030 void operator()(void *buffer) 0031 { 0032 if (buffer) { 0033 NetApiBufferFree(buffer); 0034 } 0035 } 0036 }; 0037 0038 template<typename T> 0039 class ScopedNetApiBuffer : public std::unique_ptr<T, netApiBufferDeleter> 0040 { 0041 public: 0042 // explicit scope resolution operator needed in ::netApiBufferDeleter 0043 // because of *another* MSVC bug :( 0044 inline explicit ScopedNetApiBuffer(T *data) 0045 : std::unique_ptr<T, ::netApiBufferDeleter>(data, ::netApiBufferDeleter()) 0046 { 0047 } 0048 }; 0049 0050 const auto handleCloser = [](HANDLE h) { 0051 if (h != INVALID_HANDLE_VALUE) { 0052 CloseHandle(h); 0053 } 0054 }; 0055 typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(handleCloser)> ScopedHANDLE; 0056 0057 /** Make sure the NetApi functions are called with the correct level argument (for template functions) 0058 * This argument can be retrieved by using NetApiTypeInfo<T>::level. In order to do so the type must be 0059 * registered by writing e.g. NETAPI_TYPE_INFO(GROUP_INFO, 0) for GROUP_INFO_0 0060 */ 0061 template<typename T> 0062 struct NetApiTypeInfo { 0063 }; 0064 #define NETAPI_TYPE_INFO(prefix, n) \ 0065 template<> \ 0066 struct NetApiTypeInfo<prefix##_##n> { \ 0067 enum { level = n }; \ 0068 }; 0069 NETAPI_TYPE_INFO(GROUP_INFO, 0) 0070 NETAPI_TYPE_INFO(GROUP_INFO, 3) 0071 NETAPI_TYPE_INFO(USER_INFO, 0) 0072 NETAPI_TYPE_INFO(USER_INFO, 4) 0073 NETAPI_TYPE_INFO(USER_INFO, 11) 0074 NETAPI_TYPE_INFO(GROUP_USERS_INFO, 0) 0075 0076 // T must be a USER_INFO_* structure 0077 template<typename T> 0078 ScopedNetApiBuffer<T> getUserInfo(LPCWSTR server, const QString &userName, NET_API_STATUS *errCode) 0079 { 0080 LPBYTE userInfoTmp = nullptr; 0081 // if level = 11 a USER_INFO_11 structure gets filled in and allocated by NetUserGetInfo(), etc. 0082 NET_API_STATUS status = NetUserGetInfo(server, (LPCWSTR)userName.utf16(), NetApiTypeInfo<T>::level, &userInfoTmp); 0083 if (status != NERR_Success) { 0084 userInfoTmp = nullptr; 0085 } 0086 if (errCode) { 0087 *errCode = status; 0088 } 0089 return ScopedNetApiBuffer<T>((T *)userInfoTmp); 0090 } 0091 0092 // enumeration functions 0093 /** simplify calling the Net*Enum functions to prevent copy and paste for allUsers(), allUserNames(), allGroups(), allGroupNames() 0094 * @tparam T The type that is enumerated (e.g. USER_INFO_11) Must be registered using NETAPI_TYPE_INFO. 0095 * @param callback Callback for each listed object. Signature: void(const T&) 0096 * @param enumFunc This function enumerates the data using a Net* function. 0097 * It will be called in a loop as long as it returns ERROR_MORE_DATA. 0098 * 0099 */ 0100 template<class T, class Callback, class EnumFunction> 0101 static void netApiEnumerate(uint maxCount, Callback callback, EnumFunction enumFunc) 0102 { 0103 NET_API_STATUS nStatus = NERR_Success; 0104 DWORD_PTR resumeHandle = 0; 0105 uint total = 0; 0106 int level = NetApiTypeInfo<T>::level; 0107 do { 0108 LPBYTE buffer = nullptr; 0109 DWORD entriesRead = 0; 0110 DWORD totalEntries = 0; 0111 nStatus = enumFunc(level, &buffer, &entriesRead, &totalEntries, &resumeHandle); 0112 // qDebug("Net*Enum(level = %d) returned %d entries, total was (%d), status = %d, resume handle = %llx", 0113 // level, entriesRead, totalEntries, nStatus, resumeHandle); 0114 0115 // buffer must always be freed, even if Net*Enum fails 0116 ScopedNetApiBuffer<T> groupInfo((T *)buffer); 0117 if (nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) { 0118 for (DWORD i = 0; total < maxCount && i < entriesRead; i++, total++) { 0119 callback(groupInfo.get()[i]); 0120 } 0121 } else { 0122 qCWarning(KCOREADDONS_DEBUG, "NetApi enumerate function failed: status = %d", (int)nStatus); 0123 } 0124 } while (nStatus == ERROR_MORE_DATA); 0125 } 0126 0127 template<class T, class Callback> 0128 void enumerateAllUsers(uint maxCount, Callback callback) 0129 { 0130 netApiEnumerate<T>(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { 0131 // pass 0 as filter -> get all users 0132 // Why does this function take a DWORD* as resume handle and NetUserEnum/NetGroupGetUsers a UINT64* 0133 // Great API design by Microsoft... 0134 // casting the uint64* to uint32* is fine, it just writes to the first 32 bits 0135 return NetUserEnum(nullptr, level, 0, buffer, MAX_PREFERRED_LENGTH, count, total, (PDWORD)resumeHandle); 0136 }); 0137 } 0138 0139 template<typename T, class Callback> 0140 void enumerateAllGroups(uint maxCount, Callback callback) 0141 { 0142 netApiEnumerate<T>(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { 0143 return NetGroupEnum(nullptr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); 0144 }); 0145 } 0146 0147 template<typename T, class Callback> 0148 void enumerateGroupsForUser(uint maxCount, const QString &name, Callback callback) 0149 { 0150 LPCWSTR nameStr = (LPCWSTR)name.utf16(); 0151 netApiEnumerate<T>(maxCount, callback, [&](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) -> NET_API_STATUS { 0152 Q_UNUSED(resumeHandle); 0153 NET_API_STATUS ret = NetUserGetGroups(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total); 0154 // if we return ERROR_MORE_DATA here it will result in an endless loop 0155 if (ret == ERROR_MORE_DATA) { 0156 qCWarning(KCOREADDONS_DEBUG) << "NetUserGetGroups for user" << name << "returned ERROR_MORE_DATA. This should not happen!"; 0157 ret = NERR_Success; 0158 } 0159 return ret; 0160 }); 0161 } 0162 0163 template<typename T, class Callback> 0164 void enumerateUsersForGroup(const QString &name, uint maxCount, Callback callback) 0165 { 0166 LPCWSTR nameStr = (LPCWSTR)name.utf16(); 0167 netApiEnumerate<T>(maxCount, callback, [nameStr](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { 0168 return NetGroupGetUsers(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); 0169 }); 0170 } 0171 0172 class KUserPrivate : public QSharedData 0173 { 0174 typedef QExplicitlySharedDataPointer<KUserPrivate> Ptr; 0175 KUserPrivate() 0176 : isAdmin(false) 0177 { 0178 } 0179 // takes ownership over userInfo_ 0180 KUserPrivate(KUserId uid, KGroupId gid, const QString &loginName, const QString &fullName, const QString &domain, const QString &homeDir, bool isAdmin) 0181 : uid(uid) 0182 , gid(gid) 0183 , loginName(loginName) 0184 , fullName(fullName) 0185 , domain(domain) 0186 , homeDir(homeDir) 0187 , isAdmin(isAdmin) 0188 { 0189 Q_ASSERT(uid.isValid()); 0190 } 0191 static QString guessHomeDir(const QString &username, KUserId uid) 0192 { 0193 // usri11_home_dir/usri4_home_dir is often empty 0194 // check whether it is the homedir for the current user and if not then fall back to "<user profiles dir>\<user name>" 0195 if (uid == KUserId::currentUserId()) { 0196 return QDir::homePath(); 0197 } 0198 QString homeDir; 0199 WCHAR profileDirPath[MAX_PATH]; 0200 DWORD bufSize = MAX_PATH; 0201 BOOL result = GetProfilesDirectoryW(profileDirPath, &bufSize); 0202 if (result) { 0203 // This might not be correct: e.g. with local user and domain user with same 0204 // In that case it could be C:\Users\Foo (local user) vs C:\Users\Foo.DOMAIN (domain user) 0205 // However it is still much better than the previous code which just returned the current users home dir 0206 homeDir = QString::fromWCharArray(profileDirPath) + QLatin1Char('\\') + username; 0207 } 0208 return homeDir; 0209 } 0210 0211 public: 0212 static Ptr sharedNull; 0213 KUserId uid; 0214 KGroupId gid; 0215 QString loginName; 0216 QString fullName; 0217 QString domain; 0218 QString homeDir; 0219 bool isAdmin; 0220 0221 /** Creates a user info from a SID (never returns null) */ 0222 static Ptr create(KUserId uid) 0223 { 0224 if (!uid.isValid()) { 0225 return sharedNull; 0226 } 0227 // now find the fully qualified name for the user 0228 DWORD nameBufferLen = UNLEN + 1; 0229 WCHAR nameBuffer[UNLEN + 1]; 0230 DWORD domainBufferLen = UNLEN + 1; 0231 WCHAR domainBuffer[UNLEN + 1]; 0232 SID_NAME_USE use; 0233 if (!LookupAccountSidW(nullptr, uid.nativeId(), nameBuffer, &nameBufferLen, domainBuffer, &domainBufferLen, &use)) { 0234 qCWarning(KCOREADDONS_DEBUG) << "Could not lookup user " << uid.toString() << "error =" << GetLastError(); 0235 return sharedNull; 0236 } 0237 QString loginName = QString::fromWCharArray(nameBuffer); 0238 QString domainName = QString::fromWCharArray(domainBuffer); 0239 if (use != SidTypeUser && use != SidTypeDeletedAccount) { 0240 qCWarning(KCOREADDONS_DEBUG).nospace() << "SID for " << domainName << "\\" << loginName << " (" << uid.toString() << ") is not of type user (" 0241 << SidTypeUser << " or " << SidTypeDeletedAccount << "). Got type " << use << " instead."; 0242 return sharedNull; 0243 } 0244 // now get the server name to query (could be null for local machine) 0245 LPWSTR servernameTmp = nullptr; 0246 NET_API_STATUS status = NetGetAnyDCName(nullptr, 0, (LPBYTE *)&servernameTmp); 0247 if (status != NERR_Success) { 0248 // this always fails on my desktop system, don't spam the output 0249 // qDebug("NetGetAnyDCName failed with error %d", status); 0250 } 0251 ScopedNetApiBuffer<WCHAR> servername(servernameTmp); 0252 0253 QString fullName; 0254 QString homeDir; 0255 KGroupId group; 0256 bool isAdmin = false; 0257 // must NOT pass the qualified name ("domain\user") here or lookup fails -> just the name 0258 // try USER_INFO_4 first, MSDN says it is valid only on servers (whatever that means), it works on my desktop system 0259 // If it fails fall back to USER_INFO11, which has all the needed information except primary group 0260 if (auto userInfo4 = getUserInfo<USER_INFO_4>(servername.get(), loginName, &status)) { 0261 Q_ASSERT(KUserId(userInfo4->usri4_user_sid) == uid); // if this is not the same we have a logic error 0262 fullName = QString::fromWCharArray(userInfo4->usri4_full_name); 0263 homeDir = QString::fromWCharArray(userInfo4->usri4_home_dir); 0264 isAdmin = userInfo4->usri4_priv == USER_PRIV_ADMIN; 0265 // now determine the primary group: 0266 const DWORD primaryGroup = userInfo4->usri4_primary_group_id; 0267 // primary group is a relative identifier, i.e. in order to get the SID for that group 0268 // we have to take the user SID and replace the last subauthority value with the relative identifier 0269 group = KGroupId(uid.nativeId()); // constructor does not check whether the sid refers to a group 0270 Q_ASSERT(group.isValid()); 0271 UCHAR numSubauthorities = *GetSidSubAuthorityCount(group.nativeId()); 0272 PDWORD lastSubAutority = GetSidSubAuthority(group.nativeId(), numSubauthorities - 1); 0273 *lastSubAutority = primaryGroup; 0274 } else if (auto userInfo11 = getUserInfo<USER_INFO_11>(servername.get(), loginName, &status)) { 0275 fullName = QString::fromWCharArray(userInfo11->usri11_full_name); 0276 homeDir = QString::fromWCharArray(userInfo11->usri11_home_dir); 0277 isAdmin = userInfo11->usri11_priv == USER_PRIV_ADMIN; 0278 } else { 0279 qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not get information for user " << domainName << "\\" << loginName << ": error code = " << status; 0280 return sharedNull; 0281 } 0282 if (homeDir.isEmpty()) { 0283 homeDir = guessHomeDir(loginName, uid); 0284 } 0285 // if we couldn't find a primary group just take the first group found for this user 0286 if (!group.isValid()) { 0287 enumerateGroupsForUser<GROUP_USERS_INFO_0>(1, loginName, [&](const GROUP_USERS_INFO_0 &info) { 0288 group = KGroupId::fromName(QString::fromWCharArray(info.grui0_name)); 0289 }); 0290 } 0291 return Ptr(new KUserPrivate(uid, group, loginName, fullName, domainName, homeDir, isAdmin)); 0292 } 0293 }; 0294 0295 KUserPrivate::Ptr KUserPrivate::sharedNull(new KUserPrivate()); 0296 0297 KUser::KUser(UIDMode mode) 0298 { 0299 if (mode == UseEffectiveUID) { 0300 d = KUserPrivate::create(KUserId::currentEffectiveUserId()); 0301 } else if (mode == UseRealUserID) { 0302 d = KUserPrivate::create(KUserId::currentUserId()); 0303 } else { 0304 d = KUserPrivate::sharedNull; 0305 } 0306 } 0307 0308 KUser::KUser(K_UID uid) 0309 : d(KUserPrivate::create(KUserId(uid))) 0310 { 0311 } 0312 0313 KUser::KUser(KUserId uid) 0314 : d(KUserPrivate::create(uid)) 0315 { 0316 } 0317 0318 KUser::KUser(const QString &name) 0319 : d(KUserPrivate::create(KUserId::fromName(name))) 0320 { 0321 } 0322 0323 KUser::KUser(const char *name) 0324 : d(KUserPrivate::create(KUserId::fromName(QString::fromLocal8Bit(name)))) 0325 { 0326 } 0327 0328 KUser::KUser(const KUser &user) 0329 : d(user.d) 0330 { 0331 } 0332 0333 KUser &KUser::operator=(const KUser &user) 0334 { 0335 d = user.d; 0336 return *this; 0337 } 0338 0339 bool KUser::operator==(const KUser &user) const 0340 { 0341 return isValid() && d->uid == user.d->uid; 0342 } 0343 0344 bool KUser::isValid() const 0345 { 0346 return d->uid.isValid(); 0347 } 0348 0349 bool KUser::isSuperUser() const 0350 { 0351 return d->isAdmin; 0352 } 0353 0354 QString KUser::loginName() const 0355 { 0356 return d->loginName; 0357 } 0358 0359 QString KUser::homeDir() const 0360 { 0361 return d->homeDir; 0362 } 0363 0364 // Some RAII objects to help uninitializing/destroying WinAPI stuff 0365 // used in faceIconPath. 0366 class COMInitializer 0367 { 0368 public: 0369 COMInitializer() 0370 : result(CoInitialize(nullptr)) 0371 { 0372 } 0373 ~COMInitializer() 0374 { 0375 if (SUCCEEDED(result)) { 0376 CoUninitialize(); 0377 } 0378 } 0379 HRESULT result; 0380 }; 0381 class W32Library 0382 { 0383 public: 0384 W32Library(HMODULE h) 0385 : h(h) 0386 { 0387 } 0388 ~W32Library() 0389 { 0390 if (h) { 0391 FreeLibrary(h); 0392 } 0393 } 0394 operator HMODULE() 0395 { 0396 return h; 0397 } 0398 HMODULE h; 0399 }; 0400 0401 // faceIconPath uses undocumented Windows API known as SHGetUserPicturePath, 0402 // only accessible by ordinal, unofficially documented at 0403 // http://undoc.airesoft.co.uk/shell32.dll/SHGetUserPicturePath.php 0404 0405 // The function has a different ordinal and parameters on Windows XP and Vista/7. 0406 // These structs encapsulate the differences. 0407 0408 struct FaceIconPath_XP { 0409 typedef HRESULT(WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR); 0410 static const int ordinal = 233; 0411 static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathXP, LPCWSTR username, LPWSTR buf, UINT bufsize) 0412 { 0413 Q_UNUSED(bufsize); 0414 // assumes the buffer is MAX_PATH in size 0415 return SHGetUserPicturePathXP(username, 0, buf); 0416 } 0417 }; 0418 struct FaceIconPath_Vista { 0419 typedef HRESULT(WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR, UINT); 0420 static const int ordinal = 261; 0421 static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathV, LPCWSTR username, LPWSTR buf, UINT bufsize) 0422 { 0423 return SHGetUserPicturePathV(username, 0, buf, bufsize); 0424 } 0425 }; 0426 0427 template<typename Platform> 0428 static QString faceIconPathImpl(LPCWSTR username) 0429 { 0430 static COMInitializer COMinit; 0431 0432 static W32Library shellMod = LoadLibraryA("shell32.dll"); 0433 if (!shellMod) { 0434 return QString(); 0435 } 0436 static typename Platform::funcptr_t sgupp_ptr = 0437 reinterpret_cast<typename Platform::funcptr_t>(GetProcAddress(shellMod, MAKEINTRESOURCEA(Platform::ordinal))); 0438 if (!sgupp_ptr) { 0439 return QString(); 0440 } 0441 0442 WCHAR pathBuf[MAX_PATH]; 0443 0444 HRESULT res = Platform::getPicturePath(sgupp_ptr, username, pathBuf, MAX_PATH); 0445 if (res != S_OK) { 0446 return QString(); 0447 } 0448 return QString::fromWCharArray(pathBuf); 0449 } 0450 0451 QString KUser::faceIconPath() const 0452 { 0453 if (!isValid()) { 0454 return QString(); 0455 } 0456 0457 LPCWSTR username = reinterpret_cast<const WCHAR *>(d->loginName.utf16()); 0458 return faceIconPathImpl<FaceIconPath_Vista>(username); 0459 } 0460 0461 QString KUser::shell() const 0462 { 0463 return isValid() ? QStringLiteral("cmd.exe") : QString(); 0464 } 0465 0466 KUserId KUser::userId() const 0467 { 0468 return d->uid; 0469 } 0470 0471 KGroupId KUser::groupId() const 0472 { 0473 return d->gid; 0474 } 0475 0476 QVariant KUser::property(UserProperty which) const 0477 { 0478 if (which == FullName) { 0479 return QVariant(d->fullName); 0480 } 0481 0482 return QVariant(); 0483 } 0484 0485 KUser::~KUser() 0486 { 0487 } 0488 0489 class KUserGroupPrivate : public QSharedData 0490 { 0491 public: 0492 QString name; 0493 KGroupId gid; 0494 KUserGroupPrivate() 0495 { 0496 } 0497 KUserGroupPrivate(const QString &name, KGroupId id) 0498 : name(name) 0499 , gid(id) 0500 { 0501 if (!name.isEmpty()) { 0502 PBYTE groupInfoTmp = nullptr; 0503 NET_API_STATUS status = NetGroupGetInfo(nullptr, (LPCWSTR)name.utf16(), 0, &groupInfoTmp); 0504 // must always be freed, even on error 0505 ScopedNetApiBuffer<GROUP_INFO_0> groupInfo((GROUP_INFO_0 *)groupInfoTmp); 0506 if (status != NERR_Success) { 0507 qCWarning(KCOREADDONS_DEBUG) << "Failed to find group with name" << name << "error =" << status; 0508 groupInfo.reset(); 0509 } 0510 if (!id.isValid()) { 0511 gid = KGroupId::fromName(name); 0512 } 0513 } 0514 } 0515 }; 0516 0517 KUserGroup::KUserGroup(const QString &_name) 0518 : d(new KUserGroupPrivate(_name, KGroupId())) 0519 { 0520 } 0521 0522 KUserGroup::KUserGroup(const char *_name) 0523 : d(new KUserGroupPrivate(QLatin1String(_name), KGroupId())) 0524 { 0525 } 0526 0527 static QString nameFromGroupId(KGroupId gid) 0528 { 0529 if (!gid.isValid()) { 0530 return QString(); 0531 } 0532 0533 DWORD bufferLen = UNLEN + 1; 0534 WCHAR buffer[UNLEN + 1]; 0535 DWORD domainBufferLen = UNLEN + 1; 0536 WCHAR domainBuffer[UNLEN + 1]; 0537 SID_NAME_USE eUse; 0538 QString name; 0539 if (LookupAccountSidW(NULL, gid.nativeId(), buffer, &bufferLen, domainBuffer, &domainBufferLen, &eUse)) { 0540 if (eUse == SidTypeGroup || eUse == SidTypeWellKnownGroup) { 0541 name = QString::fromWCharArray(buffer); 0542 } else { 0543 qCWarning(KCOREADDONS_DEBUG) << QString::fromWCharArray(buffer) << "is not a group, SID type is" << eUse; 0544 } 0545 } 0546 return name; 0547 } 0548 0549 KUserGroup::KUserGroup(KGroupId gid) 0550 : d(new KUserGroupPrivate(nameFromGroupId(gid), gid)) 0551 { 0552 } 0553 0554 KUserGroup::KUserGroup(K_GID gid) 0555 { 0556 KGroupId groupId(gid); 0557 d = new KUserGroupPrivate(nameFromGroupId(groupId), groupId); 0558 } 0559 0560 KUserGroup::KUserGroup(KUser::UIDMode mode) 0561 { 0562 KGroupId gid; 0563 if (mode == KUser::UseEffectiveUID) { 0564 gid = KGroupId::currentGroupId(); 0565 } else if (mode == KUser::UseRealUserID) { 0566 gid = KGroupId::currentEffectiveGroupId(); 0567 } 0568 d = new KUserGroupPrivate(nameFromGroupId(gid), gid); 0569 } 0570 0571 KUserGroup::KUserGroup(const KUserGroup &group) 0572 : d(group.d) 0573 { 0574 } 0575 0576 KUserGroup &KUserGroup::operator=(const KUserGroup &group) 0577 { 0578 d = group.d; 0579 return *this; 0580 } 0581 0582 bool KUserGroup::operator==(const KUserGroup &group) const 0583 { 0584 return isValid() && d->gid == group.d->gid && d->name == group.d->name; 0585 } 0586 0587 bool KUserGroup::isValid() const 0588 { 0589 return d->gid.isValid() && !d->name.isEmpty(); 0590 } 0591 0592 QString KUserGroup::name() const 0593 { 0594 return d->name; 0595 } 0596 0597 KGroupId KUserGroup::groupId() const 0598 { 0599 return d->gid; 0600 } 0601 0602 KUserGroup::~KUserGroup() 0603 { 0604 } 0605 0606 QList<KUser> KUser::allUsers(uint maxCount) 0607 { 0608 QList<KUser> result; 0609 // No advantage if we take a USER_INFO_11, since there is no way of copying it 0610 // and it is not owned by this function! 0611 // -> get a USER_INFO_0 instead and then use KUser(QString) 0612 // USER_INFO_23 or USER_INFO_23 would be ideal here since they contains a SID, 0613 // but that fails with error code 0x7c (bad level) 0614 enumerateAllUsers<USER_INFO_0>(maxCount, [&result](const USER_INFO_0 &info) { 0615 result.append(KUser(QString::fromWCharArray(info.usri0_name))); 0616 }); 0617 return result; 0618 } 0619 0620 QStringList KUser::allUserNames(uint maxCount) 0621 { 0622 QStringList result; 0623 enumerateAllUsers<USER_INFO_0>(maxCount, [&result](const USER_INFO_0 &info) { 0624 result.append(QString::fromWCharArray(info.usri0_name)); 0625 }); 0626 return result; 0627 } 0628 0629 QList<KUserGroup> KUserGroup::allGroups(uint maxCount) 0630 { 0631 QList<KUserGroup> result; 0632 // MSDN documentation say 3 is a valid level, however the function fails with invalid level!!! 0633 // User GROUP_INFO_0 instead... 0634 enumerateAllGroups<GROUP_INFO_0>(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { 0635 result.append(KUserGroup(QString::fromWCharArray(groupInfo.grpi0_name))); 0636 }); 0637 return result; 0638 } 0639 0640 QStringList KUserGroup::allGroupNames(uint maxCount) 0641 { 0642 QStringList result; 0643 enumerateAllGroups<GROUP_INFO_0>(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { 0644 result.append(QString::fromWCharArray(groupInfo.grpi0_name)); 0645 }); 0646 return result; 0647 } 0648 0649 QList<KUserGroup> KUser::groups(uint maxCount) const 0650 { 0651 QList<KUserGroup> result; 0652 if (!isValid()) { 0653 return result; 0654 } 0655 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { 0656 result.append(KUserGroup(QString::fromWCharArray(info.grui0_name))); 0657 }); 0658 return result; 0659 } 0660 0661 QStringList KUser::groupNames(uint maxCount) const 0662 { 0663 QStringList result; 0664 if (!isValid()) { 0665 return result; 0666 } 0667 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { 0668 result.append(QString::fromWCharArray(info.grui0_name)); 0669 }); 0670 return result; 0671 } 0672 0673 QList<KUser> KUserGroup::users(uint maxCount) const 0674 { 0675 QList<KUser> result; 0676 if (!isValid()) { 0677 return result; 0678 } 0679 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { 0680 result.append(KUser(QString::fromWCharArray(info.grui0_name))); 0681 }); 0682 return result; 0683 } 0684 0685 QStringList KUserGroup::userNames(uint maxCount) const 0686 { 0687 QStringList result; 0688 if (!isValid()) { 0689 return result; 0690 } 0691 enumerateGroupsForUser<GROUP_USERS_INFO_0>(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { 0692 result.append(QString::fromWCharArray(info.grui0_name)); 0693 }); 0694 return result; 0695 } 0696 0697 static const auto invalidSidString = QStringLiteral("<invalid SID>"); 0698 0699 static QString sidToString(void *sid) 0700 { 0701 if (!sid || !IsValidSid(sid)) { 0702 return invalidSidString; 0703 } 0704 WCHAR *sidStr; // allocated by ConvertStringSidToSidW, must be freed using LocalFree() 0705 if (!ConvertSidToStringSidW(sid, &sidStr)) { 0706 return invalidSidString; 0707 } 0708 QString ret = QString::fromWCharArray(sidStr); 0709 LocalFree(sidStr); 0710 return ret; 0711 } 0712 0713 struct WindowsSIDWrapper : public QSharedData { 0714 char sidBuffer[SECURITY_MAX_SID_SIZE]; 0715 /** @return a copy of @p sid or null if sid is not valid or an error occurs */ 0716 static WindowsSIDWrapper *copySid(PSID sid) 0717 { 0718 if (!sid || !IsValidSid(sid)) { 0719 return nullptr; 0720 } 0721 // create a copy of sid 0722 WindowsSIDWrapper *copy = new WindowsSIDWrapper(); 0723 bool success = CopySid(SECURITY_MAX_SID_SIZE, copy->sidBuffer, sid); 0724 if (!success) { 0725 QString sidString = sidToString(sid); 0726 qCWarning(KCOREADDONS_DEBUG, "Failed to copy SID %s, error = %d", qPrintable(sidString), (int)GetLastError()); 0727 delete copy; 0728 return nullptr; 0729 } 0730 return copy; 0731 } 0732 }; 0733 0734 template<> 0735 KUserOrGroupId<void *>::KUserOrGroupId() 0736 { 0737 } 0738 0739 template<> 0740 KUserOrGroupId<void *>::~KUserOrGroupId() 0741 { 0742 } 0743 0744 template<> 0745 KUserOrGroupId<void *>::KUserOrGroupId(const KUserOrGroupId<void *> &other) 0746 : data(other.data) 0747 { 0748 } 0749 0750 template<> 0751 inline KUserOrGroupId<void *> &KUserOrGroupId<void *>::operator=(const KUserOrGroupId<void *> &other) 0752 { 0753 data = other.data; 0754 return *this; 0755 } 0756 0757 template<> 0758 KUserOrGroupId<void *>::KUserOrGroupId(void *nativeId) 0759 : data(WindowsSIDWrapper::copySid(nativeId)) 0760 { 0761 } 0762 0763 template<> 0764 bool KUserOrGroupId<void *>::isValid() const 0765 { 0766 return data; 0767 } 0768 0769 template<> 0770 void *KUserOrGroupId<void *>::nativeId() const 0771 { 0772 if (!data) { 0773 return nullptr; 0774 } 0775 return data->sidBuffer; 0776 } 0777 0778 template<> 0779 bool KUserOrGroupId<void *>::operator==(const KUserOrGroupId<void *> &other) const 0780 { 0781 if (data) { 0782 if (!other.data) { 0783 return false; 0784 } 0785 return EqualSid(data->sidBuffer, other.data->sidBuffer); 0786 } 0787 return !other.data; // only equal if other data is also invalid 0788 } 0789 0790 template<> 0791 bool KUserOrGroupId<void *>::operator!=(const KUserOrGroupId<void *> &other) const 0792 { 0793 return !(*this == other); 0794 } 0795 0796 template<> 0797 QString KUserOrGroupId<void *>::toString() const 0798 { 0799 return sidToString(data ? data->sidBuffer : nullptr); 0800 } 0801 0802 /** T must be either KUserId or KGroupId, Callback has signature T(PSID, SID_NAME_USE) */ 0803 template<class T, class Callback> 0804 static T sidFromName(const QString &name, Callback callback) 0805 { 0806 if (name.isEmpty()) { 0807 // for some reason empty string will always return S-1-5-32 which is of type domain 0808 // we only want users or groups -> return invalid 0809 return T(); 0810 } 0811 char buffer[SECURITY_MAX_SID_SIZE]; 0812 DWORD sidLength = SECURITY_MAX_SID_SIZE; 0813 // ReferencedDomainName must be passed or LookupAccountNameW fails 0814 // Documentation says it is optional, however if not passed the function fails and returns the required size 0815 WCHAR domainBuffer[1024]; 0816 DWORD domainBufferSize = 1024; 0817 SID_NAME_USE sidType; 0818 bool ok = LookupAccountNameW(nullptr, (LPCWSTR)name.utf16(), buffer, &sidLength, domainBuffer, &domainBufferSize, &sidType); 0819 if (!ok) { 0820 qCWarning(KCOREADDONS_DEBUG) << "Failed to lookup account" << name << "error code =" << GetLastError(); 0821 return T(); 0822 } 0823 return callback(buffer, sidType); 0824 } 0825 0826 KUserId KUserId::fromName(const QString &name) 0827 { 0828 return sidFromName<KUserId>(name, [&](PSID sid, SID_NAME_USE sidType) -> KUserId { 0829 if (sidType != SidTypeUser && sidType != SidTypeDeletedAccount) { 0830 qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) 0831 << " is not a user." 0832 " Got SID type " 0833 << sidType << " instead."; 0834 return KUserId(); 0835 } 0836 return KUserId(sid); 0837 }); 0838 } 0839 0840 KGroupId KGroupId::fromName(const QString &name) 0841 { 0842 return sidFromName<KGroupId>(name, [&](PSID sid, SID_NAME_USE sidType) -> KGroupId { 0843 if (sidType != SidTypeGroup && sidType != SidTypeWellKnownGroup) { 0844 qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) 0845 << " is not a group." 0846 " Got SID type " 0847 << sidType << " instead."; 0848 return KGroupId(); 0849 } 0850 return KGroupId(sid); 0851 }); 0852 } 0853 0854 static std::unique_ptr<char[]> queryProcessInformation(TOKEN_INFORMATION_CLASS type) 0855 { 0856 HANDLE _token; 0857 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &_token)) { 0858 qCWarning(KCOREADDONS_DEBUG, "Failed to get the token for the current process: %d", (int)GetLastError()); 0859 return nullptr; 0860 } 0861 ScopedHANDLE token(_token, handleCloser); 0862 // query required size 0863 DWORD requiredSize; 0864 if (!GetTokenInformation(token.get(), type, nullptr, 0, &requiredSize)) { 0865 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 0866 qCWarning(KCOREADDONS_DEBUG, "Failed to get the required size for the token information %d: %d", type, (int)GetLastError()); 0867 return nullptr; 0868 } 0869 } 0870 std::unique_ptr<char[]> buffer(new char[requiredSize]); 0871 if (!GetTokenInformation(token.get(), type, buffer.get(), requiredSize, &requiredSize)) { 0872 qCWarning(KCOREADDONS_DEBUG, "Failed to get token information %d from current process: %d", type, (int)GetLastError()); 0873 return nullptr; 0874 } 0875 return buffer; 0876 } 0877 0878 KUserId KUserId::currentUserId() 0879 { 0880 std::unique_ptr<char[]> userTokenBuffer = queryProcessInformation(TokenUser); 0881 TOKEN_USER *userToken = (TOKEN_USER *)userTokenBuffer.get(); 0882 return KUserId(userToken->User.Sid); 0883 } 0884 0885 KGroupId KGroupId::currentGroupId() 0886 { 0887 std::unique_ptr<char[]> primaryGroupBuffer = queryProcessInformation(TokenPrimaryGroup); 0888 TOKEN_PRIMARY_GROUP *primaryGroup = (TOKEN_PRIMARY_GROUP *)primaryGroupBuffer.get(); 0889 return KGroupId(primaryGroup->PrimaryGroup); 0890 } 0891 0892 KUserId KUserId::currentEffectiveUserId() 0893 { 0894 return currentUserId(); 0895 } 0896 0897 KGroupId KGroupId::currentEffectiveGroupId() 0898 { 0899 return currentGroupId(); 0900 } 0901 0902 KCOREADDONS_EXPORT size_t qHash(const KUserId &id, size_t seed) 0903 { 0904 if (!id.isValid()) { 0905 return seed; 0906 } 0907 // we can't just hash the pointer since equal object must have the same hash -> hash contents 0908 char *sid = (char *)id.nativeId(); 0909 return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); 0910 } 0911 0912 KCOREADDONS_EXPORT size_t qHash(const KGroupId &id, size_t seed) 0913 { 0914 if (!id.isValid()) { 0915 return seed; 0916 } 0917 // we can't just hash the pointer since equal object must have the same hash -> hash contents 0918 char *sid = (char *)id.nativeId(); 0919 return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); 0920 }