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