#include <freeradius-devel/radiusd.h>
#include "eap.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <freeradius-devel/rad_assert.h>
#include "logging_impl.h"
#include <EAPIKEv2/connector.h>
#include "ike_conf.h"
#define PW_IKEV2_CHALLENGE 1
#define PW_IKEV2_RESPONSE 2
#define PW_IKEV2_SUCCESS 3
#define PW_IKEV2_FAILURE 4
#define PW_IKEV2_MAX_CODES 4
#define IKEV2_HEADER_LEN 4
#define IKEV2_MPPE_KEY_LEN 32
static void add_reply(VALUE_PAIR** vp,
const char* name, const char* value, int len)
{
VALUE_PAIR *reply_attr;
reply_attr = pairmake(name, "", T_OP_EQ);
if (!reply_attr) {
radlog(L_INFO, IKEv2_LOG_PREFIX "add_reply failed to create attribute %s: %s", name, fr_strerror());
return;
}
memcpy(reply_attr->vp_octets, value, len);
reply_attr->length = len;
pairadd(vp, reply_attr);
}
static int set_mppe_keys(EAP_HANDLER *handler)
{
unsigned char *p;
struct IKEv2Session *session;
VALUE_PAIR **outvps;
session = ((struct IKEv2Data*)handler->opaque)->session;
if (session->eapKeyData==NULL){
radlog( L_INFO,IKEv2_LOG_PREFIX "Key session not available!!!");
return 1;
}
outvps= &handler->request->reply->vps;
p = session->eapKeyData;
add_reply(outvps, "MS-MPPE-Recv-Key",(const char*) p, IKEV2_MPPE_KEY_LEN);
p += IKEV2_MPPE_KEY_LEN;
add_reply(outvps, "MS-MPPE-Send-Key",(const char*) p, IKEV2_MPPE_KEY_LEN);
return 0;
}
static int ComposeRadMsg(uint8_t *out,u_int32_t olen, EAP_DS *eap_ds){
eap_ds->request->type.type = PW_EAP_IKEV2;
eap_ds->request->code = ((struct EAPHeader *)out)->Code;
if(eap_ds->request->code<=PW_EAP_REQUEST && olen>4) {
int lenn=(int)ntohs(((struct EAPHeader *)out)->Length);
eap_ds->request->type.data = malloc(lenn);
if (eap_ds->request->type.data == NULL) {
radlog(L_ERR, IKEv2_LOG_PREFIX "out of memory");
return 1;
}
memcpy(eap_ds->request->type.data,out+5,lenn-5);
eap_ds->request->type.length = lenn-5;
} else {
eap_ds->request->type.data=NULL;
eap_ds->request->type.length=0;
}
return 0;
}
static int ikev2_detach(void *type_data)
{
radlog(L_DBG,IKEv2_LOG_PREFIX "dettach");
struct ikev2_ctx *data=(struct ikev2_ctx*)type_data;
if(data) {
Free_ikev2_ctx(data);
data=NULL;
}
return 0;
}
static void ikev2_free_opaque(void *opaque)
{
radlog(L_DBG,IKEv2_LOG_PREFIX "Free session data");
struct IKEv2Data *ikev2_data=(struct IKEv2Data*)opaque;
if(ikev2_data->session) {
if(ikev2_data->session->Status!=IKEv2_SST_ESTABLISHED) {
radlog(L_DBG, IKEv2_LOG_PREFIX "Unfinished IKEv2 session - cleanup!!!");
IKEv2EndSession(ikev2_data->i2,ikev2_data->session);
ikev2_data->session=NULL;
} else {
radlog(L_DBG, IKEv2_LOG_PREFIX "Unfinished IKEv2 session - keep it!!!");
ikev2_data->session=NULL;
}
}
int fastDeleted=FreeSessionIfExpired(ikev2_data->i2,time(NULL));
if(fastDeleted) {
radlog(L_DBG,IKEv2_LOG_PREFIX "Deleted %d expired IKEv2 sessions",fastDeleted);
}
free(ikev2_data);
}
static int ikev2_attach(CONF_SECTION *conf, void **type_data)
{
radlog(L_DBG,IKEv2_LOG_PREFIX "attach");
char *default_authtype=NULL;
char *usersfilename=NULL;
char *server_authtype=NULL;
char *server_idtype=NULL;
CONF_PARSER module_config[] = {
{ "CA_file", PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,trusted),NULL,NULL },
{ "private_key_file",PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,pkfile),NULL,NULL },
{ "private_key_password",PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,pkfile_pwd),NULL,NULL },
{ "certificate_file", PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,certfile),NULL,NULL },
{ "crl_file", PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,crl_file),NULL,NULL },
{ "id", PW_TYPE_STRING_PTR,
offsetof(ikev2_ctx,id),NULL,NULL },
{ "fragment_size",PW_TYPE_INTEGER,
offsetof(ikev2_ctx,max_fragment_size),NULL,IKEv2_DEFAULT_MAX_FRAGMENT_SIZE_STR},
{ "DH_counter_max", PW_TYPE_INTEGER,
offsetof(ikev2_ctx,DHCounterMax),NULL,IKEv2_DEFAULT_DH_COUNTER_MAX_STR},
{ "default_authtype",PW_TYPE_STRING_PTR,
0,&default_authtype,"both" },
{ "usersfile",PW_TYPE_STRING_PTR,
0,&usersfilename,"${confdir}/users" },
{ "server_authtype",PW_TYPE_STRING_PTR,
0,&server_authtype,"secret" },
{ "idtype",PW_TYPE_STRING_PTR,
0,&server_idtype,IKEv2_DEFAULT_IDTYPE_STR},
{ "certreq",PW_TYPE_BOOLEAN,
offsetof(ikev2_ctx,sendCertReq),NULL,"no"},
{ "fast_DH_exchange",PW_TYPE_BOOLEAN,
offsetof(ikev2_ctx,enableFastDHEx),NULL,"no"},
{ "fast_timer_expire",PW_TYPE_INTEGER,
offsetof(ikev2_ctx,fastExpire),NULL,"900"},
{ "enable_fast_reauth",PW_TYPE_BOOLEAN,
offsetof(ikev2_ctx,enableFastReconnect),NULL,"yes"},
{ NULL, -1, 0, NULL, NULL }
};
ikev2_set_log_callback(vxlogf);
struct ikev2_ctx *i2;
i2 = Create_ikev2_ctx();
if (i2 == NULL) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Error: Can't allocate mem for i2.");
return -1;
}
*type_data=i2;
if (cf_section_parse(conf,i2, module_config) < 0) {
ikev2_detach(i2);
return -1;
}
hexalize(&i2->id,&i2->idlen);
i2->authtype=rad_get_authtype(server_authtype);
if(!i2->id) {
ikev2_detach(i2);
radlog(L_ERR,IKEv2_LOG_PREFIX "'id' configuration option is required!!!");
return -1;
}
switch(i2->authtype) {
case IKEv2_AUTH_SK:
break;
case IKEv2_AUTH_CERT:
if(!i2->certfile || !i2->pkfile) {
ikev2_detach(i2);
radlog(L_ERR,IKEv2_LOG_PREFIX "'certificate_file' and 'private_key_file' items are required for 'cert' auth type");
return -1;
}
if(!file_exists(i2->certfile)) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Can not open 'certificate_file' %s",i2->certfile);
ikev2_detach(i2);
return -1;
}
if(!file_exists(i2->pkfile)) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Can not open 'private_key_file' %s",i2->pkfile);
ikev2_detach(i2);
return -1;
}
break;
}
if(!i2->trusted) {
radlog(L_AUTH,IKEv2_LOG_PREFIX "'CA_file' item not set, client cert based authentication will fail");
} else {
if(!file_exists(i2->trusted)) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Can not open 'CA_file' %s",i2->trusted);
ikev2_detach(i2);
return -1;
}
}
if(i2->crl_file) {
if(!file_exists(i2->crl_file)) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Can not open 'crl_file' %s",i2->crl_file);
ikev2_detach(i2);
return -1;
}
radlog(L_DBG,IKEv2_LOG_PREFIX "Using CRL file: %s",i2->crl_file);
}
i2->idtype=IdTypeFromName(server_idtype);
if(i2->idtype<=0) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Unsupported 'idtype': %s",server_idtype);
free(server_idtype);
server_idtype=NULL;
ikev2_detach(i2);
return -1;
}
free(server_idtype);
server_idtype=NULL;
radlog(L_DBG,IKEv2_LOG_PREFIX "Reading proposals ...");
if(rad_load_proposals(i2,conf)) {
ikev2_detach(i2);
radlog(L_ERR,IKEv2_LOG_PREFIX "Failed to load proposals");
return -1;
}
int res=rad_load_credentials(i2,usersfilename,default_authtype);
free(default_authtype);
default_authtype=NULL;
free(usersfilename);
usersfilename=NULL;
free(server_authtype);
server_authtype=NULL;
if(res==-1) {
ikev2_detach(i2);
radlog(L_ERR,IKEv2_LOG_PREFIX "Error while loading users credentials");
return -1;
}
i2->x509_store = NULL;
if(CertInit(i2)){
ikev2_detach(i2);
radlog(L_ERR,IKEv2_LOG_PREFIX "Error while loading certs/crl");
return -1;
}
return 0;
}
static int ikev2_initiate(void *type_data, EAP_HANDLER *handler)
{
radlog( L_INFO,IKEv2_LOG_PREFIX "Initiate connection!");
struct ikev2_ctx *i2=(struct ikev2_ctx*)type_data;
struct IKEv2Session *session;
handler->free_opaque=ikev2_free_opaque;
uint8_t *eap_username=handler->request->username->vp_strvalue;
session=FindSessionByFastid(i2,(const char*)eap_username);
if(!session) {
if( IKEv2BeginSession( i2, &session, IKEv2_STY_INITIATOR ) != IKEv2_RET_OK ) {
radlog(L_ERR,IKEv2_LOG_PREFIX "Error: Can't initialize IKEv2 session.");
return 1;
}
} else {
radlog(L_DBG, IKEv2_LOG_PREFIX "Fast reconnect procedure start");
}
session->timestamp=time(NULL);
struct IKEv2Data *ikev2_data=IKEv2Data_new(i2,session);
handler->opaque=ikev2_data;
#if 0
if(i2->SessionList) {
int session_count=0;
struct IKEv2Session *ss;
ss=i2->SessionList;
while(ss) {
session_count++;
ss=ss->pNext;
}
radlog(L_ERR,"XXX: session list contains:%d",session_count);
}
#endif
uint8_t *sikemsg=NULL;
u_int32_t slen=0;
if( IKEv2ProcessMsg( i2, NULL , &sikemsg, &slen, session) != IKEv2_RET_OK )
{
radlog(L_ERR,IKEv2_LOG_PREFIX "Error while processing IKEv2 message");
return 1;
}
uint8_t *out=NULL;
u_int32_t olen=0;
if( slen != 0 )
{
session->eapMsgID++;
olen = CreateIKEv2Message(i2, sikemsg, slen, false, 0, session, &out );
if( session->fragdata )
session->sendfrag = true;
}
if (olen>0&&out!=NULL){
if(ComposeRadMsg(out,olen,handler->eap_ds)){
free(out);
return 0;
}
free(out);
}
handler->stage = AUTHENTICATE;
return 1;
}
static int ikev2_authenticate(void *type_data, EAP_HANDLER *handler)
{
struct ikev2_ctx *i2=(struct ikev2_ctx*)type_data;
radlog( L_INFO, IKEv2_LOG_PREFIX "authenticate" );
rad_assert(handler->request != NULL);
rad_assert(handler->stage == AUTHENTICATE);
EAP_DS *eap_ds=handler->eap_ds;
if (!eap_ds ||
!eap_ds->response ||
(eap_ds->response->code != PW_IKEV2_RESPONSE) ||
eap_ds->response->type.type != PW_EAP_IKEV2 ||
!eap_ds->response->type.data){
radlog(L_ERR, IKEv2_LOG_PREFIX "corrupted data");
return -1;
}
uint8_t *in=NULL;
if(!(in=malloc(eap_ds->response->length))){
radlog(L_ERR, IKEv2_LOG_PREFIX "malloc error");
return -1;
}
rad_assert(in!=NULL);
struct EAPHeader *hdr = (struct EAPHeader *)in;
hdr->Code=eap_ds->response->code;
hdr->Id=eap_ds->response->id;
hdr->Length=htons(eap_ds->response->length);
hdr->Type=eap_ds->response->type.type;
memcpy(in+5,eap_ds->response->type.data,eap_ds->response->length-5);
uint8_t *out=NULL;
u_int32_t olen=0;
struct IKEv2Data *ikev2_data=(struct IKEv2Data*)handler->opaque;
struct IKEv2Session *session=ikev2_data->session;
session->timestamp=time(NULL);
if( !session->fragdata )
session->sendfrag = false;
if( session->sendfrag && !ParseFragmentAck( in, session ) ){
session->eapMsgID=eap_ds->response->id+1;
olen = CreateIKEv2Message( i2, NULL, 0, false, hdr->Id, session, (uint8_t **)&out );
free(in);
if(ComposeRadMsg(out,olen,handler->eap_ds)){
free(out);
return 0;
}
free(out);
return 1;
}
uint8_t *ikemsg;
u_int32_t len;
session->eapMsgID=eap_ds->response->id+1;
if( ParseIKEv2Message( in, &ikemsg, &len, session ) )
{
if(ikemsg!=NULL) free (ikemsg);
handler->eap_ds->request->code=PW_EAP_FAILURE;
radlog(L_INFO,IKEv2_LOG_PREFIX "Discarded packet");
return 1;
}
if( !ikemsg || !len ) {
if(session->SK_ready) session->include_integ=1;
olen = CreateFragmentAck( in, &out, session ); free(in);
in=NULL;
if(ComposeRadMsg(out,olen,handler->eap_ds)){
free(out);
return 0;
}
free(out);
return 1;
}
free(in);
in=NULL;
uint8_t *sikemsg=NULL; u_int32_t slen=0;
if( IKEv2ProcessMsg( i2, ikemsg, &sikemsg, &slen, session) != IKEv2_RET_OK )
{
radlog(L_INFO, IKEv2_LOG_PREFIX "EAP_STATE_DISCARD");
free(out);
return 1;
}
free( ikemsg );
if( slen != 0 ) {
olen = CreateIKEv2Message(i2, sikemsg, slen, false, 0, session, &out );
if( session->fragdata )
session->sendfrag = true;
} else {
if( session->Status == IKEv2_SST_FAILED )
{
radlog(L_INFO,IKEv2_LOG_PREFIX "FAILED");
olen = CreateResultMessage( false, session, &out );
}
if( session->Status == IKEv2_SST_ESTABLISHED )
{
radlog(L_INFO,IKEv2_LOG_PREFIX "SUCCESS");
olen = CreateResultMessage( true, session, &out );
session->fFastReconnect=i2->enableFastReconnect;
GenEapKeys(session,EAP_IKEv2_KEY_LEN);
set_mppe_keys(handler);
}
ikev2_data->session=NULL;
}
if (olen>0&&out!=NULL){
if(ComposeRadMsg(out,olen,handler->eap_ds)){
free(out);
return 0;
}
}
free(out);
return 1;
}
EAP_TYPE rlm_eap_ikev2 = {
"eap_ikev2",
ikev2_attach,
ikev2_initiate,
NULL,
ikev2_authenticate,
ikev2_detach
};