File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / eap.c
Revision 1.1.1.4 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:39:23 2021 UTC (3 years, 2 months ago) by misho
Branches: mpd, MAIN
CVS tags: v5_9p16, v5_9, HEAD
mpd 5.9

/*
 * See ``COPYRIGHT.mpd''
 *
 * $Id: eap.c,v 1.1.1.4 2021/03/17 00:39:23 misho Exp $
 *
 */

#include "ppp.h"
#include "radius.h"
#include "auth.h"
#include "ngfunc.h"

/*
 * INTERNAL FUNCTIONS
 */

  static void   EapSendRequest(Link l, u_char type);
  static void	EapSendNak(Link l, u_char id, u_char type);
  static void	EapSendIdentRequest(Link l);
  static void	EapIdentTimeout(void *ptr);
  static char	EapTypeSupported(u_char type);
  static void	EapRadiusProxy(Link l, AuthData auth, const u_char *pkt, u_short len);
  static void	EapRadiusProxyFinish(Link l, AuthData auth);
  static void	EapRadiusSendMsg(void *ptr);
  static void	EapRadiusSendMsgTimeout(void *ptr);
  static int	EapSetCommand(Context ctx, int ac, const char *const av[], const void *arg);

  /* Set menu options */
  enum {
    SET_ACCEPT,
    SET_DENY,
    SET_ENABLE,
    SET_DISABLE,
    SET_YES,
    SET_NO
  };

/*
 * GLOBAL VARIABLES
 */

  const struct cmdtab EapSetCmds[] = {
    { "accept [opt ...]",		"Accept option",
	EapSetCommand, NULL, 2, (void *) SET_ACCEPT },
    { "deny [opt ...]",			"Deny option",
	EapSetCommand, NULL, 2, (void *) SET_DENY },
    { "enable [opt ...]",		"Enable option",
	EapSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	EapSetCommand, NULL, 2, (void *) SET_DISABLE },
    { "yes [opt ...]",			"Enable and accept option",
	EapSetCommand, NULL, 2, (void *) SET_YES },
    { "no [opt ...]",			"Disable and deny option",
	EapSetCommand, NULL, 2, (void *) SET_NO },
    { NULL, NULL, NULL, NULL, 0, NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static const struct confinfo	gConfList[] = {
    { 0,	EAP_CONF_RADIUS,	"radius-proxy"	},
    { 1,	EAP_CONF_MD5,		"md5"		},
    { 0,	0,			NULL		},
  };



/*
 * EapInit()
 */

void
EapInit(Link l)
{
  EapInfo	eap = &l->lcp.auth.eap;

  Disable(&eap->conf.options, EAP_CONF_MD5);
  Accept(&eap->conf.options, EAP_CONF_MD5);
}

/*
 * EapStart()
 */

void
EapStart(Link l, int which)
{
  Auth		a = &l->lcp.auth;
  EapInfo	eap = &l->lcp.auth.eap;
  int	i;

  for (i = 0; i < EAP_NUM_TYPES; i++)
    eap->peer_types[i] = eap->want_types[i] = 0;

  /* fill a list of requestable auth types */
  if (Enabled(&eap->conf.options, EAP_CONF_MD5))
    eap->want_types[0] = EAP_TYPE_MD5CHAL;

  /* fill a list of acceptable auth types */
  if (Acceptable(&eap->conf.options, EAP_CONF_MD5))
    eap->peer_types[0] = EAP_TYPE_MD5CHAL;

  if (l->originate == LINK_ORIGINATE_LOCAL)
    a->params.msoft.chap_alg = a->self_to_peer_alg;
  else
    a->params.msoft.chap_alg = a->peer_to_self_alg;

  switch (which) {
    case AUTH_PEER_TO_SELF:

      /* Initialize retry counter and timer */
      eap->next_id = 1;
      eap->retry = AUTH_RETRIES;

      TimerInit(&eap->reqTimer, "EapRadiusSendMsgTimer",
	l->conf.retry_timeout * SECONDS, EapRadiusSendMsgTimeout, (void *) l);

      TimerInit(&eap->identTimer, "EapTimer",
	l->conf.retry_timeout * SECONDS, EapIdentTimeout, (void *) l);
      TimerStart(&eap->identTimer);

      /* Send first request
       * Send the request even, if the Radius-Eap-Proxy feature is active,
       * this saves on roundtrip.
       */
      EapSendIdentRequest(l);
      break;

    case AUTH_SELF_TO_PEER:	/* Just wait for authenitcaor's request */
      break;

    default:
      assert(0);
  }
}

/*
 * EapStop()
 */

void
EapStop(EapInfo eap)
{
  TimerStop(&eap->identTimer);
  TimerStop(&eap->reqTimer);
}

/*
 * EapSendRequest()
 *
 * Send an EAP request to peer.
 */

static void
EapSendRequest(Link l, u_char type)
{
  Auth		const a = &l->lcp.auth;
  EapInfo	const eap = &a->eap;
  ChapInfo	const chap = &a->chap;
  ChapParams	const cp = &a->params.chap;
  int		i = 0;
  u_char	req_type = 0;

  if (type == 0) {
    for (i = 0; i < EAP_NUM_TYPES; i++) {
      if (eap->want_types[i] != 0) {
        req_type = eap->want_types[i];
        break;
      }
    }
  } else {
    req_type = type;
  }

  if (req_type == 0) {
    Log(LG_AUTH, ("[%s] EAP: ran out of EAP Types", l->name));
    AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
    return;
  }

  /* don't request this type again */
  eap->want_types[i] = 0;

  switch (req_type) {
    case EAP_TYPE_MD5CHAL:

      /* Invalidate any old challenge data */
      cp->chal_len = 0;
      /* Initialize retry counter and timer */
      chap->next_id = 1;
      chap->retry = AUTH_RETRIES;
      chap->proto = PROTO_EAP;
      a->peer_to_self_alg = CHAP_ALG_MD5;

      TimerInit(&chap->chalTimer, "ChalTimer",
        l->conf.retry_timeout * SECONDS, ChapChalTimeout, l);
      TimerStart(&chap->chalTimer);

      /* Send first challenge */
      ChapSendChallenge(l);
      break;

    default:
      Log(LG_AUTH, ("[%s] EAP: Type %d is currently un-implemented",
	l->name, eap->want_types[i]));
      AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
  }

  return;
}

/*
 * EapSendNak()
 *
 * Send an EAP Nak to peer.
 */

static void
EapSendNak(Link l, u_char id, u_char type)
{
  Auth		const a = &l->lcp.auth;
  EapInfo	const eap = &a->eap;
  int		i = 0;
  u_char	nak_type = 0;

  (void)type;
  for (i = 0; i < EAP_NUM_TYPES; i++) {
    if (eap->peer_types[i] != 0) {
      nak_type = eap->peer_types[i];
      break;
    }
  }

  if (nak_type == 0) {
    Log(LG_AUTH, ("[%s] EAP: ran out of EAP Types", l->name));
    AuthFinish(l, AUTH_SELF_TO_PEER, FALSE);
    return;
  }

  /* don't nak this proto again */
  eap->peer_types[i] = 0;

  AuthOutput(l, PROTO_EAP, EAP_RESPONSE, id, &nak_type, 1, 0, EAP_TYPE_NAK);
  return;
}

/*
 * EapSendIdentRequest()
 *
 * Send an Ident Request to the peer.
 */

static void
EapSendIdentRequest(Link l)
{
  EapInfo	const eap = &l->lcp.auth.eap;

  /* Send the initial Identity request */
  AuthOutput(l, PROTO_EAP, EAP_REQUEST,  eap->next_id++, NULL, 0, 0, EAP_TYPE_IDENT);
}

/*
 * EapInput()
 *
 * Accept an incoming EAP packet
 */

void
EapInput(Link l, AuthData auth, const u_char *pkt, u_short len)
{
  Auth		const a = &l->lcp.auth;
  EapInfo	const eap = &a->eap;
  int		data_len = len - 1, i, acc_type;
  const u_char	*data = NULL;
  u_char	type = 0;
  
  if (pkt != NULL) {
    data = data_len > 0 ? &pkt[1] : NULL;
    type = pkt[0];
  }
  
  if (Enabled(&eap->conf.options, EAP_CONF_RADIUS)) {
	EapRadiusProxy(l, auth, pkt, len);
	return;
  }

  switch (auth->code) {
    case EAP_REQUEST:
      switch (type) {
	case EAP_TYPE_IDENT:
	  AuthOutput(l, PROTO_EAP, EAP_RESPONSE, auth->id, (u_char *) auth->conf.authname,
	    strlen(auth->conf.authname), 0, EAP_TYPE_IDENT);
	  break;

	case EAP_TYPE_NAK:
	case EAP_TYPE_NOTIF:
	  Log(LG_AUTH, ("[%s] EAP: Type %s is invalid in Request messages",
	    l->name, EapType(type)));
	  AuthFinish(l, AUTH_SELF_TO_PEER, FALSE);
	  break;

	/* deal with Auth Types */
	default:
	  acc_type = 0;
	  if (EapTypeSupported(type)) {
	    for (i = 0; i < EAP_NUM_TYPES; i++) {
	      if (eap->peer_types[i] == type) {
		acc_type = eap->peer_types[i];
		break;
	      }
	    }

	    if (acc_type == 0) {
	      Log(LG_AUTH, ("[%s] EAP: Type %s not acceptable", l->name,
	        EapType(type)));
	      EapSendNak(l, auth->id, type);
	      break;
	    }

	    switch (type) {
	      case EAP_TYPE_MD5CHAL:
		a->self_to_peer_alg = CHAP_ALG_MD5;
		auth->code = CHAP_CHALLENGE;
		ChapInput(l, auth, &pkt[1], len - 1);
		return;

	      default:
		assert(0);
	    }
	  } else {
	    Log(LG_AUTH, ("[%s] EAP: Type %s not supported", l->name, EapType(type)));
	    EapSendNak(l, auth->id, type);
	  }
      }
      break;

    case EAP_RESPONSE:
      switch (type) {
	case EAP_TYPE_IDENT:
	  TimerStop(&eap->identTimer);
	  Log(LG_AUTH, ("[%s] EAP: Identity:%*.*s",
	    l->name, data_len, data_len, data));
	  EapSendRequest(l, 0);
	  break;

	case EAP_TYPE_NOTIF:
	  Log(LG_AUTH, ("[%s] EAP: Notify:%*.*s ", l->name,
	    data_len, data_len, data));
	  break;

	case EAP_TYPE_NAK:
	  Log(LG_AUTH, ("[%s] EAP: Nak desired Type %s ", l->name,
	    EapType(data[0])));
	  if (EapTypeSupported(data[0]))
	    EapSendRequest(l, data[0]);
	  else
	    EapSendRequest(l, 0);
	  break;

	case EAP_TYPE_MD5CHAL:
	  auth->code = CHAP_RESPONSE;
	  ChapInput(l, auth, &pkt[1], len - 1);
	  return;

	default:
	  Log(LG_AUTH, ("[%s] EAP: unknown type %d", l->name, type));
	  AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
      }
      break;

    case EAP_SUCCESS:
      AuthFinish(l, AUTH_SELF_TO_PEER, TRUE);
      break;

    case EAP_FAILURE:
      AuthFinish(l, AUTH_SELF_TO_PEER, FALSE);
      break;

    default:
      Log(LG_AUTH, ("[%s] EAP: unknown code %d", l->name, auth->code));
      AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
  }
  AuthDataDestroy(auth);
}

/*
 * EapRadiusProxy()
 *
 * Proxy EAP Requests from/to the RADIUS server
 */

static void
EapRadiusProxy(Link l, AuthData auth, const u_char *pkt, u_short len)
{
  int		data_len = len - 1;
  const u_char	*data = NULL;
  u_char	type = 0;
  Auth		const a = &l->lcp.auth;
  EapInfo	const eap = &a->eap;
  struct fsmheader	lh;

  Log(LG_AUTH, ("[%s] EAP: Proxying packet to RADIUS", l->name));

  if (pkt != NULL) {
    data = data_len > 0 ? &pkt[1] : NULL;
    type = pkt[0];
  }

  if (auth->code == EAP_RESPONSE && type == EAP_TYPE_IDENT) {
    TimerStop(&eap->identTimer);
    if (data_len >= AUTH_MAX_AUTHNAME) {
      Log(LG_AUTH, ("[%s] EAP: Identity to big (%d), truncating",
	l->name, data_len));
        data_len = AUTH_MAX_AUTHNAME - 1;
    }
    memset(eap->identity, 0, sizeof(eap->identity));
    strncpy(eap->identity, (const char *)data, data_len);
    Log(LG_AUTH, ("[%s] EAP: Identity: %s", l->name, eap->identity));
  }

  TimerStop(&eap->reqTimer);

  /* prepare packet */
  lh.code = auth->code;
  lh.id = auth->id;
  lh.length = htons(len + sizeof(lh));

  auth->params.eapmsg = Malloc(MB_AUTH, len + sizeof(lh));
  memcpy(auth->params.eapmsg, &lh, sizeof(lh));
  memcpy(&auth->params.eapmsg[sizeof(lh)], pkt, len);

  auth->params.eapmsg_len = len + sizeof(lh);
  strlcpy(auth->params.authname, eap->identity, sizeof(auth->params.authname));

  auth->eap_radius = TRUE;

  auth->finish = EapRadiusProxyFinish;
  AuthAsyncStart(l, auth);
  
}

/*
 * RadiusEapProxyFinish()
 *
 * Return point from the asynch RADIUS EAP Proxy Handler.
 * 
 */
 
static void
EapRadiusProxyFinish(Link l, AuthData auth)
{
  Auth		const a = &l->lcp.auth;
  EapInfo	eap = &a->eap;
  
  Log(LG_AUTH, ("[%s] EAP: RADIUS return status: %s", 
    l->name, AuthStatusText(auth->status)));

  /* this shouldn't happen normally, however be liberal */
  if (a->params.eapmsg == NULL) {
    struct fsmheader	lh;

    Log(LG_AUTH, ("[%s] EAP: Warning, rec'd empty EAP-Message", 
      l->name));
    /* prepare packet */
    lh.code = auth->status == AUTH_STATUS_SUCCESS ? EAP_SUCCESS : EAP_FAILURE;
    lh.id = auth->id;
    lh.length = htons(sizeof(lh));

    a->params.eapmsg = Mdup(MB_AUTH, &lh, sizeof(lh));
    a->params.eapmsg_len = sizeof(lh);
  }

  if (a->params.eapmsg != NULL) {
    eap->retry = AUTH_RETRIES;
    
    EapRadiusSendMsg(l);
    if (auth->status == AUTH_STATUS_UNDEF)
      TimerStart(&eap->reqTimer);
  }

  if (auth->status == AUTH_STATUS_FAIL) {
    AuthFinish(l, AUTH_PEER_TO_SELF, FALSE);
  } else if (auth->status == AUTH_STATUS_SUCCESS) {
    AuthFinish(l, AUTH_PEER_TO_SELF, TRUE);
  } 

  AuthDataDestroy(auth);  
}

/*
 * EapRadiusSendMsg()
 *
 * Send an EAP Message to the peer
 */

static void
EapRadiusSendMsg(void *ptr)
{
  Mbuf		bp;
  Link		l = (Link)ptr;
  Auth		const a = &l->lcp.auth;
  FsmHeader	const f = (FsmHeader)(void *)a->params.eapmsg;
  char		buf[32];

  if (a->params.eapmsg_len > 4) {
    Log(LG_AUTH, ("[%s] EAP: send %s #%d len: %d, type: %s",
      l->name, EapCode(f->code, buf, sizeof(buf)), f->id, htons(f->length),
      EapType(a->params.eapmsg[4])));
  } else {
    Log(LG_AUTH, ("[%s] EAP: send %s #%d len: %d",
      l->name, EapCode(f->code, buf, sizeof(buf)), f->id, htons(f->length)));
  } 

  bp = mbcopyback(NULL, 0, a->params.eapmsg, a->params.eapmsg_len);
  NgFuncWritePppFrameLink(l, PROTO_EAP, bp);
}

/*
 * EapRadiusSendMsgTimeout()
 *
 * Timer expired for reply to our request
 */

static void
EapRadiusSendMsgTimeout(void *ptr)
{
    Link	l = (Link)ptr;
    EapInfo	const eap = &l->lcp.auth.eap;

    if (--eap->retry > 0) {
	TimerStart(&eap->reqTimer);
	EapRadiusSendMsg(l);
    }
}

/*
 * EapIdentTimeout()
 *
 * Timer expired for reply to our request
 */

static void
EapIdentTimeout(void *ptr)
{
    Link	l = (Link)ptr;
    EapInfo	const eap = &l->lcp.auth.eap;

    if (--eap->retry > 0) {
	TimerStart(&eap->identTimer);
	EapSendIdentRequest(l);
    }
}

/*
 * EapStat()
 */

int
EapStat(Context ctx, int ac, const char *const av[], const void *arg)
{
  EapInfo	const eap = &ctx->lnk->lcp.auth.eap;

  (void)ac;
  (void)av;
  (void)arg;

  Printf("\tIdentity     : %s\r\n", eap->identity);
  Printf("EAP options\r\n");
  OptStat(ctx, &eap->conf.options, gConfList);

  return (0);
}

/*
 * EapCode()
 */

const char *
EapCode(u_char code, char *buf, size_t len)
{
  switch (code) {
    case EAP_REQUEST:
	strlcpy(buf, "REQUEST", len);
	break;
    case EAP_RESPONSE:
	strlcpy(buf, "RESPONSE", len);
	break;
    case EAP_SUCCESS:
	strlcpy(buf, "SUCCESS", len);
	break;
    case EAP_FAILURE:
	strlcpy(buf, "FAILURE", len);
	break;
    default:
	snprintf(buf, len, "code%d", code);
  }
  return(buf);
}

/*
 * EapType()
 */

const char *
EapType(u_char type)
{
  switch (type) {
    case EAP_TYPE_IDENT:
      return("Identity");
    case EAP_TYPE_NOTIF:
      return("Notification");
    case EAP_TYPE_NAK:
      return("Nak");
    case EAP_TYPE_MD5CHAL:
      return("MD5 Challenge");
    case EAP_TYPE_OTP:
      return("One Time Password");
    case EAP_TYPE_GTC:
      return("Generic Token Card");
    case EAP_TYPE_EAP_TLS:
      return("TLS");
    case EAP_TYPE_MSCHAP_V2:
      return("MS-CHAPv2");
    case EAP_TYPE_EAP_TTLS:
      return("TTLS");
    default:
      return("UNKNOWN");
  }
}

/*
 * EapTypeSupported()
 */

static char
EapTypeSupported(u_char type)
{
  switch (type) {
    case EAP_TYPE_IDENT:
    case EAP_TYPE_NOTIF:
    case EAP_TYPE_NAK:
    case EAP_TYPE_MD5CHAL:
      return 1;

    default:
      return 0;
  }
}

/*
 * EapSetCommand()
 */

static int
EapSetCommand(Context ctx, int ac, const char *const av[], const void *arg)
{
  EapInfo	const eap = &ctx->lnk->lcp.auth.eap;

  if (ac == 0)
    return(-1);

  switch ((intptr_t)arg) {

    case SET_ACCEPT:
      AcceptCommand(ac, av, &eap->conf.options, gConfList);
      break;

    case SET_DENY:
      DenyCommand(ac, av, &eap->conf.options, gConfList);
      break;

    case SET_ENABLE:
      EnableCommand(ac, av, &eap->conf.options, gConfList);
      break;

    case SET_DISABLE:
      DisableCommand(ac, av, &eap->conf.options, gConfList);
      break;

    case SET_YES:
      YesCommand(ac, av, &eap->conf.options, gConfList);
      break;

    case SET_NO:
      NoCommand(ac, av, &eap->conf.options, gConfList);
      break;

    default:
      assert(0);
  }

  return(0);
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>