Fossil SCM
Better implementation of file_access() for win32: The function _waccess cannot handle long paths, and lies too much (e.g. when handling specific smb drives). Implementation borrowed from Tcl 8.6: [http://core.tcl.tk/tcl/artifact/c6b5d4f8d7?ln=1510-1756]
Commit
0b0eb52c07065e18d6c1591861746e0a662ff27e
Parent
2cb54f3981b7f00…
1 file changed
+202
-1
+202
-1
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -318,12 +318,213 @@ | ||
| 318 | 318 | /* |
| 319 | 319 | ** Wrapper around the access() system call. |
| 320 | 320 | */ |
| 321 | 321 | int file_access(const char *zFilename, int flags){ |
| 322 | 322 | #ifdef _WIN32 |
| 323 | + SECURITY_DESCRIPTOR *sdPtr = NULL; | |
| 324 | + unsigned long size; | |
| 325 | + PSID pSid = 0; | |
| 326 | + BOOL SidDefaulted; | |
| 327 | + SID_IDENTIFIER_AUTHORITY samba_unmapped = {{0, 0, 0, 0, 0, 22}}; | |
| 328 | + GENERIC_MAPPING genMap; | |
| 329 | + HANDLE hToken = NULL; | |
| 330 | + DWORD desiredAccess = 0, grantedAccess = 0; | |
| 331 | + BOOL accessYesNo = FALSE; | |
| 332 | + PRIVILEGE_SET privSet; | |
| 333 | + DWORD privSetSize = sizeof(PRIVILEGE_SET); | |
| 334 | + int rc = 0; | |
| 335 | + DWORD attr; | |
| 323 | 336 | wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); |
| 324 | - int rc = _waccess(zMbcs, flags); | |
| 337 | + | |
| 338 | + attr = GetFileAttributesW(zMbcs); | |
| 339 | + | |
| 340 | + if( attr==INVALID_FILE_ATTRIBUTES ){ | |
| 341 | + /* | |
| 342 | + * File might not exist. | |
| 343 | + */ | |
| 344 | + | |
| 345 | + if( GetLastError()!=ERROR_SHARING_VIOLATION ){ | |
| 346 | + fossil_filename_free(zMbcs); | |
| 347 | + return -1; | |
| 348 | + } | |
| 349 | + } | |
| 350 | + | |
| 351 | + if( flags==F_OK ){ | |
| 352 | + /* | |
| 353 | + * File exists, nothing else to check. | |
| 354 | + */ | |
| 355 | + | |
| 356 | + fossil_filename_free(zMbcs); | |
| 357 | + return 0; | |
| 358 | + } | |
| 359 | + | |
| 360 | + if( (flags & W_OK) | |
| 361 | + && (attr & FILE_ATTRIBUTE_READONLY) | |
| 362 | + && !(attr & FILE_ATTRIBUTE_DIRECTORY) ){ | |
| 363 | + /* | |
| 364 | + * The attributes say the file is not writable. If the file is a | |
| 365 | + * regular file (i.e., not a directory), then the file is not | |
| 366 | + * writable, full stop. For directories, the read-only bit is | |
| 367 | + * (mostly) ignored by Windows, so we can't ascertain anything about | |
| 368 | + * directory access from the attrib data. However, if we have the | |
| 369 | + * advanced 'getFileSecurityProc', then more robust ACL checks | |
| 370 | + * will be done below. | |
| 371 | + */ | |
| 372 | + | |
| 373 | + fossil_filename_free(zMbcs); | |
| 374 | + return -1; | |
| 375 | + } | |
| 376 | + | |
| 377 | + /* | |
| 378 | + * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, | |
| 379 | + * we have a more complex permissions structure so we try to check that. | |
| 380 | + * The code below is remarkably complex for such a simple thing as finding | |
| 381 | + * what permissions the OS has set for a file. | |
| 382 | + */ | |
| 383 | + | |
| 384 | + /* | |
| 385 | + * First find out how big the buffer needs to be. | |
| 386 | + */ | |
| 387 | + | |
| 388 | + size = 0; | |
| 389 | + GetFileSecurityW(zMbcs, | |
| 390 | + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | |
| 391 | + | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, | |
| 392 | + 0, 0, &size); | |
| 393 | + | |
| 394 | + /* | |
| 395 | + * Should have failed with ERROR_INSUFFICIENT_BUFFER | |
| 396 | + */ | |
| 397 | + | |
| 398 | + if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ | |
| 399 | + /* | |
| 400 | + * Most likely case is ERROR_ACCESS_DENIED, which we will convert | |
| 401 | + * to EACCES - just what we want! | |
| 402 | + */ | |
| 403 | + | |
| 404 | + fossil_filename_free(zMbcs); | |
| 405 | + return -1; | |
| 406 | + } | |
| 407 | + | |
| 408 | + /* | |
| 409 | + * Now size contains the size of buffer needed. | |
| 410 | + */ | |
| 411 | + | |
| 412 | + sdPtr = (SECURITY_DESCRIPTOR *) HeapAlloc(GetProcessHeap(), 0, size); | |
| 413 | + | |
| 414 | + if( sdPtr == NULL ){ | |
| 415 | + goto accessError; | |
| 416 | + } | |
| 417 | + | |
| 418 | + /* | |
| 419 | + * Call GetFileSecurity() for real. | |
| 420 | + */ | |
| 421 | + | |
| 422 | + if( !GetFileSecurityW(zMbcs, | |
| 423 | + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | |
| 424 | + | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, | |
| 425 | + sdPtr, size, &size) ){ | |
| 426 | + /* | |
| 427 | + * Error getting owner SD | |
| 428 | + */ | |
| 429 | + | |
| 430 | + goto accessError; | |
| 431 | + } | |
| 432 | + | |
| 433 | + /* | |
| 434 | + * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are | |
| 435 | + * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the | |
| 436 | + * top-level authority. If the file owner and group is unmapped then | |
| 437 | + * the ACL access check below will only test against world access, | |
| 438 | + * which is likely to be more restrictive than the actual access | |
| 439 | + * restrictions. Since the ACL tests are more likely wrong than | |
| 440 | + * right, skip them. Moreover, the unix owner access permissions are | |
| 441 | + * usually mapped to the Windows attributes, so if the user is the | |
| 442 | + * file owner then the attrib checks above are correct (as far as they | |
| 443 | + * go). | |
| 444 | + */ | |
| 445 | + | |
| 446 | + if( !GetSecurityDescriptorOwner(sdPtr,&pSid,&SidDefaulted) || | |
| 447 | + memcmp(GetSidIdentifierAuthority(pSid),&samba_unmapped, | |
| 448 | + sizeof(SID_IDENTIFIER_AUTHORITY))==0 ){ | |
| 449 | + HeapFree(GetProcessHeap(), 0, sdPtr); | |
| 450 | + fossil_filename_free(zMbcs); | |
| 451 | + return 0; /* Attrib tests say access allowed. */ | |
| 452 | + } | |
| 453 | + | |
| 454 | + /* | |
| 455 | + * Perform security impersonation of the user and open the resulting | |
| 456 | + * thread token. | |
| 457 | + */ | |
| 458 | + | |
| 459 | + if( !ImpersonateSelf(SecurityImpersonation) ){ | |
| 460 | + /* | |
| 461 | + * Unable to perform security impersonation. | |
| 462 | + */ | |
| 463 | + | |
| 464 | + goto accessError; | |
| 465 | + } | |
| 466 | + if( !OpenThreadToken(GetCurrentThread(), | |
| 467 | + TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken) ){ | |
| 468 | + /* | |
| 469 | + * Unable to get current thread's token. | |
| 470 | + */ | |
| 471 | + | |
| 472 | + goto accessError; | |
| 473 | + } | |
| 474 | + | |
| 475 | + RevertToSelf(); | |
| 476 | + | |
| 477 | + /* | |
| 478 | + * Setup desiredAccess according to the access priveleges we are | |
| 479 | + * checking. | |
| 480 | + */ | |
| 481 | + | |
| 482 | + if( flags & R_OK ){ | |
| 483 | + desiredAccess |= FILE_GENERIC_READ; | |
| 484 | + } | |
| 485 | + if( flags & W_OK){ | |
| 486 | + desiredAccess |= FILE_GENERIC_WRITE; | |
| 487 | + } | |
| 488 | + | |
| 489 | + memset(&genMap, 0x0, sizeof(GENERIC_MAPPING)); | |
| 490 | + genMap.GenericRead = FILE_GENERIC_READ; | |
| 491 | + genMap.GenericWrite = FILE_GENERIC_WRITE; | |
| 492 | + genMap.GenericExecute = FILE_GENERIC_EXECUTE; | |
| 493 | + genMap.GenericAll = FILE_ALL_ACCESS; | |
| 494 | + | |
| 495 | + /* | |
| 496 | + * Perform access check using the token. | |
| 497 | + */ | |
| 498 | + | |
| 499 | + if( !AccessCheck(sdPtr, hToken, desiredAccess, | |
| 500 | + &genMap, &privSet, &privSetSize, &grantedAccess, | |
| 501 | + &accessYesNo) ){ | |
| 502 | + /* | |
| 503 | + * Unable to perform access check. | |
| 504 | + */ | |
| 505 | + | |
| 506 | + accessError: | |
| 507 | + if( sdPtr != NULL ){ | |
| 508 | + HeapFree(GetProcessHeap(), 0, sdPtr); | |
| 509 | + } | |
| 510 | + if( hToken != NULL ){ | |
| 511 | + CloseHandle(hToken); | |
| 512 | + } | |
| 513 | + fossil_filename_free(zMbcs); | |
| 514 | + return -1; | |
| 515 | + } | |
| 516 | + | |
| 517 | + /* | |
| 518 | + * Clean up. | |
| 519 | + */ | |
| 520 | + | |
| 521 | + HeapFree(GetProcessHeap(), 0, sdPtr); | |
| 522 | + CloseHandle(hToken); | |
| 523 | + if( !accessYesNo ){ | |
| 524 | + rc = -1; | |
| 525 | + } | |
| 325 | 526 | #else |
| 326 | 527 | char *zMbcs = fossil_utf8_to_filename(zFilename); |
| 327 | 528 | int rc = access(zMbcs, flags); |
| 328 | 529 | #endif |
| 329 | 530 | fossil_filename_free(zMbcs); |
| 330 | 531 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -318,12 +318,213 @@ | |
| 318 | /* |
| 319 | ** Wrapper around the access() system call. |
| 320 | */ |
| 321 | int file_access(const char *zFilename, int flags){ |
| 322 | #ifdef _WIN32 |
| 323 | wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); |
| 324 | int rc = _waccess(zMbcs, flags); |
| 325 | #else |
| 326 | char *zMbcs = fossil_utf8_to_filename(zFilename); |
| 327 | int rc = access(zMbcs, flags); |
| 328 | #endif |
| 329 | fossil_filename_free(zMbcs); |
| 330 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -318,12 +318,213 @@ | |
| 318 | /* |
| 319 | ** Wrapper around the access() system call. |
| 320 | */ |
| 321 | int file_access(const char *zFilename, int flags){ |
| 322 | #ifdef _WIN32 |
| 323 | SECURITY_DESCRIPTOR *sdPtr = NULL; |
| 324 | unsigned long size; |
| 325 | PSID pSid = 0; |
| 326 | BOOL SidDefaulted; |
| 327 | SID_IDENTIFIER_AUTHORITY samba_unmapped = {{0, 0, 0, 0, 0, 22}}; |
| 328 | GENERIC_MAPPING genMap; |
| 329 | HANDLE hToken = NULL; |
| 330 | DWORD desiredAccess = 0, grantedAccess = 0; |
| 331 | BOOL accessYesNo = FALSE; |
| 332 | PRIVILEGE_SET privSet; |
| 333 | DWORD privSetSize = sizeof(PRIVILEGE_SET); |
| 334 | int rc = 0; |
| 335 | DWORD attr; |
| 336 | wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); |
| 337 | |
| 338 | attr = GetFileAttributesW(zMbcs); |
| 339 | |
| 340 | if( attr==INVALID_FILE_ATTRIBUTES ){ |
| 341 | /* |
| 342 | * File might not exist. |
| 343 | */ |
| 344 | |
| 345 | if( GetLastError()!=ERROR_SHARING_VIOLATION ){ |
| 346 | fossil_filename_free(zMbcs); |
| 347 | return -1; |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | if( flags==F_OK ){ |
| 352 | /* |
| 353 | * File exists, nothing else to check. |
| 354 | */ |
| 355 | |
| 356 | fossil_filename_free(zMbcs); |
| 357 | return 0; |
| 358 | } |
| 359 | |
| 360 | if( (flags & W_OK) |
| 361 | && (attr & FILE_ATTRIBUTE_READONLY) |
| 362 | && !(attr & FILE_ATTRIBUTE_DIRECTORY) ){ |
| 363 | /* |
| 364 | * The attributes say the file is not writable. If the file is a |
| 365 | * regular file (i.e., not a directory), then the file is not |
| 366 | * writable, full stop. For directories, the read-only bit is |
| 367 | * (mostly) ignored by Windows, so we can't ascertain anything about |
| 368 | * directory access from the attrib data. However, if we have the |
| 369 | * advanced 'getFileSecurityProc', then more robust ACL checks |
| 370 | * will be done below. |
| 371 | */ |
| 372 | |
| 373 | fossil_filename_free(zMbcs); |
| 374 | return -1; |
| 375 | } |
| 376 | |
| 377 | /* |
| 378 | * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, |
| 379 | * we have a more complex permissions structure so we try to check that. |
| 380 | * The code below is remarkably complex for such a simple thing as finding |
| 381 | * what permissions the OS has set for a file. |
| 382 | */ |
| 383 | |
| 384 | /* |
| 385 | * First find out how big the buffer needs to be. |
| 386 | */ |
| 387 | |
| 388 | size = 0; |
| 389 | GetFileSecurityW(zMbcs, |
| 390 | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
| 391 | | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, |
| 392 | 0, 0, &size); |
| 393 | |
| 394 | /* |
| 395 | * Should have failed with ERROR_INSUFFICIENT_BUFFER |
| 396 | */ |
| 397 | |
| 398 | if( GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ |
| 399 | /* |
| 400 | * Most likely case is ERROR_ACCESS_DENIED, which we will convert |
| 401 | * to EACCES - just what we want! |
| 402 | */ |
| 403 | |
| 404 | fossil_filename_free(zMbcs); |
| 405 | return -1; |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | * Now size contains the size of buffer needed. |
| 410 | */ |
| 411 | |
| 412 | sdPtr = (SECURITY_DESCRIPTOR *) HeapAlloc(GetProcessHeap(), 0, size); |
| 413 | |
| 414 | if( sdPtr == NULL ){ |
| 415 | goto accessError; |
| 416 | } |
| 417 | |
| 418 | /* |
| 419 | * Call GetFileSecurity() for real. |
| 420 | */ |
| 421 | |
| 422 | if( !GetFileSecurityW(zMbcs, |
| 423 | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
| 424 | | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, |
| 425 | sdPtr, size, &size) ){ |
| 426 | /* |
| 427 | * Error getting owner SD |
| 428 | */ |
| 429 | |
| 430 | goto accessError; |
| 431 | } |
| 432 | |
| 433 | /* |
| 434 | * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are |
| 435 | * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the |
| 436 | * top-level authority. If the file owner and group is unmapped then |
| 437 | * the ACL access check below will only test against world access, |
| 438 | * which is likely to be more restrictive than the actual access |
| 439 | * restrictions. Since the ACL tests are more likely wrong than |
| 440 | * right, skip them. Moreover, the unix owner access permissions are |
| 441 | * usually mapped to the Windows attributes, so if the user is the |
| 442 | * file owner then the attrib checks above are correct (as far as they |
| 443 | * go). |
| 444 | */ |
| 445 | |
| 446 | if( !GetSecurityDescriptorOwner(sdPtr,&pSid,&SidDefaulted) || |
| 447 | memcmp(GetSidIdentifierAuthority(pSid),&samba_unmapped, |
| 448 | sizeof(SID_IDENTIFIER_AUTHORITY))==0 ){ |
| 449 | HeapFree(GetProcessHeap(), 0, sdPtr); |
| 450 | fossil_filename_free(zMbcs); |
| 451 | return 0; /* Attrib tests say access allowed. */ |
| 452 | } |
| 453 | |
| 454 | /* |
| 455 | * Perform security impersonation of the user and open the resulting |
| 456 | * thread token. |
| 457 | */ |
| 458 | |
| 459 | if( !ImpersonateSelf(SecurityImpersonation) ){ |
| 460 | /* |
| 461 | * Unable to perform security impersonation. |
| 462 | */ |
| 463 | |
| 464 | goto accessError; |
| 465 | } |
| 466 | if( !OpenThreadToken(GetCurrentThread(), |
| 467 | TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken) ){ |
| 468 | /* |
| 469 | * Unable to get current thread's token. |
| 470 | */ |
| 471 | |
| 472 | goto accessError; |
| 473 | } |
| 474 | |
| 475 | RevertToSelf(); |
| 476 | |
| 477 | /* |
| 478 | * Setup desiredAccess according to the access priveleges we are |
| 479 | * checking. |
| 480 | */ |
| 481 | |
| 482 | if( flags & R_OK ){ |
| 483 | desiredAccess |= FILE_GENERIC_READ; |
| 484 | } |
| 485 | if( flags & W_OK){ |
| 486 | desiredAccess |= FILE_GENERIC_WRITE; |
| 487 | } |
| 488 | |
| 489 | memset(&genMap, 0x0, sizeof(GENERIC_MAPPING)); |
| 490 | genMap.GenericRead = FILE_GENERIC_READ; |
| 491 | genMap.GenericWrite = FILE_GENERIC_WRITE; |
| 492 | genMap.GenericExecute = FILE_GENERIC_EXECUTE; |
| 493 | genMap.GenericAll = FILE_ALL_ACCESS; |
| 494 | |
| 495 | /* |
| 496 | * Perform access check using the token. |
| 497 | */ |
| 498 | |
| 499 | if( !AccessCheck(sdPtr, hToken, desiredAccess, |
| 500 | &genMap, &privSet, &privSetSize, &grantedAccess, |
| 501 | &accessYesNo) ){ |
| 502 | /* |
| 503 | * Unable to perform access check. |
| 504 | */ |
| 505 | |
| 506 | accessError: |
| 507 | if( sdPtr != NULL ){ |
| 508 | HeapFree(GetProcessHeap(), 0, sdPtr); |
| 509 | } |
| 510 | if( hToken != NULL ){ |
| 511 | CloseHandle(hToken); |
| 512 | } |
| 513 | fossil_filename_free(zMbcs); |
| 514 | return -1; |
| 515 | } |
| 516 | |
| 517 | /* |
| 518 | * Clean up. |
| 519 | */ |
| 520 | |
| 521 | HeapFree(GetProcessHeap(), 0, sdPtr); |
| 522 | CloseHandle(hToken); |
| 523 | if( !accessYesNo ){ |
| 524 | rc = -1; |
| 525 | } |
| 526 | #else |
| 527 | char *zMbcs = fossil_utf8_to_filename(zFilename); |
| 528 | int rc = access(zMbcs, flags); |
| 529 | #endif |
| 530 | fossil_filename_free(zMbcs); |
| 531 |