/*
 * Copyright (c) 2003 Apple Computer, 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@
 */

/*
 * eapmschapv2_plugin.c
 * - EAP/MSCHAPv2 plug-in
 */

/* 
 * Modification History
 *
 * May 21, 2003	Dieter Siegmund (dieter@apple)
 * - created
 */
 
#include <EAP8021X/EAPClientPlugin.h>
#include <EAP8021X/EAPClientProperties.h>
#include <SystemConfiguration/SCValidation.h>
#include <mach/boolean.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include <EAP8021X/EAP.h>
#include <EAP8021X/EAPUtil.h>
#include <EAP8021X/EAPClientModule.h>
#include <EAP8021X/mschap.h>
#include "myCFUtil.h"
#include "printdata.h"

/*
 * Declare these here to ensure that the compiler
 * generates appropriate errors/warnings
 */
EAPClientPluginFuncIntrospect eapmschapv2_introspect;
static EAPClientPluginFuncVersion eapmschapv2_version;
static EAPClientPluginFuncEAPType eapmschapv2_type;
static EAPClientPluginFuncEAPName eapmschapv2_name;
static EAPClientPluginFuncInit eapmschapv2_init;
static EAPClientPluginFuncFree eapmschapv2_free;
static EAPClientPluginFuncProcess eapmschapv2_process;
static EAPClientPluginFuncFreePacket eapmschapv2_free_packet;
static EAPClientPluginFuncRequireProperties eapmschapv2_require_props;
static EAPClientPluginFuncPublishProperties eapmschapv2_publish_props;
static EAPClientPluginFuncPacketDump eapmschapv2_packet_dump;
static EAPClientPluginFuncSessionKey eapmschapv2_session_key;
static EAPClientPluginFuncServerKey eapmschapv2_server_key;

typedef struct {
    NTPasswordBlock		encrypted_password;
    uint8_t			encrypted_hash[NT_PASSWORD_HASH_SIZE];
    uint8_t			peer_challenge[MSCHAP2_CHALLENGE_SIZE];
    uint8_t			reserved[MSCHAP2_RESERVED_SIZE];
    uint8_t			nt_response[MSCHAP_NT_RESPONSE_SIZE];
    uint8_t			flags[2];
} EAPMSCHAPv2ChangePasswordResponse, * EAPMSCHAPv2ChangePasswordResponseRef;

enum {
    kMSCHAPv2ChangePasswordVersion = 3
};

#define EAP_MSCHAP2_CHANGE_PASSWORD_RESPONSE_LENGTH (NT_PASSWORD_BLOCK_SIZE \
				 + NT_PASSWORD_HASH_SIZE \
				 + MSCHAP2_CHALLENGE_SIZE \
				 + MSCHAP2_RESERVED_SIZE \
				 + MSCHAP_NT_RESPONSE_SIZE \
				 + 2)
enum {
    kMSCHAPv2OpCodeChallenge = 1,
    kMSCHAPv2OpCodeResponse = 2,
    kMSCHAPv2OpCodeSuccess = 3,
    kMSCHAPv2OpCodeFailure = 4,
    kMSCHAPv2OpCodeChangePassword = 7
};
typedef uint8_t		MSCHAPv2OpCode;

static const char *
MSCHAPv2OpCodeStr(MSCHAPv2OpCode op_code)
{
    switch (op_code) {
    case kMSCHAPv2OpCodeChallenge:
	return ("Challenge");
    case kMSCHAPv2OpCodeResponse:
	return ("Response");
    case kMSCHAPv2OpCodeSuccess:
	return ("Success");
    case kMSCHAPv2OpCodeFailure:
	return ("Failure");
    case kMSCHAPv2OpCodeChangePassword:
	return ("ChangePassword");
    default:
	break;
    }
    return ("<unknown>");
}

/*
 * EAP_MSCHAP2_MS_LENGTH_DIFFERENCE
 *
 * pkt.ms_length is always (pkt.length - 5) because ms_length is the number of
 * bytes starting with the op_code field (offsetof(pkt.op_code) == 5).
 */
#define EAP_MSCHAP2_MS_LENGTH_DIFFERENCE	(sizeof(EAPRequestPacket))

typedef struct EAPMSCHAPv2Packet_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* must be pkt.length - 5 */
    uint8_t		data[0];
} EAPMSCHAPv2Packet, *EAPMSCHAPv2PacketRef;

typedef struct EAPMSCHAPv2ChallengePacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* pkt.length - 5 */
    uint8_t		value_size;	/* MSCHAP2_CHALLENGE_SIZE */
    uint8_t		challenge[MSCHAP2_CHALLENGE_SIZE];
    uint8_t		name[0];
} EAPMSCHAPv2ChallengePacket, *EAPMSCHAPv2ChallengePacketRef;

typedef struct EAPMSCHAPv2ResponsePacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* pkt.length - 5 */
    uint8_t		value_size;	/* MSCHAP2_RESPONSE_LENGTH */
    uint8_t		response[MSCHAP2_RESPONSE_LENGTH];
    uint8_t		name[0];
} EAPMSCHAPv2ResponsePacket, *EAPMSCHAPv2ResponsePacketRef;

typedef struct EAPMSCHAPv2SuccessRequestPacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* pkt.length - 5 */
    uint8_t		auth_response[MSCHAP2_AUTH_RESPONSE_SIZE];
    uint8_t		message[0];
} EAPMSCHAPv2SuccessRequestPacket, *EAPMSCHAPv2SuccessRequestPacketRef;

typedef struct EAPMSCHAPv2SuccessResponsePacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
} EAPMSCHAPv2SuccessResponsePacket, *EAPMSCHAPv2SuccessResponsePacketRef;

typedef struct EAPMSCHAPv2FailureRequestPacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* pkt.length - 5 */
    uint8_t		message[0];
} EAPMSCHAPv2FailureRequestPacket, *EAPMSCHAPv2FailureRequestPacketRef;

typedef struct EAPMSCHAPv2FailureResponsePacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
} EAPMSCHAPv2FailureResponsePacket, *EAPMSCHAPv2FailureResponsePacketRef;

typedef struct EAPMSCHAPv2ChangePasswordPacket_s {
    uint8_t		code;
    uint8_t		identifier;
    uint8_t		length[2];	/* of entire request/response */
    uint8_t		type;
    uint8_t		op_code;
    uint8_t		mschapv2_id;
    uint8_t		ms_length[2];	/* pkt.length - 5 */
    uint8_t		data[EAP_MSCHAP2_CHANGE_PASSWORD_RESPONSE_LENGTH];
} EAPMSCHAPv2ChangePasswordPacket, *EAPMSCHAPv2ChangePasswordPacketRef;

static __inline__ void
EAPMSCHAPv2PacketSetMSLength(EAPMSCHAPv2PacketRef pkt, uint16_t length)
{
    *((u_short *)pkt->ms_length) = htons(length);
    return;
}

static __inline__ uint16_t
EAPMSCHAPv2PacketGetMSLength(const EAPMSCHAPv2PacketRef pkt)
{
    return (ntohs(*((u_short *)pkt->ms_length)));
}

typedef enum {
    kMSCHAPv2ClientStateNone = 0,
    kMSCHAPv2ClientStateResponseSent,
    kMSCHAPv2ClientStateSuccessResponseSent,
    kMSCHAPv2ClientStateChangePasswordSent,
    kMSCHAPv2ClientStateSuccess,
    kMSCHAPv2ClientStateFailure,
} MSCHAPv2ClientState;

typedef struct {
    MSCHAPv2ClientState		state;
    EAPClientState		plugin_state;
    bool			need_password;
    bool			need_new_password;
    uint32_t			last_generation;
    uint8_t			peer_challenge[MSCHAP2_CHALLENGE_SIZE];
    uint8_t			nt_response[MSCHAP_NT_RESPONSE_SIZE];
    uint8_t			auth_challenge[MSCHAP2_CHALLENGE_SIZE];
    uint8_t			session_key[NT_SESSION_KEY_SIZE * 2];
    bool			session_key_valid;
    uint8_t			pkt_buffer[1024];
} EAPMSCHAPv2PluginData, * EAPMSCHAPv2PluginDataRef;

static void
eapmschapv2_free_context(EAPMSCHAPv2PluginData * context)
{
    free(context);
    return;
}

static void
EAPMSCHAPv2PluginDataInit(EAPMSCHAPv2PluginDataRef context)
{
    context->state = kMSCHAPv2ClientStateNone;
    context->plugin_state = kEAPClientStateAuthenticating;
    context->need_password = FALSE;
    context->need_new_password = FALSE;
    context->session_key_valid = FALSE;
    return;
}

static EAPClientStatus
eapmschapv2_init(EAPClientPluginDataRef plugin, CFArrayRef * require_props,
		 EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef	context = NULL;
    EAPClientStatus		result = kEAPClientStatusOK;

    *error = 0;
    *require_props = NULL;
    context = malloc(sizeof(*context));
    plugin->private = context;
    EAPMSCHAPv2PluginDataInit(context);
    context->last_generation = plugin->generation;
    return (result);
}

static void
eapmschapv2_free(EAPClientPluginDataRef plugin)
{
    EAPMSCHAPv2PluginDataRef context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    if (context != NULL) {
	eapmschapv2_free_context(context);
	plugin->private = NULL;
    }
    return;
}

static void
eapmschapv2_free_packet(EAPClientPluginDataRef plugin, EAPPacketRef arg)
{
    EAPMSCHAPv2PluginDataRef context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    if ((void*)arg != context->pkt_buffer) {
	free(arg);
    }
    return;
}

static EAPMSCHAPv2ResponsePacketRef
EAPMSCHAPv2ResponsePacketCreate(EAPClientPluginDataRef plugin, 
				int identifier, int mschapv2_id,
				EAPClientStatus * client_status)
{
    CFDataRef				client_challenge;
    EAPMSCHAPv2PluginDataRef 		context;
    EAPMSCHAPv2ResponsePacketRef	out_pkt_p;
    MSCHAP2ResponseRef			resp_p;
    int					out_length;

    /* check for out-of-band client challenge */
    client_challenge
	= CFDictionaryGetValue(plugin->properties,
			       kEAPClientPropEAPMSCHAPv2ClientChallenge);
    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    out_length = sizeof(*out_pkt_p) + plugin->username_length;
    out_pkt_p = (EAPMSCHAPv2ResponsePacketRef)
	EAPPacketCreate(context->pkt_buffer, sizeof(context->pkt_buffer),
			kEAPCodeResponse, identifier,
			kEAPTypeMSCHAPv2, NULL, 
			out_length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE,
			NULL);

    if (client_challenge != NULL) {
	if (CFDataGetLength(client_challenge)
	    != sizeof(context->peer_challenge)) {
	    syslog(LOG_NOTICE,
		   "EAPMSCHAPv2ResponsePacketCreate: internal error %d !=%d",
		   CFDataGetLength(client_challenge),
		   sizeof(context->peer_challenge));
	    *client_status = kEAPClientStatusInternalError;
	    context->plugin_state = kEAPClientStateFailure;
	    return (NULL);
	}
	memcpy(context->peer_challenge, CFDataGetBytePtr(client_challenge),
	       sizeof(context->peer_challenge));
    }
    else {
	MSChapFillWithRandom(context->peer_challenge,
			     sizeof(context->peer_challenge));
    }
    MSChap2(context->auth_challenge, context->peer_challenge, 	
	    plugin->username, plugin->password, plugin->password_length,
	    context->nt_response);

    /* fill in the data */
    out_pkt_p->op_code = kMSCHAPv2OpCodeResponse;
    out_pkt_p->mschapv2_id = mschapv2_id;
    EAPMSCHAPv2PacketSetMSLength((EAPMSCHAPv2PacketRef)out_pkt_p,
				 out_length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE);
    out_pkt_p->value_size = sizeof(out_pkt_p->response);
    resp_p = (MSCHAP2ResponseRef)out_pkt_p->response;
    if (client_challenge == NULL) {
	memcpy(resp_p->peer_challenge, context->peer_challenge, 
	       sizeof(context->peer_challenge));
    }
    else {
	memset(resp_p->peer_challenge, 0, sizeof(resp_p->peer_challenge));
    }
    memset(resp_p->reserved, 0, sizeof(resp_p->reserved));
    memcpy(resp_p->nt_response, context->nt_response, 
	   sizeof(context->nt_response));
    resp_p->flags[0] = 0;
    memcpy(out_pkt_p->name, plugin->username, plugin->username_length);
    return (out_pkt_p);
}

static EAPMSCHAPv2PacketRef
eapmschapv2_challenge(EAPClientPluginDataRef plugin,
		      EAPMSCHAPv2PacketRef in_pkt_p,
		      uint16_t in_length,
		      EAPClientStatus * client_status,
		      EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef 		context;
    EAPMSCHAPv2ChallengePacketRef	challenge_p;
    EAPMSCHAPv2ResponsePacketRef	out_pkt_p;
    CFDataRef				server_challenge;

    if (in_length < sizeof(*challenge_p)) {
	syslog(LOG_NOTICE, "eapmschapv2_challenge: length %d < %d",
	       in_length, sizeof(*challenge_p));
	goto done;
    }
    challenge_p = (EAPMSCHAPv2ChallengePacketRef)in_pkt_p;
    context = (EAPMSCHAPv2PluginDataRef)plugin->private;

    /* if we don't have a password, ask for one */
    EAPMSCHAPv2PluginDataInit(context);
    if (plugin->password == NULL) {
	context->need_password = TRUE;
	*client_status = kEAPClientStatusUserInputRequired;
	goto done;
    }

    /* check for out-of-band server challenge */
    server_challenge
	= CFDictionaryGetValue(plugin->properties,
			       kEAPClientPropEAPMSCHAPv2ServerChallenge);
    if (server_challenge != NULL) {
	if (CFDataGetLength(server_challenge) 
	    != sizeof(context->auth_challenge)) {
	    syslog(LOG_NOTICE,
		   "eapmschapv2_challenge: internal error %d !=%d",
		   CFDataGetLength(server_challenge),
		   sizeof(context->auth_challenge));
	    *client_status = kEAPClientStatusInternalError;
	    context->plugin_state = kEAPClientStateFailure;
	    goto done;
	}
	memcpy(context->auth_challenge, CFDataGetBytePtr(server_challenge),
	       sizeof(context->auth_challenge));
    }
    else {
	/* remember the auth challenge for later */
	memcpy(context->auth_challenge, challenge_p->challenge,
	       sizeof(context->auth_challenge));
    }
    out_pkt_p = EAPMSCHAPv2ResponsePacketCreate(plugin, in_pkt_p->identifier,
						challenge_p->mschapv2_id,
						client_status);
    if (out_pkt_p == NULL) {
	goto done;
    }
    context->state = kMSCHAPv2ClientStateResponseSent;
    return ((EAPMSCHAPv2PacketRef)out_pkt_p);

 done:
    return (NULL);
}

static void
eapmschapv2_compute_session_key(EAPClientPluginDataRef plugin)
{
    EAPMSCHAPv2PluginDataRef 		context;
    uint8_t				master_key[NT_MASTER_KEY_SIZE];

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;

    MSChap2_MPPEGetMasterKey(plugin->password, plugin->password_length,
			     context->nt_response,
			     master_key);
    MSChap2_MPPEGetAsymetricStartKey(master_key,
				     context->session_key,
				     NT_SESSION_KEY_SIZE,
				     TRUE, TRUE);
    MSChap2_MPPEGetAsymetricStartKey(master_key,
				     context->session_key + NT_SESSION_KEY_SIZE,
				     NT_SESSION_KEY_SIZE,
				     FALSE, TRUE);
    context->session_key_valid = TRUE;
    return;
}

static EAPMSCHAPv2PacketRef
eapmschapv2_success_request(EAPClientPluginDataRef plugin,
			    EAPMSCHAPv2PacketRef in_pkt_p,
			    uint16_t in_length,
			    EAPClientStatus * client_status,
			    EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef 		context;
    EAPMSCHAPv2SuccessRequestPacketRef	r_p;
    EAPMSCHAPv2SuccessResponsePacketRef	out_pkt_p = NULL;

    if (in_length < sizeof(*r_p)) {
	syslog(LOG_NOTICE, "eapmschapv2_success_request: length %d < %d",
	       in_length, sizeof(*r_p));
	goto done;
    }
    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    switch (context->state) {
    case kMSCHAPv2ClientStateResponseSent:
    case kMSCHAPv2ClientStateChangePasswordSent:
	break;
    case kMSCHAPv2ClientStateSuccess:
	break;
    case kMSCHAPv2ClientStateFailure:
    default:
	goto done;
    }
    r_p = (EAPMSCHAPv2SuccessRequestPacketRef)in_pkt_p;
    /* process success request */
    if (MSChap2AuthResponseValid((const uint8_t *)plugin->password,
				 plugin->password_length,
				 context->nt_response,
				 context->peer_challenge,
				 context->auth_challenge,
				 (const uint8_t *)plugin->username,
				 r_p->auth_response) 
	== FALSE) {
	syslog(LOG_NOTICE,
	       "eapmschapv2_success_request: invalid server auth response");
	context->plugin_state = kEAPClientStateFailure;
	context->state = kMSCHAPv2ClientStateFailure;
	*client_status = kEAPClientStatusFailed;
	goto done;
    }
    switch (context->state) {
    case kMSCHAPv2ClientStateResponseSent:
	syslog(LOG_NOTICE,
	       "eapmschapv2_success_request: successfully authenticated");
	break;
    case kMSCHAPv2ClientStateChangePasswordSent:
	syslog(LOG_NOTICE,
	       "eapmschapv2_success_request: change password succeeded");
	break;
    default:
	break;
    }
    eapmschapv2_compute_session_key(plugin);
    context->state = kMSCHAPv2ClientStateSuccess;
    out_pkt_p = (EAPMSCHAPv2SuccessResponsePacketRef)
	EAPPacketCreate(context->pkt_buffer, sizeof(context->pkt_buffer),
			kEAPCodeResponse, in_pkt_p->identifier,
			kEAPTypeMSCHAPv2, NULL,
			sizeof(*out_pkt_p) - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE,
			NULL);
    out_pkt_p->op_code = kMSCHAPv2OpCodeSuccess;
    return ((EAPMSCHAPv2PacketRef)out_pkt_p);

 done:
    if (out_pkt_p != NULL) {
	free(out_pkt_p);
    }
    return (NULL);
}


#if 0
enum {
    kMSCHAP2FailureMessageFlagError = 0x1,
    kMSCHAP2FailureMessageFlagRetry = 0x2,
    kMSCHAP2FailureMessageFlagChallenge = 0x4,
    kMSCHAP2FailureMessageFlagVersion = 0x8,
    kMSCHAP2FailureMessageFlagMessage = 0x10,
};

static bool
mschap2_message_int32_attr(const uint8_t * message, uint16_t message_length,
			   const char * attr, int attr_len, 
			   int32_t * int_value)
{
    bool	present = FALSE;
    char *	val;

    val = strnstr(message, attr, message_length);
    if (val != NULL && message_length > attr_len) {
	val += attr_len;
	
	*int_value = strtol(val, NULL, 10);
	present = TRUE;
    }
    return (present);
}

static bool
mschap2_message_challenge_attr(const uint8_t * message, uint16_t message_length,
			       const char * attr,  int attr_len,
			       uint8_t challenge[MSCHAP2_CHALLENGE_SIZE])
{
    int		i;
    bool	present = FALSE;
    char *	val;

    val = strnstr(message, attr, message_length);
    if (val != NULL 
	&& message_length > (attr_len + MSCHAP2_CHALLENGE_SIZE * 2)) {
	char str[3];

	str[2] = '\0'; /* nul-terminate */
	val += attr_len;
	for (i = 0; i < MSCHAP2_CHALLENGE_SIZE; i++) {
	    str[0] = val[0];
	    str[1] = val[1];
	    challenge[i] = strtoul(str, NULL, 16);
	    val += 2;
	}
	present = TRUE;
    }
    return (present);
}

static uint8_t
MSCHAPv2ParseFailureMessage(const uint8_t * message, 
			    uint16_t message_length,
			    int32_t * error, int32_t * retry,
			    uint8_t challenge[MSCHAP2_CHALLENGE_SIZE],
			    int32_t * version, uint8_t * * ret_message)
{
    uint32_t	flags = 0;
    char *	val;

    /*
     * Parse the message string format:
     *   E=eeeeeeeee R=r C=cccccccccccccccccccccccccccccccc V=vvvvvvvvvv M=mm...
     * We don't assume that they are in any particular order, since that's
     * the most flexible.
     */
    if (mschap2_message_int32_attr(message, message_length, "E=", 2, error)) {
	flags |= kMSCHAP2FailureMessageFlagError;
    }
    if (mschap2_message_int32_attr(message, message_length, "R=", 2, retry)) {
	flags |= kMSCHAP2FailureMessageFlagRetry;
    }
    if (mschap2_message_int32_attr(message, message_length, "V=", 2, version)) {
	flags |= kMSCHAP2FailureMessageFlagVersion;
    }
    if (mschap2_message_challenge_attr(message, message_length, 
				       "C=", 2, challenge)) {
	flags |= kMSCHAP2FailureMessageFlagChallenge;
    }
    val = strnstr(message, "M=", message_length);
    if (val != NULL) {
	*ret_message = val + 2;
	flags |= kMSCHAP2FailureMessageFlagMessage;
    }
    return (flags);
}
#endif 0

static EAPMSCHAPv2PacketRef
eapmschapv2_failure_request(EAPClientPluginDataRef plugin,
			    EAPMSCHAPv2PacketRef in_pkt_p,
			    uint16_t in_length,
			    EAPClientStatus * client_status,
			    EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef 		context;
    bool				handled = FALSE;
    char *				message = NULL;
#if 0
    int					message_length;
    uint32_t				flags = 0;
    int32_t				r_error;
    int32_t				r_retry = 0;
    int32_t				r_version = 0;
    uint8_t *				r_message;
#endif 0
    EAPMSCHAPv2FailureRequestPacketRef	r_p;
    EAPMSCHAPv2PacketRef		out_pkt_p = NULL;

    if (in_length < sizeof(*r_p)) {
	syslog(LOG_NOTICE, "eapmschapv2_failure_request: length %d < %d",
	       in_length, sizeof(*r_p));
	goto done;
    }
    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    switch (context->state) {
    case kMSCHAPv2ClientStateResponseSent:
    case kMSCHAPv2ClientStateChangePasswordSent:
	break;
    case kMSCHAPv2ClientStateFailure:
	break;
    case kMSCHAPv2ClientStateSuccess:
    default:
	goto done;
    }
    r_p = (EAPMSCHAPv2FailureRequestPacketRef)in_pkt_p;
#if 0
    do { /* something to break out of */
	/* allocate a message buffer that's guaranteed to be nul-terminated */
	message_length = in_length - sizeof(*r_p);
	if (message_length == 0) {
	    break;
	}
	message = malloc(message_length + 1);
	memcpy(message, r_p->message, message_length);
	message[message_length] = '\0';
	
	/* parse it */
	flags = MSCHAPv2ParseFailureMessage(message, message_length,
					    &r_error, &r_retry, 
					    context->auth_challenge,
					    &r_version, &r_message);
	
	if ((flags & kMSCHAP2FailureMessageFlagError) == 0) {
	    break;
	}
	syslog(LOG_NOTICE, "MSCHAPv2 Error = %d, Retry = %d, Version = %d", 
	       r_error, r_retry, r_version);

	if (r_retry == 0) {
	    /* can't retry, acknowledge the error */
	    break;
	}
	if ((flags & kMSCHAP2FailureMessageFlagChallenge) == 0) {
	    syslog(LOG_NOTICE, 
		   "MSCHAPv2 Failure Request does not contain challenge");
	    break;
	}
	if (r_error != kMSCHAP2_ERROR_PASSWD_EXPIRED) {
	    if (context->last_generation == plugin->generation
		|| plugin->password == NULL) {
		context->need_password = TRUE;
		*client_status = kEAPClientStatusUserInputRequired;
	    }
	    else {
		out_pkt_p = (EAPMSCHAPv2PacketRef)
		    EAPMSCHAPv2ResponsePacketCreate(plugin, 
						    r_p->identifier,
						    r_p->mschapv2_id);
		if (out_pkt_p == NULL) {
		    break;
		}
		context->state = kMSCHAPv2ClientStateResponseSent;
	    }
	}
	else {
	    char *	new_password = NULL;

	    if (r_version != kMSCHAPv2ChangePasswordVersion) {
		break;
	    }
	    if (plugin->password == NULL) {
		break;
	    }
	    if (context->last_generation != plugin->generation
		&& plugin->properties != NULL) {
		CFStringRef		new_password_cf;
		
		new_password_cf 
		    = CFDictionaryGetValue(plugin->properties,
					   kEAPClientPropNewPassword);
		if (new_password_cf != NULL) {
		    new_password = my_CFStringToCString(new_password_cf,
							kCFStringEncodingASCII);
		}
	    }
	    if (new_password != NULL) {
		out_pkt_p 
		    = EAPMSCHAPv2ChangePasswordPacketCreate(plugin,
							    r_p->identifer,
							    r_p->mschapv2_id,
							    new_password,
							    strlen(new_password));
		if (out_pkt_p == NULL) {
		    break;
		}
		context->state = kMSCHAPv2ClientStateChangePasswordSent;
	    }
	    else {
		context->need_new_password = TRUE;
		*client_status = kEAPClientStatusUserInputRequired;
	    }
	    if (new_password != NULL) {
		free(new_password);
	    }
	}
	handled = TRUE;
    } while (0); /* something to break out of */
#endif 0

    if (handled == FALSE) {
	context->state = kMSCHAPv2ClientStateFailure;
	context->plugin_state = kEAPClientStateFailure;

	*client_status = kEAPClientStatusFailed;
	out_pkt_p = (EAPMSCHAPv2PacketRef)
	    EAPPacketCreate(context->pkt_buffer, sizeof(context->pkt_buffer),
			    kEAPCodeResponse, in_pkt_p->identifier,
			    kEAPTypeMSCHAPv2, NULL,
			    sizeof(*out_pkt_p) - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE,
			    NULL);
	out_pkt_p->op_code = kMSCHAPv2OpCodeFailure;
    }

    if (message != NULL) {
	free(message);
    }
    return (out_pkt_p);

 done:
    if (out_pkt_p != NULL) {
	free(out_pkt_p);
    }
    if (message != NULL) {
	free(message);
    }
    return (NULL);
}

static EAPPacketRef
eapmschapv2_request(EAPClientPluginDataRef plugin, 
		    const EAPPacketRef in_pkt,
		    EAPClientStatus * client_status,
		    EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef 	context;
    EAPMSCHAPv2PacketRef	mschap_in_p;
    EAPMSCHAPv2PacketRef	mschap_out_p = NULL;
    uint16_t			in_length = EAPPacketGetLength(in_pkt);

    mschap_in_p = (EAPMSCHAPv2PacketRef)in_pkt;
    if (in_length < sizeof(*mschap_in_p)) {
	syslog(LOG_NOTICE, "eapmschapv2_request: length %d < %d",
	       in_length, sizeof(*mschap_in_p));
	goto done;
    }
    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    switch (mschap_in_p->op_code) {
    case kMSCHAPv2OpCodeChallenge:
	mschap_out_p = eapmschapv2_challenge(plugin, mschap_in_p, in_length,
					     client_status, error);
	break;
    case kMSCHAPv2OpCodeResponse:
	/* ignore */
	break;
    case kMSCHAPv2OpCodeSuccess:
	mschap_out_p = eapmschapv2_success_request(plugin, mschap_in_p, 
						   in_length, client_status,
						   error);
	break;
    case kMSCHAPv2OpCodeFailure:
	mschap_out_p = eapmschapv2_failure_request(plugin, mschap_in_p, 
						   in_length, client_status,
						   error);
	break;
    default:
	break;
    }
    return ((EAPPacketRef)mschap_out_p);

 done:
    if (mschap_out_p != NULL) {
	free(mschap_out_p);
    }
    return (NULL);
}

static EAPClientState
eapmschapv2_process(EAPClientPluginDataRef plugin, 
		    const EAPPacketRef in_pkt,
		    EAPPacketRef * out_pkt_p, 
		    EAPClientStatus * client_status,
		    EAPClientDomainSpecificError * error)
{
    EAPMSCHAPv2PluginDataRef 	context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;

    *client_status = kEAPClientStatusOK;
    *error = 0;
    *out_pkt_p = NULL;
    switch (in_pkt->code) {
    case kEAPCodeRequest:
	*out_pkt_p = eapmschapv2_request(plugin, in_pkt, client_status,
					 error);
	break;
    case kEAPCodeSuccess:
	if (context->state == kMSCHAPv2ClientStateSuccess) {
	    context->plugin_state = kEAPClientStateSuccess;
	}
	break;
    case kEAPCodeFailure:
	if (context->state == kMSCHAPv2ClientStateFailure) {
	    context->plugin_state = kEAPClientStateFailure;
	}
	break;
    case kEAPCodeResponse:
    default:
	/* ignore */
	break;
    }
    context->last_generation = plugin->generation;
    return (context->plugin_state);
}

static CFDictionaryRef
eapmschapv2_publish_props(EAPClientPluginDataRef plugin)
{
    return (NULL);
}

static CFArrayRef
eapmschapv2_require_props(EAPClientPluginDataRef plugin)
{
    CFMutableArrayRef 		array = NULL;
    EAPMSCHAPv2PluginDataRef	context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    if (context->need_password || context->need_new_password) {
	array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	if (context->need_password) {
	    CFArrayAppendValue(array, kEAPClientPropUserPassword);
	}
	if (context->need_new_password) {
	    CFArrayAppendValue(array, kEAPClientPropNewPassword);
	}
    }
    return (array);
}

static void
eapmschapv2_challenge_dump(FILE * out_f, const EAPPacketRef pkt,
			   int length)
{
    EAPMSCHAPv2ChallengePacketRef	challenge_p;
    int					name_length;

    if (length < sizeof(*challenge_p)) {
	fprintf(out_f, "Error: length %d < %d\n", length, 
		(int)sizeof(*challenge_p));
	return;
    }
    challenge_p = (EAPMSCHAPv2ChallengePacketRef)pkt;
    fprintf(out_f, "MS-CHAPv2-ID %d MS-Length %d Value-Size %d\n",
	    challenge_p->mschapv2_id, 
	    EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt),
	    challenge_p->value_size);

    if (challenge_p->value_size != sizeof(challenge_p->challenge)) {
	fprintf(out_f, "Error: Value-Size should be %d\n",
		(int)sizeof(challenge_p->challenge));
    }
    if (EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt) 
	!= (length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE)) {
	fprintf(out_f, "Error: MS-Length should be %d\n",
		(int)(length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE));
    }
    fprintf(out_f, "Challenge: ");
    fprint_bytes(out_f, challenge_p->challenge, sizeof(challenge_p->challenge));
    fprintf(out_f, "\n");
    name_length = length - sizeof(*challenge_p);
    if (name_length > 0) {
	fprintf(out_f, "Name: %.*s\n", name_length, challenge_p->name);
    }
    return;
}

static void
eapmschapv2_response_dump(FILE * out_f, const EAPPacketRef pkt,
			   int length)
{
    EAPMSCHAPv2ResponsePacketRef	r_p;
    int					name_length;
    MSCHAP2ResponseRef		resp_p;

    if (length < sizeof(*r_p)) {
	fprintf(out_f, "Error: length %d < %d\n", length, 
		(int)sizeof(*r_p));
	return;
    }
    r_p = (EAPMSCHAPv2ResponsePacketRef)pkt;
    fprintf(out_f, "MS-CHAPv2-ID %d MS-Length %d Value-Size %d\n",
	    r_p->mschapv2_id, 
	    EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt),
	    r_p->value_size);

    if (r_p->value_size != sizeof(r_p->response)) {
	fprintf(out_f, "Error: Value-Size should be %d\n",
		(int)sizeof(r_p->response));
    }
    if (EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt) 
	!= (length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE)) {
	fprintf(out_f, "Error: MS-Length should be %d\n",
		(int)(length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE));
    }
    resp_p = (MSCHAP2ResponseRef)r_p->response;
    fprintf(out_f, "Response:\n");
    fprintf(out_f, 
	    "Peer Challenge: ");
    fprint_bytes(out_f, 
		 resp_p->peer_challenge, sizeof(resp_p->peer_challenge));
    fprintf(out_f, "\n"
	    "Reserved:       ");
    fprint_bytes(out_f, 
		 resp_p->reserved, sizeof(resp_p->reserved));
    fprintf(out_f, "\n"
	    "NT Response:    ");
    fprint_bytes(out_f, 
		 resp_p->nt_response, sizeof(resp_p->nt_response));
    fprintf(out_f, "\n"
	    "Flags:          ");
    fprint_bytes(out_f, 
		 resp_p->flags, sizeof(resp_p->flags));
    name_length = length - sizeof(*r_p);
    if (name_length > 0) {
	fprintf(out_f, "\n"
		"Name:           %.*s\n", name_length, r_p->name);
    }
    return;
}

static void
eapmschapv2_success_request_dump(FILE * out_f, const EAPPacketRef pkt,
				 int length)
{
    EAPMSCHAPv2SuccessRequestPacketRef	r_p;
    int					message_length;

    if (length < sizeof(*r_p)) {
	fprintf(out_f, "Error: length %d < %d\n", length, (int)sizeof(*r_p));
	return;
    }
    r_p = (EAPMSCHAPv2SuccessRequestPacketRef)pkt;
    fprintf(out_f, "MS-CHAPv2-ID %d MS-Length %d\n",
	    r_p->mschapv2_id, 
	    EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt));
    if (EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt)
	!= (length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE)) {
	fprintf(out_f, "Error: MS-Length should be %d\n",
		(int)(length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE));
    }
    fprintf(out_f, 
	    "Auth Response: %.*s\n", (int)sizeof(r_p->auth_response), 
	    r_p->auth_response);
    message_length = length - sizeof(*r_p);
    if (message_length > 1) {
	/* skip the space when we print it out */
	fprintf(out_f, 
		"Message:       %.*s\n", message_length - 1,
		r_p->message + 1);
    }
    return;
}

static void
eapmschapv2_failure_request_dump(FILE * out_f, const EAPPacketRef pkt,
				 int length)
{
    EAPMSCHAPv2FailureRequestPacketRef	r_p;
    int					message_length;

    if (length < sizeof(*r_p)) {
	fprintf(out_f, "Error: length %d < %d\n", length, (int)sizeof(*r_p));
	return;
    }
    r_p = (EAPMSCHAPv2FailureRequestPacketRef)pkt;
    fprintf(out_f, "MS-CHAPv2-ID %d MS-Length %d\n",
	    r_p->mschapv2_id, 
	    EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt));
    if (EAPMSCHAPv2PacketGetMSLength((EAPMSCHAPv2PacketRef)pkt) 
	!= (length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE)) {
	fprintf(out_f, "Error: MS-Length should be %d\n",
		(int)(length - EAP_MSCHAP2_MS_LENGTH_DIFFERENCE));
    }
    message_length = length - sizeof(*r_p);
    if (message_length > 0) {
	fprintf(out_f, 
		"Message:       %.*s\n", message_length, r_p->message);
    }
    return;
}

static bool
eapmschapv2_packet_dump(FILE * out_f, const EAPPacketRef pkt)
{
    EAPMSCHAPv2PacketRef	ms_pkt_p = (EAPMSCHAPv2PacketRef)pkt;
    int				data_length;
    u_int16_t			length = EAPPacketGetLength(pkt);

    switch (pkt->code) {
    case kEAPCodeRequest:
	break;
    case kEAPCodeResponse:
	break;
    default:
	/* don't handle it */
	return (FALSE);
    }
    if (length < sizeof(EAPMSCHAPv2SuccessResponsePacket)) {
	fprintf(out_f, "invalid packet: length %d < min length %ld",
		length, sizeof(*ms_pkt_p));
	goto done;
    }
    data_length = length - sizeof(EAPMSCHAPv2SuccessResponsePacket);
    fprintf(out_f, 
	    "EAP/MSCHAPv2 %s: Identifier %d Length %d OpCode %s ",
	    EAPCodeStr(pkt->code), pkt->identifier, length,
	    MSCHAPv2OpCodeStr(ms_pkt_p->op_code));
    if (pkt->code == kEAPCodeRequest) {
	/* Request */
	switch (ms_pkt_p->op_code) {
	case kMSCHAPv2OpCodeChallenge:
	    eapmschapv2_challenge_dump(out_f, pkt, length);
	    break;
	case kMSCHAPv2OpCodeResponse:
	    eapmschapv2_response_dump(out_f, pkt, length);
	    fprintf(out_f, 
		    "EAP Request contains MSCHAPv2 Response (invalid)\n");
	    break;
	case kMSCHAPv2OpCodeSuccess:
	    eapmschapv2_success_request_dump(out_f, pkt, length);
	    break;
	case kMSCHAPv2OpCodeFailure:
	    eapmschapv2_failure_request_dump(out_f, pkt, length);
	    break;
	default:
	    if (data_length > 0) {
		fprintf(out_f, "Unknown data:\n");
		fprint_data(out_f, &ms_pkt_p->mschapv2_id,
			    data_length);
	    }
	    break;
	}
    }
    else {
	/* Response */
	switch (ms_pkt_p->op_code) {
	case kMSCHAPv2OpCodeChallenge:
	    eapmschapv2_challenge_dump(out_f, pkt, length);
	    fprintf(out_f, 
		    "EAP Response contains MSCHAPv2 Challenge (invalid)\n");
	    break;
	case kMSCHAPv2OpCodeResponse:
	    eapmschapv2_response_dump(out_f, pkt, length);
	    break;
	case kMSCHAPv2OpCodeSuccess:
	case kMSCHAPv2OpCodeFailure:
	    fprintf(out_f, "\n");
	    break;
	default:
	    if (data_length > 0) {
		fprintf(out_f, "Unknown data:\n");
		fprint_data(out_f, &ms_pkt_p->mschapv2_id,
			    data_length);
	    }
	    break;
	}
    }
 done:
    return (TRUE);
}

static EAPType 
eapmschapv2_type()
{
    return (kEAPTypeMSCHAPv2);

}

static const char *
eapmschapv2_name()
{
    return ("MSCHAPv2");

}

static EAPClientPluginVersion 
eapmschapv2_version()
{
    return (kEAPClientPluginVersion);
}

static void * 
eapmschapv2_session_key(EAPClientPluginDataRef plugin, int * key_length)
{
    EAPMSCHAPv2PluginDataRef	context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    *key_length = 0;
    if (context->session_key_valid == FALSE) {
	return (NULL);
    }
    *key_length = sizeof(context->session_key);
    return (context->session_key);
}

static void * 
eapmschapv2_server_key(EAPClientPluginDataRef plugin, int * key_length)
{
    EAPMSCHAPv2PluginDataRef	context;

    context = (EAPMSCHAPv2PluginDataRef)plugin->private;
    *key_length = 0;
    if (context->session_key_valid == FALSE) {
	return (NULL);
    }
    *key_length = sizeof(context->session_key);
    return (context->session_key);
}


static struct func_table_ent {
    const char *		name;
    void *			func;
} func_table[] = {
#if 0
    { kEAPClientPluginFuncNameIntrospect, eapmschapv2_introspect },
#endif 0
    { kEAPClientPluginFuncNameVersion, eapmschapv2_version },
    { kEAPClientPluginFuncNameEAPType, eapmschapv2_type },
    { kEAPClientPluginFuncNameEAPName, eapmschapv2_name },
    { kEAPClientPluginFuncNameInit, eapmschapv2_init },
    { kEAPClientPluginFuncNameFree, eapmschapv2_free },
    { kEAPClientPluginFuncNameProcess, eapmschapv2_process },
    { kEAPClientPluginFuncNameFreePacket, eapmschapv2_free_packet },
    { kEAPClientPluginFuncNameSessionKey, eapmschapv2_session_key },
    { kEAPClientPluginFuncNameServerKey, eapmschapv2_server_key },
    { kEAPClientPluginFuncNameRequireProperties, eapmschapv2_require_props },
    { kEAPClientPluginFuncNamePublishProperties, eapmschapv2_publish_props },
    { kEAPClientPluginFuncNamePacketDump, eapmschapv2_packet_dump },
    { NULL, NULL},
};


EAPClientPluginFuncRef
eapmschapv2_introspect(EAPClientPluginFuncName name)
{
    struct func_table_ent * scan;


    for (scan = func_table; scan->name != NULL; scan++) {
	if (strcmp(name, scan->name) == 0) {
	    return (scan->func);
	}
    }
    return (NULL);
}
