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

KDE's Doxygen guidelines are available online.