#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sysexits.h>
#include <string.h>
#include <paths.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rc4.h>
#include <EAP8021X/EAPUtil.h>
#include <EAP8021X/EAPOLClient.h>
#include <EAP8021X/EAPClientModule.h>
#include <EAP8021X/EAPClientProperties.h>
#include <EAP8021X/EAPOLControlTypes.h>
#include <EAP8021X/EAPOLControl.h>
#include <EAP8021X/SupplicantTypes.h>
#include <EAP8021X/EAPKeychainUtil.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFBundle.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <Security/SecKeychain.h>
#include <Security/SecKeychainItem.h>
#include "Supplicant.h"
#include "Timer.h"
#include "EAPOLSocket.h"
#include "printdata.h"
#include "convert.h"
#include "mylog.h"
#include "myCFUtil.h"
#include "ClientControlInterface.h"
#include "Dialogue.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 4
#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 {
CFStringRef if_name_cf;
SupplicantState state;
Timer * timer;
EAPOLSocket * sock;
EAPOLClientRef client;
SCDynamicStoreRef store;
uint32_t generation;
CFDictionaryRef orig_config_dict;
CFDictionaryRef config_dict;
CFMutableDictionaryRef ui_config_dict;
CFStringRef config_id;
bool system_mode;
CFDictionaryRef default_dict;
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;
UserPasswordDialogueRef pw_prompt;
TrustDialogueRef trust_prompt;
int start_count;
bool authenticated;
bool no_authenticator;
struct eap_client eap;
EAPOLSocketReceiveData last_rx_packet;
EAPClientStatus last_status;
EAPClientDomainSpecificError last_error;
bool logoff_sent;
bool debug;
bool no_ui;
};
typedef enum {
kSupplicantEventStart,
kSupplicantEventData,
kSupplicantEventTimeout,
kSupplicantEventUserResponse,
kSupplicantEventMoreDataAvailable,
} SupplicantEvent;
static bool
S_set_user_password(SupplicantRef supp);
static bool
is_link_active(SCDynamicStoreRef store, CFStringRef if_name_cf);
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);
static void
eap_client_free(SupplicantRef supp)
{
if (supp->eap.module != NULL) {
EAPClientModulePluginFree(supp->eap.module, &supp->eap.plugin_data);
supp->eap.module = NULL;
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
= EAPOLSocket_if_name(supp->sock, (uint32_t *)
&supp->eap.plugin_data.unique_id_length);
S_set_uint32(&supp->eap.plugin_data.mtu,
EAPOLSocket_mtu(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);
*((CFDictionaryRef *)&supp->eap.plugin_data.properties) = supp->config_dict;
*((bool *)&supp->eap.plugin_data.log_enabled) = supp->debug;
*((bool *)&supp->eap.plugin_data.system_mode) = supp->system_mode;
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);
*((CFDictionaryRef *)&supp->eap.plugin_data.properties)
= supp->config_dict;
*((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));
}
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);
}
static void
clear_password(SupplicantRef supp)
{
supp->ignore_password = TRUE;
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) {
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)
{
EAPOLSocket_disable_receive(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++;
props_length = length - (props_start - data);
if (length <= 0) {
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;
}
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;
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));
HMAC(EVP_md5(), server_key, server_key_length,
(const u_char *)packet_copy, packet_length,
signature, NULL);
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");
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");
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");
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) {
uint8_t * enc_key;
uint8_t * rc4_key_data;
int rc4_key_data_length;
RC4_KEY rc4_key;
enc_key = malloc(key_length);
rc4_key_data_length = session_key_length + sizeof(descr_p->key_IV);
rc4_key_data = malloc(rc4_key_data_length);
bcopy(descr_p->key_IV, rc4_key_data, sizeof(descr_p->key_IV));
bcopy(session_key, rc4_key_data + sizeof(descr_p->key_IV),
session_key_length);
RC4_set_key(&rc4_key, rc4_key_data_length, rc4_key_data);
RC4(&rc4_key, key_length, descr_p->key, enc_key);
EAPOLSocket_set_key(supp->sock, type,
descr_p->key_index & kEAPOLKeyDescriptorIndexMask,
enc_key, key_length);
free(rc4_key_data);
free(enc_key);
}
else {
EAPOLSocket_set_key(supp->sock, type,
descr_p->key_index & kEAPOLKeyDescriptorIndexMask,
session_key, key_length);
}
return;
}
static void
Supplicant_force_renew(SupplicantRef supp)
{
if (supp->client == NULL) {
return;
}
(void)EAPOLClientForceRenew(supp->client);
return;
}
static void
clear_wpa_key_info(SupplicantRef supp)
{
if (EAPOLSocket_set_wpa_session_key(supp->sock, NULL, 0) == FALSE) {
my_log(LOG_DEBUG, "clearing wpa session key failed");
}
return;
}
static void
set_wpa_key_info(SupplicantRef supp)
{
uint8_t * session_key;
int session_key_length;
session_key = eap_client_session_key(supp, &session_key_length);
if (session_key != NULL) {
if (EAPOLSocket_set_wpa_session_key(supp->sock, session_key,
session_key_length) == FALSE) {
my_log(LOG_DEBUG, "setting wpa session key failed");
}
}
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 (supp->authenticated == FALSE) {
Supplicant_force_renew(supp);
supp->authenticated = TRUE;
}
UserPasswordDialogue_free(&supp->pw_prompt);
if (supp->one_time_password) {
clear_password(supp);
}
Supplicant_report_status(supp);
EAPOLSocket_enable_receive(supp->sock,
(void *)Supplicant_authenticated,
(void *)supp,
(void *)kSupplicantEventData);
if (supp->eap.module != NULL && EAPOLSocket_is_wireless(supp->sock)) {
set_wpa_key_info(supp);
}
break;
case kSupplicantEventData:
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:
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 (EAPOLSocket_is_wireless(supp->sock)) {
process_key(supp, rx->eapol_p);
}
break;
default:
break;
}
break;
default:
break;
}
return;
}
static void
Supplicant_cleanup(SupplicantRef supp)
{
supp->logoff_sent = FALSE;
supp->previous_identifier = BAD_IDENTIFIER;
EAPAcceptTypesReset(&supp->eap_accept);
supp->last_status = kEAPClientStatusOK;
supp->last_error = 0;
supp->authenticated = FALSE;
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_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;
EAPOLSocket_enable_receive(supp->sock,
(void *)Supplicant_connecting,
(void *)supp,
(void *)kSupplicantEventData);
case kSupplicantEventData:
if (rx != NULL
&& rx->eapol_p->packet_type == kEAPOLPacketTypeEAPPacket) {
EAPRequestPacket * req_p = (void *)rx->eapol_p->body;
if (req_p->code == kEAPCodeRequest) {
if (req_p->type == kEAPTypeIdentity) {
Supplicant_acquired(supp,
kSupplicantEventStart,
evdata);
break;
}
Supplicant_authenticating(supp,
kSupplicantEventStart,
evdata);
}
break;
}
case kSupplicantEventTimeout:
if (supp->start_count == S_max_start) {
supp->no_authenticator = TRUE;
Supplicant_authenticated(supp, kSupplicantEventStart, NULL);
break;
}
EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeStart,
NULL, 0, NULL, TRUE);
supp->start_count++;
Timer_set_relative(supp->timer, t,
(void *)Supplicant_connecting,
(void *)supp,
(void *)kSupplicantEventTimeout,
NULL);
break;
default:
break;
}
}
static void
respond_with_identity(SupplicantRef supp, int identifier)
{
char buf[256];
char * identity;
int length;
EAPPacketRef pkt_p;
int size;
if (supp->identity != NULL) {
identity = supp->identity;
length = supp->identity_length;
}
else {
identity = supp->username;
length = supp->username_length;
}
pkt_p = EAPPacketCreate(buf, sizeof(buf),
kEAPCodeResponse, identifier,
kEAPTypeIdentity,
identity, length,
&size);
if (EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeEAPPacket,
pkt_p, size,
NULL, TRUE) < 0) {
my_log(LOG_NOTICE,
"EAPOL_transmit Identity failed");
}
if ((char *)pkt_p != buf) {
free(pkt_p);
}
return;
}
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;
EAPOLSocket_enable_receive(supp->sock,
(void *)Supplicant_acquired,
(void *)supp,
(void *)kSupplicantEventData);
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);
supp->previous_identifier = req_p->identifier;
if (supp->username != NULL) {
supp->last_status = kEAPClientStatusOK;
Supplicant_report_status(supp);
respond_with_identity(supp, req_p->identifier);
Timer_set_relative(supp->timer, t,
(void *)Supplicant_acquired,
(void *)supp,
(void *)kSupplicantEventTimeout,
NULL);
}
else {
supp->last_status = kEAPClientStatusUserInputRequired;
Supplicant_report_status(supp);
}
break;
}
else {
if (event == kSupplicantEventStart) {
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 (supp->username != NULL) {
supp->last_status = kEAPClientStatusOK;
Supplicant_report_status(supp);
respond_with_identity(supp, supp->previous_identifier);
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;
(void)EAPPacketCreate(¬if, sizeof(notif),
kEAPCodeResponse, identifier,
kEAPTypeNotification, NULL, 0, &size);
if (EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeEAPPacket,
¬if, sizeof(notif), NULL, TRUE) < 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;
(void)EAPPacketCreate(&nak, sizeof(nak),
kEAPCodeResponse, identifier,
kEAPTypeNak, NULL,
sizeof(nak) - sizeof(EAPRequestPacket), &size);
nak.desired_type = desired_type;
if (EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeEAPPacket,
&nak, sizeof(nak), NULL, TRUE) < 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) {
supp->last_status = kEAPClientStatusProtocolNotSupported;
Supplicant_held(supp, kSupplicantEventStart, NULL);
return;
}
respond_with_nak(supp, in_pkt_p->identifier,
eap_type);
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) {
my_log(LOG_NOTICE, "eap_client_init type %d failed",
req_p->type);
Supplicant_held(supp, kSupplicantEventStart, NULL);
return;
}
save_last_packet(supp, rx);
Supplicant_report_status(supp);
return;
}
Supplicant_report_status(supp);
}
break;
case kEAPCodeResponse:
if (req_p->type != eap_client_type(supp)) {
return;
}
break;
case kEAPCodeFailure:
if (supp->eap.module == NULL) {
supp->last_status = kEAPClientStatusFailed;
Supplicant_held(supp, kSupplicantEventStart, NULL);
return;
}
break;
case kEAPCodeSuccess:
if (supp->eap.module == NULL) {
Supplicant_authenticated(supp, kSupplicantEventStart, NULL);
return;
}
break;
default:
break;
}
if (supp->eap.module == NULL) {
return;
}
my_CFRelease(&supp->eap.required_props);
my_CFRelease(&supp->eap.published_props);
if (supp->debug && rx->logged == FALSE) {
if (EAPClientModulePluginPacketDump(supp->eap.module,
stdout, in_pkt_p) == FALSE) {
eapol_packet_print(rx->eapol_p, rx->length);
}
rx->logged = TRUE;
}
state = eap_client_process(supp, in_pkt_p, &out_pkt_p,
&supp->last_status, &supp->last_error);
if (out_pkt_p != NULL) {
if (EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeEAPPacket,
out_pkt_p,
EAPPacketGetLength(out_pkt_p),
NULL, FALSE) < 0) {
my_log(LOG_NOTICE, "process_packet: EAPOL_transmit %d failed",
out_pkt_p->code);
}
if (supp->debug) {
if (EAPClientModulePluginPacketDump(supp->eap.module,
stdout, out_pkt_p) == FALSE) {
eap_packet_print(out_pkt_p,
EAPPacketGetLength(out_pkt_p));
}
}
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->system_mode && supp->eap.required_props != NULL) {
supp->last_status = kEAPClientStatusUserInputNotPossible;
Supplicant_held(supp, kSupplicantEventStart, NULL);
}
else {
Supplicant_report_status(supp);
}
}
else {
Supplicant_report_status(supp);
}
break;
case kEAPClientStateSuccess:
Supplicant_authenticated(supp, kSupplicantEventStart, NULL);
break;
case kEAPClientStateFailure:
eap_client_log_failure(supp);
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 (EAPOLSocket_is_wireless(supp->sock)) {
clear_wpa_key_info(supp);
}
supp->state = kSupplicantStateAuthenticating;
Supplicant_report_status(supp);
Timer_cancel(supp->timer);
EAPOLSocket_enable_receive(supp->sock,
(void *)Supplicant_authenticating,
(void *)supp,
(void *)kSupplicantEventData);
case kSupplicantEventData:
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) {
break;
}
if (event == kSupplicantEventStart) {
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) {
break;
}
my_log(LOG_NOTICE, "Authenticating: Notification '%.*s'",
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;
}
break;
default:
break;
}
break;
case kSupplicantEventTimeout:
Supplicant_connecting(supp, kSupplicantEventStart, NULL);
break;
default:
break;
}
return;
}
static void
dictInsertEAPTypeInfo(CFMutableDictionaryRef dict, EAPType type,
const char * type_name)
{
CFNumberRef eap_type_cf;
int eap_type = type;
if (type == kEAPTypeInvalid) {
return;
}
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);
}
eap_type_cf = CFNumberCreate(NULL, kCFNumberIntType, &eap_type);
CFDictionarySetValue(dict, kEAPOLControlEAPType, eap_type_cf);
my_CFRelease(&eap_type_cf);
return;
}
static void
dictInsertSupplicantState(CFMutableDictionaryRef dict, SupplicantState state)
{
CFNumberRef supp_state_cf;
supp_state_cf = CFNumberCreate(NULL, kCFNumberIntType, &state);
CFDictionarySetValue(dict, kEAPOLControlSupplicantState, supp_state_cf);
my_CFRelease(&supp_state_cf);
return;
}
static void
dictInsertClientStatus(CFMutableDictionaryRef dict,
EAPClientStatus status, int error)
{
CFNumberRef num_cf;
num_cf = CFNumberCreate(NULL, kCFNumberIntType, &status);
CFDictionarySetValue(dict, kEAPOLControlClientStatus, num_cf);
my_CFRelease(&num_cf);
num_cf = CFNumberCreate(NULL, kCFNumberIntType, &error);
CFDictionarySetValue(dict, kEAPOLControlDomainSpecificError, num_cf);
my_CFRelease(&num_cf);
return;
}
static void
dictInsertRequiredProperties(CFMutableDictionaryRef dict,
CFArrayRef required_props)
{
if (required_props == NULL) {
CFMutableArrayRef array;
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
Supplicant_stop(SupplicantRef supp)
{
my_log(LOG_NOTICE, "%s STOP",
EAPOLSocket_if_name(supp->sock, NULL));
eap_client_free(supp);
Supplicant_logoff(supp, kSupplicantEventStart, NULL);
Supplicant_free(&supp);
fflush(stdout);
fflush(stderr);
exit(EX_OK);
return;
}
static void
user_supplied_data(SupplicantRef supp)
{
switch (supp->state) {
case kSupplicantStateAcquired:
Supplicant_acquired(supp,
kSupplicantEventMoreDataAvailable,
NULL);
break;
case kSupplicantStateAuthenticating:
if (supp->last_rx_packet.eapol_p != NULL) {
supp->last_rx_packet.logged = TRUE;
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;
}
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",
EAPOLSocket_if_name(supp->sock, NULL));
EAPOLControlStop(EAPOLSocket_if_name(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);
}
}
if (supp->orig_config_dict != NULL) {
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",
EAPOLSocket_if_name(supp->sock, NULL));
EAPOLControlStop(EAPOLSocket_if_name(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;
}
supp->one_time_password = response->one_time_password;
if (supp->orig_config_dict != NULL) {
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));
}
static void
Supplicant_report_status(SupplicantRef supp)
{
CFMutableDictionaryRef dict;
Boolean need_username = FALSE;
Boolean need_password = FALSE;
Boolean need_trust = FALSE;
int result;
CFDateRef timestamp = NULL;
if (supp->client == NULL) {
return;
}
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (supp->system_mode) {
CFDictionarySetValue(dict, kEAPOLControlSystemMode, kCFBooleanTrue);
}
if (supp->config_id != NULL) {
CFDictionarySetValue(dict, kEAPOLControlUniqueIdentifier,
supp->config_id);
}
dictInsertSupplicantState(dict, supp->state);
if (supp->no_authenticator) {
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);
need_username = TRUE;
}
else {
dictInsertRequiredProperties(dict, supp->eap.required_props);
need_password
= my_CFArrayContainsValue(supp->eap.required_props,
kEAPClientPropUserPassword);
need_trust
= my_CFArrayContainsValue(supp->eap.required_props,
kEAPClientPropTLSUserTrustProceedCertificateChain);
}
}
dictInsertPublishedProperties(dict, supp->eap.published_props);
dictInsertIdentityAttributes(dict, supp->identity_attributes);
}
timestamp = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
CFDictionarySetValue(dict, kEAPOLControlTimestamp, timestamp);
my_CFRelease(×tamp);
result = EAPOLClientReportStatus(supp->client, dict);
if (result != 0) {
my_log(LOG_NOTICE, "EAPLClientReportStatus failed: %s",
strerror(result));
}
my_CFRelease(&dict);
if (supp->no_ui) {
goto no_ui;
}
if (need_username || need_password) {
if (supp->pw_prompt == NULL) {
CFStringRef password;
CFStringRef username;
username = CFDictionaryGetValue(supp->config_dict,
kEAPClientPropUserName);
password = CFDictionaryGetValue(supp->config_dict,
kEAPClientPropUserPassword);
supp->pw_prompt
= UserPasswordDialogue_create(password_callback, supp,
NULL, NULL,
username, password,
supp->one_time_password);
}
}
if (need_trust) {
if (supp->trust_prompt == NULL) {
CFStringRef copy;
copy = CFCopyLocalizedString(CFSTR("802.1X Authentication"),
"The label");
supp->trust_prompt
= TrustDialogue_create(trust_callback, supp, NULL,
supp->eap.published_props,
copy != NULL
? copy
: CFSTR("802.1X Authentication"));
my_CFRelease(©);
}
}
no_ui:
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:
Supplicant_cancel_pending_events(supp);
supp->state = kSupplicantStateHeld;
supp->authenticated = FALSE;
Supplicant_force_renew(supp);
Supplicant_report_status(supp);
supp->previous_identifier = BAD_IDENTIFIER;
EAPAcceptTypesReset(&supp->eap_accept);
if (supp->eap.module != NULL && supp->system_mode == FALSE
&& supp->last_status == kEAPClientStatusFailed) {
clear_password(supp);
}
supp->last_status = kEAPClientStatusOK;
supp->last_error = 0;
free_last_packet(supp);
eap_client_free(supp);
UserPasswordDialogue_free(&supp->pw_prompt);
TrustDialogue_free(&supp->trust_prompt);
EAPOLSocket_enable_receive(supp->sock,
(void *)Supplicant_held,
(void *)supp,
(void *)kSupplicantEventData);
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:
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)
{
my_log(LOG_NOTICE, "%s START",
EAPOLSocket_if_name(supp->sock, NULL));
if (is_link_active(supp->store, supp->if_name_cf)) {
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->authenticated = FALSE;
supp->no_authenticator = TRUE;
Supplicant_report_status(supp);
EAPOLSocket_enable_receive(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);
if (supp->logoff_sent == FALSE) {
EAPOLSocket_transmit(supp->sock,
kEAPOLPacketTypeLogoff,
NULL, 0, NULL, TRUE);
supp->logoff_sent = TRUE;
Supplicant_report_status(supp);
Supplicant_force_renew(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);
}
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);
}
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 (use_system_keychain) {
status = mySecKeychainCopySystemKeychain(&keychain);
if (status != noErr) {
goto done;
}
}
status = EAPSecKeychainPasswordItemCopy(keychain, unique_id_str,
&password_data);
if (status != noErr) {
my_log(LOG_NOTICE, "SecKeychainFindGenericPassword failed, %ld",
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);
}
name_cf = CFDictionaryGetValue(supp->config_dict, kEAPClientPropUserName);
name_cf = isA_CFString(name_cf);
if (name_cf != NULL) {
name = my_CFStringToCString(name_cf, kCFStringEncodingUTF8);
}
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;
}
supp->one_time_password =
myCFDictionaryGetBooleanValue(supp->config_dict,
kEAPClientPropOneTimeUserPassword,
supp->one_time_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) {
password = S_copy_password_from_keychain(supp->system_mode,
item_cf);
if (password == NULL) {
my_log(LOG_NOTICE,
"%s: failed to retrieve password from keychain",
EAPOLSocket_if_name(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;
}
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;
}
if (supp->username == NULL) {
supp->username = eap_method_user_name(&supp->eap_accept,
supp->config_dict);
if (supp->username != NULL) {
supp->username_length = strlen(supp->username);
}
}
return (change);
}
bool
Supplicant_attached(SupplicantRef supp)
{
return (supp->client != NULL);
}
static bool
get_control_command(CFNumberRef command_cf, EAPOLClientControlCommand * command)
{
if (isA_CFNumber(command_cf) == NULL
|| CFNumberGetValue(command_cf, kCFNumberIntType,
command) == FALSE) {
return (FALSE);
}
return (TRUE);
}
static void
dict_merge_key_value(const void * key, const void * value, void * context)
{
CFMutableDictionaryRef new_dict = (CFMutableDictionaryRef)context;
CFDictionaryAddValue(new_dict, key, value);
return;
}
static CFDictionaryRef
myCFDictionaryCreateMerge(CFDictionaryRef base, CFDictionaryRef additional)
{
CFDictionaryRef dict;
CFMutableDictionaryRef new_dict;
if (base == NULL && additional == NULL) {
return (NULL);
}
if (base == NULL) {
return (CFDictionaryCreateCopy(NULL, additional));
}
else if (additional == NULL) {
return (CFDictionaryCreateCopy(NULL, base));
}
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, base);
CFDictionaryApplyFunction(additional, dict_merge_key_value,
new_dict);
dict = CFDictionaryCreateCopy(NULL, new_dict);
my_CFRelease(&new_dict);
return (dict);
}
static CFDictionaryRef
create_config_dict(SupplicantRef supp, CFDictionaryRef new_config)
{
CFDictionaryRef dict;
CFDictionaryRef tmp_dict;
tmp_dict = myCFDictionaryCreateMerge(new_config,
supp->ui_config_dict);
dict = myCFDictionaryCreateMerge(tmp_dict, supp->default_dict);
my_CFRelease(&tmp_dict);
return (dict);
}
static bool
cfstring_is_empty(CFStringRef str)
{
if (str == NULL) {
return (FALSE);
}
return (isA_CFString(str) == NULL || CFStringGetLength(str) == 0);
}
static CFDictionaryRef
clean_user_password(CFDictionaryRef dict)
{
CFMutableDictionaryRef new_dict;
bool remove_password = FALSE;
bool remove_user = FALSE;
if (dict == NULL) {
return (NULL);
}
remove_user
= cfstring_is_empty(CFDictionaryGetValue(dict,
kEAPClientPropUserName));
remove_password
= cfstring_is_empty(CFDictionaryGetValue(dict,
kEAPClientPropUserPassword));
if (remove_user == FALSE && remove_password == FALSE) {
return (CFRetain(dict));
}
new_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
if (remove_user) {
CFDictionaryRemoveValue(new_dict, kEAPClientPropUserName);
}
if (remove_password) {
CFDictionaryRemoveValue(new_dict, kEAPClientPropUserPassword);
}
dict = CFDictionaryCreateCopy(NULL, new_dict);
my_CFRelease(&new_dict);
return (dict);
}
bool
Supplicant_update_configuration(SupplicantRef supp, CFDictionaryRef config_dict)
{
bool change = FALSE;
CFStringRef config_id;
CFDictionaryRef eap_config = NULL;
my_CFRelease(&supp->orig_config_dict);
supp->orig_config_dict = CFDictionaryCreateCopy(NULL, config_dict);
if (config_dict != NULL) {
eap_config = CFDictionaryGetValue(config_dict,
kEAPOLControlEAPClientConfiguration);
}
if (isA_CFDictionary(eap_config) == NULL) {
eap_config = config_dict;
}
supp->generation++;
my_CFRelease(&supp->config_dict);
eap_config = clean_user_password(eap_config);
supp->config_dict = create_config_dict(supp, eap_config);
my_CFRelease(&eap_config);
my_CFRelease(&supp->config_id);
if (config_dict != NULL) {
config_id = CFDictionaryGetValue(config_dict,
kEAPOLControlUniqueIdentifier);
if (isA_CFString(config_id) != NULL) {
supp->config_id = CFRetain(config_id);
}
if (CFDictionaryGetValue(config_dict,
kEAPOLControlLogLevel) != NULL
|| CFDictionaryGetValue(config_dict, CFSTR("_debug")) != NULL) {
Supplicant_set_debug(supp, TRUE);
}
else {
Supplicant_set_debug(supp, FALSE);
}
}
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) {
eap_client_free(supp);
change = TRUE;
}
return (change);
}
static void
eapolclient_notification(EAPOLClientRef client, Boolean server_died,
void * context)
{
bool change;
EAPOLClientControlCommand command;
CFNumberRef command_cf;
CFDictionaryRef config_dict = NULL;
CFDictionaryRef control_dict = NULL;
int result;
SupplicantRef supp = (SupplicantRef)context;
if (server_died) {
my_log(LOG_NOTICE, "Supplicant(%s): server died",
EAPOLSocket_if_name(supp->sock, NULL));
goto stop;
}
result = EAPOLClientGetConfig(client, &control_dict);
if (result != 0) {
my_log(LOG_NOTICE, "%s: EAPOLClientGetConfig failed, %s",
EAPOLSocket_if_name(supp->sock, NULL), strerror(result));
goto stop;
}
if (control_dict == NULL) {
my_log(LOG_NOTICE, "%s: EAPOLClientGetConfig returned NULL control",
EAPOLSocket_if_name(supp->sock, NULL));
goto stop;
}
command_cf = CFDictionaryGetValue(control_dict,
kEAPOLClientControlCommand);
if (get_control_command(command_cf, &command) == FALSE) {
my_log(LOG_NOTICE, "%s: invalid/missing command",
EAPOLSocket_if_name(supp->sock, NULL));
goto stop;
}
switch (command) {
case kEAPOLClientControlCommandRetry:
if (supp->state != kSupplicantStateInactive) {
Supplicant_connecting(supp, kSupplicantEventStart, NULL);
}
break;
case kEAPOLClientControlCommandRun:
config_dict = CFDictionaryGetValue(control_dict,
kEAPOLClientControlConfiguration);
if (config_dict == NULL) {
goto stop;
}
change = Supplicant_update_configuration(supp, config_dict);
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) {
supp->last_rx_packet.logged = TRUE;
process_packet(supp, &supp->last_rx_packet);
}
break;
default:
break;
}
}
if (change) {
Supplicant_disconnected(supp, kSupplicantEventStart, NULL);
}
break;
case kEAPOLClientControlCommandStop:
default:
goto stop;
}
my_CFRelease(&control_dict);
if (supp->debug) {
fflush(stdout);
fflush(stderr);
}
return;
stop:
Supplicant_stop(supp);
return;
}
static bool
is_link_active(SCDynamicStoreRef store, CFStringRef if_name_cf)
{
bool active = TRUE;
CFDictionaryRef dict;
CFStringRef key;
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
if_name_cf,
kSCEntNetLink);
dict = SCDynamicStoreCopyValue(store, key);
if (isA_CFDictionary(dict) != NULL) {
CFBooleanRef active_cf;
active_cf = CFDictionaryGetValue(dict, kSCPropNetLinkActive);
if (isA_CFBoolean(active_cf) != NULL) {
active = CFBooleanGetValue(active_cf);
}
}
my_CFRelease(&dict);
my_CFRelease(&key);
return (active);
}
static void
link_status_changed(SupplicantRef supp)
{
bool active;
struct timeval t = {0, 0};
active = is_link_active(supp->store, supp->if_name_cf);
if (active) {
t.tv_sec = S_link_active_period_secs;
Timer_set_relative(supp->timer, t,
(void *)Supplicant_connecting,
(void *)supp,
(void *)kSupplicantEventStart,
NULL);
}
else {
t.tv_sec = S_link_inactive_period_secs;
Timer_set_relative(supp->timer, t,
(void *)Supplicant_inactive,
(void *)supp,
(void *)kSupplicantEventStart,
NULL);
}
return;
}
static void
link_changed(SCDynamicStoreRef store, CFArrayRef changes, void * arg)
{
int count = 0;
Boolean link_changed = FALSE;
SupplicantRef supp = (SupplicantRef)arg;
if (changes != NULL) {
count = CFArrayGetCount(changes);
}
if (count == 0) {
return;
}
if (EAPOLSocket_is_wireless(supp->sock) == FALSE) {
link_changed = TRUE;
}
else {
Boolean airport_changed = FALSE;
int i;
for (i = 0; i < count; i++) {
CFStringRef key = CFArrayGetValueAtIndex(changes, i);
if (CFStringHasSuffix(key, kSCEntNetAirPort)) {
airport_changed = TRUE;
}
else {
link_changed = TRUE;
}
}
if (airport_changed) {
Boolean bssid_changed;
bssid_changed = EAPOLSocket_link_update(supp->sock);
if (bssid_changed) {
link_changed = TRUE;
}
}
else {
EAPOLSocket_link_update(supp->sock);
}
}
if (link_changed) {
link_status_changed(supp);
}
return;
}
static SCDynamicStoreRef
link_event_register(CFStringRef if_name_cf, Boolean is_wireless,
SCDynamicStoreCallBack func, void * arg)
{
CFMutableArrayRef keys = NULL;
CFStringRef key;
CFRunLoopSourceRef rls;
SCDynamicStoreRef store;
SCDynamicStoreContext context;
bzero(&context, sizeof(context));
context.info = arg;
store = SCDynamicStoreCreate(NULL, CFSTR("802.1x Supplicant"),
func, &context);
if (store == NULL) {
my_log(LOG_NOTICE, "SCDynamicStoreCreate() failed, %s",
SCErrorString(SCError()));
return (NULL);
}
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
if_name_cf,
kSCEntNetLink);
CFArrayAppendValue(keys, key);
my_CFRelease(&key);
if (is_wireless) {
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainState,
if_name_cf,
kSCEntNetAirPort);
CFArrayAppendValue(keys, key);
my_CFRelease(&key);
}
SCDynamicStoreSetNotificationKeys(store, keys, NULL);
my_CFRelease(&keys);
rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
my_CFRelease(&rls);
return (store);
}
static CFDictionaryRef
copy_default_dict(void)
{
CFBundleRef bundle;
uint8_t config_file[MAXPATHLEN];
CFDictionaryRef dict = NULL;
Boolean ok;
CFURLRef url;
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;
}
dict = my_CFPropertyListCreateFromFile((char *)config_file);
if (dict != NULL) {
if (isA_CFDictionary(dict) == NULL || CFDictionaryGetCount(dict) == 0) {
my_CFRelease(&dict);
}
}
done:
return (dict);
}
SupplicantRef
Supplicant_create(int fd, const struct sockaddr_dl * link, bool system_mode)
{
EAPOLClientRef client = NULL;
CFDictionaryRef control_dict = NULL;
CFDictionaryRef config_dict = NULL;
CFDictionaryRef default_dict = NULL;
CFStringRef if_name_cf = NULL;
int result;
SCDynamicStoreRef store = NULL;
EAPOLSocket * sock;
SupplicantRef supp = NULL;
Timer * timer = NULL;
sock = EAPOLSocket_create(fd, link);
if (sock == NULL) {
my_log(LOG_NOTICE, "Supplicant_create: EAPOLSocket_create failed");
goto failed;
}
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->system_mode = system_mode;
supp->timer = timer;
supp->sock = sock;
if_name_cf
= CFStringCreateWithCString(NULL,
EAPOLSocket_if_name(supp->sock, NULL),
kCFStringEncodingASCII);
supp->if_name_cf = if_name_cf;
store = link_event_register(supp->if_name_cf,
EAPOLSocket_is_wireless(supp->sock),
link_changed, supp);
supp->store = store;
client = EAPOLClientAttach(EAPOLSocket_if_name(supp->sock, NULL),
eapolclient_notification, supp,
&control_dict, &result);
supp->client = client;
default_dict = copy_default_dict();
supp->default_dict = default_dict;
if (client == NULL) {
my_log(LOG_NOTICE, "EAPOLClientAttach(%s) failed: %s",
EAPOLSocket_if_name(supp->sock, NULL), strerror(result));
}
else {
EAPOLClientControlCommand command;
CFNumberRef command_cf;
if (control_dict == NULL) {
my_log(LOG_NOTICE, "%s: control dictionary missing",
EAPOLSocket_if_name(supp->sock, NULL));
goto failed;
}
command_cf = CFDictionaryGetValue(control_dict,
kEAPOLClientControlCommand);
if (get_control_command(command_cf, &command) == FALSE) {
goto failed;
}
if (command != kEAPOLClientControlCommandRun) {
my_log(LOG_NOTICE, "%s: received stop command",
EAPOLSocket_if_name(supp->sock, NULL));
goto failed;
}
config_dict = CFDictionaryGetValue(control_dict,
kEAPOLClientControlConfiguration);
if (config_dict == NULL) {
my_log(LOG_NOTICE, "%s: configuration empty - exiting",
EAPOLSocket_if_name(supp->sock, NULL));
goto failed;
}
Supplicant_update_configuration(supp, config_dict);
}
my_CFRelease(&control_dict);
return (supp);
failed:
my_CFRelease(&if_name_cf);
if (supp != NULL) {
free(supp);
}
my_CFRelease(&store);
EAPOLClientDetach(&client);
Timer_free(&timer);
EAPOLSocket_free(&sock);
my_CFRelease(&control_dict);
my_CFRelease(&default_dict);
return (NULL);
}
void
Supplicant_free(SupplicantRef * supp_p)
{
SupplicantRef supp;
if (supp_p == NULL) {
return;
}
supp = *supp_p;
if (supp) {
UserPasswordDialogue_free(&supp->pw_prompt);
TrustDialogue_free(&supp->trust_prompt);
my_CFRelease(&supp->store);
Timer_free(&supp->timer);
EAPOLSocket_free(&supp->sock);
EAPOLClientDetach(&supp->client);
my_CFRelease(&supp->orig_config_dict);
my_CFRelease(&supp->config_dict);
my_CFRelease(&supp->ui_config_dict);
my_CFRelease(&supp->config_id);
free_last_packet(supp);
eap_client_free(supp);
my_CFRelease(&supp->if_name_cf);
my_CFRelease(&supp->default_dict);
free(supp);
}
*supp_p = NULL;
return;
}
void
Supplicant_set_debug(SupplicantRef supp, bool debug)
{
supp->debug = debug;
EAPOLSocketSetDebug(debug);
return;
}
void
Supplicant_set_no_ui(SupplicantRef supp)
{
supp->no_ui = TRUE;
return;
}