/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include // for SCLog() #endif #include #include "pppcontroller_types.h" #include "pppcontroller.h" #include #include "PPPControllerPriv.h" #include "ppp_msg.h" #include "ppp_privmsg.h" #include "../Family/ppp_domain.h" #include "../Helpers/pppd/pppd.h" #include "ppp_client.h" #include "ppp_manager.h" #include "ppp_option.h" #include "ppp_socket_server.h" #include "ppp_utils.h" /* ----------------------------------------------------------------------------- Definitions ----------------------------------------------------------------------------- */ #define CREATESERVICESETUP(a) SCDynamicStoreKeyCreateNetworkServiceEntity(0, \ kSCDynamicStoreDomainSetup, kSCCompAnyRegex, a) #define CREATESERVICESTATE(a) SCDynamicStoreKeyCreateNetworkServiceEntity(0, \ kSCDynamicStoreDomainState, kSCCompAnyRegex, a) #define CREATEPREFIXSETUP() SCDynamicStoreKeyCreate(0, CFSTR("%@/%@/%@/"), \ kSCDynamicStoreDomainSetup, kSCCompNetwork, kSCCompService) #define CREATEPREFIXSTATE() SCDynamicStoreKeyCreate(0, CFSTR("%@/%@/%@/"), \ kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompService) #define CREATEGLOBALSETUP(a) SCDynamicStoreKeyCreateNetworkGlobalEntity(0, \ kSCDynamicStoreDomainSetup, a) #define CREATEGLOBALSTATE(a) SCDynamicStoreKeyCreateNetworkGlobalEntity(0, \ kSCDynamicStoreDomainState, a) 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 ----------------------------------------------------------------------------- */ TAILQ_HEAD(, ppp) ppp_head; io_connect_t gIOPort; int gSleeping; long gSleepArgument; CFUserNotificationRef gSleepNotification; uint64_t gWakeUpTime = 0; double gTimeScaleSeconds; CFRunLoopSourceRef gStopRls; CFStringRef gLoggedInUser = NULL; uid_t gLoggedInUserUID = 0; SCDynamicStoreRef gDynamicStore = NULL; /* ----------------------------------------------------------------------------- Forward declarations ----------------------------------------------------------------------------- */ static void display_error(struct ppp *ppp); static void iosleep_notifier(void * x, io_service_t y, natural_t messageType, void *messageArgument); 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 can_sleep(); static int will_sleep(int checking); static void wake_up(); static void ipv4_state_changed(); static int is_idle(); static int log_out(); static int log_in(); static int log_switch(); static int send_pppd_params(struct ppp *ppp, CFDictionaryRef service, CFDictionaryRef options, u_int8_t dialondemand); static int change_pppd_params(struct ppp *ppp, CFDictionaryRef service, CFDictionaryRef options); static u_short findfreeunit(u_short subtype); static int add_client(struct ppp *ppp, void *client, int autoclose); static int remove_client(struct ppp *ppp, void *client); static struct ppp_client *get_client(struct ppp *ppp, void *client); static int remove_all_clients(struct ppp *ppp); static struct ppp * new_service(CFStringRef serviceID , CFStringRef subtypeRef); static int dispose_service(struct ppp *ppp); static int setup_service(CFStringRef serviceID); static void store_notifier(SCDynamicStoreRef session, CFArrayRef changedKeys, void *info); static void reorder_services(); static void finish_setup_services(); static void setup_PPPoEoAirport(struct ppp *ppp); static void dispose_PPPoEoAirport(struct ppp *ppp); #ifdef DEBUG static void print_services(); #endif /* ----------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- service management and control -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ u_long ppp_init_all() { IONotificationPortRef notify; io_object_t iterator; mach_timebase_info_data_t timebaseInfo; CFStringRef key = NULL, setup = NULL; CFMutableArrayRef keys = NULL, patterns = NULL; CFArrayRef services = NULL; int i, nb, ret = 0; CFRunLoopSourceRef rls = NULL; CFStringRef notifs[] = { kSCEntNetPPP, kSCEntNetModem, kSCEntNetInterface, kSCEntNetIPv4, kSCEntNetIPv6, kSCEntNetDNS, NULL, }; /* init list of services */ TAILQ_INIT(&ppp_head); /* setup power management callback */ gSleeping = 0; gSleepNotification = 0; gStopRls = 0; gIOPort = IORegisterForSystemPower(0, ¬ify, iosleep_notifier, &iterator); if (gIOPort == 0) { printf("IORegisterForSystemPower failed\n"); goto fail; } CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopDefaultMode); /* init time scale */ if (mach_timebase_info(&timebaseInfo) != KERN_SUCCESS) { // returns scale factor for ns printf("mach_timebase_info failed\n"); goto fail; } gTimeScaleSeconds = ((double) timebaseInfo.numer / (double) timebaseInfo.denom) / 1000000000; /* opens now our session to the Dynamic Store */ if ((gDynamicStore = SCDynamicStoreCreate(0, CFSTR("PPPController"), store_notifier, 0)) == NULL) goto fail; if ((rls = SCDynamicStoreCreateRunLoopSource(0, gDynamicStore, 0)) == NULL) goto fail; CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); gLoggedInUser = SCDynamicStoreCopyConsoleUser(gDynamicStore, &gLoggedInUserUID, 0); keys = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); patterns = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); if (keys == NULL || patterns == NULL) goto fail; /* install the notifier for the cache/setup changes */ for (i = 0; notifs[i]; i++) { if ((key = CREATESERVICESETUP(notifs[i])) == NULL) goto fail; CFArrayAppendValue(patterns, key); CFRelease(key); } /* install the notifier for the global changes */ if ((key = CREATEGLOBALSETUP(kSCEntNetIPv4)) == NULL) goto fail; CFArrayAppendValue(keys, key); CFRelease(key); if ((key = CREATEGLOBALSTATE(kSCEntNetIPv4)) == NULL) goto fail; CFArrayAppendValue(keys, key); CFRelease(key); /* install the notifier for user login/logout */ if ((key = SCDynamicStoreKeyCreateConsoleUser(0)) == NULL) goto fail; CFArrayAppendValue(keys, key); CFRelease(key); /* add all the notification in one chunk */ SCDynamicStoreSetNotificationKeys(gDynamicStore, keys, patterns); /* read the initial configured interfaces */ key = CREATESERVICESETUP(kSCEntNetPPP); setup = CREATEPREFIXSETUP(); if (key == NULL || setup == NULL) goto fail; services = SCDynamicStoreCopyKeyList(gDynamicStore, key); if (services == NULL) goto done; // no PPP service setup nb = CFArrayGetCount(services); for (i = 0; i < nb; i++) { CFStringRef serviceID; if (serviceID = parse_component(CFArrayGetValueAtIndex(services, i), setup)) { setup_service(serviceID); CFRelease(serviceID); } } reorder_services(); finish_setup_services(); //print_services(); done: my_CFRelease(services); my_CFRelease(key); my_CFRelease(setup); my_CFRelease(keys); my_CFRelease(patterns); return ret; fail: my_CFRelease(gDynamicStore); ret = -1; goto done; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void ppp_dispose_all() { } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int setup_service(CFStringRef serviceID) { CFDictionaryRef service = NULL, interface; CFStringRef subtype = NULL, iftype = NULL; struct ppp *ppp; ppp = ppp_findbyserviceID(serviceID); interface = copyEntity(kSCDynamicStoreDomainSetup, serviceID, kSCEntNetInterface); if (interface) iftype = CFDictionaryGetValue(interface, kSCPropNetInterfaceType); if (!interface || (iftype && !CFEqual(iftype, kSCValNetInterfaceTypePPP))) { // check to see if service has disappear if (ppp) { //SCDLog(LOG_INFO, CFSTR("Service has disappear : %@"), serviceID); dispose_service(ppp); } goto done; } service = copyEntity(kSCDynamicStoreDomainSetup, serviceID, kSCEntNetPPP); if (!service) // should we allow creating PPP configuration without PPP dictionnary ? goto done; /* kSCPropNetServiceSubType contains the entity key Modem, PPPoE, or L2TP */ subtype = CFDictionaryGetValue(interface, kSCPropNetInterfaceSubType); if (!subtype) goto done; //SCDLog(LOG_INFO, CFSTR("change appears, subtype = %d, serviceID = %@\n"), subtype, serviceID); if (ppp && !CFEqual(subtype, ppp->subtypeRef)) { // subtype has changed dispose_service(ppp); ppp = 0; } // check to see if it is a new service if (!ppp) { ppp = new_service(serviceID, subtype); if (!ppp) goto done; } // delay part or all the setup once all the notification for the service have been received ppp->flags |= FLAG_SETUP; done: my_CFRelease(interface); my_CFRelease(service); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void reorder_services() { CFDictionaryRef ip_dict = NULL; CFStringRef key, serviceID; CFArrayRef serviceorder; int i, nb; struct ppp *ppp; key = CREATEGLOBALSETUP(kSCEntNetIPv4); if (key) { ip_dict = (CFDictionaryRef)SCDynamicStoreCopyValue(gDynamicStore, key); if (ip_dict) { serviceorder = CFDictionaryGetValue(ip_dict, kSCPropNetServiceOrder); if (serviceorder) { nb = CFArrayGetCount(serviceorder); for (i = 0; i < nb; i++) { serviceID = CFArrayGetValueAtIndex(serviceorder, i); if (ppp = ppp_findbyserviceID(serviceID)) { /* move it to the tail */ TAILQ_REMOVE(&ppp_head, ppp, next); TAILQ_INSERT_TAIL(&ppp_head, ppp, next); } } } CFRelease(ip_dict); } CFRelease(key); } } /* ----------------------------------------------------------------------------- the configd cache/setup has changed ----------------------------------------------------------------------------- */ static void store_notifier(SCDynamicStoreRef session, CFArrayRef changedKeys, void *info) { CFStringRef setup, userkey, ipsetupkey, ipstatekey; u_long i, nb, doreorder = 0, dopostsetup = 0; if (changedKeys == NULL) return; setup = CREATEPREFIXSETUP(); userkey = SCDynamicStoreKeyCreateConsoleUser(0); ipsetupkey = CREATEGLOBALSETUP(kSCEntNetIPv4); ipstatekey = CREATEGLOBALSTATE(kSCEntNetIPv4); if (setup == NULL || userkey == NULL || ipsetupkey == NULL || ipstatekey == NULL) { #ifdef DEBUG SCLog(TRUE, LOG_ERR, CFSTR("PPPController cache_notifier : can't allocate keys\n")); #endif goto done; } nb = CFArrayGetCount(changedKeys); for (i = 0; i < nb; i++) { CFStringRef change, serviceID; change = CFArrayGetValueAtIndex(changedKeys, i); // --------- Check for change of console user --------- if (CFEqual(change, userkey)) { CFStringRef olduser = gLoggedInUser; gLoggedInUser = SCDynamicStoreCopyConsoleUser(session, &gLoggedInUserUID, 0); if (gLoggedInUser == 0) log_out(); // key disappeared, user logged out else if (olduser == 0) log_in(); // key appeared, user logged in else log_switch(); // key changed, user has switched if (olduser) CFRelease(olduser); continue; } // --------- Check for change in service order --------- if (CFEqual(change, ipsetupkey)) { // can't just reorder the list now // because the list may contain service not already created doreorder = 1; continue; } // --------- Check for change in ipv4 state --------- if (CFEqual(change, ipstatekey)) { ipv4_state_changed(); continue; } // --------- Check for change in other entities (state or setup) --------- serviceID = parse_component(change, setup); if (serviceID) { setup_service(serviceID); CFRelease(serviceID); dopostsetup = 1; continue; } } if (doreorder) { reorder_services(); //print_services(); } if (dopostsetup) finish_setup_services(); done: my_CFRelease(setup); my_CFRelease(userkey); my_CFRelease(ipsetupkey); my_CFRelease(ipstatekey); return; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ void ppp_stop_all(CFRunLoopSourceRef stopRls) { u_int32_t delay = 0; struct ppp *ppp; if (gStopRls) // should not happen return; TAILQ_FOREACH(ppp, &ppp_head, next) { if (ppp->phase != PPP_IDLE) { delay = 1; ppp_disconnect(ppp, 0, SIGTERM); } } if (delay) // need to signal the runloop source later gStopRls = stopRls; else // we are done CFRunLoopSourceSignal(stopRls); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ u_int32_t ppp_makeref(struct ppp *ppp) { return (((u_long)ppp->subtype) << 16) + ppp->unit; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ struct ppp *ppp_findbyserviceID(CFStringRef serviceID) { struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) if (CFStringCompare(ppp->serviceID, serviceID, 0) == kCFCompareEqualTo) return ppp; return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ struct ppp *ppp_findbysid(u_char *data, int len) { struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) if (ppp->sid && (strlen(ppp->sid) == len) && !strncmp(ppp->sid, data, len)) return ppp; return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ struct ppp *ppp_findbypid(pid_t pid) { struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) if (ppp->pid == pid) return ppp; return 0; } /* ----------------------------------------------------------------------------- find the ppp structure corresponding to the reference if ref == -1, then return the default structure (first in the list) if ref == ----------------------------------------------------------------------------- */ struct ppp *ppp_findbyref(u_long ref) { u_short subtype = ref >> 16; u_short unit = ref & 0xFFFF; struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) { if (((ppp->subtype == subtype) || (subtype == 0xFFFF)) && ((ppp->unit == unit) || (unit == 0xFFFF))) { return ppp; } } return 0; } /* ----------------------------------------------------------------------------- get the first free ref numer within a family ----------------------------------------------------------------------------- */ static u_short findfreeunit(u_short subtype) { struct ppp *ppp = TAILQ_FIRST(&ppp_head); u_short unit = 0; while (ppp) { if ((subtype == ppp->subtype) && (ppp->unit == unit)) { unit++; if (unit == 0xFFFF) return unit; ppp = TAILQ_FIRST(&ppp_head); // restart } else ppp = TAILQ_NEXT(ppp, next); // continue } return unit; } #ifdef DEBUG /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void print_services() { struct ppp *ppp; SCLog(TRUE, LOG_INFO, CFSTR("Printing list of ppp services : \n")); TAILQ_FOREACH(ppp, &ppp_head, next) { SCLog(TRUE, LOG_INFO, CFSTR("Service : %@, subtype = %d\n"), ppp->serviceID, ppp->subtype); } } #endif /* ----------------------------------------------------------------------------- an interface structure needs to be created unit is the ppp managed unit ----------------------------------------------------------------------------- */ static struct ppp * new_service(CFStringRef serviceID , CFStringRef subtypeRef) { struct ppp *ppp; u_short unit, subtype, len; CFURLRef url; u_char str[MAXPATHLEN], str2[32]; //SCLog(LOG_INFO, CFSTR("new_service, subtype = %%@, serviceID = %@\n"), subtypeRef, serviceID); if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPSerial)) subtype = PPP_TYPE_SERIAL; else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPoE)) subtype = PPP_TYPE_PPPoE; else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPTP)) subtype = PPP_TYPE_PPTP; else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypeL2TP)) subtype = PPP_TYPE_L2TP; // else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPoA)) // subtype = PPP_TYPE_PPPoA; // else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPISDN)) // subtype = PPP_TYPE_ISDN; // else if (CFEqual(subtypeRef, kSCValNetInterfaceSubTypePPPOther)) // subtype = PPP_TYPE_OTHER; else subtype = PPP_TYPE_OTHER; unit = findfreeunit(subtype); if (unit == 0xFFFF) return 0; // no room left... ppp = malloc(sizeof(struct ppp)); if (!ppp) return 0; // very bad... bzero(ppp, sizeof(struct ppp)); ppp->serviceID = CFRetain(serviceID); ppp->subtypeRef = CFRetain(subtypeRef); // keep a C version of the service ID len = CFStringGetLength(serviceID) + 1; if (ppp->sid = malloc(len)) { CFStringGetCString(serviceID, ppp->sid, len, kCFStringEncodingUTF8); } ppp->unit = unit; ppp->pid = -1; ppp->subtype = subtype; ppp->phase = PPP_IDLE; ppp->statusfd[READ] = -1; ppp->statusfd[WRITE] = -1; ppp->controlfd[READ] = -1; ppp->controlfd[WRITE] = -1; ppp->uid = 0; ppp->ndrv_socket = -1; ppp->flags |= FLAG_FIRSTDIAL; strcpy(str, DIR_KEXT); str2[0] = 0; CFStringGetCString(ppp->subtypeRef, str2, sizeof(str2), kCFStringEncodingUTF8); strcat(str, str2); strcat(str, ".ppp"); // add plugin suffix url = CFURLCreateFromFileSystemRepresentation(NULL, str, strlen(str), TRUE); if (url) { ppp->bundle = CFBundleCreate(0, url); CFRelease(url); } TAILQ_INIT(&ppp->client_head); TAILQ_INSERT_TAIL(&ppp_head, ppp, next); return ppp; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void display_error(struct ppp *ppp) { CFStringRef ppp_msg = NULL, dev_msg = NULL, msg = NULL; CFPropertyListRef langRef = NULL; if (ppp->laststatus == EXIT_USER_REQUEST) return; if ((ppp->flags & FLAG_ALERTERRORS) == 0) return; if (gLoggedInUser) { CFPreferencesSynchronize(kCFPreferencesAnyApplication, gLoggedInUser, kCFPreferencesAnyHost); langRef = CFPreferencesCopyValue(CFSTR("AppleLanguages"), kCFPreferencesAnyApplication, gLoggedInUser, kCFPreferencesAnyHost); } /* try again for global preferences for the current host */ if (langRef == NULL) { CFPreferencesSynchronize(kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost); langRef = CFPreferencesCopyValue(CFSTR("AppleLanguages"), kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost); } if (ppp->lastdevstatus && ppp->bundle) { dev_msg = CFStringCreateWithFormat(0, 0, CFSTR("Device Error %d"), ppp->lastdevstatus); if (dev_msg) msg = CopyUserLocalizedString(ppp->bundle, dev_msg, dev_msg, langRef); } if (msg == NULL) { ppp_msg = CFStringCreateWithFormat(0, 0, CFSTR("PPP Error %d"), ppp->laststatus); if (ppp_msg) msg = CopyUserLocalizedString(gBundleRef, ppp_msg, ppp_msg, langRef) ; } if (msg && CFStringGetLength(msg)) CFUserNotificationDisplayNotice(120, kCFUserNotificationNoteAlertLevel, gIconURLRef, NULL, gBundleURLRef, CFSTR("Internet Connect"), msg, NULL); my_CFRelease(msg); my_CFRelease(ppp_msg); my_CFRelease(dev_msg); my_CFRelease(langRef); } /* ----------------------------------------------------------------------------- for PPPoE over Airport services, install a protocol to enable the interface even when PPPoE is not connected ----------------------------------------------------------------------------- */ static void setup_PPPoEoAirport(struct ppp *ppp) { CFDictionaryRef interface; CFStringRef hardware, device; struct sockaddr_ndrv ndrv; if (ppp->subtype == PPP_TYPE_PPPoE) { interface = copyEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetInterface); if (interface) { device = CFDictionaryGetValue(interface, kSCPropNetInterfaceDeviceName); hardware = CFDictionaryGetValue(interface, kSCPropNetInterfaceHardware); if (isA_CFString(hardware) && isA_CFString(device) && (CFStringCompare(hardware, kSCEntNetAirPort, 0) == kCFCompareEqualTo)) { if (ppp->device && (CFStringCompare(device, ppp->device, 0) != kCFCompareEqualTo)) { dispose_PPPoEoAirport(ppp); } if (!ppp->device) { ppp->ndrv_socket = socket(PF_NDRV, SOCK_RAW, 0); if (ppp->ndrv_socket >= 0) { ppp->device = device; CFRetain(ppp->device); CFStringGetCString(device, ndrv.snd_name, sizeof(ndrv.snd_name), kCFStringEncodingMacRoman); ndrv.snd_len = sizeof(ndrv); ndrv.snd_family = AF_NDRV; if (bind(ppp->ndrv_socket, (struct sockaddr *)&ndrv, sizeof(ndrv)) < 0) { dispose_PPPoEoAirport(ppp); } } } } else { /* not an Airport device */ dispose_PPPoEoAirport(ppp); } CFRelease(interface); } } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void dispose_PPPoEoAirport(struct ppp *ppp) { if (ppp->ndrv_socket != -1) { close(ppp->ndrv_socket); ppp->ndrv_socket = -1; } if (ppp->device) { CFRelease(ppp->device); ppp->device = 0; } } /* ----------------------------------------------------------------------------- changed for this ppp occured in configd cache ----------------------------------------------------------------------------- */ static void finish_setup_services() { u_int32_t lval; struct ppp *ppp; CFDictionaryRef service; TAILQ_FOREACH(ppp, &ppp_head, next) { if (ppp->flags & FLAG_SETUP) { ppp->flags &= ~(FLAG_FREE + FLAG_SETUP); setup_PPPoEoAirport(ppp); switch (ppp->phase) { case PPP_IDLE: //printf("ppp_updatesetup : unit %d, PPP_IDLE\n", ppp->unit); if ((getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDialOnDemand, &lval) && lval) && (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnLogout, &lval) || !lval || gLoggedInUser)) { ppp_connect(ppp, 0, 1, 0, 0, 0, 0, 0); } break; case PPP_DORMANT: case PPP_HOLDOFF: // config has changed, dialondemand will need to be restarted ppp->flags |= FLAG_CONFIGCHANGEDNOW; ppp->flags &= ~FLAG_CONFIGCHANGEDLATER; ppp_disconnect(ppp, 0, SIGTERM); break; default : // config has changed, dialondemand will need to be restarted ppp->flags |= FLAG_CONFIGCHANGEDLATER; ppp->flags &= ~FLAG_CONFIGCHANGEDNOW; /* if ppp was started in dialondemand mode, then stop it */ // if (ppp->dialondemand) // ppp_disconnect(ppp, 0, SIGTERM); service = copyService(kSCDynamicStoreDomainSetup, ppp->serviceID); if (service) { change_pppd_params(ppp, service, ppp->connectopts); CFRelease(service); } break; } } } } /* ----------------------------------------------------------------------------- call back from power management ----------------------------------------------------------------------------- */ static void iosleep_notifier(void * x, io_service_t y, natural_t messageType, void *messageArgument) { CFMutableDictionaryRef dict; SInt32 error; int delay; //printf("messageType %08lx, arg %08lx\n",(long unsigned int)messageType, (long unsigned int)messageArgument); switch ( messageType ) { case kIOMessageSystemWillSleep: gSleeping = 1; // time to sleep gSleepArgument = (long)messageArgument; delay = will_sleep(0); if (delay == 0) IOAllowPowerChange(gIOPort, (long)messageArgument); else { if (delay & 2) { dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (dict) { CFDictionaryAddValue(dict, kCFUserNotificationIconURLKey, gIconURLRef); CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, gBundleURLRef); CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFSTR("Internet Connect")); CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, CFSTR("Waiting for disconnection")); gSleepNotification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationNoteAlertLevel + kCFUserNotificationNoDefaultButtonFlag, &error, dict); CFRelease(dict); } } } break; case kIOMessageCanSystemSleep: if (can_sleep()) IOAllowPowerChange(gIOPort, (long)messageArgument); else IOCancelPowerChange(gIOPort, (long)messageArgument); break; case kIOMessageSystemWillNotSleep: /* should get it only if someone refuse an idle sleep but I don't have anything to do here */ break; case kIOMessageSystemHasPoweredOn: gSleeping = 0; // time to wakeup gWakeUpTime = mach_absolute_time(); if (gSleepNotification) { CFUserNotificationCancel(gSleepNotification); CFRelease(gSleepNotification); gSleepNotification = 0; } wake_up(); break; } } /* ----------------------------------------------------------------------------- system is asking permission to sleep return if sleep is authorized ----------------------------------------------------------------------------- */ static int can_sleep() { struct ppp *ppp; u_int32_t prevent; // I refuse idle sleep if ppp is connected TAILQ_FOREACH(ppp, &ppp_head, next) { if (ppp->phase == PPP_RUNNING) { // by default, vpn connection don't prevent idle sleep switch (ppp->subtype) { case PPP_TYPE_PPTP: case PPP_TYPE_L2TP: prevent = 0; break; default : prevent = 1; } getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, CFSTR("PreventIdleSleep"), &prevent); if (prevent) return 0; } } return 1; } /* ----------------------------------------------------------------------------- system is going to sleep disconnect services and return if a delay is needed ----------------------------------------------------------------------------- */ static int will_sleep(int checking) { u_int32_t disc, delay = 0, alert = 0; struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) { if (ppp->phase != PPP_IDLE // by default, disconnect on sleep && (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnSleep, &disc) || disc)) { delay = 1; if (ppp->phase != PPP_DORMANT || ppp->phase != PPP_HOLDOFF) alert = 2; if (!checking) ppp_disconnect(ppp, 0, SIGTERM); } } return delay + alert; } /* ----------------------------------------------------------------------------- check is all session are idle ----------------------------------------------------------------------------- */ static int is_idle() { struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) if (ppp->phase != PPP_IDLE) return 0; return 1; } /* ----------------------------------------------------------------------------- ipv4 state has changed ----------------------------------------------------------------------------- */ static void ipv4_state_changed() { struct ppp *ppp; TAILQ_FOREACH(ppp, &ppp_head, next) { ppp->flags |= FLAG_FIRSTDIAL; } } /* ----------------------------------------------------------------------------- system is waking up need to check the dialondemand flag again ----------------------------------------------------------------------------- */ static void wake_up() { struct ppp *ppp; u_int32_t lval; TAILQ_FOREACH(ppp, &ppp_head, next) { ppp->flags |= FLAG_FIRSTDIAL; if (ppp->phase == PPP_IDLE) { if ((getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDialOnDemand, &lval) && lval) && (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnLogout, &lval) || !lval || gLoggedInUser)) { ppp_connect(ppp, 0, 1, 0, 0, 0, 0, 0); } } } } /* ----------------------------------------------------------------------------- user has looged out need to check the disconnect on logout flag for the ppp interfaces ----------------------------------------------------------------------------- */ static int log_out() { struct ppp *ppp; u_int32_t disc; TAILQ_FOREACH(ppp, &ppp_head, next) { if (ppp->phase != PPP_IDLE && getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnLogout, &disc) && disc) ppp_disconnect(ppp, 0, SIGTERM); } return 0; } /* ----------------------------------------------------------------------------- user has logged in need to check the dialondemand flag again ----------------------------------------------------------------------------- */ static int log_in() { struct ppp *ppp; u_int32_t val; TAILQ_FOREACH(ppp, &ppp_head, next) { ppp->flags |= FLAG_FIRSTDIAL; if (ppp->phase == PPP_IDLE && getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDialOnDemand, &val) && val) { ppp_connect(ppp, 0, 1, 0, 0, 0, 0, 0); } } return 0; } /* ----------------------------------------------------------------------------- user has switched need to check the disconnect on logout and dial on traffic flags for the ppp interfaces ----------------------------------------------------------------------------- */ static int log_switch() { struct ppp *ppp; u_int32_t disc, val, demand; TAILQ_FOREACH(ppp, &ppp_head, next) { ppp->flags |= FLAG_FIRSTDIAL; demand = getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDialOnDemand, &val) && val; switch (ppp->phase) { case PPP_IDLE: // rearm dial on demand if (demand) ppp_connect(ppp, 0, 1, 0, 0, 0, 0, 0); break; default: disc = 0; /* if the DisconnectOnFastUserSwitch key does not exist, use kSCPropNetPPPDisconnectOnLogout */ if (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, CFSTR("DisconnectOnFastUserSwitch"), &disc)) getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnLogout, &disc); if (disc) { // if dialondemand is set, it will need to be restarted ppp->flags &= ~FLAG_CONFIGCHANGEDLATER; if (demand) ppp->flags |= FLAG_CONFIGCHANGEDNOW; else ppp->flags &= ~FLAG_CONFIGCHANGEDNOW; ppp_disconnect(ppp, 0, SIGTERM); } } } return 0; } /* ----------------------------------------------------------------------------- an interface is come down, dispose the ppp structure ----------------------------------------------------------------------------- */ static int dispose_service(struct ppp *ppp) { // need to close the protocol first ppp_disconnect(ppp, 0, SIGTERM); if (ppp->phase != PPP_IDLE) { ppp->flags |= FLAG_FREE; return 1; } TAILQ_REMOVE(&ppp_head, ppp, next); // then free the structure dispose_PPPoEoAirport(ppp); if (ppp->sid) free(ppp->sid); if (ppp->bundle) CFRelease(ppp->bundle); CFRelease(ppp->serviceID); CFRelease(ppp->subtypeRef); free(ppp); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ 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 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 ppp *ppp, CFDictionaryRef service, CFDictionaryRef options, u_int8_t dialondemand) { char str[MAXPATHLEN], str2[256]; int needpasswd = 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; CFArrayRef array = NULL; CFStringRef string = NULL; pppdict = CFDictionaryGetValue(service, kSCEntNetPPP); if ((pppdict == 0) || (CFGetTypeID(pppdict) != CFDictionaryGetTypeID())) return -1; // that's bad... optfd = 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); } // ----------------- // verbose logging get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPVerboseLogging, options, service, &lval, 0); if (lval) writeparam(optfd, "debug"); // ----------------- // alert flags ppp->flags &= ~(FLAG_ALERTERRORS + FLAG_ALERTPASSWORDS); ppp_getoptval(ppp, options, service, PPP_OPT_ALERTENABLE, &lval, &len); if (lval & PPP_ALERT_ERRORS) ppp->flags |= FLAG_ALERTERRORS; if (lval & PPP_ALERT_PASSWORDS) ppp->flags |= FLAG_ALERTPASSWORDS; if (ppp_getoptval(ppp, 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 CFStringGetCString(ppp->subtypeRef, str2, sizeof(str2) - 4, kCFStringEncodingUTF8); strcat(str2, ".ppp"); // add plugin suffix writestrparam(optfd, "plugin", str2); // ----------------- // device name if (ppp_getoptval(ppp, options, service, PPP_OPT_DEV_NAME, sopt, &len) && sopt[0]) writestrparam(optfd, "device", sopt); // ----------------- // device speed if (ppp_getoptval(ppp, options, service, PPP_OPT_DEV_SPEED, &lval, &len) && lval) { sprintf(str, "%d", lval); writeparam(optfd, str); } // ----------------- // subtype specific parameters switch (ppp->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(ppp, kSCEntNetInterface, kSCPropNetInterfaceHardware, options, 0, sopt, &lval, ""); if (strcmp(sopt, "Modem")) { // we are done break; } 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); } 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(ppp, 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(ppp, 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(ppp, kSCEntNetL2TP, CFSTR("UDPPort"), options, service, &lval, 0 /* Dynamic port */); writeintparam(optfd, "l2tpudpport", lval); break; case PPP_TYPE_PPTP: get_int_option(ppp, kSCEntNetPPTP, CFSTR("TCPKeepAlive"), options, service, &lval, 0); if (lval) { get_int_option(ppp, kSCEntNetPPTP, CFSTR("TCPKeepAliveTimer"), options, service, &lval, 0); } else { /* option doesn't exist, piggy-back on lcp echo option */ ppp_getoptval(ppp, options, service, PPP_OPT_LCP_ECHO, &lval, &len); lval = lval >> 16; } writeintparam(optfd, "pptp-tcp-keepalive", lval); break; } // ----------------- // terminal option if (ppp_getoptval(ppp, 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 && ppp->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(ppp, options, service, PPP_OPT_COMM_TERMINALSCRIPT, sopt, &len) && sopt[0]) writestrparam(optfd, "terminalscript", sopt); } // ----------------- // generic phone number option if (ppp_getoptval(ppp, options, service, PPP_OPT_COMM_REMOTEADDR, sopt, &len) && sopt[0]) writestrparam(optfd, "remoteaddress", sopt); // ----------------- // redial options get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPCommRedialEnabled, options, service, &lval, 0); if (lval) { get_str_option(ppp, kSCEntNetPPP, kSCPropNetPPPCommAlternateRemoteAddress, options, service, sopt, &lval, ""); if (sopt[0]) writestrparam(optfd, "altremoteaddress", sopt); get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPCommRedialCount, options, service, &lval, 0); if (lval) writeintparam(optfd, "redialcount", lval); get_int_option(ppp, 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(ppp, options, service, PPP_OPT_COMM_IDLETIMER, &lval, &len) && lval) { writeintparam(optfd, "idle", lval); writeparam(optfd, "noidlerecv"); } // ----------------- // connection time option if (ppp_getoptval(ppp, options, service, PPP_OPT_COMM_SESSIONTIMER, &lval, &len) && lval) writeintparam(optfd, "maxconnect", lval); // ----------------- // dial on demand options if (dialondemand) { writeparam(optfd, "demand"); get_int_option(ppp, kSCEntNetPPP, CFSTR("HoldOffTime"), 0, service, &lval, 30); writeintparam(optfd, "holdoff", lval); if ((dialondemand & 0x2) && lval) writeparam(optfd, "holdfirst"); get_int_option(ppp, 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(ppp, 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(ppp, 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(ppp, options, service, PPP_OPT_LCP_MRU, &lval, &len) && lval) writeintparam(optfd, "mru", lval); // ----------------- // mtu option if (ppp_getoptval(ppp, options, service, PPP_OPT_LCP_MTU, &lval, &len) && lval) writeintparam(optfd, "mtu", lval); // ----------------- // receive async map option if (ppp_getoptval(ppp, 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(ppp, 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(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetIPv4)) { writeparam(optfd, "noip"); } else { // ----------------- // set ip param to be the router address if (getStringFromEntity(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(ppp, kSCEntNetIPv4, kSCPropNetOverridePrimary, 0 /* don't look in options */, service, &lval, 0); if (lval) writeparam(optfd, "defaultroute"); // ----------------- // vj compression option if (! (ppp_getoptval(ppp, options, service, PPP_OPT_IPCP_HDRCOMP, &lval, &len) && lval)) writeparam(optfd, "novj"); // ----------------- // XXX enforce the source address if (ppp->subtype == PPP_TYPE_L2TP || ppp->subtype == PPP_TYPE_PPTP ) { writeintparam(optfd, "ip-src-address-filter", 2); } // ----------------- // ip addresses options if (ppp_getoptval(ppp, 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(ppp, 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(ppp, kSCEntNetPPP, CFSTR("IPCPUsePeerDNS"), options, service, &lval, 1); if (lval) writeparam(optfd, "usepeerdns"); } // of existEntity IPv4 // ----------------- // ip6cp options if (!existEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetIPv6)) { // ipv6 is not started by default } else { writeparam(optfd, "+ipv6"); writeparam(optfd, "ipv6cp-use-persistent"); } // ----------------- // acsp options get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPACSPEnabled, options, service, &lval, 0); if (lval == 0) writeparam(optfd, "noacsp"); // ----------------- // authentication options // don't want authentication on our side... writeparam(optfd, "noauth"); if (ppp_getoptval(ppp, options, service, PPP_OPT_AUTH_PROTO, &lval, &len) && (lval != PPP_AUTH_NONE)) { if (ppp_getoptval(ppp, options, service, PPP_OPT_AUTH_NAME, sopt, &len) && sopt[0]) { writestrparam(optfd, "user", sopt); lval1 = get_str_option(ppp, kSCEntNetPPP, kSCPropNetPPPAuthPassword, options, service, sopt, &lval, ""); if (sopt[0]) { /* if the option was from the setup, check for encryption method */ if ((lval1 == 3) && (string = CFDictionaryGetValue(pppdict, kSCPropNetPPPAuthPasswordEncryption)) && (CFGetTypeID(string) == CFStringGetTypeID()) && (CFStringCompare(string, kSCValNetPPPAuthPasswordEncryptionKeychain, 0) == kCFCompareEqualTo)) { writestrparam(optfd, "keychainpassword", sopt); } else { writestrparam(optfd, "password", sopt); } } else { /* we need to prompt for a password if we have a username and no saved password */ 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; } } } // authentication variation for token card support... get_int_option(ppp, kSCEntNetPPP, CFSTR("TokenCard"), options, service, &lval, 0); if (lval) { writeintparam(optfd, "tokencard", lval); } // ----------------- // 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 get_int_option(ppp, 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 works 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(ppp, 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(ppp, kSCEntNetPPP, CFSTR("CCPMPPE128Enabled"), options, service, &lval, 1); writeparam(optfd, lval ? "mppe-128" : "nomppe-128"); get_int_option(ppp, 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 (!(ppp->flags & FLAG_ALERTPASSWORDS) || !needpasswd) writeparam(optfd, "noaskpassword"); get_str_option(ppp, 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(ppp, kSCEntNetPPP, kSCPropNetPPPIdleReminder, options, service, &lval, 0); if (lval) { get_int_option(ppp, 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 // be careful to the conflicts on options get_str_option(ppp, kSCEntNetPPP, kSCPropUserDefinedName, options, service, sopt, &lval, ""); if (sopt[0]) writestrparam(optfd, "call", sopt); writeparam(optfd, "[EOP]"); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int change_pppd_params(struct ppp *ppp, 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 = ppp->controlfd[WRITE]; writeparam(optfd, "[OPTIONS]"); // ----------------- // reminder option must be specified after PPPDialogs plugin option get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPIdleReminder, options, service, &lval, 0); if (lval) get_int_option(ppp, kSCEntNetPPP, kSCPropNetPPPIdleReminderTimer, options, service, &lval, 0); writeintparam(optfd, "reminder", lval); // ----------------- ppp_getoptval(ppp, 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; 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, mach_task_self(), &bootstrap, &result); 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; } if (bootstrap) { task_set_bootstrap_port(mach_task_self(), bootstrap); mach_port_deallocate(mach_task_self(), bootstrap); } return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_connect(struct ppp *ppp, CFDictionaryRef options, u_int8_t dialondemand, void *client, int autoclose, uid_t uid, gid_t gid, mach_port_t bootstrap) { #define MAXARG 10 char *cmdarg[MAXARG]; u_int32_t lval, i, argi = 0; CFDictionaryRef service; CFStringRef autodial; /* first determine autodial opportunity */ if (options) { int willdial = 0, autodialval = 0; autodial = CFDictionaryGetValue(options, kSCPropNetPPPOnDemandPriority); if (!autodial) { // option not set, regualar dial request willdial = 1; } else if (CFGetTypeID(autodial) == CFStringGetTypeID()) { /* first, check if the user is he current console user */ if (gLoggedInUser && uid == gLoggedInUserUID) { if (CFStringCompare(autodial, kSCValNetPPPOnDemandPriorityHigh, 0) == kCFCompareEqualTo) autodialval = 1; else if (CFStringCompare(autodial, kSCValNetPPPOnDemandPriorityLow, 0) == kCFCompareEqualTo) autodialval = 2; else if (CFStringCompare(autodial, kSCValNetPPPOnDemandPriorityDefault, 0) == kCFCompareEqualTo) { // Autodial is required in default mode. // Use the aggressivity level from the options CFDictionaryRef dict; if (dict = CFDictionaryGetValue(options, kSCEntNetPPP)) { CFStringRef str; str = CFDictionaryGetValue(dict, kSCPropNetPPPOnDemandMode); if (str && CFGetTypeID(str) == CFStringGetTypeID()) { if (CFStringCompare(str, kSCValNetPPPOnDemandModeAggressive, 0) == kCFCompareEqualTo) autodialval = 1; else if (CFStringCompare(str, kSCValNetPPPOnDemandModeConservative, 0) == kCFCompareEqualTo) autodialval = 2; else /* kSCValNetPPPOnDemandModeCompatible */ autodialval = 0; } } } } //printf("autodial = %d\n", autodial); switch (autodialval) { case 1: // high // OK, let's dial willdial = 1; break; case 2: // low // Need to implement smart behavior if (ppp->flags & FLAG_FIRSTDIAL) { // First time after major event, let's dial willdial = 1; } break; default: break; } } if (!willdial) return EIO; // SCNetworkConnection API needs to implement specific status (kSCStatusConnectionOnDemandRetryLater) */ } if (gStopRls || (gSleeping && (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnSleep, &lval) || lval))) return EIO; // not the right time to dial // reset setup flag ppp->flags &= ~FLAG_CONFIGCHANGEDNOW; ppp->flags &= ~FLAG_CONFIGCHANGEDLATER; // reset autodial flag; ppp->flags &= ~FLAG_FIRSTDIAL; switch (ppp->phase) { case PPP_IDLE: break; case PPP_DORMANT: // kill dormant process and post connection flag case PPP_HOLDOFF: my_CFRelease(ppp->newconnectopts); ppp->newconnectopts = options; ppp->newconnectuid = uid; ppp->newconnectuid = gid; ppp->newconnectbootstrap = bootstrap; my_CFRetain(ppp->newconnectopts); ppp_disconnect(ppp, 0, SIGTERM); ppp->flags |= FLAG_CONNECT; if (client) add_client(ppp, client, autoclose); return 0; default: if (client) { if (my_CFEqual(options, ppp->connectopts)) { add_client(ppp, client, autoclose); return 0; } } return EIO; // not the right time to dial } ppp->laststatus = EXIT_FATAL_ERROR; ppp->lastdevstatus = 0; service = copyService(kSCDynamicStoreDomainSetup, ppp->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, ppp->sid); addparam(cmdarg, &argi, "controlled"); if ((socketpair(AF_LOCAL, SOCK_STREAM, 0, ppp->controlfd) == -1) || (socketpair(AF_LOCAL, SOCK_STREAM, 0, ppp->statusfd) == -1)) goto end; ppp->pid = _SCDPluginExecCommand2(exec_callback, (void*)ppp_makeref(ppp), uid, gid, PATH_PPPD, cmdarg, exec_postfork, (void*)ppp_makeref(ppp)); if (ppp->pid == -1) goto end; // send options to pppd using the pipe if (send_pppd_params(ppp, service, options, dialondemand) == -1) { kill(ppp->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(ppp->statusfd[READ], 1, 0, 0); if (client) add_client(ppp, client, autoclose); ppp->laststatus = EXIT_OK; ppp_updatephase(ppp, PPP_INITIALIZE); if (dialondemand) ppp->flags |= FLAG_DIALONDEMAND; else ppp->flags &= ~FLAG_DIALONDEMAND; ppp->connectopts = options; my_CFRetain(ppp->connectopts); ppp->uid = uid; ppp->gid = gid; ppp->bootstrap = bootstrap; end: if (service) CFRelease(service); for (i = 0; i < argi; i++) free(cmdarg[i]); if (ppp->pid == -1) { my_close(ppp->statusfd[READ]); ppp->statusfd[READ] = -1; my_close(ppp->statusfd[WRITE]); ppp->statusfd[WRITE] = -1; my_close(ppp->controlfd[READ]); ppp->controlfd[READ] = -1; my_close(ppp->controlfd[WRITE]); ppp->controlfd[WRITE] = -1; display_error(ppp); } return ppp->laststatus; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void exec_postfork(pid_t pid, void *arg) { struct ppp *ppp = ppp_findbyref((u_int32_t)arg); if (pid) { /* if parent */ int yes = 1; my_close(ppp->controlfd[READ]); ppp->controlfd[READ] = -1; my_close(ppp->statusfd[WRITE]); ppp->statusfd[WRITE] = -1; if (ioctl(ppp->controlfd[WRITE], FIONBIO, &yes) == -1) { // printf("ioctl(,FIONBIO,): %s\n", strerror(errno)); } } else { /* if child */ int i; setup_bootstrap_port(); my_close(ppp->controlfd[WRITE]); ppp->controlfd[WRITE] = -1; my_close(ppp->statusfd[READ]); ppp->statusfd[READ] = -1; if (ppp->controlfd[READ] != STDIN_FILENO) { dup2(ppp->controlfd[READ], STDIN_FILENO); } if (ppp->statusfd[WRITE] != STDOUT_FILENO) { dup2(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); } return; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void exec_callback(pid_t pid, int status, struct rusage *rusage, void *context) { struct ppp *ppp = ppp_findbyref((u_int32_t)context); u_int32_t lval, failed = 0; int exitcode = WIFEXITED(status) ? WEXITSTATUS(status) : -1; if (exitcode < 0) { // ignore this case, just change phase } else if (exitcode > EXIT_PEER_NOT_AUTHORIZED) { // pppd exited because of a crash ppp_updatestatus(ppp, 127, 0); } else if (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(ppp, exitcode, 0); } // call the change phae function ppp_updatephase(ppp, PPP_IDLE); // close file descriptors //statusfd is closed by the run loop //my_close(ppp->statusfd[READ]); ppp->statusfd[READ] = -1; my_close(ppp->controlfd[WRITE]); ppp->controlfd[WRITE] = -1; my_CFRelease(ppp->connectopts); ppp->connectopts = 0; if (ppp->flags & FLAG_FREE) { ppp->flags &= ~FLAG_FREE; dispose_service(ppp); ppp = 0; } if (gSleeping && !will_sleep(1)) { if (gSleepNotification) { CFUserNotificationCancel(gSleepNotification); CFRelease(gSleepNotification); gSleepNotification = 0; } IOAllowPowerChange(gIOPort, gSleepArgument); } if (ppp == 0) return; // check if configd is going away if (gStopRls && is_idle()) { // we are done CFRunLoopSourceSignal(gStopRls); return; } // now reconnect if necessary if (ppp->flags & FLAG_CONNECT) { ppp_connect(ppp, ppp->newconnectopts, 0, 0, 0, ppp->newconnectuid, ppp->newconnectgid, ppp->newconnectbootstrap); my_CFRelease(ppp->newconnectopts); ppp->newconnectopts = 0; ppp->newconnectuid = 0; ppp->newconnectgid = 0; ppp->newconnectbootstrap = 0; ppp->flags &= ~FLAG_CONNECT; } else { // if config has changed, or ppp was previously a manual connection, then rearm dialondemand if necessary if (failed == 0 && ((ppp->flags & (FLAG_CONFIGCHANGEDNOW + FLAG_CONFIGCHANGEDLATER)) || !(ppp->flags & FLAG_DIALONDEMAND)) && (getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDialOnDemand, &lval) && lval) && (!getNumberFromEntity(kSCDynamicStoreDomainSetup, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectOnLogout, &lval) || !lval || gLoggedInUser)) { ppp_connect(ppp, 0, ppp->flags & FLAG_CONFIGCHANGEDNOW ? 1 : 3, 0, 0, 0, 0, 0); } } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_disconnect(struct ppp *ppp, void *client, int signal) { /* arbitration mechanism : disconnects only when no client is using it */ if (client) { if (get_client(ppp, client)) remove_client(ppp, client); /* check if we have at least one client */ if (TAILQ_FIRST(&ppp->client_head)) return 0; } else { remove_all_clients(ppp); } /* signal is either SIGHUP or SIGTERM SIGHUP will only disconnect the link SIGTERM will terminate pppd */ if (ppp->flags & (FLAG_CONFIGCHANGEDNOW + FLAG_CONFIGCHANGEDLATER)) signal = SIGTERM; // anticipate next phase switch (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: ppp->flags &= ~FLAG_CONNECT; // no break; case PPP_CONNECTLINK: ppp_updatephase(ppp, PPP_DISCONNECTLINK); break; default: ppp_updatephase(ppp, PPP_TERMINATE); } kill(ppp->pid, signal); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_suspend(struct ppp *ppp) { if (ppp->phase != PPP_IDLE) kill(ppp->pid, SIGTSTP); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_resume(struct ppp *ppp) { if (ppp->phase != PPP_IDLE) kill(ppp->pid, SIGCONT); return 0; } /* ----------------------------------------------------------------------------- status change for this ppp occured ----------------------------------------------------------------------------- */ void ppp_updatestatus(struct ppp *ppp, int status, int devstatus) { ppp->laststatus = status; ppp->lastdevstatus = devstatus; display_error(ppp); } /* ----------------------------------------------------------------------------- phase change for this ppp occured ----------------------------------------------------------------------------- */ void ppp_updatephase(struct ppp *ppp, int phase) { /* check if update is received pppd has exited */ if (ppp->statusfd[READ] == -1) return; /* check for new phase */ if (phase == ppp->phase) return; ppp->phase = phase; client_notify(ppp->serviceID, ppp->sid, ppp_makeref(ppp), ppp->phase, 0, CLIENT_FLAG_NOTIFY_STATUS); switch (ppp->phase) { case PPP_RUNNING: ppp->ifname[0] = 0; getStringFromEntity(kSCDynamicStoreDomainState, ppp->serviceID, kSCEntNetPPP, kSCPropInterfaceName, ppp->ifname, sizeof(ppp->ifname)); break; case PPP_DORMANT: ppp->ifname[0] = 0; getStringFromEntity(kSCDynamicStoreDomainState, ppp->serviceID, kSCEntNetPPP, kSCPropInterfaceName, ppp->ifname, sizeof(ppp->ifname)); // no break; case PPP_HOLDOFF: /* check if setup has changed */ if (ppp->flags & FLAG_CONFIGCHANGEDLATER) ppp_disconnect(ppp, 0, SIGTERM); break; } } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_getstatus(struct ppp *ppp, 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 (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 = 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, 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(kSCDynamicStoreDomainState, ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPConnectTime, &conntime); disconntime = 0; getNumberFromEntity(kSCDynamicStoreDomainState, ppp->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(kSCDynamicStoreDomainState, ppp->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(ppp->subtype, ppp->laststatus, ppp->lastdevstatus); } *replylen = sizeof(struct ppp_status); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_copyextendedstatus(struct ppp *ppp, 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, ppp->phase); if (ppp->phase != PPP_IDLE) AddStringFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPCommRemoteAddress, dict); switch (ppp->phase) { case PPP_RUNNING: case PPP_ONHOLD: AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPConnectTime, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPDisconnectTime, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPCompressionPField, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPCompressionACField, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPMRU, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPMTU, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPReceiveACCM, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPLCPTransmitACCM, dict); AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPIPCPCompressionVJ, dict); break; case PPP_WAITONBUSY: AddNumberFromState(ppp->serviceID, kSCEntNetPPP, kSCPropNetPPPRetryConnectTime, dict); break; case PPP_DORMANT: break; default: AddNumber(dict, kSCPropNetPPPLastCause, ppp->laststatus); AddNumber(dict, kSCPropNetPPPDeviceLastCause, ppp->lastdevstatus); } CFDictionaryAddValue(statusdict, kSCEntNetPPP, dict); CFRelease(dict); /* create and add Modem dictionary */ if (ppp->subtype == PPP_TYPE_SERIAL && (ppp->phase == PPP_RUNNING || ppp->phase == PPP_ONHOLD)) { if ((dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0) goto fail; AddNumberFromState(ppp->serviceID, kSCEntNetModem, kSCPropNetModemConnectSpeed, dict); CFDictionaryAddValue(statusdict, kSCEntNetModem, dict); CFRelease(dict); } /* create and add IPv4 dictionary */ if (ppp->phase == PPP_RUNNING || ppp->phase == PPP_ONHOLD) { dict = (CFMutableDictionaryRef)copyEntity(kSCDynamicStoreDomainState, ppp->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 ppp *ppp, 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 (ppp->phase != PPP_RUNNING && 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, 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 ppp *ppp, 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(kSCDynamicStoreDomainSetup, ppp->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 ppp *ppp, 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 = ppp->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; } /* ----------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- client arbritration mechanism -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ int ppp_clientgone(void *client) { struct ppp *ppp; struct ppp_client *pppclient; /* arbitration mechanism */ TAILQ_FOREACH(ppp, &ppp_head, next) { pppclient = get_client(ppp, client); if (pppclient && pppclient->autoclose) { ppp_disconnect(ppp, client, SIGTERM); } } return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int add_client(struct ppp *ppp, void *client, int autoclose) { struct ppp_client *pppclient; pppclient = malloc(sizeof(struct ppp_client)); if (pppclient == 0) return -1; // very bad... pppclient->client = client; pppclient->autoclose = autoclose; TAILQ_INSERT_TAIL(&ppp->client_head, pppclient, next); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int remove_client(struct ppp *ppp, void *client) { struct ppp_client *pppclient; TAILQ_FOREACH(pppclient, &ppp->client_head, next) { if (pppclient->client == client) { TAILQ_REMOVE(&ppp->client_head, pppclient, next); free(pppclient); return 0; } } return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static struct ppp_client *get_client(struct ppp *ppp, void *client) { struct ppp_client *pppclient; TAILQ_FOREACH(pppclient, &ppp->client_head, next) if (pppclient->client == client) return pppclient; return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int remove_all_clients(struct ppp *ppp) { struct ppp_client *pppclient; while (pppclient = TAILQ_FIRST(&ppp->client_head)) { TAILQ_REMOVE(&ppp->client_head, pppclient, next); free(pppclient); } return 0; }