AuthOverflowFile.mm   [plain text]


/*
 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#import <TargetConditionals.h>
#import <unistd.h>
#import <sys/stat.h>
#import "AuthOverflowFile.h"
#import "PSUtilitiesDefs.h"
#import "CKerberosPrincipal.h"

#define kFixedDESKey					"1POTATO2potato3PotatoFOUR"

@implementation AuthOverflowFile

-(id)init
{
	self = [super init];
	
	mOverflowPath = strdup( kPWDirPath );

	return self;
}

-(id)initWithUTF8Path:(const char *)inOverflowPath
{
	self = [super init];
	
	mOverflowPath = strdup( inOverflowPath );
	
	return self;
}


-(void)dealloc
{
	if ( mOverflowPath != NULL )
		free( mOverflowPath );
	
	[super dealloc];
}


-free
{
    [self release];
    return 0;
}


-(void)pwWait
{
}


-(void)pwSignal
{
}


-(FILE *)fopenOverflow:(const char *)path mode:(const char *)mode
{
	return fopen(path, mode);
}


-(int)fcloseOverflow:(FILE *)filePtr
{
	return fclose(filePtr);
}


-(int)getPasswordRecFromSpillBucket:(PWFileEntry *)inOutRec unObfuscate:(BOOL)unObfuscate
{
	PWFileEntry recBuff;
	off_t offset = 0;
	off_t byteCount;
	FILE *fp;
	int err = -1;
	char uidStr[35] = {0,};
	char buff[35] = {0,};
	
	if ( inOutRec == NULL )
		return -1;
	
	err = [self openOverflowFile:inOutRec create:NO fileDesc:&fp filePath:NULL];
	if ( err != 0 )
		return err;
	
	// still not found
	err = -1;
	
	// use text-based matching to avoid endian problems
	pwsf_passwordRecRefToString( inOutRec, uidStr );
	
	do
	{
		byteCount = pread( fileno(fp), buff, sizeof(buff), offset );
		if ( byteCount >= 34 && strncmp( uidStr, buff, 34 ) == 0 )
		{
			// found it
			byteCount = pread( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
			if ( byteCount > 0 )
			{
				pwsf_EndianAdjustPWFileEntry( &recBuff, 1 );
				
				// recover the password
				if ( unObfuscate )
					pwsf_DESAutoDecode( recBuff.passwordStr );
				
				// copy the record
				memcpy( inOutRec, &recBuff, sizeof(PWFileEntry) );
				
				// zero our copy
				bzero( &recBuff, sizeof(recBuff) );
				err = 0;
			}
			break;
		}
		
		offset += sizeof(PWFileEntry) + 34;
	}
	while ( byteCount == sizeof(uidStr) );
	
	[self fcloseOverflow:fp];
	
	return err;
}


//------------------------------------------------------------------------------------------------
//	saveOverflowRecord
//
//	Returns: -1, errno, or 0 for success
//
//	Updates and existing record in the overflow bucket
//------------------------------------------------------------------------------------------------

-(int)saveOverflowRecord:(PWFileEntry *)inPasswordRec obfuscate:(BOOL)obfuscate setModDate:(BOOL)setModDate
{
	off_t offset = 0;
	off_t byteCount;
	FILE *fp = NULL;
	int err = -1;
	char uidStr[35] = {0,};
	char buff[35] = {0,};
	int fileNumber = 0;
	size_t encodeLen;
    size_t writeCount;
    char *filePath = NULL;
	bool bad = false;
	PWFileEntry passRec;

	if ( inPasswordRec == NULL )
		return -1;
	
	if ( inPasswordRec->slot <= 0 )
    	return -1;	
	
	if ( mReadOnlyFileSystem )
		return -1;
	
	err = [self openOverflowFile:inPasswordRec create:YES fileDesc:&fp filePath:&filePath];
	if ( err != 0 )
		return err;
	if ( fp == NULL || filePath == NULL ) {
		err = -1;
		goto done;
	}
	
	fileNumber = [self getFileNumberFromPath:filePath];
	
	// use text-based matching to avoid endian problems
	pwsf_passwordRecRefToString( inPasswordRec, uidStr );
	
	if ( setModDate )
		pwsf_getGMTime( &inPasswordRec->modificationDate );
	
	encodeLen = strlen(inPasswordRec->passwordStr);
	encodeLen += (kFixedDESChunk - (encodeLen % kFixedDESChunk));	
	if ( encodeLen > sizeof(inPasswordRec->passwordStr) )
		encodeLen = sizeof(inPasswordRec->passwordStr);
	
	if ( obfuscate )
		pwsf_DESEncode(inPasswordRec->passwordStr, encodeLen);
	
	err = -1;
	do
	{
		byteCount = pread( fileno(fp), buff, sizeof(buff), offset );
		if ( byteCount == 0 )
			break;
		bad = (byteCount != sizeof(buff));
		if ( !bad )
		{
			pwsf_stringToPasswordRecRef( buff, &passRec );
			bad = (fileNumber >= 0 && [self simpleHash:&passRec] != fileNumber);
		}
		
		if ( bad )
		{
			[self pwWait];
			if ( offset > 0 ) {
				ftruncate( fileno(fp), offset );
			}
			else {
				[self fcloseOverflow:fp];
				fp = NULL;
				unlink( filePath );
				free( filePath );
				filePath = NULL;
			}
			[self pwSignal];
			
			err = -1;
			break;
		}
		
		if ( byteCount >= 34 && strncmp( uidStr, buff, 34 ) == 0 )
		{
			// found it
#if TARGET_RT_LITTLE_ENDIAN
			memcpy( &passRec, inPasswordRec, sizeof(PWFileEntry) );
			pwsf_EndianAdjustPWFileEntry( &passRec, 0 );
			byteCount = pwrite( fileno(fp), &passRec, sizeof(PWFileEntry), offset+34 );
			bzero( &passRec, sizeof(PWFileEntry) );
#else
			byteCount = pwrite( fileno(fp), inPasswordRec, sizeof(PWFileEntry), offset+34 );
#endif
			err = 0;
			break;
		}
		
		offset += 34+sizeof(PWFileEntry);
	}
	while ( byteCount == sizeof(buff) );

	// if not found, append the new record
	if ( err == -1 )
	{
		// if the file was damaged, it may be gone
		if ( fp == NULL )
		{
			err = [self openOverflowFile:inPasswordRec create:YES fileDesc:&fp filePath:&filePath];
			if ( err != 0 )
				goto done;
			if ( fp == NULL || filePath == NULL ) {
				err = -1;
				goto done;
			}
		}
		
		[self pwWait];
		
		err = fseek( fp, 0, SEEK_END );
		if ( err == 0 )
		{
			offset = ftell( fp );
			byteCount = offset % (34 + sizeof(PWFileEntry));
			if ( byteCount != 0 )
			{
				#if DEBUG
				errmsg( kOverflowInvalidLengthRepairMsg, filePath );
				#endif
				ftruncate( fileno(fp), offset - byteCount );
			}
			
			writeCount = fwrite( uidStr, 34, 1, fp );
			if ( writeCount != 1 )
				err = -1;
		
			if ( err == 0 )
			{
#if TARGET_RT_LITTLE_ENDIAN
				memcpy( &passRec, inPasswordRec, sizeof(PWFileEntry) );
				pwsf_EndianAdjustPWFileEntry( &passRec, 0 );
				writeCount = fwrite( &passRec, sizeof(PWFileEntry), 1, fp );
				bzero( &passRec, sizeof(PWFileEntry) );
#else
				writeCount = fwrite( inPasswordRec, sizeof(PWFileEntry), 1, fp );
#endif
				if ( writeCount != 1 ) {
					err = -1;
					
					// We don't know what happened (disk full error?)
					// However, we must make sure the file is the correct length
					offset = ftell( fp );
					byteCount = offset % (34 + sizeof(PWFileEntry));
					if ( byteCount != 0 )
					{
						#if DEBUG
						errmsg( kOverflowInvalidLengthRepairMsg, filePath );
						#endif
						ftruncate( fileno(fp), offset - byteCount );
					}
				}
			}
			fflush( fp );
		}
		[self pwSignal];
	}
	
	if ( obfuscate )
		pwsf_DESDecode(inPasswordRec->passwordStr, encodeLen);
	
done:
	if ( fp != NULL )
		[self fcloseOverflow:fp];
	
	if ( filePath != NULL )
		free( filePath );
	
	return err;
}


//------------------------------------------------------------------------------------------------
//	DeleteSlot
//------------------------------------------------------------------------------------------------

-(int)deleteSlot:(PWFileEntry *)inPasswordRec
{
	off_t offset = 0;
	off_t byteCount;
	FILE *fp;
	int err = -1;
	char uidStr[35];
	char idBuff[35];
	struct stat sb;
	ssize_t copyLen;
	unsigned char *buff = NULL;
	
	if ( inPasswordRec == NULL || inPasswordRec->slot <= 0 || mReadOnlyFileSystem )
		return -1;
	
	err = [self openOverflowFile:inPasswordRec create:NO fileDesc:&fp filePath:NULL];
	if ( err != 0 )
		return err;
	
	[self pwWait];
	err = fstat( fileno(fp), &sb );
	if ( err != 0 ) {
		[self fcloseOverflow:fp];
		#if DEBUG
		errmsg( "fstat() failed reading overflow file (err = %d).", err );
		#endif
		[self pwSignal];
		return err;
	}
	
	// use text-based matching to avoid endian problems
	pwsf_passwordRecRefToString( inPasswordRec, uidStr );
	
	err = -1;
	do
	{
		byteCount = pread( fileno(fp), idBuff, sizeof(idBuff), offset );
		if ( byteCount >= 34 && strncmp(uidStr, idBuff, 34) == 0 )
		{
			// found it
			#if DEBUG
			srvmsg( "deleting id: %s", uidStr);
			#endif
			copyLen = (ssize_t)sb.st_size - (ssize_t)offset - 34 - sizeof(PWFileEntry);
			if ( copyLen > 0 ) 
			{
				buff = (unsigned char *) malloc( copyLen );
				if ( buff != NULL )
				{
					byteCount = pread( fileno(fp), buff, copyLen, offset + 34 + sizeof(PWFileEntry) );
					if ( byteCount == copyLen )
					{
						byteCount = pwrite( fileno(fp), buff, copyLen, offset );
						if ( byteCount == copyLen )
							ftruncate( fileno(fp), sb.st_size - 34 - sizeof(PWFileEntry) );
					}
					free( buff );
					err = 0;
				}
			}
			else
			{
				if ( sb.st_size == 34 + sizeof(PWFileEntry) )
				{
					char overflowFileName[50];
					char overflowPath[1024];
					
					[self getOverflowFileName:overflowFileName forRec:inPasswordRec];
					sprintf( overflowPath, "%s/%s", kPWDirPath, overflowFileName );
					[self fcloseOverflow:fp];
					fp = NULL;
					unlink( overflowPath );
				}
				else
				{
					ftruncate( fileno(fp), sb.st_size - 34 - sizeof(PWFileEntry) );
				}
			}
			break;
		}
		
		offset += 34 + sizeof(PWFileEntry);
	}
	while ( byteCount == sizeof(idBuff) );
	
	[self pwSignal];
	
	[self fcloseOverflow:fp];
	
	return err;
}


//------------------------------------------------------------------------------------------------
//	AddOverflowToSyncFile
//------------------------------------------------------------------------------------------------

-(void)addOverflowToSyncFile:(FILE *)inSyncFile
	afterDate:(time_t)inAfterDate
	timeSkew:(long)inTimeSkew
	numUpdated:(long *)outNumRecordsUpdated
{
	CFMutableArrayRef fileListArray = NULL;
	CFIndex index, flCount;
	CFStringRef filePathString = NULL;
	off_t offset = 0;
	off_t byteCount;
	FILE *fp;
	time_t theTime;
	size_t writeCount;
	PWFileEntry recBuff;
	const char *filePathPtr = NULL;
	PWSFKerberosPrincipal* kerberosRec;
	int zeroLen = 0;
	
	if ( [self getOverflowFileList:&fileListArray customPath:NULL] )
	{
		flCount = CFArrayGetCount( fileListArray );
		for (index = 0; index < flCount; index++)
		{
			filePathString = (CFStringRef) CFArrayGetValueAtIndex( fileListArray, index );
			filePathPtr = CFStringGetCStringPtr( filePathString, kCFStringEncodingUTF8 );
			if ( filePathPtr == NULL )
				continue;
			
			fp = [self fopenOverflow:filePathPtr mode:(mReadOnlyFileSystem ? "r" : "r+")];
			if ( fp == NULL && errno == EROFS )
			{
				mReadOnlyFileSystem = YES;
				fp = [self fopenOverflow:filePathPtr mode:"r"];
			}
			if ( fp == NULL )
				continue;
			
			offset = 0;
			do
			{
				// Because every record gets evaluated for sync, there is no reason to
				// check the string form of the ID (first 34 chars).
				byteCount = pread( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
				
				// adjust time skew for comparison purposes. The record itself is
				// adjusted on the processing side.
				theTime = BSDTimeStructCopy_timegm( &recBuff.modificationDate ) + inTimeSkew;
				
				if ( theTime >= inAfterDate )
				{
					writeCount = fwrite( &recBuff, sizeof(recBuff), 1, inSyncFile );
					if ( writeCount != 1 )
						break;
					
					if ( recBuff.digest[4].digest[0] != '\0' )
					{
						char principalName[600] = {0,};
						strcpy(principalName, recBuff.usernameStr);
						strcat(principalName, "@");
						strcat(principalName, recBuff.digest[4].digest);
						PWSFKerberosPrincipal::ReadPrincipalFromDB(principalName, &kerberosRec);
					}
					else
						kerberosRec = NULL;
			
					if (kerberosRec != NULL)
						writeCount = kerberosRec->WritePrincipalToFile(inSyncFile);
					else
						writeCount = fwrite( &zeroLen, sizeof(zeroLen), 1, inSyncFile );

					if ( outNumRecordsUpdated != NULL )
						(*outNumRecordsUpdated)++;
				}
				
				offset += 34 + sizeof(PWFileEntry);
			}
			while ( byteCount == sizeof(recBuff) );
			
			[self fcloseOverflow:fp];
		}
		
		CFRelease( fileListArray );
	}
}


//------------------------------------------------------------------------------------------------
//	GetUserRecordFromName
//
//	Returns: 1 if found, 0 if not.
//
//	Only looks at the usernameStr for the user's short name.
//	Use GetUserRecordFromPrincipal to get the correct name for Kerberos.
//------------------------------------------------------------------------------------------------

-(int)getUserRecord:(PWFileEntry *)inOutUserRec fromName:(const char *)inName
{
	return [self doActionForAllOverflowFiles:kOverflowActionGetFromName principal:inName userRec:inOutUserRec purgeBefore:0];
}


//------------------------------------------------------------------------------------------------
//	GetUserRecordFromPrincipal
//
//	Returns: 1 if found, 0 if not.
//------------------------------------------------------------------------------------------------

-(int)getUserRecord:(PWFileEntry *)inOutUserRec fromPrincipal:(const char *)inPrincipal
{
	return [self doActionForAllOverflowFiles:kOverflowActionGetFromPrincipal principal:inPrincipal userRec:inOutUserRec purgeBefore:0];
}


-(void)requireNewPasswordForAllAccounts:(BOOL)inRequired
{
	[self doActionForAllOverflowFiles:inRequired ? kOverflowActionRequireNewPassword : kOverflowActionDoNotRequireNewPassword
		principal:NULL userRec:NULL purgeBefore:0];
}


-(void)kerberizeOrNewPassword
{
	[self doActionForAllOverflowFiles:kOverflowActionKerberizeOrNewPassword principal:NULL userRec:NULL purgeBefore:0];
}


//------------------------------------------------------------------------------------------------
//	DoActionForAllOverflowFiles
//
//	RETURNS: 1 if (inAction==kOverflowActionGetFromPrincipal && userRecord==found), else 0
//------------------------------------------------------------------------------------------------

-(int)doActionForAllOverflowFiles:(OverflowAction)inAction
	principal:(const char *)inPrincipal
	userRec:(PWFileEntry *)inOutUserRec
	purgeBefore:(time_t)beforeSecs
{
	CFMutableArrayRef fileListArray = NULL;
	CFStringRef filePathString = NULL;
	CFIndex index, flCount;
	off_t offset = 0;
	off_t byteCount = 0;
	FILE *fp = NULL;
	PWFileEntry recBuff = {0};
	int returnValue = 0;
	int fileNumber = -1;
	bool repairedAFile = false;
	char *thePrincDomain = NULL;
	long len = 0;
	bool bad = false;
	//unsigned long encodeLen = 0;
	char filePathStr[PATH_MAX] = {0};
	char thePrincName[256] = {0,};
	
	if ( mReadOnlyFileSystem )
	{
		if ( inAction == kOverflowActionRequireNewPassword ||
			 inAction == kOverflowActionDoNotRequireNewPassword ||
			 inAction == kOverflowActionKerberizeOrNewPassword ||
			 inAction == kOverflowActionPurgeDeadSlots )
		{
			return 0;
		}
	}
	
	// prep work
	if ( inAction == kOverflowActionGetFromPrincipal )
	{
		if ( inPrincipal == NULL || inOutUserRec == NULL )
			return 0;
		
		// break principal into name and domain
		thePrincDomain = strchr( inPrincipal, '@' );
		if ( thePrincDomain == NULL )
			return 0;
		
		// must have a principal name
		len = thePrincDomain - inPrincipal;
		if ( len == 0 )
			return 0;
		
		// advance past the '@'
		thePrincDomain++;
		
		// save the name as a c-str
		strlcpy( thePrincName, inPrincipal, len + 1 );
	}
	
	if ( [self getOverflowFileList:&fileListArray customPath:NULL] )
	{
		flCount = CFArrayGetCount( fileListArray );
		for ( index = 0; index < flCount; index++ )
		{
			filePathString = (CFStringRef) CFArrayGetValueAtIndex( fileListArray, index );
			if ( !CFStringGetCString(filePathString, filePathStr, sizeof(filePathStr), kCFStringEncodingUTF8) )
				continue;
			
			fp = [self fopenOverflow:filePathStr mode:(mReadOnlyFileSystem ? "r" : "r+")];
			if ( fp == NULL && errno == EROFS )
			{
				mReadOnlyFileSystem = true;
				
				if ( inAction == kOverflowActionRequireNewPassword ||
					 inAction == kOverflowActionDoNotRequireNewPassword ||
					 inAction == kOverflowActionKerberizeOrNewPassword )
					break;
				
				fp = [self fopenOverflow:filePathStr mode:"r"];
			}
			if ( fp == NULL )
				continue;
			
			fileNumber = [self getFileNumberFromPath:filePathStr];
			
			offset = 0;
			do
			{
				byteCount = pread( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
				if ( byteCount == 0 )
					break;
				
				// file integrity check
				bad = (byteCount != sizeof(recBuff));
				if ( !bad )
				{
					pwsf_EndianAdjustPWFileEntry( &recBuff, 1 );
					bad = (fileNumber >= 0 && [self simpleHash:&recBuff] != fileNumber);
				}
				if ( bad )
				{
					if ( offset > 0 ) {
						#if DEBUG
						errmsg( kOverflowInvalidRepairMsg, filePathStr );
						#endif
						[self pwWait];
						ftruncate( fileno(fp), offset );
						[self pwSignal];
					}
					else {
						[self fcloseOverflow:fp];
						fp = NULL;
						#if DEBUG
						errmsg( kOverflowInvalidRemoveMsg, filePathStr );
						#endif
						unlink( filePathStr );
					}
					repairedAFile = true;
					break;
				}
				
				if ( byteCount == sizeof(recBuff) )
				{
					switch( inAction )
					{
						case kOverflowActionRequireNewPassword:
							recBuff.access.newPasswordRequired = 1;
							pwrite( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
							break;
							
						case kOverflowActionDoNotRequireNewPassword:
							recBuff.access.newPasswordRequired = 0;
							pwrite( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
							break;
						
						case kOverflowActionGetFromName:
							if ( strcmp( inPrincipal, recBuff.usernameStr ) == 0 )
							{
								memcpy( inOutUserRec, &recBuff, sizeof(PWFileEntry) );
								returnValue = 1;
								break;
							}
							break;
						
						case kOverflowActionGetFromPrincipal:
							if ( strcmp( thePrincName, pwsf_GetPrincName(&recBuff) ) == 0 )
							{
								memcpy( inOutUserRec, &recBuff, sizeof(PWFileEntry) );
								returnValue = 1;
								break;
							}
							break;
						
						case kOverflowActionKerberizeOrNewPassword:
							if ( recBuff.access.passwordIsHash )
							{
								// new password required
								recBuff.access.newPasswordRequired = 1;
								pwrite( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
							}
							else
							{
								#if 0
								// un-obfuscate
								pwsf_DESAutoDecode( recBuff.passwordStr );
								if ( AddPrincipal(pwsf_GetPrincName(&recBuff), recBuff.passwordStr, recBuff.digest[kPWHashSlotKERBEROS].digest, sizeof(recBuff.digest[kPWHashSlotKERBEROS].digest)) )
								{
									if ( recBuff.digest[kPWHashSlotKERBEROS].digest[0] != '\0' )
									{
										strcpy( recBuff.digest[kPWHashSlotKERBEROS].method, "KerberosRealmName" );
										strcpy( recBuff.digest[kPWHashSlotKERBEROS_NAME].method, "KerberosPrincName" );
										strcpy( recBuff.digest[kPWHashSlotKERBEROS_NAME].digest, pwsf_GetPrincName(&recBuff) );
										
										// re-obfuscate
										encodeLen = strlen( recBuff.passwordStr );
										encodeLen += (kFixedDESChunk - (encodeLen % kFixedDESChunk));	
										if ( encodeLen > sizeof(recBuff.passwordStr) )
											encodeLen = sizeof(recBuff.passwordStr);
										pwsf_DESEncode( recBuff.passwordStr, encodeLen );
										
										// write
										pwrite( fileno(fp), (char *)&recBuff, sizeof(recBuff), offset+34 );
									}
									else
									{
										recBuff.digest[kPWHashSlotKERBEROS].method[0] = 0;
									}
								}
								else
								{
									srvmsg( "Could not add kerberos principal %s.", pwsf_GetPrincName(&recBuff) );
								}
								#endif
							}
							break;
						
						case kOverflowActionDumpRecords:
							{
								char idStr[35] = {0,};
								char modDateStr[256] = {0,};
								time_t secs;
								struct tm localTime;
								
								pwsf_passwordRecRefToString( &recBuff, idStr );
								secs = BSDTimeStructCopy_timegm( &recBuff.modificationDate );
								localtime_r( &secs, &localTime );
								strftime( modDateStr, sizeof(modDateStr), "%m/%d/%Y %r", &localTime );
								printf( "overflow %.2ld: %s %15s\t%s\n", [self simpleHash:&recBuff], idStr, recBuff.usernameStr, modDateStr );
							}
							break;
						
						case kOverflowActionPurgeDeadSlots:
							if ( recBuff.extraAccess.recordIsDead )
							{
								time_t deleteSecs = BSDTimeStructCopy_timegm( &recBuff.modDateOfPassword );
								if ( difftime(deleteSecs, beforeSecs) < 0 )
									[self deleteSlot:&recBuff];
							}
							break;
					}
					bzero( &recBuff, sizeof(recBuff) );
				}
				
				offset += 34 + sizeof(PWFileEntry);
			}
			while ( byteCount == sizeof(recBuff) );
			
			[self fcloseOverflow:fp];
		}
		
		CFRelease( fileListArray );
	}
	
	return returnValue;
}


//------------------------------------------------------------------------------------------------
//	GetOverflowFileList
//
//	RETURNS: TRUE if the list is retrieved and contains at least one overflow file
//------------------------------------------------------------------------------------------------

-(BOOL)getOverflowFileList:(CFMutableArrayRef *)outFileArray customPath:(const char *)inCustomPath
{
	int result;
	CFMutableArrayRef fileArray = NULL;
	const char *pathPtr = mOverflowPath;
	
	if ( outFileArray == nil )
		return -1;
	*outFileArray = nil;
	
	if ( inCustomPath != NULL )
		pathPtr = inCustomPath;
	
	result = pwsf_EnumerateDirectory( pathPtr, kOverflowFilePrefix, &fileArray );
	if ( result == 0 )
	{
		if ( CFArrayGetCount( fileArray ) == 0 )
		{
			CFRelease( fileArray );
			result = -1;
		}
	}
	
	if ( result == 0 )
		*outFileArray = fileArray;
	return (result == 0);
}


//------------------------------------------------------------------------------------------------
//	openOverflowFile
//------------------------------------------------------------------------------------------------

-(int)openOverflowFile:(PWFileEntry *)inPasswordRec create:(BOOL)create fileDesc:(FILE **)outFP filePath:(char **)outFilePath
{
	char overflowFileName[50] = {0,};
	char overflowPath[1024] = {0,};
	FILE *fp;
	int err = -1;
	struct stat sb;
	
	if ( inPasswordRec == NULL || outFP == NULL )
		return -1;
	
	*outFP = NULL;
	if ( outFilePath != NULL )
		*outFilePath = NULL;
	
	[self getOverflowFileName:overflowFileName forRec:inPasswordRec];
	snprintf( overflowPath, sizeof(overflowPath), "%s/%s", mOverflowPath, overflowFileName );
	
	[self pwWait];
	if ( create && lstat(overflowPath, &sb) != 0 )
	{
		// For file creation, don't use the wrapper methods.
		fp = fopen( overflowPath, "w" );
		if ( fp != NULL )
			fclose( fp );
		else
		{
			[self pwSignal];
			return -1;
		}
	}
	
	fp = [self fopenOverflow:overflowPath mode:(mReadOnlyFileSystem ? "r" : "r+")];
	if ( fp == NULL && errno == EROFS )
	{
		mReadOnlyFileSystem = YES;
		fp = [self fopenOverflow:overflowPath mode:"r"];
	}
	[self pwSignal];
	
	if ( fp == NULL )
	{
		err = errno;
		if ( err == 0 )
			err = -1;
		return err;
	}
	
	*outFP = fp;
	if ( outFilePath != NULL )
		*outFilePath = strdup( overflowPath );
	
	return 0;
}


//------------------------------------------------------------------------------------------------
//	GetFileNumberFromPath
//------------------------------------------------------------------------------------------------

-(int)getFileNumberFromPath:(const char *)inFilePath
{
	int fileNumber = -1;
	const char *numberInPathPtr = NULL;
	
	if ( inFilePath != NULL ) {
		numberInPathPtr = strstr( inFilePath, kOverflowFilePrefix );
		if ( numberInPathPtr != NULL ) {
			numberInPathPtr += sizeof( kOverflowFilePrefix );
			sscanf( numberInPathPtr, "%d", &fileNumber );
		}
	}
	
	return fileNumber;
}


//------------------------------------------------------------------------------------------------
//	getOverflowFileName
//
//	Returns: void
//	outFileName		<- 		The name of the file that contains the overflow for a given slot
//
//	The user ID overflows are put into multiple files as an optimization because we are doing
//	sequential searching.
//------------------------------------------------------------------------------------------------

-(void)getOverflowFileName:(char *)outFileName forRec:(PWFileEntry *)inPasswordRec;
{
	if ( outFileName == NULL )
		return;
	
	if ( inPasswordRec == NULL ) {
		strcpy( outFileName, kOverflowFilePrefix".exception" );
		return;
	}
	
	sprintf( outFileName, "%s.%lu", kOverflowFilePrefix, [self simpleHash:inPasswordRec] );
}


//------------------------------------------------------------------------------------------------
//	simpleHash
//------------------------------------------------------------------------------------------------

-(long)simpleHash:(PWFileEntry *)inPasswordRec
{
	return ( (inPasswordRec->time ^ inPasswordRec->rnd) * (inPasswordRec->sequenceNumber + inPasswordRec->slot) ) % 100;
}



@end