/** Process an EAP-Sim/Response/Start
 *
 * Verify that client chose a version, and provided a NONCE_MT,
 * and if so, then change states to challenge, and send the new
 * challenge, else, resend the Request/Start.
 */
static int process_eap_sim_start(eap_handler_t *handler, VALUE_PAIR *vps)
{
	REQUEST *request = handler->request;
	VALUE_PAIR *nonce_vp, *selectedversion_vp;
	eap_sim_state_t *ess;
	uint16_t simversion;
	ess = (eap_sim_state_t *)handler->opaque;

	nonce_vp = pairfind(vps, PW_EAP_SIM_NONCE_MT, 0, TAG_ANY);
	selectedversion_vp = pairfind(vps, PW_EAP_SIM_SELECTED_VERSION, 0, TAG_ANY);
	if (!nonce_vp || !selectedversion_vp) {
		RDEBUG2("Client did not select a version and send a NONCE");
		eap_sim_stateenter(handler, ess, EAPSIM_SERVER_START);

		return 1;
	}

	/*
	 *	Okay, good got stuff that we need. Check the version we found.
	 */
	if (selectedversion_vp->length < 2) {
		REDEBUG("EAP-SIM version field is too short");
		return 0;
	}
	memcpy(&simversion, selectedversion_vp->vp_strvalue, sizeof(simversion));
	simversion = ntohs(simversion);
	if (simversion != EAP_SIM_VERSION) {
		REDEBUG("EAP-SIM version %i is unknown", simversion);
		return 0;
	}

	/*
	 *	Record it for later keying
	 */
	memcpy(ess->keys.versionselect, selectedversion_vp->vp_strvalue, sizeof(ess->keys.versionselect));

	/*
	 *	Double check the nonce size.
	 */
	if(nonce_vp->length != 18) {
		REDEBUG("EAP-SIM nonce_mt must be 16 bytes (+2 bytes padding), not %zu", nonce_vp->length);
		return 0;
	}
	memcpy(ess->keys.nonce_mt, nonce_vp->vp_strvalue + 2, 16);

	/*
	 *	Everything looks good, change states
	 */
	eap_sim_stateenter(handler, ess, EAPSIM_SERVER_CHALLENGE);

	return 1;
}
/*
 * process an EAP-Sim/Response/Start.
 *
 * verify that client chose a version, and provided a NONCE_MT,
 * and if so, then change states to challenge, and send the new
 * challenge, else, resend the Request/Start.
 *
 */
static int process_eap_sim_start(EAP_HANDLER *handler, VALUE_PAIR *vps)
{
	VALUE_PAIR *nonce_vp, *selectedversion_vp;
	struct eap_sim_server_state *ess;
	uint16_t simversion;

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

	nonce_vp = pairfind(vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_NONCE_MT, 0, TAG_ANY);
	selectedversion_vp = pairfind(vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_SELECTED_VERSION, 0, TAG_ANY);

	if(nonce_vp == NULL ||
	   selectedversion_vp == NULL) {
		DEBUG2("   client did not select a version and send a NONCE");
		eap_sim_stateenter(handler, ess, eapsim_server_start);
		return 1;
	}

	/*
	 * okay, good got stuff that we need. Check the version we found.
	 */
	if(selectedversion_vp->length < 2) {
		DEBUG2("   EAP-Sim version field is too short.");
		return 0;
	}
	memcpy(&simversion, selectedversion_vp->vp_strvalue, sizeof(simversion));
	simversion = ntohs(simversion);
	if(simversion != EAP_SIM_VERSION) {
		DEBUG2("   EAP-Sim version %d is unknown.", simversion);
		return 0;
	}

	/* record it for later keying */
 	memcpy(ess->keys.versionselect, selectedversion_vp->vp_strvalue,
	       sizeof(ess->keys.versionselect));

	/*
	 * double check the nonce size.
	 */
	if(nonce_vp->length != 18) {
	  DEBUG2("   EAP-Sim nonce_mt must be 16 bytes (+2 bytes padding), not %d", (int) nonce_vp->length);
		return 0;
	}
	memcpy(ess->keys.nonce_mt, nonce_vp->vp_strvalue+2, 16);

	/* everything looks good, change states */
	eap_sim_stateenter(handler, ess, eapsim_server_challenge);
	return 1;
}
/*
 *	Initiate the EAP-SIM session by starting the state machine
 *      and initiating the state.
 */
static int eap_sim_initiate(UNUSED void *instance, eap_handler_t *handler)
{
	REQUEST *request = handler->request;
	eap_sim_state_t *ess;
	time_t n;

	ess = talloc_zero(handler, eap_sim_state_t);
	if (!ess) {
		RDEBUG2("No space for EAP-SIM state");
		return 0;
	}

	handler->opaque = ess;
	handler->stage = AUTHENTICATE;

	/*
	 *	Save the keying material, because it could change on a subsequent retrieval.
	 */
	if (!eap_sim_get_challenge(handler, request->config_items, 0, ess) ||
	    !eap_sim_get_challenge(handler, request->config_items, 1, ess) ||
	    !eap_sim_get_challenge(handler, request->config_items, 2, ess)) {
		return 0;
	}

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

	eap_sim_stateenter(handler, ess, EAPSIM_SERVER_START);

	return 1;
}
/*
 *	Initiate the EAP-SIM session by starting the state machine
 *      and initiating the state.
 */
static int eap_sim_initiate(void *type_data, EAP_HANDLER *handler)
{
	struct eap_sim_server_state *ess;
	VALUE_PAIR *vp;
	VALUE_PAIR *outvps;
	time_t n;

	outvps = handler->request->reply->vps;

	type_data = type_data;  /* shut up compiler */

	vp = pairfind(outvps, ATTRIBUTE_EAP_SIM_RAND1, 0, TAG_ANY);
	if(vp == NULL) {
	        DEBUG2("   can not initiate sim, no RAND1 attribute");
		return 0;
	}

	ess = malloc(sizeof(struct eap_sim_server_state));
	if(ess == NULL) {
		DEBUG2("   no space for eap sim state");
		return 0;
	}

	handler->opaque = ((void *)ess);
	handler->free_opaque = eap_sim_state_free;

	handler->stage = AUTHENTICATE;

	/*
	 * save the keying material, because it could change on a subsequent
	 * retrival.
	 *
	 */
	if((eap_sim_getchalans(outvps, 0, ess) +
	    eap_sim_getchalans(outvps, 1, ess) +
	    eap_sim_getchalans(outvps, 2, ess)) != 3)
	{
	        DEBUG2("   can not initiate sim, missing attributes");
		return 0;
	}

	/*
	 * this value doesn't have be strong, but it is good if it
	 * is different now and then
	 */
	time(&n);
	ess->sim_id = (n & 0xff);

	eap_sim_stateenter(handler, ess, eapsim_server_start);

	return 1;
}
/** Process an EAP-Sim/Response/Challenge
 *
 * Verify that MAC that we received matches what we would have
 * calculated from the packet with the SRESx appended.
 *
 */
static int process_eap_sim_challenge(eap_handler_t *handler, VALUE_PAIR *vps)
{
	REQUEST *request = handler->request;
	eap_sim_state_t *ess = handler->opaque;

	uint8_t srescat[EAPSIM_SRES_SIZE * 3];
	uint8_t *p = srescat;

	uint8_t calcmac[EAPSIM_CALCMAC_SIZE];

	memcpy(p, ess->keys.sres[0], EAPSIM_SRES_SIZE);
	p += EAPSIM_SRES_SIZE;
	memcpy(p, ess->keys.sres[1], EAPSIM_SRES_SIZE);
	p += EAPSIM_SRES_SIZE;
	memcpy(p, ess->keys.sres[2], EAPSIM_SRES_SIZE);

	/*
	 *	Verify the MAC, now that we have all the keys
	 */
	if (eapsim_checkmac(handler, vps, ess->keys.K_aut, srescat, sizeof(srescat), calcmac)) {
		RDEBUG2("MAC check succeed");
	} else {
		int i, j;
		char macline[20*3];
		char *m = macline;

		j=0;
		for (i = 0; i < EAPSIM_CALCMAC_SIZE; i++) {
			if(j==4) {
			  *m++ = '_';
			  j=0;
			}
			j++;

			sprintf(m, "%02x", calcmac[i]);
			m = m + strlen(m);
		}
		REDEBUG("Calculated MAC (%s) did not match", macline);
		return 0;
	}

	/* everything looks good, change states */
	eap_sim_stateenter(handler, ess, EAPSIM_SERVER_SUCCESS);
	return 1;
}
/*
 * process an EAP-Sim/Response/Challenge
 *
 * verify that MAC that we received matches what we would have
 * calculated from the packet with the SRESx appended.
 *
 */
static int process_eap_sim_challenge(EAP_HANDLER *handler, VALUE_PAIR *vps)
{
	struct eap_sim_server_state *ess;
	uint8_t srescat[EAPSIM_SRES_SIZE*3];
	uint8_t calcmac[EAPSIM_CALCMAC_SIZE];

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

	memcpy(srescat +(0*EAPSIM_SRES_SIZE), ess->keys.sres[0], EAPSIM_SRES_SIZE);
	memcpy(srescat +(1*EAPSIM_SRES_SIZE), ess->keys.sres[1], EAPSIM_SRES_SIZE);
	memcpy(srescat +(2*EAPSIM_SRES_SIZE), ess->keys.sres[2], EAPSIM_SRES_SIZE);

	/* verify the MAC, now that we have all the keys. */
	if(eapsim_checkmac(vps, ess->keys.K_aut,
			   srescat, sizeof(srescat),
			   calcmac)) {
		DEBUG2("MAC check succeed\n");
	} else {
		int i, j;
		char macline[20*3];
		char *m = macline;

		j=0;
		for (i = 0; i < EAPSIM_CALCMAC_SIZE; i++) {
			if(j==4) {
			  *m++ = '_';
			  j=0;
			}
			j++;

			sprintf(m, "%02x", calcmac[i]);
			m = m + strlen(m);
		}
		DEBUG2("calculated MAC (%s) did not match", macline);
		return 0;
	}

	/* everything looks good, change states */
	eap_sim_stateenter(handler, ess, eapsim_server_success);
	return 1;
}
/** 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 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;
}