📄 str_file.c
字号:
/****************************************************************************
* *
* File Stream I/O Functions *
* Copyright Peter Gutmann 1993-2003 *
* *
****************************************************************************/
#if defined( __UNIX__ ) && defined( __linux__ )
/* In order for the fileReadonly() check to work we need to be able to
check errno, however for this to work the headers that specify that
threading is being used must be the first headers included
(specifically, the include order has to be pthread.h, unistd.h,
everything else) or errno.h, which is pulled in by stdlib.h, gets
set up as an extern int rather than a function */
#include "crypt.h"
#endif /* Older Linux broken include-file dependencies */
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
#include "stream.h"
#elif defined( INC_CHILD )
#include "stream.h"
#else
#include "misc/stream.h"
#endif /* Compiler-specific includes */
#if defined( __BEOS__ ) || defined( __SYMBIAN32__ ) || \
defined( __TANDEMOSS__ ) || defined( __UNIX__ )
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#if !( defined( __APPLE__ ) || defined( __BEOS__ ) || \
defined( __bsdi__ ) || defined( __CYGWIN__ ) || \
defined( __FreeBSD__ ) || defined( __hpux ) || \
defined( __linux__ ) || defined( _M_XENIX ) || \
defined( __MVS__ ) || defined( __OpenBSD__ ) || \
( defined( sun ) && OSVERSION == 4 ) || \
defined ( __SYMBIAN32__ ) )
#include <sys/mode.h>
#endif /* Vaguely non-SYSV-ish systems */
#include <unistd.h>
#if defined( _AIX ) || defined( __alpha__ ) || defined( __BEOS__ ) || \
defined( __bsdi__ ) || defined( __FreeBSD__ ) || \
defined( __linux__ ) || defined( _MPRAS ) || defined( __MVS__ ) || \
defined( _M_XENIX ) || defined( __OpenBSD__ ) || \
defined( __osf__ ) || defined( __SCO_VERSION__ ) || defined( sun )
#include <utime.h> /* It's a SYSV thing... */
#endif /* SYSV Unixen */
#ifdef __APPLE__
#include <sys/time.h>
#endif /* OS X */
#ifdef __CYGWIN__
#include <sys/utime.h>
#endif /* __CYGWIN__ */
#if defined( _AIX ) || defined( __BEOS__ ) || defined( __CYGWIN__ ) || \
defined( __hpux ) || defined( _MPRAS ) || defined( __MVS__ ) || \
defined( _M_XENIX ) || defined( __SCO_VERSION__ ) || \
( defined( sun ) && ( OSVERSION >= 5 ) )
#define USE_FCNTL_LOCKING
/* By default we try and use flock()-locking, if this isn't available we
fall back to fcntl() locking (see the long comment further on).
Actually Slowaris does have flock(), but there are lots of warnings
in the manpage about using it only on BSD platforms, and the result
won't work with any of the system libraries. SunOS did support it
without any problems, it's only Slowaris that breaks it. In addition
UnixWare (== SCO) supports something called flockfile() but this only
provides thread-level locking that isn't useful */
#endif /* Some older SYSV-ish systems */
#if ( defined( _M_XENIX ) && ( OSVERSION == 3 ) )
#define ftruncate( a, b ) chsize( a, b )
#endif /* SCO */
#if defined( __CYGWIN__ )
#define LOCK_SH 1
#define LOCK_EX 2
#define LOCK_NB 4
#define LOCK_UN 8
#endif /* Cygwin */
#elif defined( __AMIGA__ )
#include <proto/dos.h>
#elif defined( __MSDOS16__ ) || defined( __WIN16__ )
#include <io.h>
#elif defined( __OS2__ )
#define INCL_DOSFILEMGR /* DosQueryPathInfo(),DosSetFileSize(),DosSetPathInfo */
#define INCL_DOSMISC /* DosQuerySysInfo() */
#include <os2.h> /* FILESTATUS */
#include <io.h>
#elif defined( __IBM4758__ )
#include <scc_err.h>
#include <scc_int.h>
#elif defined( __TANDEMNSK__ )
#include <errno.h>
#elif defined( __MAC__ )
#include <Script.h>
#if defined __MWERKS__
#pragma mpwc_relax off
#pragma extended_errorcheck on
#endif
#endif /* OS-specific includes and defines */
/* In order to get enhanced control over things like file security and
buffering we can't use stdio but have to rely on using OS-level file
routines, which is essential for working with things like ACL's for
sensitive files and forcing disk writes for files we want to erase.
Without the forced disk write the data in the cache doesn't get flushed
before the file delete request arrives, after which it's discarded rather
than being written, so the file never gets overwritten. In addition some
embedded environments don't support stdio so we have to supply our own
alternatives.
When implementing the following for new systems there are certain things
that you need to ensure to guarantee error-free operation:
- File permissions should be set as indicated by the file open flags.
- File sharing controls (shared vs. exclusive access locks) should be
implemented.
- If the file is locked for exclusive access, the open call should either
block until the lock is released (they're never held for more than a
fraction of a second) or return CRYPT_ERROR_TIMEOUT depending on how
the OS handles locks */
/****************************************************************************
* *
* Windows File Stream Functions *
* *
****************************************************************************/
#if defined( __WIN32__ )
/* File flags to use when accessing a file and attributes to use when
creating a file. For access we tell the OS that we'll be reading the
file sequentially, for creation we prevent the OS from groping around
inside the file */
#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
#endif /* VC++ <= 6.0 */
#define FILE_FLAGS FILE_FLAG_SEQUENTIAL_SCAN
#define FILE_ATTRIBUTES FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
/* Older versions of the Windows SDK don't include the defines for system
directories so we define them ourselves if necesary */
#ifndef CSIDL_PERSONAL
#define CSIDL_PERSONAL 0x05 /* 'My Documents' */
#define CSIDL_APPDATA 0x1A /* '<luser name>/Application Data' */
#endif /* !CSIDL_PERSONAL */
#ifndef CSIDL_FLAG_CREATE
#define CSIDL_FLAG_CREATE 0x8000 /* Force directory creation */
#endif /* !CSIDL_FLAG_CREATE */
#ifndef SHGFP_TYPE_CURRENT
#define SHGFP_TYPE_CURRENT 0
#endif /* !SHGFP_TYPE_CURRENT */
/* Check whether a user's SID is known to a server providing a network
share, so that we can set file ACLs based on it */
#define TOKEN_BUFFER_SIZE 256
#define UNI_BUFFER_SIZE ( 256 + _MAX_PATH )
#define PATH_BUFFER_SIZE ( _MAX_PATH + 16 )
static BOOLEAN checkUserKnown( const char *fileName )
{
HANDLE hToken;
BYTE uniBuffer[ UNI_BUFFER_SIZE ], tokenBuffer[ TOKEN_BUFFER_SIZE ];
char pathBuffer[ PATH_BUFFER_SIZE ], nameBuffer[ PATH_BUFFER_SIZE ];
char domainBuffer[ PATH_BUFFER_SIZE ], *fileNamePtr;
UNIVERSAL_NAME_INFO *nameInfo = ( UNIVERSAL_NAME_INFO * ) uniBuffer;
TOKEN_USER *pTokenUser = ( TOKEN_USER * ) tokenBuffer;
SID_NAME_USE eUse;
BOOLEAN isMappedDrive = FALSE, tokenOK = FALSE, retVal;
int uniBufSize = UNI_BUFFER_SIZE, nameBufSize = PATH_BUFFER_SIZE;
int domainBufSize = PATH_BUFFER_SIZE, serverNameLength;
assert( sizeof( UNIVERSAL_NAME_INFO ) + _MAX_PATH <= UNI_BUFFER_SIZE );
/* Win95 doesn't have any ACL-based security, there's nothing to do */
if( isWin95 )
return( TRUE );
/* Canonicalise the path name. This turns relative paths into absolute
ones and converts forward to backwards slashes. The latter is
necessary because while the Windows filesystem functions will accept
Unix-style forward slashes in paths, the WNetGetUniversalName()
networking function doesn't */
if( GetFullPathName( fileName, PATH_BUFFER_SIZE, pathBuffer,
&fileNamePtr ) )
fileName = pathBuffer;
/* If the path is too short to contain a drive letter or UNC path, it
must be local */
if( strlen( fileName ) <= 2 )
return( TRUE );
/* If there's a drive letter present, check whether it's a local or
remote drive. GetDriveType() is rather picky about what it'll accept
so we have to extract just the drive letter from the path */
if( fileName[ 1 ] == ':' )
{
char drive[ 8 ];
memcpy( drive, fileName, 2 );
drive[ 2 ] = '\0';
if( GetDriveType( drive ) != DRIVE_REMOTE )
/* It's a local drive, the user should be known */
return( TRUE );
isMappedDrive = TRUE;
}
else
/* If it's not a UNC name, it's local (or something weird like a
mapped web page to which we shouldn't be writing keys anyway) */
if( memcmp( fileName, "\\\\", 2 ) )
return( TRUE );
/* If it's a mapped network drive, get the name in UNC form. What to do
in case of failure is a bit tricky. If we get here we know that it's
a network share, but if there's some problem mapping it to a UNC (the
usual reason for this will be that there's a problem with the network
and the share is a cached remnant of a persistent connection), all we
can do is fail safe and hope that the user is known */
if( isMappedDrive )
{
typedef DWORD ( WINAPI *WNETGETUNIVERSALNAMEA )( LPCSTR lpLocalPath,
DWORD dwInfoLevel, LPVOID lpBuffer,
LPDWORD lpBufferSize );
WNETGETUNIVERSALNAMEA pWNetGetUniversalNameA;
HINSTANCE hMPR;
BOOLEAN loadedMPR = FALSE, gotUNC = FALSE;
if( ( hMPR = GetModuleHandle( "Mpr.dll" ) ) == NULL )
{
hMPR = LoadLibrary( "Mpr.dll" );
loadedMPR = TRUE;
}
if( hMPR == NULL )
/* Should never happen, we can't have a mapped network drive if
no network is available */
return( TRUE ); /* Default fail-safe */
/* Get the translated UNC name. The UNIVERSAL_NAME_INFO struct is
one of those variable-length ones where the lpUniversalName
member points to extra data stored off the end of the struct, so
we overlay it onto a much larger buffer */
pWNetGetUniversalNameA = ( WNETGETUNIVERSALNAMEA ) \
GetProcAddress( hMPR, "WNetGetUniversalNameA" );
if( pWNetGetUniversalNameA != NULL && \
pWNetGetUniversalNameA( fileName, UNIVERSAL_NAME_INFO_LEVEL,
nameInfo, &uniBufSize ) == NO_ERROR )
{
fileName = nameInfo->lpUniversalName;
gotUNC = TRUE;
}
if( loadedMPR )
FreeLibrary( hMPR );
if( !gotUNC )
return( TRUE ); /* Default fail-safe */
}
assert( !memcmp( fileName, "\\\\", 2 ) );
/* We've got the network share in UNC form, extract the server name. If
for some reason the name is still an absolute path, the following will
convert it to "x:\", which is fine */
for( serverNameLength = 2; \
fileName[ serverNameLength ] && fileName[ serverNameLength ] != '\\'; \
serverNameLength++ );
memmove( pathBuffer, fileName, serverNameLength );
memcpy( pathBuffer + serverNameLength, "\\", 2 );
/* Check whether the current user's SID is known to the server */
if( OpenThreadToken( GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken ) || \
OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ) )
{
DWORD cbTokenUser;
tokenOK = GetTokenInformation( hToken, TokenUser, pTokenUser,
TOKEN_BUFFER_SIZE, &cbTokenUser );
CloseHandle( hToken );
}
if( !tokenOK )
return( TRUE ); /* Default fail-safe */
retVal = LookupAccountSid( pathBuffer, pTokenUser->User.Sid,
nameBuffer, &nameBufSize,
domainBuffer, &domainBufSize, &eUse );
if( !retVal && GetLastError() == ERROR_NONE_MAPPED )
/* The user with this SID isn't known to the server */
return( FALSE );
/* Either the user is known to the server or it's a fail-safe */
return( TRUE );
}
/* Open/close a file stream */
int sFileOpen( STREAM *stream, const char *fileName, const int mode )
{
HANDLE hFile;
UINT uErrorMode;
void *aclInfo = NULL;
int status = CRYPT_OK;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( fileName != NULL );
assert( mode != 0 );
/* Initialise the stream structure */
memset( stream, 0, sizeof( STREAM ) );
stream->type = STREAM_TYPE_FILE;
if( ( mode & FILE_RW_MASK ) == FILE_READ )
stream->flags = STREAM_FLAG_READONLY;
/* Don't allow escapes to disable path parsing, and make sure that the
path has a sensible length. The latter is both to avoid possible
overflows in the Windows filesystem functions and because some of the
filesystem checks need to copy the name into a fixed-size temporary
buffer when they canonicalise it */
if( !strncmp( fileName, "\\\\?\\", 4 ) )
return( CRYPT_ERROR_OPEN );
if( strlen( fileName ) > _MAX_PATH )
return( CRYPT_ERROR_OPEN );
/* If we're creating the file and we don't want others to get to it, set
up the security attributes to reflect this if the OS supports it.
Unfortunately creating the file with ACLs doesn't always work when
the file is located on a network share because what's:
create file, ACL = user SID access
on a local drive can become:
create file, ACL = <unknown SID> access
on the network share if the user is accessing it as a member of a
group and their individual SID isn't known to the server. As a
result, they can't read the file that they've just created. To get
around this, we need to perform an incredibly convoluted check (via
checkUserKnown()) to see whether the path is a network path and if
so, if the user is known to the server providing the network share */
if( !isWin95 && ( mode & FILE_WRITE ) && ( mode & FILE_PRIVATE ) && \
checkUserKnown( fileName ) && \
( aclInfo = initACLInfo( FILE_GENERIC_READ | \
FILE_GENERIC_WRITE ) ) == NULL )
return( CRYPT_ERROR_OPEN );
/* Check that the file isn't a special file type, for example a device
pseudo-file that can crash the system under Win95/98/ME/whatever */
hFile = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAGS, NULL );
if( hFile != INVALID_HANDLE_VALUE )
{
const DWORD type = GetFileType( hFile );
CloseHandle( hFile );
if( type != FILE_TYPE_DISK )
{
freeACLInfo( aclInfo );
return( CRYPT_ERROR_OPEN );
}
}
/* Try and open the file */
uErrorMode = SetErrorMode( SEM_FAILCRITICALERRORS );
if( ( mode & FILE_RW_MASK ) == FILE_WRITE )
{
BOOLEAN isNetworkShare = FALSE;
/* If we're creating the file, we need to remove any existing file
of the same name before we try and create a new one, otherwise
the OS will pick up the permissions for the existing file and
apply them to the new one. This is safe because if an attacker
tries to slip in a wide-open file between the delete and the
create, we'll get a file-already-exists status returned that we
can trap and turn into an error */
DeleteFile( fileName );
stream->hFile = CreateFile( fileName, GENERIC_READ | GENERIC_WRITE, 0,
getACLInfo( aclInfo ), CREATE_ALWAYS,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -