str_file.c
来自「老外写的加密库cryptlib(版本3.1)」· C语言 代码 · 共 1,890 行 · 第 1/5 页
C
1,890 行
#ifdef EBCDIC_CHARS
char fileNameBuffer[ MAX_PATH_LENGTH ];
#endif /* EBCDIC_CHARS */
#ifdef USE_FCNTL_LOCKING
struct flock flockInfo;
#endif /* USE_FCNTL_LOCKING */
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;
/* If we're trying to write to the file, check whether we've got
permission to do so */
if( ( mode & FILE_WRITE ) && fileReadonly( fileName ) )
return( CRYPT_ERROR_PERMISSION );
#ifdef EBCDIC_CHARS
fileName = bufferToEbcdic( fileNameBuffer, fileName );
#endif /* EBCDIC_CHARS */
/* Defending against writing through links is somewhat difficult since
there's no atomic way to do this. What we do is lstat() the file,
open it as appropriate, and if it's an existing file ftstat() it and
compare various important fields to make sure that the file wasn't
changed between the lstat() and the open(). If everything is OK, we
then use the lstat() information to make sure that it isn't a symlink
(or at least that it's a normal file) and that the link count is 1.
These checks also catch other weird things like STREAMS stuff
fattach()'d over files. If these checks pass and the file already
exists we truncate it to mimic the effect of an open with create */
if( ( mode & FILE_RW_MASK ) == FILE_WRITE )
{
struct stat lstatInfo;
/* lstat() the file. If it doesn't exist, create it with O_EXCL. If
it does exist, open it for read/write and perform the fstat()
check */
if( lstat( fileName, &lstatInfo ) == -1 )
{
/* If the lstat() failed for reasons other than the file not
existing, return a file open error */
if( errno != ENOENT )
return( CRYPT_ERROR_OPEN );
/* The file doesn't exist, create it with O_EXCL to make sure
that an attacker can't slip in a file between the lstat() and
open() */
if( ( stream->fd = open( fileName, O_CREAT | O_EXCL | O_RDWR,
0600 ) ) == -1 )
return( CRYPT_ERROR_OPEN );
}
else
{
struct stat fstatInfo;
/* Open an existing file */
if( ( stream->fd = open( fileName, O_RDWR ) ) == -1 )
return( CRYPT_ERROR_OPEN );
/* fstat() the opened file and check that the file mode bits and
inode and device match */
if( fstat( stream->fd, &fstatInfo ) == -1 || \
lstatInfo.st_mode != fstatInfo.st_mode || \
lstatInfo.st_ino != fstatInfo.st_ino || \
lstatInfo.st_dev != fstatInfo.st_dev )
{
close( stream->fd );
return( CRYPT_ERROR_OPEN );
}
/* If the above check was passed, we know that the lstat() and
fstat() were done to the same file. Now check that there's
only one link, and that it's a normal file (this isn't
strictly necessary because the fstat() vs. lstat() st_mode
check would also find this) */
if( fstatInfo.st_nlink > 1 || !S_ISREG( lstatInfo.st_mode ) )
{
close( stream->fd );
return( CRYPT_ERROR_OPEN );
}
/* Turn the file into an empty file */
ftruncate( stream->fd, 0 );
}
}
else
{
/* Try and open the file */
if( ( stream->fd = open( fileName, openMode ) ) == -1 )
/* The open failed, determine whether it was because the file
doesn't exist or because we can't use that access mode */
return( ( access( fileName, 0 ) == -1 ) ? \
CRYPT_ERROR_NOTFOUND : CRYPT_ERROR_OPEN );
}
/* Set the file access permissions so that only the owner can access it */
if( mode & FILE_PRIVATE )
chmod( fileName, 0600 );
/* Lock the file if possible to make sure that no-one else tries to do
things to it. If available we used the (BSD-style) flock(), if not we
fall back to Posix fcntl() locking (both mechanisms are broken, but
flock() is less broken). fcntl() locking has two disadvantages over
flock():
1. Locking is per-process rather than per-thread (specifically it's
based on processes and inodes rather than flock()'s file table
entries, for which any new handles created via dup()/fork()/open()
all refer to the same file table entry so there's a single location
at which to handle locking), so another thread in the same process
could still access the file. Whether this is a good thing or not
is context-dependant: We want multiple threads to be able to read
from the file (if one keyset handle is shared among threads), but
not necessarily for multiple threads to be able to write. We could
if necessary use mutexes for per-thread lock synchronisation, but
this gets incredibly ugly since we then have to duplicate parts of
the the system file table with per-thread mutexes, mess around with
an fstat() on each file access to determine if we're accessing an
already-open file, wrap all that up in more mutexes, etc etc, as
well as being something that's symtomatic of a user application bug
rather than normal behaviour that we can defend against.
2. Closing *any* descriptor for an fcntl()-locked file releases *all*
locks on the file (!!) (one manpage appropriately describes this
behaviour as "the completely stupid semantics of System V and IEEE
Std 1003.1-1988 (= POSIX.1)"). In other words if two threads or
processes open an fcntl()-locked file for shared read access then
the first close of the file releases all locks on it. Since
fcntl() requires a file handle to work, the only way to determine
whether a file is locked requires opening it, but as soon as we
close it again (for example to abort the access if there's a lock
on it) all locks are released.
The downside of flock()-locking is that it doesn't usually work with
NFS unless special hacks have been applied. fcntl() passes lock
requests to rpc.lockd to handle, but this is its own type of mess
since it's often unreliable, so it's really not much worse than
flock(). In addition locking support under filesystems like AFS is
often nonexistant, with the lock apparently succeeding but no lock
actually being applied. Finally, locking is almost always advisory
only, but even mandatory locking can be bypassed by tricks such as
copying the original, unlinking it, and renaming the copy back to the
original (the unlinked - and still locked - original goes away once
the handle is closed) - this mechanism is standard practice for many
Unix utilities like text editors. In addition mandatory locking is
wierd in that an open for write (or read, on a write-locked file) will
succeed, it's only a later attempt to read/write that will fail.
This mess is why dotfile-locking is still so popular, but that's
probably going a bit far for simple keyset accesses */
#ifndef USE_FCNTL_LOCKING
if( flock( stream->fd, ( mode & FILE_EXCLUSIVE_ACCESS ) ? \
LOCK_EX | LOCK_NB : LOCK_SH | LOCK_NB ) == -1 && \
errno == EWOULDBLOCK )
{
close( stream->fd );
return( CRYPT_ERROR_PERMISSION );
}
#else
memset( &flockInfo, 0, sizeof( struct flock ) );
flockInfo.l_type = ( mode & FILE_EXCLUSIVE_ACCESS ) ? \
F_WRLCK : F_RDLCK;
flockInfo.l_whence = SEEK_SET;
flockInfo.l_start = flockInfo.l_len = 0;
if( fcntl( stream->fd, F_SETLK, flockInfo ) == -1 && \
( errno == EACCES || errno == EDEADLK ) )
{
/* Now we're in a bind. If we close the file and exit, the lock
we've just detected on the file is released (see the comment on
this utter braindamage above). OTOH if we don't close the file
we'll leak the file handle, which is bad for long-running
processes. Feedback from users indicates that leaking file
handles is less desirable than the possiblity of having the file
unlocked during an update (the former is a situation that occurs
far more frequently than the latter), so we close the handle and
hope that the update by the other process completes quickly */
close( stream->fd );
return( CRYPT_ERROR_PERMISSION );
}
#endif /* flock() vs. fcntl() locking */
return( CRYPT_OK );
}
#endif /* MVS USS special-case handling */
int sFileClose( STREAM *stream )
{
#ifdef USE_FCNTL_LOCKING
struct flock flockInfo;
#endif /* USE_FCNTL_LOCKING */
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( stream->type == STREAM_TYPE_FILE );
/* Unlock the file if necessary. If we're using fcntl() locking there's
no need to unlock the file since all locks are automatically released
as soon as any handle to it is closed (see the long comment above for
more on this complete braindamage) */
#ifndef USE_FCNTL_LOCKING
flock( stream->fd, LOCK_UN );
#endif /* !USE_FCNTL_LOCKING */
close( stream->fd );
zeroise( stream, sizeof( STREAM ) );
return( CRYPT_OK );
}
/* Read/write a block of data from/to a file stream */
int fileRead( STREAM *stream, void *buffer, const int length )
{
int bytesRead;
if( ( bytesRead = read( stream->fd, buffer, length ) ) == -1 )
return( CRYPT_ERROR_READ );
return( bytesRead );
}
int fileWrite( STREAM *stream, const void *buffer, const int length )
{
if( write( stream->fd, buffer, length ) != length )
return( CRYPT_ERROR_WRITE );
return( CRYPT_OK );
}
/* Commit data in a file stream to backing storage */
int fileFlush( STREAM *stream )
{
fsync( stream->fd );
return( CRYPT_OK );
}
/* Change the read/write position in a file */
int fileSeek( STREAM *stream, const long position )
{
#if defined( DDNAME_IO )
/* If we're using ddnames, we only seek if we're not already at the
start of the file to prevent postioning to 0 in a new empty PDS
member, which fails */
if( ( stream->bufCount > 0 || stream->bufPos > 0 || position > 0 ) )
/* Drop through */
#endif /* MVS USS special-case */
if( lseek( stream->fd, position, SEEK_SET ) == ( off_t ) -1 )
return( CRYPT_ERROR_WRITE );
return( CRYPT_OK );
}
/* Check whether a file is writeable */
BOOLEAN fileReadonly( const char *fileName )
{
#ifdef EBCDIC_CHARS
char fileNameBuffer[ MAX_PATH_LENGTH ];
fileName = bufferToEbcdic( fileNameBuffer, fileName );
#endif /* EBCDIC_CHARS */
#if defined( DDNAME_IO )
/* Requires a RACF check to determine this */
return( FALSE );
#else
if( access( fileName, W_OK ) == -1 && errno != ENOENT )
return( TRUE );
#endif /* OS-specific file accessibility check */
return( FALSE );
}
/* File deletion functions: Wipe a file from the current position to EOF,
and wipe and delete a file (although it's not terribly rigorous).
Vestigia nulla retrorsum */
static void eraseFile( const STREAM *stream, long position, long length )
{
/* Wipe the file. This is a fairly crude function that performs a
single pass of overwriting the data with random data, it's not
possible to do much better than this without getting terribly OS-
specific.
You'll NEVER get rid of me, Toddy */
while( length > 0 )
{
RESOURCE_DATA msgData;
BYTE buffer[ 1024 ];
const int bytesToWrite = min( length, 1024 );
/* We need to make sure that we fill the buffer with random data for
each write, otherwise compressing filesystems will just compress
it to nothing */
setMessageData( &msgData, buffer, bytesToWrite );
krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
if( write( stream->fd, buffer, bytesToWrite ) <= bytesToWrite )
break; /* An error occurred while writing, exit */
length -= bytesToWrite;
}
fsync( stream->fd );
ftruncate( stream->fd, position );
}
void fileClearToEOF( const STREAM *stream )
{
struct stat fstatInfo;
long position, length;
assert( isReadPtr( stream, sizeof( STREAM ) ) );
assert( stream->type == STREAM_TYPE_FILE );
/* Wipe everything past the current position in the file */
if( fstat( stream->fd, &fstatInfo ) == -1 )
return;
position = lseek( stream->fd, 0, SEEK_CUR );
length = fstatInfo.st_size - position;
if( length <= 0 )
return; /* Nothing to do, exit */
eraseFile( stream, position, length );
}
void fileErase( const char *fileName )
{
STREAM stream;
struct stat fstatInfo;
#ifndef __APPLE__
struct utimbuf timeStamp;
#endif /* OS-specific variable declarations */
#ifdef EBCDIC_CHARS
char fileNameBuffer[ MAX_PATH_LENGTH ];
#endif /* EBCDIC_CHARS */
int length, status;
assert( fileName != NULL );
#ifdef EBCDIC_CHARS
fileName = bufferToEbcdic( fileNameBuffer, fileName );
#endif /* EBCDIC_CHARS */
/* Try and open the file so that we can erase it. If this fails, the
best that we can do is a straight unlink */
status = sFileOpen( &stream, fileName,
FILE_READ | FILE_WRITE | FILE_EXCLUSIVE_ACCESS );
if( cryptStatusError( status ) )
{
unlink( fileName );
return;
}
/* Determine the size of the file and erase it */
if( fstat( stream.fd, &fstatInfo ) == 0 )
eraseFile( &stream, 0, fstatInfo.st_size );
/* Reset the time stamps and delete the file */
sFileClose( &stream );
#ifdef __APPLE__
utimes( fileName, NULL );
#else
memset( &timeStamp, 0, sizeof( struct utimbuf ) );
utime( fileName, &timeStamp );
#endif /* OS-specific size and date-mangling */
unlink( fileName );
}
/* Build the path to a file in the cryptlib directory */
#include <pwd.h>
void fileBuildCryptlibPath( char *path, const char *fileName,
const BOOLEAN createPath )
{
struct passwd *passwd;
int length;
#ifdef EBCDIC_CHARS
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?