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 + -
显示快捷键?