NCBI C++ ToolKit
ncbi_os_mswin.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: ncbi_os_mswin.cpp 99241 2023-03-01 20:22:24Z ucko $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Vladimir Ivanov
27  *
28  * File Description: MS Windows specifics.
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 #include <corelib/ncbidiag.hpp>
34 #include <corelib/ncbistr.hpp>
35 #include <corelib/ncbierror.hpp>
36 #include "ncbi_os_mswin_p.hpp"
37 
38 
39 // According to MSDN max account name size is 20, domain name size is 256
40 #define MAX_ACCOUNT_LEN 256
41 
42 // Hopefully this makes UID/GID compatible with what CYGWIN reports
43 # ifdef PRIMARY_POSIX_OFFSET
44 # define CYGWIN_PRIMARY_ID_OFFSET PRIMARY_POSIX_OFFSET
45 # else
46 # define CYGWIN_PRIMARY_ID_OFFSET 0x100000
47 # endif /*PRIMARY_POSIX_OFFSET*/
48 
49 // Security access info
50 #define ACCOUNT_SECURITY_INFO (OWNER_SECURITY_INFORMATION | \
51  GROUP_SECURITY_INFORMATION)
52 
53 #define FILE_SECURITY_INFO (OWNER_SECURITY_INFORMATION | \
54  GROUP_SECURITY_INFORMATION | \
55  DACL_SECURITY_INFORMATION)
56 
58 
59 
61 {
62  TXChar name[UNLEN + 1];
63  DWORD name_size = sizeof(name) / sizeof(name[0]) - 1;
64 
65  if ( !::GetUserName(name, &name_size) ) {
67  return kEmptyStr;
68  }
69  name[name_size] = _TX('\0');
70  return _T_STDSTRING(name);
71 }
72 
73 
74 // Get SID by account name.
75 // Return NULL on error.
76 // Do not forget to free the returned SID by calling LocalFree().
77 
78 static PSID x_GetAccountSidByName(const string& account, SID_NAME_USE type = (SID_NAME_USE)0)
79 {
80  PSID sid = NULL;
81  DWORD sid_size = 0;
82  TXChar* domain = NULL;
83  DWORD domain_size = 0;
84  SID_NAME_USE use;
85 
86  TXString name(_T_XSTRING(account));
87 
88  // First call to LookupAccountName() to get the buffer sizes
89  if ( !::LookupAccountName(NULL, name.c_str(), sid, &sid_size, domain, &domain_size, &use) ) {
90  DWORD err = ::GetLastError();
91  if (err != ERROR_INSUFFICIENT_BUFFER) {
93  return NULL;
94  }
95  }
96  try {
97  // Allocate buffers
98  sid = (PSID) LocalAlloc(LMEM_FIXED, sid_size);
99  domain = (TXChar*) malloc(domain_size * sizeof(TXChar));
100  if ( !sid || !domain ) {
101  throw(0);
102  }
103  // Second call to get the actual account info
104  if ( !LookupAccountName(NULL, name.c_str(), sid, &sid_size, domain, &domain_size, &use) ) {
106  throw(0);
107  }
108  // Check type of account
109  if (type && type != use ) {
111  throw(0);
112  }
113  }
114  catch (int) {
115  ::LocalFree(sid);
116  sid = NULL;
117  }
118  // Clean up
119  if ( domain ) free(domain);
120 
121  return sid;
122 }
123 
124 
125 // Get account name by SID.
126 // Note: *domatch is reset to 0 if the account type has no domain match.
127 #include <stdio.h>
128 #include <tchar.h>
129 
130 static bool x_GetAccountNameBySid(PSID sid, string* account, int* domatch = 0)
131 {
132  // Use predefined buffers for account/domain names to avoid additional
133  // step to get its sizes. According to MSDN max account/domain size
134  // does not exceed MAX_ACCOUNT_LEN symbols (char or wchar).
135 
136  TXChar account_name[MAX_ACCOUNT_LEN + 2];
137  TXChar domain_name [MAX_ACCOUNT_LEN + 2];
138  DWORD account_size = sizeof(account_name)/sizeof(account_name[0]) - 1;
139  DWORD domain_size = sizeof(domain_name)/sizeof(domain_name[0]) - 1;
140  SID_NAME_USE use;
141 
142  // Always get both account & domain name, even we don't need last.
143  // Because if domain name is NULL, this function can throw unhandled
144  // exception in Unicode builds on some platforms.
145  if ( !::LookupAccountSid(NULL, sid,
146  account_name, &account_size,
147  domain_name, &domain_size, &use) ) {
149  return false;
150  }
151 
152  // Save account information
153  if (account) {
154  account_name[account_size] = _TX('\0');
155  account->assign(_T_STDSTRING(account_name));
156  }
157 
158  if (domatch) {
159  if (*domatch != int(use)) {
160  *domatch = 0;
161  } else {
162  domain_name[domain_size] = _TX('\0');
163  string domain(_T_STDSTRING(domain_name));
164  if (domain.empty()
165  || NStr::EqualNocase(domain, "builtin")
166  || NStr::FindNoCase(domain, " ") != NPOS
167  /*|| x_DomainIsLocalComputer(domain_name)*/) {
168  *domatch = 0;
169  }
170  }
171  }
172  return true;
173 }
174 
175 
176 // Get account owner/group names and uids by SID
177 
178 static bool s_GetOwnerGroupFromSIDs(PSID owner_sid, PSID group_sid,
179  string* owner_name, string* group_name,
180  unsigned int* uid, unsigned int* gid)
181 {
182  bool success = true;
183 
184  // Get numeric owner
185  if ( uid ) {
186  int match = SidTypeUser;
187  if ( !x_GetAccountNameBySid(owner_sid, owner_name, &match) ) {
188  if ( owner_name )
189  success = false;
190  *uid = 0;
191  } else {
192  *uid = match ? CYGWIN_PRIMARY_ID_OFFSET : 0;
193  }
194  owner_name = NULL;
195  *uid += *::GetSidSubAuthority(owner_sid, *::GetSidSubAuthorityCount(owner_sid) - 1);
196  }
197  // Get numeric group
198  if ( gid ) {
199  int match = SidTypeGroup;
200  if ( !x_GetAccountNameBySid(group_sid, group_name, &match) ) {
201  *gid = 0;
202  } else {
203  *gid = match ? CYGWIN_PRIMARY_ID_OFFSET : 0;
204  }
205  group_name = NULL;
206  *gid += *::GetSidSubAuthority(group_sid, *::GetSidSubAuthorityCount(group_sid) - 1);
207  }
208  if ( !success ) {
209  return false;
210  }
211 
212  // Get owner name
213  if ( owner_name && !x_GetAccountNameBySid(owner_sid, owner_name) ) {
214  return false;
215  }
216  // Get group name
217  if ( group_name && !x_GetAccountNameBySid(group_sid, group_name) ) {
218  // This is not an error, actually, because group name on Windows is an
219  // auxiliary information. Sometimes accounts do not belong to any
220  // group(s), or we don't have permissions to get such information.
221  group_name->clear();
222  }
223  return true;
224 }
225 
226 
228  SE_OBJECT_TYPE obj_type,
229  string* owner, string* group,
230  unsigned int* uid, unsigned int* gid)
231 {
232  PSID sid_owner;
233  PSID sid_group;
234  PSECURITY_DESCRIPTOR sd;
235 
236  DWORD res = ::GetSecurityInfo(obj_handle, obj_type, ACCOUNT_SECURITY_INFO,
237  &sid_owner, &sid_group, NULL, NULL, &sd);
238  if ( res != ERROR_SUCCESS ) {
240  return false;
241  }
242  bool retval = s_GetOwnerGroupFromSIDs(sid_owner, sid_group, owner, group, uid, gid);
243  ::LocalFree(sd);
244  return retval;
245 }
246 
247 
248 bool CWinSecurity::GetObjectOwner(const string& obj_name,
249  SE_OBJECT_TYPE obj_type,
250  string* owner, string* group,
251  unsigned int* uid, unsigned int* gid)
252 {
253  PSID sid_owner;
254  PSID sid_group;
255  PSECURITY_DESCRIPTOR sd;
256 
257  DWORD res = ::GetNamedSecurityInfo(_T_XCSTRING(obj_name), obj_type,
259  &sid_owner, &sid_group, NULL, NULL, &sd);
260  if ( res != ERROR_SUCCESS ) {
262  return false;
263  }
264  bool retval = s_GetOwnerGroupFromSIDs(sid_owner, sid_group, owner, group, uid, gid);
265  ::LocalFree(sd);
266  return retval;
267 }
268 
269 
270 // Get current thread token. Return INVALID_HANDLE_VALUE on error.
271 
273 {
274  HANDLE token;
275  if ( !::OpenThreadToken(GetCurrentThread(), access, FALSE, &token) ) {
276  DWORD res = GetLastError();
277  if ( res == ERROR_NO_TOKEN ) {
278  if ( !::ImpersonateSelf(SecurityImpersonation) ) {
279  // Failed to obtain a token for the current thread and user
281  return INVALID_HANDLE_VALUE;
282  }
283  if ( !::OpenThreadToken(GetCurrentThread(), access, FALSE, &token) ) {
284  // Failed to open the current threads token with the required access rights
286  token = INVALID_HANDLE_VALUE;
287  }
288  ::RevertToSelf();
289  } else {
290  // Failed to open the current threads token with the required access rights
292  return NULL;
293  }
294  }
295  return token;
296 }
297 
298 
299 bool CWinSecurity::SetFileOwner(const string& filename,
300  const string& owner, const string& group,
301  unsigned int* uid, unsigned int* gid)
302 {
303  _ASSERT(!owner.empty() || !group.empty());
304 
306  PSID owner_sid = NULL;
307  PSID group_sid = NULL;
308  bool success = false;
309  SECURITY_INFORMATION security_info = 0;
310 
311  // Get SIDs for new owner and group
312  if ( !owner.empty() ) {
313  owner_sid = x_GetAccountSidByName(owner, SidTypeUser);
314  if (!owner_sid) {
315  return false;
316  }
317  }
318  if ( !group.empty() ) {
319  group_sid = x_GetAccountSidByName(group, SidTypeGroup);
320  if (!group_sid) {
321  goto cleanup;
322  }
323  }
324  if (uid || gid) {
325  s_GetOwnerGroupFromSIDs(owner_sid, group_sid, NULL, NULL, uid, gid);
326  }
327 
328  // Change owner
329 
330  if ( owner_sid ) {
331  security_info |= OWNER_SECURITY_INFORMATION;
332  }
333  if ( group_sid ) {
334  security_info |= GROUP_SECURITY_INFORMATION;
335  }
336 
337  // Set new owner/group in the object's security descriptor
338  if ( ::SetNamedSecurityInfo((TXChar*)_T_XCSTRING(filename),
339  SE_FILE_OBJECT, security_info,
340  owner_sid, group_sid, NULL, NULL) == ERROR_SUCCESS ) {
341  success = true;
342  goto cleanup;
343  }
344 
345  // If the previous call failed because access was denied, try to enable the
346  // necessary admin privileges for the current thread, then try again.
347 
348  if ( (token = s_GetCurrentThreadToken(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY)) == INVALID_HANDLE_VALUE) {
349  goto cleanup;
350  }
351  bool prev_ownership_name;
352  bool prev_restore_name;
353 
354  if ( !SetTokenPrivilege(token, SE_TAKE_OWNERSHIP_NAME, true, &prev_ownership_name) ) {
355  goto cleanup;
356  }
357  if ( !SetTokenPrivilege(token, SE_RESTORE_NAME, true, &prev_restore_name) ) {
358  goto cleanup2;
359  }
360  if ( ::SetNamedSecurityInfo((TXChar*)_T_XCSTRING(filename),
361  SE_FILE_OBJECT, security_info,
362  owner_sid, group_sid, NULL, NULL) == ERROR_SUCCESS ) {
363  success = true;
364  }
365  // Restore privileges
366  SetTokenPrivilege(token, SE_RESTORE_NAME, prev_restore_name);
367 cleanup2:
368  SetTokenPrivilege(token, SE_TAKE_OWNERSHIP_NAME, prev_ownership_name);
369 
370 cleanup:
371  if ( token != INVALID_HANDLE_VALUE ) ::CloseHandle(token);
372  if ( group_sid ) ::LocalFree(group_sid);
373  if ( owner_sid ) ::LocalFree(owner_sid);
374 
375  return success;
376 }
377 
378 
379 bool CWinSecurity::SetTokenPrivilege(HANDLE token, LPCTSTR privilege,
380  bool enable, bool* prev)
381 {
382  // Get privilege unique identifier
383  LUID luid;
384  if ( !::LookupPrivilegeValue(NULL, privilege, &luid) ) {
386  return false;
387  }
388 
389  // Get current privilege setting
390 
391  TOKEN_PRIVILEGES tp;
392  TOKEN_PRIVILEGES tp_prev;
393  DWORD tp_size = sizeof(tp);
394 
395  tp.PrivilegeCount = 1;
396  tp.Privileges[0].Luid = luid;
397  tp.Privileges[0].Attributes = 0;
398 
399  ::AdjustTokenPrivileges(token, FALSE, &tp, tp_size, &tp_prev, &tp_size);
400  DWORD res = GetLastError();
401  if ( res != ERROR_SUCCESS ) {
402  // Failed to obtain the current token's privileges
404  return false;
405  }
406 
407  // Enable/disable privilege
408 
409  tp.PrivilegeCount = 1;
410  tp.Privileges[0].Luid = luid;
411  if (prev) {
412  *prev = ((tp_prev.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED);
413  }
414  tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
415 
416  ::AdjustTokenPrivileges(token, FALSE, &tp, tp_size, NULL, NULL);
417  res = GetLastError();
418  if ( res != ERROR_SUCCESS ) {
419  // Failed to change privileges
421  return false;
422  }
423  // Privilege settings changed
424  return true;
425 }
426 
427 
428 bool CWinSecurity::SetThreadPrivilege(LPCTSTR privilege, bool enable, bool* prev)
429 {
430  HANDLE token = s_GetCurrentThreadToken(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY);
431  if ( token == INVALID_HANDLE_VALUE) {
432  return false;
433  }
434  bool res = SetTokenPrivilege(token, privilege, enable, prev);
435  return res;
436 }
437 
438 
439 // Get file security descriptor. Return NULL on error.
440 // NOTE: Do not forget to deallocated memory for returned descriptor.
441 
442 static PSECURITY_DESCRIPTOR s_GetFileSecurityDescriptor(const string& path)
443 {
444  if ( path.empty() ) {
446  return NULL;
447  }
448  PSECURITY_DESCRIPTOR sd = NULL;
449  DWORD size = 0;
450  DWORD size_need = 0;
451 
452  if ( !::GetFileSecurity(_T_XCSTRING(path), FILE_SECURITY_INFO, sd, size, &size_need) ) {
453  DWORD err = ::GetLastError();
454  if (err != ERROR_INSUFFICIENT_BUFFER) {
456  return NULL;
457  }
458  // Allocate memory for the buffer
459  sd = (PSECURITY_DESCRIPTOR) ::LocalAlloc(LMEM_FIXED, size_need);
460  if ( !sd ) {
462  return NULL;
463  }
464  size = size_need;
465  if ( !::GetFileSecurity(_T_XCSTRING(path), FILE_SECURITY_INFO, sd, size, &size_need) ) {
467  ::LocalFree((HLOCAL) sd);
468  return NULL;
469  }
470  }
471  return sd;
472 }
473 
474 
475 // We don't use GetEffectiveRightsFromAcl() here because it is very limited
476 // and very often works incorrectly. Microsoft doesn't recommend to use it.
477 // So, permissions can be taken for the current process thread owner only :(
478 
479 bool CWinSecurity::GetFilePermissions(const string& path, ACCESS_MASK* permissions)
480 {
481  if ( !permissions ) {
483  return false;
484  }
485 
486  // Get security descriptor for the file
487  PSECURITY_DESCRIPTOR sd = s_GetFileSecurityDescriptor(path);
488  if ( !sd ) {
489  if ( CNcbiError::GetLast().Native() == ERROR_ACCESS_DENIED ) {
490  *permissions = 0;
491  return true;
492  }
493  return false;
494  }
495 
497  bool success = true;
498 
499  try {
500  // Open current thread token
501  token = s_GetCurrentThreadToken(TOKEN_DUPLICATE | TOKEN_QUERY);
502  if ( token == INVALID_HANDLE_VALUE) {
503  throw(0);
504  }
505  GENERIC_MAPPING mapping;
506  memset(&mapping, 0, sizeof(mapping));
507 
508  PRIVILEGE_SET privileges;
509  DWORD privileges_size = sizeof(privileges);
510  BOOL status;
511 
512  if ( !::AccessCheck(sd, token, MAXIMUM_ALLOWED, &mapping,
513  &privileges, &privileges_size, permissions,
514  &status) || !status ) {
516  throw(0);
517  }
518  }
519  catch (int) {
520  *permissions = 0;
521  success = false;
522  }
523  // Clean up
524  ::CloseHandle(token);
525  ::LocalFree(sd);
526 
527  return success;
528 }
529 
530 
531 bool CWinFeature::FindProcessEntry(DWORD id, PROCESSENTRY32& entry)
532 {
533  entry = { 0 };
534  HANDLE const snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
535 
536  if (snapshot != INVALID_HANDLE_VALUE) {
537  entry.dwSize = sizeof(PROCESSENTRY32);
538  BOOL ret = ::Process32First(snapshot, &entry);
539  while (ret && entry.th32ProcessID != id) {
540  ret = ::Process32Next(snapshot, &entry);
541  }
542  ::CloseHandle(snapshot);
543  return true;
544  }
545  return false;
546 }
547 
548 
static bool FindProcessEntry(DWORD id, PROCESSENTRY32 &entry)
Find process entry information by process identifier (pid).
static bool GetFilePermissions(const string &path, ACCESS_MASK *permissions)
Get file access permissions.
static string GetUserName(void)
Get name of the current user.
static bool SetTokenPrivilege(HANDLE token, LPCTSTR privilege, bool enable, bool *prev=0)
Enables or disables privileges in the specified access token.
static bool SetFileOwner(const string &filename, const string &owner, const string &group=kEmptyStr, unsigned int *uid=0, unsigned int *gid=0)
Set file object owner.
static bool GetObjectOwner(const string &obj_name, SE_OBJECT_TYPE obj_type, string *owner, string *group, unsigned int *uid=0, unsigned int *gid=0)
Get owner name of specified system object.
static bool SetThreadPrivilege(LPCTSTR privilege, bool enable, bool *prev=0)
Enables or disables privileges for the current thread.
The NCBI C++ standard methods for dealing with std::string.
static void cleanup(void)
Definition: ct_dynamic.c:30
static DLIST_TYPE *DLIST_NAME() prev(DLIST_LIST_TYPE *list, DLIST_TYPE *item)
Definition: dlist.tmpl.h:61
#define NULL
Definition: ncbistd.hpp:225
static void SetFromWindowsError(void)
Set last error on MS Windows using GetLastError()
Definition: ncbierror.cpp:290
static void Set(ECode code)
Set last error using native error code enum.
Definition: ncbierror.cpp:160
static const CNcbiError & GetLast(void)
Get the error that was last set (in the current thread)
Definition: ncbierror.cpp:72
static void SetWindowsError(int native_err_code)
Set last error using Windows-specific error code.
Definition: ncbierror.cpp:260
@ eInvalidArgument
Definition: ncbierror.hpp:86
@ eUnknown
Unknown error.
Definition: ncbierror.hpp:138
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
#define kEmptyStr
Definition: ncbistr.hpp:123
static SIZE_TYPE FindNoCase(const CTempString str, const CTempString pattern, SIZE_TYPE start, SIZE_TYPE end, EOccurrence which=eFirst)
Find the pattern in the specified range of a string using a case insensitive search.
Definition: ncbistr.cpp:2989
char TXChar
Definition: ncbistr.hpp:172
#define NPOS
Definition: ncbistr.hpp:133
string TXString
Definition: ncbistr.hpp:173
#define _T_STDSTRING(x)
Definition: ncbistr.hpp:180
#define _T_XCSTRING(x)
Definition: ncbistr.hpp:181
#define _TX(x)
Definition: ncbistr.hpp:176
#define _T_XSTRING(x)
Definition: ncbistr.hpp:179
static bool EqualNocase(const CTempString s1, SIZE_TYPE pos, SIZE_TYPE n, const char *s2)
Case-insensitive equality of a substring with another string.
Definition: ncbistr.hpp:5352
#define INVALID_HANDLE_VALUE
A value for an invalid file handle.
Definition: mdb.c:389
#define HANDLE
An abstraction for a file handle.
Definition: mdb.c:383
const struct ncbi::grid::netcache::search::fields::SIZE size
#define FILE_SECURITY_INFO
static PSID x_GetAccountSidByName(const string &account, SID_NAME_USE type=(SID_NAME_USE) 0)
#define MAX_ACCOUNT_LEN
#define CYGWIN_PRIMARY_ID_OFFSET
static bool x_GetAccountNameBySid(PSID sid, string *account, int *domatch=0)
static bool s_GetOwnerGroupFromSIDs(PSID owner_sid, PSID group_sid, string *owner_name, string *group_name, unsigned int *uid, unsigned int *gid)
static HANDLE s_GetCurrentThreadToken(DWORD access)
static PSECURITY_DESCRIPTOR s_GetFileSecurityDescriptor(const string &path)
#define ACCOUNT_SECURITY_INFO
Defines MS Windows specific private functions and classes.
Defines NCBI C++ diagnostic APIs, classes, and macros.
Defines NCBI C++ Toolkit portable error codes.
static int match(register const pcre_uchar *eptr, register const pcre_uchar *ecode, const pcre_uchar *mstart, int offset_top, match_data *md, eptrblock *eptrb, unsigned int rdepth)
Definition: pcre_exec.c:513
unsigned int DWORD
Definition: sqltypes.h:98
Definition: type.c:6
int BOOL
Definition: sybdb.h:150
#define _ASSERT
@ FALSE
Definition: testodbc.c:27
void free(voidpf ptr)
voidp malloc(uInt size)
Modified on Fri Dec 08 08:21:56 2023 by modify_doxy.py rev. 669887