- initial version of rlm_jradius with directions and dictionary
authordwbird <dwbird>
Mon, 24 Sep 2007 16:16:21 +0000 (16:16 +0000)
committerdwbird <dwbird>
Mon, 24 Sep 2007 16:16:21 +0000 (16:16 +0000)
- updated chilli* dictionary

doc/rlm_jradius [new file with mode: 0644]
share/dictionary.chillispot
share/dictionary.jradius [new file with mode: 0644]
src/modules/rlm_jradius/Makefile [new file with mode: 0644]
src/modules/rlm_jradius/jradius.conf [new file with mode: 0644]
src/modules/rlm_jradius/rlm_jradius.c [new file with mode: 0644]

diff --git a/doc/rlm_jradius b/doc/rlm_jradius
new file mode 100644 (file)
index 0000000..bd438d3
--- /dev/null
@@ -0,0 +1,95 @@
+
+                              rlm_jradius 
+
+JRadius is a Java RADIUS client and server framework. As a client, it
+provides a pure Java interface. When ran as a (pure Java) server, JRadius
+listens for requests from the rlm_jradius module running in FreeRADIUS. The
+module sends JRadius the RADIUS request, reply, and the internal FreeRADIUS
+'config_items' attribute list and returns the same after processing. 
+
+Configuring rlm_jradius for use with a running JRadius server starts with
+the module definition, in jradius.conf, as follows:
+
+  jradius {
+    name             = "example"           # Name of the JRadius Application
+                                           # (known as "Sender" in JRadius)
+    primary          = "127.0.0.1"         # Primary JRadius Server
+                                           # (using default port of 1814)
+    secondary        = "192.168.1.2:1815"  # Secondary JRadius Server (port 1815)
+    tertiary         = "192.168.1.2:1816"  # Tertiary JRadius Server
+    timeout          = 1                   # JRadius Server Connect Timeout (default 5)
+    onfail           = NOOP                # What to do if no JRadius
+                                           # Server is found. Options are:
+                                           # FAIL (default), OK, REJECT, NOOP
+    keepalive        = yes                 # Keep connections to JRadius pooled
+    connections      = 8                   # Number of pooled JRadius connections
+    allow_codechange = yes                 # Allow the changing the RADIUS code/type
+    allow_idchange   = yes                 # Allow the change of the RADIUS pkt id
+  }
+
+The 'name' can be used in the JRadius server to define a specific logic
+"chain" of handlers. Three JRadius servers can be defined for redundancy -
+making it easier to upgrade JRadius and its handlers. It is recommended to
+run the FreeRADIUS module in 'keepalive' mode whereby connections to JRadius
+are pool, similar to the rlm_sql module. 
+
+The jradius.conf configuration file then needs to be included into the
+radiusd.conf modules block, for instance:
+
+  modules {
+    ...
+
+    $INCLUDE  ${confdir}/jradius.conf
+  }
+
+And the 'jradius' module must be added to the appropriate handler blocks,
+commonly:
+
+  authorize {
+    ...
+
+    jradius
+  }
+
+  preacct {
+    ...
+
+    jradius
+  }
+
+  accounting {
+    ...
+
+    jradius
+  }
+
+  ...and so on...
+
+JRadius Quick-start:
+
+  Starting JRadius server:
+
+    wget http://jradius.net/dist/jradius-current.zip
+    unzip jradius-current.zip
+    cd jradius
+    sh jradius.sh
+
+  Running the JRadius Simulator:
+
+    sh jRadiusSimulator.sh
+
+  Using RadClient:
+
+    echo "User-Name = test"        > radius.pkt
+    echo "User-Password = test"   >> radius.pkt
+
+    sh radclient.sh localhost testing123 radius.pkt
+
+  To build a new jradius-dictionary.jar for your specific FreeRADIUS
+  dictionary, see the ant's build.xml or maven's dictionary/pom.xml in the
+  source svn.
+
+  JRadius does have some Vendor Specific Attributes; see dictionary.jradius
+  and ensure it is included in your main dictionary file. 
+
+For more information, see http://jradius.net/
index 15b3c63..8b1c4a5 100644 (file)
@@ -1,8 +1,9 @@
 # -*- text -*-
 ##############################################################################
 #
-#      ChilliSpot captive portal
+#      ChilliSpot (and CoovaChilli) captive portal
 #      http://www.chillispot.org
+#      http://coova.org/wiki/index.php/CoovaChilli
 #
 #      $Id$
 #
@@ -15,8 +16,15 @@ BEGIN-VENDOR ChilliSpot
 ATTRIBUTE      ChilliSpot-Max-Input-Octets             1       integer
 ATTRIBUTE      ChilliSpot-Max-Output-Octets            2       integer
 ATTRIBUTE      ChilliSpot-Max-Total-Octets             3       integer
+ATTRIBUTE       ChilliSpot-Bandwidth-Max-Up            4       integer
+ATTRIBUTE       ChilliSpot-Bandwidth-Max-Down          5       integer
+ATTRIBUTE       ChilliSpot-Config                      6       string
+ATTRIBUTE       ChilliSpot-Lang                        7       string
+ATTRIBUTE       ChilliSpot-Version                     8       string
+ATTRIBUTE       ChilliSpot-OriginalURL                 9       string
 
-# Configuration management parameters
+
+# Configuration management parameters (ChilliSpot Only)
 ATTRIBUTE      ChilliSpot-UAM-Allowed                  100     string
 ATTRIBUTE      ChilliSpot-MAC-Allowed                  101     string
 ATTRIBUTE      ChilliSpot-Interval                     102     integer
@@ -24,7 +32,7 @@ ATTRIBUTE     ChilliSpot-Interval                     102     integer
 # Inline with RFC 2882 use of VSE-Authorize-Only for remote config
 # Note that 14559 = 0x38df is used as prefix for the VSE.
 # This is recognized as the best (but bad) way of doing VSEs.
-#
+# (ChilliSpot Only - CoovaChilli uses Service-Type = Administrative-User)
 VALUE  Service-Type                    ChilliSpot-Authorize-Only 0x38df0001
 
 END-VENDOR     ChilliSpot
diff --git a/share/dictionary.jradius b/share/dictionary.jradius
new file mode 100644 (file)
index 0000000..3e18266
--- /dev/null
@@ -0,0 +1,4 @@
+VENDOR         JRadius                 19211
+ATTRIBUTE      JRadius-Request-Id      1       string  JRadius
+ATTRIBUTE      JRadius-Session-Id      2       string  JRadius
+ATTRIBUTE      JRadius-Proxy-Client    3       octets  JRadius
diff --git a/src/modules/rlm_jradius/Makefile b/src/modules/rlm_jradius/Makefile
new file mode 100644 (file)
index 0000000..53295c5
--- /dev/null
@@ -0,0 +1,13 @@
+TARGET      = rlm_jradius
+SRCS        = rlm_jradius.c 
+HEADERS     = 
+RLM_CFLAGS  = 
+RLM_LIBS    = 
+
+## this uses the RLM_CFLAGS and RLM_LIBS and SRCS defs to make TARGET.
+include ../rules.mak
+
+$(STATIC_OBJS): $(HEADERS)
+
+$(DYNAMIC_OBJS): $(HEADERS)
+
diff --git a/src/modules/rlm_jradius/jradius.conf b/src/modules/rlm_jradius/jradius.conf
new file mode 100644 (file)
index 0000000..ba2d52e
--- /dev/null
@@ -0,0 +1,17 @@
+
+jradius {
+  name             = "example"           # Name of the JRadius Application
+                                         # (known as "Sender" in JRadius)
+  primary          = "127.0.0.1"         # Primary JRadius Server
+                                         # (using default port of 1814)
+  secondary        = "192.168.1.2:1815"  # Secondary JRadius Server (port 1815)
+  tertiary         = "192.168.1.2:1816"  # Tertiary JRadius Server
+  timeout          = 1                   # JRadius Server Connect Timeout (default 5)
+  onfail           = NOOP                # What to do if no JRadius
+                                         # Server is found. Options are:
+                                         # FAIL (default), OK, REJECT, NOOP
+  keepalive        = yes                 # Keep connections to JRadius pooled
+  connections      = 8                   # Number of pooled JRadius connections
+  allow_codechange = yes                 # Allow the changing the RADIUS code/type
+  allow_idchange   = yes                 # Allow the change of the RADIUS pkt id
+}
diff --git a/src/modules/rlm_jradius/rlm_jradius.c b/src/modules/rlm_jradius/rlm_jradius.c
new file mode 100644 (file)
index 0000000..44b9bb1
--- /dev/null
@@ -0,0 +1,940 @@
+/**
+ * rlm_jradius - The FreeRADIUS JRadius Server Module
+ * Copyright (C) 2004-2006 PicoPoint, B.V.
+ * Copyright (c) 2007 David Bird
+ * 
+ * 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
+ *
+ *  This module is used to connect FreeRADIUS to the JRadius server. 
+ *  JRadius is a Java RADIUS client and server framework, see doc/rlm_jradius
+ *  and http://jradius.net/ for more information. 
+ *
+ *  Author(s): David Bird <dbird@acm.org>
+ *
+ *  Connection pooling code based on rlm_sql, see rlm_sql/sql.c for copyright and license.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/autoconf.h>
+#include <freeradius-devel/libradius.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/conffile.h>
+#define STR_VALUE(p) ((p)->data.strvalue)
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK O_NDELAY
+#endif
+
+static const int JRADIUS_PORT         = 1814;
+static const int HALF_MESSAGE_LEN     = 16384;
+static const int MESSAGE_LEN          = 32768;
+
+static const int JRADIUS_authenticate = 1;
+static const int JRADIUS_authorize    = 2;
+static const int JRADIUS_preacct      = 3;
+static const int JRADIUS_accounting   = 4;
+static const int JRADIUS_checksimul   = 5;
+static const int JRADIUS_pre_proxy    = 6;
+static const int JRADIUS_post_proxy   = 7;
+static const int JRADIUS_post_auth    = 8;
+
+#define LOG_PREFIX  "rlm_jradius: "
+#define MAX_HOSTS   4
+
+typedef struct jradius_socket {
+  int  id;
+#ifdef HAVE_PTHREAD_H
+  pthread_mutex_t mutex;
+#endif
+  struct jradius_socket *next;
+  enum { is_connected, not_connected } state;
+  
+  union {
+    int sock;
+  } con;
+} JRSOCK;
+
+typedef struct jradius_inst {
+  time_t      connect_after;
+  JRSOCK    * sock_pool;
+  JRSOCK    * last_used;
+
+  char     * name;
+  char     * host   [MAX_HOSTS];
+  uint32_t   ipaddr [MAX_HOSTS];
+  int        port   [MAX_HOSTS];
+  int        timeout;
+  int        allow_codechange;
+  int        allow_idchange;
+  int        onfail;
+  char     * onfail_s;
+  int        keepalive;
+  int        jrsock_cnt;
+} JRADIUS;
+
+typedef struct _byte_array
+{
+  unsigned int size;
+  unsigned int pos;
+  unsigned int left;
+  unsigned char * b;
+} byte_array;
+
+static CONF_PARSER module_config[] = {
+  { "name",         PW_TYPE_STRING_PTR,  offsetof(JRADIUS, name),       NULL,  "localhost"},
+  { "primary",      PW_TYPE_STRING_PTR,  offsetof(JRADIUS, host[0]),    NULL,  "localhost"},
+  { "secondary",    PW_TYPE_STRING_PTR,  offsetof(JRADIUS, host[1]),    NULL,  NULL},
+  { "tertiary",     PW_TYPE_STRING_PTR,  offsetof(JRADIUS, host[2]),    NULL,  NULL},
+  { "timeout",      PW_TYPE_INTEGER,     offsetof(JRADIUS, timeout),    NULL,  "5"},
+  { "onfail",       PW_TYPE_STRING_PTR,  offsetof(JRADIUS, onfail_s),   NULL,  NULL},
+  { "keepalive",    PW_TYPE_BOOLEAN,     offsetof(JRADIUS, keepalive),  NULL,  "yes"},
+  { "connections",  PW_TYPE_INTEGER,     offsetof(JRADIUS, jrsock_cnt), NULL,  "8"},
+  { "allow_codechange", PW_TYPE_BOOLEAN, offsetof(JRADIUS, allow_codechange),  NULL,  "no"},
+  { "allow_idchange",   PW_TYPE_BOOLEAN, offsetof(JRADIUS, allow_idchange),    NULL,  "no"},
+  { NULL, -1, 0, NULL, NULL }
+};
+
+static int connect_socket(JRSOCK *jrsock, JRADIUS *inst)
+{
+  struct sockaddr_in local_addr, serv_addr;
+  int i, connected = 0;
+  char buff[128];
+  int sock;
+
+  /*
+   *     Connect to jradius servers until we succeed or die trying
+   */
+  for (i = 0; !connected && i < MAX_HOSTS && inst->ipaddr[i] > 0; i++) {
+
+    /*
+     *     Allocate a TCP socket
+     */
+    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+      radlog(L_ERR, LOG_PREFIX "could not allocate TCP socket");
+      goto failed;
+    }
+    
+    /*
+     *     If we have a timeout value set, make the socket non-blocking
+     */
+    if (inst->timeout > 0 &&
+       fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK) == -1) {
+      radlog(L_ERR, LOG_PREFIX "could not set non-blocking on socket");
+      goto failed;
+    }
+    
+    /*
+     *     Bind to any local port
+     */
+    memset(&local_addr, 0, sizeof(local_addr));
+    local_addr.sin_family = AF_INET;
+    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    local_addr.sin_port = htons(0);
+    
+    if (bind(sock, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) {
+      radlog(L_ERR, LOG_PREFIX "could not locally bind TCP socket");
+      goto failed;
+    }
+    
+    /*
+     *     Attempt connection to remote server
+     */
+    memset(&serv_addr, 0, sizeof(serv_addr));
+    serv_addr.sin_family = AF_INET;
+    memcpy((char *) &serv_addr.sin_addr, &(inst->ipaddr[i]), 4);
+    serv_addr.sin_port = htons(inst->port[i]);
+    
+    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
+      if (inst->timeout > 0 && (errno == EINPROGRESS || errno == EWOULDBLOCK)) {
+       /*
+        *     Wait to see if non-blocking socket connects or times-out
+        */
+       struct pollfd pfd;
+       memset(&pfd, 0, sizeof(pfd));
+
+       pfd.fd = sock;
+       pfd.events = POLLOUT;
+
+       if (poll(&pfd, 1, inst->timeout * 1000) == 1 && pfd.revents) {
+         /*
+          *     Lets make absolutely sure we are connected
+          */
+         struct sockaddr_in sa;
+         unsigned int salen = sizeof(sa);
+         if (getpeername(sock, (struct sockaddr *) &sa, &salen) != -1) {
+           /*
+            *     CONNECTED! break out of for-loop
+            */
+           connected = 1;
+           break;
+         }
+       }
+      }
+
+      /*
+       *     Timed-out
+       */
+      radlog(L_ERR, LOG_PREFIX "could not connect to %s:%d", 
+            ip_ntoa(buff, inst->ipaddr[i]), inst->port[i]);
+
+    } else {
+      /*
+       *     CONNECTED (instantly)! break out of for-loop
+       */
+      connected = 1;
+      break;
+    }
+
+    /*
+     *     Unable to connect, cleanup and start over
+     */
+    close(sock); sock=0;
+  }
+
+  if (!connected) {
+    radlog(L_ERR, LOG_PREFIX "could not find any jradius server!");
+    goto failed;
+  }
+
+  /*
+   *     If we previously set the socket to non-blocking, restore blocking 
+   */
+  if (inst->timeout > 0 &&
+      fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) & ~O_NONBLOCK) == -1) {
+    radlog(L_ERR, LOG_PREFIX "could not set blocking on socket");
+    goto failed;
+  }
+
+  jrsock->state = is_connected;
+  jrsock->con.sock = sock;
+  return 1;
+
+ failed:
+  if (sock > 0) { shutdown(sock, 2); close(sock); }
+  jrsock->state = not_connected;
+  return 0;
+}
+
+static void close_socket(JRADIUS * inst, JRSOCK *jrsock) {
+  radlog(L_INFO, "rlm_jradius: Closing JRadius connection %d", jrsock->id);
+  if (jrsock->con.sock > 0) { 
+    shutdown(jrsock->con.sock, 2); 
+    close(jrsock->con.sock); 
+  }
+  jrsock->state = not_connected;
+  jrsock->con.sock = 0;
+}
+
+static void free_socket(JRADIUS * inst, JRSOCK *jrsock) {
+  close_socket(inst, jrsock);
+  if (inst->keepalive) {
+#ifdef HAVE_PTHREAD_H
+    pthread_mutex_destroy(&jrsock->mutex);
+#endif
+    free(jrsock);
+  }
+}
+
+static int socket_send(JRSOCK *jrsock, unsigned char *b, unsigned int blen) {
+  return send(jrsock->con.sock, b, blen, 0);
+}
+
+static int init_socketpool(JRADIUS * inst)
+{
+  int i, rcode;
+  int success = 0;
+  JRSOCK *jrsock;
+  
+  inst->connect_after = 0;
+  inst->sock_pool = NULL;
+  
+  for (i = 0; i < inst->jrsock_cnt; i++) {
+    radlog(L_INFO, "rlm_jradius: starting JRadius connection %d", i);
+    
+    if ((jrsock = rad_malloc(sizeof(*jrsock))) == 0) return -1;
+    
+    memset(jrsock, 0, sizeof(*jrsock));
+    jrsock->id = i;
+    jrsock->state = not_connected;
+
+#ifdef HAVE_PTHREAD_H
+    rcode = pthread_mutex_init(&jrsock->mutex,NULL);
+    if (rcode != 0) {
+      radlog(L_ERR, "rlm_jradius: Failed to init lock: %s", strerror(errno));
+      return 0;
+    }
+#endif
+
+    if (time(NULL) > inst->connect_after)
+      if (connect_socket(jrsock, inst))
+       success = 1;
+    
+    jrsock->next = inst->sock_pool;
+    inst->sock_pool = jrsock;
+  }
+  inst->last_used = NULL;
+  
+  if (!success) {
+    radlog(L_DBG, "rlm_jradius: Failed to connect to JRadius server.");
+  }
+  
+  return 1;
+}
+
+static void free_socketpool(JRADIUS * inst)
+{
+  JRSOCK *cur;
+  JRSOCK *next;
+
+  for (cur = inst->sock_pool; cur; cur = next) {
+    next = cur->next;
+    free_socket(inst, cur);
+  }
+  
+  inst->sock_pool = NULL;
+}
+
+static JRSOCK * get_socket(JRADIUS * inst)
+{
+  JRSOCK *cur, *start;
+  int tried_to_connect = 0;
+  int unconnected = 0;
+
+  start = inst->last_used;
+  if (!start) start = inst->sock_pool;
+  
+  cur = start;
+  
+  while (cur) {
+#ifdef HAVE_PTHREAD_H
+    if (pthread_mutex_trylock(&cur->mutex) != 0) {
+      goto next;
+    } 
+#endif
+    
+    if ((cur->state == not_connected) && (time(NULL) > inst->connect_after)) {
+      radlog(L_INFO, "rlm_jradius: Trying to (re)connect unconnected handle %d", cur->id);
+      tried_to_connect++;
+      connect_socket(cur, inst);
+    }
+    
+    if (cur->state == not_connected) {
+      radlog(L_DBG, "rlm_jradius: Ignoring unconnected handle %d", cur->id);
+      unconnected++;
+#ifdef HAVE_PTHREAD_H
+      pthread_mutex_unlock(&cur->mutex);
+#endif
+      goto next;
+    }
+    
+    radlog(L_DBG, "rlm_jradius: Reserving JRadius socket id: %d", cur->id);
+    
+    if (unconnected != 0 || tried_to_connect != 0) {
+      radlog(L_INFO, "rlm_jradius: got socket %d after skipping %d unconnected handles, tried to reconnect %d though", 
+            cur->id, unconnected, tried_to_connect);
+    }
+
+    inst->last_used = cur->next;
+    return cur;
+    
+  next:
+    cur = cur->next;
+    if (!cur) cur = inst->sock_pool;
+    if (cur == start) break;
+  }
+  
+  radlog(L_INFO, "rlm_jradius: There are no sockets to use! skipped %d, tried to connect %d", 
+        unconnected, tried_to_connect);
+  return NULL;
+}
+
+static int release_socket(JRADIUS * inst, JRSOCK * jrsock)
+{
+#ifdef HAVE_PTHREAD_H
+  pthread_mutex_unlock(&jrsock->mutex);
+#endif
+  
+  radlog(L_DBG, "rlm_jradius: Released JRadius socket id: %d", jrsock->id);
+  
+  return 0;
+}
+
+
+/*
+ *     Initialize the jradius module
+ */
+static int jradius_instantiate(CONF_SECTION *conf, void **instance)
+{
+  JRADIUS *inst = (JRADIUS *) instance;
+  char host[128], b[128], *h;
+  int i, p, idx, port;
+
+  inst = rad_malloc(sizeof(JRADIUS));
+  memset(inst, 0, sizeof(JRADIUS));
+
+  if (cf_section_parse(conf, inst, module_config) < 0) {
+    free(inst);
+    return -1;
+  }
+
+  for (i = 0, idx = 0; i < MAX_HOSTS; i++) {
+    if (inst->host[i] && strlen(inst->host[i]) < sizeof(host)) {
+      h = inst->host[i];
+      p = JRADIUS_PORT;
+      
+      strcpy(b, h);
+      if (sscanf(b, "%[^:]:%d", host, &port) == 2) { h = host; p = port; }
+
+      if (h) {
+       lrad_ipaddr_t ipaddr;
+       if (ip_hton(h, AF_INET, &ipaddr) < 0) {
+         radlog(L_ERR, "Can't find IP address for host %s", h);
+         continue;
+       }
+       if ((inst->ipaddr[idx] = ipaddr.ipaddr.ip4addr.s_addr) != htonl(INADDR_NONE)) {
+         inst->port[idx] = p;
+         radlog(L_INFO, LOG_PREFIX "configuring jradius server %s:%d", h, p);
+         idx++;
+       } else {
+         radlog(L_ERR, LOG_PREFIX "invalid jradius server %s", h);
+       }
+      }
+    }
+  }
+
+  if (inst->keepalive) init_socketpool(inst);
+
+  inst->onfail = RLM_MODULE_FAIL;
+
+  if (inst->onfail_s) {
+    if      (!strcmp(inst->onfail_s, "NOOP"))    inst->onfail = RLM_MODULE_NOOP;
+    else if (!strcmp(inst->onfail_s, "REJECT"))  inst->onfail = RLM_MODULE_REJECT;
+    else if (!strcmp(inst->onfail_s, "OK"))      inst->onfail = RLM_MODULE_OK;
+    else if (!strcmp(inst->onfail_s, "FAIL"))    inst->onfail = RLM_MODULE_FAIL;
+    else radlog(L_ERR, LOG_PREFIX "invalid jradius 'onfail' state %s", inst->onfail_s);
+  }
+
+  *instance = inst;
+
+  return 0;
+}
+
+/*
+ *     Initialize a byte array buffer structure
+ */
+static void init_byte_array(byte_array * ba, unsigned char *b, int blen)
+{
+  ba->b = b;
+  ba->size = ba->left = blen;
+  ba->pos = 0;
+}
+
+/*
+ *     Pack a single byte into a byte array buffer
+ */
+static int pack_byte(byte_array * ba, unsigned char c)
+{
+  if (ba->left < 1) return -1;
+
+  ba->b[ba->pos] = c;
+  ba->pos++;
+  ba->left--;
+
+  return 0;
+}
+
+/*
+ *     Pack an array of bytes into a byte array buffer
+ */
+static int pack_bytes(byte_array * ba, unsigned char *d, unsigned int dlen)
+{
+  if (ba->left < dlen) return -1;
+
+  memcpy((void *)(ba->b + ba->pos), d, dlen);
+  ba->pos  += dlen;
+  ba->left -= dlen;
+
+  return 0;
+}
+
+/*
+ *     Pack an integer into a byte array buffer (adjusting for byte-order)
+ */
+static int pack_int(byte_array * ba, unsigned int i)
+{
+  if (ba->left < 4) return -1;
+
+  i = htonl(i);
+
+  memcpy((void *)(ba->b + ba->pos), (void *)&i, 4);
+  ba->pos  += 4;
+  ba->left -= 4;
+
+  return 0;
+}
+
+/*
+ *     Pack one byte array buffer into another byte array buffer
+ */
+static int pack_array(byte_array * ba, byte_array * a)
+{
+  if (ba->left < a->pos) return -1;
+
+  memcpy((void *)(ba->b + ba->pos), (void *)a->b, a->pos);
+  ba->pos  += a->pos;
+  ba->left -= a->pos;
+
+  return 0;
+}
+
+/*
+ *     Pack radius attributes into a byte array buffer
+ */
+static int pack_vps(byte_array * ba, VALUE_PAIR * vps)
+{
+  VALUE_PAIR * vp;
+
+  for (vp = vps; vp != NULL; vp = vp->next) {
+
+    radlog(L_DBG, LOG_PREFIX "packing attribute %s (type: %d; len: %d)", 
+          vp->name, vp->attribute, vp->length);
+
+    if (pack_int(ba, vp->attribute) == -1) return -1;
+    if (pack_int(ba, vp->length)    == -1) return -1;
+    if (pack_int(ba, vp->operator)  == -1) return -1;
+
+    switch (vp->type) {
+      case PW_TYPE_INTEGER:
+      case PW_TYPE_DATE:
+       if (pack_int(ba, vp->lvalue) == -1) return -1;
+       break;
+      case PW_TYPE_IPADDR:
+       if (pack_bytes(ba, (void *)&vp->lvalue, vp->length) == -1) return -1;
+       break;
+      default:
+       if (pack_bytes(ba, (void *)STR_VALUE(vp), vp->length) == -1) return -1;
+       break;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ *     Pack a radius packet into a byte array buffer
+ */
+static int pack_packet(byte_array * ba, RADIUS_PACKET * p)
+{
+  /*unsigned char code = p->code;*/
+  unsigned char buff[HALF_MESSAGE_LEN];
+  byte_array pba;
+
+  init_byte_array(&pba, buff, sizeof(buff));
+
+  if (pack_vps   (&pba, p->vps) == -1) return -1;
+
+  radlog(L_DBG, LOG_PREFIX "packing packet with code: %d (attr length: %d)", p->code, pba.pos);
+
+  if (pack_byte  (ba, p->code)  == -1) return -1;
+  if (pack_byte  (ba, p->id)    == -1) return -1;
+  if (pack_int   (ba, pba.pos)  == -1) return -1;
+  if (pba.pos == 0) return 0;
+  if (pack_array (ba, &pba)     == -1) return -1;
+
+  return 0;
+}
+
+static int pack_request(byte_array * ba, REQUEST *r)
+{
+  unsigned char buff[HALF_MESSAGE_LEN];
+  byte_array pba;
+
+  init_byte_array(&pba, buff, sizeof(buff));
+
+  if (pack_vps   (&pba, r->config_items) == -1) return -1;
+  if (pack_int   (ba, pba.pos)  == -1) return -1;
+  if (pba.pos == 0) return 0;
+  if (pack_array (ba, &pba)     == -1) return -1;
+      
+  return 0;
+}
+
+/*
+ *     Read a single byte from socket
+ */
+static int read_byte(JRSOCK *jrsock, unsigned char *b)
+{
+  return (read(jrsock->con.sock, b, 1) == 1) ? 0 : -1;
+}
+
+static void unpack_int(unsigned char *c, unsigned int *i)
+{
+  unsigned int ii;
+  memcpy((void *)&ii, c, 4);
+  *i = ntohl(ii);
+}
+
+/*
+ *     Read an integer from the socket (adjusting for byte-order)
+ */
+static int read_int(JRSOCK *jrsock, unsigned int *i)
+{
+  unsigned char c[4];
+
+  if (read(jrsock->con.sock, c, 4) != 4) return -1;
+  unpack_int(c, i);
+
+  return 0;
+}
+
+/*
+ *     Read a value-pair list from the socket
+ */
+static int read_vps(JRSOCK *jrsock, VALUE_PAIR **pl, int plen)
+{
+  VALUE_PAIR *vp;
+  unsigned char buff[MESSAGE_LEN];
+  unsigned int alen, atype, aop;
+  int rlen = 0;
+  
+  while (rlen < plen) {
+    if (read_int (jrsock, &atype) == -1) return -1; rlen += 4;
+    if (read_int (jrsock, &alen)  == -1) return -1; rlen += 4;
+    if (read_int (jrsock, &aop)   == -1) return -1; rlen += 4; 
+
+    radlog(L_DBG, LOG_PREFIX "reading attribute: type=%d; len=%d", atype, alen);
+
+    if (alen >= sizeof(buff)) {
+      radlog(L_ERR, LOG_PREFIX "packet value too large (len: %d)", alen);
+      return -1;
+    }
+
+    if (read(jrsock->con.sock, buff, alen) != (int)alen) return -1; rlen += alen;
+    buff[alen]=0;
+
+    /*
+     *     Create new attribute
+     */
+    vp = paircreate(atype, -1);
+    vp->operator = aop;
+
+    if (vp->type == -1) {
+      /*
+       *     FreeRADIUS should know about the same attributes that JRadius knows
+       */
+      radlog(L_ERR, LOG_PREFIX "received attribute we do not recognize (type: %d)", atype);
+      pairbasicfree(vp);
+      continue;
+    }
+
+    /*
+     *     Fill in the attribute value based on type
+     */
+    switch (vp->type) {
+      case PW_TYPE_INTEGER:
+      case PW_TYPE_DATE:
+       {
+          unpack_int(buff, &vp->lvalue);
+         vp->length = 4;
+       }
+       break;
+
+      case PW_TYPE_IPADDR:
+       memcpy((void *)&vp->lvalue, buff, 4);
+       vp->length = 4;
+       break;
+
+      default:
+        if (alen < sizeof(STR_VALUE(vp))) {
+         memcpy((void *)STR_VALUE(vp), buff, alen);
+           vp->length = alen;
+       }
+       break;
+    }
+
+    /*
+     *     Add the attribute to the packet
+     */
+    pairadd(pl, vp);
+  } 
+
+  return rlen;
+}
+
+/*
+ *     Read a radius packet from the socket
+ */
+static int read_packet(JRADIUS * inst, JRSOCK *jrsock, RADIUS_PACKET *p)
+{
+  unsigned char code;
+  unsigned char id;
+  unsigned int plen;
+
+  if (read_byte (jrsock, &code) == -1) return -1;
+  if (read_byte (jrsock, &id)   == -1) return -1;
+  if (read_int  (jrsock, &plen) == -1) return -1;
+
+  radlog(L_DBG, LOG_PREFIX "reading packet: code=%d len=%d", (int)code, plen);
+
+  if (inst->allow_codechange)
+    if (code != p->code) {
+      radlog(L_INFO, LOG_PREFIX "changing packet code from %d to %d", p->code, code);
+      p->code = code;
+    }
+
+  if (inst->allow_idchange)
+    if (id != p->id) {
+      radlog(L_INFO, LOG_PREFIX "changing packet id from %d to %d", p->id, id);
+      p->id = id;
+    }
+  
+  /*
+   *     Delete previous attribute list
+   */
+  pairfree(&p->vps);
+
+  if (plen == 0) return 0;
+
+  if (read_vps (jrsock, &p->vps, plen) == -1) return -1;
+
+  return 0;
+}
+
+static int read_request(JRSOCK *jrsock, REQUEST *p)
+{
+  unsigned int plen;
+
+  if (read_int(jrsock, &plen) == -1) return -1;
+
+  radlog(L_DBG, LOG_PREFIX "reading request: config_item: len=%d", plen);
+
+  /*
+   *     Delete previous attribute list
+   */
+  pairfree(&p->config_items);
+
+  if (plen == 0) return 0;
+
+  if (read_vps(jrsock, &p->config_items, plen) == -1) return -1;
+
+  return 0;
+}
+
+static int rlm_jradius_call(char func, void *instance, REQUEST *req, int isproxy)
+{
+  JRADIUS        * inst    = instance;
+  RADIUS_PACKET  * request = isproxy ? req->proxy : req->packet;
+  RADIUS_PACKET  * reply   = isproxy ? req->proxy_reply : req->reply;
+  JRSOCK         * jrsock  = 0;
+  JRSOCK           sjrsock;
+
+  int exitstatus = inst->onfail;
+  unsigned char rcode, pcount;
+
+  unsigned char buff[MESSAGE_LEN];
+  byte_array ba;
+
+  char * n = inst->name;
+  unsigned int nlen = strlen(n);
+  const char * err = 0;
+  int rc, attempt2=0;
+
+#define W_ERR(s) { err=s; goto packerror;  }
+#define R_ERR(s) { err=s; goto parseerror; }
+
+  if (inst->keepalive) {
+    jrsock = get_socket(inst);
+    if (!jrsock) return exitstatus;
+  } else {
+    jrsock = &sjrsock;
+    memset(jrsock, 0, sizeof(*jrsock));
+    jrsock->state = not_connected;
+  }
+
+  init_byte_array(&ba, buff, sizeof(buff));
+
+  pcount = 0;
+  if (request) pcount++;
+  if (reply) pcount++;
+
+  /*
+   *     Create byte array to send to jradius
+   */
+  if ((rc = pack_int    (&ba, nlen))                  == -1)  W_ERR("pack_int(nlen)");
+  if ((rc = pack_bytes  (&ba, (void *)n, nlen))       == -1)  W_ERR("pack_bytes(name)");
+  if ((rc = pack_byte   (&ba, func))                  == -1)  W_ERR("pack_byte(fun)");
+  if ((rc = pack_byte   (&ba, pcount))                == -1)  W_ERR("pack_byte(pcnt)");
+  if (pcount > 0 && (rc = pack_packet (&ba, request)) == -1)  W_ERR("pack_packet(req)");
+  if (pcount > 1 && (rc = pack_packet (&ba, reply))   == -1)  W_ERR("pack_packet(rep)");
+  if ((rc = pack_request(&ba, req))                   == -1)  W_ERR("pack_request()");
+
+  /*
+   *     Send data
+   */
+ start_over:
+  if (jrsock->state == not_connected) {
+    if (attempt2) radlog(L_ERR, LOG_PREFIX "reconnecting socket id %d", jrsock->id);
+    if (!connect_socket(jrsock, inst)) {
+      if (attempt2) radlog(L_ERR, LOG_PREFIX "could not reconnect socket %d, giving up", jrsock->id);
+      goto cleanup;
+    }
+  }
+  radlog(L_DBG, LOG_PREFIX "sending %d bytes to socket %d", ba.pos, jrsock->id);
+  if (socket_send(jrsock, ba.b, ba.pos) != (int)ba.pos ||
+      (rc = read_byte (jrsock, &rcode)) == -1) {
+    /*
+     *   With an error on the write or the first read, try closing the socket
+     *   and reconnecting to see if that improves matters any (tries this only once)
+     */
+    radlog(L_ERR, LOG_PREFIX "error sending request with socket %d", jrsock->id);
+    if (!inst->keepalive || attempt2) W_ERR("socket_send/first_read");
+    close_socket(inst, jrsock);
+    attempt2 = 1;
+    goto start_over;
+  }
+
+  /*
+   *     Read result
+   */
+  if ((rc = read_byte (jrsock, &pcount)) == -1)  R_ERR("read_byte(pcnt)");
+
+  radlog(L_DBG, LOG_PREFIX "return code %d; receiving %d packets", (int)rcode, (int)pcount);
+
+  if (pcount > 0 && request) if ((rc = read_packet (inst, jrsock, request)) == -1)  R_ERR("read_packet(req)");
+  if (pcount > 1 && reply)   if ((rc = read_packet (inst, jrsock, reply))   == -1)  R_ERR("read_packet(rep)");
+
+  if ((rc = read_request (jrsock, req)) == -1) R_ERR("read_request()");
+
+  /*
+   *    Since we deleted all the attribute lists in the request,
+   *    we need to reconfigure a few pointers in the REQUEST object
+   */
+  if (req->username) {
+    req->username = pairfind(request->vps, PW_USER_NAME);
+  }
+  if (req->password) {
+    req->password = pairfind(request->vps, PW_PASSWORD);
+    if (!req->password) req->password = pairfind(request->vps, PW_CHAP_PASSWORD);
+  }
+
+  /*
+   *    All done, set return code and cleanup
+   */
+  exitstatus = (int)rcode;
+  goto cleanup;
+
+ parseerror:
+  radlog(L_ERR, LOG_PREFIX "problem parsing the data [%s]",err);
+  if (inst->keepalive) close_socket(inst, jrsock);
+  goto cleanup;
+
+ packerror:
+  radlog(L_ERR, LOG_PREFIX "problem packing the data[%s]",err);
+  if (inst->keepalive) close_socket(inst, jrsock);
+
+ cleanup:
+  if (inst->keepalive) 
+    release_socket(inst, jrsock);
+  else  
+    close_socket(inst, jrsock);
+
+  return exitstatus;
+}
+
+static int jradius_authenticate(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_authenticate, instance, request, 0);
+}
+
+static int jradius_authorize(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_authorize, instance, request, 0);
+}
+
+static int jradius_preacct(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_preacct, instance, request, 0);
+}
+
+static int jradius_accounting(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_accounting, instance, request, 0);
+}
+
+static int jradius_checksimul(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_checksimul, instance, request, 0);
+}
+
+static int jradius_pre_proxy(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_pre_proxy, instance, request, 1);
+}
+
+static int jradius_post_proxy(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_post_proxy, instance, request, 1);
+}
+
+static int jradius_post_auth(void *instance, REQUEST *request)
+{
+  return rlm_jradius_call(JRADIUS_post_auth, instance, request, 0);
+}
+
+static int jradius_detach(void *instance)
+{
+  JRADIUS *inst = (JRADIUS *) instance;
+  free_socketpool(inst);
+  free(inst);
+  return 0;
+}
+
+module_t rlm_jradius = {
+  RLM_MODULE_INIT,
+  "jradius",
+  RLM_TYPE_THREAD_SAFE,
+  jradius_instantiate,
+  jradius_detach,
+  {
+    jradius_authenticate,
+    jradius_authorize,
+    jradius_preacct,
+    jradius_accounting,
+    jradius_checksimul,
+    jradius_pre_proxy,
+    jradius_post_proxy,
+    jradius_post_auth
+  },
+};
+