File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / coova-chilli / src / radius.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 22:48:25 2012 UTC (12 years, 3 months ago) by misho
Branches: coova-chilli, MAIN
CVS tags: v1_0_12, HEAD
coova-chilli

/* 
 *
 * Radius client functions.
 * Copyright (C) 2003, 2004, 2005 Mondru AB.
 * Copyright (c) 2006-2008 David Bird <david@coova.com>
 * 
 * The contents of this file may be used under the terms of the GNU
 * General Public License Version 2, provided that the above copyright
 * notice and this permission notice is included in all copies or
 * substantial portions of the software.
 * 
 */

#include "system.h"
#include "syserr.h"
#include "radius.h"
#include "md5.h"
#include "dhcp.h"
#include "redir.h"
#include "chilli.h"
#include "options.h"
#include "radius_wispr.h"
#include "radius_chillispot.h"


void radius_addnasip(struct radius_t *radius, struct radius_packet_t *pack)  {
  struct in_addr inaddr;
  struct in_addr *paddr = 0;

  if (options.nasip && *options.nasip)
    if (inet_aton(options.nasip, &inaddr))
      paddr = &inaddr;

  if (!paddr && options.radiuslisten.s_addr != 0)
    paddr = &options.radiuslisten;

  if (!paddr)
    paddr = &options.uamlisten;
    
  radius_addattr(radius, pack, RADIUS_ATTR_NAS_IP_ADDRESS, 0, 0, ntohl(paddr->s_addr), NULL, 0); 
}

void radius_addcalledstation(struct radius_t *radius, struct radius_packet_t *pack)  {
  uint8_t b[24];
  uint8_t *mac= (uint8_t*)"";

  if (options.nasmac)
    mac = (uint8_t *)options.nasmac;
  else 
    sprintf((char*)(mac=b), "%.2X-%.2X-%.2X-%.2X-%.2X-%.2X", 
	    radius->nas_hwaddr[0],radius->nas_hwaddr[1],radius->nas_hwaddr[2],
	    radius->nas_hwaddr[3],radius->nas_hwaddr[4],radius->nas_hwaddr[5]);

  radius_addattr(radius, pack, RADIUS_ATTR_CALLED_STATION_ID, 0, 0, 0, mac, strlen((char*)mac)); 
}

int radius_printqueue(struct radius_t *this) {
  int n;
  printf("next %d, first %d, last %d\n", 
	 this->next, this->first, this ->last);

  for(n=0; n<256; n++) {
    if (this->queue[n].state) {
      printf("%3d %3d %3d %3d %8d %8d %d\n",
	     n, this->queue[n].state,
	     this->queue[n].next,
	     this->queue[n].prev,
	     (int) this->queue[n].timeout.tv_sec,
	     (int) this->queue[n].timeout.tv_usec,
	     (int) this->queue[n].retrans);
    }
  }

  return 0;
}

/* 
 * radius_hmac_md5()
 * Calculate HMAC MD5 on a radius packet. 
 */
int radius_hmac_md5(struct radius_t *this, struct radius_packet_t *pack, 
		    char *secret, int secretlen, uint8_t *dst) {
  unsigned char digest[RADIUS_MD5LEN];
  size_t length;

  MD5_CTX context;

  uint8_t *key;
  size_t key_len;

  unsigned char k_ipad[65];
  unsigned char k_opad[65];
  unsigned char tk[RADIUS_MD5LEN];
  int i;

  if (secretlen > 64) { /* TODO: If Microsoft truncate to 64 instead */
    MD5Init(&context);
    MD5Update(&context, (uint8_t*)secret, secretlen);
    MD5Final(tk, &context);
    key = tk;
    key_len = 16;
  }
  else {
    key = (uint8_t*)secret;
    key_len = secretlen;
  }

  length = ntohs(pack->length);

  memset(k_ipad, 0x36, sizeof k_ipad);
  memset(k_opad, 0x5c, sizeof k_opad);

  for (i=0; i<key_len; i++) {
    k_ipad[i] ^= key[i];
    k_opad[i] ^= key[i];
  }

  /* Perform inner MD5 */
  MD5Init(&context);
  MD5Update(&context, k_ipad, 64);
  MD5Update(&context, (uint8_t*) pack, length);
  MD5Final(digest, &context);

  /* Perform outer MD5 */
  MD5Init(&context);
  MD5Update(&context, k_opad, 64);
  MD5Update(&context, digest, 16);
  MD5Final(digest, &context);
  
  memcpy(dst, digest, RADIUS_MD5LEN);

  return 0;
}

/* 
 * radius_acctreq_authenticator()
 * Update a packet with an accounting request authenticator
 */
int radius_acctreq_authenticator(struct radius_t *this,
				 struct radius_packet_t *pack) {

  /* From RFC 2866: Authenticator is the MD5 hash of:
     Code + Identifier + Length + 16 zero octets + request attributes +
     shared secret */
  
  MD5_CTX context;

  memset(pack->authenticator, 0, RADIUS_AUTHLEN);

  /* Get MD5 hash on secret + authenticator */
  MD5Init(&context);
  MD5Update(&context, (void*) pack, ntohs(pack->length));
  MD5Update(&context, (uint8_t*) this->secret, this->secretlen);
  MD5Final(pack->authenticator, &context);
  
  return 0;
}


/* 
 * radius_authresp_authenticator()
 * Update a packet with an authentication response authenticator
 */
int radius_authresp_authenticator(struct radius_t *this,
				 struct radius_packet_t *pack,
				 uint8_t *req_auth,
				 char *secret, size_t secretlen) {

  /* From RFC 2865: Authenticator is the MD5 hash of:
     Code + Identifier + Length + request authenticator + request attributes +
     shared secret */
  
  MD5_CTX context;

  memcpy(pack->authenticator, req_auth, RADIUS_AUTHLEN);

  /* Get MD5 hash on secret + authenticator */
  MD5Init(&context);
  MD5Update(&context, (void*) pack, ntohs(pack->length));
  MD5Update(&context, (uint8_t*) secret, secretlen);
  MD5Final(pack->authenticator, &context);
  
  return 0;
}


/* 
 * radius_queue_in()
 * Place data in queue for later retransmission.
 */
int radius_queue_in(struct radius_t *this, struct radius_packet_t *pack,
		    void *cbp) {
  struct timeval *tv;
  struct radius_attr_t *ma = NULL; /* Message authenticator */

  if (this->debug) {
    log_dbg("radius_queue_in");
    radius_printqueue(this);
  }

  if (this->queue[this->next].state == 1) {
    log_err(0, "radius queue is full!");
    /* Queue is not really full. It only means that the next space
       in queue is not available, but there might be space elsewhere */
    return -1;
  }

  pack->id = this->next;

  /* If packet contains message authenticator: Calculate it! */
  if (!radius_getattr(pack, &ma, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0,0,0)) {
    radius_hmac_md5(this, pack, this->secret, this->secretlen, ma->v.t);
  }
  
  /* If accounting request: Calculate authenticator */
  if (pack->code == RADIUS_CODE_ACCOUNTING_REQUEST)
    radius_acctreq_authenticator(this, pack);

  memcpy(&this->queue[this->next].p, pack, RADIUS_PACKSIZE);
  this->queue[this->next].state = 1;
  this->queue[this->next].cbp = cbp;
  this->queue[this->next].retrans = 0;

  tv = &this->queue[this->next].timeout;
  gettimeofday(tv, NULL);

  tv->tv_sec += options.radiustimeout;

  this->queue[this->next].lastsent = this->lastreply;

  /* Insert in linked list for handling timeouts */
  this->queue[this->next].next = -1;         /* Last in queue */
  this->queue[this->next].prev = this->last; /* Link to previous */

  if (this->last != -1)
    this->queue[this->last].next=this->next; /* Link previous to us */
  this->last = this->next;                   /* End of queue */

  if (this->first == -1)
    this->first = this->next; /* First and last */

  this->next++; /* next = next % RADIUS_QUEUESIZE */

  if (this->debug) {
    printf("radius_queue_out end\n");
    radius_printqueue(this);
  }

  return 0;
}


/* 
 * radius_queue_in()
 * Remove data from queue.
 */
int radius_queue_out(struct radius_t *this, struct radius_packet_t *pack,
		     int id, void **cbp) {

  if (this->debug) if (this->debug) printf("radius_queue_out\n");

  if (this->queue[id].state != 1) {
    log_err(0, "No such id in radius queue: %d!", id);
    return -1;
  }

  if (this->debug) {
    log_dbg("radius_queue_out");
    radius_printqueue(this);
  }
  
  memcpy(pack, &this->queue[id].p, RADIUS_PACKSIZE);
  *cbp = this->queue[id].cbp;

  this->queue[id].state = 0;

  /* Remove from linked list */
  if (this->queue[id].next == -1) /* Are we the last in queue? */
    this->last = this->queue[id].prev;
  else
    this->queue[this->queue[id].next].prev = this->queue[id].prev;
    
  if (this->queue[id].prev == -1) /* Are we the first in queue? */
    this->first = this->queue[id].next;
  else
    this->queue[this->queue[id].prev].next = this->queue[id].next;

  if (this->debug) {
    log_dbg("radius_queue_out end");
    radius_printqueue(this);
  }

  return 0;
}

/* 
 * radius_queue_reschedule()
 * Recalculate the timeout value of a packet in the queue.
 */
int radius_queue_reschedule(struct radius_t *this, int id) {
  struct timeval *tv;

  /* sanity check */
  if (id < 0 || id >= RADIUS_QUEUESIZE) {
    log_err(0, "bad id (%d)", id);
    return -1;
  }

  if (this->debug) 
    log_dbg("radius_queue_reschedule");

  if (this->queue[id].state != 1) {
    log_err(0, "No such id in radius queue: %d!", id);
    return -1;
  }

  if (this->debug) {
    log_dbg("radius_reschedule");
    radius_printqueue(this);
  }

  this->queue[id].retrans++;

  tv = &this->queue[id].timeout;
  gettimeofday(tv, NULL);

  tv->tv_sec += options.radiustimeout;

  /* Remove from linked list */
  if (this->queue[id].next == -1) /* Are we the last in queue? */
    this->last = this->queue[id].prev;
  else
    this->queue[this->queue[id].next].prev = this->queue[id].prev;
    
  if (this->queue[id].prev == -1) /* Are we the first in queue? */
    this->first = this->queue[id].next;
  else
    this->queue[this->queue[id].prev].next = this->queue[id].next;

  /* Insert in linked list for handling timeouts */
  this->queue[id].next = -1;         /* Last in queue */
  this->queue[id].prev = this->last; /* Link to previous (could be -1) */

  if (this->last != -1)
    this->queue[this->last].next=id; /* If not empty: link previous to us */

  this->last = id;                   /* End of queue */

  if (this->first == -1)
    this->first = id;                /* First and last */

  if (this->debug) {
    radius_printqueue(this);
  }

  return 0;
}


/* 
 * radius_cmptv()
 * Returns an integer less than, equal to or greater than zero if tv1
 * is found, respectively, to be less than, to match or be greater than tv2.
 */
int 
radius_cmptv(struct timeval *tv1, struct timeval *tv2)
{
  struct timeval diff;

  if (0) {
    printf("tv1 %8d %8d tv2 %8d %8d\n", 
	   (int) tv1->tv_sec, (int) tv1->tv_usec, 
	   (int) tv2->tv_sec, (int) tv2->tv_usec);
  }
  
  /* First take the difference with |usec| < 1000000 */
  diff.tv_sec = (tv1->tv_usec  - tv2->tv_usec) / 1000000 +
                (tv1->tv_sec   - tv2->tv_sec);
  diff.tv_usec = (tv1->tv_usec - tv2->tv_usec) % 1000000;

  if (0) {
    printf("tv1 %8d %8d tv2 %8d %8d diff %8d %8d\n", 
	   (int) tv1->tv_sec, (int) tv1->tv_usec, 
	   (int) tv2->tv_sec, (int) tv2->tv_usec, 
	   (int) diff.tv_sec, (int) diff.tv_usec);
  }

  /* If sec and usec have different polarity add or subtract 1 second */
  if ((diff.tv_sec > 0) & (diff.tv_usec < 0)) {
    diff.tv_sec--;
    diff.tv_usec += 1000000;
  }
  if ((diff.tv_sec < 0) & (diff.tv_usec > 0)) {
    diff.tv_sec++;
    diff.tv_usec -= 1000000;
  }
  if (0) {
    printf("tv1 %8d %8d tv2 %8d %8d diff %8d %8d\n", 
	   (int) tv1->tv_sec, (int) tv1->tv_usec, 
	   (int) tv2->tv_sec, (int) tv2->tv_usec, 
	   (int) diff.tv_sec, (int) diff.tv_usec);
  }

  if (diff.tv_sec < 0) {if (0) printf("-1\n"); return -1; }
  if (diff.tv_sec > 0) {if (0) printf("1\n"); return  1; }

  if (diff.tv_usec < 0) {if (0) printf("-1\n"); return -1;}
  if (diff.tv_usec > 0) {if (0) printf("1\n"); return  1;}
  if (0) printf("0 \n");
  return 0;

}


/* 
 * radius_timeleft()
 * Determines how nuch time is left until we need to call 
 * radius_timeout().
 * Only modifies timeout if new value is lower than current value.
 */
int 
radius_timeleft(struct radius_t *this, struct timeval *timeout) 
{
  struct timeval now, later, diff;

  if (this->first == -1) /* Queue is empty */
    return 0;

  gettimeofday(&now, NULL);
  later.tv_sec = this->queue[this->first].timeout.tv_sec;
  later.tv_usec = this->queue[this->first].timeout.tv_usec;

  /* First take the difference with |usec| < 1000000 */
  diff.tv_sec = (later.tv_usec  - now.tv_usec) / 1000000 +
                (later.tv_sec   - now.tv_sec);
  diff.tv_usec = (later.tv_usec - now.tv_usec) % 1000000;

  /* If sec and usec have different polarity add or subtract 1 second */
  if ((diff.tv_sec > 0) & (diff.tv_usec < 0)) {
    diff.tv_sec--;
    diff.tv_usec += 1000000;
  }
  if ((diff.tv_sec < 0) & (diff.tv_usec > 0)) {
    diff.tv_sec++;
    diff.tv_usec -= 1000000;
  }

  /* If negative set to zero */
  if ((diff.tv_sec < 0) || (diff.tv_usec < 0)) {
    diff.tv_sec = 0;
    diff.tv_usec = 0;
  }

  /* If original was smaller do nothing */
  if (radius_cmptv(timeout, &diff) <=0) 
    return 0;

  timeout->tv_sec = diff.tv_sec;
  timeout->tv_usec = diff.tv_usec;
  return 0;
}

/* 
 * radius_timeout()
 * Retransmit any outstanding packets. This function should be called at
 * regular intervals. Use radius_timeleft() to determine how much time is 
 * left before this function should be called.
 */
int radius_timeout(struct radius_t *this) {
  /* Retransmit any outstanding packets */
  /* Remove from queue if maxretrans exceeded */
  struct timeval now;
  struct sockaddr_in addr;
  struct radius_packet_t pack_req;
  void *cbp;

  gettimeofday(&now, NULL);

#if(1)
  if (this->debug) {
    log_dbg("radius_timeout %8d %8d", (int)now.tv_sec, (int)now.tv_usec);
    radius_printqueue(this);
  }
#endif

  while (this->first != -1 && 
	 radius_cmptv(&now, &this->queue[this->first].timeout) >= 0) {
    
    if (this->queue[this->first].retrans < options.radiusretry) {
      memset(&addr, 0, sizeof(addr));
      addr.sin_family = AF_INET;
      
      if (this->queue[this->first].retrans == (options.radiusretrysec-1)) {
	/* Use the other server for next retransmission */
	if (this->queue[this->first].lastsent) {
	  addr.sin_addr = this->hisaddr0;
	  this->queue[this->first].lastsent = 0;
	}
	else {
	  addr.sin_addr = this->hisaddr1;
	  this->queue[this->first].lastsent = 1;
	}
      } 
      else {
	/* Use the same server for next retransmission */
	if (this->queue[this->first].lastsent) {
	  addr.sin_addr = this->hisaddr1;
	}
	else {
	  addr.sin_addr = this->hisaddr0;
	}
      }
      
      /* Use the correct port for accounting and authentication */
      if (this->queue[this->first].p.code == RADIUS_CODE_ACCOUNTING_REQUEST)
	addr.sin_port = htons(this->acctport);
      else
	addr.sin_port = htons(this->authport);
      
      
      if (sendto(this->fd, &this->queue[this->first].p,
		 ntohs(this->queue[this->first].p.length), 0,
		 (struct sockaddr *) &addr, sizeof(addr)) < 0) {
	log_err(errno, "sendto() failed!");
	radius_queue_reschedule(this, this->first);
	return -1;
      }

      radius_queue_reschedule(this, this->first);
    }
    else { /* Finished retrans */
      if (radius_queue_out(this, &pack_req, this->first, &cbp)) {
	log_warn(0, "Matching request was not found in queue: %d!", this->first);
	return -1;
      }

      if ((pack_req.code == RADIUS_CODE_ACCOUNTING_REQUEST) &&
	  (this->cb_acct_conf))
	  return this->cb_acct_conf(this, NULL, &pack_req, cbp);

      if ((pack_req.code == RADIUS_CODE_ACCESS_REQUEST) &&
	  (this->cb_auth_conf))
	return this->cb_auth_conf(this, NULL, &pack_req, cbp);
    }    
  }
  
  if (this->debug) {
    printf("radius_timeout\n");
    if (this->first > 0) {
      printf("first %d, timeout %8d %8d\n", this->first, 
	     (int) this->queue[this->first].timeout.tv_sec, 
	     (int) this->queue[this->first].timeout.tv_usec); 
    }
    radius_printqueue(this);
  }

  return 0;
}



/* 
 * radius_addattr()
 * Add an attribute to a packet. The packet length is modified 
 * accordingly.
 * If data==NULL and dlen!=0 insert null attribute.
 */
int 
radius_addattr(struct radius_t *this, struct radius_packet_t *pack, 
	       uint8_t type, uint32_t vendor_id, uint8_t vendor_type,
	       uint32_t value, uint8_t *data, uint16_t dlen) {
  struct radius_attr_t *a;
  char passwd[RADIUS_PWSIZE];
  uint16_t length = ntohs(pack->length);
  uint16_t vlen;
  size_t pwlen;

  a = (struct radius_attr_t *)((uint8_t*)pack + length);

  if (type == RADIUS_ATTR_USER_PASSWORD) {
    radius_pwencode(this, 
		    (uint8_t*) passwd, RADIUS_PWSIZE, 
		    &pwlen, 
		    data, dlen, 
		    pack->authenticator,
		    this->secret, this->secretlen);
    data = (uint8_t *)passwd;
    dlen = (uint16_t)pwlen;
  }

  if (type != RADIUS_ATTR_VENDOR_SPECIFIC) {
    if (dlen) { /* If dlen != 0 it is a text/string attribute */
      vlen = dlen;
    }
    else {
      vlen = 4; /* address, integer or time */
    }
    
    if (vlen > RADIUS_ATTR_VLEN) {
      log_warn(0, "Truncating RADIUS attribute (type:%d/%d/%d) from %d to %d bytes [%s]", 
	       type, vendor_id, vendor_type, vlen, RADIUS_ATTR_VLEN, data);
      vlen = RADIUS_ATTR_VLEN;
    }

    if ((length+vlen+2) > RADIUS_PACKSIZE) {
      log_err(0, "No more space!");
      return -1;
    }

    length += vlen + 2;

    pack->length = htons(length);

    a->t = type;
    a->l = vlen+2;

    if (data)
      memcpy(a->v.t, data, vlen);
    else if (dlen)
      memset(a->v.t, 0, vlen);
    else
      a->v.i = htonl(value);
  }
  else { /* Vendor specific */
    if (dlen) { /* If dlen != 0 it is a text/string attribute */
      vlen = dlen;
    }
    else {
      vlen = 4; /* address, integer or time */
    }

    if (vlen > RADIUS_ATTR_VLEN-8) {
      log_warn(0, "Truncating RADIUS attribute (type:%d/%d/%d) from %d to %d [%s]", 
	       type, vendor_id, vendor_type, vlen, RADIUS_ATTR_VLEN-8, data);
      vlen = RADIUS_ATTR_VLEN-8;
    }

    if ((length+vlen+2) > RADIUS_PACKSIZE) { 
      log_err(0, "No more space!");
      return -1;
    }

    length += vlen + 8;

    pack->length = htons(length);

    a->t = type;
    a->l = vlen+8;

    a->v.vv.i = htonl(vendor_id);
    a->v.vv.t = vendor_type;
    a->v.vv.l = vlen+2;

    if (data)
      memcpy(((void*) a)+8, data, dlen);
    else if (dlen)
      memset(((void*) a)+8, 0, dlen); 
    else
      a->v.vv.v.i = htonl(value);
  }

  return 0;
}


/* 
 * radius_getattr()
 * Search for an attribute in a packet. Returns -1 if attribute is not found.
 * The first instance matching attributes will be skipped
 */
int
radius_getattr(struct radius_packet_t *pack, struct radius_attr_t **attr,
	       uint8_t type, uint32_t vendor_id, uint8_t vendor_type,
	       int instance) {
  size_t offset = 0;
  return radius_getnextattr(pack, attr, type, vendor_id, vendor_type, instance, &offset);
}

int
radius_getnextattr(struct radius_packet_t *pack, struct radius_attr_t **attr,
	       uint8_t type, uint32_t vendor_id, uint8_t vendor_type,
	       int instance, size_t *roffset) {
  struct radius_attr_t *t;
  size_t len = ntohs(pack->length) - RADIUS_HDRSIZE;
  size_t offset = *roffset;
  int count = 0;

  if (0) {
    printf("radius_getattr payload(len=%d,off=%d) %.2x %.2x %.2x %.2x\n",
	   len, offset, pack->payload[0], pack->payload[1], pack->payload[2], 
	   pack->payload[3]);
  }

  while (offset < len) {
    t = (struct radius_attr_t *)(&pack->payload[offset]);

    if (0) {
      printf("radius_getattr %d %d %d %.2x %.2x \n", t->t, t->l, 
	     ntohl(t->v.vv.i), (int) t->v.vv.t, (int) t->v.vv.l);
    }

    offset +=  t->l;
    
    if (t->t != type) 
      continue;
    
    if (t->t == RADIUS_ATTR_VENDOR_SPECIFIC && 
	(ntohl(t->v.vv.i) != vendor_id || t->v.vv.t != vendor_type))
      continue;
    
    if (count == instance) {
      
      if (type == RADIUS_ATTR_VENDOR_SPECIFIC)
	*attr = (struct radius_attr_t *) &t->v.vv.t;
      else
	*attr = t;
      
      if (0) printf("Found\n");
      
      *roffset = offset;
      return 0;
    }
    else {
      count++;
    }
  }
  
  return -1; /* Not found */
}

/* 
 * radius_countattr()
 * Count the number of instances of an attribute in a packet.
 */
int 
radius_countattr(struct radius_packet_t *pack, uint8_t type) {
  struct radius_attr_t *t;
  size_t offset = 0;
  int count = 0;
  
  /* Need to check pack -> length */
  
  do {
    t = (struct radius_attr_t*)(&pack->payload[offset]);
    if (t->t == type) {
      count++;
    }
    offset +=  2 + t->l;
  } while (offset < ntohs(pack->length));
  
  if (0) printf("Count %d\n", count);
  return count;
}


/* 
 * radius_cmpattr()
 * Compare two attributes to see if they are the same.
 */
int 
radius_cmpattr(struct radius_attr_t *t1, struct radius_attr_t *t2) {
  if (t1->t != t2->t) return -1;
  if (t1->l != t2->l) return -1;
  if (memcmp(t1->v.t, t2->v.t, t1->l)) return -1; /* Also int/time/addr */
  return 0;
}


/*
 * radius_keydecode()
 * Decode an MPPE key using MD5.
 */
int radius_keydecode(struct radius_t *this, 
		     uint8_t *dst, size_t dstsize, size_t *dstlen, 
		     uint8_t *src, size_t srclen,
		     uint8_t *authenticator, 
		     char *secret, size_t secretlen) {
  MD5_CTX context;
  unsigned char b[RADIUS_MD5LEN];
  int blocks;
  int i, n;

  blocks = ((int)srclen - 2) / RADIUS_MD5LEN;

  if ((blocks * RADIUS_MD5LEN + 2) != (int)srclen) {
    log_err(0, "radius_keydecode: srclen must be 2 plus n*16");
    return -1;
  }

  if (blocks < 1) {
    log_err(0, "radius_keydecode srclen must be at least 18");
    return -1;
  }

  /* Get MD5 hash on secret + authenticator (First 16 octets) */
  MD5Init(&context);
  MD5Update(&context, (uint8_t *)secret, secretlen);
  MD5Update(&context, authenticator, RADIUS_AUTHLEN);
  MD5Update(&context, src, 2);
  MD5Final(b, &context);

  if ((src[2] ^ b[0]) > dstsize) {
    log_err(0,"radius_keydecode dstsize too small");
    return -1; 
  }

  if ((src[2] ^ b[0]) > (srclen - 3)) {
    log_err(0,"radius_keydecode dstlen > srclen - 3");
    return -1; 
  }

  *dstlen = (size_t)(src[2] ^ b[0]);

  for (i = 1; i < RADIUS_MD5LEN; i++)
    if ((i-1) < (int)*dstlen)
      dst[i-1] = src[i+2] ^ b[i];

  /* Next blocks of 16 octets */
  for (n=1; n < blocks; n++) {
    MD5Init(&context);
    MD5Update(&context, (uint8_t *)secret, secretlen);
    MD5Update(&context, &src[2 + ((n-1) * RADIUS_MD5LEN)], RADIUS_MD5LEN);
    MD5Final(b, &context);
    for (i = 0; i < RADIUS_MD5LEN; i++)
      if ((i-1+n*RADIUS_MD5LEN) < (int)*dstlen)
	dst[i-1+n*RADIUS_MD5LEN] = src[i+2+n*RADIUS_MD5LEN] ^ b[i];
  }

  return 0;
}

/* 
 * radius_keyencode()
 * Encode an MPPE key using MD5.
 */
int radius_keyencode(struct radius_t *this, 
		     uint8_t *dst, size_t dstsize, size_t *dstlen, 
		     uint8_t *src, size_t srclen,
		     uint8_t *authenticator, 
		     char *secret, size_t secretlen) {
  MD5_CTX context;
  unsigned char b[RADIUS_MD5LEN];
  int blocks;
  int i, n;

  blocks = ((int)srclen + 1) / RADIUS_MD5LEN;
  if ((blocks * RADIUS_MD5LEN) < ((int)srclen + 1)) blocks++;
  
  if (((blocks * RADIUS_MD5LEN) + 2) > (int)dstsize) {
    log_err(0, "radius_keyencode dstsize too small");
    return -1;
  }

  *dstlen = (size_t)((blocks * RADIUS_MD5LEN) + 2);

  /* Read two salt octets */
  if (fread(dst, 1, 2, this->urandom_fp) != 2) {
    log_err(errno, "fread() failed");
    return -1;
  }

  /* Get MD5 hash on secret + authenticator (First 16 octets) */
  MD5Init(&context);
  MD5Update(&context, (uint8_t *)secret, secretlen);
  MD5Update(&context, authenticator, RADIUS_AUTHLEN);
  MD5Update(&context, dst, 2);
  MD5Final(b, &context);
  dst[2] = (uint8_t)srclen ^ b[0]; /* Length of key */
  for (i = 1; i < RADIUS_MD5LEN; i++)
    if ((i-1) < (int)srclen)
      dst[i+2] = src[i-1] ^ b[i];
    else
      dst[i+2] = b[i];

  /* Get MD5 hash on secret + c(n-1) (Next j 16 octets) */
  for (n=1; n < blocks; n++) {
    MD5Init(&context);
    MD5Update(&context, (uint8_t *)secret, secretlen);
    MD5Update(&context, &dst[2 + ((n-1) * RADIUS_MD5LEN)], RADIUS_MD5LEN);
    MD5Final(b, &context);
    for (i = 0; i < RADIUS_MD5LEN; i++)
      if ((i-1) < (int)srclen)
	dst[i+2+n*RADIUS_MD5LEN] = src[i-1+n*RADIUS_MD5LEN] ^ b[i];
      else
	dst[i+2+n*RADIUS_MD5LEN] = b[i];
  }

  return 0;
}


/* 
 * radius_pwdecode()
 * Decode a password using MD5. Also used for MSCHAPv1 MPPE keys.
 */
int radius_pwdecode(struct radius_t *this, 
		    uint8_t *dst, size_t dstsize, size_t *dstlen, 
		    uint8_t *src, size_t srclen, 
		    uint8_t *authenticator, 
		    char *secret, size_t secretlen) {
  int i, n;
  MD5_CTX context;
  unsigned char output[RADIUS_MD5LEN];

  if (srclen > dstsize) {
    log_err(0, "radius_pwdecode srclen larger than dstsize");
    return -1;
  }

  if (srclen % RADIUS_MD5LEN) {
    log_err(0, "radius_pwdecode srclen is not multiple of 16 octets");
    return -1;
  }

  *dstlen = srclen;

  if (this->debug) {
    printf("pwdecode srclen %d\n", srclen);
    for (n=0; n< srclen; n++) {
      printf("%.2x ", src[n]);
      if ((n % 16) == 15)
	printf("\n");
    }
    printf("\n");

    printf("pwdecode authenticator \n");
    for (n=0; n< RADIUS_AUTHLEN; n++) {
      printf("%.2x ", authenticator[n]);
      if ((n % 16) == 15)
	printf("\n");
    }
    printf("\n");

    printf("pwdecode secret \n");
    for (n=0; n< secretlen; n++) {
      printf("%.2x ", secret[n]);
      if ((n % 16) == 15)
	printf("\n");
    }
    printf("\n");
  }

  /* Get MD5 hash on secret + authenticator */
  MD5Init(&context);
  MD5Update(&context, (uint8_t*) secret, secretlen);
  MD5Update(&context, authenticator, RADIUS_AUTHLEN);
  MD5Final(output, &context);

  /* XOR first 16 octets of passwd with MD5 hash */
  for (i = 0; i < RADIUS_MD5LEN; i++)
    dst[i] = src[i] ^ output[i];

  /* Continue with the remaining octets of passwd if any */
  for (n = 0; n < 128 && n < (*dstlen - RADIUS_AUTHLEN); n += RADIUS_AUTHLEN) {
    MD5Init(&context);
    MD5Update(&context, (uint8_t*) secret, secretlen);
    MD5Update(&context, src + n, RADIUS_AUTHLEN);
    MD5Final(output, &context);
    for (i = 0; i < RADIUS_AUTHLEN; i++)
      dst[i + n + RADIUS_AUTHLEN] = src[i + n + RADIUS_AUTHLEN] ^ output[i];
  }    

  if (this->debug) {
    printf("pwdecode dest \n");
    for (n=0; n< 32; n++) {
      printf("%.2x ", dst[n]);
      if ((n % 16) == 15)
	printf("\n");
    }
    printf("\n");
  }

  return 0;
}


/* 
 * radius_pwencode()
 * Encode a password using MD5.
 */
int radius_pwencode(struct radius_t *this, 
		    uint8_t *dst, size_t dstsize,
		    size_t *dstlen, 
		    uint8_t *src, size_t srclen, 
		    uint8_t *authenticator, 
		    char *secret, size_t secretlen) {

  unsigned char output[RADIUS_MD5LEN];
  MD5_CTX context;
  size_t i, n;

  memset(dst, 0, dstsize);

  /* Make dstlen multiple of 16 */
  if (srclen & 0x0f) 
    *dstlen = (srclen & 0xf0) + 0x10; /* Padding 1 to 15 zeros */
  else
    *dstlen = srclen;                 /* No padding */

  /* Is dstsize too small ? */
  if (dstsize <= *dstlen) {
    *dstlen = 0;
    return -1;
  }

  /* Copy first 128 octets of src into dst */
  if (srclen > 128) 
    memcpy(dst, src, 128);
  else
    memcpy(dst, src, srclen);

  /* Get MD5 hash on secret + authenticator */
  MD5Init(&context);
  MD5Update(&context, (uint8_t*) secret, secretlen);
  MD5Update(&context, authenticator, RADIUS_AUTHLEN);
  MD5Final(output, &context);

  /* XOR first 16 octets of dst with MD5 hash */
  for (i = 0; i < RADIUS_MD5LEN; i++)
    dst[i] ^= output[i];

  /* if (*dstlen <= RADIUS_MD5LEN) return 0;  Finished */

  /* Continue with the remaining octets of dst if any */
  for (n = 0; 
       n < 128 && n < (*dstlen - RADIUS_AUTHLEN); 
       n += RADIUS_AUTHLEN) {
    MD5Init(&context);
    MD5Update(&context, (uint8_t*) secret, secretlen);
    MD5Update(&context, dst + n, RADIUS_AUTHLEN);
    MD5Final(output, &context);
    for (i = 0; i < RADIUS_AUTHLEN; i++)
      dst[i + n + RADIUS_AUTHLEN] ^= output[i];
  }    

  return 0;
}


/* 
 * radius_new()
 * Allocate a new radius instance.
 */
int radius_new(struct radius_t **this,
	       struct in_addr *listen, uint16_t port, int coanocheck,
	       struct in_addr *proxylisten, uint16_t proxyport,
	       struct in_addr *proxyaddr, struct in_addr *proxymask,
	       char* proxysecret) {
  struct sockaddr_in addr;
  struct radius_t *new_radius;

  /* Allocate storage for instance */
  if (!(new_radius = calloc(sizeof(struct radius_t), 1))) {
    log_err(0, "calloc() failed");
    return -1;
  }

  new_radius->coanocheck = coanocheck;

  /* Radius parameters */
  new_radius->ouraddr.s_addr = listen->s_addr;
  new_radius->ourport = port;

  /* Proxy parameters */
  if (proxylisten && proxyport && proxysecret) {
    new_radius->proxylisten.s_addr = proxylisten->s_addr;
    new_radius->proxyport = proxyport;
    
    if (proxyaddr)
      new_radius->proxyaddr.s_addr = proxyaddr->s_addr;
    else
      new_radius->proxyaddr.s_addr = ~0;
    
    if (proxymask)
      new_radius->proxymask.s_addr = proxymask->s_addr;
    else
      new_radius->proxymask.s_addr = 0;
    
    if ((new_radius->proxysecretlen = strlen(proxysecret)) < RADIUS_SECRETSIZE) {
      memcpy(new_radius->proxysecret, proxysecret, new_radius->proxysecretlen);
    }
  }

  /* Initialise queue */
  new_radius->next = 0;
  new_radius->first = -1;
  new_radius->last = -1;
  
  if ((new_radius->urandom_fp = fopen("/dev/urandom", "r")) < 0) {
    log_err(errno, "fopen(/dev/urandom, r) failed");
    free(new_radius);
    return -1;
  }
  
  /* Initialise radius socket */
  if ((new_radius->fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
    log_err(errno, "socket() failed!");
    fclose(new_radius->urandom_fp);
    free(new_radius);
    return -1;
  }

  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr = new_radius->ouraddr;
  addr.sin_port = htons(new_radius->ourport);
  
  if (bind(new_radius->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
    log_err(errno, "bind() failed!");
    fclose(new_radius->urandom_fp);
    close(new_radius->fd);
    free(new_radius);
    return -1;
  }

  /* Initialise proxy socket */
  if (proxylisten && proxyport && proxysecret) {
    if ((new_radius->proxyfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
      log_err(errno, "socket() failed for proxyfd!");
      fclose(new_radius->urandom_fp);
      close(new_radius->fd);
      free(new_radius);
      return -1;
    }
    
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = new_radius->proxylisten;
    addr.sin_port = htons(new_radius->proxyport);
    
    if (bind(new_radius->proxyfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
      log_err(errno, "bind() failed for proxylisten!");
      fclose(new_radius->urandom_fp);
      close(new_radius->fd);
      close(new_radius->proxyfd);
      free(new_radius);
      return -1;
    }
  }
  else {
    new_radius->proxyfd = -1; /* Indicate that proxy is not used */
  }

  *this = new_radius;
  return 0;
}


/* 
 * radius_free()
 * Free a radius instance. (Undo radius_new() 
 */
int 
radius_free(struct radius_t *this)
{
  if (fclose(this->urandom_fp)) {
    log_err(errno, "fclose() failed!");
  }
  if (close(this->fd)) {
    log_err(errno, "close() failed!");
  }
  free(this);
  return 0;
}

void radius_set(struct radius_t *this, unsigned char *hwaddr, int debug) {
  this->debug = debug;

  /* Remote radius server parameters */
  this->hisaddr0.s_addr = options.radiusserver1.s_addr;
  this->hisaddr1.s_addr = options.radiusserver2.s_addr;

  if (options.radiusauthport) {
    this->authport = options.radiusauthport;
  }
  else {
    this->authport = RADIUS_AUTHPORT;
  }
  
  if (options.radiusacctport) {
    this->acctport = options.radiusacctport;
  }
  else {
    this->acctport = RADIUS_ACCTPORT;
  }

  if ((this->secretlen = strlen(options.radiussecret)) > RADIUS_SECRETSIZE) {
    log_err(0, "Radius secret too long. Truncating to %d characters", 
	    RADIUS_SECRETSIZE);
    this->secretlen = RADIUS_SECRETSIZE;
  }

  if (hwaddr)
    memcpy(this->nas_hwaddr, hwaddr, sizeof(this->nas_hwaddr));

  memcpy(this->secret, options.radiussecret, this->secretlen);

  this->lastreply = 0; /* Start out using server 0 */  
  return;
}


/* 
 * radius_set_cb_ind()
 * Set callback function received requests
 */
int radius_set_cb_ind(struct radius_t *this,
  int (*cb_ind) (struct radius_t *radius, struct radius_packet_t *pack,
		 struct sockaddr_in *peer)) {

  this->cb_ind = cb_ind;
  return 0;
}


/* 
 * radius_set_cb_auth_conf()
 * Set callback function for responses to access request
 */
int
radius_set_cb_auth_conf(struct radius_t *this,
int (*cb_auth_conf) (struct radius_t *radius, struct radius_packet_t *pack,
		       struct radius_packet_t *pack_req, void *cbp)) {

  this->cb_auth_conf = cb_auth_conf;
  return 0;
}

/* 
 * radius_set_cb_acct_conf()
 * Set callback function for responses to accounting request
 */
int
radius_set_cb_acct_conf(struct radius_t *this,
int (*cb_acct_conf) (struct radius_t *radius, struct radius_packet_t *pack,
		     struct radius_packet_t *pack_req, void *cbp)) {

  this->cb_acct_conf = cb_acct_conf;
  return 0;
}

/* 
 * radius_set_cb_coa_ind()
 * Set callback function for coa and disconnect request
 */
int
radius_set_cb_coa_ind(struct radius_t *this,
int (*cb_coa_ind) (struct radius_t *radius, struct radius_packet_t *pack,
		   struct sockaddr_in *peer)) {

  this->cb_coa_ind = cb_coa_ind;
  return 0;
}


/* 
 * radius_req()
 * Send of a packet and place it in the retransmit queue
 */
int radius_req(struct radius_t *this,
	       struct radius_packet_t *pack,
	       void *cbp)
{
  struct sockaddr_in addr;
  size_t len = ntohs(pack->length);

  /* Place packet in queue */
  if (radius_queue_in(this, pack, cbp)) {
    return -1;
  }

  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;

  if (this->debug) printf("Lastreply: %d\n", this->lastreply);

  if (!this->lastreply) {
    addr.sin_addr = this->hisaddr0;
  }
  else {
    addr.sin_addr = this->hisaddr1;
  }

  if (pack->code == RADIUS_CODE_ACCOUNTING_REQUEST)
    addr.sin_port = htons(this->acctport);
  else
    addr.sin_port = htons(this->authport);
      
  
  if (sendto(this->fd, pack, len, 0,
	     (struct sockaddr *) &addr, sizeof(addr)) < 0) {
    log_err(errno, "sendto() failed!");
    return -1;
  } 
    
  return 0;
}


/* 
 * radius_resp()
 * Send of a packet (no retransmit queue)
 */
int radius_resp(struct radius_t *this,
		struct radius_packet_t *pack,
		struct sockaddr_in *peer, uint8_t *req_auth) {

  size_t len = ntohs(pack->length);
  struct radius_attr_t *ma = NULL; /* Message authenticator */

  /* Prepare for message authenticator TODO */
  memset(pack->authenticator, 0, RADIUS_AUTHLEN);
  memcpy(pack->authenticator, req_auth, RADIUS_AUTHLEN);

  /* If packet contains message authenticator: Calculate it! */
  if (!radius_getattr(pack, &ma, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0,0,0)) {
    radius_hmac_md5(this, pack, this->proxysecret, this->proxysecretlen, ma->v.t);
  }

  radius_authresp_authenticator(this, pack, req_auth, 
				this->proxysecret,
				this->proxysecretlen);
  
  if (sendto(this->proxyfd, pack, len, 0,
	     (struct sockaddr *) peer, sizeof(struct sockaddr_in)) < 0) {
    log_err(errno, "sendto() failed!");
    return -1;
  } 
  
  return 0;
}

/* 
 * radius_coaresp()
 * Send of a packet (no retransmit queue)
 */
int radius_coaresp(struct radius_t *this,
		   struct radius_packet_t *pack,
		   struct sockaddr_in *peer, uint8_t *req_auth) {

  size_t len = ntohs(pack->length);
  struct radius_attr_t *ma = NULL; /* Message authenticator */

  /* Prepare for message authenticator TODO */
  memset(pack->authenticator, 0, RADIUS_AUTHLEN);
  memcpy(pack->authenticator, req_auth, RADIUS_AUTHLEN);

  /* If packet contains message authenticator: Calculate it! */
  if (!radius_getattr(pack, &ma, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0,0,0)) {
    radius_hmac_md5(this, pack, this->secret, this->secretlen, ma->v.t);
  }

  radius_authresp_authenticator(this, pack, req_auth,
				this->secret,
				this->secretlen);
  
  if (sendto(this->fd, pack, len, 0,
	     (struct sockaddr *) peer, sizeof(struct sockaddr_in)) < 0) {
    log_err(errno, "sendto() failed!");
    return -1;
  } 
  
  return 0;
}


/* 
 * radius_default_pack()
 * Return an empty packet which can be used in subsequent to 
 * radius_addattr()
 */
int
radius_default_pack(struct radius_t *this,
		    struct radius_packet_t *pack, 
		    int code)
{
  memset(pack, 0, RADIUS_PACKSIZE);
  pack->code = code;
  pack->id = 0; /* Let the send procedure queue the packet and assign id */
  pack->length = htons(RADIUS_HDRSIZE);
  
  if (fread(pack->authenticator, 1, RADIUS_AUTHLEN, this->urandom_fp) != RADIUS_AUTHLEN) {
    log_err(errno, "fread() failed");
    return -1;
  }

  /* always add the chillispot version to requests */
  radius_addattr(this, pack, RADIUS_ATTR_VENDOR_SPECIFIC,
		 RADIUS_VENDOR_CHILLISPOT, RADIUS_ATTR_CHILLISPOT_VERSION, 
		 0, (uint8_t*)VERSION, strlen(VERSION));
  
  return 0;
}


/* 
 * radius_authcheck()
 * Check that the authenticator on a reply is correct.
 */
int radius_authcheck(struct radius_t *this, struct radius_packet_t *pack, 
		     struct radius_packet_t *pack_req)
{
  uint8_t auth[RADIUS_AUTHLEN];
  MD5_CTX context;

  MD5Init(&context);
  MD5Update(&context, (uint8_t *) pack, RADIUS_HDRSIZE-RADIUS_AUTHLEN);
  MD5Update(&context, pack_req->authenticator, RADIUS_AUTHLEN);
  MD5Update(&context, ((uint8_t *) pack) + RADIUS_HDRSIZE, 
	    ntohs(pack->length) - RADIUS_HDRSIZE);
  MD5Update(&context, (uint8_t *)this->secret, this->secretlen);
  MD5Final(auth, &context);
  
  return memcmp(pack->authenticator, auth, RADIUS_AUTHLEN);
}

/* 
 * radius_acctcheck()
 * Check that the authenticator on an accounting request is correct.
 */
int radius_acctcheck(struct radius_t *this, struct radius_packet_t *pack)
{
  uint8_t auth[RADIUS_AUTHLEN];
  uint8_t padd[RADIUS_AUTHLEN] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  MD5_CTX context;
  
  MD5Init(&context);
  MD5Update(&context, (uint8_t *)pack, RADIUS_HDRSIZE-RADIUS_AUTHLEN);
  MD5Update(&context, (uint8_t *)padd, RADIUS_AUTHLEN);
  MD5Update(&context, ((uint8_t *)pack) + RADIUS_HDRSIZE, 
	    ntohs(pack->length) - RADIUS_HDRSIZE);
  MD5Update(&context, (uint8_t *)this->secret, this->secretlen);
  MD5Final(auth, &context);
  
  return memcmp(pack->authenticator, auth, RADIUS_AUTHLEN);
}


/* 
 * radius_decaps()
 * Read and process a received radius packet.
 */
int radius_decaps(struct radius_t *this) {
  ssize_t status;
  struct radius_packet_t pack;
  struct radius_packet_t pack_req;
  void *cbp;
  struct sockaddr_in addr;
  socklen_t fromlen = sizeof(addr);
  int coarequest = 0;

  if (this->debug) 
    log_dbg("Received radius packet");
  
  if ((status = recvfrom(this->fd, &pack, sizeof(pack), 0, 
			 (struct sockaddr *) &addr, &fromlen)) <= 0) {
    log_err(errno, "recvfrom() failed");
    return -1;
  }

  if (status < RADIUS_HDRSIZE) {
    log_warn(0, "Received radius packet which is too short: %d < %d!",
	     status, RADIUS_HDRSIZE);
    return -1;
  }

  if (ntohs(pack.length) != (uint16_t)status) {
    log_warn(errno, "Received radius packet with wrong length field %d != %d!",
	     ntohs(pack.length), status);
    return -1;
  }


  switch (pack.code) {
  case RADIUS_CODE_DISCONNECT_REQUEST:
  case RADIUS_CODE_COA_REQUEST:
    coarequest = 1;
    break;
  default:
    coarequest = 0;
  }

  if (!coarequest) {
    /* Check that reply is from correct address */
    if ((addr.sin_addr.s_addr != this->hisaddr0.s_addr) &&
	(addr.sin_addr.s_addr != this->hisaddr1.s_addr)) {
      log_warn(0, "Received radius reply from wrong address %.8x!",
	      addr.sin_addr.s_addr);
      return -1;
    }
    
    /* Check that UDP source port is correct */
    if ((addr.sin_port != htons(this->authport)) &&
	(addr.sin_port != htons(this->acctport))) {
      log_warn(0, "Received radius packet from wrong port %.4x!",
	      addr.sin_port);
      return -1;
    }
    
    if (radius_queue_out(this, &pack_req, pack.id, &cbp)) {
      log_warn(0, "Matching request was not found in queue: %d!", pack.id);
      return -1;
    }

    if (radius_authcheck(this, &pack, &pack_req)) {
      log_warn(0, "Authenticator does not match request!");
      return -1;
    }

    /* Set which radius server to use next */
    if (addr.sin_addr.s_addr == this->hisaddr0.s_addr)
      this->lastreply = 0;
    else
      this->lastreply = 1;
    
  }
  else {
    if (!this->coanocheck) {
      /* Check that request is from correct address */
      if ((addr.sin_addr.s_addr != this->hisaddr0.s_addr) &&
	  (addr.sin_addr.s_addr != this->hisaddr1.s_addr)) {
	log_warn(0, "Received radius request from wrong address %.8x!",
		addr.sin_addr.s_addr);
	return -1;
      }
    }

    if (radius_acctcheck(this, &pack)) {
      log_warn(0, "Authenticator did not match MD5 of packet!");
      return -1;
    }
  }
    
  /* TODO: Check consistency of attributes vs packet length */
  
  switch (pack.code) {
  case RADIUS_CODE_ACCESS_ACCEPT:
  case RADIUS_CODE_ACCESS_REJECT:
  case RADIUS_CODE_ACCESS_CHALLENGE:
  case RADIUS_CODE_DISCONNECT_ACK:
  case RADIUS_CODE_DISCONNECT_NAK:
  case RADIUS_CODE_STATUS_ACCEPT:
  case RADIUS_CODE_STATUS_REJECT:
    if (this->cb_auth_conf)
      return this->cb_auth_conf(this, &pack, &pack_req, cbp);
    else
      return 0;
    break;
  case RADIUS_CODE_ACCOUNTING_RESPONSE:
    if (this->cb_acct_conf)
      return this->cb_acct_conf(this, &pack, &pack_req, cbp);
    else
      return 0;
    break;
  case RADIUS_CODE_DISCONNECT_REQUEST:
  case RADIUS_CODE_COA_REQUEST:
    if (this->cb_coa_ind)
      return this->cb_coa_ind(this, &pack, &addr);
    else
      return 0;
    break;
  default:
    log_warn(0, "Received unknown radius packet %d!", pack.code);
    return -1;
  }
  
  log_warn(0, "Received unknown radius packet %d!", pack.code);
  return -1;
}

/* 
 * radius_proxy_ind()
 * Read and process a received radius packet.
 */
int radius_proxy_ind(struct radius_t *this) {
  ssize_t status;
  struct radius_packet_t pack;
  struct sockaddr_in addr;
  socklen_t fromlen = sizeof(addr);

  if (this->debug) 
    log_dbg("Received radius packet");
  
  if ((status = recvfrom(this->proxyfd, &pack, sizeof(pack), 0, 
			 (struct sockaddr *) &addr, &fromlen)) <= 0) {
    log_err(errno, "recvfrom() failed");
    return -1;
  }

  if (status < RADIUS_HDRSIZE) {
    log_warn(0, "Received radius packet which is too short: %d < %d!",
	    status, RADIUS_HDRSIZE);
    return -1;
  }

  if (ntohs(pack.length) != (uint16_t)status) {
    log_err(0, "Received radius packet with wrong length field %d != %d!",
	    ntohs(pack.length), status);
    return -1;
  }

  /* Is this a known request? */
  if ((this->cb_ind) &&
      ((pack.code == RADIUS_CODE_ACCESS_REQUEST) ||
       (pack.code == RADIUS_CODE_ACCOUNTING_REQUEST) ||
       (pack.code == RADIUS_CODE_DISCONNECT_REQUEST) ||
       (pack.code == RADIUS_CODE_STATUS_REQUEST))) {

    /* Check that request is from a known client */
    /* Any of the two servers or from one of the clients */
    if ((addr.sin_addr.s_addr & this->proxymask.s_addr)!= 
	this->proxyaddr.s_addr) {
      log_warn(0, "Received radius request from wrong address %.8x!",
	       addr.sin_addr.s_addr);
      return -1;
    }
    
    return this->cb_ind(this, &pack, &addr);
  }

  log_warn(0, "Received unknown radius packet %d!", pack.code);
  return -1;
}

struct app_conn_t admin_session;

int chilliauth_radius(struct radius_t *radius) {
  struct radius_packet_t radius_pack;
  int ret = -1;

  if (radius_default_pack(radius, &radius_pack, RADIUS_CODE_ACCESS_REQUEST)) {
    log_err(0, "radius_default_pack() failed");
    return ret;
  }

  /* adminuser is required */
  radius_addattr(radius, &radius_pack, RADIUS_ATTR_USER_NAME, 0, 0, 0,
		 (uint8_t *)options.adminuser, strlen(options.adminuser));

  if (options.adminpasswd)
    radius_addattr(radius, &radius_pack, RADIUS_ATTR_USER_PASSWORD, 0, 0, 0,
		   (uint8_t *)options.adminpasswd, strlen(options.adminpasswd));

  radius_addattr(radius, &radius_pack, RADIUS_ATTR_SERVICE_TYPE, 0, 0,
		 RADIUS_SERVICE_TYPE_ADMIN_USER, NULL, 0); 

  
  radius_addattr(radius, &radius_pack, RADIUS_ATTR_NAS_PORT_TYPE, 0, 0,
		 options.radiusnasporttype, NULL, 0);

  radius_addnasip(radius, &radius_pack);

  radius_addcalledstation(radius, &radius_pack);

  if (options.radiusnasid)
    radius_addattr(radius, &radius_pack, RADIUS_ATTR_NAS_IDENTIFIER, 0, 0, 0,
		   (uint8_t *)options.radiusnasid, strlen(options.radiusnasid));
  
  if (options.radiuslocationid)
    radius_addattr(radius, &radius_pack, RADIUS_ATTR_VENDOR_SPECIFIC,
		   RADIUS_VENDOR_WISPR, RADIUS_ATTR_WISPR_LOCATION_ID, 0,
		   (uint8_t *)options.radiuslocationid, strlen(options.radiuslocationid));
  
  if (options.radiuslocationname)
    radius_addattr(radius, &radius_pack, RADIUS_ATTR_VENDOR_SPECIFIC,
		   RADIUS_VENDOR_WISPR, RADIUS_ATTR_WISPR_LOCATION_NAME, 0,
		   (uint8_t *)options.radiuslocationname, 
		   strlen(options.radiuslocationname));

  radius_addattr(radius, &radius_pack, RADIUS_ATTR_ACCT_SESSION_ID, 0, 0, 0,
		 (uint8_t*)admin_session.s_state.sessionid, REDIR_SESSIONID_LEN-1);

  if (admin_session.s_state.redir.classlen) {
    radius_addattr(radius, &radius_pack, RADIUS_ATTR_CLASS, 0, 0, 0,
		   admin_session.s_state.redir.classbuf,
		   admin_session.s_state.redir.classlen);
  }

  radius_addattr(radius, &radius_pack, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 
		 0, 0, 0, NULL, RADIUS_MD5LEN);

  return radius_req(radius, &radius_pack, &admin_session); 
}


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