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

Go to the SVN repository for this file.

00001 /*  $Id: ncbi_os_mswin.cpp 60966 2013-12-16 23:58:22Z ivanov $
00002  * ===========================================================================
00003  *
00004  *                            PUBLIC DOMAIN NOTICE
00005  *               National Center for Biotechnology Information
00006  *
00007  *  This software/database is a "United States Government Work" under the
00008  *  terms of the United States Copyright Act.  It was written as part of
00009  *  the author's official duties as a United States Government employee and
00010  *  thus cannot be copyrighted.  This software/database is freely available
00011  *  to the public for use. The National Library of Medicine and the U.S.
00012  *  Government have not placed any restriction on its use or reproduction.
00013  *
00014  *  Although all reasonable efforts have been taken to ensure the accuracy
00015  *  and reliability of the software and data, the NLM and the U.S.
00016  *  Government do not and cannot warrant the performance or results that
00017  *  may be obtained by using this software or data. The NLM and the U.S.
00018  *  Government disclaim all warranties, express or implied, including
00019  *  warranties of performance, merchantability or fitness for any particular
00020  *  purpose.
00021  *
00022  *  Please cite the author in any work or product based on this material.
00023  *
00024  * ===========================================================================
00025  *
00026  * Authors:  Vladimir Ivanov
00027  *
00028  * File Description:   MS Windows specifics.
00029  *
00030  */
00031 
00032 #include <ncbi_pch.hpp>
00033 #include <corelib/ncbidiag.hpp>
00034 #include <corelib/ncbistr.hpp>
00035 #include <corelib/ncbierror.hpp>
00036 #include "ncbi_os_mswin_p.hpp"
00037 
00038 
00039 // According to MSDN max account name size is 20, domain name size is 256
00040 #define MAX_ACCOUNT_LEN  256
00041 
00042 // Hopefully this makes UID/GID compatible with what CYGWIN reports
00043 #define CYGWIN_MAGIC_ID_OFFSET  10000
00044 
00045 // Security access info
00046 #define ACCOUNT_SECURITY_INFO  (OWNER_SECURITY_INFORMATION | \
00047                                 GROUP_SECURITY_INFORMATION)
00048 
00049 #define FILE_SECURITY_INFO     (OWNER_SECURITY_INFORMATION | \
00050                                 GROUP_SECURITY_INFORMATION | \
00051                                 DACL_SECURITY_INFORMATION)
00052 
00053 BEGIN_NCBI_SCOPE
00054 
00055 
00056 string CWinSecurity::GetUserName(void)
00057 {
00058     TXChar name[UNLEN + 1];
00059     DWORD  name_size = sizeof(name) / sizeof(name[0]) - 1;
00060 
00061     if ( !::GetUserName(name, &name_size) ) {
00062         CNcbiError::SetFromWindowsError();
00063         return kEmptyStr;
00064     }
00065     name[name_size] = _TX('\0');
00066     return _T_STDSTRING(name);
00067 }
00068 
00069 
00070 // Get SID by account name.
00071 // Return NULL on error.
00072 // Do not forget to free the returned SID by calling LocalFree().
00073 
00074 static PSID x_GetAccountSidByName(const string& account, SID_NAME_USE type = (SID_NAME_USE)0)
00075 {
00076     PSID         sid         = NULL;
00077     DWORD        sid_size    = 0;
00078     TXChar*      domain      = NULL;
00079     DWORD        domain_size = 0;
00080     SID_NAME_USE use;
00081 
00082     TXString name(_T_XSTRING(account));
00083 
00084     // First call to LookupAccountName() to get the buffer sizes
00085     if ( !LookupAccountName(NULL, name.c_str(), sid, &sid_size, domain, &domain_size, &use) 
00086           &&  GetLastError() != ERROR_INSUFFICIENT_BUFFER ) {
00087         CNcbiError::SetFromWindowsError();
00088         return NULL;
00089     }
00090     try {
00091         // Allocate buffers
00092         sid    = (PSID) LocalAlloc(LMEM_FIXED, sid_size);
00093         domain = (TXChar*) malloc(domain_size * sizeof(TXChar));
00094         if ( !sid  ||  !domain ) {
00095             throw(0);
00096         }
00097         // Second call to get the actual account info
00098         if ( !LookupAccountName(NULL, name.c_str(), sid, &sid_size, domain, &domain_size, &use) ) {
00099             CNcbiError::SetFromWindowsError();
00100             throw(0);
00101         }
00102         // Check type of account
00103         if (type  &&  type != use ) {
00104             CNcbiError::Set(CNcbiError::eUnknown);
00105             throw(0);
00106         }
00107     }
00108     catch (int) {
00109         LocalFree(sid);
00110         sid = NULL;
00111     }
00112     // Clean up
00113     if ( domain ) free(domain);
00114 
00115     return sid;
00116 }
00117 
00118 
00119 // Get account name by SID.
00120 // Note: *domatch is reset to 0 if the account type has no domain match.
00121 #include <stdio.h>
00122 #include <tchar.h>
00123 
00124 static bool x_GetAccountNameBySid(PSID sid, string* account, int* domatch = 0)
00125 {
00126     _ASSERT(account);
00127 
00128     // Use predefined buffers for account/domain names to avoid additional
00129     // step to get its sizes. According to MSDN max account/domain size
00130     // do not exceed MAX_ACCOUNT_LEN symbols (char or wchar).
00131 
00132     TXChar   account_name[MAX_ACCOUNT_LEN + 2];
00133     TXChar   domain_name [MAX_ACCOUNT_LEN + 2];
00134     DWORD    account_size = sizeof(account_name)/sizeof(account_name[0]) - 1;
00135     DWORD    domain_size  = sizeof(domain_name)/sizeof(domain_name[0]) - 1;
00136     SID_NAME_USE use;
00137 
00138     // Always get both account & domain name, even we don't need last.
00139     // Because if domain name is NULL, this function can throw unhandled
00140     // exception in Unicode builds on some platforms.
00141     if ( !LookupAccountSid(NULL, sid, 
00142                            account_name, &account_size,
00143                            domain_name,  &domain_size, &use) ) {
00144         CNcbiError::SetFromWindowsError();
00145         return false;
00146     }
00147 
00148     // Save account information
00149     account_name[account_size] = _TX('\0');
00150     account->assign(_T_STDSTRING(account_name));
00151 
00152     if (domatch) {
00153         domain_name[domain_size] = _TX('\0');
00154         string domain(_T_STDSTRING(domain_name));
00155         if (*domatch != int(use)  ||  domain.empty()
00156             ||  NStr::EqualNocase(domain, "builtin")
00157             ||  NStr::FindNoCase(domain, " ") != NPOS
00158             /*||  x_DomainIsLocalComputer(domain_name)*/) {
00159             *domatch = 0;
00160         }
00161     }
00162     return true;
00163 }
00164 
00165 
00166 // Get account owner/group names and uids by SID
00167 
00168 static bool s_GetOwnerGroupFromSIDs(PSID owner_sid, PSID group_sid,
00169                                     string* owner_name, string* group_name,
00170                                     unsigned int* uid, unsigned int* gid)
00171 {
00172     bool success = true;
00173 
00174     // Get numeric owner
00175     if ( uid ) {
00176         int match = SidTypeUser;
00177         if ( !x_GetAccountNameBySid(owner_sid, owner_name, &match) ) {
00178             if ( owner_name )
00179                 success = false;
00180             *uid = 0;
00181         } else {
00182             *uid = match ? CYGWIN_MAGIC_ID_OFFSET : 0;
00183         }
00184         owner_name = NULL;
00185         *uid += *GetSidSubAuthority(owner_sid, *GetSidSubAuthorityCount(owner_sid) - 1);
00186     }
00187     // Get numeric group
00188     if ( gid ) {
00189         int match = SidTypeGroup;
00190         if ( !x_GetAccountNameBySid(group_sid, group_name, &match) ) {
00191             *gid = 0;
00192         } else {
00193             *gid = match ? CYGWIN_MAGIC_ID_OFFSET : 0;
00194         }
00195         group_name = NULL;
00196         *gid += *GetSidSubAuthority(group_sid, *GetSidSubAuthorityCount(group_sid) - 1);
00197     }
00198     if ( !success ) {
00199         return false;
00200     }
00201 
00202     // Get owner name
00203     if ( owner_name  &&  !x_GetAccountNameBySid(owner_sid, owner_name) ) {
00204         return false;
00205     }
00206     // Get group name
00207     if ( group_name  &&  !x_GetAccountNameBySid(group_sid, group_name) ) {
00208         // This is not an error, because the group name on Windows
00209         // is an auxiliary information.  Sometimes accounts cannot
00210         // belong to groups, or we don't have permissions to get
00211         // such information.
00212         group_name->clear();
00213     }
00214     return true;
00215 }
00216 
00217 
00218 bool CWinSecurity::GetObjectOwner(HANDLE         obj_handle,
00219                                   SE_OBJECT_TYPE obj_type,
00220                                   string* owner, string* group,
00221                                   unsigned int* uid, unsigned int* gid)
00222 {
00223     PSID sid_owner;
00224     PSID sid_group;
00225     PSECURITY_DESCRIPTOR sd;
00226 
00227     DWORD res = GetSecurityInfo(obj_handle, obj_type, ACCOUNT_SECURITY_INFO,
00228                                 &sid_owner, &sid_group, NULL, NULL, &sd );
00229     if ( res != ERROR_SUCCESS ) {
00230         CNcbiError::SetWindowsError(res);
00231         return false;
00232     }
00233     bool retval = s_GetOwnerGroupFromSIDs(sid_owner, sid_group, owner, group, uid, gid);
00234     LocalFree(sd);
00235     return retval;
00236 }
00237 
00238 
00239 bool CWinSecurity::GetObjectOwner(const string&  obj_name,
00240                                   SE_OBJECT_TYPE obj_type,
00241                                   string* owner, string* group,
00242                                   unsigned int* uid, unsigned int* gid)
00243 {
00244     PSID sid_owner;
00245     PSID sid_group;
00246     PSECURITY_DESCRIPTOR sd;
00247 
00248     DWORD res = GetNamedSecurityInfo(_T_XCSTRING(obj_name), obj_type,
00249                                      ACCOUNT_SECURITY_INFO,
00250                                      &sid_owner, &sid_group, NULL, NULL, &sd );
00251     if ( res != ERROR_SUCCESS ) {
00252         CNcbiError::SetWindowsError(res);
00253         return false;
00254     }
00255     bool retval = s_GetOwnerGroupFromSIDs(sid_owner, sid_group, owner, group, uid, gid);
00256     LocalFree(sd);
00257     return retval;
00258 }
00259 
00260 
00261 // Get current thread token. Return INVALID_HANDLE_VALUE on error.
00262 
00263 static HANDLE s_GetThreadToken(DWORD access)
00264 {
00265     HANDLE token;
00266     if ( !OpenThreadToken(GetCurrentThread(), access, FALSE, &token) ) {
00267         DWORD res = GetLastError();
00268         if ( res == ERROR_NO_TOKEN ) {
00269             if ( !ImpersonateSelf(SecurityImpersonation) ) {
00270                 // Failed to obtain a token for the current thread and user
00271                 CNcbiError::SetFromWindowsError();
00272                 return INVALID_HANDLE_VALUE;
00273             }
00274             if ( !OpenThreadToken(GetCurrentThread(), access, FALSE, &token) ) {
00275                 // Failed to open the current threads token with the required access rights
00276                 CNcbiError::SetFromWindowsError();
00277                 token = INVALID_HANDLE_VALUE;
00278             }
00279             RevertToSelf();
00280         } else {
00281             // Failed to open the current threads token with the required access rights
00282             CNcbiError::SetWindowsError(res);
00283             return NULL;
00284         }
00285     }
00286     return token;
00287 }
00288 
00289 
00290 bool CWinSecurity::SetFileOwner(const string& filename,
00291                                 const string& owner, const string& group, 
00292                                 unsigned int* uid, unsigned int* gid)
00293 {
00294     if ( uid ) *uid = 0;
00295     if ( gid ) *gid = 0;
00296 
00297     if ( owner.empty()  &&  group.empty() ) {
00298         CNcbiError::Set(CNcbiError::eInvalidArgument);
00299         return false;
00300     }
00301 
00302     HANDLE  token     = INVALID_HANDLE_VALUE;
00303     PSID    owner_sid = NULL;
00304     PSID    group_sid = NULL;
00305     bool    success   = false;
00306 
00307     // Get SIDs for new owner and group
00308     if ( !owner.empty() ) {
00309         owner_sid = x_GetAccountSidByName(owner, SidTypeUser);
00310         if (!owner_sid) {
00311             return false;
00312         }
00313     }
00314     if ( !group.empty() ) {
00315         group_sid = x_GetAccountSidByName(group, SidTypeGroup);
00316         if (!group_sid) {
00317             goto cleanup;
00318         }
00319     }
00320     if (uid || gid) {
00321         s_GetOwnerGroupFromSIDs(owner_sid, group_sid, NULL, NULL, uid, gid);
00322     }
00323 
00324     // Change owner
00325 
00326     SECURITY_INFORMATION security_info = 0;
00327     if ( owner_sid ) {
00328         security_info |= OWNER_SECURITY_INFORMATION;
00329     }
00330     if ( group_sid ) {
00331         security_info |= GROUP_SECURITY_INFORMATION;
00332     }
00333 
00334     // Set new owner/group in the object's security descriptor
00335     if ( SetNamedSecurityInfo((TXChar*)_T_XCSTRING(filename),
00336                               SE_FILE_OBJECT, security_info,
00337                               owner_sid, group_sid, NULL, NULL) == ERROR_SUCCESS ) {
00338         success = true;
00339         goto cleanup;
00340     }
00341 
00342     // If the previous call failed because access was denied,
00343     // enable the necessary admin privileges for the current thread and try again.
00344 
00345     token = s_GetThreadToken(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY);
00346     if ( token == INVALID_HANDLE_VALUE) {
00347         goto cleanup;
00348     }
00349     bool prev_ownership_name;
00350     bool prev_restore_name;
00351 
00352     if ( !SetTokenPrivilege(token, SE_TAKE_OWNERSHIP_NAME, true, &prev_ownership_name) ||
00353          !SetTokenPrivilege(token, SE_RESTORE_NAME, true, &prev_restore_name) ) {
00354         goto cleanup;
00355     }
00356     if ( SetNamedSecurityInfo((TXChar*)_T_XCSTRING(filename),
00357                               SE_FILE_OBJECT, security_info,
00358                               owner_sid, group_sid, NULL, NULL) == ERROR_SUCCESS ) {
00359         success = true;
00360     }
00361     // Restore privileges
00362     SetTokenPrivilege(token, SE_TAKE_OWNERSHIP_NAME, prev_ownership_name);
00363     SetTokenPrivilege(token, SE_RESTORE_NAME, prev_restore_name);
00364 
00365 
00366 cleanup:
00367     if ( owner_sid ) LocalFree(owner_sid);
00368     if ( group_sid ) LocalFree(group_sid);
00369     if ( token != INVALID_HANDLE_VALUE) CloseHandle(token);
00370 
00371     return success;
00372 }
00373 
00374 
00375 bool CWinSecurity::SetTokenPrivilege(HANDLE token, LPCTSTR privilege,
00376                                      bool enable, bool* prev)
00377 {
00378     // Get privilege unique identifier
00379     LUID luid;
00380     if ( !LookupPrivilegeValue(NULL, privilege, &luid) ) {
00381         CNcbiError::SetFromWindowsError();
00382         return false;
00383     }
00384 
00385     // Get current privilege setting
00386 
00387     TOKEN_PRIVILEGES tp;
00388     TOKEN_PRIVILEGES tp_prev;
00389     DWORD            tp_size = sizeof(tp);
00390 
00391     tp.PrivilegeCount = 1;
00392     tp.Privileges[0].Luid = luid;
00393     tp.Privileges[0].Attributes = 0;
00394 
00395     AdjustTokenPrivileges(token, FALSE, &tp, tp_size, &tp_prev, &tp_size);
00396     DWORD res = GetLastError();
00397     if ( res != ERROR_SUCCESS ) {
00398         // Failed to obtain the current token's privileges
00399         CNcbiError::SetWindowsError(res);
00400         return false;
00401     }
00402 
00403     // Enable/disable privilege
00404 
00405     tp.PrivilegeCount = 1;
00406     tp.Privileges[0].Luid = luid;
00407     if (prev) {
00408         *prev = ((tp_prev.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED);
00409     }
00410     tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
00411 
00412     AdjustTokenPrivileges(token, FALSE, &tp, tp_size, NULL, NULL);
00413     res = GetLastError();
00414     if ( res != ERROR_SUCCESS ) {
00415         // Failed to change privileges
00416         CNcbiError::SetWindowsError(res);
00417         return false;
00418     }
00419     // Privilege settings changed
00420     return true;
00421 }
00422 
00423 
00424 bool CWinSecurity::SetThreadPrivilege(LPCTSTR privilege, bool enable, bool* prev)
00425 {
00426     HANDLE token = s_GetThreadToken(TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY);
00427     if ( token == INVALID_HANDLE_VALUE) {
00428         return false;
00429     }
00430     bool res = SetTokenPrivilege(token, privilege, enable, prev);
00431     return res;
00432 }
00433 
00434 
00435 // Get file security descriptor. Return NULL on error.
00436 // NOTE: Do not forget to deallocated memory for returned descriptor.
00437 
00438 static PSECURITY_DESCRIPTOR s_GetFileSecurityDescriptor(const string& path)
00439 {
00440     if ( path.empty() ) {
00441         CNcbiError::Set(CNcbiError::eInvalidArgument);
00442         return NULL;
00443     }
00444     PSECURITY_DESCRIPTOR sd = NULL;
00445     DWORD size              = 0;
00446     DWORD size_need         = 0;
00447 
00448     if ( !GetFileSecurity(_T_XCSTRING(path), FILE_SECURITY_INFO, sd, size, &size_need) ) {
00449         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
00450             CNcbiError::SetFromWindowsError();
00451             return NULL;
00452         }
00453         // Allocate memory for the buffer
00454         sd = (PSECURITY_DESCRIPTOR) LocalAlloc(LMEM_FIXED, size_need);
00455         if ( !sd ) {
00456             CNcbiError::SetFromWindowsError();
00457             return NULL;
00458         }
00459         size = size_need;
00460         if ( !GetFileSecurity(_T_XCSTRING(path), FILE_SECURITY_INFO,
00461                               sd, size, &size_need) ) {
00462             CNcbiError::SetFromWindowsError();
00463             LocalFree((HLOCAL) sd);
00464             return NULL;
00465         }
00466     }
00467     return sd;
00468 }
00469 
00470 
00471 // We don't use GetEffectiveRightsFromAcl() here because it is very limited
00472 // and very often works incorrectly.  Microsoft doesn't recommend to use it.
00473 // So, permissions can be taken for the current process thread owner only :(
00474 
00475 bool CWinSecurity::GetFilePermissions(const string& path,
00476                                       ACCESS_MASK*  permissions)
00477 {
00478     if ( !permissions ) {
00479         CNcbiError::Set(CNcbiError::eBadAddress);
00480         return false;
00481     }
00482 
00483     // Get security descriptor for the file
00484     PSECURITY_DESCRIPTOR sd = s_GetFileSecurityDescriptor(path);
00485     if ( !sd ) {
00486         if ( CNcbiError::GetLast().Native() == ERROR_ACCESS_DENIED ) {
00487             *permissions = 0;  
00488             return true;
00489         }
00490         return false;
00491     }
00492 
00493     HANDLE token = INVALID_HANDLE_VALUE;
00494     bool success = true;
00495 
00496     try {
00497         // Open current thread token
00498         token = s_GetThreadToken(TOKEN_DUPLICATE | TOKEN_QUERY);
00499         if ( token == INVALID_HANDLE_VALUE) {
00500             throw(0);
00501         }
00502         GENERIC_MAPPING mapping;
00503         memset(&mapping, 0, sizeof(mapping));
00504 
00505         PRIVILEGE_SET privileges;
00506         DWORD         privileges_size = sizeof(privileges);
00507         BOOL          status;
00508 
00509         if ( !AccessCheck(sd, token, MAXIMUM_ALLOWED, &mapping,
00510                           &privileges, &privileges_size, permissions,
00511                           &status)  ||  !status ) {
00512             CNcbiError::SetFromWindowsError();
00513             throw(0);
00514         }
00515     }
00516     catch (int) {
00517         *permissions = 0;
00518         success = false;
00519     }
00520     // Clean up
00521     CloseHandle(token);
00522     LocalFree(sd);
00523 
00524     return success;
00525 }
00526 
00527 
00528 END_NCBI_SCOPE
Modified on Fri Sep 19 19:27:40 2014 by modify_doxy.py rev. 426318