#include <string.h>
#include <stdio.h>
#include <sys/errno.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/fcntl.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFUserNotification.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h> // for SCLog()
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
#include <mach/mach_time.h>
#include <mach/task_special_ports.h>
#include <mach/mach.h>
#include <mach/message.h>
#include <mach/boolean.h>
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <notify.h>
#include <sys/sysctl.h>
#include <pthread.h>
#include "scnc_mach_server.h"
#include "scnc_main.h"
#include "ppp_msg.h"
#include "ppp_privmsg.h"
#include "scnc_client.h"
#include "ipsec_manager.h"
#include "ppp_manager.h"
#include "ppp_option.h"
#include "ppp_socket_server.h"
#include "scnc_utils.h"
#include "if_ppplink.h"
enum {
do_nothing = 0,
do_process,
do_close,
do_error
};
#define ICON "NetworkConnect.icns"
static int init_things();
static void iosleep_notifier(void * x, io_service_t y, natural_t messageType, void *messageArgument);
static void store_notifier(SCDynamicStoreRef session, CFArrayRef changedKeys, void *info);
static void print_services();
static int update_service(CFStringRef serviceID);
static void finish_update_services();
static struct service * new_service(CFStringRef serviceID , CFStringRef typeRef, CFStringRef subtypeRef, CFStringRef iscisco);
static int dispose_service(struct service *serv);
static u_short findfreeunit(u_short type, u_short subtype);
static int cleanup_plugin_store();
static int ondemand_add_service(struct service *serv);
static int ondemand_remove_service(struct service *serv);
static void post_ondemand_token(uint64_t state64);
static int add_client(struct service *serv, void *client, int autoclose);
static int remove_client(struct service *serv, void *client);
static struct service_client *get_client(struct service *serv, void *client);
static int remove_all_clients(struct service *serv);
static int can_sleep();
static int will_sleep(int checking);
static void wake_up();
#if !TARGET_EMBEDDED_OS
static void log_out();
static void log_in();
static void log_switch();
#endif
static void ipv4_state_changed();
static void reorder_services();
CFStringRef gPluginsDir = 0;
CFBundleRef gBundleRef = 0;
CFURLRef gBundleURLRef = 0;
CFURLRef gIconURLRef = 0;
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;
char *gIPSecAppVersion = NULL;
int gNotifyOnDemandToken = -1;
int gSCNCVerbose = 0;
int gSCNCDebug = 0;
CFRunLoopRef gControllerRunloop = NULL;
CFRunLoopRef gPluginRunloop = NULL;
CFRunLoopSourceRef gTerminalrls = NULL;
#ifdef TARGET_EMBEDDED_OS
int gNattKeepAliveInterval = -1;
#endif
TAILQ_HEAD(, service) service_head;
#if !TARGET_EMBEDDED_OS
static vproc_transaction_t gController_vt = NULL;
#endif
static u_int32_t gWaitForPrimaryService = 0;
void terminate_all()
{
struct service *serv;
CFRunLoopSourceInvalidate(gTerminalrls);
CFRelease(gTerminalrls);
TAILQ_FOREACH(serv, &service_head, next) {
scnc_stop(serv, 0, SIGTERM);
}
allow_stop();
}
void *pppcntl_run_thread(void *arg)
{
if (ppp_mach_start_server())
pthread_exit( (void*) EXIT_FAILURE);
if (ppp_socket_start_server())
pthread_exit( (void*) EXIT_FAILURE);
if (client_init_all())
pthread_exit( (void*) EXIT_FAILURE);
init_things();
CFRunLoopRun();
return NULL;
}
void load(CFBundleRef bundle, Boolean debug)
{
pthread_attr_t tattr;
pthread_t pppcntl_thread;
CFRunLoopSourceContext rlContext = { 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, terminate_all};
gBundleRef = bundle;
gSCNCVerbose = _sc_verbose | debug;
gSCNCDebug = debug;
gPluginRunloop = CFRunLoopGetCurrent();
gTerminalrls = CFRunLoopSourceCreate(NULL, 0, &rlContext);
if (!gTerminalrls){
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: cannot create signal gTerminalrls in load"));
return;
}
pthread_attr_init(&tattr);
pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&pppcntl_thread, &tattr, pppcntl_run_thread, NULL)) {
CFRelease(gTerminalrls);
gTerminalrls = NULL;
return;
}
pthread_attr_destroy(&tattr);
CFRetain(bundle);
}
void stop(CFRunLoopSourceRef stopRls)
{
cleanup_plugin_store();
if (gStopRls)
return;
gStopRls = stopRls;
if (gTerminalrls)
CFRunLoopSourceSignal(gTerminalrls);
if (gControllerRunloop)
CFRunLoopWakeUp(gControllerRunloop);
}
int allow_stop()
{
struct service *serv;
SCNetworkConnectionStatus status;
if (gStopRls) {
TAILQ_FOREACH(serv, &service_head, next) {
switch (serv->type) {
case TYPE_PPP: status = ppp_getstatus(serv); break;
case TYPE_IPSEC: status = ipsec_getstatus(serv); break;
}
if (status != kSCNetworkConnectionDisconnected)
return 0;
}
CFRunLoopSourceSignal(gStopRls);
if ( gPluginRunloop )
CFRunLoopWakeUp(gPluginRunloop);
return 1;
}
return 0;
}
int allow_dispose(struct service *serv)
{
if (serv->flags & FLAG_FREE) {
serv->flags &= ~FLAG_FREE;
dispose_service(serv);
return 1;
}
return 0;
}
int init_things()
{
CFURLRef urlref, absurlref;
IONotificationPortRef notify;
io_object_t iterator;
mach_timebase_info_data_t timebaseInfo;
CFStringRef key = NULL, setup = NULL, entity = 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,
kSCEntNetIPSec,
#ifdef TARGET_EMBEDDED_OS
CFSTR("com.apple.payload"),
#endif
NULL,
};
gBundleURLRef = CFBundleCopyBundleURL(gBundleRef);
urlref = CFBundleCopyBuiltInPlugInsURL(gBundleRef);
if (urlref) {
absurlref = CFURLCopyAbsoluteURL(urlref);
if (absurlref) {
gPluginsDir = CFURLCopyPath(absurlref);
CFRelease(absurlref);
}
CFRelease(urlref);
}
gIconURLRef = CFBundleCopyResourceURL(gBundleRef, CFSTR(ICON), NULL, NULL);
post_ondemand_token(0);
gSleeping = 0;
gSleepNotification = 0;
gStopRls = 0;
gIOPort = IORegisterForSystemPower(0, ¬ify, iosleep_notifier, &iterator);
if (gIOPort == 0) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: IORegisterForSystemPower failed"));
goto fail;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notify),
kCFRunLoopDefaultMode);
if (mach_timebase_info(&timebaseInfo) != KERN_SUCCESS) { SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: mach_timebase_info failed"));
goto fail;
}
gTimeScaleSeconds = ((double) timebaseInfo.numer / (double) timebaseInfo.denom) / 1000000000;
if ((gDynamicStore = SCDynamicStoreCreate(0, CFSTR("SCNCController"), store_notifier, 0)) == NULL)
goto fail;
if ((rls = SCDynamicStoreCreateRunLoopSource(0, gDynamicStore, 0)) == NULL)
goto fail;
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
cleanup_plugin_store();
#if !TARGET_EMBEDDED_OS
gLoggedInUser = SCDynamicStoreCopyConsoleUser(gDynamicStore, &gLoggedInUserUID, 0);
#endif // !TARGET_EMBEDDED_OS
keys = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
if (keys == NULL || patterns == NULL)
goto fail;
for (i = 0; notifs[i]; i++) {
if ((key = CREATESERVICESETUP(notifs[i])) == NULL)
goto fail;
CFArrayAppendValue(patterns, key);
CFRelease(key);
}
if ((key = CREATEGLOBALSETUP(kSCEntNetIPv4)) == NULL)
goto fail;
CFArrayAppendValue(keys, key);
CFRelease(key);
if ((key = CREATEGLOBALSTATE(kSCEntNetIPv4)) == NULL)
goto fail;
CFArrayAppendValue(keys, key);
CFRelease(key);
#if !TARGET_EMBEDDED_OS
if ((key = SCDynamicStoreKeyCreateConsoleUser(0)) == NULL)
goto fail;
CFArrayAppendValue(keys, key);
CFRelease(key);
#endif // !TARGET_EMBEDDED_OS
SCDynamicStoreSetNotificationKeys(gDynamicStore, keys, patterns);
TAILQ_INIT(&service_head);
entity = CFStringCreateWithFormat(NULL, NULL, CFSTR("(%@|%@)"), kSCEntNetPPP, kSCEntNetIPSec);
key = CREATESERVICESETUP(entity);
setup = CREATEPREFIXSETUP();
if (key == NULL || setup == NULL)
goto fail;
services = SCDynamicStoreCopyKeyList(gDynamicStore, key);
if (services == NULL)
goto done;
nb = CFArrayGetCount(services);
for (i = 0; i < nb; i++) {
CFStringRef serviceID;
if (serviceID = parse_component(CFArrayGetValueAtIndex(services, i), setup)) {
update_service(serviceID);
CFRelease(serviceID);
}
}
reorder_services();
finish_update_services();
if (gSCNCVerbose)
print_services();
done:
my_CFRelease(&entity);
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;
}
static
void iosleep_notifier(void * x, io_service_t y, natural_t messageType, void *messageArgument)
{
CFMutableDictionaryRef dict;
SInt32 error;
int delay;
switch ( messageType ) {
case kIOMessageSystemWillSleep:
gSleeping = 1; 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("Network Connection"));
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:
break;
case kIOMessageSystemWillPowerOn:
gSleeping = 0; gWakeUpTime = mach_absolute_time();
if (gSleepNotification) {
CFUserNotificationCancel(gSleepNotification);
CFRelease(gSleepNotification);
gSleepNotification = 0;
}
break;
case kIOMessageSystemHasPoweredOn:
wake_up();
break;
}
}
static
void store_notifier(SCDynamicStoreRef session, CFArrayRef changedKeys, void *info)
{
CFStringRef setup, ipsetupkey, ipstatekey;
int i, nb, doreorder = 0, dopostsetup = 0;
#if !TARGET_EMBEDDED_OS
CFStringRef userkey;
#endif // !TARGET_EMBEDDED_OS
if (changedKeys == NULL)
return;
setup = CREATEPREFIXSETUP();
#if !TARGET_EMBEDDED_OS
userkey = SCDynamicStoreKeyCreateConsoleUser(0);
#endif // !TARGET_EMBEDDED_OS
ipsetupkey = CREATEGLOBALSETUP(kSCEntNetIPv4);
ipstatekey = CREATEGLOBALSTATE(kSCEntNetIPv4);
if (setup == NULL || ipsetupkey == NULL || ipstatekey == NULL) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: cache_notifier can't allocate keys"));
goto done;
}
#if !TARGET_EMBEDDED_OS
if (userkey == NULL) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: cache_notifier can't allocate keys"));
goto done;
}
#endif // !TARGET_EMBEDDED_OS
nb = CFArrayGetCount(changedKeys);
for (i = 0; i < nb; i++) {
CFStringRef change, serviceID;
change = CFArrayGetValueAtIndex(changedKeys, i);
#if !TARGET_EMBEDDED_OS
if (CFEqual(change, userkey)) {
CFStringRef olduser = gLoggedInUser;
gLoggedInUser = SCDynamicStoreCopyConsoleUser(session, &gLoggedInUserUID, 0);
if (gLoggedInUser == 0)
log_out(); else if (olduser == 0)
log_in(); else
log_switch(); if (olduser) CFRelease(olduser);
continue;
}
#endif // !TARGET_EMBEDDED_OS
if (CFEqual(change, ipsetupkey)) {
doreorder = 1;
continue;
}
if (CFEqual(change, ipstatekey)) {
ipv4_state_changed();
continue;
}
serviceID = parse_component(change, setup);
if (serviceID) {
update_service(serviceID);
CFRelease(serviceID);
dopostsetup = 1;
continue;
}
}
if (doreorder)
reorder_services();
if (dopostsetup)
finish_update_services();
if (gSCNCVerbose)
print_services();
done:
my_CFRelease(&setup);
#if !TARGET_EMBEDDED_OS
my_CFRelease(&userkey);
#endif // !TARGET_EMBEDDED_OS
my_CFRelease(&ipsetupkey);
my_CFRelease(&ipstatekey);
return;
}
static
int can_sleep()
{
struct service *serv;
u_int32_t prevent = 0;
TAILQ_FOREACH(serv, &service_head, next) {
switch (serv->type) {
case TYPE_PPP: prevent = !ppp_can_sleep(serv); break;
case TYPE_IPSEC: prevent = !ipsec_can_sleep(serv); break;
}
if (prevent)
break;
}
return !prevent;
}
static
int will_sleep(int checking)
{
u_int32_t ret = 0;
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
switch (serv->type) {
case TYPE_PPP: ret |= ppp_will_sleep(serv, checking); break;
case TYPE_IPSEC: ret |= ipsec_will_sleep(serv, checking); break;
}
}
return ret;
}
static
void wake_up()
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
serv->flags |= FLAG_FIRSTDIAL;
switch (serv->type) {
case TYPE_PPP: ppp_wake_up(serv); break;
case TYPE_IPSEC: ipsec_wake_up(serv); break;
}
}
}
int allow_sleep()
{
if (gSleeping && !will_sleep(1)) {
if (gSleepNotification) {
CFUserNotificationCancel(gSleepNotification);
CFRelease(gSleepNotification);
gSleepNotification = 0;
}
IOAllowPowerChange(gIOPort, gSleepArgument);
return 1;
}
return 0;
}
void service_started(struct service *serv)
{
switch (serv->type) {
case TYPE_PPP:
#if !TARGET_EMBEDDED_OS
serv->vt = vproc_transaction_begin(NULL);
if ( serv->vt == NULL)
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: vproc_transaction_begin rts NULL"));
#endif
break;
case TYPE_IPSEC:
break;
}
}
static void
service_ending_verify_primaryservice(struct service *serv)
{
CFStringRef newPrimary;
if (gWaitForPrimaryService)
return;
if (serv->type == TYPE_PPP &&
(serv->subtype == PPP_TYPE_PPPoE ||
serv->subtype == PPP_TYPE_SERIAL ||
serv->subtype == PPP_TYPE_SYNCSERIAL)) {
return;
}
newPrimary = copyPrimaryService(gDynamicStore);
if ((newPrimary == NULL) || _SC_CFEqual(serv->serviceID, newPrimary)) {
#if !TARGET_EMBEDDED_OS
gController_vt = vproc_transaction_begin(NULL);
if (gController_vt)
#endif
gWaitForPrimaryService = 1;
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: %s, waiting for PrimaryService. status = %x\n"),
__FUNCTION__, gWaitForPrimaryService);
}
if (newPrimary)
CFRelease(newPrimary);
}
void service_ended(struct service *serv)
{
service_ending_verify_primaryservice(serv);
switch (serv->type) {
case TYPE_PPP:
#if !TARGET_EMBEDDED_OS
if ( serv->vt)
vproc_transaction_end(NULL, serv->vt);
#endif
break;
case TYPE_IPSEC:
break;
}
}
#if !TARGET_EMBEDDED_OS
static
void log_out()
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
switch (serv->type) {
case TYPE_PPP: ppp_log_out(serv); break;
case TYPE_IPSEC: ipsec_log_out(serv); break;
}
}
}
static
void log_in()
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
serv->flags |= FLAG_FIRSTDIAL;
switch (serv->type) {
case TYPE_PPP: ppp_log_in(serv); break;
case TYPE_IPSEC: ipsec_log_in(serv); break;
}
}
}
static
void log_switch()
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
serv->flags |= FLAG_FIRSTDIAL;
switch (serv->type) {
case TYPE_PPP: ppp_log_switch(serv); break;
case TYPE_IPSEC: ipsec_log_switch(serv); break;
}
}
}
#endif
static
void ipv4_state_changed()
{
struct service *serv;
int found = 0;
CFStringRef newPrimary = NULL;
if (gWaitForPrimaryService)
newPrimary = copyPrimaryService(gDynamicStore);
TAILQ_FOREACH(serv, &service_head, next) {
serv->flags |= FLAG_FIRSTDIAL;
switch (serv->type) {
case TYPE_PPP: ppp_ipv4_state_changed(serv); break;
case TYPE_IPSEC: ipsec_ipv4_state_changed(serv); break;
}
if (!found &&
newPrimary &&
_SC_CFEqual(serv->serviceID, newPrimary)) {
found = 1;
}
}
if (!found &&
newPrimary &&
gWaitForPrimaryService) {
gWaitForPrimaryService = 0;
#if !TARGET_EMBEDDED_OS
vproc_transaction_end(NULL, gController_vt);
gController_vt = NULL;
#endif
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: %s, done waiting for ServiceID.\n"),
__FUNCTION__);
}
if (newPrimary)
CFRelease(newPrimary);
}
static
int update_service(CFStringRef serviceID)
{
CFDictionaryRef service = NULL, interface;
CFStringRef subtype = NULL, iftype = NULL, iscisco = NULL;
struct service *serv;
serv = findbyserviceID(serviceID);
interface = copyEntity(gDynamicStore, kSCDynamicStoreDomainSetup, serviceID, kSCEntNetInterface);
if (interface)
iftype = CFDictionaryGetValue(interface, kSCPropNetInterfaceType);
if (!interface || (serv && !my_CFEqual(iftype, serv->typeRef))) {
if (serv) {
dispose_service(serv);
}
goto done;
}
subtype = CFDictionaryGetValue(interface, kSCPropNetInterfaceSubType);
if (serv && !my_CFEqual(subtype, serv->subtypeRef)) {
dispose_service(serv);
serv = 0;
}
iscisco = CFDictionaryGetValue(interface, CFSTR("IsCisco"));
if (!serv) {
serv = new_service(serviceID, iftype, subtype, iscisco);
if (!serv)
goto done;
}
serv->flags |= FLAG_SETUP;
done:
my_CFRelease(&interface);
my_CFRelease(&service);
return 0;
}
static
struct service * new_service(CFStringRef serviceID , CFStringRef typeRef, CFStringRef subtypeRef, CFStringRef iscisco)
{
struct service *serv = 0;
u_short len;
int err = 0;
serv = malloc(sizeof(struct service));
if (!serv)
return 0;
bzero(serv, sizeof(struct service));
serv->serviceID = my_CFRetain(serviceID);
serv->typeRef = my_CFRetain(typeRef);
serv->subtypeRef = my_CFRetain(subtypeRef);
len = CFStringGetLength(serviceID) + 1;
if (serv->sid = malloc(len)) {
CFStringGetCString(serviceID, (char*)serv->sid, len, kCFStringEncodingUTF8);
}
if (my_CFEqual(typeRef, kSCValNetInterfaceTypePPP)) {
serv->type = TYPE_PPP;
serv->subtype = ppp_subtype(subtypeRef);
#if FAKE_L2TP_IPSEC
if (my_CFEqual(subtypeRef, kSCValNetInterfaceSubTypeL2TP) && iscisco) {
serv->type = TYPE_IPSEC;
serv->subtype = ipsec_subtype(subtypeRef);
}
#endif
}
else if (my_CFEqual(typeRef, kSCValNetInterfaceTypeIPSec)) {
serv->type = TYPE_IPSEC;
serv->subtype = ipsec_subtype(subtypeRef);
}
else
goto failed;
serv->unit = findfreeunit(serv->type, serv->subtype);
if (serv->unit == 0xFFFF)
goto failed;
switch (serv->type) {
case TYPE_PPP: err = ppp_new_service(serv); break;
case TYPE_IPSEC: err = ipsec_new_service(serv); break;
}
if (err)
goto failed;
serv->uid = 0;
serv->flags |= FLAG_FIRSTDIAL;
serv->pid = -1;
TAILQ_INIT(&serv->client_head);
TAILQ_INSERT_TAIL(&service_head, serv, next);
client_notify(serv->serviceID, serv->sid, makeref(serv), 0, 0, CLIENT_FLAG_NOTIFY_STATUS, kSCNetworkConnectionDisconnected);
return serv;
failed:
if (serv) {
my_CFRelease(&serv->serviceID);
my_CFRelease(&serv->typeRef);
my_CFRelease(&serv->subtypeRef);
if (serv->sid)
free(serv->sid);
free(serv);
}
return 0;
}
static
int dispose_service(struct service *serv)
{
int delay = 0;
scnc_stop(serv, 0, SIGTERM);
switch (serv->type) {
case TYPE_PPP: delay = ppp_dispose_service(serv); break;
case TYPE_IPSEC: delay = ipsec_dispose_service(serv); break;
}
if (delay) {
serv->flags |= FLAG_FREE;
return 1;
}
if (serv->flags & FLAG_SETUP_ONDEMAND) {
serv->flags &= ~FLAG_SETUP_ONDEMAND;
ondemand_remove_service(serv);
}
TAILQ_REMOVE(&service_head, serv, next);
client_notify(serv->serviceID, serv->sid, makeref(serv), 0, 0, CLIENT_FLAG_NOTIFY_STATUS, kSCNetworkConnectionInvalid);
if (serv->sid)
free(serv->sid);
if (serv->userNotificationRef) {
CFUserNotificationCancel(serv->userNotificationRef);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), serv->userNotificationRLS, kCFRunLoopDefaultMode);
my_CFRelease(&serv->userNotificationRef);
my_CFRelease(&serv->userNotificationRLS);
}
my_CFRelease(&serv->serviceID);
my_CFRelease(&serv->subtypeRef);
my_CFRelease(&serv->typeRef);
free(serv);
return 0;
}
void reorder_services()
{
CFDictionaryRef ip_dict = NULL;
CFStringRef key, serviceID;
CFArrayRef serviceorder;
int i, nb;
struct service *serv;
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 (serv = findbyserviceID(serviceID)) {
TAILQ_REMOVE(&service_head, serv, next);
TAILQ_INSERT_TAIL(&service_head, serv, next);
}
}
}
CFRelease(ip_dict);
}
CFRelease(key);
}
}
static
void finish_update_services()
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
if (serv->flags & FLAG_SETUP) {
serv->flags &= ~(FLAG_FREE + FLAG_SETUP);
if (serv->flags & FLAG_SETUP_ONDEMAND) {
serv->flags &= ~FLAG_SETUP_ONDEMAND;
ondemand_remove_service(serv);
}
switch (serv->type) {
case TYPE_PPP: ppp_setup_service(serv); break;
case TYPE_IPSEC: ipsec_setup_service(serv); break;
}
if (serv->flags & FLAG_SETUP_ONDEMAND)
ondemand_add_service(serv);
}
}
}
struct service *findbyserviceID(CFStringRef serviceID)
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next)
if (CFStringCompare(serv->serviceID, serviceID, 0) == kCFCompareEqualTo)
return serv;
return 0;
}
struct service *findbypid(pid_t pid)
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next)
if (serv->pid == pid)
return serv;
return 0;
}
struct service *findbysid(u_char *data, int len)
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next)
if (serv->sid && (strlen((char*)serv->sid) == len) && !strncmp((char*)serv->sid, (char*)data, len))
return serv;
return 0;
}
u_int32_t makeref(struct service *serv)
{
return (((u_int32_t)serv->subtype) << 16) + serv->unit;
}
struct service *findbyref(u_int16_t type, u_int32_t ref)
{
u_short subtype = ref >> 16;
u_short unit = ref & 0xFFFF;
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
if ((type == serv->type)
&& (((serv->subtype == subtype) || (subtype == 0xFFFF))
&& ((serv->unit == unit) || (unit == 0xFFFF)))) {
return serv;
}
}
return 0;
}
static
u_short findfreeunit(u_short type, u_short subtype)
{
struct service *serv = TAILQ_FIRST(&service_head);
u_short unit = 0;
while (serv) {
if ((type == serv->type)
&& (subtype == serv->subtype)
&& (serv->unit == unit)) {
unit++;
if (unit == 0xFFFF)
return unit;
serv = TAILQ_FIRST(&service_head); }
else
serv = TAILQ_NEXT(serv, next); }
return unit;
}
static
void print_services()
{
struct service *serv;
SCLog(TRUE, LOG_INFO, CFSTR("SCNC Controller: Printing list of ppp services : "));
TAILQ_FOREACH(serv, &service_head, next) {
SCLog(TRUE, LOG_INFO, CFSTR("SCNC Controller: Service = %@, type = %s, subtype = %d"), serv->serviceID, (serv->type == TYPE_PPP) ? "PPP" : (serv->type == TYPE_IPSEC) ? "IPSec" : "???" , serv->subtype);
}
}
void phase_changed(struct service *serv, int phase)
{
if (serv->flags & FLAG_SETUP_ONDEMAND)
ondemand_add_service(serv);
client_notify(serv->serviceID, serv->sid, makeref(serv), phase, 0, CLIENT_FLAG_NOTIFY_STATUS, scnc_getstatus(serv));
}
void user_notification_callback(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
{
struct service *serv;
TAILQ_FOREACH(serv, &service_head, next) {
if (serv->userNotificationRef == userNotification) {
CFRetain(userNotification);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), serv->userNotificationRLS, kCFRunLoopDefaultMode);
my_CFRelease(&serv->userNotificationRef);
my_CFRelease(&serv->userNotificationRLS);
switch (serv->type) {
case TYPE_PPP: ppp_user_notification_callback(serv, userNotification, responseFlags); break;
case TYPE_IPSEC: ipsec_user_notification_callback(serv, userNotification, responseFlags); break;
}
CFRelease(userNotification);
break;
}
}
}
int cleanup_plugin_store()
{
CFStringRef ondemand_key = NULL;
ondemand_key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetOnDemand);
if (ondemand_key == NULL)
goto fail;
SCDynamicStoreRemoveValue(gDynamicStore, ondemand_key);
fail:
my_CFRelease(&ondemand_key);
return 0;
}
static
void post_ondemand_token(uint64_t state64)
{
uint32_t status;
if (gNotifyOnDemandToken == -1) {
status = notify_register_check(kSCNETWORKCONNECTION_ONDEMAND_NOTIFY_KEY, &gNotifyOnDemandToken);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: notify_register_check failed, status = %d"), status);
goto fail;
}
}
status = notify_set_state(gNotifyOnDemandToken, state64);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: notify_set_state failed, status = %d"), status);
goto fail;
}
status = notify_post(kSCNETWORKCONNECTION_ONDEMAND_NOTIFY_KEY);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_ERR, CFSTR("SCNC Controller: notify_post failed, status = %d"), status);
goto fail;
}
return;
fail:
if (gNotifyOnDemandToken != -1) {
notify_cancel(gNotifyOnDemandToken);
gNotifyOnDemandToken = -1;
}
}
static
int ondemand_add_service(struct service *serv)
{
CFMutableDictionaryRef new_ondemand_dict = NULL, new_trigger_dict = NULL;
CFStringRef serviceid, ondemand_key = NULL;
CFNumberRef num;
CFDictionaryRef ondemand_dict = NULL, current_trigger_dict = NULL;
CFMutableArrayRef new_triggers_array = NULL;
CFArrayRef current_triggers_array = NULL;
int val = 0, ret = 0, count = 0, i, found = 0, found_index = 0;
ondemand_key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetOnDemand);
if (ondemand_key == NULL)
goto fail;
ondemand_dict = SCDynamicStoreCopyValue(gDynamicStore, ondemand_key);
if (ondemand_dict) {
current_triggers_array = CFDictionaryGetValue(ondemand_dict, kSCNetworkConnectionOnDemandTriggers);
if (current_triggers_array) {
found = 0;
count = CFArrayGetCount(current_triggers_array);
for (i = 0; i < count; i++) {
current_trigger_dict = CFArrayGetValueAtIndex(current_triggers_array, i);
if (current_trigger_dict == NULL)
continue;
serviceid = CFDictionaryGetValue(current_trigger_dict, kSCNetworkConnectionOnDemandServiceID);
if (serviceid == NULL)
continue;
if (CFStringCompare(serviceid, serv->serviceID, 0) == kCFCompareEqualTo) {
found = 1;
found_index = i;
break;
}
}
new_triggers_array = CFArrayCreateMutableCopy(0, 0, current_triggers_array);
if (new_triggers_array == NULL)
goto fail;
if (found) {
CFArrayRemoveValueAtIndex(new_triggers_array, found_index);
}
}
new_ondemand_dict = CFDictionaryCreateMutableCopy(0, 0, ondemand_dict);
if (new_ondemand_dict == NULL)
goto fail;
}
if (new_ondemand_dict == NULL) {
if ((new_ondemand_dict = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
goto fail;
}
if (new_triggers_array == NULL) {
if ((new_triggers_array = CFArrayCreateMutable(0, 1, &kCFTypeArrayCallBacks)) == NULL)
goto fail;
}
if ((new_trigger_dict = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == 0)
goto fail;
switch (serv->type) {
case TYPE_PPP:
break;
case TYPE_IPSEC:
ipsec_ondemand_add_service_data(serv, new_trigger_dict);
break;
}
CFDictionarySetValue(new_trigger_dict, kSCNetworkConnectionOnDemandServiceID, serv->serviceID);
val = scnc_getstatus(serv);
num = CFNumberCreate(NULL, kCFNumberIntType, &val);
if (num) {
CFDictionarySetValue(new_trigger_dict, kSCNetworkConnectionOnDemandStatus, num);
CFRelease(num);
}
CFArrayAppendValue(new_triggers_array, new_trigger_dict);
CFDictionarySetValue(new_ondemand_dict, kSCNetworkConnectionOnDemandTriggers, new_triggers_array);
if (SCDynamicStoreSetValue(gDynamicStore, ondemand_key, new_ondemand_dict) == 0) {
; }
post_ondemand_token(CFArrayGetCount(new_triggers_array));
ret = 1;
goto done;
fail:
ret = 0;
done:
my_CFRelease(&new_ondemand_dict);
my_CFRelease(&new_trigger_dict);
my_CFRelease(&ondemand_key);
my_CFRelease(&new_triggers_array);
my_CFRelease(&ondemand_dict);
return ret;
}
static
int ondemand_remove_service(struct service *serv)
{
CFMutableDictionaryRef new_ondemand_dict = NULL;
CFStringRef ondemand_key = NULL, serviceid;
CFDictionaryRef ondemand_dict, current_trigger_dict;
CFMutableArrayRef new_triggers_array = NULL;
CFArrayRef current_triggers_array = NULL;
int count = 0, i, ret = 0, found = 0, found_index = 0;
ondemand_key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetOnDemand);
if (ondemand_key == NULL)
goto fail;
ondemand_dict = SCDynamicStoreCopyValue(gDynamicStore, ondemand_key);
if (ondemand_dict == NULL)
goto fail;
current_triggers_array = CFDictionaryGetValue(ondemand_dict, kSCNetworkConnectionOnDemandTriggers);
if (current_triggers_array == NULL)
goto fail;
found = 0;
count = CFArrayGetCount(current_triggers_array);
for (i = 0; i < count; i++) {
current_trigger_dict = CFArrayGetValueAtIndex(current_triggers_array, i);
if (current_trigger_dict == NULL)
continue;
serviceid = CFDictionaryGetValue(current_trigger_dict, kSCNetworkConnectionOnDemandServiceID);
if (serviceid == NULL)
continue;
if (CFStringCompare(serviceid, serv->serviceID, 0) == kCFCompareEqualTo) {
found = 1;
found_index = i;
break;
}
}
if (!found)
goto fail;
if (count == 1) {
SCDynamicStoreRemoveValue(gDynamicStore, ondemand_key);
post_ondemand_token(0);
ret = 1;
goto done;
}
new_triggers_array = CFArrayCreateMutableCopy(0, 0, current_triggers_array);
if (new_triggers_array == NULL)
goto fail;
CFArrayRemoveValueAtIndex(new_triggers_array, found_index);
new_ondemand_dict = CFDictionaryCreateMutableCopy(0, 0, ondemand_dict);
if (new_ondemand_dict == NULL)
goto fail;
CFDictionarySetValue(new_ondemand_dict, kSCNetworkConnectionOnDemandTriggers, new_triggers_array);
if (SCDynamicStoreSetValue(gDynamicStore, ondemand_key, new_ondemand_dict) == 0) {
; }
post_ondemand_token(CFArrayGetCount(new_triggers_array));
ret = 1;
goto done;
fail:
ret = 0;
done:
my_CFRelease(&ondemand_dict);
my_CFRelease(&ondemand_key);
my_CFRelease(&new_ondemand_dict);
my_CFRelease(&new_triggers_array);
return ret;
}
void disable_ondemand(struct service *serv)
{
if (serv->flags & FLAG_SETUP_ONDEMAND) {
serv->flags &= ~FLAG_SETUP_ONDEMAND;
ondemand_remove_service(serv);
}
}
int client_gone(void *client)
{
struct service *serv;
struct service_client *servclient;
TAILQ_FOREACH(serv, &service_head, next) {
servclient = get_client(serv, client);
if (servclient && servclient->autoclose) {
scnc_stop(serv, client, SIGTERM);
}
}
return 0;
}
static
int add_client(struct service *serv, void *client, int autoclose)
{
struct service_client *servclient;
servclient = malloc(sizeof(struct service_client));
if (servclient == 0)
return -1;
servclient->client = client;
servclient->autoclose = autoclose;
TAILQ_INSERT_TAIL(&serv->client_head, servclient, next);
return 0;
}
static
int remove_client(struct service *serv, void *client)
{
struct service_client *servclient;
TAILQ_FOREACH(servclient, &serv->client_head, next) {
if (servclient->client == client) {
TAILQ_REMOVE(&serv->client_head, servclient, next);
free(servclient);
return 0;
}
}
return 0;
}
static
struct service_client *get_client(struct service *serv, void *client)
{
struct service_client *servclient;
TAILQ_FOREACH(servclient, &serv->client_head, next)
if (servclient->client == client)
return servclient;
return 0;
}
static
int remove_all_clients(struct service *serv)
{
struct service_client *servclient;
while (servclient = TAILQ_FIRST(&serv->client_head)) {
TAILQ_REMOVE(&serv->client_head, servclient, next);
free(servclient);
}
return 0;
}
int scnc_stop(struct service *serv, void *client, int signal)
{
int ret = -1;
if (client) {
if (get_client(serv, client))
remove_client(serv, client);
if (TAILQ_FIRST(&serv->client_head))
return 0;
}
else {
remove_all_clients(serv);
}
switch (serv->type) {
case TYPE_PPP: ret = ppp_stop(serv, signal); break;
case TYPE_IPSEC: ret = ipsec_stop(serv, signal); break;
}
return ret;
}
int scnc_start(struct service *serv, CFDictionaryRef options, void *client, int autoclose, uid_t uid, gid_t gid, mach_port_t bootstrap)
{
int ret = EIO, onDemand = 0;
if (options) {
CFStringRef priority;
int dialMode = 1;
if (!CFDictionaryContainsKey(options, kSCNetworkConnectionSelectionOptionOnDemandHostName)) {
goto dial;
}
#if !TARGET_EMBEDDED_OS
if (!gLoggedInUser
|| uid != gLoggedInUserUID)
return EIO; #endif
priority = CFDictionaryGetValue(options, kSCPropNetPPPOnDemandPriority);
if (isString(priority)) {
if (CFStringCompare(priority, kSCValNetPPPOnDemandPriorityHigh, 0) == kCFCompareEqualTo)
dialMode = 1;
else if (CFStringCompare(priority, kSCValNetPPPOnDemandPriorityLow, 0) == kCFCompareEqualTo)
dialMode = 2;
else if (CFStringCompare(priority, kSCValNetPPPOnDemandPriorityDefault, 0) == kCFCompareEqualTo) {
CFDictionaryRef dict;
if (dict = CFDictionaryGetValue(options, kSCEntNetPPP)) {
CFStringRef str;
str = CFDictionaryGetValue(dict, kSCPropNetPPPOnDemandMode);
if (isString(str)) {
if (CFStringCompare(str, kSCValNetPPPOnDemandModeAggressive, 0) == kCFCompareEqualTo)
dialMode = 1;
else if (CFStringCompare(str, kSCValNetPPPOnDemandModeConservative, 0) == kCFCompareEqualTo)
dialMode = 2;
else if (CFStringCompare(str, kSCValNetPPPOnDemandModeCompatible, 0) == kCFCompareEqualTo)
dialMode = 0;
}
}
}
}
switch (dialMode) {
case 1: onDemand = 1;
break;
case 2: if (serv->flags & FLAG_FIRSTDIAL) {
onDemand = 1;
}
break;
default:
break;
}
if (!onDemand)
return EIO; }
dial:
if (gStopRls ||
(gSleeping && (serv->flags & FLAG_SETUP_DISCONNECTONSLEEP)))
return EIO;
switch (serv->type) {
case TYPE_PPP: ret = ppp_start(serv, options, uid, gid, bootstrap, 0, onDemand); break;
case TYPE_IPSEC: ret = ipsec_start(serv, options, uid, gid, bootstrap, 0, onDemand); break;
}
if (ret == 0) {
serv->flags &= ~FLAG_FIRSTDIAL;
if (client)
add_client(serv, client, autoclose);
}
return ret;
}
int scnc_suspend(struct service *serv)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_suspend(serv); break;
case TYPE_IPSEC: break;
}
return ret;
}
int scnc_resume(struct service *serv)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_resume(serv); break;
case TYPE_IPSEC: break;
}
return ret;
}
int scnc_getstatus(struct service *serv)
{
SCNetworkConnectionStatus status = kSCNetworkConnectionDisconnected;
switch (serv->type) {
case TYPE_PPP: status = ppp_getstatus(serv); break;
case TYPE_IPSEC: status = ipsec_getstatus(serv); break;
}
return status;
}
int scnc_copyextendedstatus(struct service *serv, void **reply, u_int16_t *replylen)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_copyextendedstatus(serv, reply, replylen); break;
case TYPE_IPSEC: ret = ipsec_copyextendedstatus(serv, reply, replylen); break;
}
return ret;
}
int scnc_copystatistics(struct service *serv, void **reply, u_int16_t *replylen)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_copystatistics(serv, reply, replylen); break;
case TYPE_IPSEC: ret = ipsec_copystatistics(serv, reply, replylen); break;
}
return ret;
}
int scnc_getconnectdata(struct service *serv, void **reply, u_int16_t *replylen, int all)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_getconnectdata(serv, reply, replylen, all); break;
case TYPE_IPSEC: ret = ipsec_getconnectdata(serv, reply, replylen, all); break;
}
return ret;
}
int scnc_getconnectsystemdata(struct service *serv, void **reply, u_int16_t *replylen)
{
int ret = -1;
switch (serv->type) {
case TYPE_PPP: ret = ppp_getconnectsystemdata(serv, reply, replylen); break;
case TYPE_IPSEC: ; break;
}
return ret;
}