1/*
2 * Copyright (c) 2010-2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 *  eapol_monitor.c
26 *  - EAPOLMonitor user event agent that initiates 802.1X authentication
27 *    sessions
28 */
29
30/*
31 * Modification History
32 *
33 * September 15, 2010	Dieter Siegmund
34 * - created
35 */
36
37#include <stdlib.h>
38#include <unistd.h>
39#include <stdio.h>
40#include <fcntl.h>
41#include <sys/param.h>
42#include <paths.h>
43#include <pthread.h>
44#include <mach/mach.h>
45#include <sys/socket.h>
46#include <sys/ioctl.h>
47#include <net/if.h>
48#include <net/if_media.h>
49#include <sys/sockio.h>
50#include <SystemConfiguration/SystemConfiguration.h>
51#include <SystemConfiguration/SCPrivate.h>
52#include <SystemConfiguration/SCDPlugin.h>
53#include <CoreFoundation/CFUserNotification.h>
54#include <CoreFoundation/CFData.h>
55#include <CoreFoundation/CFDictionary.h>
56#include <CoreFoundation/CFRunLoop.h>
57#include <notify.h>
58#include "myCFUtil.h"
59#include "EAPOLClientConfiguration.h"
60#include "EAPOLClientConfigurationPrivate.h"
61#include "EAPOLControlTypes.h"
62#include "EAPOLControl.h"
63#include "EAPOLControlPrivate.h"
64#include "EAPOLControlTypesPrivate.h"
65#include "SupplicantTypes.h"
66#include "EAPClientTypes.h"
67#include "EAPLog.h"
68#include "EAPOLControlPrefs.h"
69
70#include "UserEventAgentInterface.h"
71#include <stdio.h>
72#include <stdlib.h>
73
74#define MY_BUNDLE_ID	CFSTR("com.apple.UserEventAgent.EAPOLMonitor")
75
76typedef enum {
77    kMonitorStateIdle,
78    kMonitorStatePrompting,
79    kMonitorStateStarted,
80    kMonitorStateIgnorePermanently
81} MonitorState;
82
83struct MonitoredInterface_s;
84typedef struct MonitoredInterface_s * MonitoredInterfaceRef;
85
86#define LIST_HEAD_MonitoredInterfaceHead LIST_HEAD(MonitoredInterfaceHead, MonitoredInterface_s)
87static LIST_HEAD_MonitoredInterfaceHead S_MonitoredInterfaceHead;
88static struct MonitoredInterfaceHead * 	S_MonitoredInterfaceHead_p = &S_MonitoredInterfaceHead;
89
90#define LIST_ENTRY_MonitoredInterface	LIST_ENTRY(MonitoredInterface_s)
91
92struct MonitoredInterface_s {
93    LIST_ENTRY_MonitoredInterface	entries;
94
95    char				if_name[IFNAMSIZ];
96    CFStringRef				if_name_cf;
97    MonitorState			state;
98    CFAbsoluteTime			start_time;
99    bool				ignore_until_link_status_changes;
100    bool				link_active_when_started;
101    bool				require_fresh_detection;
102    CFUserNotificationRef 		cfun;
103    CFRunLoopSourceRef			rls;
104
105    /* EAPOLClientConfigurationRef	cfg; */ /* XXX might need in future */
106    CFArrayRef				profiles;
107};
108
109typedef struct {
110    UserEventAgentInterfaceStruct *	_UserEventAgentInterface;
111    CFUUIDRef				_factoryID;
112    UInt32 				_refCount;
113    SCDynamicStoreRef			store;
114    CFRunLoopSourceRef			rls;
115
116    /* notifications for user settings changing */
117    struct {
118	CFMachPortRef			mp;
119	int				token;
120    } settings_change;
121} MyType;
122
123static bool				S_auto_connect;
124
125/**
126 ** Utility routines
127 **/
128
129static bool
130S_on_console(SCDynamicStoreRef store)
131{
132    bool			on_console;
133    uid_t			uid;
134    CFStringRef 		user;
135
136    user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL);
137    if (user == NULL || uid != getuid()) {
138	on_console = FALSE;
139    }
140    else {
141	on_console = TRUE;
142    }
143    my_CFRelease(&user);
144    return (on_console);
145}
146
147static CFBundleRef
148S_get_bundle()
149{
150    CFBundleRef		bundle;
151
152    bundle = CFBundleGetBundleWithIdentifier(MY_BUNDLE_ID);
153    if (bundle == NULL) {
154	bundle = CFBundleGetMainBundle();
155    }
156    return (bundle);
157}
158
159static CFStringRef
160copy_localized_if_name(CFStringRef if_name_cf)
161{
162    int			count;
163    int			i;
164    CFArrayRef		if_list;
165    CFStringRef		loc_name = NULL;
166
167    if_list = SCNetworkInterfaceCopyAll();
168    if (if_list == NULL) {
169	goto done;
170    }
171    count = CFArrayGetCount(if_list);
172    for (i = 0; i < count; i++) {
173	CFStringRef		this_name;
174	SCNetworkInterfaceRef	sc_if;
175
176	sc_if = (SCNetworkInterfaceRef)CFArrayGetValueAtIndex(if_list, i);
177	this_name = SCNetworkInterfaceGetBSDName(sc_if);
178	if (this_name != NULL
179	    && CFEqual(this_name, if_name_cf)) {
180	    loc_name = SCNetworkInterfaceGetLocalizedDisplayName(sc_if);
181	    if (loc_name != NULL) {
182		CFRetain(loc_name);
183	    }
184	    break;
185	}
186    }
187
188 done:
189    if (if_list != NULL) {
190	CFRelease(if_list);
191    }
192    return (loc_name);
193}
194
195static bool
196is_link_active(const char * name)
197{
198    bool		active = FALSE;
199    struct ifmediareq	ifm;
200    int			s;
201
202    s = socket(AF_INET, SOCK_DGRAM, 0);
203    if (s < 0) {
204	perror("socket");
205	goto done;
206    }
207    bzero(&ifm, sizeof(ifm));
208    strlcpy(ifm.ifm_name, name, sizeof(ifm.ifm_name));
209    if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifm) < 0) {
210	goto done;
211    }
212    if ((ifm.ifm_status & IFM_AVALID) == 0
213	|| (ifm.ifm_status & IFM_ACTIVE) != 0) {
214	active = TRUE;
215    }
216
217 done:
218    if (s >= 0) {
219	close(s);
220    }
221    return (active);
222}
223
224static int
225get_ifm_type(const char * name)
226{
227    struct ifmediareq	ifm;
228    int			s;
229    int			ifm_type = 0;
230
231    s = socket(AF_INET, SOCK_DGRAM, 0);
232    if (s < 0) {
233	perror("socket");
234	goto done;
235    }
236    bzero(&ifm, sizeof(ifm));
237    strlcpy(ifm.ifm_name, name, sizeof(ifm.ifm_name));
238    if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifm) < 0) {
239	goto done;
240    }
241    ifm_type = IFM_TYPE(ifm.ifm_current);
242 done:
243    if (s >= 0) {
244	close(s);
245    }
246    return (ifm_type);
247}
248
249static uint32_t
250S_get_plist_uint32(CFDictionaryRef plist, CFStringRef key, uint32_t def)
251{
252    CFNumberRef 	n;
253    uint32_t		ret = def;
254
255    n = isA_CFNumber(CFDictionaryGetValue(plist, key));
256    if (n) {
257	if (CFNumberGetValue(n, kCFNumberSInt32Type, &ret) == FALSE) {
258	    ret = def;
259	}
260    }
261    return (ret);
262}
263
264static __inline__ CFOptionFlags
265S_CFUserNotificationResponse(CFOptionFlags flags)
266{
267    return (flags & 0x3);
268}
269
270#define kNetworkPrefPath "/System/Library/PreferencePanes/Network.prefPane"
271
272static CFURLRef
273copy_icon_url(CFStringRef icon)
274{
275    CFBundleRef		np_bundle;
276    CFURLRef		np_url;
277    CFURLRef		url = NULL;
278
279    np_url = CFURLCreateWithFileSystemPath(NULL,
280					   CFSTR(kNetworkPrefPath),
281					   kCFURLPOSIXPathStyle, FALSE);
282    if (np_url != NULL) {
283	np_bundle = CFBundleCreate(NULL, np_url);
284	if (np_bundle != NULL) {
285	    url = CFBundleCopyResourceURL(np_bundle, icon,
286					  CFSTR("icns"), NULL);
287	    if (url == NULL) {
288		url = CFBundleCopyResourceURL(np_bundle, icon,
289					      CFSTR("tiff"), NULL);
290	    }
291	    CFRelease(np_bundle);
292	}
293	CFRelease(np_url);
294    }
295    return (url);
296}
297
298/**
299 ** PromptStartContext
300 **/
301typedef struct {
302    EAPOLClientConfigurationRef		cfg;
303    CFArrayRef				profiles;
304    CFArrayRef				profile_names;
305} PromptStartContext, * PromptStartContextRef;
306
307static void
308PromptStartContextRelease(PromptStartContextRef context)
309{
310    my_CFRelease(&context->cfg);
311    my_CFRelease(&context->profiles);
312    my_CFRelease(&context->profile_names);
313    return;
314}
315
316static void
317PromptStartContextInit(PromptStartContextRef context)
318{
319    context->cfg = EAPOLClientConfigurationCreate(NULL);
320    context->profiles = EAPOLClientConfigurationCopyProfiles(context->cfg);
321    context->profile_names = NULL;
322    if (context->profiles != NULL) {
323	CFBundleRef		bundle;
324	int			count;
325	int			i;
326	CFRange			r;
327	CFStringRef		use_defaults;
328	CFMutableArrayRef	profile_names;
329
330	count = CFArrayGetCount(context->profiles);
331	profile_names = CFArrayCreateMutable(NULL, count + 1,
332					     &kCFTypeArrayCallBacks);
333	bundle = S_get_bundle();
334	use_defaults
335	    = CFBundleCopyLocalizedString(bundle,
336					  CFSTR("DEFAULT_CONFIGURATION"),
337					  NULL, NULL);
338	CFArrayAppendValue(profile_names, use_defaults);
339	CFRelease(use_defaults);
340	r.location = 0;
341	r.length = 1;
342	for (i = 0; i < count; i++) {
343	    int				instance;
344	    CFStringRef			name;
345	    CFStringRef			new_name;
346	    EAPOLClientProfileRef	profile;
347
348	    profile = (EAPOLClientProfileRef)
349		CFArrayGetValueAtIndex(context->profiles, i);
350	    name = EAPOLClientProfileGetUserDefinedName(profile);
351	    if (name == NULL) {
352		name = EAPOLClientProfileGetID(profile);
353	    }
354	    for (instance = 2, new_name = CFRetain(name);
355		 CFArrayContainsValue(profile_names, r, new_name); instance++) {
356		CFRelease(new_name);
357		new_name
358		    = CFStringCreateWithFormat(NULL, NULL,
359					       CFSTR("%@ (%d)"), name,
360					       instance);
361
362	    }
363	    CFArrayAppendValue(profile_names, new_name);
364	    CFRelease(new_name);
365	    r.length++;
366	}
367	context->profile_names = profile_names;
368    }
369    return;
370
371}
372
373/**
374 ** MonitoredInterfaceRef
375 **/
376
377static MonitoredInterfaceRef
378MonitoredInterfaceCreate(CFStringRef ifname)
379{
380    MonitoredInterfaceRef	mon;
381
382    mon = (MonitoredInterfaceRef)malloc(sizeof(*mon));
383    bzero(mon, sizeof(*mon));
384    mon->if_name_cf = CFRetain(ifname);
385    CFStringGetCString(ifname, mon->if_name, sizeof(mon->if_name),
386		       kCFStringEncodingASCII);
387    LIST_INSERT_HEAD(S_MonitoredInterfaceHead_p, mon, entries);
388    return (mon);
389}
390
391static void
392MonitoredInterfaceReleasePrompt(MonitoredInterfaceRef mon)
393{
394    if (mon->cfun != NULL) {
395	(void)CFUserNotificationCancel(mon->cfun);
396	my_CFRelease(&mon->cfun);
397    }
398    if (mon->rls != NULL) {
399	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mon->rls,
400			      kCFRunLoopDefaultMode);
401	my_CFRelease(&mon->rls);
402    }
403    my_CFRelease(&mon->profiles);
404    return;
405}
406
407static void
408MonitoredInterfaceRelease(MonitoredInterfaceRef * mon_p)
409{
410    MonitoredInterfaceRef 	mon;
411
412    mon = *mon_p;
413    if (mon == NULL) {
414	return;
415    }
416    *mon_p = NULL;
417    LIST_REMOVE(mon, entries);
418    my_CFRelease(&mon->if_name_cf);
419    MonitoredInterfaceReleasePrompt(mon);
420    free(mon);
421    return;
422}
423
424static MonitoredInterfaceRef
425MonitoredInterfaceLookupByName(CFStringRef if_name_cf)
426{
427    MonitoredInterfaceRef	scan;
428
429    LIST_FOREACH(scan, S_MonitoredInterfaceHead_p, entries) {
430	if (CFEqual(scan->if_name_cf, if_name_cf)) {
431	    return (scan);
432	}
433    }
434    return (NULL);
435}
436
437static MonitoredInterfaceRef
438MonitoredInterfaceLookupByCFUN(CFUserNotificationRef cfun)
439{
440    MonitoredInterfaceRef	scan;
441
442    LIST_FOREACH(scan, S_MonitoredInterfaceHead_p, entries) {
443	if (scan->cfun == cfun) {
444	    return (scan);
445	}
446    }
447    return (NULL);
448}
449
450static const CFStringRef	S_manager_name = CFSTR("com.apple.UserEventAgent.EAPOLMonitor");
451
452static void
453MonitoredInterfaceStartAuthentication(MonitoredInterfaceRef mon,
454				      EAPOLClientItemIDRef itemID)
455{
456    CFStringRef			key;
457    CFDictionaryRef		options;
458    EAPOLClientProfileRef	profile = EAPOLClientItemIDGetProfile(itemID);
459    int				status;
460
461    key = kEAPOLControlStartOptionManagerName;
462    options = CFDictionaryCreate(NULL,
463				 (const void * *)&key,
464				 (const void * *)&S_manager_name,
465				 1,
466				 &kCFTypeDictionaryKeyCallBacks,
467				 &kCFTypeDictionaryValueCallBacks);
468    EAPLOG(LOG_NOTICE,
469	   "EAPOLMonitor: %s starting %s",
470	   mon->if_name,
471	   (profile == NULL) ? "(Default Settings)" : "(Profile)");
472    status = EAPOLControlStartWithOptions(mon->if_name, itemID, options);
473    CFRelease(options);
474    if (status == 0) {
475	mon->state = kMonitorStateStarted;
476	mon->start_time = CFAbsoluteTimeGetCurrent();
477	mon->link_active_when_started = is_link_active(mon->if_name);
478    }
479    else {
480	/* XXX how to handle this ? */
481	EAPLOG(LOG_NOTICE,
482	       "EAPOLMonitor: %s start failed %d %s",
483	       mon->if_name,
484	       (profile == NULL) ? "(Default Settings)" : "(Profile)");
485    }
486    return;
487}
488
489static void
490MonitoredInterfaceStartAuthenticationWithProfile(MonitoredInterfaceRef mon,
491						 EAPOLClientProfileRef profile)
492{
493    EAPOLClientItemIDRef 	itemID;
494
495    if (profile == NULL) {
496    	itemID = EAPOLClientItemIDCreateDefault();
497    }
498    else {
499	itemID = EAPOLClientItemIDCreateWithProfile(profile);
500    }
501    MonitoredInterfaceStartAuthentication(mon, itemID);
502    CFRelease(itemID);
503    return;
504}
505
506static void
507MonitoredInterfaceStopAuthentication(MonitoredInterfaceRef mon)
508{
509    int			status;
510
511    EAPLOG(LOG_NOTICE, "EAPOLMonitor: %s stopping", mon->if_name);
512    mon->state = kMonitorStateIdle;
513    status = EAPOLControlStop(mon->if_name);
514    if (status != 0) {
515	EAPLOG(LOG_NOTICE,
516	       "EAPOLMonitor: %s stop failed with %d",
517	       mon->if_name, status);
518    }
519    return;
520}
521
522static void
523MonitoredInterfaceStopIfNecessary(MonitoredInterfaceRef mon)
524{
525    EAPOLControlState 		control_state = kEAPOLControlStateIdle;
526    CFDictionaryRef		dict;
527    int				error;
528
529    error = EAPOLControlCopyStateAndStatus(mon->if_name, &control_state, &dict);
530    if (error != 0) {
531	EAPLOG(LOG_DEBUG,
532	       "EAPOLMonitor: EAPOLControlCopyStateAndStatus(%s) returned %d",
533	       mon->if_name, error);
534	return;
535    }
536    switch (control_state) {
537    case kEAPOLControlStateIdle:
538    case kEAPOLControlStateStopping:
539	break;
540    case kEAPOLControlStateStarting:
541	/* XXX we really need to wait for the state to be Running */
542    case kEAPOLControlStateRunning:
543	if (dict != NULL) {
544	    CFStringRef		manager_name;
545
546	    manager_name = CFDictionaryGetValue(dict,
547						kEAPOLControlManagerName);
548	    if (manager_name != NULL
549		&& CFEqual(manager_name, S_manager_name)) {
550		/* it's ours, so stop it */
551		MonitoredInterfaceStopAuthentication(mon);
552	    }
553	}
554	break;
555    }
556    my_CFRelease(&dict);
557    return;
558}
559
560static void
561MonitoredInterfacePromptComplete(CFUserNotificationRef cfun,
562				 CFOptionFlags response_flags)
563{
564    MonitoredInterfaceRef 	mon;
565    EAPOLClientProfileRef	profile;
566    int				which_profile = -1;
567
568    mon = MonitoredInterfaceLookupByCFUN(cfun);
569    if (mon == NULL) {
570	EAPLOG(LOG_ERR, "EAPOLMonitor: can't find user notification");
571	return;
572    }
573    switch (S_CFUserNotificationResponse(response_flags)) {
574    case kCFUserNotificationDefaultResponse:
575	/* start the authentication */
576	which_profile = (response_flags
577			 & CFUserNotificationPopUpSelection(-1)) >> 24;
578	if (which_profile == 0) {
579	    profile = NULL;
580	}
581	else {
582	    profile = (EAPOLClientProfileRef)
583		CFArrayGetValueAtIndex(mon->profiles, which_profile - 1);
584	}
585	MonitoredInterfaceStartAuthenticationWithProfile(mon, profile);
586	break;
587    default:
588	mon->state = kMonitorStateIdle;
589	mon->ignore_until_link_status_changes = TRUE;
590	break;
591    }
592    MonitoredInterfaceReleasePrompt(mon);
593    return;
594}
595
596static void
597MonitoredInterfaceDisplayProfilePrompt(MonitoredInterfaceRef mon,
598				       PromptStartContextRef context)
599{
600    CFBundleRef			bundle;
601    CFUserNotificationRef 	cfun = NULL;
602    CFMutableDictionaryRef	dict = NULL;
603    SInt32			error = 0;
604    CFStringRef			if_name_loc;
605    CFStringRef			notif_header;
606    CFStringRef			notif_header_format;
607    CFRunLoopSourceRef		rls = NULL;
608    CFURLRef			url = NULL;
609
610    MonitoredInterfaceReleasePrompt(mon);
611    bundle = S_get_bundle();
612    if (bundle == NULL) {
613	EAPLOG(LOG_NOTICE, "EAPOLMonitor: can't find bundle");
614	return;
615    }
616    url = CFBundleCopyBundleURL(bundle);
617    if (url == NULL) {
618	EAPLOG(LOG_ERR, "EAPOLMonitor: can't find bundle URL");
619	goto done;
620    }
621    dict = CFDictionaryCreateMutable(NULL, 0,
622				     &kCFTypeDictionaryKeyCallBacks,
623				     &kCFTypeDictionaryValueCallBacks);
624
625    CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey,
626			 CFSTR("CANCEL"));
627    CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey,
628			 CFSTR("CONNECT"));
629    notif_header_format
630	= CFBundleCopyLocalizedString(bundle,
631				      CFSTR("NOTIFICATION_HEADER"),
632				      CFSTR("NOTIFICATION_HEADER %@"),
633				      NULL);
634    if_name_loc = copy_localized_if_name(mon->if_name_cf);
635    notif_header = CFStringCreateWithFormat(NULL, NULL,
636					    notif_header_format,
637					    (if_name_loc != NULL)
638					    ? if_name_loc
639					    : mon->if_name_cf);
640    CFRelease(notif_header_format);
641    my_CFRelease(&if_name_loc);
642    CFDictionarySetValue(dict, kCFUserNotificationAlertHeaderKey,
643			 notif_header);
644    CFRelease(notif_header);
645    CFDictionarySetValue(dict, kCFUserNotificationPopUpTitlesKey,
646			 context->profile_names);
647    CFDictionarySetValue(dict, kCFUserNotificationLocalizationURLKey,
648			 url);
649    CFRelease(url);
650
651    /* icon */
652    url = copy_icon_url(CFSTR("Network"));
653    if (url != NULL) {
654	CFDictionarySetValue(dict, kCFUserNotificationIconURLKey,
655			     url);
656	CFRelease(url);
657    }
658    cfun = CFUserNotificationCreate(NULL, 0, 0, &error, dict);
659    if (cfun == NULL) {
660	EAPLOG(LOG_ERR, "CFUserNotificationCreate() failed, %d", error);
661	goto done;
662    }
663    rls = CFUserNotificationCreateRunLoopSource(NULL, cfun,
664						MonitoredInterfacePromptComplete,
665						0);
666    if (rls == NULL) {
667	EAPLOG(LOG_ERR, "CFUserNotificationCreateRunLoopSource() failed");
668	my_CFRelease(&cfun);
669    }
670    else {
671	CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
672	mon->rls = rls;
673	mon->cfun = cfun;
674	mon->profiles = CFRetain(context->profiles);
675	mon->state = kMonitorStatePrompting;
676	EAPLOG(LOG_DEBUG, "EAPOLMonitor: %s prompting user",
677	       mon->if_name);
678    }
679
680 done:
681    my_CFRelease(&dict);
682    return;
683}
684
685
686static void
687MonitoredInterfaceProcessStatus(MonitoredInterfaceRef mon,
688				CFDictionaryRef status_dict)
689{
690    CFDataRef			authenticator;
691    EAPClientStatus		client_status;
692    CFStringRef			manager_name;
693    bool			our_manager;
694    CFStringRef			profileID;
695    SupplicantState		supp_state;
696
697    supp_state = S_get_plist_uint32(status_dict, kEAPOLControlSupplicantState,
698				    -1);
699    client_status = S_get_plist_uint32(status_dict, kEAPOLControlClientStatus,
700				       kEAPClientStatusOK);
701    /* ignore this if it's not ours */
702    if (S_get_plist_uint32(status_dict, kEAPOLControlUID, -1) != getuid()) {
703	return;
704    }
705    EAPLOG(LOG_DEBUG, "EAPOLMonitor: %s is %s", mon->if_name,
706	   SupplicantStateString(supp_state));
707    manager_name = CFDictionaryGetValue(status_dict,
708					kEAPOLControlManagerName);
709    authenticator = CFDictionaryGetValue(status_dict,
710					 kEAPOLControlAuthenticatorMACAddress);
711    profileID = CFDictionaryGetValue(status_dict,
712				     kEAPOLControlUniqueIdentifier);
713    our_manager = (manager_name != NULL
714		   && CFEqual(manager_name, S_manager_name));
715    switch (supp_state) {
716    case kSupplicantStateDisconnected:
717    case kSupplicantStateConnecting:
718    case kSupplicantStateAcquired:
719    case kSupplicantStateAuthenticating:
720    case kSupplicantStateLogoff:
721	break;
722    case kSupplicantStateAuthenticated: {
723	EAPOLClientItemIDRef	itemID = NULL;
724
725	if (profileID != NULL) {
726	    EAPOLClientConfigurationRef	cfg;
727	    EAPOLClientProfileRef	profile;
728
729	    cfg = EAPOLClientConfigurationCreate(NULL);
730	    profile = EAPOLClientConfigurationGetProfileWithID(cfg, profileID);
731	    if (profile != NULL) {
732		itemID = EAPOLClientItemIDCreateWithProfile(profile);
733	    }
734	}
735	else {
736	    itemID = EAPOLClientItemIDCreateDefault();
737	}
738	if (itemID != NULL && authenticator != NULL) {
739	    EAPOLControlSetItemIDForAuthenticator(authenticator, itemID);
740	    CFRelease(itemID);
741	}
742	break;
743    }
744    case kSupplicantStateHeld:
745	if (our_manager) {
746	    switch (client_status) {
747	    case kEAPClientStatusOK:
748	    case kEAPClientStatusFailed:
749		break;
750	    default:
751		/* clear the binding, since we ran into trouble */
752		if (authenticator != NULL) {
753		    EAPOLControlSetItemIDForAuthenticator(authenticator, NULL);
754		}
755		break;
756	    }
757	}
758	break;
759    case kSupplicantStateInactive:
760	if (our_manager) {
761	    bool		stop_it = FALSE;
762
763	    if (mon->state != kMonitorStateStarted) {
764		stop_it = TRUE;
765	    }
766	    else if (mon->link_active_when_started == FALSE) {
767		CFAbsoluteTime	now;
768
769		/*
770		 * <rdar://problem/8644279>
771		 * If the link was inactive when we were started, ignore
772		 * eapolclient entering the Inactive state if it's been
773		 * less than the INACTIVE_IGNORE_INTERVAL (0.5 seconds).
774		 * It is a larger value than we need since the USB Ethernet
775		 * driver typically has a delay of just over 0.02 seconds,
776		 * but is short enough to avoid doing the wrong thing in
777		 * other cases.
778		 */
779		now = CFAbsoluteTimeGetCurrent();
780#define INACTIVE_IGNORE_INTERVAL	0.5
781		if ((now - mon->start_time) > INACTIVE_IGNORE_INTERVAL) {
782		    stop_it = TRUE;
783		}
784		else {
785		    /* if we *just* started the authentication, ignore this */
786		    EAPLOG(LOG_DEBUG, "EAPOLMonitor: %s delay was %g seconds",
787			   mon->if_name, now - mon->start_time);
788		}
789	    }
790	    else {
791		stop_it = TRUE;
792	    }
793	    if (stop_it) {
794		MonitoredInterfaceStopAuthentication(mon);
795	    }
796	}
797	break;
798    case kSupplicantStateNoAuthenticator:
799	break;
800    }
801    return;
802}
803
804static void
805MonitoredInterfaceCheckStatus(MonitoredInterfaceRef mon)
806{
807    EAPOLControlState 		control_state = kEAPOLControlStateIdle;
808    CFDictionaryRef		dict;
809    int				error;
810
811    MonitoredInterfaceReleasePrompt(mon);
812    error = EAPOLControlCopyStateAndStatus(mon->if_name, &control_state, &dict);
813    if (error != 0) {
814	EAPLOG(LOG_DEBUG,
815	       "EAPOLMonitor: EAPOLControlCopyStateAndStatus(%s) returned %d",
816	       mon->if_name, error);
817	return;
818    }
819    switch (control_state) {
820    case kEAPOLControlStateIdle:
821    case kEAPOLControlStateStopping:
822	if (mon->state == kMonitorStateStarted) {
823	    /* user is controlling things */
824	    mon->ignore_until_link_status_changes = TRUE;
825	}
826	mon->state = kMonitorStateIdle;
827	if (EAPOLControlDidUserCancel(mon->if_name)) {
828	    mon->ignore_until_link_status_changes = TRUE;
829	}
830	break;
831    case kEAPOLControlStateStarting:
832	/* not quite ready yet, we'll wait for Running */
833	break;
834    case kEAPOLControlStateRunning:
835	if (mon->state != kMonitorStateStarted) {
836	    /* user is controlling things */
837	    mon->state = kMonitorStateIdle;
838	    mon->ignore_until_link_status_changes = TRUE;
839	}
840	if (dict != NULL) {
841	    MonitoredInterfaceProcessStatus(mon, dict);
842	}
843	break;
844    }
845    my_CFRelease(&dict);
846    return;
847}
848
849static void
850MonitoredInterfaceLinkStatusChanged(MonitoredInterfaceRef mon)
851{
852    bool		link_active;
853
854    link_active = is_link_active(mon->if_name);
855    if (link_active == FALSE) {
856	if (mon->state == kMonitorStatePrompting) {
857	    MonitoredInterfaceReleasePrompt(mon);
858	    mon->state = kMonitorStateIdle;
859	}
860    }
861    mon->ignore_until_link_status_changes = FALSE;
862    return;
863}
864
865static void
866prompt_or_start(const void * key, const void * value, void * arg)
867{
868    uint32_t			age_seconds = 0;
869    CFDataRef			authenticator = NULL;
870    CFDictionaryRef		dict;
871    EAPOLClientItemIDRef	itemID = NULL;
872    PromptStartContextRef	context_p = (PromptStartContextRef)arg;
873    CFStringRef			if_name_cf = (CFStringRef)key;
874    MonitoredInterfaceRef	mon;
875
876    mon = MonitoredInterfaceLookupByName(if_name_cf);
877    if (mon == NULL) {
878	mon = MonitoredInterfaceCreate(if_name_cf);
879    }
880    EAPLOG(LOG_DEBUG, "EAPOLMonitor: %s auto-detected", mon->if_name);
881    if (mon->ignore_until_link_status_changes) {
882	EAPLOG(LOG_DEBUG, "EAPOLMonitor: %s ignoring until link status changes",
883	       mon->if_name);
884	/* wait until the link status changes before dealing with this */
885	return;
886    }
887    if (mon->state != kMonitorStateIdle) {
888	return;
889    }
890
891    /* check for an existing binding */
892    dict = isA_CFDictionary(value);
893    if (dict != NULL) {
894	authenticator
895	    = CFDictionaryGetValue(dict,
896				   kEAPOLAutoDetectAuthenticatorMACAddress);
897	authenticator = isA_CFData(authenticator);
898    }
899    if (authenticator != NULL) {
900	itemID = EAPOLControlCopyItemIDForAuthenticator(authenticator);
901    }
902    if (itemID != NULL) {
903	MonitoredInterfaceStartAuthentication(mon, itemID);
904	CFRelease(itemID);
905    }
906    else if (context_p->profiles != NULL) {
907	MonitoredInterfaceDisplayProfilePrompt(mon, context_p);
908    }
909    else {
910	MonitoredInterfaceStartAuthenticationWithProfile(mon, NULL);
911    }
912    return;
913}
914
915static void
916process_auto_detect_info(CFDictionaryRef auto_detect_info)
917{
918    PromptStartContext		context;
919
920    EAPLOG(LOG_DEBUG, "EAPOLMonitor: processing auto-detect information");
921    PromptStartContextInit(&context);
922    CFDictionaryApplyFunction(auto_detect_info, prompt_or_start, &context);
923    PromptStartContextRelease(&context);
924    return;
925}
926
927static void
928process_interface_change_info(CFArrayRef if_status_changed)
929{
930    int		count;
931    int		i;
932
933    count = CFArrayGetCount(if_status_changed);
934    for (i = 0; i < count; i++) {
935	CFStringRef		if_name_cf;
936	MonitoredInterfaceRef	mon;
937
938	if_name_cf = CFArrayGetValueAtIndex(if_status_changed, i);
939	mon = MonitoredInterfaceLookupByName(if_name_cf);
940	if (mon != NULL) {
941	    if (mon->state == kMonitorStateIgnorePermanently) {
942		/* ignore non-ethernet interfaces */
943		continue;
944	    }
945	}
946	else {
947	    mon = MonitoredInterfaceCreate(if_name_cf);
948	    if (get_ifm_type(mon->if_name) != IFM_ETHER) {
949		mon->state = kMonitorStateIgnorePermanently;
950		continue;
951	    }
952	}
953	MonitoredInterfaceCheckStatus(mon);
954    }
955    return;
956}
957
958static void
959handle_changes(SCDynamicStoreRef store, CFArrayRef changes, void * info)
960{
961    bool		check_auto_detect = FALSE;
962    int			count;
963    int			i;
964    CFMutableArrayRef	if_status_changed = NULL;
965
966    count = CFArrayGetCount(changes);
967    for (i = 0; i < count; i++) {
968	CFStringRef	key = CFArrayGetValueAtIndex(changes, i);
969
970	if (CFEqual(key, kEAPOLControlAutoDetectInformationNotifyKey)) {
971	    if (S_auto_connect) {
972		check_auto_detect = TRUE;
973	    }
974	}
975	else if (CFStringHasSuffix(key, kSCEntNetLink)) {
976	    CFStringRef			if_name_cf;
977	    MonitoredInterfaceRef	mon;
978
979	    /* State:/Network/Interface/<ifname>/Link */
980	    if_name_cf = my_CFStringCopyComponent(key, CFSTR("/"), 3);
981	    if (if_name_cf == NULL) {
982		continue;
983	    }
984	    mon = MonitoredInterfaceLookupByName(if_name_cf);
985	    CFRelease(if_name_cf);
986	    if (mon != NULL) {
987		MonitoredInterfaceLinkStatusChanged(mon);
988	    }
989	}
990	else {
991	    CFStringRef		this_if;
992
993	    this_if = EAPOLControlKeyCopyInterface(key);
994	    if (this_if != NULL) {
995		if (if_status_changed == NULL) {
996		    if_status_changed
997			= CFArrayCreateMutable(NULL, count,
998					       &kCFTypeArrayCallBacks);
999		}
1000		CFArrayAppendValue(if_status_changed, this_if);
1001		CFRelease(this_if);
1002	    }
1003	}
1004    }
1005
1006    /* process EAPOL state changes */
1007    if (if_status_changed != NULL) {
1008	process_interface_change_info(if_status_changed);
1009    }
1010
1011    /* process interfaces that we've auto-detected 802.1X */
1012    if (check_auto_detect && S_on_console(store)) {
1013	CFDictionaryRef    	auto_detect_info = NULL;
1014
1015	(void)EAPOLControlCopyAutoDetectInformation(&auto_detect_info);
1016	if (auto_detect_info != NULL) {
1017	    process_auto_detect_info(auto_detect_info);
1018	}
1019	my_CFRelease(&auto_detect_info);
1020    }
1021    my_CFRelease(&if_status_changed);
1022    return;
1023}
1024
1025static void
1026do_cleanup(SCDynamicStoreRef store, void * info)
1027{
1028    MonitoredInterfaceRef	mon;
1029
1030    /* configd went away, throw away all of our state */
1031    EAPLOG(LOG_DEBUG, "EAPOLMonitor: cleaning up state");
1032    while ((mon = LIST_FIRST(S_MonitoredInterfaceHead_p)) != NULL) {
1033	MonitoredInterfaceRelease(&mon);
1034    }
1035    return;
1036}
1037
1038static void
1039check_settings(SCDynamicStoreRef store)
1040{
1041    if (S_auto_connect) {
1042	/* start auto-connect */
1043	CFDictionaryRef		auto_detect_info;
1044
1045	if (S_on_console(store)) {
1046	    EAPLOG(LOG_DEBUG, "EAPOLMonitor: auto-connect enabled");
1047	    (void)EAPOLControlCopyAutoDetectInformation(&auto_detect_info);
1048	    if (auto_detect_info != NULL) {
1049		process_auto_detect_info(auto_detect_info);
1050		CFRelease(auto_detect_info);
1051	    }
1052	}
1053    }
1054    else {
1055	MonitoredInterfaceRef	scan;
1056
1057	/* stop auto-connect */
1058	EAPLOG(LOG_NOTICE, "EAPOLMonitor: auto-connect disabled");
1059	LIST_FOREACH(scan, S_MonitoredInterfaceHead_p, entries) {
1060	    MonitoredInterfaceStopIfNecessary(scan);
1061	    MonitoredInterfaceReleasePrompt(scan);
1062	    scan->state = kMonitorStateIdle;
1063	    scan->ignore_until_link_status_changes = FALSE;
1064	}
1065
1066    }
1067    return;
1068}
1069
1070static void
1071settings_changed(CFMachPortRef port, void * msg, CFIndex size, void * info)
1072{
1073    bool		auto_connect;
1074    MyType *		me = (MyType *)info;
1075
1076    EAPLOG(LOG_DEBUG, "EAPOLMonitor: settings changed");
1077    auto_connect = EAPOLControlIsUserAutoConnectEnabled();
1078    if (auto_connect != S_auto_connect) {
1079	S_auto_connect = auto_connect;
1080	check_settings(me->store);
1081    }
1082    return;
1083}
1084
1085static void
1086check_prefs(SCPreferencesRef prefs)
1087{
1088    uint32_t	log_flags = EAPOLControlPrefsGetLogFlags();
1089
1090    EAPLogSetVerbose(log_flags != 0);
1091    EAPOLControlPrefsSynchronize();
1092    return;
1093}
1094
1095static void
1096add_settings_notification(MyType * me)
1097{
1098    CFMachPortContext		context = {0, NULL, NULL, NULL, NULL};
1099    CFMachPortRef		notify_port_cf;
1100    mach_port_t			notify_port;
1101    int				notify_token;
1102    CFRunLoopSourceRef		rls;
1103    uint32_t			status;
1104
1105    notify_port = MACH_PORT_NULL;
1106    status = notify_register_mach_port(kEAPOLControlUserSettingsNotifyKey,
1107				       &notify_port, 0, &notify_token);
1108    if (status != NOTIFY_STATUS_OK) {
1109	EAPLOG(LOG_ERR, "EAPOLMonitor: notify_register_mach_port() failed");
1110	return;
1111    }
1112    context.info = me;
1113    notify_port_cf = CFMachPortCreateWithPort(NULL, notify_port,
1114					      settings_changed,
1115					      &context,
1116					      NULL);
1117    if (notify_port_cf == NULL) {
1118	EAPLOG(LOG_ERR, "EAPOLMonitor: CFMachPortCreateWithPort() failed");
1119	(void)notify_cancel(notify_token);
1120	return;
1121    }
1122    rls = CFMachPortCreateRunLoopSource(NULL, notify_port_cf, 0);
1123    if (rls == NULL) {
1124	EAPLOG(LOG_ERR, "EAPOLMonitor: CFMachPortCreateRunLoopSource() failed");
1125	CFRelease(notify_port_cf);
1126	(void)notify_cancel(notify_token);
1127	return;
1128    }
1129    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
1130    CFRelease(rls);
1131    me->settings_change.mp = notify_port_cf;
1132    me->settings_change.token = notify_token;
1133    check_prefs(EAPOLControlPrefsInit(CFRunLoopGetCurrent(), check_prefs));
1134    return;
1135}
1136
1137/**
1138 ** Plug-in functions
1139 **/
1140static void
1141myInstall(void * myInstance)
1142{
1143    SCDynamicStoreContext    	context = {0, myInstance, NULL, NULL, NULL};
1144    MyType *			me = (MyType *)myInstance;
1145    CFStringRef			key[2];
1146    CFArrayRef			keys;
1147    CFArrayRef			patterns;
1148    CFRunLoopSourceRef		rls;
1149    SCDynamicStoreRef		store;
1150
1151    store = SCDynamicStoreCreate(NULL, CFSTR("EAPOLMonitor"),
1152				 handle_changes, &context);
1153    if (store == NULL) {
1154	EAPLOG(LOG_ERR,
1155	       "EAPOLMonitor: SCDynamicStoreCreate failed, %s",
1156	       SCErrorString(SCError()));
1157	return;
1158    }
1159
1160    key[0] = kEAPOLControlAutoDetectInformationNotifyKey;
1161    keys = CFArrayCreate(NULL, (const void **)key, 1,
1162			 &kCFTypeArrayCallBacks);
1163
1164    key[0] = EAPOLControlAnyInterfaceKeyCreate();
1165    key[1] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
1166							   kSCDynamicStoreDomainState,
1167							   kSCCompAnyRegex,
1168							   kSCEntNetLink);
1169    patterns = CFArrayCreate(NULL, (const void **)key, 2,
1170			     &kCFTypeArrayCallBacks);
1171    CFRelease(key[0]);
1172    CFRelease(key[1]);
1173
1174    SCDynamicStoreSetNotificationKeys(store, keys, patterns);
1175    CFRelease(keys);
1176    CFRelease(patterns);
1177
1178    rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
1179    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
1180    me->store = store;
1181    me->rls = rls;
1182
1183    SCDynamicStoreSetDisconnectCallBack(store, do_cleanup);
1184
1185    add_settings_notification(me);
1186    S_auto_connect = EAPOLControlIsUserAutoConnectEnabled();
1187    check_settings(store);
1188    return;
1189}
1190
1191static void _deallocMyType(MyType *myInstance);
1192
1193static HRESULT myQueryInterface(void *myInstance, REFIID iid, LPVOID *ppv) {
1194    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
1195
1196    // Test the requested ID against the valid interfaces.
1197    if (CFEqual(interfaceID, kUserEventAgentInterfaceID)) {
1198	((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
1199	*ppv = myInstance;
1200	CFRelease(interfaceID);
1201	return S_OK;
1202    }
1203    if (CFEqual(interfaceID, IUnknownUUID)) {
1204	((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
1205	*ppv = myInstance;
1206	CFRelease(interfaceID);
1207	return S_OK;
1208    }
1209    //  Requested interface unknown, bail with error.
1210    *ppv = NULL;
1211    CFRelease(interfaceID);
1212    return E_NOINTERFACE;
1213}
1214
1215static ULONG myAddRef(void *myInstance) {
1216    ((MyType *) myInstance)->_refCount++;
1217    return ((MyType *) myInstance)->_refCount;
1218}
1219
1220static ULONG myRelease(void *myInstance) {
1221    ((MyType *) myInstance)->_refCount--;
1222    if (((MyType *) myInstance)->_refCount == 0) {
1223	_deallocMyType((MyType *) myInstance);
1224	return 0;
1225    } else return ((MyType *) myInstance)->_refCount;
1226}
1227
1228static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
1229    NULL,                    // Required padding for COM
1230    myQueryInterface,        // These three are the required COM functions
1231    myAddRef,
1232    myRelease,
1233    myInstall	             // Interface implementation
1234};
1235
1236static MyType *_allocMyType(CFUUIDRef factoryID) {
1237    MyType *newOne = (MyType *)malloc(sizeof(MyType));
1238
1239    bzero(newOne, sizeof(*newOne));
1240    newOne->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
1241
1242    if (factoryID) {
1243	newOne->_factoryID = (CFUUIDRef)CFRetain(factoryID);
1244	CFPlugInAddInstanceForFactory(factoryID);
1245	newOne->store = NULL;
1246    }
1247
1248    newOne->_refCount = 1;
1249    return newOne;
1250}
1251
1252static void
1253my_CFRunLoopSourceRelease(CFRunLoopSourceRef * rls_p)
1254{
1255    CFRunLoopSourceRef	rls = *rls_p;
1256
1257    if (rls != NULL) {
1258	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls,
1259			      kCFRunLoopDefaultMode);
1260	my_CFRelease(rls_p);
1261    }
1262    return;
1263}
1264
1265static void _deallocMyType(MyType * me)
1266{
1267    CFUUIDRef factoryID = me->_factoryID;
1268
1269    if (factoryID != NULL) {
1270	MonitoredInterfaceRef		mon;
1271
1272	CFPlugInRemoveInstanceForFactory(factoryID);
1273	my_CFRelease(&me->store);
1274	my_CFRunLoopSourceRelease(&me->rls);
1275
1276	if (me->settings_change.mp != NULL) {
1277	    CFMachPortInvalidate(me->settings_change.mp);
1278	    my_CFRelease(&me->settings_change.mp);
1279	    (void)notify_cancel(me->settings_change.token);
1280	}
1281
1282	/* release the list of MonitoredInterfaceRef's */
1283	while ((mon = LIST_FIRST(S_MonitoredInterfaceHead_p)) != NULL) {
1284	    MonitoredInterfaceStopIfNecessary(mon);
1285	    MonitoredInterfaceRelease(&mon);
1286	}
1287	CFRelease(factoryID);
1288    }
1289    free(me);
1290    return;
1291}
1292
1293void *UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
1294{
1295    if (CFEqual(typeID, kUserEventAgentTypeID)) {
1296	MyType *result = _allocMyType(kUserEventAgentFactoryID);
1297	return result;
1298    } else return NULL;
1299}
1300
1301#ifdef TEST_PLUGIN
1302int
1303main()
1304{
1305    MyType *newOne = (MyType *)malloc(sizeof(MyType));
1306
1307    bzero(newOne, sizeof(*newOne));
1308    myInstall(newOne);
1309    CFRunLoopRun();
1310    exit(0);
1311    return (0);
1312}
1313#endif /* TEST_PLUGIN */
1314