/*
 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * 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@
 */

/*
 * Modification History
 *
 * December 20, 2002		Christophe Allie <callie@apple.com>
 * - initial revision
 */

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFRuntime.h>

#include <Security/Security.h>
#include "dy_framework.h"

#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#include <SystemConfiguration/SCValidation.h>

#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>

#include "ppp.h"


/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

typedef struct {

	/* base CFType information */
	CFRuntimeBase	cfBase;

	/* service ID */
	CFStringRef	serviceID;		/* serviceID */

	int		eventRef;		/* ref to PPP controller for event messages */
	CFSocketRef	eventRefCF;		/* ref to PPP controller for event messages */
	int		controlRef;		/* ref to PPP controller for control messages */
	//u_int32_t	status;			/* current status of the connection */
	//char		ifname[IFNAMSIZ];	/* ppp interface used for this connection */

	/* run loop source, callout, context, rl scheduling info */
	CFRunLoopSourceRef		rls;
	SCNetworkConnectionCallBack	rlsFunction;
	SCNetworkConnectionContext	rlsContext;
	CFMutableArrayRef		rlList;

} SCNetworkConnectionPrivate, *SCNetworkConnectionPrivateRef;

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static __inline__ CFTypeRef
isA_SCNetworkConnection(CFTypeRef obj)
{
	return (isA_CFType(obj, SCNetworkConnectionGetTypeID()));
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static CFStringRef
__SCNetworkConnectionCopyDescription(CFTypeRef cf)
{
	CFAllocatorRef			allocator = CFGetAllocator(cf);
	CFMutableStringRef		result;
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)cf;

	result = CFStringCreateMutable(allocator, 0);
	CFStringAppendFormat(result, NULL, CFSTR("<SCNetworkConnection, %p [%p]> {\n"), cf, allocator);
	CFStringAppendFormat(result, NULL, CFSTR("   serviceID = %@ \n"), connectionPrivate->serviceID);
	CFStringAppendFormat(result, NULL, CFSTR("}"));

	return result;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
static void
__SCNetworkConnectionDeallocate(CFTypeRef cf)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)cf;

	SCLog(_sc_verbose, LOG_DEBUG, CFSTR("__SCNetworkConnectionDeallocate:"));

	/* release resources */
	if (connectionPrivate->eventRef != -1) {
		while (CFArrayGetCount(connectionPrivate->rlList)) {
			CFRunLoopRef	runLoop;
			CFStringRef		runLoopMode;

			runLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(connectionPrivate->rlList, 1);
			runLoopMode = CFArrayGetValueAtIndex(connectionPrivate->rlList, 2);
			CFRunLoopRemoveSource(runLoop, connectionPrivate->rls, runLoopMode);

			CFArrayRemoveValueAtIndex(connectionPrivate->rlList, 2);
			CFArrayRemoveValueAtIndex(connectionPrivate->rlList, 1);
			CFArrayRemoveValueAtIndex(connectionPrivate->rlList, 0);
		}
		CFRelease(connectionPrivate->rls);
		CFRelease(connectionPrivate->rlList);
		//PPPDispose(connectionPrivate->eventRef);
		CFSocketInvalidate(connectionPrivate->eventRefCF);
		CFRelease(connectionPrivate->eventRefCF);
	}
	if (connectionPrivate->controlRef != -1)
		PPPDispose(connectionPrivate->controlRef);
	if (connectionPrivate->rlsContext.release)
		connectionPrivate->rlsContext.release(connectionPrivate->rlsContext.info);
	if (connectionPrivate->serviceID)
		CFRelease(connectionPrivate->serviceID);

	return;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static pthread_once_t initialized		= PTHREAD_ONCE_INIT;

static CFTypeID __kSCNetworkConnectionTypeID	= _kCFRuntimeNotATypeID;

static const CFRuntimeClass __SCNetworkConnectionClass = {
	0,					// version
	"SCNetworkConnection",			// className
	NULL,					// init
	NULL,					// copy
	__SCNetworkConnectionDeallocate,	// dealloc
	NULL,					// equal
	NULL,					// hash
	NULL,					// copyFormattingDesc
	__SCNetworkConnectionCopyDescription	// copyDebugDesc
};

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static void
__SCNetworkConnectionInitialize(void)
{
	__kSCNetworkConnectionTypeID = _CFRuntimeRegisterClass(&__SCNetworkConnectionClass);
	return;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
static SCNetworkConnectionPrivateRef
__SCNetworkConnectionCreatePrivate(CFAllocatorRef allocator, CFStringRef serviceID)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = 0;
	uint32_t			size;
	struct ppp_status		*stats = 0;
	int				error = kSCStatusFailed;

	/* initialize runtime */
	pthread_once(&initialized, __SCNetworkConnectionInitialize);

	SCLog(_sc_verbose, LOG_DEBUG, CFSTR("__SCNetworkConnectionCreatePrivate:"));

	/* allocate NetworkConnection */
	size = sizeof(SCNetworkConnectionPrivate) - sizeof(CFRuntimeBase);
	connectionPrivate = (SCNetworkConnectionPrivateRef)_CFRuntimeCreateInstance(allocator, __kSCNetworkConnectionTypeID,size, NULL);
	if (connectionPrivate == 0)
		goto fail;

	/* zero the data structure */
	bzero(((u_char*)connectionPrivate)+sizeof(CFRuntimeBase), size);

	/* save the serviceID */
	connectionPrivate->serviceID = CFStringCreateCopy(NULL, serviceID);

	connectionPrivate->controlRef = -1;
	connectionPrivate->eventRef = -1;

	if (PPPInit(&connectionPrivate->controlRef))
		goto fail;

	if (PPPStatus(connectionPrivate->controlRef, serviceID, 0, &stats)) {
		error = kSCStatusInvalidArgument;	// XXX can't get status, invalid service id
		goto fail;
	}

	CFAllocatorDeallocate(NULL, stats);
	stats = 0;

	/* success, return the connection reference */
	return connectionPrivate;

    fail:

	/* failure, clean up and leave */
	if (connectionPrivate)
		CFRelease(connectionPrivate);
	if (stats)
		CFAllocatorDeallocate(NULL, stats);
	_SCErrorSet(error);
	return NULL;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

CFTypeID
SCNetworkConnectionGetTypeID (void) {
	pthread_once(&initialized, __SCNetworkConnectionInitialize);	/* initialize runtime */
	return __kSCNetworkConnectionTypeID;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
static SCNetworkConnectionStatus
__SCNetworkConnectionConvertStatus (int state)
{
	SCNetworkConnectionStatus	status = kSCNetworkConnectionDisconnected;

	switch (state) {
		case PPP_INITIALIZE:
		case PPP_CONNECTLINK:
		case PPP_ESTABLISH:
		case PPP_AUTHENTICATE:
		case PPP_CALLBACK:
		case PPP_NETWORK:
		case PPP_WAITONBUSY:
			status = kSCNetworkConnectionConnecting;
			break;
		case PPP_TERMINATE:
		case PPP_DISCONNECTLINK:
		case PPP_HOLDOFF:
			status = kSCNetworkConnectionDisconnecting;
			break;
		case PPP_RUNNING:
		case PPP_ONHOLD:
			status = kSCNetworkConnectionConnected;
			break;
		case PPP_IDLE:
		case PPP_STATERESERVED:
		default:
			status = kSCNetworkConnectionDisconnected;
	}
	return status;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

SCNetworkConnectionRef
SCNetworkConnectionCreateWithServiceID (CFAllocatorRef			allocator,
					CFStringRef			serviceID,
					SCNetworkConnectionCallBack	callout,
					SCNetworkConnectionContext	*context)
{
	SCNetworkConnectionPrivateRef	connectionPrivate;

	if (!isA_CFString(serviceID)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	connectionPrivate = __SCNetworkConnectionCreatePrivate(allocator, serviceID);

	if (connectionPrivate) {
		connectionPrivate->rlsFunction = callout;
		if (context) {
			bcopy(context, &connectionPrivate->rlsContext, sizeof(SCNetworkConnectionContext));
			if (context->retain) {
				connectionPrivate->rlsContext.info = (void *)context->retain(context->info);
			}
		}
	}

	return (SCNetworkConnectionRef)connectionPrivate;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

CFStringRef
SCNetworkConnectionCopyServiceID (SCNetworkConnectionRef connection)
{
	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	return CFRetain(((SCNetworkConnectionPrivateRef)connection)->serviceID);
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

CFDictionaryRef
SCNetworkConnectionCopyStatistics (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	int				error			= kSCStatusFailed;
	struct ppp_status		*stats			= 0;
	CFMutableDictionaryRef		dict			= 0;
	CFMutableDictionaryRef		statsdict		= 0;

#define ADDNUMBER(d, k, n)					\
{								\
	CFNumberRef num;					\
	num = CFNumberCreate(NULL, kCFNumberSInt32Type, n);	\
	if (num) {						\
		CFDictionaryAddValue(d, k, num);		\
		CFRelease(num);					\
	}							\
}

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	/* get status and check connected state */
	if (PPPStatus(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, &stats))
		goto fail;

	if (__SCNetworkConnectionConvertStatus(stats->status) != kSCNetworkConnectionConnected)
		goto fail;

	/* create dictionaries */
	if ((statsdict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
		goto fail;

	if ((dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
		goto fail;

	/* add statistics */
	ADDNUMBER(dict, kSCNetworkConnectionBytesIn, &stats->s.run.inBytes);
	ADDNUMBER(dict, kSCNetworkConnectionBytesOut, &stats->s.run.outBytes);
	ADDNUMBER(dict, kSCNetworkConnectionPacketsIn, &stats->s.run.inPackets);
	ADDNUMBER(dict, kSCNetworkConnectionPacketsOut, &stats->s.run.outPackets);
	ADDNUMBER(dict, kSCNetworkConnectionErrorsIn, &stats->s.run.inErrors);
	ADDNUMBER(dict, kSCNetworkConnectionErrorsOut, &stats->s.run.outErrors);

	/* add the PPP dictionary to the statistics dictionary */
	CFDictionaryAddValue(statsdict, kSCEntNetPPP, dict);
	CFRelease(dict);

	/* done */
	CFAllocatorDeallocate(NULL, stats);
	return statsdict;

    fail:

	if (stats)
		CFAllocatorDeallocate(NULL, stats);
	if (dict)
		CFRelease(dict);
	if (statsdict)
		CFRelease(statsdict);
	_SCErrorSet(error);
	return NULL;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

SCNetworkConnectionStatus
SCNetworkConnectionGetStatus (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	struct ppp_status		*stats			= 0;
	SCNetworkConnectionStatus	status;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (PPPStatus(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, &stats))
		return kSCNetworkConnectionDisconnected; // XXX

	status = __SCNetworkConnectionConvertStatus(stats->status);

	CFAllocatorDeallocate(NULL, stats);
	return status;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
CFDictionaryRef
SCNetworkConnectionCopyExtendedStatus (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	CFPropertyListRef		status			= 0;
	void				*data			= 0;
	u_int32_t			datalen;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (PPPExtendedStatus(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, &data, &datalen))
		goto fail;

	if (!data
	    || !(status = PPPUnserialize(data, datalen))
	    || !isA_CFDictionary(status))
		goto fail;

	CFAllocatorDeallocate(NULL, data);
	return status;

    fail:

	_SCErrorSet(kSCStatusFailed);
	if (status)
		CFRelease(status);
	if (data)
		CFAllocatorDeallocate(NULL, data);
	return NULL;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionStart (SCNetworkConnectionRef	connection,
			  CFDictionaryRef		userOptions,
			  Boolean			linger)
{
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	CFDataRef			dataref			= 0;
	void				*data			= 0;
	u_int32_t			datalen			= 0;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (userOptions && !(dataref = PPPSerialize(userOptions, &data, &datalen)))
		goto fail;

	if (PPPConnect(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, data, datalen, linger))
		goto fail;

	if (dataref)
		CFRelease(dataref);

	/* connection is now started */
	return TRUE;

    fail:

	if (dataref)
		CFRelease(dataref);
	_SCErrorSet(kSCStatusFailed);	// XXX
	return FALSE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionStop (SCNetworkConnectionRef	connection,
			 Boolean		forceDisconnect)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)connection;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	if (PPPDisconnect(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, forceDisconnect)) {
		_SCErrorSet(kSCStatusFailed);
		return FALSE;
	}

	/* connection is now disconnecting */
	return TRUE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionSuspend (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)connection;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (PPPSuspend(connectionPrivate->controlRef, connectionPrivate->serviceID, 0)) {
		_SCErrorSet(kSCStatusFailed);
		return FALSE;
	}

	/* connection is now suspended */
	return TRUE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionResume (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)connection;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (PPPResume(connectionPrivate->controlRef, connectionPrivate->serviceID, 0)) {
		_SCErrorSet(kSCStatusFailed);
		return FALSE;
	}

	/* connection is now resume */
	return TRUE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

CFDictionaryRef
SCNetworkConnectionCopyUserOptions (SCNetworkConnectionRef connection)
{
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	void				*data			= 0;
	u_int32_t			datalen;
	CFPropertyListRef		userOptions		= 0;

	if (!isA_SCNetworkConnection(connection)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	if (PPPGetConnectData(connectionPrivate->controlRef, connectionPrivate->serviceID, 0, &data, &datalen))
		goto fail;

	// no data were used, return an empty dictionary
	if (data == 0) {
		CFDictionaryRef dict;

		dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		if (dict == 0)
			_SCErrorSet(kSCStatusFailed); // XXX
		return dict;
	}

	userOptions = PPPUnserialize(data, datalen);
	if (!isA_CFDictionary(userOptions))
		goto fail;

	CFAllocatorDeallocate(NULL, data);
	return userOptions;

    fail:

	_SCErrorSet(kSCStatusFailed);
	if (userOptions)
		CFRelease(userOptions);
	if (data)
		CFAllocatorDeallocate(NULL, data);
	return NULL;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static Boolean
__isScheduled(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList)
{
	CFIndex	i;
	CFIndex	n	= CFArrayGetCount(rlList);

	for (i = 0; i < n; i += 3) {
		if (obj         && !CFEqual(obj,         CFArrayGetValueAtIndex(rlList, i))) {
			continue;
		}
		if (runLoop     && !CFEqual(runLoop,     CFArrayGetValueAtIndex(rlList, i+1))) {
			continue;
		}
		if (runLoopMode && !CFEqual(runLoopMode, CFArrayGetValueAtIndex(rlList, i+2))) {
			continue;
		}
		return TRUE;
	}

	return FALSE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
static void
__schedule(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList)
{
	CFArrayAppendValue(rlList, obj);
	CFArrayAppendValue(rlList, runLoop);
	CFArrayAppendValue(rlList, runLoopMode);

	return;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */
static Boolean
__unschedule(CFTypeRef obj, CFRunLoopRef runLoop, CFStringRef runLoopMode, CFMutableArrayRef rlList, Boolean all)
{
	CFIndex	i	= 0;
	Boolean	found	= FALSE;
	CFIndex	n	= CFArrayGetCount(rlList);

	while (i < n) {
		if (obj         && !CFEqual(obj,         CFArrayGetValueAtIndex(rlList, i))) {
			i += 3;
			continue;
		}
		if (runLoop     && !CFEqual(runLoop,     CFArrayGetValueAtIndex(rlList, i+1))) {
			i += 3;
			continue;
		}
		if (runLoopMode && !CFEqual(runLoopMode, CFArrayGetValueAtIndex(rlList, i+2))) {
			i += 3;
			continue;
		}

		found = TRUE;

		CFArrayRemoveValueAtIndex(rlList, i + 2);
		CFArrayRemoveValueAtIndex(rlList, i + 1);
		CFArrayRemoveValueAtIndex(rlList, i);

		if (!all) {
			return found;
		}

		n -= 3;
	}

	return found;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

static void
__SCNetworkConnectionCallBack(CFSocketRef		inref,
			      CFSocketCallBackType	type,
			      CFDataRef			address,
			      const void		*data,
			      void			*info)
{
	void				*context_info;
	void				(*context_release)(const void *);
	SCNetworkConnectionRef		connection		= (SCNetworkConnectionRef)info;
	SCNetworkConnectionPrivateRef	connectionPrivate	= (SCNetworkConnectionPrivateRef)connection;
	SCNetworkConnectionCallBack	rlsFunction;
	SCNetworkConnectionStatus	status;
	int				pppstatus;
	int				err;

	err = PPPReadEvent(connectionPrivate->eventRef, &pppstatus);
	if (err)
		return;

	rlsFunction = connectionPrivate->rlsFunction;
	if (connectionPrivate->rlsContext.retain && connectionPrivate->rlsContext.info) {
		context_info	= (void *)connectionPrivate->rlsContext.retain(connectionPrivate->rlsContext.info);
		context_release	= connectionPrivate->rlsContext.release;
	}
	else {
		context_info	= connectionPrivate->rlsContext.info;
		context_release	= NULL;
	}

	status = __SCNetworkConnectionConvertStatus(pppstatus);

	(*rlsFunction)(connection, status, context_info);
	if (context_release && context_info) {
		context_release(context_info);
	}
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionScheduleWithRunLoop(SCNetworkConnectionRef	connection,
				       CFRunLoopRef		runLoop,
				       CFStringRef		runLoopMode)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)connection;
	//CFSocketRef			ref;

	if (!isA_SCNetworkConnection(connection) || runLoop == NULL || runLoopMode == NULL) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	if (connectionPrivate->rlsFunction == 0) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	if (connectionPrivate->rlList
	    && __isScheduled(NULL, runLoop, runLoopMode, connectionPrivate->rlList)) {
		/* already scheduled */
		_SCErrorSet(kSCStatusFailed);
		return FALSE;
	}

	if (connectionPrivate->eventRef == -1) {
		CFSocketContext	context = { 0, (void*)connection, CFRetain, CFRelease, CFCopyDescription };

		if (PPPInit(&connectionPrivate->eventRef)) {
			_SCErrorSet(kSCStatusFailed);
			return FALSE;
		}

		PPPEnableEvents(connectionPrivate->eventRef, connectionPrivate->serviceID, 0, 1);

		connectionPrivate->eventRefCF = CFSocketCreateWithNative(NULL, connectionPrivate->eventRef,
									 kCFSocketReadCallBack, __SCNetworkConnectionCallBack, &context);
		connectionPrivate->rls = CFSocketCreateRunLoopSource(NULL, connectionPrivate->eventRefCF, 0);
		connectionPrivate->rlList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

		//CFRelease(ref);
	}

	CFRunLoopAddSource(runLoop, connectionPrivate->rls, runLoopMode);
	__schedule(connectionPrivate, runLoop, runLoopMode, connectionPrivate->rlList);

	return TRUE;
}

/* -------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------- */

Boolean
SCNetworkConnectionUnscheduleFromRunLoop(SCNetworkConnectionRef		connection,
					 CFRunLoopRef			runLoop,
					 CFStringRef			runLoopMode)
{
	SCNetworkConnectionPrivateRef	connectionPrivate = (SCNetworkConnectionPrivateRef)connection;

	if (!isA_SCNetworkConnection(connection) || runLoop == NULL || runLoopMode == NULL) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	if (connectionPrivate->rlList == NULL
	    || !__unschedule(connectionPrivate, runLoop, runLoopMode, connectionPrivate->rlList, FALSE)) {
		/* if not currently scheduled */
		_SCErrorSet(kSCStatusFailed);
		return FALSE;
	}

	CFRunLoopRemoveSource(runLoop, connectionPrivate->rls, runLoopMode);

	if (CFArrayGetCount(connectionPrivate->rlList) == 0) {
		CFRelease(connectionPrivate->rls);
		connectionPrivate->rls = NULL;
		CFRelease(connectionPrivate->rlList);
		connectionPrivate->rlList = NULL;
		//PPPDispose(connectionPrivate->eventRef);
		CFSocketInvalidate(connectionPrivate->eventRefCF);
		CFRelease(connectionPrivate->eventRefCF);
		connectionPrivate->eventRefCF = 0;
		connectionPrivate->eventRef = -1;
	}

	return TRUE;
}


//************************* USER LEVEL DIAL API **********************************


#define k_NetworkConnect_Pref_File	CFSTR("com.apple.networkConnect")
#define k_InterentConnect_Pref_File	CFSTR("com.apple.internetconnect")

#define k_Dial_Default_Key		CFSTR("ConnectByDefault") // needs to go into SC
#define k_Last_Service_Id_Key		CFSTR("ServiceID")
#define k_Unique_Id_Key	 		CFSTR("UniqueIdentifier")


/* Private Prototypes */
static Boolean SCNetworkConnectionPrivateCopyDefaultServiceIDForDial	(SCDynamicStoreRef session, CFStringRef *serviceID);
static Boolean SCNetworkConnectionPrivateGetPPPServiceFromDynamicStore	(SCDynamicStoreRef session, CFStringRef *serviceID);
static Boolean SCNetworkConnectionPrivateCopyDefaultUserOptionsFromArray(CFArrayRef userOptionsArray, CFDictionaryRef *userOptions);
static Boolean SCNetworkConnectionPrivateIsPPPService			(SCDynamicStoreRef session, CFStringRef serviceID);
static void addPasswordFromKeychain(CFDictionaryRef *userOptions);
static CFArrayRef copyKeychainEnumerator(CFStringRef uniqueIdentifier);

Boolean
SCNetworkConnectionCopyUserPreferences (CFDictionaryRef	selectionOptions,
					CFStringRef	*serviceID,
					CFDictionaryRef	*userOptions)
{
	SCDynamicStoreRef	session	= SCDynamicStoreCreate(NULL, CFSTR("SCNetworkConnection"), NULL, NULL);
	Boolean			success	= FALSE;

	// NOTE:  we are currently ignoring selectionOptions

	if (session != NULL) {
		// (1) Figure out which service ID we care about, allocate it into passed "serviceID"
		success = SCNetworkConnectionPrivateCopyDefaultServiceIDForDial(session, serviceID);

		if (success && (*serviceID != NULL)) {
			// (2) Get the list of user data for this service ID
			CFPropertyListRef userServices = CFPreferencesCopyValue(*serviceID,
										k_NetworkConnect_Pref_File,
										kCFPreferencesCurrentUser,
										kCFPreferencesCurrentHost);

			// (3) We are expecting an array if the user has defined records for this service ID or NULL if the user hasn't
			if (userServices != NULL) {
				if (isA_CFArray(userServices)) {
					// (4) Get the default set of user options for this service
					success =  SCNetworkConnectionPrivateCopyDefaultUserOptionsFromArray(
									  (CFArrayRef)userServices,
									userOptions);
					if(success && userOptions != NULL)
					{
					    addPasswordFromKeychain(userOptions);
					}
				} else {
					fprintf(stderr, "Error, userServices are not of type CFArray!\n");
				}

				CFRelease(userServices); // this is OK because SCNetworkConnectionPrivateISExpectedCFType() checks for NULL
			}
		}

		CFRelease(session);
	} else {
		fprintf(stderr, "Error,  SCNetworkConnectionCopyUserPreferences, SCDynamicStoreCreate() returned NULL!\n");
	}

	return success;
}

//*******************************************************************************************
// SCNetworkConnectionPrivateCopyDefaultServiceIDForDial
// ----------------------------------------------------
// Try to find the service id to connect
// (1) Start by looking at the last service used in Internet Connect
// (2) If Internet Connect has not been used, find the PPP service with the highest ordering
//********************************************************************************************
static Boolean
SCNetworkConnectionPrivateCopyDefaultServiceIDForDial(SCDynamicStoreRef session, CFStringRef *serviceID)
{
	Boolean			foundService		= FALSE;
	CFPropertyListRef	lastServiceSelectedInIC = NULL;

	// NULL out the pointer
	*serviceID = NULL;

	// read out the last service from the Internet Connect preference file
	lastServiceSelectedInIC = CFPreferencesCopyValue(k_Last_Service_Id_Key,
							 k_InterentConnect_Pref_File,
							 kCFPreferencesCurrentUser,
							 kCFPreferencesAnyHost);

	// we found the service the user last had open in IC
	if (lastServiceSelectedInIC != NULL) {
		// make sure its a PPP service
		if (SCNetworkConnectionPrivateIsPPPService(session, lastServiceSelectedInIC)) {
			// make sure the service that we found is valid
			CFDictionaryRef	dict;
			CFStringRef	key;

			key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
									  kSCDynamicStoreDomainSetup,
									  lastServiceSelectedInIC,
									  kSCEntNetInterface);
			dict = SCDynamicStoreCopyValue(session, key);
			CFRelease(key);
			if (dict) {
				*serviceID = CFRetain(lastServiceSelectedInIC);
				foundService = TRUE;
				CFRelease(dict);
			}
		}
		CFRelease(lastServiceSelectedInIC);
	}

	if (!foundService) {
		foundService = SCNetworkConnectionPrivateGetPPPServiceFromDynamicStore(session, serviceID);
	}

	return foundService;
}

//********************************************************************************
// SCNetworkConnectionPrivateGetPPPServiceFromDynamicStore
// -------------------------------------------------------
// Find the highest ordered PPP service in the dynamic store
//********************************************************************************
static Boolean
SCNetworkConnectionPrivateGetPPPServiceFromDynamicStore(SCDynamicStoreRef session, CFStringRef *serviceID)
{
	Boolean		success		= FALSE;
	CFStringRef	key		= NULL;
	CFDictionaryRef	dict		= NULL;
	CFArrayRef	serviceIDs	= NULL;

	*serviceID = NULL;

	do {
		CFIndex count;
		CFIndex i;

		key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainSetup, kSCEntNetIPv4);
		if (key == NULL) {
			fprintf(stderr, "Error, Setup Key == NULL!\n");
			break;
		}

		dict = SCDynamicStoreCopyValue(session, key);
		if (dict == NULL) {
			fprintf(stderr, "Error, Dictionary for setup key == NULL!\n");
			break;
		}

		serviceIDs = CFDictionaryGetValue(dict, kSCPropNetServiceOrder); // array of service id's
		if (isA_CFArray(serviceIDs) == NULL) {
			if (serviceIDs == NULL)
				fprintf(stderr, "Error, Array of service IDs == NULL!\n");
			else
				fprintf(stderr, "Error, serviceIds are not of type CFArray!\n");
			break;
		}

		count = CFArrayGetCount(serviceIDs);
		for (i = 0; i < count; i++) {
			CFStringRef service = CFArrayGetValueAtIndex(serviceIDs, i);

			if (SCNetworkConnectionPrivateIsPPPService(session, service)) {
				*serviceID = CFRetain(service);
				success = TRUE;
				break;
			}
		}
	} while (FALSE);

	if (key != NULL)
		CFRelease(key);

	if (dict != NULL)
		CFRelease(dict);

	return success;
}

//********************************************************************************
// SCNetworkConnectionPrivateCopyDefaultUserOptionsFromArray
// ---------------------------------------------------------
// Copy over user preferences for a particular service if they exist
//********************************************************************************
static Boolean
SCNetworkConnectionPrivateCopyDefaultUserOptionsFromArray(CFArrayRef userOptionsArray, CFDictionaryRef *userOptions)
{
	CFIndex	count	= CFArrayGetCount(userOptionsArray);
	int	i;

	*userOptions = NULL;

	for (i = 0; i < count; i++) {
		// (1) Find the dictionary
		CFPropertyListRef propertyList = CFArrayGetValueAtIndex(userOptionsArray, i);

		if (isA_CFDictionary(propertyList) != NULL) {
			// See if there's a value for dial on demand
			CFPropertyListRef value = CFDictionaryGetValue((CFDictionaryRef)propertyList,
								       k_Dial_Default_Key);
			if (isA_CFBoolean(value) != NULL) {
				if (CFBooleanGetValue(value)) {
					// we found the default user options
					*userOptions = CFDictionaryCreateCopy(NULL,
									      (CFDictionaryRef)propertyList);
					break;
				}
			}
		}
	}

	return TRUE;
}

//********************************************************************************
// SCNetworkConnectionPrivateIsPPPService
// --------------------------------------
// Check and see if the service is a PPP service
//********************************************************************************
static Boolean
SCNetworkConnectionPrivateIsPPPService(SCDynamicStoreRef session, CFStringRef serviceID)
{
	CFStringRef	entityKey;
	Boolean		isPPPService	= FALSE;
	Boolean		isModemOrPPPoE	= FALSE;

	entityKey = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
								kSCDynamicStoreDomainSetup,
								serviceID,
								kSCEntNetInterface);
	if (entityKey != NULL) {
		CFDictionaryRef	serviceDict;

		serviceDict = SCDynamicStoreCopyValue(session, entityKey);
		if (serviceDict != NULL) {
			if (isA_CFDictionary(serviceDict)) {
				CFStringRef	type;
				CFStringRef	subtype;

				type = CFDictionaryGetValue(serviceDict, kSCPropNetInterfaceType);
				if (isA_CFString(type)) {
					isPPPService = CFEqual(type, kSCValNetInterfaceTypePPP);
				}

				subtype = CFDictionaryGetValue(serviceDict, kSCPropNetInterfaceSubType);
				if (isA_CFString(subtype)) {
					isModemOrPPPoE = (CFEqual(subtype, kSCValNetInterfaceSubTypePPPSerial) ||
							  CFEqual(subtype, kSCValNetInterfaceSubTypePPPoE));
				}
			}
			CFRelease(serviceDict);
		}
		CFRelease(entityKey);
	}

	return (isPPPService && isModemOrPPPoE);
}

//********************************************************************************
// addPasswordFromKeychain
// --------------------------------------
// Get the password out of the keychain and add it to the PPP dictionary
//********************************************************************************
static void
addPasswordFromKeychain(CFDictionaryRef *userOptions)
{
	CFArrayRef		enumerator;
	CFIndex			n;
	CFDictionaryRef		oldDict;
	CFPropertyListRef	uniqueID	= NULL;

	oldDict = *userOptions;
	if(oldDict == NULL) {
		return;		// if no userOptions
	}

	uniqueID = CFDictionaryGetValue(oldDict, k_Unique_Id_Key);
	if(!isA_CFString(uniqueID)) {
		return;		// if no unique ID
	}

	enumerator = copyKeychainEnumerator(uniqueID);
	if(enumerator == NULL) {
		return;		// if no keychain enumerator
	}


	n = CFArrayGetCount(enumerator);
	if (n > 0) {
		void			*data	= NULL;
		UInt32			dataLen	= 0;
		SecKeychainItemRef	itemRef;
		OSStatus		result;

		itemRef = (SecKeychainItemRef)CFArrayGetValueAtIndex(enumerator, 0);
		result = SecKeychainItemCopyContent(itemRef,		// itemRef
						    NULL,		// itemClass
						    NULL,		// attrList
						    &dataLen,		// length
						    (void *)&data);	// outData
		if(result == noErr && data != NULL && dataLen > 0) {
			CFStringRef		pass;

			pass = CFStringCreateWithBytes(NULL, data, dataLen, kCFStringEncodingUTF8, TRUE);
			if (pass) {
				CFMutableDictionaryRef	newDict;
				CFMutableDictionaryRef	newPPP;
				CFDictionaryRef		pppDict;

				newDict = CFDictionaryCreateMutableCopy(NULL, 0, oldDict);
				pppDict = CFDictionaryGetValue(newDict, kSCEntNetPPP);
				if (isA_CFDictionary(pppDict)) {
					newPPP = CFDictionaryCreateMutableCopy(NULL, 0, pppDict);
				} else {
					newPPP = CFDictionaryCreateMutable(NULL,
									   0,
									   &kCFTypeDictionaryKeyCallBacks,
									   &kCFTypeDictionaryValueCallBacks);
				}

				// set the PPP password
				CFDictionarySetValue(newPPP, kSCPropNetPPPAuthPassword, pass);
				CFRelease(pass);

				// update the PPP entity
				CFDictionarySetValue(newDict, kSCEntNetPPP, newPPP);
				CFRelease(newPPP);

				// update the userOptions dictionary
				CFRelease(oldDict);
				*userOptions = CFDictionaryCreateCopy(NULL, newDict);
				CFRelease(newDict);
			}
		}
	}

	CFRelease(enumerator);
	return;
}

//********************************************************************************
// copyKeychainEnumerator
// --------------------------------------
// Gather Keychain Enumerator
//********************************************************************************
static CFArrayRef
copyKeychainEnumerator(CFStringRef uniqueIdentifier)
{
	char			*buf		= NULL;
	CFMutableArrayRef	itemArray	= NULL;
	OSStatus		result;
	SecKeychainSearchRef	search		= NULL;

	buf = _SC_cfstring_to_cstring(uniqueIdentifier, NULL, 0, kCFStringEncodingUTF8);
	if (buf != NULL) {
		// search for unique identifier in "svce" attribute
		SecKeychainAttribute		attributes[]	= {{ kSecServiceItemAttr,
								     CFStringGetLength(uniqueIdentifier),
								     (void *)buf
								   }};

		SecKeychainAttributeList	attrList	= { sizeof(attributes) / sizeof(*attributes),
								    attributes };

		result = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
		if (result == noErr) {
			itemArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

			while (result == noErr) {
				SecKeychainItemRef	itemFound	= NULL;

				result = SecKeychainSearchCopyNext(search, &itemFound);
				if (result != noErr) {
					break;
				}

				if (itemFound) {
					CFArrayAppendValue(itemArray, itemFound);
					CFRelease(itemFound);
				}
			}
		}
	}

	if (search)	CFRelease(search);
	if (buf)	CFAllocatorDeallocate(NULL, buf);

	return itemArray;
}
