EAPSIMAKAPersistentState.c [plain text]
#include <CoreFoundation/CFPreferences.h>
#include <notify.h>
#include <SystemConfiguration/SCValidation.h>
#include "symbol_scope.h"
#include "myCFUtil.h"
#include "EAP.h"
#include "EAPKeychainUtil.h"
#include "EAPLog.h"
#include "EAPSIMAKAPersistentState.h"
#include "EAPOLSIMPrefsManage.h"
#define kEAPSIMAKAPrefsChangedNotification \
"com.apple.network.eapclient.eapsimaka.prefs"
typedef Boolean
(*IMSIMatchFuncRef)(CFStringRef imsi, CFDictionaryRef info,
const void * context);
STATIC void
IMSIListRemoveMatches(EAPType type, IMSIMatchFuncRef iter,
const void * context);
STATIC void
EAPSIMAKAPersistentStatePurgePrefs(void);
STATIC Boolean
prefs_did_change(uint32_t * gen_p)
{
Boolean changed;
int check = 0;
uint32_t notify_status;
STATIC uint32_t S_generation;
STATIC int S_token;
STATIC Boolean S_token_valid = FALSE;
changed = FALSE;
if (!S_token_valid) {
notify_status =
notify_register_check(kEAPSIMAKAPrefsChangedNotification,
&S_token);
if (notify_status != NOTIFY_STATUS_OK) {
EAPLOG_FL(LOG_NOTICE,
"notify_register_check returned %d",
notify_status);
goto done;
}
S_token_valid = TRUE;
}
notify_status = notify_check(S_token, &check);
if (notify_status != NOTIFY_STATUS_OK) {
EAPLOG_FL(LOG_NOTICE, "notify_check returned %d",
notify_status);
goto done;
}
if (check != 0) {
S_generation++;
}
if (*gen_p != S_generation) {
changed = TRUE;
}
*gen_p = S_generation;
done:
return (changed);
}
STATIC void
prefs_notify_change(void)
{
uint32_t status;
status = notify_post(kEAPSIMAKAPrefsChangedNotification);
if (status != NOTIFY_STATUS_OK) {
EAPLOG_FL(LOG_NOTICE, "notify_post returned %d", status);
}
return;
}
STATIC CFStringRef
MasterKeyItemIDCreate(CFStringRef proto, CFStringRef imsi)
{
return CFStringCreateWithFormat(NULL,
NULL,
CFSTR("com.apple.network.%@.master-key.%@"),
proto,
imsi);
}
STATIC CFDataRef
MasterKeyCopyFromKeychain(CFStringRef proto, CFStringRef imsi)
{
CFDataRef data = NULL;
CFStringRef key;
OSStatus result;
key = MasterKeyItemIDCreate(proto, imsi);
result = EAPSecKeychainPasswordItemCopy(NULL, key, &data);
CFRelease(key);
if (result != noErr && result != errSecItemNotFound) {
EAPLOG_FL(LOG_NOTICE, "Failed to read a keychain item: %d",
(int)result);
return NULL;
}
return (data);
}
STATIC void
MasterKeySaveToKeychain(CFStringRef proto, CFStringRef imsi, CFDataRef data)
{
CFStringRef key;
OSStatus result;
key = MasterKeyItemIDCreate(proto, imsi);
result = EAPSecKeychainPasswordItemSet(NULL, key, data);
if (result != noErr) {
result = EAPSecKeychainPasswordItemCreateWithAccess(NULL,
NULL,
key,
NULL,
NULL,
NULL,
data);
}
CFRelease(key);
if (result != noErr) {
EAPLOG_FL(LOG_NOTICE, "Failed to update/create a keychain item: %d",
(int)result);
}
return;
}
STATIC void
MasterKeyRemoveFromKeychain(CFStringRef proto, CFStringRef imsi)
{
CFStringRef key;
key = MasterKeyItemIDCreate(proto, imsi);
EAPSecKeychainPasswordItemRemove(NULL, key);
CFRelease(key);
return;
}
#define kEAPSIMAppIDStr "com.apple.network.eapclient.eapsim"
#define kEAPAKAAppIDStr "com.apple.network.eapclient.eapaka"
typedef struct {
const CFStringRef appID;
const CFStringRef proto;
uint32_t generation;
} ProtoInfo, * ProtoInfoRef;
STATIC ProtoInfo S_eapsim_info = {
CFSTR(kEAPSIMAppIDStr), CFSTR("eapsim"), 0,
};
STATIC ProtoInfo S_eapaka_info = {
CFSTR(kEAPAKAAppIDStr), CFSTR("eapaka"), 0,
};
STATIC ProtoInfoRef
ProtoInfoForType(EAPType type)
{
ProtoInfoRef info;
switch (type) {
case kEAPTypeEAPSIM:
info = &S_eapsim_info;
break;
case kEAPTypeEAPAKA:
info = &S_eapaka_info;
break;
default:
EAPLOG_FL(LOG_NOTICE, "unrecognized type %d", type);
info = NULL;
break;
}
return (info);
}
STATIC void
ProtoInfoChangedCheck(ProtoInfoRef proto_info)
{
if (prefs_did_change(&proto_info->generation)) {
CFPreferencesSynchronize(proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
}
return;
}
STATIC void
ProtoInfoNotifyChange(ProtoInfoRef proto_info)
{
prefs_notify_change();
return;
}
STATIC CFStringRef kPrefsPseudonym = CFSTR("Pseudonym");
STATIC CFStringRef kPrefsPseudonymStartTime = CFSTR("PseudonymStartTime");
STATIC CFStringRef kPrefsReauthCounter = CFSTR("ReauthCounter");
STATIC CFStringRef kPrefsReauthID = CFSTR("ReauthID");
STATIC CFStringRef kPrefsSSID = CFSTR("SSID");
STATIC CFStringRef kPrefsGenID = CFSTR("GenerationID");
struct EAPSIMAKAPersistentState {
EAPType type;
EAPSIMAKAAttributeType identity_type;
CFStringRef imsi;
uint32_t generation_id;
uint16_t counter;
CFStringRef pseudonym;
CFDateRef pseudonym_start_time;
CFStringRef reauth_id;
ProtoInfoRef proto_info;
int master_key_size;
uint8_t master_key[1];
};
INLINE unsigned int
EAPSIMAKAPersistentStateComputeSize(unsigned int n)
{
return ((int)offsetof(struct EAPSIMAKAPersistentState, master_key[n]));
}
PRIVATE_EXTERN uint8_t *
EAPSIMAKAPersistentStateGetMasterKey(EAPSIMAKAPersistentStateRef persist)
{
return (persist->master_key);
}
PRIVATE_EXTERN int
EAPSIMAKAPersistentStateGetMasterKeySize(EAPSIMAKAPersistentStateRef persist)
{
return (persist->master_key_size);
}
PRIVATE_EXTERN CFStringRef
EAPSIMAKAPersistentStateGetIMSI(EAPSIMAKAPersistentStateRef persist)
{
return (persist->imsi);
}
PRIVATE_EXTERN CFStringRef
EAPSIMAKAPersistentStateGetPseudonym(EAPSIMAKAPersistentStateRef persist, CFDateRef * start_time)
{
if (start_time != NULL) {
*start_time = persist->pseudonym_start_time;
}
return (persist->pseudonym);
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateSetPseudonym(EAPSIMAKAPersistentStateRef persist,
CFStringRef pseudonym)
{
if (persist->identity_type == kAT_PERMANENT_ID_REQ) {
return;
}
my_FieldSetRetainedCFType(&persist->pseudonym, pseudonym);
return;
}
PRIVATE_EXTERN CFStringRef
EAPSIMAKAPersistentStateGetReauthID(EAPSIMAKAPersistentStateRef persist)
{
return (persist->reauth_id);
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateSetReauthID(EAPSIMAKAPersistentStateRef persist,
CFStringRef reauth_id)
{
if (persist->identity_type == kAT_ANY_ID_REQ) {
my_FieldSetRetainedCFType(&persist->reauth_id, reauth_id);
}
return;
}
PRIVATE_EXTERN uint16_t
EAPSIMAKAPersistentStateGetCounter(EAPSIMAKAPersistentStateRef persist)
{
return (persist->counter);
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateSetCounter(EAPSIMAKAPersistentStateRef persist,
uint16_t counter)
{
persist->counter = counter;
return;
}
PRIVATE_EXTERN EAPSIMAKAPersistentStateRef
EAPSIMAKAPersistentStateCreate(EAPType type, int master_key_size,
CFStringRef imsi,
EAPSIMAKAAttributeType identity_type)
{
EAPSIMAKAPersistentStateRef persist = NULL;
ProtoInfoRef proto_info;
if (imsi == NULL) {
EAPLOG_FL(LOG_NOTICE, "imsi is NULL");
goto done;
}
proto_info = ProtoInfoForType(type);
if (proto_info == NULL) {
goto done;
}
persist = (EAPSIMAKAPersistentStateRef)
malloc(EAPSIMAKAPersistentStateComputeSize(master_key_size));
bzero(persist, sizeof(*persist));
persist->type = type;
persist->imsi = CFRetain(imsi);
persist->proto_info = proto_info;
persist->master_key_size = master_key_size;
persist->identity_type = identity_type;
persist->generation_id = EAPOLSIMGenerationGet();
if (identity_type != kAT_PERMANENT_ID_REQ) {
CFPropertyListRef info;
CFStringRef pseudonym = NULL;
uint32_t mobile_gen_id;
CFNumberRef num_gen_id = NULL;
ProtoInfoChangedCheck(proto_info);
info = CFPreferencesCopyValue(imsi,
proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (isA_CFDictionary(info) != NULL) {
CFDictionaryRef dict = (CFDictionaryRef)info;
num_gen_id = isA_CFNumber(CFDictionaryGetValue(dict,
kPrefsGenID));
if (num_gen_id != NULL) {
CFNumberGetValue(num_gen_id, kCFNumberSInt32Type,
(void *)&mobile_gen_id);
} else {
mobile_gen_id = 0;
}
if (persist->generation_id != mobile_gen_id) {
my_CFRelease(&info);
EAPSIMAKAPersistentStatePurgePrefs();
goto done;
}
pseudonym
= isA_CFString(CFDictionaryGetValue(dict, kPrefsPseudonym));
if (pseudonym == NULL) {
pseudonym
= isA_CFString(CFDictionaryGetValue(dict,
CFSTR("PseudonymID")));
}
if (pseudonym != NULL) {
persist->pseudonym_start_time
= CFDictionaryGetValue(dict,
kPrefsPseudonymStartTime);
}
if (identity_type == kAT_ANY_ID_REQ) {
CFNumberRef counter;
CFDataRef master_key;
CFStringRef reauth_id;
counter
= isA_CFNumber(CFDictionaryGetValue(dict,
kPrefsReauthCounter));
reauth_id =
isA_CFString(CFDictionaryGetValue(dict, kPrefsReauthID));
master_key
= MasterKeyCopyFromKeychain(proto_info->proto, imsi);
if (counter != NULL
&& reauth_id != NULL
&& master_key != NULL
&& CFDataGetLength(master_key) == master_key_size) {
uint16_t val;
CFDataGetBytes(master_key,
CFRangeMake(0, master_key_size),
persist->master_key);
CFNumberGetValue(counter, kCFNumberSInt16Type,
(void *)&val);
EAPSIMAKAPersistentStateSetCounter(persist, val);
EAPSIMAKAPersistentStateSetReauthID(persist, reauth_id);
}
my_CFRelease(&master_key);
}
}
else if (isA_CFString(info) != NULL) {
pseudonym = (CFStringRef)info;
}
if (pseudonym != NULL) {
EAPSIMAKAPersistentStateSetPseudonym(persist, pseudonym);
}
my_CFRelease(&info);
}
done:
return (persist);
}
STATIC Boolean
IMSIDoesNotMatch(CFStringRef imsi, CFDictionaryRef info,
const void * context)
{
CFStringRef imsi_to_check = (CFStringRef)context;
return (my_CFEqual(imsi, imsi_to_check) == FALSE);
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateSave(EAPSIMAKAPersistentStateRef persist,
Boolean master_key_valid,
CFStringRef ssid)
{
CFMutableDictionaryRef info = NULL;
if (persist->identity_type == kAT_PERMANENT_ID_REQ) {
return;
}
IMSIListRemoveMatches(persist->type, IMSIDoesNotMatch, persist->imsi);
if (EAPSIMAKAPersistentStateGetPseudonym(persist, NULL) != NULL) {
CFDateRef pseudonym_start_time = NULL;
info = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(info, kPrefsPseudonym,
EAPSIMAKAPersistentStateGetPseudonym(persist, NULL));
pseudonym_start_time = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
if (pseudonym_start_time != NULL) {
CFDictionarySetValue(info, kPrefsPseudonymStartTime, pseudonym_start_time);
CFRelease(pseudonym_start_time);
}
}
if (EAPSIMAKAPersistentStateGetReauthID(persist) != NULL
&& master_key_valid) {
CFNumberRef counter;
CFDataRef master_key;
if (info == NULL) {
info = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
}
counter = CFNumberCreate(NULL, kCFNumberSInt16Type,
(const void *)&persist->counter);
CFDictionarySetValue(info, kPrefsReauthCounter, counter);
CFRelease(counter);
CFDictionarySetValue(info, kPrefsReauthID,
EAPSIMAKAPersistentStateGetReauthID(persist));
master_key
= CFDataCreate(NULL,
EAPSIMAKAPersistentStateGetMasterKey(persist),
EAPSIMAKAPersistentStateGetMasterKeySize(persist));
MasterKeySaveToKeychain(persist->proto_info->proto, persist->imsi,
master_key);
CFRelease(master_key);
}
if (info != NULL && ssid != NULL) {
CFNumberRef generation_id = NULL;
CFDictionarySetValue(info, kPrefsSSID, ssid);
generation_id = CFNumberCreate(NULL, kCFNumberSInt32Type,
(const void *)&persist->generation_id);
CFDictionarySetValue(info, kPrefsGenID, generation_id);
CFRelease(generation_id);
}
ProtoInfoChangedCheck(persist->proto_info);
CFPreferencesSetValue(persist->imsi, info,
persist->proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
my_CFRelease(&info);
CFPreferencesSynchronize(persist->proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
ProtoInfoNotifyChange(persist->proto_info);
return;
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateRelease(EAPSIMAKAPersistentStateRef persist)
{
my_CFRelease(&persist->imsi);
EAPSIMAKAPersistentStateSetReauthID(persist, NULL);
EAPSIMAKAPersistentStateSetPseudonym(persist, NULL);
free(persist);
return;
}
STATIC void
IMSIListRemoveMatches(EAPType type, IMSIMatchFuncRef iter,
const void * context)
{
Boolean changed = FALSE;
int i;
CFArrayRef imsi_list;
ProtoInfoRef proto_info;
proto_info = ProtoInfoForType(type);
if (proto_info == NULL) {
return;
}
ProtoInfoChangedCheck(proto_info);
imsi_list = CFPreferencesCopyKeyList(proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (imsi_list == NULL) {
return;
}
for (i = 0; i < CFArrayGetCount(imsi_list); i++) {
CFStringRef imsi = CFArrayGetValueAtIndex(imsi_list, i);
CFPropertyListRef prefs;
prefs = CFPreferencesCopyValue(imsi,
proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (prefs == NULL) {
continue;
}
if (isA_CFDictionary(prefs) != NULL) {
CFDictionaryRef info = (CFDictionaryRef)prefs;
if ((*iter)(imsi, info, context)) {
CFPreferencesSetValue(imsi, NULL,
proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
CFPreferencesSynchronize(proto_info->appID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
MasterKeyRemoveFromKeychain(proto_info->proto, imsi);
changed = TRUE;
}
}
CFRelease(prefs);
}
if (changed) {
ProtoInfoNotifyChange(proto_info);
}
CFRelease(imsi_list);
return;
}
STATIC Boolean
IMSIMatchesSSID(CFStringRef imsi, CFDictionaryRef info,
const void * context)
{
Boolean match = FALSE;
CFStringRef saved_ssid;
CFStringRef ssid = (CFStringRef)context;
saved_ssid = CFDictionaryGetValue(info, kPrefsSSID);
if (my_CFEqual(ssid, saved_ssid)) {
match = TRUE;
}
return (match);
}
PRIVATE_EXTERN void
EAPSIMAKAPersistentStateForgetSSID(CFStringRef ssid)
{
IMSIListRemoveMatches(kEAPTypeEAPSIM, IMSIMatchesSSID, ssid);
IMSIListRemoveMatches(kEAPTypeEAPAKA, IMSIMatchesSSID, ssid);
return;
}
STATIC Boolean
IMSIMatchesEverything(CFStringRef imsi, CFDictionaryRef info,
const void * context)
{
return TRUE;
}
STATIC void
EAPSIMAKAPersistentStatePurgePrefs(void)
{
IMSIListRemoveMatches(kEAPTypeEAPSIM, IMSIMatchesEverything, NULL);
IMSIListRemoveMatches(kEAPTypeEAPAKA, IMSIMatchesEverything, NULL);
return;
}
#ifdef TEST_EAPSIMAKA_PERSISTENT_STATE
#define USE_SYSTEMCONFIGURATION_PRIVATE_HEADERS 1
#include <SystemConfiguration/SCPrivate.h>
#include <string.h>
#include <sysexits.h>
#include <CommonCrypto/CommonDigest.h>
#include "printdata.h"
STATIC void
usage(const char * progname)
{
printf("usage: %s \"sim\" | \"aka\" <command> <parameters>\n"
" <command> is one of \"get\", \"set\", or \"remove\"\n",
progname);
exit(EX_USAGE);
}
STATIC void
EAPSIMAKAPersistentStatePrint(EAPSIMAKAPersistentStateRef persist)
{
if (persist->pseudonym != NULL) {
SCPrint(TRUE, stdout, CFSTR("Pseudonym: %@\n"), persist->pseudonym);
}
if (persist->reauth_id != NULL) {
SCPrint(TRUE, stdout, CFSTR("Reauth ID: %@\n"), persist->reauth_id);
if (persist->master_key_size != 0) {
printf("Master Key: ");
print_bytes(persist->master_key, persist->master_key_size);
printf("\n");
}
SCPrint(TRUE, stdout, CFSTR("Reauth counter: %d\n"),
persist->counter);
}
return;
}
STATIC void
handle_get(const char * progname, EAPType type, int argc, char * argv[])
{
CFStringRef imsi;
EAPSIMAKAPersistentStateRef persist;
if (argc < 1) {
fprintf(stderr, "%s %s get <IMSI>\n",
progname, (type == kEAPTypeEAPSIM) ? "sim" : "aka");
exit(EX_USAGE);
}
imsi = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
persist = EAPSIMAKAPersistentStateCreate(type,
CC_SHA1_DIGEST_LENGTH,
imsi,
kAT_ANY_ID_REQ);
CFRelease(imsi);
EAPSIMAKAPersistentStatePrint(persist);
EAPSIMAKAPersistentStateRelease(persist);
return;
}
STATIC void
set_usage(const char * progname, EAPType type)
{
fprintf(stderr, "usage: %s %s set <IMSI> "
"[ -c <counter> -r <reauth_id> ] "
"[ -p <pseudonym> ] [ -s <ssid> ]\n",
progname, (type == kEAPTypeEAPSIM) ? "sim" : "aka");
exit(EX_USAGE);
return;
}
STATIC void
handle_set(const char * progname, EAPType type, int argc, char * argv[])
{
int ch;
Boolean counter_set = FALSE;
CFStringRef imsi;
EAPSIMAKAPersistentStateRef persist;
CFStringRef pseudonym = NULL;
CFStringRef reauth_id = NULL;
CFStringRef ssid = NULL;
if (argc < 1) {
set_usage(progname, type);
}
imsi = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
persist = EAPSIMAKAPersistentStateCreate(type,
CC_SHA1_DIGEST_LENGTH,
imsi,
kAT_ANY_ID_REQ);
CFRelease(imsi);
while ((ch = getopt(argc, argv, "c:p:r:s:")) != EOF) {
switch (ch) {
case 'c':
if (counter_set) {
fprintf(stderr, "-c specified multiple times\n");
set_usage(progname, type);
exit(EX_USAGE);
}
counter_set = TRUE;
EAPSIMAKAPersistentStateSetCounter(persist,
strtoul(optarg, NULL, 0));
break;
case 'p':
if (pseudonym != NULL) {
fprintf(stderr, "-p specified multiple times\n");
set_usage(progname, type);
}
pseudonym = CFStringCreateWithCString(NULL, optarg,
kCFStringEncodingUTF8);
EAPSIMAKAPersistentStateSetPseudonym(persist, pseudonym);
break;
case 'r':
if (reauth_id != NULL) {
fprintf(stderr, "-r specified multiple times\n");
set_usage(progname, type);
}
reauth_id = CFStringCreateWithCString(NULL, optarg,
kCFStringEncodingUTF8);
EAPSIMAKAPersistentStateSetReauthID(persist, reauth_id);
break;
case 's':
if (ssid != NULL) {
fprintf(stderr, "-s specified multiple times\n");
set_usage(progname, type);
}
ssid = CFStringCreateWithCString(NULL, optarg,
kCFStringEncodingUTF8);
break;
default:
set_usage(progname, type);
break;
}
}
if (reauth_id != NULL || counter_set) {
int count;
int i;
uint8_t * scan;
if (reauth_id == NULL || counter_set == FALSE) {
fprintf(stderr, "-c and -r must both be specified\n");
set_usage(progname, type);
}
count = EAPSIMAKAPersistentStateGetMasterKeySize(persist);
scan = EAPSIMAKAPersistentStateGetMasterKey(persist);
for (i = 0; i < count; i++, scan++) {
*scan = '0' + i;
}
}
if (pseudonym == NULL && reauth_id == NULL) {
set_usage(progname, type);
}
EAPSIMAKAPersistentStateSave(persist, (reauth_id != NULL), ssid);
EAPSIMAKAPersistentStateRelease(persist);
return;
}
STATIC void
handle_remove(const char * progname, EAPType type, int argc, char * argv[])
{
CFStringRef imsi;
EAPSIMAKAPersistentStateRef persist;
CFStringRef ssid;
if (argc < 1) {
fprintf(stderr, "%s %s remove <SSID>\n",
progname, (type == kEAPTypeEAPSIM) ? "sim" : "aka");
exit(EX_USAGE);
}
ssid = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
IMSIListRemoveMatches(type, IMSIMatchesSSID, ssid);
CFRelease(ssid);
return;
}
int
main(int argc, char * argv[])
{
const char * command;
EAPType eap_type;
CFStringRef imsi = NULL;
const char * progname = argv[0];
CFStringRef ssid = NULL;
const char * type;
if (argc < 3) {
usage(progname);
}
type = argv[1];
if (strcasecmp(type, "sim") == 0) {
eap_type = kEAPTypeEAPSIM;
}
else if (strcasecmp(type, "aka") == 0) {
eap_type = kEAPTypeEAPAKA;
}
else {
usage(progname);
}
command = argv[2];
argc -= 3;
argv += 3;
if (strcmp(command, "get") == 0) {
handle_get(progname, eap_type, argc, argv);
}
else if (strcmp(command, "set") == 0) {
handle_set(progname, eap_type, argc, argv);
}
else if (strcmp(command, "remove") == 0) {
handle_remove(progname, eap_type, argc, argv);
}
else {
usage(progname);
}
exit(0);
return (0);
}
#endif