ppp_manager.c   [plain text]


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


/* -----------------------------------------------------------------------------
includes
----------------------------------------------------------------------------- */

#include <fcntl.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <paths.h>
#include <net/if.h>
#include <net/ndrv.h>
#include <mach/message.h>
#include <mach/boolean.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <CoreFoundation/CFMachPort.h>
#include <mach/mach_time.h>
#include <servers/bootstrap.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFUserNotification.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCDPlugin.h>
#include <SystemConfiguration/SCPrivate.h>      // for SCLog() and VPN private keys
#include <mach/task_special_ports.h>
#include "pppcontroller_types.h"
#include "pppcontroller.h"
#include <SystemConfiguration/SCValidation.h>
#include <bsm/libbsm.h>
#include <pwd.h>

#include "ppp_msg.h"
#include "ppp_privmsg.h"
#include "../Family/ppp_domain.h"
#include "../Helpers/pppd/pppd.h"

#include "scnc_main.h"
#include "scnc_client.h"
#include "ppp_manager.h"
#include "ppp_option.h"
#include "ppp_socket_server.h"
#include "scnc_utils.h"

#include "../Drivers/PPTP/PPTP-plugin/pptp.h"
#include "../Drivers/L2TP/L2TP-plugin/l2tp.h"
#include "../Drivers/PPPoE/PPPoE-extension/PPPoE.h"

#include "sessionTracer.h"


/* -----------------------------------------------------------------------------
Definitions
----------------------------------------------------------------------------- */

enum {
	READ	= 0,	// read end of standard UNIX pipe
	WRITE	= 1	// write end of standard UNIX pipe
};

#define MAX_EXTRACONNECTTIME 20 /* allows 20 seconds after wakeup */
#define MIN_EXTRACONNECTTIME 3 /* if we do give extra time, give at lease 3 seconds */

/* -----------------------------------------------------------------------------
globals
----------------------------------------------------------------------------- */

extern TAILQ_HEAD(, service) 	service_head;


/* -----------------------------------------------------------------------------
Forward declarations
----------------------------------------------------------------------------- */

static void display_error(struct service *serv);
static void exec_callback(pid_t pid, int status, struct rusage *rusage, void *context);
static void exec_postfork(pid_t pid, void *arg);
static int send_pppd_params(struct service *serv, CFDictionaryRef service, CFDictionaryRef options, u_int8_t onTraffic);
static int change_pppd_params(struct service *serv, CFDictionaryRef service, CFDictionaryRef options);

static void setup_PPPoE(struct service *serv);
static void dispose_PPPoE(struct service *serv);

/* -----------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
    service management and control
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
u_int16_t ppp_subtype(CFStringRef subtypeRef) 
{

   if (my_CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPSerial)) 
        return PPP_TYPE_SERIAL;
    else if (my_CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPoE))
        return PPP_TYPE_PPPoE;
    else if (my_CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPTP))
        return PPP_TYPE_PPTP;
    else if (my_CFEqual(subtypeRef, kSCValNetInterfaceSubTypeL2TP))
        return PPP_TYPE_L2TP;

	return PPP_TYPE_OTHER;
}

/* -----------------------------------------------------------------------------
an interface structure needs to be created
unit is the ppp managed unit
----------------------------------------------------------------------------- */
int ppp_new_service(struct service *serv)
{
    CFURLRef		url;
    u_char			str[MAXPATHLEN], str2[32];

   //  SCLog(TRUE, LOG_INFO, CFSTR("ppp_new_service, subtype = %%@, serviceID = %@\n"), serv->subtypeRef, serv->serviceID);

    serv->u.ppp.ndrv_socket = -1;
    serv->u.ppp.phase = PPP_IDLE;
    serv->u.ppp.statusfd[READ] = -1;
    serv->u.ppp.statusfd[WRITE] = -1;
    serv->u.ppp.controlfd[READ] = -1;
    serv->u.ppp.controlfd[WRITE] = -1;

	if (serv->subtypeRef) {
		strcpy(str, DIR_KEXT);
		str2[0] = 0;
		CFStringGetCString(serv->subtypeRef, str2, sizeof(str2), kCFStringEncodingUTF8);
		strcat(str, str2);
		strcat(str, ".ppp");	// add plugin suffix
		url = CFURLCreateFromFileSystemRepresentation(NULL, str, strlen(str), TRUE);
		if (url) {
			serv->u.ppp.bundle = CFBundleCreate(0, url);
			CFRelease(url);
		}
	}
	
    return 0;
}

/* -----------------------------------------------------------------------------
an interface is come down, dispose the ppp structure
----------------------------------------------------------------------------- */
int ppp_dispose_service(struct service *serv)
{

    if (serv->u.ppp.phase != PPP_IDLE)
        return 1;
    
    // then free the structure
	dispose_PPPoE(serv);
    my_CFRelease(&serv->u.ppp.bundle);
    return 0;
}

/* -----------------------------------------------------------------------------
 ----------------------------------------------------------------------------- */
void ppp_user_notification_callback(struct service *serv, CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
{
	
}

/* -----------------------------------------------------------------------------
 ----------------------------------------------------------------------------- */
static
void display_error(struct service *serv) 
{
    CFStringRef 	msg = NULL;
    CFMutableDictionaryRef 	dict = NULL;
    SInt32 			err;

    SESSIONTRACERSTOP(serv);

    if (serv->u.ppp.laststatus == EXIT_USER_REQUEST)
        return;
	
    if ((serv->flags & FLAG_ALERTERRORS) == 0)
        return;
	
	/* <rdar://6701793>, we do not want to display error if the device is gone and ppp ONTRAFFIC mode is enabled */
    if ((serv->flags & FLAG_ONTRAFFIC) && (serv->u.ppp.laststatus == EXIT_DEVICE_ERROR))
        return;

    if (serv->u.ppp.lastdevstatus) {
		
		switch (serv->subtype) {
			case PPP_TYPE_L2TP:
				// filter out the following messages
				switch (serv->u.ppp.lastdevstatus) {
#ifdef TARGET_EMBEDDED_OS
						/* Error 6 */
					case EXIT_L2TP_NETWORKCHANGED: /* L2TP Error 6 */
						return;
#endif
				}
				break;
				
			case PPP_TYPE_PPTP:
				// filter out the following messages
				switch (serv->u.ppp.lastdevstatus) {
#ifdef TARGET_EMBEDDED_OS
						/* Error 6 */
					case EXIT_PPTP_NETWORKCHANGED: /* PPTP Error 6 */
						return;
#endif
				}
				break;
		}
		
		msg = CFStringCreateWithFormat(0, 0, CFSTR("%@ Error %d"), serv->subtypeRef, serv->u.ppp.lastdevstatus);
    }
	
    if (msg == NULL) {
		
		// filter out the following messages
		switch (serv->u.ppp.laststatus) {
			case EXIT_USER_REQUEST: /* PPP Error 5 */
				return;
		}

		msg = CFStringCreateWithFormat(0, 0, CFSTR("PPP Error %d"), serv->u.ppp.laststatus);
	}
	
    if (!msg || !CFStringGetLength(msg))
		goto done;
		
    dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    if (!dict)
		goto done;
	
	if (gIconURLRef)
		CFDictionaryAddValue(dict, kCFUserNotificationIconURLKey, gIconURLRef);
	if (gBundleURLRef)
		CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, gBundleURLRef);
	
	CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, msg);
	CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, (serv->subtype == PPP_TYPE_L2TP || serv->subtype == PPP_TYPE_PPTP) ? CFSTR("VPN Connection") : CFSTR("Network Connection"));
	
	if (serv->userNotificationRef) {
		err = CFUserNotificationUpdate(serv->userNotificationRef, 0, kCFUserNotificationNoteAlertLevel, dict);
	}
	else {
		serv->userNotificationRef = CFUserNotificationCreate(NULL, 0, kCFUserNotificationNoteAlertLevel, &err, dict);
		if (!serv->userNotificationRef)
			goto done;
		
		serv->userNotificationRLS = CFUserNotificationCreateRunLoopSource(NULL, serv->userNotificationRef, 
																		  user_notification_callback, 0);
		if (!serv->userNotificationRLS) {
			my_CFRelease(&serv->userNotificationRef);
			goto done;
		}
		CFRunLoopAddSource(CFRunLoopGetCurrent(), serv->userNotificationRLS, kCFRunLoopDefaultMode);
	}

	
done:
    my_CFRelease(&dict);
    my_CFRelease(&msg);
}

/* -----------------------------------------------------------------------------
	for PPPoE over Airport or Ethernet services, install a protocol to enable the interface
	even when PPPoE is not connected
----------------------------------------------------------------------------- */
static 
void setup_PPPoE(struct service *serv)
{
    CFDictionaryRef	interface;
	CFStringRef		hardware, device;
    struct sockaddr_ndrv 	ndrv;

	if (serv->subtype == PPP_TYPE_PPPoE) {

		interface = copyEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetInterface);
		if (interface) {
		
			device = CFDictionaryGetValue(interface, kSCPropNetInterfaceDeviceName);
			hardware = CFDictionaryGetValue(interface, kSCPropNetInterfaceHardware);

			if (isA_CFString(hardware) && isA_CFString(device) 
				&& ((CFStringCompare(hardware, kSCEntNetAirPort, 0) == kCFCompareEqualTo)
					|| (CFStringCompare(hardware, kSCEntNetEthernet, 0) == kCFCompareEqualTo))) {

				if (serv->device 
					&& (CFStringCompare(device, serv->device, 0) != kCFCompareEqualTo)) {
					dispose_PPPoE(serv);
				}
				
				if (!serv->device) {
					serv->u.ppp.ndrv_socket = socket(PF_NDRV, SOCK_RAW, 0);
					if (serv->u.ppp.ndrv_socket >= 0) {
						serv->device = device;
						CFRetain(serv->device);
						CFStringGetCString(device, ndrv.snd_name, sizeof(ndrv.snd_name), kCFStringEncodingMacRoman);
						ndrv.snd_len = sizeof(ndrv);
						ndrv.snd_family = AF_NDRV;
						if (bind(serv->u.ppp.ndrv_socket, (struct sockaddr *)&ndrv, sizeof(ndrv)) < 0) {
							dispose_PPPoE(serv);
						}
					}
				}
			}
			else {
				/* not an Airport device */
				dispose_PPPoE(serv);
			}

			CFRelease(interface);
		}
	}
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void dispose_PPPoE(struct service *serv)
{
	if (serv->u.ppp.ndrv_socket != -1) {
		close(serv->u.ppp.ndrv_socket);
		serv->u.ppp.ndrv_socket = -1;
	}
	if (serv->device) {
		CFRelease(serv->device);
		serv->device = 0;
	}
}

/* -----------------------------------------------------------------------------
changed for this ppp occured in configd cache
----------------------------------------------------------------------------- */
int ppp_setup_service(struct service *serv)
{
    u_int32_t 		lval;
    CFDictionaryRef	dict;
    
	/* get some general setting flags first */
	serv->flags &= ~(
		FLAG_SETUP_ONTRAFFIC +
		FLAG_SETUP_DISCONNECTONLOGOUT +
		FLAG_SETUP_DISCONNECTONSLEEP +
		FLAG_SETUP_PREVENTIDLESLEEP +
		FLAG_SETUP_DISCONNECTONFASTUSERSWITCH);

	dict = copyEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetPPP);
	if (dict) {
		lval = 0;
		getNumber(dict, kSCPropNetPPPDialOnDemand, &lval);
		if (lval) serv->flags |= FLAG_SETUP_ONTRAFFIC;

		lval = 0;
		getNumber(dict, kSCPropNetPPPDisconnectOnLogout, &lval);
		if (lval) serv->flags |= FLAG_SETUP_DISCONNECTONLOGOUT;

		// by default, vpn connection don't disconnect on sleep
		switch (serv->subtype) {
			case PPP_TYPE_PPTP:
			case PPP_TYPE_L2TP:
				lval = 0;
				break;
			default :
				lval = 1;
		}
		getNumber(dict, kSCPropNetPPPDisconnectOnSleep, &lval);
		if (lval) serv->flags |= FLAG_SETUP_DISCONNECTONSLEEP;
			
		// by default, vpn connection don't prevent idle sleep
		switch (serv->subtype) {
			case PPP_TYPE_PPTP:
			case PPP_TYPE_L2TP:
				lval = 0;
				break;
			default :
				lval = 1;
		}
		getNumber(dict, CFSTR("PreventIdleSleep"), &lval);
		if (lval) serv->flags |= FLAG_SETUP_PREVENTIDLESLEEP;

		/* if the DisconnectOnFastUserSwitch key does not exist, use kSCPropNetPPPDisconnectOnLogout */
		lval = (serv->flags & FLAG_SETUP_DISCONNECTONLOGOUT);
		getNumber(dict, CFSTR("DisconnectOnFastUserSwitch"), &lval);
		if (lval) serv->flags |= FLAG_SETUP_DISCONNECTONFASTUSERSWITCH;

		CFRelease(dict);
		dict = NULL;
	}

	setup_PPPoE(serv);

	switch (serv->u.ppp.phase) {

		case PPP_IDLE:
			//printf("ppp_updatesetup : unit %d, PPP_IDLE\n", ppp->unit);
			if (serv->flags & FLAG_SETUP_ONTRAFFIC
				 && (!(serv->flags & FLAG_SETUP_DISCONNECTONLOGOUT) || gLoggedInUser)) {
					ppp_start(serv, 0, 0, 0, 0, 1, 0);
			}
			break;

		case PPP_DORMANT:
		case PPP_HOLDOFF:
			// config has changed, dialontraffic will need to be restarted
			serv->flags |= FLAG_CONFIGCHANGEDNOW;
			serv->flags &= ~FLAG_CONFIGCHANGEDLATER;
			scnc_stop(serv, 0, SIGTERM);
			break;

		default :
			// config has changed, dialontraffic will need to be restarted
			serv->flags |= FLAG_CONFIGCHANGEDLATER;
			serv->flags &= ~FLAG_CONFIGCHANGEDNOW;
			/* if ppp was started in dialontraffic mode, then stop it */
//                    if (ppp->dialontraffic)
//                        ppp_disconnect(ppp, 0, SIGTERM);

			dict = copyService(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID);
			if (dict) {
				change_pppd_params(serv, dict, serv->connectopts);
				CFRelease(dict);
			}
			break;
	}
	return 0;
}

/* -----------------------------------------------------------------------------
system is asking permission to sleep
return if sleep is authorized
----------------------------------------------------------------------------- */
int ppp_can_sleep(struct service *serv)
{
    // I refuse idle sleep if ppp is connected
	if (serv->u.ppp.phase == PPP_RUNNING
		&& (serv->flags & FLAG_SETUP_PREVENTIDLESLEEP))
		return 0;
	
    return 1;
}

/* -----------------------------------------------------------------------------
system is going to sleep
disconnect services and return if a delay is needed
----------------------------------------------------------------------------- */ 
int ppp_will_sleep(struct service *serv, int checking)
{
    u_int32_t			delay = 0, alert = 0;
            
	if (serv->u.ppp.phase != PPP_IDLE
		&& (serv->flags & FLAG_SETUP_DISCONNECTONSLEEP)) { 
		
		delay = 1;
		if (serv->u.ppp.phase != PPP_DORMANT || serv->u.ppp.phase != PPP_HOLDOFF) 
			alert = 2;
		if (!checking)
			scnc_stop(serv, 0, SIGTERM);
	}
        
    return delay + alert;
}

/* -----------------------------------------------------------------------------
ipv4 state has changed 
----------------------------------------------------------------------------- */
void ppp_ipv4_state_changed(struct service *serv)
{
}

/* -----------------------------------------------------------------------------
system is waking up
need to check the dialontraffic flag again
----------------------------------------------------------------------------- */
void ppp_wake_up(struct service	*serv)
{

	if (serv->u.ppp.phase == PPP_IDLE) {
		if ((serv->flags & FLAG_SETUP_ONTRAFFIC)
				&& (!(serv->flags & FLAG_SETUP_DISCONNECTONLOGOUT) || gLoggedInUser)) {
				ppp_start(serv, 0, 0, 0, 0, 1, 0);
		}
	}
}

/* -----------------------------------------------------------------------------
user has looged out
need to check the disconnect on logout flag for the ppp interfaces
----------------------------------------------------------------------------- */
void ppp_log_out(struct service	*serv)
{

	if (serv->u.ppp.phase != PPP_IDLE
		&& (serv->flags & FLAG_SETUP_DISCONNECTONLOGOUT))
		scnc_stop(serv, 0, SIGTERM);
}

/* -----------------------------------------------------------------------------
user has logged in
need to check the dialontraffic flag again
----------------------------------------------------------------------------- */
void ppp_log_in(struct service	*serv)
{

	if (serv->u.ppp.phase == PPP_IDLE
		&& (serv->flags & FLAG_SETUP_ONTRAFFIC))
		ppp_start(serv, 0, 0, 0, 0, 1, 0);
}

/* -----------------------------------------------------------------------------
user has switched
need to check the disconnect on logout and dial on traffic 
flags for the ppp interfaces
----------------------------------------------------------------------------- */
void ppp_log_switch(struct service *serv)
{
	switch (serv->u.ppp.phase) {
		case PPP_IDLE:
			// rearm dial on demand
			if (serv->flags & FLAG_SETUP_ONTRAFFIC)
				ppp_start(serv, 0, 0, 0, 0, 1, 0);
			break;
			
		default:
			if (serv->flags & FLAG_SETUP_DISCONNECTONFASTUSERSWITCH) {
					
				// if dialontraffic is set, it will need to be restarted
				serv->flags &= ~FLAG_CONFIGCHANGEDLATER;
				if (serv->flags & FLAG_SETUP_ONTRAFFIC)
					serv->flags |= FLAG_CONFIGCHANGEDNOW;
				else
					serv->flags &= ~FLAG_CONFIGCHANGEDNOW;
				scnc_stop(serv, 0, SIGTERM);
			}
	}
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void addparam(char **arg, u_int32_t *argi, char *param)
{
    int len = strlen(param);

    if (len && (arg[*argi] = malloc(len + 1))) {
        strcpy(arg[*argi], param);
        (*argi)++;
    }
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void writeparam(int fd, char *param)
{
    
    write(fd, param, strlen(param));
    write(fd, " ", 1);
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void writeintparam(int fd, char *param, u_int32_t val)
{
    u_char	str[32];
    
    writeparam(fd, param);
    sprintf(str, "%d", val);
    writeparam(fd, str);
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void writedataparam(int fd, char *param, void *data, int len)
{
    
    writeintparam(fd, param, len);
    write(fd, data, len);
    write(fd, " ", 1);
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void writestrparam(int fd, char *param, char *val)
{
    
    write(fd, param, strlen(param));

    /* we need to quote and escape the parameter */
    write(fd, " \"", 2);
    while (*val) {
        switch (*val) {
            case '\\':
            case '\"':
				write(fd, "\\", 1);
				break;
            case '\'':
            case ' ':
                //write(fd, "\\", 1);
                ;
        }
        write(fd, val, 1);
        val++;
    }
    write(fd, "\" ", 2);
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
int send_pppd_params(struct service *serv, CFDictionaryRef service, CFDictionaryRef options, u_int8_t onTraffic)
{
    char 			str[MAXPATHLEN], str2[256];
    int 			needpasswd = 0, tokendone = 0, auth_default = 1, from_service, optfd, awaketime;
    u_int32_t			auth_bits = 0xF; /* PAP + CHAP + MSCHAP1 + MPCHAP2 */
    u_int32_t			len, lval, lval1, i;
    u_char 			sopt[OPT_STR_LEN];
    CFDictionaryRef		pppdict = NULL, dict, modemdict;
    CFArrayRef			array = NULL;
    CFStringRef			string = NULL;
    CFDataRef			dataref = 0;
    void			*dataptr = 0;
    u_int32_t			datalen = 0;

    pppdict = CFDictionaryGetValue(service, kSCEntNetPPP);
    if ((pppdict == 0) || (CFGetTypeID(pppdict) != CFDictionaryGetTypeID()))
        return -1; 	// that's bad...
    
    optfd = serv->u.ppp.controlfd[WRITE];

    writeparam(optfd, "[OPTIONS]");

    // -----------------
    // add the dialog plugin
    if (gPluginsDir) {
        CFStringGetCString(gPluginsDir, str, sizeof(str), kCFStringEncodingUTF8);
        strcat(str, "PPPDialogs.ppp");
        writestrparam(optfd, "plugin", str);
		if (serv->subtype == PPP_TYPE_L2TP || serv->subtype == PPP_TYPE_PPTP ) {
			writeintparam(optfd, "dialogtype", 1);
		}
	}

    // -----------------
    // verbose logging 
    get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPVerboseLogging, options, service, &lval, 0);
    if (lval)
        writeparam(optfd, "debug");

    // -----------------
    // alert flags 
    serv->flags &= ~(FLAG_ALERTERRORS + FLAG_ALERTPASSWORDS);
    ppp_getoptval(serv, options, service, PPP_OPT_ALERTENABLE, &lval, &len);
    if (lval & PPP_ALERT_ERRORS)
        serv->flags |= FLAG_ALERTERRORS;
    if (lval & PPP_ALERT_PASSWORDS)
        serv->flags |= FLAG_ALERTPASSWORDS;
                
    if (ppp_getoptval(serv, options, service, PPP_OPT_LOGFILE, sopt, &len) && sopt[0]) {
        // if logfile start with /, it's a full path
        // otherwise it's relative to the logs folder (convention)
        // we also strongly advise to name the file with the link number
        // for example ppplog0
        // the default path is /var/log
        // it's useful to have the debug option with the logfile option
        // with debug option, pppd will log the negociation
        // debug option is different from kernel debug trace

        sprintf(str, "%s%s", sopt[0] == '/' ? "" : DIR_LOGS, sopt);
        writestrparam(optfd, "logfile", str);
    }

    // -----------------
    // connection plugin
    if (serv->subtypeRef) {
		CFStringGetCString(serv->subtypeRef, str2, sizeof(str2) - 4, kCFStringEncodingUTF8);
		strcat(str2, ".ppp");	// add plugin suffix
		writestrparam(optfd, "plugin", str2);
	}
	
    // -----------------
    // device name 
    if (ppp_getoptval(serv, options, service, PPP_OPT_DEV_NAME, sopt, &len) && sopt[0])
        writestrparam(optfd, "device", sopt);

    // -----------------
    // device speed 
    if (ppp_getoptval(serv, options, service, PPP_OPT_DEV_SPEED, &lval, &len) && lval) {
        sprintf(str, "%d", lval);
        writeparam(optfd, str);
    }
        
    // -----------------
    // subtype specific parameters 

    switch (serv->subtype) {
    
        case PPP_TYPE_SERIAL: 
    
            // the controller has a built-in knowledge of serial connections
    
            /*  PPPSerial currently supports Modem
                in case of Modem, the DeviceName key will contain the actual device,
                while the Modem dictionnary will contain the modem settings.
                if Hardware is undefined, or different from Modem, 
                we expect to find external configuration files. 
                (This is the case for ppp over ssh) 
            */
            get_str_option(serv, kSCEntNetInterface, kSCPropNetInterfaceHardware, options, 0, sopt, &lval, "");
            if (strcmp(sopt, "Modem")) {
                // we are done
                break;
            }
        
	#if 1
			/* merge all modem options into modemdict */
			modemdict = copyEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetModem);
			if (!modemdict) {
                break;
			}

			if (options) {
				dict = CFDictionaryGetValue(options, kSCEntNetModem);
				if (dict && 
					(CFGetTypeID(dict) == CFDictionaryGetTypeID()) && 
					CFDictionaryGetCount(dict)) {
				
					CFMutableDictionaryRef modemdict_mutable = CFDictionaryCreateMutableCopy(NULL, 0, modemdict);
					if (modemdict_mutable) {
						CFTypeRef value;

						// merge it into modemdict only some of the keys
						
						if (value = CFDictionaryGetValue(dict, kSCPropNetModemConnectionScript))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemConnectionScript, value);

						if (value = CFDictionaryGetValue(dict, kSCPropNetModemSpeaker))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemSpeaker, value);
						if (value = CFDictionaryGetValue(dict, kSCPropNetModemErrorCorrection))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemErrorCorrection, value);
						if (value = CFDictionaryGetValue(dict, kSCPropNetModemDataCompression))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemDataCompression, value);
						if (value = CFDictionaryGetValue(dict, kSCPropNetModemPulseDial))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemPulseDial, value);
						if (value = CFDictionaryGetValue(dict, kSCPropNetModemDialMode))
							CFDictionarySetValue(modemdict_mutable, kSCPropNetModemDialMode, value);
							
						CFRelease(modemdict);
						modemdict = modemdict_mutable;
					}
				}
			}
			
			/* serialize the modem dictionary, and pass it as a parameter */
			if (dataref = Serialize(modemdict, &dataptr, &datalen)) {

				writedataparam(optfd, "modemdict", dataptr, datalen);
				CFRelease(dataref);
			}

			CFRelease(modemdict);
#endif
	#if 0
	
            if (ppp_getoptval(ppp, options, 0, PPP_OPT_DEV_CONNECTSCRIPT, sopt, &len) && sopt[0]) {
                // ---------- connect script parameter ----------
                writestrparam(optfd, "modemscript", sopt);
                
                // add all the ccl flags
                get_int_option(ppp, kSCEntNetModem, kSCPropNetModemSpeaker, options, 0, &lval, 1);
                writeparam(optfd, lval ? "modemsound" : "nomodemsound");
        
                get_int_option(ppp, kSCEntNetModem, kSCPropNetModemErrorCorrection, options, 0, &lval, 1);
                writeparam(optfd, lval ? "modemreliable" : "nomodemreliable");
    
                get_int_option(ppp, kSCEntNetModem, kSCPropNetModemDataCompression, options, 0, &lval, 1);
                writeparam(optfd, lval ? "modemcompress" : "nomodemcompress");
    
                get_int_option(ppp, kSCEntNetModem, kSCPropNetModemPulseDial, options, 0, &lval, 0);
                writeparam(optfd, lval ? "modempulse" : "modemtone");
        
                // dialmode : 0 = normal, 1 = blind(ignoredialtone), 2 = manual
                lval = 0;
                ppp_getoptval(ppp, options, 0, PPP_OPT_DEV_DIALMODE, &lval, &len);
                writeintparam(optfd, "modemdialmode", lval);
            }
#endif
            break;
            
        case PPP_TYPE_L2TP: 
        
            string = get_cf_option(kSCEntNetL2TP, kSCPropNetL2TPTransport, CFStringGetTypeID(), options, service, 0);
            if (string) {
                if (CFStringCompare(string, kSCValNetL2TPTransportIP, 0) == kCFCompareEqualTo)
                    writeparam(optfd, "l2tpnoipsec");
            }
    
			/* check for SharedSecret keys in L2TP dictionary */
            get_str_option(serv, kSCEntNetL2TP, kSCPropNetL2TPIPSecSharedSecret, options, service, sopt, &lval, "");
            if (sopt[0]) {
                writestrparam(optfd, "l2tpipsecsharedsecret", sopt);                        

				string = get_cf_option(kSCEntNetL2TP, kSCPropNetL2TPIPSecSharedSecretEncryption, CFStringGetTypeID(), options, service, 0);
				if (string) {
					if (CFStringCompare(string, CFSTR("Key"), 0) == kCFCompareEqualTo)
						writestrparam(optfd, "l2tpipsecsharedsecrettype", "key");                        
					else if (CFStringCompare(string, kSCValNetL2TPIPSecSharedSecretEncryptionKeychain, 0) == kCFCompareEqualTo)
						writestrparam(optfd, "l2tpipsecsharedsecrettype", "keychain");                        
				}
            } 
			/* then check IPSec dictionary */
			else {		
				get_str_option(serv, kSCEntNetIPSec, kSCPropNetIPSecSharedSecret, options, service, sopt, &lval, "");
				if (sopt[0]) {
					writestrparam(optfd, "l2tpipsecsharedsecret", sopt);                        
					string = get_cf_option(kSCEntNetL2TP, kSCPropNetIPSecSharedSecretEncryption, CFStringGetTypeID(), options, service, 0);
					if (string) {
						if (CFStringCompare(string, CFSTR("Key"), 0) == kCFCompareEqualTo)
							writestrparam(optfd, "l2tpipsecsharedsecrettype", "key");                        
						else if (CFStringCompare(string, kSCValNetIPSecSharedSecretEncryptionKeychain, 0) == kCFCompareEqualTo)
							writestrparam(optfd, "l2tpipsecsharedsecrettype", "keychain");                        
					}
				}
			}
			
            get_int_option(serv, kSCEntNetL2TP, CFSTR("UDPPort"), options, service, &lval, 0 /* Dynamic port */);
            writeintparam(optfd, "l2tpudpport", lval);
            break;
    
        case PPP_TYPE_PPTP: 
            get_int_option(serv, kSCEntNetPPTP, CFSTR("TCPKeepAlive"), options, service, &lval, 0);
            if (lval) {
                get_int_option(serv, kSCEntNetPPTP, CFSTR("TCPKeepAliveTimer"), options, service, &lval, 0);
            }
            else {
                /* option doesn't exist, piggy-back on lcp echo option */
                ppp_getoptval(serv, options, service, PPP_OPT_LCP_ECHO, &lval, &len);
                lval = lval >> 16;
            }
            writeintparam(optfd, "pptp-tcp-keepalive", lval);
            break;
    }
    
    // -----------------
    // terminal option
    if (ppp_getoptval(serv, options, service, PPP_OPT_COMM_TERMINALMODE, &lval, &len)) {

        /* add the PPPSerial plugin if not already present
         Fix me : terminal mode is only supported in PPPSerial types of connection
         but subtype using ptys can use it the same way */    
        if (lval != PPP_COMM_TERM_NONE && serv->subtype != PPP_TYPE_SERIAL)
            writestrparam(optfd, "plugin", "PPPSerial.ppp");

        if (lval == PPP_COMM_TERM_WINDOW)
            writeparam(optfd, "terminalwindow");
        else if (lval == PPP_COMM_TERM_SCRIPT)
            if (ppp_getoptval(serv, options, service, PPP_OPT_COMM_TERMINALSCRIPT, sopt, &len) && sopt[0])
                writestrparam(optfd, "terminalscript", sopt);            
    }

    // -----------------
    // generic phone number option
    if (ppp_getoptval(serv, options, service, PPP_OPT_COMM_REMOTEADDR, sopt, &len) && sopt[0])
        writestrparam(optfd, "remoteaddress", sopt);
    
    // -----------------
    // redial options 
    get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPCommRedialEnabled, options, service, &lval, 0);
    if (lval) {
            
        get_str_option(serv, kSCEntNetPPP, kSCPropNetPPPCommAlternateRemoteAddress, options, service, sopt, &lval, "");
        if (sopt[0])
            writestrparam(optfd, "altremoteaddress", sopt);
        
        get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPCommRedialCount, options, service, &lval, 0);
        if (lval)
            writeintparam(optfd, "redialcount", lval);

        get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPCommRedialInterval, options, service, &lval, 0);
        if (lval)
            writeintparam(optfd, "redialtimer", lval);
    }

	awaketime = gSleeping ? 0 : ((mach_absolute_time() - gWakeUpTime) * gTimeScaleSeconds);
	if (awaketime < MAX_EXTRACONNECTTIME) {
        writeintparam(optfd, "extraconnecttime", MAX(MAX_EXTRACONNECTTIME - awaketime, MIN_EXTRACONNECTTIME));
	}
	
	// -----------------
    // idle options 
    if (ppp_getoptval(serv, options, service, PPP_OPT_COMM_IDLETIMER, &lval, &len) && lval) {
        writeintparam(optfd, "idle", lval);
        writeparam(optfd, "noidlerecv");
    }

    // -----------------
    // connection time option 
    if (ppp_getoptval(serv, options, service, PPP_OPT_COMM_SESSIONTIMER, &lval, &len) && lval)
        writeintparam(optfd, "maxconnect", lval);
    
    // -----------------
    // dial on demand options 
    if (onTraffic) {
        writeparam(optfd, "demand");
        get_int_option(serv, kSCEntNetPPP, CFSTR("HoldOffTime"), 0, service, &lval, 30);
        writeintparam(optfd, "holdoff", lval);
		if ((onTraffic & 0x2) && lval)
			writeparam(optfd, "holdfirst");
        get_int_option(serv, kSCEntNetPPP, CFSTR("MaxFailure"), 0, service, &lval, 3);
        writeintparam(optfd, "maxfail", lval);
    }

    // -----------------
    // lcp echo options 
    // set echo option, so ppp hangup if we pull the modem cable
    // echo option is 2 bytes for interval + 2 bytes for failure
    if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_ECHO, &lval, &len) && lval) {
        if (lval >> 16)
            writeintparam(optfd, "lcp-echo-interval", lval >> 16);

        if (lval & 0xffff)
            writeintparam(optfd, "lcp-echo-failure", lval & 0xffff);
    }
    
    // -----------------
    // address and protocol field compression options 
    if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_HDRCOMP, &lval, &len)) {
        if (!(lval & 1))
            writeparam(optfd, "nopcomp");
        if (!(lval & 2))
            writeparam(optfd, "noaccomp");
    }

    // -----------------
    // mru option 
    if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_MRU, &lval, &len) && lval)
        writeintparam(optfd, "mru", lval);

    // -----------------
    // mtu option 
    if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_MTU, &lval, &len) && lval)
        writeintparam(optfd, "mtu", lval);

    // -----------------
    // receive async map option 
    if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_RCACCM, &lval, &len)) {
        if (lval)
			writeintparam(optfd, "asyncmap", lval);
		else 
			writeparam(optfd, "receive-all");
	} 
	else 
		writeparam(optfd, "default-asyncmap");

    // -----------------
    // send async map option 
     if (ppp_getoptval(serv, options, service, PPP_OPT_LCP_TXACCM, &lval, &len) && lval) {
            writeparam(optfd, "escape");
            str[0] = 0;
            for (lval1 = 0; lval1 < 32; lval1++) {
                if ((lval >> lval1) & 1) {
                    sprintf(str2, "%d,", lval1);
                    strcat(str, str2);
               }
            }
            str[strlen(str)-1] = 0; // remove last ','
            writeparam(optfd, str);
       }

    // -----------------
    // ipcp options 
    if (!existEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetIPv4)) {
        writeparam(optfd, "noip");
    }
    else {
    
        // -----------------
        // set ip param to be the router address 
        if (getStringFromEntity(gDynamicStore, kSCDynamicStoreDomainState, 0, 
            kSCEntNetIPv4, kSCPropNetIPv4Router, sopt, OPT_STR_LEN) && sopt[0])
            writestrparam(optfd, "ipparam", sopt);
        
        // OverridePrimary option not handled yet in Setup by IPMonitor
        get_int_option(serv, kSCEntNetIPv4, kSCPropNetOverridePrimary, 0 /* don't look in options */, service, &lval, 0);
        if (lval) {
            writeparam(optfd, "defaultroute");
	    writeparam(optfd, "noacsp"); // acsp not need when all traffic is sent over PPP
	}
    
        // -----------------
        // vj compression option 
        if (! (ppp_getoptval(serv, options, service, PPP_OPT_IPCP_HDRCOMP, &lval, &len) && lval))
            writeparam(optfd, "novj");
    
        // -----------------
        // XXX  enforce the source address
        if (serv->subtype == PPP_TYPE_L2TP || serv->subtype == PPP_TYPE_PPTP ) {
            writeintparam(optfd, "ip-src-address-filter", 2);
        }
        
        // -----------------
        // ip addresses options
        if (ppp_getoptval(serv, options, service, PPP_OPT_IPCP_LOCALADDR, &lval, &len) && lval)
            sprintf(str2, "%d.%d.%d.%d", lval >> 24, (lval >> 16) & 0xFF, (lval >> 8) & 0xFF, lval & 0xFF);
        else 
            strcpy(str2, "0");
    
        strcpy(str, str2);
        strcat(str, ":");
        if (ppp_getoptval(serv, options, service, PPP_OPT_IPCP_REMOTEADDR, &lval, &len) && lval) 
            sprintf(str2, "%d.%d.%d.%d", lval >> 24, (lval >> 16) & 0xFF, (lval >> 8) & 0xFF, lval & 0xFF);
        else 
            strcpy(str2, "0");
        strcat(str, str2);
        writeparam(optfd, str);
    
        writeparam(optfd, "noipdefault");
        writeparam(optfd, "ipcp-accept-local");
        writeparam(optfd, "ipcp-accept-remote");
    
        // -----------------
		// add a route for the interface subnet
		writeparam(optfd, "addifroute");

    /* ************************************************************************* */
    
        // usepeerdns option
		get_int_option(serv, kSCEntNetPPP, CFSTR("IPCPUsePeerDNS"), options, service, &lval, 1);
        if (lval)
            writeparam(optfd, "usepeerdns");

		// usepeerwins if a SMB dictionary is present
		// but make sure it is not disabled in PPP
#ifndef TARGET_EMBEDDED_OS
		if (existEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetSMB)) {
			get_int_option(serv, kSCEntNetPPP, CFSTR("IPCPUsePeerWINS"), options, service, &lval, 1);
			if (lval)
				writeparam(optfd, "usepeerwins");
		}
#endif

    } // of existEntity IPv4
    
    // -----------------
    // ip6cp options 
    if (!existEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID, kSCEntNetIPv6)) {
        // ipv6 is not started by default
    }
    else {
        writeparam(optfd, "+ipv6");
        writeparam(optfd, "ipv6cp-use-persistent");
    }

    // -----------------
    // acsp options 
    get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPACSPEnabled, options, service, &lval, 0);
    if (lval == 0)
        writeparam(optfd, "noacsp");

	// dhcp is on by default for vpn, and off for everything else 
    get_int_option(serv, kSCEntNetPPP, CFSTR("UseDHCP"), options, service, &lval,  (serv->subtype == PPP_TYPE_L2TP || serv->subtype == PPP_TYPE_PPTP) ? 1 : 0);
    if (lval == 1)
        writeparam(optfd, "use-dhcp");
    

    // -----------------
    // authentication options 

    // don't want authentication on our side...
    writeparam(optfd, "noauth");

     if (ppp_getoptval(serv, options, service, PPP_OPT_AUTH_PROTO, &lval, &len) && (lval != PPP_AUTH_NONE)) {

		CFStringRef			encryption = NULL;
        
		if (ppp_getoptval(serv, options, service, PPP_OPT_AUTH_NAME, sopt, &len) && sopt[0]) {


            writestrparam(optfd, "user", sopt);
			needpasswd = 1;

            lval1 = get_str_option(serv, kSCEntNetPPP, kSCPropNetPPPAuthPassword, options, service, sopt, &lval, "");
            if (sopt[0]) {
			
                /* get the encryption method at the same place the password is coming from. */
				encryption = get_cf_option(kSCEntNetPPP, kSCPropNetPPPAuthPasswordEncryption, CFStringGetTypeID(), 
					(lval1 == 3) ? NULL : options, (lval1 == 3) ? service : NULL , NULL);

				if (encryption && (CFStringCompare(encryption, kSCValNetPPPAuthPasswordEncryptionKeychain, 0) == kCFCompareEqualTo)) {
					writestrparam(optfd, (lval1 == 3) ? "keychainpassword" : "userkeychainpassword", sopt);
				}
				else if (encryption && (CFStringCompare(encryption, kSCValNetPPPAuthPasswordEncryptionToken, 0) == kCFCompareEqualTo)) {
					writeintparam(optfd, "tokencard", 1);
					tokendone = 1;
				}
				else {
					CFStringRef aString = CFStringCreateWithCString(NULL, sopt, kCFStringEncodingUTF8);
					if (aString) {
						CFStringGetCString(aString, sopt, OPT_STR_LEN, kCFStringEncodingWindowsLatin1);
						CFRelease(aString);
					}
					writestrparam(optfd, "password", sopt);
				}
            }
            else { 
				encryption = get_cf_option(kSCEntNetPPP, kSCPropNetPPPAuthPasswordEncryption, CFStringGetTypeID(), options, service, NULL);
				if (encryption && (CFStringCompare(encryption, kSCValNetPPPAuthPasswordEncryptionToken, 0) == kCFCompareEqualTo)) {
					writeintparam(optfd, "tokencard", 1);
					tokendone = 1;
				}
            }
        }
		else {
			encryption = get_cf_option(kSCEntNetPPP, kSCPropNetPPPAuthPasswordEncryption, CFStringGetTypeID(), options, service, NULL);
			if (encryption && (CFStringCompare(encryption, kSCValNetPPPAuthPasswordEncryptionToken, 0) == kCFCompareEqualTo)) {
				writeintparam(optfd, "tokencard", 1);
				tokendone = 1;
				needpasswd = 1;
			}
		}
    }

    // load authentication protocols
    array = 0;
    if (options) {
        dict = CFDictionaryGetValue(options, kSCEntNetPPP);
        if (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID()))
            array = CFDictionaryGetValue(dict, kSCPropNetPPPAuthProtocol);            
    }
    if ((array == 0) || (CFGetTypeID(array) != CFArrayGetTypeID())) {
        array = CFDictionaryGetValue(pppdict, kSCPropNetPPPAuthProtocol);
    }
    if (array && (CFGetTypeID(array) == CFArrayGetTypeID()) && (lval = CFArrayGetCount(array))) {
        auth_default = 0;
        auth_bits = 0; // clear bits
        for (i = 0; i < lval; i++) {
            string = CFArrayGetValueAtIndex(array, i);
            if (string && (CFGetTypeID(string) == CFStringGetTypeID())) {
                if (CFStringCompare(string, kSCValNetPPPAuthProtocolPAP, 0) == kCFCompareEqualTo)
                    auth_bits |= 1;
                else if (CFStringCompare(string, kSCValNetPPPAuthProtocolCHAP, 0) == kCFCompareEqualTo)
                    auth_bits |= 2;
                else if (CFStringCompare(string, CFSTR("MSCHAP1") /*kSCValNetPPPAuthProtocolMSCHAP1*/ , 0) == kCFCompareEqualTo)
                    auth_bits |= 4;
                else if (CFStringCompare(string,  CFSTR("MSCHAP2") /*kSCValNetPPPAuthProtocolMSCHAP2*/, 0) == kCFCompareEqualTo)
                    auth_bits |= 8;
                else if (CFStringCompare(string,  CFSTR("EAP") /*kSCValNetPPPAuthProtocolEAP*/, 0) == kCFCompareEqualTo)
                    auth_bits |= 0x10;
            }
        }
    }

	

	if (!tokendone) {
		// authentication variation for token card support...
		get_int_option(serv, kSCEntNetPPP, CFSTR("TokenCard"), options, service, &lval, 0);
		if (lval) {
			writeintparam(optfd, "tokencard", lval);
			needpasswd = 1;
		}
	}
	
    // -----------------
    // eap options 

    if (auth_bits & 0x10) {
        auth_bits &= ~0x10; // clear EAP flag
        array = 0;
        from_service = 0;
        if (options) {
            dict = CFDictionaryGetValue(options, kSCEntNetPPP);
            if (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID()))
                array = CFDictionaryGetValue(dict, CFSTR("AuthEAPPlugins") /*kSCPropNetPPPAuthEAPPlugins*/);
        }
        if ((array == 0) || (CFGetTypeID(array) != CFArrayGetTypeID())) {
            array = CFDictionaryGetValue(pppdict, CFSTR("AuthEAPPlugins") /*kSCPropNetPPPAuthEAPPlugins*/);
            from_service = 1;
        }
        if (array && (CFGetTypeID(array) == CFArrayGetTypeID()) && (lval = CFArrayGetCount(array))) {
            for (i = 0; i < lval; i++) {
                string = CFArrayGetValueAtIndex(array, i);
                if (string && (CFGetTypeID(string) == CFStringGetTypeID())) {
                    CFStringGetCString(string, str, sizeof(str) - 4, kCFStringEncodingUTF8);
                    // for user options, we only accept plugin in the EAP directory (/System/Library/Extensions)
                    if (from_service || strchr(str, '\\') == 0) {
                        strcat(str, ".ppp");	// add plugin suffix
                        writestrparam(optfd, "eapplugin", str);
                        auth_bits |= 0x10; // confirm EAP flag
                    }
                }
            }
        }
    }

    // -----------------
    // ccp options, only for PPTP subtype.  N/A for other sub-types
	lval = 0;
	
	if(serv->subtype == PPP_TYPE_PPTP)
		get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPCCPEnabled, options, service, &lval, 0);
	
    if (lval
        // Fix me : to enforce use of MS-CHAP, refuse any alteration of default auth proto 
        // a dialer specifying PAP or CHAP will work without CCP/MPPE
        // even is CCP is enabled in the configuration.
        // Will be revisited when addition compression modules and
        // authentication modules will be added 
        && ppp_getoptval(serv, options, service, PPP_OPT_AUTH_PROTO, &lval, &len) 
                && (lval == OPT_AUTH_PROTO_DEF)) {

        // Fix me : mppe is the only currently supported compression 
        // if the CCPAccepted and CCPRequired array are not there, 
        // assume we accept all types of compression we support

        writeparam(optfd, "mppe-stateless");
		get_int_option(serv, kSCEntNetPPP, CFSTR("CCPMPPE128Enabled"), options, service, &lval, 1);
		writeparam(optfd, lval ? "mppe-128" : "nomppe-128");        
		get_int_option(serv, kSCEntNetPPP, CFSTR("CCPMPPE40Enabled"), options, service, &lval, 1);
        writeparam(optfd, lval ? "mppe-40" : "nomppe-40");        

        // No authentication specified, also enforce the use of MS-CHAP
        if (auth_default)
            auth_bits = 0xc; /* MSCHAP 1 and 2 only */
    }
    else {
        // no compression protocol
        writeparam(optfd, "noccp");	
    }
    
    // set authentication protocols parameters
    if ((auth_bits & 1) == 0)
        writeparam(optfd, "refuse-pap");
    if ((auth_bits & 2) == 0)
        writeparam(optfd, "refuse-chap-md5");
    if ((auth_bits & 4) == 0)
        writeparam(optfd, "refuse-mschap");
    if ((auth_bits & 8) == 0)
        writeparam(optfd, "refuse-mschap-v2");
    if ((auth_bits & 0x10) == 0)
        writeparam(optfd, "refuse-eap");
        
    // if EAP is the only method, pppd doesn't need to ask for the password
    // let the EAP plugin handle that.
    // if there is an other protocol than EAP, then we still need to prompt for password 
    if (auth_bits == 0x10)
        needpasswd = 0;

    // loop local traffic destined to the local ip address
    // Radar #3124639.
    //writeparam(optfd, "looplocal");       

    if (!(serv->flags & FLAG_ALERTPASSWORDS) || !needpasswd)
        writeparam(optfd, "noaskpassword");

    get_str_option(serv, kSCEntNetPPP, kSCPropNetPPPAuthPrompt, options, service, sopt, &lval, "");
    if (sopt[0]) {
        str2[0] = 0;
        CFStringGetCString(kSCValNetPPPAuthPromptAfter, str2, sizeof(str2), kCFStringEncodingUTF8);
        if (!strcmp(sopt, str2))
            writeparam(optfd, "askpasswordafter");
    }
    
    // -----------------
    // no need for pppd to detach.
    writeparam(optfd, "nodetach");

    // -----------------
    // reminder option must be specified after PPPDialogs plugin option
    get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPIdleReminder, options, service, &lval, 0);
    if (lval) {
        get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPIdleReminderTimer, options, service, &lval, 0);
        if (lval)
            writeintparam(optfd, "reminder", lval);
    }

    // -----------------
    // add any additional plugin we want to load
    array = CFDictionaryGetValue(pppdict, kSCPropNetPPPPlugins);
    if (array && (CFGetTypeID(array) == CFArrayGetTypeID())) {
        lval = CFArrayGetCount(array);
        for (i = 0; i < lval; i++) {
            string = CFArrayGetValueAtIndex(array, i);
            if (string && (CFGetTypeID(string) == CFStringGetTypeID())) {
                CFStringGetCString(string, str, sizeof(str) - 4, kCFStringEncodingUTF8);
                strcat(str, ".ppp");	// add plugin suffix
                writestrparam(optfd, "plugin", str);
            }
        }
    }
    
    // -----------------
     // always try to use options defined in /etc/ppp/peers/[service provider] 
    // they can override what have been specified by the PPPController
    // look first in ppp dictionary, then in service
	if (GetStrFromDict(pppdict, kSCPropUserDefinedName, sopt, OPT_STR_LEN, "")
		|| GetStrFromDict(service, kSCPropUserDefinedName, sopt, OPT_STR_LEN, "")) 
        writestrparam(optfd, "call", sopt);
	
    writeparam(optfd, "[EOP]");

    return 0;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
int change_pppd_params(struct service *serv, CFDictionaryRef service, CFDictionaryRef options)
{
    int 			optfd;
    u_int32_t			lval, len;
    CFDictionaryRef		pppdict = NULL;

    pppdict = CFDictionaryGetValue(service, kSCEntNetPPP);
    if ((pppdict == 0) || (CFGetTypeID(pppdict) != CFDictionaryGetTypeID()))
        return -1; 	// that's bad...
    
    optfd = serv->u.ppp.controlfd[WRITE];

    writeparam(optfd, "[OPTIONS]");

    // -----------------
    // reminder option must be specified after PPPDialogs plugin option
    get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPIdleReminder, options, service, &lval, 0);
    if (lval)
        get_int_option(serv, kSCEntNetPPP, kSCPropNetPPPIdleReminderTimer, options, service, &lval, 0);
    writeintparam(optfd, "reminder", lval);

    // -----------------
    ppp_getoptval(serv, options, service, PPP_OPT_COMM_IDLETIMER, &lval, &len);
    writeintparam(optfd, "idle", lval);

    writeparam(optfd, "[EOP]");

    return 0;
}

/* ----------------------------------------------------------------------------- 
----------------------------------------------------------------------------- */
static
int setup_bootstrap_port()
{    
	mach_port_t			server, bootstrap = 0;
	int					result;
	kern_return_t		status;
	audit_token_t		audit_token;
	uid_t               euid;

	status = bootstrap_look_up(bootstrap_port, PPPCONTROLLER_SERVER, &server);
	switch (status) {
		case BOOTSTRAP_SUCCESS :
			/* service currently registered, "a good thing" (tm) */
			break;
		case BOOTSTRAP_UNKNOWN_SERVICE :
			/* service not currently registered, try again later */
			return -1;
		default :
			return -1;
	}

	/* open a new session with the server */
	status = pppcontroller_bootstrap(server, &bootstrap, &result, &audit_token);
	mach_port_deallocate(mach_task_self(), server);

	if (status != KERN_SUCCESS) {
		printf("pppcontroller_start error: %s\n", mach_error_string(status));
		if (status != MACH_SEND_INVALID_DEST)
			printf("pppcontroller_start error NOT MACH_SEND_INVALID_DEST: %s\n", mach_error_string(status));
		return -1;
	}

	audit_token_to_au32(audit_token,
				NULL,			// auidp
				&euid,			// euid
				NULL,			// egid
				NULL,			// ruid
				NULL,			// rgid
				NULL,			// pid
				NULL,			// asid
				NULL);			// tid
	if (euid != 0) {
		printf("pppcontroller_start cannot authenticate bootstrap port from controller\n");
		return -1;
	}

	if (bootstrap) {
		task_set_bootstrap_port(mach_task_self(), bootstrap);
		mach_port_deallocate(mach_task_self(), bootstrap);
	}

    return 0;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_start(struct service *serv, CFDictionaryRef options, uid_t uid, gid_t gid, mach_port_t bootstrap, u_int8_t onTraffic, u_int8_t onDemand)
{
#define MAXARG 10
    char 			*cmdarg[MAXARG];
    u_int32_t		i, argi = 0;
    CFDictionaryRef		service;

    // reset setup flag
    serv->flags &= ~FLAG_CONFIGCHANGEDNOW;
    serv->flags &= ~FLAG_CONFIGCHANGEDLATER;

	// reset autodial flag;
    serv->flags &= ~FLAG_FIRSTDIAL;

    switch (serv->u.ppp.phase) {
        case PPP_IDLE:
            break;

        case PPP_DORMANT:	// kill dormant process and post connection flag
        case PPP_HOLDOFF:
            my_CFRelease(&serv->u.ppp.newconnectopts);
            serv->u.ppp.newconnectopts = options;
            serv->u.ppp.newconnectuid = uid;
            serv->u.ppp.newconnectgid = gid;
            serv->u.ppp.newconnectbootstrap = bootstrap;
            my_CFRetain(serv->u.ppp.newconnectopts);

            scnc_stop(serv, 0, SIGTERM);
            serv->flags |= FLAG_CONNECT;
            return 0;

        default:
			if (my_CFEqual(options, serv->connectopts)) {
				// notify client, so at least then can get the status if they were waiting got it 
				phase_changed(serv, serv->u.ipsec.phase);				
				return 0;
			}
            return EIO;	// not the right time to dial
    }

	/* remove any pending notification */
	if (serv->userNotificationRef) {
		CFUserNotificationCancel(serv->userNotificationRef);
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), serv->userNotificationRLS, kCFRunLoopDefaultMode);
		my_CFRelease(&serv->userNotificationRef);
		my_CFRelease(&serv->userNotificationRLS);			
	}
	
	serv->u.ppp.laststatus =  EXIT_FATAL_ERROR;
    serv->u.ppp.lastdevstatus = 0;

    service = copyService(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID);
    if (!service)
        goto end;	// that's bad...

    // create arguments and fork pppd 
    for (i = 0; i < MAXARG; i++) 
        cmdarg[i] = 0;
    addparam(cmdarg, &argi, PPPD_PRGM);
    addparam(cmdarg, &argi, "serviceid");
    addparam(cmdarg, &argi, serv->sid);
    addparam(cmdarg, &argi, "controlled");

    if ((socketpair(AF_LOCAL, SOCK_STREAM, 0, serv->u.ppp.controlfd) == -1) 
		|| (socketpair(AF_LOCAL, SOCK_STREAM, 0, serv->u.ppp.statusfd) == -1))
        goto end;

    serv->uid = uid;
    serv->gid = gid;
    serv->bootstrap = bootstrap;

	serv->pid = _SCDPluginExecCommand2(exec_callback, 
									   (void*)(uintptr_t)makeref(serv), 
									   geteuid(), 
									   getegid(), 
									   PATH_PPPD, 
									   cmdarg, 
									   exec_postfork, 
									   (void*)(uintptr_t)makeref(serv));
    if (serv->pid == -1)
        goto end;

    // send options to pppd using the pipe
    if (send_pppd_params(serv, service, options, onTraffic) == -1) {
        kill(serv->pid, SIGTERM);
        goto end;
    }
    
    // all options have been sent, close the pipe.
    //my_close(ppp->controlfd[WRITE]);
    //ppp->controlfd[WRITE] = -1;

    // add the pipe to runloop
    ppp_socket_create_client(serv->u.ppp.statusfd[READ], 1, 0, 0);

    serv->u.ppp.laststatus = EXIT_OK;
    ppp_updatephase(serv, PPP_INITIALIZE);
	service_started(serv);

    if (onTraffic)
        serv->flags |= FLAG_ONTRAFFIC;
	else
		serv->flags &= ~FLAG_ONTRAFFIC;

    serv->connectopts = options;
    my_CFRetain(serv->connectopts);

end:
    
    if (service)
        CFRelease(service);

    for (i = 0; i < argi; i++)
        free(cmdarg[i]);

    if (serv->pid == -1) {
        
        my_close(serv->u.ppp.statusfd[READ]);
        serv->u.ppp.statusfd[READ] = -1;
        my_close(serv->u.ppp.statusfd[WRITE]);
        serv->u.ppp.statusfd[WRITE] = -1;
        my_close(serv->u.ppp.controlfd[READ]);
        serv->u.ppp.controlfd[READ] = -1;
        my_close(serv->u.ppp.controlfd[WRITE]);
        serv->u.ppp.controlfd[WRITE] = -1;
        
        display_error(serv);
    }

    return serv->u.ppp.laststatus;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static void
exec_postfork(pid_t pid, void *arg)
{
    struct service 	*serv = findbyref(TYPE_PPP, (u_int32_t)(uintptr_t)arg);

    if (pid) {
        /* if parent */

        int	yes	= 1;

        my_close(serv->u.ppp.controlfd[READ]);
        serv->u.ppp.controlfd[READ] = -1;
        my_close(serv->u.ppp.statusfd[WRITE]);
        serv->u.ppp.statusfd[WRITE] = -1;
        if (ioctl(serv->u.ppp.controlfd[WRITE], FIONBIO, &yes) == -1) {
//           printf("ioctl(,FIONBIO,): %s\n", strerror(errno));
        }

    } else {
        /* if child */

		gid_t	egid;
		uid_t	euid;
		int	i;

		setup_bootstrap_port();

        my_close(serv->u.ppp.controlfd[WRITE]);
        serv->u.ppp.controlfd[WRITE] = -1;
        my_close(serv->u.ppp.statusfd[READ]);
        serv->u.ppp.statusfd[READ] = -1;

        if (serv->u.ppp.controlfd[READ] != STDIN_FILENO) {
            dup2(serv->u.ppp.controlfd[READ], STDIN_FILENO);
        }

        if (serv->u.ppp.statusfd[WRITE] != STDOUT_FILENO) {
            dup2(serv->u.ppp.statusfd[WRITE], STDOUT_FILENO);
        }

        close(STDERR_FILENO);
        open(_PATH_DEVNULL, O_RDWR, 0);

        /* close any other open FDs */
        for (i = getdtablesize() - 1; i > STDERR_FILENO; i--) close(i);

		// get real and effective ids
		egid = getegid();
		euid = geteuid();

		if (egid != serv->gid) {
			(void) setgid(serv->gid);
		}

		if ((euid != serv->uid) || (egid != serv->gid)) {
			char		buf[1024];
			struct passwd	pwd;
			struct passwd	*result	= NULL;

			if ((getpwuid_r(serv->uid, &pwd, buf, sizeof(buf), &result) == 0) &&
			     (result != NULL)) {
				initgroups(result->pw_name, serv->gid);
			}
		}

		if (euid != serv->uid) {
			// only set real (and not effective) UID
			(void) setruid(serv->uid);
		}

    }

    return;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
static 
void exec_callback(pid_t pid, int status, struct rusage *rusage, void *context)
{
    struct service 	*serv = findbyref(TYPE_PPP, (u_int32_t)(uintptr_t)context);
    u_int32_t	failed = 0;
	int exitcode = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
	
	if (exitcode < 0) {
		// ignore this case, just change phase
	}
	else if (exitcode > EXIT_PEER_UNREACHABLE) {
		// pppd exited because of a crash
		ppp_updatestatus(serv, 127, 0);
	}
	else if (serv->u.ppp.phase == PPP_INITIALIZE) {
        // an error occured and status has not been updated
        // happens for example when an error is encountered while parsing arguments
		failed = 1;
        ppp_updatestatus(serv, exitcode, 0);
    }

    // call the change phae function
    ppp_updatephase(serv, PPP_IDLE);
	service_ended(serv);

    // close file descriptors
    //statusfd is closed by the run loop
    //my_close(ppp->statusfd[READ]);
    serv->u.ppp.statusfd[READ] = -1;
    my_close(serv->u.ppp.controlfd[WRITE]);
    serv->u.ppp.controlfd[WRITE] = -1;
    my_CFRelease(&serv->connectopts);
    serv->connectopts = 0;

	if (allow_dispose(serv))
		serv = 0;

    allow_sleep();

    if (serv == 0)
        return;
        
    // check if configd is going away
    if (allow_stop())
		return;
	
    // now reconnect if necessary    
	
    if (serv->flags & FLAG_CONNECT) {        
        ppp_start(serv, serv->u.ppp.newconnectopts, serv->u.ppp.newconnectuid, serv->u.ppp.newconnectgid, serv->u.ppp.newconnectbootstrap, 0, 0);
        my_CFRelease(&serv->u.ppp.newconnectopts);
        serv->u.ppp.newconnectopts = 0;
		serv->u.ppp.newconnectuid = 0;
		serv->u.ppp.newconnectgid = 0;
		serv->u.ppp.newconnectbootstrap = 0;
        serv->flags &= ~FLAG_CONNECT;
    }
    else {
        // if config has changed, or ppp was previously a manual connection, then rearm onTraffic if necessary
		if (failed == 0
			&& ((serv->flags & (FLAG_CONFIGCHANGEDNOW + FLAG_CONFIGCHANGEDLATER)) || !(serv->flags & FLAG_ONTRAFFIC))
            && ((serv->flags & FLAG_SETUP_ONTRAFFIC) && (!(serv->flags & FLAG_SETUP_DISCONNECTONLOGOUT)|| gLoggedInUser))) {
            ppp_start(serv, 0, 0, 0, 0, serv->flags & FLAG_CONFIGCHANGEDNOW ? 1 : 3, 0);
        }
    }
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_stop(struct service *serv, int signal)
{
    
	/* 
		signal is either SIGHUP or SIGTERM 
		SIGHUP will only disconnect the link
		SIGTERM will terminate pppd
	*/
	if (serv->flags & (FLAG_CONFIGCHANGEDNOW + FLAG_CONFIGCHANGEDLATER))
		signal = SIGTERM;
	
    // anticipate next phase
    switch (serv->u.ppp.phase) {
    
        case PPP_IDLE:
            return 0;

        case PPP_DORMANT:
			/* SIGHUP has no effect on effect on DORMANT phase */
			if (signal == SIGHUP)
				return 0;
            break;

        case PPP_HOLDOFF:
			/* we don't want SIGHUP to stop the HOLDOFF phase */
			if (signal == SIGHUP)
				return 0;
            break;
            
        case PPP_DISCONNECTLINK:
        case PPP_TERMINATE:
            break;
            
        case PPP_INITIALIZE:
            serv->flags &= ~FLAG_CONNECT;
            // no break;
            
        case PPP_CONNECTLINK:
            ppp_updatephase(serv, PPP_DISCONNECTLINK);
            break;
        
        default:
            ppp_updatephase(serv, PPP_TERMINATE);
    }

    kill(serv->pid, signal);
    return 0;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_suspend(struct service *serv)
{

    if (serv->u.ppp.phase != PPP_IDLE)
        kill(serv->pid, SIGTSTP);

    return 0;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_resume(struct service *serv)
{
    if (serv->u.ppp.phase != PPP_IDLE)
        kill(serv->pid, SIGCONT);

    return 0;
}

/* -----------------------------------------------------------------------------
status change for this ppp occured
----------------------------------------------------------------------------- */
void ppp_updatestatus(struct service *serv, int status, int devstatus)
{

    serv->u.ppp.laststatus = status;
    serv->u.ppp.lastdevstatus = devstatus;

    display_error(serv);
}

/* -----------------------------------------------------------------------------
phase change for this ppp occured
----------------------------------------------------------------------------- */
void ppp_updatephase(struct service *serv, int phase)
{

  /* check if update is received pppd has  exited */
  if (serv->u.ppp.statusfd[READ] == -1)
      return;


    /* check for new phase */
    if (phase == serv->u.ppp.phase)
        return;
    
    serv->u.ppp.phase = phase;
	phase_changed(serv, phase);

    switch (serv->u.ppp.phase) {
        case PPP_INITIALIZE:
            serv->connecttime = mach_absolute_time() * gTimeScaleSeconds;
            break;

        case PPP_RUNNING:
            serv->u.ppp.ifname[0] = 0;
            getStringFromEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, 
                    kSCEntNetPPP, kSCPropInterfaceName, serv->u.ppp.ifname, sizeof(serv->u.ppp.ifname));
            SESSIONTRACERESTABLISHED(serv);
            break;
            
        case PPP_DORMANT:
            serv->u.ppp.ifname[0] = 0;
            getStringFromEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, 
                    kSCEntNetPPP, kSCPropInterfaceName, serv->u.ppp.ifname, sizeof(serv->u.ppp.ifname));
            // no break;

        case PPP_HOLDOFF:

            /* check if setup has changed */
            if (serv->flags & FLAG_CONFIGCHANGEDLATER)
                scnc_stop(serv, 0, SIGTERM);
            break;
    }
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
SCNetworkConnectionStatus ppp_getstatus(struct service *serv)
{
	SCNetworkConnectionStatus	status = kSCNetworkConnectionDisconnected;

	switch (serv->u.ppp.phase) {
		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_WAITING:
			status = kSCNetworkConnectionDisconnecting;
			break;
		case PPP_RUNNING:
		case PPP_ONHOLD:
			status = kSCNetworkConnectionConnected;
			break;
		case PPP_IDLE:
		case PPP_DORMANT:
		case PPP_HOLDOFF:
		default:
			status = kSCNetworkConnectionDisconnected;
	}
	
	return status;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_getstatus1(struct service *serv, void **reply, u_int16_t *replylen)
{
    struct ppp_status 		*stat;
    struct ifpppstatsreq 	rq;
    int		 		s;
    u_int32_t			retrytime, conntime, disconntime, curtime;

    *reply = my_Allocate(sizeof(struct ppp_status));
    if (*reply == 0) {
        return ENOMEM;
    }
    stat = (struct ppp_status *)*reply;

    bzero (stat, sizeof (struct ppp_status));
    switch (serv->u.ppp.phase) {
        case PPP_DORMANT:
        case PPP_HOLDOFF:
            stat->status = PPP_IDLE;		// Dial on demand does not exist in the api
            break;
        default:
            stat->status = serv->u.ppp.phase;
    }

    switch (stat->status) {
        case PPP_RUNNING:
        case PPP_ONHOLD:

            s = socket(AF_INET, SOCK_DGRAM, 0);
            if (s < 0) {
            	my_Deallocate(*reply, sizeof(struct ppp_status));
                return errno;
            }
    
            bzero (&rq, sizeof (rq));
    
            strncpy(rq.ifr_name, serv->u.ppp.ifname, IFNAMSIZ);
            if (ioctl(s, SIOCGPPPSTATS, &rq) < 0) {
                close(s);
            	my_Deallocate(*reply, sizeof(struct ppp_status));
                return errno;
            }
    
            close(s);

            conntime = 0;
            getNumberFromEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, 
                kSCEntNetPPP, kSCPropNetPPPConnectTime, &conntime);
            disconntime = 0;
            getNumberFromEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, 
                kSCEntNetPPP, kSCPropNetPPPDisconnectTime, &disconntime);

            curtime = mach_absolute_time() * gTimeScaleSeconds;
            if (conntime)
				stat->s.run.timeElapsed = curtime - conntime;
            if (!disconntime)	// no limit...
     	       stat->s.run.timeRemaining = 0xFFFFFFFF;
            else
      	      stat->s.run.timeRemaining = (disconntime > curtime) ? disconntime - curtime : 0;

            stat->s.run.outBytes = rq.stats.p.ppp_obytes;
            stat->s.run.inBytes = rq.stats.p.ppp_ibytes;
            stat->s.run.inPackets = rq.stats.p.ppp_ipackets;
            stat->s.run.outPackets = rq.stats.p.ppp_opackets;
            stat->s.run.inErrors = rq.stats.p.ppp_ierrors;
            stat->s.run.outErrors = rq.stats.p.ppp_ierrors;
            break;
            
        case PPP_WAITONBUSY:
        
            stat->s.busy.timeRemaining = 0;
            retrytime = 0;
            getNumberFromEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, 
                kSCEntNetPPP, kSCPropNetPPPRetryConnectTime, &retrytime);
            if (retrytime) {
                curtime = mach_absolute_time() * gTimeScaleSeconds;
                stat->s.busy.timeRemaining = (curtime < retrytime) ? retrytime - curtime : 0;
            }
            break;
         
        default:
            stat->s.disc.lastDiscCause = ppp_translate_error(serv->subtype, serv->u.ppp.laststatus, serv->u.ppp.lastdevstatus);
    }

    *replylen = sizeof(struct ppp_status);
    return 0;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_copyextendedstatus(struct service *serv, void **reply, u_int16_t *replylen)
{
    CFMutableDictionaryRef	statusdict = 0, dict = 0;
    CFDataRef			dataref = 0;
    void			*dataptr = 0;
    u_int32_t			datalen = 0;
    
    if ((statusdict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
        goto fail;

    /* create and add PPP dictionary */
    if ((dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
        goto fail;
    
    AddNumber(dict, kSCPropNetPPPStatus, serv->u.ppp.phase);
    
    if (serv->u.ppp.phase != PPP_IDLE)
        AddStringFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPCommRemoteAddress, dict);

    switch (serv->u.ppp.phase) {
        case PPP_RUNNING:
        case PPP_ONHOLD:

            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPConnectTime, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectTime, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPCompressionPField, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPCompressionACField, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPMRU, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPMTU, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPReceiveACCM, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPTransmitACCM, dict);
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPIPCPCompressionVJ, dict);
            break;
            
        case PPP_WAITONBUSY:
            AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetPPP, kSCPropNetPPPRetryConnectTime, dict);
            break;
         
        case PPP_DORMANT:
            break;
            
        default:
            AddNumber(dict, kSCPropNetPPPLastCause, serv->u.ppp.laststatus);
            AddNumber(dict, kSCPropNetPPPDeviceLastCause, serv->u.ppp.lastdevstatus);
    }

    CFDictionaryAddValue(statusdict, kSCEntNetPPP, dict);
    CFRelease(dict);

    /* create and add Modem dictionary */
    if (serv->subtype == PPP_TYPE_SERIAL
        && (serv->u.ppp.phase == PPP_RUNNING || serv->u.ppp.phase == PPP_ONHOLD)) {
        if ((dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
            goto fail;

        AddNumberFromState(gDynamicStore, serv->serviceID, kSCEntNetModem, kSCPropNetModemConnectSpeed, dict);
            
        CFDictionaryAddValue(statusdict, kSCEntNetModem, dict);
        CFRelease(dict);
    }

    /* create and add IPv4 dictionary */
    if (serv->u.ppp.phase == PPP_RUNNING || serv->u.ppp.phase == PPP_ONHOLD) {
        dict = (CFMutableDictionaryRef)copyEntity(gDynamicStore, kSCDynamicStoreDomainState, serv->serviceID, kSCEntNetIPv4);
        if (dict) {
            CFDictionaryAddValue(statusdict, kSCEntNetIPv4, dict);
            CFRelease(dict);
        }
    }

    /* We are done, now serialize it */
    if ((dataref = Serialize(statusdict, &dataptr, &datalen)) == 0)
        goto fail;
    
    *reply = my_Allocate(datalen);
    if (*reply == 0)
        goto fail;

    bcopy(dataptr, *reply, datalen);    
    CFRelease(statusdict);
    CFRelease(dataref);
    *replylen = datalen;
    return 0;

fail:
    if (statusdict)
        CFRelease(statusdict);
    if (dict)
        CFRelease(dict);
    if (dataref)
        CFRelease(dataref);
    return ENOMEM;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_copystatistics(struct service *serv, void **reply, u_int16_t *replylen)
{
    CFMutableDictionaryRef	statsdict = 0, dict = 0;
    CFDataRef			dataref = 0;
    void				*dataptr = 0;
    u_int32_t			datalen = 0;
    int					s = -1;
    struct ifpppstatsreq 	rq;
	int					error = 0;
	
	if (serv->u.ppp.phase != PPP_RUNNING
		&& serv->u.ppp.phase != PPP_ONHOLD)
			return EINVAL;
			
    if ((statsdict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0) {
		error = ENOMEM;
		goto fail;
	}

    /* create and add PPP dictionary */
    if ((dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0) {
		error = ENOMEM;
		goto fail;
	}
    
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) {
		error = errno;
        goto fail;
	}

	bzero (&rq, sizeof (rq));

	strncpy(rq.ifr_name, serv->u.ppp.ifname, IFNAMSIZ);
	if (ioctl(s, SIOCGPPPSTATS, &rq) < 0) {
		error = errno;
        goto fail;
	}

	close(s);
	s = -1;

	AddNumber(dict, kSCNetworkConnectionBytesIn, rq.stats.p.ppp_ibytes);
	AddNumber(dict, kSCNetworkConnectionBytesOut, rq.stats.p.ppp_obytes);
	AddNumber(dict, kSCNetworkConnectionPacketsIn, rq.stats.p.ppp_ipackets);
	AddNumber(dict, kSCNetworkConnectionPacketsOut, rq.stats.p.ppp_opackets);
	AddNumber(dict, kSCNetworkConnectionErrorsIn, rq.stats.p.ppp_ierrors);
	AddNumber(dict, kSCNetworkConnectionErrorsOut, rq.stats.p.ppp_ierrors);

    CFDictionaryAddValue(statsdict, kSCEntNetPPP, dict);
    CFRelease(dict);

    /* We are done, now serialize it */
    if ((dataref = Serialize(statsdict, &dataptr, &datalen)) == 0) {
		error = ENOMEM;
        goto fail;
	}
    
    *reply = my_Allocate(datalen);
    if (*reply == 0) {
		error = ENOMEM;
        goto fail;
	}

    bcopy(dataptr, *reply, datalen);    

    CFRelease(statsdict);
    CFRelease(dataref);
    *replylen = datalen;
    return 0;

fail:
	if (s != -1)
		close(s);
    if (statsdict)
        CFRelease(statsdict);
    if (dict)
        CFRelease(dict);
    if (dataref)
        CFRelease(dataref);
    return error;
}

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_getconnectsystemdata(struct service *serv, void **reply, u_int16_t *replylen)
{
	CFDictionaryRef	service = NULL;
    CFDataRef			dataref = NULL;
    void			*dataptr = 0;
    u_int32_t			datalen = 0;
	int				err = 0;

	service = copyService(gDynamicStore, kSCDynamicStoreDomainSetup, serv->serviceID);
    if (service == 0) {
        // no data
        *replylen = 0;
        return 0;
    }
    
    if ((dataref = Serialize(service, &dataptr, &datalen)) == 0) {
		err = ENOMEM;
		goto end;
    }
    
    *reply = my_Allocate(datalen);
    if (*reply == 0) {
		err = ENOMEM;
		goto end;
    }
    else {
        bcopy(dataptr, *reply, datalen);
        *replylen = datalen;
    }

end:
    if (service)
		CFRelease(service);
    if (dataref)
		CFRelease(dataref);
    return err;
 }

/* -----------------------------------------------------------------------------
----------------------------------------------------------------------------- */
int ppp_getconnectdata(struct service *serv, void **reply, u_int16_t *replylen, int all)
{
    CFDataRef			dataref = NULL;
    void			*dataptr = 0;
    u_int32_t			datalen = 0;
    CFDictionaryRef		opts;
    CFMutableDictionaryRef	mdict = NULL, mdict1;
    CFDictionaryRef	dict;
	int err = 0;
        
    /* return saved data */
    opts = serv->connectopts;

    if (opts == 0) {
        // no data
        *replylen = 0;
        return 0;
    }
    
	if (!all) {
		/* special code to remove secret information */

		mdict = CFDictionaryCreateMutableCopy(0, 0, opts);
		if (mdict == 0) {
			// no data
			*replylen = 0;
			return 0;
		}
		
		dict = CFDictionaryGetValue(mdict, kSCEntNetPPP);
		if (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID())) {
			mdict1 = CFDictionaryCreateMutableCopy(0, 0, dict);
			if (mdict1) {
				CFDictionaryRemoveValue(mdict1, kSCPropNetPPPAuthPassword);
				CFDictionarySetValue(mdict, kSCEntNetPPP, mdict1);
				CFRelease(mdict1);
			}
		}

		dict = CFDictionaryGetValue(mdict, kSCEntNetL2TP);
		if (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID())) {
			mdict1 = CFDictionaryCreateMutableCopy(0, 0, dict);
			if (mdict1) {
				CFDictionaryRemoveValue(mdict1, kSCPropNetL2TPIPSecSharedSecret);
				CFDictionarySetValue(mdict, kSCEntNetL2TP, mdict1);
				CFRelease(mdict1);
			}
		}

		dict = CFDictionaryGetValue(mdict, kSCEntNetIPSec);
		if (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID())) {
			mdict1 = CFDictionaryCreateMutableCopy(0, 0, dict);
			if (mdict1) {
				CFDictionaryRemoveValue(mdict1, kSCPropNetIPSecSharedSecret);
				CFDictionarySetValue(mdict, kSCEntNetIPSec, mdict1);
				CFRelease(mdict1);
			}
		}
	}

    if ((dataref = Serialize(all ? opts : mdict, &dataptr, &datalen)) == 0) {
		err = ENOMEM;
        goto end;
    }
    
    *reply = my_Allocate(datalen);
    if (*reply == 0) {
		err = ENOMEM;
        goto end;
    }
    else {
        bcopy(dataptr, *reply, datalen);
        *replylen = datalen;
    }

end:
    if (mdict)
		CFRelease(mdict);
    if (dataref)
		CFRelease(dataref);
    return err;
}

/* -----------------------------------------------------------------------------
translate a pppd native cause into a PPP API cause
----------------------------------------------------------------------------- */
u_int32_t ppp_translate_error(u_int16_t subtype, u_int32_t native_ppp_error, u_int32_t native_dev_error)
{
    u_int32_t	error = PPP_ERR_GEN_ERROR; 
    
    switch (native_ppp_error) {
        case EXIT_USER_REQUEST:
            error = 0;
            break;
        case EXIT_CONNECT_FAILED:
            error = PPP_ERR_GEN_ERROR;
            break;
        case EXIT_TERMINAL_FAILED:
            error = PPP_ERR_TERMSCRIPTFAILED;
            break;
        case EXIT_NEGOTIATION_FAILED:
            error = PPP_ERR_LCPFAILED;
            break;
        case EXIT_AUTH_TOPEER_FAILED:
            error = PPP_ERR_AUTHFAILED;
            break;
        case EXIT_IDLE_TIMEOUT:
            error = PPP_ERR_IDLETIMEOUT;
            break;
        case EXIT_CONNECT_TIME:
            error = PPP_ERR_SESSIONTIMEOUT;
            break;
        case EXIT_LOOPBACK:
            error = PPP_ERR_LOOPBACK;
            break;
        case EXIT_PEER_DEAD:
            error = PPP_ERR_PEERDEAD;
            break;
        case EXIT_OK:
            error = PPP_ERR_DISCBYPEER;
            break;
        case EXIT_HANGUP:
            error = PPP_ERR_DISCBYDEVICE;
            break;
    }
    
    // override with a more specific error
    if (native_dev_error) {
        switch (subtype) {
            case PPP_TYPE_SERIAL:
                switch (native_dev_error) {
                    case EXIT_PPPSERIAL_NOCARRIER:
                        error = PPP_ERR_MOD_NOCARRIER;
                        break;
                    case EXIT_PPPSERIAL_NONUMBER:
                        error = PPP_ERR_MOD_NONUMBER;
                        break;
                    case EXIT_PPPSERIAL_BADSCRIPT:
                        error = PPP_ERR_MOD_BADSCRIPT;
                        break;
                    case EXIT_PPPSERIAL_BUSY:
                        error = PPP_ERR_MOD_BUSY;
                        break;
                    case EXIT_PPPSERIAL_NODIALTONE:
                        error = PPP_ERR_MOD_NODIALTONE;
                        break;
                    case EXIT_PPPSERIAL_ERROR:
                        error = PPP_ERR_MOD_ERROR;
                        break;
                    case EXIT_PPPSERIAL_NOANSWER:
                        error = PPP_ERR_MOD_NOANSWER;
                        break;
                    case EXIT_PPPSERIAL_HANGUP:
                        error = PPP_ERR_MOD_HANGUP;
                        break;
                    default :
                        error = PPP_ERR_CONNSCRIPTFAILED;
                }
                break;
    
            case PPP_TYPE_PPPoE:
                // need to handle PPPoE specific error codes
                break;
        }
    }
    
    return error;
}