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]

jan.nijtmans 2013-12-13 13:05 trunk
Commit 0b0eb52c07065e18d6c1591861746e0a662ff27e
1 file changed +202 -1
+202 -1
--- src/file.c
+++ src/file.c
@@ -318,12 +318,213 @@
318318
/*
319319
** Wrapper around the access() system call.
320320
*/
321321
int file_access(const char *zFilename, int flags){
322322
#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;
323336
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
+ }
325526
#else
326527
char *zMbcs = fossil_utf8_to_filename(zFilename);
327528
int rc = access(zMbcs, flags);
328529
#endif
329530
fossil_filename_free(zMbcs);
330531
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button