1/* 2 win32/nt.c - Zip 3 3 4 Copyright (c) 1990-2007 Info-ZIP. All rights reserved. 5 6 See the accompanying file LICENSE, version 2007-Mar-4 or later 7 (the contents of which are also included in zip.h) for terms of use. 8 If, for some reason, all these files are missing, the Info-ZIP license 9 also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html 10*/ 11/*++ 12 13Copyright (c) 1996 Scott Field 14 15Module Name: 16 17 nt.c (formerly nt_zip.c) 18 19Abstract: 20 21 This module implements WinNT security descriptor operations for the 22 Win32 Info-ZIP project. Operation such as querying file security, 23 using/querying local and remote privileges. The contents of this module 24 are only relevant when the code is running on Windows NT, and the target 25 volume supports persistent Acl storage. 26 27 User privileges that allow accessing certain privileged aspects of the 28 security descriptor (such as the Sacl) are only used if the user specified 29 to do so. 30 31 In the future, this module may be expanded to support storage of 32 OS/2 EA data, Macintosh resource forks, and hard links, which are all 33 supported by NTFS. 34 35Author: 36 37 Scott Field (sfield@microsoft.com) 27-Sep-96 38 39--*/ 40 41#include "../zip.h" 42 43#define WIN32_LEAN_AND_MEAN 44#include <windows.h> 45#ifdef __RSXNT__ 46# include "../win32/rsxntwin.h" 47#endif 48#include "../win32/nt.h" 49 50#ifdef NTSD_EAS /* This file is only needed for NTSD handling */ 51 52/* Borland C++ does not define FILE_SHARE_DELETE. Others also? */ 53#ifndef FILE_SHARE_DELETE 54# define FILE_SHARE_DELETE 0x00000004 55#endif 56 57/* This macro definition is missing in old versions of MS' winbase.h. */ 58#ifndef InterlockedExchangePointer 59# define InterlockedExchangePointer(Target, Value) \ 60 (PVOID)InterlockedExchange((PLONG)(Target), (LONG)(Value)) 61#endif 62 63/* private prototypes */ 64 65static BOOL Initialize(VOID); 66#if 0 /* currently unused */ 67static BOOL Shutdown(VOID); 68#endif 69static VOID GetRemotePrivilegesGet(CHAR *FileName, PDWORD dwRemotePrivileges); 70static VOID InitLocalPrivileges(VOID); 71 72 73BOOL bZipInitialized = FALSE; /* module level stuff initialized? */ 74HANDLE hZipInitMutex = NULL; /* prevent multiple initialization */ 75 76BOOL g_bBackupPrivilege = FALSE; /* for local get file security override */ 77BOOL g_bZipSaclPrivilege = FALSE; /* for local get sacl operations, only when 78 backup privilege not present */ 79 80/* our single cached volume capabilities structure that describes the last 81 volume root we encountered. A single entry like this works well in the 82 zip/unzip scenario for a number of reasons: 83 1. typically one extraction path during unzip. 84 2. typically process one volume at a time during zip, and then move 85 on to the next. 86 3. no cleanup code required and no memory leaks. 87 4. simple code. 88 89 This approach should be reworked to a linked list approach if we expect to 90 be called by many threads which are processing a variety of input/output 91 volumes, since lock contention and stale data may become a bottleneck. */ 92 93VOLUMECAPS g_VolumeCaps; 94CRITICAL_SECTION VolumeCapsLock; 95 96 97static BOOL Initialize(VOID) 98{ 99 HANDLE hMutex; 100 HANDLE hOldMutex; 101 102 if(bZipInitialized) return TRUE; 103 104 hMutex = CreateMutex(NULL, TRUE, NULL); 105 if(hMutex == NULL) return FALSE; 106 107 hOldMutex = (HANDLE)InterlockedExchangePointer((void *)&hZipInitMutex, 108 hMutex); 109 110 if(hOldMutex != NULL) { 111 /* somebody setup the mutex already */ 112 InterlockedExchangePointer((void *)&hZipInitMutex, 113 hOldMutex); 114 115 CloseHandle(hMutex); /* close new, un-needed mutex */ 116 117 /* wait for initialization to complete and return status */ 118 WaitForSingleObject(hOldMutex, INFINITE); 119 ReleaseMutex(hOldMutex); 120 121 return bZipInitialized; 122 } 123 124 /* initialize module level resources */ 125 126 InitializeCriticalSection( &VolumeCapsLock ); 127 memset(&g_VolumeCaps, 0, sizeof(VOLUMECAPS)); 128 129 InitLocalPrivileges(); 130 131 bZipInitialized = TRUE; 132 133 ReleaseMutex(hMutex); /* release correct mutex */ 134 135 return TRUE; 136} 137 138#if 0 /* currently not used ! */ 139static BOOL Shutdown(VOID) 140{ 141 /* really need to free critical sections, disable enabled privilges, etc, 142 but doing so brings up possibility of race conditions if those resources 143 are about to be used. The easiest way to handle this is let these 144 resources be freed when the process terminates... */ 145 146 return TRUE; 147} 148#endif /* never */ 149 150 151static VOID GetRemotePrivilegesGet(char *FileName, PDWORD dwRemotePrivileges) 152{ 153 HANDLE hFile; 154 155 *dwRemotePrivileges = 0; 156 157 /* see if we have the SeBackupPrivilege */ 158 159 hFile = CreateFileA( 160 FileName, 161 ACCESS_SYSTEM_SECURITY | GENERIC_READ | READ_CONTROL, 162 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 163 NULL, 164 OPEN_EXISTING, 165 FILE_FLAG_BACKUP_SEMANTICS, 166 NULL 167 ); 168 169 if(hFile != INVALID_HANDLE_VALUE) { 170 /* no remote way to determine SeBackupPrivilege -- just try a read 171 to simulate it */ 172 SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION; 173 PSECURITY_DESCRIPTOR sd; 174 DWORD cbBuf = 0; 175 176 GetKernelObjectSecurity(hFile, si, NULL, cbBuf, &cbBuf); 177 178 if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) { 179 if((sd = HeapAlloc(GetProcessHeap(), 0, cbBuf)) != NULL) { 180 if(GetKernelObjectSecurity(hFile, si, sd, cbBuf, &cbBuf)) { 181 *dwRemotePrivileges |= OVERRIDE_BACKUP; 182 } 183 HeapFree(GetProcessHeap(), 0, sd); 184 } 185 } 186 187 CloseHandle(hFile); 188 } else { 189 190 /* see if we have the SeSecurityPrivilege */ 191 /* note we don't need this if we have SeBackupPrivilege */ 192 193 hFile = CreateFileA( 194 FileName, 195 ACCESS_SYSTEM_SECURITY, 196 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* maximum sharing */ 197 NULL, 198 OPEN_EXISTING, 199 0, 200 NULL 201 ); 202 203 if(hFile != INVALID_HANDLE_VALUE) { 204 CloseHandle(hFile); 205 *dwRemotePrivileges |= OVERRIDE_SACL; 206 } 207 } 208} 209 210 211BOOL ZipGetVolumeCaps( 212 char *rootpath, /* filepath, or NULL */ 213 char *name, /* filename associated with rootpath */ 214 PVOLUMECAPS VolumeCaps /* result structure describing capabilities */ 215 ) 216{ 217 char TempRootPath[MAX_PATH + 1]; 218 DWORD cchTempRootPath = 0; 219 BOOL bSuccess = TRUE; /* assume success until told otherwise */ 220 221 if(!bZipInitialized) if(!Initialize()) return FALSE; 222 223 /* process the input path to produce a consistent path suitable for 224 compare operations and also suitable for certain picky Win32 API 225 that don't like forward slashes */ 226 227 if(rootpath != NULL && rootpath[0] != '\0') { 228 DWORD i; 229 230 cchTempRootPath = lstrlen(rootpath); 231 if(cchTempRootPath > MAX_PATH) return FALSE; 232 233 /* copy input, converting forward slashes to back slashes as we go */ 234 235 for(i = 0 ; i <= cchTempRootPath ; i++) { 236 if(rootpath[i] == '/') TempRootPath[i] = '\\'; 237 else TempRootPath[i] = rootpath[i]; 238 } 239 240 /* check for UNC and Null terminate or append trailing \ as appropriate */ 241 242 /* possible valid UNCs we are passed follow: 243 \\machine\foo\bar (path is \\machine\foo\) 244 \\machine\foo (path is \\machine\foo\) 245 \\machine\foo\ 246 \\.\c$\ (FIXFIX: Win32API doesn't like this - GetComputerName()) 247 LATERLATER: handling mounted DFS drives in the future will require 248 slightly different logic which isn't available today. 249 This is required because directories can point at 250 different servers which have differing capabilities. 251 */ 252 253 if(TempRootPath[0] == '\\' && TempRootPath[1] == '\\') { 254 DWORD slash = 0; 255 256 for(i = 2 ; i < cchTempRootPath ; i++) { 257 if(TempRootPath[i] == '\\') { 258 slash++; 259 260 if(slash == 2) { 261 i++; 262 TempRootPath[i] = '\0'; 263 cchTempRootPath = i; 264 break; 265 } 266 } 267 } 268 269 /* if there was only one slash found, just tack another onto the end */ 270 271 if(slash == 1 && TempRootPath[cchTempRootPath] != '\\') { 272 TempRootPath[cchTempRootPath] = TempRootPath[0]; /* '\' */ 273 TempRootPath[cchTempRootPath+1] = '\0'; 274 cchTempRootPath++; 275 } 276 277 } else { 278 279 if(TempRootPath[1] == ':') { 280 281 /* drive letter specified, truncate to root */ 282 TempRootPath[2] = '\\'; 283 TempRootPath[3] = '\0'; 284 cchTempRootPath = 3; 285 } else { 286 287 /* must be file on current drive */ 288 TempRootPath[0] = '\0'; 289 cchTempRootPath = 0; 290 } 291 292 } 293 294 } /* if path != NULL */ 295 296 /* grab lock protecting cached entry */ 297 EnterCriticalSection( &VolumeCapsLock ); 298 299 if(!g_VolumeCaps.bValid || lstrcmpi(g_VolumeCaps.RootPath, TempRootPath) != 0) { 300 301 /* no match found, build up new entry */ 302 303 DWORD dwFileSystemFlags; 304 DWORD dwRemotePrivileges = 0; 305 BOOL bRemote = FALSE; 306 307 /* release lock during expensive operations */ 308 LeaveCriticalSection( &VolumeCapsLock ); 309 310 bSuccess = GetVolumeInformation( 311 (TempRootPath[0] == '\0') ? NULL : TempRootPath, 312 NULL, 0, 313 NULL, NULL, 314 &dwFileSystemFlags, 315 NULL, 0); 316 317 /* only if target volume supports Acls, and we were told to use 318 privileges do we need to go out and test for the remote case */ 319 320 if(bSuccess && (dwFileSystemFlags & FS_PERSISTENT_ACLS) && VolumeCaps->bUsePrivileges) { 321 if(GetDriveType( (TempRootPath[0] == '\0') ? NULL : TempRootPath ) == DRIVE_REMOTE) { 322 bRemote = TRUE; 323 324 /* make a determination about our remote capabilities */ 325 326 GetRemotePrivilegesGet(name, &dwRemotePrivileges); 327 } 328 } 329 330 /* always take the lock again, since we release it below */ 331 EnterCriticalSection( &VolumeCapsLock ); 332 333 /* replace the existing data if successful */ 334 if(bSuccess) { 335 336 lstrcpynA(g_VolumeCaps.RootPath, TempRootPath, cchTempRootPath+1); 337 g_VolumeCaps.bProcessDefer = FALSE; 338 g_VolumeCaps.dwFileSystemFlags = dwFileSystemFlags; 339 g_VolumeCaps.bRemote = bRemote; 340 g_VolumeCaps.dwRemotePrivileges = dwRemotePrivileges; 341 g_VolumeCaps.bValid = TRUE; 342 } 343 } 344 345 if(bSuccess) { 346 /* copy input elements */ 347 g_VolumeCaps.bUsePrivileges = VolumeCaps->bUsePrivileges; 348 g_VolumeCaps.dwFileAttributes = VolumeCaps->dwFileAttributes; 349 350 /* give caller results */ 351 memcpy(VolumeCaps, &g_VolumeCaps, sizeof(VOLUMECAPS)); 352 } else { 353 g_VolumeCaps.bValid = FALSE; 354 } 355 356 LeaveCriticalSection( &VolumeCapsLock ); /* release lock */ 357 358 return bSuccess; 359} 360 361BOOL SecurityGet( 362 char *resource, 363 PVOLUMECAPS VolumeCaps, 364 unsigned char *buffer, 365 DWORD *cbBuffer 366 ) 367{ 368 HANDLE hFile; 369 DWORD dwDesiredAccess; 370 DWORD dwFlags; 371 PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)buffer; 372 SECURITY_INFORMATION RequestedInfo; 373 BOOL bBackupPrivilege = FALSE; 374 BOOL bSaclPrivilege = FALSE; 375 BOOL bSuccess = FALSE; 376 377 DWORD cchResourceLen; 378 379 if(!bZipInitialized) if(!Initialize()) return FALSE; 380 381 /* see if we are dealing with a directory */ 382 /* rely on the fact resource has a trailing [back]slash, rather 383 than calling expensive GetFileAttributes() */ 384 385 cchResourceLen = lstrlenA(resource); 386 387 if(resource[cchResourceLen-1] == '/' || resource[cchResourceLen-1] == '\\') 388 VolumeCaps->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; 389 390 /* setup privilege usage based on if told we can use privileges, and if so, 391 what privileges we have */ 392 393 if(VolumeCaps->bUsePrivileges) { 394 if(VolumeCaps->bRemote) { 395 /* use remotely determined privileges */ 396 if(VolumeCaps->dwRemotePrivileges & OVERRIDE_BACKUP) 397 bBackupPrivilege = TRUE; 398 399 if(VolumeCaps->dwRemotePrivileges & OVERRIDE_SACL) 400 bSaclPrivilege = TRUE; 401 } else { 402 /* use local privileges */ 403 bBackupPrivilege = g_bBackupPrivilege; 404 bSaclPrivilege = g_bZipSaclPrivilege; 405 } 406 } 407 408 /* always try to read the basic security information: Dacl, Owner, Group */ 409 410 dwDesiredAccess = READ_CONTROL; 411 412 RequestedInfo = OWNER_SECURITY_INFORMATION | 413 GROUP_SECURITY_INFORMATION | 414 DACL_SECURITY_INFORMATION; 415 416 /* if we have the SeBackupPrivilege or SeSystemSecurityPrivilege, read 417 the Sacl, too */ 418 419 if(bBackupPrivilege || bSaclPrivilege) { 420 dwDesiredAccess |= ACCESS_SYSTEM_SECURITY; 421 RequestedInfo |= SACL_SECURITY_INFORMATION; 422 } 423 424 dwFlags = 0; 425 426 /* if we have the backup privilege, specify that */ 427 /* opening a directory requires FILE_FLAG_BACKUP_SEMANTICS */ 428 429 if(bBackupPrivilege || (VolumeCaps->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) 430 dwFlags |= FILE_FLAG_BACKUP_SEMANTICS; 431 432 hFile = CreateFileA( 433 resource, 434 dwDesiredAccess, 435 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* maximum sharing */ 436 NULL, 437 OPEN_EXISTING, 438 dwFlags, 439 NULL 440 ); 441 442 if(hFile == INVALID_HANDLE_VALUE) return FALSE; 443 444 if(GetKernelObjectSecurity(hFile, RequestedInfo, sd, *cbBuffer, cbBuffer)) { 445 *cbBuffer = GetSecurityDescriptorLength( sd ); 446 bSuccess = TRUE; 447 } 448 449 CloseHandle(hFile); 450 451 return bSuccess; 452} 453 454static VOID InitLocalPrivileges(VOID) 455{ 456 HANDLE hToken; 457 TOKEN_PRIVILEGES tp; 458 459 /* try to enable some interesting privileges that give us the ability 460 to get some security information that we normally cannot. 461 462 note that enabling privileges is only relevant on the local machine; 463 when accessing files that are on a remote machine, any privileges 464 that are present on the remote machine get enabled by default. */ 465 466 if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) 467 return; 468 469 tp.PrivilegeCount = 1; 470 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 471 472 /* try to enable SeBackupPrivilege. 473 if this succeeds, we can read all aspects of the security descriptor */ 474 475 if(LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid)) { 476 if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) && 477 GetLastError() == ERROR_SUCCESS) g_bBackupPrivilege = TRUE; 478 } 479 480 /* try to enable SeSystemSecurityPrivilege if SeBackupPrivilege not present. 481 if this succeeds, we can read the Sacl */ 482 483 if(!g_bBackupPrivilege && 484 LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) { 485 486 if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) && 487 GetLastError() == ERROR_SUCCESS) g_bZipSaclPrivilege = TRUE; 488 } 489 490 CloseHandle(hToken); 491} 492#endif /* NTSD_EAS */ 493