/*
	$Id: CAccount.cpp,v 1.1 2003/04/20 23:45:33 dasenbro Exp $

	File:		CAccount.cpp

	Contains:	C++ implementation of account objects

	Version:	Apple Mail Server - Mac OS X :  $Revision: 1.1 $

	Written by:	Nick Brosnahan

	Copyright:	© 1996-2001 by Apple Computer, Inc., all rights reserved.

 	NOT_FOR_OPEN_SOURCE <to be reevaluated at a later time>

	File Ownership:
		DRI:				David M. O'Rourke
		Other Contact:		Michael Dasenbrock & Doug Scott
		Technology:			AppleShare IP

	Change History:

		$Log: CAccount.cpp,v $
		Revision 1.1  2003/04/20 23:45:33  dasenbro
		Initial check-in.
		
		Revision 1.29  2002/06/04 17:57:58  dasenbro
		Added UnLockTheCache() to the catch().
		
		Revision 1.28  2002/05/30 17:09:29  dasenbro
		Added a bool to GetSpoolsSize() to verify the mail spools.
		
		Revision 1.27  2002/05/09 16:58:58  dasenbro
		Changed all str... calls to CUtils::Str... to be NULL safe.
		
		Revision 1.26  2002/04/18 18:09:11  dasenbro
		Changed bool to Bool for word alignment.
		
		Revision 1.25  2002/04/16 06:03:13  dasenbro
		Removed an unnecessary MailAssert().
		
		Revision 1.24  2002/03/21 16:41:23  dasenbro
		Updated file version information.
		
		Revision 1.23  2002/02/20 21:01:41  dasenbro
		Added GetSpoolsSize() for user disk quota checks.
		
		Revision 1.22  2002/01/14 17:27:40  dasenbro
		Initial S4 updates.
		
		Revision 1.21  2001/08/14 00:11:17  dasenbro
		Need to calculate the hidden header size when setting partial fetch offsets.
		
		Revision 1.20  2001/06/21 20:50:58  dasenbro
		Updated file header info.
		
		Revision 1.19  2001/06/21 17:38:33  dasenbro
		Added user IP information to Account object for caching.
		
		Revision 1.18  2001/05/23 21:50:18  dasenbro
		Added get/set methods for ACL folder time stamp.
		

	 <ASM13>	 3/28/99	MED		Set rved2 and fASDUserID to 0 when the account is disabled.
	 <ASM12>	 3/17/99	DOR		Fix some object lock/release bugs.
	 <ASM11>	 3/15/99	MED		Changed Post to post to imap/pop inboxes when the user cannot be
									verified for shared U&G.
	 <ASM10>	 2/24/99	MED		Set the new fingerprint over the old one.
	  <ASM9>	 2/11/99	MED		Added new fingerprint checking fixes.
	  <ASM8>	  2/8/99	MED		Added a 'create' parameter to CAccount::Find() and we now first
									look for a users inbox (IMAP and POP) and only if it's not found
									do we create a new one.
	  <ASM7>	  2/4/99	DOR		Don't add string index's for zero-ID user accounts.
	  <ASM6>	  2/4/99	MED		Added kStringNameRequest to GetSetFields for string lookup for
									accounts.
	  <ASM5>	 1/24/99	MED		Added getters for POP3 and IMAP inbox ID's.
	  <ASM4>	 1/24/99	MED		Added getters for IMAP and POP3 inbox ID's.
		 <3>	10/28/98	DOR		Cause the IMAP4 Inbox to be created every time we create an
									account to prevent other folders named "INBOX" from being added
									to the account via IMAP/ASIA commands.
		 <2>	10/13/98	DOR		Lock the cache when deleting an object.
		<29>	 9/21/98	DOR		Don't Use NULL Pointers.
		<28>	 9/14/98	MED		Added Get/Set for unused 1 and 2.
		<27>	 6/16/98	MED		#2246044 - Added a ReleaseIterator to catch.
		<26>	  6/1/98	DOR		Remove the "kAdd" argument from Create since we no longer use
									it.
		<25>	  6/1/98	DOR		#22240677 - print out error log _ONLY_ at the time we actually
									disable the user account, rather than simply because a Validate
									failed.
		<24>	  6/1/98	MED		Changed ::Create to be a more kinder and gentler ::Create.
		<23>	 5/19/98	DOR		Fix a fingerprint bug.
		<22>	 5/19/98	MED		Added future expansion data members.
		<21>	  4/7/98	MED		::Delete now deletes all the mail spools for the deleted
									account.
		<20>	  4/6/98	DOR		Add "::" to beginning of C-Library calls.
		<19>	  4/6/98	DOR		Lock the CAccount object during creation to prevent
									CPeriodicTask from messing with it.  And use stricmp in the set
									name routines.
		<18>	  4/2/98	DOR		Change call to gDB->removeObject
		<17>	 3/26/98	MED		We verify the existence of the pop/imap inboxes before we return
									an object ID.
		<16>	 3/24/98	MED		Rename the CAccount name when we mark it mismatched for admin
									port readability.
		<15>	03/23/98	DOR		Fix a race condition in the Creation Mail box portion of
									CAccount, there now should be _NO_ way that more than one
									POP/IMAP Mail spool will get created.
		<14>	 3/14/98	DOR		Close up a race condition in ::Create..
		<12>	 3/11/98	MED		Changed FindByID() to do a Use() only if it created the object,
									FindMe() now does an explicit Use().
		<11>	  3/9/98	DOR		Get/Set/Clear OwnerThreadID has been moved to CDBObject.
		 <9>	 2/23/98	MED		Fixed a memory leak in ::Post() when calling GetUserByShortID().
		 <8>	 2/17/98	DOR		Clean up ::Post to supress duplicates, and handle the shared vs.
									separate Inbox issue.
		 <7>	  2/3/98	MED		Cleaned OSUtil.cp into CUtils.cp, COSUtils.cp and
									CSmartYield.cp.
		 <6>	 1/27/98	MED		Changed the spool name of the POP inbox for IMAP support.
		 <5>	12/16/97	MED		Added owner thread ID's for CNotifyClient and Post wakes up the
									notify threads.
		 <4>	12/15/97	MED		Added support for NotifyMail.
		 <3>	12/15/97	DOR		Update the object version to "include" the size of the
									dataportion of the object, and add an assert to "FindByID" that
									checks the version of the object.
		 <2>	 12/3/97	MED		Post now takes a outEnvInfoID arg to return the new envelope
									info.


	To Do:
*/


// NOTE: the account assumes the MailUser is "update to date" - it does not do any explicite synchronization with
// the directory services. The synchronizations are done at the DSMailUser class.

#include <sys/stat.h>			// for fstat(), stat() and structs
#include <sys/types.h>
#include <stdlib.h>
#include <dirent.h>

#include "CAccount.h"
#include "CGlobals.h"
#include "COSUtils.h"

// Database
#include "Database.h"
#include "CMailSpool.h"
#include "CEnvelope.h"
#include "CEnvelopeInfo.h"

// Utilities
#include "CString.h"
#include "CUtils.h"
#include "CFile.h"

#include "DSMgr.h"
#include "DSMailUser.h"


// -- Globals --------------------------------------------------
#define		kUnknowFrom		"<unknown>\n"
uInt32		gMailGroupID	= 0;


Boolean	CAccount::GetSetFields ( const eFieldDataRequest inFieldRequest, const void *inObjDataPtr, void *outFieldData )
{
	Boolean			result			= false;
	CAccountData	*theData		= (CAccountData *) inObjDataPtr;
	CAccount		*aTempObject	= NULL;
	char			*theDestString	= (char *) outFieldData;
	ObjID			*anObjIDPtr		= (ObjID *) outFieldData;
	char			numStr[256];

	switch ( inFieldRequest )
	{
		case kSetObjID:
			if(theData != NULL)
			{
				theData->fAccountID = *anObjIDPtr;
			}
			break;

		case kStringNameRequest:
			if ( theData->fReturnNameOk == kOkToReturnName )
			{
				::sprintf( (char *)numStr, "%ld", theData->fMailUserID );
				if ( CUtils::Strlen( numStr ) != 0 )
				{
					CUtils::Strncpy( theDestString, numStr, kStr2IDMaxStringSize );
					result = true;
				}
			}
			break;

		case kObjectPINGRequest:
			// cause the objects constructor to get called, so we force a check of
			// the struct alignment...
			aTempObject = new CAccount;
			delete aTempObject;
			break;
	
		case k2ndIDTargetChk:
			result = true;
			break;

		default:
			result = false;
			break;
	}

	return result;
} // GetSetFields


//--------------------------------------------------------------------------------------------------
// * Find ()
//
//--------------------------------------------------------------------------------------------------

CAccount* CAccount::Find ( DSMailUser *inMailUser, CreateOnDemand inCreate )
{
	ObjID			anAcctID = 0;
	CAccount	   *pAcctObj = NULL;
	OSErr			result = kNoErr;
	char			aString[ kCNameLen ];

	if ( inMailUser == NULL )
	{
		return NULL;
	}

	try
	{
		// Change the user account ID to a string
		::sprintf( aString, "%lu", inMailUser->GetUserID() );

		result = gDB->GetObjDataByString( kAccountSignature, (char *)aString, anAcctID );
		if ( anAcctID != 0 )		// there is already an account for this user ID
		{
			pAcctObj = CAccount::FindByID( anAcctID );
			if ( pAcctObj != NULL )
			{
				pAcctObj->SetAccountName( inMailUser->GetUserName() );
				pAcctObj->SetMailLocation( inMailUser->GetMailLocation() );
				return( pAcctObj );
			}
		}
		
		// It't doesn't exist
		if ( (pAcctObj == nil) && (inCreate == kCreate) )
		{
			// We need to create the account we are looking for
			pAcctObj = CAccount::Create( inMailUser );
		}
	}

	Catch_( err )
	{
		if ( pAcctObj != NULL )
		{
			pAcctObj->Done( pAcctObj );
		}
		return( NULL );
	}
	
	return( pAcctObj );

} // Find


//--------------------------------------------------------------------------------------------------
// * ObjectAllocNew ()
//
//--------------------------------------------------------------------------------------------------

CDBBaseObject * CAccount::ObjectAllocNew ( void )
{
	return( new CAccount );
} // ObjectAllocNew


//--------------------------------------------------------------------------------------------------
// * FindByID ()
//
//--------------------------------------------------------------------------------------------------

CAccount* CAccount::FindByID ( const ObjID inObjectID )
{
	return (CAccount *) CDBMailBaseObject::MailUniversalFindByID(	inObjectID,
														CAccount::GetObjTypeConstant(),
														CAccount::GetObjVersConstant(),
														CAccount::GetObjSizeConstant(),
														CAccount::ObjectAllocNew );
} // FindByID


ObjID CAccount::GetObjectID	( void )
{
	return( this->GetAccountID() );
}

//--------------------------------------------------------------------------------------------------
//	* Done ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::Done ( CAccount* &inPtr )
{
	CDBBaseObject *aDBObject = (CDBBaseObject *) inPtr;
	aDBObject->Done(aDBObject);

	inPtr = (CAccount *)aDBObject;
} // Done


//--------------------------------------------------------------------------------------------------
//	* Create () - [ static ]
//
//--------------------------------------------------------------------------------------------------

CAccount* CAccount::Create ( DSMailUser *inMailUser )
{
	CAccount	   *pAcctObj	 = NULL;
	OSStatus		result		= kNoErr;
	ObjID			aNewID		= 0;
	ObjID			aJunkID		= 0;

	if ( inMailUser == NULL )
	{
		return( NULL );
	}


	try
	{
		pAcctObj = new CAccount;

		(gDB->GetBaseObjectCachePtr())->LockTheCache( CAccount::GetObjTypeConstant() );

		result = gDB->CreateObj( CAccount::GetObjTypeConstant(), aNewID, pAcctObj->GetObjectData() );
		if ( result == CMailDatabase::kDBNoErr )
		{
			pAcctObj->Use();

			if ( pAcctObj->GetObjectLock( true ) == kNoErr )
			{
				// Set the account name and internet alias when we create the account
				pAcctObj->SetMailUserID( inMailUser->GetUserID() );
				pAcctObj->SetAccountName( inMailUser->GetUserName() );
//				pAcctObj->SetDirectoryPath( inMailUser->GetDirectoryPath() );

				// Synch inbox location
				pAcctObj->SynchAccount( inMailUser );

				// Mark the account as active
				pAcctObj->SetAccountState( kActive );
				aJunkID = pAcctObj->GetIMAP4InboxID();

				pAcctObj->SetDirty();

				pAcctObj->ReleaseObjectLock( true );
				pAcctObj->Done( pAcctObj );

				pAcctObj = CAccount::FindByID( aNewID );
			}
			else
			{
				delete( pAcctObj );
				pAcctObj = NULL;
			}
		}
		else
		{
			delete( pAcctObj );
			pAcctObj = NULL;
		}

		// Just in case a disk full error deleted the account
		if ( pAcctObj != NULL )
		{
			// Display in the Log
		}

		(gDB->GetBaseObjectCachePtr())->UnLockTheCache(CAccount::GetObjTypeConstant());
	}

	Catch_ ( err )
	{
		(gDB->GetBaseObjectCachePtr())->UnLockTheCache(CAccount::GetObjTypeConstant());
	}

	return( pAcctObj );

} // Create



//--------------------------------------------------------------------------------------------------
//	* SetDirty ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetDirty	( void )
{
	if ((this != NULL) && (this->GetAccountID() != 0))
	{
		this->setDirty();	// call the base class method
	}
} // SetDirty


//--------------------------------------------------------------------------------------------------
//	* Delete - [ static ]
//
//--------------------------------------------------------------------------------------------------

Boolean CAccount::Delete ( CAccount* inAcctObj )
{
	OSStatus		result;
	ObjID			objID		= 0;
	Boolean			deleted		= false;
	CMailSpool	   *mailSpool	= nil;
	SDBIterator		iterator(gDB);

	Try_
	{
		(gDB->GetBaseObjectCachePtr())->LockTheCache(CAccount::GetObjTypeConstant());

		if ( inAcctObj->GetObjectLock( true ) == kNoErr )
		{
			result = gDB->CreateIterator( kMailSpoolSignature, &iterator, inAcctObj->GetAccountID() );
			if ( result == kNoErr )
			{
				while ( gDB->GetCurObject( &iterator, objID ) == kNoErr )
				{
					mailSpool = CMailSpool::FindByID( objID );
					if ( mailSpool != nil )
					{
						CMailSpool::Delete( mailSpool );
						mailSpool->Done( mailSpool );
						mailSpool = nil;
					}
				}
			}

			inAcctObj->ReleaseObjectLock(true);
			gDB->ReleaseIterator( &iterator );
		}

		result = gDB->RemoveObj( kAccountSignature, inAcctObj->GetAccountID() );
		deleted = (result == kNoErr);
		if ( deleted == true )
		{
			inAcctObj->fAccountData.fAccountID = 0;
		}

		(gDB->GetBaseObjectCachePtr())->UnLockTheCache(CAccount::GetObjTypeConstant());
	}
	Catch_ (err)
	{
		if (mailSpool != NULL)
		{
			mailSpool->Done( mailSpool );
		}
		gDB->ReleaseIterator( &iterator );

		(gDB->GetBaseObjectCachePtr())->UnLockTheCache(CAccount::GetObjTypeConstant());
	}

	return ( deleted );

} // Delete



//--------------------------------------------------------------------------------------------------
//	* Count - [ static ]
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::Count ( void )
{
	uInt32	aCount = 0;
	OSErr	result;

	result = gDB->GetObjectCount( kAccountSignature, aCount );

	if (result != kNoErr)
	{
		aCount = 0;
	}

	return aCount;
} // Count


#pragma mark -

CAccount::CAccount(void) :
	CDBMailBaseObject(	&fAccountData.fDBHeader,
				&fAccountData.fDBFooter,
				&fAccountData,
				CAccount::GetObjTypeConstant(),
				CAccount::GetObjVersConstant(),
				CAccount::GetObjSizeConstant() )
{
//	fPOPContext							= NULL;

	fAccountData.fAccountID				= 0;
	fAccountData.fPOP3InboxID			= 0;
	fAccountData.fIMAP4InboxID			= 0;
	fAccountData.fMailUserID			= 0;
	fAccountData.fAccountState			= kUnknown;
	fAccountData.fNotify				= 0;
	fAccountData.fNotifyIPAddress		= 0;
	fAccountData.fNotifyIPAddrCache		= 0;
	fAccountData.fNotifyFailures		= 0;
	fAccountData.fReturnNameOk			= 0;
	fAccountData.fACLFolderSynch		= 0;

	fAccountData.fReserved2				= kReservedConst;

	::memset( &fAccountData.fAcctNameObj,   0, sizeof( TextStrObj ) );	// CName from DSMailUser
	::memset( &fAccountData.fDirPathObj,   0, sizeof( TextStrObj ) );	// Account Location

	fMailLocation	= nil;
	fAcctNameStr	= nil;
	fDirPathStr		= nil;
}

CAccount::~CAccount()
{
	if ( fMailLocation != nil )
	{
		delete( fMailLocation );
		fMailLocation = nil;
	}

	if ( fAcctNameStr != nil )
	{
		free( fAcctNameStr );
		fAcctNameStr = nil;
	}

	if ( fDirPathStr != nil )
	{
		free( fDirPathStr );
		fDirPathStr = nil;
	}
}

void	CAccount::ChkCompilerStructAlignment	( void )
{
	/*
		this dump was aquired by using the MPW command DumpSYM - TTE from the .xsym file produced
		from a Code Warrior Pro 1 Release Build of the AppleShare IP Mail Server for MacOS version 6.2
		
		this version of the structure is consider the "reference" standard for all future ASIP Mail Servers.
		If the structure does NOT match these offsets the version of the Mail Server that 
		you are compiling will _NOT_ be compatible with AppleShare IP 6.0, 6.1, or 6.2 (this also effects
		the AppleShare IP Mail Tool, and to a more limited extent the AppleShare IP Mail Admin since some
		of these structures are passed across the wire..)
		
		If _ANY_ of these Asserts "fire" you've got a backwards compatibility problem that _MUST_ be fixed!!
		
		this code wasn't put here for my personal amusement, this code was put here from a hard earned lesson
		that simple changes to headers/compilers _WILL_ break this stuff, better to know now than after we
		ship.  Look upon this code as a debugging AIDE, not an impediment.
		
		Type Name:  CAccountData		TINFO offset: 51646		(TINFO SYM file offset (hex): $1249BE)
			offset 0 NamedTypeOf fDBHeader TypeDef of "Type ID 248"
			offset 12 NamedTypeOf fAccountID unsigned long
			offset 16 NamedTypeOf fPOP3InboxID unsigned long
			offset 20 NamedTypeOf fIMAP4InboxID unsigned long
			offset 24 NamedTypeOf fASDUserID unsigned long
			offset 28 NamedTypeOf fReserved1 unsigned long
			offset 32 NamedTypeOf fReserved2 unsigned long
			offset 36 NamedTypeOf fAccountName CString
			offset 68 NamedTypeOf fInternetAlias TypeDef of "Type ID 326"
			offset 100 NamedTypeOf fAccountState TypeDef of "Type ID 327"
			offset 102 NamedTypeOf fFingerprint TypeDef of "Type ID 262"
			offset 118 NamedTypeOf fNotify unsigned byte
			offset 120 NamedTypeOf fNotifyIPAddress unsigned long
			offset 124 NamedTypeOf fNotifyIPAddrCache unsigned long
			offset 128 NamedTypeOf fNotifyFailures unsigned long
			offset 132 NamedTypeOf fDBFooter TypeDef of "Type ID 250"
	*/
	
	static Boolean	aRunOnceFlag = false;	
	if (aRunOnceFlag == false)
	{
		//FileFormatSaftyChk(CAccountData, fDBHeader,				0);
		//FileFormatSaftyChk(CAccountData, fAccountID,			12);
		//FileFormatSaftyChk(CAccountData, fPOP3InboxID,	 		16);
		//FileFormatSaftyChk(CAccountData, fIMAP4InboxID,	 		20);
		//FileFormatSaftyChk(CAccountData, fASDUserID,	 		24);
		//FileFormatSaftyChk(CAccountData, fReserved1,	 			28);
		//FileFormatSaftyChk(CAccountData, fReserved2,	 			32);
		//FileFormatSaftyChk(CAccountData, fAccountName,	 		36);
		//FileFormatSaftyChk(CAccountData, fInternetAlias,	 	68);
		//FileFormatSaftyChk(CAccountData, fAccountState,	 		100);
		//FileFormatSaftyChk(CAccountData, fFingerprint,	 		102);
		//FileFormatSaftyChk(CAccountData, fNotify,	 			118);
		//FileFormatSaftyChk(CAccountData, fNotifyIPAddress,		120);
		//FileFormatSaftyChk(CAccountData, fNotifyIPAddrCache,	124);
		//FileFormatSaftyChk(CAccountData, fNotifyFailures,		128);
		//FileFormatSaftyChk(CAccountData, fDBFooter,				132);
//		this->ReportBackwardCompatibility(sizeof(CAccountData),	136,	"CAccountData", "SIZEOF()");
//		this->ReportBackwardCompatibility(kAccountDataSize,		136,	"CAccountData", "kAccountDataSize");
	}
	
	aRunOnceFlag = true;
}


#pragma mark -

ObjID CAccount::GetAccountID ( void )
{
	return( fAccountData.fAccountID );
}

ObjID CAccount::GetPOP3InboxIDOnly ( void )
{
	return( fAccountData.fPOP3InboxID );
}

ObjID CAccount::GetIMAP4InboxIDOnly ( void )
{
	return( fAccountData.fIMAP4InboxID );
}

ObjID CAccount::GetPOP3InboxID ( CreateOnDemand inCreate )
{
	Boolean				found			= false;
	OSErr				result			= kNoErr;
	ObjID				spoolID			= 0;
	CMailSpool		   *spoolObj		= nil;
	SDBIterator			iterator(gDB);

	Try_
	{
		// Make sure the spool exists if we say it does
		if ( fAccountData.fPOP3InboxID != 0 )
		{
			spoolObj = CMailSpool::FindByID( fAccountData.fPOP3InboxID );
			if ( spoolObj == nil )
			{
				// Bad object ID
				this->SetPOP3InboxID (0);
			}
			else
			{
				spoolObj->Done( spoolObj );
			}
		}

		// Look for our POP3 inbox that may have been created, but never linked
		if ( fAccountData.fPOP3InboxID == 0 )
		{
			result = gDB->CreateIterator( kMailSpoolSignature, &iterator, this->GetAccountID() );
			if ( result == kNoErr )
			{
				while ( !found && (gDB->NextObject( &iterator, spoolID ) == kNoErr) )
				{
					spoolObj = CMailSpool::FindByID( spoolID );
					if ( spoolObj != nil )
					{
						if ( (CUtils::Stricmp( spoolObj->GetMailSpoolName(), "POP3 INBOX" ) == 0) &&
							 (spoolObj->GetParentID() == CMailSpool::kRootParent) )
						{
							// Found it.  Now set it and git...
							this->SetPOP3InboxID( spoolObj->GetMailSpoolID() );
							found = true;
						}

						spoolObj->Done( spoolObj );
					}
				}
			}
			gDB->ReleaseIterator( &iterator );
		}


		while ( (fAccountData.fPOP3InboxID == 0) )// POP3 inbox mail spool does not exist yet
		{
			if (inCreate == kCreate)
			{
				if (this->GetObjectLock(true) == kNoErr)
				{
					if ( fAccountData.fPOP3InboxID == 0 )
					{
						// Create a MailSpool first
						spoolObj = CMailSpool::Create( CMailSpool::kPOP3Spool, "POP3 INBOX", this->GetAccountID() );
						if (spoolObj != NULL)
						{
							// Save the MailSpoolID
							this->SetPOP3InboxID( spoolObj->GetMailSpoolID() );

							// Save the AccountID in the MailSpool
							spoolObj->SetAccountID( this->GetAccountID() );
							spoolObj->Done( spoolObj );
						}
					}

					this->ReleaseObjectLock(true);
				}
			}
			else
			{
				break;
			}
		}

		return( fAccountData.fPOP3InboxID );
	}

	Catch_( err )
	{
		spoolObj->Done( spoolObj );

		return 0;
	}
}

void CAccount::SetPOP3InboxID (ObjID inMailSpoolID)
{
	if (fAccountData.fPOP3InboxID != inMailSpoolID)
	{
		fAccountData.fPOP3InboxID = inMailSpoolID;
		this->SetDirty();
	}
}


ObjID CAccount::GetIMAP4InboxID ( CreateOnDemand inCreate )
{
	Boolean				found			= false;
	OSErr				result			= kNoErr;
	ObjID				spoolID			= 0;
	CMailSpool		   *spoolObj		= nil;
	SDBIterator			iterator(gDB);

	Try_
	{
		// Make sure the spool exists if we say it does
		if ( fAccountData.fIMAP4InboxID != 0 )
		{
			spoolObj = CMailSpool::FindByID( fAccountData.fIMAP4InboxID );
			if ( spoolObj == nil )
			{
				// Bad object ID
				this->SetIMAP4InboxID(0);
				this->SetDirty();
			}
			else
			{
				spoolObj->Done( spoolObj );
			}
		}

		// Look for our INBOX that may have been created, but never linked
		if ( fAccountData.fIMAP4InboxID == 0 )
		{
			result = gDB->CreateIterator( kMailSpoolSignature, &iterator, this->GetAccountID() );
			if ( result == kNoErr )
			{
				while ( !found && (gDB->NextObject( &iterator, spoolID ) == kNoErr) )
				{
					spoolObj = CMailSpool::FindByID( spoolID );
					if ( spoolObj != nil )
					{
						if ( (CUtils::Stricmp( spoolObj->GetMailSpoolName(), "INBOX" ) == 0) &&
							 (spoolObj->GetParentID() == CMailSpool::kRootParent) )
						{
							// Found it.  Now set it and git...
							this->SetIMAP4InboxID( spoolObj->GetMailSpoolID() );
							found = true;
						}

						spoolObj->Done( spoolObj );
					}
				}
			}
			gDB->ReleaseIterator( &iterator );
		}


		while ( (fAccountData.fIMAP4InboxID == 0) )	// IMAP4 inbox mail spool does not exist yet
		{
			if (inCreate == kCreate)
			{
				if (this->GetObjectLock(true) == kNoErr)
				{
					if (fAccountData.fIMAP4InboxID == 0)
					{
						// Create a MailSpool first
						spoolObj = CMailSpool::Create( CMailSpool::kIMAP4InBoxSpool,
															"INBOX",
															this->GetAccountID(), CMailSpool::kRootParent );
						if (spoolObj != NULL)
						{
							// Save the MailSpoolID
							this->SetIMAP4InboxID( spoolObj->GetMailSpoolID() );

							// Save the AccountID in the MailSpool
							spoolObj->SetAccountID(this->GetAccountID());
							spoolObj->Done (spoolObj);
						}
					}

					this->ReleaseObjectLock(true);
				}
			}
			else
			{
				break;
			}
		}

		return (fAccountData.fIMAP4InboxID);
	}

	Catch_( err )
	{
		spoolObj->Done( spoolObj );

		return( 0 );
	}
}

void CAccount::SetIMAP4InboxID (ObjID inMailSpoolID)
{
	if (fAccountData.fIMAP4InboxID != inMailSpoolID)
	{
		fAccountData.fIMAP4InboxID = inMailSpoolID;
		this->SetDirty();
	}
}

#pragma mark -

//--------------------------------------------------------------------------------------------------
// * GetAccountName ()
//
//--------------------------------------------------------------------------------------------------

const char* CAccount::GetAccountName (void)
{
	sInt32		siResult	= kNoErr;

	if ( fAcctNameStr == nil )
	{
		(void)CTextObjMgr::GetString( &fAccountData.fAcctNameObj, &fAcctNameStr );
	}

	return( fAcctNameStr );

} // GetAccountName


//--------------------------------------------------------------------------------------------------
// * SetAccountName ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetAccountName ( const char* inAccountName )
{
	sInt32		siResult	= kNoErr;

	this->GetAccountName();

	if ( (fAcctNameStr == nil) || (CUtils::Strcmp( fAcctNameStr, inAccountName ) != 0) )
	{
		if ( fAcctNameStr != nil )
		{
			siResult = CTextObjMgr::RemoveString( &fAccountData.fAcctNameObj );

			free( fAcctNameStr );
			fAcctNameStr = nil;
		}

		siResult = CTextObjMgr::AddString( inAccountName, &fAccountData.fAcctNameObj );

		siResult = CTextObjMgr::GetString( &fAccountData.fAcctNameObj, &fAcctNameStr );

		this->SetDirty();
	}
} // SetAccountName


//--------------------------------------------------------------------------------------------------
// * GetMailLocation ()
//
//--------------------------------------------------------------------------------------------------

const char* CAccount::GetMailLocation (void)
{
	if ( fMailLocation == nil )
	{
		fMailLocation = new CString( 64 );
		ThrowIfNULL_( fMailLocation );

	}

	if ( fMailLocation->GetLength() == 0 )
	{
		fMailLocation->Set( COSUtils::GetFullAccountFolderPath() );
		fMailLocation->Append( "/" );
		fMailLocation->Append( this->GetAccountName() );
	}

	return( fMailLocation->GetData() );

} // GetMailLocation


//--------------------------------------------------------------------------------------------------
// * SetMailLocation ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetMailLocation ( const char* inMailLocation )
{
	if ( fMailLocation == nil )
	{
		fMailLocation = new CString( 64 );
		ThrowIfNULL_( fMailLocation );
	}

	fMailLocation->Clear();

	if ( inMailLocation != nil )
	{
		fMailLocation->Set( inMailLocation );
	}
	
} // SetMailLocation


void CAccount::SetAccountState( eAccountState inAccountState )
{
	if ( fAccountData.fAccountState != inAccountState )
	{
		fAccountData.fAccountState = inAccountState;
		this->SetDirty();
	}
}

eAccountState CAccount::GetAccountState( void )
{
	return( fAccountData.fAccountState );
}

void CAccount::SetMailUserID ( const ObjID inMailUserID )
{
	if ( (fAccountData.fMailUserID != inMailUserID) || (fAccountData.fReturnNameOk != kOkToReturnName) )
	{
		fAccountData.fMailUserID = inMailUserID;
		this->SetReturnNameOk( kOkToReturnName );
		
		this->SetDirty();
	}
}

ObjID CAccount::GetMailUserID ( void )
{
	return( fAccountData.fMailUserID );
}


//--------------------------------------------------------------------------------------------------
//	* GetACLFolderSynchStamp ()
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::GetACLFolderSynchStamp ( void )
{
	return( fAccountData.fACLFolderSynch );
} // GetACLFolderSynchStamp


//--------------------------------------------------------------------------------------------------
//	* SetACLFolderSynchStamp ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetACLFolderSynchStamp ( const uInt32 inTimeStamp )
{
	if ( fAccountData.fACLFolderSynch != inTimeStamp )
	{
		fAccountData.fACLFolderSynch = inTimeStamp;
		this->SetDirty();
	}
} // SetACLFolderSynchStamp


const char * CAccount::GetDirectoryPath ( void )
{
	sInt32		siResult	= kNoErr;

	if ( fDirPathStr == nil )
	{
		siResult = CTextObjMgr::GetString( &fAccountData.fDirPathObj, &fDirPathStr );
	}

	return( fDirPathStr );
//	return fAccountData.fDirPath;

} // GetDirectoryPath


void CAccount::SetDirectoryPath ( const char *inDirPath )
{
	sInt32		siResult	= kNoErr;

	if ( (fDirPathStr == nil) || (CUtils::Strcmp( fDirPathStr, inDirPath ) != 0) )
	{
		if ( fDirPathStr != nil )
		{
			siResult = CTextObjMgr::RemoveString( &fAccountData.fDirPathObj );

			free( fDirPathStr );
			fDirPathStr = nil;
		}

		siResult = CTextObjMgr::AddString( inDirPath, &fAccountData.fDirPathObj );

		siResult = CTextObjMgr::GetString( &fAccountData.fDirPathObj, &fDirPathStr );

		this->SetDirty();
	}
} // SetDirectoryPath


Boolean CAccount::Validate ( DSMailUser* inMailUser )
{
#pragma unused (inMailUser)

	return true;
} // Validate

#pragma mark -

//--------------------------------------------------------------------------------------------------
//	* GetNotifyIPAddress
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::GetNotifyIPAddress ( void )
{
	return fAccountData.fNotifyIPAddress;
} // GetNotifyIPAddress



//--------------------------------------------------------------------------------------------------
//	* SetNotifyIPAddress
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetNotifyIPAddress ( uInt32 inNotifyAddress )
{
	if ( fAccountData.fNotifyIPAddress != inNotifyAddress )
	{
		fAccountData.fNotifyIPAddress = inNotifyAddress;
		this->SetDirty();
	}
} // SetNotifyIPAddress



//--------------------------------------------------------------------------------------------------
//	* SetNotifyUserOn ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetNotifyUserOn ( void )
{
	if ( (fAccountData.fNotify & kNotifyUser) == 0 )
	{
		fAccountData.fNotify |= kNotifyUser;
		this->SetDirty();
	}
} // SetNotifyUserOn


//--------------------------------------------------------------------------------------------------
//	* SetNotifyUserOff ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetNotifyUserOff ( void )
{
	if ( (fAccountData.fNotify & kNotifyUser) != 0 )
	{
		fAccountData.fNotify &= ~kNotifyUser;
		this->SetDirty();
	}
} // SetNotifyUserOff


//--------------------------------------------------------------------------------------------------
//	* SetNotifyOn ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetNotifyOn ( void )
{
	if ( (fAccountData.fNotify & kNotifyOn) == 0 )
	{
		fAccountData.fNotify |= kNotifyOn;
		this->SetDirty();
	}
} // SetNotifyOn


//--------------------------------------------------------------------------------------------------
//	* SetNotifyOff ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetNotifyOff ( void )
{
	if ( (fAccountData.fNotify & kNotifyOn) != 0 )
	{
		fAccountData.fNotify &= ~kNotifyOn;
		this->SetDirty();
	}
} // SetNotifyOff


//--------------------------------------------------------------------------------------------------
//	* SetUseLastIPAddrOn ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetUseLastIPAddrOn ( void )
{
	if ( (fAccountData.fNotify & kUseLastIP) == 0 )
	{
		fAccountData.fNotify |= kUseLastIP;
		this->SetDirty();
	}
} // SetUseLastIPAddrOn


//--------------------------------------------------------------------------------------------------
//	* SetUseLastIPAddrOff ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetUseLastIPAddrOff ( void )
{
	if ( (fAccountData.fNotify & kUseLastIP) != 0 )
	{
		fAccountData.fNotify &= ~kUseLastIP;
		this->SetDirty();
	}
} // SetUseLastIPAddrOff


//--------------------------------------------------------------------------------------------------
//	* IsNotifyOn ()
//
//--------------------------------------------------------------------------------------------------

Bool CAccount::IsNotifyOn ( void )
{
	Bool	bResult	= false;

	if ( (fAccountData.fNotify & kNotifyOn) != 0 )
	{
		bResult = true;
	}

	return( bResult );

} // IsNotifyOn


//--------------------------------------------------------------------------------------------------
//	* UseLastIPAddress ()
//
//--------------------------------------------------------------------------------------------------

Bool CAccount::UseLastIPAddress ( void )
{
	Bool	bResult	= false;

	if ( (fAccountData.fNotify & kUseLastIP) != 0 )
	{
		bResult = true;
	}

	return( bResult );

} // UseLastIPAddress


//--------------------------------------------------------------------------------------------------
//	* IsNotifyPending
//
//--------------------------------------------------------------------------------------------------

Boolean CAccount::IsNotifyPending ( void )
{
	Boolean		bResult		= false;

	if ( (fAccountData.fNotify & kNotifyUser) != 0 )
	{
		bResult = true;
	}

	return( bResult );

} // IsNotifyPending


//--------------------------------------------------------------------------------------------------
//	* ResetNotifyFailures
//
//--------------------------------------------------------------------------------------------------

void CAccount::ResetNotifyFailures ( void )
{
	if ( fAccountData.fNotifyFailures != 0 )
	{
		fAccountData.fNotifyFailures = 0;
		this->SetDirty();
	}
} // ResetNotifyFailures



//--------------------------------------------------------------------------------------------------
//	* IncreaseNotifyFailures
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::IncreaseNotifyFailures ( void )
{
	fAccountData.fNotifyFailures++;
	this->SetDirty();

	return( fAccountData.fNotifyFailures );

} // IncreaseNotifyFailures



//--------------------------------------------------------------------------------------------------
//	* GetAdminSetIPNotifyAddr ()
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::GetAdminSetIPNotifyAddr ( void )
{
	return( fAccountData.fNotifyIPAddrCache );
} // GetAdminSetIPNotifyAddr



//--------------------------------------------------------------------------------------------------
//	* SetAdminSetIPNotifyAddr ()
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetAdminSetIPNotifyAddr ( uInt32 inAdminSetIPAddr )
{
	if ( fAccountData.fNotifyIPAddrCache != inAdminSetIPAddr )
	{
		fAccountData.fNotifyIPAddrCache = inAdminSetIPAddr;
		this->SetDirty();
	}
} // SetAdminSetIPNotifyAddr

#pragma mark -

Boolean CAccount::Post ( ObjID inEnvelopeID, DSMailUser *inMailUser )
{
	Boolean			result				= false;
	Bool			bPostToPopInbox		= false;
	eMUGError		theResult			= kMUGErrNoErr;
	CMailSpool	   *imapMailSpool		= NULL;
	CMailSpool	   *pop3MailSpool		= NULL;
	ObjID			junkIDNotUsed		= 0;
	uInt32			uiIPAddr			= 0;
	const char	   *ipAddress			= nil;

	if ( inMailUser != NULL )
	{
		// Local file delivery option
		if ( inMailUser->DeliverToUNIXInbox() == true )
		{
			if ( (inMailUser->IsPOPLoginEnabled() == true) || (inMailUser->IsIMAPLoginEnabled() == true) )
			{
				if ( inMailUser->IsInboxShared() == false )
				{
					bPostToPopInbox = true;
				}
				this->PostToInbox( inEnvelopeID, inMailUser, bPostToPopInbox );
			}
		}

		if ( inMailUser->IsPOPLoginEnabled() == true )
		{
			if ( inMailUser->IsInboxShared() == false )
			{
				// GetPOP3SpoolID may create a mail spool if it does not already exist
				pop3MailSpool = CMailSpool::FindByID( this->GetPOP3InboxID() );
			}
			else
			{
				pop3MailSpool = CMailSpool::FindByID( this->GetIMAP4InboxID() );
			}

			if ( pop3MailSpool != nil )
			{
				result = pop3MailSpool->Post( inEnvelopeID, inMailUser, true, junkIDNotUsed );
				pop3MailSpool->Done( pop3MailSpool );
			}
		}

		if ( inMailUser->IsIMAPLoginEnabled() == true )
		{
			// GetIMAP4InboxID may create a mail spool if it does not already exist
			imapMailSpool = CMailSpool::FindByID( this->GetIMAP4InboxID() );
			if ( imapMailSpool != nil )
			{
				junkIDNotUsed	= 0;
				result = imapMailSpool->Post( inEnvelopeID, inMailUser, true, junkIDNotUsed );
				imapMailSpool->Done( imapMailSpool );
			}
		}

		// Mail Notification update
		if ( inMailUser->IsNotificationOn() == true )
		{
			// Get the last used IP address
			if ( inMailUser->UseLastIPAddress() == true )
			{
				SetUseLastIPAddrOn();
			}
			else
			{
				SetUseLastIPAddrOff();

				// Get the IP address from the user record in the directory
				ipAddress = inMailUser->GetNotifyIPAddress();
				if ( ipAddress != nil )
				{
//					CNetworkUtilities::StringToIPAddr( ipAddress, &uiIPAddr );
//					SetAdminSetIPNotifyAddr( uiIPAddr );
				}
			}

			SetNotifyOn();
			SetNotifyUserOn();
		}
		else
		{
			SetNotifyUserOff();
			SetNotifyOff();
		}
	}
	else
	{
		// post to one or both inboxes ( if they exist )
		pop3MailSpool = CMailSpool::FindByID( this->GetPOP3InboxID(kDontCreate) );
		if ( pop3MailSpool != nil )
		{
			result = pop3MailSpool->Post( inEnvelopeID, inMailUser, true, junkIDNotUsed );
			pop3MailSpool->Done( pop3MailSpool );
		}

		imapMailSpool = CMailSpool::FindByID( this->GetIMAP4InboxID() );
		if ( imapMailSpool != nil )
		{
			result = imapMailSpool->Post( inEnvelopeID, inMailUser, true, junkIDNotUsed );
			imapMailSpool->Done( imapMailSpool );
		}
	}

	return ( result );
} // Post


sInt32 CAccount::PostToInbox ( ObjID inEnvD, DSMailUser *inUser, Bool inPostToPopInbox )
{
	sInt32			siResult	= kNoErr;
	uInt32			uiLength	= 0;
	uInt32			uiFromLen	= 0;
	uInt32			uiGroupID	= 0;
	const char	   *pFromLine	= nil;
	const char	   *pMsg		= nil;
	CFile		   *pInbox		= nil;
	CEnvelope	   *pEnvObj		= nil;
	CString			csPath( "/var/mail/" );

	if ( inUser != nil )
	{
		pEnvObj = CEnvelope::FindByID( inEnvD );
		if ( pEnvObj != nil )
		{
			pFromLine = pEnvObj->GetFromLine();
			if ( pFromLine == nil )
			{
				pFromLine = kUnknowFrom;
			}
			uiFromLen = CUtils::Strlen( pFromLine );

			pMsg = pEnvObj->GetData( uiLength );
			if ( pMsg != nil )
			{
				uiGroupID = gDSMgr->GetMailGroupID();
				if ( uiGroupID == 0 )
				{
					uiGroupID = inUser->GetGroupID();
				}

				csPath.Append( inUser->GetUserName() );
				pInbox = new CFile( csPath.GetData(), inUser->GetUserID(), uiGroupID );
				if ( pInbox != nil )
				{
					pInbox->seekp( 0, ios_base::end );

					pInbox->write( pFromLine, uiFromLen );
					pInbox->write( pMsg, uiLength );

					delete( pInbox );
					pInbox = nil;
				}

				if ( inPostToPopInbox == true )
				{
					csPath.Append( ".POP" );
					pInbox = new CFile( csPath.GetData(), inUser->GetUserID(), inUser->GetGroupID() );
					if ( pInbox != nil )
					{
						pInbox->seekp( 0, ios_base::end );

						pInbox->write( pFromLine, uiFromLen );
						pInbox->write( pMsg, uiLength );

						delete( pInbox );
						pInbox = nil;
					}
				}

				delete( pMsg );
				pMsg = nil;
			}


			pEnvObj->Done( pEnvObj );
		}
	}

	return( siResult );
} // PostToInbox


#pragma mark -

Boolean CAccount::GetPOPContext( DSMailUser *inDSMailUser, char* outPOPContext )
{
return( false );
}

void CAccount::ReleasePOPContext( char *inPOPContext )
{
}


//--------------------------------------------------------------------------------------------------
//	* GetReturnNameOk
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::GetReturnNameOk ( void )
{
	return ( fAccountData.fReturnNameOk );
} // GetReturnNameOk


//--------------------------------------------------------------------------------------------------
//	* SetReturnNameOk
//
//--------------------------------------------------------------------------------------------------

void CAccount::SetReturnNameOk ( uInt32 inValue )
{
	if ( inValue != fAccountData.fReturnNameOk )
	{
		fAccountData.fReturnNameOk = inValue;
		this->SetDirty();
	}
} // SetReturnNameOk


//--------------------------------------------------------------------------------------------------
//	* SynchAccount ()
//
//--------------------------------------------------------------------------------------------------

sInt32 CAccount::SynchAccount ( DSMailUser *inMailUser )
{
	sInt32			siResult		= kNoErr;
	uInt32			uiGroupID		= 0;
	const char	   *pFolderPath		= nil;
	CString			csFolderPath( 128 );

	// Get the group ID for mail
	uiGroupID = gDSMgr->GetMailGroupID();
	if ( uiGroupID == 0 )
	{
		// If there is no group ID for mail, use the user's gid
		uiGroupID = inMailUser->GetGroupID();
	}

	csFolderPath.Set( COSUtils::GetFullAccountFolderPath() );
	csFolderPath.Append( "/" );
	csFolderPath.Append( inMailUser->GetUserName() );

	// Create and/or set folder permissions
	COSUtils::CreateDirectory( csFolderPath.GetData(), 0100700, inMailUser->GetUserID(), uiGroupID );

	// Create inbox
	csFolderPath.Append( "/INBOX" );

	// Create and/or set folder permissions
	COSUtils::CreateDirectory( csFolderPath.GetData(), 0100700, inMailUser->GetUserID(), uiGroupID );

	csFolderPath.Set( COSUtils::GetFullAccountFolderPath() );
	csFolderPath.Append( "/" );
	csFolderPath.Append( inMailUser->GetUserName() );

	siResult = SynchFolders( csFolderPath.GetData(), inMailUser->GetUserID(), uiGroupID );

	return( siResult );

} // SynchAccount


//--------------------------------------------------------------------------------------------------
//	* SynchFolders ()
//
//--------------------------------------------------------------------------------------------------

sInt32 CAccount::SynchFolders ( const char *inPath, const uInt32 inAcctID, const uInt32 inGroupID )
{
	sInt32			siResult		= kNoErr;
	sInt32			siFolderStat	= kNoErr;
	CFile		   *pMailFile		= nil;
	DIR			   *pDir			= nil;
	struct dirent  *pDirInfo		= nil;
	struct stat		ssFolder;
	CString			csString( 128 );

	// Open the directory
	pDir = ::opendir( inPath );
	if ( pDir != nil )
	{
		while ( pDirInfo = ::readdir( pDir ) )
		{
			if ( (pDirInfo->d_name[ 0 ] != '.') && (pDirInfo->d_type == DT_DIR) )
			{
				csString.Set( inPath );
				csString.Append( "/" );
				csString.Append( pDirInfo->d_name );

				siFolderStat = ::stat( csString.GetData(), &ssFolder );
				if ( siFolderStat == kNoErr )
				{
					// can we read/write and cd to this dir
					csString.Append( "/" );
					csString.Append( "mail.dat" );

					try
					{
						pMailFile = new CFile( csString.GetData(), inAcctID, inGroupID );
						if ( pMailFile != nil )
						{
							delete( pMailFile );
							pMailFile = nil;
						}

						csString.Set( inPath );
						csString.Append( "/" );
						csString.Append( pDirInfo->d_name );

						::chmod( csString.GetData(), 0100700 );
						::chown( csString.GetData(), inAcctID, inGroupID );

						this->SynchFolders( csString.GetData(), inAcctID, inGroupID );
					}

					catch ( ... )
					{
					}
				}
			}
		}

		::closedir( pDir );
	}

	return( siResult );

} // SynchFolders


//--------------------------------------------------------------------------------------------------
//	* GetSpoolsSize ()
//
//--------------------------------------------------------------------------------------------------

uInt32 CAccount::GetSpoolsSize ( bool inVerifySpools )
{
	uInt32			uiOutResult	= 0;
	OSStatus		siResult	= kNoErr;
	ObjID			objID		= 0;
	CMailSpool	   *mbSpool		= nil;
	SDBIterator		iterator;

	try
	{
		// Look for inferiors
		siResult = gDB->CreateIterator( kMailSpoolSignature, &iterator, this->GetObjectID() );
		if ( siResult == kNoErr )
		{
			while ( gDB->NextObject( &iterator, objID ) == kNoErr )
			{
				mbSpool = CMailSpool::FindByID( objID );
				if ( mbSpool != nil )
				{
					uiOutResult += mbSpool->GetSpoolSize();
					mbSpool->Done( mbSpool );
				}
			}
		}
		gDB->ReleaseIterator( &iterator );
	}

	catch ( long err )
	{
		mbSpool->Done( mbSpool );

		gDB->ReleaseIterator( &iterator );

		siResult = err;
	}

	return( uiOutResult );

} // GetSpoolsSize


