/* * Copyright (c) 2001-2009 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 * * November 8, 2001 Dieter Siegmund * - created */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ! TARGET_OS_EMBEDDED #include #include #include "Dialogue.h" #endif /* ! TARGET_OS_EMBEDDED */ #include "Supplicant.h" #include "Timer.h" #include "EAPOLSocket.h" #include "printdata.h" #include "mylog.h" #include "myCFUtil.h" #include "ClientControlInterface.h" #define START_PERIOD_SECS 30 #define AUTH_PERIOD_SECS 30 #define HELD_PERIOD_SECS 60 #define LINK_ACTIVE_PERIOD_SECS 4 #define LINK_INACTIVE_PERIOD_SECS 1 #define MAX_START 3 #define BAD_IDENTIFIER (-1) static int S_start_period_secs = START_PERIOD_SECS; static int S_auth_period_secs = AUTH_PERIOD_SECS; static int S_held_period_secs = HELD_PERIOD_SECS; static int S_link_active_period_secs = LINK_ACTIVE_PERIOD_SECS; static int S_link_inactive_period_secs = LINK_INACTIVE_PERIOD_SECS; static int S_max_start = MAX_START; struct eap_client { EAPClientModuleRef module; EAPClientPluginData plugin_data; CFArrayRef required_props; CFDictionaryRef published_props; EAPType last_type; const char * last_type_name; }; typedef struct { int * types; int count; int index; bool use_identity; } EAPAcceptTypes, * EAPAcceptTypesRef; struct Supplicant_s { SupplicantState state; TimerRef timer; EAPOLSocket * sock; uint32_t generation; CFDictionaryRef orig_config_dict; CFDictionaryRef config_dict; CFMutableDictionaryRef ui_config_dict; CFStringRef config_id; int previous_identifier; char * identity; int identity_length; char * username; int username_length; char * password; int password_length; bool one_time_password; bool ignore_password; EAPAcceptTypes eap_accept; CFArrayRef identity_attributes; #if ! TARGET_OS_EMBEDDED UserPasswordDialogueRef pw_prompt; TrustDialogueRef trust_prompt; #endif /* ! TARGET_OS_EMBEDDED */ int start_count; bool no_authenticator; struct eap_client eap; EAPOLSocketReceiveData last_rx_packet; EAPClientStatus last_status; EAPClientDomainSpecificError last_error; bool debug; bool pmk_set; bool no_ui; }; typedef enum { kSupplicantEventStart, kSupplicantEventData, kSupplicantEventTimeout, kSupplicantEventUserResponse, kSupplicantEventMoreDataAvailable, } SupplicantEvent; static CFDictionaryRef eapolclient_default_dict(void); static bool S_set_user_password(SupplicantRef supp); static void Supplicant_acquired(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_authenticated(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_authenticating(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_connecting(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_held(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_logoff(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_inactive(SupplicantRef supp, SupplicantEvent event, void * evdata); static void Supplicant_report_status(SupplicantRef supp); static void respond_to_notification(SupplicantRef supp, int identifier); /** ** Logging **/ static void eapolclient_log_config_dict(uint32_t flags, CFDictionaryRef d); /** ** EAP client module access convenience routines **/ static void eap_client_free_properties(SupplicantRef supp) { my_CFRelease((CFDictionaryRef *)&supp->eap.plugin_data.properties); } #if TARGET_OS_EMBEDDED static void eap_client_set_properties(SupplicantRef supp) { CFStringRef config_domain; CFStringRef config_ident; eap_client_free_properties(supp); config_domain = CFDictionaryGetValue(supp->config_dict, kEAPClientPropTLSTrustExceptionsDomain); config_ident = CFDictionaryGetValue(supp->config_dict, kEAPClientPropTLSTrustExceptionsID); if (config_domain != NULL && config_ident != NULL) { /* configuration already specifies the trust domain/ID */ *((CFDictionaryRef *)&supp->eap.plugin_data.properties) = CFRetain(supp->config_dict); } else { CFMutableDictionaryRef dict; CFStringRef domain; CFStringRef ident; CFStringRef if_name_cf = NULL; ident = NULL; if (EAPOLSocketIsWireless(supp->sock)) { ident = EAPOLSocketGetSSID(supp->sock); } if (ident != NULL) { domain = kEAPTLSTrustExceptionsDomainWirelessSSID; } else if (supp->config_id != NULL) { domain = kEAPTLSTrustExceptionsDomainProfileID; ident = supp->config_id; } else { if_name_cf = CFStringCreateWithCString(NULL, EAPOLSocketIfName(supp->sock, NULL), kCFStringEncodingASCII); domain = kEAPTLSTrustExceptionsDomainNetworkInterfaceName; ident = if_name_cf; } dict = CFDictionaryCreateMutableCopy(NULL, 0, supp->config_dict); CFDictionarySetValue(dict, kEAPClientPropTLSTrustExceptionsDomain, domain); CFDictionarySetValue(dict, kEAPClientPropTLSTrustExceptionsID, ident); *((CFDictionaryRef *)&supp->eap.plugin_data.properties) = dict; my_CFRelease(&if_name_cf); } return; } #else /* TARGET_OS_EMBEDDED */ static void eap_client_set_properties(SupplicantRef supp) { eap_client_free_properties(supp); *((CFDictionaryRef *)&supp->eap.plugin_data.properties) = CFRetain(supp->config_dict); return; } #endif /* TARGET_OS_EMBEDDED */ static void eap_client_free(SupplicantRef supp) { if (supp->eap.module != NULL) { EAPClientModulePluginFree(supp->eap.module, &supp->eap.plugin_data); supp->eap.module = NULL; eap_client_free_properties(supp); bzero(&supp->eap.plugin_data, sizeof(supp->eap.plugin_data)); } my_CFRelease(&supp->eap.required_props); my_CFRelease(&supp->eap.published_props); supp->eap.last_type = kEAPTypeInvalid; supp->eap.last_type_name = NULL; return; } static EAPType eap_client_type(SupplicantRef supp) { if (supp->eap.module == NULL) { return (kEAPTypeInvalid); } return (EAPClientModulePluginEAPType(supp->eap.module)); } static __inline__ void S_set_uint32(const uint32_t * v_p, uint32_t value) { *((uint32_t *)v_p) = value; return; } static bool eap_client_init(SupplicantRef supp, EAPType type) { EAPClientModule * module; supp->eap.last_type = kEAPTypeInvalid; supp->eap.last_type_name = NULL; if (supp->eap.module != NULL) { my_log(LOG_NOTICE, "eap_client_init: already initialized"); return (TRUE); } module = EAPClientModuleLookup(type); if (module == NULL) { return (FALSE); } my_CFRelease(&supp->eap.required_props); my_CFRelease(&supp->eap.published_props); bzero(&supp->eap.plugin_data, sizeof(supp->eap.plugin_data)); supp->eap.plugin_data.unique_id = EAPOLSocketIfName(supp->sock, (uint32_t *) &supp->eap.plugin_data.unique_id_length); S_set_uint32(&supp->eap.plugin_data.mtu, EAPOLSocketMTU(supp->sock) - sizeof(EAPOLPacket)); supp->eap.plugin_data.username = (uint8_t *)supp->username; S_set_uint32(&supp->eap.plugin_data.username_length, supp->username_length); supp->eap.plugin_data.password = (uint8_t *)supp->password; S_set_uint32(&supp->eap.plugin_data.password_length, supp->password_length); eap_client_set_properties(supp); *((bool *)&supp->eap.plugin_data.log_enabled) = supp->debug; *((bool *)&supp->eap.plugin_data.system_mode) = (EAPOLSocketGetMode(supp->sock) == kEAPOLControlModeSystem); supp->last_status = EAPClientModulePluginInit(module, &supp->eap.plugin_data, &supp->eap.required_props, &supp->last_error); supp->eap.last_type_name = EAPClientModulePluginEAPName(module); supp->eap.last_type = type; if (supp->last_status != kEAPClientStatusOK) { return (FALSE); } supp->eap.module = module; return (TRUE); } static CFArrayRef eap_client_require_properties(SupplicantRef supp) { return (EAPClientModulePluginRequireProperties(supp->eap.module, &supp->eap.plugin_data)); } static CFDictionaryRef eap_client_publish_properties(SupplicantRef supp) { return (EAPClientModulePluginPublishProperties(supp->eap.module, &supp->eap.plugin_data)); } static EAPClientState eap_client_process(SupplicantRef supp, EAPPacketRef in_pkt_p, EAPPacketRef * out_pkt_p, EAPClientStatus * status, EAPClientDomainSpecificError * error) { EAPClientState cstate; supp->eap.plugin_data.username = (uint8_t *)supp->username; S_set_uint32(&supp->eap.plugin_data.username_length, supp->username_length); supp->eap.plugin_data.password = (uint8_t *)supp->password; S_set_uint32(&supp->eap.plugin_data.password_length, supp->password_length); S_set_uint32(&supp->eap.plugin_data.generation, supp->generation); eap_client_set_properties(supp); *((bool *)&supp->eap.plugin_data.log_enabled) = supp->debug; cstate = EAPClientModulePluginProcess(supp->eap.module, &supp->eap.plugin_data, in_pkt_p, out_pkt_p, status, error); return (cstate); } static void eap_client_free_packet(SupplicantRef supp, EAPPacketRef out_pkt_p) { EAPClientModulePluginFreePacket(supp->eap.module, &supp->eap.plugin_data, out_pkt_p); } static void eap_client_log_failure(SupplicantRef supp) { const char * err; err = EAPClientModulePluginFailureString(supp->eap.module, &supp->eap.plugin_data); if (err) { my_log(LOG_NOTICE, "error string '%s'", err); } return; } static void * eap_client_session_key(SupplicantRef supp, int * key_length) { return (EAPClientModulePluginSessionKey(supp->eap.module, &supp->eap.plugin_data, key_length)); } static void * eap_client_server_key(SupplicantRef supp, int * key_length) { return (EAPClientModulePluginServerKey(supp->eap.module, &supp->eap.plugin_data, key_length)); } /** ** EAPAcceptTypes routines **/ static void EAPAcceptTypesCopy(EAPAcceptTypesRef dest, EAPAcceptTypesRef src) { *dest = *src; dest->types = (int *)malloc(src->count * sizeof(*dest->types)); bcopy(src->types, dest->types, src->count * sizeof(*dest->types)); return; } static void EAPAcceptTypesFree(EAPAcceptTypesRef accept) { if (accept->types != NULL) { free(accept->types); } bzero(accept, sizeof(*accept)); return; } static void EAPAcceptTypesInit(EAPAcceptTypesRef accept, CFDictionaryRef config_dict) { CFArrayRef accept_types = NULL; int i; int count; int tunneled_count; CFNumberRef type_cf; int type_i; int * types; int types_count; EAPAcceptTypesFree(accept); if (config_dict != NULL) { accept_types = CFDictionaryGetValue(config_dict, kEAPClientPropAcceptEAPTypes); } if (accept_types == NULL) { return; } count = CFArrayGetCount(accept_types); if (count == 0) { return; } types = (int *)malloc(count * sizeof(*types)); tunneled_count = types_count = 0; for (i = 0; i < count; i++) { type_cf = CFArrayGetValueAtIndex(accept_types, i); if (isA_CFNumber(type_cf) == NULL) { my_log(LOG_NOTICE, "AcceptEAPTypes[%d] contains invalid type, ignoring", i); continue; } if (CFNumberGetValue(type_cf, kCFNumberIntType, &type_i) == FALSE) { my_log(LOG_NOTICE, "AcceptEAPTypes[%d] contains invalid number, ignoring", i); continue; } if (EAPClientModuleLookup(type_i) == NULL) { my_log(LOG_NOTICE, "AcceptEAPTypes[%d] specifies unsupported type %d, ignoring", i, type_i); continue; } types[types_count++] = type_i; if (type_i == kEAPTypePEAP || type_i == kEAPTypeTTLS || type_i == kEAPTypeEAPFAST) { tunneled_count++; } } if (types_count == 0) { free(types); } else { accept->types = types; accept->count = types_count; if (tunneled_count == types_count) { accept->use_identity = TRUE; } } return; } static EAPType EAPAcceptTypesNextType(EAPAcceptTypesRef accept) { if (accept->types == NULL || accept->index >= accept->count) { return (kEAPTypeInvalid); } return (accept->types[accept->index++]); } static void EAPAcceptTypesReset(EAPAcceptTypesRef accept) { accept->index = 0; return; } static bool EAPAcceptTypesIsSupportedType(EAPAcceptTypesRef accept, EAPType type) { int i; if (accept->types == NULL) { return (FALSE); } for (i = 0; i < accept->count; i++) { if (accept->types[i] == type) { return (TRUE); } } return (FALSE); } static bool EAPAcceptTypesUseIdentity(EAPAcceptTypesRef accept) { return (accept->use_identity); } /** ** Supplicant routines **/ static void clear_password(SupplicantRef supp) { supp->ignore_password = TRUE; /* clear the password */ free(supp->password); supp->password = NULL; supp->password_length = 0; return; } static void free_last_packet(SupplicantRef supp) { if (supp->last_rx_packet.eapol_p != NULL) { free(supp->last_rx_packet.eapol_p); bzero(&supp->last_rx_packet, sizeof(supp->last_rx_packet)); } return; } static void save_last_packet(SupplicantRef supp, EAPOLSocketReceiveDataRef rx_p) { EAPOLPacketRef last_eapol_p; last_eapol_p = supp->last_rx_packet.eapol_p; if (last_eapol_p == rx_p->eapol_p) { /* don't bother re-saving the same buffer */ return; } bzero(&supp->last_rx_packet, sizeof(supp->last_rx_packet)); supp->last_rx_packet.eapol_p = (EAPOLPacketRef)malloc(rx_p->length); supp->last_rx_packet.length = rx_p->length; bcopy(rx_p->eapol_p, supp->last_rx_packet.eapol_p, rx_p->length); if (last_eapol_p != NULL) { free(last_eapol_p); } return; } static void Supplicant_cancel_pending_events(SupplicantRef supp) { EAPOLSocketDisableReceive(supp->sock); Timer_cancel(supp->timer); return; } static void S_update_identity_attributes(SupplicantRef supp, void * data, int length) { int props_length; void * props_start; CFStringRef props_string; my_CFRelease(&supp->identity_attributes); props_start = memchr(data, '\0', length); if (props_start == NULL) { return; } props_start++; /* skip '\0' */ props_length = length - (props_start - data); if (length <= 0) { /* no props there */ return; } props_string = CFStringCreateWithBytes(NULL, props_start, props_length, kCFStringEncodingASCII, FALSE); if (props_string != NULL) { supp->identity_attributes = CFStringCreateArrayBySeparatingStrings(NULL, props_string, CFSTR(",")); my_CFRelease(&props_string); } return; } /* * Function: eapol_key_verify_signature * * Purpose: * Verify that the signature in the key packet is valid. * Notes: * As documented in IEEE802.1X, section 7.6.8, * compute the HMAC-MD5 hash over the entire EAPOL key packet * with a zero signature using the server key as the key. * The resulting signature is compared with the signature in * the packet. If they match, the packet is valid, if not * it is invalid. * Returns: * TRUE if the signature is valid, FALSE otherwise. */ static bool eapol_key_verify_signature(EAPOLPacketRef packet, int packet_length, char * server_key, int server_key_length, bool debug) { EAPOLKeyDescriptorRef descr_p; EAPOLPacketRef packet_copy; u_char signature[16]; bool valid = FALSE; /* make a copy of the entire packet */ packet_copy = (EAPOLPacketRef)malloc(packet_length); bcopy(packet, packet_copy, packet_length); descr_p = (EAPOLKeyDescriptorRef)packet_copy->body; bzero(descr_p->key_signature, sizeof(descr_p->key_signature)); CCHmac(kCCHmacAlgMD5, server_key, server_key_length, packet_copy, packet_length, signature); descr_p = (EAPOLKeyDescriptorRef)packet->body; valid = (bcmp(descr_p->key_signature, signature, sizeof(signature)) == 0); if (debug) { printf("Signature: "); print_bytes(signature, sizeof(signature)); printf(" is %s\n", valid ? "valid" : "INVALID"); fflush(stdout); } free(packet_copy); return (valid); } static void process_key(SupplicantRef supp, EAPOLPacketRef eapol_p) { int body_length; EAPOLKeyDescriptorRef descr_p; int key_length; int key_data_length; int packet_length; char * server_key; int server_key_length = 0; uint8_t * session_key; int session_key_length = 0; wirelessKeyType type; descr_p = (EAPOLKeyDescriptorRef)eapol_p->body; session_key = eap_client_session_key(supp, &session_key_length); if (session_key == NULL) { my_log(LOG_NOTICE, "Supplicant process_key: session key is NULL"); eapolclient_log(kLogFlagBasic, "key process: NULL session key\n"); return; } server_key = eap_client_server_key(supp, &server_key_length); if (server_key == NULL) { my_log(LOG_NOTICE, "Supplicant process_key: server key is NULL"); eapolclient_log(kLogFlagBasic, "key process: NULL server key\n"); return; } body_length = EAPOLPacketGetLength(eapol_p); packet_length = sizeof(EAPOLPacket) + body_length; if (eapol_key_verify_signature(eapol_p, packet_length, server_key, server_key_length, supp->debug) == FALSE) { my_log(LOG_NOTICE, "Supplicant process key: key signature mismatch, ignoring"); eapolclient_log(kLogFlagBasic, "key process: signature mismatch\n"); return; } if (descr_p->key_index & kEAPOLKeyDescriptorIndexUnicastFlag) { type = kKeyTypeIndexedTx; } else { type = kKeyTypeMulticast; } key_length = EAPOLKeyDescriptorGetLength(descr_p); key_data_length = body_length - sizeof(*descr_p); if (key_data_length > 0) { size_t bytes_processed; CCCryptorStatus c_status; uint8_t * enc_key; uint8_t * rc4_key; int rc4_key_length; /* decrypt the key from the packet */ rc4_key_length = sizeof(descr_p->key_IV) + session_key_length; rc4_key = malloc(rc4_key_length); bcopy(descr_p->key_IV, rc4_key, sizeof(descr_p->key_IV)); bcopy(session_key, rc4_key + sizeof(descr_p->key_IV), session_key_length); enc_key = malloc(key_data_length); c_status = CCCrypt(kCCDecrypt, kCCAlgorithmRC4, 0, rc4_key, rc4_key_length, NULL, descr_p->key, key_data_length, enc_key, key_data_length, &bytes_processed); if (c_status != kCCSuccess) { eapolclient_log(kLogFlagBasic, "key process: RC4 decrypt failed %d\n", c_status); } else { eapolclient_log(kLogFlagBasic, "set %s key length %d using descriptor\n", (type == kKeyTypeIndexedTx) ? "Unicast" : "Broadcast", key_length); EAPOLSocketSetKey(supp->sock, type, (descr_p->key_index & kEAPOLKeyDescriptorIndexMask), enc_key, key_length); } free(enc_key); free(rc4_key); } else { eapolclient_log(kLogFlagBasic, "set %s key length %d using session key\n", (type == kKeyTypeIndexedTx) ? "Unicast" : "Broadcast", key_length); EAPOLSocketSetKey(supp->sock, type, descr_p->key_index & kEAPOLKeyDescriptorIndexMask, session_key, key_length); } return; } static void clear_wpa_key_info(SupplicantRef supp) { (void)EAPOLSocketSetPMK(supp->sock, NULL, 0); supp->pmk_set = FALSE; return; } static void set_wpa_key_info(SupplicantRef supp) { uint8_t * session_key; int session_key_length; if (supp->pmk_set) { /* already set */ return; } session_key = eap_client_session_key(supp, &session_key_length); if (session_key != NULL && EAPOLSocketSetPMK(supp->sock, session_key, session_key_length)) { supp->pmk_set = TRUE; } return; } static void Supplicant_authenticated(SupplicantRef supp, SupplicantEvent event, void * evdata) { EAPRequestPacket * req_p; EAPOLSocketReceiveDataRef rx = evdata; switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateAuthenticated; free_last_packet(supp); #if ! TARGET_OS_EMBEDDED UserPasswordDialogue_free(&supp->pw_prompt); #endif /* ! TARGET_OS_EMBEDDED */ if (supp->one_time_password) { clear_password(supp); } Supplicant_report_status(supp); EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_authenticated, (void *)supp, (void *)kSupplicantEventData); break; case kSupplicantEventData: Timer_cancel(supp->timer); switch (rx->eapol_p->packet_type) { case kEAPOLPacketTypeEAPPacket: req_p = (EAPRequestPacket *)rx->eapol_p->body; switch (req_p->code) { case kEAPCodeRequest: switch (req_p->type) { case kEAPTypeIdentity: Supplicant_acquired(supp, kSupplicantEventStart, evdata); break; case kEAPTypeNotification: /* need to display information to the user XXX */ my_log(LOG_NOTICE, "Authenticated: Notification '%.*s'", EAPPacketGetLength((EAPPacketRef)req_p) - sizeof(*req_p), req_p->type_data); respond_to_notification(supp, req_p->identifier); break; default: Supplicant_authenticating(supp, kSupplicantEventStart, evdata); break; } } break; case kEAPOLPacketTypeKey: if (EAPOLSocketIsWireless(supp->sock)) { EAPOLKeyDescriptorRef descr_p; descr_p = (EAPOLKeyDescriptorRef)(rx->eapol_p->body); switch (descr_p->descriptor_type) { case kEAPOLKeyDescriptorTypeRC4: process_key(supp, rx->eapol_p); break; case kEAPOLKeyDescriptorTypeIEEE80211: /* wireless family takes care of these */ break; default: break; } } break; default: break; } break; default: break; } return; } static void Supplicant_cleanup(SupplicantRef supp) { supp->previous_identifier = BAD_IDENTIFIER; EAPAcceptTypesReset(&supp->eap_accept); supp->last_status = kEAPClientStatusOK; supp->last_error = 0; eap_client_free(supp); free_last_packet(supp); return; } static void Supplicant_disconnected(SupplicantRef supp, SupplicantEvent event, void * evdata) { switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateDisconnected; Supplicant_report_status(supp); Supplicant_cleanup(supp); Supplicant_connecting(supp, kSupplicantEventStart, NULL); break; default: break; } return; } static void Supplicant_no_authenticator(SupplicantRef supp, SupplicantEvent event, void * evdata) { switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateNoAuthenticator; supp->no_authenticator = TRUE; Supplicant_report_status(supp); /* let Connecting state handle any packets that may arrive */ EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_connecting, (void *)supp, (void *)kSupplicantEventStart); break; default: break; } return; } static void Supplicant_connecting(SupplicantRef supp, SupplicantEvent event, void * evdata) { EAPOLSocketReceiveDataRef rx = evdata; struct timeval t = {S_start_period_secs, 0}; switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateConnecting; Supplicant_report_status(supp); supp->start_count = 0; EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_connecting, (void *)supp, (void *)kSupplicantEventData); /* FALL THROUGH */ case kSupplicantEventData: if (rx != NULL) { EAPOLIEEE80211KeyDescriptorRef ieee80211_descr_p; EAPRequestPacketRef req_p; switch (rx->eapol_p->packet_type) { case kEAPOLPacketTypeEAPPacket: req_p = (void *)rx->eapol_p->body; if (req_p->code == kEAPCodeRequest) { if (req_p->type == kEAPTypeIdentity) { Supplicant_acquired(supp, kSupplicantEventStart, evdata); } else { Supplicant_authenticating(supp, kSupplicantEventStart, evdata); } } return; case kEAPOLPacketTypeKey: ieee80211_descr_p = (void *)rx->eapol_p->body; if (ieee80211_descr_p->descriptor_type == kEAPOLKeyDescriptorTypeIEEE80211) { break; } return; default: return; } } /* FALL THROUGH */ case kSupplicantEventTimeout: if (rx == NULL) { if (supp->start_count == S_max_start) { /* no response from Authenticator */ Supplicant_no_authenticator(supp, kSupplicantEventStart, NULL); break; } supp->start_count++; } EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeStart, NULL, 0); Timer_set_relative(supp->timer, t, (void *)Supplicant_connecting, (void *)supp, (void *)kSupplicantEventTimeout, NULL); break; default: break; } } static bool S_retrieve_identity(SupplicantRef supp) { CFStringRef identity_cf; char * identity; /* no module is active yet, use what we have */ if (supp->eap.module == NULL) { goto use_default; } /* if we have an active module, ask it for the identity to use */ identity_cf = EAPClientModulePluginCopyIdentity(supp->eap.module, &supp->eap.plugin_data); if (identity_cf == NULL) { goto use_default; } identity = my_CFStringToCString(identity_cf, kCFStringEncodingUTF8); my_CFRelease(&identity_cf); if (identity == NULL) { goto use_default; } if (supp->username != NULL) { free(supp->username); } supp->username = identity; supp->username_length = strlen(identity); use_default: return (supp->username != NULL); } static bool respond_with_identity(SupplicantRef supp, int identifier) { char buf[256]; char * identity; int length; EAPPacketRef pkt_p; int size; if (S_retrieve_identity(supp) == FALSE) { return (FALSE); } if (supp->identity != NULL) { identity = supp->identity; length = supp->identity_length; } else { identity = supp->username; length = supp->username_length; } eapolclient_log(kLogFlagBasic, "EAP Response Identity %.*s\n", length, identity); /* transmit a response/Identity */ pkt_p = EAPPacketCreate(buf, sizeof(buf), kEAPCodeResponse, identifier, kEAPTypeIdentity, identity, length, &size); if (EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeEAPPacket, pkt_p, size) < 0) { my_log(LOG_NOTICE, "EAPOL_transmit Identity failed"); } if ((char *)pkt_p != buf) { free(pkt_p); } return (TRUE); } static void Supplicant_acquired(SupplicantRef supp, SupplicantEvent event, void * evdata) { SupplicantState prev_state = supp->state; EAPOLSocketReceiveDataRef rx; EAPRequestPacket * req_p; struct timeval t = {S_auth_period_secs, 0}; switch (event) { case kSupplicantEventStart: EAPAcceptTypesReset(&supp->eap_accept); Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateAcquired; EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_acquired, (void *)supp, (void *)kSupplicantEventData); /* FALL THROUGH */ case kSupplicantEventData: supp->no_authenticator = FALSE; rx = evdata; if (rx->eapol_p->packet_type != kEAPOLPacketTypeEAPPacket) { break; } req_p = (EAPRequestPacket *)rx->eapol_p->body; if (req_p->code == kEAPCodeRequest && req_p->type == kEAPTypeIdentity) { int len; len = EAPPacketGetLength((EAPPacketRef)req_p) - sizeof(*req_p); S_update_identity_attributes(supp, req_p->type_data, len); eapolclient_log(kLogFlagBasic, "EAP Request Identity\n"); supp->previous_identifier = req_p->identifier; if (respond_with_identity(supp, req_p->identifier)) { supp->last_status = kEAPClientStatusOK; Supplicant_report_status(supp); /* set a timeout */ Timer_set_relative(supp->timer, t, (void *)Supplicant_acquired, (void *)supp, (void *)kSupplicantEventTimeout, NULL); } else if (supp->no_ui) { eapolclient_log(kLogFlagBasic, "Acquired: cannot prompt for " "missing user name\n"); my_log(LOG_NOTICE, "No user name provided and user interaction not allowed," " authentication held."); supp->last_status = kEAPClientStatusUserInputNotPossible; Supplicant_held(supp, kSupplicantEventStart, NULL); } else { supp->last_status = kEAPClientStatusUserInputRequired; Supplicant_report_status(supp); } break; } else { if (event == kSupplicantEventStart) { /* this will not happen if we're bug free */ my_log(LOG_NOTICE, "internal error: " "Supplicant_acquired: " "recursion avoided from state %s", SupplicantStateString(prev_state)); break; } Supplicant_authenticating(supp, kSupplicantEventStart, evdata); } break; case kSupplicantEventMoreDataAvailable: if (respond_with_identity(supp, supp->previous_identifier)) { supp->last_status = kEAPClientStatusOK; Supplicant_report_status(supp); /* set a timeout */ Timer_set_relative(supp->timer, t, (void *)Supplicant_acquired, (void *)supp, (void *)kSupplicantEventTimeout, NULL); } else { supp->last_status = kEAPClientStatusUserInputRequired; Supplicant_report_status(supp); } break; case kSupplicantEventTimeout: Supplicant_connecting(supp, kSupplicantEventStart, NULL); break; default: break; } return; } static void respond_to_notification(SupplicantRef supp, int identifier) { EAPNotificationPacket notif; int size; eapolclient_log(kLogFlagBasic, "EAP Response Notification\n"); /* transmit a response/Notification */ (void)EAPPacketCreate(¬if, sizeof(notif), kEAPCodeResponse, identifier, kEAPTypeNotification, NULL, 0, &size); if (EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeEAPPacket, ¬if, sizeof(notif)) < 0) { my_log(LOG_NOTICE, "EAPOL_transmit Notification failed"); } return; } static void respond_with_nak(SupplicantRef supp, int identifier, uint8_t desired_type) { EAPNakPacket nak; int size; /* transmit a response/Nak */ (void)EAPPacketCreate(&nak, sizeof(nak), kEAPCodeResponse, identifier, kEAPTypeNak, NULL, sizeof(nak) - sizeof(EAPRequestPacket), &size); nak.desired_type = desired_type; if (EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeEAPPacket, &nak, sizeof(nak)) < 0) { my_log(LOG_NOTICE, "EAPOL_transmit Nak failed"); } return; } static void process_packet(SupplicantRef supp, EAPOLSocketReceiveDataRef rx) { EAPPacketRef in_pkt_p = (EAPPacketRef)(rx->eapol_p->body); EAPPacketRef out_pkt_p = NULL; EAPRequestPacket * req_p = (EAPRequestPacket *)in_pkt_p; EAPClientState state; struct timeval t = {S_auth_period_secs, 0}; if (supp->username == NULL) { return; } switch (in_pkt_p->code) { case kEAPCodeRequest: if (req_p->type == kEAPTypeInvalid) { return; } if (req_p->type != eap_client_type(supp)) { if (EAPAcceptTypesIsSupportedType(&supp->eap_accept, req_p->type) == FALSE) { EAPType eap_type = EAPAcceptTypesNextType(&supp->eap_accept); if (eap_type == kEAPTypeInvalid) { eapolclient_log(kLogFlagBasic, "EAP Request: EAP type %d not enabled\n", req_p->type); supp->last_status = kEAPClientStatusProtocolNotSupported; Supplicant_held(supp, kSupplicantEventStart, NULL); return; } eapolclient_log(kLogFlagBasic, "EAP Request: NAK'ing EAP type %d with %d\n", req_p->type, eap_type); respond_with_nak(supp, in_pkt_p->identifier, eap_type); /* set a timeout */ Timer_set_relative(supp->timer, t, (void *)Supplicant_authenticating, (void *)supp, (void *)kSupplicantEventTimeout, NULL); return; } Timer_cancel(supp->timer); eap_client_free(supp); if (eap_client_init(supp, req_p->type) == FALSE) { if (supp->last_status != kEAPClientStatusUserInputRequired) { eapolclient_log(kLogFlagBasic, "EAP Request: EAP type %d" " init failed, %d\n", req_p->type, supp->last_status); my_log(LOG_NOTICE, "eap_client_init type %d failed, %d", req_p->type, supp->last_status); Supplicant_held(supp, kSupplicantEventStart, NULL); return; } save_last_packet(supp, rx); Supplicant_report_status(supp); return; } eapolclient_log(kLogFlagBasic, "EAP Request: EAP type %d accepted\n", req_p->type); Supplicant_report_status(supp); } else { eapolclient_log(kLogFlagBasic, "EAP Request: EAP type %d\n", req_p->type); } break; case kEAPCodeResponse: if (req_p->type != eap_client_type(supp)) { /* this should not happen, but if it does, ignore the packet */ return; } eapolclient_log(kLogFlagBasic, "EAP Response: EAP type %d\n", req_p->type); break; case kEAPCodeFailure: eapolclient_log(kLogFlagBasic, "EAP Failure\n"); if (supp->eap.module == NULL) { supp->last_status = kEAPClientStatusFailed; Supplicant_held(supp, kSupplicantEventStart, NULL); return; } break; case kEAPCodeSuccess: eapolclient_log(kLogFlagBasic, "EAP Success\n"); if (supp->eap.module == NULL) { Supplicant_authenticated(supp, kSupplicantEventStart, NULL); return; } break; default: break; } if (supp->eap.module == NULL) { return; } /* invoke the authentication method "process" function */ my_CFRelease(&supp->eap.required_props); my_CFRelease(&supp->eap.published_props); state = eap_client_process(supp, in_pkt_p, &out_pkt_p, &supp->last_status, &supp->last_error); if (out_pkt_p != NULL) { /* send the output packet */ if (EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeEAPPacket, out_pkt_p, EAPPacketGetLength(out_pkt_p)) < 0) { my_log(LOG_NOTICE, "process_packet: EAPOL_transmit %d failed", out_pkt_p->code); } /* and free the packet */ eap_client_free_packet(supp, out_pkt_p); } supp->eap.published_props = eap_client_publish_properties(supp); switch (state) { case kEAPClientStateAuthenticating: if (supp->last_status == kEAPClientStatusUserInputRequired) { save_last_packet(supp, rx); supp->eap.required_props = eap_client_require_properties(supp); if (supp->no_ui) { CFTypeRef props = supp->eap.required_props; if (props == NULL) { props = CFSTR(""); } SCLog(TRUE, LOG_NOTICE, CFSTR("Cannot prompt for missing properties," " authentication held\nMissing properties %@"), props); eapolclient_log(kLogFlagBasic, "Authenticating: missing properties\n"); if (supp->eap.required_props != NULL) { eapolclient_log_plist(kLogFlagBasic, supp->eap.required_props); } supp->last_status = kEAPClientStatusUserInputNotPossible; Supplicant_held(supp, kSupplicantEventStart, NULL); return; } eapolclient_log(kLogFlagBasic, "Authenticating: user input required\n"); if (supp->eap.required_props != NULL) { eapolclient_log_plist(kLogFlagBasic, supp->eap.required_props); } } Supplicant_report_status(supp); /* try to set the session key, if it is available */ if (EAPOLSocketIsWireless(supp->sock)) { set_wpa_key_info(supp); } break; case kEAPClientStateSuccess: /* authentication method succeeded */ my_log(LOG_NOTICE, "%s: successfully authenticated", supp->eap.last_type_name); /* try to set the session key, if it is available */ if (EAPOLSocketIsWireless(supp->sock)) { set_wpa_key_info(supp); } Supplicant_authenticated(supp, kSupplicantEventStart, NULL); break; case kEAPClientStateFailure: /* authentication method failed */ eap_client_log_failure(supp); my_log(LOG_NOTICE, "%s: authentication failed with status %d", supp->eap.last_type_name, supp->last_status); Supplicant_held(supp, kSupplicantEventStart, NULL); break; } return; } static void Supplicant_authenticating(SupplicantRef supp, SupplicantEvent event, void * evdata) { EAPRequestPacket * req_p; SupplicantState prev_state = supp->state; EAPOLSocketReceiveDataRef rx = evdata; switch (event) { case kSupplicantEventStart: if (EAPOLSocketIsWireless(supp->sock)) { clear_wpa_key_info(supp); } supp->state = kSupplicantStateAuthenticating; Supplicant_report_status(supp); EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_authenticating, (void *)supp, (void *)kSupplicantEventData); /* FALL THROUGH */ case kSupplicantEventData: Timer_cancel(supp->timer); supp->no_authenticator = FALSE; if (rx->eapol_p->packet_type != kEAPOLPacketTypeEAPPacket) { break; } req_p = (EAPRequestPacket *)rx->eapol_p->body; switch (req_p->code) { case kEAPCodeSuccess: process_packet(supp, rx); break; case kEAPCodeFailure: process_packet(supp, rx); break; case kEAPCodeRequest: case kEAPCodeResponse: switch (req_p->type) { case kEAPTypeIdentity: if (req_p->code == kEAPCodeResponse) { /* don't care about responses */ break; } if (event == kSupplicantEventStart) { /* this will not happen if we're bug free */ my_log(LOG_NOTICE, "internal error:" " Supplicant_authenticating: " "recursion avoided from state %s", SupplicantStateString(prev_state)); break; } Supplicant_acquired(supp, kSupplicantEventStart, evdata); break; case kEAPTypeNotification: if (req_p->code == kEAPCodeResponse) { /* don't care about responses */ break; } /* need to display information to the user XXX */ my_log(LOG_NOTICE, "Authenticating: Notification '%.*s'", EAPPacketGetLength((EAPPacketRef)req_p) - sizeof(*req_p), req_p->type_data); eapolclient_log(kLogFlagBasic, "EAP Request Notification '%.*s'\n", EAPPacketGetLength((EAPPacketRef)req_p) - sizeof(*req_p), req_p->type_data); respond_to_notification(supp, req_p->identifier); break; default: process_packet(supp, rx); break; } /* switch (req_p->type) */ break; default: break; } /* switch (req_p->code) */ break; case kSupplicantEventTimeout: Supplicant_connecting(supp, kSupplicantEventStart, NULL); break; default: break; } return; } static void dictInsertNumber(CFMutableDictionaryRef dict, CFStringRef prop, uint32_t num) { CFNumberRef num_cf; num_cf = CFNumberCreate(NULL, kCFNumberSInt32Type, &num); CFDictionarySetValue(dict, prop, num_cf); my_CFRelease(&num_cf); return; } static void dictInsertEAPTypeInfo(CFMutableDictionaryRef dict, EAPType type, const char * type_name) { if (type == kEAPTypeInvalid) { return; } /* EAPTypeName */ if (type_name != NULL) { CFStringRef eap_type_name_cf; eap_type_name_cf = CFStringCreateWithCString(NULL, type_name, kCFStringEncodingASCII); CFDictionarySetValue(dict, kEAPOLControlEAPTypeName, eap_type_name_cf); my_CFRelease(&eap_type_name_cf); } /* EAPType */ dictInsertNumber(dict, kEAPOLControlEAPType, type); return; } static void dictInsertSupplicantState(CFMutableDictionaryRef dict, SupplicantState state) { dictInsertNumber(dict, kEAPOLControlSupplicantState, state); return; } static void dictInsertClientStatus(CFMutableDictionaryRef dict, EAPClientStatus status, int error) { dictInsertNumber(dict, kEAPOLControlClientStatus, status); dictInsertNumber(dict, kEAPOLControlDomainSpecificError, error); return; } static void dictInsertGeneration(CFMutableDictionaryRef dict, uint32_t generation) { dictInsertNumber(dict, kEAPOLControlConfigurationGeneration, generation); } static void dictInsertRequiredProperties(CFMutableDictionaryRef dict, CFArrayRef required_props) { if (required_props == NULL) { CFMutableArrayRef array; /* to start, we at least need the user name */ array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); CFArrayAppendValue(array, kEAPClientPropUserName); CFDictionarySetValue(dict, kEAPOLControlRequiredProperties, array); my_CFRelease(&array); } else { CFDictionarySetValue(dict, kEAPOLControlRequiredProperties, required_props); } return; } static void dictInsertPublishedProperties(CFMutableDictionaryRef dict, CFDictionaryRef published_props) { if (published_props == NULL) { return; } CFDictionarySetValue(dict, kEAPOLControlAdditionalProperties, published_props); } static void dictInsertIdentityAttributes(CFMutableDictionaryRef dict, CFArrayRef identity_attributes) { if (identity_attributes == NULL) { return; } CFDictionarySetValue(dict, kEAPOLControlIdentityAttributes, identity_attributes); } static void dictInsertMode(CFMutableDictionaryRef dict, EAPOLControlMode mode) { if (mode == kEAPOLControlModeNone) { return; } dictInsertNumber(dict, kEAPOLControlMode, mode); if (mode == kEAPOLControlModeSystem) { CFDictionarySetValue(dict, kEAPOLControlSystemMode, kCFBooleanTrue); } return; } void Supplicant_stop(SupplicantRef supp) { eapolclient_log(kLogFlagBasic, "stop\n"); eap_client_free(supp); Supplicant_logoff(supp, kSupplicantEventStart, NULL); Supplicant_free(&supp); fflush(stdout); fflush(stderr); return; } static void user_supplied_data(SupplicantRef supp) { eapolclient_log(kLogFlagBasic, "user_supplied_data\n"); switch (supp->state) { case kSupplicantStateAcquired: Supplicant_acquired(supp, kSupplicantEventMoreDataAvailable, NULL); break; case kSupplicantStateAuthenticating: if (supp->last_rx_packet.eapol_p != NULL) { process_packet(supp, &supp->last_rx_packet); } break; default: break; } } static void create_ui_config_dict(SupplicantRef supp) { if (supp->ui_config_dict != NULL) { return; } supp->ui_config_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); return; } #if ! TARGET_OS_EMBEDDED static Boolean dicts_compare_arrays(CFDictionaryRef dict1, CFDictionaryRef dict2, CFStringRef propname) { CFArrayRef array1 = NULL; CFArrayRef array2 = NULL; if (dict1 != NULL && dict2 != NULL) { array1 = CFDictionaryGetValue(dict1, propname); array2 = CFDictionaryGetValue(dict2, propname); } if (array1 == NULL || array2 == NULL) { return (FALSE); } return (CFEqual(array1, array2)); } static void trust_callback(const void * arg1, const void * arg2, TrustDialogueResponseRef response) { CFDictionaryRef config_dict = NULL; SupplicantRef supp = (SupplicantRef)arg1; CFDictionaryRef trust_info; CFArrayRef trust_proceed; if (supp->trust_prompt == NULL) { return; } trust_info = TrustDialogue_trust_info(supp->trust_prompt); if (trust_info != NULL) { CFRetain(trust_info); } TrustDialogue_free(&supp->trust_prompt); if (trust_info == NULL) { return; } if (response->proceed == FALSE) { my_log(LOG_NOTICE, "%s: user cancelled", EAPOLSocketIfName(supp->sock, NULL)); EAPOLControlStop(EAPOLSocketIfName(supp->sock, NULL)); goto done; } if (supp->last_status != kEAPClientStatusUserInputRequired || supp->eap.published_props == NULL) { goto done; } create_ui_config_dict(supp); if (dicts_compare_arrays(trust_info, supp->eap.published_props, kEAPClientPropTLSServerCertificateChain) == FALSE) { CFDictionaryRemoveValue(supp->ui_config_dict, kEAPClientPropTLSUserTrustProceedCertificateChain); } else { trust_proceed = CFDictionaryGetValue(supp->eap.published_props, kEAPClientPropTLSServerCertificateChain); if (trust_proceed != NULL) { CFDictionarySetValue(supp->ui_config_dict, kEAPClientPropTLSUserTrustProceedCertificateChain, trust_proceed); } } config_dict = CFDictionaryCreateCopy(NULL, supp->orig_config_dict); Supplicant_update_configuration(supp, config_dict); my_CFRelease(&config_dict); user_supplied_data(supp); done: my_CFRelease(&trust_info); return; } static void password_callback(const void * arg1, const void * arg2, UserPasswordDialogueResponseRef response) { CFDictionaryRef config_dict = NULL; SupplicantRef supp = (SupplicantRef)arg1; UserPasswordDialogue_free(&supp->pw_prompt); if (response->user_cancelled) { my_log(LOG_NOTICE, "%s: user cancelled", EAPOLSocketIfName(supp->sock, NULL)); EAPOLControlStop(EAPOLSocketIfName(supp->sock, NULL)); return; } if (supp->last_status != kEAPClientStatusUserInputRequired) { return; } create_ui_config_dict(supp); if (response->username != NULL) { CFDictionarySetValue(supp->ui_config_dict, kEAPClientPropUserName, response->username); } if (response->password != NULL) { CFDictionarySetValue(supp->ui_config_dict, kEAPClientPropUserPassword, response->password); supp->ignore_password = FALSE; } config_dict = CFDictionaryCreateCopy(NULL, supp->orig_config_dict); Supplicant_update_configuration(supp, config_dict); my_CFRelease(&config_dict); user_supplied_data(supp); return; } static Boolean my_CFArrayContainsValue(CFArrayRef list, CFStringRef value) { if (list == NULL) { return (FALSE); } return (CFArrayContainsValue(list, CFRangeMake(0, CFArrayGetCount(list)), value)); } #define EAPOLCONTROLLER_PATH "/System/Library/SystemConfiguration/EAPOLController.bundle" static CFBundleRef get_bundle(void) { static CFBundleRef bundle = NULL; CFURLRef url; if (bundle != NULL) { return (bundle); } url = CFURLCreateWithFileSystemPath(NULL, CFSTR(EAPOLCONTROLLER_PATH), kCFURLPOSIXPathStyle, 1); if (url == NULL) { my_log(LOG_NOTICE, "can't find EAPOLController bundle"); goto failed; } bundle = CFBundleCreate(NULL, url); CFRelease(url); if (bundle == NULL) { my_log(LOG_NOTICE, "EAPOLController bundle create failed - using main bundle"); goto failed; } return (bundle); failed: return (CFBundleGetMainBundle()); } #define kAirPort8021XTitleFormat "Authenticating to network \"%@\"" #define kEthernet8021XTitle "Authenticating to 802.1X network" static CFStringRef copy_localized_title(SupplicantRef supp) { CFStringRef title = NULL; CFStringRef ssid = EAPOLSocketGetSSID(supp->sock); if (ssid != NULL) { CFStringRef format; format = CFBundleCopyLocalizedString(get_bundle(), CFSTR(kAirPort8021XTitleFormat), CFSTR(kAirPort8021XTitleFormat), NULL); if (format != NULL) { title = CFStringCreateWithFormat(NULL, NULL, format, ssid); CFRelease(format); } } else { title = CFBundleCopyLocalizedString(get_bundle(), CFSTR(kEthernet8021XTitle), CFSTR(kEthernet8021XTitle), NULL); } if (title == NULL) { title = CFRetain(CFSTR(kEthernet8021XTitle)); } return (title); } #endif /* ! TARGET_OS_EMBEDDED */ static void Supplicant_report_status(SupplicantRef supp) { CFMutableDictionaryRef dict; #if ! TARGET_OS_EMBEDDED Boolean need_username = FALSE; Boolean need_password = FALSE; Boolean need_trust = FALSE; #endif /* ! TARGET_OS_EMBEDDED */ CFDateRef timestamp = NULL; dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); dictInsertMode(dict, EAPOLSocketGetMode(supp->sock)); if (supp->config_id != NULL) { CFDictionarySetValue(dict, kEAPOLControlUniqueIdentifier, supp->config_id); } dictInsertSupplicantState(dict, supp->state); if (supp->no_authenticator) { /* don't report EAP type information if no auth was present */ dictInsertClientStatus(dict, kEAPClientStatusOK, 0); } else { dictInsertEAPTypeInfo(dict, supp->eap.last_type, supp->eap.last_type_name); dictInsertClientStatus(dict, supp->last_status, supp->last_error); if (supp->last_status == kEAPClientStatusUserInputRequired) { if (supp->username == NULL) { dictInsertRequiredProperties(dict, NULL); #if ! TARGET_OS_EMBEDDED need_username = TRUE; #endif /* ! TARGET_OS_EMBEDDED */ } else { dictInsertRequiredProperties(dict, supp->eap.required_props); #if ! TARGET_OS_EMBEDDED need_password = my_CFArrayContainsValue(supp->eap.required_props, kEAPClientPropUserPassword); need_trust = my_CFArrayContainsValue(supp->eap.required_props, kEAPClientPropTLSUserTrustProceedCertificateChain); #endif /* ! TARGET_OS_EMBEDDED */ } } dictInsertPublishedProperties(dict, supp->eap.published_props); dictInsertIdentityAttributes(dict, supp->identity_attributes); } dictInsertGeneration(dict, supp->generation); timestamp = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent()); CFDictionarySetValue(dict, kEAPOLControlTimestamp, timestamp); my_CFRelease(×tamp); if (eapolclient_should_log(kLogFlagBasic)) { eapolclient_log(kLogFlagBasic, "Supplicant %s status: state=%s\n", EAPOLSocketName(supp->sock), SupplicantStateString(supp->state)); } eapolclient_log_plist(kLogFlagStatus, dict); EAPOLSocketReportStatus(supp->sock, dict); my_CFRelease(&dict); #if ! TARGET_OS_EMBEDDED if (supp->no_ui) { goto no_ui; } if (need_username || need_password) { if (supp->pw_prompt == NULL) { CFStringRef password; CFStringRef title; CFStringRef username; title = copy_localized_title(supp); username = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserName); if (supp->one_time_password) { password = NULL; } else { password = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserPassword); } supp->pw_prompt = UserPasswordDialogue_create(password_callback, supp, NULL, NULL, title, NULL, username, password); my_CFRelease(&title); } } if (need_trust) { if (supp->trust_prompt == NULL) { CFStringRef title; title = copy_localized_title(supp); supp->trust_prompt = TrustDialogue_create(trust_callback, supp, NULL, supp->eap.published_props, NULL, title); my_CFRelease(&title); } } no_ui: #endif /* ! TARGET_OS_EMBEDDED */ return; } static void Supplicant_held(SupplicantRef supp, SupplicantEvent event, void * evdata) { EAPRequestPacket * req_p; EAPOLSocketReceiveDataRef rx = evdata; struct timeval t = {S_held_period_secs, 0}; switch (event) { case kSupplicantEventStart: if (EAPOLSocketIsWireless(supp->sock)) { clear_wpa_key_info(supp); } Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateHeld; Supplicant_report_status(supp); supp->previous_identifier = BAD_IDENTIFIER; EAPAcceptTypesReset(&supp->eap_accept); if (supp->eap.module != NULL && supp->no_ui == FALSE && supp->last_status == kEAPClientStatusFailed) { clear_password(supp); } supp->last_status = kEAPClientStatusOK; supp->last_error = 0; free_last_packet(supp); eap_client_free(supp); #if ! TARGET_OS_EMBEDDED UserPasswordDialogue_free(&supp->pw_prompt); TrustDialogue_free(&supp->trust_prompt); #endif /* ! TARGET_OS_EMBEDDED */ EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_held, (void *)supp, (void *)kSupplicantEventData); /* set a timeout */ Timer_set_relative(supp->timer, t, (void *)Supplicant_held, (void *)supp, (void *)kSupplicantEventTimeout, NULL); break; case kSupplicantEventTimeout: Supplicant_connecting(supp, kSupplicantEventStart, NULL); break; case kSupplicantEventData: if (rx->eapol_p->packet_type != kEAPOLPacketTypeEAPPacket) { break; } req_p = (EAPRequestPacket *)rx->eapol_p->body; switch (req_p->code) { case kEAPCodeRequest: switch (req_p->type) { case kEAPTypeIdentity: Supplicant_acquired(supp, kSupplicantEventStart, evdata); break; case kEAPTypeNotification: /* need to display information to the user XXX */ my_log(LOG_NOTICE, "Held: Notification '%.*s'", EAPPacketGetLength((EAPPacketRef)req_p) - sizeof(*req_p), req_p->type_data); respond_to_notification(supp, req_p->identifier); break; default: break; } break; default: break; } break; default: break; } } void Supplicant_start(SupplicantRef supp) { if (EAPOLSocketIsLinkActive(supp->sock)) { Supplicant_disconnected(supp, kSupplicantEventStart, NULL); } else { Supplicant_inactive(supp, kSupplicantEventStart, NULL); } return; } static void Supplicant_inactive(SupplicantRef supp, SupplicantEvent event, void * evdata) { switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); supp->state = kSupplicantStateInactive; supp->no_authenticator = TRUE; Supplicant_report_status(supp); EAPOLSocketEnableReceive(supp->sock, (void *)Supplicant_connecting, (void *)supp, (void *)kSupplicantEventStart); break; default: break; } return; } static void Supplicant_logoff(SupplicantRef supp, SupplicantEvent event, void * evdata) { switch (event) { case kSupplicantEventStart: Supplicant_cancel_pending_events(supp); if (supp->state != kSupplicantStateAuthenticated) { break; } supp->state = kSupplicantStateLogoff; supp->last_status = kEAPClientStatusOK; eap_client_free(supp); EAPOLSocketTransmit(supp->sock, kEAPOLPacketTypeLogoff, NULL, 0); Supplicant_report_status(supp); break; default: break; } return; } static bool my_strcmp(char * s1, char * s2) { if (s1 == NULL || s2 == NULL) { if (s1 == s2) { return (0); } if (s1 == NULL) { return (-1); } return (1); } return (strcmp(s1, s2)); } static char * eap_method_user_name(EAPAcceptTypesRef accept, CFDictionaryRef config_dict) { int i; for (i = 0; i < accept->count; i++) { EAPClientModuleRef module; CFStringRef eap_user; module = EAPClientModuleLookup(accept->types[i]); if (module == NULL) { continue; } eap_user = EAPClientModulePluginUserName(module, config_dict); if (eap_user != NULL) { char * user; user = my_CFStringToCString(eap_user, kCFStringEncodingUTF8); my_CFRelease(&eap_user); return (user); } } return (NULL); } #if ! TARGET_OS_EMBEDDED static OSStatus mySecKeychainCopySystemKeychain(SecKeychainRef * ret_keychain) { SecKeychainRef keychain = NULL; OSStatus status; status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); if (status != noErr) { my_log(LOG_NOTICE, "SecKeychainSetPreferenceDomain() failed, %ld", status); goto done; } status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain); if (status != noErr) { my_log(LOG_NOTICE, "SecKeychainCopyDomainDefault() failed, %ld", status); } done: *ret_keychain = keychain; return (status); } #endif /* ! TARGET_OS_EMBEDDED */ static char * S_copy_password_from_keychain(bool use_system_keychain, CFStringRef unique_id_str) { SecKeychainRef keychain = NULL; char * password = NULL; CFDataRef password_data = NULL; int password_length; OSStatus status; #if ! TARGET_OS_EMBEDDED if (use_system_keychain) { status = mySecKeychainCopySystemKeychain(&keychain); if (status != noErr) { goto done; } } #endif /* ! TARGET_OS_EMBEDDED */ status = EAPSecKeychainPasswordItemCopy(keychain, unique_id_str, &password_data); if (status != noErr) { my_log(LOG_NOTICE, "SecKeychainFindGenericPassword failed, %d", status); goto done; } password_length = CFDataGetLength(password_data); password = malloc(password_length + 1); bcopy(CFDataGetBytePtr(password_data), password, password_length); password[password_length] = '\0'; done: my_CFRelease(&password_data); my_CFRelease(&keychain); return ((char *)password); } static Boolean myCFDictionaryGetBooleanValue(CFDictionaryRef properties, CFStringRef propname, Boolean def_value) { bool ret = def_value; if (properties != NULL) { CFBooleanRef val; val = CFDictionaryGetValue(properties, propname); if (isA_CFBoolean(val)) { ret = CFBooleanGetValue(val); } } return (ret); } static bool S_set_user_password(SupplicantRef supp) { bool change = FALSE; CFStringRef identity_cf = NULL; char * identity = NULL; CFStringRef name_cf = NULL; char * name = NULL; CFStringRef password_cf = NULL; char * password = NULL; if (supp->config_dict == NULL) { return (TRUE); } /* extract the username */ name_cf = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserName); name_cf = isA_CFString(name_cf); if (name_cf != NULL) { name = my_CFStringToCString(name_cf, kCFStringEncodingUTF8); } if (name == NULL) { /* no username specified, ask EAP types if they can come up with one */ name = eap_method_user_name(&supp->eap_accept, supp->config_dict); } if (my_strcmp(supp->username, name) != 0) { change = TRUE; } if (supp->username != NULL) { free(supp->username); } supp->username = name; if (name != NULL) { supp->username_length = strlen(name); } else { supp->username_length = 0; } /* check whether one-time use password */ supp->one_time_password = myCFDictionaryGetBooleanValue(supp->config_dict, kEAPClientPropOneTimeUserPassword, supp->one_time_password); /* extract the password */ if (supp->ignore_password == FALSE) { password_cf = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserPassword); password_cf = isA_CFString(password_cf); if (password_cf != NULL) { password = my_CFStringToCString(password_cf, kCFStringEncodingMacRoman); } else { CFStringRef item_cf; item_cf = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserPasswordKeychainItemID); item_cf = isA_CFString(item_cf); if (item_cf != NULL) { bool system_mode; system_mode = (EAPOLSocketGetMode(supp->sock) == kEAPOLControlModeSystem); password = S_copy_password_from_keychain(system_mode, item_cf); if (password == NULL) { my_log(LOG_NOTICE, "%s: failed to retrieve password from keychain", EAPOLSocketIfName(supp->sock, NULL)); } } } } if (supp->password != NULL) { free(supp->password); } supp->password = password; if (password != NULL) { supp->password_length = strlen(password); } else { supp->password_length = 0; } /* extract the identity */ if (EAPAcceptTypesUseIdentity(&supp->eap_accept) == TRUE) { identity_cf = CFDictionaryGetValue(supp->config_dict, kEAPClientPropOuterIdentity); identity_cf = isA_CFString(identity_cf); if (identity_cf != NULL) { identity = my_CFStringToCString(identity_cf, kCFStringEncodingUTF8); } } if (my_strcmp(supp->identity, identity) != 0) { change = TRUE; } if (supp->identity != NULL) { free(supp->identity); } supp->identity = identity; if (identity != NULL) { supp->identity_length = strlen(identity); } else { supp->identity_length = 0; } return (change); } static void dict_add_key_value(const void * key, const void * value, void * context) { CFMutableDictionaryRef new_dict = (CFMutableDictionaryRef)context; /* add the (key, value) if the key doesn't already exist */ CFDictionaryAddValue(new_dict, key, value); return; } static void dict_set_key_value(const void * key, const void * value, void * context) { CFMutableDictionaryRef new_dict = (CFMutableDictionaryRef)context; /* set the (key, value) */ CFDictionarySetValue(new_dict, key, value); return; } static bool cfstring_is_empty(CFStringRef str) { if (str == NULL) { return (FALSE); } return (isA_CFString(str) == NULL || CFStringGetLength(str) == 0); } static bool debug_properties_present(CFDictionaryRef dict) { bool ret = FALSE; if (CFDictionaryGetValue(dict, kEAPOLControlLogLevel) != NULL || CFDictionaryGetValue(dict, CFSTR("_debug")) != NULL) { ret = TRUE; } return (ret); } bool Supplicant_update_configuration(SupplicantRef supp, CFDictionaryRef config_dict) { bool change = FALSE; CFStringRef config_id; bool debug_on = FALSE; CFDictionaryRef default_dict; CFDictionaryRef default_eap_config = NULL; CFDictionaryRef eap_config; bool empty_password = FALSE; bool empty_user = FALSE; #if ! TARGET_OS_EMBEDDED CFBooleanRef enable_ui; #endif /* ! TARGET_OS_EMBEDDED */ /* keep a copy of the original around */ my_CFRelease(&supp->orig_config_dict); supp->orig_config_dict = CFDictionaryCreateCopy(NULL, config_dict); /* get the new configuration */ eap_config = CFDictionaryGetValue(config_dict, kEAPOLControlEAPClientConfiguration); if (isA_CFDictionary(eap_config) == NULL) { eap_config = config_dict; } empty_user = cfstring_is_empty(CFDictionaryGetValue(eap_config, kEAPClientPropUserName)); empty_password = cfstring_is_empty(CFDictionaryGetValue(eap_config, kEAPClientPropUserPassword)); default_dict = eapolclient_default_dict(); if (default_dict != NULL) { if (debug_properties_present(default_dict)) { debug_on = TRUE; } default_eap_config = CFDictionaryGetValue(default_dict, kEAPOLControlEAPClientConfiguration); if (default_eap_config == NULL) { default_eap_config = default_dict; } } my_CFRelease(&supp->config_dict); /* clean up empty username/password, add UI properties and default */ if (empty_user || empty_password || supp->ui_config_dict != NULL || default_eap_config != NULL) { CFMutableDictionaryRef new_eap_config = NULL; new_eap_config = CFDictionaryCreateMutableCopy(NULL, 0, eap_config); if (empty_user) { CFDictionaryRemoveValue(new_eap_config, kEAPClientPropUserName); } if (empty_password) { CFDictionaryRemoveValue(new_eap_config, kEAPClientPropUserPassword); } if (supp->ui_config_dict != NULL) { CFDictionaryApplyFunction(supp->ui_config_dict, dict_add_key_value, new_eap_config); } if (default_eap_config != NULL) { CFDictionaryApplyFunction(default_eap_config, dict_add_key_value, new_eap_config); } supp->config_dict = new_eap_config; } else { supp->config_dict = CFRetain(eap_config); } /* bump the configuration generation */ supp->generation++; /* get the configuration identifier */ my_CFRelease(&supp->config_id); config_id = CFDictionaryGetValue(config_dict, kEAPOLControlUniqueIdentifier); if (isA_CFString(config_id) != NULL) { supp->config_id = CFRetain(config_id); } #if ! TARGET_OS_EMBEDDED /* check whether we should allow UI or not */ enable_ui = CFDictionaryGetValue(config_dict, kEAPOLControlEnableUserInterface); if (isA_CFBoolean(enable_ui) != NULL) { supp->no_ui = !CFBooleanGetValue(enable_ui); } #endif /* ! TARGET_OS_EMBEDDED */ /* update the list of EAP types we accept */ EAPAcceptTypesInit(&supp->eap_accept, supp->config_dict); if (S_set_user_password(supp)) { change = TRUE; } if (EAPAcceptTypesIsSupportedType(&supp->eap_accept, eap_client_type(supp)) == FALSE) { /* negotiated EAP type is no longer valid, start over */ eap_client_free(supp); change = TRUE; } /* enable debugging */ if (debug_on || debug_properties_present(config_dict) || debug_properties_present(supp->config_dict)) { Supplicant_set_debug(supp, TRUE); } else { Supplicant_set_debug(supp, FALSE); } eapolclient_log(kLogFlagBasic, "update_configuration\n"); eapolclient_log_config_dict(kLogFlagConfig, supp->config_dict); return (change); } bool Supplicant_control(SupplicantRef supp, EAPOLClientControlCommand command, CFDictionaryRef control_dict) { bool change; CFDictionaryRef config_dict = NULL; bool should_stop = FALSE; CFDictionaryRef user_input_dict = NULL; switch (command) { case kEAPOLClientControlCommandRetry: if (supp->state != kSupplicantStateInactive) { Supplicant_connecting(supp, kSupplicantEventStart, NULL); } break; case kEAPOLClientControlCommandTakeUserInput: user_input_dict = CFDictionaryGetValue(control_dict, kEAPOLClientControlUserInput); if (user_input_dict != NULL) { /* add the user input to the ui_config_dict */ create_ui_config_dict(supp); CFDictionaryApplyFunction(user_input_dict, dict_set_key_value, supp->ui_config_dict); } config_dict = CFDictionaryCreateCopy(NULL, supp->orig_config_dict); Supplicant_update_configuration(supp, config_dict); my_CFRelease(&config_dict); user_supplied_data(supp); break; case kEAPOLClientControlCommandRun: config_dict = CFDictionaryGetValue(control_dict, kEAPOLClientControlConfiguration); if (config_dict == NULL) { should_stop = TRUE; break; } change = Supplicant_update_configuration(supp, config_dict); if (EAPOLSocketIsLinkActive(supp->sock) == FALSE) { /* no point in doing anything if the link is down */ break; } if (supp->last_status == kEAPClientStatusUserInputRequired) { switch (supp->state) { case kSupplicantStateAcquired: change = FALSE; if (supp->username != NULL) { Supplicant_acquired(supp, kSupplicantEventMoreDataAvailable, NULL); } break; case kSupplicantStateAuthenticating: if (change == FALSE && supp->last_rx_packet.eapol_p != NULL) { process_packet(supp, &supp->last_rx_packet); } break; default: break; } } if (change) { Supplicant_disconnected(supp, kSupplicantEventStart, NULL); } break; case kEAPOLClientControlCommandStop: should_stop = TRUE; break; default: break; } if (supp->debug) { fflush(stdout); fflush(stderr); } return (should_stop); } void Supplicant_link_status_changed(SupplicantRef supp, bool active) { struct timeval t = {0, 0}; if (active) { t.tv_sec = S_link_active_period_secs; switch (supp->state) { case kSupplicantStateInactive: case kSupplicantStateConnecting: /* give the Authenticator a chance to initiate */ t.tv_sec = 0; t.tv_usec = 500 * 1000; /* 1/2 second */ /* FALL THROUGH */ default: /* * wait awhile before entering connecting state to avoid * disrupting an existing conversation */ Timer_set_relative(supp->timer, t, (void *)Supplicant_connecting, (void *)supp, (void *)kSupplicantEventStart, NULL); break; } } else { /* * wait awhile before entering the inactive state to avoid * disrupting an existing conversation */ t.tv_sec = S_link_inactive_period_secs; /* if link is down, enter wait for link state */ Timer_set_relative(supp->timer, t, (void *)Supplicant_inactive, (void *)supp, (void *)kSupplicantEventStart, NULL); } return; } static CFDictionaryRef eapolclient_default_dict(void) { static CFDictionaryRef default_dict = NULL; static bool done = FALSE; CFBundleRef bundle; uint8_t config_file[MAXPATHLEN]; Boolean ok; CFURLRef url; if (done) { return (default_dict); } done = TRUE; bundle = CFBundleGetMainBundle(); if (bundle == NULL) { goto done; } url = CFBundleCopyResourceURL(bundle, CFSTR("eapolclient_default"), CFSTR("plist"), NULL); if (url == NULL) { goto done; } ok = CFURLGetFileSystemRepresentation(url, TRUE, config_file, sizeof(config_file)); CFRelease(url); if (ok == FALSE) { goto done; } default_dict = my_CFPropertyListCreateFromFile((char *)config_file); if (default_dict != NULL) { if (isA_CFDictionary(default_dict) == NULL || CFDictionaryGetCount(default_dict) == 0) { my_CFRelease(&default_dict); } } done: return (default_dict); } SupplicantRef Supplicant_create(EAPOLSocketRef sock) { SupplicantRef supp = NULL; TimerRef timer = NULL; timer = Timer_create(); if (timer == NULL) { my_log(LOG_NOTICE, "Supplicant_create: Timer_create failed"); goto failed; } supp = malloc(sizeof(*supp)); if (supp == NULL) { my_log(LOG_NOTICE, "Supplicant_create: malloc failed"); goto failed; } bzero(supp, sizeof(*supp)); supp->timer = timer; supp->sock = sock; return (supp); failed: if (supp != NULL) { free(supp); } Timer_free(&timer); return (NULL); } SupplicantRef Supplicant_create_with_supplicant(EAPOLSocketRef sock, SupplicantRef main_supp) { SupplicantRef supp; supp = Supplicant_create(sock); if (supp == NULL) { return (NULL); } supp->generation = main_supp->generation; supp->config_dict = CFRetain(main_supp->config_dict); if (main_supp->ui_config_dict) { supp->ui_config_dict = CFDictionaryCreateMutableCopy(NULL, 0, main_supp->ui_config_dict); } if (main_supp->identity != NULL) { supp->identity = strdup(main_supp->identity); supp->identity_length = main_supp->identity_length; } if (main_supp->username != NULL) { supp->username = strdup(main_supp->username); supp->username_length = main_supp->username_length; } if (main_supp->password != NULL) { supp->password = strdup(main_supp->password); supp->password_length = main_supp->password_length; } EAPAcceptTypesCopy(&supp->eap_accept, &main_supp->eap_accept); supp->debug = main_supp->debug; supp->no_ui = TRUE; return (supp); } void Supplicant_free(SupplicantRef * supp_p) { SupplicantRef supp; if (supp_p == NULL) { return; } supp = *supp_p; if (supp != NULL) { #if ! TARGET_OS_EMBEDDED UserPasswordDialogue_free(&supp->pw_prompt); TrustDialogue_free(&supp->trust_prompt); #endif /* ! TARGET_OS_EMBEDDED */ Timer_free(&supp->timer); my_CFRelease(&supp->orig_config_dict); my_CFRelease(&supp->config_dict); my_CFRelease(&supp->ui_config_dict); my_CFRelease(&supp->config_id); my_CFRelease(&supp->identity_attributes); if (supp->identity != NULL) { free(supp->identity); } if (supp->username != NULL) { free(supp->username); } if (supp->password != NULL) { free(supp->password); } EAPAcceptTypesFree(&supp->eap_accept); free_last_packet(supp); eap_client_free(supp); free(supp); } *supp_p = NULL; return; } void Supplicant_set_debug(SupplicantRef supp, bool debug) { supp->debug = debug; EAPOLSocketSetDebug(debug); /* my_log_set_verbose(debug); */ return; } SupplicantState Supplicant_get_state(SupplicantRef supp, EAPClientStatus * last_status) { *last_status = supp->last_status; return (supp->state); } void Supplicant_set_no_ui(SupplicantRef supp) { supp->no_ui = TRUE; return; } static void eapolclient_log_config_dict(uint32_t flags, CFDictionaryRef d) { CFStringRef password; CFStringRef new_password; if (eapolclient_should_log(flags) == FALSE) { return; } password = CFDictionaryGetValue(d, kEAPClientPropUserPassword); new_password = CFDictionaryGetValue(d, kEAPClientPropNewPassword); if (password != NULL || new_password != NULL) { CFMutableDictionaryRef d_copy; d_copy = CFDictionaryCreateMutableCopy(NULL, 0, d); if (password != NULL) { CFDictionarySetValue(d_copy, kEAPClientPropUserPassword, CFSTR("XXXXXXXX")); } if (new_password != NULL) { CFDictionarySetValue(d_copy, kEAPClientPropNewPassword, CFSTR("XXXXXXXX")); } eapolclient_log_plist(flags, d_copy); CFRelease(d_copy); } else { eapolclient_log_plist(flags, d); } return; }