/*
 * Copyright (c) 2002 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@
 */

/*!
 * @header CNiPlugIn
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include <ctype.h>
#include <regex.h>
#include <syslog.h>		// for syslog() to log calls
#include <openssl/md5.h>
#include <openssl/sha.h>

#include <Security/Authorization.h>

#include "CNiPlugIn.h"
#include "CNetInfoPlugin.h"
#include "SMBAuth.h"
#include "CBuff.h"
#include "CSharedData.h"
#include "CString.h"
#include "CFile.h"
#include "NiLib2.h"
#include "NiLib3.h"
#include "DSUtils.h"
#include "DirServicesConst.h"
#include "DirServices.h"
#include "DirServicesUtils.h"
#include "ServerModuleLib.h"
#include "CRecTypeList.h"
#include "CRecNameList.h"
#include "SharedConsts.h"
#include "PrivateTypes.h"
#include "CPlugInRef.h"
#include "CContinue.h"
#include "CRCCalc.h"
#include "CDataBuff.h"
#include "CLog.h"
#include "TimConditional.h"
#include "my_ni_pwdomain.h"

// Auth server
#ifdef TIM_CLIENT_PRESENT
#include <TimClient/TimClient.h>
#endif

// -- Typedef's -------------------------------------

#define		kAttrConsts		130

//re-ordered more closely in terms of frequency of use
//TODO KW put this in a CFDictionary or STL map class for keyed access
//NOTE: Updating this list likely requires a corressponding update in DSAgent
static const char *sAttrMap[ kAttrConsts ][ 2 ] =
{
				{ kDSNAttrRecordName,			"name" },
				{ kDS1AttrDistinguishedName,	"realname" },
				{ kDS1AttrPassword,				"passwd" },
				{ kDS1AttrPasswordPlus,			"passwd" },  //forward mapping only as reverse mapping uses entry above
				{ kDS1AttrUniqueID,				"uid" },
				{ kDS1AttrPrimaryGroupID,		"gid" },
				{ kDS1AttrUserShell,			"shell" },
				{ kDS1AttrNFSHomeDirectory,		"home" },
				{ kDSNAttrAuthenticationAuthority, "authentication_authority" },
				{ kDSNAttrHomeDirectory,		"home_loc" }, // or HomeLocation
				{ kDS1AttrHomeLocOwner,			"home_loc_owner" },
				{ kDS1AttrHomeDirectoryQuota,	"homedirectoryquota" },
				{ kDS1AttrHomeDirectorySoftQuota, "homedirectorysoftquota" },
				{ kDS1AttrPicture,				"picture" },
				{ kDS1AttrAuthenticationHint,	"hint" },
				{ kDS1AttrRARA,					"RARA" },
				{ kDS1AttrGeneratedUID,			"generateduid" },
				{ kDS1AttrRealUserID,			"ruid" },
				{ kDSNAttrGroupMembership,		"users" },
				{ kDSNAttrEMailAddress,			"mail" },
				{ kDS1AttrAlternateDatastoreLocation, "alternatedatastorelocation" },
				{ kDSNAttrURL,					"URL" },
				{ kDSNAttrURLForNSL,			"URL" },
				{ kDSNAttrMIME,					"mime" },
				{ kDSNAttrHTML,					"htmldata" },
				{ kDSNAttrIPAddress,			"IP_Address" },
				{ kDS1AttrENetAddress,			"en_address" },
				{ kDSNAttrKeywords,				"keywords" },
				{ kDSNAttrComputers,			"computers" },
				{ kDS1AttrMCXFlags,				"mcx_flags" },
				{ kDS1AttrMCXSettings,			"mcx_settings" },
				{ kDSNAttrMCXSettings,			"mcx_settings" },
				{ kDS1AttrCopyTimestamp,		"copy_timestamp" },
				{ kDS1AttrOriginalNodeName,		"original_node_name" },
				{ kDS1AttrOriginalNFSHomeDirectory,	"original_home" },
				{ kDSNAttrOriginalHomeDirectory,"original_home_loc" },
				{ kDS1AttrPasswordPolicyOptions,"passwordpolicyoptions" },
				{ kDS1AttrPasswordServerList,	"passwordserverlist" },
				{ kDS1AttrXMLPlist,				"XMLPlist" },
				{ kDS1AttrDataStamp,			"data_stamp" },
				{ kDS1AttrPrintServiceInfoText,	"PrintServiceInfoText" },
				{ kDS1AttrPrintServiceInfoXML,	"PrintServiceInfoXML" },
				{ kDS1AttrPrintServiceUserData,	"appleprintservice" },
				{ kDS1AttrNote,					"note" },
				{ kDS1AttrPrinterLPRHost,		"rm" },
				{ kDS1AttrPrinterLPRQueue,		"rp" },
				{ kDS1AttrPrinterType,			"ty" },
				{ kDS1AttrPrinter1284DeviceID,  "1284deviceid" },
				{ kDS1AttrPrinterMakeAndModel,  "makeandmodel" },
				{ kDS1AttrPrinterURI,			"uri" },
				{ kDSNAttrPrinterXRISupported,  "xrisupported" },
				{ kDS1AttrVFSType,				"vfstype" },
				{ kDS1AttrVFSPassNo,			"passno" },
				{ kDS1AttrVFSDumpFreq,			"dump_freq" },
				{ kDS1AttrVFSLinkDir,			"dir" },
				{ kDSNAttrVFSOpts,				"opts" },
				{ kDS1AttrAliasData,			"alias_data" },
				{ kDSNAttrPhoneNumber,			"phonenumber" },
				{ kDSNAttrMember,				"users" },
				{ kDSNAttrAllNames,				"dsAttrTypeStandard:AllNames" },
				{ kStandardTargetAlias,			"dsAttrTypeStandard:AppleMetaAliasTarget" },
				{ kStandardSourceAlias,			"dsAttrTypeStandard:AppleMetaAliasSource" },
				{ kDSNAttrMetaNodeLocation,		"dsAttrTypeStandard:AppleMetaNodeLocation" },
				{ kDS1AttrComment,				"comment" },
				{ kDS1AttrChange,				"change" },
				{ kDS1AttrExpire,				"expire" },
				{ kDSNAttrGroup,				"groups" },
				{ kDS1AttrFirstName,			"firstname" },
				{ kDS1AttrMiddleName,			"middlename" },
				{ kDS1AttrLastName,				"lastname" },
				{ kDSNAttrAreaCode ,			"areacode" },
				{ kDSNAttrAddressLine1,			"address1" },
				{ kDSNAttrAddressLine2,			"address2" },
				{ kDSNAttrAddressLine3,			"address3" },
				{ kDSNAttrCity,					"city" },
				{ kDSNAttrState,				"state" },
				{ kDSNAttrPostalCode,			"zip" },
				{ kDSNAttrOrganizationName,		"orgname" },
				{ kDS1AttrSetupOccupation,		"occupation" },
				{ kDSNAttrFaxNumber,			"faxnumber" },
				{ kDSNAttrMobileNumber,			"mobilenumber" },
				{ kDSNAttrPagerNumber,			"pagernumber" },
				{ kDSNAttrPostalAddress,		"postaladdress" },
				{ kDSNAttrStreet,				"street" },
				{ kDSNAttrDepartment,			"department" },
				{ kDSNAttrNickName,				"nickname" },
				{ kDSNAttrJobTitle,				"jobtitle" },
				{ kDSNAttrIMHandle,				"imhandle" },
				{ kDSNAttrBuilding,				"building" },
				{ kDSNAttrCountry,				"country" },
				{ kDSNAttrNamePrefix,			"nameprefix" },
				{ kDSNAttrNameSuffix,			"namesuffix" },
				{ kDS1AttrSetupLocation,		"location" },
				{ kDS1AttrSetupAdvertising,		"spam" },
				{ kDS1AttrSetupAutoRegister,	"autoregister" },
				{ kDSNAttrKDCAuthKey,			"kdcauthkey" },
				{ kDS1AttrKDCConfigData,		"kdcconfigdata" },
				{ kDS1AttrPresetUserIsAdmin,	"preset_user_is_admin" },
				{ kDS1AttrPasswordServerLocation, "passwordserverlocation" },
				{ kDSNAttrBootParams,			"bootparams" },
				{ kDSNAttrNetGroups,			"netgroups" },
				{ kDS1AttrSMBRID,				"smb_rid" },
				{ kDS1AttrSMBGroupRID,			"smb_group_rid" },
				{ kDS1AttrSMBHomeDrive,			"smb_home_drive" },
				{ kDS1AttrSMBHome,				"smb_home" },
				{ kDS1AttrSMBScriptPath,		"smb_script_path" },
				{ kDS1AttrSMBProfilePath,		"smb_profile_path" },
				{ kDS1AttrSMBUserWorkstations,	"smb_user_workstations" },
				{ kDS1AttrSMBAcctFlags,			"smb_acctFlags" },
				{ kDS1AttrSMBPWDLastSet,		"smb_pwd_last_set" },
				{ kDS1AttrSMBLogonTime,			"smb_logon_time" },
				{ kDS1AttrSMBLogoffTime,		"smb_logoff_time" },
				{ kDS1AttrSMBKickoffTime,		"smb_kickoff_time" },
				{ kDS1AttrDNSDomain,			"domain" },
				{ kDS1AttrDNSNameServer,		"nameserver" },
				{ "dsAttrTypeStandard:NIAutoSwitchToLDAP",	"niautoswitchtoldap" },
				{ kDS1AttrLocation,				"location" },
				{ kDS1AttrPort,					"port" },
				{ kDS1AttrServiceType,			"servicetype" },
				{ kDSNAttrPGPPublicKey,			"pgppublickey" },
				{ kDS1AttrInternetAlias,		"InetAlias" },
				{ kDS1AttrMailAttribute,		"applemail" },
				{ kDSNAttrNBPEntry,				"NBPEntry" },
				{ kDSNAttrDNSName,				"dnsname" },
				{ kDS1AttrAdminStatus,			"AdminStatus" },
				{ kDS1AttrAdminLimits,			"admin_limits" },
				{ kDS1AttrCapabilities,			"capabilities" },
				{ kDSNAttrProtocols,			"protocols" },
				{ kDS1AttrPwdAgingPolicy,		"PwdAgingPolicy" },
				{ kDSNAttrRecordAlias,			"RecordAlias" }	// <- go away?
};


#define		kRecConsts		44

static const char *sRecMap[ kRecConsts ][ 2 ] =
{
				{ kDSStdRecordTypeUsers,			"users" },
				{ kDSStdRecordTypeUserAliases,		"user_aliases" },
				{ kDSStdRecordTypeGroups,			"groups" },
				{ kDSStdRecordTypeGroupAliases,		"group_aliases" },
				{ kDSStdRecordTypeMachines,			"machines" },
				{ kDSStdRecordTypeComputers,		"computers" },	
				{ kDSStdRecordTypeComputerLists,	"computer_lists" },
				{ kDSStdRecordTypePrinters,			"printers" },
				{ kDSStdRecordTypeHosts,			"machines" },
				{ kDSStdRecordTypeAliases,			"aliases" },
				{ kDSStdRecordTypeNetworks,			"networks" },
				{ kDSStdRecordTypeServer,			"servers" },
				{ kDSStdRecordTypePasswordServer,	"passwordservers" },
				{ kDSStdRecordTypeWebServer,   		"httpservers" },
				{ kDSStdRecordTypeFTPServer,		"ftpservers" },
				{ kDSStdRecordTypeAFPServer,		"afpservers" },
				{ kDSStdRecordTypeLDAPServer,		"ldapservers" },
				{ kDSStdRecordTypeSMBServer,		"smbservers" },
				{ kDSStdRecordTypeQTSServer,		"qtsservers" },
				{ kDSStdRecordTypeNFS,				"mounts" },
				{ kDSStdRecordTypeServices,			"services" },
				{ kDSStdRecordTypePrintService,		"PrintService" },
				{ kDSStdRecordTypeConfig,			"config" },
				{ kDSStdUserNamesMeta,				"dsRecTypeStandard:MetaUserNames" }, //?
				{ kDSStdRecordTypeMeta,				"dsRecTypeStandard:AppleMetaRecord" },
				{ "dsRecTypeStandard:UsreNames",	"users" }, //?
				{ kDSStdRecordTypeAFPUserAliases,	"afpuser_aliases" },
				{ kDSStdRecordTypeMounts,			"mounts" },
				{ kDSStdRecordTypePrintServiceUser,	"printserviceusers" },
				{ kDSStdRecordTypePresetUsers,		"presets_users" },
				{ kDSStdRecordTypePresetGroups,		"presets_groups" },
				{ kDSStdRecordTypePresetComputerLists,	"presets_computer_lists" },
				{ kDSStdRecordTypeHosts,			"hosts" },
				{ kDSStdRecordTypeProtocols,		"protocols" },
				{ kDSStdRecordTypeRPC,				"rpcs" },
				{ kDSStdRecordTypeBootp,			"bootp" },
				{ kDSStdRecordTypeNetDomains,		"netdomains" },
				{ kDSStdRecordTypeEthernets,		"ethernets" },
				{ kDSStdRecordTypeNetGroups,		"netgroups" },
				{ kDSStdRecordTypeHostServices,		"hostservices" },
				{ kDSStdRecordTypeAutoServerSetup,	"autoserversetup" },
				{ kDSStdRecordTypeLocations,		"locations" },
				{ kDSStdRecordTypeSharePoints,		"config/SharePoints" },
				{ kDSStdRecordTypePeople,			"people" }
};

typedef struct NetInfoAuthAuthorityHandler {
	char* fTag;
	NetInfoAuthAuthorityHandlerProc fHandler;
} NetInfoAuthAuthorityHandler;

#define		kNetInfoAuthAuthorityHandlerProcs		6

static NetInfoAuthAuthorityHandler sNetInfoAuthAuthorityHandlerProcs[ kNetInfoAuthAuthorityHandlerProcs ] =
{
	{ kDSTagAuthAuthorityBasic,				(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoBasicAuth },
	{ kDSTagAuthAuthorityLocalWindowsHash,	(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoShadowHashAuth },
	{ kDSTagAuthAuthorityShadowHash,		(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoShadowHashAuth },
	{ kDSTagAuthAuthorityPasswordServer,	(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoPasswordServerAuth },
	{ kDSTagAuthAuthorityDisabledUser,		(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoDisabledAuth },
	{ kDSTagAuthAuthorityLocalCachedUser,	(NetInfoAuthAuthorityHandlerProc)CNiPlugIn::DoLocalCachedUserAuth }
};

// --------------------------------------------------------------------------------
//	Globals

CPlugInRef		   *gNINodeRef			= nil;
CContinue		   *gNIContinue			= nil;
time_t				gCheckTimAgainTime	= 0;
bool				gTimIsRunning		= false;

static const uInt32		kNodeInfoBuffTag	= 'NInf';


// Enums -----------------------------------------------------------------------------

typedef	enum
{
	eHasAuthMethod			= -7000,
	eNoAuthServer			= -7001,
	eNoAuthMethods			= -7002,
	eAuthMethodNotFound		= -7003
} eAuthValues;


//------------------------------------------------------------------------------------
//	* CNiPlugIn
//------------------------------------------------------------------------------------

CNiPlugIn::CNiPlugIn ( void )
{
	if ( gNINodeRef == nil )
	{
		gNINodeRef = new CPlugInRef( CNiPlugIn::ContextDeallocProc );
	}

	if ( gNIContinue == nil )
	{
		gNIContinue = new CContinue( CNiPlugIn::ContinueDeallocProc );
	}

	fRecData	= new CDataBuff();
	fAttrData	= new CDataBuff();
	fTmpData	= new CDataBuff();
	
} // CNiPlugIn


//------------------------------------------------------------------------------------
//	* InitService
//------------------------------------------------------------------------------------

CNiPlugIn::~CNiPlugIn ( void )
{
	if ( fRecData != nil )
	{
		delete( fRecData );
		fRecData = nil;
	}

	if ( fAttrData != nil )
	{
		delete( fAttrData );
		fAttrData = nil;
	}

	if ( fTmpData != nil )
	{
		delete( fTmpData );
		fTmpData = nil;
	}

} // ~CNiPlugIn


//------------------------------------------------------------------------------------
//	* HandleRequest
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::HandleRequest ( void *inData )
{
	sInt32		siResult	= 0;
	sHeader	   *pMsgHdr		= nil;

	if ( inData == nil )
	{
		return( -8088 );
	}

	pMsgHdr = (sHeader *)inData;

	switch ( pMsgHdr->fType )
	{
		case kReleaseContinueData:
			siResult = ReleaseContinueData( (sReleaseContinueData *)inData );
			break;

		case kOpenDirNode:
			siResult = OpenDirNode( (sOpenDirNode *)inData );
			break;

		case kCloseDirNode:
			siResult = CloseDirNode( (sCloseDirNode *)inData );
			break;

		case kGetDirNodeInfo:
			siResult = GetDirNodeInfo( (sGetDirNodeInfo *)inData );
			break;

		case kGetRecordList:
			siResult = GetRecordList( (sGetRecordList *)inData );
			break;

		case kGetRecordEntry:
			siResult = GetRecordEntry( (sGetRecordEntry *)inData );
			break;

		case kGetAttributeEntry:
			siResult = GetAttributeEntry( (sGetAttributeEntry *)inData );
			break;

		case kGetAttributeValue:
			siResult = GetAttributeValue( (sGetAttributeValue *)inData );
			break;

		case kCloseAttributeList:
			siResult = CloseAttributeList( (sCloseAttributeList *)inData );
			break;

		case kCloseAttributeValueList:
			siResult = CloseAttributeValueList( (sCloseAttributeValueList *)inData );
			break;

		case kOpenRecord:
			siResult = OpenRecord( (sOpenRecord *)inData );
			break;

		case kGetRecordReferenceInfo:
			siResult = GetRecRefInfo( (sGetRecRefInfo *)inData );
			break;

		case kGetRecordAttributeInfo:
			siResult = GetRecAttribInfo( (sGetRecAttribInfo *)inData );
			break;

		case kGetRecordAttributeValueByID:
			siResult = GetRecordAttributeValueByID( (sGetRecordAttributeValueByID *)inData );
			break;

		case kGetRecordAttributeValueByIndex:
			siResult = GetRecAttrValueByIndex( (sGetRecordAttributeValueByIndex *)inData );
			break;

		case kCloseRecord:
			siResult = CloseRecord( (sCloseRecord *)inData );
			break;

		case kSetRecordName:
			siResult = SetRecordName( (sSetRecordName *)inData );
			break;

		case kSetRecordType:
			siResult = SetRecordType( (sSetRecordType *)inData );
			break;

		case kDeleteRecord:
			siResult = DeleteRecord( (sDeleteRecord *)inData );
			break;

		case kCreateRecord:
		case kCreateRecordAndOpen:
			siResult = CreateRecord( (sCreateRecord *)inData );
			break;

		case kAddAttribute:
			siResult = AddAttribute( (sAddAttribute *)inData );
			break;

		case kRemoveAttribute:
			siResult = RemoveAttribute( (sRemoveAttribute *)inData );
			break;

		case kAddAttributeValue:
			siResult = AddAttributeValue( (sAddAttributeValue *)inData );
			break;

		case kRemoveAttributeValue:
			siResult = RemoveAttributeValue( (sRemoveAttributeValue *)inData );
			break;

		case kSetAttributeValue:
			siResult = SetAttributeValue( (sSetAttributeValue *)inData );
			break;

		case kDoDirNodeAuth:
			siResult = DoAuthentication( (sDoDirNodeAuth *)inData );
			break;

		case kDoDirNodeAuthOnRecordType:
			siResult = DoAuthenticationOnRecordType( (sDoDirNodeAuthOnRecordType *)inData );
			break;

		case kDoAttributeValueSearch:
		case kDoAttributeValueSearchWithData:
			siResult = DoAttributeValueSearch( (sDoAttrValueSearchWithData *)inData );
			break;

		case kDoPlugInCustomCall:
			siResult = DoPlugInCustomCall( (sDoPlugInCustomCall *)inData );
			break;

		case kFlushRecord:
			siResult = eDSNoErr;
			break;

		default:
			siResult = eNotHandledByThisNode;
			break;
	}

	pMsgHdr->fResult = siResult;

	return( siResult );

} // HandleRequest


//------------------------------------------------------------------------------------
//	* ReleaseContinueData
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::ReleaseContinueData ( sReleaseContinueData *inData )
{
	sInt32	siResult	= eDSNoErr;

	gNetInfoMutex->Wait();
	
	// RemoveItem calls our ContinueDeallocProc to clean up
	if ( gNIContinue->RemoveItem( inData->fInContinueData ) != eDSNoErr )
	{
		siResult = eDSInvalidContext;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // ReleaseContinueData


//------------------------------------------------------------------------------------
//	* OpenDirNode
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::OpenDirNode ( sOpenDirNode *inData )
{
	sInt32			siResult		= eDSNoErr;
	char		   *nameStr			= nil;
	char		   *pathStr			= nil;
	char		   *domName			= nil;
	void		   *domain			= nil;
	sInt32			timeOutSecs		= 3;
	sNIContextData   *pContext		= nil;
	ni_id			niRootDir;

	try
	{
		nameStr = ::dsGetPathFromListPriv( inData->fInDirNodeName, kstrDelimiter );
		if ( nameStr == nil ) throw( (sInt32)eDSNullNodeName );

		pathStr = BuildDomainPathFromName( nameStr );
		if ( pathStr == nil ) throw( (sInt32)eDSUnknownNodeName );

		siResult = CNetInfoPlugin::SafeOpen( pathStr, timeOutSecs, &niRootDir, &domain, &domName );
		if ( siResult != eDSNoErr ) throw( siResult );

		if (::strcmp(domName,"") == 0)
		{
			siResult = eDSOpenNodeFailed;
			if ( siResult != eDSNoErr ) throw( siResult );
		}
		
		pContext = MakeContextData();
		if ( pContext == nil ) throw( (sInt32)eMemoryAllocError );

		pContext->fDomain = domain;
		pContext->fDomainName = domName; //we own this char* now
		pContext->fUID = inData->fInUID;
		pContext->fEffectiveUID = inData->fInEffectiveUID;
		if (strcmp(pathStr,".") == 0)
		{
			pContext->bIsLocal = true;
		}

		gNINodeRef->AddItem( inData->fOutNodeRef, pContext );
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( pathStr != nil )
	{
		free( pathStr );
		pathStr = nil;
	}
	if ( nameStr != nil )
	{
		free( nameStr );
		nameStr = nil;
	}

	return( siResult );

} // OpenDirNode


//------------------------------------------------------------------------------------
//	* CloseDirNode
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::CloseDirNode ( sCloseDirNode *inData )
{
	sInt32			siResult		= NI_OK;
	sNIContextData   *pContext		= nil;

	//gNetInfoMutex->Wait(); //don't hold mutex here since leads to deadlock in cleanup

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		// RemoveItem calls our ContextDeallocProc to clean up
		gNINodeRef->RemoveItem( inData->fInNodeRef );
		gNIContinue->RemoveItems( inData->fInNodeRef );
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	//gNetInfoMutex->Signal();

	return( siResult );

} // CloseDirNode


//------------------------------------------------------------------------------------
//	* GetDirNodeInfo
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetDirNodeInfo ( sGetDirNodeInfo *inData )
{
	sInt32				siResult		= eDSNoErr;
	uInt32				uiOffset		= 0;
#ifdef TIM_CLIENT_PRESENT
	sInt32				siTimStatus		= eDSNoErr;
	TIMHandle		   *timHndl			= nil;
	Buffer			   *pTmp			= nil;
	Buffer			   *pTimInfo		= nil;
	uInt32				timAuthCount	= 0;
	char			   *pBuffStr		= nil;
	CString			   *pAuthStr		= nil;
	uInt32				timAuthAvail	= 0;
#endif
	uInt32				i				= 0;
	uInt32				authCount		= 0;
	uInt32				uiCntr			= 1;
	uInt32				uiAttrCnt		= 0;
	CAttributeList	   *inAttrList		= nil;
	char			   *pAttrName		= nil;
	char			   *pData			= nil;
	sNIContextData	   *pContext		= nil;
	sNIContextData	   *pAttrContext	= nil;
	char			   *niDomNameStr	= nil;
	tDataList		   *pNodePath		= nil;
	tDataNode		   *pDataNode		= nil;
	CBuff				outBuff;
	time_t				delayedNI		= 0;

	gNetInfoMutex->Wait();

	try
	{
		if ( inData == nil ) throw( (sInt32)eMemoryError );

		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		inAttrList = new CAttributeList( inData->fInDirNodeInfoTypeList );
		if ( inAttrList == nil ) throw( (sInt32)eDSNullNodeInfoTypeList );
		if (inAttrList->GetCount() == 0) throw( (sInt32)eDSEmptyNodeInfoTypeList );

		siResult = outBuff.Initialize( inData->fOutDataBuff, true );
		if ( siResult != eDSNoErr ) throw( siResult );

		siResult = outBuff.SetBuffType( 'Gdni' ); //Cannot use 'StdA' since no tRecordEntry returned
		if ( siResult != eDSNoErr ) throw( siResult );

		fRecData->Clear();
		fAttrData->Clear();

		// Set the record name and type
		fRecData->AppendShort( ::strlen( "dsAttrTypeStandard:DirectoryNodeInfo" ) );
		fRecData->AppendString( "dsAttrTypeStandard:DirectoryNodeInfo" );
		fRecData->AppendShort( ::strlen( "DirectoryNodeInfo" ) );
		fRecData->AppendString( "DirectoryNodeInfo" );

		while ( inAttrList->GetAttribute( uiCntr++, &pAttrName ) == eDSNoErr )
		{
			if ( (::strcmp( pAttrName, kDSAttributesAll ) == 0) || 
				 (::strcmp( pAttrName, kDSNAttrAuthMethod ) == 0) )
			{
				fTmpData->Clear();

				uiAttrCnt++;

				// Append the attribute name
				fTmpData->AppendShort( ::strlen( kDSNAttrAuthMethod ) );
				fTmpData->AppendString( kDSNAttrAuthMethod );

				if ( inData->fInAttrInfoOnly == false )
				{
#ifdef TIM_CLIENT_PRESENT
					//if tim is running then we can add some additional auth methods to our list
					if ( IsTimRunning() )
					{
						authCount = 7;

						// call tim to ask what auth methods are available
						//and compare to the ones we might actually use since we determine what are available and
						//we can call whatever tim methods we want to support those methods

						timHndl = ::timServerForDomain( pContext->fDomain );
						if ( timHndl != nil )
						{
							siTimStatus = ::timGetOperations( timHndl, TimOpAuthentication, &pTimInfo );
							if ( siTimStatus == eDSNoErr )
							{
								timAuthCount = ::bufferArrayCount( pTimInfo );
								if ( timAuthCount != 0 )
								{
									for ( i = 0; i < timAuthCount; i++ )
									{
										pTmp = ::bufferArrayBufferAtIndex( pTimInfo, i );
										if ( pTmp != nil )
										{
											pBuffStr = ::bufferToString( pTmp );
											pAuthStr = GetAuthTypeStr( pBuffStr );
											if ( pAuthStr != nil )
											{
												if (strcmp(kDSStdAuth2WayRandom,pAuthStr->GetData()) == 0)
												{
													authCount++;
													timAuthAvail |= 1;
												}
												if (strcmp(kDSStdAuthSMB_NT_Key,pAuthStr->GetData()) == 0)
												{
													authCount++;
													timAuthAvail |= 2;
												}
												if (strcmp(kDSStdAuthSMB_LM_Key,pAuthStr->GetData()) == 0)
												{
													authCount++;
													timAuthAvail |= 4;
												}
												delete( pAuthStr );
												pAuthStr = nil;
											}

											::bufferRelease( pTmp );
											pTmp = nil;

											if ( pBuffStr != nil )
											{
												free( pBuffStr );
												pBuffStr = nil;
											}
										}
									}
								}
							}
							if ( pTimInfo != nil )
							{
								::bufferRelease( pTimInfo );
								pTimInfo = nil;
							}
							::timHandleFree( timHndl );
							timHndl = nil;
						}
						
						fTmpData->AppendShort( authCount );
						if (timAuthAvail & 1)
						{
							fTmpData->AppendLong( ::strlen( kDSStdAuth2WayRandom ) );
							fTmpData->AppendString( kDSStdAuth2WayRandom );
						}
						if (timAuthAvail & 2)
						{
							fTmpData->AppendLong( ::strlen( kDSStdAuthSMB_NT_Key ) );
							fTmpData->AppendString( kDSStdAuthSMB_NT_Key );
						}
						if (timAuthAvail & 4)
						{
							fTmpData->AppendLong( ::strlen( kDSStdAuthSMB_LM_Key ) );
							fTmpData->AppendString( kDSStdAuthSMB_LM_Key );
						}
					}
					else //we have our own internal auth methods
#endif
					{
						authCount = 7;
						fTmpData->AppendShort( authCount );
					}
					//for either tim or not these methods are available
					fTmpData->AppendLong( ::strlen( kDSStdAuthClearText ) );
					fTmpData->AppendString( kDSStdAuthClearText );
					fTmpData->AppendLong( ::strlen( kDSStdAuthCrypt ) );
					fTmpData->AppendString( kDSStdAuthCrypt );
					fTmpData->AppendLong( ::strlen( kDSStdAuthSetPasswd ) );
					fTmpData->AppendString( kDSStdAuthSetPasswd );
					fTmpData->AppendLong( ::strlen( kDSStdAuthChangePasswd ) );
					fTmpData->AppendString( kDSStdAuthChangePasswd );
					fTmpData->AppendLong( ::strlen( kDSStdAuthSetPasswdAsRoot ) );
					fTmpData->AppendString( kDSStdAuthSetPasswdAsRoot );
					fTmpData->AppendLong( ::strlen( kDSStdAuthNodeNativeClearTextOK ) );
					fTmpData->AppendString( kDSStdAuthNodeNativeClearTextOK );
					fTmpData->AppendLong( ::strlen( kDSStdAuthNodeNativeNoClearText ) );
					fTmpData->AppendString( kDSStdAuthNodeNativeNoClearText );

				}
				else
				{
					fTmpData->AppendShort( 0 );
				}

				// Add the attribute length
				fAttrData->AppendLong( fTmpData->GetLength() );
				fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

				// Clear the temp block
				fTmpData->Clear();
			}
			
			if ( (::strcmp( pAttrName, kDSAttributesAll ) == 0) || 
				 (::strcmp( pAttrName, kDS1AttrReadOnlyNode ) == 0) )
			{
				fTmpData->Clear();

				uiAttrCnt++;

				// Append the attribute name
				fTmpData->AppendShort( ::strlen( kDS1AttrReadOnlyNode ) );
				fTmpData->AppendString( kDS1AttrReadOnlyNode );

				if ( inData->fInAttrInfoOnly == false )
				{
					// Attribute value count
					fTmpData->AppendShort( 1 );

					//possible for a node to be ReadOnly, ReadWrite, WriteOnly
					//note that ReadWrite does not imply fully readable or writable
					
					// Add the root node as an attribute value
					fTmpData->AppendLong( ::strlen( "ReadWrite" ) );
					fTmpData->AppendString( "ReadWrite" );

				}
				else
				{
					fTmpData->AppendShort( 0 );
				}

				// Add the attribute length and data
				fAttrData->AppendLong( fTmpData->GetLength() );
				fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

				// Clear the temp block
				fTmpData->Clear();
			}
				 
			if ( (::strcmp( pAttrName, kDSAttributesAll ) == 0) || 
				 (::strcmp( pAttrName, kDSNAttrNodePath ) == 0) )
			{
				delayedNI = time(nil) + 2; //normally my_ni_pwdomain will complete in under 2 secs
				siResult = ::my_ni_pwdomain( pContext->fDomain, &niDomNameStr );
				if ( delayedNI < time(nil) )
				{
					if (pContext->fDomainName != nil)
					{
						syslog(LOG_INFO,"GetDirNodeInfo::Call to my_ni_pwdomain was with argument domain name: %s and lasted %d seconds.", pContext->fDomainName, (uInt32)(2 + time(nil) - delayedNI));
					}
					else
					{
						syslog(LOG_INFO,"GetDirNodeInfo::Call to my_ni_pwdomain lasted %d seconds.", (uInt32)(2 + time(nil) - delayedNI));
					}
					if (niDomNameStr != nil)
					{
						syslog(LOG_INFO,"GetDirNodeInfo::Call to my_ni_pwdomain returned domain name: %s.", niDomNameStr);
					}
				}
				//assume that if siResult is an error then niDomNameStr is invalid
				if ( ( siResult != eDSNoErr ) || (niDomNameStr == nil) )
				{
					syslog(LOG_INFO,"GetDirNodeInfo::Call to my_ni_pwdomain failed or returned nil name.");
					//here we force success from the my_ni_pwdomain call
					niDomNameStr = (char *) calloc(1,2);
					strcpy(niDomNameStr,"/");
				}
				if (niDomNameStr != nil)
				{
					fTmpData->Clear();

					uiAttrCnt++;

					// Append the attribute name
					fTmpData->AppendShort( ::strlen( kDSNAttrNodePath ) );
					fTmpData->AppendString( kDSNAttrNodePath );

					if ( inData->fInAttrInfoOnly == false )
					{
						// Is this is the root node
						if ( ::strcmp( niDomNameStr, "/" ) == 0 )
						{
							// Attribute count of 1
							fTmpData->AppendShort( 2 );

							// Add the kstrNetInfoName as an attribute value
							fTmpData->AppendLong( ::strlen( kstrNetInfoName ) );
							fTmpData->AppendString( kstrNetInfoName );

							// Add the kstrRootOnly as an attribute value
							fTmpData->AppendLong( ::strlen( kstrRootOnly ) );
							fTmpData->AppendString( kstrRootOnly );
						}
						else
						{
							// Attribute count of (list count) + 2

							pNodePath = ::dsBuildFromPathPriv( niDomNameStr, "/" );
							if ( pNodePath == nil ) throw( (sInt32)eMemoryAllocError );

							fTmpData->AppendShort( ::dsDataListGetNodeCountPriv( pNodePath ) + 2 );

							// Add the kstrNetInfoName as an attribute value
							fTmpData->AppendLong( ::strlen( kstrNetInfoName ) );
							fTmpData->AppendString( kstrNetInfoName );

							// Add the kstrRootOnly as an attribute value
							fTmpData->AppendLong( ::strlen( kstrRootOnly ) );
							fTmpData->AppendString( kstrRootOnly );

							i = 1;
							while ( ::dsDataListGetNodeAllocPriv( pNodePath, i, &pDataNode ) == eDSNoErr )
							{
								fTmpData->AppendLong( ::strlen( pDataNode->fBufferData ) );
								fTmpData->AppendString( pDataNode->fBufferData );

								::dsDataBufferDeallocatePriv( pDataNode );
								pDataNode = nil;
								i++;
							}

							::dsDataListDeallocatePriv( pNodePath );
							free( pNodePath );
							pNodePath = nil;
						}
					}
					else
					{
						fTmpData->AppendShort( 0 );
					}

					// Add the attribute length and data
					fAttrData->AppendLong( fTmpData->GetLength() );
					fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

					// Clear the temp block
					fTmpData->Clear();
					
					free(niDomNameStr);
					niDomNameStr = nil;
				}
			}
		}

		fRecData->AppendShort( uiAttrCnt );
		fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );

		outBuff.AddData( fRecData->GetData(), fRecData->GetLength() );
		inData->fOutAttrInfoCount = uiAttrCnt;

		pData = outBuff.GetDataBlock( 1, &uiOffset );
		if ( pData != nil )
		{
			pAttrContext = MakeContextData();
			if ( pAttrContext == nil ) throw( (sInt32)eMemoryAllocError );

		//add to the offset for the attr list the length of the GetDirNodeInfo fixed record labels
//		record length = 4
//		fRecData->AppendShort( ::strlen( "dsAttrTypeStandard:DirectoryNodeInfo" ) ); = 2
//		fRecData->AppendString( "dsAttrTypeStandard:DirectoryNodeInfo" ); = 36
//		fRecData->AppendShort( ::strlen( "DirectoryNodeInfo" ) ); = 2
//		fRecData->AppendString( "DirectoryNodeInfo" ); = 17
//		total adjustment = 4 + 2 + 36 + 2 + 17 = 61

			pAttrContext->offset = uiOffset + 61;

			gNINodeRef->AddItem( inData->fOutAttrListRef, pAttrContext );
		}
		else
		{
			siResult = eDSBufferTooSmall;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	if ( inAttrList != nil )
	{
		delete( inAttrList );
		inAttrList = nil;
	}

	return( siResult );

} // GetDirNodeInfo


//------------------------------------------------------------------------------------
//	* GetAttributeEntry
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetAttributeEntry ( sGetAttributeEntry *inData )
{
	sInt32					siResult			= eDSNoErr;
	uInt16					usAttrTypeLen		= 0;
	uInt16					usAttrCnt			= 0;
	uInt32					usAttrLen			= 0;
	uInt16					usValueCnt			= 0;
	uInt32					usValueLen			= 0;
	uInt32					i					= 0;
	uInt32					uiIndex				= 0;
	uInt32					uiAttrEntrySize		= 0;
	uInt32					uiOffset			= 0;
	uInt32					uiTotalValueSize	= 0;
	uInt32					offset				= 0;
	uInt32					buffSize			= 0;
	uInt32					buffLen				= 0;
	char				   *p			   		= nil;
	char				   *pAttrType	   		= nil;
	tDataBuffer			   *pDataBuff			= nil;
	tAttributeValueListRef	attrValueListRef	= 0;
	tAttributeEntryPtr		pAttribInfo			= nil;
	sNIContextData		   *pAttrContext		= nil;
	sNIContextData		   *pValueContext		= nil;

	try
	{
		if ( inData  == nil ) throw( (sInt32)eMemoryError );

		pAttrContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInAttrListRef );
		if ( pAttrContext  == nil ) throw( (sInt32)eDSBadContextData );

		uiIndex = inData->fInAttrInfoIndex;
		if (uiIndex == 0) throw( (sInt32)eDSInvalidIndex );
				
		pDataBuff = inData->fInOutDataBuff;
		if ( pDataBuff  == nil ) throw( (sInt32)eDSNullDataBuff );
		
		buffSize	= pDataBuff->fBufferSize;
		//buffLen		= pDataBuff->fBufferLength;
		//here we can't use fBufferLength for the buffLen SINCE the buffer is packed at the END of the data block
		//and the fBufferLength is the overall length of the data for all blocks at the end of the data block
		//the value ALSO includes the bookkeeping data at the start of the data block
		//so we need to read it here

		p		= pDataBuff->fBufferData + pAttrContext->offset;
		offset	= pAttrContext->offset;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffSize)  throw( (sInt32)eDSInvalidBuffFormat );
				
		// Get the attribute count
		::memcpy( &usAttrCnt, p, 2 );
		if (uiIndex > usAttrCnt) throw( (sInt32)eDSInvalidIndex );

		// Move 2 bytes
		p		+= 2;
		offset	+= 2;

		// Skip to the attribute that we want
		for ( i = 1; i < uiIndex; i++ )
		{
			// Do record check, verify that offset is not past end of buffer, etc.
			if (4 + offset > buffSize)  throw( (sInt32)eDSInvalidBuffFormat );
		
			// Get the length for the attribute
			::memcpy( &usAttrLen, p, 4 );

			// Move the offset past the length word and the length of the data
			p		+= 4 + usAttrLen;
			offset	+= 4 + usAttrLen;
		}

		// Get the attribute offset
		uiOffset = offset;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (4 + offset > buffSize)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the length for the attribute block
		::memcpy( &usAttrLen, p, 4 );

		// Skip past the attribute length
		p		+= 4;
		offset	+= 4;

		//set the bufLen to stricter range
		buffLen = offset + usAttrLen;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the length for the attribute type
		::memcpy( &usAttrTypeLen, p, 2 );
		
		pAttrType = p + 2;
		p		+= 2 + usAttrTypeLen;
		offset	+= 2 + usAttrTypeLen;
		
		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get number of values for this attribute
		::memcpy( &usValueCnt, p, 2 );
		
		p		+= 2;
		offset	+= 2;
		
		for ( i = 0; i < usValueCnt; i++ )
		{
			// Do record check, verify that offset is not past end of buffer, etc.
			if (4 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
			// Get the length for the value
			::memcpy( &usValueLen, p, 4 );
			
			p		+= 4 + usValueLen;
			offset	+= 4 + usValueLen;
			
			uiTotalValueSize += usValueLen;
		}

		uiAttrEntrySize = sizeof( tAttributeEntry ) + usAttrTypeLen + kBuffPad;
		pAttribInfo = (tAttributeEntry *)::calloc( 1, uiAttrEntrySize );

		pAttribInfo->fAttributeValueCount				= usValueCnt;
		pAttribInfo->fAttributeDataSize					= uiTotalValueSize;
		pAttribInfo->fAttributeValueMaxSize				= 512;				// KW this is never used anywhere
		pAttribInfo->fAttributeSignature.fBufferSize	= usAttrTypeLen + kBuffPad;
		pAttribInfo->fAttributeSignature.fBufferLength	= usAttrTypeLen;
		::memcpy( pAttribInfo->fAttributeSignature.fBufferData, pAttrType, usAttrTypeLen );

		attrValueListRef = inData->fOutAttrValueListRef;

		pValueContext = MakeContextData();
		if ( pValueContext  == nil ) throw( (sInt32)eMemoryAllocError );

		pValueContext->offset = uiOffset;

		gNINodeRef->AddItem( inData->fOutAttrValueListRef, pValueContext );

		inData->fOutAttrInfoPtr = pAttribInfo;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	return( siResult );

} // GetAttributeEntry


//------------------------------------------------------------------------------------
//	* GetRecordEntry
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecordEntry ( sGetRecordEntry *inData )
{
	sInt32					siResult		= eDSNoErr;
	uInt32					uiIndex			= 0;
	uInt32					uiCount			= 0;
	uInt32					uiOffset		= 0;
	uInt32					uberOffset		= 0;
	char 				   *pData			= nil;
	tRecordEntryPtr			pRecEntry		= nil;
	sNIContextData		   *pContext		= nil;
	CBuff					inBuff;
	uInt32					offset			= 0;
	uInt16					usTypeLen		= 0;
	char				   *pRecType		= nil;
	uInt16					usNameLen		= 0;
	char				   *pRecName		= nil;
	uInt16					usAttrCnt		= 0;
	uInt32					buffLen			= 0;

	try
	{
		if ( inData  == nil ) throw( (sInt32)eMemoryError );
		if ( inData->fInOutDataBuff  == nil ) throw( (sInt32)eDSEmptyBuffer );
		if (inData->fInOutDataBuff->fBufferSize == 0) throw( (sInt32)eDSEmptyBuffer );

		siResult = inBuff.Initialize( inData->fInOutDataBuff );
		if ( siResult != eDSNoErr ) throw( siResult );

		siResult = inBuff.GetDataBlockCount( &uiCount );
		if ( siResult != eDSNoErr ) throw( siResult );

		uiIndex = inData->fInRecEntryIndex;
		if ((uiIndex > uiCount) || (uiIndex == 0)) throw( (sInt32)eDSInvalidIndex );

		pData = inBuff.GetDataBlock( uiIndex, &uberOffset );
		if ( pData  == nil ) throw( (sInt32)eDSCorruptBuffer );

		//assume that the length retrieved is valid
		buffLen = inBuff.GetDataBlockLength( uiIndex );
		
		// Skip past this same record length obtained from GetDataBlockLength
		pData	+= 4;
		offset	= 0; //buffLen does not include first four bytes

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the length for the record type
		::memcpy( &usTypeLen, pData, 2 );
		
		pData	+= 2;
		offset	+= 2;

		pRecType = pData;
		
		pData	+= usTypeLen;
		offset	+= usTypeLen;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the length for the record name
		::memcpy( &usNameLen, pData, 2 );
		
		pData	+= 2;
		offset	+= 2;

		pRecName = pData;
		
		pData	+= usNameLen;
		offset	+= usNameLen;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the attribute count
		::memcpy( &usAttrCnt, pData, 2 );

		pRecEntry = (tRecordEntry *)::calloc( 1, sizeof( tRecordEntry ) + usNameLen + usTypeLen + 4 + kBuffPad );

		pRecEntry->fRecordNameAndType.fBufferSize	= usNameLen + usTypeLen + 4 + kBuffPad;
		pRecEntry->fRecordNameAndType.fBufferLength	= usNameLen + usTypeLen + 4;

		// Add the record name length
		::memcpy( pRecEntry->fRecordNameAndType.fBufferData, &usNameLen, 2 );
		uiOffset += 2;

		// Add the record name
		::memcpy( pRecEntry->fRecordNameAndType.fBufferData + uiOffset, pRecName, usNameLen );
		uiOffset += usNameLen;

		// Add the record type length
		::memcpy( pRecEntry->fRecordNameAndType.fBufferData + uiOffset, &usTypeLen, 2 );

		// Add the record type
		uiOffset += 2;
		::memcpy( pRecEntry->fRecordNameAndType.fBufferData + uiOffset, pRecType, usTypeLen );

		pRecEntry->fRecordAttributeCount = usAttrCnt;

		pContext = MakeContextData();
		if ( pContext  == nil ) throw( (sInt32)eMemoryAllocError );

		pContext->offset = uberOffset + offset + 4;	// context used by next calls of GetAttributeEntry
													// include the four bytes of the buffLen
		
		gNINodeRef->AddItem( inData->fOutAttrListRef, pContext );

		inData->fOutRecEntryPtr = pRecEntry;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	return( siResult );

} // GetRecordEntry


//------------------------------------------------------------------------------------
//	* GetRecordList
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecordList ( sGetRecordList *inData )
{
	sInt32					siResult		= eDSNoErr;
	uInt32					uiTotal			= 0;
	uInt32					uiCount			= 0;
	char				   *pRecName		= nil;
	char				   *pRecType		= nil;
	char				   *pNIRecType		= nil;
	bool					bAttribOnly		= false;
	tDirPatternMatch		pattMatch		= eDSNoMatch1;
	CRecNameList		   *cpRecNameList	= nil;
	CRecTypeList		   *cpRecTypeList	= nil;
	CAttributeList		   *cpAttrTypeList 	= nil;
	sNIContextData		   *pContext		= nil;
	sNIContinueData		   *pContinue		= nil;
	CBuff				   *outBuff			= nil;
	bool					bBuffFull			= false;
	bool					separateRecTypes	= false;
	uInt32					countDownRecTypes	= 0;

	try
	{
		// Verify all the parameters
		if ( inData == nil ) throw( (sInt32)eMemoryError );
		if ( inData->fInDataBuff == nil ) throw( (sInt32)eDSEmptyBuffer );
		if (inData->fInDataBuff->fBufferSize == 0) throw( (sInt32)eDSEmptyBuffer );

		if ( inData->fInRecNameList == nil ) throw( (sInt32)eDSEmptyRecordNameList );
		if ( inData->fInRecTypeList == nil ) throw( (sInt32)eDSEmptyRecordTypeList );
		if ( inData->fInAttribTypeList == nil ) throw( (sInt32)eDSEmptyAttributeTypeList );

		// Node context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		if ( inData->fIOContinueData == nil )
		{
			pContinue = (sNIContinueData *)::calloc( sizeof( sNIContinueData ), sizeof( char ) );

			gNIContinue->AddItem( pContinue, inData->fInNodeRef );

			pContinue->fRecNameIndex	= 1;
			pContinue->fAllRecIndex		= 0;
			pContinue->fTotalRecCount	= 0;
			pContinue->fMultiMapIndex	= 0;
			pContinue->fRecTypeIndex	= 1;
			pContinue->fAttrIndex		= 1;
			pContinue->fLimitRecSearch	= 0;
			
			//check if the client has requested a limit on the number of records to return
			//we only do this the first call into this context for pContinue
			if (inData->fOutRecEntryCount >= 0)
			{
				pContinue->fLimitRecSearch = inData->fOutRecEntryCount;
			}
		}
		else
		{
			pContinue = (sNIContinueData *)inData->fIOContinueData;
			if ( gNIContinue->VerifyItem( pContinue ) == false )
			{
				throw( (sInt32)eDSInvalidContinueData );
			}
		}

		inData->fIOContinueData		= nil;
		//return zero if we don't find anything
		inData->fOutRecEntryCount	= 0;

		outBuff = new CBuff();
		if ( outBuff == nil ) throw( (sInt32)eMemoryError );

		siResult = outBuff->Initialize( inData->fInDataBuff, true );
		if ( siResult != eDSNoErr ) throw( siResult );

		siResult = outBuff->GetBuffStatus();
		if ( siResult != eDSNoErr ) throw( siResult );

		siResult = outBuff->SetBuffType( 'StdA' );
		if ( siResult != eDSNoErr ) throw( siResult );

		// Get the record name list
		cpRecNameList = new CRecNameList( inData->fInRecNameList );
		if ( cpRecNameList == nil ) throw( (sInt32)eDSEmptyRecordNameList );
		if (cpRecNameList->GetCount() == 0) throw( (sInt32)eDSEmptyRecordNameList );

		// Get the record pattern match
		pattMatch = inData->fInPatternMatch;
		siResult = VerifyPatternMatch( pattMatch );
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidPatternMatchType );

		// Get the record type list
		cpRecTypeList = new CRecTypeList( inData->fInRecTypeList );
		if ( cpRecTypeList == nil ) throw( (sInt32)eDSEmptyRecordTypeList );
		if (cpRecTypeList->GetCount() == 0) throw( (sInt32)eDSEmptyRecordTypeList );
		//save the number of rec types here to use in separating the buffer data
		countDownRecTypes = cpRecTypeList->GetCount() - pContinue->fRecTypeIndex + 1;

		// Get the attribute list
		cpAttrTypeList = new CAttributeList( inData->fInAttribTypeList );
		if ( cpAttrTypeList == nil ) throw( (sInt32)eDSEmptyAttributeTypeList );
		if (cpAttrTypeList->GetCount() == 0) throw( (sInt32)eDSEmptyAttributeTypeList );

		// Get the attribute info only flag
		bAttribOnly = inData->fInAttribInfoOnly;

//KW need to be smarter on exiting from the limit search condition ie. stop calling into more record types and names
// if the limit has already been reached ie. check in this routine as well before continuing the search
		// get records of these types
		while ( ( cpRecTypeList->GetAttribute( pContinue->fRecTypeIndex, &pRecType ) == eDSNoErr ) &&
				(!bBuffFull) && (!separateRecTypes) )
		{
			// Do we support this record type?
			//	- There could be a whole list so don't quit if we encounter a bad one.

			pNIRecType = MapRecToNetInfoType( pRecType );
			//code here assumes that there is a single mapping possible
			if ( pNIRecType != nil )
			{
				// Get the records of this type and these names
				while ( (cpRecNameList->GetAttribute( pContinue->fRecNameIndex, &pRecName ) == eDSNoErr) &&
						(siResult == eDSNoErr) && (!bBuffFull) )
				{
					bBuffFull = false;
					if ( ::strcmp( pRecName, kDSRecordsAll ) == 0 )
					{
						siResult = GetAllRecords( pNIRecType,
													cpAttrTypeList,
													pContinue,
													pContext->fDomain,
													pContext->fDomainName,
													bAttribOnly,
													outBuff,
													uiCount );
					}
					else
					{
						siResult = GetTheseRecords ( pRecName,
													pRecType,
													pNIRecType,
													pattMatch,
													cpAttrTypeList,
													pContext->fDomain,
													pContext->fDomainName,
													bAttribOnly,
													outBuff,
													pContinue,
													uiCount );
					}

					//outBuff->GetDataBlockCount( &uiCount );
					//cannot use this as there may be records added from different record names
					//at which point the first name will fill the buffer with n records and
					//uiCount is reported as n but as the second name fills the buffer with m MORE records
					//the statement above will return the total of n+m and add it to the previous n
					//so that the total erroneously becomes 2n+m and not what it should be as n+m
					//therefore uiCount is extracted directly out of the GetxxxRecord(s) calls

					if ( siResult == CBuff::kBuffFull )
					{
						bBuffFull = true;
						inData->fIOContinueData = pContinue;

						// check to see if buffer is empty and no entries added
						// which implies that the buffer is too small
						if ( ( uiCount == 0 ) && ( uiTotal == 0 ) )
						{
							throw( (sInt32)eDSBufferTooSmall );
						}
						uiTotal += uiCount;
		
						inData->fOutRecEntryCount = uiTotal;
						outBuff->SetLengthToSize();

						siResult = eDSNoErr;
					}
					else if ( siResult == eDSNoErr )
					{
						uiTotal += uiCount;
						pContinue->fRecNameIndex++;
					}

				} // while loop over record names
				delete( pNIRecType );
				pNIRecType = nil;

			}
			else
			{
				siResult = eDSInvalidRecordType;
			}

			if ( !bBuffFull )
			{
				pRecType = nil;
				pContinue->fRecTypeIndex++;
				pContinue->fRecNameIndex = 1;
				//KW? here we decide to exit with data full of the current type of records
				// and force a good exit with the data we have so we can come back for the next rec type
				separateRecTypes = true;
				//set continue since there may be more data available
				inData->fIOContinueData = pContinue;
				//make sure that the ni_entry_list is cleared for the previous record type
				if ( pContinue->fNIEntryList.ni_entrylist_len > 0 )
				{
					::ni_entrylist_free(&(pContinue->fNIEntryList));
					pContinue->fNIEntryList.ni_entrylist_len = 0;
				}
				siResult = eDSNoErr;

				//however if this was the last rec type then there will be no more data
				// check the number of rec types left
				countDownRecTypes--;
				if ( countDownRecTypes == 0 )
				{
					inData->fIOContinueData = nil;
				}
			}
			
		} // while loop over record types

		if ( (siResult == eDSNoErr) && (!bBuffFull) )
		{
			if ( uiTotal == 0 )
			{
				//KW to remove "if statement" and "siResult = eDSRecordNotFound" altogether
				// see 2531386  dsGetRecordList should not return an error if record not found
				//if ( ( inData->fIOContinueData == nil ) && ( pContinue->fTotalRecCount == 0) )
				//{
					//only set the record not found if no records were found in any of the record types
					//and this is the last record type looked for
					//siResult = eDSRecordNotFound;
				//}
				outBuff->ClearBuff();
			}
			else
			{
				outBuff->SetLengthToSize();
			}

			inData->fOutRecEntryCount = uiTotal;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( (inData->fIOContinueData == nil) && (pContinue != nil) )
	{
		// we've decided not to return continue data, so we should clean up
		gNetInfoMutex->Wait();
		gNIContinue->RemoveItem( pContinue );
		gNetInfoMutex->Signal();
		pContinue = nil;
	}

	if ( cpRecNameList != nil )
	{
		delete( cpRecNameList );
		cpRecNameList = nil;
	}

	if ( cpRecTypeList != nil )
	{
		delete( cpRecTypeList );
		cpRecTypeList = nil;
	}

	if ( cpAttrTypeList != nil )
	{
		delete( cpAttrTypeList );
		cpAttrTypeList = nil;
	}

	if ( outBuff != nil )
	{
		delete( outBuff );
		outBuff = nil;
	}

	if ( pNIRecType != nil )
	{
		delete( pNIRecType );
		pNIRecType = nil;
	}

	return( siResult ); 

} // GetRecordList


//------------------------------------------------------------------------------------
//	* GetAllRecords
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetAllRecords (	char			*inNI_RecType,
									CAttributeList	*inAttrTypeList,
									sNIContinueData	*inContinue,
									void			*inDomain,
									char			*inDomainName,
									bool			inAttrOnly,
									CBuff			*inBuff,
									uInt32			&outRecCount )
{
	sInt32			siResult		= eDSNoErr;
	sInt32			siValCnt		= 0;
	char		   *pDS_RecType		= 0;
	u_int			en				= 0;
	ni_index		niIndex			= 0;
	ni_id			niDirID;
	ni_entry		niEntry;
	ni_proplist		niPropList;
	ni_entrylist	niEntryList;

	gNetInfoMutex->Wait();

	try
	{
		NI_INIT( &niDirID );
		NI_INIT( &niEntry );
		NI_INIT( &niPropList );
		NI_INIT( &niEntryList );

		outRecCount = 0; //need to track how many records were found by this call to GetAllRecords

		//assuming that record types have no embedded "/"s in them
		siResult = ::ni_pathsearch( inDomain, &niDirID, inNI_RecType );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		if ( inContinue->fNIEntryList.ni_entrylist_len == 0 )
		{
			siResult = ::ni_list( inDomain, &niDirID, "name", &(inContinue->fNIEntryList) );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}
		niEntryList = inContinue->fNIEntryList;

		for ( en = inContinue->fAllRecIndex; (en < niEntryList.ni_entrylist_len) &&
					(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
					 (inContinue->fLimitRecSearch == 0)); en++ )
		{
			niEntry = niEntryList.ni_entrylist_val[ en ];
			niDirID.nii_object = niEntry.id;

			siResult = ::ni_read( inDomain, &niDirID, &niPropList );
			if ( siResult == NI_OK )
			{
				siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siValCnt );
				if ( siResult == eDSNoErr )
				{
					fRecData->Clear();

					pDS_RecType = MapNetInfoRecToDSType( inNI_RecType );
					if ( pDS_RecType != nil )
					{
						fRecData->AppendShort( ::strlen( pDS_RecType ) );
						fRecData->AppendString( pDS_RecType );

						delete( pDS_RecType );
						pDS_RecType = nil;
					}

					niIndex = ::ni_proplist_match( niPropList, "name", NULL );
					if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
					{
						fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
						fRecData->AppendString( "*** No Name ***" );
					}
					else
					{
						fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
						fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
					}

					if ( siValCnt == 0 )
					{
						// Attribute count
						fRecData->AppendShort( 0 );
					}
					else
					{
						// Attribute count
						fRecData->AppendShort( siValCnt );
						fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
					}
					siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );

					if ( siResult == CBuff::kBuffFull )
					{
						inContinue->fAllRecIndex = en;
						::ni_proplist_free( &niPropList );
						break;
					}
					else if ( siResult == eDSNoErr )
					{
						outRecCount++;
						inContinue->fTotalRecCount++;
					}
            		else
            		{
						inContinue->fAllRecIndex = 0;
						::ni_proplist_free( &niPropList );
               			siResult = eDSInvalidBuffFormat;
						break;
		            }
				}
				::ni_proplist_free( &niPropList );
			}
		}
		if ( siResult == eDSNoErr )
		{
			inContinue->fAllRecIndex = 0;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult ); 

} // GetAllRecords


//------------------------------------------------------------------------------------
//	* GetTheseRecords
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn:: GetTheseRecords (	char				*inConstRecName,
									char				*inConstRecType,
									char				*inNativeRecType,
									tDirPatternMatch	 inPattMatch,
									CAttributeList		*inAttrTypeList,
									void				*inDomain,
									char				*inDomainName,
									bool				 inAttrOnly,
									CBuff				*inBuff,
									sNIContinueData		*inContinue,
									uInt32				&outRecCount )
//note that inConstRecName should really be called inPatt2Match just like in FindTheseRecords

{
	sInt32			siResult	= eDSNoErr;
	sInt32			siCount		= 0;
	u_int			en			= 0;
	ni_status		niStatus	= NI_OK;
	ni_index		niIndex		= 0;
	ni_index		niIndexComp	= 0;
	ni_id			niDirID;
	ni_proplist		niPropList;
	ni_entry		niEntry;
	ni_entrylist	niEntryList;
	bool			bGetThisOne	= true;
	char		   *inConstRegExpRecName	= nil;
	bool			bGotAMatch	= false;

	gNetInfoMutex->Wait();

	try
	{
		NI_INIT( &niDirID );
		NI_INIT( &niEntry );
		NI_INIT( &niPropList );
		NI_INIT( &niEntryList );

		outRecCount = 0; //need to track how many records were found by this call to GetTheseRecords

		//need to flush buffer so that if we retrieve multiple entries with one call to this routine they
		//will not get added to each other
		fRecData->Clear();

		//need to handle continue data now with the fMultiMapIndex field so that we know
		//where we left off on the last time in here
		//KW need to get rid of this index and consolidate the code together
		
		if ( (::strcmp( inNativeRecType, "users" ) == 0 ) &&
			((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 2) || (inContinue->fMultiMapIndex == 3)) )

		{
			niStatus = ::ni_pathsearch( inDomain, &niDirID, "/users" );
			if ( niStatus == NI_OK )
			{
//no definitive mapping of record names yet ie. only hardcoded here
//KW how can we ensure that a search that "hits" both the name and realname will not return two found records when indeed
//there is only one record in NetInfo
//KW can this command handle a search on two separate attributes??? - not likely
				//make the reg exp that ni_search needs
				inConstRegExpRecName = BuildRegExp(inConstRecName);
				niStatus = ::ni_search( inDomain, &niDirID, (char *)"name", inConstRegExpRecName, REG_ICASE, &niEntryList );
				free(inConstRegExpRecName);
				if ( (niStatus == NI_OK) &&
					((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 2)) )
				{
					for ( en = inContinue->fAllRecIndex; (en < niEntryList.ni_entrylist_len) &&
							(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
					 		(inContinue->fLimitRecSearch == 0)); en++ )
//					for ( en = inContinue->fAllRecIndex; en < niEntryList.ni_entrylist_len; en++ )
					{
						niEntry = niEntryList.ni_entrylist_val[ en ];
						niDirID.nii_object = niEntry.id;

						niStatus = ::ni_read( inDomain, &niDirID, &niPropList );
						if ( niStatus == NI_OK )
						{
							niIndex = ::ni_proplist_match( niPropList, "name", NULL );
							if ( niIndex != NI_INDEX_NULL )
							{
								//code is here to determine if the regcomp operation on inConstRecName for NetInfo actually
								//returned what was requested ie. it will always return at least it but probably much more
								//so filter out the much more
								
								bGotAMatch = false;
								// For each value in the namelist for this property
								for ( uInt32 pv = 0; pv < niPropList.nipl_val[ niIndex ].nip_val.ninl_len; pv++ )
								{
									// check if we find a match
									if ( DoesThisMatch(	(const char*)(niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ pv ]),
														(const char*)inConstRecName,
														inPattMatch ) )
									{
										bGotAMatch = true;
										break;
									}
								}

								if (bGotAMatch)
								{
									//need to flush buffer so that if we retrieve multiple entries with one call to this routine they
									//will not get added to each other
									//fRecData->Clear(); // not anymore since two loops now
		
									fRecData->AppendShort( ::strlen( inConstRecType ) );
									fRecData->AppendString( inConstRecType );

									niIndex = ::ni_proplist_match( niPropList, "name", NULL );
									if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
									{
										fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
										fRecData->AppendString( "*** No Name ***" );
									}
									else
									{
										fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
										fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
									}

									siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
									if ( siResult == eDSNoErr )
									{
										if ( siCount == 0 )
										{
											// Attribute count
											fRecData->AppendShort( 0 );
										}
										else
										{
											// Attribute count
											fRecData->AppendShort( siCount );
											fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
										}
										siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
										fRecData->Clear();

										if ( siResult == CBuff::kBuffFull )
										{
											inContinue->fMultiMapIndex = 2;
											inContinue->fAllRecIndex = en;
											::ni_proplist_free( &niPropList );
											throw( siResult );
										}
										else if ( siResult == eDSNoErr )
										{
											inContinue->fMultiMapIndex = 0;
											outRecCount++;
											inContinue->fTotalRecCount++;
										}
            							else
            							{
											inContinue->fMultiMapIndex = 0;
											::ni_proplist_free( &niPropList );
               								throw( (sInt32)eDSInvalidBuffFormat );
		            					}
									} // GetTheseAttributes(...)
								} // bGotAMatch
							}
							::ni_proplist_free( &niPropList );
						} // if ( ::ni_read( inDomain, &niDirID, &niPropList == NI_OK ))
							
					} // for loop over niEntryList.ni_entrylist_len
				} // if ( ::ni_search( inDomain, &niDirID, (char *)"name", inConstRecName, REG_ICASE, &niEntryList ) == NI_OK )


				if (niEntryList.ni_entrylist_len > 0)
				{
					::ni_entrylist_free( &niEntryList );
					niEntryList.ni_entrylist_len = 0;
				}
				NI_INIT( &niEntryList );

				//ensure that if a client only asks for n records that we stop searching if
				//we have already found n records ie. critical for performance when asking for one record
				if ( (inContinue->fTotalRecCount < inContinue->fLimitRecSearch) || (inContinue->fLimitRecSearch == 0) )
				{
					NI_INIT( &niDirID );
					niStatus = ::ni_pathsearch( inDomain, &niDirID, "/users" );
					if ( niStatus == NI_OK )
					{
						//make the reg exp that ni_search needs
						inConstRegExpRecName = BuildRegExp(inConstRecName);
						niStatus = ::ni_search( inDomain, &niDirID, (char *)"realname", inConstRegExpRecName, REG_ICASE, &niEntryList );
						free(inConstRegExpRecName);
						if ( (niStatus == NI_OK) &&
							((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 3)) )
						{
							for ( en = inContinue->fAllRecIndex; (en < niEntryList.ni_entrylist_len) &&
									(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
									(inContinue->fLimitRecSearch == 0)); en++ )
	//						for ( en = inContinue->fAllRecIndex; en < niEntryList.ni_entrylist_len; en++ )
							{
								niEntry = niEntryList.ni_entrylist_val[ en ];
								niDirID.nii_object = niEntry.id;
	
								niStatus = ::ni_read( inDomain, &niDirID, &niPropList );
								if ( niStatus == NI_OK )
								{
									niIndex = ::ni_proplist_match( niPropList, "realname", NULL );
									if ( niIndex != NI_INDEX_NULL )
									{
										//code is here to determine if the regcomp operation on inConstRecName for NetInfo actually
										//returned what was requested ie. it will always return at least it but probably much more
										//so filter out the much more
										bGotAMatch = false;
										// For each value in the namelist for this property
										for ( uInt32 pv = 0; pv < niPropList.nipl_val[ niIndex ].nip_val.ninl_len; pv++ )
										{
											// check if we find a match
											if ( DoesThisMatch(	(const char*)(niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ pv ]),
																(const char*)inConstRecName,
																inPattMatch ) )
											{
												bGotAMatch = true;
												break;
											}
										}

										if (bGotAMatch)
										{
											bGetThisOne = true;
											//check here if we have already grabbed this record
											//read the name and see if there is a match then ignore if true
											niIndexComp = ::ni_proplist_match( niPropList, "name", NULL );
											if ( niIndexComp != NI_INDEX_NULL )
											{
												for ( uInt32 pv = 0; pv < niPropList.nipl_val[ niIndexComp ].nip_val.ninl_len; pv++ )
												{
													// check if we find a match
													if ( DoesThisMatch(	(const char*)(niPropList.nipl_val[ niIndexComp ].nip_val.ninl_val[ pv ]),
																		(const char*)inConstRecName,
																		inPattMatch ) )
													{
														//should have already gotten this one above in search on name
														bGetThisOne = false;
														break;
													}
												}
											}
	
											if (bGetThisOne)
											{
												//need to flush buffer so that if we retrieve multiple entries with one call to this routine they
												//will not get added to each other
												//fRecData->Clear(); // not anymore since two loops now
			
												fRecData->AppendShort( ::strlen( inConstRecType ) );
												fRecData->AppendString( inConstRecType );
	
												niIndex = ::ni_proplist_match( niPropList, "name", NULL );
												if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
												{
													fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
													fRecData->AppendString( "*** No Name ***" );
												}
												else
												{
													fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
													fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
												}
	
												siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
												if ( siResult == eDSNoErr )
												{
													if ( siCount == 0 )
													{
														// Attribute count
														fRecData->AppendShort( 0 );
													}
													else
													{
														// Attribute count
														fRecData->AppendShort( siCount );
														fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
													}
													siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
													fRecData->Clear();
	
													if ( siResult == CBuff::kBuffFull )
													{
														inContinue->fMultiMapIndex = 3;
														inContinue->fAllRecIndex = en;
														::ni_proplist_free( &niPropList );
														if ( siResult != eDSNoErr ) throw( siResult );
													}
													else if ( siResult == eDSNoErr )
													{
														inContinue->fMultiMapIndex = 0;
														outRecCount++;
														inContinue->fTotalRecCount++;
													}
													else
													{
														inContinue->fMultiMapIndex = 0;
														::ni_proplist_free( &niPropList );
														throw( (sInt32)eDSInvalidBuffFormat );
													}
												} // GetTheseAttributes(...)
												
											}// bGetThisOne
										} // bGotAMatch
									}
									::ni_proplist_free( &niPropList );
								} // if ( ::ni_read( inDomain, &niDirID, &niPropList == NI_OK ))
							} // for loop over niEntryList.ni_entrylist_len
						} // if ( ::ni_search( inDomain, &niDirID, (char *)"realname", inConstRecName, REG_ICASE, &niEntryList ) == NI_OK )
						if ( niEntryList.ni_entrylist_len > 0 )
						{
							::ni_entrylist_free(&niEntryList);
							niEntryList.ni_entrylist_len = 0;
						}
					} // if (::ni_pathsearch( inDomain, &niDirID, "/users" ) == NI_OK)
				} //if the client limit on records to return wasn't reached check realname as well
			} // if ( ::ni_pathsearch( inDomain, &niDirID, "/users" ) == NI_OK )
		} // if ( ::strcmp( inNativeRecType, "users" ) == 0 )


		else if ( (inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 1) )
		{
			//check to see if this type exists
			niStatus = ::ni_pathsearch( inDomain, &niDirID, inNativeRecType );
			if ( niStatus == NI_OK )
			{
				//check to see if this record exists
				//make the reg exp that ni_search needs
				inConstRegExpRecName = BuildRegExp(inConstRecName);
				if ( (strcmp(inConstRecName,"/") == 0) &&
					((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 1)) )
				{
					//want to get the attributes from the root directory
					niStatus = ::ni_read( inDomain, &niDirID, &niPropList );
					if ( niStatus == NI_OK )
					{
						fRecData->AppendShort( ::strlen( inConstRecType ) );
						fRecData->AppendString( inConstRecType );

						fRecData->AppendShort( 1 );
						fRecData->AppendString( "/" );

						siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
						if ( siResult == eDSNoErr )
						{
							if ( siCount == 0 )
							{
								// Attribute count
								fRecData->AppendShort( 0 );
							}
							else
							{
								// Attribute count
								fRecData->AppendShort( siCount );
								fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
							}
							siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
							fRecData->Clear();

							if ( siResult == CBuff::kBuffFull )
							{
								inContinue->fMultiMapIndex = 1;
								inContinue->fAllRecIndex = en;
								::ni_proplist_free( &niPropList );
								if ( siResult != eDSNoErr ) throw( siResult );
							}
							else if ( siResult == eDSNoErr )
							{
								inContinue->fMultiMapIndex = 0;
								outRecCount++;
								inContinue->fTotalRecCount++;
							}
							else
							{
								inContinue->fMultiMapIndex = 0;
								::ni_proplist_free( &niPropList );
								throw( (sInt32)eDSInvalidBuffFormat );
							}
						} // GetTheseAttributes(...)
						::ni_proplist_free( &niPropList );
					} // if ( ::ni_read( inDomain, &niDirID, &niPropList == NI_OK ))
				}
				else
				{
					niStatus = ::ni_search( inDomain, &niDirID, (char *)"name", inConstRegExpRecName, REG_ICASE, &niEntryList );
					if ( (niStatus == NI_OK) &&
						((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 1)) )
					{
						for ( en = inContinue->fAllRecIndex; (en < niEntryList.ni_entrylist_len) &&
								(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
								(inContinue->fLimitRecSearch == 0)); en++ )
	
							{
								niEntry = niEntryList.ni_entrylist_val[ en ];
								niDirID.nii_object = niEntry.id;
	
								niStatus = ::ni_read( inDomain, &niDirID, &niPropList );
								if ( niStatus == NI_OK )
								{
									niIndex = ::ni_proplist_match( niPropList, "name", NULL );
									if ( niIndex != NI_INDEX_NULL )
									{
										//code is here to determine if the regcomp operation on inConstRecName for NetInfo actually
										//returned what was requested ie. it will always return at least it but probably much more
										//so filter out the much more
										bGotAMatch = false;
										// For each value in the namelist for this property
										for ( uInt32 pv = 0; pv < niPropList.nipl_val[ niIndex ].nip_val.ninl_len; pv++ )
										{
											// check if we find a match
											if ( DoesThisMatch(	(const char*)(niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ pv ]),
																(const char*)inConstRecName,
																inPattMatch ) )
											{
												bGotAMatch = true;
												break;
											}
										}
	
										if (bGotAMatch)
										{		
											fRecData->AppendShort( ::strlen( inConstRecType ) );
											fRecData->AppendString( inConstRecType );
	
											niIndex = ::ni_proplist_match( niPropList, "name", NULL );
											if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
											{
												fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
												fRecData->AppendString( "*** No Name ***" );
											}
											else
											{
												//no longer assuming perfect match on the record name
												fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
												fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
											}
	
											siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
											if ( siResult == eDSNoErr )
											{
												if ( siCount == 0 )
												{
													// Attribute count
													fRecData->AppendShort( 0 );
												}
												else
												{
													// Attribute count
													fRecData->AppendShort( siCount );
													fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
												}
												siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
												fRecData->Clear();
	
												if ( siResult == CBuff::kBuffFull )
												{
													inContinue->fMultiMapIndex = 1;
													inContinue->fAllRecIndex = en;
													::ni_proplist_free( &niPropList );
													if ( siResult != eDSNoErr ) throw( siResult );
												}
												else if ( siResult == eDSNoErr )
												{
													inContinue->fMultiMapIndex = 0;
													outRecCount++;
													inContinue->fTotalRecCount++;
												}
												else
												{
													inContinue->fMultiMapIndex = 0;
													::ni_proplist_free( &niPropList );
													throw( (sInt32)eDSInvalidBuffFormat );
												}
											} // GetTheseAttributes(...)
										} // bGotAMatch
									}
									::ni_proplist_free( &niPropList );
								} // if ( ::ni_read( inDomain, &niDirID, &niPropList == NI_OK ))
							} // for loop over niEntryList.ni_entrylist_len
	
					} // if ( (niStatus == NI_OK) && ((inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 1)) )
					if ( niEntryList.ni_entrylist_len > 0 )
					{
						::ni_entrylist_free(&niEntryList);
						niEntryList.ni_entrylist_len = 0;
					}
				}
				free(inConstRegExpRecName);
			} // if ( niStatus == NI_OK )
		}// else if ( (inContinue->fMultiMapIndex == 0) || (inContinue->fMultiMapIndex == 1) )

		inContinue->fMultiMapIndex = 0;
		inContinue->fAllRecIndex = 0;

	} // try

	catch( sInt32 err )
	{
		if ( niEntryList.ni_entrylist_len > 0 )
		{
			::ni_entrylist_free(&niEntryList);
			niEntryList.ni_entrylist_len = 0;
		}
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult ); 

} // GetTheseRecords


//------------------------------------------------------------------------------------
//	* GetTheseAttributes
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetTheseAttributes (	CAttributeList	*inAttrTypeList,
										ni_proplist		*inPropList,
										bool			 inAttrOnly,
										void			*inDomain,
										char			*inDomainName,
										sInt32			&outCount )
{
	sInt32			siResult		= eDSNoErr;
	uInt32			pn				= 0;
	uInt32			pv				= 0;
	sInt32			attrTypeIndex	= 1;
	char		   *pNI_AttrType	= nil;
	char		   *pDS_AttrType	= nil;
	char		   *pDS_AttrName	= nil;
	ni_index		niIndex			= 0;
	ni_property		niProp;
	ni_namelist		niNameList;

//KW this is now passing fAttrData via a member function - bad style but mutex protected for now
	gNetInfoMutex->Wait();

	try
	{
		outCount = 0;
		fAttrData->Clear();

		// Get the record attributes with/without the values
		while ( inAttrTypeList->GetAttribute( attrTypeIndex++, &pDS_AttrType ) == eDSNoErr )
		{
			siResult = eDSNoErr;
			pNI_AttrType = MapAttrToNetInfoType( pDS_AttrType );
			if ( pNI_AttrType != nil )
			{
				if ( ::strcmp( kDSNAttrMetaNodeLocation, pNI_AttrType ) == 0 )
				{
					fTmpData->Clear();

					outCount++;

					// Append the attribute name
					fTmpData->AppendShort( ::strlen( pDS_AttrType ) );
					fTmpData->AppendString( pDS_AttrType );

					if ( inAttrOnly == false )
					{
						// Append the attribute value count
						fTmpData->AppendShort( 1 );

						fTmpData->AppendLong( ::strlen( inDomainName ) );
						fTmpData->AppendString( inDomainName );
					}
					else
					{
						fTmpData->AppendShort( 0 );
					}

					// Add the attribute length
					fAttrData->AppendLong( 	fTmpData->GetLength() );
					fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

					// Clear the temp block
					fTmpData->Clear();
				}
				else
				{
					// Get all attributes
					if ( ( ::strcmp( kDSAttributesAll, pNI_AttrType ) == 0 ) ||
					     ( ::strcmp( kDSAttributesStandardAll, pNI_AttrType ) == 0 ) )
					{
						//code here is to add the attribute kDSNAttrMetaNodeLocation to
						// kDSAttributesAll and kDSAttributesStandardAll
						fTmpData->Clear();

						outCount++;

						// Append the attribute name
						fTmpData->AppendShort( ::strlen( kDSNAttrMetaNodeLocation ) );
						fTmpData->AppendString( kDSNAttrMetaNodeLocation );

						if ( inAttrOnly == false )
						{
							// Append the attribute value count
							fTmpData->AppendShort( 1 );

							fTmpData->AppendLong( ::strlen( inDomainName ) );
							fTmpData->AppendString( inDomainName );
						}
						else
						{
							fTmpData->AppendShort( 0 );
						}

						// Add the attribute length
						fAttrData->AppendLong( 	fTmpData->GetLength() );
						fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

						// Clear the temp block
						fTmpData->Clear();
						
						for ( pn = 0; pn < inPropList->ni_proplist_len; pn++ )
						{
							niProp = inPropList->ni_proplist_val[ pn ];

							fTmpData->Clear();

							pDS_AttrName = MapNetInfoAttrToDSType( niProp.nip_name );
							if ( pDS_AttrName != nil )
							{
								if ( ( ::strncmp(pDS_AttrName,kDSNativeAttrTypePrefix,strlen(kDSNativeAttrTypePrefix)) == 0 ) &&
									( ::strcmp( kDSAttributesStandardAll, pNI_AttrType ) == 0 ) )
								{
									delete( pDS_AttrName );
									pDS_AttrName = nil;
									//native not mapped to standard type so skip in this case
									continue;
								}
								else
								{
									//get the attribute in this case
									fTmpData->AppendShort( ::strlen( pDS_AttrName ) );
									fTmpData->AppendString( pDS_AttrName );
		
									outCount++;
		
									delete( pDS_AttrName );
									pDS_AttrName = nil;
		
									if ( inAttrOnly == false )
									{
										niNameList = niProp.nip_val;
		
										// Append the attribute value count
										fTmpData->AppendShort( niNameList.ni_namelist_len );
		
										// For each value in the namelist for this property
										for ( pv = 0; pv < niNameList.ni_namelist_len; pv++ )
										{
											// Append attribute value "n"
											fTmpData->AppendLong( ::strlen( niNameList.ni_namelist_val[ pv ] ) );
											fTmpData->AppendString( niNameList.ni_namelist_val[ pv ] );
										}
									}
									else
									{
										fTmpData->AppendShort( 0 );
									}
					
									// Add the attribute length
									fAttrData->AppendLong( 	fTmpData->GetLength() );
									fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );
		
									// Clear the temp block
									fTmpData->Clear();
								} //get the attribute in this case
							} //if ( pDS_AttrName != nil )
						}
					}
					else
					{
						// Get one attribute at a time
						niIndex = ::ni_proplist_match( *inPropList, pNI_AttrType, NULL );
						if ( niIndex != NI_INDEX_NULL )
						{
							fTmpData->Clear();

							outCount++;

							fTmpData->AppendShort( ::strlen( pDS_AttrType ) );
							fTmpData->AppendString( pDS_AttrType );

							if ( inAttrOnly == false )
							{
								niNameList = inPropList->nipl_val[ niIndex ].nip_val;

								// Append the attribute value count
								fTmpData->AppendShort( niNameList.ni_namelist_len );

								// For each value in the namelist for this property
								for ( pv = 0; pv < niNameList.ni_namelist_len; pv++ )
								{
									// Append attribute value "n"
									fTmpData->AppendLong( ::strlen( niNameList.ni_namelist_val[ pv ] ) );
									fTmpData->AppendString( niNameList.ni_namelist_val[ pv ] );
								}
							}
							else
							{
								fTmpData->AppendShort( 0 );
							}
							
							// Add the attribute length
							fAttrData->AppendLong( fTmpData->GetLength() );
							fAttrData->AppendBlock( fTmpData->GetData(), fTmpData->GetLength() );

							// Clear the temp block
							fTmpData->Clear();
						}
					}
				}
				delete( pNI_AttrType );
				pNI_AttrType = nil;
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // GetTheseAttributes


//------------------------------------------------------------------------------------
//	* AddAttribute
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::AddAttribute ( sAddAttribute *inData )
{
	sInt32					siResult		= eDSNoErr;
	tDataNode			   *pNewAttr		= nil;
	char				   *pAttrKey		= nil;
	tDataNode			   *pAttrValue		= nil;
	sNIContextData		   *pContext		= nil;
	char				   *pEmptyValue		= (char *)"";
	char				   *pValue			= nil;
	ni_namelist				niValues;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Record context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pNewAttr = inData->fInNewAttr;
		if ( pNewAttr == nil ) throw( (sInt32)eDSNullAttribute );

		pAttrKey = MapAttrToNetInfoType( pNewAttr->fBufferData );
		if ( pAttrKey == nil ) throw( (sInt32)eDSNullAttribute );

		pAttrValue = inData->fInFirstAttrValue;
		if ( pAttrValue == nil )
		{
			pValue = pEmptyValue;
		}
		else
		{
			pValue = pAttrValue->fBufferData;
		}

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			if ( ( authUser == NULL ) && (strcmp(pAttrKey,"picture") == 0) )
			{
				authUser = GetUserNameForUID( pContext->fEffectiveUID, pContext->fDomain );
			}
			ni_proplist niPropList;
			siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			::ni_proplist_free( &niPropList );

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}

		NI_INIT( &niValues );
//		::ni_namelist_insert( &niValues, pAttrValue->fBufferData, NI_INDEX_NULL );
		::ni_namelist_insert( &niValues, pValue, NI_INDEX_NULL );
		siResult = DoAddAttribute( pContext->fDomain, &pContext->dirID, pAttrKey, niValues );
		::ni_namelist_free( &niValues );
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	if ( pAttrKey != nil )
	{
		delete( pAttrKey );
		pAttrKey = nil;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // AddAttribute


//------------------------------------------------------------------------------------
//	* DoAddAttribute
//
//		- Taken from ni2_createdirprop but modified to be readable.
//			Createprop given a directory rather than a pathname
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoAddAttribute (	void			*inDomain,
									ni_id			*inDir,
									const ni_name	 inKey,
									ni_namelist		 inValues )
{
	sInt32			siResult	= eDSNoErr;
	ni_index		where		= 0;
	ni_property		niPropList;
	ni_namelist		niNameList;

	gNetInfoMutex->Wait();

	try
	{
		// need to be talking to the master
		::ni_needwrite( inDomain, 1 );

		// fetch list of property keys from directory
		NI_INIT( &niNameList );
		siResult = ::ni_listprops( inDomain, inDir, &niNameList );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		// check for existing property with this key
		where = ::ni_namelist_match( niNameList, inKey );
		::ni_namelist_free( &niNameList );

		// if property doesn't exist, create it
		if ( where == NI_INDEX_NULL )
		{
			NI_INIT( &niPropList );
			niPropList.nip_name	= ::ni_name_dup( inKey );
			niPropList.nip_val	= ::ni_namelist_dup( inValues );
			siResult = ::ni_createprop( inDomain, inDir, niPropList, NI_INDEX_NULL );
			::ni_prop_free( &niPropList );
		}
		else
		{
			// property exists: replace the existing values
			siResult = ::ni_writeprop( inDomain, inDir, where, inValues );
		}
		
		siResult = MapNetInfoErrors( siResult );
		
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // DoAddAttribute


//------------------------------------------------------------------------------------
//	* GetAttributeValue
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetAttributeValue ( sGetAttributeValue *inData )
{
	sInt32						siResult		= eDSNoErr;
	uInt16						usValueCnt		= 0;
	uInt32						usValueLen		= 0;
	uInt16						usAttrNameLen	= 0;
	uInt32						i				= 0;
	uInt32						uiIndex			= 0;
	uInt32						offset			= 0;
	char					   *p				= nil;
	tDataBuffer				   *pDataBuff		= nil;
	tAttributeValueEntry	   *pAttrValue		= nil;
	sNIContextData			   *pValueContext	= nil;
	uInt32						buffSize		= 0;
	uInt32						buffLen			= 0;
	uInt32						attrLen			= 0;

	try
	{
		pValueContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInAttrValueListRef );
		if ( pValueContext  == nil ) throw( (sInt32)eDSBadContextData );

		uiIndex = inData->fInAttrValueIndex;
		if (uiIndex == 0) throw( (sInt32)eDSInvalidIndex );

		pDataBuff = inData->fInOutDataBuff;
		if ( pDataBuff  == nil ) throw( (sInt32)eDSNullDataBuff );

		buffSize	= pDataBuff->fBufferSize;
		//buffLen		= pDataBuff->fBufferLength;
		//here we can't use fBufferLength for the buffLen SINCE the buffer is packed at the END of the data block
		//and the fBufferLength is the overall length of the data for all blocks at the end of the data block
		//the value ALSO includes the bookkeeping data at the start of the data block
		//so we need to read it here

		p		= pDataBuff->fBufferData + pValueContext->offset;
		offset	= pValueContext->offset;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (4 + offset > buffSize)  throw( (sInt32)eDSInvalidBuffFormat );
				
		// Get the buffer length
		::memcpy( &attrLen, p, 4 );

		//now add the offset to the attr length for the value of buffLen to be used to check for buffer overruns
		//AND add the length of the buffer length var as stored ie. 4 bytes
		buffLen		= attrLen + pValueContext->offset + 4;
		if (buffLen > buffSize)  throw( (sInt32)eDSInvalidBuffFormat );

		// Skip past the attribute length
		p		+= 4;
		offset	+= 4;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the attribute name length
		::memcpy( &usAttrNameLen, p, 2 );
		
		p		+= 2 + usAttrNameLen;
		offset	+= 2 + usAttrNameLen;

		// Do record check, verify that offset is not past end of buffer, etc.
		if (2 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		// Get the value count
		::memcpy( &usValueCnt, p, 2 );
		
		p		+= 2;
		offset	+= 2;

		if (uiIndex > usValueCnt)  throw( (sInt32)eDSInvalidIndex );

		// Skip to the value that we want
		for ( i = 1; i < uiIndex; i++ )
		{
			// Do record check, verify that offset is not past end of buffer, etc.
			if (4 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
			// Get the length for the value
			::memcpy( &usValueLen, p, 4 );
			
			p		+= 4 + usValueLen;
			offset	+= 4 + usValueLen;
		}

		// Do record check, verify that offset is not past end of buffer, etc.
		if (4 + offset > buffLen)  throw( (sInt32)eDSInvalidBuffFormat );
		
		::memcpy( &usValueLen, p, 4 );
		
		p		+= 4;
		offset	+= 4;

		//if (usValueLen == 0)  throw( (sInt32)eDSInvalidBuffFormat ); //if zero is it okay?

		pAttrValue = (tAttributeValueEntry *)::calloc( 1, sizeof( tAttributeValueEntry ) + usValueLen + kBuffPad );

		pAttrValue->fAttributeValueData.fBufferSize		= usValueLen + kBuffPad;
		pAttrValue->fAttributeValueData.fBufferLength	= usValueLen;
		
		// Do record check, verify that offset is not past end of buffer, etc.
		if ( usValueLen + offset > buffLen ) throw( (sInt32)eDSInvalidBuffFormat );
		
		::memcpy( pAttrValue->fAttributeValueData.fBufferData, p, usValueLen );

		// Set the attribute value ID
		pAttrValue->fAttributeValueID = CalcCRC( pAttrValue->fAttributeValueData.fBufferData );

		inData->fOutAttrValue = pAttrValue;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	return( siResult );

} // GetAttributeValue


//------------------------------------------------------------------------------------
//	* GetRecAttribInfo
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecAttribInfo ( sGetRecAttribInfo *inData )
{
	bool					bFound			= false;
	sInt32					siResult		= eDSNoErr;
	ni_status				niStatus		= NI_OK;
	uInt32					uiPv			= 0;
	uInt32					uiTypeLen		= 0;
	uInt32					uiDataLen		= 0;
	tDataNodePtr			pAttrType		= nil;
	char				   *pNI_AttrType	= nil;
	tAttributeEntryPtr		pOutAttrEntry	= nil;
	sNIContextData		   *pContext		= nil;
	ni_id					niDirID;
	ni_namelist				niNameList;		// values;
	ni_index				niIndex;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pAttrType = inData->fInAttrType;
		if ( pAttrType == nil ) throw( (sInt32)eDSEmptyAttributeType );

		pNI_AttrType = MapAttrToNetInfoType( pAttrType->fBufferData );
		if ( pNI_AttrType == nil ) throw( (sInt32)eDSInvalidAttributeType );

		::memcpy( &niDirID, &pContext->dirID, sizeof( ni_id ) );

		NI_INIT( &niNameList );
		niStatus = ::ni_listprops( pContext->fDomain, &niDirID, &niNameList );
		if ( niStatus == NI_OK )
		{
			niIndex = ::ni_namelist_match( niNameList, pNI_AttrType );
			::ni_namelist_free( &niNameList );

			if ( niIndex != NI_INDEX_NULL )
			{
				NI_INIT( &niNameList );
				niStatus = ::ni_readprop( pContext->fDomain, &niDirID, niIndex, &niNameList );
				if ( niStatus == NI_OK )
				{
					uiTypeLen = ::strlen( pAttrType->fBufferData );
					pOutAttrEntry = (tAttributeEntry *)::calloc( (sizeof( tAttributeEntry ) + uiTypeLen + kBuffPad), sizeof( char ) );

//					pOutAttrEntry = (tAttributeEntry *)::malloc( sizeof( tAttributeEntry ) + uiTypeLen + kBuffPad );
//					::memset( pOutAttrEntry, 0, sizeof( tAttributeEntry ) + uiTypeLen + kBuffPad );

					pOutAttrEntry->fAttributeSignature.fBufferSize		= uiTypeLen;
					pOutAttrEntry->fAttributeSignature.fBufferLength	= uiTypeLen;
					::memcpy( pOutAttrEntry->fAttributeSignature.fBufferData, pAttrType->fBufferData, uiTypeLen ); 

					pOutAttrEntry->fAttributeValueCount = niNameList.ni_namelist_len;
					pOutAttrEntry->fAttributeValueMaxSize = 255;

					for ( uiPv = 0; uiPv < niNameList.ni_namelist_len; uiPv++ )
					{
						uiDataLen += ::strlen( niNameList.ni_namelist_val[ uiPv ] );
					}

					pOutAttrEntry->fAttributeDataSize = uiDataLen;

					inData->fOutAttrInfoPtr = pOutAttrEntry;

					bFound = true;

					::ni_namelist_free( &niNameList );
				}
			}
			else
			{
				siResult = eDSAttributeNotFound;
			}
		}
		else
		{
			siResult = MapNetInfoErrors( niStatus );
		}
		delete( pNI_AttrType );
		pNI_AttrType = nil;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // GetRecAttribInfo


//------------------------------------------------------------------------------------
//	* GetRecordAttributeValueByID
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecordAttributeValueByID ( sGetRecordAttributeValueByID *inData )
{
	bool						done			= false;
	sInt32						siResult		= eDSNoErr;
	ni_status					niStatus		= NI_OK;
	uInt32						crcVal			= 0;
	uInt32						uiDataLen		= 0;
	uInt32						pv				= 0;
	char					   *pNI_AttrType	= nil;
	tAttributeValueEntryPtr		pOutAttrValue	= nil;
	sNIContextData			   *pContext		= nil;
	ni_namelist					niNameList;		// values;
	ni_index					niIndex;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );
		if ( inData->fInAttrType == nil ) throw( (sInt32)eDSEmptyAttributeType );

		pNI_AttrType = MapAttrToNetInfoType( inData->fInAttrType->fBufferData );
		if ( pNI_AttrType == nil ) throw( (sInt32)eDSInvalidAttributeType );

		NI_INIT( &niNameList );
		niStatus = ::ni_listprops( pContext->fDomain, &pContext->dirID, &niNameList );
		if ( niStatus == NI_OK )
		{
			niIndex = ::ni_namelist_match( niNameList, pNI_AttrType );
			::ni_namelist_free( &niNameList );

			if ( niIndex != NI_INDEX_NULL )
			{
				NI_INIT( &niNameList );
				niStatus = ::ni_readprop( pContext->fDomain, &pContext->dirID, niIndex, &niNameList );
				if ( niStatus == NI_OK )
				{
					// For each value in the namelist for this property
					for ( pv = 0; (pv < niNameList.ni_namelist_len) && (done == false); pv++ )
					{
						crcVal = CalcCRC( niNameList.ni_namelist_val[ pv ] );

						if ( crcVal == inData->fInValueID )
						{
							if ( niNameList.ni_namelist_val[ pv ] != nil )
							{
								uiDataLen = ::strlen( niNameList.ni_namelist_val[ pv ] );
							}
							else
							{
								uiDataLen = 0;
							}

							pOutAttrValue = (tAttributeValueEntry *)::calloc( (sizeof( tAttributeValueEntry ) + uiDataLen + kBuffPad), sizeof( char ) );

//							pOutAttrValue = (tAttributeValueEntry *)::malloc( sizeof( tAttributeValueEntry ) + uiDataLen + kBuffPad );
//							::memset( pOutAttrValue, 0, sizeof( tAttributeValueEntry ) + uiDataLen + kBuffPad );

							pOutAttrValue->fAttributeValueData.fBufferSize = uiDataLen;
							pOutAttrValue->fAttributeValueData.fBufferLength = uiDataLen;
							if ( niNameList.ni_namelist_val[ pv ] != nil )
							{
								pOutAttrValue->fAttributeValueID = crcVal;
								::memcpy( pOutAttrValue->fAttributeValueData.fBufferData, niNameList.ni_namelist_val[ pv ], uiDataLen ); 
							}

							inData->fOutEntryPtr = pOutAttrValue;

							done = true;

							break;
						}
					}
					::ni_namelist_free( &niNameList );
				}
			}
			else
			{
				siResult = eDSAttributeNotFound;
			}
		}

		delete( pNI_AttrType );
		pNI_AttrType = nil;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // GetRecordAttributeValueByID


//------------------------------------------------------------------------------------
//	* GetRecAttrValueByIndex
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecAttrValueByIndex ( sGetRecordAttributeValueByIndex *inData )
{
	sInt32						siResult		= eDSNoErr;
	ni_status					niStatus		= NI_OK;
	uInt32						uiDataLen		= 0;
	uInt32						uiIndex			= 0;
	char					   *pNI_AttrType	= nil;
	tAttributeValueEntryPtr		pOutAttrValue	= nil;
	sNIContextData			   *pContext		= nil;
	ni_id						niDirID;
	ni_namelist					niNameList;		// values;
	ni_index					niIndex;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );
		if ( inData->fInAttrType == nil ) throw( (sInt32)eDSEmptyAttributeType );

		pNI_AttrType = MapAttrToNetInfoType( inData->fInAttrType->fBufferData );
		if ( pNI_AttrType == nil ) throw( (sInt32)eDSInvalidAttributeType );

		// Get the index value
		uiIndex = inData->fInAttrValueIndex;

		// Get the domain and dir ID
		::memcpy( &niDirID, &pContext->dirID, sizeof( ni_id ) );

		NI_INIT( &niNameList );
		niStatus = ::ni_listprops( pContext->fDomain, &niDirID, &niNameList );
		if ( niStatus == NI_OK )
		{
			niIndex = ::ni_namelist_match( niNameList, pNI_AttrType );
			::ni_namelist_free( &niNameList );

			if ( niIndex != NI_INDEX_NULL )
			{
				NI_INIT( &niNameList );
				niStatus = ::ni_readprop( pContext->fDomain, &niDirID, niIndex, &niNameList );
				if ( niStatus == NI_OK )
				{
					if ( niNameList.ni_namelist_len >= uiIndex )
					{
						if ( niNameList.ni_namelist_val[ uiIndex - 1 ] != nil )
						{
							uiDataLen = ::strlen( niNameList.ni_namelist_val[ uiIndex - 1 ] );
						}
						else
						{
							uiDataLen = 0;
						}

						pOutAttrValue = (tAttributeValueEntry *)::calloc( 1, sizeof( tAttributeValueEntry ) + uiDataLen + kBuffPad );
//						::memset( pOutAttrValue, 0, sizeof( tAttributeValueEntry ) + uiDataLen + kBuffPad );

						pOutAttrValue->fAttributeValueData.fBufferSize = uiDataLen;
						pOutAttrValue->fAttributeValueData.fBufferLength = uiDataLen;
						if ( niNameList.ni_namelist_val[ uiIndex - 1 ] != nil )
						{
							pOutAttrValue->fAttributeValueID = CalcCRC( niNameList.ni_namelist_val[ uiIndex - 1 ] );
							::memcpy( pOutAttrValue->fAttributeValueData.fBufferData, niNameList.ni_namelist_val[ uiIndex - 1 ], uiDataLen ); 
						}

						inData->fOutEntryPtr = pOutAttrValue;
					}
					else
					{
						siResult = eDSIndexOutOfRange;
					}

					::ni_namelist_free( &niNameList );
				}
			}
			else
			{
				siResult = eDSAttributeNotFound;
			}
		}

		delete( pNI_AttrType );
		pNI_AttrType = nil;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // GetRecAttrValueByIndex


//------------------------------------------------------------------------------------
//	* CreateRecord
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::CreateRecord ( sCreateRecord *inData )
{
	volatile sInt32		siResult		= eDSNoErr;
	tDataNodePtr		pRecName		= nil;
	tDataNodePtr		pRecType		= nil;
	char			   *pNIRecType		= nil;
	sNIContextData	   *pContext		= nil;
	sNIContextData	   *pRecContext		= nil;
	ni_id				niDirID;
	char			   *pPath			= nil;
	char			   *pRecTypePath	= nil;
	ni_namelist	 		niValues;
	char			   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		pRecType = inData->fInRecType;
		if ( pRecType == nil ) throw( (sInt32)eDSNullRecType );

		pRecName = inData->fInRecName;
		if ( pRecName == nil ) throw( (sInt32)eDSNullRecName );

		pNIRecType = MapRecToNetInfoType( pRecType->fBufferData );
		if ( pNIRecType == nil ) throw( (sInt32)eDSInvalidRecordType );

		pPath = BuildRecordNamePath( pRecName->fBufferData, pNIRecType );
		if ( pPath == nil ) throw( (sInt32)eDSInvalidRecordName );
		
		NI_INIT( &niDirID );
		(void)::ni_root( pContext->fDomain, &niDirID );

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			ni_proplist niPropList;
			ni_id		typeDirID;
			
			pRecTypePath = (char*)calloc( strlen( pNIRecType ) + 2, 1 );
			pRecTypePath[0] = '/';
			strcpy( pRecTypePath + 1, pNIRecType );
			
			siResult = ::ni_pathsearch( pContext->fDomain, &typeDirID, pRecTypePath);
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			siResult = ::ni_read( pContext->fDomain, &typeDirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			::ni_proplist_free( &niPropList );
			
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}
		
		//ValidateCreateRecord
		siResult = DoCreateRecord( pContext->fDomain, &niDirID, pPath );
		if ( siResult != eDSNoErr ) throw( siResult );

		//make sure that the type is users only here when we add these attributes
		if ( (siResult == eDSNoErr) && (strcmp(pNIRecType,"users") == 0) )
		{
			NI_INIT( &niValues );
			::ni_namelist_insert( &niValues, pRecName->fBufferData, NI_INDEX_NULL );

			siResult = DoAddAttribute( pContext->fDomain, &niDirID, (const ni_name)"_writers_passwd", niValues );
			
#ifdef TIM_CLIENT_PRESENT
			siResult = DoAddAttribute( pContext->fDomain, &niDirID, (const ni_name)"_writers_tim_password", niValues );
#endif
			
			siResult = DoAddAttribute( pContext->fDomain, &niDirID, (const ni_name)"_writers_picture", niValues );
			
			::ni_namelist_free( &niValues );
		}

		free(pPath);
		pPath = nil;

		if ( inData->fInOpen == true )
		{
			pRecContext = MakeContextData();
			if ( pRecContext == nil ) throw( (sInt32)eMemoryAllocError );

			pRecContext->fDomain = pContext->fDomain;
			::memcpy( &pRecContext->dirID, &niDirID, sizeof( ni_id ) );
			pRecContext->fUID = pContext->fUID;
			pRecContext->fEffectiveUID = pContext->fEffectiveUID;
			if (pContext->fAuthenticatedUserName != NULL)
			{
				pRecContext->fAuthenticatedUserName = strdup( pContext->fAuthenticatedUserName );
			}

			pRecContext->fRecType = (char *)::malloc( ::strlen( pRecType->fBufferData ) + 2 );
			pRecContext->fRecType[0] = '\0';
			::strcpy( pRecContext->fRecType, pRecType->fBufferData );

			pRecContext->fRecName = (char *)::malloc( pRecName->fBufferLength + 2 );
			pRecContext->fRecName[0] = '\0';
			::strcpy( pRecContext->fRecName, pRecName->fBufferData );

			gNINodeRef->AddItem( inData->fOutRecRef, pRecContext );
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
		if (pPath != nil)
		{
			free(pPath);
			pPath = nil;
		}
	}
	
	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

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

	if ( pNIRecType != nil )
	{
		delete( pNIRecType );
		pNIRecType = nil;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // CreateRecord



//------------------------------------------------------------------------------------
//	* DoCreateRecord
//
//		- Taken from ni2_createpath but modified to be readable
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoCreateRecord ( void *inDomain, ni_id *inDir, char *inPathName )
{
	sInt32		siResult	= eDSNoErr;
	ni_status	niStatus	= NI_OK;
	sInt32		siCntr1		= 0;
	sInt32		siCntr2		= 0;
	sInt32		siLen		= 0;
	bool		bSimple		= false;
	bool		bMadeOne	= false;
	ni_id		niTmpDir;
	CString		csDirName( 128 );

	gNetInfoMutex->Wait();

	try
	{
		// Pull out every pathname component and create the directory
		siCntr1 = 0;

		while ( (inPathName[ siCntr1 ] != '\0') && (niStatus == NI_OK) )
		{
			// Search forward for a path component ( a directory )
			bSimple = true;
			for ( siCntr2 = siCntr1; inPathName[ siCntr2 ] != '\0' && bSimple; siCntr2++ )
			{
				if ( inPathName[ siCntr2 ] == '\\' && inPathName[ siCntr2 + 1 ] == '/' )
				{
					if ( inPathName[ siCntr2 + 2 ] == '/' )
					{
						bSimple = false;
					}
					siCntr2++;
					// only advance by one since the for loop advances by the additional one
					// this skips over both the \ and the /
				}
				else if ( inPathName[ siCntr2 ] == '/' )
				{
					bSimple = false;
				}
			}

			siLen = siCntr2 - siCntr1;
			if ( !bSimple )
			{
				siLen--;
			}
			csDirName.Set( inPathName + siCntr1, siLen );

			// Advance the pointer
			siCntr1 = siCntr2;

			// Does this directory exist?
			//this handles the "/"s correctly since the input to the function is adjusted
			::memcpy( &niTmpDir, inDir, sizeof( ni_id ) );
			niStatus = ::ni_pathsearch( inDomain, inDir, csDirName.GetData() );

			// If it doesn't exist, create it
			if ( niStatus == NI_NODIR )
			{
				::memcpy( inDir, &niTmpDir, sizeof( ni_id ) );
				niStatus = DoCreateChild( inDomain, inDir, csDirName.GetData() );
				bMadeOne = true;
			}
		}

		if ( niStatus == NI_OK )
		{
			if ( bMadeOne == false )
			{
				siResult = eDSRecordAlreadyExists;
			}
		}
		else
		{
			siResult = MapNetInfoErrors( niStatus );
		}
	}


	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // DoCreateRecord



//------------------------------------------------------------------------------------
//	* DoCreateChild
//
//		- Taken from ni2_createchild but modified to be readable
//------------------------------------------------------------------------------------

ni_status CNiPlugIn::DoCreateChild ( void *inDomain, ni_id *inDir, const ni_name inDirName )
{
	ni_status		niStatus	= NI_OK;
	sInt32			siCntr1		= 0;
	sInt32			siCntr2		= 0;
	sInt32			siLen		= 0;
	ni_proplist		niPropList;
	ni_id			niChild;
	char		   *pKey	= nil;
	char		   *pValue	= nil;

	gNetInfoMutex->Wait();

	try
	{
		pKey = (char *)::malloc( 5 );
		strcpy( pKey, "name" );

		// compress out backslashes in value
		siLen = strlen( inDirName );
		pValue = (char *)::malloc( siLen + 1 );
		for ( siCntr1 = 0, siCntr2 = 0; siCntr1 < siLen; siCntr1++, siCntr2++ )
		{
			if ( inDirName[ siCntr1 ] == '\\'
				&& ( (inDirName[ siCntr1 + 1 ] == '/') || (inDirName[ siCntr1 + 1 ] == '\\') ) )
			{
				siCntr1++;
			}
			pValue[ siCntr2 ] = inDirName[ siCntr1 ];
		}
		pValue[ siCntr2 ] = '\0';

		// set up the new directory
		NI_INIT( &niPropList );
		NiLib3::AppendProp( &niPropList, pKey, pValue );

		// create it
		niStatus = ::ni_create( inDomain, inDir, niPropList, &niChild, NI_INDEX_NULL );
		if ( niStatus == NI_OK )
		{
			::memcpy( inDir, &niChild, sizeof( ni_id ) );
		}

		::ni_proplist_free( &niPropList );

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

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

	catch( sInt32 err )
	{
		niStatus = (ni_status)err;
	}

	gNetInfoMutex->Signal();

	return( niStatus );

} // DoCreateChild


//------------------------------------------------------------------------------------
//	* CloseAttributeList
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::CloseAttributeList ( sCloseAttributeList *inData )
{
	sInt32				siResult		= eDSNoErr;
	sNIContextData	   *pContext		= nil;

	pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInAttributeListRef );
	if ( pContext != nil )
	{
		//only "offset" should have been used in the Context
		gNINodeRef->RemoveItem( inData->fInAttributeListRef );
	}
	else
	{
		siResult = eDSInvalidAttrListRef;
	}

	return( siResult );

} // CloseAttributeList


//------------------------------------------------------------------------------------
//	* CloseAttributeValueList
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::CloseAttributeValueList ( sCloseAttributeValueList *inData )
{
	sInt32				siResult		= eDSNoErr;
	sNIContextData	   *pContext		= nil;

	pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInAttributeValueListRef );
	if ( pContext != nil )
	{
		//only "offset" should have been used in the Context
		gNINodeRef->RemoveItem( inData->fInAttributeValueListRef );
	}
	else
	{
		siResult = eDSInvalidAttrValueRef;
	}

	return( siResult );

} // CloseAttributeValueList


//------------------------------------------------------------------------------------
//	* OpenRecord
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::OpenRecord ( sOpenRecord *inData )
{
	sInt32				siResult		= eDSNoErr;
	tDataNodePtr		pRecName		= nil;
	tDataNodePtr		pRecType		= nil;
	char			   *pNIRecType		= nil;
	sNIContextData	   *pContext		= nil;
	sNIContextData	   *pRecContext		= nil;
	ni_id				niDirID;
	bool				bFoundRealname	= false;
	char			   *aRealname		= nil;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		pRecType = inData->fInRecType;
		if ( pRecType == nil ) throw( (sInt32)eDSNullRecType );

		pRecName = inData->fInRecName;
		if ( pRecName == nil ) throw( (sInt32)eDSNullRecName );

		pNIRecType = MapRecToNetInfoType( pRecType->fBufferData );
		if ( pNIRecType == nil ) throw( (sInt32)eDSInvalidRecordType );

		if (pRecName->fBufferLength == 0) throw( (sInt32)eDSEmptyRecordNameList );
		if (pRecType->fBufferLength == 0) throw( (sInt32)eDSEmptyRecordTypeList );

		NI_INIT( &niDirID );
		siResult = IsValidRecordName( pRecName->fBufferData, pNIRecType, pContext->fDomain, niDirID );
		if (siResult != eDSNoErr)
		{
			//here we can check if the record type is /users so that we will search now on "realname" as well
			if (::strcmp(pNIRecType,"users") == 0)
			{
				siResult = IsValidRealname( pRecName->fBufferData, pContext->fDomain, &aRealname );
				//need to get the correct niDirID and set the true record name "aRealname" for the context data
				if (siResult == eDSNoErr)
				{
					bFoundRealname = true;
					siResult = IsValidRecordName( aRealname, pNIRecType, pContext->fDomain, niDirID );
				}
			}
			if ( siResult != eDSNoErr ) throw( siResult );
		}

		pRecContext = MakeContextData();
		if ( pRecContext == nil ) throw( (sInt32)eMemoryAllocError );

		pRecContext->fDomain = pContext->fDomain;
		pRecContext->fUID = pContext->fUID;
		pRecContext->fEffectiveUID = pContext->fEffectiveUID;
		if (pContext->fAuthenticatedUserName != NULL)
		{
			pRecContext->fAuthenticatedUserName = strdup( pContext->fAuthenticatedUserName );
		}
		
		pRecContext->fRecType = (char *)::calloc( 1, ::strlen( pRecType->fBufferData ) + 1 );
		::strcpy( pRecContext->fRecType, pRecType->fBufferData );

		if (bFoundRealname)
		{
			if (aRealname != nil)
			{
				pRecContext->fRecName = aRealname;
			}
		}
		else
		{
			pRecContext->fRecName = (char *)::calloc( 1, ::strlen( pRecName->fBufferData ) + 1 );
			::strcpy( pRecContext->fRecName, pRecName->fBufferData );
		}

		::memcpy( &pRecContext->dirID, &niDirID, sizeof( ni_id ) );

		gNINodeRef->AddItem( inData->fOutRecRef, pRecContext );
	}

	catch( sInt32 err )
	{
		if (siResult == eDSInvalidRecordName)
		{
			siResult = eDSRecordNotFound; //retaining consistent error code returned to clients
		}
		else
		{
			siResult = err;
		}
	}

	if ( pNIRecType != nil )
	{
		delete( pNIRecType );
		pNIRecType = nil;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // OpenRecord



//------------------------------------------------------------------------------------
//	* GetRecRefInfo
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::GetRecRefInfo ( sGetRecRefInfo *inData )
{
	sInt32					siResult		= eDSNoErr;
	uInt32					uiRecSize		= 0;
	uInt32					uiRecTypeSize	= 0;
	uInt32					uiRecNameSize	= 0;
	tRecordEntry		   *pRecEntry		= nil;
	sNIContextData		   *pContext		= nil;

//KW temp fix for the member buffer vars is to put in the mutex here even though
//there is no netinfo call

	gNetInfoMutex->Wait();
	
	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		if ( pContext->fRecType != nil )
		{
			uiRecTypeSize = ::strlen( pContext->fRecType );
		}

		if ( pContext->fRecName != nil )
		{
			uiRecNameSize = ::strlen( pContext->fRecName );
		}

		uiRecSize = sizeof( tRecordEntry ) + uiRecTypeSize + uiRecNameSize + 8;
		pRecEntry = (tRecordEntry *)::calloc( 1, uiRecSize );

		fRecData->Clear();

		// Record type
		fRecData->AppendShort( uiRecNameSize );
		fRecData->AppendString( pContext->fRecName );

		fRecData->AppendShort( uiRecTypeSize );
		fRecData->AppendString( pContext->fRecType );

		::memcpy( pRecEntry->fRecordNameAndType.fBufferData, fRecData->GetData(), fRecData->GetLength() );
		pRecEntry->fRecordNameAndType.fBufferSize = uiRecTypeSize + uiRecNameSize + 8;
		pRecEntry->fRecordNameAndType.fBufferLength = fRecData->GetLength();

		inData->fOutRecInfo = pRecEntry;
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // GetRecRefInfo


//------------------------------------------------------------------------------------
//	* CloseRecord
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::CloseRecord ( sCloseRecord *inData )
{
	sInt32				siResult		= eDSNoErr;
	sNIContextData	   *pContext		= nil;

	gNetInfoMutex->Wait();

	pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
	if ( pContext != nil )
	{
		gNINodeRef->RemoveItem( inData->fInRecRef );
	}
	else
	{
		siResult = eDSInvalidRecordRef;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // CloseRecord


//------------------------------------------------------------------------------------
//	* SetRecordType
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::SetRecordType ( sSetRecordType *inData )
{
	sInt32					siResult		= eDSNoErr;
	tDataNode			   *pData			= nil;
	char				   *pNewType		= nil;
	char				   *pNIRecType		= nil;
	char				   *pRecTypePath	= nil;
	sNIContextData		   *pContext		= nil;
	char				   *pPath			= nil;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Get the new name
		pData = inData->fInNewRecType;
		if ( pData == nil ) throw( (sInt32)eDSNullRecType );

		pNewType = pData->fBufferData;

		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pNIRecType = MapRecToNetInfoType( pNewType );
		if ( pNIRecType == nil ) throw( (sInt32)eDSInvalidRecordType );

		pPath = BuildRecordNamePath( pContext->fRecName, pNIRecType );
		if ( pPath == nil ) throw( (sInt32)eDSInvalidRecordName );

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			ni_proplist niPropList;
			ni_id		niDirID;
			ni_index	tempIndex = NI_INDEX_NULL;

			::memcpy( &niDirID, &pContext->dirID, sizeof( ni_id ) );
			siResult = ::ni_parent( pContext->fDomain, &niDirID, &tempIndex );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
			niDirID.nii_object = tempIndex;
			siResult = ::ni_self( pContext->fDomain, &niDirID );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
			siResult = ::ni_read( pContext->fDomain, &niDirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			// must have write to the parent type of existing record...
			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			::ni_proplist_free( &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			pRecTypePath = (char*)calloc( strlen( pNIRecType ) + 2, 1 );
			pRecTypePath[0] = '/';
			strcpy( pRecTypePath + 1, pNIRecType );
			
			siResult = ::ni_pathsearch( pContext->fDomain, &niDirID, pRecTypePath );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
			siResult = ::ni_read( pContext->fDomain, &niDirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			// and the destination type (parent of new record)
			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			::ni_proplist_free( &niPropList );			

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}
		
//		::strcat( pTypePath, pNIRecType );
//		::strcat( pTypePath, "/" );
//		::strcat( pTypePath, pContext->fRecName );

//		siResult = NiLib2::Copy( pContext->fDomain, &pContext->dirID, pTypePath, pContext->fDomain, true );
		siResult = NiLib2::Copy( pContext->fDomain, &pContext->dirID, pPath, pContext->fDomain, true );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		siResult = NiLib2::Destroy( pContext->fDomain, &pContext->dirID );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		//could have called IsValidRecordName here but since pPath already built above no need to
		siResult = ::ni_pathsearch( pContext->fDomain, &pContext->dirID, pPath );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		free(pPath);
		pPath = nil;
	}

	catch( sInt32 err )
	{
		siResult = err;
		if (pPath != nil)
		{
			free(pPath);
			pPath = nil;
		}
	}
	
	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

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

	gNetInfoMutex->Signal();

	return( siResult );

} // SetRecordType



//------------------------------------------------------------------------------------
//	* DeleteRecord
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DeleteRecord ( sDeleteRecord *inData )
{
	sInt32					siResult		= eDSNoErr;
	ni_status				niStatus		= NI_OK;
	sNIContextData		   *pContext		= nil;
	char				   *authUser		= nil;
	ni_namelist				niValuesGUID;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext != nil )
		{
			// check access for non-admin users
			if ( ( pContext->fEffectiveUID != 0 )
				&& ( (pContext->fAuthenticatedUserName == NULL) 
					|| (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
			{
				authUser = pContext->fAuthenticatedUserName;
				ni_proplist niPropList;
				ni_id		parentDir;
				ni_index	tempIndex = NI_INDEX_NULL;

				::memcpy( &parentDir, &pContext->dirID, sizeof( ni_id ) );
				siResult = ::ni_parent( pContext->fDomain, &parentDir, &tempIndex );
				if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
				parentDir.nii_object = tempIndex;
				siResult = ::ni_self( pContext->fDomain, &parentDir );
				if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
				siResult = ::ni_read( pContext->fDomain, &parentDir, &niPropList );
				if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

				siResult = NiLib2::ValidateDir( authUser, &niPropList );
				::ni_proplist_free( &niPropList );

				if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
			}
			
			{
				//need the recordname and GUID
				//lookup GUID for ShadowHash but don't worry if not found
				ni_status niResultGUID = ni_lookupprop( pContext->fDomain, &pContext->dirID, 
											"generateduid", &niValuesGUID );
				char* GUIDString = nil;
				if (niResultGUID == NI_OK)
				{
					if (niValuesGUID.ni_namelist_len > 0)
					{
						if (niValuesGUID.ni_namelist_val[0] != nil)
						{
							GUIDString = strdup(niValuesGUID.ni_namelist_val[0]);
						}
						ni_namelist_free(&niValuesGUID);
					}
				}
				//check to remove all corresponding hash files
				RemoveShadowHash( pContext->fRecName, GUIDString, true );
				if (GUIDString != nil)
				{
					free(GUIDString);
					GUIDString = nil;
				}
			}
			
			niStatus = NiLib2::Destroy( pContext->fDomain, &pContext->dirID );
			if ( niStatus == NI_NODIR )
			{
				// Record was not found...
				siResult = eDSRecordNotFound;
			}
			else if ( niStatus != NI_OK )
			{
				siResult = MapNetInfoErrors( niStatus );
			}
			else
			{
				gNINodeRef->RemoveItem( inData->fInRecRef );
			}
		}
		else
		{
			siResult = eDSInvalidRecordRef;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // DeleteRecord



//------------------------------------------------------------------------------------
//	* SetRecordName
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::SetRecordName ( sSetRecordName *inData )
{
	sInt32					siResult		= eDSNoErr;
	bool					bFreePropList	= false;
	uInt32					uiNameLen		= 0;
	char				   *pNewName		= nil;
	sNIContextData		   *pContext		= nil;
	ni_proplist				niPropList;
	ni_index				niWhere;
	ni_namelist				niValues;
	ni_namelist				niCurrentName;
	char				   *pOldName		= nil;
	uInt32					pv				= 0;
	ni_namelist				niValue;
	ni_property				niProp;
	ni_namelist				niNameList;
	bool					bDidNotFindValue= true;
	char				   *pNIRecType		= nil;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		// need to be talking to the master
		::ni_needwrite( pContext->fDomain, 1 );

		siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		bFreePropList = true;

		niWhere = ::ni_proplist_match( niPropList, "name", nil );
		if (niWhere == NI_INDEX_NULL) throw( (sInt32)ePlugInError );

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			if ( siResult != NI_OK )
			{
				siResult = NiLib2::ValidateName( authUser, &niPropList, niWhere );
			}

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}

		//read the current name
		NI_INIT(&niCurrentName);
		siResult = ::ni_readprop(pContext->fDomain, &pContext->dirID, niWhere, &niCurrentName);
		//name replaced below is first one so only get that one
		pOldName = (char *)::calloc(1, strlen( niCurrentName.ni_namelist_val[ 0 ] ));
		strcpy(pOldName, niCurrentName.ni_namelist_val[ 0 ]);
		::ni_namelist_free( &niCurrentName );		

		// Get the new name
		uiNameLen = ::strlen( inData->fInNewRecName->fBufferData );
		if (uiNameLen == 0) throw( (sInt32)eDSNullRecName );

		pNewName = (char *)::malloc( uiNameLen );
		if ( pNewName == nil ) throw( (sInt32)eMemoryAllocError );

		// Remove any unexpected characters in the record name
		this->MakeGood( inData->fInNewRecName->fBufferData, pNewName );

		// Now set it
		siResult = ::ni_writename( pContext->fDomain, &pContext->dirID, niWhere, 0, pNewName );
		siResult = MapNetInfoErrors( siResult );

		//make sure that the type is users only here for setting additional attributes
		if (pContext->fRecType != nil)
		{
			pNIRecType = MapRecToNetInfoType( pContext->fRecType );
		}

		if ( (siResult == eDSNoErr) && (pNIRecType != nil) && (::strcmp(pNIRecType,"users") == 0) )
		{
			//create and set the attribute _writers_passwd

			//check if it exists if so replace otherwise create
			niWhere = ::ni_proplist_match( niPropList, "_writers_passwd", nil );
			if (niWhere != NI_INDEX_NULL)
			{
				//now replace the old with the new
				niProp = niPropList.ni_proplist_val[ niWhere ];
				niValue = niProp.nip_val;

				// For each value in the namelist for this property
				for ( pv = 0; pv < niValue.ni_namelist_len; pv++ )
				{
					if ( strcmp( niValue.ni_namelist_val[ pv ], pOldName ) == 0 )
					{
						NI_INIT( &niNameList );
						::ni_namelist_insert( &niNameList, niValue.ni_namelist_val[ pv ], NI_INDEX_NULL );

						siResult = NiLib2::DestroyDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_passwd", niNameList );

						siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_passwd", pNewName, pv );
						siResult = MapNetInfoErrors( siResult );

						::ni_namelist_free( &niNameList );

						bDidNotFindValue = false;

						break;
					}
				}
				if (bDidNotFindValue)
				{
					siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_passwd", pNewName, niValue.ni_namelist_len );
					siResult = MapNetInfoErrors( siResult );
				}
			}
			else
			{
				NI_INIT( &niValues );
				::ni_namelist_insert( &niValues, pNewName, NI_INDEX_NULL );

				siResult = DoAddAttribute( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_passwd", niValues );
			
				::ni_namelist_free( &niValues );
			}
			
			//create and set the attribute _writers_picture

			//check if it exists if so replace otherwise create
			niWhere = ::ni_proplist_match( niPropList, "_writers_picture", nil );
			if (niWhere != NI_INDEX_NULL)
			{
				//now replace the old with the new
				niProp = niPropList.ni_proplist_val[ niWhere ];
				niValue = niProp.nip_val;

				// For each value in the namelist for this property
				for ( pv = 0; pv < niValue.ni_namelist_len; pv++ )
				{
					if ( strcmp( niValue.ni_namelist_val[ pv ], pOldName ) == 0 )
					{
						NI_INIT( &niNameList );
						::ni_namelist_insert( &niNameList, niValue.ni_namelist_val[ pv ], NI_INDEX_NULL );

						siResult = NiLib2::DestroyDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_picture", niNameList );

						siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_picture", pNewName, pv );
						siResult = MapNetInfoErrors( siResult );

						::ni_namelist_free( &niNameList );

						bDidNotFindValue = false;

						break;
					}
				}
				if (bDidNotFindValue)
				{
					siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_picture", pNewName, niValue.ni_namelist_len );
					siResult = MapNetInfoErrors( siResult );
				}
			}
			else
			{
				NI_INIT( &niValues );
				::ni_namelist_insert( &niValues, pNewName, NI_INDEX_NULL );

				siResult = DoAddAttribute( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_picture", niValues );
			
				::ni_namelist_free( &niValues );
			}
			
#ifdef TIM_CLIENT_PRESENT
			bDidNotFindValue = true;
			
			//check if it exists if so replace otherwise create
			niWhere = ::ni_proplist_match( niPropList, "_writers_tim_password", nil );
			if (niWhere != NI_INDEX_NULL)
			{
				//now replace the old with the new
				niProp = niPropList.ni_proplist_val[ niWhere ];
				niValue = niProp.nip_val;

				// For each value in the namelist for this property
				for ( pv = 0; pv < niValue.ni_namelist_len; pv++ )
				{
					if ( strcmp( niValue.ni_namelist_val[ pv ], pOldName ) == 0 )
					{
						NI_INIT( &niNameList );
						::ni_namelist_insert( &niNameList, niValue.ni_namelist_val[ pv ], NI_INDEX_NULL );

						siResult = NiLib2::DestroyDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_tim_password", niNameList );

						siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_tim_password", pNewName, pv );
						siResult = MapNetInfoErrors( siResult );

						::ni_namelist_free( &niNameList );

						bDidNotFindValue = false;

						break;
					}
				}
				if (bDidNotFindValue)
				{
					siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_tim_password", pNewName, niValue.ni_namelist_len );
					siResult = MapNetInfoErrors( siResult );
				}
			}
			else
			{
				NI_INIT( &niValues );
				::ni_namelist_insert( &niValues, pNewName, NI_INDEX_NULL );

				siResult = DoAddAttribute( pContext->fDomain, &pContext->dirID, (const ni_name)"_writers_tim_password", niValues );
			
				::ni_namelist_free( &niValues );
			}
#endif
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	if ( bFreePropList == true )
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();
	
	if ( pNIRecType != nil )
	{
		free( pNIRecType );
		pNIRecType = nil;
	}

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

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

	return( siResult );

} // SetRecordName


//------------------------------------------------------------------------------------
//	* RemoveAttribute
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::RemoveAttribute ( sRemoveAttribute *inData )
{
	sInt32					siResult		= eDSNoErr;
	char				   *pAttrType		= nil;
	sNIContextData		   *pContext		= nil;
	ni_namelist				niAttribute;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Record context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pAttrType = MapAttrToNetInfoType( inData->fInAttribute->fBufferData );
		if ( pAttrType == nil ) throw( (sInt32)eDSInvalidAttributeType );

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			if ( ( authUser == NULL ) && (strcmp(pAttrType,"picture") == 0) )
			{
				authUser = GetUserNameForUID( pContext->fEffectiveUID, pContext->fDomain );
			}
			ni_proplist niPropList;

			siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			::ni_proplist_free( &niPropList );

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}

		// check if the auth authority of a shadow hash user is being removed
		// if so we should also remove the hash file for that user
		if ( (pAttrType != nil) && (strcmp(pAttrType,"authentication_authority") == 0) )
		{
			ni_namelist niValues;
			ni_namelist niValuesGUID;
			NI_INIT( &niValues );
			NI_INIT( &niValuesGUID );
			//lookup authauthority attribute
			siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "authentication_authority", &niValues );
			if ( (siResult == eDSNoErr) && (niValues.ni_namelist_len > 0) && (niValues.ni_namelist_val[0] != nil))
			{
				if ( strstr(niValues.ni_namelist_val[0], kDSValueAuthAuthorityShadowHash) != nil )
				{
					siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "generateduid", &niValuesGUID );
					if ( (siResult == eDSNoErr) && (niValuesGUID.ni_namelist_len > 0) && (niValuesGUID.ni_namelist_val[0] != nil))
					{
						//check to remove all corresponding hash files
						RemoveShadowHash( pContext->fRecName, niValuesGUID.ni_namelist_val[0], true );
					}
					if (niValuesGUID.ni_namelist_len > 0)
					{
						ni_namelist_free(&niValuesGUID);
					}
				}
			}
			if (niValues.ni_namelist_len > 0)
			{
				ni_namelist_free(&niValues);
			}
		}
		
		NI_INIT( &niAttribute );
		::ni_namelist_insert( &niAttribute, pAttrType, NI_INDEX_NULL );

		siResult = NiLib2::DestroyDirProp( pContext->fDomain, &pContext->dirID, niAttribute );
		siResult = MapNetInfoErrors( siResult );
		
		::ni_namelist_free( &niAttribute );

		if ( pAttrType != nil )
		{
			delete( pAttrType );
			pAttrType = nil;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // RemoveAttribute


//------------------------------------------------------------------------------------
//	* AddAttributeValue
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::AddAttributeValue ( sAddAttributeValue *inData )
{
	sInt32					siResult		= eDSNoErr;
	char				   *pAttrType		= nil;
	char				   *pAttrValue		= nil;
	sNIContextData		   *pContext		= nil;
	ni_namelist				niNameList;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Record context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pAttrValue = inData->fInAttrValue->fBufferData;
		pAttrType = MapAttrToNetInfoType( inData->fInAttrType->fBufferData );
		if ( pAttrType == nil ) throw( (sInt32)eDSInvalidAttributeType );

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			if ( ( authUser == NULL ) && (strcmp(pAttrType,"picture") == 0) )
			{
				authUser = GetUserNameForUID( pContext->fEffectiveUID, pContext->fDomain );
			}
			ni_proplist niPropList;
			ni_index	niIndex;

			siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			if ( siResult != NI_OK )
			{
				niIndex = ::ni_proplist_match( niPropList, pAttrType, NULL );
				siResult = NiLib2::ValidateName( authUser, &niPropList, niIndex );
			}
			::ni_proplist_free( &niPropList );

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}

		NI_INIT( &niNameList );
		::ni_namelist_insert( &niNameList, pAttrValue, NI_INDEX_NULL );

		siResult = NiLib2::AppendDirProp( pContext->fDomain, &pContext->dirID, pAttrType, niNameList );
		siResult = MapNetInfoErrors( siResult );

		::ni_namelist_free( &niNameList );

		if ( pAttrType != nil )
		{
			delete( pAttrType );
			pAttrType = nil;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // AddAttributeValue


//------------------------------------------------------------------------------------
//	* RemoveAttributeValue
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::RemoveAttributeValue ( sRemoveAttributeValue *inData )
{
	bool					done			= false;
	sInt32					siResult		= eDSNoErr;
	bool					bFreePropList	= false;
	uInt32					pn				= 0;
	uInt32					pv				= 0;
	char				   *pAttrType		= nil;
	sNIContextData		   *pContext		= nil;
	ni_namelist				niNameList;
	ni_namelist				niValue;
	ni_property				niProp;
	ni_proplist				niPropList;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Record context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		// Get the native net info attribute
		pAttrType = MapAttrToNetInfoType( inData->fInAttrType->fBufferData );

		// read the attribute list
		siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		bFreePropList = true;

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			if ( ( authUser == NULL ) && (strcmp(pAttrType,"picture") == 0) )
			{
				authUser = GetUserNameForUID( pContext->fEffectiveUID, pContext->fDomain );
			}
			ni_index	niIndex;

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			if ( siResult != NI_OK )
			{
				niIndex = ::ni_proplist_match( niPropList, pAttrType, NULL );
				siResult = NiLib2::ValidateName( authUser, &niPropList, niIndex );
			}

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}		
		
		siResult = eDSAttributeNotFound;

// xxx use ni_namelist_match for better performance

		for ( pn = 0; (pn < niPropList.ni_proplist_len) && (done == false); pn++ )
		{
			niProp = niPropList.ni_proplist_val[ pn ];

			if ( (pAttrType != nil) && (::strcmp( niProp.nip_name, pAttrType ) == 0) )
			{
				niValue = niProp.nip_val;

				// For each value in the namelist for this property
				for ( pv = 0; (pv < niValue.ni_namelist_len) && (done == false); pv++ )
				{
					uInt32 crcVal = CalcCRC( niValue.ni_namelist_val[ pv ] );

					if ( crcVal == inData->fInAttrValueID )
					{
						// check if the auth authority value of shadow hash for a user is being removed
						// if so we should also remove the hash file for that user
						if ( (pAttrType != nil) && (strcmp(pAttrType,"authentication_authority") == 0) )
						{
							ni_namelist niValues;
							ni_namelist niValuesGUID;
							NI_INIT( &niValues );
							NI_INIT( &niValuesGUID );
							//lookup authauthority attribute
							siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "authentication_authority", &niValues );
							if ( (siResult == eDSNoErr) && (niValues.ni_namelist_len > 0) && (niValues.ni_namelist_val[0] != nil))
							{
								if ( strstr(niValues.ni_namelist_val[0], kDSValueAuthAuthorityShadowHash) != nil )
								{
									siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "generateduid", &niValuesGUID );
									if ( (siResult == eDSNoErr) && (niValuesGUID.ni_namelist_len > 0) && (niValuesGUID.ni_namelist_val[0] != nil))
									{
										//check to remove all corresponding hash files
										RemoveShadowHash( pContext->fRecName, niValuesGUID.ni_namelist_val[0], true );
									}
									if (niValuesGUID.ni_namelist_len > 0)
									{
										ni_namelist_free(&niValuesGUID);
									}
								}
							}
							if (niValues.ni_namelist_len > 0)
							{
								ni_namelist_free(&niValues);
							}
						}

						NI_INIT( &niNameList );
						::ni_namelist_insert( &niNameList, niValue.ni_namelist_val[ pv ], NI_INDEX_NULL );

						siResult = NiLib2::DestroyDirVal( pContext->fDomain, &pContext->dirID, pAttrType, niNameList );
						if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

						::ni_namelist_free( &niNameList );

						done = true;

						break;
					}
				}
			}
		}
		
		if ( pAttrType != nil )
		{
			delete( pAttrType );
			pAttrType = nil;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	if (bFreePropList)
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // RemoveAttributeValue



//------------------------------------------------------------------------------------
//	* SetAttributeValue
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::SetAttributeValue ( sSetAttributeValue *inData )
{
	bool					done			= false;
	sInt32					siResult		= eDSNoErr;
	bool					bFreePropList	= false;
	uInt32					pn				= 0;
	uInt32					pv				= 0;
	char				   *pAttrType		= nil;
	char				   *pAttrValue		= nil;
	sNIContextData		   *pContext		= nil;
	ni_namelist				niNameList;
	ni_namelist				niValue;
	ni_property				niProp;
	ni_proplist				niPropList;
	char				   *authUser		= nil;

	gNetInfoMutex->Wait();

	try
	{
		// Record context data
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInRecRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidRecordRef );

		pAttrType = MapAttrToNetInfoType( inData->fInAttrType->fBufferData );

		siResult = ::ni_read( pContext->fDomain, &pContext->dirID, &niPropList );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		bFreePropList = true;

		// check access for non-admin users
		if ( ( pContext->fEffectiveUID != 0 )
			 && ( (pContext->fAuthenticatedUserName == NULL) 
				  || (strcmp(pContext->fAuthenticatedUserName,"root") != 0) ) )
		{
			authUser = pContext->fAuthenticatedUserName;
			if ( ( authUser == NULL ) && (strcmp(pAttrType,"picture") == 0) )
			{
				authUser = GetUserNameForUID( pContext->fEffectiveUID, pContext->fDomain );
			}
			ni_index	niIndex;

			siResult = NiLib2::ValidateDir( authUser, &niPropList );
			if ( siResult != NI_OK )
			{
				niIndex = ::ni_proplist_match( niPropList, pAttrType, NULL );
				siResult = NiLib2::ValidateName( authUser, &niPropList, niIndex );
			}

			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}

		siResult = eDSAttributeNotFound;
		for ( pn = 0; pn < (niPropList.ni_proplist_len) && (done == false); pn++ )
		{
			niProp = niPropList.ni_proplist_val[ pn ];

			if ( (pAttrType != nil) && (::strcmp( niProp.nip_name, pAttrType ) == 0) )
			{
				niValue = niProp.nip_val;

				// For each value in the namelist for this property
				for ( pv = 0; (pv < niValue.ni_namelist_len) && (done == false); pv++ )
				{
					uInt32 crcVal = CalcCRC( niValue.ni_namelist_val[ pv ] );
					if ( crcVal == inData->fInAttrValueEntry->fAttributeValueID )
					{
						pAttrValue = inData->fInAttrValueEntry->fAttributeValueData.fBufferData;

						// check if the auth authority value of shadow hash for a user is being replaced
						// if so we should also remove the hash file for that user
						if ( (pAttrType != nil) && (strcmp(pAttrType,"authentication_authority") == 0) )
						{
							ni_namelist niValues;
							ni_namelist niValuesGUID;
							NI_INIT( &niValues );
							NI_INIT( &niValuesGUID );
							//lookup authauthority attribute
							siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "authentication_authority", &niValues );
							if ( (siResult == eDSNoErr) && (niValues.ni_namelist_len > 0) && (niValues.ni_namelist_val[0] != nil))
							{
								if ( strstr(niValues.ni_namelist_val[0], kDSValueAuthAuthorityShadowHash) != nil ) 
								{
									if (	( strstr(pAttrValue, kDSValueAuthAuthorityShadowHash) == nil ) &&
											( strstr(pAttrValue, kDSTagAuthAuthorityDisabledUser) == nil ) )
									{
										siResult = ni_lookupprop( pContext->fDomain, &pContext->dirID, "generateduid", &niValuesGUID );
										if ( (siResult == eDSNoErr) && (niValuesGUID.ni_namelist_len > 0) && (niValuesGUID.ni_namelist_val[0] != nil))
										{
											//check to remove all corresponding hash files
											RemoveShadowHash( pContext->fRecName, niValuesGUID.ni_namelist_val[0], true );
										}
										if (niValuesGUID.ni_namelist_len > 0)
										{
											ni_namelist_free(&niValuesGUID);
										}
									}
								}
							}
							if (niValues.ni_namelist_len > 0)
							{
								ni_namelist_free(&niValues);
							}
						}

						NI_INIT( &niNameList );
						::ni_namelist_insert( &niNameList, niValue.ni_namelist_val[ pv ], NI_INDEX_NULL );

						siResult = NiLib2::DestroyDirVal( pContext->fDomain, &pContext->dirID, pAttrType, niNameList );

						siResult = NiLib2::InsertDirVal( pContext->fDomain, &pContext->dirID, pAttrType, pAttrValue, pv );
						siResult = MapNetInfoErrors( siResult );

						::ni_namelist_free( &niNameList );

						done = true;

						break;
					}
				}
			}
		}

		if ( pAttrType != nil )
		{
			delete( pAttrType );
			pAttrType = nil;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (authUser != nil)
	{
		if (pContext != nil)
		{
			if (pContext->fAuthenticatedUserName == nil)
			{
				free(authUser);
				authUser = nil;
			}
		}
	}

	if (bFreePropList)
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // SetAttributeValue


//------------------------------------------------------------------------------------
//	* DoPlugInCustomCall
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoPlugInCustomCall ( sDoPlugInCustomCall *inData )
{
	sInt32					siResult		= eDSNoErr;
	sNIContextData		   *pContext		= nil;

	gNetInfoMutex->Wait();

	try
	{
		if ( inData == nil ) throw( (sInt32)eMemoryError );

		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		switch ( inData->fInRequestCode )
		{
			default:
				siResult = eNotHandledByThisNode;
				break;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );


} // DoPlugInCustomCall


//------------------------------------------------------------------------------------
//	* DoAttributeValueSearch
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoAttributeValueSearch ( sDoAttrValueSearchWithData *inData )
{
	sInt32					siResult			= eDSNoErr;
	bool					bAttrInfoOnly		= false;
	uInt32					uiCount				= 0;
	uInt32					uiTotal				= 0;
	CRecTypeList		   *cpRecTypeList		= nil;
	CAttributeList		   *cpAttrTypeList		= nil;
	char				   *pSearchStr			= nil;
	char				   *pDS_AttrType		= nil;
	char				   *pNI_RecType			= nil;
	char				   *pDS_RecType			= nil;
	sNIContextData		   *pContext			= nil;
	sNIContinueData		   *pContinue			= nil;
	tDirPatternMatch		pattMatch			= eDSExact;
	tDataList			   *pTmpDataList		= nil;
	CBuff				   *outBuff				= nil;
	bool					bBuffFull			= false;
	bool					separateRecTypes	= false;
	uInt32					countDownRecTypes	= 0;

	gNetInfoMutex->Wait();

	try
	{
		if ( inData == nil ) throw( (sInt32)eMemoryError );

		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		if ( inData->fIOContinueData == nil )
		{
			pContinue = (sNIContinueData *)::calloc( 1, sizeof( sNIContinueData ) );
			gNIContinue->AddItem( pContinue, inData->fInNodeRef );

			pContinue->fRecNameIndex = 1;
			pContinue->fRecTypeIndex = 1;
			pContinue->fAttrIndex = 1;
			pContinue->fAllRecIndex		= 0;
			pContinue->fTotalRecCount	= 0;
			pContinue->fLimitRecSearch	= 0;
			
			//check if the client has requested a limit on the number of records to return
			//we only do this the first call into this context for pContinue
			if (inData->fOutMatchRecordCount >= 0)
			{
				pContinue->fLimitRecSearch = inData->fOutMatchRecordCount;
			}
		}
		else
		{
			pContinue = (sNIContinueData *)inData->fIOContinueData;
			if ( gNIContinue->VerifyItem( pContinue ) == false )
			{
				throw( (sInt32)eDSInvalidContinueData );
			}
		}

		inData->fIOContinueData			= nil;
		//return zero if we don't find anything
		inData->fOutMatchRecordCount	= 0;

		outBuff = new CBuff;
		if ( outBuff == nil ) throw( (sInt32)eMemoryAllocError );

		siResult = outBuff->Initialize( inData->fOutDataBuff, true );
		if ( siResult != eDSNoErr ) throw( siResult );

		siResult = outBuff->SetBuffType( 'StdA' );
		if ( siResult != eDSNoErr ) throw( siResult );

		// Get the record type list
		cpRecTypeList = new CRecTypeList( inData->fInRecTypeList );
		if ( cpRecTypeList == nil ) throw( (sInt32)eDSEmptyRecordTypeList );
		if (cpRecTypeList->GetCount() == 0) throw( (sInt32)eDSEmptyRecordTypeList );
		//save the number of rec types here to use in separating the buffer data
		countDownRecTypes = cpRecTypeList->GetCount() - pContinue->fRecTypeIndex + 1;

		pDS_AttrType = inData->fInAttrType->fBufferData;
		if ( pDS_AttrType == nil ) throw( (sInt32)eDSEmptyAttributeType );

		pSearchStr = inData->fInPatt2Match->fBufferData;
		if ( pSearchStr == nil ) throw( (sInt32)eDSEmptyPattern2Match );

		if ( inData->fType == kDoAttributeValueSearchWithData )
		{
			// Get the attribute list
			cpAttrTypeList = new CAttributeList( inData->fInAttrTypeRequestList );
			if ( cpAttrTypeList == nil ) throw( (sInt32)eDSEmptyAttributeTypeList );
			if (cpAttrTypeList->GetCount() == 0) throw( (sInt32)eDSEmptyAttributeTypeList );

			bAttrInfoOnly = inData->fInAttrInfoOnly;
		}
		else
		{
			pTmpDataList = ::dsBuildListFromStringsPriv( kDSAttributesAll, nil );
			if ( pTmpDataList != nil )
			{
				cpAttrTypeList = new CAttributeList( pTmpDataList );
				if ( cpAttrTypeList == nil ) throw( (sInt32)eDSEmptyAttributeTypeList );
				if (cpAttrTypeList->GetCount() == 0) throw( (sInt32)eDSEmptyAttributeTypeList );
			}
		}

		pattMatch = inData->fInPattMatchType;
		siResult = VerifyPatternMatch( pattMatch );
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidPatternMatchType );

//KW need to be smarter on exiting from the limit search condition ie. stop calling into more record types and names
// if the limit has already been reached ie. check in this routine as well before continuing the search

		while ( (cpRecTypeList->GetAttribute( pContinue->fRecTypeIndex, &pDS_RecType ) == eDSNoErr) &&
				(!bBuffFull) && (!separateRecTypes)  )
		{
			// Do we support this record type?
			//	- There could be a whole list so don't quit if we encounter a bad one.

			pNI_RecType = MapRecToNetInfoType( pDS_RecType );
			if ( pNI_RecType != nil )
			{
				if ( ::strcmp( pDS_AttrType, kDSAttributesAll ) == 0 )
				{
					siResult = FindAllRecords ( pNI_RecType,
												pDS_RecType,
												pSearchStr,
												pattMatch,
												cpAttrTypeList,
												bAttrInfoOnly,
												pContinue,
												pContext->fDomain,
												pContext->fDomainName,
												outBuff,
												uiCount );
				}
				else
				{
					siResult = FindTheseRecords ( pNI_RecType,
												pDS_RecType,
												pDS_AttrType,
												pSearchStr,
												pattMatch,
												cpAttrTypeList,
												bAttrInfoOnly,
												pContinue,
												pContext->fDomain,
												pContext->fDomainName,
												outBuff,
												uiCount );

				}
				
				//outBuff->GetDataBlockCount( &uiCount );
				//cannot use this as there may be records added from different record names
				//at which point the first name will fill the buffer with n records and
				//uiCount is reported as n but as the second name fills the buffer with m MORE records
				//the statement above will return the total of n+m and add it to the previous n
				//so that the total erroneously becomes 2n+m and not what it should be as n+m
				//therefore uiCount is extracted directly out of the GetxxxRecord(s) calls

				if ( siResult == CBuff::kBuffFull )
				{
					bBuffFull = true;
					inData->fIOContinueData = pContinue;

					// check to see if buffer is empty and no entries added
					// which implies that the buffer is too small
					if ( ( uiCount == 0 ) && ( uiTotal == 0 ) )
					{
						siResult = eDSBufferTooSmall;
						break;
					}
					uiTotal += uiCount;
		
					inData->fOutMatchRecordCount = uiTotal;
					outBuff->SetLengthToSize();

					siResult = eDSNoErr;
				}
				else if ( siResult == eDSNoErr )
				{
					uiTotal += uiCount;
					pContinue->fRecNameIndex++;
				}
				delete( pNI_RecType );
				pNI_RecType = nil;
			}

			if ( !bBuffFull )
			{
				pDS_RecType = nil;
				pContinue->fRecTypeIndex++;
				pContinue->fRecNameIndex = 1;
				//KW? here we decide to exit with data full of the current type of records
				// and force a good exit with the data we have so we can come back for the next rec type
				separateRecTypes = true;
				//set continue since there may be more data available
				inData->fIOContinueData = pContinue;
				//make sure that the ni_entry_list is cleared for the previous record type
				if ( pContinue->fNIEntryList.ni_entrylist_len > 0 )
				{
					::ni_entrylist_free(&(pContinue->fNIEntryList));
					pContinue->fNIEntryList.ni_entrylist_len = 0;
				}
				siResult = eDSNoErr;

				//however if this was the last rec type then there will be no more data
				// check the number of rec types left
				countDownRecTypes--;
				if ( countDownRecTypes == 0 )
				{
					inData->fIOContinueData = nil;
				}
			}
		}

		if ( (siResult == eDSNoErr) && (!bBuffFull) )
		{
			if ( uiTotal == 0 )
			{
				//KW to remove "siResult = eDSRecordNotFound" altogether
				// see 2531386  dsGetRecordList should not return an error if record not found
				//siResult = eDSRecordNotFound;
				outBuff->ClearBuff();
			}
			else
			{
				outBuff->SetLengthToSize();
			}

			inData->fOutMatchRecordCount = uiTotal;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( (inData->fIOContinueData == nil) && (pContinue != nil) )
	{
		// we've decided not to return continue data, so we should clean up
		gNIContinue->RemoveItem( pContinue );
		pContinue = nil;
	}

	gNetInfoMutex->Signal();

	if ( cpAttrTypeList != nil )
	{
		delete( cpAttrTypeList );
		cpAttrTypeList = nil;
	}
	if ( cpRecTypeList != nil )
	{
		delete( cpRecTypeList );
		cpRecTypeList = nil;
	}
	if ( outBuff != nil )
	{
		delete( outBuff );
		outBuff = nil;
	}
	if ( pNI_RecType != nil )
	{
		delete( pNI_RecType );
		pNI_RecType = nil;
	}

	if ( pTmpDataList != nil )
	{
		(void)::dsDataListDeallocatePriv( pTmpDataList );
		//need to free the header as well
		free( pTmpDataList );
		pTmpDataList = nil;
	}

	return( siResult );

} // DoAttributeValueSearch


//------------------------------------------------------------------------------------
//	* FindAllRecords
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn:: FindAllRecords (	const char			*inNI_RecType,
									const char			*inDS_RecType,
									const char			*inPatt2Match,
									tDirPatternMatch	 inHow,
									CAttributeList		*inAttrTypeList,
									bool				 inAttrOnly,
									sNIContinueData		*inContinue,
									void				*inDomain,
									char				*inDomainName,
									CBuff				*inBuff,
									uInt32				&outRecCount )
{
	uInt32			pn				= 0;
	uInt32			pv				= 0;
	sInt32			siResult		= eDSNoErr;
//	ni_status		niStatus		= NI_OK;
	sInt32			siCount			= 0;
	uInt32			uiSearchFlag	= REG_BASIC;
	bool			bMatches		= false;
//	u_int			en				= 0;
	u_int			en2				= 0;
	ni_index		niIndex			= 0;
	ni_id			niDirID;
	ni_entry		niEntry;
	ni_proplist		niPropList;
	ni_entrylist	niEntryList;
	ni_namelist		niNameList;
	CString			csNI_RecName( 128 );
	CString			csNI_AttrType( 128 );
	ni_property		niProp;

	gNetInfoMutex->Wait();

	outRecCount = 0; //need to track how many records were found by this call to FindAllRecords

	try
	{
		NI_INIT( &niDirID );
		NI_INIT( &niEntry );
		NI_INIT( &niPropList );
		NI_INIT( &niNameList );
		NI_INIT( &niEntryList );

		// Case sensitive check
		if ( (inHow >= eDSiExact) && (inHow <= eDSiRegularExpression) )
		{
			uiSearchFlag = REG_ICASE;
		}

		// Get the dir ID for the record type
		siResult = ::ni_pathsearch( inDomain, &niDirID, inNI_RecType );
		if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

		// Do the search
		if ( inContinue->fNIEntryList.ni_entrylist_len == 0 )
		{
			siResult = ::ni_list( inDomain, &niDirID, "name", &(inContinue->fNIEntryList) );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );
		}
		niEntryList = inContinue->fNIEntryList;

		for ( en2 = inContinue->fAllRecIndex; (en2 < niEntryList.ni_entrylist_len) &&
								(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
					 			(inContinue->fLimitRecSearch == 0)); en2++ )
		{
			niEntry = niEntryList.ni_entrylist_val[ en2 ];
			niDirID.nii_object = niEntry.id;

			if ( niEntry.names != NULL )
			{
				siResult = ::ni_read( inDomain, &niDirID, &niPropList );
				if ( siResult == NI_OK )
				{
					bMatches = false;
					
					for ( pn = 0; ((pn < niPropList.ni_proplist_len) && (bMatches == false)); pn++ )
					{
						niProp = niPropList.ni_proplist_val[ pn ];
						niNameList = niProp.nip_val;
						for ( pv = 0; ((pv < niNameList.ni_namelist_len) && (bMatches == false)); pv++ )
						{
							bMatches = DoesThisMatch( niNameList.ni_namelist_val[ pv ], inPatt2Match, inHow );
						}
					}
					if ( bMatches == true )
					{
						fAttrData->Clear();
						fRecData->Clear();
									
						fRecData->AppendShort( ::strlen( inDS_RecType ) );
						fRecData->AppendString( inDS_RecType );

						niIndex = ::ni_proplist_match( niPropList, "name", NULL );
						if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
						{
							fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
							fRecData->AppendString( "*** No Name ***" );
						}
						else
						{
							fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
							fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
						}

						siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
						if ( siResult == eDSNoErr )
						{
							if ( siCount == 0 )
							{
								// Attribute count
								fRecData->AppendShort( 0 );
							}
							else
							{
								// Attribute count
								fRecData->AppendShort( siCount );
								fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
							}

							siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
										
							//specifically check for buffer full here and segregate true errors
							if ( siResult == CBuff::kBuffFull )
							{
								inContinue->fAllRecIndex = en2;
								::ni_proplist_free( &niPropList );
								break;
							}
							else if ( siResult == eDSNoErr )
							{
								outRecCount++;
								inContinue->fTotalRecCount++;
							}
            				else
            				{
								inContinue->fAllRecIndex = 0;
								::ni_proplist_free( &niPropList );
								siResult = eDSInvalidBuffFormat;
								break;
		            		}
						}
					} // if ( bMatches == true )
					//need to free this regardless if there were matches or not
					::ni_proplist_free( &niPropList );
				} // if ( siResult == NI_OK )
			} // if ( niEntry.names != NULL )
		} // main for loop on en2
		if ( siResult == eDSNoErr )
		{
			inContinue->fAllRecIndex	= 0;
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult ); 

} // FindAllRecords


//------------------------------------------------------------------------------------
//	* FindTheseRecords
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::FindTheseRecords (	const char		 *inNI_RecType,
										const char		 *inDS_RecType,
										const char		 *inDS_AttrType,
										const char		 *inPatt2Match,
										tDirPatternMatch inHow,
										CAttributeList	 *inAttrTypeList,
										bool			 inAttrOnly,
										sNIContinueData	 *inContinue,
										void			 *inDomain,
										char			 *inDomainName,
										CBuff			 *inBuff,
										uInt32			 &outRecCount )

{
	bool			done			= false;
	bool			bMatches		= false;
	uInt32			i				= 0;
	sInt32			siCount			= 0;
	sInt32			siResult		= eDSNoErr;
	uInt32			uiSearchFlag	= REG_BASIC;
	ni_status		niStatus		= NI_OK;
	u_int			en				= 0;
	ni_index		niIndex			= 0;
	ni_id			niDirID;
	ni_entry		niEntry;
	ni_entrylist	niEntryList;
	ni_proplist		niPropList;
	ni_namelist		niNameList;
	CString			csNI_AttrType( 128 );
	char		   * inConstRegExpPatt2Match	= nil;


	gNetInfoMutex->Wait();

	outRecCount = 0; //need to track how many records were found by this call to FindTheseRecords

	try
	{
		// Case sensitive check
		if ( (inHow >= eDSiExact) && (inHow <= eDSiRegularExpression) )
		{
			uiSearchFlag = REG_ICASE;
		}

		while ( !done )
		{
			NI_INIT( &niDirID );
			NI_INIT( &niEntryList );

			// Get the dir ID for the record
			siResult = ::ni_pathsearch( inDomain, &niDirID, inNI_RecType );
			if ( siResult != eDSNoErr ) throw( MapNetInfoErrors( siResult ) );

			if ( ::strcmp( inDS_AttrType, kDSNAttrRecordName ) == 0 )
			{
				if ( csNI_AttrType.GetLength() == 0 )
				{
					csNI_AttrType.Set( "name" );
				}
				else
				{
					csNI_AttrType.Set( "realname" );
					done = true;
				}
			}
			else
			{
				char *netInfoType = MapAttrToNetInfoType( inDS_AttrType );
				if ( netInfoType != nil )
				{
					csNI_AttrType.Set( netInfoType );
					free( netInfoType );
					netInfoType = nil;
				}
				done = true;
			}

			//ensure that if a client only asks for n records that we stop searching if
			//we have already found n records ie. critical for performance when asking for one record
			if ( (inContinue->fTotalRecCount < inContinue->fLimitRecSearch) || (inContinue->fLimitRecSearch == 0) )
			{
				// Do the search
				//make the reg exp that ni_search needs
				inConstRegExpPatt2Match = BuildRegExp(inPatt2Match);
				niStatus = ::ni_search( inDomain, &niDirID, csNI_AttrType.GetData(), inConstRegExpPatt2Match, uiSearchFlag, &niEntryList );
				free(inConstRegExpPatt2Match);
				if ( niStatus == NI_OK )
				{
					for ( en = inContinue->fAllRecIndex; (en < niEntryList.ni_entrylist_len) &&
						(((inContinue->fTotalRecCount) < inContinue->fLimitRecSearch) ||
						(inContinue->fLimitRecSearch == 0)); en++ )
	//				for ( en = inContinue->fAllRecIndex; en < niEntryList.ni_entrylist_len; en++ )
					{
						niEntry = niEntryList.ni_entrylist_val[ en ];
						niDirID.nii_object = niEntry.id;
	
						if ( niEntry.names != NULL )
						{
							niNameList = *niEntry.names;
							bMatches = false;
							for ( i = 0; ((i < niNameList.ni_namelist_len) && (bMatches == false)); i++ )
							{
								bMatches = DoesThisMatch( niNameList.ni_namelist_val[ i ], inPatt2Match, inHow );
							}
							if ( bMatches == true )
							{
								siResult = ::ni_read( inDomain, &niDirID, &niPropList );
								if ( siResult == NI_OK )
								{
									
									fAttrData->Clear();
									fRecData->Clear();
	
									fRecData->AppendShort( ::strlen( inDS_RecType ) );
									fRecData->AppendString( inDS_RecType );
	
									niIndex = ::ni_proplist_match( niPropList, "name", NULL );
									if ( niIndex == NI_INDEX_NULL || niPropList.nipl_val[ niIndex ].nip_val.ninl_len == 0 )
									{
										fRecData->AppendShort( ::strlen( "*** No Name ***" ) );
										fRecData->AppendString( "*** No Name ***" );
									}
									else
									{
										fRecData->AppendShort( ::strlen( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] ) );
										fRecData->AppendString( niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ] );
									}
	
									siResult = GetTheseAttributes( inAttrTypeList, &niPropList, inAttrOnly, inDomain, inDomainName, siCount );
									if ( siResult == eDSNoErr )
									{
										if ( siCount == 0 )
										{
											// Attribute count
											fRecData->AppendShort( 0 );
										}
										else
										{
											// Attribute count
											fRecData->AppendShort( siCount );
											fRecData->AppendBlock( fAttrData->GetData(), fAttrData->GetLength() );
										}
	
										siResult = inBuff->AddData( fRecData->GetData(), fRecData->GetLength() );
										//again need to segregate true errors away from buffer full
										if ( siResult == CBuff::kBuffFull )
										{
											inContinue->fAllRecIndex = en;
											::ni_proplist_free( &niPropList );
											if ( siResult != eDSNoErr ) throw( siResult );
										}
										else if ( siResult == eDSNoErr )
										{
											outRecCount++;
											inContinue->fTotalRecCount++;
										}
										else
										{
											inContinue->fAllRecIndex = 0;
											::ni_proplist_free( &niPropList );
											throw( (sInt32)eDSInvalidBuffFormat );
										}
									}
									::ni_proplist_free( &niPropList );
									siResult = eDSNoErr;
								}
							}
						}
					}
				} //ni_search successful
				if ( niEntryList.ni_entrylist_len > 0 )
				{
					::ni_entrylist_free(&niEntryList);
					niEntryList.ni_entrylist_len = 0;
				}
			} // need to get more records ie. did not hit the limit yet
		} // while
		inContinue->fAllRecIndex = 0;
	}

	catch( sInt32 err )
	{
		if ( niEntryList.ni_entrylist_len > 0 )
		{
			::ni_entrylist_free(&niEntryList);
			niEntryList.ni_entrylist_len = 0;
		}
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult ); 

} // FindTheseRecords


//------------------------------------------------------------------------------------
//	* DoAuthentication
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoAuthentication ( sDoDirNodeAuth *inData )
{
	sInt32				siResult		= eDSAuthFailed;
	ni_status			niResult		= NI_OK;
	ni_status			niResultGUID	= NI_OK;
	uInt32				uiAuthMethod	= 0;
	sNIContextData	   *pContext		= nil;
	sNIContinueData	   *pContinueData	= NULL;
	char*				userName		= NULL;
	ni_id				niDirID;
	ni_namelist			niValues;
	ni_namelist			niValuesGUID;
	NetInfoAuthAuthorityHandlerProc handlerProc = NULL;
	const char		   *pNIRecType		= "users";
	
	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );
		if ( inData->fInAuthStepData == nil ) throw( (sInt32)eDSNullAuthStepData );
		
		if ( inData->fIOContinueData != NULL )
		{
			// get info from continue
			pContinueData = (sNIContinueData *)inData->fIOContinueData;
			if ( gNIContinue->VerifyItem( pContinueData ) == false )
			{
				throw( (sInt32)eDSInvalidContinueData );
			}
			
			if ( pContinueData->fAuthHandlerProc == nil ) throw( (sInt32)eDSInvalidContinueData );
			handlerProc = (NetInfoAuthAuthorityHandlerProc)(pContinueData->fAuthHandlerProc);
			siResult = (handlerProc)(	inData->fInNodeRef, inData->fInAuthMethod, pContext,
										&pContinueData, inData->fInAuthStepData, 
										inData->fOutAuthStepDataResponse, 
										inData->fInDirNodeAuthOnlyFlag, false,
										pContinueData->fAuthAuthorityData, NULL, pNIRecType);
		}
		else
		{
			// first call
			// we do not want to fail if the method is unknown at this point.
			// For password server users, the PSPlugin may know the method.
			siResult = GetAuthMethod( inData->fInAuthMethod, &uiAuthMethod );
			
			// unsupported auth methods are allowed if the user is on password server
			// otherwise, unsupported auth methods are rejected in their handlers
			if ( siResult == eDSNoErr || siResult == eDSAuthMethodNotSupported )
			{
				if ( uiAuthMethod == kAuthWithAuthorizationRef )
				{
					AuthorizationRef authRef = 0;
					AuthorizationItemSet* resultRightSet = NULL;
					if ( inData->fInAuthStepData->fBufferLength < sizeof( AuthorizationExternalForm ) ) throw( (sInt32)eDSInvalidBuffFormat );
					siResult = AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)inData->fInAuthStepData->fBufferData,
													&authRef);
					if (siResult != errAuthorizationSuccess)
					{
						throw( (sInt32)eDSPermissionError );
					}

					AuthorizationItem rights[] = { {"system.preferences", 0, 0, 0} };
					AuthorizationItemSet rightSet = { sizeof(rights)/ sizeof(*rights), rights };

					siResult = AuthorizationCopyRights(authRef, &rightSet, NULL,
										kAuthorizationFlagExtendRights, &resultRightSet);
					if (resultRightSet != NULL)
					{
						AuthorizationFreeItemSet(resultRightSet);
						resultRightSet = NULL;
					}
					if (siResult != errAuthorizationSuccess)
					{
						throw( (sInt32)eDSPermissionError );
					}
					if (inData->fInDirNodeAuthOnlyFlag == false)
					{
						if (pContext->fAuthenticatedUserName != NULL)
						{
							free(pContext->fAuthenticatedUserName);
						}
						pContext->fAuthenticatedUserName = strdup("root");
					}
					AuthorizationFree( authRef, 0 ); // really should hang onto this instead
					siResult = eDSNoErr;
				}
				else //everything but AuthRef method
				{
					if ( uiAuthMethod != kAuth2WayRandom )
					{
						siResult = GetUserNameFromAuthBuffer( inData->fInAuthStepData, 1, &userName );
						if ( siResult != eDSNoErr ) throw( siResult );
					}
					else
					{
						// for 2way random the first buffer is the username
						if ( inData->fInAuthStepData->fBufferLength > inData->fInAuthStepData->fBufferSize ) throw( (sInt32)eDSInvalidBuffFormat );
						userName = (char*)calloc( inData->fInAuthStepData->fBufferLength + 1, 1 );
						strncpy( userName, inData->fInAuthStepData->fBufferData, inData->fInAuthStepData->fBufferLength );
					}
					//printf("username: %s\n", userName);
					// get the auth authority
					siResult = IsValidRecordName ( userName, pNIRecType, pContext->fDomain, niDirID );
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed ); // unknown user really
					gNetInfoMutex->Wait();
					//lookup GUID for ShadowHash verification but don't worry if not found
					niResultGUID = ni_lookupprop( pContext->fDomain, &niDirID, 
											"generateduid", &niValuesGUID );
					//lookup authauthority attribute
					niResult = ni_lookupprop( pContext->fDomain, &niDirID, 
											"authentication_authority", &niValues );
					gNetInfoMutex->Signal();
					if ( (niResult == NI_OK) && (niValues.ni_namelist_len > 0) )
					{
						// loop through all possibilities for set
						// do first auth authority that supports the method for check password
						unsigned int i = 0;
						bool bLoopAll = IsWriteAuthRequest(uiAuthMethod);
						bool bIsSecondary = false;
						char* aaVersion = NULL;
						char* aaTag = NULL;
						char* aaData = NULL;
						char* GUIDString = NULL;
						
						
						if ( (niResultGUID == NI_OK) && (niValuesGUID.ni_namelist_len > 0) )
						{
							if (niValuesGUID.ni_namelist_val[0] != nil)
							{
								GUIDString = strdup(niValuesGUID.ni_namelist_val[0]);
							}
							gNetInfoMutex->Wait();
							ni_namelist_free(&niValuesGUID);
							gNetInfoMutex->Signal();
						}
						siResult = eDSAuthMethodNotSupported;
						while ( i < niValues.ni_namelist_len 
								&& (siResult == eDSAuthMethodNotSupported ||
									(bLoopAll && siResult == eDSNoErr)))
						{
							//parse this value of auth authority
							siResult = ParseAuthAuthority( niValues.ni_namelist_val[i], &aaVersion, 
														&aaTag, &aaData );
							// JT need to check version
							if (siResult != eDSNoErr)
							{
								siResult = eDSAuthFailed;
								//KW do we want to bail if one of the writes failed?
								//could end up with mismatched passwords/hashes regardless of how we handle this
								//note "continue" here instead of "break" would have same effect because of check for siResult in "while" above
								break;
							}
							handlerProc = GetNetInfoAuthAuthorityHandler( aaTag );
							if (handlerProc != NULL)
							{
								siResult = (handlerProc)(inData->fInNodeRef, inData->fInAuthMethod, pContext, 
														&pContinueData, 
														inData->fInAuthStepData, 
														inData->fOutAuthStepDataResponse,
														inData->fInDirNodeAuthOnlyFlag,
														bIsSecondary, aaData, GUIDString, pNIRecType);
								if (siResult == eDSNoErr)
								{
									if (pContinueData != NULL)
									{
										// we are supposed to return continue data
										// remember the proc we used
										pContinueData->fAuthHandlerProc = (void*)handlerProc;
										pContinueData->fAuthAuthorityData = aaData;
										aaData = NULL;
										break;
									}
									else
									{
										bIsSecondary = true;
									}
								}
								
							} // if (handlerProc != NULL)
							else
							{
								siResult = eDSAuthMethodNotSupported;
							}
							if (aaVersion != NULL) {
								free(aaVersion);
								aaVersion = NULL;
							}
							if (aaTag != NULL) {
								free(aaTag);
								aaTag = NULL;
							}
							if (aaData != NULL) {
								free(aaData);
								aaData = NULL;
							}
							++i;
						} //while
						
						if ( bIsSecondary && siResult == eDSAuthMethodNotSupported )
							siResult = eDSNoErr;
						
						if (aaVersion != NULL) {
							free(aaVersion);
							aaVersion = NULL;
						}
						if (aaTag != NULL) {
							free(aaTag);
							aaTag = NULL;
						}
						if (aaData != NULL) {
							free(aaData);
							aaData = NULL;
						}
						if (GUIDString != NULL) {
							free(GUIDString);
							GUIDString = NULL;
						}
						gNetInfoMutex->Wait();
						ni_namelist_free(&niValues);
						gNetInfoMutex->Signal();
					}
					else
					{
						//revert to basic
						siResult = DoBasicAuth(inData->fInNodeRef,inData->fInAuthMethod, pContext, 
											&pContinueData, inData->fInAuthStepData,
											inData->fOutAuthStepDataResponse,
											inData->fInDirNodeAuthOnlyFlag, false, NULL, NULL, pNIRecType);
						if (pContinueData != NULL && siResult == eDSNoErr)
						{
							// we are supposed to return continue data
							// remember the proc we used
							pContinueData->fAuthHandlerProc = (void*)CNiPlugIn::DoBasicAuth;
						}
					}
				} // //everything but AuthRef method
			}
		} //( inData->fIOContinueData == NULL )
	}

	catch( sInt32 err )
	{
		siResult = err;
	}
	
	if (userName != NULL)
	{
		free(userName);
		userName = NULL;
	}

	inData->fResult = siResult;
	inData->fIOContinueData = pContinueData;

	return( siResult );

} // DoAuthentication

//------------------------------------------------------------------------------------
//	* DoAuthenticationOnRecordType
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoAuthenticationOnRecordType ( sDoDirNodeAuthOnRecordType *inData )
{
	sInt32				siResult		= eDSAuthFailed;
	ni_status			niResult		= NI_OK;
	ni_status			niResultGUID	= NI_OK;
	uInt32				uiAuthMethod	= 0;
	sNIContextData	   *pContext		= nil;
	sNIContinueData	   *pContinueData	= NULL;
	char*				userName		= NULL;
	ni_id				niDirID;
	ni_namelist			niValues;
	ni_namelist			niValuesGUID;
	NetInfoAuthAuthorityHandlerProc handlerProc = NULL;
	char			   *pNIRecType		= nil;
	
	try
	{
		pContext = (sNIContextData *)gNINodeRef->GetItemData( inData->fInNodeRef );
		if ( pContext == nil ) throw( (sInt32)eDSInvalidNodeRef );
		if ( inData->fInAuthStepData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( inData->fInRecordType == nil ) throw( (sInt32)eDSNullRecType );
		if ( inData->fInRecordType->fBufferData == nil ) throw( (sInt32)eDSNullRecType );
		pNIRecType = MapRecToNetInfoType( inData->fInRecordType->fBufferData );
		if (pNIRecType == nil) throw( (sInt32)eDSAuthFailed );
		
		if ( inData->fIOContinueData != NULL )
		{
			// get info from continue
			pContinueData = (sNIContinueData *)inData->fIOContinueData;
			if ( gNIContinue->VerifyItem( pContinueData ) == false )
			{
				throw( (sInt32)eDSInvalidContinueData );
			}
			
			if ( pContinueData->fAuthHandlerProc == nil ) throw( (sInt32)eDSInvalidContinueData );
			handlerProc = (NetInfoAuthAuthorityHandlerProc)(pContinueData->fAuthHandlerProc);
			siResult = (handlerProc)(	inData->fInNodeRef, inData->fInAuthMethod, pContext,
										&pContinueData, inData->fInAuthStepData, 
										inData->fOutAuthStepDataResponse, 
										inData->fInDirNodeAuthOnlyFlag, false,
										pContinueData->fAuthAuthorityData, NULL, (const char *)pNIRecType);
		}
		else
		{
			// first call
			// we do not want to fail if the method is unknown at this point.
			// For password server users, the PSPlugin may know the method.
			siResult = GetAuthMethod( inData->fInAuthMethod, &uiAuthMethod );
			
			// unsupported auth methods are allowed if the user is on password server
			// otherwise, unsupported auth methods are rejected in their handlers
			if ( siResult == eDSNoErr || siResult == eDSAuthMethodNotSupported )
			{
				if ( uiAuthMethod == kAuthWithAuthorizationRef )
				{
					AuthorizationRef authRef = 0;
					AuthorizationItemSet* resultRightSet = NULL;
					if ( inData->fInAuthStepData->fBufferLength < sizeof( AuthorizationExternalForm ) ) throw( (sInt32)eDSInvalidBuffFormat );
					siResult = AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)inData->fInAuthStepData->fBufferData,
													&authRef);
					if (siResult != errAuthorizationSuccess)
					{
						throw( (sInt32)eDSPermissionError );
					}

					AuthorizationItem rights[] = { {"system.preferences", 0, 0, 0} };
					AuthorizationItemSet rightSet = { sizeof(rights)/ sizeof(*rights), rights };

					siResult = AuthorizationCopyRights(authRef, &rightSet, NULL,
										kAuthorizationFlagExtendRights, &resultRightSet);
					if (resultRightSet != NULL)
					{
						AuthorizationFreeItemSet(resultRightSet);
						resultRightSet = NULL;
					}
					if (siResult != errAuthorizationSuccess)
					{
						throw( (sInt32)eDSPermissionError );
					}
					if (inData->fInDirNodeAuthOnlyFlag == false)
					{
						if (pContext->fAuthenticatedUserName != NULL)
						{
							free(pContext->fAuthenticatedUserName);
						}
						pContext->fAuthenticatedUserName = strdup("root");
					}
					AuthorizationFree( authRef, 0 ); // really should hang onto this instead
					siResult = eDSNoErr;
				}
				else //everything but AuthRef method
				{
					if ( uiAuthMethod != kAuth2WayRandom )
					{
						siResult = GetUserNameFromAuthBuffer( inData->fInAuthStepData, 1, &userName );
						if ( siResult != eDSNoErr ) throw( siResult );
					}
					else
					{
						// for 2way random the first buffer is the username
						if ( inData->fInAuthStepData->fBufferLength > inData->fInAuthStepData->fBufferSize ) throw( (sInt32)eDSInvalidBuffFormat );
						userName = (char*)calloc( inData->fInAuthStepData->fBufferLength + 1, 1 );
						strncpy( userName, inData->fInAuthStepData->fBufferData, inData->fInAuthStepData->fBufferLength );
					}
					
					// get the auth authority
					siResult = IsValidRecordName( userName, (const char *)pNIRecType, pContext->fDomain, niDirID );
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed ); // unknown user really
					
					gNetInfoMutex->Wait();
					//lookup GUID for ShadowHash verification but don't worry if not found
					niResultGUID = ni_lookupprop( pContext->fDomain, &niDirID, 
											"generateduid", &niValuesGUID );
					//lookup authauthority attribute
					niResult = ni_lookupprop( pContext->fDomain, &niDirID, 
											"authentication_authority", &niValues );
					gNetInfoMutex->Signal();
					if ( (niResult == NI_OK) && (niValues.ni_namelist_len > 0) )
					{
						// loop through all possibilities for set
						// do first auth authority that supports the method for check password
						unsigned int i = 0;
						bool bLoopAll = IsWriteAuthRequest(uiAuthMethod);
						bool bIsSecondary = false;
						char* aaVersion = NULL;
						char* aaTag = NULL;
						char* aaData = NULL;
						char* GUIDString = NULL;
						if ( (niResultGUID == NI_OK) && (niValuesGUID.ni_namelist_len > 0) )
						{
							if (niValuesGUID.ni_namelist_val[0] != nil)
							{
								GUIDString = strdup(niValuesGUID.ni_namelist_val[0]);
							}
							gNetInfoMutex->Wait();
							ni_namelist_free(&niValuesGUID);
							gNetInfoMutex->Signal();
						}
						siResult = eDSAuthMethodNotSupported;
						while ( i < niValues.ni_namelist_len 
								&& (siResult == eDSAuthMethodNotSupported ||
									(bLoopAll && siResult == eDSNoErr)))
						{
							//parse this value of auth authority
							siResult = ParseAuthAuthority( niValues.ni_namelist_val[i], &aaVersion, 
														&aaTag, &aaData );
							// JT need to check version
							if (siResult != eDSNoErr)
							{
								siResult = eDSAuthFailed;
								//KW do we want to bail if one of the writes failed?
								//could end up with mismatched passwords/hashes regardless of how we handle this
								//note "continue" here instead of "break" would have same effect because of check for siResult in "while" above
								break;
							}
							handlerProc = GetNetInfoAuthAuthorityHandler( aaTag );
							if (handlerProc != NULL)
							{
								siResult = (handlerProc)(inData->fInNodeRef, inData->fInAuthMethod, pContext, 
														&pContinueData, 
														inData->fInAuthStepData, 
														inData->fOutAuthStepDataResponse,
														inData->fInDirNodeAuthOnlyFlag,
														bIsSecondary, aaData, GUIDString, (const char *)pNIRecType);
								if (siResult == eDSNoErr)
								{
									if (pContinueData != NULL)
									{
										// we are supposed to return continue data
										// remember the proc we used
										pContinueData->fAuthHandlerProc = (void*)handlerProc;
										pContinueData->fAuthAuthorityData = aaData;
										aaData = NULL;
										break;
									}
									else
									{
										bIsSecondary = true;
									}
								}
								
							} // if (handlerProc != NULL)
							else
							{
								siResult = eDSAuthMethodNotSupported;
							}
							if (aaVersion != NULL) {
								free(aaVersion);
								aaVersion = NULL;
							}
							if (aaTag != NULL) {
								free(aaTag);
								aaTag = NULL;
							}
							if (aaData != NULL) {
								free(aaData);
								aaData = NULL;
							}
							++i;
						} //while
						
						if (aaVersion != NULL) {
							free(aaVersion);
							aaVersion = NULL;
						}
						if (aaTag != NULL) {
							free(aaTag);
							aaTag = NULL;
						}
						if (aaData != NULL) {
							free(aaData);
							aaData = NULL;
						}
						if (GUIDString != NULL) {
							free(GUIDString);
							GUIDString = NULL;
						}
						gNetInfoMutex->Wait();
						ni_namelist_free(&niValues);
						gNetInfoMutex->Signal();
					}
					else
					{
						//revert to basic
						siResult = DoBasicAuth(inData->fInNodeRef,inData->fInAuthMethod, pContext, 
											&pContinueData, inData->fInAuthStepData,
											inData->fOutAuthStepDataResponse,
											inData->fInDirNodeAuthOnlyFlag, false, NULL, NULL, (const char *)pNIRecType);
						if (pContinueData != NULL && siResult == eDSNoErr)
						{
							// we are supposed to return continue data
							// remember the proc we used
							pContinueData->fAuthHandlerProc = (void*)CNiPlugIn::DoBasicAuth;
						}
					}
				} // //everything but AuthRef method
			}
		} //( inData->fIOContinueData == NULL )
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if (pNIRecType != NULL)
	{
		free(pNIRecType);
		pNIRecType = nil;
	}

	if (userName != NULL)
	{
		free(userName);
		userName = NULL;
	}

	inData->fResult = siResult;
	inData->fIOContinueData = pContinueData;

	return( siResult );

} // DoAuthenticationOnRecordType

//------------------------------------------------------------------------------------
//	* IsWriteAuthRequest
//------------------------------------------------------------------------------------

bool CNiPlugIn::IsWriteAuthRequest ( uInt32 uiAuthMethod )
{
	switch ( uiAuthMethod )
	{
		case kAuthSetPasswd:
		case kAuthSetPasswdAsRoot:
		case kAuthChangePasswd:
			return true;
			
		default:
			return false;
	}
} // IsWriteAuthRequest


//------------------------------------------------------------------------------------
//	* DoBasicAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoBasicAuth ( tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
								sNIContextData* inContext, 
								sNIContinueData** inOutContinueData, 
								tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
								bool inAuthOnly, bool isSecondary, char* inAuthAuthorityData,
								char* inGUIDString, const char* inNativeRecType )
{
	sInt32				siResult		= eDSNoErr;
	uInt32				uiAuthMethod	= 0;

	try
	{
		if ( inContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		DBGLOG( kLogPlugin, "DoBasicAuth::" );
		siResult = GetAuthMethod( inAuthMethod, &uiAuthMethod );
		if ( siResult == eDSNoErr )
		{
			switch( uiAuthMethod )
			{
				case kAuthSetPolicy:
					siResult = eDSAuthMethodNotSupported;
					break;
				
				//native auth is always UNIX crypt possibly followed by 2-way random using tim auth server
				case kAuthNativeMethod:
				case kAuthNativeNoClearText:
				case kAuthNativeClearTextOK:
					if ( outAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
					siResult = DoUnixCryptAuth( inContext, inAuthData, inAuthOnly, inNativeRecType );
					if ( siResult == eDSNoErr )
					{
						if ( outAuthData->fBufferSize > ::strlen( kDSStdAuthCrypt ) )
						{
							::strcpy( outAuthData->fBufferData, kDSStdAuthCrypt );
						}
					}
					else if ( (siResult != eDSAuthFailed) && (siResult != eDSInvalidBuffFormat) )
					{
						siResult = DoNodeNativeAuth( inContext, inAuthData, inAuthOnly, inNativeRecType );
						if ( siResult == eDSNoErr )
						{
							if ( outAuthData->fBufferSize > ::strlen( kDSStdAuth2WayRandom ) )
							{
								::strcpy( outAuthData->fBufferData, kDSStdAuth2WayRandom );
							}
						}
					}
					break;

				case kAuthClearText:
				case kAuthCrypt:
					siResult = DoUnixCryptAuth( inContext, inAuthData, inAuthOnly, inNativeRecType );
					break;

				case kAuthSetPasswd:
					siResult = DoSetPassword( inContext, inAuthData, inNativeRecType );
					break;

				case kAuthSetPasswdAsRoot:
					siResult = DoSetPasswordAsRoot( inContext, inAuthData, inNativeRecType );
					break;

				case kAuthChangePasswd:
					siResult = DoChangePassword( inContext, inAuthData, isSecondary, inNativeRecType );
					break;

#ifdef TIM_CLIENT_PRESENT
				case kAuthSMB_NT_Key:
				case kAuthSMB_LM_Key:
					siResult = DoTimSMBAuth( inContext, inAuthData, uiAuthMethod );
					break;

				case kAuth2WayRandom:
					siResult = DoTimMultiPassAuth( inNodeRef, inAuthMethod, inContext, 
												   inOutContinueData, inAuthData, 
												   outAuthData, inAuthOnly );
					break;

				case kAuthAPOP:
				case kAuthCRAM_MD5:
					siResult = ValidateDigest( inContext, inAuthData, uiAuthMethod );
					break;
#endif

				default:
					siResult = eDSAuthFailed;
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	return( siResult );

} // DoBasicAuth


//------------------------------------------------------------------------------------
//	* DoShadowHashAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoShadowHashAuth (	tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
										sNIContextData* inContext, 
										sNIContinueData** inOutContinueData, 
										tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
										bool inAuthOnly, bool isSecondary,
										char* inAuthAuthorityData, char* inGUIDString,
										const char* inNativeRecType )
{
	sInt32				siResult								= eDSAuthFailed;
	uInt32				uiAuthMethod							= 0;
	char			   *pUserName								= nil;
	char			   *pNewPassword							= nil;
	char			   *pOldPassword							= nil;
	char			   *pAdminUser								= nil;
	char			   *pAdminPassword							= nil;
	unsigned char		P21[kHashShadowKeyLength]				= {0};
	tDataNodePtr		pC8Node									= NULL;
	unsigned char 		C8[kHashShadowChallengeLength] 			= {0};
	unsigned char		P24[kHashShadowResponseLength] 			= {0};
	unsigned char		P24Input[kHashShadowResponseLength]		= {0};
	tDataNodePtr		pP24InputNode							= NULL;
	tDataListPtr		dataList								= NULL;
	char			   *path									= NULL;
	unsigned char		hashes[kHashTotalLength]				= {0};
	unsigned char		generatedHashes[kHashTotalLength]		= {0};
	bool				bUseBothHashes							= true; //default to use both hashes
	uInt32				hashLength								= kHashTotalLength;
	tDataNodePtr		secureHashNode							= NULL;
	unsigned char 		secureHash[kHashSecureLength] 			= {0};
	unsigned char 		secureNULLHash[kHashSecureLength] 		= {0};
	unsigned char 		shadowNULLHash[kHashShadowBothLength] 	= {0};
	unsigned char		digestData[kHashSecureLength]			= {0};
	SHA_CTX				sha_context								= {};
	SHA_CTX				sha_context2							= {};
					

	// kHashShadowBothLength bytes for NT hash and kHashShadowBothLength for LAN Manager hash, both hex encoded
	// 40 bytes for secure hash, hex encoded

	try
	{
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( inContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		DBGLOG( kLogPlugin, "DoShadowHashAuth::" );
		siResult = GetAuthMethod( inAuthMethod, &uiAuthMethod );
		if ( siResult == eDSNoErr )
		{
			//set up flag if we want only better hash kDSTagAuthAuthorityBetterHashOnly
			if (inAuthAuthorityData != nil)
			{
				if (strcasecmp(inAuthAuthorityData,kDSTagAuthAuthorityBetterHashOnly) == 0)
				{
					bUseBothHashes = false;
				}
			}
			
			// Parse input buffer, read hash(es)
			switch( uiAuthMethod )
			{
				case kAuthSetPolicy:
					siResult = eDSAuthMethodNotSupported;
					break;
				
				case kAuthSMB_NT_Key:
				case kAuthSMB_LM_Key:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 3 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
					// these are not copies
					siResult = dsDataListGetNodePriv(dataList, 2, &pC8Node);
					if ( pC8Node == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( pC8Node->fBufferLength != kHashShadowChallengeLength ) throw( (sInt32)eDSInvalidBuffFormat);
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidBuffFormat );
					memmove(C8, ((tDataBufferPriv*)pC8Node)->fBufferData, kHashShadowChallengeLength);
					
					siResult = dsDataListGetNodePriv(dataList, 3, &pP24InputNode);
					if ( pP24InputNode == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( pP24InputNode->fBufferLength != kHashShadowResponseLength ) throw( (sInt32)eDSInvalidBuffFormat);
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidBuffFormat );
					memmove(P24Input, ((tDataBufferPriv*)pP24InputNode)->fBufferData, kHashShadowResponseLength);
					
					//read file
					siResult = ReadShadowHash(pUserName, inGUIDString, hashes);
					break;

				case kAuthSecureHash:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 2 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
					// these are not copies
					siResult = dsDataListGetNodePriv(dataList, 2, &secureHashNode);
					if ( secureHashNode == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( secureHashNode->fBufferLength != kHashSecureLength ) throw( (sInt32)eDSInvalidBuffFormat);
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidBuffFormat );
					memmove(secureHash, ((tDataBufferPriv*)secureHashNode)->fBufferData, kHashSecureLength);
					
					//read file
					siResult = ReadShadowHash(pUserName, inGUIDString, hashes);
					break;

				case kAuthWriteSecureHash:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 2 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
					// these are not copies
					siResult = dsDataListGetNodePriv(dataList, 2, &secureHashNode);
					if ( secureHashNode == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( secureHashNode->fBufferLength != kHashSecureLength ) throw( (sInt32)eDSInvalidBuffFormat);
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSInvalidBuffFormat );
					memmove(secureHash, ((tDataBufferPriv*)secureHashNode)->fBufferData, kHashSecureLength);
					
					break;

				case kAuthReadSecureHash:
					if ( outAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 1 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
					
					break;

				// set password operations
				case kAuthSetPasswd:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 4 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
					
					// this allocates a copy of the string
					pNewPassword = dsDataListGetNodeStringPriv(dataList, 2);
					if ( pNewPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					// this allocates a copy of the string
					pAdminUser = dsDataListGetNodeStringPriv(dataList, 3);
					if ( pAdminUser == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					// this allocates a copy of the string
					pAdminPassword = dsDataListGetNodeStringPriv(dataList, 4);
					if ( pAdminPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					//read file
					siResult = ReadShadowHash(pAdminUser, inGUIDString, hashes);
					break;

				case kAuthSetPasswdAsRoot:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 2 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );

					// this allocates a copy of the string
					pNewPassword = dsDataListGetNodeStringPriv(dataList, 2);
					if ( pNewPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					break;

				case kAuthChangePasswd:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 3 ) throw( (sInt32)eDSInvalidBuffFormat );
						
					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );

					// this allocates a copy of the string
					pOldPassword = dsDataListGetNodeStringPriv(dataList, 2);
					if ( pOldPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					// this allocates a copy of the string
					pNewPassword = dsDataListGetNodeStringPriv(dataList, 3);
					if ( pNewPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					//read file
					siResult = ReadShadowHash(pUserName, inGUIDString, hashes);
					break;

				case kAuthNativeClearTextOK:
				case kAuthNativeNoClearText:
					// parse input first
					dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
					if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( dsDataListGetNodeCountPriv(dataList) != 2 ) throw( (sInt32)eDSInvalidBuffFormat );

					// this allocates a copy of the string
					pUserName = dsDataListGetNodeStringPriv(dataList, 1);
					if ( pUserName == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					if ( strlen(pUserName) < 1 ) throw( (sInt32)eDSInvalidBuffFormat );

					// this allocates a copy of the string
					pOldPassword = dsDataListGetNodeStringPriv(dataList, 2);
					if ( pOldPassword == nil ) throw( (sInt32)eDSInvalidBuffFormat );
					
					//read file
					siResult = ReadShadowHash(pUserName, inGUIDString, hashes);
					break;

				default:
					break;
			}
			if ((siResult != eDSNoErr) && !(isSecondary && (uiAuthMethod == kAuthChangePasswd)))
			{
				throw( siResult );
			}
			
			// complete the operation
			switch( uiAuthMethod )
			{
				case kAuthSMB_NT_Key:
					memmove(P21, hashes, kHashShadowOneLength);
					CalculateP24(P21, C8, P24);
					if (memcmp(P24,P24Input,kHashShadowResponseLength) == 0)
					{
						siResult = eDSNoErr;
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				case kAuthSMB_LM_Key:
					memmove(P21, hashes+kHashShadowOneLength, kHashShadowOneLength);
					CalculateP24(P21, C8, P24);
					if (memcmp(P24,P24Input,kHashShadowResponseLength) == 0)
					{
						siResult = eDSNoErr;
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				case kAuthSecureHash:
					if (memcmp(secureHash,hashes+kHashShadowBothLength,kHashSecureLength) == 0)
					{
						siResult = eDSNoErr;
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				case kAuthWriteSecureHash:
					// allow root to directly overwrite the secure hash
					if ( inContext->fEffectiveUID != 0 )
					{
						throw( (sInt32)eDSPermissionError );
					}
					//write this secure hash with the other hashes as empty
					//we will reconstruct the other hashes upon first successful auth
					memmove(generatedHashes+kHashShadowBothLength, secureHash, kHashSecureLength);

					siResult = WriteShadowHash(pUserName, inGUIDString, generatedHashes);
					break;

				case kAuthReadSecureHash:
					// allow root to directly read the secure hash
					if ( inContext->fEffectiveUID != 0 )
					{
						throw( (sInt32)eDSPermissionError );
					}
					//read file
					siResult = ReadShadowHash(pUserName, inGUIDString, hashes);
					if (siResult == eDSNoErr)
					{
						if ( outAuthData->fBufferSize >= kHashSecureLength )
						{
							memmove( outAuthData->fBufferData, hashes+kHashShadowBothLength, kHashSecureLength );
							outAuthData->fBufferLength = kHashSecureLength;
						}
						else
						{
							throw( (sInt32)eDSInvalidBuffFormat );
						}
					}
					break;

				case kAuthNativeClearTextOK:
				case kAuthNativeNoClearText:
					CalculateSMBNTHash(pOldPassword,generatedHashes);
					hashLength = kHashShadowOneLength;
					if (bUseBothHashes)
					{
						CalculateSMBLANManagerHash(pOldPassword,generatedHashes+kHashShadowOneLength);
						hashLength = kHashShadowBothLength;
					}
	
					/* calculate digest */
					SHA1_Init(&sha_context);
					SHA1_Update(&sha_context, (unsigned char *)pOldPassword, strlen(pOldPassword));
					SHA1_Final(digestData, &sha_context);
					memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
					hashLength = kHashTotalLength;
					
					if (	(memcmp(hashes,generatedHashes,hashLength) == 0) ||
							( (memcmp(hashes,generatedHashes,kHashShadowBothLength) == 0) &&
							(memcmp(hashes+kHashShadowBothLength,secureNULLHash,kHashSecureLength) == 0) ) ||
							( (memcmp(hashes+kHashShadowBothLength,generatedHashes+kHashShadowBothLength,kHashSecureLength) == 0) &&
							(memcmp(hashes,shadowNULLHash,kHashShadowBothLength) == 0) ) )
					//if all hashes present use them
					//if secure hash missing then for backward compatibility use only the shadow hashes
					//if shadow hashes missing ie. PUA then only use secure hash
					{
						if ( ( (memcmp(hashes,generatedHashes,kHashShadowBothLength) == 0) &&
							(memcmp(hashes+kHashShadowBothLength,secureNULLHash,kHashSecureLength) == 0) ) ||
							( (memcmp(hashes+kHashShadowBothLength,generatedHashes+kHashShadowBothLength,kHashSecureLength) == 0) &&
							(memcmp(hashes,shadowNULLHash,kHashShadowBothLength) == 0) ) )
						{
							//sync up the hashes ie. shadow and secure
							//go ahead and write the shadow hashes since we have a secure hash already 
							//and we know the secure hash was correct OR
							//write the secure hash since we know we already have the shadow hashes
							siResult = eDSNoErr;
							siResult = WriteShadowHash(pUserName, inGUIDString, generatedHashes);
						}

						if (inAuthOnly == false)
						{
							siResult = AuthOpen( inContext, pUserName, pOldPassword );
						}
						else
						{
							siResult = eDSNoErr;
						}
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				// set password operations
				case kAuthSetPasswd:
					// check admin user password then change password
					CalculateSMBNTHash(pAdminPassword,generatedHashes);
					hashLength = kHashShadowOneLength;
					if (bUseBothHashes)
					{
						CalculateSMBLANManagerHash(pAdminPassword,generatedHashes+kHashShadowOneLength);
						hashLength = kHashShadowBothLength;
					}

					/* calculate digest */
					SHA1_Init(&sha_context);
					SHA1_Update(&sha_context, (unsigned char *)pAdminPassword, strlen(pAdminPassword));
					SHA1_Final(digestData, &sha_context);
					memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
					hashLength = kHashTotalLength;
					
					if (	(memcmp(hashes,generatedHashes,hashLength) == 0) ||
							( (memcmp(hashes,generatedHashes,kHashShadowBothLength) == 0) &&
							(memcmp(hashes+kHashShadowBothLength,secureNULLHash,kHashSecureLength) == 0) ) ||
							( (memcmp(hashes+kHashShadowBothLength,generatedHashes+kHashShadowBothLength,kHashSecureLength) == 0) &&
							(memcmp(hashes,shadowNULLHash,kHashShadowBothLength) == 0) ) )
					//if all hashes present use them
					//if secure hash missing then for backward compatibility use only the shadow hashes
					//if shadow hashes missing ie. PUA then only use secure hash
					{
						if (UserIsAdmin(pAdminUser,inContext->fDomain))
						{
							siResult = eDSNoErr;
							bzero(generatedHashes,kHashTotalLength);
							CalculateSMBNTHash(pNewPassword,generatedHashes);
							if (bUseBothHashes)
							{
								CalculateSMBLANManagerHash(pNewPassword,generatedHashes+kHashShadowOneLength);
							}
							bzero(digestData, kHashSecureLength);

							/* calculate digest */
							SHA1_Init(&sha_context2);
							SHA1_Update(&sha_context2, (unsigned char *)pNewPassword, strlen(pNewPassword));
							SHA1_Final(digestData, &sha_context2);
							memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
							hashLength = kHashTotalLength;
					
							siResult = WriteShadowHash(pUserName, inGUIDString, generatedHashes);
						}
						else
						{
							siResult = eDSPermissionError;
						}
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				case kAuthSetPasswdAsRoot:
					// allow root to change anyone's password and
					// others to change their own password
					if ( inContext->fEffectiveUID != 0 )
					{
						if ((inContext->fAuthenticatedUserName == NULL)
							|| ((::strcmp(inContext->fAuthenticatedUserName, "root") != 0)
								&& (::strcmp(inContext->fAuthenticatedUserName, pUserName) != 0)))
						{
							throw( (sInt32)eDSPermissionError );
						}
					}
					// write new password
					CalculateSMBNTHash(pNewPassword,generatedHashes);
					if (bUseBothHashes)
					{
						CalculateSMBLANManagerHash(pNewPassword,generatedHashes+kHashShadowOneLength);
					}

					/* calculate digest */
					SHA1_Init(&sha_context);
					SHA1_Update(&sha_context, (unsigned char *)pNewPassword, strlen(pNewPassword));
					SHA1_Final(digestData, &sha_context);
					memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
					hashLength = kHashTotalLength;
					
					siResult = WriteShadowHash(pUserName, inGUIDString, generatedHashes);
					break;

				case kAuthChangePasswd:
					// check old password then write new one
					CalculateSMBNTHash(pOldPassword,generatedHashes);
					hashLength = kHashShadowOneLength;
					if (bUseBothHashes)
					{
						CalculateSMBLANManagerHash(pOldPassword,generatedHashes+kHashShadowOneLength);
						hashLength = kHashShadowBothLength;
					}

					/* calculate digest */
					SHA1_Init(&sha_context);
					SHA1_Update(&sha_context, (unsigned char *)pOldPassword, strlen(pOldPassword));
					SHA1_Final(digestData, &sha_context);
					memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
					hashLength = kHashTotalLength;
					
					if (	(memcmp(hashes,generatedHashes,hashLength) == 0) ||
							( (memcmp(hashes,generatedHashes,kHashShadowBothLength) == 0) &&
							(memcmp(hashes+kHashShadowBothLength,secureNULLHash,kHashSecureLength) == 0) ) ||
							( (memcmp(hashes+kHashShadowBothLength,generatedHashes+kHashShadowBothLength,kHashSecureLength) == 0) &&
							(memcmp(hashes,shadowNULLHash,kHashShadowBothLength) == 0) ) ||
							isSecondary )
					//if all hashes present use them
					//if secure hash missing then for backward compatibility use only the shadow hashes
					//if shadow hashes missing ie. PUA then only use secure hash
					// normally require the old password to match
					// but if another auth authority already successfully changed
					// assume we have an out of sync crypt vs. SMB password
					// and allow the change
					{
						// set new password
						bzero(generatedHashes,kHashTotalLength);
						CalculateSMBNTHash(pNewPassword,generatedHashes);
						if (bUseBothHashes)
						{
							CalculateSMBLANManagerHash(pNewPassword,generatedHashes+kHashShadowOneLength);
						}
						bzero(digestData, kHashSecureLength);

						/* calculate digest */
						SHA1_Init(&sha_context2);
						SHA1_Update(&sha_context2, (unsigned char *)pNewPassword, strlen(pNewPassword));
						SHA1_Final(digestData, &sha_context2);
						memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
						hashLength = kHashTotalLength;
					
						siResult = WriteShadowHash(pUserName, inGUIDString, generatedHashes);
					}
					else
					{
						siResult = eDSAuthFailed;
					}
					break;

				default:
					siResult = eDSAuthMethodNotSupported;
					break;
			}
		}
	}
	catch( sInt32 err )
	{
		siResult = err;
	}

	if (dataList != NULL)
	{
		dsDataListDeallocatePriv(dataList);
		free(dataList);
		dataList = NULL;
	}
	
	if ( pUserName != nil )
	{
		free( pUserName );
		pUserName = nil;
	}
	
	if ( pNewPassword != nil )
	{
		free( pNewPassword );
		pNewPassword = nil;
	}
	
	if ( pOldPassword != nil )
	{
		free( pOldPassword );
		pOldPassword = nil;
	}
	
	if ( pAdminUser != nil )
	{
		free( pAdminUser );
		pAdminUser = nil;
	}
	
	if ( pAdminPassword != nil )
	{
		free( pAdminPassword );
		pAdminPassword = nil;
	}
	
	if (path != NULL)
	{
		free(path);
		path = NULL;
	}
	
	return( siResult );

} // DoShadowHashAuth


//--------------------------------------------------------------------------------------------------
// * ReadShadowHash ()
//--------------------------------------------------------------------------------------------------

sInt32 CNiPlugIn::ReadShadowHash ( const char *inUserName, char *inGUIDString, unsigned char outHashes[kHashTotalLength] )
{
	sInt32	siResult						= eDSAuthFailed;
	char   *path							= NULL;
	char	hexHashes[kHashTotalHexLength]	= { 0 };
	sInt32	readBytes						= 0;
	uInt32	outBytes						= 0;
	CFile  *hashFile						= nil;

	try
	{
		if (inGUIDString != nil)
		{
			path = (char*)::calloc(1, strlen("/var/db/shadow/hash/") + strlen(inGUIDString) + 1);
		}
		else
		{
			path = (char*)::calloc(strlen("/var/db/shadow/hash/") + strlen(inUserName) + 1, 1);
		}
		if ( path != NULL )
		{
			if (inGUIDString != nil)
			{
				sprintf(path, "%s%s", "/var/db/shadow/hash/", inGUIDString);
			}
			else
			{
				sprintf(path, "%s%s", "/var/db/shadow/hash/", inUserName);
			}

			// CFile throws, so let's catch, otherwise our logic won't work, could use stat,
			// but for consistency using try/catch
			try {
				hashFile = new CFile(path, false);
			} catch ( ... ) {
				
			}

			if (hashFile != nil && hashFile->is_open())
			{
				readBytes = hashFile->ReadBlock( hexHashes, kHashTotalHexLength );
				delete(hashFile);
				hashFile = nil;
				// should check the right number of bytes is there
				if ( readBytes < kHashShadowBothHexLength ) throw( (sInt32)eDSAuthFailed );
				HexToBinaryConversion( hexHashes, &outBytes, outHashes );
				siResult = eDSNoErr;
			}
			else //support older hash files
			{
				if (hashFile != nil)
				{
					delete(hashFile);
					hashFile = nil;
				}
				free( path );
				path = NULL;
				path = (char*)::calloc(strlen("/var/db/samba/hash/") + strlen(inUserName) + 1, 1);
				if ( path != NULL )
				{
					sprintf(path, "%s%s", "/var/db/samba/hash/", inUserName);
					
					// CFile throws so we must catch...
					try
					{
						hashFile = new CFile(path, false);
						
						if (hashFile->is_open())
						{
							//old hash file format has only kHashShadowBothHexLength bytes
							readBytes = hashFile->ReadBlock( hexHashes, kHashShadowBothHexLength );
							delete(hashFile);
							hashFile = nil;
							// should check the right number of bytes is there
							if ( readBytes != kHashShadowBothHexLength ) throw( (sInt32)eDSAuthFailed );
							HexToBinaryConversion( hexHashes, &outBytes, outHashes );
							siResult = eDSNoErr;
						}
					}
					catch( ... )
					{
						
					}
					free(path);
					path = nil;
				}
			}
		}
	}
	catch( ... )
    {
        siResult = eDSAuthFailed;
    }
	if ( path != NULL )
	{
		free( path );
		path = NULL;
	}
	if (hashFile != nil)
	{
		delete(hashFile);
		hashFile = nil;
	}
	return( siResult );
} // ReadShadowHash


//--------------------------------------------------------------------------------------------------
// * WriteShadowHash ()
//--------------------------------------------------------------------------------------------------

sInt32 CNiPlugIn::WriteShadowHash ( const char *inUserName, char *inGUIDString, unsigned char inHashes[kHashTotalLength] )
{
	sInt32		result			= eDSAuthFailed;
	char	   *path			= NULL;
	char		hexHashes[kHashTotalHexLength]	= { 0 };
	sInt32		siResult		= eDSNoErr;
	struct stat	statResult;
	CFile	  *hashFile			= nil;
	
	try
	{
		//check to remove any old hash file
		RemoveShadowHash( inUserName, nil, false );
		if (inGUIDString != nil)
		{
			path = (char*)::calloc(1, strlen("/var/db/shadow/hash/") + strlen(inGUIDString) + 1);
		}
		else
		{
			path = (char*)::calloc(strlen("/var/db/shadow/hash/") + strlen(inUserName) + 1, 1);
		}
		if ( path != NULL )
		{
			if (inGUIDString != nil)
			{
				sprintf(path, "%s%s", "/var/db/shadow/hash/", inGUIDString);
			}
			else
			{
				sprintf(path, "%s%s", "/var/db/shadow/hash/", inUserName);
			}
			
			siResult = stat( "/var/db/shadow/hash", &statResult );
			if (siResult != eDSNoErr)
			{
				siResult = ::stat( "/var/db/shadow", &statResult );
				//if first sub directory does not exist
				if (siResult != eDSNoErr)
				{
					::mkdir( "/var/db/shadow", 0700 );
					::chmod( "/var/db/shadow", 0700 );
				}
				siResult = ::stat( "/var/db/shadow/hash", &statResult );
				//if second sub directory does not exist
				if (siResult != eDSNoErr)
				{
					::mkdir( "/var/db/shadow/hash", 0700 );
					::chmod( "/var/db/shadow/hash", 0700 );
				}
			}
			
			// CFile throws, but it is okay here
			hashFile = new CFile(path, true);
	
			if (hashFile->is_open())
			{
				BinaryToHexConversion( inHashes, kHashTotalLength, hexHashes );
				hashFile->seekp( 0 ); // start at beginning
				hashFile->write( hexHashes, kHashTotalHexLength );
				chmod( path, 0600 ); //set root as rw only
				delete(hashFile);
				hashFile = nil;
				result = eDSNoErr;
			}
		}
	}
	catch( ... )
    {
        result = eDSAuthFailed;
    }
	if ( path != NULL ) {
		free( path );
		path = NULL;
	}
	if (hashFile != nil)
	{
		delete(hashFile);
		hashFile = nil;
	}
	return( result );
} // WriteShadowHash


//--------------------------------------------------------------------------------------------------
// * RemoveShadowHash ()
//--------------------------------------------------------------------------------------------------

void CNiPlugIn::RemoveShadowHash ( const char *inUserName, char *inGUIDString, bool bShadowToo )
{
	char   *path							= NULL;
	char	hexHashes[kHashTotalHexLength]	= { 0 };
	bool	bRemovePath						= false;
	CFile  *hashFile						= nil;

	try
	{
		if (bShadowToo) //if flag set remove shadow file
		{
			//accept possibility that orignal file creation used only username but now we have the GUID
			//ie. don't want to cleanup in this case since we might cleanup a file that doesn't actually
			//belong to this record
			if (inGUIDString != nil)
			{
				path = (char*)::calloc(1, strlen("/var/db/shadow/hash/") + strlen(inGUIDString) + 1);
			}
			else
			{
				path = (char*)::calloc(strlen("/var/db/shadow/hash/") + strlen(inUserName) + 1, 1);
			}
			if ( path != NULL )
			{
				if (inGUIDString != nil)
				{
					sprintf(path, "%s%s", "/var/db/shadow/hash/", inGUIDString);
				}
				else
				{
					sprintf(path, "%s%s", "/var/db/shadow/hash/", inUserName);
				}
				
				// CFile throws, so we need to catch here so we can continue
				try
				{
					hashFile = new CFile(path, false); //destructor calls close
		
					if (hashFile->is_open())
					{
						hashFile->seekp( 0 ); // start at beginning
						//overwrite with zeros
						hashFile->write( hexHashes, kHashTotalHexLength );
						delete(hashFile);
						hashFile = nil;
						bRemovePath = true;
					}
					if (bRemovePath)
					{
						//remove the file
						unlink(path);
					}
				}
				catch( ... )
				{
					
				}
				free( path );
				path = NULL;
			}
		}
		
		if (hashFile != nil)
		{
			delete(hashFile);
			hashFile = nil;
		}
		bRemovePath = false;
		//check always to remove the older file if present
		if (inUserName != nil)
		{
			path = (char*)::calloc(strlen("/var/db/samba/hash/") + strlen(inUserName) + 1, 1);
		}
		if ( path != NULL )
		{
			sprintf(path, "%s%s", "/var/db/samba/hash/", inUserName);
			
			// this throws, but is okay because we expect the throw
			hashFile = new CFile(path, false); //destructor calls close

			if (hashFile->is_open())
			{
				hashFile->seekp( 0 ); // start at beginning
				//overwrite with zeros
				hashFile->write( hexHashes, kHashShadowBothHexLength );
				delete(hashFile);
				hashFile = nil;
				bRemovePath = true;
			}
			if (bRemovePath)
			{
				//remove the file
				unlink(path);
			}
			free( path );
			path = NULL;
		}
	}
	catch( ... )
    {
    }
	if ( path != NULL ) {
		free( path );
		path = NULL;
	}
	if (hashFile != nil)
	{
		delete(hashFile);
		hashFile = nil;
	}
	return;
} // RemoveShadowHash


//
//--------------------------------------------------------------------------------------------------
// * PWOpenDirNode ()
//
//--------------------------------------------------------------------------------------------------

sInt32 CNiPlugIn::PWOpenDirNode ( tDirNodeReference fDSRef, char *inNodeName, tDirNodeReference *outNodeRef )
{
	sInt32			error		= eDSNoErr;
	sInt32			error2		= eDSNoErr;
	tDataList	   *pDataList	= nil;

	pDataList = ::dsBuildFromPathPriv( inNodeName, "/" );
    if ( pDataList != nil )
    {
        error = ::dsOpenDirNode( fDSRef, pDataList, outNodeRef );
        error2 = ::dsDataListDeallocatePriv( pDataList );
        free( pDataList );
    }

    return( error );

} // PWOpenDirNode


//--------------------------------------------------------------------------------------------------
// * PWSetReplicaData ()
//
//	Note:	inAuthorityData is the UserID + RSA_key, but the IP address should be pre-stripped by
//			the calling function.
//--------------------------------------------------------------------------------------------------

sInt32 CNiPlugIn::PWSetReplicaData( sNIContextData *inContext, const char *inAuthorityData )
{
	sInt32				error					= eDSNoErr;
	ni_status			niResult				= NI_OK;
	bool				bFoundWithHash			= false;
	long				replicaListLen			= 0;
	char				*rsaKeyPtr				= NULL;
	char				*nativeRecType			= NULL;
	char				*nativeAttrType			= NULL;
	tDataBufferPtr		replicaBuffer			= NULL;
    tDataBufferPtr		replyBuffer				= NULL;
    ni_id				niDirID;
	ni_namelist			niValues;
	char				recordName[64];
	char				hashStr[34];
	
	nativeRecType = MapRecToNetInfoType( kDSStdRecordTypeConfig );
	if ( nativeRecType == NULL )
		return eDSInvalidRecordType;
	
	try
	{
		nativeAttrType = MapAttrToNetInfoType( kDS1AttrPasswordServerList );
		if ( nativeAttrType == NULL )
			throw( (sInt32)eDSInvalidAttributeType );
		
		// get /config/passwordserver_HEXHASH
		rsaKeyPtr = strchr( inAuthorityData, ',' );
		if ( rsaKeyPtr != NULL )
		{
			MD5_CTX ctx;
			unsigned char pubKeyHash[MD5_DIGEST_LENGTH];
			
			MD5_Init( &ctx );
			rsaKeyPtr++;
			MD5_Update( &ctx, rsaKeyPtr, strlen(rsaKeyPtr) );
			MD5_Final( pubKeyHash, &ctx );
			
			BinaryToHexConversion( pubKeyHash, MD5_DIGEST_LENGTH, hashStr );
			sprintf( recordName, "passwordserver_%s", hashStr );
			
			error = IsValidRecordName( recordName, nativeRecType, inContext->fDomain, niDirID );
			if ( error == eDSNoErr )
				bFoundWithHash = true;
		}
		
		if ( ! bFoundWithHash )
		{
			error = IsValidRecordName( "passwordserver", nativeRecType, inContext->fDomain, niDirID );
			if ( error != eDSNoErr ) 
				throw( error );
		}
		
		gNetInfoMutex->Wait();
		//lookup authauthority attribute
		niResult = ni_lookupprop( inContext->fDomain, &niDirID, nativeAttrType, &niValues );
		gNetInfoMutex->Signal();
		if ( niResult != NI_OK ) throw( (sInt32)eDSAuthFailed );
		
		if ( niValues.ni_namelist_len >= 1 )
		{
			replicaListLen = strlen( niValues.ni_namelist_val[0] );
			replicaBuffer = ::dsDataBufferAllocatePriv( replicaListLen + 1 );
			if ( replicaBuffer == nil ) throw( (sInt32)eMemoryError );
			
			replyBuffer = ::dsDataBufferAllocatePriv( 1 );
			if ( replyBuffer == nil ) throw( (sInt32)eMemoryError );
			
			replicaBuffer->fBufferLength = replicaListLen;
			memcpy( replicaBuffer->fBufferData, niValues.ni_namelist_val[0], replicaListLen );
			
			error = dsDoPlugInCustomCall( inContext->fPWSNodeRef, 1, replicaBuffer, replyBuffer );
			
			::dsDataBufferDeallocatePriv( replicaBuffer );
			::dsDataBufferDeallocatePriv( replyBuffer );
		}
		
		if ( nativeAttrType != NULL )
			delete nativeAttrType;
		
		gNetInfoMutex->Wait();
		if (niValues.ni_namelist_len > 0)
		{
			ni_namelist_free( &niValues );
		}
		gNetInfoMutex->Signal();
	}
	catch( sInt32 catchErr )
	{
		error = catchErr;
	}
	
	if ( nativeRecType != NULL )
		delete nativeRecType;
	
	return error;
}


//--------------------------------------------------------------------------------------------------
//	RepackBufferForPWServer
//
//	Replace the user name with the uesr id.
//--------------------------------------------------------------------------------------------------

sInt32 CNiPlugIn::RepackBufferForPWServer ( tDataBufferPtr inBuff, const char *inUserID, unsigned long inUserIDNodeNum, tDataBufferPtr *outBuff )
{
	sInt32 result = eDSNoErr;
    tDataListPtr dataList = NULL;
    tDataNodePtr dataNode = NULL;
	unsigned long index, nodeCount;
	unsigned long uidLen;
                
    if ( !inBuff || !inUserID || !outBuff )
        return eDSAuthParameterError;
    
    try
    {	
        uidLen = strlen(inUserID);
        *outBuff = ::dsDataBufferAllocatePriv( inBuff->fBufferLength + uidLen + 1 );
        if ( *outBuff == nil ) throw( (sInt32)eMemoryError );
        
        (*outBuff)->fBufferLength = 0;
        
        dataList = dsAuthBufferGetDataListAllocPriv(inBuff);
        if ( dataList == nil ) throw( (sInt32)eDSInvalidBuffFormat );
        
        nodeCount = dsDataListGetNodeCountPriv(dataList);
        if ( nodeCount < 1 ) throw( (sInt32)eDSInvalidBuffFormat );
        
        for ( index = 1; index <= nodeCount; index++ )
        {
            if ( index == inUserIDNodeNum )
            {
                // write 4 byte length
                memcpy( (*outBuff)->fBufferData + (*outBuff)->fBufferLength, &uidLen, sizeof(unsigned long) );
                (*outBuff)->fBufferLength += sizeof(unsigned long);
                
                // write uid
                memcpy( (*outBuff)->fBufferData + (*outBuff)->fBufferLength, inUserID, uidLen );
                (*outBuff)->fBufferLength += uidLen;
            }
            else
            {
                // get a node
                result = dsDataListGetNodeAllocPriv(dataList, index, &dataNode);
                if ( result != eDSNoErr ) throw( (sInt32)eDSInvalidBuffFormat );
            
                // copy it
                memcpy((*outBuff)->fBufferData + (*outBuff)->fBufferLength, &dataNode->fBufferLength, sizeof(unsigned long));
                (*outBuff)->fBufferLength += sizeof(unsigned long);
                
                memcpy( (*outBuff)->fBufferData + (*outBuff)->fBufferLength, dataNode->fBufferData, dataNode->fBufferLength );
                (*outBuff)->fBufferLength += dataNode->fBufferLength;
                
                // clean up
                dsDataBufferDeallocatePriv(dataNode);
            }
            
        }
        
        (void)dsDataListDeallocatePriv(dataList);
        free(dataList);
    }
    
    catch( sInt32 error )
    {
        result = error;
    }
    
    return result;
} // RepackBufferForPWServer


//------------------------------------------------------------------------------------
//	* DoLocalCachedUserAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoLocalCachedUserAuth ( 	tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
											sNIContextData* inContext, 
											sNIContinueData** inOutContinueData, 
											tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
											bool inAuthOnly, bool isSecondary,
											char* inAuthAuthorityData, char* inGUIDString,
											const char* inNativeRecType )
{
	sInt32					siResult				= eDSAuthFailed;
	uInt32					uiAuthMethod			= 0;
	char				   *networkNodename			= nil;
	char				   *userRecordName			= nil;
	char				   *userGUID				= nil;
	tDirReference			aDirRef					= NULL;
	sInt32					result					= eDSNoErr;
	tDataList			   *netNode					= nil;
	tDataBuffer			   *dataBuffer				= nil;
	uInt32					nodeCount				= 0;
	tContextData			tContinue				= nil;
	bool					bNetworkNodeReachable	= false;
	tDirNodeReference		aNodeRef				= NULL;
	tDirNodeReference		aSearchNodeRef			= NULL;
	tDataNode			   *authMethodPtr			= nil;
	bool					bAuthLocally			= false;
	sNIContextData		   *tmpContext				= nil;
	tDataList			   *pSearchNode				= nil;
	tDataList			   *pSearchNodeList			= nil;
	tAttributeListRef		attrListRef				= 0;
	tAttributeValueListRef	attrValueListRef		= 0;
	tAttributeValueEntry   *pAttrValueEntry			= nil;
	tAttributeEntry		   *pAttrEntry				= nil;
	uInt32					aIndex					= 0;

	try
	{
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( inContext == nil ) throw( (sInt32)eDSInvalidNodeRef );

		DBGLOG( kLogPlugin, "DoLocalCachedUserAuth::" );
		siResult = GetAuthMethod( inAuthMethod, &uiAuthMethod );
		
		if ( siResult == eDSNoErr )
		{
			siResult = ParseLocalCacheUserData(inAuthAuthorityData, &networkNodename, &userRecordName, &userGUID);
			
			result = dsOpenDirService(&aDirRef);
			if (result == eDSNoErr)
			{
				netNode = dsBuildFromPathPriv( networkNodename, "/" );
				if ( netNode == nil ) throw( (sInt32)eMemoryError );
				dataBuffer = ::dsDataBufferAllocate( aDirRef, 1024 );
				if ( dataBuffer == nil ) throw( (sInt32)eMemoryError );
	
				result = dsFindDirNodes( aDirRef, dataBuffer, netNode, eDSiExact, &nodeCount, &tContinue );
				if ( (result == eDSNoErr) && (nodeCount == 1) )
				{
					//now check if the node is actually on the search policy
					//get thesearch node. open it and call dsgetdirnodeinfo for kDS1AttrSearchPath
					//extract the node list
					result = dsFindDirNodes( aDirRef, dataBuffer, nil, eDSAuthenticationSearchNodeName, &nodeCount, &tContinue );
					if ( ( result == eDSNoErr ) && ( nodeCount == 1 ) )
					{
						result = dsGetDirNodeName( aDirRef, dataBuffer, 1, &pSearchNode );
						if ( result == eDSNoErr )
						{
							result = dsOpenDirNode( aDirRef, pSearchNode, &aSearchNodeRef );
							if ( pSearchNode != NULL )
							{
								dsDataListDeallocatePriv( pSearchNode );
								free( pSearchNode );
								pSearchNode = NULL;
							}
							if ( result == eDSNoErr )
							{
								pSearchNodeList = dsBuildFromPathPriv( kDS1AttrSearchPath, "/" );
								if ( pSearchNodeList == nil ) throw( (sInt32)eMemoryError );
								do
								{
									nodeCount = 0;
									result = dsGetDirNodeInfo( aSearchNodeRef, pSearchNodeList, dataBuffer, false, &nodeCount, &attrListRef, nil  );
									if (result == eDSBufferTooSmall)
									{
										uInt32 bufSize = dataBuffer->fBufferSize;
										dsDataBufferDeallocatePriv( dataBuffer );
										dataBuffer = nil;
										dataBuffer = ::dsDataBufferAllocate( aDirRef, bufSize * 2 );
									}
								} while (result == eDSBufferTooSmall);
								
								if ( ( result == eDSNoErr ) && (nodeCount > 0) )
								{	
									//assume first attribute since only 1 expected
									result = dsGetAttributeEntry( aSearchNodeRef, dataBuffer, attrListRef, 1, &attrValueListRef, &pAttrEntry );
									if ( result != eDSNoErr ) throw( result );
									
									//retrieve the node path strings
									for (aIndex=1; aIndex < (pAttrEntry->fAttributeValueCount+1); aIndex++)
									{
										result = dsGetAttributeValue( aSearchNodeRef, dataBuffer, aIndex, attrValueListRef, &pAttrValueEntry );
										if ( result != eDSNoErr ) throw( result );
										if ( pAttrValueEntry->fAttributeValueData.fBufferData == nil ) throw( (sInt32)eMemoryAllocError );
										
										if (strcmp( networkNodename, pAttrValueEntry->fAttributeValueData.fBufferData ) == 0 )
										{
											bNetworkNodeReachable = true; //node is registered in DS
											dsDeallocAttributeValueEntry(aDirRef, pAttrValueEntry);
											pAttrValueEntry = nil;
											break;
										}
										dsDeallocAttributeValueEntry(aDirRef, pAttrValueEntry);
										pAttrValueEntry = nil;
									}
									
									dsCloseAttributeList(attrListRef);
									dsCloseAttributeValueList(attrValueListRef);
									dsDeallocAttributeEntry(aDirRef, pAttrEntry);
									pAttrEntry = nil;
									
									//close dir node after releasing attr references
									result = ::dsCloseDirNode(aSearchNodeRef);
									if ( result != eDSNoErr ) throw( result );
								}
							}
						}
					}
					//formerly no check for network node on the search policy
					//bNetworkNodeReachable = true; //node is registered in DS
				}
			}
			
			if ( siResult == eDSNoErr )
			{
				siResult = eDSAuthFailed;
				// look at auth method request and decide what to do
				switch( uiAuthMethod )
				{
					case kAuthSetPasswdAsRoot:
						//do local only with no network sync as root is local
						//note here that password may become unsynced
					case kAuthWriteSecureHash:
					case kAuthReadSecureHash:
						//supported since local cached users are completely copied upon cache updates
					case kAuthSecureHash:
						//local only since cannot sync this hash ie. no password
						siResult = CNiPlugIn::DoShadowHashAuth(	inNodeRef,
																inAuthMethod,
																inContext,
																inOutContinueData,
																inAuthData,
																outAuthData,
																inAuthOnly,
																isSecondary,
																NULL,		//no need to pass in the network data
																inGUIDString,
																inNativeRecType );
						break;
	
					case kAuthChangePasswd:
						//network and if success then local otherwise fails immediately
						if (bNetworkNodeReachable)
						{
							result = dsOpenDirNode(aDirRef, netNode, &aNodeRef);
							if ( result == eDSNoErr)
							{
								//user records only here
								siResult = dsDoDirNodeAuth( aNodeRef, inAuthMethod, inAuthOnly, inAuthData, outAuthData, &tContinue );
								//no checking of continue data
								if (siResult == eDSNoErr)
								{
									siResult = CNiPlugIn::DoShadowHashAuth(	inNodeRef,
																			inAuthMethod,
																			inContext,
																			inOutContinueData,
																			inAuthData,
																			outAuthData,
																			inAuthOnly,
																			isSecondary,
																			NULL,		//no need to pass in the network data
																			inGUIDString,
																			inNativeRecType );
								}
							}
						}
						break;
	
					case kAuthNativeClearTextOK:
					case kAuthNativeNoClearText:
					case kAuthNativeMethod:
						//network if available and if success then switch to kAuthSetPasswdAsRoot
						//otherwise local auth only
						bAuthLocally = false;
						if (bNetworkNodeReachable)
						{
							result = dsOpenDirNode(aDirRef, netNode, &aNodeRef);
							if ( result == eDSNoErr)
							{
								//user records only here
								//network auth should not have session changed so inAuthOnly fixed at true
								siResult = dsDoDirNodeAuth( aNodeRef, inAuthMethod, true, inAuthData, outAuthData, &tContinue );
								//no checking of continue data
								if (siResult == eDSNoErr)
								{
									//try the local auth as well to see if the password needs to be sync'ed up
									siResult = CNiPlugIn::DoShadowHashAuth(	inNodeRef,
																			inAuthMethod,
																			inContext,
																			inOutContinueData,
																			inAuthData,
																			outAuthData,
																			inAuthOnly,
																			isSecondary,
																			NULL,		//no need to pass in the network data
																			inGUIDString,
																			inNativeRecType );
									if (siResult != eDSNoErr)
									{
										DBGLOG( kLogPlugin, "DoLocalCachedUserAuth::Local auth not sync'ed so try to set local password" );
										//set the auth method to SetPasswdAsRoot for local password sync
										authMethodPtr = dsDataNodeAllocateString( aDirRef, kDSStdAuthSetPasswdAsRoot );
										if (authMethodPtr == nil ) throw( (sInt32)eMemoryError );
										
										//repackage the context data to ensure uid == 0 for writing shadow hash
										tmpContext = (sNIContextData *) calloc(1, sizeof(sNIContextData));
										//directly assign pointers so no need to free internals after call
										//not all these are actually needed but assigned for completeness
										tmpContext->fDomain					= inContext->fDomain;
										tmpContext->fDomainName				= inContext->fDomainName;
										tmpContext->fRecType				= inContext->fRecType;
										tmpContext->fRecName				= inContext->fRecName;
										tmpContext->dirID					= inContext->dirID;
										tmpContext->offset					= inContext->offset;
										tmpContext->index					= inContext->index;
										tmpContext->fDontUseSafeClose		= inContext->fDontUseSafeClose;
										tmpContext->fUID					= getuid();
										tmpContext->fEffectiveUID			= geteuid();
										tmpContext->fAuthenticatedUserName	= inContext->fAuthenticatedUserName;
										tmpContext->fPWSRef					= inContext->fPWSRef;
										tmpContext->fPWSNodeRef				= inContext->fPWSNodeRef;
										tmpContext->bIsLocal				= true;
										
										//no need to repackage the auth buffer for the call to SetPasswdAsRoot locally
										//ie. still username and password so should work fine
										siResult = CNiPlugIn::DoShadowHashAuth(	inNodeRef,
																				authMethodPtr,
																				tmpContext,
																				inOutContinueData,
																				inAuthData,
																				outAuthData,
																				inAuthOnly,
																				isSecondary,
																				NULL,		//no need to pass in the network data
																				inGUIDString,
																				inNativeRecType );
										free(tmpContext);
										tmpContext = nil;
									}
								}
							}
							else
							{
								//couldn't open the network node so go ahead and auth locally
								bAuthLocally = true;
							}
						}
						else
						{
							bAuthLocally = true;
						}
						if (bAuthLocally)
						{
							siResult = CNiPlugIn::DoShadowHashAuth(	inNodeRef,
																	inAuthMethod,
																	inContext,
																	inOutContinueData,
																	inAuthData,
																	outAuthData,
																	inAuthOnly,
																	isSecondary,
																	NULL,		//no need to pass in the network data
																	inGUIDString,
																	inNativeRecType );
						}
						break;
	
					case kAuthSetPolicy:
						//only password server support at this time 05-16-03
					case kAuthSetPasswd:
						//could allow call to network and if success then switch to kAuthSetPasswdAsRoot for local
					case kAuthSMB_NT_Key:
					case kAuthSMB_LM_Key:
						//not supported at this time 05-16-03
						//not discussed in any design conversations
					default:
						siResult = eDSAuthMethodNotSupported;
						break;
				}
	
			}
		}
	}
	catch( sInt32 err )
	{
		siResult = err;
	}

	//cleanup
	if (networkNodename != nil)
	{
		free(networkNodename);
		networkNodename = nil;
	}
	if (userRecordName != nil)
	{
		free(userRecordName);
		userRecordName = nil;
	}
	if (userGUID != nil)
	{
		free(userGUID);
		userGUID = nil;
	}
    if ( dataBuffer != nil )
	{
        dsDataBufferDeallocatePriv( dataBuffer );
		dataBuffer = nil;
	}
	if (netNode != nil)
	{
		dsDataListDeallocatePriv(netNode);
		free(netNode);
		netNode = NULL;
	}
	if (authMethodPtr != nil)
	{
		dsDataBufferDeallocatePriv( authMethodPtr );
		authMethodPtr = nil;
	}
	if (aNodeRef != NULL)
	{
		dsCloseDirNode(aNodeRef);
		aNodeRef = NULL;
	}
	if (aDirRef != NULL)
	{
		dsCloseDirService(aDirRef);
		aDirRef = NULL;
	}
	
	return( siResult );

} // DoLocalCachedUserAuth


//------------------------------------------------------------------------------------
//	* DoDisabledAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoDisabledAuth ( 		tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
										sNIContextData* inContext, 
										sNIContinueData** inOutContinueData, 
										tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
										bool inAuthOnly, bool isSecondary,
										char* inAuthAuthorityData, char* inGUIDString,
										const char* inNativeRecType )
{
	return (sInt32)eDSAuthFailed;
} // DoDisabledAuth


//------------------------------------------------------------------------------------
//	* DoPasswordServerAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoPasswordServerAuth ( tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
										 sNIContextData* inContext, 
										 sNIContinueData** inOutContinueData, 
										 tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
										 bool inAuthOnly, bool isSecondary, char* inAuthAuthorityData,
										 char* inGUIDString, const char* inNativeRecType )
{
    sInt32 result = eDSAuthFailed;
    sInt32 error;
    uInt32 authMethod;
    char *serverAddr;
    char *uidStr = NULL;
    long uidStrLen;
    tDataBufferPtr authDataBuff = NULL;
    tDataBufferPtr authDataBuffTemp = NULL;
    char *nodeName = NULL;
    char *userName = NULL;
	char *password = NULL;
    sNIContinueData *pContinue = NULL;
    tContextData continueData = NULL;
    
    if ( !inAuthAuthorityData || *inAuthAuthorityData == '\0' )
        return eDSAuthParameterError;
    
    try
    {
        //DBGLOG1( kLogPlugin, "AuthorityData=%s\n", inAuthAuthorityData);
        
        serverAddr = strchr( inAuthAuthorityData, ':' );
        if ( serverAddr )
        {
            uidStrLen = serverAddr - inAuthAuthorityData;
            uidStr = (char *) calloc(1, uidStrLen+1);
            if ( uidStr == nil ) throw( (sInt32)eMemoryError );
            strncpy( uidStr, inAuthAuthorityData, uidStrLen );
            
            // advance past the colon
            serverAddr++;
            
			// try any method to the password server, even if unknown
			DBGLOG( kLogPlugin, "DoPasswordServerAuth::" );
			error = GetAuthMethod( inAuthMethod, &authMethod );
			if ( error != eDSNoErr && error != eDSAuthMethodNotSupported )
				throw( error );
			
            switch( authMethod )
            {
                case kAuth2WayRandom:
                    if ( inOutContinueData == nil )
                        throw( (sInt32)eDSNullParameter );
                    
                    if ( *inOutContinueData == nil )
                    {
                        pContinue = (sNIContinueData *)::calloc( 1, sizeof( sNIContinueData ) );
                        if ( pContinue == nil )
                            throw( (sInt32)eMemoryError );
                        
                        gNIContinue->AddItem( pContinue, inNodeRef );
                        *inOutContinueData = pContinue;
                        
                        // make a buffer for the user ID
                        authDataBuff = ::dsDataBufferAllocatePriv( uidStrLen + 1 );
                        if ( authDataBuff == nil ) throw ( (sInt32)eMemoryError );
                        
                        // fill
                        strcpy( authDataBuff->fBufferData, uidStr );
                        authDataBuff->fBufferLength = uidStrLen;
                    }
                    else
                    {
                        pContinue = *inOutContinueData;
                        if ( gNIContinue->VerifyItem( pContinue ) == false )
                            throw( (sInt32)eDSInvalidContinueData );
                            
                        authDataBuff = inAuthData;
                    }
                    break;
                    
                case kAuthSetPasswd:
				case kAuthSetPolicy:
				    {
                        ni_id niDirID;
                        ni_namelist niValues;
                        ni_status niResult = NI_OK;
                        char* aaVersion = NULL;
                        char* aaTag = NULL;
                        char* aaData = NULL;
                        unsigned int idx;
                        sInt32 lookupResult;
                        char* endPtr = NULL;
                        
                        // lookup the user that wasn't passed to us
                       	error = GetUserNameFromAuthBuffer( inAuthData, 3, &userName );
                        if ( error != eDSNoErr ) throw( error );
                        
                        // get the auth authority
                        error = IsValidRecordName ( userName, inNativeRecType, inContext->fDomain, niDirID );
                        if ( error != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
                        
						gNetInfoMutex->Wait();
                        //lookup authauthority attribute
                        niResult = ni_lookupprop( inContext->fDomain, &niDirID, 
                                                "authentication_authority", &niValues );
						gNetInfoMutex->Signal();
                        if (niResult != NI_OK) throw( (sInt32)eDSAuthFailed );
                     	
                        // don't break or throw to guarantee cleanup
                        lookupResult = eDSAuthFailed;
                        for ( idx = 0; idx < niValues.ni_namelist_len && lookupResult == eDSAuthFailed; idx++ )
                        {
                            // parse this value of auth authority
                            error = ParseAuthAuthority( niValues.ni_namelist_val[idx], &aaVersion, &aaTag, &aaData );
                            // need to check version
                            if (error != eDSNoErr)
                                lookupResult = eParameterError;
                            
                            if ( error == eDSNoErr && strcmp(aaTag, kDSTagAuthAuthorityPasswordServer) == 0 )
                            {
                                endPtr = strchr( aaData, ':' );
                                if ( endPtr == NULL )
                                {
                                    lookupResult = eParameterError;
                                }
                                else
                                {
                                    *endPtr = '\0';
                                    lookupResult = eDSNoErr;
                                }
                            }
                            
                            if (aaVersion != NULL) {
                                free(aaVersion);
                                aaVersion = NULL;
                            }
                            if (aaTag != NULL) {
                                free(aaTag);
                                aaTag = NULL;
                            }
                            if (lookupResult != eDSNoErr && aaData != NULL) {
                                free(aaData);
                                aaData = NULL;
                            }
                        }
						gNetInfoMutex->Wait();
						if (niValues.ni_namelist_len > 0)
						{
							ni_namelist_free(&niValues);
						}
						gNetInfoMutex->Signal();
                        
                        if ( lookupResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
                        
                        // do the usual
                        error = RepackBufferForPWServer(inAuthData, uidStr, 1, &authDataBuffTemp );
                        if ( error != eDSNoErr ) throw( error );
                        
                        // put the admin user ID in slot 3
                        error = RepackBufferForPWServer(authDataBuffTemp, aaData, 3, &authDataBuff );
                        if (aaData != NULL) {
                            free(aaData);
                            aaData = NULL;
                        }
                        if ( error != eDSNoErr ) throw( error );
                    }
                    break;
					
				case kAuthClearText:
				case kAuthNativeClearTextOK:
				case kAuthNativeNoClearText:
				case kAuthCrypt:
					{
						tDataListPtr dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
						if (dataList != NULL)
						{
							userName = dsDataListGetNodeStringPriv(dataList, 1);
							password = dsDataListGetNodeStringPriv(dataList, 2);
							// this allocates a copy of the string
							
							dsDataListDeallocatePriv(dataList);
							free(dataList);
							dataList = NULL;
						}
					}
					//fall through
                
                default:
                    error = RepackBufferForPWServer(inAuthData, uidStr, 1, &authDataBuff );
                    if ( error != eDSNoErr ) throw( error );
            }
            
            //DBGLOG( kLogPlugin, "ready to call PSPlugin\n");
			
            if ( inContext->fPWSRef == 0 )
            {
                error = ::dsOpenDirService( &inContext->fPWSRef );
                if ( error != eDSNoErr ) throw( error );
            }
            
            if ( inContext->fPWSNodeRef == 0 )
            {
                nodeName = (char *)calloc(1,strlen(serverAddr)+17);
                if ( nodeName == nil ) throw ( (sInt32)eMemoryError );
                
                sprintf( nodeName, "/PasswordServer/%s", serverAddr );
                error = PWOpenDirNode( inContext->fPWSRef, nodeName, &inContext->fPWSNodeRef );
                if ( error != eDSNoErr ) throw( error );
            }
            
            if ( pContinue )
                continueData = pContinue->fPassPlugContinueData;
            
            result = dsDoDirNodeAuth( inContext->fPWSNodeRef, inAuthMethod, inAuthOnly,
                                        authDataBuff, outAuthData, &continueData );
            
			if ( result == eDSAuthNoAuthServerFound || result == eDSAuthServerError )
			{
				result = PWSetReplicaData( inContext, uidStr );
				if ( result == eDSNoErr )
					result = dsDoDirNodeAuth( inContext->fPWSNodeRef, inAuthMethod, inAuthOnly,
												authDataBuff, outAuthData, &continueData );
			}
			
            if ( pContinue )
            {
                pContinue->fPassPlugContinueData = continueData;
                if ( continueData == NULL )
                {
                    gNIContinue->RemoveItem( pContinue );
                    if ( inOutContinueData == nil )
                        throw( (sInt32)eDSNullParameter );
                    *inOutContinueData = nil;
                }
            }
            
			if ( (result == eDSNoErr) && (inAuthOnly == false) && (userName != NULL) && (password != NULL) )
			{
				result = AuthOpen( inContext, userName, password );
			}
        }
    }
    
    catch(sInt32 err )
    {
        result = err;
    }
    
	if ( nodeName )
        free( nodeName );

	if ( uidStr != NULL )
	{
		free( uidStr );
		uidStr = NULL;
	}
    if ( userName != NULL )
	{
		free( userName );
		userName = NULL;
	}
	if ( password != NULL )
	{
		free( password );
		password = NULL;
	}
    
    if ( authDataBuff )
        dsDataBufferDeallocatePriv( authDataBuff );
    if ( authDataBuffTemp )
        dsDataBufferDeallocatePriv( authDataBuffTemp );
                        
	return( result );
} // DoPasswordServerAuth


//------------------------------------------------------------------------------------
//	* DoSetPassword
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoSetPassword ( sNIContextData *inContext, tDataBuffer *inAuthData, const char *inNativeRecType )
{
	sInt32				siResult		= eDSAuthFailed;
	bool				bFreePropList	= false;
	char			   *pData			= nil;
	char			   *userName		= nil;
	uInt32				userNameLen		= 0;
	char			   *userPwd			= nil;
	uInt32				userPwdLen		= 0;
	char			   *rootName		= nil;
	uInt32				rootNameLen		= 0;
	char			   *rootPwd			= nil;
	uInt32				rootPwdLen		= 0;
	uInt32				offset			= 0;
	uInt32				buffSize		= 0;
	uInt32				buffLen			= 0;
	ni_proplist			niPropList;
#ifdef TIM_CLIENT_PRESENT
	TIMHandle		   *pTimHndl		= nil;
#endif
	void			   *domain			= nil;

	gNetInfoMutex->Wait();

	try
	{
		if ( inContext == nil ) throw( (sInt32)eDSAuthFailed );
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );

		pData = inAuthData->fBufferData;
		buffSize = inAuthData->fBufferSize;
		buffLen = inAuthData->fBufferLength;

		if ( buffLen > buffSize ) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the length of the first data block and verify that it
		//	is greater than 0 and doesn't go past the end of the buffer
		//	(user name)

		if ( buffLen < 4 * sizeof( unsigned long ) + 2 ) throw( (sInt32)eDSInvalidBuffFormat );
		// need length for username, password, root username, and root password.
		// both usernames must be at least one character
		::memcpy( &userNameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (userNameLen == 0) throw( (sInt32)eDSInvalidBuffFormat );
		if (offset + userNameLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		userName = (char *)::calloc( 1, userNameLen + 1 );
		::memcpy( userName, pData, userNameLen );
		pData += userNameLen;
		offset += userNameLen;
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the users new password
		::memcpy( &userPwdLen, pData, sizeof( unsigned long ) );
  		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (offset + userPwdLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		userPwd = (char *)::calloc( 1, userPwdLen + 1 );
		::memcpy( userPwd, pData, userPwdLen );
		pData += userPwdLen;
		offset += userPwdLen;
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the root users name
		::memcpy( &rootNameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (rootNameLen == 0) throw( (sInt32)eDSInvalidBuffFormat );
		if (rootNameLen > (buffSize - offset)) throw( (sInt32)eDSInvalidBuffFormat );

		rootName = (char *)::calloc( 1, rootNameLen + 1 );
		::memcpy( rootName, pData, rootNameLen );
		pData += rootNameLen;
		offset += rootNameLen;
		if (sizeof( unsigned long ) > (buffSize - offset)) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the root users password
		::memcpy( &rootPwdLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (rootPwdLen > (buffSize - offset)) throw( (sInt32)eDSInvalidBuffFormat );

		rootPwd = (char *)::calloc( 1, rootPwdLen + 1 );
		::memcpy( rootPwd, pData, rootPwdLen );
		pData += rootPwdLen;
		offset += rootPwdLen;

#ifdef TIM_CLIENT_PRESENT
		//opt out early if we know tim is not there **** check on local first
		if ( IsTimRunning() )
		{
			pTimHndl = ::timServerForDomain( inContext->fDomain );
			if ( pTimHndl != nil )
			{
				// Set the password for the user
				siResult = ::timSetPasswordWithTIMHandle( pTimHndl, rootName, rootPwd, userName, userPwd );
				timHandleFree( pTimHndl );

				if ( siResult != eDSNoErr )
				{
				
				}
			}

#ifdef DEBUG
			if ( siResult != TimStatusOK )
			{
				DBGLOG1( kLogPlugin, "-- timSetPasswordForUser -- failed with %l.", siResult );
			}
#endif
			siResult = MapAuthResult( siResult );
		}
		else
#endif
		{
			//need some code here to directly set the NI Crypt password
			//also first check to see if the password already exists

			char			salt[3];
			char			hashPwd[ 32 ];
			ni_id			niDirID;
			ni_index   		niWhere			= 0;
			ni_namelist		niValue;
			char		   *niPwd			= nil;

			domain = inContext->fDomain;
			//need some code here to directly change the NI Crypt password
			//first check to see that the old password is valid
			DBGLOG( kLogPlugin, "Attempting UNIX Crypt password change" );
			siResult = IsValidRecordName ( rootName, inNativeRecType, domain, niDirID );

#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
#endif

			siResult = ::ni_read( domain, &niDirID, &niPropList );
#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
			if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
			if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthFailed );
#endif
			bFreePropList = true;
			niWhere = ::ni_proplist_match( niPropList, "uid", nil );

			if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthFailed );
			siResult = eDSAuthFailed;
			niValue = niPropList.ni_proplist_val[ niWhere ].nip_val;

			// make sure the user we are going to verify actually is root OR an admin user
			if ( !( ( (niValue.ni_namelist_val[ 0 ] != NULL) && (strcmp( "0", niValue.ni_namelist_val[ 0 ]) == 0) ) || 
					(UserIsAdmin(rootName, domain)) ) )
			{
				throw( (sInt32)eDSAuthFailed );
			}

			niWhere = ::ni_proplist_match( niPropList, "passwd", nil );
#ifdef DEBUG
			if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthBadPassword );
			siResult = eDSAuthBadPassword;
#else
			if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthFailed );
			siResult = eDSAuthFailed;
#endif
			niValue = niPropList.ni_proplist_val[ niWhere ].nip_val;
			// be careful not to dereference nil here.
			// We assume it is an empty password if there are no values for passwd.
			if ((niValue.ni_namelist_len > 0) && (niValue.ni_namelist_val != nil))
			{
				niPwd = niPropList.ni_proplist_val[ niWhere ].nip_val.ni_namelist_val[ 0 ];
			}
			else
			{
				niPwd = (char*)""; // empty string, we are not freeing it so direct assignment is OK
			}

			//account for the case where niPwd == "" such that we will auth if pwdLen is 0
			if (::strcmp(niPwd,"") != 0)
			{
				salt[ 0 ] = niPwd[0];
				salt[ 1 ] = niPwd[1];
				salt[ 2 ] = '\0';

				::memset( hashPwd, 0, 32 );
				::strcpy( hashPwd, ::crypt( rootPwd, salt ) );

				siResult = eDSAuthFailed;
				if ( ::strcmp( hashPwd, niPwd ) == 0 )
				{
					siResult = eDSNoErr;
				}
			
			}
			else // niPwd is == ""
			{
				if (::strcmp(rootPwd,"") != 0)
				{
					siResult = eDSNoErr;
				}
			}

			if (siResult == eDSNoErr)
			{
				if ( inContext->bIsLocal ) //local node and assume crypt password length is correct
				{
					MigrateToShadowHash(domain, &niDirID, &niPropList, userName, userPwd);
				}
				else
				{
					// we successfully authenticated with root password, now set new user password.
					//set with the new password
					const char *saltchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	
					::memset( hashPwd, 0, 32 );
	
					if ( ::strlen(userPwd) > 0 )
					{
						// only need crypt if password is not empty
						::srandom(getpid() + time(0));
						salt[0] = saltchars[random() % 64];
						salt[1] = saltchars[random() % 64];
						salt[2] = '\0';
	
						::strcpy( hashPwd, ::crypt( userPwd, salt ) );
					}
	
					siResult = IsValidRecordName ( userName, inNativeRecType, domain, niDirID );
	
	#ifdef DEBUG
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
	#else
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
	#endif
					::ni_proplist_free( &niPropList );
					bFreePropList = false;
					siResult = ::ni_read( domain, &niDirID, &niPropList );
	#ifdef DEBUG
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
	#else
					if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
	#endif
					bFreePropList = true;
					
					niWhere = ::ni_proplist_match( niPropList, "passwd", nil );
					if ( niWhere != NI_INDEX_NULL )
					{
						// password already exists, delete it
						niValue = niPropList.ni_proplist_val[ niWhere ].nip_val;
	
						siResult = NiLib2::DestroyDirVal( domain, &niDirID, (char*)"passwd", niValue );
					}
	
					siResult = NiLib2::InsertDirVal( domain, &niDirID, (char*)"passwd", hashPwd, 0 );
					siResult = MapNetInfoErrors( siResult );
				}
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( bFreePropList )
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();

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

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

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

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

	return( siResult );

} // DoSetPassword


//------------------------------------------------------------------------------------
//	* DoSetPasswordAsRoot
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoSetPasswordAsRoot ( sNIContextData *inContext, tDataBuffer *inAuthData, const char *inNativeRecType )
{
	sInt32				siResult		= eDSAuthFailed;
	bool				bFreePropList	= false;
	char			   *pData			= nil;
	char			   *userName		= nil;
	uInt32				userNameLen		= 0;
	char			   *newPasswd		= nil;
	uInt32				newPwdLen		= 0;
	uInt32				offset			= 0;
	uInt32				buffSize		= 0;
	uInt32				buffLen			= 0;
	ni_proplist			niPropList;
#ifdef TIM_CLIENT_PRESENT
	char			   *pTag			= nil;
	uInt32				uiCount			= 0;
	uInt32				i				= 0;
	sInt32				timResult		= eDSAuthFailed;
	bool				done			= false;
	TIMHandle		   *pTimHndl		= nil;
	Buffer			   *pUserBuff		= nil;
	Buffer			   *pBuffArray		= nil;
	Buffer			   *pTmpBuff		= nil;
#endif
  	void			   *domain			= nil;

	gNetInfoMutex->Wait();

	try
	{
		if ( inContext == nil ) throw( (sInt32)eDSAuthFailed );
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );

		pData = inAuthData->fBufferData;
		buffSize = inAuthData->fBufferSize;
		buffLen = inAuthData->fBufferLength;

		domain = inContext->fDomain;

   		if ( buffLen > buffSize ) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the length of the first data block and verify that it
		//	is greater than 0 and doesn't go past the end of the buffer
		//	(user name)

		if ( buffLen < 2 * sizeof( unsigned long ) + 1 ) throw( (sInt32)eDSInvalidBuffFormat );
		// need length for both username and password, plus username must be at least one character
		::memcpy( &userNameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (userNameLen == 0) throw( (sInt32)eDSInvalidBuffFormat );
		if (userNameLen > (buffSize - offset)) throw( (sInt32)eDSInvalidBuffFormat );

		userName = (char *)::calloc( 1, userNameLen + 1 );
		::memcpy( userName, pData, userNameLen );
		pData += userNameLen;
		offset += userNameLen;
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the users new password
		::memcpy( &newPwdLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (newPwdLen > (buffSize - offset)) throw( (sInt32)eDSInvalidBuffFormat );

		newPasswd = (char *)::calloc( 1, newPwdLen + 1 );
		::memcpy( newPasswd, pData, newPwdLen );
		pData += newPwdLen;
		offset += newPwdLen;

		// allow root to change anyone's password and
		// others to change their own password
		if ( inContext->fEffectiveUID != 0 )
		{
			if ((inContext->fAuthenticatedUserName == NULL)
				|| ((::strcmp(inContext->fAuthenticatedUserName, "root") != 0)
					&& (::strcmp(inContext->fAuthenticatedUserName, userName) != 0)))
			{
				throw( (sInt32)eDSPermissionError );
			}
		}
#ifdef TIM_CLIENT_PRESENT
		//opt out early if we know tim is not there **** check on local first
		if ( IsTimRunning() )
		{
			// Get a tim handle
			pTimHndl = ::timServerForDomain( inContext->fDomain );
			if ( pTimHndl != nil )
			{
				pUserBuff = ::bufferFromString( userName );
				if ( pUserBuff != nil )
				{
					// Get the user tags from the auth server
					timResult = ::timGetTagsForUser( pTimHndl, pUserBuff, &pBuffArray );
					if ( timResult == TimStatusOK )
					{
						uiCount = bufferArrayCount( pBuffArray );
						while ( (i < uiCount) && !done )
						{
							pTmpBuff = bufferArrayBufferAtIndex( pBuffArray, i++ );
							if ( pTmpBuff != nil )
							{
								pTag = bufferToString( pTmpBuff );
								if ( pTag != nil )
								{
									// Set the password for the user
									timResult = ::timSetPasswordForUserAsRoot( userName, newPasswd, pTag );
#ifdef DEBUG
									if ( timResult != TimStatusOK )
									{
										DBGLOG1( kLogPlugin, "-- timSetPasswordForUserAsRoot -- failed with %l.", timResult );
									}
#endif
									if ( timResult == TimStatusOK )
									{
										siResult = eDSNoErr;
										done = true;
									}

									free( pTag );
									pTag = nil;
								}
								::bufferRelease( pTmpBuff );
								pTmpBuff = nil;
							}
						}
					}
					if ( pBuffArray != nil ) {
						::bufferRelease( pBuffArray );
						pBuffArray = nil;
					}
					::bufferRelease( pUserBuff );
					pUserBuff = nil;
				}
				::timHandleFree( pTimHndl );
				pTimHndl = nil;
			} // pTimHndl != nil
		}
		else
#endif
		{
			//need some code here to directly set the NI Crypt password
			//also first check to see if the password already exists
			char			salt[3];
			char			hashPwd[ 32 ];
			ni_id			niDirID;
			ni_index   		niWhere			= 0;
			ni_namelist		niValue;
//			char		   *niPwd			= nil;

			//need some code here to directly change the NI Crypt password
			//first check to see that the old password is valid
			DBGLOG( kLogPlugin, "Attempting UNIX Crypt password change" );
			siResult = IsValidRecordName ( userName, inNativeRecType, domain, niDirID );

#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
#endif

			siResult = ::ni_read( domain, &niDirID, &niPropList );
#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
#endif
			bFreePropList = true;

			niWhere = ::ni_proplist_match( niPropList, "passwd", nil );

			if (siResult == eDSNoErr)
			{
				if ( inContext->bIsLocal ) //local node and assume crypt password length is correct
				{
					MigrateToShadowHash(domain, &niDirID, &niPropList, userName, newPasswd);
				}
				else
				{
					// we successfully authenticated with old password, now change to new password.
					//set with the new password
					const char *saltchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	
					::memset( hashPwd, 0, 32 );
	
					if ( ::strlen(newPasswd) > 0 )
					{
						// only need crypt if password is not empty
						::srandom(getpid() + time(0));
						salt[0] = saltchars[random() % 64];
						salt[1] = saltchars[random() % 64];
						salt[2] = '\0';
	
						::strcpy( hashPwd, ::crypt( newPasswd, salt ) );
					}
	
					if ( niWhere != NI_INDEX_NULL )
					{
						niValue = niPropList.ni_proplist_val[ niWhere ].nip_val;
	
						siResult = NiLib2::DestroyDirVal( domain, &niDirID, (char*)"passwd", niValue );
					}
	
					siResult = NiLib2::InsertDirVal( domain, &niDirID, (char*)"passwd", hashPwd, 0 );
					siResult = MapNetInfoErrors( siResult );
				}
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( bFreePropList )
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();

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

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

	return( siResult );

} // DoSetPasswordAsRoot


//------------------------------------------------------------------------------------
//	* DoChangePassword
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoChangePassword ( sNIContextData *inContext, tDataBuffer *inAuthData,
									 bool isSecondary, const char *inNativeRecType )
{
	sInt32			siResult		= eDSAuthFailed;
#ifdef TIM_CLIENT_PRESENT
	TIMHandle	   *timHandle		= nil;
#endif
	bool			bFreePropList	= false;
	char		   *pData			= nil;
	char		   *name			= nil;
	uInt32			nameLen			= 0;
	char		   *oldPwd			= nil;
	uInt32			OldPwdLen		= 0;
	char		   *newPwd			= nil;
	uInt32			newPwdLen		= 0;
	uInt32			offset			= 0;
	uInt32			buffSize		= 0;
	uInt32			buffLen			= 0;
	char		   *pathStr			= nil;
	void		   *domain			= nil;
	ni_proplist		niPropList;

	gNetInfoMutex->Wait();

	try
	{
		if ( inContext == nil ) throw( (sInt32)eDSAuthFailed );

		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );

		pData = inAuthData->fBufferData;
		buffSize = inAuthData->fBufferSize;
		buffLen = inAuthData->fBufferLength;

		if ( buffLen > buffSize ) throw( (sInt32)eDSInvalidBuffFormat );

		// Get the length of the first data block and verify that it
		//	is greater than 0 and doesn't go past the end of the buffer

		if ( buffLen < 3 * sizeof( unsigned long ) + 1 ) throw( (sInt32)eDSInvalidBuffFormat );
		// we need at least 3 x 4 bytes for lengths of three strings,
		// and username must be at least 1 long
		::memcpy( &nameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (nameLen == 0) throw( (sInt32)eDSInvalidBuffFormat );
		if (offset + nameLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		name = (char *)::calloc( 1, nameLen + 1 );
		::memcpy( name, pData, nameLen );
		pData += nameLen;
		offset += nameLen;
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		::memcpy( &OldPwdLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (offset + OldPwdLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		oldPwd = (char *)::calloc( 1, OldPwdLen + 1 );
		::memcpy( oldPwd, pData, OldPwdLen );
		pData += OldPwdLen;
		offset += OldPwdLen;
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		::memcpy( &newPwdLen, pData, sizeof( unsigned long ) );
   		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (offset + newPwdLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		newPwd = (char *)::calloc( 1, newPwdLen + 1 );
		::memcpy( newPwd, pData, newPwdLen );
		pData += newPwdLen;
		offset += newPwdLen;

#ifdef TIM_CLIENT_PRESENT
		//opt out early if we know tim is not there **** check on local first
		if ( IsTimRunning() )
		{
			timHandle = ::timServerForDomain( inContext->fDomain );
			if ( timHandle == nil ) throw( (sInt32)eDSAuthFailed );

			// Set the password
			siResult = ::timSetPasswordWithTIMHandle( timHandle, name, oldPwd, name, newPwd );
#ifdef DEBUG
			if ( siResult != TimStatusOK )
			{
				DBGLOG1( kLogPlugin, "-- timSetPassword -- failed with %l.", siResult );
			}
#endif
			siResult = MapAuthResult( siResult );
			::timHandleFree( timHandle );
			timHandle = nil;
		}
		else
#endif
		{
			char			salt[3];
			char			hashPwd[ 32 ];
			ni_id			niDirID;
			ni_index		niWhere			= 0;
			ni_namelist		niValue;
			char		   *niPwd			= nil;
			ni_status		niStatus		= NI_OK;
			
			//first check to see that the old password is valid
			DBGLOG( kLogPlugin, "Attempting UNIX Crypt password change" );
			pathStr = BuildDomainPathFromName( inContext->fDomainName );
			if ( pathStr == nil ) throw( (sInt32)eDSAuthFailed );
			
			niStatus = ::ni_open( nil, pathStr, &domain );
			if ( niStatus != 0) throw( (sInt32)eDSAuthFailed );

			niStatus = ::ni_setuser( domain, name );
			niStatus = ::ni_setpassword( domain, oldPwd );

			siResult = IsValidRecordName ( name, inNativeRecType, domain, niDirID );

#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
#endif

			siResult = ::ni_read( domain, &niDirID, &niPropList );
#ifdef DEBUG
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
			if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthUnknownUser );
#else
			if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
			if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthFailed );
#endif
			bFreePropList = true;

			niWhere = ::ni_proplist_match( niPropList, "passwd", nil );
#ifdef DEBUG
			if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthBadPassword );
			siResult = eDSAuthBadPassword;
#else
			if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthFailed );
			siResult = eDSAuthFailed;
#endif
			niValue = niPropList.ni_proplist_val[ niWhere ].nip_val;
			// be careful not to dereference nil here.
			// We assume it is an empty password if there are no values for passwd.
			if ((niValue.ni_namelist_len > 0) && (niValue.ni_namelist_val != nil))
			{
				niPwd = niPropList.ni_proplist_val[ niWhere ].nip_val.ni_namelist_val[ 0 ];
			}
			else
			{
				niPwd = (char*)""; // empty string, we are not freeing it so direct assignment is OK
			}

			siResult = NiLib2::ValidateDir( name, &niPropList );
			if ( siResult != NI_OK )
			{
				siResult = NiLib2::ValidateName( name, &niPropList, niWhere );
			}

			if ( siResult != eDSNoErr )
			{
				siResult = MapNetInfoErrors( siResult );
			}
			else if ( !isSecondary )
			{
				//if another auth authority has successfully changed the password
				//assume we have out of sync passwords and allow the change
				//account for the case where niPwd == "" such that we will auth if pwdLen is 0
				siResult = eDSAuthFailed;
				if (::strcmp(niPwd,"") != 0)
				{
					salt[ 0 ] = niPwd[0];
					salt[ 1 ] = niPwd[1];
					salt[ 2 ] = '\0';
	
					::memset( hashPwd, 0, 32 );
					::strcpy( hashPwd, ::crypt( oldPwd, salt ) );
	
					if ( ::strcmp( hashPwd, niPwd ) == 0 )
					{
						siResult = eDSNoErr;
					}
				
				}
				else // niPwd is == ""
				{
					if (::strcmp(oldPwd,"") == 0)
					{
						siResult = eDSNoErr;
					}
				}
			}

			if (siResult == eDSNoErr)
			{
				if ( inContext->bIsLocal ) //local node and assume crypt password length is correct
				{
					MigrateToShadowHash(domain, &niDirID, &niPropList, name, newPwd);
				}
				else
				{
					// we successfully authenticated with old password, now change to new password.
					//set with the new password
					const char *saltchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	
					::memset( hashPwd, 0, 32 );
	
					if ( ::strlen(newPwd) > 0 )
					{
						// only need crypt if password is not empty
						::srandom(getpid() + time(0));
						salt[0] = saltchars[random() % 64];
						salt[1] = saltchars[random() % 64];
						salt[2] = '\0';
	
						::strcpy( hashPwd, ::crypt( newPwd, salt ) );
					}
	
					siResult = NiLib2::DestroyDirVal( domain, &niDirID, (char*)"passwd", niValue );
	
					siResult = NiLib2::InsertDirVal( domain, &niDirID, (char*)"passwd", hashPwd, 0 );
					siResult = MapNetInfoErrors( siResult );
				}
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	if ( bFreePropList )
	{
		ni_proplist_free( &niPropList );
	}

	if ( domain != nil )
	{
		ni_free( domain );
		domain = nil;
	}

	gNetInfoMutex->Signal();

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

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

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

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

	return( siResult );

} // DoChangePassword


//------------------------------------------------------------------------------------
//	* DoTimSMBAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoTimSMBAuth ( sNIContextData *inContext, tDataBuffer *inAuthData, 
								 uInt32 inWhichOne )
{
	sInt32				siResult		= eDSAuthFailed;
#ifdef TIM_CLIENT_PRESENT

	TIMHandle		   *timHandle		= nil;
	char			   *pData			= nil;
	char			   *pUserName		= nil;
	uInt32				userNameLen		= 0;
	char			   *pC8				= nil;
	uInt32				c8Len			= 0;
	char			   *pP24			= nil;
	uInt32				p24Len			= 0;
	uInt32				offset			= 0;
	uInt32				buffSize		= 0;
	uInt32				buffLen			= 0;


	gNetInfoMutex->Wait();

	//opt out early if we know tim is not there **** check on local first
	if ( !IsTimRunning() )
	{
		gNetInfoMutex->Signal();
		return( eDSAuthMethodNotSupported );
	}

	try
	{
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( inContext == nil ) throw( (sInt32)eDSNullParameter );

		pData = inAuthData->fBufferData;
		buffSize = inAuthData->fBufferSize;
		buffLen = inAuthData->fBufferLength;

		if ( buffLen > buffSize ) throw( (sInt32)eDSInvalidBuffFormat );

		if ( buffLen < 3 * sizeof( unsigned long ) + 1 ) throw( (sInt32)eDSInvalidBuffFormat );
		// we need at least 3 x 4 bytes for lengths of three blocks of data,
		// and username must be at least 1 long
		::memcpy( &userNameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (userNameLen == 0) throw( (sInt32)eDSInvalidBuffFormat );
		if (offset + userNameLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		pUserName = (char *)::calloc( 1, userNameLen + 1 );
		::memcpy( pUserName, pData, userNameLen );
		pData += userNameLen;
		offset += userNameLen;
   		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		::memcpy( &c8Len, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (offset + c8Len > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		pC8 = (char *)::calloc( 1, c8Len + 1 );
		::memcpy( pC8, pData, c8Len );
		pData += c8Len;
		offset += c8Len;
   		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		::memcpy( &p24Len, pData, sizeof( unsigned long ) );
   		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		if (offset + p24Len > buffLen) throw( (sInt32)eDSInvalidBuffFormat );

		pP24 = (char *)::calloc(  1, p24Len + 1 );
		::memcpy( pP24, pData, p24Len );
		pData += p24Len;
		offset += p24Len;

		timHandle = timServerForDomain( inContext->fDomain );
		if ( timHandle == nil ) throw( (sInt32)eDSAuthFailed );

		switch ( inWhichOne )
		{
			case kAuthSMB_NT_Key:
				siResult = ::timAuthenticateSMBNTKeyWithTIMHandle( timHandle, pUserName, pC8, pP24 );
				break;

			case kAuthSMB_LM_Key:
				siResult = ::timAuthenticateSMBLMKeyWithTIMHandle( timHandle, pUserName, pC8, pP24 );
				break;
		}

		timHandleFree( timHandle );
		timHandle = nil;

#ifdef DEBUG
		if ( siResult != TimStatusOK )
		{
			DBGLOG2( kLogPlugin, "-- timAuthenticateSMBLMKey -- failed with %l for key %l.", siResult, inWhichOne );
		}
#endif
		siResult = MapAuthResult( siResult );
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

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

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

	if ( pP24 != nil )
	{
		free( pP24 );
		pP24 = nil;
	}
#endif
	return( siResult );

} // DoTimSMBAuth


//------------------------------------------------------------------------------------
//	* DoTimMultiPassAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoTimMultiPassAuth ( tDirNodeReference inNodeRef, tDataNodePtr inAuthMethod, 
									   sNIContextData *inContext, sNIContinueData** inOutContinueData, 
									   tDataBufferPtr inAuthData, tDataBufferPtr outAuthData, 
									   bool inAuthOnly )
{
	sInt32			siResult		= eDSAuthFailed;
#ifdef TIM_CLIENT_PRESENT
	tDataBuffer	   *authData		= nil;
	tDataBuffer	   *outData			= nil;
	TIMHandle	   *authHndl		= nil;
	Buffer		   *pAuthBuff		= nil;
	Buffer		   *pAuthOutBuff	= nil;
	CString		   *pAuthStr		= nil;
	sNIContinueData  *pContinue		= nil;

	gNetInfoMutex->Wait(); //assume tim using netinfo needs mutex protection
	//opt out early if we know tim is not there **** check on local first
	if ( !IsTimRunning() )
	{
		gNetInfoMutex->Signal();
		return( eDSAuthMethodNotSupported );
	}

	try
	{
		if ( inContext == nil ) throw( (sInt32)eDSNullDataBuff );
		if ( inAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( outAuthData == nil ) throw( (sInt32)eDSNullAuthStepData );
		if ( inOutContinueData == nil ) throw( (sInt32)eDSNullParameter );

		if ( *inOutContinueData == nil )
		{
			pContinue = (sNIContinueData *)::calloc( 1, sizeof( sNIContinueData ) );
			gNIContinue->AddItem( pContinue, inNodeRef );
		}
		else
		{
			pContinue = *inOutContinueData;
			if ( gNIContinue->VerifyItem( pContinue ) == false )
			{
				throw( (sInt32)eDSInvalidContinueData );
			}
		}

		// First pass setup
		if ( pContinue->fAuthPass == 0 )
		{
			pAuthStr = GetAuthString( inAuthMethod );
			if ( pAuthStr == nil ) throw( (sInt32)eDSAuthMethodNotSupported );

			// Is there an auth server running?
			authHndl = ::timServerForDomain( inContext->fDomain );
			if ( authHndl == nil ) throw( (sInt32)eDSAuthNoAuthServerFound );

			// Start a session with the auth server
			pAuthBuff = ::bufferFromString( pAuthStr->GetData() );
			siResult = ::timSessionStart( authHndl, 0, pAuthBuff);
			::bufferRelease( pAuthBuff );
			pAuthBuff = nil;
			if ( siResult != eDSNoErr ) throw( siResult );

			pContinue->fAuthHndl = authHndl;

			delete( pAuthStr );
			pAuthStr = nil;
		}
		else
		{
			//if ( *inOutContinueData == authHndl ) // JT why are we doing this?
			// authHndl is nil at this point anyway
			if ( *inOutContinueData == NULL )
			{
				throw( (sInt32)eDSInvalidContext );
			}
		}

		pContinue->fAuthPass++;

		authData = inAuthData;
		outData	 = outAuthData;
		authHndl = (TIMHandle *)pContinue->fAuthHndl;

		pAuthBuff = ::bufferFromData( authData->fBufferData, authData->fBufferLength );
		siResult = ::timSessionStep( authHndl, pAuthBuff, &pAuthOutBuff );
		::bufferRelease( pAuthBuff );
		pAuthBuff = nil;

		if ( (siResult != TimStatusOK) && (siResult != TimStatusContinue) )
		{
			// Things didn't go well, clean up and get out.
			::bufferRelease( pAuthOutBuff );
			pAuthOutBuff = nil;

			::timSessionEnd( authHndl );
			authHndl = nil;

			gNIContinue->RemoveItem( pContinue );
			*inOutContinueData = nil;
			
			if ( siResult != eDSNoErr ) throw( siResult );
		}

		if ( ::bufferLength( pAuthOutBuff ) <= outData->fBufferSize )
		{
			::memcpy( outData->fBufferData, pAuthOutBuff->data, pAuthOutBuff->length );
			outData->fBufferLength = pAuthOutBuff->length;
			::bufferRelease( pAuthOutBuff );

			if ( siResult == TimStatusOK )
			{
				// This is a good thing.
				// Release the auth handle
				::timSessionEnd( authHndl );
				authHndl = nil;

				// Clean up the continue data
				gNIContinue->RemoveItem( pContinue );

				// No continue data sent back.
				*inOutContinueData = nil;
			}
			else if ( siResult == TimStatusContinue )
			{
				// Save the session handle
				pContinue->fAuthHndl = authHndl;

				*inOutContinueData = pContinue;
				siResult = eDSNoErr;
			}
		}
	}

	catch( sInt32 err )
	{
		siResult = err;

		if ( siResult > -14000 )
		{
#ifdef DEBUG
			switch ( siResult )
			{
				case TimStatusUserUnknown:
					siResult = eDSAuthUnknownUser;
					break;

				case TimStatusInvalidName:
					siResult = eDSAuthInvalidUserName;
					break;

				case TimStatusUnrecoverablePassword:
					siResult = eDSAuthCannotRecoverPasswd;
					break;

				case TimStatusAuthenticationFailed:
					siResult = eDSAuthFailed;
					break;

				default:
					DBGLOG1( kLogPlugin, "Authentication Server Error = %d.", siResult);
					siResult = eDSAuthServerError;
					break;
			}

			if ( siResult != TimStatusOK )
			{
				DBGLOG1( kLogPlugin, "-- DoMultiPassAuth -- failed with %l.", siResult );
			}
#else
			siResult = eDSAuthFailed;
#endif
		}
	}

	gNetInfoMutex->Signal();
#endif
	return( siResult );

} // DoTimMultiPassAuth


//------------------------------------------------------------------------------------
//	* DoNodeNativeAuth
//------------------------------------------------------------------------------------

//this is REALLY 2-way random with tim authserver ie. name is very mis-leading
sInt32 CNiPlugIn::DoNodeNativeAuth ( sNIContextData *inContext, tDataBuffer *inAuthData, bool inAuthOnly, const char *inNativeRecType )
{
	sInt32			siResult		= eDSAuthFailed;
#ifdef TIM_CLIENT_PRESENT
	TIMHandle	   *timHandle		= nil;
	char		   *pathStr			= nil;
	char		   *pData			= nil;
	char		   *userName		= nil;
	uInt32			nameLen			= 0;
	char		   *pwd				= nil;
	uInt32			pwdLen			= 0;

	gNetInfoMutex->Wait();

	//opt out early if we know tim is not there **** check on local first
	if ( !IsTimRunning() )
	{
		gNetInfoMutex->Signal();
		return( eDSAuthMethodNotSupported );
	}
	try
	{
		if ( inAuthData != nil )
		{
			pData = inAuthData->fBufferData;
			if ( pData != nil )
			{
				// Can we do clear text auth for this domain
//				validMethod = VerifyAuthMethod( inContext->fDomain, AppleStandardTwoWay );
//				if ( validMethod == eDSNoErr )
				{
					// Get the length of the user name
					::memcpy( &nameLen, pData, sizeof( unsigned long ) );
					if ( nameLen != 0 )
					{
						userName = (char *)::calloc(  1, nameLen + 1 );

						// Copy the user name
//						::memset( userName, 0, nameLen + 1 );
						pData += sizeof( unsigned long );
						::memcpy( userName, pData, nameLen );
						pData += nameLen;

						// Get the length of the user password
						::memcpy( &pwdLen, pData, sizeof( unsigned long ) );

						if ( pwdLen != 0 )
						{
							pwd = (char *)::calloc( 1, pwdLen + 1 );

							// Copy the user password
//							::memset( pwd, 0, pwdLen + 1 );
							pData += sizeof( unsigned long );
							::memcpy( pwd, pData, pwdLen );
							pData += pwdLen;

							DBGLOG( kLogPlugin, "Attempting Auth Server Node Native Authentication." );

							timHandle = timServerForDomain( inContext->fDomain );
							if ( timHandle == nil ) throw( (sInt32)eDSAuthFailed );
							
							siResult = ::timAuthenticate2WayRandomWithTIMHandle( timHandle, userName, pwd );

							free( pwd );
							pwd = nil;
							timHandleFree( timHandle );
							timHandle = nil;
#ifdef DEBUG
							if ( siResult != TimStatusOK )
							{
								DBGLOG1( kLogPlugin, "-- timAuthenticate2WayRandom -- failed with %l.", siResult );
							}
#endif
							siResult = MapAuthResult( siResult );

							if ( (siResult == eDSNoErr) && (inAuthOnly == false) )
							{
								siResult = AuthOpen( inContext, userName, pwd );
							}
						}
						else
						{
#ifdef DEBUG
							siResult = eDSAuthInBuffFormatError;
#else
							siResult = eDSAuthFailed;
#endif
						}
						free( userName );
						userName = nil;
					}
					else
					{
#ifdef DEBUG
						siResult = eDSAuthInBuffFormatError;
#else
						siResult = eDSAuthFailed;
#endif
					}
				}
//				else
//				{
//					siResult = eDSAuthMethodNotSupported;
//				}
			}
			else
			{
				siResult = eDSNullDataBuff;
			}
		}
		else
		{
			siResult = eDSNullDataBuff;
		}
	}

	catch( sInt32 err )
	{
		DBGLOG1( kLogPlugin, "2 way random authentication error %l.", err );
		siResult = err;
	}

	gNetInfoMutex->Signal();

	if ( pathStr != nil )
	{
		free( pathStr );
		pathStr = nil;
	}
#endif
	return( siResult );

} // DoNodeNativeAuth

//------------------------------------------------------------------------------------
//	* DoUnixCryptAuth
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::DoUnixCryptAuth ( sNIContextData *inContext, tDataBuffer *inAuthData, bool inAuthOnly, const char *inNativeRecType )
{
	sInt32			siResult		= eDSAuthFailed;
	bool			bFreePropList	= false;
	ni_index   		niWhere			= 0;
	char		   *pData			= nil;
	char		   *niPwd			= nil;
	char		   *name			= nil;
	uInt32			nameLen			= 0;
	char		   *pwd				= nil;
	uInt32			pwdLen			= 0;
	uInt32			offset			= 0;
	uInt32			buffSize		= 0;
	uInt32			buffLen			= 0;
	void		   *domain			= nil;
	char			salt[ 9 ];
	char			hashPwd[ 32 ];
	ni_id			niDirID;
	ni_proplist		niPropList;

	gNetInfoMutex->Wait();

	try
	{
		domain = inContext->fDomain;

#ifdef DEBUG
		if ( inAuthData == nil ) throw( (sInt32)eDSAuthParameterError );
#else
		if ( inAuthData == nil ) throw( (sInt32)eDSAuthFailed );
#endif

		pData		= inAuthData->fBufferData;
		buffSize	= inAuthData->fBufferSize;
		buffLen		= inAuthData->fBufferLength;

		if (buffLen > buffSize) throw( (sInt32)eDSInvalidBuffFormat );

		if (offset + (2 * sizeof( unsigned long) + 1) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );
		// need username length, password length, and username must be at least 1 character
		::memcpy( &nameLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		
#ifdef DEBUG
		if (nameLen == 0) throw( (sInt32)eDSAuthUnknownUser );
		if (nameLen > 256) throw( (sInt32)eDSInvalidBuffFormat );
		if (offset + nameLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );
#else
		if (nameLen == 0) throw( (sInt32)eDSAuthFailed );
		if (nameLen > 256) throw( (sInt32)eDSAuthFailed );
		if (offset + nameLen > buffLen) throw( (sInt32)eDSAuthFailed );
#endif

		name = (char *)::calloc( nameLen + 1, sizeof( char ) );
		::memcpy( name, pData, nameLen );
		pData += nameLen;
		offset += nameLen;

		DBGLOG( kLogPlugin, "Attempting UNIX Crypt authentication" );

//		::strcat( userPath, name );
		siResult = IsValidRecordName ( name, inNativeRecType, domain, niDirID );
//		siResult = ::ni_pathsearch( domain, &niDirID, userPath );

#ifdef DEBUG
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
#else
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
#endif

		siResult = ::ni_read( domain, &niDirID, &niPropList );
#ifdef DEBUG
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthUnknownUser );
		if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthUnknownUser );
#else
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
		if ( niPropList.ni_proplist_val == nil ) throw( (sInt32)eDSAuthFailed );
#endif
		bFreePropList = true;

		niWhere = ::ni_proplist_match( niPropList, "passwd", nil );
#ifdef DEBUG
		if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthBadPassword );
		siResult = eDSAuthBadPassword;
#else
		if (niWhere == NI_INDEX_NULL) throw( (sInt32)eDSAuthFailed );
		siResult = eDSAuthFailed;
#endif

		// be careful not to dereference nil here.
		// We assume it is an empty password if there are no values for passwd.
		if ((niPropList.ni_proplist_val[ niWhere ].nip_val.ni_namelist_len > 0)
			&& (niPropList.ni_proplist_val[ niWhere ].nip_val.ni_namelist_val != nil))
		{
			niPwd = niPropList.ni_proplist_val[ niWhere ].nip_val.ni_namelist_val[ 0 ];
		}
		else
		{
			niPwd = (char*)""; // empty string, we are not freeing it so direct assignment is OK
		}

#ifdef DEBUG
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSInvalidBuffFormat );
#else
		if (offset + sizeof( unsigned long ) > buffLen) throw( (sInt32)eDSAuthFailed );
#endif
		::memcpy( &pwdLen, pData, sizeof( unsigned long ) );
		pData += sizeof( unsigned long );
		offset += sizeof( unsigned long );
		
#ifdef DEBUG
		if (offset + pwdLen > buffLen) throw( (sInt32)eDSInvalidBuffFormat );
		if (pwdLen > 256) throw( (sInt32)eDSAuthFailed );
#else
		if (offset + pwdLen > buffLen) throw( (sInt32)eDSAuthFailed );
		if (pwdLen > 256) throw( (sInt32)eDSAuthFailed );
#endif
		pwd = (char *)::calloc( pwdLen + 1, sizeof( char ) );
		::memcpy( pwd, pData, pwdLen );

		//account for the case where niPwd == "" such that we will auth if pwdLen is 0
		if (::strcmp(niPwd,"") != 0)
		{
			salt[ 0 ] = niPwd[0];
			salt[ 1 ] = niPwd[1];
			salt[ 2 ] = '\0';

			::memset( hashPwd, 0, 32 );
			::strcpy( hashPwd, ::crypt( pwd, salt ) );

			siResult = eDSAuthFailed;
			if ( ::strcmp( hashPwd, niPwd ) == 0 )
			{
				siResult = eDSNoErr;
			}
			
		}
		else // niPwd is == ""
		{
			if ( ::strcmp(pwd,"") == 0 )
			{
				siResult = eDSNoErr;
			}
		}

		if (siResult == eDSNoErr)
		{
			if ( inContext->bIsLocal && (strlen(pwd) < 8) ) //local node and true crypt password since length is 7 or less
			{
				MigrateToShadowHash(domain, &niDirID, &niPropList, name, pwd);
			}

			if (inAuthOnly == false)
			{
				siResult = AuthOpen( inContext, name, pwd );
			}
		}
	}

	catch( sInt32 err )
	{
		DBGLOG1( kLogPlugin, "Crypt authentication error %l", err );
		siResult = err;
	}

	if ( bFreePropList )
	{
		::ni_proplist_free( &niPropList );
	}

	gNetInfoMutex->Signal();
	
	if ( name != nil )
	{
		free( name );
		name = nil;
	}

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

	return( siResult );

} // DoUnixCryptAuth


//------------------------------------------------------------------------------------
//	* MigrateToShadowHash ()
//
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::MigrateToShadowHash( void *inDomain, ni_id *inNIDirID, ni_proplist *inNIPropList, const char *inUserName, const char *inPassword )
{
	sInt32			siResult							= eDSAuthFailed;
	unsigned char   generatedHashes[kHashTotalLength]   = {0};
	SHA_CTX			sha_context							= {};
	unsigned char   digestData[kHashSecureLength]		= {0};
	ni_namelist		niAttribute;
	ni_namelist		niValues;
	ni_index		niWhere								= NI_INDEX_NULL;

	//find the GUID
	niWhere = ::ni_proplist_match( *inNIPropList, "generateduid", nil );
	char* GUIDString = nil; //no need to free this below
	if (niWhere != NI_INDEX_NULL)
	{
		if ((inNIPropList->ni_proplist_val[ niWhere ].nip_val.ni_namelist_len > 0)
			&& (inNIPropList->ni_proplist_val[ niWhere ].nip_val.ni_namelist_val != nil))
		{
			GUIDString = inNIPropList->ni_proplist_val[ niWhere ].nip_val.ni_namelist_val[ 0 ];
		}
	}
	if (GUIDString == nil)
	{
		//no pre-existing GUID so we make one here
		CFUUIDRef       myUUID;
		CFStringRef     myUUIDString;
		char            genUIDValue[100];

		memset( genUIDValue, 0, 100 );
		myUUID = CFUUIDCreate(kCFAllocatorDefault);
		myUUIDString = CFUUIDCreateString(kCFAllocatorDefault, myUUID);
		CFStringGetCString(myUUIDString, genUIDValue, 100, kCFStringEncodingASCII);
		CFRelease(myUUID);
		CFRelease(myUUIDString);
		GUIDString = strdup(genUIDValue);
		//write the GUID value to the user record
		NI_INIT( &niValues );
		::ni_namelist_insert( &niValues, GUIDString, NI_INDEX_NULL );
		siResult = DoAddAttribute( inDomain, inNIDirID, "generateduid", niValues );
		::ni_namelist_free( &niValues );
	}

	CalculateSMBNTHash(inPassword,generatedHashes);
	CalculateSMBLANManagerHash(inPassword,generatedHashes+kHashShadowOneLength);
	
	/* calculate digest */
	SHA1_Init(&sha_context);
	SHA1_Update(&sha_context, (unsigned char *)inPassword, strlen(inPassword));
	SHA1_Final(digestData, &sha_context);
	memmove(generatedHashes+kHashShadowBothLength, digestData, kHashSecureLength);
	
	siResult = WriteShadowHash(inUserName, GUIDString, generatedHashes);
	if (siResult == eDSNoErr)
	{
		NI_INIT( &niAttribute );
		::ni_namelist_insert( &niAttribute, "authentication_authority", NI_INDEX_NULL );
		siResult = NiLib2::DestroyDirProp( inDomain, inNIDirID, niAttribute );
		siResult = MapNetInfoErrors( siResult );
		::ni_namelist_free( &niAttribute );
		
		NI_INIT( &niAttribute );
		::ni_namelist_insert( &niAttribute, "passwd", NI_INDEX_NULL );
		siResult = NiLib2::DestroyDirProp( inDomain, inNIDirID, niAttribute );
		siResult = MapNetInfoErrors( siResult );
		::ni_namelist_free( &niAttribute );

		NI_INIT( &niValues );
		::ni_namelist_insert( &niValues, kDSValueAuthAuthorityShadowHash, NI_INDEX_NULL );
		siResult = DoAddAttribute( inDomain, inNIDirID, "authentication_authority", niValues );
		::ni_namelist_free( &niValues );

		NI_INIT( &niValues );
		::ni_namelist_insert( &niValues, kDSValueNonCryptPasswordMarker, NI_INDEX_NULL );
		siResult = DoAddAttribute( inDomain, inNIDirID, "passwd", niValues );
		::ni_namelist_free( &niValues );
	}
	return(siResult);
}

//------------------------------------------------------------------------------------
//	* ValidateDigest ()
//
//------------------------------------------------------------------------------------

sInt32 CNiPlugIn::ValidateDigest ( sNIContextData *inContext, tDataBuffer *inAuthData, uInt32 inAuthMethod )
{
	sInt32			siResult		= eDSAuthFailed;
#ifdef TIM_CLIENT_PRESENT
	TIMHandle	   *pTimHndl		= nil;
	char		   *pathStr			= nil;
	char		   *pData			= nil;
	char		   *pUserName		= nil;
	uInt32			nameLen			= 0;
	char		   *pChallenge		= nil;
	uInt32			chalLen			= 0;
	char		   *pResponse		= nil;
	uInt32			respLen			= 0;
	sInt32			siBuffErr		= 0;
#ifdef DEBUG
	siBuffErr = eDSAuthInBuffFormatError;
#else
	siBuffErr = eDSAuthFailed;
#endif

	gNetInfoMutex->Wait();

	//opt out early if we know tim is not there **** check on local first
	if ( !IsTimRunning() )
	{
		gNetInfoMutex->Signal();
		return( eDSAuthMethodNotSupported );
	}
	try
	{
		if ( inAuthData != nil )
		{
			pData = inAuthData->fBufferData;
			if ( pData != nil )
			{
				// Get the length of the user name
				::memcpy( &nameLen, pData, sizeof( unsigned long ) );
				if ( nameLen != 0 )
				{
					pUserName = (char *)::calloc(  1, nameLen + 1 );

					// Copy the user name
					pData += sizeof( unsigned long );
					::memcpy( pUserName, pData, nameLen );
					pData += nameLen;

					// Get the length of the challenge
					::memcpy( &chalLen, pData, sizeof( unsigned long ) );
					if ( chalLen != 0 )
					{
						pChallenge = (char *)::calloc( 1, chalLen + 1 );

						pData += sizeof( unsigned long );
						::memcpy( pChallenge, pData, chalLen );
						pData += chalLen;

						// Get the length of the challenge
						::memcpy( &respLen, pData, sizeof( unsigned long ) );
						if ( respLen != 0 )
						{
							pResponse = (char *)::calloc( 1, respLen + 1 );

							// Copy the user response
							pData += sizeof( unsigned long );
							::memcpy( pResponse, pData, respLen );
							pData += respLen;

							DBGLOG( kLogPlugin, "Attempting Auth ValidateDigest." );

							pTimHndl = ::timServerForDomain( inContext->fDomain );
							if ( pTimHndl == nil ) throw( (sInt32)eDSAuthFailed );

							if ( inAuthMethod == kAuthAPOP )
							{
								siResult = ::timAuthenticateAPOPWithTIMHandle( pTimHndl, pUserName, pChallenge, pResponse );
							}
							else if ( inAuthMethod == kAuthCRAM_MD5 )
							{
								siResult = ::timAuthenticateCRAM_MD5WithTIMHandle( pTimHndl, pUserName, pChallenge, pResponse );
							}
							else
							{
								siResult = TimStatusServiceUnavailable;
							}

							free( pResponse );
							pResponse = nil;
							timHandleFree( pTimHndl );
							pTimHndl = nil;
#ifdef DEBUG
							if ( siResult != TimStatusOK )
							{
								DBGLOG1( kLogPlugin, "-- timAuthenticateCRAM_MD5WithTIMHandle -- failed with %l.", siResult );
							}
#endif
							siResult = MapAuthResult( siResult );

							free( pResponse );
							pResponse = nil;

						}
						else
						{
							siResult = siBuffErr;
						}
						free( pChallenge );
						pChallenge = nil;
					}
					else
					{
						siResult = siBuffErr;
					}
					free( pUserName );
					pUserName = nil;
				}
				else
				{
					siResult = siBuffErr;
				}
			}
			else
			{
				siResult = eDSNullDataBuff;
			}
		}
		else
		{
			siResult = eDSNullDataBuff;
		}
	}

	catch( sInt32 err )
	{
		DBGLOG1( kLogPlugin, "2 way random authentication error %l.", err );
		siResult = err;
	}

	gNetInfoMutex->Signal();

	if ( pathStr != nil )
	{
		free( pathStr );
		pathStr = nil;
	}
#endif
	return( siResult );

} // ValidateDigest


// ---------------------------------------------------------------------------
//	* AuthOpen
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::AuthOpen ( sNIContextData *inContext, const char * inUserName, 
							 const char * inPassword )
{
	sInt32			siResult		= eDSAuthFailed;
	void		   *domain			= nil;
	char		   *pathStr			= nil;
	
	gNetInfoMutex->Wait();
	
	try 
	{
		if ( inContext == nil ) throw( (sInt32)eDSAuthFailed );
		if ( inUserName == nil ) throw( (sInt32)eDSAuthFailed );
		if ( inPassword == nil ) throw( (sInt32)eDSAuthFailed );		
		pathStr = BuildDomainPathFromName( inContext->fDomainName );
		if ( pathStr == nil ) throw( (sInt32)eDSAuthFailed ); //shouldn't happen since we opened the node OK
		
		siResult = ::ni_open( nil, pathStr, &domain );
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
		// we need to establish authentication against this domain
		siResult = ::ni_setuser( domain, inUserName );
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
		siResult = ::ni_setpassword( domain, inPassword );
		if ( siResult != eDSNoErr ) throw( (sInt32)eDSAuthFailed );
		if ( siResult == NI_OK )
		{
			// free up the old one and save our new domain in the context
			if ( inContext->fDontUseSafeClose )
			{
				::ni_free(inContext->fDomain);
			}
			else if (inContext->fDomainName != nil)
			{
				CNetInfoPlugin::SafeClose( inContext->fDomainName );
			}
			inContext->fDomain = domain;
			inContext->fDontUseSafeClose = true;
			if ( inContext->fAuthenticatedUserName != NULL )
			{
				free( inContext->fAuthenticatedUserName );
			}
			// check for admin group, and treat admin users as root
			if (UserIsAdmin(inUserName, domain)) {
				inContext->fAuthenticatedUserName = strdup( "root" );
			} else {
				inContext->fAuthenticatedUserName = strdup( inUserName );
			}
			// save it in the context
		}
	}
	
	catch( sInt32 err )
	{
		if ( domain != nil )
		{
			::ni_free(domain);
			domain = nil;
		}
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return siResult;
} // AuthOpen


// ---------------------------------------------------------------------------
//	* VerifyPatternMatch
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::VerifyPatternMatch ( const tDirPatternMatch inPatternMatch )
{
	sInt32		siResult = eDSNoErr;

	switch ( inPatternMatch )
	{
		case eDSExact:
		case eDSStartsWith:
		case eDSEndsWith:
		case eDSContains:
		case eDSLessThan:
		case eDSGreaterThan:
		case eDSLessEqual:
		case eDSGreaterEqual:
		//case eDSWildCardPattern:
		//case eDSRegularExpression:
		case eDSiExact:
		case eDSiStartsWith:
		case eDSiEndsWith:
		case eDSiContains:
		case eDSiLessThan:
		case eDSiGreaterThan:
		case eDSiLessEqual:
		case eDSiGreaterEqual:
		//case eDSiWildCardPattern:
		//case eDSiRegularExpression:
		case eDSAnyMatch:
			siResult = eDSNoErr;
			break;

		default:
			siResult = eDSInvalidPatternMatchType;
			break;
	}


	return( siResult );

} // VerifyPatternMatch


// ---------------------------------------------------------------------------
//	* MapAttrToNetInfoType
// ---------------------------------------------------------------------------

char* CNiPlugIn::MapAttrToNetInfoType ( const char *inAttrType )
{
	char	   *outResult	= nil;
	uInt32		uiStrLen	= 0;
	uInt32		uiNativeLen	= ::strlen( kDSNativeAttrTypePrefix );
	uInt32		uiStdLen	= ::strlen( kDSStdAttrTypePrefix );

	if ( inAttrType != nil )
	{
		uiStrLen = ::strlen( inAttrType );

		// First look for native attribute type
		if ( ::strncmp( inAttrType, kDSNativeAttrTypePrefix, uiNativeLen ) == 0 )
		{
			// Make sure we have data past the prefix
			if ( uiStrLen > uiNativeLen )
			{
				uiStrLen = uiStrLen - uiNativeLen;
				outResult = new char[ uiStrLen + 1 ];
				::strcpy( outResult, inAttrType + uiNativeLen );
			}
		}
		else if ( ::strncmp( inAttrType, kDSStdAttrTypePrefix, uiStdLen ) == 0 )
		{
			for ( int i = 0; i < kAttrConsts; i++ )
			{
				if ( ::strcmp( inAttrType, sAttrMap[ i ][ 0 ] ) == 0 )
				{
					outResult = new char[ ::strlen( sAttrMap[ i ][ 1 ] ) + 1 ];
					::strcpy( outResult, sAttrMap[ i ][ 1 ] );
					break;
				}
			}
		}
		else if ( ::strcmp( inAttrType, kDSAttributesAll ) == 0 )
		{
			outResult = new char[ ::strlen( kDSAttributesAll ) + 1 ];
			::strcpy( outResult, kDSAttributesAll );
		}
		else if ( ::strcmp( inAttrType, kDSAttributesStandardAll ) == 0 )
		{
			outResult = new char[ ::strlen( kDSAttributesStandardAll ) + 1 ];
			::strcpy( outResult, kDSAttributesStandardAll );
		}
	}

	return( outResult );

} // MapAttrToNetInfoType


// ---------------------------------------------------------------------------
//	* MapNetInfoAttrToDSType
// ---------------------------------------------------------------------------

char* CNiPlugIn::MapNetInfoAttrToDSType ( const char *inAttrType )
{
	char	   *outResult	= nil;

	if ( inAttrType != nil )
	{
		// Look for a standard type
		for ( int i = 0; i < kAttrConsts; i++ )
		{
			if ( ::strcmp( inAttrType, sAttrMap[ i ][ 1 ] ) == 0 )
			{
				outResult = new char[ ::strlen( sAttrMap[ i ][ 0 ] ) + 1 ];
				::strcpy( outResult, sAttrMap[ i ][ 0 ] );
				break;
			}
		}

		if ( outResult == nil )
		{
			outResult = new char[ ::strlen( kDSNativeAttrTypePrefix ) + ::strlen( inAttrType ) + 1 ];
			::strcpy( outResult, kDSNativeAttrTypePrefix );
			::strcat( outResult, inAttrType );
		}
	}

	return( outResult );

} // MapNetInfoAttrToDSType


// ---------------------------------------------------------------------------
//	* MapRecToNetInfoType
// ---------------------------------------------------------------------------

char* CNiPlugIn::MapRecToNetInfoType ( const char *inRecType )
{
	char	   *outResult	= nil;
	uInt32		uiStrLen	= 0;
	uInt32		uiNativeLen	= ::strlen( kDSNativeRecordTypePrefix );
	uInt32		uiStdLen	= ::strlen( kDSStdRecordTypePrefix );


	// xxxx check for imbedded '/'

	if ( inRecType != nil )
	{
		uiStrLen = ::strlen( inRecType );

		// First look for native record type
		if ( ::strncmp( inRecType, kDSNativeRecordTypePrefix, uiNativeLen ) == 0 )
		{
			// Make sure we have data past the prefix
			if ( uiStrLen > uiNativeLen )
			{
				uiStrLen = uiStrLen - uiNativeLen;
				outResult = new char[ uiStrLen + 2 ];
				::strcpy( outResult, inRecType + uiNativeLen );
			}
		}
		else if ( ::strncmp( inRecType, kDSStdRecordTypePrefix, uiStdLen ) == 0 )
		{
			for ( int i = 0; i < kRecConsts; i++ )
			{
				if ( ::strcmp( inRecType, sRecMap[ i ][ 0 ] ) == 0 )
				{
					outResult = new char[ ::strlen( sRecMap[ i ][ 1 ] ) + 1 ];
					::strcpy( outResult, sRecMap[ i ][ 1 ] );
					break;
				}
			}
		}
		//this is not possible ie. ni_pathsearch needs a NI record type
		//else if ( ::strcmp( inRecType, kDSRecordsAll ) == 0 )
		//{
			//outResult = new char[ ::strlen( kDSRecordsAll ) + 1 ];
			//::strcpy( outResult, kDSRecordsAll );
		//}
	}

	return( outResult );

} // MapRecToNetInfoType


// ---------------------------------------------------------------------------
//	* MapNetInfoRecToDSType
// ---------------------------------------------------------------------------

char* CNiPlugIn::MapNetInfoRecToDSType ( const char *inRecType )
{
	char	   *outResult	= nil;

	if ( inRecType != nil )
	{
		// Look for a standard type
		for ( int i = 0; i < kRecConsts; i++ )
		{
			if ( ::strcmp( inRecType, sRecMap[ i ][ 1 ] ) == 0 )
			{
				outResult = new char[ ::strlen( sRecMap[ i ][ 0 ] ) + 1 ];
				::strcpy( outResult, sRecMap[ i ][ 0 ] );
				break;
			}
		}

		if ( outResult == nil )
		{
			outResult = new char[ ::strlen( kDSNativeRecordTypePrefix ) + ::strlen( inRecType ) + 1 ];
			::strcpy( outResult, kDSNativeRecordTypePrefix );
			::strcat( outResult, inRecType );
		}
	}

	return( outResult );

} // MapNetInfoRecToDSType


// ---------------------------------------------------------------------------
//	* GetAuthTypeStr
// ---------------------------------------------------------------------------

CString* CNiPlugIn::GetAuthTypeStr ( const char *inNativeAuthStr )
{
	CString	   *pOutString	= nil;

	if ( inNativeAuthStr == nil )
	{
		return( nil );
	}

	pOutString = new CString( 128 );
	if ( pOutString == nil )
	{
		return( nil );
	}
#ifdef TIM_CLIENT_PRESENT
	if ( ::strcmp( inNativeAuthStr, AppleStandardCleartext ) == 0 )
	{
		pOutString->Set( kDSStdAuthClearText );
	}
	else if ( ::strcmp( inNativeAuthStr, AppleStandardTwoWay ) == 0 )
	{
		pOutString->Set( kDSStdAuth2WayRandom );
	}
	else if ( ::strcmp( inNativeAuthStr, AppleStandardSMBNTKey ) == 0 )
	{
		pOutString->Set( kDSStdAuthSMB_NT_Key );
	}
	else if ( ::strcmp( inNativeAuthStr, AppleStandardSMBLMKey ) == 0 )
	{
		pOutString->Set( kDSStdAuthSMB_LM_Key );
	}
	else
#endif
	{
		pOutString->Set( kDSNativeAuthMethodPrefix );
		pOutString->Append( inNativeAuthStr );
	}

	return( pOutString );

} // GetAuthTypeStr


// ---------------------------------------------------------------------------
//	* GetAuthString
// ---------------------------------------------------------------------------

CString* CNiPlugIn::GetAuthString ( tDataNode *inData )
{
	CString		   *pOutString		= nil;

#ifdef TIM_CLIENT_PRESENT
	char		   *p				= nil;
	uInt32			uiNativeLen		= 0;
	uInt32			uiStrLen		= 0;

	if ( inData == nil )
	{
		return( nil );
	}

	pOutString = new CString( 128 );
	if ( pOutString == nil )
	{
		return( nil );
	}

	p = (char *)inData->fBufferData;

	if ( ::strcmp( p, kDSStdAuthClearText ) == 0 )
	{
		// Clear text auth method
		pOutString->Set( AppleStandardCleartext );
	}
	else if ( (::strcmp( p, kDSStdAuthNodeNativeClearTextOK ) == 0) || 
			  (::strcmp( p, kDSStdAuthNodeNativeNoClearText) == 0 ) )
	{
		// Node native auth method
		pOutString->Set( AppleStandardTwoWay );
	}
	else if ( ::strcmp( p, kDSStdAuth2WayRandom ) == 0 )
	{
		// Two way random auth method
		pOutString->Set( AppleStandardTwoWay );
	}
	else if ( ::strcmp( p, kDSStdAuthSetPasswd ) == 0 )
	{
		// Admin set password
		pOutString->Set( AppleStandardSetPassword );
	}
	else if ( ::strcmp( p, kDSStdAuthChangePasswd ) == 0 )
	{
		// User change password
		pOutString->Set( AppleSetPasswordV2 );
	}
	else if ( ::strcmp( p, kDSStdAuthSetPasswdAsRoot ) == 0 )
	{
		// User change password
		pOutString->Set( AppleSetPasswordV2 );
	}
	else if ( ::strcmp( p, kDSStdAuthSMB_NT_Key ) == 0 )
	{
		// User change password
		pOutString->Set( AppleStandardSMBNTKey );
	}
	else if ( ::strcmp( p, kDSStdAuthSMB_LM_Key ) == 0 )
	{
		// User change password
		pOutString->Set( AppleStandardSMBLMKey );
	}
	else
	{
		uiStrLen = ::strlen( p );
		uiNativeLen	= ::strlen( kDSNativeAuthMethodPrefix );

		if ( ::strncmp( p, kDSNativeAuthMethodPrefix, uiNativeLen ) == 0 )
		{
			// Make sure we have data past the prefix
			if ( uiStrLen > uiNativeLen )
			{
				uiStrLen = uiStrLen - uiNativeLen;
				pOutString->Set( p + uiNativeLen );
			}
		}
		else
		{
			delete( pOutString );
			pOutString = nil;
		}
	}
#endif
	return( pOutString );

} // GetAuthString


// ---------------------------------------------------------------------------
//	* GetAuthMethod
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::GetAuthMethod ( tDataNode *inData, uInt32 *outAuthMethod )
{
	sInt32			siResult		= eDSNoErr;
	uInt32			uiNativeLen		= 0;
	char		   *p				= nil;

	if ( inData == nil )
	{
		*outAuthMethod = kAuthUnknownMethod;
#ifdef DEBUG
		return( eDSAuthParameterError );
#else
		return( eDSAuthFailed );
#endif
	}

	p = (char *)inData->fBufferData;

	DBGLOG1( kLogPlugin, "Using authentication method %s.", p );

	if ( ::strcmp( p, kDSStdAuthClearText ) == 0 )
	{
		// Clear text auth method
		*outAuthMethod = kAuthClearText;
	}
	else if ( ::strcmp( p, kDSStdAuthNodeNativeClearTextOK ) == 0 )
	{
		// Node native auth method
		*outAuthMethod = kAuthNativeClearTextOK;
	}
	else if ( ::strcmp( p, kDSStdAuthNodeNativeNoClearText ) == 0 )
	{
		// Node native auth method
		*outAuthMethod = kAuthNativeNoClearText;
	}
	else if ( ::strcmp( p, kDSStdAuthCrypt ) == 0 )
	{
		// Unix Crypt auth method
		*outAuthMethod = kAuthCrypt;
	}
	else if ( ::strcmp( p, kDSStdAuth2WayRandom ) == 0 )
	{
		// Two way random auth method
		*outAuthMethod = kAuth2WayRandom;
	}
	else if ( ::strcmp( p, kDSStdAuth2WayRandomChangePasswd ) == 0 )
	{
		// Two way random auth method
		*outAuthMethod = kAuth2WayRandomChangePass;
	}
	else if ( ::strcmp( p, kDSStdAuthSMB_NT_Key ) == 0 )
	{
		// Two way random auth method
		*outAuthMethod = kAuthSMB_NT_Key;
	}
	else if ( ::strcmp( p, kDSStdAuthSMB_LM_Key ) == 0 )
	{
		// Two way random auth method
		*outAuthMethod = kAuthSMB_LM_Key;
	}
	else if ( ::strcmp( p, kDSStdAuthSecureHash ) == 0 )
	{
		// secure hash method
		*outAuthMethod = kAuthSecureHash;
	}
	else if ( ::strcmp( p, kDSStdAuthReadSecureHash ) == 0 )
	{
		// secure hash method
		*outAuthMethod = kAuthReadSecureHash;
	}
	else if ( ::strcmp( p, kDSStdAuthWriteSecureHash ) == 0 )
	{
		// secure hash method
		*outAuthMethod = kAuthWriteSecureHash;
	}
	else if ( ::strcmp( p, kDSStdAuthSetPasswd ) == 0 )
	{
		// Admin set password
		*outAuthMethod = kAuthSetPasswd;
	}
	else if ( ::strcmp( p, kDSStdAuthSetPasswdAsRoot ) == 0 )
	{
		// Admin set password
		*outAuthMethod = kAuthSetPasswdAsRoot;
	}
	else if ( ::strcmp( p, kDSStdAuthChangePasswd ) == 0 )
	{
		// User change password
		*outAuthMethod = kAuthChangePasswd;
	}
	else if ( ::strcmp( p, kDSStdAuthAPOP ) == 0 )
	{
		// APOP auth method
		*outAuthMethod = kAuthAPOP;
	}
	else if ( ::strcmp( p, kDSStdAuthCRAM_MD5 ) == 0 )
	{
		// CRAM-MD5 auth method
		*outAuthMethod = kAuthCRAM_MD5;
	}
	else if ( ::strcmp( p, kDSStdAuthSetPolicy ) == 0 )
	{
		*outAuthMethod = kAuthSetPolicy;
	}
	else if ( ::strcmp( p, kDSStdAuthWithAuthorizationRef ) == 0 )
	{
		*outAuthMethod = kAuthWithAuthorizationRef;
	}
	else
	{
		uiNativeLen	= ::strlen( kDSNativeAuthMethodPrefix );

		if ( ::strncmp( p, kDSNativeAuthMethodPrefix, uiNativeLen ) == 0 )
		{
			// User change password
			*outAuthMethod = kAuthNativeMethod;
		}
		else
		{
			*outAuthMethod = kAuthUnknownMethod;
			siResult = eDSAuthMethodNotSupported;
		}
	}
	
	return( siResult );

} // GetAuthMethod


// ---------------------------------------------------------------------------
//	* IsValidRecordName
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::IsValidRecordName ( const char	*inRecName,
									  const char	*inRecType,
									  void			*inDomain,
									  ni_id			&outDirID )
{
	sInt32			siResult	= eDSInvalidRecordName;
	char		   *pData		= nil;
	ni_status		niStatus	= NI_OK;

	gNetInfoMutex->Wait();

	try
	{
		if ( inDomain == nil ) throw( (sInt32)eDSInvalidDomain );
		if ( inRecName == nil ) throw( (sInt32)eDSInvalidRecordName );
		if ( inRecType == nil ) throw( (sInt32)eDSInvalidRecordType );

		pData = BuildRecordNamePath( inRecName, inRecType );
		if ( pData != nil )
		{
			niStatus = ::ni_pathsearch( inDomain, &outDirID, pData );
			if ( niStatus == NI_OK )
			{
				siResult = eDSNoErr;
			}
			free( pData );
		}
	}

	catch( sInt32 err )
	{
		siResult = err;
	}

	gNetInfoMutex->Signal();

	return( siResult );

} // IsValidRecordName


// ---------------------------------------------------------------------------
//	* BuildRecordNamePath
// ---------------------------------------------------------------------------

char *CNiPlugIn:: BuildRecordNamePath (	const char	*inRecName,
										const char	*inRecType )
{
	const char	   *pData		= nil;
	CString			csPath( 128 );
	char		   *outPath		= nil;
	uInt32			recNameLen	= 0;
	uInt32			nativeLen	= 0;
	uInt32			stdLen		= 0;

	try
	{
		if ( inRecName == nil ) throw( (sInt32)eDSInvalidRecordName );
		if ( inRecType == nil ) throw( (sInt32)eDSInvalidRecordType );
		
		recNameLen	= ::strlen( inRecName );
		nativeLen	= ::strlen( kDSNativeAttrTypePrefix );
		stdLen		= ::strlen( kDSStdAttrTypePrefix );

		if ( ::strncmp( inRecName, kDSNativeAttrTypePrefix, nativeLen ) == 0  )
		{
			if ( recNameLen > nativeLen )
			{
				pData = inRecName + nativeLen;
			}
		}
		else if ( ::strncmp( inRecName, kDSStdAttrTypePrefix, stdLen ) == 0  )
		{
			if ( recNameLen > stdLen )
			{
				pData = inRecName + stdLen;
			}
		}
		else
		{
			pData = inRecName;
		}

		if ( pData != nil )
		{
			//KW check if the recordname has "/" or "\\" in it
			//if so then replace the "/" with "\\/" so that NetInfo can handle the forward slashes
			//separate of the subdirectory delimiters
			//also replace the "\\" with "\\\\" so that NetInfo can handle the backslashes if
			//the intent is for them to be inside the name itself
			if ( (::strstr( pData, "/" ) != nil) || (::strstr( pData, "\\" ) != nil) )
			{
				csPath.Set( "/" );
				csPath.Append( inRecType );
				csPath.Append( "/" );
				while(pData[0] != '\0')
				{
					if (pData[0] == '/')
					{
						csPath.Append( "\\/" );
					}
					else if (pData[0] == '\\')
					{
						csPath.Append( "\\\\" );
					}
					else
					{
						csPath.Append( pData[0] );
					}
					pData++;
				}
			}
			else
			{
				csPath.Set( "/" );
				csPath.Append( inRecType );
				csPath.Append( "/" );
				csPath.Append( pData );

			}
			//check for the case of trying to access the root of netinfo above all the records
			//ie. record type was "/" and record name was also "/" which led to csPath of "///\\/"
			if (strcmp(csPath.GetData(),"///\\/") == 0)
			{
				outPath = (char *)::calloc( 2, sizeof(char));
				if ( outPath == nil ) throw( (sInt32)eMemoryError );
				strcpy(outPath,"/");
			}
			else
			{
				outPath = (char *)::calloc( csPath.GetLength() + 1, sizeof(char));
				if ( outPath == nil ) throw( (sInt32)eMemoryError );
				strcpy(outPath,csPath.GetData());
			}
		}
	}

	catch( sInt32 err )
	{
		if (outPath != nil)
		{
			free(outPath);
			outPath = nil;
		}
	}

	return( outPath );

} // BuildRecordNamePath

// ---------------------------------------------------------------------------
//	* IsTimRunning
//    - check for a tim server on the local domain
// ---------------------------------------------------------------------------

bool CNiPlugIn::IsTimRunning()
{
	bool bTimRunning = false;
#ifdef TIM_CLIENT_PRESENT
	TIMHandle* timHandle = NULL;
	ni_status niStatus = NI_OK;
	void* domain = NULL;
	Buffer *tagBuffer;
	int timSessionStatus;
	
	//use set value if 60 secs have not gone by
	if ( time(nil) < gCheckTimAgainTime )
	{
		return gTimIsRunning;
	}

	gNetInfoMutex->Wait();

	gTimIsRunning		= false;
	gCheckTimAgainTime	= time( nil) + 60;

	niStatus = ::ni_open( NULL, ".", &domain );
	if (niStatus == NI_OK)
	{
		timHandle = ::timServerForDomain( domain );
		if (timHandle != NULL)
		{
			//checking here now if tim was properly configured and not just started by mistake
			tagBuffer = bufferFromString("local");
			timHandleSetTag(timHandle, tagBuffer);
			bufferRelease(tagBuffer);
	
			tagBuffer = bufferFromString("Status NetInfo Domain");
			timSessionStatus = timSessionStart(timHandle, TimOpUtility, tagBuffer);
			bufferRelease(tagBuffer);
			if (timSessionStatus == TimStatusOK)
			{
				timSessionStatus = timSessionStep(timHandle, NULL, &tagBuffer);
				bufferRelease(tagBuffer);
				if (timSessionStatus == TimStatusOK)
				{
					bTimRunning		= true;
					gTimIsRunning	= true;
				}	
				timSessionEnd(timHandle);
			}
			::timHandleFree( timHandle );
			timHandle = NULL;
		}
		ni_free(domain);
		domain = NULL;
	}

	gNetInfoMutex->Signal();
#endif
	return bTimRunning;
} // IsTimRunning()

// ---------------------------------------------------------------------------
//	* MapAuthResult
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::MapAuthResult ( sInt32 inAuthErr )
{
	sInt32		siResult	= eUndefinedError;

	switch ( inAuthErr )
	{
#ifdef TIM_CLIENT_PRESENT
		case TimStatusOK:
			siResult = eDSNoErr;
			break;

#ifdef DEBUG
		case TimStatusContinue:
			siResult = eDSContinue;
			break;

		case TimStatusServerTimeout:
			siResult = eDSServerTimeout;
			break;

		case TimStatusInvalidHandle:
			siResult = eDSInvalidHandle;
			break;

		case TimStatusSendFailed:
			siResult = eDSSendFailed;
			break;

		case TimStatusReceiveFailed:
			siResult = eDSReceiveFailed;
			break;

		case TimStatusBadPacket:
			siResult = eDSBadPacket;
			break;

		case TimStatusInvalidTag:
			siResult = eDSInvalidTag;
			break;

		case TimStatusInvalidSession:
			siResult = eDSInvalidSession;
			break;

		case TimStatusInvalidName:
			siResult = eDSInvalidName;
			break;

		case TimStatusUserUnknown:
			siResult = eDSUserUnknown;
			break;

		case TimStatusUnrecoverablePassword:
			siResult = eDSUnrecoverablePassword;
			break;

		case TimStatusAuthenticationFailed:
			siResult = eDSAuthenticationFailed;
			break;

		case TimStatusBogusServer:
			siResult = eDSBogusServer;
			break;

		case TimStatusOperationFailed:
			siResult = eDSOperationFailed;
			break;

		case TimStatusNotAuthorized:
			siResult = eDSNotAuthorized;
			break;

		case TimStatusNetInfoError:
			siResult = eDSNetInfoError;
			break;

		case TimStatusContactMaster:
			siResult = eDSContactMaster;
			break;

		case TimStatusServiceUnavailable:
			siResult = eDSServiceUnavailable;
			break;
#endif
#endif
		default:
			siResult = eDSAuthFailed;
			break;
	}

	return( siResult );

} // MapAuthResult

// ---------------------------------------------------------------------------
//	* MapNetInfoErrors
//
//		- xxxx make sure i do the right thing with the errors
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::MapNetInfoErrors ( sInt32 inNiError )
{
	sInt32		siOutError	= 0;

	switch ( inNiError )
	{
		case NI_OK:
			siOutError = eDSNoErr;
			break;

		case NI_BADID:
			siOutError = eUnknownPlugIn;
			break;

		case NI_STALE:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NOSPACE:
			siOutError = eUnknownPlugIn;
			break;

		case NI_PERM:
			siOutError = eDSPermissionError;
			break;

		case NI_NODIR:
			siOutError = eDSInvalidRecordName;
			break;

		case NI_NOPROP:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NONAME:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NOTEMPTY:
			siOutError = eUnknownPlugIn;
			break;

		case NI_UNRELATED:
			siOutError = eUnknownPlugIn;
			break;

		case NI_SERIAL:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NETROOT:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NORESPONSE:
			siOutError = eUnknownPlugIn;
			break;

		case NI_RDONLY:
			siOutError = eDSReadOnly;
			break;

		case NI_SYSTEMERR:
			siOutError = eUnknownPlugIn;
			break;

		case NI_ALIVE:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NOTMASTER:
			siOutError = eUnknownPlugIn;
			break;

		case NI_CANTFINDADDRESS:
			siOutError = eUnknownPlugIn;
			break;

		case NI_DUPTAG:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NOTAG:
			siOutError = eUnknownPlugIn;
			break;

		case NI_AUTHERROR:
			siOutError = eUnknownPlugIn;
			break;

		case NI_NOUSER:
			siOutError = eUnknownPlugIn;
			break;

		case NI_MASTERBUSY:
			siOutError = eUnknownPlugIn;
			break;

		case NI_INVALIDDOMAIN:
			siOutError = eDSInvalidDomain;
			break;

		case NI_BADOP:
			siOutError = eUnknownPlugIn;
			break;

		case NI_FAILED:
			siOutError = eUnknownPlugIn;
			break;

		default:
			siOutError = eUnknownPlugIn;
			break;
	}

	return( siOutError );

} // MapNetInfoErrors


// ---------------------------------------------------------------------------
//	* CalcCRC
// ---------------------------------------------------------------------------

uInt32 CNiPlugIn::CalcCRC ( const char *inStr )
{
	const char 	   *p			= inStr;
	sInt32			siI			= 0;
	sInt32			siStrLen	= 0;
	uInt32			uiCRC		= 0xFFFFFFFF;
	CRCCalc			aCRCCalc;

	if ( inStr != nil )
	{
		siStrLen = ::strlen( inStr );

		for ( siI = 0; siI < siStrLen; ++siI )
		{
			uiCRC = aCRCCalc.UPDC32( *p, uiCRC );
			p++;
		}
	}

	return( uiCRC );

} // CalcCRC


// ---------------------------------------------------------------------------
//	* MakeGood
// ---------------------------------------------------------------------------

void CNiPlugIn::MakeGood( char *inStr, char *outStr )
{
	uInt32	uiI		= 0;
	uInt32	uiJ		= 0;

	if ( (inStr != nil) && (outStr != nil) )
	{
		while ( inStr[ uiI ] != '\0' )
		{
			if ( (inStr[ uiI ] == '\\') && (inStr[ uiI + 1 ] == '/') )
			{
				// Skip past the escape before the separator char
				uiI++;
			}

			outStr[ uiJ ] = inStr[ uiI ];			
			uiI++;
			uiJ++;
		}
		outStr[ uiJ ] = '\0';
	}
} // MakeGood


// ---------------------------------------------------------------------------
//	* MakeContextData
// ---------------------------------------------------------------------------

sNIContextData* CNiPlugIn::MakeContextData ( void )
{
	sNIContextData	*pOut	= nil;

	pOut = (sNIContextData *) ::calloc( 1, sizeof( sNIContextData) );
	pOut->fUID = (uid_t)-2;
	pOut->fEffectiveUID = (uid_t)-2;
	pOut->bIsLocal = false;

	return( pOut );

} // MakeContextData

// ---------------------------------------------------------------------------
//	* CleanContextData
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::CleanContextData ( sNIContextData *inContext )
{
    sInt32	siResult = eDSNoErr;
    
    if ( inContext == nil )
    {
        siResult = eDSBadContextData;
	}
    else
    {
		if (inContext->fDomain != nil)
		{
			if ( inContext->fDontUseSafeClose )
			{
				gNetInfoMutex->Wait();
				::ni_free(inContext->fDomain);
				gNetInfoMutex->Signal();
				inContext->fDomain = nil;
			}
			else if (inContext->fDomainName != nil)
			{
				CNetInfoPlugin::SafeClose( inContext->fDomainName );
				inContext->fDomain = nil;
			}
		}
		//now we do own this pointer
		if (inContext->fDomainName != nil)
		{
			free(inContext->fDomainName);
			inContext->fDomainName = nil;
		}
		if (inContext->fRecType != nil)
		{
			free(inContext->fRecType);
			inContext->fRecType = nil;
		}
		if (inContext->fRecName != nil)
		{
			free(inContext->fRecName);
			inContext->fRecName = nil;
		}
		if (inContext->fAuthenticatedUserName != NULL)
		{
			free(inContext->fAuthenticatedUserName);
			inContext->fAuthenticatedUserName = NULL;
		}
		//dirID is a struct of two ulongs
		inContext->offset				= 0;
		inContext->index				= 0;
		inContext->fDontUseSafeClose	= false;
		inContext->bIsLocal				= false;
        
        if ( inContext->fPWSNodeRef != 0 )
            dsCloseDirNode(inContext->fPWSNodeRef);
        if ( inContext->fPWSRef != 0 )
            dsCloseDirService(inContext->fPWSRef);
	}
    
	return( siResult );

} // CleanContextData


// ---------------------------------------------------------------------------
//	* DoesThisMatch
// ---------------------------------------------------------------------------

bool CNiPlugIn::DoesThisMatch ( const char		   *inString,
								const char		   *inPatt,
								tDirPatternMatch	inHow )
{
	bool		bOutResult	= false;
	CFMutableStringRef	strRef	= CFStringCreateMutable(NULL, 0);
	CFMutableStringRef	patRef	= CFStringCreateMutable(NULL, 0);
	CFRange		range;

	if ( (inString == nil) || (inPatt == nil) || (strRef == nil) || (patRef == nil) )
	{
		return( false );
	}

	CFStringAppendCString( strRef, inString, kCFStringEncodingUTF8 );
	CFStringAppendCString( patRef, inPatt, kCFStringEncodingUTF8 );	
	if ( (inHow >= eDSiExact) && (inHow <= eDSiRegularExpression) )
	{
		CFStringUppercase( strRef, NULL );
		CFStringUppercase( patRef, NULL );
	}

	switch ( inHow )
	{
		case eDSExact:
		case eDSiExact:
		{
			if ( CFStringCompare( strRef, patRef, 0 ) == kCFCompareEqualTo )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSStartsWith:
		case eDSiStartsWith:
		{
			if ( CFStringHasPrefix( strRef, patRef ) )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSEndsWith:
		case eDSiEndsWith:
		{
			if ( CFStringHasSuffix( strRef, patRef ) )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSContains:
		case eDSiContains:
		{
			range = CFStringFind( strRef, patRef, 0 );
			if ( range.location != kCFNotFound )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSLessThan:
		case eDSiLessThan:
		{
			if ( CFStringCompare( strRef, patRef, 0 ) == kCFCompareLessThan )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSGreaterThan:
		case eDSiGreaterThan:
		{
			if ( CFStringCompare( strRef, patRef, 0 ) == kCFCompareGreaterThan )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSLessEqual:
		case eDSiLessEqual:
		{
			if ( CFStringCompare( strRef, patRef, 0 ) != kCFCompareGreaterThan )
			{
				bOutResult = true;
			}
		}
		break;

		case eDSGreaterEqual:
		case eDSiGreaterEqual:
		{
			if ( CFStringCompare( strRef, patRef, 0 ) != kCFCompareLessThan )
			{
				bOutResult = true;
			}
		}
		break;

		default:
			break;
	}

	CFRelease( strRef );
	strRef = nil;
	CFRelease( patRef );
	patRef = nil;

	return( bOutResult );

} // DoesThisMatch


// ---------------------------------------------------------------------------
//	* BuildRegExp
// ---------------------------------------------------------------------------

char* CNiPlugIn:: BuildRegExp ( const char *inString )

{
	char		   *outRegExp	= nil;
	unsigned long  	i			= 0;
	unsigned long	j			= 0;
	unsigned long	inLength	= 0;

	if (inString == nil)
	{
		return( outRegExp );
	}

	// JT we need to first make sure the string is composed UTF8
	//allocate at most twice the length plus one ie. assume worst case to escape every character
	outRegExp = (char *) calloc( 1, 1 + 2*strlen(inString) );

	j = 0;
	inLength = strlen(inString);
	for (i=0; i< inLength; i++)
	{
		//workaround since ni_search cannot handle UTF-8 characters
		//regex used by ni_search also has problems with ')' and '('
		if ( (isascii(inString[i])) && ( (inString[i]) != '(' ) && ( (inString[i]) != ')' ) )
		{
			if (isalnum(inString[i]) || isspace(inString[i]) || (inString[i]) == '_' || (inString[i]) == '-')
			{
				memcpy(outRegExp+j,&inString[i],1);
				j++;
			}
			else
			{
				memcpy(outRegExp+j,"\\",1);
				j++;
				memcpy(outRegExp+j,& inString[i],1);
				j++;
			}
		}
		else
		{
			memcpy(outRegExp+j,".",1);
			j++;
		}
	}

	return( outRegExp );

} // BuildRegExp


// ---------------------------------------------------------------------------
//	* ContinueDeallocProc
// ---------------------------------------------------------------------------

void CNiPlugIn::ContinueDeallocProc ( void* inContinueData )
{
	sNIContinueData *pContinue = (sNIContinueData *)inContinueData;

	if ( pContinue != nil )
	{
	// fAuthHndl is only used in DoMultiPassAuth
	// and it is freed? there by timSessionEnd
		if ( pContinue->fAliasList != nil )
		{
			::dsDataListDeallocatePriv( pContinue->fAliasList );
			//need to free the header as well
			free( pContinue->fAliasList );
			pContinue->fAliasList = nil;
		}

		if ( pContinue->fAliasAttribute != nil )
		{
			::dsDataListDeallocatePriv( pContinue->fAliasAttribute );
			//need to free the header as well
			free( pContinue->fAliasAttribute );
			pContinue->fAliasAttribute = nil;
		}

		if ( pContinue->fDataBuff != nil )
		{
			::dsDataBufferDeallocatePriv( pContinue->fDataBuff );
			pContinue->fDataBuff = nil;
		}
		
		if ( pContinue->fNIEntryList.ni_entrylist_len > 0 )
		{
			::ni_entrylist_free(&(pContinue->fNIEntryList));
			pContinue->fNIEntryList.ni_entrylist_len = 0;
		}
		
		free( pContinue );
		pContinue = nil;
	}
} // ContinueDeallocProc


// ---------------------------------------------------------------------------
//	* ContextDeallocProc
// ---------------------------------------------------------------------------

void CNiPlugIn::ContextDeallocProc ( void* inContextData )
{
	sNIContextData *pContext = (sNIContextData *) inContextData;

	if ( pContext != nil )
	{
		CleanContextData( pContext );
		
		free( pContext );
		pContext = nil;
	}
} // ContextDeallocProc


// ---------------------------------------------------------------------------
//	* BuildDomainPathFromName
// ---------------------------------------------------------------------------

char* CNiPlugIn::BuildDomainPathFromName( char* inDomainName )
{
	CString		csPathStr( 128 );
	char	   *pathStr = nil;	

	if ( inDomainName == NULL )
		return NULL;
	
	if (::strncmp( inDomainName, kstrRootName, ::strlen( kstrRootName ) ) != 0)
	{
		return nil;
	}
	
	// Check for local domain
	if ( (::strcmp( inDomainName, kstrLocalDomain ) == 0) ||
		 (::strcmp( inDomainName + ::strlen( kstrRootNodeName ), kstrLocalDot ) == 0) ||
		 (::strcmp( inDomainName, kstrDefaultLocalNodeName ) == 0 ) )
	{
		csPathStr.Set( "." );
	}
	else if ( (::strcmp( inDomainName, kstrRootName ) == 0) ||
			  (::strcmp( inDomainName, kstrPrefixName ) == 0) ||
			  (::strcmp( inDomainName, kstrRootNodeName ) == 0) )
	{
		// Fully qualified root
		csPathStr.Set( kstrDelimiter );
	}
	else if ( ::strcmp( inDomainName, kstrParentDomain ) == 0 )
	{
		csPathStr.Set( ".." );
	}
	else
	{
		csPathStr.Set( inDomainName + 13 ); //this strips off the first "/NetInfo/root" prefix
	}

	pathStr = (char *)::calloc( csPathStr.GetLength() + 1, sizeof( char ) );
	strcpy( pathStr, csPathStr.GetData());
	
	return pathStr;
} // BuildDomainPathFromName


// ---------------------------------------------------------------------------
//	* IsValidRealname
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::IsValidRealname ( char   *inRealname,
									void   *inDomain,
									char  **outRecName )
{
	sInt32			siResult	= eDSInvalidRecordName;
	ni_id			niDirID;
	ni_status		niStatus	= NI_OK;
	char		   *inConstRegExpRecName	= nil;
	ni_entrylist	niEntryList;
	u_int			en			= 0;
	ni_entry		niEntry;
	ni_proplist		niPropList;
	ni_index		niIndex		= 0;
	ni_index		niIndexComp	= 0;

	gNetInfoMutex->Wait();

	NI_INIT( &niDirID );
	NI_INIT( &niEntry );
	NI_INIT( &niPropList );
	NI_INIT( &niEntryList );
	
	niStatus = ::ni_pathsearch( inDomain, &niDirID, "/users" );
	if ( niStatus == NI_OK )
	{
		//make the reg exp that ni_search needs
		inConstRegExpRecName = BuildRegExp(inRealname);
		niStatus = ::ni_search( inDomain, &niDirID, (char *)"realname", inConstRegExpRecName, REG_ICASE, &niEntryList );
		free(inConstRegExpRecName);
		if ( niStatus == NI_OK )
		{
			for ( en = 0; en < niEntryList.ni_entrylist_len; en++ )
			{
				niEntry = niEntryList.ni_entrylist_val[ en ];
				niDirID.nii_object = niEntry.id;

				niStatus = ::ni_read( inDomain, &niDirID, &niPropList );
				if ( niStatus == NI_OK )
				{
					niIndex = ::ni_proplist_match( niPropList, "realname", NULL );
					if ( niIndex != NI_INDEX_NULL )
					{
						//determine if the regcomp operation on inConstRegExpRecName for NetInfo actually
						//returned what was requested ie. it will always return at least it but probably much more
						//so filter out the much more
						if (DoesThisMatch( (const char*)(niPropList.nipl_val[ niIndex ].nip_val.ninl_val[ 0 ]),
											(const char*)inRealname, eDSExact ))
						{
							//get the record name now that we found a match
							niIndexComp = ::ni_proplist_match( niPropList, "name", NULL );
							if ( niIndexComp != NI_INDEX_NULL )
							{
								//pass out the record name value
								*outRecName = (char *) calloc( 1, 1 + strlen(niPropList.nipl_val[ niIndexComp ].nip_val.ninl_val[ 0 ]) );
								strcpy(*outRecName,niPropList.nipl_val[ niIndexComp ].nip_val.ninl_val[ 0 ]);
								siResult = eDSNoErr;
								::ni_proplist_free( &niPropList );
								break;
							}
						}
					} // if ( niIndex != NI_INDEX_NULL )
					::ni_proplist_free( &niPropList );
				}
			}// loop over possible matches
			
			if (niEntryList.ni_entrylist_len > 0)
			{
				::ni_entrylist_free( &niEntryList );
				niEntryList.ni_entrylist_len = 0;
			}
		
		} // found some realname matches using ni_search
								
	} //if ( niStatus == NI_OK ) for ::ni_pathsearch( inDomain, &niDirID, "/users" );

	gNetInfoMutex->Signal();

	return( siResult );

} // IsValidRealname


// ---------------------------------------------------------------------------
//	* UserIsAdmin
// ---------------------------------------------------------------------------

bool CNiPlugIn::UserIsAdmin ( const char   *inUserName,
							  void   *inDomain )
{
	ni_id			niDirID;
	ni_status		niStatus	= NI_OK;
	ni_proplist		niPropList;
	ni_index		niIndex		= 0;
	bool			isAdmin = false;

	if ( inUserName == NULL || inDomain == NULL ) {
		return false;
	}
	
	gNetInfoMutex->Wait();

	niStatus = ::ni_pathsearch( inDomain, &niDirID, "/groups/admin" );

	if (niStatus == NI_OK) {
		niStatus = ::ni_read( inDomain, &niDirID, &niPropList );

		if (niStatus == NI_OK) {
			niIndex = ::ni_proplist_match( niPropList, "users", nil );
			if (niIndex != NI_INDEX_NULL) {
				niIndex = ni_namelist_match(niPropList.nipl_val[niIndex].nip_val,inUserName);
				isAdmin = (niIndex != NI_INDEX_NULL);
			}
			::ni_proplist_free( &niPropList );
		}
	}
	
	gNetInfoMutex->Signal();

	return isAdmin;
}


// ---------------------------------------------------------------------------
//	* GetUserNameForUID
// ---------------------------------------------------------------------------

char* CNiPlugIn::GetUserNameForUID ( uid_t   inUserID,
									 void   *inDomain )
{
	ni_id			niDirID;
	ni_status		niStatus	= NI_OK;
	ni_proplist		niPropList;
	ni_entry		niEntry;
	ni_entrylist	niEntryList;
	ni_namelist		niValues;
	char			idStr[ 64 ] = { 0 };
	u_int			en			= 0;
	char*			userName	= NULL;

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

	gNetInfoMutex->Wait();

	NI_INIT( &niDirID );
	NI_INIT( &niEntry );
	NI_INIT( &niPropList );
	NI_INIT( &niEntryList );
	NI_INIT( &niValues );

	niStatus = ::ni_pathsearch( inDomain, &niDirID, "/users" );

	if (niStatus == NI_OK) {
		// Do the search
		//make the reg exp that ni_search needs
		snprintf( idStr, 64, "%d", inUserID );
		niStatus = ni_search( inDomain, &niDirID, "uid", idStr, REG_BASIC, &niEntryList );
		if ( niStatus == NI_OK )
		{
			for ( en = 0; en < niEntryList.ni_entrylist_len; en++ )
			{
				niEntry = niEntryList.ni_entrylist_val[ en ];
				niDirID.nii_object = niEntry.id;

				niStatus = ni_lookupprop( inDomain, &niDirID, "name", &niValues );

				if ( niStatus == NI_OK )
				{
					if (niValues.ni_namelist_len > 0)
					{
						if (niValues.ni_namelist_val[0] != NULL)
						{
							userName = strdup( niValues.ni_namelist_val[0] );
							ni_namelist_free( &niValues );
							break;
						}
						ni_namelist_free( &niValues );
					}
				}
			}
		} //ni_search successful
		if ( niEntryList.ni_entrylist_len > 0 )
		{
			::ni_entrylist_free(&niEntryList);
			niEntryList.ni_entrylist_len = 0;
		}
	}

	gNetInfoMutex->Signal();

	return userName;
}


// ---------------------------------------------------------------------------
//	* GetUserNameFromAuthBuffer
//    retrieve the username from a standard auth buffer
//    buffer format should be 4 byte length followed by username, then optional
//    additional data after. Buffer length must be at least 5 (length + 1 character name)
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::GetUserNameFromAuthBuffer ( tDataBufferPtr inAuthData, unsigned long inUserNameIndex, 
											  char  **outUserName )
{
	tDataListPtr dataList = dsAuthBufferGetDataListAllocPriv(inAuthData);
	if (dataList != NULL)
	{
		*outUserName = dsDataListGetNodeStringPriv(dataList, inUserNameIndex);
		// this allocates a copy of the string
		
		dsDataListDeallocatePriv(dataList);
		free(dataList);
		dataList = NULL;
		return eDSNoErr;
	}
	return eDSInvalidBuffFormat;
}


// ---------------------------------------------------------------------------
//	* GetNetInfoAuthAuthorityHandler
// ---------------------------------------------------------------------------

NetInfoAuthAuthorityHandlerProc CNiPlugIn::GetNetInfoAuthAuthorityHandler ( const char* inTag )
{
	if (inTag == NULL)
	{
		return NULL;
	}
	for (unsigned int i = 0; i < kNetInfoAuthAuthorityHandlerProcs; ++i)
	{
		if (strcasecmp(inTag,sNetInfoAuthAuthorityHandlerProcs[i].fTag) == 0)
		{
			// found it
			return sNetInfoAuthAuthorityHandlerProcs[i].fHandler;
		}
	}
	return NULL;
}


// ---------------------------------------------------------------------------
//	* ParseAuthAuthority
//    retrieve version, tag, and data from authauthority
//    format is version;tag;data
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::ParseAuthAuthority ( const char	 * inAuthAuthority,
									   char			** outVersion,
									   char			** outAuthTag,
									   char			** outAuthData )
{
	char* authAuthority = NULL;
	char* current = NULL;
	char* tempPtr = NULL;
	sInt32 result = eDSAuthFailed;
	if ( inAuthAuthority == NULL || outVersion == NULL 
		 || outAuthTag == NULL || outAuthData == NULL )
	{
		return eDSAuthFailed;
	}
	authAuthority = strdup(inAuthAuthority);
	if (authAuthority == NULL)
	{
		return eDSAuthFailed;
	}
	current = authAuthority;
	do {
		tempPtr = strsep(&current, ";");
		if (tempPtr == NULL) break;
		*outVersion = strdup(tempPtr);
		
		tempPtr = strsep(&current, ";");
		if (tempPtr == NULL) break;
		*outAuthTag = strdup(tempPtr);
		
		tempPtr = strsep(&current, ";");
		if (tempPtr == NULL) break;
		*outAuthData = strdup(tempPtr);
		
		result = eDSNoErr;
	} while (false);
	
	free(authAuthority);
	authAuthority = NULL;
	
	if (result != eDSNoErr)
	{
		if (*outVersion != NULL)
		{
			free(*outVersion);
			*outVersion = NULL;
		}
		if (*outAuthTag != NULL)
		{
			free(*outAuthTag);
			*outAuthTag = NULL;
		}
		if (*outAuthData != NULL)
		{
			free(*outAuthData);
			*outAuthData = NULL;
		}
	}
	return result;
} // ParseAuthAuthority

// ---------------------------------------------------------------------------
//	* ParseLocalCacheUserData
//    retrieve network nodename, user recordname, and user generated UID from authdata
//    format is version;tag;data
// ---------------------------------------------------------------------------

sInt32 CNiPlugIn::ParseLocalCacheUserData ( const char	   *inAuthData,
											char		  **outNodeName,
											char		  **outRecordName,
											char		  **outGUID )
{
	char* authData = NULL;
	char* current = NULL;
	char* tempPtr = NULL;
	sInt32 result = eDSAuthFailed;

	if ( inAuthData == NULL )
	{
		return (sInt32)eDSEmptyBuffer;
	}
	if ( outNodeName == NULL )
	{
		return (sInt32)eDSEmptyNodeName;
	}
	if ( outRecordName == NULL )
	{
		return (sInt32)eDSEmptyRecordName;
	}
	if ( outGUID == NULL )
	{
		return (sInt32)eDSEmptyParameter;
	}

	authData = strdup(inAuthData);
	if (authData == NULL)
	{
		return eDSAuthFailed;
	}
	current = authData;
	do {
		tempPtr = strsep(&current, ":");
		if (tempPtr == NULL)
		{
			result = eDSEmptyNodeName;
			break;
		}
		*outNodeName = strdup(tempPtr);
		
		tempPtr = strsep(&current, ":");
		if (tempPtr == NULL)
		{
			result = eDSEmptyRecordName;
			break;
		}
		*outRecordName = strdup(tempPtr);
		
		tempPtr = strsep(&current, ":");
		if (tempPtr == NULL)
		{
			result = eDSEmptyParameter;
			break;
		}
		*outGUID = strdup(tempPtr);
		
		result = eDSNoErr;
	} while (false);
	
	free(authData);
	authData = NULL;
	
	if (result != eDSNoErr)
	{
		if (*outNodeName != NULL)
		{
			free(*outNodeName);
			*outNodeName = NULL;
		}
		if (*outRecordName != NULL)
		{
			free(*outRecordName);
			*outRecordName = NULL;
		}
		if (*outGUID != NULL)
		{
			free(*outGUID);
			*outGUID = NULL;
		}
	}
	return result;
} // ParseLocalCacheUserData
