/*
 *	Authenticate a previously sent challenge.
 */
static int eap_sim_authenticate(void *arg, EAP_HANDLER *handler)
{
	struct eap_sim_server_state *ess;
	VALUE_PAIR *vp, *vps;
	enum eapsim_subtype subtype;
	int success;

	arg = arg; /* shut up compiler */

	ess = (struct eap_sim_server_state *)handler->opaque;

	/* vps is the data from the client */
	vps = handler->request->packet->vps;

	success= unmap_eapsim_basictypes(handler->request->packet,
					 handler->eap_ds->response->type.data,
					 handler->eap_ds->response->type.length);

	if(!success) {
	  return 0;
	}

	/* see what kind of message we have gotten */
	if((vp = pairfind(vps, ATTRIBUTE_EAP_SIM_SUBTYPE, 0, TAG_ANY)) == NULL)
	{
		DEBUG2("   no subtype attribute was created, message dropped");
		return 0;
	}
	subtype = vp->vp_integer;

	/*
	 *	Client error supersedes anything else.
	 */
	if (subtype == eapsim_client_error) {
		return 0;
	}

	switch(ess->state) {
	case eapsim_server_start:
		switch(subtype) {
		default:
			/*
			 * pretty much anything else here is illegal,
			 * so we will retransmit the request.
			 */
			eap_sim_stateenter(handler, ess, eapsim_server_start);
			return 1;

		case eapsim_start:
			/*
			 * a response to our EAP-Sim/Request/Start!
			 *
			 */
			return process_eap_sim_start(handler, vps);
		}
		break;
	case eapsim_server_challenge:
		switch(subtype) {
		default:
			/*
			 * pretty much anything else here is illegal,
			 * so we will retransmit the request.
			 */
			eap_sim_stateenter(handler, ess, eapsim_server_challenge);
			return 1;

		case eapsim_challenge:
			/*
			 * a response to our EAP-Sim/Request/Challenge!
			 *
			 */
			return process_eap_sim_challenge(handler, vps);
		}
		break;

	default:
		/* if we get into some other state, die, as this
		 * is a coding error!
		 */
		DEBUG2("  illegal-unknown state reached in eap_sim_authenticate\n");
		rad_assert(0 == 1);
 	}

	return 0;
}
/** Authenticate a previously sent challenge
 *
 */
static int mod_authenticate(UNUSED void *arg, eap_handler_t *handler)
{
	REQUEST *request = handler->request;
	eap_sim_state_t *ess = handler->opaque;

	VALUE_PAIR *vp, *vps;

	enum eapsim_subtype subtype;

	int success;

	/*
	 *	VPS is the data from the client
	 */
	vps = handler->request->packet->vps;

	success = unmap_eapsim_basictypes(handler->request->packet,
					  handler->eap_ds->response->type.data,
					  handler->eap_ds->response->type.length);

	if (!success) return 0;

	/*
	 *	See what kind of message we have gotten
	 */
	vp = pairfind(vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY);
	if (!vp) {
		REDEBUG2("No subtype attribute was created, message dropped");
		return 0;
	}
	subtype = vp->vp_integer;

	/*
	 *	Client error supersedes anything else.
	 */
	if (subtype == EAPSIM_CLIENT_ERROR) {
		return 0;
	}

	switch (ess->state) {
	case EAPSIM_SERVER_START:
		switch (subtype) {
		/*
		 *	Pretty much anything else here is illegal, so we will retransmit the request.
		 */
		default:

			eap_sim_stateenter(handler, ess, EAPSIM_SERVER_START);
			return 1;
		/*
		 * 	A response to our EAP-Sim/Request/Start!
		 */
		case EAPSIM_START:
			return process_eap_sim_start(handler, vps);
		}
		break;

	case EAPSIM_SERVER_CHALLENGE:
		switch(subtype) {
		/*
		 *	Pretty much anything else here is illegal, so we will retransmit the request.
		 */
		default:
			eap_sim_stateenter(handler, ess, EAPSIM_SERVER_CHALLENGE);
			return 1;
		/*
		 *	A response to our EAP-Sim/Request/Challenge!
		 */
		case EAPSIM_CHALLENGE:
			return process_eap_sim_challenge(handler, vps);
		}
		break;

	default:
		rad_assert(0 == 1);
	}

	return 0;
}
/** Authenticate a previously sent challenge
 *
 */
static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session)
{
	REQUEST			*request = eap_session->request;
	eap_sim_session_t	*eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
	fr_sim_decode_ctx_t	ctx = {
					.keys = &eap_sim_session->keys,
				};
	VALUE_PAIR		*subtype_vp, *from_peer, *vp;
	fr_cursor_t		cursor;

	eap_sim_subtype_t	subtype;

	int			ret;

	/*
	 *	VPS is the data from the client
	 */
	from_peer = eap_session->request->packet->vps;

	fr_cursor_init(&cursor, &request->packet->vps);
	fr_cursor_tail(&cursor);

	ret = fr_sim_decode(eap_session->request,
			    &cursor,
			    dict_eap_sim,
			    eap_session->this_round->response->type.data,
			    eap_session->this_round->response->type.length,
			    &ctx);
	/*
	 *	RFC 4186 says we *MUST* notify, not just
	 *	send an EAP-Failure in this case where
	 *	we cannot decode an EAP-AKA packet.
	 */
	if (ret < 0) {
		RPEDEBUG2("Failed decoding EAP-SIM attributes");
		eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;	/* We need to process more packets */
	}

	vp = fr_cursor_current(&cursor);
	if (vp && RDEBUG_ENABLED2) {
		RDEBUG2("Decoded EAP-SIM attributes");
		log_request_pair_list(L_DBG_LVL_2, request, vp, NULL);
	}

	subtype_vp = fr_pair_find_by_da(from_peer, attr_eap_sim_subtype, TAG_ANY);
	if (!subtype_vp) {
		REDEBUG("Missing EAP-SIM-Subtype");
		eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;				/* We need to process more packets */
	}
	subtype = subtype_vp->vp_uint16;

	switch (eap_sim_session->state) {
	/*
	 *	Response to our advertised versions and request for an ID
	 *	This is very similar to Identity negotiation in EAP-AKA[']
	 */
	case EAP_SIM_SERVER_START:
		switch (subtype) {
		case EAP_SIM_START:
			if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED;
			eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
			return RLM_MODULE_HANDLED;	/* We need to process more packets */

		/*
		 *	Case 1 where we're allowed to send an EAP-Failure
		 *
		 *	This can happen in the case of a conservative
		 *	peer, where it refuses to provide the permanent
		 *	identity.
		 */
		case EAP_SIM_CLIENT_ERROR:
		{
			char buff[20];

			vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY);
			if (!vp) {
				REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but "
					"has not supplied a client error code",
					fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"));
			} else {
				REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)",
					fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"),
					fr_pair_value_enum(vp, buff), vp->vp_uint16);
			}
			eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
			return RLM_MODULE_REJECT;
		}

		case EAP_SIM_NOTIFICATION:
		notification:
		{
			char buff[20];

			vp = fr_pair_afrom_da(from_peer, attr_eap_sim_notification);
			if (!vp) {
				REDEBUG2("Received SIM-Notification with no notification code");
				eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
				return RLM_MODULE_HANDLED;			/* We need to process more packets */
			}

			/*
			 *	Case 2 where we're allowed to send an EAP-Failure
			 */
			if (!(vp->vp_uint16 & 0x8000)) {
				REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff),
					 vp->vp_uint16);
				eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
				return RLM_MODULE_REJECT;
			}

			/*
			 *	...if it's not a failure, then re-enter the
			 *	current state.
			 */
			REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16);
			eap_sim_state_enter(eap_session, eap_sim_session->state);
			return RLM_MODULE_HANDLED;

		default:
		unexpected_subtype:
			/*
			 *	RFC 4186 says we *MUST* notify, not just
			 *	send an EAP-Failure in this case.
			 */
			REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
			eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
			return RLM_MODULE_HANDLED;				/* We need to process more packets */
		}
		}

	/*
	 *	Process the response to our previous challenge.
	 */
	case EAP_SIM_SERVER_CHALLENGE:
		switch (subtype) {
		/*
		 *	A response to our EAP-Sim/Request/Challenge!
		 */
		case EAP_SIM_CHALLENGE:
			switch (process_eap_sim_challenge(eap_session, from_peer)) {
			case 1:
				return RLM_MODULE_HANDLED;

			case 0:
				return RLM_MODULE_OK;

			case -1:
				eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
				return RLM_MODULE_HANDLED;			/* We need to process more packets */
			}

		case EAP_SIM_CLIENT_ERROR:
		{
			char buff[20];

			vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY);
			if (!vp) {
				REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but "
					"has not supplied a client error code");
			} else {
				REDEBUG("Client rejected SIM-Challenge with error: %s (%i)",
					fr_pair_value_enum(vp, buff), vp->vp_uint16);
			}
			eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
			return RLM_MODULE_REJECT;
		}

		case EAP_SIM_NOTIFICATION:
			goto notification;

		default:
			goto unexpected_subtype;
		}

	/*
	 *	Peer acked our failure
	 */
	case EAP_SIM_SERVER_FAILURE_NOTIFICATION:
		switch (subtype) {
		case EAP_SIM_NOTIFICATION:
			RDEBUG2("SIM-Notification ACKed, sending EAP-Failure");
			eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
			return RLM_MODULE_REJECT;

		default:
			goto unexpected_subtype;
		}

	/*
	 *	Something bad happened...
	 */
	default:
		rad_assert(0);
		eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;				/* We need to process more packets */
	}
}

/*
 *	Initiate the EAP-SIM session by starting the state machine
 *      and initiating the state.
 */
static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session)
{
	REQUEST				*request = eap_session->request;
	eap_sim_session_t		*eap_sim_session;
	rlm_eap_sim_t			*inst = instance;
	fr_sim_id_type_t		type;
	fr_sim_method_hint_t		method;

	MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t));

	eap_session->opaque = eap_sim_session;

	/*
	 *	Set default configuration, we may allow these
	 *	to be toggled by attributes later.
	 */
	eap_sim_session->send_result_ind = inst->protected_success;
	eap_sim_session->id_req = SIM_ANY_ID_REQ;	/* Set the default */

	/*
	 *	This value doesn't have be strong, but it is
	 *	good if it is different now and then.
	 */
	eap_sim_session->sim_id = (fr_rand() & 0xff);

	/*
	 *	Save the keying material, because it could change on a subsequent retrieval.
	 */
	RDEBUG2("New EAP-SIM session");

	/*
	 *	Process the identity that we received in the
	 *	EAP-Identity-Response and use it to determine
	 *	the initial request we send to the Supplicant.
	 */
	if (fr_sim_id_type(&type, &method,
			   eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) {
		RPWDEBUG2("Failed parsing identity, continuing anyway");
	}

	switch (method) {
	default:
		RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM",
			fr_int2str(sim_id_method_hint_table, method, "<INVALID>"));
		break;

	case SIM_METHOD_HINT_SIM:
	case SIM_METHOD_HINT_UNKNOWN:
		break;
	}
	eap_session->process = mod_process;

	/*
	 *	Figure out what type of identity we have
	 *	and use it to determine the initial
	 *	request we send.
	 */
	switch (type) {
	/*
	 *	These types need to be transformed into something
	 *	usable before we can do anything.
	 */
	case SIM_ID_TYPE_UNKNOWN:
	case SIM_ID_TYPE_PSEUDONYM:
	case SIM_ID_TYPE_FASTAUTH:
	/*
	 *	Permanent ID means we can just send the challenge
	 */
	case SIM_ID_TYPE_PERMANENT:
		eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1;
		MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity,
								   eap_sim_session->keys.identity_len));
		eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START);
		return RLM_MODULE_HANDLED;
	}

	return RLM_MODULE_HANDLED;
}