EAPOLSocket.c   [plain text]


/*
 * Copyright (c) 2001-2012 Apple 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@
 */

/* 
 * Modification History
 *
 * October 26, 2001	Dieter Siegmund (dieter@apple.com)
 * - created
 * May 21, 2008		Dieter Siegmund (dieter@apple.com)
 * - added multiple Supplicant support
 */

/*
 * EAPOLSocket.c
 * - "object" that wraps access to EAP over LAN
 */
#include <stdlib.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/queue.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/filio.h>
#include <stdint.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/dlil.h>
#include <net/ndrv.h>
#include <net/ethernet.h>
#include <net/if_media.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#include "EAPOLClient.h"
#include "EAPOLControl.h"
#include "EAPOLControlPrivate.h"
#include "EAPUtil.h"
#include "EAPOLUtil.h"
#include "eapol_socket.h"
#include "FDHandler.h"
#include "Timer.h"
#include "EAPOLSocket.h"
#include "EAPOLSocketPrivate.h"
#include "myCFUtil.h"
#include "mylog.h"
#include "printdata.h"
#include <TargetConditionals.h>

#if ! TARGET_OS_EMBEDDED
#include "my_darwin.h"
#endif /* TARGET_OS_EMBEDDED */

#include "InterestNotification.h"

#ifndef NO_WIRELESS
#include "wireless.h"
#endif /* NO_WIRELESS */

#define EAPOLSOCKET_RECV_BUFSIZE	1600
#define EAPOLSOCKET_SEND_BUFSIZE	1600

static const struct ether_addr eapol_multicast = {
    EAPOL_802_1_X_GROUP_ADDRESS
};

/* MTU */
#define kMTU				CFSTR("MTU")
#define EAPOL_MTU_DEFAULT		1280
#define EAPOL_MTU_MIN			576
static int				S_mtu;

/* pre-auth tunables */
#define kPreauthentication		CFSTR("Preauthentication")
#define kScanDelayAuthenticatedSeconds	CFSTR("ScanDelayAuthenticatedSeconds")
#define kScanDelayRoamSeconds		CFSTR("ScanDelayRoamSeconds")
#define kScanPeriodSeconds		CFSTR("ScanPeriodSeconds")
#define kEnablePreauthentication	CFSTR("EnablePreauthentication")
#define kNumberOfScans			CFSTR("NumberOfScans")

/* testing tunables */
#define kTesting			CFSTR("Testing")
#define kTransmitPacketLossPercent	CFSTR("TransmitPacketLossPercent")
#define kReceivePacketLossPercent	CFSTR("ReceivePacketLossPercent")

/* 
 * Static: S_enable_preauth
 * 
 * Purpose:
 *   Controls whether pre-authentication will occur on wireless interfaces.
 */
static bool	S_enable_preauth = FALSE;

/*
 * Static Variable: S_scan_delay_authenticated_secs, S_scan_delay_roam_secs
 *
 * Purpose:
 *   Affect when the SSID-directed scan will occur.
 *   
 *   S_scan_delay_authenticated_secs controls when/if the scan gets scheduled
 *   after the main Supplicant reaches the Authenticated state.
 *
 *   S_scan_delay_roam_secs controls when/if the scan gets scheduled after
 *   we roam from one AP to another.
 *
 *   If the value is >= 0, the scan will be scheduled after that many seconds.
 *   If the value is < 0, the scan will not be scheduled.
 */
#define SCAN_DELAY_AUTHENTICATED_SECS	10
#define SCAN_DELAY_ROAM_SECS		10

static int	S_scan_delay_authenticated_secs = SCAN_DELAY_AUTHENTICATED_SECS;
static int	S_scan_delay_roam_secs = SCAN_DELAY_ROAM_SECS;

/*
 * Static: S_scan_period_secs
 *
 * Purpose:
 *   After a scan completes, controls when/if another scan gets scheduled
 *   in a certain period of time.
 *
 *   A periodic scan gets scheduled if the value is > 0, otherwise it does
 *   not get scheduled.
 */
#define SCAN_PERIOD_SECS		(-1)
static int	S_scan_period_secs = SCAN_PERIOD_SECS;

/* 
 * Static: S_number_of_scans
 *
 * Purpose:
 *   The number of 802.11 scans to do each time we initiate a scan.
 */
#define NUMBER_OF_SCANS			1
static int	S_number_of_scans = NUMBER_OF_SCANS;

/* 
 * Static: S_debug
 *
 * Purpose:
 *   Controls whether the packet trace is dumped to stdout or not.
 */
static bool	S_debug = FALSE;

/*
 * Static: S_transmit_loss_percent, S_receive_loss_percent
 * Purpose:
 *   When set, used to simulate packet loss with the given packet loss
 *   percentage.
 */
static int 	S_transmit_loss_percent;
static int 	S_receive_loss_percent;

struct EAPOLSocket_s;

TAILQ_HEAD(EAPOLSocketHead_s, EAPOLSocket_s);

struct EAPOLSocketSource_s {
    EAPOLClientRef			client;
    char				if_name[IF_NAMESIZE];
    int					if_name_length;
    struct ether_addr			ether;
    EAPOLSocketReceiveData		rx;
    FDHandler *				handler;
    bool				is_wireless;
    bool				is_wpa_enterprise;
    bool				link_active;
    bool				authenticated;
    bool				need_force_renew;
    InterestNotificationRef		interest;
    struct ether_addr			authenticator_mac;
    bool				authenticator_mac_valid;
#ifndef NO_WIRELESS
    wireless_t				wref;
    CFStringRef				ssid;
    /* BSSID for the default 802.1X connection */
    struct ether_addr			bssid;
    bool				bssid_valid;
#endif /* NO_WIRELESS */
    CFRunLoopObserverRef		observer;
    bool				process_removals;
    TimerRef				scan_timer;
    SCDynamicStoreRef			store;
    EAPOLSocketRef			sock;
    struct EAPOLSocketHead_s		preauth_sockets;
    int					preauth_sockets_count;
    EAPOLControlMode			mode;
};

struct EAPOLSocket_s {
    struct ether_addr			bssid;
    EAPOLSocketReceiveCallbackRef	func;
    void *				arg1;
    void *				arg2;
    EAPOLSocketSourceRef		source;
    SupplicantRef			supp;
    bool				remove;
    EAPPacketRef			eap_tx_packet;
    int					eap_tx_packet_length;
    TAILQ_ENTRY(EAPOLSocket_s)		link;
};

/**
 ** forward declarations
 **/
static int
EAPOLSocketSourceTransmit(EAPOLSocketSourceRef source,
			  EAPOLSocketRef sock,
			  EAPOLPacketType packet_type,
			  void * body, unsigned int body_length);
static bool
EAPOLSocketSourceUpdateWirelessInfo(EAPOLSocketSourceRef source);

static EAPOLSocketRef
EAPOLSocketSourceLookupPreauthSocket(EAPOLSocketSourceRef source, 
				     const struct ether_addr * bssid);

static void
EAPOLSocketSourceInitiateScan(EAPOLSocketSourceRef source);

static void
EAPOLSocketSourceCancelScan(EAPOLSocketSourceRef source);

static void
EAPOLSocketSourceScheduleScan(EAPOLSocketSourceRef source, int delay_secs);

static void
EAPOLSocketSourceMarkPreauthSocketsForRemoval(EAPOLSocketSourceRef source);

static void
EAPOLSocketSourceScheduleHandshakeNotification(EAPOLSocketSourceRef source);

static void
EAPOLSocketSourceUnscheduleHandshakeNotification(EAPOLSocketSourceRef source);

static void
EAPOLSocketSourceForceRenew(EAPOLSocketSourceRef source);

/**
 ** EAPOLSocket* routines
 **/

static boolean_t
S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key, boolean_t def)
{
    CFBooleanRef 	b;
    boolean_t		ret = def;

    b = isA_CFBoolean(CFDictionaryGetValue(plist, key));
    if (b != NULL) {
	ret = CFBooleanGetValue(b);
    }
    if (eapolclient_should_log(kLogFlagTunables)) {
	FILE *	log_file = eapolclient_log_file();

	SCPrint(TRUE, log_file,
		CFSTR("%@ = %s\n"), key, ret == TRUE ? "true" : "false");
	fflush(log_file);
    }
    return (ret);
}


static int
S_get_plist_int_log(CFDictionaryRef plist, CFStringRef key, int def,
		    bool should_log)
{
    CFNumberRef 	n;
    int			ret = def;

    n = isA_CFNumber(CFDictionaryGetValue(plist, key));
    if (n != NULL) {
	if (CFNumberGetValue(n, kCFNumberIntType, &ret) == FALSE) {
	    ret = def;
	}
    }
    if (should_log && eapolclient_should_log(kLogFlagTunables)) {
	FILE *	log_file = eapolclient_log_file();

	SCPrint(TRUE, log_file, CFSTR("%@ = %d\n"), key, ret);
	fflush(log_file);
    }
    return (ret);
}

__private_extern__ int
get_plist_int(CFDictionaryRef plist, CFStringRef key, int def)
{
    return (S_get_plist_int_log(plist, key, def, TRUE));
}

/*
 * Function: S_simulated_event_occurred
 * Purpose:
 *   Given the rate of occurrence of the event in 'percent', return
 *   whether the simulated event has occurred.
 * Returns:
 *   true if the event occurred, false otherwise.
 */
static bool
S_simulated_event_occurred(int percent)
{
    u_int32_t	value;

    if (percent == 0) {
	return (false);
    }
    value = arc4random() / (UINT32_MAX / 100);
    if (value < percent) {
	return (true);
    }
    return (false);
}

void
EAPOLSocketSetGlobals(SCPreferencesRef prefs)
{
    CFDictionaryRef	plist;
    CFNumberRef		mtu;

    if (prefs == NULL) {
	return;
    }
    mtu = SCPreferencesGetValue(prefs, kMTU);
    if (isA_CFNumber(mtu) != NULL) {
	int		mtu_val;

	if (CFNumberGetValue(mtu, kCFNumberIntType, 
			     &mtu_val) == FALSE) {
	    my_log(LOG_NOTICE, "com.apple.eapolclient.MTU invalid");
	}
	else if (mtu_val < EAPOL_MTU_MIN) {
	    my_log(LOG_NOTICE,
		   "com.apple.eapolclient.MTU %d < %d, using default %d",
		   mtu_val, EAPOL_MTU_MIN, EAPOL_MTU_DEFAULT);
	}
	else {
	    S_mtu = mtu_val;
	}
    }

    plist = SCPreferencesGetValue(prefs, kPreauthentication);
    if (isA_CFDictionary(plist) != NULL) {
	S_enable_preauth
	    = S_get_plist_boolean(plist, kEnablePreauthentication,
				  FALSE);
	if (S_enable_preauth) {
	    S_scan_delay_authenticated_secs
		= get_plist_int(plist, kScanDelayAuthenticatedSeconds,
				SCAN_DELAY_AUTHENTICATED_SECS);
	    S_scan_delay_roam_secs
		= get_plist_int(plist, kScanDelayRoamSeconds,
				SCAN_DELAY_ROAM_SECS);
	    S_scan_period_secs
		= get_plist_int(plist, kScanPeriodSeconds,
				SCAN_PERIOD_SECS);
	    S_number_of_scans
		= get_plist_int(plist, kNumberOfScans,
				NUMBER_OF_SCANS);
	}
    }

    plist = SCPreferencesGetValue(prefs, kTesting);
    if (isA_CFDictionary(plist) != NULL) {
	S_transmit_loss_percent 
	    = S_get_plist_int_log(plist, kTransmitPacketLossPercent, 0, FALSE);
	if (S_transmit_loss_percent != 0) {
	    eapolclient_log(kLogFlagBasic,
			    "Will simulate %d%% transmit packet loss\n",
			    S_transmit_loss_percent);
	    my_log(LOG_NOTICE,
		   "Will simulate %d%% transmit packet loss",
		   S_transmit_loss_percent);
	}
	S_receive_loss_percent
	    = S_get_plist_int_log(plist, kReceivePacketLossPercent, 0, FALSE);
	if (S_receive_loss_percent != 0) {
	    eapolclient_log(kLogFlagBasic,
			    "Will simulate %d%% receive packet loss\n",
			    S_receive_loss_percent);
	    my_log(LOG_NOTICE,
		   "Will simulate %d%% receive packet loss",
		   S_receive_loss_percent);
	}
    }
    return;
}

/*
 * Function: EAPOLSocketSetEAPTxPacket
 * Purpose:
 *   Set the last EAP transmit packet.
 *   If 'pkt' is NULL, then just clear the old packet that may be there.
 *   If 'pkt' is not NULL, clear the old packet, and make a copy of the
 *   new packet and save it.
 */
static void
EAPOLSocketSetEAPTxPacket(EAPOLSocketRef sock, EAPPacketRef pkt, int length)
{
    if (sock->eap_tx_packet != NULL) {
	free(sock->eap_tx_packet);
    }
    if (pkt == NULL) {
	sock->eap_tx_packet = NULL;
	sock->eap_tx_packet_length = 0;
    }
    else {
	sock->eap_tx_packet = (EAPPacketRef)malloc(length);
	bcopy(pkt, sock->eap_tx_packet, length);
	sock->eap_tx_packet_length = length;
    }
    return;
}

void
EAPOLSocketSetDebug(boolean_t debug)
{
    S_debug = debug;
    return;
}

static void
EAPOLSocketMarkForRemoval(EAPOLSocketRef sock)
{
    sock->remove = TRUE;
    sock->source->process_removals = TRUE;
    return;
}

static boolean_t
EAPOLSocketIsMain(EAPOLSocketRef sock)
{
    return (sock->source->sock == sock);
}

const char *
EAPOLSocketIfName(EAPOLSocketRef sock, uint32_t * name_length)
{
    EAPOLSocketSourceRef	source = sock->source;

    if (name_length != NULL) {
	*name_length = source->if_name_length;
    }
    return (source->if_name);
}

const char *
EAPOLSocketName(EAPOLSocketRef sock)
{
    const char *	name;

    if (EAPOLSocketIsMain(sock)) {
	name = "(main)";
    }
    else {
	name = ether_ntoa(&sock->bssid);
    }
    return (name);
}

boolean_t
EAPOLSocketIsWireless(EAPOLSocketRef sock)
{
    return (sock->source->is_wireless);
}

static void
EAPOLSocketFree(EAPOLSocketRef * sock_p)
{
    EAPOLSocketRef 		sock;

    if (sock_p == NULL) {
	return;
    }
    sock = *sock_p;
    if (sock != NULL) {
	EAPOLSocketSourceRef	source;

	source = sock->source;
	if (sock == source->sock) {
	    /* main supplicant */
	    source->sock = NULL;
	}
	else {
	    /* pre-auth supplicant */
	    TAILQ_REMOVE(&source->preauth_sockets, sock, link);
	    source->preauth_sockets_count--;
	}
	EAPOLSocketSetEAPTxPacket(sock, NULL, 0);
	free(sock);
    }
    *sock_p = NULL;
    return;
}

boolean_t
EAPOLSocketSetKey(EAPOLSocketRef sock, wirelessKeyType type, 
		    int index, const uint8_t * key, int key_length)
{
#ifdef NO_WIRELESS
    return (FALSE);
#else /* NO_WIRELESS */
    if (sock->source->is_wireless == FALSE) {
	return (FALSE);
    }
    return (wireless_set_key(sock->source->wref, type, index, key, key_length));
#endif /* NO_WIRELESS */
}

CFStringRef
EAPOLSocketGetSSID(EAPOLSocketRef sock)
{
#ifdef NO_WIRELESS
    return (NULL);
#else /* NO_WIRELESS */
    if (sock->source->is_wireless == FALSE) {
	return (NULL);
    }
    return (sock->source->ssid);
#endif /* NO_WIRELESS */
}

int
EAPOLSocketMTU(EAPOLSocketRef sock)
{
    if (S_mtu != 0) {
	return (S_mtu);
    }
    return (EAPOL_MTU_DEFAULT);
}

const struct ether_addr *
EAPOLSocketGetAuthenticatorMACAddress(EAPOLSocketRef sock)
{
    EAPOLSocketSourceRef	source = sock->source;

    if (source->sock != sock || source->authenticator_mac_valid == FALSE) {
	return (NULL);
    }
    return (&source->authenticator_mac);
}

void
EAPOLSocketEnableReceive(EAPOLSocketRef sock,
			 EAPOLSocketReceiveCallback * func,
			 void * arg1, void * arg2)
{
    sock->func = func;
    sock->arg1 = arg1;
    sock->arg2 = arg2;
    return;
}

void
EAPOLSocketDisableReceive(EAPOLSocketRef sock)
{
    sock->func = NULL;
    return;
}

int
EAPOLSocketTransmit(EAPOLSocketRef sock,
		    EAPOLPacketType packet_type,
		    void * body, unsigned int body_length)
{
    if (packet_type == kEAPOLPacketTypeEAPPacket) {
	EAPOLSocketSetEAPTxPacket(sock, body, body_length);
    }
    else {
	EAPOLSocketSetEAPTxPacket(sock, NULL, 0);
    }
    return (EAPOLSocketSourceTransmit(sock->source, sock, packet_type,
				      body, body_length));
}

void
EAPOLSocketClearPMKCache(EAPOLSocketRef sock)
{
#ifndef NO_WIRELESS
    EAPOLSocketSourceRef	source = sock->source;

    if (source->is_wireless == FALSE || source->sock != sock) {
	return;
    }
    if (wireless_clear_wpa_pmk_cache(source->wref)) {
	eapolclient_log(kLogFlagBasic, "PMK cache cleared\n");
    }
#endif /* NO_WIRELESS */
    return;
}

boolean_t
EAPOLSocketSetWPAKey(EAPOLSocketRef sock, 
		     const uint8_t * session_key, int session_key_length,
		     const uint8_t * server_key, int server_key_length)
{
#ifdef NO_WIRELESS
    return (FALSE);
#else /* NO_WIRELESS */
    const struct ether_addr *	bssid;
    EAPOLSocketSourceRef	source = sock->source;

    if (source->is_wireless == FALSE || source->is_wpa_enterprise == FALSE) {
	return (FALSE);
    }
    if (source->sock == sock) {
	/* main supplicant */
	bssid = NULL;
	if (session_key_length != 0) {
	    EAPOLSocketSourceScheduleHandshakeNotification(source);
	}
	else {
	    /* if the notification is still active, de-activate it */
	    EAPOLSocketSourceUnscheduleHandshakeNotification(source);
	}
    }
    else {
	/* pre-auth supplicant */
	bssid = &sock->bssid;
    }
    if (eapolclient_should_log(kLogFlagBasic)) {
	if (bssid == NULL) {
	    eapolclient_log(kLogFlagBasic, "set_key %d/%d\n",
			    session_key_length, server_key_length);
	}
	else {
	    eapolclient_log(kLogFlagBasic, 
			    "set_key %s %d/%d\n", ether_ntoa(bssid),
			    session_key_length, server_key_length);
	}
    }
    return (wireless_set_wpa_key(source->wref, bssid,
				 session_key, session_key_length,
				 server_key, server_key_length));
#endif /* NO_WIRELESS */
}

bool
EAPOLSocketIsLinkActive(EAPOLSocketRef sock)
{
    return (sock->source->link_active);
}

void
EAPOLSocketReportStatus(EAPOLSocketRef sock, CFDictionaryRef status_dict)
{
    EAPOLClientRef		client;
    int				result;
    EAPOLSocketSourceRef	source = sock->source;

    client = source->client;
    if (client == NULL) {
	return;
    }

    /* for now, only report status for the main supplicant */
    if (source->sock == sock) {
	EAPClientStatus		client_status;
	SupplicantState		supplicant_state;

	supplicant_state = Supplicant_get_state(sock->supp, &client_status);
	switch (supplicant_state) {
	case kSupplicantStateInactive:
	    EAPOLSocketSourceUnscheduleHandshakeNotification(source);
	    source->authenticated = FALSE;
	    break;
	case kSupplicantStateAuthenticated:
	    if (source->authenticated == FALSE) {
		EAPOLSocketSourceUnscheduleHandshakeNotification(source);
		EAPOLSocketSourceForceRenew(source);
		source->authenticated = TRUE;
	    }
	    break;
	case kSupplicantStateHeld:
	    EAPOLSocketSourceUnscheduleHandshakeNotification(source);
	    source->authenticated = FALSE;
	    EAPOLSocketSourceForceRenew(source);
	    break;
	case kSupplicantStateLogoff:
	    if (EAPOLSocketIsWireless(sock) == FALSE) {
		/* 5900529: wait for 1/2 second before the force renew */
		usleep(500 * 1000);
	    }
	    EAPOLSocketSourceForceRenew(source);
	    break;
	}
	result = EAPOLClientReportStatus(client, status_dict);
	if (result != 0) {
	    my_log(LOG_NOTICE, "EAPOLClientReportStatus failed: %s",
		   strerror(result));
	}
	if (S_enable_preauth && sock->source->is_wireless) {
	    switch (supplicant_state) {
	    case kSupplicantStateAuthenticated:
		EAPOLSocketSourceScheduleScan(sock->source,
					      S_scan_delay_authenticated_secs);
		break;
	    default:
		/* get rid of the pre-auth supplicants */
		EAPOLSocketSourceCancelScan(sock->source);
		EAPOLSocketSourceMarkPreauthSocketsForRemoval(sock->source);
		break;
	    }
	}
    }
    else {
	EAPClientStatus		client_status;

	switch (Supplicant_get_state(sock->supp, &client_status)) {
	case kSupplicantStateHeld:
	    my_log(LOG_NOTICE, "Supplicant %s Held, status %d",
		   ether_ntoa(&sock->bssid), client_status);
	    eapolclient_log(kLogFlagBasic,
			    "Supplicant %s Held, status %d\n",
			    ether_ntoa(&sock->bssid), client_status);
	    EAPOLSocketMarkForRemoval(sock);
	    break;
	case kSupplicantStateAuthenticated:
	    if (eapolclient_should_log(kLogFlagBasic)) {
		eapolclient_log(kLogFlagBasic,
				"Supplicant %s Authenticated - Complete\n",
				ether_ntoa(&sock->bssid));
	    }
	    EAPOLSocketMarkForRemoval(sock);
	    break;
	case kSupplicantStateAuthenticating:
	    /* check for user input required, if so kill it */
	    if (client_status == kEAPClientStatusUserInputRequired) {
		my_log(LOG_NOTICE,
		       "Supplicant %s Authenticating, requires user input",
		       ether_ntoa(&sock->bssid));
		eapolclient_log(kLogFlagBasic,
				"Supplicant %s Authenticating,"
				" requires user input\n", 
				ether_ntoa(&sock->bssid));
		EAPOLSocketMarkForRemoval(sock);
	    }
	    break;
	case kSupplicantStateConnecting:
	case kSupplicantStateAcquired:
	case kSupplicantStateLogoff:
	case kSupplicantStateInactive:
	case kSupplicantStateDisconnected:
	default:
	    break;
	}
    }
    return;
}

EAPOLControlMode
EAPOLSocketGetMode(EAPOLSocketRef sock)
{
    return (sock->source->mode);
}

void
EAPOLSocketStopClient(EAPOLSocketRef sock)
{
    EAPOLSocketSourceRef	source = sock->source;

    if (source->sock != sock) {
	return;
    }
#if TARGET_OS_EMBEDDED
    EAPOLControlStop(EAPOLSocketIfName(sock, NULL));
#else /* TARGET_OS_EMBEDDED */
    EAPOLClientUserCancelled(source->client);
#endif /* TARGET_OS_EMBEDDED */
    if (EAPOLSocketIsWireless(sock)) {
	wireless_disassociate(source->wref);
    }
    return;
}

boolean_t
EAPOLSocketReassociate(EAPOLSocketRef sock)
{
    boolean_t			ret;
    CFDictionaryRef		scan_record;
    EAPOLSocketSourceRef	source = sock->source;

    if (EAPOLSocketIsWireless(sock) == FALSE) {
	return (FALSE);
    }
    if (EAPOLSocketIsMain(sock) == FALSE) {
	return (FALSE);
    }
    scan_record = wireless_copy_scan_record(source->if_name, source->store);
    if (scan_record == NULL) {
	return (FALSE);
    }
    ret = wireless_reassociate(source->wref, scan_record);
    CFRelease(scan_record);
    return (ret);
}

/**
 ** packet validation/printing routines
 **/
static void
ether_header_fprint(FILE * f, struct ether_header * eh_p)
{
    fprintf(f, "Ether packet: dest %s ",
	    ether_ntoa((void *)eh_p->ether_dhost));
    fprintf(f, "source %s type 0x%04x\n", 
	    ether_ntoa((void *)eh_p->ether_shost),
	    ntohs(eh_p->ether_type));
    return;
}

static bool
ether_header_valid(struct ether_header * eh_p, unsigned int length,
		   FILE * f)
{
    if (length < sizeof(*eh_p)) {
	if (f != NULL) {
	    fprintf(f, "Packet length %d < sizeof(*eh_p) %ld\n",
		    length, sizeof(*eh_p));
	    fprint_data(f, (void *)eh_p, length);
	}
	return (FALSE);
    }
    if (f != NULL) {
	ether_header_fprint(f, eh_p);
    }
    return (TRUE);
}

/**
 ** EAPOLSocketSource routines
 **/

static SCDynamicStoreRef
link_event_register(const char * if_name,
		    SCDynamicStoreCallBack func, void * arg)
{
    CFMutableArrayRef		keys = NULL;
    CFStringRef			key;
    CFRunLoopSourceRef		rls;
    SCDynamicStoreRef		store;
    SCDynamicStoreContext	context;

    bzero(&context, sizeof(context));
    context.info = arg;
    store = SCDynamicStoreCreate(NULL, CFSTR("EAPOLClient"), 
				 func, &context);
    if (store == NULL) {
	my_log(LOG_NOTICE, "SCDynamicStoreCreate() failed, %s",
	       SCErrorString(SCError()));
	return (NULL);
    }
    keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    key = SCDynamicStoreKeyCreate(NULL, 
				  CFSTR("%@/%@/%@/%s/%@"),
				  kSCDynamicStoreDomainState,
				  kSCCompNetwork,
				  kSCCompInterface,
				  if_name,
				  kSCEntNetLink);
    CFArrayAppendValue(keys, key);
    my_CFRelease(&key);
    SCDynamicStoreSetNotificationKeys(store, keys, NULL);
    my_CFRelease(&keys);

    rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    my_CFRelease(&rls);
    return (store);
}

static bool
get_number(CFNumberRef num_cf, uint32_t * num_p)
{
    if (isA_CFNumber(num_cf) == NULL
	|| CFNumberGetValue(num_cf, kCFNumberIntType, num_p) == FALSE) {
	return (FALSE);
    }
    return (TRUE);
}

static void
EAPOLSocketSourceForceRenew(EAPOLSocketSourceRef source)
{
    EAPOLClientRef	client;

    client = source->client;
    if (client == NULL) {
	return;
    }
    eapolclient_log(kLogFlagBasic, "force renew\n");
    (void)EAPOLClientForceRenew(client);
    return;
}

static void
EAPOLSocketSourceStop(EAPOLSocketSourceRef source)
{
    my_log(LOG_NOTICE, "%s STOP", source->if_name);
    Supplicant_stop(source->sock->supp);
    EAPOLSocketSourceFree(&source);
    exit(EX_OK);
    /* NOT REACHED */
    return;
}

static void
EAPOLSocketSourceClientNotification(EAPOLClientRef client, Boolean server_died,
				    void * context)
{
    EAPOLClientControlCommand	command;
    CFNumberRef			command_cf;
    CFDictionaryRef		control_dict = NULL;
    int				result;
    EAPOLSocketSourceRef	source = (EAPOLSocketSourceRef)context;

    if (server_died) {
	my_log(LOG_NOTICE, "%s: EAPOLController died", source->if_name);
	if (source->mode == kEAPOLControlModeUser) {
	    goto stop;
	}
	/* just exit, don't send EAPOL Logoff packet <rdar://problem/6418520> */
	exit(EX_OK);
    }
    result = EAPOLClientGetConfig(client, &control_dict);
    if (result != 0) {
	my_log(LOG_NOTICE, "%s: EAPOLClientGetConfig failed, %s",
	       source->if_name, strerror(result));
	goto stop;
    }
    if (control_dict == NULL) {
	my_log(LOG_NOTICE, "%s: EAPOLClientGetConfig returned NULL control",
	       source->if_name);
	goto stop;
    }
    command_cf = CFDictionaryGetValue(control_dict,
				      kEAPOLClientControlCommand);
    if (get_number(command_cf, &command) == FALSE) {
	my_log(LOG_NOTICE, "%s: invalid/missing command",
	       source->if_name);
	goto stop;
    }
    if (Supplicant_control(source->sock->supp, command, 
			   control_dict) == TRUE) {
	goto stop;
    }
    my_CFRelease(&control_dict);
    return;

 stop:
    EAPOLSocketSourceStop(source);
    /* NOT REACHED */
    return;
}

static EAPOLSocketRef
EAPOLSocketSourceLookupPreauthSocket(EAPOLSocketSourceRef source, 
				     const struct ether_addr * bssid)
{
    EAPOLSocketRef		scan;

    TAILQ_FOREACH(scan, &source->preauth_sockets, link) {
	if (bcmp(&scan->bssid, bssid, sizeof(scan->bssid)) == 0) {
	    return (scan);
	}
    }
    return (NULL);
}

static void
EAPOLSocketSourceMarkPreauthSocketsForRemoval(EAPOLSocketSourceRef source)
{
    EAPOLSocketRef		scan;

    TAILQ_FOREACH(scan, &source->preauth_sockets, link) {
	EAPOLSocketMarkForRemoval(scan);
    }
    return;
}

static bool
is_link_active(const char * name)
{
    bool		active = FALSE;
    struct ifmediareq	ifm;
    int			s;

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0) {
	perror("socket");
	goto done;
    }
    bzero(&ifm, sizeof(ifm));
    strlcpy(ifm.ifm_name, name, sizeof(ifm.ifm_name));
    if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifm) < 0) {
	goto done;
    }
    if ((ifm.ifm_status & IFM_AVALID) == 0
	|| (ifm.ifm_status & IFM_ACTIVE) != 0) {
	active = TRUE;
    }

 done:
    if (s >= 0) {
	close(s);
    }
    return (active);
}

static void
EAPOLSocketSourceLinkStatusChanged(SCDynamicStoreRef session,
				   CFArrayRef _not_used,
				   void * info)
{
    EAPOLSocketSourceRef	source = (EAPOLSocketSourceRef)info;

    source->link_active = is_link_active(source->if_name);
    eapolclient_log(kLogFlagBasic, "link %s\n",
		    source->link_active ? "active" : "inactive");

    /* make sure our wireless information is up to date */
    if (source->is_wireless) {
	EAPOLSocketSourceUpdateWirelessInfo(source);
    }

    /* let the 802.1X Supplicant know about the link status change */
    if (source->sock != NULL) {
	if (source->link_active == FALSE) {
	    /* toss last packet in case the Authenticator re-uses identifier */
	    EAPOLSocketSetEAPTxPacket(source->sock, NULL, 0);
	}
	Supplicant_link_status_changed(source->sock->supp, source->link_active);
    }
    return;
}

static void
EAPOLSocketSourceReceive(void * arg1, void * arg2)
{
    uint32_t			buf[EAPOLSOCKET_RECV_BUFSIZE / sizeof(uint32_t)];
    EAPOLPacketRef		eapol_p;
    struct ether_header *	eh_p = (struct ether_header *)buf;
    uint16_t			ether_type;
    int				length;
    int 			n;
    EAPOLSocketReceiveDataRef	rx;
    EAPOLSocketRef		sock = NULL;
    EAPOLSocketSourceRef 	source = (EAPOLSocketSourceRef)arg1;

    n = recv(FDHandler_fd(source->handler), (char *)buf, sizeof(buf), 0);
    if (n <= 0) {
	if (n < 0) {
	    my_log(LOG_NOTICE, "EAPOLSocketSourceReceive: recv failed %s",
		   strerror(errno));
	}
	goto done;
    }
    if (S_debug) {
	printf("\n"
	       "----------------------------------------\n");
	timestamp_fprintf(stdout, "Receive Packet Size: %d\n", n);
    }
    if (ether_header_valid(eh_p, n, S_debug ? stdout : NULL)
	== FALSE) {
	goto done;
    }
    ether_type = ntohs(eh_p->ether_type);
    switch (ether_type) {
    case EAPOL_802_1_X_ETHERTYPE:
    case IEEE80211_PREAUTH_ETHERTYPE:
	break;
    default:
	if (S_debug) {
	    fprintf(stdout, "Unexpected ethertype (%02x)\n",
		    ether_type);
	}
	goto done;
    }
    eapol_p = (void *)(eh_p + 1);
    length = n - sizeof(*eh_p);
    if (EAPOLPacketValid(eapol_p, length, S_debug ? stdout : NULL) == FALSE) {
	goto done;
    }
    if (ether_type == EAPOL_802_1_X_ETHERTYPE) {
	bcopy(eh_p->ether_shost, &source->authenticator_mac, 
	      sizeof(source->authenticator_mac));
	source->authenticator_mac_valid = TRUE;
	if (source->is_wireless) {
	    if (source->bssid_valid == FALSE
		|| bcmp(eh_p->ether_shost, &source->bssid,
			sizeof(eh_p->ether_shost)) != 0) {
		EAPOLSocketSourceUpdateWirelessInfo(source);
	    }
	}
    }
    rx = &source->rx;
    rx->length = length;
    rx->eapol_p = eapol_p;
    if (eapolclient_should_log(kLogFlagPacketDetails)) {
	FILE *	log_file = eapolclient_log_file();
	
	eapolclient_log(kLogFlagPacketDetails,
			"Receive Packet Size %d\n", n);
	ether_header_fprint(log_file, eh_p);
	EAPOLPacketValid(eapol_p, length, log_file);
	fflush(log_file);
    }
    else if (eapolclient_should_log(kLogFlagBasic)) {
	eapolclient_log(kLogFlagBasic,
			"Receive Size %d Type 0x%04x From %s\n",
			n, ntohs(eh_p->ether_type),
			ether_ntoa((void *)eh_p->ether_shost));
    }
    /* dispatch the packet to the right socket */
    if (ether_type == EAPOL_802_1_X_ETHERTYPE) {
	sock = source->sock;
    }
    else {
	sock = EAPOLSocketSourceLookupPreauthSocket(source,
						    (const struct ether_addr *)
						    eh_p->ether_shost);
    }
    if (sock != NULL) {
	EAPRequestPacketRef	req_p;
	bool			retransmit = FALSE;

	req_p = (EAPRequestPacketRef)eapol_p->body;
	if (sock->eap_tx_packet != NULL) {
	    if (eapol_p->packet_type == kEAPOLPacketTypeEAPPacket
		&& req_p->code == kEAPCodeRequest
		&& req_p->identifier == sock->eap_tx_packet->identifier) {
		retransmit = TRUE;
	    }
	    else {
		EAPOLSocketSetEAPTxPacket(sock, NULL, 0);
	    }
	}
	if (retransmit) {
	    eapolclient_log(kLogFlagBasic,
			    "Retransmit EAP packet %d bytes\n",
			    sock->eap_tx_packet_length);
	    EAPOLSocketSourceTransmit(sock->source, sock,
				      kEAPOLPacketTypeEAPPacket,
				      sock->eap_tx_packet,
				      sock->eap_tx_packet_length);
	}
	else if (S_receive_loss_percent != 0 
		 && S_simulated_event_occurred(S_receive_loss_percent)) {
	    /* drop the packet */
	    eapolclient_log(kLogFlagBasic,
			    "Simulate receive packet loss: dropping %d bytes\n",
			    length);
	    my_log(LOG_NOTICE,
		   "Simulate receive packet loss: dropping %d bytes",
		   length);
	}
	else if (sock->func != NULL) {
	    (*sock->func)(sock->arg1, sock->arg2, rx);
	}
    }
    rx->eapol_p = NULL;
    if (S_debug) {
	fflush(stdout);
	fflush(stderr);
    }

 done:
    return;
}

static int
EAPOLSocketSourceTransmit(EAPOLSocketSourceRef source,
			  EAPOLSocketRef sock,
			  EAPOLPacketType packet_type,
			  void * body, unsigned int body_length)
{
    uint32_t			buf[EAPOLSOCKET_SEND_BUFSIZE / sizeof(uint32_t)];
    EAPOLPacket *		eapol_p;
    struct ether_header *	eh_p;
    struct sockaddr_ndrv 	ndrv;
    unsigned int		size;

    size = sizeof(*eh_p) + sizeof(*eapol_p);
    if (body != NULL) {
	size += body_length;
    }
    else {
	body_length = 0;
    }

    bzero(buf, size);
    eh_p = (struct ether_header *)buf;
    eapol_p = (void *)(eh_p + 1);

    if (source->sock == sock) {
	if (source->is_wireless) {
	    /* if we don't know the bssid, try to update it now */
	    if (source->bssid_valid == FALSE) {
		EAPOLSocketSourceUpdateWirelessInfo(source);
		if (source->bssid_valid == FALSE) {
		    /* bssid unknown, drop the packet */
		    eapolclient_log(kLogFlagBasic,
				    "Transmit: unknown BSSID,"
				    " not sending %d bytes\n",
				    body_length + sizeof(*eapol_p));
		    my_log(LOG_DEBUG,
			   "EAPOLSocketSourceTransmit: unknown BSSID"
			   ", not sending %d bytes",
			   body_length + sizeof(*eapol_p));
		    return (-1);
		}
	    }
	    /* copy the current bssid */
	    bcopy(&source->bssid, &eh_p->ether_dhost,
		  sizeof(eh_p->ether_dhost));
	}
	else {
	    /* ethernet uses the multicast address */
	    bcopy(&eapol_multicast, &eh_p->ether_dhost, 
		  sizeof(eh_p->ether_dhost));
	}
	eh_p->ether_type = htons(EAPOL_802_1_X_ETHERTYPE);
    }
    else {
	/* pre-auth uses a specific BSSID */
	bcopy(&sock->bssid, &eh_p->ether_dhost,
	      sizeof(eh_p->ether_dhost));
	eh_p->ether_type = htons(IEEE80211_PREAUTH_ETHERTYPE);

    }
    bcopy(&source->ether, eh_p->ether_shost, 
	  sizeof(eh_p->ether_shost));
    eapol_p->protocol_version = EAPOL_802_1_X_PROTOCOL_VERSION;
    eapol_p->packet_type = packet_type;
    EAPOLPacketSetLength(eapol_p, body_length);
    if (body != NULL) {
	bcopy(body, eapol_p->body, body_length);
    }

    /* the contents of ndrv are ignored */
    bzero(&ndrv, sizeof(ndrv));
    ndrv.snd_len = sizeof(ndrv);
    ndrv.snd_family = AF_NDRV;

    if (S_debug) {
	printf("\n"
	       "========================================\n");
	timestamp_fprintf(stdout, "Transmit Packet Size %d\n", size);
	ether_header_valid(eh_p, size, stdout);
	EAPOLPacketValid(eapol_p, body_length + sizeof(*eapol_p), stdout);
	fflush(stdout);
	fflush(stderr);
    }

    if (eapolclient_should_log(kLogFlagPacketDetails)) {
	FILE *		log_file = eapolclient_log_file();

	eapolclient_log(kLogFlagPacketDetails,
			"Transmit Packet Size %d\n", 
			body_length + sizeof(*eapol_p));
	ether_header_fprint(log_file, eh_p);
	EAPOLPacketValid(eapol_p, 
			 body_length + sizeof(*eapol_p),
			 log_file);
	fflush(log_file);
    }
    else if (eapolclient_should_log(kLogFlagBasic)) {
	eapolclient_log(kLogFlagBasic,
			"Transmit Size %d Type 0x%04x To %s\n",
			body_length + sizeof(*eapol_p),
			ntohs(eh_p->ether_type),
			ether_ntoa((void *)eh_p->ether_dhost));
    }
    if (S_transmit_loss_percent != 0 
	&& S_simulated_event_occurred(S_transmit_loss_percent)) {
	eapolclient_log(kLogFlagBasic,
			"Simulate transmit packet loss: dropping %d bytes\n",
			body_length);
	my_log(LOG_NOTICE,
	       "Simulate transmit packet loss: dropping %d bytes",
	       body_length);
    }
    else if (sendto(FDHandler_fd(source->handler), eh_p, size,
		    0, (struct sockaddr *)&ndrv, sizeof(ndrv)) < size) {
	my_log(LOG_NOTICE, "EAPOLSocketSourceTransmit: sendto failed, %s",
	       strerror(errno));
	return (-1);
    }
    return (0);
}

static void
EAPOLSocketSourceRemovePreauthSockets(EAPOLSocketSourceRef source)
{
    int				i;
    EAPOLSocketRef		remove_list[source->preauth_sockets_count];
    int				remove_list_count;
    EAPOLSocketRef		scan;

    /* remove all pre-auth sockets marked with remove */
    remove_list_count = 0;
    TAILQ_FOREACH(scan, &source->preauth_sockets, link) {
	if (scan->remove) {
	    remove_list[remove_list_count++] = scan;
	}
    }
    for (i = 0; i < remove_list_count; i++) {
	EAPOLSocketRef	sock = remove_list[i];

	if (eapolclient_should_log(kLogFlagBasic)) {
	    eapolclient_log(kLogFlagBasic, "Removing Supplicant for %s\n",
			    ether_ntoa(&sock->bssid));
	}
	Supplicant_free(&sock->supp);
	EAPOLSocketFree(&sock);
    }
    return;
}

#define N_REMOVE_STATIC		10
static void
EAPOLSocketSourceObserver(CFRunLoopObserverRef observer, 
			  CFRunLoopActivity activity, void * info)
{
    EAPOLSocketSourceRef	source = (EAPOLSocketSourceRef)info;

    if (source->process_removals) {
	EAPOLSocketSourceRemovePreauthSockets(source);
	source->process_removals = FALSE;
    }
    return;
}

static bool
fd_is_socket(int fd)
{
    struct stat		sb;

    if (fstat(fd, &sb) == 0) {
	if (S_ISSOCK(sb.st_mode)) {
	    return (TRUE);
	}
    }
    return (FALSE);
}

EAPOLSocketSourceRef
EAPOLSocketSourceCreate(const char * if_name,
			const struct ether_addr * ether,
			CFDictionaryRef * control_dict_p)
{
    int				fd = -1;
    FDHandler *			handler = NULL;
    bool			is_wireless = FALSE;
    CFRunLoopObserverRef	observer = NULL;
    int				result;
    EAPOLSocketSourceRef	source = NULL;
    SCDynamicStoreRef		store = NULL;
    TimerRef			scan_timer = NULL;
    wireless_t			wref = NULL;

    *control_dict_p = NULL;
#ifndef NO_WIRELESS
    /* is this a wireless interface? */
    if (wireless_bind(if_name, &wref)) {
	is_wireless = TRUE;
    }
#endif /* NO_WIRELESS */
    if (fd_is_socket(STDIN_FILENO)) {
	/* already have the socket we need */
	fd = 0;
    }
    else {
	fd = eapol_socket(if_name, is_wireless);
	if (fd == -1) {
	    my_log(LOG_NOTICE,
		   "EAPOLSocketSourceCreate: eapol_socket(%s) failed, %m");
	    goto failed;
	}
    }
    handler = FDHandler_create(fd);
    if (handler == NULL) {
	my_log(LOG_NOTICE, "EAPOLSocketSourceCreate: FDHandler_create failed");
	goto failed;
    }

    source = malloc(sizeof(*source));
    if (source == NULL) {
	my_log(LOG_NOTICE, "EAPOLSocketSourceCreate: malloc failed");
	goto failed;
    }
    bzero(source, sizeof(*source));
    if (is_wireless) {
	CFRunLoopObserverContext context = { 0, NULL, NULL, NULL, NULL };
	context.info = source;
	observer = CFRunLoopObserverCreate(NULL,
					   kCFRunLoopBeforeWaiting,
					   TRUE, 0,
					   EAPOLSocketSourceObserver,
					   &context);
	if (observer == NULL) {
	    my_log(LOG_INFO, "CFRunLoopObserverCreate failed\n");
	    goto failed;
	}
	scan_timer = Timer_create();
	if (scan_timer == NULL) {
	    my_log(LOG_INFO, "Timer_create failed\n");
	    goto failed;
	}
    }
    store = link_event_register(if_name,
				EAPOLSocketSourceLinkStatusChanged,
				source);
    if (store == NULL) {
	my_log(LOG_NOTICE, "link_event_register failed: %s",
	       SCErrorString(SCError()));
	goto failed;
    }
    TAILQ_INIT(&source->preauth_sockets);
    strlcpy(source->if_name, if_name, sizeof(source->if_name));
    source->if_name_length = strlen(source->if_name);
    source->ether = *ether;
    source->handler = handler;
    source->store = store;
    source->is_wireless = is_wireless;
    source->wref = wref;
    FDHandler_enable(handler, EAPOLSocketSourceReceive, source, NULL);
    EAPOLSocketSourceLinkStatusChanged(source->store, NULL, source);
    source->client = EAPOLClientAttach(source->if_name,
				       EAPOLSocketSourceClientNotification, 
				       source, control_dict_p, &result);
    if (source->client == NULL) {
	my_log(LOG_NOTICE, "EAPOLClientAttach(%s) failed: %s",
	       source->if_name, strerror(result));
    }
    if (observer != NULL) {
	source->observer = observer;
	CFRunLoopAddObserver(CFRunLoopGetCurrent(), source->observer, 
			     kCFRunLoopDefaultMode);
    }
    source->scan_timer = scan_timer;
    return (source);

 failed:
#ifndef NO_WIRELESS
    if (wref != NULL) {
	wireless_free(wref);
    }
#endif /* NO_WIRELESS */
    if (source != NULL) {
	free(source);
    }
    if (handler != NULL) {
	FDHandler_free(&handler);
    }
    else if (fd >= 0) {
	close(fd);
    }
    if (store != NULL) {
	CFRelease(store);
    }
    if (observer != NULL) {
	CFRelease(observer);
    }
    Timer_free(&scan_timer);
    return (NULL);
}


static void
EAPOLSocketSourceRemoveSocketWithBSSID(EAPOLSocketSourceRef source,
				       const struct ether_addr * bssid)
{
    EAPOLSocketRef	sock;

    sock = EAPOLSocketSourceLookupPreauthSocket(source, bssid);
    if (sock == NULL) {
	/* no such socket */
	return;
    }
    if (eapolclient_should_log(kLogFlagBasic)) {
	eapolclient_log(kLogFlagBasic, "Removing Supplicant for %s\n",
			ether_ntoa(bssid));
    }
    Supplicant_free(&sock->supp);
    EAPOLSocketFree(&sock);
    return;
}

static bool
EAPOLSocketSourceUpdateWirelessInfo(EAPOLSocketSourceRef source)
{
#ifdef NO_WIRELESS
    return (FALSE);
#else /* NO_WIRELESS */
    struct ether_addr	ap_mac;
    bool 		ap_mac_valid = FALSE;
    bool		changed = FALSE;

    if (source->is_wireless == FALSE) {
	return (FALSE);
    }
    ap_mac_valid = wireless_ap_mac(source->wref, &ap_mac);
    if (ap_mac_valid == FALSE) {
	my_log(LOG_DEBUG,
	       "EAPOLSocketSourceUpdateWirelessInfo: not associated");
	changed = source->bssid_valid;
	source->bssid_valid = FALSE;
	source->is_wpa_enterprise = FALSE;
	EAPOLSocketSourceUnscheduleHandshakeNotification(source);
	eapolclient_log(kLogFlagBasic, "Disassociated\n");
	my_CFRelease(&source->ssid);
	Timer_cancel(source->scan_timer);
	wireless_scan_cancel(source->wref);
	source->authenticated = FALSE;
    }
    else {
	CFStringRef	ssid;

	if (source->bssid_valid == FALSE
	    || bcmp(&ap_mac, &source->bssid, sizeof(ap_mac)) != 0) {
	    changed = TRUE;

	    if (S_enable_preauth) {
		/* remove any pre-auth socket with the new bssid */
		EAPOLSocketSourceRemoveSocketWithBSSID(source, &ap_mac);
		if (source->bssid_valid == TRUE) {
		    /* we roamed */
		    EAPOLSocketSourceScheduleScan(source,
						  S_scan_delay_roam_secs);
		}
	    }
	}
	source->bssid_valid = TRUE;
	source->bssid = ap_mac;
	ssid = wireless_copy_ssid_string(source->wref);
	source->is_wpa_enterprise = wireless_is_wpa_enterprise(source->wref);
	if (source->ssid != NULL && ssid != NULL
	    && !CFEqual(source->ssid, ssid)) {
	    EAPOLSocketSourceCancelScan(source);
	}
	my_CFRelease(&source->ssid);
	source->ssid = ssid;
	if (S_debug) {
	    SCLog(TRUE, LOG_NOTICE, 
		  CFSTR("EAPOLSocketSourceUpdateWirelessInfo:"
			" ssid %@ bssid %s"),
		  (source->ssid != NULL) ? source->ssid : CFSTR("<unknown>"),
		  ether_ntoa(&ap_mac));
	}
	if (eapolclient_should_log(kLogFlagBasic)) {
	    FILE *	log_file = eapolclient_log_file();

	    eapolclient_log(kLogFlagBasic, "Associated");
	    SCPrint(TRUE, log_file, CFSTR(" SSID %@ BSSID %s\n"),
		    (source->ssid != NULL) ? source->ssid : CFSTR("<unknown>"),
		    ether_ntoa(&ap_mac));
	    fflush(log_file);
	}
    }
    return (changed);
#endif /* NO_WIRELESS */
}

void
EAPOLSocketSourceFree(EAPOLSocketSourceRef * source_p)
{
    EAPOLSocketSourceRef 	source;

    if (source_p == NULL) {
	return;
    }
    source = *source_p;

    if (source != NULL) {
	FDHandler_free(&source->handler);
#ifndef NO_WIRELESS
	if (source->is_wireless) {
	    wireless_free(source->wref);
	}
	my_CFRelease(&source->ssid);
#endif /* NO_WIRELESS */
	if (source->observer != NULL) {
	    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), source->observer, 
				    kCFRunLoopDefaultMode);
	    my_CFRelease(&source->observer);
	}
	my_CFRelease(&source->store);
	EAPOLClientDetach(&source->client);

	Timer_free(&source->scan_timer);
	EAPOLSocketSourceUnscheduleHandshakeNotification(source);
	free(source);
    }
    *source_p = NULL;
    return;
}


static EAPOLSocketRef
EAPOLSocketSourceCreateSocket(EAPOLSocketSourceRef source, 
			      const struct ether_addr * bssid)
{
    EAPOLSocketRef		sock = NULL;

    sock = malloc(sizeof(*sock));
    if (sock == NULL) {
	my_log(LOG_NOTICE, "EAOLSocketSourceCreateSocket: malloc failed");
	return (NULL);
    }
    bzero(sock, sizeof(*sock));
    sock->source = source;
    if (bssid != NULL) {
	sock->bssid = *bssid;
	TAILQ_INSERT_TAIL(&source->preauth_sockets, sock, link);
	source->preauth_sockets_count++;
    }
    else {
	source->sock = sock;
    }
    return (sock);
}

SupplicantRef
EAPOLSocketSourceCreateSupplicant(EAPOLSocketSourceRef source,
				  CFDictionaryRef control_dict)
{
    CFDictionaryRef		config_dict = NULL;
    EAPOLControlMode		mode = kEAPOLControlModeNone;
    bool			should_stop = FALSE;
    EAPOLSocketRef		sock = NULL;
    SupplicantRef		supp = NULL;

    if (control_dict != NULL) {
	EAPOLClientControlCommand	command;
	CFNumberRef			command_cf;
	CFNumberRef			mode_cf;

	command_cf = CFDictionaryGetValue(control_dict,
					  kEAPOLClientControlCommand);
	if (get_number(command_cf, &command) == FALSE) {
	    goto failed;
	}
	if (command != kEAPOLClientControlCommandRun) {
	    my_log(LOG_NOTICE, "%s: received stop command", source->if_name);
	    goto failed;
	}
	mode_cf = CFDictionaryGetValue(control_dict,
				       kEAPOLClientControlMode);
	if (mode_cf != NULL
	    && get_number(mode_cf, &mode) == FALSE) {
	    my_log(LOG_NOTICE, "%s: Mode property invalid",
		   source->if_name);
	    goto failed;
	}
	config_dict = CFDictionaryGetValue(control_dict,
					   kEAPOLClientControlConfiguration);
	if (config_dict == NULL) {
	    my_log(LOG_NOTICE, "%s: configuration empty", source->if_name);
	    goto failed;
	}
    }
    source->mode = mode;
    sock = EAPOLSocketSourceCreateSocket(source, NULL);
    if (sock == NULL) {
	goto failed;
    }
    supp = Supplicant_create(sock);
    if (supp == NULL) {
	goto failed;
    }
    switch (mode) {
    case kEAPOLControlModeSystem:
    case kEAPOLControlModeLoginWindow:
	Supplicant_set_no_ui(supp);
	break;
    default:
	break;
    }
    if (config_dict != NULL) {
	Supplicant_update_configuration(supp, config_dict, &should_stop);
	if (should_stop) {
	    return (NULL);
	}
    }
    sock->supp = supp;
    return (supp);

 failed:
    EAPOLSocketFree(&sock);
    Supplicant_free(&supp);
    return (NULL);
}

static void
S_log_bssid_list(CFArrayRef bssid_list)
{
    int		count;
    int		i;
    FILE *	log_file = eapolclient_log_file();

    count = CFArrayGetCount(bssid_list);
    eapolclient_log(kLogFlagBasic, "Scan complete: %d AP%s = {", 
		    count, (count == 1) ? "" : "s");
    for (i = 0; i < count; i++) {
	CFDataRef			bssid_data;
	const struct ether_addr *	bssid;
		
	bssid_data = CFArrayGetValueAtIndex(bssid_list, i);
	bssid = (const struct ether_addr *)CFDataGetBytePtr(bssid_data);
	fprintf(log_file, "%s%s", (i == 0) ? "" : ", ",
		ether_ntoa(bssid));
    }
    fprintf(log_file, "}\n");
    fflush(log_file);
}

static void
EAPOLSocketSourceScanCallback(wireless_t wref,
			      CFArrayRef bssid_list, void * arg)
{
    EAPOLSocketSourceRef	source = (EAPOLSocketSourceRef)arg;

    if (bssid_list == NULL) {
	eapolclient_log(kLogFlagBasic, "Scan complete: no APs\n");
    }
    else if (source->bssid_valid == FALSE) {
	eapolclient_log(kLogFlagBasic,
			"Scan complete: Supplicant bssid unknown\n");
	my_log(LOG_NOTICE, "main Supplicant bssid is unknown, skipping");
    }
    else {
	int	count;
	int	i;

	if (eapolclient_should_log(kLogFlagBasic)) {
	    S_log_bssid_list(bssid_list);
	}
	count = CFArrayGetCount(bssid_list);
	for (i = 0; i < count; i++) {
	    CFDataRef			bssid_data;
	    const struct ether_addr *	bssid;
	    EAPOLSocketRef		sock;
		
	    bssid_data = CFArrayGetValueAtIndex(bssid_list, i);
	    bssid = (const struct ether_addr *)CFDataGetBytePtr(bssid_data);
	    if (bcmp(bssid, &source->bssid, sizeof(source->bssid)) == 0) {
		/* skip matching on the main Supplicant */
		continue;
	    }
	    sock = EAPOLSocketSourceLookupPreauthSocket(source, bssid);
	    if (sock != NULL) {
		/* already one running */
		continue;
	    }
	    sock = EAPOLSocketSourceCreateSocket(source, bssid);
	    if (sock == NULL) {
		continue;
	    }
	    sock->supp = Supplicant_create_with_supplicant(sock,
							   source->sock->supp);
	    if (sock->supp == NULL) {
		my_log(LOG_NOTICE, "Supplicant create %s failed",
		       ether_ntoa(&sock->bssid));
		if (eapolclient_should_log(kLogFlagBasic)) {
		    eapolclient_log(kLogFlagBasic,
				    "Supplicant create %s failed\n",
				    ether_ntoa(&sock->bssid));
		}
		EAPOLSocketFree(&sock);
	    }
	    else {
		if (eapolclient_should_log(kLogFlagBasic)) {
		    eapolclient_log(kLogFlagBasic,
				    "Supplicant %s created\n",
				    ether_ntoa(&sock->bssid));
		}
		Supplicant_start(sock->supp);
	    }
	}
    }
    if (S_scan_period_secs > 0) {
	EAPOLSocketSourceScheduleScan(source, S_scan_period_secs);
    }
    return;
}

static void
EAPOLSocketSourceInitiateScan(EAPOLSocketSourceRef source)
{
    if (source->ssid != NULL) {
	wireless_scan(source->wref, source->ssid,
		      S_number_of_scans, EAPOLSocketSourceScanCallback,
		      (void *)source);
	eapolclient_log(kLogFlagBasic, "Scan initiated\n");
    }
    return;
}

static void
EAPOLSocketSourceCancelScan(EAPOLSocketSourceRef source)
{
    Timer_cancel(source->scan_timer);
    wireless_scan_cancel(source->wref);
    return;
}


static void
EAPOLSocketSourceScheduleScan(EAPOLSocketSourceRef source, int delay)
{
    struct timeval	t;

    if (delay < 0) {
	/* don't schedule a scan if the delay is negative */
	return;
    }
    t.tv_sec = delay;
    t.tv_usec = 0;
    Timer_set_relative(source->scan_timer, t,
		       (void *)EAPOLSocketSourceInitiateScan,
		       (void *)source, NULL, NULL);
    return;
}

static boolean_t
EAPOLSocketSourceReleaseHandshakeNotification(EAPOLSocketSourceRef source)
{
    if (source->interest == NULL) {
	return (FALSE);
    }
    InterestNotificationRelease(source->interest);
    source->interest = NULL;
    return (TRUE);
}

static void
EAPOLSocketSourceHandshakeComplete(InterestNotificationRef interest_p,
				   const void * arg)
{
    EAPClientStatus		client_status;
    EAPOLSocketSourceRef	source = (EAPOLSocketSourceRef)arg;
    SupplicantState		supplicant_state;

    eapolclient_log(kLogFlagBasic, "4-way handshake complete\n");
    supplicant_state = Supplicant_get_state(source->sock->supp, &client_status);
    switch (supplicant_state) {
    case kSupplicantStateAuthenticated:
	if (source->need_force_renew) {
	    EAPOLSocketSourceForceRenew(source);
	}
	break;
    case kSupplicantStateAuthenticating:
	/* if we're still authenticating, we likely lost the EAP Success */
	Supplicant_simulate_success(source->sock->supp);
	break;
    }
    EAPOLSocketSourceReleaseHandshakeNotification(source);
    return;
}

static void
EAPOLSocketSourceScheduleHandshakeNotification(EAPOLSocketSourceRef source)
{
    EAPOLSocketSourceUnscheduleHandshakeNotification(source);
    source->interest
	= InterestNotificationCreate(source->if_name, 
				     EAPOLSocketSourceHandshakeComplete,
				     source);
    if (source->interest != NULL) {
	if (source->authenticated == FALSE) {
	    /* only need force renew the first time after the link goes up */
	    source->need_force_renew = TRUE;
	}
	else {
	    source->need_force_renew = FALSE;
	}
	source->authenticated = TRUE;
	eapolclient_log(kLogFlagBasic,
			"4-way handshake notification scheduled\n");
    }
    return;
}

static void
EAPOLSocketSourceUnscheduleHandshakeNotification(EAPOLSocketSourceRef source)
{
    if (EAPOLSocketSourceReleaseHandshakeNotification(source)) {
	eapolclient_log(kLogFlagBasic,
			"4-way handshake notification unscheduled\n");
    }
    return;
}