KCoreAddons

kuser_win.cpp
1 /*
2  KUser - represent a user/account (Windows)
3 
4  SPDX-FileCopyrightText: 2007 Bernhard Loos <[email protected]>
5  SPDX-FileCopyrightText: 2014 Alex Richardson <[email protected]>
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)
29 struct netApiBufferDeleter {
30  void operator()(void *buffer)
31  {
32  if (buffer) {
33  NetApiBufferFree(buffer);
34  }
35  }
36 };
37 
38 template<typename T>
39 class ScopedNetApiBuffer : public std::unique_ptr<T, netApiBufferDeleter>
40 {
41 public:
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 
50 const auto handleCloser = [](HANDLE h) {
51  if (h != INVALID_HANDLE_VALUE) {
52  CloseHandle(h);
53  }
54 };
55 typedef 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  */
61 template<typename T>
62 struct NetApiTypeInfo {
63 };
64 #define NETAPI_TYPE_INFO(prefix, n) \
65  template<> \
66  struct NetApiTypeInfo<prefix##_##n> { \
67  enum { level = n }; \
68  };
69 NETAPI_TYPE_INFO(GROUP_INFO, 0)
70 NETAPI_TYPE_INFO(GROUP_INFO, 3)
71 NETAPI_TYPE_INFO(USER_INFO, 0)
72 NETAPI_TYPE_INFO(USER_INFO, 4)
73 NETAPI_TYPE_INFO(USER_INFO, 11)
74 NETAPI_TYPE_INFO(GROUP_USERS_INFO, 0)
75 
76 // T must be a USER_INFO_* structure
77 template<typename T>
78 ScopedNetApiBuffer<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  */
100 template<class T, class Callback, class EnumFunction>
101 static 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 
127 template<class T, class Callback>
128 void 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 
139 template<typename T, class Callback>
140 void 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 
147 template<typename T, class Callback>
148 void 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 
163 template<typename T, class Callback>
164 void 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 
172 class 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)
184  , fullName(fullName)
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 
211 public:
212  static Ptr sharedNull;
213  KUserId uid;
214  KGroupId gid;
215  QString loginName;
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, 0, (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 
295 KUserPrivate::Ptr KUserPrivate::sharedNull(new KUserPrivate());
296 
297 KUser::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 
308 KUser::KUser(K_UID uid)
309  : d(KUserPrivate::create(KUserId(uid)))
310 {
311 }
312 
314  : d(KUserPrivate::create(uid))
315 {
316 }
317 
318 KUser::KUser(const QString &name)
319  : d(KUserPrivate::create(KUserId::fromName(name)))
320 {
321 }
322 
323 KUser::KUser(const char *name)
324  : d(KUserPrivate::create(KUserId::fromName(QString::fromLocal8Bit(name))))
325 {
326 }
327 
328 KUser::KUser(const KUser &user)
329  : d(user.d)
330 {
331 }
332 
333 KUser &KUser::operator=(const KUser &user)
334 {
335  d = user.d;
336  return *this;
337 }
338 
339 bool KUser::operator==(const KUser &user) const
340 {
341  return isValid() && d->uid == user.d->uid;
342 }
343 
344 bool KUser::isValid() const
345 {
346  return d->uid.isValid();
347 }
348 
349 bool KUser::isSuperUser() const
350 {
351  return d->isAdmin;
352 }
353 
355 {
356  return d->loginName;
357 }
358 
359 QString KUser::homeDir() const
360 {
361  return d->homeDir;
362 }
363 
364 // Some RAII objects to help uninitializing/destroying WinAPI stuff
365 // used in faceIconPath.
366 class COMInitializer
367 {
368 public:
369  COMInitializer()
370  : result(CoInitialize(nullptr))
371  {
372  }
373  ~COMInitializer()
374  {
375  if (SUCCEEDED(result)) {
376  CoUninitialize();
377  }
378  }
379  HRESULT result;
380 };
381 class W32Library
382 {
383 public:
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 
408 struct 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 };
418 struct 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 
427 template<typename Platform>
428 static 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 
461 QString KUser::shell() const
462 {
463  return isValid() ? QStringLiteral("cmd.exe") : QString();
464 }
465 
466 KUserId KUser::userId() const
467 {
468  return d->uid;
469 }
470 
471 KGroupId KUser::groupId() const
472 {
473  return d->gid;
474 }
475 
476 QVariant 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 
489 class KUserGroupPrivate : public QSharedData
490 {
491 public:
492  QString name;
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 
517 KUserGroup::KUserGroup(const QString &_name)
518  : d(new KUserGroupPrivate(_name, KGroupId()))
519 {
520 }
521 
522 KUserGroup::KUserGroup(const char *_name)
523  : d(new KUserGroupPrivate(QLatin1String(_name), KGroupId()))
524 {
525 }
526 
527 static 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;
538  QString name;
539  if (LookupAccountSidW(NULL, gid.nativeId(), buffer, &bufferLen, domainBuffer, &domainBufferLen, &eUse)) {
540  if (eUse == SidTypeGroup || eUse == SidTypeWellKnownGroup) {
541  name = QString::fromWCharArray(buffer);
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 
554 KUserGroup::KUserGroup(K_GID gid)
555 {
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 
582 bool KUserGroup::operator==(const KUserGroup &group) const
583 {
584  return isValid() && d->gid == group.d->gid && d->name == group.d->name;
585 }
586 
587 bool 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 
606 QList<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 
620 QStringList 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 
649 QList<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 
661 QStringList 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 
673 QList<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 
685 QStringList 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 
697 static const auto invalidSidString = QStringLiteral("<invalid SID>");
698 
699 static 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 
713 struct 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 
734 template<>
736 {
737 }
738 
739 template<>
741 {
742 }
743 
744 template<>
746  : data(other.data)
747 {
748 }
749 
750 template<>
752 {
753  data = other.data;
754  return *this;
755 }
756 
757 template<>
759  : data(WindowsSIDWrapper::copySid(nativeId))
760 {
761 }
762 
763 template<>
765 {
766  return data;
767 }
768 
769 template<>
771 {
772  if (!data) {
773  return nullptr;
774  }
775  return data->sidBuffer;
776 }
777 
778 template<>
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 
790 template<>
792 {
793  return !(*this == other);
794 }
795 
796 template<>
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) */
803 template<class T, class Callback>
804 static 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 
826 KUserId KUserId::fromName(const QString &name)
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 
854 static 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 
902 KCOREADDONS_EXPORT uint qHash(const KUserId &id, uint 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 
912 KCOREADDONS_EXPORT uint qHash(const KGroupId &id, uint 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 }
void append(const T &value)
KGroupId()
Creates an invalid KGroupId.
Definition: kuser.h:170
K_GID gid() const
Returns the group id of the group.
Definition: kuser.h:551
static KUserId fromName(const QString &name)
Definition: kuser_unix.cpp:587
KGroupId groupId() const
Definition: kuser_unix.cpp:485
KUserId()
Creates an invalid KUserId.
Definition: kuser.h:135
A platform independent user or group ID.
Definition: kuser.h:59
NativeType nativeId() const
Definition: kuser.h:617
QByteArray fromRawData(const char *data, int size)
KUserGroup(const QString &name)
Create an object from a group name.
Definition: kuser_unix.cpp:449
static QList< KUserGroup > allGroups(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:551
bool isValid() const
Definition: kuser.h:602
KUserId userId() const
Definition: kuser_unix.cpp:214
QStringView level(QStringView ifopt)
bool isValid() const
Returns whether the group is valid.
Definition: kuser_unix.cpp:480
static QList< KUser > allUsers(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:351
QString homePath()
@ UseEffectiveUID
Use the effective user id.
Definition: kuser.h:229
QList< KUserGroup > groups(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:328
bool operator!=(const KUserOrGroupId &other) const
Definition: kuser.h:612
UIDMode
Definition: kuser.h:228
static KUserId currentUserId()
Definition: kuser_unix.cpp:615
static KGroupId currentGroupId()
Definition: kuser_unix.cpp:625
A platform independent user ID.
Definition: kuser.h:133
Represents a group on your system.
Definition: kuser.h:457
QString fromWCharArray(const wchar_t *string, int size)
QList< KUser > users(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:533
bool isEmpty() const const
bool isValid() const
Returns true if the user is valid.
Definition: kuser_unix.cpp:209
typedef HANDLE
~KUserGroup()
Destructor.
Definition: kuser_unix.cpp:583
Q_SCRIPTABLE CaptureState status()
KUserOrGroupId()
Creates an invalid KUserOrGroupId.
Definition: kuser.h:627
QString name() const
The name of the group.
Definition: kuser_unix.cpp:490
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
bool isSuperUser() const
Checks whether the user is the super user (root).
Definition: kuser_unix.cpp:224
QAction * create(StandardGameAction id, const QObject *recvr, const char *slot, QObject *parent)
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:367
QString loginName() const
The login name of the user.
Definition: kuser_unix.cpp:229
static KGroupId fromName(const QString &name)
Definition: kuser_unix.cpp:601
A platform independent group ID.
Definition: kuser.h:168
Represents a user on your system.
Definition: kuser.h:225
QString toString() const
Definition: kuser.h:622
~KUser()
Destructor.
Definition: kuser_unix.cpp:382
QVariant property(UserProperty which) const
Returns an extended property.
Definition: kuser_unix.cpp:346
bool operator==(const KUserGroup &group) const
Two KUserGroup objects are equal if their gid()s are identical.
Definition: kuser_unix.cpp:475
const ushort * utf16() const const
static KUserId currentEffectiveUserId()
Definition: kuser_unix.cpp:620
KUser & operator=(const KUser &user)
Copies a user.
Definition: kuser_unix.cpp:198
bool isValid(QStringView ifopt)
QString name(StandardShortcut id)
KUser(UIDMode mode=UseEffectiveUID)
Creates an object that contains information about the current user.
Definition: kuser_unix.cpp:151
QString faceIconPath() const
The path to the user's face file.
Definition: kuser_unix.cpp:239
bool operator==(const KUser &user) const
Two KUser objects are equal if the userId() are identical.
Definition: kuser_unix.cpp:204
static KGroupId currentEffectiveGroupId()
Definition: kuser_unix.cpp:630
QStringList userNames(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:542
bool operator==(const KUserOrGroupId &other) const
Definition: kuser.h:607
QString homeDir() const
The path to the user's home directory.
Definition: kuser_unix.cpp:234
QStringList groupNames(uint maxCount=KCOREADDONS_UINT_MAX) const
Definition: kuser_unix.cpp:337
KUserGroup & operator=(const KUserGroup &group)
Copies a group.
Definition: kuser_unix.cpp:469
@ UseRealUserID
Use the real user id.
Definition: kuser.h:230
QString fullName(const PartType &type)
static QStringList allGroupNames(uint maxCount=KCOREADDONS_UINT_MAX)
Definition: kuser_unix.cpp:567
const QList< QKeySequence > & copy()
KGroupId groupId() const
Definition: kuser_unix.cpp:219
QString shell() const
The path to the user's login shell.
Definition: kuser_unix.cpp:259
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:04:52 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.