Merge pull request #50 from arr2036/rlm_rest
authorAlan DeKok <aland@freeradius.org>
Wed, 22 Feb 2012 08:37:49 +0000 (00:37 -0800)
committerAlan DeKok <aland@freeradius.org>
Wed, 22 Feb 2012 08:37:49 +0000 (00:37 -0800)
Add rlm_rest which does REST calls to an external HTTP server.

raddb/modules/rest [new file with mode: 0644]
src/modules/rlm_rest/Makefile [new file with mode: 0644]
src/modules/rlm_rest/Makefile.clean [new file with mode: 0644]
src/modules/rlm_rest/Makefile.in [new file with mode: 0644]
src/modules/rlm_rest/all.mk [new file with mode: 0644]
src/modules/rlm_rest/config.h.in [new file with mode: 0644]
src/modules/rlm_rest/configure.in [new file with mode: 0644]
src/modules/rlm_rest/demo.pl [new file with mode: 0755]
src/modules/rlm_rest/rest.c [new file with mode: 0644]
src/modules/rlm_rest/rest.h [new file with mode: 0644]
src/modules/rlm_rest/rlm_rest.c [new file with mode: 0644]

diff --git a/raddb/modules/rest b/raddb/modules/rest
new file mode 100644 (file)
index 0000000..dca280c
--- /dev/null
@@ -0,0 +1,39 @@
+rest {
+       # rlm_rest will open a connection to the server specified in connect_uri
+       # to populate the connection cache, ready for the first request.
+       # The server will not start if the server specified is unreachable.
+       #
+       # If you wish to disable this pre-caching and reachability check,
+       # comment out the configuration item below.
+       connect_uri = "http://power.freeradius.org"
+       
+       pool {
+            start = 5
+            max = 10
+            spare = 3
+            uses = 0
+            idle_timeout = 100
+            lifetime = 0
+       }
+
+       authorize {
+               uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?section=authorize"
+               method = "get"`
+       }
+       authenticate {
+               uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?section=authenticate"
+               method = "get"
+       }
+       accounting {
+               uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?section=accounting"
+               method = "post"
+       }
+       session {
+               uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?section=checksimul"
+               method = "post"
+       }
+       post-auth {
+               uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?section=post-auth"
+               method = "post"
+       }
+}
diff --git a/src/modules/rlm_rest/Makefile b/src/modules/rlm_rest/Makefile
new file mode 100644 (file)
index 0000000..c66b8c0
--- /dev/null
@@ -0,0 +1,37 @@
+#######################################################################
+#
+# TARGET should be set by autoconf only.  Don't touch it.
+#
+# The SRCS definition should list ALL source files.
+#
+# The HEADERS definition should list ALL header files
+#
+# RLM_CFLAGS defines addition C compiler flags.  You usually don't
+# want to modify this, though.  Get it from autoconf.
+#
+# The RLM_LIBS definition should list ALL required libraries.
+# These libraries really should be pulled from the 'config.mak'
+# definitions, if at all possible.  These definitions are also
+# echoed into another file in ../lib, where they're picked up by
+# ../main/Makefile for building the version of the server with
+# statically linked modules.  Get it from autoconf.
+#
+# RLM_INSTALL is the names of additional rules you need to install
+# some particular portion of the module.  Usually, leave it blank.
+#
+#######################################################################
+TARGET      = rlm_rest
+SRCS        = rlm_rest.c rest.c
+HEADERS     = rest.h
+RLM_CFLAGS  =     
+RLM_LIBS    = -ljson -lcurl 
+RLM_INSTALL = install-rest
+
+## this uses the RLM_CFLAGS and RLM_LIBS and SRCS defs to make TARGET.
+include ../rules.mak
+
+$(LT_OBJS): $(HEADERS)
+
+## the rule that RLM_INSTALL tells the parent rules.mak to use.
+install-rest:
+       touch .
diff --git a/src/modules/rlm_rest/Makefile.clean b/src/modules/rlm_rest/Makefile.clean
new file mode 100644 (file)
index 0000000..0e07944
--- /dev/null
@@ -0,0 +1,11 @@
+TARGET         = rlm_rest
+SRCS           = rlm_rest.c rest.c
+HEADERS                = rest.h
+RLM_CFLAGS     =
+RLM_LIBS       = 
+
+include ../rules.mak
+
+$(STATIC_OBJS): $(HEADERS)
+
+$(DYNAMIC_OBJS): $(HEADERS)
diff --git a/src/modules/rlm_rest/Makefile.in b/src/modules/rlm_rest/Makefile.in
new file mode 100644 (file)
index 0000000..7684e4a
--- /dev/null
@@ -0,0 +1,37 @@
+#######################################################################
+#
+# TARGET should be set by autoconf only.  Don't touch it.
+#
+# The SRCS definition should list ALL source files.
+#
+# The HEADERS definition should list ALL header files
+#
+# RLM_CFLAGS defines addition C compiler flags.  You usually don't
+# want to modify this, though.  Get it from autoconf.
+#
+# The RLM_LIBS definition should list ALL required libraries.
+# These libraries really should be pulled from the 'config.mak'
+# definitions, if at all possible.  These definitions are also
+# echoed into another file in ../lib, where they're picked up by
+# ../main/Makefile for building the version of the server with
+# statically linked modules.  Get it from autoconf.
+#
+# RLM_INSTALL is the names of additional rules you need to install
+# some particular portion of the module.  Usually, leave it blank.
+#
+#######################################################################
+TARGET      = @targetname@
+SRCS        = rlm_rest.c rest.c
+HEADERS     = rest.h
+RLM_CFLAGS  = @rest_cflags@
+RLM_LIBS    = @rest_ldflags@
+RLM_INSTALL = install-rest
+
+## this uses the RLM_CFLAGS and RLM_LIBS and SRCS defs to make TARGET.
+include ../rules.mak
+
+$(LT_OBJS): $(HEADERS)
+
+## the rule that RLM_INSTALL tells the parent rules.mak to use.
+install-rest:
+       touch .
diff --git a/src/modules/rlm_rest/all.mk b/src/modules/rlm_rest/all.mk
new file mode 100644 (file)
index 0000000..a403561
--- /dev/null
@@ -0,0 +1,3 @@
+SOURCES := rlm_rest.c rest.c
+
+TARGET := rlm_rest.a
diff --git a/src/modules/rlm_rest/config.h.in b/src/modules/rlm_rest/config.h.in
new file mode 100644 (file)
index 0000000..7c783cc
--- /dev/null
@@ -0,0 +1,52 @@
+/* config.h.in.  Generated from configure.in by autoheader.  */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `printf' function. */
+#undef HAVE_PRINTF
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
diff --git a/src/modules/rlm_rest/configure.in b/src/modules/rlm_rest/configure.in
new file mode 100644 (file)
index 0000000..e431f98
--- /dev/null
@@ -0,0 +1,177 @@
+AAC_PREREQ([2.61])
+AC_INIT(rlm_rest.c)
+AC_REVISION($Revision$)
+AC_DEFUN(modname,[rlm_rest])
+
+fail=
+SMART_LIBS=
+SMART_CLFAGS=
+if test x$with_[]modname != xno; then
+
+       dnl ############################################################
+       dnl # Check for command line options
+       dnl ############################################################
+
+       dnl extra argument: --with-curl-include-dir=DIR
+       curl_include_dir=
+       AC_ARG_WITH(curl-include-dir,
+       [AS_HELP_STRING([--with-curl-include-dir=DIR],
+               [Directory where the curl includes may be found])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need curl-include-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               curl_include_dir="$withval"
+               ;;
+       esac])
+
+       dnl extra argument: --with-curl-lib-dir=DIR
+       curl_lib_dir=
+       AC_ARG_WITH(curl-lib-dir,
+       [AS_HELP_STRING([--with-curl-lib-dir=DIR],
+               [Directory where the curl libraries may be found])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need curl-lib-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               curl_lib_dir="$withval"
+               ;;
+       esac])
+
+       dnl extra argument: --with-curl-dir=DIR
+       AC_ARG_WITH(curl-dir,
+       [AS_HELP_STRING([--with-curl-dir=DIR],
+               [Base directory where curl is installed])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need curl-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               curl_lib_dir="$withval/lib"
+               curl_include_dir="$withval/include"
+               ;;
+       esac])
+       
+       dnl extra argument: --with-json-include-dir=DIR
+       json_include_dir=
+       AC_ARG_WITH(json-include-dir,
+       [AS_HELP_STRING([--with-json-include-dir=DIR],
+               [Directory where the json includes may be found])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need json-include-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               json_include_dir="$withval"
+               ;;
+       esac])
+
+       dnl extra argument: --with-json-lib-dir=DIR
+       json_lib_dir=
+       AC_ARG_WITH(json-lib-dir,
+       [AS_HELP_STRING([--with-json-lib-dir=DIR],
+               [Directory where the json-c libraries may be found])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need json-lib-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               json_lib_dir="$withval"
+               ;;
+       esac])
+
+       dnl extra argument: --with-json-dir=DIR
+       AC_ARG_WITH(json-dir,
+       [AS_HELP_STRING([--with-json-dir=DIR],
+               [Base directory where json-c is installed])],
+       [case "$withval" in
+               no)
+               AC_MSG_ERROR(Need json-dir)
+               ;;
+               yes)
+               ;;
+               *)
+               json_lib_dir="$withval/lib"
+               json_include_dir="$withval/include"
+               ;;
+       esac])
+
+       dnl ############################################################
+       dnl # Check for programs
+       dnl ############################################################
+
+       AC_PROG_CC
+
+       dnl ############################################################
+       dnl # Check for libraries
+       dnl ############################################################
+
+       smart_try_dir="$curl_lib_dir"
+       FR_SMART_CHECK_LIB(curl, curl_easy_init)
+       if test "x$ac_cv_lib_curl_curl_easy_init" != "xyes"
+       then
+         AC_MSG_WARN([curl libraries not found. Use --with-curl-lib-dir=<path>.])
+         fail="$fail libcurl"
+       fi
+       
+       smart_try_dir="$json_lib_dir"
+       FR_SMART_CHECK_LIB(json, json_tokener_parse)
+       if test "x$ac_cv_lib_json_json_tokener_parse" != "xyes"
+       then
+         AC_MSG_WARN([json-c libraries not found. Use --with-json-lib-dir=<path>.])
+         fail="$fail json"
+       fi
+
+       dnl ############################################################
+       dnl # Check for header files
+       dnl ############################################################
+
+       smart_try_dir="$curl_include_dir"
+       FR_SMART_CHECK_INCLUDE(curl/curl.h)
+       if test "x$ac_cv_header_curl_curl_h" != "xyes"; then
+         AC_MSG_WARN([curl headers not found. Use --with-curl-include-dir=<path>.])
+         fail="$fail curl/curl.h"
+       fi
+       
+       smart_try_dir="$json_include_dir"
+       FR_SMART_CHECK_INCLUDE(json/json.h)
+       if test "x$ac_cv_header_json_json_h" != "xyes"; then
+         AC_MSG_WARN([json-c headers not found. Use --with-json-include-dir=<path>.])
+         fail="$fail json/json.h"
+       fi
+
+       targetname=modname
+else
+       targetname=
+       echo \*\*\* module modname is disabled.
+fi
+
+dnl Don't change this section.
+if test "x$fail" != x; then
+       if test "x${enable_strict_dependencies}" = xyes; then
+               AC_MSG_ERROR([set --without-]modname[ to disable it explicitly.])
+       else
+               AC_MSG_WARN([silently not building ]modname[.])
+               AC_MSG_WARN([FAILURE: ]modname[ requires:$fail.]);
+               targetname=
+       fi
+fi
+
+rest_ldflags="$SMART_LIBS"
+rest_cflags="$SMART_CFLAGS"
+AC_SUBST(rest_ldflags)
+AC_SUBST(rest_cflags)
+AC_SUBST(targetname)
+AC_OUTPUT(Makefile)
diff --git a/src/modules/rlm_rest/demo.pl b/src/modules/rlm_rest/demo.pl
new file mode 100755 (executable)
index 0000000..805f7c3
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use HTTP::Daemon;
+use HTTP::Status;
+use HTTP::Response;
+
+my $d = new HTTP::Daemon(LocalAddr => '127.0.0.1', LocalPort => 9090);
+print "Please contact me at: <URL:", $d->url, ">\n";
+while (my $c = $d->accept) {
+       while (my $r = $c->get_request) {
+               print "Got " . $r->method . " request\n";
+               if ($r->method eq 'POST' and $r->url->path eq "/") {
+                       my $resp = HTTP::Response->new( '200', 'OK' );
+       
+                       #$resp->header("Content-Type" => "application/x-www-form-urlencoded");  
+                       #$resp->content("reply:User-Name=kittens&reply:Service-Type=Framed-User&reply:HP-Cos=0000&reply:HP-Bandwidth-Max-Ingress=999999999999999999999999999999999999&reply:User-Name=mittens");
+               
+                       $resp->header("Content-Type" => "application/json");    
+                       $resp->content('{
+                               "reply|User-Name":"kittens %{User-Name}",
+                       }');
+
+                       $c->send_response($resp);
+               } else {
+                       $c->send_error(RC_FORBIDDEN)
+               }
+       }
+       $c->close;
+       undef($c);
+ }
diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c
new file mode 100644 (file)
index 0000000..7d0b8d8
--- /dev/null
@@ -0,0 +1,2382 @@
+/** Functions and datatypes for the REST (HTTP) transport.
+ *
+ * @file rest.c
+ *
+ * Version:    $Id$
+ *
+ *   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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2011  Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+#include <curl/curl.h>
+#include <json/json.h>
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/libradius.h>
+#include <freeradius-devel/connection.h>
+
+#include "rest.h"
+
+/** Table of encoder/decoder support.
+ *
+ * Indexes in this table match the http_body_type_t enum, and should be
+ * updated if additional enum values are added.
+ *
+ * @see http_body_type_t
+ */
+const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
+       HTTP_BODY_UNSUPPORTED,  // HTTP_BODY_UNKOWN
+       HTTP_BODY_UNSUPPORTED,  // HTTP_BODY_UNSUPPORTED
+       HTTP_BODY_UNSUPPORTED,  // HTTP_BODY_INVALID
+       HTTP_BODY_POST,         // HTTP_BODY_POST
+       HTTP_BODY_JSON,         // HTTP_BODY_JSON
+       HTTP_BODY_UNSUPPORTED,  // HTTP_BODY_XML
+       HTTP_BODY_UNSUPPORTED,  // HTTP_BODY_YAML
+       HTTP_BODY_INVALID,      // HTTP_BODY_HTML
+       HTTP_BODY_INVALID       // HTTP_BODY_PLAIN
+};
+
+/*
+ *     Lib CURL doesn't define symbols for unsupported auth methods
+ */
+#ifndef CURLOPT_TLSAUTH_SRP
+#define CURLOPT_TLSAUTH_SRP    0
+#endif
+#ifndef CURLAUTH_BASIC
+#define CURLAUTH_BASIC                 0
+#endif
+#ifndef CURLAUTH_DIGEST
+#define CURLAUTH_DIGEST        0
+#endif
+#ifndef CURLAUTH_DIGEST_IE
+#define CURLAUTH_DIGEST_IE     0
+#endif
+#ifndef CURLAUTH_GSSNEGOTIATE
+#define CURLAUTH_GSSNEGOTIATE  0
+#endif
+#ifndef CURLAUTH_NTLM
+#define CURLAUTH_NTLM          0
+#endif
+#ifndef CURLAUTH_NTLM_WB
+#define CURLAUTH_NTLM_WB       0
+#endif
+
+const http_body_type_t http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
+       0,                      // HTTP_AUTH_UNKNOWN
+       0,                      // HTTP_AUTH_NONE
+       CURLOPT_TLSAUTH_SRP,    // HTTP_AUTH_TLS_SRP
+       CURLAUTH_BASIC,         // HTTP_AUTH_BASIC
+       CURLAUTH_DIGEST,        // HTTP_AUTH_DIGEST
+       CURLAUTH_DIGEST_IE,     // HTTP_AUTH_DIGEST_IE
+       CURLAUTH_GSSNEGOTIATE,  // HTTP_AUTH_GSSNEGOTIATE
+       CURLAUTH_NTLM,          // HTTP_AUTH_NTLM
+       CURLAUTH_NTLM_WB,       // HTTP_AUTH_NTLM_WB
+       CURLAUTH_ANY,           // HTTP_AUTH_ANY
+       CURLAUTH_ANYSAFE        // HTTP_AUTH_ANY_SAFE
+};
+
+
+/** Conversion table for method config values.
+ * 
+ * HTTP verb strings for http_method_t enum values. Used by libcurl in the
+ * status line of the outgoing HTTP header, by rest_write_header for decoding
+ * incoming HTTP responses, and by the configuration parser.
+ *
+ * @see http_method_t
+ * @see fr_str2int
+ * @see fr_int2str
+ */
+const FR_NAME_NUMBER http_method_table[] = {
+       { "GET",                HTTP_METHOD_GET         },
+       { "POST",               HTTP_METHOD_POST        },
+       { "PUT",                HTTP_METHOD_PUT         },
+       { "DELETE",             HTTP_METHOD_DELETE      },
+
+       {  NULL , -1 }
+};
+
+/** Conversion table for type config values.
+ *
+ * Textual names for http_body_type_t enum values, used by the
+ * configuration parser.
+ *
+ * @see http_body_Type_t
+ * @see fr_str2int
+ * @see fr_int2str
+ */
+const FR_NAME_NUMBER http_body_type_table[] = {
+       { "unknown",            HTTP_BODY_UNKNOWN       },
+       { "unsupported",        HTTP_BODY_UNSUPPORTED   },
+       { "invalid",            HTTP_BODY_INVALID       },
+       { "post",               HTTP_BODY_POST          },
+       { "json",               HTTP_BODY_JSON          },
+       { "xml",                HTTP_BODY_XML           },
+       { "yaml",               HTTP_BODY_YAML          },
+       { "html",               HTTP_BODY_HTML          },
+       { "plain",              HTTP_BODY_PLAIN         },
+
+       {  NULL , -1 }
+};
+
+const FR_NAME_NUMBER http_auth_table[] = {
+       { "none",               HTTP_AUTH_NONE          },
+       { "srp",                HTTP_AUTH_TLS_SRP       },
+       { "basic",              HTTP_AUTH_BASIC         },
+       { "digest",             HTTP_AUTH_DIGEST        },
+       { "digest-ie",          HTTP_AUTH_DIGEST_IE     },
+       { "gss-negotiate",      HTTP_AUTH_GSSNEGOTIATE  },
+       { "ntlm",               HTTP_AUTH_NTLM          },
+       { "ntlm-winbind",       HTTP_AUTH_NTLM_WB       },
+       { "any",                HTTP_AUTH_ANY           },
+       { "safe",               HTTP_AUTH_ANY_SAFE      },
+
+       {  NULL , -1 }
+};
+
+/** Conversion table for "Content-Type" header values.
+ *
+ * Used by rest_write_header for parsing incoming headers.
+ *
+ * Values we expect to see in the 'Content-Type:' header of the incoming
+ * response.
+ *
+ * Some data types (like YAML) do no have standard MIME types defined,
+ * so multiple types, are listed here.
+ *
+ * @see http_body_Type_t
+ * @see fr_str2int
+ * @see fr_int2str
+ */
+const FR_NAME_NUMBER http_content_type_table[] = {
+       { "application/x-www-form-urlencoded", HTTP_BODY_POST },
+       { "application/json",   HTTP_BODY_JSON          },
+       { "text/html",          HTTP_BODY_HTML          },
+       { "text/plain",         HTTP_BODY_PLAIN         },
+       { "text/xml",           HTTP_BODY_XML           },
+       { "text/yaml",          HTTP_BODY_YAML          },
+       { "text/x-yaml",        HTTP_BODY_YAML          },
+       { "application/yaml",   HTTP_BODY_YAML          },
+       { "application/x-yaml", HTTP_BODY_YAML          },
+       {  NULL , -1 }
+};
+
+/** Flags to control the conversion of JSON values to VALUE_PAIRs.
+ *
+ * These fields are set when parsing the expanded format for value pairs in
+ * JSON, and control how json_pairmake_leaf and json_pairmake convert the JSON
+ * value, and move the new VALUE_PAIR into an attribute list.
+ *
+ * @see json_pairmake
+ * @see json_pairmake_leaf
+ */
+typedef struct json_flags {
+       boolean  do_xlat;       //!< If TRUE value will be expanded with xlat.
+       boolean  is_json;       //!< If TRUE value will be inserted as raw JSON
+                               // (multiple values not supported).
+       FR_TOKEN operator;      //!< The operator that determines how the new VP
+                               // is processed. @see fr_tokens
+} json_flags_t;
+
+/** Initialises libcurl.
+ *
+ * Allocates global variables and memory required for libcurl to fundtion.
+ * MUST only be called once per module instance.
+ *
+ * rest_cleanup must not be called if rest_init fails.
+ *
+ * @see rest_cleanup
+ *
+ * @param[in] instance configuration data.
+ * @return TRUE if init succeeded FALSE if it failed.
+ */
+int rest_init(rlm_rest_t *instance)
+{
+       CURLcode ret;
+
+       ret = curl_global_init(CURL_GLOBAL_ALL);
+       if (ret != CURLE_OK) {
+               radlog(L_ERR,
+                      "rlm_rest (%s): CURL init returned error: %i - %s",
+                      instance->xlat_name,
+                      ret, curl_easy_strerror(ret));
+
+               curl_global_cleanup();
+               return FALSE;
+       }
+
+       radlog(L_DBG, "rlm_rest (%s): CURL library version: %s",
+              instance->xlat_name,
+              curl_version());
+
+       return TRUE;
+}
+
+/** Cleans up after libcurl.
+ *
+ * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
+ * Must only be called once per call of rest_init.
+ *
+ * @see rest_init
+ */
+void rest_cleanup(void)
+{
+       curl_global_cleanup();
+}
+
+/** Creates a new connection handle for use by the FR connection API.
+ *
+ * Matches the fr_connection_create_t function prototype, is passed to
+ * fr_connection_pool_init, and called when a new connection is required by the
+ * connection pool API.
+ *
+ * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
+ * which hold the context data required for generating requests and parsing
+ * responses. Calling rest_socket_delete will free this memory.
+ *
+ * If instance->connect_uri is not NULL libcurl will attempt to open a 
+ * TCP socket to the server specified in the URI. This is done so that when the
+ * socket is first used, there will already be a cached TCP connection to the
+ * REST server associated with the curl handle. 
+ *
+ * @see rest_socket_delete
+ * @see fr_connection_pool_init
+ * @see fr_connection_create_t
+ * @see connection.c
+ *
+ * @param[in] instance configuration data.
+ * @return connection handle or NULL if the connection failed or couldn't
+ *     be initialised.
+ */
+void *rest_socket_create(void *instance) 
+{
+       rlm_rest_t *inst = instance;
+
+       rlm_rest_handle_t       *randle;
+       rlm_rest_curl_context_t *ctx;
+
+       CURL *candle = curl_easy_init();
+       CURLcode ret;
+
+       if (!candle) {
+               radlog(L_ERR, "rlm_rest (%s): Failed to create CURL handle", 
+                      inst->xlat_name);
+               return NULL;
+       }
+
+       if (!*inst->connect_uri) {
+               radlog(L_ERR, "rlm_rest (%s): Skipping pre-connect,"
+                      " connect_uri not specified", inst->xlat_name);
+               return candle;
+       }
+
+       /*
+        *      Pre-establish TCP connection to webserver. This would usually be
+        *      done on the first request, but we do it here to minimise
+        *      latency.
+        */
+       ret = curl_easy_setopt(candle, CURLOPT_CONNECT_ONLY, 1);
+       if (ret != CURLE_OK) goto error;
+
+       ret = curl_easy_setopt(candle, CURLOPT_URL,
+                              inst->connect_uri);
+       if (ret != CURLE_OK) goto error;
+
+       radlog(L_DBG, "rlm_rest (%s): Connecting to \"%s\"",
+              inst->xlat_name,
+              inst->connect_uri);
+
+       ret = curl_easy_perform(candle);
+       if (ret != CURLE_OK) {
+               radlog(L_ERR, "rlm_rest (%s): Connection failed: %i - %s",
+                       inst->xlat_name,
+                       ret, curl_easy_strerror(ret));
+
+               goto connection_error;
+       }
+
+       /* 
+        *      Malloc memory for the connection handle abstraction.
+        */
+       randle = malloc(sizeof(*randle));
+       memset(randle, 0, sizeof(*randle));
+
+       ctx = malloc(sizeof(*ctx));
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->headers = NULL; /* CURL needs this to be NULL */
+       ctx->read.instance = inst;
+
+       randle->ctx = ctx;
+       randle->handle = candle;
+
+       /*
+        *      Clear any previously configured options for the first request.
+        */
+       curl_easy_reset(candle);
+
+       return randle;
+
+       /*
+        *      Cleanup for error conditions.
+        */
+       error:
+
+       radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
+                       inst->xlat_name,
+                       ret, curl_easy_strerror(ret));
+
+       /* 
+        *      So we don't leak CURL handles.
+        */
+       connection_error:
+
+       curl_easy_cleanup(candle);
+
+       return NULL;
+}
+
+/** Verifies that the last TCP socket associated with a handle is still active.
+ *
+ * Quieries libcurl to try and determine if the TCP socket associated with a
+ * connection handle is still viable.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] handle to check.
+ * @returns FALSE if the last socket is dead, or if the socket state couldn't be
+ *     determined, else TRUE.
+ */
+int rest_socket_alive(void *instance, void *handle)
+{
+       rlm_rest_t *inst                = instance;
+       rlm_rest_handle_t *randle       = handle;
+       CURL *candle                    = randle->handle;
+
+       long last_socket;
+       CURLcode ret;
+
+       curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
+       if (ret != CURLE_OK) {
+               radlog(L_ERR,
+                      "rlm_rest (%s): Couldn't determine socket"
+                      " state: %i - %s", inst->xlat_name, ret,
+                      curl_easy_strerror(ret));
+
+               return FALSE;
+       }
+
+       if (last_socket == -1) {
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/** Frees a libcurl handle, and any additional memory used by context data.
+ * 
+ * @param[in] instance configuration data.
+ * @param[in] handle rlm_rest_handle_t to close and free.
+ * @return returns TRUE.
+ */
+int rest_socket_delete(UNUSED void *instance, void *handle)
+{   
+       rlm_rest_handle_t *randle       = handle;
+       CURL *candle                    = randle->handle;
+
+       curl_easy_cleanup(candle);
+
+       free(randle->ctx);
+       free(randle);
+
+       return TRUE;
+}
+
+/** Encodes VALUE_PAIR linked list in POST format
+ *
+ * This is a stream function matching the rest_read_t prototype. Multiple
+ * successive calls will return additional encoded VALUE_PAIRs. 
+ * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
+ * will be written to the ptr buffer.
+ *
+ * POST request format is:
+ * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
+ *
+ * All attributes and values are url encoded. There is currently no support for
+ * nested attributes, or attribute qualifiers.
+ *
+ * Nested attributes may be added in the future using
+ * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
+ * to denotate nesting.
+ *
+ * Requires libcurl for url encoding.
+ *
+ * @see rest_decode_post
+ *
+ * @param[out] ptr Char buffer to write encoded data to.
+ * @param[in] size Multiply by nmemb to get the length of ptr.
+ * @param[in] nmemb Multiply by size to get the length of ptr.
+ * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
+ * @return length of data (including NULL) written to ptr, or 0 if no more
+ *     data to write.
+ */
+static size_t rest_encode_post(void *ptr, size_t size, size_t nmemb,
+                              void *userdata)
+{
+       rlm_rest_read_t *ctx    = userdata;
+       REQUEST *request        = ctx->request; /* Used by RDEBUG */
+       VALUE_PAIR **current    = ctx->next;
+
+       char *p = ptr;  /* Position in buffer */
+       char *f = ptr;  /* Position in buffer of last fully encoded attribute or value */
+       char *escaped;  /* Pointer to current URL escaped data */
+
+       ssize_t len = 0;
+       ssize_t s = (size * nmemb) - 1;
+
+       /* Allow manual chunking */
+       if ((ctx->chunk) && (ctx->chunk <= s)) {
+               s = (ctx->chunk - 1);
+       }
+
+       if (ctx->state == READ_STATE_END) return FALSE;
+
+       /* Post data requires no headers */
+       if (ctx->state == READ_STATE_INIT) {
+               ctx->state = READ_STATE_ATTR_BEGIN;
+       }
+
+       while (s > 0) {
+               if (!*current) {
+                       ctx->state = READ_STATE_END;
+
+                       goto end_chunk;
+               }
+
+               RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
+
+               if (ctx->state == READ_STATE_ATTR_BEGIN) {
+                       escaped = curl_escape(current[0]->name,
+                                             strlen(current[0]->name));
+                       len = strlen(escaped);
+
+                       if (s < (1 + len)) {
+                               curl_free(escaped);
+                               goto no_space;
+                       }
+
+                       len = sprintf(p, "%s=", escaped);
+
+                       curl_free(escaped);
+
+                       p += len;
+                       s -= len;
+
+                       /* 
+                        *      We wrote the attribute header, record progress.
+                        */
+                       f = p;
+                       ctx->state = READ_STATE_ATTR_CONT;
+               }
+
+               /*
+                *      Write out single attribute string.
+                */
+               len = vp_prints_value(p , s, current[0], 0);
+               escaped = curl_escape(p, len);
+               len = strlen(escaped);
+
+               if (s < len) {
+                       curl_free(escaped);
+                       goto no_space;
+               }
+
+               len = strlcpy(p, escaped, len + 1);
+
+               curl_free(escaped);
+
+               RDEBUG("\tLength : %i", len);
+               RDEBUG("\tValue  : %s", p);
+
+               p += len;
+               s -= len;
+
+               if (*++current) {
+                       if (!--s) goto no_space;
+                       *p++ = '&';
+               }
+
+               /* 
+                *      We wrote one full attribute value pair, record progress.
+                */
+               f = p;
+               ctx->next = current;
+               ctx->state = READ_STATE_ATTR_BEGIN;
+       }
+
+       end_chunk:
+
+       *p = '\0';
+
+       len = p - (char*)ptr;
+
+       RDEBUG2("POST Data: %s", (char*) ptr);
+       RDEBUG2("Returning %i bytes of POST data", len);
+
+       return len;
+
+       /*
+        *      Cleanup for error conditions
+        */ 
+       no_space:
+
+       *f = '\0';
+
+       len = f - (char*)ptr;
+
+       RDEBUG2("POST Data: %s", (char*) ptr);
+
+       /*
+        *      The buffer wasn't big enough to encode a single attribute chunk.
+        */
+       if (!len) {
+               radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length" 
+                      " or chunk", ctx->instance->xlat_name);
+       } else {
+               RDEBUG2("Returning %i bytes of POST data"
+                       " (buffer full or chunk exceeded)", len);
+       }
+
+       return len;
+}
+
+/** Encodes VALUE_PAIR linked list in JSON format
+ *
+ * This is a stream function matching the rest_read_t prototype. Multiple
+ * successive calls will return additional encoded VALUE_PAIRs.
+ *
+ * Only complete attribute headers
+ * @verbatim "<name>":{"type":"<type>","value":['</pre> @endverbatim
+ * and complete attribute values will be written to ptr.
+ *
+ * If an attribute occurs multiple times in the request the attribute values
+ * will be concatenated into a single value array.
+ *
+ * JSON request format is:
+@verbatim
+{
+       "<attribute0>":{
+               "type":"<type0>",
+               "value":[<value0>,<value1>,<valueN>]
+       },
+       "<attribute1>":{
+               "type":"<type1>",
+               "value":[...]
+       },
+       "<attributeN>":{
+               "type":"<typeN>",
+               "value":[...]
+       },
+}
+@endverbatim
+ *
+ * @param[out] ptr Char buffer to write encoded data to.
+ * @param[in] size Multiply by nmemb to get the length of ptr.
+ * @param[in] nmemb Multiply by size to get the length of ptr.
+ * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
+ * @return length of data (including NULL) written to ptr, or 0 if no more
+ *     data to write.
+ */
+static size_t rest_encode_json(void *ptr, size_t size, size_t nmemb,
+                              void *userdata)
+{
+       rlm_rest_read_t *ctx    = userdata;
+       REQUEST *request        = ctx->request; /* Used by RDEBUG */
+       VALUE_PAIR **current    = ctx->next;
+
+       char *p = ptr;  /* Position in buffer */
+       char *f = ptr;  /* Position in buffer of last fully encoded attribute or value */
+
+       const char *type;
+
+       ssize_t len = 0;
+       ssize_t s = (size * nmemb) - 1;
+
+       assert(s > 0);
+
+       /* Allow manual chunking */
+       if ((ctx->chunk) && (ctx->chunk <= s)) {
+               s = (ctx->chunk - 1);
+       }
+       
+       if (ctx->state == READ_STATE_END) return FALSE;
+
+       if (ctx->state == READ_STATE_INIT) {
+               ctx->state = READ_STATE_ATTR_BEGIN;
+
+               if (!--s) goto no_space;
+               *p++ = '{';
+       }
+
+       while (s > 0) {
+               if (!*current) {
+                       ctx->state = READ_STATE_END;
+
+                       if (!--s) goto no_space;
+                       *p++ = '}';
+
+                       goto end_chunk;
+               }
+
+               /*
+                *      New attribute, write name, type, and beginning of
+                *      value array.
+                */
+               RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
+               if (ctx->state == READ_STATE_ATTR_BEGIN) {
+                       type = fr_int2str(dict_attr_types, current[0]->type,
+                                         "¿Unknown?");
+
+                       len  = strlen(type);
+                       len += strlen(current[0]->name);
+
+                       if (s < (23 + len)) goto no_space;
+
+                       len = sprintf(p, "\"%s\":{\"type\":\"%s\",\"value\":[" ,
+                                     current[0]->name, type);
+                       p += len;
+                       s -= len;
+
+                       RDEBUG2("\tType   : %s", type);
+
+                       /* 
+                        *      We wrote the attribute header, record progress
+                        */
+                       f = p;
+                       ctx->state = READ_STATE_ATTR_CONT;
+               }
+
+               /*
+                *      Put all attribute values in an array for easier remote
+                *      parsing whether they're multivalued or not.
+                */
+               while (TRUE) {
+                       len = vp_prints_value_json(p , s, current[0]);
+                       assert((s - len) >= 0);
+
+                       if (len < 0) goto no_space;
+
+                       /*
+                        *      Show actual value length minus quotes
+                        */
+                       RDEBUG2("\tLength : %i", (*p == '"') ? (len - 2) : len);
+                       RDEBUG2("\tValue  : %s", p);
+
+                       p += len;
+                       s -= len;
+
+                       /* 
+                        *      Multivalued attribute
+                        */
+                       if (current[1] && 
+                           ((current[0]->attribute == current[1]->attribute) &&
+                            (current[0]->vendor == current[1]->vendor))) {
+                               *p++ = ',';
+                               current++;
+
+                               /* 
+                                *      We wrote one attribute value, record
+                                *      progress.
+                                */
+                               f = p;
+                               ctx->next = current;
+                       } else {
+                               break;
+                       }
+               }
+
+               if (!(s -= 2)) goto no_space;
+               *p++ = ']';
+               *p++ = '}';
+
+               if (*++current) {
+                       if (!--s) goto no_space;
+                       *p++ = ',';
+               }
+
+               /* 
+                *      We wrote one full attribute value pair, record progress.
+                */
+               f = p;
+               ctx->next = current;
+               ctx->state = READ_STATE_ATTR_BEGIN;
+       }
+
+       end_chunk:
+
+       *p = '\0';
+
+       len = p - (char*)ptr;
+
+       RDEBUG2("JSON Data: %s", (char*) ptr);
+       RDEBUG2("Returning %i bytes of JSON data", len);
+
+       return len;
+
+       /* 
+        * Were out of buffer space
+        */ 
+       no_space:
+
+       *f = '\0';
+
+       len = f - (char*)ptr;
+
+       RDEBUG2("JSON Data: %s", (char*) ptr);
+
+       /*
+        *      The buffer wasn't big enough to encode a single attribute chunk.
+        */
+       if (!len) {
+               radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
+                      " or chunk", ctx->instance->xlat_name);
+       } else {
+               RDEBUG2("Returning %i bytes of JSON data"
+                       " (buffer full or chunk exceeded)", len);
+       }
+
+       return len;
+}
+
+/** Emulates successive libcurl calls to an encoding function
+ *
+ * This function is used when the request will be sent to the HTTP server as one
+ * contiguous entity. A buffer of REST_BODY_INCR bytes is allocated and passed
+ * to the stream encoding function.
+ * 
+ * If the stream function does not return 0, a new buffer is allocated which is
+ * the size of the previous buffer + REST_BODY_INCR bytes, the data from the
+ * previous buffer is copied, and freed, and another call is made to the stream
+ * function, passing a pointer into the new buffer at the end of the previously
+ * written data.
+ * 
+ * This process continues until the stream function signals (by returning 0)
+ * that it has no more data to write.
+ *
+ * @param[out] buffer where the pointer to the malloced buffer should
+ *     be written.
+ * @param[in] func Stream function.
+ * @param[in] limit Maximum buffer size to alloc.
+ * @param[in] userdata rlm_rest_read_t to keep encoding state between calls to
+ *     stream function.
+ * @return the length of the data written to the buffer (excluding NULL) or -1
+ *     if alloc >= limit.
+ */
+static ssize_t rest_read_wrapper(char **buffer, rest_read_t func,
+                                size_t limit, void *userdata)
+{
+       char *previous = NULL;
+       char *current;
+
+       size_t alloc = REST_BODY_INCR;  /* Size of buffer to malloc */
+       size_t used  = 0;               /* Size of data written */
+       size_t len   = 0;
+
+       while (alloc < limit) {
+               current = rad_malloc(alloc);
+
+               if (previous) {
+                       strlcpy(current, previous, used + 1);
+                       free(previous);
+               }
+
+               len = func(current + used, REST_BODY_INCR, 1, userdata);
+               used += len;
+               if (!len) {
+                       *buffer = current;
+                       return used;
+               }
+
+               alloc += REST_BODY_INCR;
+               previous = current;
+       };
+
+       free(current);
+
+       return -1;
+}
+
+/** (Re-)Initialises the data in a rlm_rest_read_t.
+ *
+ * Resets the values of a rlm_rest_read_t to their defaults.
+ * 
+ * Must be called between encoding sessions.
+ *
+ * As part of initialisation all VALUE_PAIR pointers in the REQUEST packet are
+ * written to an array.
+ *
+ * If sort is TRUE, this array of VALUE_PAIR pointers will be sorted by vendor
+ * and then by attribute. This is for stream encoders which may concatenate
+ * multiple attribute values together into an array.
+ *
+ * After the encoding session has completed this array must be freed by calling
+ * rest_read_ctx_free .
+ *
+ * @see rest_read_ctx_free
+ *
+ * @param[in] request Current request.
+ * @param[in] read to initialise.
+ * @param[in] sort If TRUE VALUE_PAIRs will be sorted within the VALUE_PAIR
+ *     pointer array.
+ */
+static void rest_read_ctx_init(REQUEST *request,
+                              rlm_rest_read_t *ctx,
+                              boolean sort)
+{
+       unsigned short count = 0, i;
+       unsigned short swap;
+
+       VALUE_PAIR **current, *tmp;
+
+       /*
+        * Setup stream read data
+        */
+       ctx->request = request;
+       ctx->state   = READ_STATE_INIT;
+
+       /*
+        * Create sorted array of VP pointers
+        */
+       tmp = request->packet->vps;
+       while (tmp != NULL) {
+               tmp = tmp->next;
+               count++;
+       }
+
+       ctx->first = current = rad_malloc((sizeof(tmp) * (count + 1)));
+       ctx->next = ctx->first;
+
+       tmp = request->packet->vps;
+       while (tmp != NULL) {
+               *current++ = tmp;
+               tmp = tmp->next;
+       }
+       current[0] = NULL;
+       current = ctx->first;
+
+       if (!sort || (count < 2)) return;
+
+       /* TODO: Quicksort would be faster... */
+       do {
+               for(i = 1; i < count; i++) {
+                       assert(current[i-1]->attribute &&
+                              current[i]->attribute);
+
+                       swap = 0;
+                       if ((current[i-1]->vendor > current[i]->vendor) ||
+                           ((current[i-1]->vendor == current[i]->vendor) &&
+                            (current[i-1]->attribute > current[i]->attribute)
+                           )) {
+                               tmp          = current[i];
+                               current[i]   = current[i-1];
+                               current[i-1] = tmp;
+                               swap = 1;
+                       }
+               }
+       } while (swap);
+}
+
+/** Frees the VALUE_PAIR array created by rest_read_ctx_init.
+ *
+ * Must be called between encoding sessions else module will leak VALUE_PAIR
+ * pointers.
+ *
+ * @see rest_read_ctx_init
+ *
+ * @param[in] read to free.
+ */
+static void rest_read_ctx_free(rlm_rest_read_t *ctx)
+{
+       if (ctx->first != NULL) {
+               free(ctx->first);
+       }
+}
+
+/** Verify that value wasn't truncated when it was converted to a VALUE_PAIR
+ *
+ * Certain values may be truncated when they're converted into VALUE_PAIRs
+ * for example 64bit integers converted to 32bit integers. Warn the user
+ * when this happens.
+ * 
+ * @param[in] raw string from decoder.
+ * @param[in] vp containing parsed value.
+ */
+static void rest_check_truncation(REQUEST *request, const char *raw,
+                                 VALUE_PAIR *vp)
+{
+       char cooked[1024];
+
+       vp_prints_value(cooked, sizeof(cooked), vp, 0);
+       if (strcmp(raw, cooked) != 0) {
+               RDEBUG("WARNING: Value-Pair does not match POST value, "
+                      "truncation may have occurred");
+               RDEBUG("\tValue (pair) : \"%s\"", cooked);
+               RDEBUG("\tValue (post) : \"%s\"", raw);
+       }
+}
+
+/** Converts POST response into VALUE_PAIRs and adds them to the request
+ *
+ * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
+ * addition of optional attribute list qualifiers as part of the attribute name
+ * string.
+ * 
+ * If no qualifiers are specified, will default to the request list.
+ *
+ * POST response format is:
+ * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
+ *
+ * @see rest_encode_post
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] handle rlm_rest_handle_t to use.
+ * @param[in] request Current request.
+ * @param[in] raw buffer containing POST data.
+ * @param[in] rawlen Length of data in raw buffer.
+ * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
+ */
+static int rest_decode_post(rlm_rest_t *instance,
+                           UNUSED rlm_rest_section_t *section,
+                           REQUEST *request, void *handle, char *raw,
+                           UNUSED size_t rawlen)
+{
+       rlm_rest_handle_t *randle = handle;
+       CURL *candle              = randle->handle;
+
+       const char *p = raw, *q;
+
+       const char *attribute;
+       char *name  = NULL;
+       char *value = NULL;
+
+       const DICT_ATTR *da;
+       VALUE_PAIR *vp;
+
+       const DICT_ATTR **current, *processed[REST_BODY_MAX_ATTRS + 1];
+       VALUE_PAIR *tmp;
+
+       pair_lists_t list;
+       REQUEST *reference = request;
+       VALUE_PAIR **vps;
+
+       size_t len;
+       int curl_len; /* Length from last curl_easy_unescape call */
+
+       int count = 0;
+
+       processed[0] = NULL;
+
+       /*
+        * Empty response?
+        */
+       while (isspace(*p)) p++;
+
+       if (p == NULL) return FALSE;
+
+       while (((q = strchr(p, '=')) != NULL) &&
+              (count < REST_BODY_MAX_ATTRS)) {
+               attribute = name;
+               reference = request;
+
+               name = curl_easy_unescape(candle, p, (q - p), &curl_len);
+               p = (q + 1);
+
+               RDEBUG("Decoding attribute \"%s\"", name);
+
+               if (!radius_ref_request(&reference, &attribute)) {
+                       RDEBUG("WARNING: Attribute name refers to outer request"
+                              " but not in a tunnel, skipping");
+
+                       curl_free(name);
+
+                       continue;
+               }
+
+               list = radius_list_name(&attribute, PAIR_LIST_REPLY);
+               if (list == PAIR_LIST_UNKNOWN) {
+                       RDEBUG("WARNING: Invalid list qualifier, skipping");
+
+                       curl_free(name);
+
+                       continue;
+               }
+
+               da = dict_attrbyname(attribute);
+               if (!da) {
+                       RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
+                              attribute);
+
+                       curl_free(name);
+
+                       continue;
+               }
+
+               vps = radius_list(reference, list);
+
+               assert(vps);
+
+               RDEBUG2("\tType  : %s", fr_int2str(dict_attr_types, da->type,
+                       "¿Unknown?"));
+
+               q = strchr(p, '&');
+               len = (q == NULL) ? (rawlen - (p - raw)) : (unsigned)(q - p);
+
+               value = curl_easy_unescape(candle, p, len, &curl_len);
+
+               /* 
+                *      If we found a delimiter we want to skip over it,
+                *      if we didn't we do *NOT* want to skip over the end
+                *      of the buffer...
+                */
+               p += (q == NULL) ? len : (len + 1);
+
+               RDEBUG2("\tLength : %i", curl_len);
+               RDEBUG2("\tValue  : \"%s\"", value);
+
+               vp = paircreate(da->attr, da->vendor, da->type);
+               if (!vp) {
+                       radlog(L_ERR, "rlm_rest (%s): Failed creating"
+                              " value-pair", instance->xlat_name);
+
+                       goto error;
+               }
+
+               vp->operator = T_OP_SET;
+               /*
+                *      Check to see if we've already processed an
+                *      attribute of the same type if we have, change the op
+                *      from T_OP_ADD to T_OP_SET.
+                */
+               current = processed;
+               while (*current++) {
+                       if ((current[0]->attr == da->attr) &&
+                           (current[0]->vendor == da->vendor)) {
+                               vp->operator = T_OP_ADD;
+                               break;
+                       }
+               }
+               
+               if (vp->operator != T_OP_ADD) {
+                       current[0] = da;
+                       current[1] = NULL;
+               }
+
+               tmp = pairparsevalue(vp, value);
+               if (tmp == NULL) {
+                       RDEBUG("Incompatible value assignment, skipping");
+                       pairbasicfree(vp);
+                       goto skip;
+               }
+               vp = tmp;
+
+               rest_check_truncation(request, value, vp);
+
+               vp->flags.do_xlat = 1;
+
+               RDEBUG("Performing xlat expansion of response value", value);
+               pairxlatmove(request, vps, &vp);
+
+               if (++count == REST_BODY_MAX_ATTRS) {
+                       radlog(L_ERR, "rlm_rest (%s): At maximum"
+                              " attribute limit", instance->xlat_name);
+                       return count;
+               }
+
+               skip:
+
+               curl_free(name);
+               curl_free(value);
+
+               continue;
+
+               error:
+
+               curl_free(name);
+               curl_free(value);
+
+               return count;
+       }
+
+       if (!count) {
+               radlog(L_ERR, "rlm_rest (%s): Malformed POST data \"%s\"",
+                      instance->xlat_name, raw);
+       }
+
+       return count;
+
+}
+
+/** Converts JSON "value" key into VALUE_PAIR.
+ *
+ * If leaf is not in fact a leaf node, but contains JSON data, the data will
+ * written to the attribute in JSON string format.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] request Current request.
+ * @param[in] attribute name without qualifiers.
+ * @param[in] flags containing the operator other flags controlling value
+ *     expansion.
+ * @param[in] leaf object containing the VALUE_PAIR value.
+ * @return The VALUE_PAIR just created, or NULL on error.
+ */
+static VALUE_PAIR *json_pairmake_leaf(rlm_rest_t *instance,
+                                     UNUSED rlm_rest_section_t *section,
+                                     REQUEST *request, const DICT_ATTR *da,
+                                     json_flags_t *flags, json_object *leaf)
+{
+       const char *value;
+       VALUE_PAIR *vp, *tmp;
+
+       /*
+        *      Should encode any nested JSON structures into JSON strings.
+        *
+        *      "I knew you liked JSON so I put JSON in your JSON!"
+        */
+       value = json_object_get_string(leaf);
+
+       RDEBUG2("\tType   : %s", fr_int2str(dict_attr_types, da->type,
+                                           "¿Unknown?"));
+       RDEBUG2("\tLength : %i", strlen(value));
+       RDEBUG2("\tValue  : \"%s\"", value);
+
+       vp = paircreate(da->attr, da->vendor, da->type);
+       if (!vp) {
+               radlog(L_ERR, "rlm_rest (%s): Failed creating value-pair",
+                      instance->xlat_name);
+               return NULL;
+       }
+
+       vp->operator = flags->operator;
+
+       tmp = pairparsevalue(vp, value);
+       if (tmp == NULL) {
+               RDEBUG("Incompatible value assignment, skipping");
+               pairbasicfree(vp);
+               return NULL;
+       }
+       vp = tmp;
+
+       rest_check_truncation(request, value, vp);
+
+       if (flags->do_xlat) vp->flags.do_xlat = 1;
+
+       return vp;
+}
+
+/** Processes JSON response and converts it into multiple VALUE_PAIRs
+ * 
+ * Processes JSON attribute declarations in the format below. Will recurse when
+ * processing nested attributes. When processing nested attributes flags and
+ * operators from previous attributes are not inherited.
+ *
+ * JSON response format is:
+@verbatim
+{
+       "<attribute0>":{
+               do_xlat:<bool>,
+               is_json:<bool>,
+               "op":"<operator>",
+               "value":[<value0>,<value1>,<valueN>]
+       },
+       "<attribute1>":{
+               "value":{
+                       "<nested-attribute0>":{
+                               "op":"<operator>",
+                               "value":<value0>
+                       }
+               }
+       },
+       "<attribute2>":"<value0>",
+       "<attributeN>":"[<value0>,<value1>,<valueN>]"
+}
+@endverbatim
+ * 
+ * JSON valuepair flags (bools):
+ *  - do_xlat  (optional) Controls xlat expansion of values. Defaults to TRUE.
+ *  - is_json  (optional) If TRUE, any nested JSON data will be copied to the
+ *                        VALUE_PAIR in string form. Defaults to TRUE.
+ *  - op       (optional) Controls how the attribute is inserted into
+ *                        the target list. Defaults to ':=' (T_OP_SET).
+ *
+ * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
+ * second and subsequent values in multivalued attributes. This does not work
+ * between multiple attribute declarations.
+ *
+ * @see fr_tokens
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] request Current request.
+ * @param[in] object containing root node, or parent node.
+ * @param[in] level Current nesting level.
+ * @param[in] max_attrs counter, decremented after each VALUE_PAIR is created,
+ * when 0 no more attributes will be processed.
+ * @return VALUE_PAIR or NULL on error.
+ */
+static VALUE_PAIR *json_pairmake(rlm_rest_t *instance,
+                                UNUSED rlm_rest_section_t *section,
+                                REQUEST *request, json_object *object,
+                                int level, int *max_attrs)
+{
+       const char *p;
+       char *q;
+       
+       const char *name, *attribute;
+
+       struct json_object *value, *idx, *tmp;
+       struct lh_entry *entry;
+       json_flags_t flags;
+
+       const DICT_ATTR *da;
+       VALUE_PAIR *vp;
+       
+       pair_lists_t list;
+       REQUEST *reference = request;
+       VALUE_PAIR **vps;
+
+       int i, len;
+
+       if (!json_object_is_type(object, json_type_object)) {
+               RDEBUG("Can't process VP container, expected JSON object,"
+                      " got \"%s\", skipping",
+                      json_object_get_type(object));
+               return NULL;
+       }
+   
+       /*
+        *      Process VP container
+        */
+       entry = json_object_get_object(object)->head;
+       while (entry) {
+               flags.operator = T_OP_SET;
+               flags.do_xlat  = 1;
+               flags.is_json  = 0;
+
+               name = (char*)entry->k;
+
+               /* Fix the compiler warnings regarding const... */
+               memcpy(&value, &entry->v, sizeof(value)); 
+
+               entry = entry->next;
+   
+               /*
+                *      For people handcrafting JSON responses
+                */
+               p = name;
+               while ((p = q = strchr(p, '|'))) {
+                       *q = ':';
+                       p++;
+               }
+
+               attribute = name;
+               reference = request;
+        
+               /*
+                *      Resolve attribute name to a dictionary entry and
+                *      pairlist.
+                */
+               RDEBUG2("Decoding attribute \"%s\"", name);
+
+               if (!radius_ref_request(&reference, &attribute)) {
+                       RDEBUG("WARNING: Attribute name refers to outer request"
+                              " but not in a tunnel, skipping");
+
+                       continue;
+               }
+
+               list = radius_list_name(&attribute, PAIR_LIST_REPLY);
+               if (list == PAIR_LIST_UNKNOWN) {
+                       RDEBUG("WARNING: Invalid list qualifier, skipping");
+
+                       continue;
+               }
+
+               da = dict_attrbyname(attribute);
+               if (!da) {
+                       RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
+                              attribute);
+
+                       continue;
+               }
+
+               vps = radius_list(reference, list);
+
+               assert(vps);
+
+               /*
+                *      Alternate JSON structure that allows operator,
+                *      and other flags to be specified.
+                *
+                *      "<name>":{
+                *              "do_xlat":<bool>,
+                *              "is_json":<bool>,
+                *              "op":"<op>",
+                *              "value":<value>
+                *      }
+                *
+                *      Where value is a:
+                *        - []  Multivalued array
+                *        - {}  Nested Valuepair
+                *        - *   Integer or string value
+                */
+               if (json_object_is_type(value, json_type_object)) {
+                       /*
+                        *      Process operator if present.
+                        */
+                       tmp = json_object_object_get(value, "op");
+                       if (tmp) {
+                               flags.operator = fr_str2int(fr_tokens,
+                                                           json_object_get_string(tmp), 0);
+
+                               if (!flags.operator) {
+                                       RDEBUG("Invalid operator value \"%s\","
+                                              " skipping", tmp);
+                                       continue;
+                               }
+                       }
+
+                       /*
+                        *      Process optional do_xlat bool.
+                        */
+                       tmp = json_object_object_get(value, "do_xlat");
+                       if (tmp) {
+                               flags.do_xlat = json_object_get_boolean(tmp);
+                       }
+
+                       /*
+                        *      Process optional is_json bool.
+                        */
+                       tmp = json_object_object_get(value, "is_json");
+                       if (tmp) {
+                               flags.is_json = json_object_get_boolean(tmp);
+                       }
+
+                       /*
+                        *      Value key must be present if were using
+                        *      the expanded syntax.
+                        */
+                       value = json_object_object_get(value, "value");
+                       if (!value) {
+                               RDEBUG("Value key missing, skipping", value);
+                               continue;
+                       }
+               }
+
+       /*
+        *      Setup pairmake / recursion loop.
+        */
+       if (!flags.is_json &&
+           json_object_is_type(value, json_type_array)) {
+               len = json_object_array_length(value);
+               if (!len) {
+                       RDEBUG("Zero length value array, skipping", value);
+                       continue;
+               }
+               idx = json_object_array_get_idx(value, 0);
+       } else {
+               len = 1;
+               idx = value;
+       }
+
+       i = 0;
+       do {
+               if (!(*max_attrs)--) {
+                               radlog(L_ERR, "rlm_rest (%s): At maximum"
+                                      " attribute limit", instance->xlat_name);
+                               return NULL;
+               }
+
+               /*
+                *      Automagically switch the op for multivalued
+                *      attributes.
+                */
+               if (((flags.operator == T_OP_SET) ||
+                    (flags.operator == T_OP_EQ)) && (len > 1)) {
+                       flags.operator = T_OP_ADD;
+               }
+
+               if (!flags.is_json &&
+                   json_object_is_type(value, json_type_object)) {
+                       /* TODO: Insert nested VP into VP structure...*/
+                       RDEBUG("Found nested VP", value);
+                       vp = json_pairmake(instance, section,
+                                          request, value,
+                                          level + 1, max_attrs);
+               } else {
+                       vp = json_pairmake_leaf(instance, section,
+                                               request, da, &flags,
+                                               idx);
+
+                       if (vp != NULL) {
+                               if (vp->flags.do_xlat) {
+                                       RDEBUG("Performing xlat"
+                                              " expansion of response"
+                                              " value", value);
+                               }
+
+                               pairxlatmove(request, vps, &vp);
+                       }
+               }
+       } while ((++i < len) && (idx = json_object_array_get_idx(value, i)));
+   }
+
+   return vp;
+}
+
+/** Converts JSON response into VALUE_PAIRs and adds them to the request.
+ * 
+ * Converts the raw JSON string into a json-c object tree and passes it to
+ * json_pairmake. After the tree has been parsed json_object_put is called
+ * which decrements the reference, count to the root node by one, and frees
+ * the entire tree.
+ *
+ * @see rest_encode_json
+ * @see json_pairmake
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] g to use.
+ * @param[in] request Current request.
+ * @param[in] raw buffer containing JSON data.
+ * @param[in] rawlen Length of data in raw buffer.
+ * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
+ */
+static int rest_decode_json(rlm_rest_t *instance,
+                           UNUSED rlm_rest_section_t *section,
+                           UNUSED REQUEST *request, UNUSED void *handle,
+                           char *raw, UNUSED size_t rawlen)
+{
+       const char *p = raw;
+       
+       struct json_object *json;
+       
+       int max = REST_BODY_MAX_ATTRS;
+
+       /*
+        *      Empty response?
+        */
+       while (isspace(*p)) p++;
+       if (p == NULL) return FALSE;
+
+       json = json_tokener_parse(p);
+       if (!json) {
+               radlog(L_ERR, "rlm_rest (%s): Malformed JSON data \"%s\"",
+                       instance->xlat_name, raw);
+               return -1;
+       }
+
+       json_pairmake(instance, section, request, json, 0, &max);
+
+       /*
+        *      Decrement reference count for root object, should free entire
+        *      JSON tree.
+        */
+       json_object_put(json);
+
+       return (REST_BODY_MAX_ATTRS - max);
+}
+
+/** Processes incoming HTTP header data from libcurl.
+ *
+ * Processes the status line, and Content-Type headers from the incoming HTTP
+ * response.
+ *
+ * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
+ * by libcurl.
+ *
+ * @param[in] ptr Char buffer where inbound header data is written.
+ * @param[in] size Multiply by nmemb to get the length of ptr.
+ * @param[in] nmemb Multiply by size to get the length of ptr.
+ * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
+ * @return Length of data processed, or 0 on error.
+ */
+static size_t rest_write_header(void *ptr, size_t size, size_t nmemb,
+                               void *userdata)
+{
+       rlm_rest_write_t *ctx  = userdata;
+       REQUEST *request       = ctx->request; /* Used by RDEBUG */
+       
+       const char *p = ptr, *q;
+       char *tmp;
+
+       const size_t t = (size * nmemb);
+       size_t s = t;
+       size_t len;
+
+       http_body_type_t type;
+       http_body_type_t supp;
+
+       switch (ctx->state)
+       {
+               case WRITE_STATE_INIT:
+                       RDEBUG("Processing header");
+
+                       /* 
+                        * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
+                        *
+                        * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
+                        */
+                       if (s < 14) goto malformed;
+
+                       /* 
+                        * Check start of header matches...
+                        */
+                       if (strncasecmp("HTTP/", p, 5) != 0) goto malformed;
+
+                       p += 5;
+                       s -= 5;
+
+                       /*
+                        * Skip the version field, next space should mark start
+                        * of reason_code.
+                        */
+                       q = memchr(p, ' ', s);
+                       if (q == NULL) goto malformed;
+
+                       s -= (q - p);
+                       p  = q;
+
+                       /* 
+                        * Process reason_code.
+                        *
+                        * " 100" (4) + "\r\n" (2) = 6
+                        */
+                       if (s < 6) goto malformed;
+                       p++;
+                       s--;
+
+                       /* Char after reason code must be a space, or \r */
+                       if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
+
+                       ctx->code = atoi(p);
+
+                       /*
+                        * Process reason_phrase (if present).
+                        */
+                       if (p[3] == ' ') {
+                               p += 4;
+                               s -= 4;
+
+                               q = memchr(p, '\r', s);
+                               if (q == NULL) goto malformed;
+
+                               len = (q - p);
+
+                               tmp = rad_malloc(len + 1);
+                               strlcpy(tmp, p, len + 1);
+
+                               RDEBUG("\tStatus : %i (%s)", ctx->code, tmp);
+
+                               free(tmp);
+                       } else {
+                               RDEBUG("\tStatus : %i", ctx->code);
+                       }
+
+                       ctx->state = WRITE_STATE_PARSE_HEADERS;
+
+                       break;
+
+               case WRITE_STATE_PARSE_HEADERS:
+                       if ((s >= 14) &&
+                           (strncasecmp("Content-Type: ", p, 14) == 0)) {
+                               p += 14;
+                               s -= 14;
+
+                               /* 
+                                *      Check to see if there's a parameter
+                                *      separator.
+                                */
+                               q = memchr(p, ';', s);
+
+                               /*
+                                *      If there's not, find the end of this
+                                *      header.
+                                */
+                               if (q == NULL) q = memchr(p, '\r', s);
+
+                               len = (q == NULL) ? s : (unsigned)(q - p);
+
+                               type = fr_substr2int(http_content_type_table,
+                                       p, HTTP_BODY_UNKNOWN,
+                                       len);
+
+                               supp = http_body_type_supported[type];
+
+                               tmp = rad_malloc(len + 1);
+                               strlcpy(tmp, p, len + 1);
+
+                               RDEBUG("\tType   : %s (%s)",
+                                       fr_int2str(http_body_type_table, type,
+                                               "¿Unknown?"), tmp);
+
+                               free(tmp);
+
+                               if (type == HTTP_BODY_UNKNOWN) {
+                                       RDEBUG("Couldn't determine type, using"
+                                              " request type \"%s\".",
+                                              fr_int2str(http_body_type_table,
+                                                         ctx->type,
+                                                         "¿Unknown?"));
+
+                               } else if (supp == HTTP_BODY_UNSUPPORTED) {
+                                       RDEBUG("Type \"%s\" is currently"
+                                              " unsupported",
+                                              fr_int2str(http_body_type_table,
+                                                         type, "¿Unknown?"));
+                                       ctx->type = HTTP_BODY_UNSUPPORTED;
+
+                               } else if (supp == HTTP_BODY_INVALID) {
+                                       RDEBUG("Type \"%s\" is not a valid web"
+                                              " API data markup format",
+                                              fr_int2str(http_body_type_table,
+                                                         type, "¿Unknown?"));
+
+                                       ctx->type = HTTP_BODY_INVALID;
+
+                               } else if (type != ctx->type) {
+                                       ctx->type = type;
+                               }
+                       }
+                       break;
+                       
+               default:
+                       break;
+       }
+       return t;
+
+       malformed:
+
+       RDEBUG("Incoming header was malformed");
+       ctx->code = -1;
+
+       return (t - s);
+}
+
+/** Processes incoming HTTP body data from libcurl.
+ *
+ * Writes incoming body data to an intermediary buffer for later parsing by
+ * one of the decode functions.
+ *
+ * @param[in] ptr Char buffer where inbound header data is written
+ * @param[in] size Multiply by nmemb to get the length of ptr.
+ * @param[in] nmemb Multiply by size to get the length of ptr.
+ * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
+ * @return length of data processed, or 0 on error.
+ */
+static size_t rest_write_body(void *ptr, size_t size, size_t nmemb,
+                             void *userdata)
+{
+       rlm_rest_write_t *ctx  = userdata;
+       REQUEST *request       = ctx->request; /* Used by RDEBUG */
+       
+       const char *p = ptr;
+       char *tmp;
+
+       const size_t t = (size * nmemb);
+
+       /*
+        *      Any post processing of headers should go here...
+        */
+       if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
+               ctx->state = WRITE_STATE_PARSE_CONTENT;
+       }
+
+       switch (ctx->type)
+       {
+               case HTTP_BODY_UNSUPPORTED:
+                       return t;
+
+               case HTTP_BODY_INVALID:
+                       tmp = rad_malloc(t + 1);
+                       strlcpy(tmp, p, t + 1);
+
+                       RDEBUG2("%s", tmp);
+
+                       free(tmp);
+
+                       return t;
+
+               default:
+                       if (t > (ctx->alloc - ctx->used)) {
+                               ctx->alloc += ((t + 1) > REST_BODY_INCR) ?
+                                       t + 1 : REST_BODY_INCR;
+
+                               tmp = ctx->buffer;
+
+                               ctx->buffer = rad_malloc(ctx->alloc);
+
+                               /* If data has been written previously */
+                               if (tmp) {
+                                       strlcpy(ctx->buffer, tmp,
+                                              (ctx->used + 1));
+                                       free(tmp);
+                               }
+                       }
+                       strlcpy(ctx->buffer + ctx->used, p, t + 1);
+                       ctx->used += t;
+
+                       break;
+       }
+
+       return t;
+}
+
+/** (Re-)Initialises the data in a rlm_rest_write_t.
+ *
+ * This resets the values of the a rlm_rest_write_t to their defaults.
+ * Must be called between encoding sessions.
+ *
+ * @see rest_write_body
+ * @see rest_write_header
+ * 
+ * @param[in] request Current request.
+ * @param[in] data to initialise.
+ * @param[in] type Default http_body_type to use when decoding raw data, may be
+ * overwritten by rest_write_header.
+ */
+static void rest_write_ctx_init(REQUEST *request, rlm_rest_write_t *ctx,
+                               http_body_type_t type)
+{
+       ctx->request    = request;
+       ctx->type       = type;
+       ctx->state      = WRITE_STATE_INIT;
+       ctx->alloc      = 0;
+       ctx->used       = 0;
+       ctx->buffer     = NULL;
+}
+
+/** Frees the intermediary buffer created by rest_write.
+ *
+ * @param[in] data to be freed.
+ */
+static void rest_write_free(rlm_rest_write_t *ctx)
+{
+       if (ctx->buffer != NULL) {
+               free(ctx->buffer);
+       }
+}
+
+/** Configures body specific curlopts.
+ * 
+ * Configures libcurl handle to use either chunked mode, where the request
+ * data will be sent using multiple HTTP requests, or contiguous mode where
+ * the request data will be sent in a single HTTP request.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] handle rlm_rest_handle_t to configure.
+ * @param[in] func to pass to libcurl for chunked.
+ *     transfers (NULL if not using chunked mode).
+ * @return TRUE on success FALSE on error.
+ */
+static int rest_request_config_body(rlm_rest_t *instance,
+                                   rlm_rest_section_t *section,
+                                   rlm_rest_handle_t *handle,
+                                   rest_read_t func)
+{
+       rlm_rest_curl_context_t *ctx = handle->ctx;
+       CURL *candle                 = handle->handle;
+
+       ssize_t len;
+       CURLcode ret;
+
+       if (section->chunk > 0) {
+               ret = curl_easy_setopt(candle, CURLOPT_READDATA,
+                                      &ctx->read);
+               if (ret != CURLE_OK) goto error;
+
+               ret = curl_easy_setopt(candle, CURLOPT_READFUNCTION,
+                                      rest_encode_json);
+               if (ret != CURLE_OK) goto error;
+       } else {
+               len = rest_read_wrapper(&ctx->body, func,
+                                       REST_BODY_MAX_LEN , &ctx->read);
+               if (len <= 0) {
+                       radlog(L_ERR, "rlm_rest (%s): Failed creating HTTP"
+                              " body content", instance->xlat_name);
+                       return FALSE;
+               }
+
+               ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDS,
+                                      ctx->body);
+               if (ret != CURLE_OK) goto error;
+
+               ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDSIZE,
+                                      len);
+               if (ret != CURLE_OK) goto error;
+       }
+
+       return TRUE;
+
+       error:
+       radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
+               instance->xlat_name, ret, curl_easy_strerror(ret));
+
+       return FALSE;
+}
+
+/** Configures request curlopts.
+ * 
+ * Configures libcurl handle setting various curlopts for things like local
+ * client time, Content-Type, and other FreeRADIUS custom headers.
+ * 
+ * Current FreeRADIUS custom headers are:
+ *  - X-FreeRADIUS-Section     The module section being processed.
+ *  - X-FreeRADIUS-Server      The current virtual server the REQUEST is
+ *                             passing through.
+ *
+ * Sets up callbacks for all response processing (buffers and body data).
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] handle to configure.
+ * @param[in] request Current request.
+ * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
+ * @param[in] type Content-Type for request encoding, also sets the default
+ *     for decoding.
+ * @param[in] uri buffer containing the expanded URI to send the request to.
+ * @return TRUE on success (all opts configured) FALSE on error.
+ */
+int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
+                       REQUEST *request, void *handle, http_method_t method,
+                       http_body_type_t type, char *uri)
+{
+       rlm_rest_handle_t *randle       = handle;
+       rlm_rest_curl_context_t *ctx    = randle->ctx;
+       CURL *candle                    = randle->handle;
+       
+       http_auth_type_t auth = section->auth;
+
+       CURLcode ret;
+       long val = 1;
+
+       char buffer[512];
+
+       buffer[(sizeof(buffer) - 1)] = '\0';
+
+       /*
+        *      Setup any header options and generic headers.
+        */
+       ret = curl_easy_setopt(candle, CURLOPT_URL, uri);
+       if (ret != CURLE_OK) goto error;
+
+       ret = curl_easy_setopt(candle, CURLOPT_USERAGENT, "FreeRADIUS");
+       if (ret != CURLE_OK) goto error;
+  
+       snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s",
+                fr_int2str(http_content_type_table, type, "¿Unknown?"));
+       ctx->headers = curl_slist_append(ctx->headers, buffer);
+       if (!ctx->headers) goto error_header;
+       
+       if (section->timeout) {
+               ret = curl_easy_setopt(candle, CURLOPT_TIMEOUT,
+                                      section->timeout);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       ret = curl_easy_setopt(candle, CURLOPT_PROTOCOLS,
+                              (CURLPROTO_HTTP | CURLPROTO_HTTPS));
+       if (ret != CURLE_OK) goto error;
+       
+       /*
+        *      FreeRADIUS custom headers
+        */
+       snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Section: %s",
+                section->name);
+       ctx->headers = curl_slist_append(ctx->headers, buffer);
+       if (!ctx->headers) goto error_header;
+
+       snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Server: %s",
+                request->server);
+       ctx->headers = curl_slist_append(ctx->headers, buffer);
+       if (!ctx->headers) goto error_header;
+
+       /*
+        *      Configure HTTP verb (GET, POST, PUT, DELETE, other...)
+        */
+       switch (method)
+       {
+               case HTTP_METHOD_GET :
+                       ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
+                                              val);
+                       if (ret != CURLE_OK) goto error;
+
+                       break;
+
+               case HTTP_METHOD_POST :
+                       ret = curl_easy_setopt(candle, CURLOPT_POST,
+                                              val);
+                       if (ret != CURLE_OK) goto error;
+
+                       break;
+
+               case HTTP_METHOD_PUT :
+                       ret = curl_easy_setopt(candle, CURLOPT_PUT,
+                                              val);
+                       if (ret != CURLE_OK) goto error;
+
+                       break;
+
+               case HTTP_METHOD_DELETE :
+                       ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
+                                              val);
+                       if (ret != CURLE_OK) goto error;
+
+                       ret = curl_easy_setopt(candle,
+                                              CURLOPT_CUSTOMREQUEST, "DELETE");
+                       if (ret != CURLE_OK) goto error;
+
+                       break;
+
+               case HTTP_METHOD_CUSTOM :
+                       ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
+                                              val);
+                       if (ret != CURLE_OK) goto error;
+
+                       ret = curl_easy_setopt(candle,
+                                              CURLOPT_CUSTOMREQUEST,
+                                              section->method);
+                       if (ret != CURLE_OK) goto error;
+
+               default:
+                       assert(0);
+                       break;
+       };
+       
+       /*
+        *      Set user based authentication parameters
+        */
+       if (auth) {
+               if ((auth >= HTTP_AUTH_BASIC) &&
+                   (auth <= HTTP_AUTH_ANY_SAFE)) {
+                       ret = curl_easy_setopt(candle, CURLOPT_HTTPAUTH,
+                                              http_curl_auth[auth]);
+                       if (ret != CURLE_OK) goto error;
+                       
+                       if (section->username) {
+                               radius_xlat(buffer, sizeof(buffer),
+                                           section->username, request, NULL);
+                                           
+                               ret = curl_easy_setopt(candle, CURLOPT_USERNAME,
+                                                      buffer);
+                               if (ret != CURLE_OK) goto error;
+                       }
+                       if (section->password) {
+                               radius_xlat(buffer, sizeof(buffer),
+                                           section->password, request, NULL);
+                                           
+                               ret = curl_easy_setopt(candle, CURLOPT_PASSWORD,
+                                                      buffer);
+                               if (ret != CURLE_OK) goto error;
+                       }
+               } else if (type == HTTP_AUTH_TLS_SRP) {
+                       ret = curl_easy_setopt(candle, CURLOPT_TLSAUTH_TYPE,
+                                              http_curl_auth[auth]);
+               
+                       if (section->username) {
+                               radius_xlat(buffer, sizeof(buffer),
+                                           section->username, request, NULL);
+                                           
+                               ret = curl_easy_setopt(candle,
+                                                      CURLOPT_TLSAUTH_USERNAME,
+                                                      buffer);
+                               if (ret != CURLE_OK) goto error;
+                       }
+                       if (section->password) {
+                               radius_xlat(buffer, sizeof(buffer),
+                                           section->password, request, NULL);
+                                           
+                               ret = curl_easy_setopt(candle,
+                                                      CURLOPT_TLSAUTH_PASSWORD,
+                                                      buffer);
+                               if (ret != CURLE_OK) goto error;
+                       }
+       
+               }
+       }
+       
+       /*
+        *      Set SSL authentication parameters
+        */
+       if (section->certificate_file) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_SSLCERT,
+                                      section->certificate_file);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       if (section->file_type == FALSE) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_SSLCERT,
+                                      "DER");
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       if (section->private_key_file) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_SSLCERT,
+                                      section->private_key_file);
+               if (ret != CURLE_OK) goto error;
+       }
+
+       if (section->private_key_password) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_KEYPASSWD,
+                                      section->private_key_password);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       if (section->ca_file) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_ISSUERCERT,
+                                      section->ca_file);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       if (section->ca_path) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_CAPATH,
+                                      section->ca_path);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       if (section->random_file) {
+               ret = curl_easy_setopt(candle,
+                                      CURLOPT_RANDOM_FILE,
+                                      section->random_file);
+               if (ret != CURLE_OK) goto error;
+       }
+       
+       ret = curl_easy_setopt(candle,
+                              CURLOPT_SSL_VERIFYHOST,
+                              (section->check_cert_cn == TRUE) ?
+                               2 : 0);
+       if (ret != CURLE_OK) goto error;
+               
+       /*
+        *      Tell CURL how to get HTTP body content, and how to process
+        *      incoming data.
+        */
+       rest_write_ctx_init(request, &ctx->write, type);
+
+       ret = curl_easy_setopt(candle, CURLOPT_HEADERFUNCTION,
+                              rest_write_header);
+       if (ret != CURLE_OK) goto error;
+
+       ret = curl_easy_setopt(candle, CURLOPT_HEADERDATA,
+                              &ctx->write);
+       if (ret != CURLE_OK) goto error;
+
+       ret = curl_easy_setopt(candle, CURLOPT_WRITEFUNCTION,
+                              rest_write_body);
+       if (ret != CURLE_OK) goto error;
+
+       ret = curl_easy_setopt(candle, CURLOPT_WRITEDATA,
+                              &ctx->write);
+       if (ret != CURLE_OK) goto error;
+
+       switch (method)
+       {
+               case HTTP_METHOD_GET :
+               case HTTP_METHOD_DELETE :
+                       return FALSE;
+                       break;
+
+               case HTTP_METHOD_POST :
+               case HTTP_METHOD_PUT :
+               case HTTP_METHOD_CUSTOM :
+                       if (section->chunk > 0) {
+                               ctx->read.chunk = section->chunk;
+
+                               ctx->headers = curl_slist_append(ctx->headers,
+                                                                "Expect:");
+                               if (!ctx->headers) goto error_header;
+
+                               ctx->headers = curl_slist_append(ctx->headers,
+                                                                "Transfer-Encoding: chunked");
+                               if (!ctx->headers) goto error_header;
+                       }
+
+                       switch (type)
+                       {
+                               case HTTP_BODY_JSON:
+                                       rest_read_ctx_init(request,
+                                                          &ctx->read, 1);
+
+                                       ret = rest_request_config_body(instance,
+                                                                      section,
+                                                                      handle,
+                                                                      rest_encode_json);
+                                       if (!ret) return -1;
+
+                                       break;
+
+                               case HTTP_BODY_POST:
+                                       rest_read_ctx_init(request,
+                                                          &ctx->read, 0);
+
+                                       ret = rest_request_config_body(instance,
+                                                                      section,
+                                                                      handle,
+                                                                      rest_encode_post);
+                                       if (!ret) return -1;
+
+                                       break;
+
+                               default:
+                                       assert(0);
+                       }
+
+                       ret = curl_easy_setopt(candle, CURLOPT_HTTPHEADER,
+                                              ctx->headers);
+                       if (ret != CURLE_OK) goto error;
+
+                       break;
+
+               default:
+                       assert(0);
+       };
+
+       return TRUE;
+
+       error:
+       radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
+              instance->xlat_name, ret, curl_easy_strerror(ret));
+       return FALSE;
+
+       error_header:
+       radlog(L_ERR, "rlm_rest (%s): Failed creating header",
+              instance->xlat_name);
+       return FALSE;
+}
+
+/** Sends a REST (HTTP) request.
+ * 
+ * Send the actual REST request to the server. The response will be handled by
+ * the numerous callbacks configured in rest_request_config.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] handle to use.
+ * @return TRUE on success or FALSE on error.
+ */
+int rest_request_perform(rlm_rest_t *instance,
+                        UNUSED rlm_rest_section_t *section, void *handle)
+{
+       rlm_rest_handle_t *randle = handle;
+       CURL *candle              = randle->handle;
+       CURLcode ret;
+
+       ret = curl_easy_perform(candle);
+       if (ret != CURLE_OK) {
+               radlog(L_ERR, "rlm_rest (%s): Request failed: %i - %s",
+                      instance->xlat_name, ret, curl_easy_strerror(ret));
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/** Sends the response to the correct decode function.
+ * 
+ * Uses the Content-Type information written in rest_write_header to
+ * determine the correct decode function to use. The decode function will
+ * then convert the raw received data into VALUE_PAIRs.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] request Current request.
+ * @param[in] handle to use.
+ * @return TRUE on success or FALSE on error.
+ */
+int rest_request_decode(rlm_rest_t *instance, 
+                       UNUSED rlm_rest_section_t *section,
+                       REQUEST *request, void *handle)
+{
+       rlm_rest_handle_t *randle       = handle;
+       rlm_rest_curl_context_t *ctx    = randle->ctx;
+
+       int ret;
+
+       if (ctx->write.buffer == NULL) {
+               RDEBUG("Skipping attribute processing, no body data received");
+               return FALSE;
+       }
+
+       RDEBUG("Processing body", ret);
+
+       switch (ctx->write.type)
+       {
+               case HTTP_BODY_POST:
+                       ret = rest_decode_post(instance, section, request,
+                                              handle, ctx->write.buffer,
+                                              ctx->write.used);
+                       break;
+
+               case HTTP_BODY_JSON:
+                       ret = rest_decode_json(instance, section, request,
+                                              handle, ctx->write.buffer,
+                                              ctx->write.used);
+                       break;
+
+               case HTTP_BODY_UNSUPPORTED:
+               case HTTP_BODY_INVALID:
+                       return -1;
+
+               default:
+                       assert(0);
+       }
+
+       return ret;
+}
+
+/** Cleans up after a REST request.
+ * 
+ * Resets all options associated with a CURL handle, and frees any headers
+ * associated with it.
+ *
+ * Calls rest_read_ctx_free and rest_write_free to free any memory used by
+ * context data.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] handle to cleanup.
+ * @return TRUE on success or FALSE on error.
+ */
+void rest_request_cleanup(UNUSED rlm_rest_t *instance,
+                         UNUSED rlm_rest_section_t *section, void *handle)
+{
+       rlm_rest_handle_t *randle       = handle;
+       rlm_rest_curl_context_t *ctx    = randle->ctx;
+       CURL *candle                    = randle->handle;
+
+       /*
+        * Clear any previously configured options
+        */
+       curl_easy_reset(candle);
+
+       /*
+        * Free header list
+        */
+       if (ctx->headers != NULL) {
+               curl_slist_free_all(ctx->headers);
+               ctx->headers = NULL;
+       }
+
+       /*
+        * Free body data (only used if chunking is disabled)
+        */
+       if (ctx->body != NULL) free(ctx->body);
+  
+       /*
+        * Free other context info
+        */
+       rest_read_ctx_free(&ctx->read);
+       rest_write_free(&ctx->write);
+}
+
+/** URL encodes a string.
+ * 
+ * Encode special chars as per RFC 3986 section 4.
+ *
+ * @param[out] out Where to write escaped string.
+ * @param[in] outlen Size of out buffer.
+ * @param[in] raw string to be urlencoded.
+ * @return length of data written to out (excluding NULL).
+ */
+static size_t rest_uri_escape(char *out, size_t outlen, const char *raw)
+{
+       char *escaped;
+
+       escaped = curl_escape(raw, strlen(raw));
+       strlcpy(out, escaped, outlen);
+       curl_free(escaped);
+
+       return strlen(out);
+}
+
+/** Builds URI; performs XLAT expansions and encoding.
+ * 
+ * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
+ * Both components are expanded, but values expanded for the second component
+ * are also url encoded.
+ *
+ * @param[in] instance configuration data.
+ * @param[in] section configuration data.
+ * @param[in] request Current request
+ * @param[out] buffer to write expanded URI to.
+ * @param[in] bufsize Size of buffer.
+ * @return length of data written to buffer (excluding NULL) or < 0 if an error
+ *     occurred.
+ */
+ssize_t rest_uri_build(rlm_rest_t *instance, rlm_rest_section_t *section,
+                      REQUEST *request, char *buffer, size_t bufsize)
+{
+       const char *p, *q;
+
+       char *out, *scheme;
+       const char *path;
+
+       unsigned short count = 0;
+
+       size_t len;
+
+       p = section->uri;
+
+       while ((q = strchr(p, '/')) && (count++ < 3)) p = (q + 1);
+
+       if (count != 3) {
+               radlog(L_ERR, "rlm_rest (%s): Error URI is malformed,"
+                      " can't find start of path", instance->xlat_name);
+               return -1;
+       }
+
+       len = (q - p);
+
+       scheme = rad_malloc(len + 1);
+       strlcpy(scheme, section->uri, len + 1);
+
+       path = (q + 1);
+
+       out = buffer;
+       out += radius_xlat(out, bufsize, scheme, request, NULL);
+
+       free(scheme);
+
+       out += radius_xlat(out, (bufsize - (buffer - out)), path, request,
+                        rest_uri_escape);
+
+       return (buffer - out);
+}
\ No newline at end of file
diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h
new file mode 100644 (file)
index 0000000..a95de96
--- /dev/null
@@ -0,0 +1,250 @@
+/** Function prototypes datatypes for the REST (HTTP) transport.
+ *
+ * @file rest.h
+ *
+ * Version:    $Id$
+ *
+ *   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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2011  Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
+ */
+#include <freeradius-devel/ident.h>
+#include <freeradius-devel/connection.h>
+
+RCSIDH(other_h, "$Id$")
+
+
+#define REST_URI_MAX_LEN               2048
+#define REST_BODY_MAX_LEN              8192
+#define REST_BODY_INCR                 512
+#define REST_BODY_MAX_ATTRS            256
+
+typedef enum {
+       HTTP_METHOD_CUSTOM,
+       HTTP_METHOD_GET,
+       HTTP_METHOD_POST,
+       HTTP_METHOD_PUT,
+       HTTP_METHOD_DELETE
+} http_method_t;
+
+typedef enum {
+       HTTP_BODY_UNKNOWN = 0,
+       HTTP_BODY_UNSUPPORTED,
+       HTTP_BODY_INVALID,
+       HTTP_BODY_POST,
+       HTTP_BODY_JSON,
+       HTTP_BODY_XML,
+       HTTP_BODY_YAML,
+       HTTP_BODY_HTML,
+       HTTP_BODY_PLAIN,
+       HTTP_BODY_NUM_ENTRIES
+} http_body_type_t;
+
+typedef enum {
+       HTTP_AUTH_UNKNOWN = 0,
+       HTTP_AUTH_NONE,
+       HTTP_AUTH_TLS_SRP,
+       HTTP_AUTH_BASIC,
+       HTTP_AUTH_DIGEST,
+       HTTP_AUTH_DIGEST_IE,
+       HTTP_AUTH_GSSNEGOTIATE,
+       HTTP_AUTH_NTLM,
+       HTTP_AUTH_NTLM_WB,
+       HTTP_AUTH_ANY,
+       HTTP_AUTH_ANY_SAFE,
+       HTTP_AUTH_NUM_ENTRIES
+} http_auth_type_t;
+
+/*
+ *     Must be updated (in rest.c) if additional values are added to
+ *     http_body_type_t
+ */
+extern const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES];
+
+extern const http_body_type_t http_curl_auth[HTTP_AUTH_NUM_ENTRIES];
+
+extern const FR_NAME_NUMBER http_auth_table[];
+
+extern const FR_NAME_NUMBER http_method_table[];
+
+extern const FR_NAME_NUMBER http_body_table[];
+
+extern const FR_NAME_NUMBER http_content_header_table[];
+
+/*
+ *     Structure for section configuration
+ */
+typedef struct rlm_rest_section_t {
+       const char *name;
+       char *uri;
+       
+       char *method_str;
+       http_method_t method;
+
+       char *body_str;
+       http_body_type_t body;
+
+       char *username;
+       char *password;
+       char *auth_str;
+       http_auth_type_t auth;
+       int require_auth;
+       
+       char *certificate_file;
+       int file_type;
+       char *private_key_file;
+       char *private_key_password;
+       char *ca_file;
+       char *ca_path;
+       char *random_file;
+       int check_cert_cn;
+
+       int timeout;
+       unsigned int chunk;
+} rlm_rest_section_t;
+
+/*
+ *     Structure for module configuration
+ */
+typedef struct rlm_rest_t {
+       const char *xlat_name;
+
+       char *connect_uri;
+
+       fr_connection_pool_t *conn_pool;
+
+       rlm_rest_section_t authorize;
+       rlm_rest_section_t authenticate;
+       rlm_rest_section_t accounting;
+       rlm_rest_section_t checksimul;
+       rlm_rest_section_t postauth;
+} rlm_rest_t;
+
+/*
+ *     States for stream based attribute encoders
+ */
+typedef enum {
+       READ_STATE_INIT = 0,
+       READ_STATE_ATTR_BEGIN,
+       READ_STATE_ATTR_CONT,
+       READ_STATE_END,
+} read_state_t;
+
+/*
+ *     States for the response parser
+ */
+typedef enum {
+       WRITE_STATE_INIT = 0,
+       WRITE_STATE_PARSE_HEADERS,
+       WRITE_STATE_PARSE_CONTENT,
+       WRITE_STATE_DISCARD,
+} write_state_t;
+
+/*
+ *     Outbound data context (passed to CURLOPT_READFUNCTION as CURLOPT_READDATA)
+ */
+typedef struct rlm_rest_read_t {
+       rlm_rest_t      *instance;
+       REQUEST         *request;
+       read_state_t    state;
+
+       VALUE_PAIR      **first;
+       VALUE_PAIR      **next;
+
+       unsigned int    chunk;
+} rlm_rest_read_t;
+
+/*
+ *     Curl inbound data context (passed to CURLOPT_WRITEFUNCTION and
+ *     CURLOPT_HEADERFUNCTION as CURLOPT_WRITEDATA and CURLOPT_HEADERDATA)
+ */
+typedef struct rlm_rest_write_t {
+       rlm_rest_t       *instance;
+       REQUEST          *request;
+       write_state_t    state;
+
+       char             *buffer;       /* HTTP incoming raw data */
+       size_t           alloc;         /* Space allocated for buffer */
+       size_t           used;          /* Space used in buffer */ 
+
+       int              code;          /* HTTP Status Code */
+       http_body_type_t type;          /* HTTP Content Type */
+} rlm_rest_write_t;
+
+/*
+ *     Curl context data
+ */
+typedef struct rlm_rest_curl_context_t {
+       struct curl_slist       *headers;
+       char                    *body;
+       rlm_rest_read_t         read;
+       rlm_rest_write_t        write;
+} rlm_rest_curl_context_t;
+
+/*
+ *     Connection API handle
+ */
+typedef struct rlm_rest_handle_t {
+       void    *handle;        /* Real Handle */
+       void    *ctx;           /* Context */
+} rlm_rest_handle_t;
+
+/*
+ *     Function prototype for rest_read_wrapper. Matches CURL's
+ *     CURLOPT_READFUNCTION prototype.
+ */
+typedef size_t (*rest_read_t)(void *ptr, size_t size, size_t nmemb,
+                             void *userdata);
+
+/*
+ *     Connection API callbacks
+ */
+int rest_init(rlm_rest_t *instance);
+
+void rest_cleanup(void);
+
+void *rest_socket_create(void *instance);
+
+int rest_socket_alive(void *instance, void *handle);
+
+int rest_socket_delete(void *instance, void *handle);
+
+/*
+ *     Request processing API
+ */
+int rest_request_config(rlm_rest_t *instance,
+                       rlm_rest_section_t *section, REQUEST *request,
+                       void *handle, http_method_t method,
+                       http_body_type_t type, char *uri);
+
+int rest_request_perform(rlm_rest_t *instance, rlm_rest_section_t *section,
+                        void *handle);
+
+int rest_request_decode(rlm_rest_t *instance,
+                       UNUSED rlm_rest_section_t *section, REQUEST *request,
+                       void *handle);
+
+void rest_request_cleanup(rlm_rest_t *instance, rlm_rest_section_t *section,
+                         void *handle);
+
+#define rest_get_handle_code(handle)(((rlm_rest_curl_context_t*)((rlm_rest_handle_t*)handle)->ctx)->write.code)
+
+#define rest_get_handle_type(handle)(((rlm_rest_curl_context_t*)((rlm_rest_handle_t*)handle)->ctx)->write.type)
+
+/*
+ *     Helper functions
+ */
+ssize_t rest_uri_build(rlm_rest_t *instance, rlm_rest_section_t *section,
+                      REQUEST *request, char *buffer, size_t bufsize);
\ No newline at end of file
diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c
new file mode 100644 (file)
index 0000000..a76a4cc
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * rlm_rest.c
+ *
+ * Version:    $Id$
+ *
+ *   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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2011  Arran Cudbard-Bell <arran.cudbardb@freeradius.org>>
+ */
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/token.h>
+
+#include "rest.h"
+
+/*
+ *     A mapping of configuration file names to internal variables.
+ *
+ *     Note that the string is dynamically allocated, so it MUST
+ *     be freed.  When the configuration file parse re-reads the string,
+ *     it free's the old one, and strdup's the new one, placing the pointer
+ *     to the strdup'd string into 'config.string'.  This gets around
+ *     buffer over-flows.
+ */
+static const CONF_PARSER section_config[] = {
+       { "uri", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, uri),        NULL, ""  },
+       { "method", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, method_str), NULL, "GET"   },
+       { "body", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, body_str),   NULL, "post"  },
+        
+       /* User authentication */
+       { "auth", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, auth_str),   NULL, "none"  },
+       { "username", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, username),   NULL, ""  },
+       { "password", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_section_t, password),   NULL, ""  },
+       { "require_auth", PW_TYPE_BOOLEAN,
+        offsetof(rlm_rest_section_t, require_auth), NULL, "no"},
+
+       /* SSL authentication */
+       { "certificate_file", PW_TYPE_FILENAME,
+         offsetof(rlm_rest_section_t, certificate_file), NULL, NULL },
+       { "pem_file_type", PW_TYPE_BOOLEAN,
+         offsetof(rlm_rest_section_t, file_type), NULL, "yes" },
+       { "private_key_file", PW_TYPE_FILENAME,
+         offsetof(rlm_rest_section_t, private_key_file), NULL, NULL },
+       { "private_key_password", PW_TYPE_STRING_PTR,
+         offsetof(rlm_rest_section_t, private_key_password), NULL, NULL },  
+       { "CA_file", PW_TYPE_FILENAME,
+         offsetof(rlm_rest_section_t, ca_file), NULL, NULL },
+       { "CA_path", PW_TYPE_FILENAME,
+         offsetof(rlm_rest_section_t, ca_path), NULL, NULL },
+       { "random_file", PW_TYPE_STRING_PTR,
+         offsetof(rlm_rest_section_t, random_file), NULL, NULL },
+       { "check_cert_cn", PW_TYPE_BOOLEAN,
+         offsetof(rlm_rest_section_t, check_cert_cn), NULL, "yes"},
+         
+       /* Transfer configuration */
+       { "timeout", PW_TYPE_INTEGER, 
+        offsetof(rlm_rest_section_t, timeout),    NULL, "0" },
+       { "chunk", PW_TYPE_INTEGER,
+        offsetof(rlm_rest_section_t, chunk),      NULL, "0" },
+
+       { NULL, -1, 0, NULL, NULL }
+};
+static const CONF_PARSER module_config[] = {
+       { "connect_uri", PW_TYPE_STRING_PTR,
+        offsetof(rlm_rest_t, connect_uri), NULL, "http://localhost/" },
+
+       { NULL, -1, 0, NULL, NULL }
+};
+
+static int rlm_rest_perform (rlm_rest_t *instance, rlm_rest_section_t *section,
+                            void *handle, REQUEST *request)
+{
+       size_t uri_len;
+       char uri[REST_URI_MAX_LEN];
+       
+       int ret;
+       
+       RDEBUG("Expanding URI components");
+       /*
+        *      Build xlat'd URI, this allows REST servers to be specified by
+        *      request attributes.
+        */
+       uri_len = rest_uri_build(instance, section, request, uri, sizeof(uri));
+       if (uri_len <= 0) return -1;
+
+       RDEBUG("Sending HTTP %s to \"%s\"",
+               fr_int2str(http_method_table, section->method, NULL),
+               uri);
+
+       /*
+        *      Configure various CURL options, and initialise the read/write
+        *      context data.
+        */
+       ret = rest_request_config(instance, section, request,
+               handle,
+               section->method,
+               section->body,
+               uri);
+       if (ret <= 0) return -1;
+
+       /*
+        *      Send the CURL request, pre-parse headers, aggregate incoming
+        *      HTTP body data into a single contiguous buffer.
+        */
+       ret = rest_request_perform(instance, section, handle);
+       if (ret <= 0) return -1;
+
+       return 1;
+}
+
+static void rlm_rest_cleanup (rlm_rest_t *instance, rlm_rest_section_t *section,
+                             void *handle)
+{
+       rest_request_cleanup(instance, section, handle);
+};
+
+static int parse_sub_section(CONF_SECTION *parent, 
+                            rlm_rest_t *instance, rlm_rest_section_t *config,
+                            rlm_components_t comp)
+{
+       CONF_SECTION *cs;
+
+       const char *name = section_type_value[comp].section;
+
+       cs = cf_section_sub_find(parent, name);
+       if (!cs) {
+               /* TODO: Should really setup section with default values */
+               return 0;
+       }
+       cf_section_parse(cs, config, section_config);
+
+       /*
+        *      Add section name (Maybe add to headers later?).
+        */
+       config->name = name;
+
+       /*
+        *      Convert HTTP method auth and body type strings into their
+        *      integer equivalents.
+        */
+       config->auth = fr_str2int(http_auth_table, config->auth_str,
+                                 HTTP_AUTH_UNKNOWN);
+                                 
+       if (config->auth == HTTP_AUTH_UNKNOWN) {
+               radlog(L_ERR, "rlm_rest (%s): Unknown HTTP auth type \"%s\"",
+                      instance->xlat_name, config->auth_str);
+               return -1;      
+       }
+       
+       if (!http_curl_auth[config->auth]) {
+               radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP auth type \"%s\""
+                      ", check libcurl version, OpenSSL build configuration," 
+                      " then recompile this module",
+                      instance->xlat_name, config->body_str);
+               return -1;
+       }
+                                   
+       config->method = fr_str2int(http_method_table, config->method_str,
+                                   HTTP_METHOD_CUSTOM);
+
+       config->body = fr_str2int(http_body_table, config->body_str,
+                                 HTTP_BODY_UNKNOWN);
+
+       if (config->body == HTTP_BODY_UNKNOWN) {
+               radlog(L_ERR, "rlm_rest (%s): Unknown HTTP body type \"%s\"",
+                      instance->xlat_name, config->body_str);
+               return -1;
+       }
+
+       if (http_body_type_supported[config->body] == HTTP_BODY_UNSUPPORTED) {
+               radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP body type \"%s\""
+                      ", please submit patches", instance->xlat_name,
+                      config->body_str);
+               return -1;
+       }
+
+       return 1;
+}
+
+/*
+ *     Do any per-module initialization that is separate to each
+ *     configured instance of the module.  e.g. set up connections
+ *     to external databases, read configuration files, set up
+ *     dictionary entries, etc.
+ *
+ *     If configuration information is given in the config section
+ *     that must be referenced in later calls, store a handle to it
+ *     in *instance otherwise put a null pointer there.
+ */
+static int rlm_rest_instantiate(CONF_SECTION *conf, void **instance)
+{
+       rlm_rest_t *data;
+       const char *xlat_name;
+
+       /*
+        *      Allocate memory for instance data.
+        */
+       data = rad_malloc(sizeof(*data));
+       if (!data) {
+               return -1;
+       }
+       memset(data, 0, sizeof(*data));
+
+       /*
+        *      If the configuration parameters can't be parsed, then
+        *      fail.
+        */
+       if (cf_section_parse(conf, data, module_config) < 0) {
+               free(data);
+               return -1;
+       }
+
+       xlat_name = cf_section_name2(conf);
+       if (xlat_name == NULL) {
+               xlat_name = cf_section_name1(conf);
+       }
+
+       data->xlat_name = xlat_name;
+
+       /*
+        *      Parse sub-section configs.
+        */
+       if (
+               (parse_sub_section(conf, data, &data->authorize,
+                                  RLM_COMPONENT_AUTZ) < 0) ||
+               (parse_sub_section(conf, data, &data->authenticate,
+                                  RLM_COMPONENT_AUTH) < 0) ||
+               (parse_sub_section(conf, data, &data->accounting,
+                                  RLM_COMPONENT_ACCT) < 0) ||
+               (parse_sub_section(conf, data, &data->checksimul,
+                                  RLM_COMPONENT_SESS) < 0) ||
+               (parse_sub_section(conf, data, &data->postauth,
+                                  RLM_COMPONENT_POST_AUTH) < 0))
+       {
+               return -1;
+       }
+
+       /*
+        *      Initialise REST libraries.
+        */
+       if (!rest_init(data)) {
+               return -1;
+       }
+
+       data->conn_pool = fr_connection_pool_init(conf, data,
+                                                 rest_socket_create,
+                                                 rest_socket_alive,
+                                                 rest_socket_delete);
+
+       if (!data->conn_pool) {
+               return -1;
+       }
+
+       *instance = data;
+
+       return 0;
+}
+
+/*
+ *     Find the named user in this modules database.  Create the set
+ *     of attribute-value pairs to check and reply with for this user
+ *     from the database. The authentication code only needs to check
+ *     the password, the rest is done here.
+ */
+static int rlm_rest_authorize(void *instance, REQUEST *request)
+{
+       rlm_rest_t *my_instance = instance;
+       rlm_rest_section_t *section = &my_instance->authorize;
+
+       void *handle;
+       int hcode;
+       int rcode = RLM_MODULE_OK;
+       int ret;
+
+       handle = fr_connection_get(my_instance->conn_pool);
+       if (!handle) return RLM_MODULE_FAIL;
+
+       ret = rlm_rest_perform(instance, section, handle, request);
+       if (ret < 0) { 
+               rcode = RLM_MODULE_FAIL;
+               goto end;
+       }
+
+       hcode = rest_get_handle_code(handle);
+
+       switch (hcode) {
+               case 404:
+               case 410:
+                       rcode = RLM_MODULE_NOTFOUND;
+                       break;
+               case 403:
+                       rcode = RLM_MODULE_USERLOCK;
+                       break;
+               case 401:
+                       /*
+                        *      Attempt to parse content if there was any.
+                        */
+                       ret = rest_request_decode(my_instance, section,
+                                                 request, handle);
+                       if (ret < 0) {
+                               rcode = RLM_MODULE_FAIL;
+                               break;
+                       }
+
+                       rcode = RLM_MODULE_REJECT;
+                       break;
+               case 204:
+                       rcode = RLM_MODULE_OK;
+                       break;
+               default:
+                       /*
+                        *      Attempt to parse content if there was any.
+                        */
+                       if ((hcode >= 200) && (hcode < 300)) {
+                               ret = rest_request_decode(my_instance, section,
+                                                         request, handle);
+                               if (ret < 0)       rcode = RLM_MODULE_FAIL;
+                               else if (ret == 0) rcode = RLM_MODULE_OK;
+                               else               rcode = RLM_MODULE_UPDATED;
+                               break;
+                       } else if (hcode < 500) {
+                               rcode = RLM_MODULE_INVALID;
+                       } else {
+                               rcode = RLM_MODULE_FAIL;
+                       }
+       }
+
+       end:
+
+       rlm_rest_cleanup(instance, section, handle);
+
+       fr_connection_release(my_instance->conn_pool, handle);
+
+       return rcode;
+}
+
+/*
+ *     Authenticate the user with the given password.
+ */
+static int rlm_rest_authenticate(void *instance, REQUEST *request)
+{
+       /* quiet the compiler */
+       instance = instance;
+       request = request;
+
+       return RLM_MODULE_OK;
+}
+
+/*
+ *     Write accounting information to this modules database.
+ */
+static int rlm_rest_accounting(void *instance, REQUEST *request)
+{
+       /* quiet the compiler */
+       instance = instance;
+       request = request;
+
+       return RLM_MODULE_OK;
+}
+
+/*
+ *     See if a user is already logged in. Sets request->simul_count to the
+ *     current session count for this user and sets request->simul_mpp to 2
+ *     if it looks like a multilink attempt based on the requested IP
+ *     address, otherwise leaves request->simul_mpp alone.
+ *
+ *     Check twice. If on the first pass the user exceeds his
+ *     max. number of logins, do a second pass and validate all
+ *     logins by querying the terminal server (using eg. SNMP).
+ */
+static int rlm_rest_checksimul(void *instance, REQUEST *request)
+{
+       instance = instance;
+
+       request->simul_count=0;
+
+       return RLM_MODULE_OK;
+}
+
+/*
+ *     Only free memory we allocated.  The strings allocated via
+ *     cf_section_parse() do not need to be freed.
+ */
+static int rlm_rest_detach(void *instance)
+{
+       rlm_rest_t *my_instance = instance;
+
+       fr_connection_pool_delete(my_instance->conn_pool);
+
+       free(my_instance);
+
+       /* Free any memory used by libcurl */
+       rest_cleanup();
+
+       return 0;
+}
+
+/*
+ *     The module name should be the only globally exported symbol.
+ *     That is, everything else should be 'static'.
+ *
+ *     If the module needs to temporarily modify it's instantiation
+ *     data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ *     The server will then take care of ensuring that the module
+ *     is single-threaded.
+ */
+module_t rlm_rest = {
+       RLM_MODULE_INIT,
+       "rlm_rest",
+       RLM_TYPE_THREAD_SAFE,           /* type */
+       rlm_rest_instantiate,           /* instantiation */
+       rlm_rest_detach,                /* detach */
+       {
+               rlm_rest_authenticate,  /* authentication */
+               rlm_rest_authorize,     /* authorization */
+               NULL,                   /* preaccounting */
+               rlm_rest_accounting,    /* accounting */
+               rlm_rest_checksimul,    /* checksimul */
+               NULL,                   /* pre-proxy */
+               NULL,                   /* post-proxy */
+               NULL                    /* post-auth */
+       },
+};