/**
 * A client-side 802.1x implementation supporting EAP/TLS
 *
 * This code is released under both the GPL version 2 and BSD licenses.
 * Either license may be used.  The respective licenses are found below.
 * 
 * Copyright (C) 2002 Bryan D. Payne & Nick L. Petroni Jr.
 * All Rights Reserved
 *
 * --- GPL Version 2 License ---
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * --- BSD License ---
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  - All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *       This product includes software developed by the University of
 *       Maryland at College Park and its contributors.
 *  - Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*******************************************************************
 * EAP-MSCHAPv2 Function implementations
 * 
 * File: eapmschapv2.c
 *
 * Authors: Chris.Hessing@utah.edu
 *
 * $Id: eapmschapv2.c,v 1.23 2004/06/29 01:35:35 chessing Exp $
 * $Date: 2004/06/29 01:35:35 $
 * $Log: eapmschapv2.c,v $
 * Revision 1.23  2004/06/29 01:35:35  chessing
 *
 * Added credit to eapleap.c for Gilbert Goodwill (who gave me the clues I needed to figure out the keying piece for LEAP).  Added patch from Toby Collett to make PEAP work for broken implementations of IAS.  (New option ias_quirk has been added.)
 *
 * Revision 1.22  2004/06/15 03:35:20  chessing
 *
 * New updates including fixes to LEAP (keying now works with wireless) and adding EAP-AKA.
 *
 * Revision 1.21  2004/06/15 03:22:29  chessing
 *
 * XSupplicant Release 1.0
 *
 *
 *******************************************************************/

#include <openssl/rand.h>
#include <string.h>
#include <netinet/in.h>

#include "profile.h"
#include "config.h"
#include "xsup_debug.h"
#include "xsup_err.h"
#include "frame_structs.h"
#include "eapmschapv2.h"
#include "mschapv2.h"
#include "eap.h"
#include "interactive.h"


int eapmschapv2_setup(struct generic_eap_data *thisint)
{
  struct mschapv2_vars *myvars;

  if (!thisint)
    {
      debug_printf(DEBUG_NORMAL, "Invalid interface structure passed in to eapmschapv2_setup()!\n");
      return XEMALLOC;
    }

  thisint->eap_data = (u_char *)malloc(sizeof(struct mschapv2_vars));
  if (thisint->eap_data == NULL) return XEMALLOC;
  memset(thisint->eap_data, 0, sizeof(struct mschapv2_vars));

  myvars = thisint->eap_data;

  myvars->AuthenticatorChallenge = NULL;
  myvars->PeerChallenge = NULL;
  myvars->NtResponse = NULL;
  myvars->keyingMaterial = NULL;

  return XENONE;
}

int eapmschapv2_process(struct generic_eap_data *thisint, u_char *dataoffs, 
			int insize, u_char *outframe, int *outsize)
{
  struct mschapv2_challenge *challenge;
  struct mschapv2_response *response;
  struct mschapv2_success_request *success;
  struct mschapv2_vars *myvars;
  char *username;
  int respOk;
  u_char recv[41];
  u_char NtHash[16], NtHashHash[16], MasterKey[16];
  u_char mppeSend[16], mppeRecv[16];
  struct config_eap_mschapv2 *userdata;

  if (!thisint)
    {
      debug_printf(DEBUG_NORMAL, "Invalid interface structure passed in to eapmschapv2_process()!\n");
      return XEMALLOC;
    }
  
  if (!outframe)
    {
      debug_printf(DEBUG_NORMAL, "Invalid return buffer in eapmschapv2_process()!\n");
      return XEMALLOC;
    }

  if (!thisint->eap_conf_data)
    {
      debug_printf(DEBUG_NORMAL, "No valid configuration data available for MSCHAP-V2!\n");
      return XEMALLOC;
    }

  userdata = (struct config_eap_mschapv2 *)thisint->eap_conf_data;

  if (!thisint->eap_data)
    {
      debug_printf(DEBUG_NORMAL, "Invalid state configuration in MSCHAP-V2!\n");
      return XEMALLOC;
    }

  myvars = (struct mschapv2_vars *)thisint->eap_data;

  if ((thisint->tempPwd == NULL) && (userdata->password == NULL))
    {
      thisint->need_password = 1;
      thisint->eaptype = strdup("EAP-MS-CHAPv2");
      thisint->eapchallenge = NULL;
      *outsize = 0;
      return XENONE;
    }

  // Make sure we have something to process...
  if (dataoffs == NULL) return XENONE;

  if (userdata->username == NULL)
    {
      username = thisint->identity;
    } else {
      username = userdata->username;
    }

  if ((userdata->password == NULL) && (thisint->tempPwd != NULL))
    {
      userdata->password = thisint->tempPwd;
      thisint->tempPwd = NULL;
    }

  switch ((uint8_t)dataoffs[0])
    {
    case MS_CHAPV2_CHALLENGE:
      debug_printf(DEBUG_AUTHTYPES, "(EAP-MSCHAPv2) Challenge\n");
      challenge = (struct mschapv2_challenge *)dataoffs;
      response = (struct mschapv2_response *)outframe;

      debug_printf(DEBUG_AUTHTYPES, "(EAP-MS-CHAPv2) ID : %02X\n",
		   challenge->MS_CHAPv2_ID);

      // This value should *ALWAYS* be 16!
      if (challenge->Value_Size != 0x10)
	{
	  if (thisint->ias_quirk == 1)
	    {
	      debug_printf(DEBUG_NORMAL, "(EAP-MS-CHAPv2) Invalid Value-Size! (%d), forced to 0x10 (ias_quirk=yes)\n", challenge->Value_Size);
	      challenge->Value_Size = 0x10;
	    }
	  else 
	    {
	      debug_printf(DEBUG_NORMAL, "(EAP-MS-CHAPv2) Invalid Value-Size! (%d)\n", challenge->Value_Size);
	      debug_printf(DEBUG_NORMAL, "(EAP-MS-CHAPv2) Should you enable ias_quirk?\n");
	      return XEMSCHAPV2LEN;
	    }
	}

      if (myvars->AuthenticatorChallenge != NULL)
	{
	  free(myvars->AuthenticatorChallenge);
	  myvars->AuthenticatorChallenge = NULL;
	}

      myvars->AuthenticatorChallenge = (u_char *)malloc(16);
      if (myvars->AuthenticatorChallenge == NULL) return XEMALLOC;

      memcpy(myvars->AuthenticatorChallenge, &challenge->Challenge, 16);
      
      debug_printf(DEBUG_AUTHTYPES, "Authenticator Challenge : ");
      debug_hex_printf(DEBUG_AUTHTYPES, myvars->AuthenticatorChallenge, 16);

      if (myvars->PeerChallenge != NULL)
	{
	  free(myvars->PeerChallenge);
	  myvars->PeerChallenge = NULL;
	}

      // Ignore the RADIUS host, we probably don't care.
      myvars->PeerChallenge = (u_char *)malloc(16);
      if (myvars->PeerChallenge == NULL) return XEMALLOC;

      RAND_bytes(myvars->PeerChallenge, 16);

      debug_printf(DEBUG_AUTHTYPES, "Generated PeerChallenge : ");
      debug_hex_printf(DEBUG_AUTHTYPES, myvars->PeerChallenge,16);

      if (myvars->NtResponse != NULL)
	{
	  free(myvars->NtResponse);
	  myvars->NtResponse = NULL;
	}

      myvars->NtResponse = (u_char *)malloc(24);
      if (myvars->NtResponse == NULL) return XEMALLOC;

      GenerateNTResponse(myvars->AuthenticatorChallenge, myvars->PeerChallenge,
			 username, userdata->password, myvars->NtResponse);

      debug_printf(DEBUG_AUTHTYPES, "myvars->NtResponse = ");
      debug_hex_printf(DEBUG_AUTHTYPES, myvars->NtResponse, 24);

      response->OpCode = MS_CHAPV2_RESPONSE;
      response->MS_CHAPv2_ID = challenge->MS_CHAPv2_ID;
      response->MS_Length = htons(54+strlen(username));   
      response->Value_Size = 49;
      memcpy((u_char *)&response->Peer_Challenge, myvars->PeerChallenge, 16);
      bzero((u_char *)&response->Reserved, 8);
      memcpy((u_char *)&response->NT_Response, myvars->NtResponse, 24);
      debug_printf(DEBUG_AUTHTYPES, "response->NT_Response = ");
      debug_hex_printf(DEBUG_AUTHTYPES, response->NT_Response, 24);
      response->Flags = 0;
      memcpy(&outframe[54],username, strlen(username));
      *outsize = (54 + strlen(username));
      break;

    case MS_CHAPV2_RESPONSE:
      debug_printf(DEBUG_NORMAL, "Got an MS-CHAPv2 Response!?  Ignoring.\n");
      *outsize = 0;
      break;

    case MS_CHAPV2_SUCCESS:
      debug_printf(DEBUG_AUTHTYPES, "(EAP-MSCHAPv2) Success!\n");
      success = (struct mschapv2_success_request *)dataoffs;

      bzero((u_char *)&recv[0], 41);
      memcpy((u_char *)&recv[0], (u_char *)&success->MsgField[2], 40);
      CheckAuthenticatorResponse(userdata->password, 
				 myvars->NtResponse, myvars->PeerChallenge,
				 myvars->AuthenticatorChallenge,
				 username, (u_char *)&recv[0], &respOk);

      if (respOk == 1)
	{
	  debug_printf(DEBUG_AUTHTYPES, "Server authentication check success!  Sending phase 2 success!\n");
	  outframe[0] = MS_CHAPV2_SUCCESS;
	  
	  // We were successful, so generate keying material.
	  NtPasswordHash(userdata->password, (u_char *)&NtHash);
	  HashNtPasswordHash((u_char *)&NtHash, (u_char *)&NtHashHash);
	  GetMasterKey((u_char *)&NtHashHash, myvars->NtResponse, (u_char *)&MasterKey);
	  
	  // Now, get the send key.
	  GetAsymetricStartKey((u_char *)&MasterKey, (u_char *)&mppeSend, 16, TRUE, FALSE);

	  // And the recv key.
	  GetAsymetricStartKey((u_char *)&MasterKey, (u_char *)&mppeRecv, 16, FALSE, FALSE);

	  // Finally, populate our myvars->keyingMaterial.
	  if (myvars->keyingMaterial != NULL)
	    {
	      free(myvars->keyingMaterial);
	      myvars->keyingMaterial = NULL;
	    }
	  myvars->keyingMaterial = (u_char *)malloc(64);  // 32 bytes each.
	  if (myvars->keyingMaterial == NULL) return XEMALLOC;

	  bzero(myvars->keyingMaterial, 64);
	  memcpy(&myvars->keyingMaterial[32], &mppeRecv, 16);
	  memcpy(myvars->keyingMaterial, &mppeSend, 16);
	} else {
	  debug_printf(DEBUG_AUTHTYPES, "Server verification check failed!  Sending PHASE 2 FAILURE!\n");
	  outframe[0] = MS_CHAPV2_FAILURE;
	}
      *outsize = 1;

      

      break;

    case MS_CHAPV2_FAILURE:
      debug_printf(DEBUG_NORMAL, "MS-CHAPv2 Authentication Failure!\n");
      *outsize = 0;
      // We should probably process the failure info, and respond as needed,
      // but, we really don't care if a failure is retryable, as 802.1x will
      // just try again anyway. ;)
      break;

    case MS_CHAPV2_CHANGE_PWD:
      debug_printf(DEBUG_NORMAL, "Password changing is not supported!\n");
      break;
    }

  return XENONE;
}

int eapmschapv2_get_keys(struct interface_data *thisint)
{
  struct mschapv2_vars *myconf;

  if ((!thisint) || (!thisint->userdata) || (!thisint->userdata->activemethod) || (!thisint->userdata->activemethod->eap_data))
      return XEMALLOC;

  myconf = (struct mschapv2_vars *)thisint->userdata->activemethod->eap_data;
  if (thisint->keyingMaterial != NULL)
    {
      free(thisint->keyingMaterial);
    }

  thisint->keyingMaterial = (char *)malloc(64);
  if (thisint->keyingMaterial == NULL) return -1;

  memcpy(thisint->keyingMaterial, myconf->keyingMaterial, 64);
  thisint->keyingLength = 32;
  
  return XENONE;
}

int eapmschapv2_failed(struct generic_eap_data *thisint)
{
  struct config_eap_mschapv2 *userdata;

  if ((!thisint) || (!thisint->eap_conf_data))
    {
      debug_printf(DEBUG_AUTHTYPES, "No EAP MS-CHAPv2 configuration data!  Nothing to do!\n");
      return XEMALLOC;
    }

  userdata = (struct config_eap_mschapv2 *)thisint->eap_conf_data;

#ifndef NO_PWD_RESET
  /*
  if (userdata->password != NULL)
    {
      free(userdata->password);
      userdata->password = NULL;
    }
  */
#endif

  return XENONE;
}

int eapmschapv2_cleanup(struct generic_eap_data *thisint)
{
  struct mschapv2_vars *myvars;

  if (!thisint)
    {
      debug_printf(DEBUG_NORMAL, "Invalid interface structure in eapmschapv2_cleanup()!\n");
      return XEMALLOC;
    }

  myvars = (struct mschapv2_vars *)thisint->eap_data;

  if (thisint->eap_data != NULL)
    {
      if (myvars->AuthenticatorChallenge != NULL)
	{
	  free(myvars->AuthenticatorChallenge);
	  myvars->AuthenticatorChallenge = NULL;
	}

      if (myvars->PeerChallenge != NULL)
	{
	  free(myvars->PeerChallenge);
	  myvars->PeerChallenge = NULL;
	}

      if (myvars->NtResponse != NULL)
	{
	  free(myvars->NtResponse);
	  myvars->NtResponse = NULL;
	}
      
      if (myvars->keyingMaterial != NULL)
	{
	  free(myvars->keyingMaterial);
	  myvars->keyingMaterial = NULL;
	}

      free(thisint->eap_data);
      thisint->eap_data = NULL;
    }
  return XENONE;
}
