Initial commit
Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/extensions/app_radgw/CMakeLists.txt b/extensions/app_radgw/CMakeLists.txt
new file mode 100644
index 0000000..fb91c2b
--- /dev/null
+++ b/extensions/app_radgw/CMakeLists.txt
@@ -0,0 +1,118 @@
+# The app_radgw extension
+PROJECT("RADIUS/Diameter extensible gateway application for freeDiameter" C)
+
+
+########### Main app_radgw extension #############
+
+# Parser files
+BISON_FILE(rgw_conf.y)
+FLEX_FILE(rgw_conf.l)
+SET_SOURCE_FILES_PROPERTIES(lex.rgw_conf.c rgw_conf.tab.c PROPERTIES COMPILE_FLAGS "-I ${CMAKE_CURRENT_SOURCE_DIR}")
+
+# List of source files
+SET( RGW_DEFAULT_SRC
+ radius.c
+ md5.c
+ rgw_msg_codes.c
+ rgw_msg_attrtype.c
+ rgw_main.c
+ lex.rgw_conf.c
+ rgw_conf.tab.c
+ rgw_conf.tab.h
+ rgw_clients.c
+ rgw_plugins.c
+ rgw_servers.c
+ rgw_worker.c
+)
+
+SET( RG_COMMON_HEADER
+ rgw_common.h
+ radius.h
+ md5.h
+ hostap_compat.h
+)
+
+# Compile these files as a freeDiameter extension.
+FD_ADD_EXTENSION(app_radgw ${RGW_DEFAULT_SRC} ${RG_COMMON_HEADER})
+
+
+########### RADIUS/Diameter translation agent plugins (support for RADIUS protocol) ############
+# Use the macro RGWX_ADD_PLUGIN(name files...) to create a plugin.
+# It is equivalent to add_library with the appropriate parameters
+# and naming conventions (.rgwx : Radius GateWay eXtension)
+MACRO(RGWX_ADD_PLUGIN PLGNAME)
+ ADD_LIBRARY(${PLGNAME} MODULE ${ARGN})
+ SET_TARGET_PROPERTIES(${PLGNAME} PROPERTIES PREFIX "" )
+ SET_TARGET_PROPERTIES(${PLGNAME} PROPERTIES SUFFIX ".rgwx" )
+ INSTALL(TARGETS ${PLGNAME}
+ LIBRARY DESTINATION ${INSTALL_EXTENSIONS_SUFFIX}
+ COMPONENT freeDiameter-radius-gateway)
+ENDMACRO(RGWX_ADD_PLUGIN)
+
+# Ask unless ALL_EXTENSIONS is set:
+MACRO(FD_OPTION_PLUGIN PLGVAR DESCR DEFLT)
+ IF (NOT ALL_EXTENSIONS)
+ OPTION(BUILD_${PLGVAR} ${DESCR} ${DEFLT})
+ ENDIF (NOT ALL_EXTENSIONS)
+ IF (BUILD_${PLGVAR} OR ALL_EXTENSIONS)
+ SET(${PLGVAR} TRUE)
+ ELSE (BUILD_${PLGVAR} OR ALL_EXTENSIONS)
+ SET(${PLGVAR} FALSE)
+ ENDIF (BUILD_${PLGVAR} OR ALL_EXTENSIONS)
+ENDMACRO(FD_OPTION_PLUGIN PLGVAR DESCR DEFLT)
+
+
+### Debug
+# Example of plugin:
+
+FD_OPTION_PLUGIN(RGWX_SAMPLE "Build sample plugin? (for developers only)" OFF)
+ IF (RGWX_SAMPLE)
+ RGWX_ADD_PLUGIN(sample ${RG_COMMON_HEADER} rgwx_sample.c)
+ ENDIF (RGWX_SAMPLE)
+
+# A plugin for debug: dumps RADIUS and Diameter messages state at the time the plugin is called.
+FD_OPTION_PLUGIN(RGWX_DEBUG "Build debug plugin? (display status of RADIUS and Diameter messages)" ON)
+ IF (RGWX_DEBUG)
+ RGWX_ADD_PLUGIN(debug ${RG_COMMON_HEADER} rgwx_debug.c)
+ ENDIF (RGWX_DEBUG)
+
+
+### Authentication, Authorization messages translation.
+FD_OPTION_PLUGIN(RGWX_AUTH "Build Authentication & Authorization RADIUS translation plugin? (RFC2865, RFC3579)" ON)
+ IF (RGWX_AUTH)
+ RGWX_ADD_PLUGIN(auth ${RG_COMMON_HEADER} rgwx_auth.c)
+ ENDIF (RGWX_AUTH)
+
+### SIP Authentication, Authorization messages translation.
+FD_OPTION_PLUGIN(RGWX_SIP "Build SIP RADIUS translation plugin? (RFC4740 or RFC5090)" OFF)
+ IF (RGWX_SIP)
+ RGWX_ADD_PLUGIN(sip ${RG_COMMON_HEADER} rgwx_sip.c)
+ ENDIF (RGWX_SIP)
+
+
+
+### Accounting messages translation.
+FD_OPTION_PLUGIN(RGWX_ACCT "Build Accounting RADIUS translation plugin? (RFC2866)" ON)
+ IF (RGWX_ACCT)
+ RGWX_ADD_PLUGIN(acct ${RG_COMMON_HEADER} rgwx_acct.c)
+ ENDIF (RGWX_ACCT)
+
+
+### Generic plugin to handle some attributes (either delete them or simply echo them in the answer)
+FD_OPTION_PLUGIN(RGWX_ECHODROP "Build 'echo/drop' plugin? (drop specific RADIUS attributes or echo them in RADIUS answer)" ON)
+ IF (RGWX_ECHODROP)
+ BISON_FILE(rgwx_echodrop.y)
+ FLEX_FILE(rgwx_echodrop.l)
+ SET_SOURCE_FILES_PROPERTIES(lex.rgwx_echodrop.c rgwx_echodrop.tab.c PROPERTIES COMPILE_FLAGS "-I ${CMAKE_CURRENT_SOURCE_DIR}")
+ RGWX_ADD_PLUGIN(echodrop ${RG_COMMON_HEADER} rgwx_echodrop.h rgwx_echodrop.c lex.rgwx_echodrop.c rgwx_echodrop.tab.c rgwx_echodrop.tab.h )
+ ENDIF (RGWX_ECHODROP)
+
+
+####
+## INSTALL section ##
+
+INSTALL(TARGETS app_radgw
+ LIBRARY DESTINATION ${INSTALL_EXTENSIONS_SUFFIX}
+ COMPONENT freeDiameter-radius-gateway)
+
+# Note that all compiled plugins are added by the RGWX_ADD_PLUGIN macro in the component freeDiameter-radius-gateway as well.
diff --git a/extensions/app_radgw/hostap_compat.h b/extensions/app_radgw/hostap_compat.h
new file mode 100644
index 0000000..9bee2ac
--- /dev/null
+++ b/extensions/app_radgw/hostap_compat.h
@@ -0,0 +1,194 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2011, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* This file contains compatibility bindings for hostap files.
+ Most of the definitions here come from different files from the hostap project.
+
+ We don't care for OS-specific definitions since we are only compatible with POSIX systems.
+
+ */
+
+#ifndef _HOSTAP_COMPAT_H
+#define _HOSTAP_COMPAT_H
+
+#include <sys/time.h>
+
+typedef uint64_t u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+typedef int64_t s64;
+typedef int32_t s32;
+typedef int16_t s16;
+typedef int8_t s8;
+
+/* md5.c uses a different macro name than freeDiameter for endianness */
+#ifdef HOST_BIG_ENDIAN
+# define WORDS_BIGENDIAN HOST_BIG_ENDIAN
+#else /* HOST_BIG_ENDIAN */
+# undef WORDS_BIGENDIAN
+#endif /* HOST_BIG_ENDIAN */
+
+/* freeDiameter uses the POSIX API, so we don't provide alternatives. This may be changed later as needed */
+#define os_malloc(s) malloc((s))
+#define os_realloc(p, s) realloc((p), (s))
+#define os_free(p) free((p))
+
+#define os_memcpy(d, s, n) memcpy((d), (s), (n))
+#define os_memmove(d, s, n) memmove((d), (s), (n))
+#define os_memset(s, c, n) memset(s, c, n)
+#define os_memcmp(s1, s2, n) memcmp((s1), (s2), (n))
+
+#define os_strdup(s) strdup(s)
+#define os_strlen(s) strlen(s)
+#define os_strcasecmp(s1, s2) strcasecmp((s1), (s2))
+#define os_strncasecmp(s1, s2, n) strncasecmp((s1), (s2), (n))
+#define os_strchr(s, c) strchr((s), (c))
+#define os_strcmp(s1, s2) strcmp((s1), (s2))
+#define os_strncmp(s1, s2, n) strncmp((s1), (s2), (n))
+#define os_strncpy(d, s, n) strncpy((d), (s), (n))
+#define os_strrchr(s, c) strrchr((s), (c))
+#define os_strstr(h, n) strstr((h), (n))
+#define os_snprintf snprintf
+
+#define os_random() random()
+
+static __inline__ void * os_zalloc(size_t size)
+{
+ void *n = os_malloc(size);
+ if (n)
+ os_memset(n, 0, size);
+ return n;
+}
+
+typedef long os_time_t;
+struct os_time {
+ os_time_t sec;
+ os_time_t usec;
+};
+
+static __inline__ int os_get_time(struct os_time *t)
+{
+ int res;
+ struct timeval tv;
+ res = gettimeofday(&tv, NULL);
+ t->sec = tv.tv_sec;
+ t->usec = tv.tv_usec;
+ return res;
+}
+
+/* Macros for handling unaligned memory accesses */
+#define WPA_GET_BE16(a) ((u16) (((a)[0] << 8) | (a)[1]))
+#define WPA_PUT_BE16(a, val) \
+ do { \
+ (a)[0] = ((u16) (val)) >> 8; \
+ (a)[1] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_LE16(a) ((u16) (((a)[1] << 8) | (a)[0]))
+#define WPA_PUT_LE16(a, val) \
+ do { \
+ (a)[1] = ((u16) (val)) >> 8; \
+ (a)[0] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_BE24(a) ((((u32) (a)[0]) << 16) | (((u32) (a)[1]) << 8) | \
+ ((u32) (a)[2]))
+#define WPA_PUT_BE24(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[2] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE32(a) ((((u32) (a)[0]) << 24) | (((u32) (a)[1]) << 16) | \
+ (((u32) (a)[2]) << 8) | ((u32) (a)[3]))
+#define WPA_PUT_BE32(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[3] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE32(a) ((((u32) (a)[3]) << 24) | (((u32) (a)[2]) << 16) | \
+ (((u32) (a)[1]) << 8) | ((u32) (a)[0]))
+#define WPA_PUT_LE32(a, val) \
+ do { \
+ (a)[3] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[0] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE64(a) ((((u64) (a)[0]) << 56) | (((u64) (a)[1]) << 48) | \
+ (((u64) (a)[2]) << 40) | (((u64) (a)[3]) << 32) | \
+ (((u64) (a)[4]) << 24) | (((u64) (a)[5]) << 16) | \
+ (((u64) (a)[6]) << 8) | ((u64) (a)[7]))
+#define WPA_PUT_BE64(a, val) \
+ do { \
+ (a)[0] = (u8) (((u64) (val)) >> 56); \
+ (a)[1] = (u8) (((u64) (val)) >> 48); \
+ (a)[2] = (u8) (((u64) (val)) >> 40); \
+ (a)[3] = (u8) (((u64) (val)) >> 32); \
+ (a)[4] = (u8) (((u64) (val)) >> 24); \
+ (a)[5] = (u8) (((u64) (val)) >> 16); \
+ (a)[6] = (u8) (((u64) (val)) >> 8); \
+ (a)[7] = (u8) (((u64) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE64(a) ((((u64) (a)[7]) << 56) | (((u64) (a)[6]) << 48) | \
+ (((u64) (a)[5]) << 40) | (((u64) (a)[4]) << 32) | \
+ (((u64) (a)[3]) << 24) | (((u64) (a)[2]) << 16) | \
+ (((u64) (a)[1]) << 8) | ((u64) (a)[0]))
+
+
+/* Packing structures to avoid padding */
+#ifdef __GNUC__
+#define STRUCT_PACKED __attribute__ ((packed))
+#else
+#define STRUCT_PACKED
+#endif
+
+/* For md5.c file */
+#define INTERNAL_MD5
+#define CONFIG_CRYPTO_INTERNAL
+
+/* For radius.c file */
+#define CONFIG_IPV6
+#include <arpa/inet.h>
+
+
+#endif /* _HOSTAP_COMPAT_H */
diff --git a/extensions/app_radgw/md5.c b/extensions/app_radgw/md5.c
new file mode 100644
index 0000000..055fcf0
--- /dev/null
+++ b/extensions/app_radgw/md5.c
@@ -0,0 +1,399 @@
+/*********************************************************************************/
+/* freeDiameter author note:
+ * The content from this file comes directly from the hostap project.
+ * It is redistributed under the terms of the BSD license, as allowed
+ * by the original copyright reproduced below.
+ * In addition to this notice, only the #include directives have been modified.
+ */
+#include "rgw_common.h"
+/*********************************************************************************/
+
+
+/*
+ * MD5 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+/**
+ * hmac_md5_vector - HMAC-MD5 over data vector (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash (16 bytes)
+ */
+void hmac_md5_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac)
+{
+ u8 k_pad[64]; /* padding - key XORd with ipad/opad */
+ u8 tk[16];
+ const u8 *_addr[6];
+ size_t i, _len[6];
+
+ if (num_elem > 5) {
+ /*
+ * Fixed limit on the number of fragments to avoid having to
+ * allocate memory (which could fail).
+ */
+ return;
+ }
+
+ /* if key is longer than 64 bytes reset it to key = MD5(key) */
+ if (key_len > 64) {
+ md5_vector(1, &key, &key_len, tk);
+ key = tk;
+ key_len = 16;
+ }
+
+ /* the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected */
+
+ /* start out by storing key in ipad */
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+
+ /* XOR key with ipad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x36;
+
+ /* perform inner MD5 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ for (i = 0; i < num_elem; i++) {
+ _addr[i + 1] = addr[i];
+ _len[i + 1] = len[i];
+ }
+ md5_vector(1 + num_elem, _addr, _len, mac);
+
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with opad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x5c;
+
+ /* perform outer MD5 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ _addr[1] = mac;
+ _len[1] = MD5_MAC_LEN;
+ md5_vector(2, _addr, _len, mac);
+}
+
+
+/**
+ * hmac_md5 - HMAC-MD5 over data buffer (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @data: Pointers to the data area
+ * @data_len: Length of the data area
+ * @mac: Buffer for the hash (16 bytes)
+ */
+void hmac_md5(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac)
+{
+ hmac_md5_vector(key, key_len, 1, &data, &data_len, mac);
+}
+
+
+#ifdef INTERNAL_MD5
+
+struct MD5Context {
+ u32 buf[4];
+ u32 bits[2];
+ u8 in[64];
+};
+
+#ifndef CONFIG_CRYPTO_INTERNAL
+static void MD5Init(struct MD5Context *context);
+static void MD5Update(struct MD5Context *context, unsigned char const *buf,
+ unsigned len);
+static void MD5Final(unsigned char digest[16], struct MD5Context *context);
+#endif /* CONFIG_CRYPTO_INTERNAL */
+static void MD5Transform(u32 buf[4], u32 const in[16]);
+
+
+typedef struct MD5Context MD5_CTX;
+
+
+/**
+ * md5_vector - MD5 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ */
+void md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac)
+{
+ MD5_CTX ctx;
+ size_t i;
+
+ MD5Init(&ctx);
+ for (i = 0; i < num_elem; i++)
+ MD5Update(&ctx, addr[i], len[i]);
+ MD5Final(mac, &ctx);
+}
+
+
+/* ===== start - public domain MD5 implementation ===== */
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#ifndef WORDS_BIGENDIAN
+#define byteReverse(buf, len) /* Nothing */
+#else
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse(unsigned char *buf, unsigned longs)
+{
+ u32 t;
+ do {
+ t = (u32) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(u32 *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+ u32 t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((u32) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ os_memcpy(p, buf, len);
+ return;
+ }
+ os_memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (u32 *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ os_memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (u32 *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ os_memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(unsigned char digest[16], struct MD5Context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ os_memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (u32 *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ os_memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ os_memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((u32 *) ctx->in)[14] = ctx->bits[0];
+ ((u32 *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (u32 *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ os_memcpy(digest, ctx->buf, 16);
+ os_memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
+}
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(u32 buf[4], u32 const in[16])
+{
+ register u32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+/* ===== end - public domain MD5 implementation ===== */
+
+#endif /* INTERNAL_MD5 */
diff --git a/extensions/app_radgw/md5.h b/extensions/app_radgw/md5.h
new file mode 100644
index 0000000..bcbfdc3
--- /dev/null
+++ b/extensions/app_radgw/md5.h
@@ -0,0 +1,46 @@
+/*********************************************************************************/
+/* freeDiameter author note:
+ * The content from this file comes directly from the hostap project.
+ * It is redistributed under the terms of the BSD license, as allowed
+ * by the original copyright reproduced below.
+ */
+
+/*********************************************************************************/
+
+/*
+ * MD5 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+#define MD5_MAC_LEN 16
+
+void hmac_md5_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac);
+void hmac_md5(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac);
+
+#ifdef CONFIG_CRYPTO_INTERNAL
+struct MD5Context;
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+ unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+#endif /* CONFIG_CRYPTO_INTERNAL */
+
+ /* Forward declaration: */
+void md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac);
+
+#endif /* MD5_H */
diff --git a/extensions/app_radgw/radius.c b/extensions/app_radgw/radius.c
new file mode 100644
index 0000000..44755eb
--- /dev/null
+++ b/extensions/app_radgw/radius.c
@@ -0,0 +1,1373 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* freeDiameter author note:
+ * The content from this file comes for the main part from the hostap project.
+ * It is redistributed under the terms of the BSD license, as allowed
+ * by the original copyright reproduced below.
+ * The modifications to this file are placed under the copyright of the freeDiameter project.
+ */
+
+/*
+ * hostapd / RADIUS message processing
+ * Copyright (c) 2002-2008, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+/*********************************************************************************/
+#include "rgw.h"
+
+static struct radius_attr_hdr *
+radius_get_attr_hdr(struct radius_msg *msg, int idx)
+{
+ return (struct radius_attr_hdr *) (msg->buf + msg->attr_pos[idx]);
+}
+
+
+struct radius_msg *radius_msg_new(u8 code, u8 identifier)
+{
+ struct radius_msg *msg;
+
+ msg = os_malloc(sizeof(*msg));
+ if (msg == NULL)
+ return NULL;
+
+ if (radius_msg_initialize(msg, RADIUS_DEFAULT_MSG_SIZE)) {
+ os_free(msg);
+ return NULL;
+ }
+
+ radius_msg_set_hdr(msg, code, identifier);
+
+ return msg;
+}
+
+
+int radius_msg_initialize(struct radius_msg *msg, size_t init_len)
+{
+ if (msg == NULL || init_len < sizeof(struct radius_hdr))
+ return -1;
+
+ os_memset(msg, 0, sizeof(*msg));
+ msg->buf = os_zalloc(init_len);
+ if (msg->buf == NULL)
+ return -1;
+
+ msg->buf_size = init_len;
+ msg->hdr = (struct radius_hdr *) msg->buf;
+ msg->buf_used = sizeof(*msg->hdr);
+
+ msg->attr_pos =
+ os_zalloc(RADIUS_DEFAULT_ATTR_COUNT * sizeof(*msg->attr_pos));
+ if (msg->attr_pos == NULL) {
+ os_free(msg->buf);
+ msg->buf = NULL;
+ msg->hdr = NULL;
+ return -1;
+ }
+
+ msg->attr_size = RADIUS_DEFAULT_ATTR_COUNT;
+ msg->attr_used = 0;
+
+ return 0;
+}
+
+
+void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier)
+{
+ msg->hdr->code = code;
+ msg->hdr->identifier = identifier;
+}
+
+
+void radius_msg_free(struct radius_msg *msg)
+{
+ os_free(msg->buf);
+ msg->buf = NULL;
+ msg->hdr = NULL;
+ msg->buf_size = msg->buf_used = 0;
+
+ os_free(msg->attr_pos);
+ msg->attr_pos = NULL;
+ msg->attr_size = msg->attr_used = 0;
+}
+
+/* Destroy a message */
+void rgw_msg_free(struct rgw_radius_msg_meta ** msg)
+{
+ if (!msg || !*msg)
+ return;
+
+ radius_msg_free(&(*msg)->radius);
+ free(*msg);
+ *msg = NULL;
+}
+
+
+
+struct radius_attr_type {
+ u8 type;
+ char *name;
+ enum {
+ RADIUS_ATTR_UNDIST, RADIUS_ATTR_TEXT, RADIUS_ATTR_IP,
+ RADIUS_ATTR_HEXDUMP, RADIUS_ATTR_INT32, RADIUS_ATTR_IPV6
+ } data_type;
+};
+
+static struct radius_attr_type radius_attrs[] =
+{
+ { RADIUS_ATTR_USER_NAME, "User-Name", RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_USER_PASSWORD, "User-Password", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_NAS_IP_ADDRESS, "NAS-IP-Address", RADIUS_ATTR_IP },
+ { RADIUS_ATTR_NAS_PORT, "NAS-Port", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_FRAMED_MTU, "Framed-MTU", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_REPLY_MESSAGE, "Reply-Message", RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_STATE, "State", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_CLASS, "Class", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_VENDOR_SPECIFIC, "Vendor-Specific", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_SESSION_TIMEOUT, "Session-Timeout", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_IDLE_TIMEOUT, "Idle-Timeout", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_TERMINATION_ACTION, "Termination-Action",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_CALLED_STATION_ID, "Called-Station-Id",
+ RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_CALLING_STATION_ID, "Calling-Station-Id",
+ RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_NAS_IDENTIFIER, "NAS-Identifier", RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_PROXY_STATE, "Proxy-State", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_ACCT_STATUS_TYPE, "Acct-Status-Type",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_DELAY_TIME, "Acct-Delay-Time", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_INPUT_OCTETS, "Acct-Input-Octets",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_OUTPUT_OCTETS, "Acct-Output-Octets",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_SESSION_ID, "Acct-Session-Id", RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_ACCT_AUTHENTIC, "Acct-Authentic", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_SESSION_TIME, "Acct-Session-Time",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_INPUT_PACKETS, "Acct-Input-Packets",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_OUTPUT_PACKETS, "Acct-Output-Packets",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_TERMINATE_CAUSE, "Acct-Terminate-Cause",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_MULTI_SESSION_ID, "Acct-Multi-Session-Id",
+ RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_ACCT_LINK_COUNT, "Acct-Link-Count", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_INPUT_GIGAWORDS, "Acct-Input-Gigawords",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS, "Acct-Output-Gigawords",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_EVENT_TIMESTAMP, "Event-Timestamp",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_NAS_PORT_TYPE, "NAS-Port-Type", RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP },
+ { RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type",
+ RADIUS_ATTR_HEXDUMP },
+ { RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator",
+ RADIUS_ATTR_UNDIST },
+ { RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, "Tunnel-Private-Group-Id",
+ RADIUS_ATTR_HEXDUMP },
+ { RADIUS_ATTR_ACCT_INTERIM_INTERVAL, "Acct-Interim-Interval",
+ RADIUS_ATTR_INT32 },
+ { RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargeable-User-Identity",
+ RADIUS_ATTR_TEXT },
+ { RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 },
+};
+#define RADIUS_ATTRS (sizeof(radius_attrs) / sizeof(radius_attrs[0]))
+
+
+static struct radius_attr_type *radius_get_attr_type(u8 type)
+{
+ size_t i;
+
+ for (i = 0; i < RADIUS_ATTRS; i++) {
+ if (type == radius_attrs[i].type)
+ return &radius_attrs[i];
+ }
+
+ return NULL;
+}
+
+static char print_char_buf[5];
+
+static char * print_char(char c)
+{
+ if (c >= 32 && c < 127)
+ sprintf(print_char_buf, "%c", c);
+ else
+ sprintf(print_char_buf, "<%02x>", c);
+ return print_char_buf;
+}
+
+
+static char * radius_msg_dump_attr_val(struct radius_attr_hdr *hdr, char * outbuf, size_t buflen)
+{
+ struct radius_attr_type *attr;
+ int i, len;
+ unsigned char *pos;
+ u8 attrtype;
+
+ memset(outbuf, 0, buflen);
+
+ attr = radius_get_attr_type(hdr->type);
+
+ if (attr == NULL)
+ attrtype = RADIUS_ATTR_HEXDUMP;
+ else
+ attrtype = attr->data_type;
+
+ len = hdr->length - sizeof(struct radius_attr_hdr);
+ pos = (unsigned char *) (hdr + 1);
+
+ switch (attrtype) {
+ case RADIUS_ATTR_TEXT:
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Value: '");
+ for (i = 0; i < len; i++)
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), "%s", print_char(pos[i]));
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), "'");
+ break;
+
+ case RADIUS_ATTR_IP:
+ if (len == 4) {
+ struct in_addr addr;
+ os_memcpy(&addr, pos, 4);
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Value: %s", inet_ntoa(addr));
+ } else
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Invalid IP address length %d", len);
+ break;
+
+ case RADIUS_ATTR_IPV6:
+ if (len == 16) {
+ char buf[128];
+ const char *atxt;
+ struct in6_addr *addr = (struct in6_addr *) pos;
+ atxt = inet_ntop(AF_INET6, addr, buf, sizeof(buf));
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Value: %s", atxt ? atxt : "?");
+ } else
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Invalid IPv6 address length %d", len);
+ break;
+
+ case RADIUS_ATTR_INT32:
+ if (len == 4)
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Value: %u", WPA_GET_BE32(pos));
+ else
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Invalid INT32 length %d", len);
+ break;
+
+ case RADIUS_ATTR_HEXDUMP:
+ case RADIUS_ATTR_UNDIST:
+ default:
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " Value:");
+ for (i = 0; i < len; i++)
+ snprintf(outbuf + strlen(outbuf), buflen - strlen(outbuf), " %02x", pos[i]);
+ break;
+ }
+
+ return outbuf;
+}
+
+/* Dump a message */
+void rgw_msg_dump(struct rgw_radius_msg_meta * msg, int has_meta)
+{
+ unsigned char *auth;
+ char buf[256];
+ size_t i;
+ if (! TRACE_BOOL(FULL) )
+ return;
+
+ auth = &(msg->radius.hdr->authenticator[0]);
+
+ fd_log_debug("------ RADIUS msg dump -------");
+ fd_log_debug(" id : 0x%02hhx, code : %hhd (%s), length : %d", msg->radius.hdr->identifier, msg->radius.hdr->code, rgw_msg_code_str(msg->radius.hdr->code), ntohs(msg->radius.hdr->length));
+ fd_log_debug(" auth: %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
+ auth[0], auth[1], auth[2], auth[3], auth[4], auth[5], auth[6], auth[7]);
+ fd_log_debug(" %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
+ auth[8], auth[9], auth[10], auth[11], auth[12], auth[13], auth[14], auth[15]);
+ for (i = 0; i < msg->radius.attr_used; i++) {
+ struct radius_attr_hdr *attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[i]);
+ fd_log_debug(" - Type: 0x%02hhx (%s) Len: %-3hhu", attr->type, rgw_msg_attrtype_str(attr->type), attr->length);
+ fd_log_debug("%s", radius_msg_dump_attr_val(attr, buf, sizeof(buf)));
+ }
+ if (has_meta && msg->ps_nb) {
+ fd_log_debug("---- hidden attributes:");
+ for (i = msg->ps_first; i < msg->ps_first + msg->ps_nb; i++) {
+ struct radius_attr_hdr *attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[i]);
+ fd_log_debug(" - Type: 0x%02hhx (%s) Len: %-3hhu", attr->type, rgw_msg_attrtype_str(attr->type), attr->length);
+ fd_log_debug("%s", radius_msg_dump_attr_val(attr, buf, sizeof(buf)));
+ }
+ }
+ fd_log_debug("-----------------------------");
+}
+
+
+int radius_msg_finish(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len)
+{
+ if (secret) {
+ u8 auth[MD5_MAC_LEN];
+ struct radius_attr_hdr *attr;
+
+ os_memset(auth, 0, MD5_MAC_LEN);
+ attr = radius_msg_add_attr(msg,
+ RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
+ auth, MD5_MAC_LEN);
+ if (attr == NULL) {
+ fd_log_debug("WARNING: Could not add Message-Authenticator");
+ return -1;
+ }
+ msg->hdr->length = htons(msg->buf_used);
+ hmac_md5(secret, secret_len, msg->buf, msg->buf_used,
+ (u8 *) (attr + 1));
+ } else
+ msg->hdr->length = htons(msg->buf_used);
+
+ if (msg->buf_used > 0xffff) {
+ fd_log_debug("WARNING: too long RADIUS message (%lu)",
+ (unsigned long) msg->buf_used);
+ return -1;
+ }
+ return 0;
+}
+
+
+int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, const u8 *req_authenticator)
+{
+ u8 auth[MD5_MAC_LEN];
+ struct radius_attr_hdr *attr;
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (msg->hdr->code != RADIUS_CODE_ACCOUNTING_RESPONSE) {
+ os_memset(auth, 0, MD5_MAC_LEN);
+ attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
+ auth, MD5_MAC_LEN);
+ if (attr == NULL) {
+ fd_log_debug("WARNING: Could not add Message-Authenticator");
+ return -1;
+ }
+ msg->hdr->length = htons(msg->buf_used);
+ os_memcpy(msg->hdr->authenticator, req_authenticator,
+ sizeof(msg->hdr->authenticator));
+ hmac_md5(secret, secret_len, msg->buf, msg->buf_used,
+ (u8 *) (attr + 1));
+ } else {
+ msg->hdr->length = htons(msg->buf_used);
+ }
+
+ /* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
+ addr[0] = (u8 *) msg->hdr;
+ len[0] = 1 + 1 + 2;
+ addr[1] = req_authenticator;
+ len[1] = MD5_MAC_LEN;
+ addr[2] = (u8 *) (msg->hdr + 1);
+ len[2] = msg->buf_used - sizeof(*msg->hdr);
+ addr[3] = secret;
+ len[3] = secret_len;
+ md5_vector(4, addr, len, msg->hdr->authenticator);
+
+ if (msg->buf_used > 0xffff) {
+ fd_log_debug("WARNING: too long RADIUS message (%lu)",
+ (unsigned long) msg->buf_used);
+ return -1;
+ }
+ return 0;
+}
+
+
+void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len)
+{
+ const u8 *addr[2];
+ size_t len[2];
+
+ msg->hdr->length = htons(msg->buf_used);
+ os_memset(msg->hdr->authenticator, 0, MD5_MAC_LEN);
+ addr[0] = msg->buf;
+ len[0] = msg->buf_used;
+ addr[1] = secret;
+ len[1] = secret_len;
+ md5_vector(2, addr, len, msg->hdr->authenticator);
+
+ if (msg->buf_used > 0xffff) {
+ fd_log_debug("WARNING: too long RADIUS messages (%lu)",
+ (unsigned long) msg->buf_used);
+ }
+}
+
+
+int radius_msg_add_attr_to_array(struct radius_msg *msg,
+ struct radius_attr_hdr *attr)
+{
+ if (msg->attr_used >= msg->attr_size) {
+ size_t *nattr_pos;
+ int nlen = msg->attr_size * 2;
+
+ nattr_pos = os_realloc(msg->attr_pos,
+ nlen * sizeof(*msg->attr_pos));
+ if (nattr_pos == NULL)
+ return -1;
+
+ msg->attr_pos = nattr_pos;
+ msg->attr_size = nlen;
+ }
+
+ msg->attr_pos[msg->attr_used++] = (unsigned char *) attr - msg->buf;
+
+ return 0;
+}
+
+
+struct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
+ const u8 *data, size_t data_len)
+{
+ size_t buf_needed;
+ struct radius_attr_hdr *attr;
+
+ if (data_len > RADIUS_MAX_ATTR_LEN) {
+ fd_log_debug("radius_msg_add_attr: too long attribute (%lu bytes)",
+ (unsigned long) data_len);
+ return NULL;
+ }
+
+ buf_needed = msg->buf_used + sizeof(*attr) + data_len;
+
+ if (msg->buf_size < buf_needed) {
+ /* allocate more space for message buffer */
+ unsigned char *nbuf;
+ size_t nlen = msg->buf_size;
+
+ while (nlen < buf_needed)
+ nlen *= 2;
+ nbuf = os_realloc(msg->buf, nlen);
+ if (nbuf == NULL)
+ return NULL;
+ msg->buf = nbuf;
+ msg->hdr = (struct radius_hdr *) msg->buf;
+ os_memset(msg->buf + msg->buf_size, 0, nlen - msg->buf_size);
+ msg->buf_size = nlen;
+ }
+
+ attr = (struct radius_attr_hdr *) (msg->buf + msg->buf_used);
+ attr->type = type;
+ attr->length = sizeof(*attr) + data_len;
+ if (data_len > 0)
+ os_memcpy(attr + 1, data, data_len);
+
+ msg->buf_used += sizeof(*attr) + data_len;
+
+ if (radius_msg_add_attr_to_array(msg, attr))
+ return NULL;
+
+ return attr;
+}
+
+
+/* Modified version of radius_msg_parse */
+int rgw_msg_parse(unsigned char * buf, size_t len, struct rgw_radius_msg_meta ** msg)
+{
+ struct rgw_radius_msg_meta * temp_msg = NULL;
+ struct radius_hdr *hdr;
+ struct radius_attr_hdr *attr;
+ size_t msg_len;
+ unsigned char *pos, *end;
+ int ret = 0;
+
+ TRACE_ENTRY("%p %zd %p", buf, len, msg);
+
+ CHECK_PARAMS( buf && len >= sizeof(*hdr) && msg );
+
+ *msg = NULL;
+
+ /* Parse the RADIUS message */
+ hdr = (struct radius_hdr *) buf;
+ msg_len = ntohs(hdr->length);
+ if (msg_len < sizeof(*hdr) || msg_len > len) {
+ TRACE_DEBUG(INFO, "Invalid RADIUS message length");
+ return EINVAL;
+ }
+
+ if (msg_len < len) {
+ TRACE_DEBUG(INFO, "Ignored %lu extra bytes after RADIUS message",
+ (unsigned long) len - msg_len);
+ }
+
+ CHECK_MALLOC( temp_msg = malloc(sizeof(struct rgw_radius_msg_meta)) );
+ memset(temp_msg, 0, sizeof(struct rgw_radius_msg_meta));
+
+ if (radius_msg_initialize(&temp_msg->radius, msg_len)) {
+ TRACE_DEBUG(INFO, "Error in radius_msg_initialize, returning ENOMEM.");
+ free(temp_msg);
+ return ENOMEM;
+ }
+
+ /* Store the received data in the alloc'd buffer */
+ memcpy(temp_msg->radius.buf, buf, msg_len);
+ temp_msg->radius.buf_size = temp_msg->radius.buf_used = msg_len;
+
+ /* parse attributes */
+ pos = (unsigned char *) (temp_msg->radius.hdr + 1);
+ end = temp_msg->radius.buf + temp_msg->radius.buf_used;
+
+ while (pos < end) {
+ if ((size_t) (end - pos) < sizeof(*attr)) {
+ TRACE_DEBUG(INFO, "Trucated attribute found in RADIUS buffer, EINVAL.");
+ ret = EINVAL;
+ break;
+ }
+
+ attr = (struct radius_attr_hdr *) pos;
+
+ if (pos + attr->length > end || attr->length < sizeof(*attr)) {
+ TRACE_DEBUG(INFO, "Trucated attribute found in RADIUS buffer, EINVAL.");
+ ret = EINVAL;
+ break;
+ }
+
+ if (radius_msg_add_attr_to_array(&temp_msg->radius, attr)) {
+ TRACE_DEBUG(INFO, "Error in radius_msg_add_attr_to_array, ENOMEM");
+ ret = ENOMEM;
+ break;
+ }
+
+ if (attr->type == RADIUS_ATTR_PROXY_STATE)
+ temp_msg->ps_nb += 1;
+
+ pos += attr->length;
+ }
+
+ if (ret != 0) {
+ radius_msg_free(&temp_msg->radius);
+ free(temp_msg);
+ return ret;
+ }
+
+ /* Now move all the proxy-state attributes at the end of the attr_pos array */
+ if (temp_msg->ps_nb) {
+ size_t *temp_ps = NULL;
+ int n, new_n = 0, p = 0;
+
+ CHECK_MALLOC( temp_ps = calloc(temp_msg->ps_nb, sizeof(size_t)) );
+
+ /* Move all the Proxy-State attributes into the temp_ps array */
+ for (n=0; n < temp_msg->radius.attr_used; n++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(temp_msg->radius.buf + temp_msg->radius.attr_pos[n]);
+
+ if (attr->type == RADIUS_ATTR_PROXY_STATE) {
+ temp_ps[p++] = temp_msg->radius.attr_pos[n];
+ } else {
+ temp_msg->radius.attr_pos[new_n++] = temp_msg->radius.attr_pos[n];
+ }
+ }
+ temp_msg->radius.attr_used = new_n; /* hide the proxy-state to other modules */
+ temp_msg->ps_first = new_n;
+
+ /* And back into the array */
+ memcpy(temp_msg->radius.attr_pos + new_n, temp_ps, p * sizeof(size_t));
+ free(temp_ps);
+ }
+
+ *msg = temp_msg;
+ return 0;
+}
+
+
+
+int radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len)
+{
+ const u8 *pos = data;
+ size_t left = data_len;
+
+ while (left > 0) {
+ int len;
+ if (left > RADIUS_MAX_ATTR_LEN)
+ len = RADIUS_MAX_ATTR_LEN;
+ else
+ len = left;
+
+ if (!radius_msg_add_attr(msg, RADIUS_ATTR_EAP_MESSAGE,
+ pos, len))
+ return 0;
+
+ pos += len;
+ left -= len;
+ }
+
+ return 1;
+}
+
+
+u8 *radius_msg_get_eap(struct radius_msg *msg, size_t *eap_len)
+{
+ u8 *eap, *pos;
+ size_t len, i;
+ struct radius_attr_hdr *attr;
+
+ if (msg == NULL)
+ return NULL;
+
+ len = 0;
+ for (i = 0; i < msg->attr_used; i++) {
+ attr = radius_get_attr_hdr(msg, i);
+ if (attr->type == RADIUS_ATTR_EAP_MESSAGE)
+ len += attr->length - sizeof(struct radius_attr_hdr);
+ }
+
+ if (len == 0)
+ return NULL;
+
+ eap = os_malloc(len);
+ if (eap == NULL)
+ return NULL;
+
+ pos = eap;
+ for (i = 0; i < msg->attr_used; i++) {
+ attr = radius_get_attr_hdr(msg, i);
+ if (attr->type == RADIUS_ATTR_EAP_MESSAGE) {
+ int flen = attr->length - sizeof(*attr);
+ os_memcpy(pos, attr + 1, flen);
+ pos += flen;
+ }
+ }
+
+ if (eap_len)
+ *eap_len = len;
+
+ return eap;
+}
+
+
+int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, const u8 *req_auth)
+{
+ u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN];
+ u8 orig_authenticator[16];
+ struct radius_attr_hdr *attr = NULL, *tmp;
+ size_t i;
+
+ for (i = 0; i < msg->attr_used; i++) {
+ tmp = radius_get_attr_hdr(msg, i);
+ if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) {
+ if (attr != NULL) {
+ fd_log_debug("Multiple Message-Authenticator attributes in RADIUS message");
+ return 1;
+ }
+ attr = tmp;
+ }
+ }
+
+ if (attr == NULL) {
+ fd_log_debug("No Message-Authenticator attribute found");
+ return 1;
+ }
+
+ os_memcpy(orig, attr + 1, MD5_MAC_LEN);
+ os_memset(attr + 1, 0, MD5_MAC_LEN);
+ if (req_auth) {
+ os_memcpy(orig_authenticator, msg->hdr->authenticator,
+ sizeof(orig_authenticator));
+ os_memcpy(msg->hdr->authenticator, req_auth,
+ sizeof(msg->hdr->authenticator));
+ }
+ hmac_md5(secret, secret_len, msg->buf, msg->buf_used, auth);
+ os_memcpy(attr + 1, orig, MD5_MAC_LEN);
+ if (req_auth) {
+ os_memcpy(msg->hdr->authenticator, orig_authenticator,
+ sizeof(orig_authenticator));
+ }
+
+ if (os_memcmp(orig, auth, MD5_MAC_LEN) != 0) {
+ fd_log_debug("Invalid Message-Authenticator!");
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int radius_msg_verify(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, struct radius_msg *sent_msg, int auth)
+{
+ const u8 *addr[4];
+ size_t len[4];
+ u8 hash[MD5_MAC_LEN];
+
+ if (sent_msg == NULL) {
+ fd_log_debug("No matching Access-Request message found");
+ return 1;
+ }
+
+ if (auth &&
+ radius_msg_verify_msg_auth(msg, secret, secret_len,
+ sent_msg->hdr->authenticator)) {
+ return 1;
+ }
+
+ /* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
+ addr[0] = (u8 *) msg->hdr;
+ len[0] = 1 + 1 + 2;
+ addr[1] = sent_msg->hdr->authenticator;
+ len[1] = MD5_MAC_LEN;
+ addr[2] = (u8 *) (msg->hdr + 1);
+ len[2] = msg->buf_used - sizeof(*msg->hdr);
+ addr[3] = secret;
+ len[3] = secret_len;
+ md5_vector(4, addr, len, hash);
+ if (os_memcmp(hash, msg->hdr->authenticator, MD5_MAC_LEN) != 0) {
+ fd_log_debug("Response Authenticator invalid!");
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src,
+ u8 type)
+{
+ struct radius_attr_hdr *attr;
+ size_t i;
+ int count = 0;
+
+ for (i = 0; i < src->attr_used; i++) {
+ attr = radius_get_attr_hdr(src, i);
+ if (attr->type == type) {
+ if (!radius_msg_add_attr(dst, type, (u8 *) (attr + 1),
+ attr->length - sizeof(*attr)))
+ return -1;
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+/* Create Request Authenticator. The value should be unique over the lifetime
+ * of the shared secret between authenticator and authentication server.
+ * Use one-way MD5 hash calculated from current timestamp and some data given
+ * by the caller. */
+void radius_msg_make_authenticator(struct radius_msg *msg,
+ const u8 *data, size_t len)
+{
+ struct os_time tv;
+ long int l;
+ const u8 *addr[3];
+ size_t elen[3];
+
+ os_get_time(&tv);
+ l = os_random();
+ addr[0] = (u8 *) &tv;
+ elen[0] = sizeof(tv);
+ addr[1] = data;
+ elen[1] = len;
+ addr[2] = (u8 *) &l;
+ elen[2] = sizeof(l);
+ md5_vector(3, addr, elen, msg->hdr->authenticator);
+}
+
+
+/* Get Vendor-specific RADIUS Attribute from a parsed RADIUS message.
+ * Returns the Attribute payload and sets alen to indicate the length of the
+ * payload if a vendor attribute with subtype is found, otherwise returns NULL.
+ * The returned payload is allocated with os_malloc() and caller must free it
+ * by calling os_free().
+ */
+static u8 *radius_msg_get_vendor_attr(struct radius_msg *msg, u32 vendor,
+ u8 subtype, size_t *alen)
+{
+ u8 *data, *pos;
+ size_t i, len;
+
+ if (msg == NULL)
+ return NULL;
+
+ for (i = 0; i < msg->attr_used; i++) {
+ struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
+ size_t left;
+ u32 vendor_id;
+ struct radius_attr_vendor *vhdr;
+
+ if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC)
+ continue;
+
+ left = attr->length - sizeof(*attr);
+ if (left < 4)
+ continue;
+
+ pos = (u8 *) (attr + 1);
+
+ os_memcpy(&vendor_id, pos, 4);
+ pos += 4;
+ left -= 4;
+
+ if (ntohl(vendor_id) != vendor)
+ continue;
+
+ while (left >= sizeof(*vhdr)) {
+ vhdr = (struct radius_attr_vendor *) pos;
+ if (vhdr->vendor_length > left ||
+ vhdr->vendor_length < sizeof(*vhdr)) {
+ left = 0;
+ break;
+ }
+ if (vhdr->vendor_type != subtype) {
+ pos += vhdr->vendor_length;
+ left -= vhdr->vendor_length;
+ continue;
+ }
+
+ len = vhdr->vendor_length - sizeof(*vhdr);
+ data = os_malloc(len);
+ if (data == NULL)
+ return NULL;
+ os_memcpy(data, pos + sizeof(*vhdr), len);
+ if (alen)
+ *alen = len;
+ return data;
+ }
+ }
+
+ return NULL;
+}
+
+
+static u8 * decrypt_ms_key(const u8 *key, size_t len,
+ const u8 *req_authenticator,
+ const u8 *secret, size_t secret_len, size_t *reslen)
+{
+ u8 *plain, *ppos, *res;
+ const u8 *pos;
+ size_t left, plen;
+ u8 hash[MD5_MAC_LEN];
+ int i, first = 1;
+ const u8 *addr[3];
+ size_t elen[3];
+
+ /* key: 16-bit salt followed by encrypted key info */
+
+ if (len < 2 + 16)
+ return NULL;
+
+ pos = key + 2;
+ left = len - 2;
+ if (left % 16) {
+ fd_log_debug("Invalid ms key len %lu", (unsigned long) left);
+ return NULL;
+ }
+
+ plen = left;
+ ppos = plain = os_malloc(plen);
+ if (plain == NULL)
+ return NULL;
+ plain[0] = 0;
+
+ while (left > 0) {
+ /* b(1) = MD5(Secret + Request-Authenticator + Salt)
+ * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
+
+ addr[0] = secret;
+ elen[0] = secret_len;
+ if (first) {
+ addr[1] = req_authenticator;
+ elen[1] = MD5_MAC_LEN;
+ addr[2] = key;
+ elen[2] = 2; /* Salt */
+ } else {
+ addr[1] = pos - MD5_MAC_LEN;
+ elen[1] = MD5_MAC_LEN;
+ }
+ md5_vector(first ? 3 : 2, addr, elen, hash);
+ first = 0;
+
+ for (i = 0; i < MD5_MAC_LEN; i++)
+ *ppos++ = *pos++ ^ hash[i];
+ left -= MD5_MAC_LEN;
+ }
+
+ if (plain[0] == 0 || plain[0] > plen - 1) {
+ fd_log_debug("Failed to decrypt MPPE key");
+ os_free(plain);
+ return NULL;
+ }
+
+ res = os_malloc(plain[0]);
+ if (res == NULL) {
+ os_free(plain);
+ return NULL;
+ }
+ os_memcpy(res, plain + 1, plain[0]);
+ if (reslen)
+ *reslen = plain[0];
+ os_free(plain);
+ return res;
+}
+
+
+static void encrypt_ms_key(const u8 *key, size_t key_len, u16 salt,
+ const u8 *req_authenticator,
+ const u8 *secret, size_t secret_len,
+ u8 *ebuf, size_t *elen)
+{
+ int i, len, first = 1;
+ u8 hash[MD5_MAC_LEN], saltbuf[2], *pos;
+ const u8 *addr[3];
+ size_t _len[3];
+
+ WPA_PUT_BE16(saltbuf, salt);
+
+ len = 1 + key_len;
+ if (len & 0x0f) {
+ len = (len & 0xf0) + 16;
+ }
+ os_memset(ebuf, 0, len);
+ ebuf[0] = key_len;
+ os_memcpy(ebuf + 1, key, key_len);
+
+ *elen = len;
+
+ pos = ebuf;
+ while (len > 0) {
+ /* b(1) = MD5(Secret + Request-Authenticator + Salt)
+ * b(i) = MD5(Secret + c(i - 1)) for i > 1 */
+ addr[0] = secret;
+ _len[0] = secret_len;
+ if (first) {
+ addr[1] = req_authenticator;
+ _len[1] = MD5_MAC_LEN;
+ addr[2] = saltbuf;
+ _len[2] = sizeof(saltbuf);
+ } else {
+ addr[1] = pos - MD5_MAC_LEN;
+ _len[1] = MD5_MAC_LEN;
+ }
+ md5_vector(first ? 3 : 2, addr, _len, hash);
+ first = 0;
+
+ for (i = 0; i < MD5_MAC_LEN; i++)
+ *pos++ ^= hash[i];
+
+ len -= MD5_MAC_LEN;
+ }
+}
+
+
+struct radius_ms_mppe_keys *
+radius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
+ const u8 *secret, size_t secret_len)
+{
+ u8 *key;
+ size_t keylen;
+ struct radius_ms_mppe_keys *keys;
+
+ if (msg == NULL || sent_msg == NULL)
+ return NULL;
+
+ keys = os_zalloc(sizeof(*keys));
+ if (keys == NULL)
+ return NULL;
+
+ key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
+ RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY,
+ &keylen);
+ if (key) {
+ keys->send = decrypt_ms_key(key, keylen,
+ sent_msg->hdr->authenticator,
+ secret, secret_len,
+ &keys->send_len);
+ os_free(key);
+ }
+
+ key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_MICROSOFT,
+ RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY,
+ &keylen);
+ if (key) {
+ keys->recv = decrypt_ms_key(key, keylen,
+ sent_msg->hdr->authenticator,
+ secret, secret_len,
+ &keys->recv_len);
+ os_free(key);
+ }
+
+ return keys;
+}
+
+
+struct radius_ms_mppe_keys *
+radius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
+ const u8 *secret, size_t secret_len)
+{
+ u8 *key;
+ size_t keylen;
+ struct radius_ms_mppe_keys *keys;
+
+ if (msg == NULL || sent_msg == NULL)
+ return NULL;
+
+ keys = os_zalloc(sizeof(*keys));
+ if (keys == NULL)
+ return NULL;
+
+ key = radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_CISCO,
+ RADIUS_CISCO_AV_PAIR, &keylen);
+ if (key && keylen == 51 &&
+ os_memcmp(key, "leap:session-key=", 17) == 0) {
+ keys->recv = decrypt_ms_key(key + 17, keylen - 17,
+ sent_msg->hdr->authenticator,
+ secret, secret_len,
+ &keys->recv_len);
+ }
+ os_free(key);
+
+ return keys;
+}
+
+
+int radius_msg_add_mppe_keys(struct radius_msg *msg,
+ const u8 *req_authenticator,
+ const u8 *secret, size_t secret_len,
+ const u8 *send_key, size_t send_key_len,
+ const u8 *recv_key, size_t recv_key_len)
+{
+ struct radius_attr_hdr *attr;
+ u32 vendor_id = htonl(RADIUS_VENDOR_ID_MICROSOFT);
+ u8 *buf;
+ struct radius_attr_vendor *vhdr;
+ u8 *pos;
+ size_t elen;
+ int hlen;
+ u16 salt;
+
+ hlen = sizeof(vendor_id) + sizeof(*vhdr) + 2;
+
+ /* MS-MPPE-Send-Key */
+ buf = os_malloc(hlen + send_key_len + 16);
+ if (buf == NULL) {
+ return 0;
+ }
+ pos = buf;
+ os_memcpy(pos, &vendor_id, sizeof(vendor_id));
+ pos += sizeof(vendor_id);
+ vhdr = (struct radius_attr_vendor *) pos;
+ vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY;
+ pos = (u8 *) (vhdr + 1);
+ salt = os_random() | 0x8000;
+ WPA_PUT_BE16(pos, salt);
+ pos += 2;
+ encrypt_ms_key(send_key, send_key_len, salt, req_authenticator, secret,
+ secret_len, pos, &elen);
+ vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
+
+ attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
+ buf, hlen + elen);
+ os_free(buf);
+ if (attr == NULL) {
+ return 0;
+ }
+
+ /* MS-MPPE-Recv-Key */
+ buf = os_malloc(hlen + send_key_len + 16);
+ if (buf == NULL) {
+ return 0;
+ }
+ pos = buf;
+ os_memcpy(pos, &vendor_id, sizeof(vendor_id));
+ pos += sizeof(vendor_id);
+ vhdr = (struct radius_attr_vendor *) pos;
+ vhdr->vendor_type = RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY;
+ pos = (u8 *) (vhdr + 1);
+ salt ^= 1;
+ WPA_PUT_BE16(pos, salt);
+ pos += 2;
+ encrypt_ms_key(recv_key, recv_key_len, salt, req_authenticator, secret,
+ secret_len, pos, &elen);
+ vhdr->vendor_length = hlen + elen - sizeof(vendor_id);
+
+ attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC,
+ buf, hlen + elen);
+ os_free(buf);
+ if (attr == NULL) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* Add User-Password attribute to a RADIUS message and encrypt it as specified
+ * in RFC 2865, Chap. 5.2 */
+struct radius_attr_hdr *
+radius_msg_add_attr_user_password(struct radius_msg *msg,
+ const u8 *data, size_t data_len,
+ const u8 *secret, size_t secret_len)
+{
+ u8 buf[128];
+ int padlen, i;
+ size_t buf_len, pos;
+ const u8 *addr[2];
+ size_t len[2];
+ u8 hash[16];
+
+ if (data_len > 128)
+ return NULL;
+
+ os_memcpy(buf, data, data_len);
+ buf_len = data_len;
+
+ padlen = data_len % 16;
+ if (padlen) {
+ padlen = 16 - padlen;
+ os_memset(buf + data_len, 0, padlen);
+ buf_len += padlen;
+ }
+
+ addr[0] = secret;
+ len[0] = secret_len;
+ addr[1] = msg->hdr->authenticator;
+ len[1] = 16;
+ md5_vector(2, addr, len, hash);
+
+ for (i = 0; i < 16; i++)
+ buf[i] ^= hash[i];
+ pos = 16;
+
+ while (pos < buf_len) {
+ addr[0] = secret;
+ len[0] = secret_len;
+ addr[1] = &buf[pos - 16];
+ len[1] = 16;
+ md5_vector(2, addr, len, hash);
+
+ for (i = 0; i < 16; i++)
+ buf[pos + i] ^= hash[i];
+
+ pos += 16;
+ }
+
+ return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD,
+ buf, buf_len);
+}
+
+
+int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len)
+{
+ struct radius_attr_hdr *attr = NULL, *tmp;
+ size_t i, dlen;
+
+ for (i = 0; i < msg->attr_used; i++) {
+ tmp = radius_get_attr_hdr(msg, i);
+ if (tmp->type == type) {
+ attr = tmp;
+ break;
+ }
+ }
+
+ if (!attr)
+ return -1;
+
+ dlen = attr->length - sizeof(*attr);
+ if (buf)
+ os_memcpy(buf, (attr + 1), dlen > len ? len : dlen);
+ return dlen;
+}
+
+
+int radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf,
+ size_t *len, const u8 *start)
+{
+ size_t i;
+ struct radius_attr_hdr *attr = NULL, *tmp;
+
+ for (i = 0; i < msg->attr_used; i++) {
+ tmp = radius_get_attr_hdr(msg, i);
+ if (tmp->type == type &&
+ (start == NULL || (u8 *) tmp > start)) {
+ attr = tmp;
+ break;
+ }
+ }
+
+ if (!attr)
+ return -1;
+
+ *buf = (u8 *) (attr + 1);
+ *len = attr->length - sizeof(*attr);
+ return 0;
+}
+
+
+int radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len)
+{
+ size_t i;
+ int count;
+
+ for (count = 0, i = 0; i < msg->attr_used; i++) {
+ struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i);
+ if (attr->type == type &&
+ attr->length >= sizeof(struct radius_attr_hdr) + min_len)
+ count++;
+ }
+
+ return count;
+}
+
+
+struct radius_tunnel_attrs {
+ int tag_used;
+ int type; /* Tunnel-Type */
+ int medium_type; /* Tunnel-Medium-Type */
+ int vlanid;
+};
+
+
+/**
+ * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information
+ * @msg: RADIUS message
+ * Returns: VLAN ID for the first tunnel configuration of -1 if none is found
+ */
+int radius_msg_get_vlanid(struct radius_msg *msg)
+{
+ struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun;
+ size_t i;
+ struct radius_attr_hdr *attr = NULL;
+ const u8 *data;
+ char buf[10];
+ size_t dlen;
+
+ os_memset(&tunnel, 0, sizeof(tunnel));
+
+ for (i = 0; i < msg->attr_used; i++) {
+ attr = radius_get_attr_hdr(msg, i);
+ data = (const u8 *) (attr + 1);
+ dlen = attr->length - sizeof(*attr);
+ if (attr->length < 3)
+ continue;
+ if (data[0] >= RADIUS_TUNNEL_TAGS)
+ tun = &tunnel[0];
+ else
+ tun = &tunnel[data[0]];
+
+ switch (attr->type) {
+ case RADIUS_ATTR_TUNNEL_TYPE:
+ if (attr->length != 6)
+ break;
+ tun->tag_used++;
+ tun->type = WPA_GET_BE24(data + 1);
+ break;
+ case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE:
+ if (attr->length != 6)
+ break;
+ tun->tag_used++;
+ tun->medium_type = WPA_GET_BE24(data + 1);
+ break;
+ case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID:
+ if (data[0] < RADIUS_TUNNEL_TAGS) {
+ data++;
+ dlen--;
+ }
+ if (dlen >= sizeof(buf))
+ break;
+ os_memcpy(buf, data, dlen);
+ buf[dlen] = '\0';
+ tun->tag_used++;
+ tun->vlanid = atoi(buf);
+ break;
+ }
+ }
+
+ for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) {
+ tun = &tunnel[i];
+ if (tun->tag_used &&
+ tun->type == RADIUS_TUNNEL_TYPE_VLAN &&
+ tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 &&
+ tun->vlanid > 0)
+ return tun->vlanid;
+ }
+
+ return -1;
+}
+
+
+void radius_free_class(struct radius_class_data *c)
+{
+ size_t i;
+ if (c == NULL)
+ return;
+ for (i = 0; i < c->count; i++)
+ os_free(c->attr[i].data);
+ os_free(c->attr);
+ c->attr = NULL;
+ c->count = 0;
+}
+
+
+int radius_copy_class(struct radius_class_data *dst,
+ const struct radius_class_data *src)
+{
+ size_t i;
+
+ if (src->attr == NULL)
+ return 0;
+
+ dst->attr = os_zalloc(src->count * sizeof(struct radius_attr_data));
+ if (dst->attr == NULL)
+ return -1;
+
+ dst->count = 0;
+
+ for (i = 0; i < src->count; i++) {
+ dst->attr[i].data = os_malloc(src->attr[i].len);
+ if (dst->attr[i].data == NULL)
+ break;
+ dst->count++;
+ os_memcpy(dst->attr[i].data, src->attr[i].data,
+ src->attr[i].len);
+ dst->attr[i].len = src->attr[i].len;
+ }
+
+ return 0;
+}
diff --git a/extensions/app_radgw/radius.h b/extensions/app_radgw/radius.h
new file mode 100644
index 0000000..c278472
--- /dev/null
+++ b/extensions/app_radgw/radius.h
@@ -0,0 +1,331 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/*********************************************************************************/
+/* freeDiameter author note:
+ * The content from this file comes mostly from the hostap project.
+ * It is redistributed under the terms of the BSD license, as allowed
+ * by the original copyright reproduced below.
+ * The changes to this file are placed under the copyright of the freeDiameter project.
+ */
+
+/*
+ * hostapd / RADIUS message processing
+ * Copyright (c) 2002-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+/*********************************************************************************/
+
+
+#ifndef RADIUS_H
+#define RADIUS_H
+
+/* RFC 2865 - RADIUS */
+
+#ifdef _MSC_VER
+#pragma pack(push, 1)
+#endif /* _MSC_VER */
+
+struct radius_hdr {
+ u8 code;
+ u8 identifier;
+ u16 length; /* including this header */
+ u8 authenticator[16];
+ /* followed by length-20 octets of attributes */
+} STRUCT_PACKED;
+
+enum { RADIUS_CODE_ACCESS_REQUEST = 1,
+ RADIUS_CODE_ACCESS_ACCEPT = 2,
+ RADIUS_CODE_ACCESS_REJECT = 3,
+ RADIUS_CODE_ACCOUNTING_REQUEST = 4,
+ RADIUS_CODE_ACCOUNTING_RESPONSE = 5,
+ RADIUS_CODE_ACCESS_CHALLENGE = 11,
+ RADIUS_CODE_STATUS_SERVER = 12,
+ RADIUS_CODE_STATUS_CLIENT = 13,
+ RADIUS_CODE_RESERVED = 255
+};
+
+struct radius_attr_hdr {
+ u8 type;
+ u8 length; /* including this header */
+ /* followed by length-2 octets of attribute value */
+} STRUCT_PACKED;
+
+#define RADIUS_MAX_ATTR_LEN (255 - sizeof(struct radius_attr_hdr))
+
+enum { RADIUS_ATTR_USER_NAME = 1,
+ RADIUS_ATTR_USER_PASSWORD = 2,
+ RADIUS_ATTR_NAS_IP_ADDRESS = 4,
+ RADIUS_ATTR_NAS_PORT = 5,
+ RADIUS_ATTR_FRAMED_MTU = 12,
+ RADIUS_ATTR_REPLY_MESSAGE = 18,
+ RADIUS_ATTR_STATE = 24,
+ RADIUS_ATTR_CLASS = 25,
+ RADIUS_ATTR_VENDOR_SPECIFIC = 26,
+ RADIUS_ATTR_SESSION_TIMEOUT = 27,
+ RADIUS_ATTR_IDLE_TIMEOUT = 28,
+ RADIUS_ATTR_TERMINATION_ACTION = 29,
+ RADIUS_ATTR_CALLED_STATION_ID = 30,
+ RADIUS_ATTR_CALLING_STATION_ID = 31,
+ RADIUS_ATTR_NAS_IDENTIFIER = 32,
+ RADIUS_ATTR_PROXY_STATE = 33,
+ RADIUS_ATTR_ACCT_STATUS_TYPE = 40,
+ RADIUS_ATTR_ACCT_DELAY_TIME = 41,
+ RADIUS_ATTR_ACCT_INPUT_OCTETS = 42,
+ RADIUS_ATTR_ACCT_OUTPUT_OCTETS = 43,
+ RADIUS_ATTR_ACCT_SESSION_ID = 44,
+ RADIUS_ATTR_ACCT_AUTHENTIC = 45,
+ RADIUS_ATTR_ACCT_SESSION_TIME = 46,
+ RADIUS_ATTR_ACCT_INPUT_PACKETS = 47,
+ RADIUS_ATTR_ACCT_OUTPUT_PACKETS = 48,
+ RADIUS_ATTR_ACCT_TERMINATE_CAUSE = 49,
+ RADIUS_ATTR_ACCT_MULTI_SESSION_ID = 50,
+ RADIUS_ATTR_ACCT_LINK_COUNT = 51,
+ RADIUS_ATTR_ACCT_INPUT_GIGAWORDS = 52,
+ RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS = 53,
+ RADIUS_ATTR_EVENT_TIMESTAMP = 55,
+ RADIUS_ATTR_NAS_PORT_TYPE = 61,
+ RADIUS_ATTR_TUNNEL_TYPE = 64,
+ RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65,
+ RADIUS_ATTR_CONNECT_INFO = 77,
+ RADIUS_ATTR_EAP_MESSAGE = 79,
+ RADIUS_ATTR_MESSAGE_AUTHENTICATOR = 80,
+ RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID = 81,
+ RADIUS_ATTR_ACCT_INTERIM_INTERVAL = 85,
+ RADIUS_ATTR_CHARGEABLE_USER_IDENTITY = 89,
+ RADIUS_ATTR_NAS_IPV6_ADDRESS = 95
+};
+
+
+/* Termination-Action */
+#define RADIUS_TERMINATION_ACTION_DEFAULT 0
+#define RADIUS_TERMINATION_ACTION_RADIUS_REQUEST 1
+
+/* NAS-Port-Type */
+#define RADIUS_NAS_PORT_TYPE_IEEE_802_11 19
+
+/* Acct-Status-Type */
+#define RADIUS_ACCT_STATUS_TYPE_START 1
+#define RADIUS_ACCT_STATUS_TYPE_STOP 2
+#define RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE 3
+#define RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON 7
+#define RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF 8
+
+/* Acct-Authentic */
+#define RADIUS_ACCT_AUTHENTIC_RADIUS 1
+#define RADIUS_ACCT_AUTHENTIC_LOCAL 2
+#define RADIUS_ACCT_AUTHENTIC_REMOTE 3
+
+/* Acct-Terminate-Cause */
+#define RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST 1
+#define RADIUS_ACCT_TERMINATE_CAUSE_LOST_CARRIER 2
+#define RADIUS_ACCT_TERMINATE_CAUSE_LOST_SERVICE 3
+#define RADIUS_ACCT_TERMINATE_CAUSE_IDLE_TIMEOUT 4
+#define RADIUS_ACCT_TERMINATE_CAUSE_SESSION_TIMEOUT 5
+#define RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_RESET 6
+#define RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT 7
+#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_ERROR 8
+#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_ERROR 9
+#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_REQUEST 10
+#define RADIUS_ACCT_TERMINATE_CAUSE_NAS_REBOOT 11
+#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_UNNEEDED 12
+#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_PREEMPTED 13
+#define RADIUS_ACCT_TERMINATE_CAUSE_PORT_SUSPENDED 14
+#define RADIUS_ACCT_TERMINATE_CAUSE_SERVICE_UNAVAILABLE 15
+#define RADIUS_ACCT_TERMINATE_CAUSE_CALLBACK 16
+#define RADIUS_ACCT_TERMINATE_CAUSE_USER_ERROR 17
+#define RADIUS_ACCT_TERMINATE_CAUSE_HOST_REQUEST 18
+
+#define RADIUS_TUNNEL_TAGS 32
+
+/* Tunnel-Type */
+#define RADIUS_TUNNEL_TYPE_PPTP 1
+#define RADIUS_TUNNEL_TYPE_L2TP 3
+#define RADIUS_TUNNEL_TYPE_IPIP 7
+#define RADIUS_TUNNEL_TYPE_GRE 10
+#define RADIUS_TUNNEL_TYPE_VLAN 13
+
+/* Tunnel-Medium-Type */
+#define RADIUS_TUNNEL_MEDIUM_TYPE_IPV4 1
+#define RADIUS_TUNNEL_MEDIUM_TYPE_IPV6 2
+#define RADIUS_TUNNEL_MEDIUM_TYPE_802 6
+
+
+struct radius_attr_vendor {
+ u8 vendor_type;
+ u8 vendor_length;
+} STRUCT_PACKED;
+
+#define RADIUS_VENDOR_ID_CISCO 9
+#define RADIUS_CISCO_AV_PAIR 1
+
+/* RFC 2548 - Microsoft Vendor-specific RADIUS Attributes */
+#define RADIUS_VENDOR_ID_MICROSOFT 311
+
+enum { RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY = 16,
+ RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY = 17
+};
+
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif /* _MSC_VER */
+
+struct radius_ms_mppe_keys {
+ u8 *send;
+ size_t send_len;
+ u8 *recv;
+ size_t recv_len;
+};
+
+
+/* RADIUS message structure for new and parsed messages */
+struct radius_msg {
+ unsigned char *buf;
+ size_t buf_size; /* total size allocated for buf */
+ size_t buf_used; /* bytes used in buf */
+
+ struct radius_hdr *hdr;
+
+ size_t *attr_pos; /* array of indexes to attributes (number of bytes
+ * from buf to the beginning of
+ * struct radius_attr_hdr). */
+ size_t attr_size; /* total size of the attribute pointer array */
+ size_t attr_used; /* total number of attributes in the array */
+};
+
+
+/* Default size to be allocated for new RADIUS messages */
+#define RADIUS_DEFAULT_MSG_SIZE 1024
+
+/* Default size to be allocated for attribute array */
+#define RADIUS_DEFAULT_ATTR_COUNT 16
+
+
+/* MAC address ASCII format for IEEE 802.1X use
+ * (draft-congdon-radius-8021x-20.txt) */
+#define RADIUS_802_1X_ADDR_FORMAT "%02X-%02X-%02X-%02X-%02X-%02X"
+/* MAC address ASCII format for non-802.1X use */
+#define RADIUS_ADDR_FORMAT "%02x%02x%02x%02x%02x%02x"
+
+struct radius_msg *radius_msg_new(u8 code, u8 identifier);
+int radius_msg_initialize(struct radius_msg *msg, size_t init_len);
+void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier);
+void radius_msg_free(struct radius_msg *msg);
+int radius_msg_finish(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len);
+int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, const u8 *req_authenticator);
+void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len);
+struct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type,
+ const u8 *data, size_t data_len);
+int radius_msg_add_eap(struct radius_msg *msg, const u8 *data,
+ size_t data_len);
+u8 *radius_msg_get_eap(struct radius_msg *msg, size_t *len);
+int radius_msg_verify(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, struct radius_msg *sent_msg,
+ int auth);
+int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret,
+ size_t secret_len, const u8 *req_auth);
+int radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src,
+ u8 type);
+void radius_msg_make_authenticator(struct radius_msg *msg,
+ const u8 *data, size_t len);
+struct radius_ms_mppe_keys *
+radius_msg_get_ms_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
+ const u8 *secret, size_t secret_len);
+struct radius_ms_mppe_keys *
+radius_msg_get_cisco_keys(struct radius_msg *msg, struct radius_msg *sent_msg,
+ const u8 *secret, size_t secret_len);
+int radius_msg_add_mppe_keys(struct radius_msg *msg,
+ const u8 *req_authenticator,
+ const u8 *secret, size_t secret_len,
+ const u8 *send_key, size_t send_key_len,
+ const u8 *recv_key, size_t recv_key_len);
+struct radius_attr_hdr *
+radius_msg_add_attr_user_password(struct radius_msg *msg,
+ const u8 *data, size_t data_len,
+ const u8 *secret, size_t secret_len);
+int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len);
+int radius_msg_get_vlanid(struct radius_msg *msg);
+
+static inline int radius_msg_add_attr_int32(struct radius_msg *msg, u8 type,
+ u32 value)
+{
+ u32 val = htonl(value);
+ return radius_msg_add_attr(msg, type, (u8 *) &val, 4) != NULL;
+}
+
+int radius_msg_add_attr_to_array(struct radius_msg *msg, struct radius_attr_hdr *attr);
+static inline int radius_msg_get_attr_int32(struct radius_msg *msg, u8 type,
+ u32 *value)
+{
+ u32 val;
+ int res;
+ res = radius_msg_get_attr(msg, type, (u8 *) &val, 4);
+ if (res != 4)
+ return -1;
+
+ *value = ntohl(val);
+ return 0;
+}
+int radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf,
+ size_t *len, const u8 *start);
+int radius_msg_count_attr(struct radius_msg *msg, u8 type, int min_len);
+
+
+struct radius_attr_data {
+ u8 *data;
+ size_t len;
+};
+
+struct radius_class_data {
+ struct radius_attr_data *attr;
+ size_t count;
+};
+
+void radius_free_class(struct radius_class_data *c);
+int radius_copy_class(struct radius_class_data *dst,
+ const struct radius_class_data *src);
+
+#endif /* RADIUS_H */
diff --git a/extensions/app_radgw/rgw.h b/extensions/app_radgw/rgw.h
new file mode 100644
index 0000000..b6c0911
--- /dev/null
+++ b/extensions/app_radgw/rgw.h
@@ -0,0 +1,134 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* This file contains the definitions for the app_radgw internal use. */
+
+#ifndef _RGW_H
+#define _RGW_H
+
+/* include the common definitions */
+#include "rgw_common.h"
+
+/* RADIUS messages + metadata */
+struct rgw_radius_msg_meta {
+
+ /* The RADIUS message */
+ struct radius_msg radius;
+
+ /* Metadata */
+ struct {
+ /* The port it was sent from, in network byte order */
+ unsigned port :16;
+
+ /* received on ACCT or AUTH port? */
+ unsigned serv_type :2;
+
+ /* The message has a valid Message-Authenticator attribute */
+ unsigned valid_mac :1;
+ };
+
+ /* For Proxy-State attributes: */
+ int ps_first; /* The index of the first Proxy-State attribute in radius.attr_pos. It is always >= radius.attr_used */
+ int ps_nb; /* The number of Proxy-State attributes. The real radius.attr_pos size is attr_used + ps_nb */
+};
+void rgw_msg_free(struct rgw_radius_msg_meta ** msg);
+int rgw_msg_parse(unsigned char * buf, size_t len, struct rgw_radius_msg_meta ** msg);
+void rgw_msg_dump(struct rgw_radius_msg_meta * msg, int has_meta);
+
+/* Local RADIUS server(s) configuration */
+struct rgw_serv {
+ unsigned disabled :1;
+ unsigned ip_disabled :1;
+ unsigned ip6_disabled :1;
+ unsigned :13; /* padding */
+
+ uint16_t port; /* stored in network byte order */
+
+ struct in_addr ip_endpoint;
+ struct in6_addr ip6_endpoint;
+};
+
+extern struct rgw_servs {
+ struct rgw_serv auth_serv;
+ struct rgw_serv acct_serv;
+} rgw_servers;
+
+int rgw_servers_init(void);
+int rgw_servers_start(void);
+void rgw_servers_dump(void);
+int rgw_servers_send(int type, unsigned char *buf, size_t buflen, struct sockaddr *to, uint16_t to_port);
+void rgw_servers_fini(void);
+
+
+/* Clients management */
+enum rgw_cli_type { RGW_CLI_NAS, RGW_CLI_PXY };
+int rgw_clients_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth);
+int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type );
+int rgw_clients_getkey(struct rgw_client * cli, unsigned char **key, size_t *key_len);
+int rgw_clients_gettype(struct rgw_client * cli, enum rgw_cli_type *type);
+int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref);
+int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli);
+int rgw_clients_create_origin(struct rgw_radius_msg_meta *msg, struct rgw_client * cli, struct msg ** diam);
+int rgw_client_finish_send(struct radius_msg ** msg, struct rgw_radius_msg_meta * req, struct rgw_client * cli);
+int rgw_client_finish_nosend(struct rgw_radius_msg_meta * req, struct rgw_client * cli);
+void rgw_clients_dispose(struct rgw_client ** ref);
+void rgw_clients_dump(void);
+int rgw_clients_init(void);
+void rgw_clients_fini(void);
+int rgw_client_session_add(struct rgw_client * cli, struct session *sess, char * dest_realm, char * dest_host, application_id_t appid);
+int rgw_client_session_stop(struct rgw_client * cli, struct session * sess, int32_t reason);
+
+
+/* Management of plugins */
+int rgw_plg_add( char * plgfile, char * conffile, int port, unsigned char ** codes_array, size_t codes_sz );
+void rgw_plg_dump(void);
+void rgw_plg_start_cache(void);
+int rgw_plg_loop_req(struct rgw_radius_msg_meta **rad, struct msg **diam_msg, struct rgw_client * cli);
+int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli);
+void rgw_plg_fini(void);
+
+
+/* Parse configuration file */
+int rgw_conf_handle(char * conffile);
+
+
+/* Worker module, process incoming RADIUS messages (in separated threads) */
+int rgw_work_start(void);
+int rgw_work_add(struct rgw_radius_msg_meta * msg, struct rgw_client * client);
+void rgw_work_fini(void);
+
+
+#endif /* _RGW_H */
+
diff --git a/extensions/app_radgw/rgw_clients.c b/extensions/app_radgw/rgw_clients.c
new file mode 100644
index 0000000..10922ce
--- /dev/null
+++ b/extensions/app_radgw/rgw_clients.c
@@ -0,0 +1,1181 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Manage the list of RADIUS clients, along with their shared secrets. */
+
+/* Probably some changes are needed to support RADIUS Proxies */
+
+#include "rgw.h"
+
+#define REVERSE_DNS_SIZE_MAX 512 /* length of our buffer for reverse DNS */
+#define DUPLICATE_CHECK_LIFETIME 60 /* number of seconds that the received RADIUS records are kept for duplicate checking . TODO: make it configurable if needed */
+
+/* Ordered lists of clients. The order relationship is a memcmp on the address zone.
+ For same addresses, the port is compared.
+ The same address cannot be added twice, once with a 0-port and once with another port value.
+ */
+static struct fd_list cli_ip = FD_LIST_INITIALIZER(cli_ip);
+static struct fd_list cli_ip6 = FD_LIST_INITIALIZER(cli_ip6);
+
+/* Lock to protect the previous lists. We use a rwlock because this list is mostly static, to allow parallel reading */
+static pthread_rwlock_t cli_rwl = PTHREAD_RWLOCK_INITIALIZER;
+
+/* Structure describing one received RADIUS message, for duplicate checks purpose. */
+struct req_info {
+ uint16_t port; /* UDP source port of the request */
+ uint8_t id; /* The identifier in the request header */
+ uint8_t auth[16]; /* Request authenticator, since some RADIUS clients do not implement the id mechanism properly. */
+ struct radius_msg *ans; /* The replied answer if any, in case the previous answer got lost. */
+
+ int nbdup; /* Number of times this request was received as a duplicate */
+ struct fd_list by_id; /* The list of requests ordered by their id, port, and auth */
+ time_t received; /* When was the last duplicate received? */
+ struct fd_list by_time; /* The list of requests ordered by the 'received' value . */
+};
+
+static pthread_t dbt_expire = (pthread_t)NULL; /* The thread that will remove old requests information from all clients (one thread for all) */
+
+/* Structure describing one client */
+struct rgw_client {
+ /* Link information in global list (cli_ip or cli_ip6) */
+ struct fd_list chain;
+
+ /* Reference count */
+ int refcount;
+
+ /* The address and optional port (alloc'd during configuration file parsing). */
+ union {
+ struct sockaddr *sa; /* generic pointer */
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ };
+
+ /* The FQDN, realm, and optional aliases */
+ int is_local; /* true if the RADIUS client runs on the same host -- we use Diameter Identity in that case */
+ enum rgw_cli_type type; /* is it a proxy ? */
+ DiamId_t fqdn; /* malloc'd here */
+ size_t fqdn_len;
+ DiamId_t realm; /* references another string, do not free */
+ size_t realm_len;
+ struct {
+ os0_t name;
+ size_t len;
+ } *aliases; /* Received aliases */
+ size_t aliases_nb;
+
+ /* The secret key data. */
+ struct {
+ unsigned char * data;
+ size_t len;
+ } key;
+
+ /* information of previous msg received, for duplicate checks. */
+ struct {
+ pthread_mutex_t dupl_lock; /* The mutex protecting the following lists */
+ struct fd_list dupl_by_id; /* The list of req_info structures ordered by their id, port, and auth */
+ struct fd_list dupl_by_time; /* The list of req_info structures ordered by their time (approximative) */
+ } dupl_info[2]; /*[0] for auth, [1] for acct. */
+};
+
+
+/* Create a new req_info structure and initialize its data from a RADIUS request message */
+static struct req_info * dupl_new_req_info(struct rgw_radius_msg_meta *msg) {
+ struct req_info * ret = NULL;
+ CHECK_MALLOC_DO( ret = malloc(sizeof(struct req_info)), return NULL );
+ memset(ret, 0, sizeof(struct req_info));
+ ret->port = msg->port;
+ ret->id = msg->radius.hdr->identifier;
+ memcpy(&ret->auth[0], &msg->radius.hdr->authenticator[0], 16);
+ fd_list_init(&ret->by_id, ret);
+ fd_list_init(&ret->by_time, ret);
+ ret->received = time(NULL);
+ return ret;
+}
+
+/* Destroy a req_info structure, after it has been unlinked */
+static void dupl_free_req_info(struct req_info * r) {
+ CHECK_PARAMS_DO( r && FD_IS_LIST_EMPTY(&r->by_id) && FD_IS_LIST_EMPTY(&r->by_time), return );
+ if (r->ans) {
+ /* Free this RADIUS message */
+ radius_msg_free(r->ans);
+ free(r->ans);
+ }
+
+ /* Use r->nbdup for some purpose? */
+
+ free(r);
+}
+
+/* The core of the purge thread */
+static int dupl_purge_list(struct fd_list * clients) {
+
+ struct fd_list *li = NULL;
+
+ for (li = clients->next; li != clients; li = li->next) {
+ struct rgw_client * client = (struct rgw_client *)li;
+ int p;
+
+ for (p=0; p<=1; p++) {
+
+ /* Lock this list */
+ time_t now;
+ CHECK_POSIX( pthread_mutex_lock(&client->dupl_info[p].dupl_lock) );
+
+ now = time(NULL);
+
+ while (!FD_IS_LIST_EMPTY(&client->dupl_info[p].dupl_by_time)) {
+
+ /* Check the first item in the list */
+ struct req_info * r = (struct req_info *)(client->dupl_info[p].dupl_by_time.next->o);
+
+ if (now - r->received > DUPLICATE_CHECK_LIFETIME) {
+
+ TRACE_DEBUG(ANNOYING + 1, "Purging RADIUS request (id: %02hhx, port: %hu, dup #%d, age %ld secs)", r->id, ntohs(r->port), r->nbdup, (long)(now - r->received));
+
+ /* Remove this record */
+ fd_list_unlink(&r->by_time);
+ fd_list_unlink(&r->by_id);
+ dupl_free_req_info(r);
+ } else {
+ /* We are done for this list */
+ break;
+ }
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock(&client->dupl_info[p].dupl_lock) );
+ }
+ }
+ return 0;
+}
+
+/* Thread that purges old RADIUS requests */
+static void * dupl_th(void * arg) {
+ /* Set the thread name */
+ fd_log_threadname ( "app_radgw:duplicate_purge" );
+
+ /* The thread will be canceled */
+ while (1) {
+
+ /* We don't use a cond var, we simply wake up every 5 seconds. If the size of the duplicate cache is critical, it might be changed */
+ sleep(5);
+
+ /* When we wake up, we will check all clients duplicate lists one by one */
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&cli_rwl), break );
+
+ CHECK_FCT_DO( dupl_purge_list(&cli_ip), break );
+ CHECK_FCT_DO( dupl_purge_list(&cli_ip6), break );
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), break );
+
+ /* Loop */
+ }
+
+ /* If we reach this part, some fatal error was encountered */
+ CHECK_FCT_DO(fd_core_shutdown(), );
+ TRACE_DEBUG(FULL, "Thread terminated");
+ return NULL;
+}
+
+
+/* create a new rgw_client. the arguments are MOVED into the structure (to limit malloc & free calls). */
+static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type )
+{
+ struct rgw_client *tmp = NULL;
+ DiamId_t fqdn;
+ size_t fqdn_len = 0;
+ int ret, i;
+ int loc = 0;
+
+ /* Check if the IP address is local */
+ if ( ( ((*ip_port)->sa_family == AF_INET ) && ( IN_IS_ADDR_LOOPBACK( &((struct sockaddr_in *)(*ip_port))->sin_addr ) ) )
+ ||( ((*ip_port)->sa_family == AF_INET6) && ( IN6_IS_ADDR_LOOPBACK( &((struct sockaddr_in6 *)(*ip_port))->sin6_addr) ) )) {
+ /* The client is local */
+ loc = 1;
+ } else {
+ char buf[255];
+
+ /* Search FQDN for the client */
+ ret = getnameinfo( *ip_port, sizeof(struct sockaddr_storage), &buf[0], sizeof(buf), NULL, 0, 0 );
+ if (ret) {
+ TRACE_DEBUG(INFO, "Unable to resolve peer name: %s", gai_strerror(ret));
+ return EINVAL;
+ }
+ fqdn = &buf[0];
+ CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&fqdn, &fqdn_len, 1),
+ {
+ TRACE_DEBUG(INFO, "Unable to use resolved peer name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+ return ret;
+ } );
+ }
+
+ /* Create the new object */
+ CHECK_MALLOC( tmp = malloc(sizeof (struct rgw_client)) );
+ memset(tmp, 0, sizeof(struct rgw_client));
+ fd_list_init(&tmp->chain, NULL);
+
+ /* Initialize the duplicate list info */
+ for (i=0; i<=1; i++) {
+ CHECK_POSIX( pthread_mutex_init(&tmp->dupl_info[i].dupl_lock, NULL) );
+ fd_list_init(&tmp->dupl_info[i].dupl_by_id, NULL);
+ fd_list_init(&tmp->dupl_info[i].dupl_by_time, NULL);
+ }
+ tmp->type = type;
+
+ if (loc) {
+ tmp->is_local = 1;
+ } else {
+ /* Copy the fqdn */
+ tmp->fqdn = fqdn;
+ tmp->fqdn_len = fqdn_len;
+
+ /* Find an appropriate realm */
+ tmp->realm = strchr(fqdn, '.');
+ if (tmp->realm) {
+ tmp->realm += 1;
+ tmp->realm_len = tmp->fqdn_len - (tmp->realm - fqdn);
+ }
+ if ((!tmp->realm) || (*tmp->realm == '\0')) { /* in case the fqdn was "localhost." for example, if it is possible... */
+ tmp->realm = fd_g_config->cnf_diamrlm;
+ tmp->realm_len = fd_g_config->cnf_diamrlm_len;
+ }
+ }
+
+ /* move the sa info reference */
+ tmp->sa = *ip_port;
+ *ip_port = NULL;
+
+ /* move the key material */
+ tmp->key.data = *key;
+ tmp->key.len = keylen;
+ *key = NULL;
+
+ /* Done! */
+ *res = tmp;
+ return 0;
+}
+
+/* Decrease refcount on a client; the lock must be held when this function is called. */
+static void client_unlink(struct rgw_client * client)
+{
+ client->refcount -= 1;
+
+ if (client->refcount <= 0) {
+ int idx;
+ /* to be sure: the refcount should be 0 only when client_fini is called */
+ ASSERT( FD_IS_LIST_EMPTY(&client->chain) );
+
+ /* Free the data */
+ for (idx = 0; idx < client->aliases_nb; idx++)
+ free(client->aliases[idx].name);
+ free(client->aliases);
+ free(client->fqdn);
+ free(client->sa);
+ free(client->key.data);
+
+ /* Free the duplicate info */
+ for (idx=0; idx <= 1; idx++){
+ CHECK_POSIX_DO( pthread_mutex_lock( &client->dupl_info[idx].dupl_lock ), /* continue */ );
+
+ while (!FD_IS_LIST_EMPTY(&client->dupl_info[idx].dupl_by_id)) {
+ struct req_info * r = (struct req_info *)(client->dupl_info[idx].dupl_by_id.next->o);
+ fd_list_unlink( &r->by_id );
+ fd_list_unlink( &r->by_time );
+ dupl_free_req_info(r);
+ }
+
+ CHECK_POSIX_DO( pthread_mutex_unlock( &client->dupl_info[idx].dupl_lock ), /* continue */ );
+
+ }
+
+ free(client);
+ }
+}
+
+
+/* Macro to avoid duplicating the code in the next function */
+#define client_search_family( _family_ ) \
+ case AF_INET##_family_: { \
+ struct sockaddr_in##_family_ * sin##_family_ = (struct sockaddr_in##_family_ *)ip_port; \
+ for (ref = cli_ip##_family_.next; ref != &cli_ip##_family_; ref = ref->next) { \
+ cmp = memcmp(&sin##_family_->sin##_family_##_addr, \
+ &((struct rgw_client *)ref)->sin##_family_->sin##_family_##_addr, \
+ sizeof(struct in##_family_##_addr)); \
+ if (cmp > 0) continue; /* search further in the list */ \
+ if (cmp < 0) break; /* this IP is not in the list */ \
+ /* Now compare the ports as follow: */ \
+ /* If the ip_port we are searching does not contain a port, just return the first match result */ \
+ if ( (sin##_family_->sin##_family_##_port == 0) \
+ /* If the entry in the list does not contain a port, return it as a match */ \
+ || (((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port == 0) \
+ /* If both ports are equal, it is a match */ \
+ || (sin##_family_->sin##_family_##_port == \
+ ((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port)) { \
+ *res = (struct rgw_client *)ref; \
+ return EEXIST; \
+ } \
+ /* Otherwise, the list is ordered by port value (byte order does not matter */ \
+ if (sin##_family_->sin##_family_##_port \
+ > ((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port) continue; \
+ else break; \
+ } \
+ *res = (struct rgw_client *)(ref->prev); \
+ return ENOENT; \
+ }
+/* Function to look for an existing rgw_client, or the previous element.
+ The cli_rwl must be held for reading (at least) when calling this function.
+ Returns ENOENT if the matching client does not exist, and res points to the previous element in the list.
+ Returns EEXIST if the matching client is found, and res points to this element.
+ Returns other error code on other error. */
+static int client_search(struct rgw_client ** res, struct sockaddr * ip_port )
+{
+ int cmp;
+ struct fd_list *ref = NULL;
+
+ CHECK_PARAMS(res && ip_port);
+
+ switch (ip_port->sa_family) {
+ client_search_family()
+ break;
+
+ client_search_family( 6 )
+ break;
+ }
+
+ /* We're never supposed to reach this point */
+ ASSERT(0);
+ return EINVAL;
+}
+
+int rgw_clients_getkey(struct rgw_client * cli, unsigned char **key, size_t *key_len)
+{
+ CHECK_PARAMS( cli && key && key_len );
+ *key = cli->key.data;
+ *key_len = cli->key.len;
+ return 0;
+}
+
+int rgw_clients_gettype(struct rgw_client * cli, enum rgw_cli_type *type)
+{
+ CHECK_PARAMS( cli && type );
+ *type = cli->type;
+ return 0;
+}
+
+
+int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref)
+{
+ int ret = 0;
+
+ TRACE_ENTRY("%p %p", ip_port, ref);
+
+ CHECK_PARAMS(ip_port && ref);
+
+ CHECK_POSIX( pthread_rwlock_rdlock(&cli_rwl) );
+
+ ret = client_search(ref, ip_port);
+ if (ret == EEXIST) {
+ (*ref)->refcount ++;
+ ret = 0;
+ } else {
+ *ref = NULL;
+ }
+
+ CHECK_POSIX( pthread_rwlock_unlock(&cli_rwl) );
+
+ return ret;
+}
+
+int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli)
+{
+ int p, dup = 0;
+ struct fd_list * li;
+ struct req_info * r;
+
+ TRACE_ENTRY("%p %p", msg, cli);
+
+ CHECK_PARAMS( msg && cli );
+
+ if ((*msg)->serv_type == RGW_PLG_TYPE_AUTH)
+ p = 0;
+ else
+ p = 1;
+
+ CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) );
+
+ /* Search if we have this message in our list */
+ for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) {
+ int cmp = 0;
+ r = (struct req_info *)(li->o);
+ if (r->id < (*msg)->radius.hdr->identifier)
+ continue;
+ if (r->id > (*msg)->radius.hdr->identifier)
+ break;
+ if (r->port < (*msg)->port)
+ continue;
+ if (r->port > (*msg)->port)
+ break;
+ cmp = memcmp(&r->auth[0], &(*msg)->radius.hdr->authenticator[0], 16);
+ if (cmp < 0)
+ continue;
+ if (cmp > 0)
+ break;
+ dup = 1;
+ break;
+ }
+
+ if (dup) {
+ time_t now = time(NULL);
+ r->nbdup += 1;
+ TRACE_DEBUG(INFO, "Received duplicated RADIUS message (id: %02hhx, port: %hu, dup #%d, previously seen %ld secs ago).",
+ r->id, ntohs(r->port), r->nbdup, (long)(now - r->received));
+
+ if (r->ans) {
+ /* Resend the answer */
+ CHECK_FCT_DO( rgw_servers_send((*msg)->serv_type, r->ans->buf, r->ans->buf_used, cli->sa, r->port), );
+
+ /* Should we delete 'r' so that a further duplicate will again be converted to Diameter? */
+ }
+
+ /* Update the timestamp */
+ r->received = now;
+ fd_list_unlink(&r->by_time);
+ fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time); /* Move as last entry, since it is the most recent */
+
+ /* Delete the request message */
+ rgw_msg_free(msg);
+
+ } else {
+ /* The message was not a duplicate, we save it */
+ /* li currently points the the next entry in list_by_id */
+ CHECK_MALLOC_DO( r= dupl_new_req_info(*msg), { CHECK_POSIX_DO(pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ), ); return ENOMEM; } );
+ fd_list_insert_before(li, &r->by_id);
+ fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time); /* it is the most recent */
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) );
+
+ return 0;
+}
+
+/* Check if the message has a valid authenticator, and update the meta-data accordingly */
+int rgw_clients_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth)
+{
+ unsigned char * key;
+ size_t keylen;
+ int count;
+
+ TRACE_ENTRY("%p %p %p", msg, cli, req_auth);
+
+ CHECK_PARAMS(msg && cli);
+
+ CHECK_FCT(rgw_clients_getkey(cli, &key, &keylen));
+
+ count = radius_msg_count_attr(&msg->radius, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0);
+ if (count > 1) {
+ TRACE_DEBUG(INFO, "Too many Message-Authenticator attributes (%d), discarding message.", count);
+ return EINVAL;
+ }
+ if (count == 0) {
+ TRACE_DEBUG(FULL, "Message does not contain a Message-Authenticator attributes.");
+ msg->valid_mac = 0;
+ } else {
+ if (radius_msg_verify_msg_auth( &msg->radius, key, keylen, req_auth )) {
+ TRACE_DEBUG(INFO, "Invalid Message-Authenticator received, discarding message.");
+ return EINVAL;
+ }
+ msg->valid_mac = 1;
+ }
+
+ return 0;
+}
+
+static struct dict_object * cache_orig_host = NULL;
+static struct dict_object * cache_orig_realm = NULL;
+static struct dict_object * cache_route_record = NULL;
+
+int rgw_clients_init(void)
+{
+ TRACE_ENTRY();
+ CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &cache_orig_host, ENOENT) );
+ CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &cache_orig_realm, ENOENT) );
+ CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &cache_route_record, ENOENT) );
+
+ /* Create the thread that will purge old RADIUS duplicates */
+ CHECK_POSIX( pthread_create( &dbt_expire, NULL, dupl_th, NULL) );
+
+ return 0;
+}
+
+
+/* The following function checks if a RADIUS message contains a valid NAS identifier, and initializes an empty Diameter
+ message with the appropriate routing information */
+/* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */
+/* Also update the client list of aliases if needed */
+int rgw_clients_create_origin(struct rgw_radius_msg_meta *msg, struct rgw_client * cli, struct msg ** diam)
+{
+ int idx;
+ int valid_nas_info = 0;
+ struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL;
+ size_t nas_id_len;
+ char * oh_str = NULL; size_t oh_strlen = 0; int oh_free = 0;
+ char * or_str = NULL; size_t or_strlen = 0;
+ char * rr_str = NULL; size_t rr_strlen = 0;
+ char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */
+
+ struct avp *avp = NULL;
+ union avp_value avp_val;
+
+ TRACE_ENTRY("%p %p %p", msg, cli, diam);
+ CHECK_PARAMS(msg && cli && diam && (*diam == NULL));
+
+ /* Find the relevant attributes, if any */
+ for (idx = 0; idx < msg->radius.attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[idx]);
+ size_t attr_len = attr->length - sizeof(struct radius_attr_hdr);
+
+ if ((attr->type == RADIUS_ATTR_NAS_IP_ADDRESS) && (attr_len = 4)) {
+ nas_ip = attr;
+ continue;
+ }
+
+ if ((attr->type == RADIUS_ATTR_NAS_IDENTIFIER) && (attr_len > 0)) {
+ nas_id = attr;
+ nas_id_len = attr_len;
+ continue;
+ }
+
+ if ((attr->type == RADIUS_ATTR_NAS_IPV6_ADDRESS) && (attr_len = 16)) {
+ nas_ip6 = attr;
+ continue;
+ }
+ }
+
+ if (!nas_ip && !nas_ip6 && !nas_id) {
+ TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute.");
+
+ /* Get information on this peer */
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+
+ goto diameter;
+ }
+
+ /* Check if the message was received from the IP in NAS-IP-Address attribute */
+ if (nas_ip && (cli->sa->sa_family == AF_INET) && !memcmp(nas_ip+1, &cli->sin->sin_addr, sizeof(struct in_addr))) {
+ TRACE_DEBUG(FULL, "NAS-IP-Address contains the same address as the message was received from.");
+ valid_nas_info |= 1;
+ }
+ if (nas_ip6 && (cli->sa->sa_family == AF_INET6) && !memcmp(nas_ip6+1, &cli->sin6->sin6_addr, sizeof(struct in6_addr))) {
+ TRACE_DEBUG(FULL, "NAS-IPv6-Address contains the same address as the message was received from.");
+ valid_nas_info |= 2;
+ }
+
+
+ /*
+ In RADIUS it would be possible for a rogue NAS to forge the NAS-IP-
+ Address attribute value. Diameter/RADIUS translation agents MUST
+ check a received NAS-IP-Address or NAS-IPv6-Address attribute against
+ the source address of the RADIUS packet. If they do not match and
+ the Diameter/RADIUS translation agent does not know whether the
+ packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State
+ attribute), then by default it is assumed that the source address
+ corresponds to a RADIUS proxy, and that the NAS Address is behind
+ that proxy, potentially with some additional RADIUS proxies in
+ between. The Diameter/RADIUS translation agent MUST insert entries
+ in the Route-Record AVP corresponding to the apparent route. This
+ implies doing a reverse lookup on the source address and NAS-IP-
+ Address or NAS-IPv6-Address attributes to determine the corresponding
+ FQDNs.
+
+ If the source address and the NAS-IP-Address or NAS-IPv6-Address do
+ not match, and the Diameter/RADIUS translation agent knows that it is
+ talking directly to the NAS (e.g., there are no RADIUS proxies
+ between it and the NAS), then the error should be logged, and the
+ packet MUST be discarded.
+
+ Diameter agents and servers MUST check whether the NAS-IP-Address AVP
+ corresponds to an entry in the Route-Record AVP. This is done by
+ doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve
+ the corresponding FQDN, and by checking for a match with the Route-
+ Record AVP. If no match is found, then an error is logged, but no
+ other action is taken.
+ */
+ if (nas_ip || nas_ip6) {
+ if (!valid_nas_info) {
+ if ((!cli->is_local) && (cli->type == RGW_CLI_NAS)) {
+ TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different from the sender's. Please configure as Proxy if this is expected. Message discarded.");
+ return EINVAL;
+ } else {
+ int ret;
+ sSS ss;
+ /* the peer is configured as a proxy, or running on localhost, so accept the message */
+
+ /* In that case, the cli will be stored as Route-Record and the NAS-IP-Address as origin */
+ if (!cli->is_local) {
+ rr_str = cli->fqdn;
+ rr_strlen = cli->fqdn_len;
+ }
+
+ /* We must DNS-reverse the NAS-IP*-Address */
+ memset(&ss, 0 , sizeof(sSS));
+ if (nas_ip) {
+ sSA4 * sin = (sSA4 *)&ss;
+ sin->sin_family = AF_INET;
+ memcpy(&sin->sin_addr, nas_ip + 1, sizeof(struct in_addr));
+ } else {
+ sSA6 * sin6 = (sSA6 *)&ss;
+ sin6->sin6_family = AF_INET6;
+ memcpy(&sin6->sin6_addr, nas_ip6 + 1, sizeof(struct in6_addr));
+ }
+ CHECK_SYS_DO( getnameinfo( (sSA *)&ss, sSAlen(&ss), &buf[0], sizeof(buf), NULL, 0, NI_NAMEREQD),
+ {
+ if (cli->is_local) {
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ goto diameter;
+ }
+
+ TRACE_DEBUG(INFO, "The NAS-IP*-Address cannot be DNS reversed in order to create the Origin-Host AVP; rejecting the message (translation is impossible).");
+ return EINVAL;
+ } );
+
+ oh_str = &buf[0];
+ CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&oh_str, &oh_strlen, 1),
+ {
+ if (cli->is_local) {
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ goto diameter;
+ }
+
+ TRACE_DEBUG(INFO, "Unable to use resolved client name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+ return ret;
+ } );
+ oh_free = 1;
+
+ or_str = strchr(oh_str, '.');
+ if (or_str) {
+ or_str ++; /* move after the first dot */
+ if (*or_str == '\0')
+ or_str = NULL; /* Discard this realm, we will use the local realm later */
+ else
+ or_strlen = oh_strlen - (or_str - oh_str);
+ }
+ }
+ } else {
+ /* The attribute matches the source address, just use this in origin-host */
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ }
+
+ goto diameter; /* we ignore the nas_id in that case */
+ }
+
+ /* We don't have a NAS-IP*-Address attribute if we are here */
+ if (cli->is_local) {
+ /* Simple: we use our own configuration */
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ goto diameter;
+ }
+
+ /* At this point, we only have nas_id, and the client is not local */
+ ASSERT(nas_id);
+
+ {
+ int found, ret;
+ struct addrinfo hint, *res, *ptr;
+
+ /*
+ In RADIUS it would be possible for a rogue NAS to forge the NAS-
+ Identifier attribute. Diameter/RADIUS translation agents SHOULD
+ attempt to check a received NAS-Identifier attribute against the
+ source address of the RADIUS packet, by doing an A/AAAA RR query. If
+ the NAS-Identifier attribute contains an FQDN, then such a query
+ would resolve to an IP address matching the source address. However,
+ the NAS-Identifier attribute is not required to contain an FQDN, so
+ such a query could fail. If it fails, an error should be logged, but
+ no action should be taken, other than a reverse lookup on the source
+ address and insert the resulting FQDN into the Route-Record AVP.
+
+ Diameter agents and servers SHOULD check whether a NAS-Identifier AVP
+ corresponds to an entry in the Route-Record AVP. If no match is
+ found, then an error is logged, but no other action is taken.
+ */
+
+ /* first, check if the nas_id is the fqdn of the peer or a known alias */
+ if (!fd_os_almostcasesrch(nas_id + 1, nas_id_len,
+ cli->fqdn, cli->fqdn_len, NULL)) {
+ TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the client");
+ found = 1;
+ } else {
+ for (idx = 0; idx < cli->aliases_nb; idx++) {
+ if (!fd_os_cmp(nas_id + 1, nas_id_len,
+ cli->aliases[idx].name, cli->aliases[idx].len)) {
+ TRACE_DEBUG(FULL, "NAS-Identifier valid value found in the cache");
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ /* The NAS-Identifier matches the source IP */
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+
+ goto diameter;
+ }
+
+ /* Attempt DNS resolution of the identifier */
+ ASSERT( nas_id_len < sizeof(buf) );
+ memcpy(buf, nas_id + 1, nas_id_len);
+ buf[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0';
+
+ /* Now check if this alias is valid for this peer */
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_flags = AI_CANONNAME;
+ ret = getaddrinfo(buf, NULL, &hint, &res);
+ if (ret == 0) {
+ strncpy(buf, res->ai_canonname, sizeof(buf));
+ /* The name was resolved correctly, does it match the IP of the client? */
+ for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
+ if (cli->sa->sa_family != ptr->ai_family)
+ continue;
+ if (memcmp(cli->sa, ptr->ai_addr, sSAlen(cli->sa)))
+ continue;
+
+ found = 1;
+ break;
+ }
+ freeaddrinfo(res);
+
+ if (!found) {
+ if (cli->type == RGW_CLI_NAS) {
+ TRACE_DEBUG(INFO, "The NAS-Identifier value '%.*s' resolves to a different IP than the client's, discarding the message. Configure this client as a Proxy if this message should be valid.",
+ (int)nas_id_len, (char *)(nas_id + 1));
+ return EINVAL;
+ } else {
+ /* This identifier matches a different IP, assume it is a proxied message */
+ if (!cli->is_local) {
+ rr_str = cli->fqdn;
+ rr_strlen = cli->fqdn_len;
+ }
+ oh_str = &buf[0]; /* The canonname resolved */
+ oh_strlen = 0;
+ CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&oh_str, &oh_strlen, 1),
+ {
+ TRACE_DEBUG(INFO, "Unable to use resolved client name '%s' as DiameterIdentity: %s", buf, strerror(ret));
+ return ret;
+ } );
+ oh_free = 1;
+ or_str = strchr(oh_str, '.');
+ if (or_str) {
+ or_str ++; /* move after the first dot */
+ if (*or_str == '\0')
+ or_str = NULL; /* Discard this realm, we will use the local realm later */
+ else
+ or_strlen = oh_strlen - (or_str - oh_str);
+ }
+ }
+ } else {
+ /* It is a valid alias, save it */
+ CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(cli->aliases[0])) );
+
+ CHECK_MALLOC( cli->aliases[cli->aliases_nb + 1].name = os0dup(nas_id + 1, nas_id_len ) );
+ cli->aliases[cli->aliases_nb + 1].len = nas_id_len;
+
+ cli->aliases_nb ++;
+ TRACE_DEBUG(FULL, "Saved valid alias for client: '%.*s' -> '%s'", (int)nas_id_len, (char *)(nas_id + 1), cli->fqdn);
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ }
+ } else {
+ /* Error resolving the name */
+ TRACE_DEBUG(INFO, "NAS-Identifier '%s' cannot be resolved: %s. Ignoring...", buf, gai_strerror(ret));
+ /* Assume this is a valid identifier for the client */
+ CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &oh_strlen, &or_str, &or_strlen) );
+ }
+ }
+
+ /* Now, let's create the empty Diameter message with Origin-Host, -Realm, and Route-Record if needed. */
+diameter:
+ ASSERT(oh_str); /* If it is not defined here, there is a bug... */
+ if (!or_str) {
+ or_str = fd_g_config->cnf_diamrlm; /* Use local realm in that case */
+ or_strlen = fd_g_config->cnf_diamrlm_len;
+ }
+
+ /* Create an empty Diameter message so that extensions can store their AVPs */
+ CHECK_FCT( fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam ) );
+
+ /* Add the Origin-Host as next AVP */
+ CHECK_FCT( fd_msg_avp_new ( cache_orig_host, 0, &avp ) );
+ memset(&avp_val, 0, sizeof(avp_val));
+ avp_val.os.data = (unsigned char *)oh_str;
+ avp_val.os.len = oh_strlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Add the Origin-Realm as next AVP */
+ CHECK_FCT( fd_msg_avp_new ( cache_orig_realm, 0, &avp ) );
+ memset(&avp_val, 0, sizeof(avp_val));
+ avp_val.os.data = (unsigned char *)or_str;
+ avp_val.os.len = or_strlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
+
+ if (rr_str) {
+ CHECK_FCT( fd_msg_avp_new ( cache_route_record, 0, &avp ) );
+ memset(&avp_val, 0, sizeof(avp_val));
+ avp_val.os.data = (unsigned char *)rr_str;
+ avp_val.os.len = rr_strlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+ if (oh_free)
+ free(oh_str);
+
+ /* Done! */
+ return 0;
+}
+
+int rgw_clients_get_origin(struct rgw_client *cli, DiamId_t *fqdn, size_t *fqdnlen, DiamId_t *realm, size_t *realmlen)
+{
+ TRACE_ENTRY("%p %p %p %p %p", cli, fqdn, fqdnlen, realm, realmlen);
+ CHECK_PARAMS(cli && fqdn && fqdnlen);
+
+ if (cli->is_local) {
+ *fqdn = fd_g_config->cnf_diamid;
+ *fqdnlen = fd_g_config->cnf_diamid_len;
+ if (realm)
+ *realm= fd_g_config->cnf_diamrlm;
+ if (realmlen)
+ *realmlen= fd_g_config->cnf_diamrlm_len;
+ } else {
+ *fqdn = cli->fqdn;
+ *fqdnlen = cli->fqdn_len;
+ if (realm)
+ *realm= cli->realm;
+ if (realmlen)
+ *realmlen= cli->realm_len;
+ }
+
+ return 0;
+}
+
+char * rgw_clients_id(struct rgw_client *cli)
+{
+ return cli->is_local ? "(local)" : cli->fqdn;
+}
+
+
+void rgw_clients_dispose(struct rgw_client ** ref)
+{
+ TRACE_ENTRY("%p", ref);
+ CHECK_PARAMS_DO(ref, return);
+
+ CHECK_POSIX_DO( pthread_rwlock_wrlock(&cli_rwl), );
+ client_unlink(*ref);
+ *ref = NULL;
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), );
+}
+
+int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type )
+{
+ struct rgw_client * prev = NULL, *new = NULL;
+ int ret;
+
+ TRACE_ENTRY("%p %p %zu", ip_port, key, keylen);
+
+ CHECK_PARAMS( ip_port && key && *key && keylen );
+ CHECK_PARAMS( (ip_port->sa_family == AF_INET) || (ip_port->sa_family == AF_INET6) );
+ CHECK_PARAMS( (type == RGW_CLI_NAS) || (type == RGW_CLI_PXY) );
+
+ /* Dump the entry in debug mode */
+ if (TRACE_BOOL(FULL + 1 )) {
+ char sa_buf[sSA_DUMP_STRLEN];
+ fd_sa_sdump_numeric(sa_buf, ip_port);
+ TRACE_DEBUG(FULL, "Adding %s:", (type == RGW_CLI_NAS) ? "NAS" : "PROXY" );
+ TRACE_DEBUG(FULL, "\tIP : %s", sa_buf );
+ TRACE_BUFFER(FD_LOG_DEBUG, FULL, "\tKey: [", *key, keylen, "]" );
+ }
+
+ /* Lock the lists */
+ CHECK_POSIX( pthread_rwlock_wrlock(&cli_rwl) );
+
+ /* Check if the same entry does not already exist */
+ ret = client_search(&prev, ip_port );
+ if (ret == ENOENT) {
+ /* No duplicate found, Ok to add */
+ CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen, type ), goto end );
+ fd_list_insert_after(&prev->chain, &new->chain);
+ new->refcount++;
+ ret = 0;
+ goto end;
+ }
+
+ if (ret == EEXIST) {
+ char sa_buf[sSA_DUMP_STRLEN];
+ /* Check if the key is the same, then skip or return an error */
+ if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) ) && (type == prev->type)) {
+ TRACE_DEBUG(INFO, "Skipping duplicate client description");
+ ret = 0;
+ goto end;
+ }
+
+ fd_log_error("ERROR: Conflicting RADIUS clients descriptions!");
+ TRACE_ERROR("Previous entry: %s", (prev->type == RGW_CLI_NAS) ? "NAS" : "PROXY");
+ fd_sa_sdump_numeric(sa_buf, prev->sa);
+ TRACE_ERROR("\tIP : %s", sa_buf);
+ TRACE_BUFFER(FD_LOG_ERROR, NONE, "\tKey: [", prev->key.data, prev->key.len, "]" );
+ TRACE_ERROR("Conflicting entry: %s", (type == RGW_CLI_NAS) ? "NAS" : "PROXY");
+ fd_sa_sdump_numeric(sa_buf, ip_port);
+ TRACE_ERROR("\tIP : %s", sa_buf);
+ TRACE_BUFFER(FD_LOG_ERROR, NONE, "\tKey: [", *key, keylen, "]" );
+ }
+end:
+ /* release the lists */
+ CHECK_POSIX( pthread_rwlock_unlock(&cli_rwl) );
+
+ return ret;
+}
+
+static void dump_cli_list(struct fd_list *senti)
+{
+ struct rgw_client * client = NULL;
+ struct fd_list *ref = NULL;
+
+ for (ref = senti->next; ref != senti; ref = ref->next) {
+ char sa_buf[sSA_DUMP_STRLEN];
+ client = (struct rgw_client *)ref;
+ fd_sa_sdump_numeric(sa_buf, client->sa);
+ LOG_D(" - %s%s", sa_buf, (client->type == RGW_CLI_NAS) ? "" : " [PROXY]" );
+ }
+}
+
+void rgw_clients_dump(void)
+{
+ if ( ! TRACE_BOOL(FULL) )
+ return;
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&cli_rwl), /* ignore error */ );
+
+ if (!FD_IS_LIST_EMPTY(&cli_ip))
+ fd_log_debug(" RADIUS IP clients list:");
+ dump_cli_list(&cli_ip);
+
+ if (!FD_IS_LIST_EMPTY(&cli_ip6))
+ fd_log_debug(" RADIUS IPv6 clients list:");
+ dump_cli_list(&cli_ip6);
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), /* ignore error */ );
+}
+
+void rgw_clients_fini(void)
+{
+ struct fd_list * client;
+
+ TRACE_ENTRY();
+
+ CHECK_POSIX_DO( pthread_rwlock_wrlock(&cli_rwl), /* ignore error */ );
+
+ CHECK_FCT_DO( fd_thr_term(&dbt_expire), /* continue */ );
+
+ /* empty the lists */
+ while ( ! FD_IS_LIST_EMPTY(&cli_ip) ) {
+ client = cli_ip.next;
+ fd_list_unlink(client);
+ client_unlink((struct rgw_client *)client);
+ }
+ while (! FD_IS_LIST_EMPTY(&cli_ip6)) {
+ client = cli_ip6.next;
+ fd_list_unlink(client);
+ client_unlink((struct rgw_client *)client);
+ }
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), /* ignore error */ );
+
+}
+
+int rgw_client_finish_send(struct radius_msg ** msg, struct rgw_radius_msg_meta * req, struct rgw_client * cli)
+{
+ int p;
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %p %p", msg, req, cli);
+ CHECK_PARAMS( msg && *msg && cli );
+
+ if (!req) {
+ /* We don't support this case yet */
+ ASSERT(0);
+ return ENOTSUP;
+ }
+
+ /* Add all the Proxy-States back in the message */
+ for (p = 0; p < req->ps_nb; p++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(req->radius.buf + req->radius.attr_pos[req->ps_first + p]);
+
+ if (radius_msg_add_attr_to_array(*msg, attr)) {
+ TRACE_DEBUG(INFO, "Error in radius_msg_add_attr_to_array, ENOMEM");
+ radius_msg_free(*msg);
+ free(*msg);
+ *msg = NULL;
+ return ENOMEM;
+ }
+ }
+
+ /* Add the Message-Authenticator if needed, and other final tasks */
+ if (radius_msg_finish_srv(*msg, cli->key.data, cli->key.len, req->radius.hdr->authenticator)) {
+ TRACE_DEBUG(INFO, "An error occurred while preparing the RADIUS answer");
+ radius_msg_free(*msg);
+ free(*msg);
+ *msg = NULL;
+ return EINVAL;
+ }
+
+ /* Debug */
+ TRACE_DEBUG(FULL, "RADIUS message ready for sending:");
+ rgw_msg_dump((struct rgw_radius_msg_meta *)*msg, 0);
+
+ /* Send the message */
+ CHECK_FCT( rgw_servers_send(req->serv_type, (*msg)->buf, (*msg)->buf_used, cli->sa, req->port) );
+
+ /* update the duplicate cache */
+ if (req->serv_type == RGW_PLG_TYPE_AUTH)
+ p = 0;
+ else
+ p = 1;
+
+ CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) );
+
+ /* Search this message in our list */
+ for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) {
+ int cmp = 0;
+ struct req_info * r = (struct req_info *)(li->o);
+ if (r->id < req->radius.hdr->identifier)
+ continue;
+ if (r->id > req->radius.hdr->identifier)
+ break;
+ if (r->port < req->port)
+ continue;
+ if (r->port > req->port)
+ break;
+ cmp = memcmp(&r->auth[0], &req->radius.hdr->authenticator[0], 16);
+ if (cmp < 0)
+ continue;
+ if (cmp > 0)
+ break;
+
+ /* We have the request in our duplicate cache */
+ /* This should not happen, but just in case... */
+ if (r->ans) {
+ radius_msg_free(r->ans);
+ free(r->ans);
+ }
+
+ /* Now save the message */
+ r->ans = *msg;
+ *msg = NULL;
+
+ /* Update the timestamp */
+ {
+ time_t now = time(NULL);
+ r->received = now;
+ fd_list_unlink(&r->by_time); /* Move as last entry, since it is the most recent */
+ fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time);
+ }
+ break;
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) );
+
+ /* If we have not found the request in our list, the purge time is probably too small */
+ if (*msg) {
+ TODO("Augment the purge time...");
+ /* If we receive the duplicate request again, it will be converted to Diameter... */
+ radius_msg_free(*msg);
+ free(*msg);
+ *msg = NULL;
+ }
+
+ /* Finished */
+ return 0;
+}
+
+/* Call this function when a RADIUS request has explicitely no answer (mainly accounting) so
+that we purge the duplicate cache and allow further message to be translated again.
+This is useful for example when a temporary error occurred in Diameter (like UNABLE_TO_DELIVER) */
+int rgw_client_finish_nosend(struct rgw_radius_msg_meta * req, struct rgw_client * cli)
+{
+ int p;
+ struct fd_list * li;
+
+ TRACE_ENTRY("%p %p", req, cli);
+ CHECK_PARAMS( req && cli );
+
+ /* update the duplicate cache */
+ if (req->serv_type == RGW_PLG_TYPE_AUTH)
+ p = 0;
+ else
+ p = 1;
+
+ CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) );
+
+ /* Search this message in our list */
+ for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) {
+ int cmp = 0;
+ struct req_info * r = (struct req_info *)(li->o);
+ if (r->id < req->radius.hdr->identifier)
+ continue;
+ if (r->id > req->radius.hdr->identifier)
+ break;
+ if (r->port < req->port)
+ continue;
+ if (r->port > req->port)
+ break;
+ cmp = memcmp(&r->auth[0], &req->radius.hdr->authenticator[0], 16);
+ if (cmp < 0)
+ continue;
+ if (cmp > 0)
+ break;
+
+ /* We have the request in our duplicate cache, remove it */
+ fd_list_unlink(&r->by_id);
+ fd_list_unlink(&r->by_time);
+ dupl_free_req_info(r);
+ break;
+ }
+
+ CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) );
+
+ /* Finished */
+ return 0;
+}
+
diff --git a/extensions/app_radgw/rgw_common.h b/extensions/app_radgw/rgw_common.h
new file mode 100644
index 0000000..bfb700a
--- /dev/null
+++ b/extensions/app_radgw/rgw_common.h
@@ -0,0 +1,287 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* This file contains definitions for both app_radgw extension and its plugins. */
+
+#ifndef _RGW_COMMON_H
+#define _RGW_COMMON_H
+
+/* Include definitions from the freeDiameter framework */
+#include <freeDiameter/extension.h>
+
+/* Include hostap files for RADIUS processings */
+#include "hostap_compat.h"
+#include "md5.h"
+#include "radius.h"
+
+
+/**************************************************************/
+/* Interface with gateway's plug-ins */
+/**************************************************************/
+/* This structure is private for each plugin */
+struct rgwp_config;
+
+/* This structure points to a RADIUS client description, the definition is not known to plugins */
+struct rgw_client;
+
+int rgw_clients_getkey(struct rgw_client * cli, unsigned char **key, size_t *key_len);
+char * rgw_clients_id(struct rgw_client *cli);
+int rgw_clients_get_origin(struct rgw_client *cli, DiamId_t *fqdn, size_t *fqdnlen, DiamId_t *realm, size_t *realmlen);
+
+/* Each plugin must provide the following structure. */
+extern struct rgw_api {
+ /* The name of the plugin */
+ const char * rgwp_name;
+
+ /* Parse the configuration file. It may be called several times with different configurations.
+ Called even if no configuration file is passed (with NULL conf_file parameter then) */
+ int (*rgwp_conf_parse) ( char * conf_file, struct rgwp_config ** state );
+
+ /* Cleanup the configuration state when the daemon is exiting (called even if state is NULL). */
+ void (*rgwp_conf_free) (struct rgwp_config * state);
+
+ /* handle an incoming RADIUS message */
+ int (*rgwp_rad_req) ( struct rgwp_config * conf, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli );
+ /* ret >0: critical error (errno), log and exit.
+ ret 0: continue;
+ ret -1: stop processing this message
+ ret -2: reply the content of rad_ans to the RADIUS client immediatly
+ */
+
+ /* handle the corresponding Diameter answer */
+ int (*rgwp_diam_ans) ( struct rgwp_config * conf, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli );
+ /* ret 0: continue; ret >0: error; ret: -1 ... (tbd) */
+
+} rgwp_descriptor;
+
+
+
+/**************************************************************/
+/* Additional definitions */
+/**************************************************************/
+/* Type of message / server */
+#define RGW_PLG_TYPE_AUTH 1
+#define RGW_PLG_TYPE_ACCT 2
+
+/* Class attribute prefix to store the Auth Application Id (required to send STR) */
+#define CLASS_AAI_PREFIX "fD/rgwx/aai:"
+
+/* Attributes missing from radius.h (not used in EAP) */
+enum { RADIUS_ATTR_CHAP_PASSWORD = 3,
+ RADIUS_ATTR_SERVICE_TYPE = 6,
+ RADIUS_ATTR_FRAMED_PROTOCOL = 7,
+ RADIUS_ATTR_FRAMED_IP_ADDRESS = 8,
+ RADIUS_ATTR_FRAMED_IP_NETMASK = 9,
+ RADIUS_ATTR_FRAMED_ROUTING = 10,
+ RADIUS_ATTR_FILTER_ID = 11,
+ RADIUS_ATTR_FRAMED_COMPRESSION = 13,
+ RADIUS_ATTR_LOGIN_IP_HOST = 14,
+ RADIUS_ATTR_LOGIN_SERVICE = 15,
+ RADIUS_ATTR_LOGIN_TCP_PORT = 16,
+ RADIUS_ATTR_CALLBACK_NUMBER = 19,
+ RADIUS_ATTR_CALLBACK_ID = 20,
+ RADIUS_ATTR_FRAMED_ROUTE = 22,
+ RADIUS_ATTR_FRAMED_IPX_NETWORK = 23,
+ RADIUS_ATTR_LOGIN_LAT_SERVICE = 34,
+ RADIUS_ATTR_LOGIN_LAT_NODE = 35,
+ RADIUS_ATTR_LOGIN_LAT_GROUP = 36,
+ RADIUS_ATTR_FRAMED_APPLETALK_LINK = 37,
+ RADIUS_ATTR_FRAMED_APPLETALK_NETWORK = 38,
+ RADIUS_ATTR_FRAMED_APPLETALK_ZONE = 39,
+ RADIUS_ATTR_CHAP_CHALLENGE = 60,
+ RADIUS_ATTR_PORT_LIMIT = 62,
+ RADIUS_ATTR_LOGIN_LAT_PORT = 63,
+ RADIUS_ATTR_TUNNEL_CLIENT_ENDPOINT = 66,
+ RADIUS_ATTR_TUNNEL_SERVER_ENDPOINT = 67,
+ RADIUS_ATTR_TUNNEL_PASSWORD = 69,
+ RADIUS_ATTR_ARAP_PASSWORD = 70,
+ RADIUS_ATTR_ARAP_FEATURES = 71,
+ RADIUS_ATTR_ARAP_ZONE_ACCESS = 72,
+ RADIUS_ATTR_ARAP_SECURITY = 73,
+ RADIUS_ATTR_ARAP_SECURITY_DATA = 74,
+ RADIUS_ATTR_PASSWORD_RETRY = 75,
+ RADIUS_ATTR_PROMPT = 76,
+ RADIUS_ATTR_CONFIGURATION_TOKEN = 78,
+ RADIUS_ATTR_TUNNEL_ASSIGNMENT_ID = 82,
+ RADIUS_ATTR_TUNNEL_PREFERENCE = 83,
+ RADIUS_ATTR_ARAP_CHALLENGE_RESPONSE = 84,
+ RADIUS_ATTR_NAS_PORT_ID = 87,
+ RADIUS_ATTR_FRAMED_POOL = 88,
+ RADIUS_ATTR_TUNNEL_CLIENT_AUTH_ID = 90,
+ RADIUS_ATTR_TUNNEL_SERVER_AUTH_ID = 91,
+ RADIUS_ATTR_ORIGINATING_LINE_INFO = 94,
+ RADIUS_ATTR_FRAMED_INTERFACE_ID = 96,
+ RADIUS_ATTR_FRAMED_IPV6_PREFIX = 97,
+ RADIUS_ATTR_LOGIN_IPV6_HOST = 98,
+ RADIUS_ATTR_FRAMED_IPV6_ROUTE = 99,
+ RADIUS_ATTR_FRAMED_IPV6_POOL = 100,
+ RADIUS_ATTR_ERROR_CAUSE = 101,
+ RADIUS_ATTR_EAP_KEY_NAME = 102,
+ RADIUS_ATTR_DIGEST_RESPONSE = 103,
+ RADIUS_ATTR_DIGEST_REALM = 104,
+ RADIUS_ATTR_DIGEST_NONCE = 105,
+ RADIUS_ATTR_DIGEST_RESPONSE_AUTH=106,
+ RADIUS_ATTR_DIGEST_NEXTNONCE = 107,
+ RADIUS_ATTR_DIGEST_METHOD = 108,
+ RADIUS_ATTR_DIGEST_URI = 109,
+ RADIUS_ATTR_DIGEST_QOP = 110,
+ RADIUS_ATTR_DIGEST_ALGORITHM = 111,
+ RADIUS_ATTR_DIGEST_ENTITY_BODY_HASH=112,
+ RADIUS_ATTR_DIGEST_CNONCE = 113,
+ RADIUS_ATTR_DIGEST_NONCE_COUNT = 114,
+ RADIUS_ATTR_DIGEST_USERNAME = 115,
+ RADIUS_ATTR_DIGEST_HA1 = 121,
+ RADIUS_ATTR_SIP_AOR = 122
+};
+
+enum { DIAM_ATTR_USER_NAME = 1,
+ DIAM_ATTR_USER_PASSWORD = 2,
+ DIAM_ATTR_NAS_IP_ADDRESS = 4,
+ DIAM_ATTR_NAS_PORT = 5,
+ DIAM_ATTR_SERVICE_TYPE = 6,
+ DIAM_ATTR_FRAMED_PROTOCOL = 7,
+ DIAM_ATTR_FRAMED_IP_ADDRESS = 8,
+ DIAM_ATTR_FRAMED_IP_NETMASK = 9,
+ DIAM_ATTR_FRAMED_ROUTING = 10,
+ DIAM_ATTR_FILTER_ID = 11,
+ DIAM_ATTR_FRAMED_MTU = 12,
+ DIAM_ATTR_FRAMED_COMPRESSION = 13,
+ DIAM_ATTR_LOGIN_IP_HOST = 14,
+ DIAM_ATTR_LOGIN_SERVICE = 15,
+ DIAM_ATTR_LOGIN_TCP_PORT = 16,
+ DIAM_ATTR_REPLY_MESSAGE = 18,
+ DIAM_ATTR_CALLBACK_NUMBER = 19,
+ DIAM_ATTR_CALLBACK_ID = 20,
+ DIAM_ATTR_FRAMED_ROUTE = 22,
+ DIAM_ATTR_FRAMED_IPX_NETWORK = 23,
+ DIAM_ATTR_STATE = 24,
+ DIAM_ATTR_CLASS = 25,
+ DIAM_ATTR_IDLE_TIMEOUT = 28,
+ DIAM_ATTR_NAS_IDENTIFIER = 32,
+ DIAM_ATTR_LOGIN_LAT_SERVICE = 34,
+ DIAM_ATTR_LOGIN_LAT_NODE = 35,
+ DIAM_ATTR_LOGIN_LAT_GROUP = 36,
+ DIAM_ATTR_FRAMED_APPLETALK_LINK = 37,
+ DIAM_ATTR_FRAMED_APPLETALK_NETWORK = 38,
+ DIAM_ATTR_FRAMED_APPLETALK_ZONE = 39,
+ DIAM_ATTR_ACCT_SESSION_ID = 44,
+ DIAM_ATTR_ACCT_MULTI_SESSION_ID = 50,
+ DIAM_ATTR_EVENT_TIMESTAMP = 55,
+ DIAM_ATTR_NAS_PORT_TYPE = 61,
+ DIAM_ATTR_PORT_LIMIT = 62,
+ DIAM_ATTR_LOGIN_LAT_PORT = 63,
+ DIAM_ATTR_TUNNEL_TYPE = 64,
+ DIAM_ATTR_TUNNEL_MEDIUM_TYPE = 65,
+ DIAM_ATTR_TUNNEL_CLIENT_ENDPOINT = 66,
+ DIAM_ATTR_TUNNEL_SERVER_ENDPOINT = 67,
+ DIAM_ATTR_TUNNEL_PASSWORD = 69,
+ DIAM_ATTR_ARAP_FEATURES = 71,
+ DIAM_ATTR_ARAP_ZONE_ACCESS = 72,
+ DIAM_ATTR_ARAP_SECURITY = 73,
+ DIAM_ATTR_ARAP_SECURITY_DATA = 74,
+ DIAM_ATTR_PASSWORD_RETRY = 75,
+ DIAM_ATTR_PROMPT = 76,
+ DIAM_ATTR_CONFIGURATION_TOKEN = 78,
+ DIAM_ATTR_TUNNEL_PRIVATE_GROUP_ID = 81,
+ DIAM_ATTR_TUNNEL_ASSIGNMENT_ID = 82,
+ DIAM_ATTR_TUNNEL_PREFERENCE = 83,
+ DIAM_ATTR_ARAP_CHALLENGE_RESPONSE = 84,
+ DIAM_ATTR_ACCT_INTERIM_INTERVAL = 85,
+ DIAM_ATTR_NAS_PORT_ID = 87,
+ DIAM_ATTR_FRAMED_POOL = 88,
+ DIAM_ATTR_TUNNEL_CLIENT_AUTH_ID = 90,
+ DIAM_ATTR_TUNNEL_SERVER_AUTH_ID = 91,
+ DIAM_ATTR_NAS_IPV6_ADDRESS = 95,
+ DIAM_ATTR_FRAMED_INTERFACE_ID = 96,
+ DIAM_ATTR_FRAMED_IPV6_PREFIX = 97,
+ DIAM_ATTR_LOGIN_IPV6_HOST = 98,
+ DIAM_ATTR_FRAMED_IPV6_ROUTE = 99,
+ DIAM_ATTR_FRAMED_IPV6_POOL = 100,
+ DIAM_ATTR_EAP_KEY_NAME = 102,
+ DIAM_ATTR_DIGEST_RESPONSE = 103,
+ DIAM_ATTR_DIGEST_REALM = 104,
+ DIAM_ATTR_DIGEST_NONCE = 105,
+ DIAM_ATTR_DIGEST_RESPONSE_AUTH=106,
+ DIAM_ATTR_DIGEST_NEXTNONCE = 107,
+ DIAM_ATTR_DIGEST_METHOD = 108,
+ DIAM_ATTR_DIGEST_URI = 109,
+ DIAM_ATTR_DIGEST_QOP = 110,
+ DIAM_ATTR_DIGEST_ALGORITHM = 111,
+ DIAM_ATTR_DIGEST_ENTITY_BODY_HASH=112,
+ DIAM_ATTR_DIGEST_CNONCE = 113,
+ DIAM_ATTR_DIGEST_NONCE_COUNT = 114,
+ DIAM_ATTR_DIGEST_USERNAME = 115,
+ DIAM_ATTR_DIGEST_HA1 = 121,
+ DIAM_ATTR_SIP_AOR = 122,
+ DIAM_ATTR_AUTH_APPLICATION_ID = 258,
+ DIAM_ATTR_ACCT_APPLICATION_ID = 259,
+ DIAM_ATTR_VENDOR_SPECIFIC_APPLICATION_ID = 260,
+ DIAM_ATTR_SESSION_ID = 263,
+ DIAM_ATTR_ORIGIN_HOST = 264,
+ DIAM_ATTR_MULTI_ROUND_TIMEOUT = 272,
+ DIAM_ATTR_AUTH_REQUEST_TYPE = 274,
+ DIAM_ATTR_AUTH_GRACE_PERIOD = 276,
+ DIAM_ATTR_AUTH_SESSION_STATE = 277,
+ DIAM_ATTR_ORIGIN_STATE_ID = 278,
+ DIAM_ATTR_FAILED_AVP = 279,
+ DIAM_ATTR_ERROR_MESSAGE = 281,
+ DIAM_ATTR_ROUTE_RECORD = 282,
+ DIAM_ATTR_PROXY_INFO = 284,
+ DIAM_ATTR_ACCOUNTING_SUB_SESSION_ID = 287,
+ DIAM_ATTR_ERROR_REPORTING_HOST = 294,
+ DIAM_ATTR_TERMINATION_CAUSE = 295,
+ DIAM_ATTR_ORIGIN_REALM = 296,
+ DIAM_ATTR_SIP_AUTH_DATA_ITEM = 376,
+ DIAM_ATTR_SIP_AUTHENTICATION_SCHEME = 377,
+ DIAM_ATTR_SIP_AUTHENTICATE = 379,
+ DIAM_ATTR_SIP_NUMBER_AUTH_ITEMS = 382,
+ DIAM_ATTR_NAS_FILTER_RULE = 400,
+ DIAM_ATTR_TUNNELING = 401,
+ DIAM_ATTR_QOS_FILTER_RULE = 407,
+ DIAM_ATTR_ORIGIN_AAA_PROTOCOL = 408,
+ DIAM_ATTR_EAP_PAYLOAD = 462,
+ DIAM_ATTR_EAP_REISSUED_PAYLOAD = 463,
+ DIAM_ATTR_EAP_MASTER_SESSION_KEY = 464,
+ DIAM_ATTR_ACCOUNTING_EAP_AUTH_METHOD = 465,
+ DIAM_ATTR_ACCOUNTING_RECORD_TYPE = 480,
+ DIAM_ATTR_ACCOUNTING_REALTIME_REQUIRED = 483,
+ DIAM_ATTR_ACCOUNTING_RECORD_NUMBER = 485
+};
+
+const char * rgw_msg_attrtype_str(unsigned char c);
+const char * rgw_msg_code_str(unsigned char c);
+
+#endif /* _RGW_COMMON_H */
+
diff --git a/extensions/app_radgw/rgw_conf.l b/extensions/app_radgw/rgw_conf.l
new file mode 100644
index 0000000..8afd779
--- /dev/null
+++ b/extensions/app_radgw/rgw_conf.l
@@ -0,0 +1,277 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+/* Lex configuration parser for radius_gw extension.
+ *
+ */
+
+%{
+#include "rgw.h"
+#include "rgw_conf.tab.h"
+
+/* Update the column information */
+#define YY_USER_ACTION { \
+ yylloc->first_column = yylloc->last_column + 1; \
+ yylloc->last_column = yylloc->first_column + yyleng - 1; \
+}
+
+/* %option noinput ? */
+#define YY_NO_INPUT
+%}
+
+%option bison-bridge bison-locations
+%option noyywrap
+%option nounput
+
+/* Use the following start condition to parse an URI */
+%x IN_PLG
+%x IN_CLI1
+%x IN_CLI2
+%x EXPECT_IP4
+%x EXPECT_IP6
+%x EXPECT_DECINT
+
+/* Quoted string. Multilines do not match. */
+qstring \"[^\"\n]*\"
+
+/* Used to match IP, IP6, and port */
+IP4 [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
+IP6 [[:xdigit:]]*:[[:xdigit:]]*:[[:xdigit:].:]*
+BR_PORT [[][0-9]+[]]
+
+
+%%
+
+ /* All sections */
+<*>\n {
+ /* Update the line count */
+ yylloc->first_line++;
+ yylloc->last_line++;
+ yylloc->last_column=0;
+ }
+
+<*>([[:space:]]{-}[\n])+ ; /* Eat all spaces, not new lines */
+<*>#.*$ ; /* Eat all comments */
+
+<*>{qstring} {
+ /* First copy the string without the quotes for use in the yacc parser */
+ yylval->string = strdup(yytext+1);
+ if (yylval->string == NULL) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ yylval->string[yyleng-2] = '\0';
+
+ /* the yacc parser will check the string is valid */
+ return QSTRING;
+ }
+
+
+ /* Extension section */
+(?i:"RGWX") { BEGIN(IN_PLG); return PLG_PREFIX; }
+
+<IN_PLG>(?i:"auth") { return AUTH; }
+<IN_PLG>(?i:"acct") { return ACCT; }
+
+<IN_PLG,IN_CLI2>[[:xdigit:]]+ {
+ /* Convert this to an integer value */
+ int ret = sscanf(yytext, "%x", &yylval->integer);
+ if (ret != 1) {
+ /* No matching: an error occurred */
+ fd_log_debug("Unable to convert the value '%s' to a valid number: %s", yytext, strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ /* Maybe we could REJECT instead of failing here? */
+ }
+ return INTEGER;
+ }
+
+<IN_PLG>[:] { return yytext[0]; }
+
+
+ /* Client section */
+(?i:"nas"|"cli") { BEGIN(IN_CLI1); yylval->integer=RGW_CLI_NAS; return NAS_OR_PXY; }
+(?i:"pxy") { BEGIN(IN_CLI1); yylval->integer=RGW_CLI_PXY; return NAS_OR_PXY; }
+
+ /* Match an IP (4 or 6) and optional port */
+<IN_CLI1>({IP4}|{IP6}){BR_PORT}? {
+ char * work;
+ char * port;
+ unsigned short p = 0;
+
+ work = strdup(yytext);
+ if ( work == NULL ) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ if ((port = strchr(work, '[')) != NULL) {
+ *port = '\0';
+ port++;
+ if (sscanf(port, "%hu]", &p) != 1) {
+ fd_log_debug("'%s' is not a valid port: %s", port, strerror(errno));
+ free(work);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+ }
+
+ /* Do we have an IP or IPv6? Let's check if we have ':' char somewhere in the beginning */
+ if (memchr(work, ':', 5) != NULL) {
+ struct sockaddr_in6 * sin6 = NULL;
+
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ free(work);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ memset(sin6, 0, sizeof(struct sockaddr_in6));
+ sin6->sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, work, &sin6->sin6_addr) != 1) {
+ fd_log_debug("'%s' is not a valid IPv6 address: %s", work, strerror(errno));
+ free(work);
+ free(sin6);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+ sin6->sin6_port = htons(p);
+ yylval->ss = (struct sockaddr *)sin6;
+ } else {
+ struct sockaddr_in * sin = NULL;
+
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ free(work);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_family = AF_INET;
+ if (inet_pton(AF_INET, work, &sin->sin_addr) != 1) {
+ fd_log_debug("'%s' is not a valid IP address: %s", work, strerror(errno));
+ free(work);
+ free(sin);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ sin->sin_port = htons(p);
+ yylval->ss = (struct sockaddr *)sin;
+ }
+ free(work);
+ return IP;
+ }
+
+
+<IN_CLI1>"/" { BEGIN(IN_CLI2); return '/'; }
+
+
+ /* Servers section */
+(?i:"auth_server_enable") { BEGIN(EXPECT_DECINT); return AUTH_ENABLE; }
+(?i:"auth_server_port") { BEGIN(EXPECT_DECINT); return AUTH_PORT; }
+(?i:"auth_server_ip4") { BEGIN(EXPECT_IP4); return AUTH_IP4; }
+(?i:"auth_server_ip6") { BEGIN(EXPECT_IP6); return AUTH_IP6; }
+(?i:"acct_server_enable") { BEGIN(EXPECT_DECINT); return ACCT_ENABLE; }
+(?i:"acct_server_port") { BEGIN(EXPECT_DECINT); return ACCT_PORT; }
+(?i:"acct_server_ip4") { BEGIN(EXPECT_IP4); return ACCT_IP4; }
+(?i:"acct_server_ip6") { BEGIN(EXPECT_IP6); return ACCT_IP6; }
+
+<EXPECT_DECINT>[[:digit:]]+ {
+ /* Match an integer (not hexa) */
+ int ret = sscanf(yytext, "%d", &yylval->integer);
+ if (ret != 1) {
+ /* No matching: an error occurred */
+ fd_log_debug("Unable to convert the value '%s' to a valid number: %s", yytext, strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ /* Maybe we could REJECT instead of failing here? */
+ }
+ return INTEGER;
+ }
+
+<EXPECT_IP4,EXPECT_IP6>(?i:"disable") { return DISABLED; }
+
+<EXPECT_IP4>{IP4} {
+ struct sockaddr_in * sin = NULL;
+
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ memset(sin, 0, sizeof(struct sockaddr_in));
+ sin->sin_family = AF_INET;
+ if (inet_pton(AF_INET, yytext, &sin->sin_addr) != 1) {
+ fd_log_debug("'%s' is not a valid IP address: %s", yytext, strerror(errno));
+ free(sin);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+ yylval->ss = (struct sockaddr *)sin;
+ return IP;
+ }
+
+<EXPECT_IP6>{IP6} {
+ struct sockaddr_in6 * sin6 = NULL;
+
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL) {
+ fd_log_debug("Unable to allocate memory: %s", strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+
+ memset(sin6, 0, sizeof(struct sockaddr_in6));
+ sin6->sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, yytext, &sin6->sin6_addr) != 1) {
+ fd_log_debug("'%s' is not a valid IPv6 address: %s", yytext, strerror(errno));
+ free(sin6);
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+ yylval->ss = (struct sockaddr *)sin6;
+ return IP;
+ }
+
+
+ /* Valid single characters for yyparse in all contexts */
+<*>[=] { return yytext[0]; }
+<*>[;] { BEGIN(INITIAL); return yytext[0]; }
+
+ /* Unrecognized token */
+<*>[[:alnum:]]+ | /* This rule is only useful to print a complete token in error messages */
+ /* Unrecognized character */
+<*>. {
+ fd_log_debug("Unrecognized text on line %d col %d: '%s'.", yylloc->first_line, yylloc->first_column, yytext);
+ return LEX_ERROR;
+ }
+
+%%
diff --git a/extensions/app_radgw/rgw_conf.y b/extensions/app_radgw/rgw_conf.y
new file mode 100644
index 0000000..53ab746
--- /dev/null
+++ b/extensions/app_radgw/rgw_conf.y
@@ -0,0 +1,404 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Yacc extension's configuration parser.
+ * See doc/app_radgw.conf.sample for configuration file format
+ */
+
+/* For development only : */
+%debug
+%error-verbose
+
+/* The parser receives the configuration file filename as parameter */
+%parse-param {char * conffile}
+
+/* Keep track of location */
+%locations
+%pure-parser
+
+%{
+#include "rgw.h"
+#include "rgw_conf.tab.h" /* bison is not smart enough to define the YYLTYPE before including this code, so... */
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+/* Forward declaration */
+int yyparse(char * conffile);
+
+/* Parse the configuration file */
+int rgw_conf_handle(char * conffile)
+{
+ extern FILE * rgw_confin;
+ int ret;
+
+ rgw_confin = fopen(conffile, "r");
+ if (rgw_confin == NULL) {
+ ret = errno;
+ fd_log_debug("Unable to open extension configuration file %s for reading: %s", conffile, strerror(ret));
+ return ret;
+ }
+
+ ret = rgw_confparse(conffile);
+
+ fclose(rgw_confin);
+
+ if (ret != 0) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/* The Lex parser prototype */
+int rgw_conflex(YYSTYPE *lvalp, YYLTYPE *llocp);
+
+/* Function to report the errors */
+void yyerror (YYLTYPE *ploc, char * conffile, char const *s)
+{
+ if (ploc->first_line != ploc->last_line)
+ fd_log_debug("%s:%d.%d-%d.%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_line, ploc->last_column, s);
+ else if (ploc->first_column != ploc->last_column)
+ fd_log_debug("%s:%d.%d-%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_column, s);
+ else
+ fd_log_debug("%s:%d.%d : %s", conffile, ploc->first_line, ploc->first_column, s);
+}
+
+/* Very simple byte stack management */
+static unsigned char * buf = NULL;
+static size_t buf_sz = 0;
+static size_t buf_rsz = 0;
+static inline int buf_add(unsigned char val) /* add a value in the array */
+{
+ buf_sz++;
+
+ if (buf_sz > buf_rsz) {
+ void * rez=NULL;
+ buf_rsz += 256;
+ rez = realloc(buf, buf_rsz);
+ if (rez == NULL) {
+ fd_log_debug("Error on memory allocation: %s", strerror(errno));
+ return 0;
+ }
+ buf = (unsigned char *)rez;
+ }
+ buf[buf_sz - 1] = val;
+ return 1;
+}
+static inline void buf_reinit(void)
+{
+ free(buf);
+ buf = NULL;
+ buf_sz = 0;
+ buf_rsz = 0;
+}
+
+static int port = 0;
+static char * plgconffile = NULL;
+
+%}
+
+/* Values returned by lex for token */
+%union {
+ char *string; /* The string is allocated by strdup in lex.*/
+ int integer; /* Store integer values */
+ struct sockaddr *ss; /* sockaddr to free after use (alloc in lex) */
+}
+
+/* typed data */
+%token <string> QSTRING
+%token <integer> INTEGER
+%token <ss> IP
+
+%type <string> FINDFILEEXT
+
+%token <integer> NAS_OR_PXY
+
+/* simple tokens */
+%token DISABLED
+%token AUTH
+%token ACCT
+
+%token PLG_PREFIX
+
+%token AUTH_ENABLE
+%token AUTH_PORT
+%token AUTH_IP4
+%token AUTH_IP6
+%token ACCT_ENABLE
+%token ACCT_PORT
+%token ACCT_IP4
+%token ACCT_IP6
+
+/* In case of error in the lexical analysis */
+%token LEX_ERROR
+
+
+/* -------------------------------------- */
+%%
+
+ /* The grammar definition */
+conffile: /* empty grammar is OK */
+ | conffile plugin
+ | conffile clientdef
+ | conffile authserv
+ | conffile acctserv
+ ;
+
+
+/* -------------------------------------- */
+FINDFILEEXT: QSTRING
+ {
+ char * fname = $1;
+ FILE * fd;
+
+ /* First, check if the file exists */
+ fd = fopen(fname, "r");
+ if ((fd == NULL) && (*fname != '/')) {
+ char * bkp = fname;
+ CHECK_MALLOC_DO( fname = malloc( strlen(bkp) + strlen(DEFAULT_EXTENSIONS_PATH) + 2 ),
+ { yyerror (&yylloc, conffile, "Not enough memory"); YYERROR; } );
+ sprintf(fname, DEFAULT_EXTENSIONS_PATH "/%s", bkp);
+ free(bkp);
+ fd = fopen(fname, "r");
+ }
+ if (fd == NULL) {
+ int ret = errno;
+ TRACE_DEBUG(INFO, "Unable to open file %s for reading: %s", fname, strerror(ret));
+ yyerror (&yylloc, conffile, "Error adding plugin");
+ YYERROR;
+ }
+ fclose(fd);
+
+ $$ = fname;
+ }
+ ;
+/* -------------------------------------- */
+plugin: {
+ /* Reset the parameters */
+ buf_reinit();
+ port = RGW_PLG_TYPE_AUTH | RGW_PLG_TYPE_ACCT ;
+ free(plgconffile); plgconffile = NULL;
+ }
+ PLG_PREFIX '=' FINDFILEEXT plg_attributes ';'
+ {
+ /* Add this extension in the list */
+ if ( rgw_plg_add( $4, plgconffile, port, &buf, buf_sz ) ) {
+ yyerror (&yylloc, conffile, "Error parsing / adding extension !");
+ YYERROR;
+ }
+
+ /* Free the array */
+ buf_reinit();
+
+ /* stop conffile from being freed here */
+ plgconffile = NULL;
+ }
+ ;
+
+plg_attributes: /* empty */
+ | plg_attributes ':' QSTRING
+ {
+ plgconffile = $3;
+ }
+ | plg_attributes ':' AUTH
+ {
+ port = RGW_PLG_TYPE_AUTH;
+ }
+ | plg_attributes ':' ACCT
+ {
+ port = RGW_PLG_TYPE_ACCT;
+ }
+ | plg_attributes ':' extcodes_list
+ ;
+
+extcodes_list: /* empty */
+ | extcodes_list INTEGER
+ {
+ if ($2 < 0 || $2 > 255) {
+ yyerror (&yylloc, conffile, "Invalid command code value!");
+ YYERROR;
+ }
+ if ( ! buf_add((unsigned char)$2) ) {
+ yyerror (&yylloc, conffile, "Error allocating memory!");
+ YYERROR;
+ }
+ }
+ ;
+
+/* -------------------------------------- */
+
+clientdef: {
+ buf_reinit();
+ }
+ NAS_OR_PXY '=' IP '/' clisecret_key ';'
+ {
+ /* Add this client */
+ if ( rgw_clients_add( $4, &buf, buf_sz, $2 ) ) {
+ yyerror (&yylloc, conffile, "Error parsing / adding client !");
+ YYERROR;
+ }
+
+ /* reinit the buffer */
+ buf_reinit();
+ }
+ ;
+
+clisecret_key: /* empty */
+ | clisecret_key QSTRING
+ {
+ int i;
+ size_t len = strlen($2);
+ for (i = 0; i < len; i++) {
+ if ( ! buf_add( $2 [i] ) ) {
+ yyerror (&yylloc, conffile, "Memory allocation error.");
+ YYERROR;
+ }
+ }
+
+ free($2);
+ }
+ | clisecret_key INTEGER
+ {
+ if ( $2 < 0 || $2 > 255 ) {
+ yyerror (&yylloc, conffile, "Invalid value in key.");
+ YYERROR;
+ }
+
+ if ( ! buf_add( $2 ) ) {
+ yyerror (&yylloc, conffile, "Memory allocation error.");
+ YYERROR;
+ }
+ }
+ ;
+
+/* -------------------------------------- */
+
+authserv: AUTH_ENABLE '=' INTEGER ';'
+ {
+ if ($3 == 0) {
+ rgw_servers.auth_serv.disabled = 1;
+ } else {
+ rgw_servers.auth_serv.disabled = 0;
+ }
+ }
+ | AUTH_PORT '=' INTEGER ';'
+ {
+ if ($3 <= 0 || $3 > 65535) {
+ yyerror (&yylloc, conffile, "Invalid port number !");
+ YYERROR;
+ }
+
+ rgw_servers.auth_serv.port = htons($3);
+ }
+ | AUTH_IP4 '=' DISABLED ';'
+ {
+ rgw_servers.auth_serv.ip_disabled = 1;
+ }
+ | AUTH_IP4 '=' IP ';'
+ {
+ if (((struct sockaddr *)($3))->sa_family != AF_INET) {
+ yyerror (&yylloc, conffile, "Invalid address specification !");
+ YYERROR;
+ }
+ memcpy( & rgw_servers.auth_serv.ip_endpoint, &((struct sockaddr_in *)($3))->sin_addr, sizeof(struct in_addr) );
+ free($3);
+ rgw_servers.auth_serv.ip_disabled = 0;
+ }
+ | AUTH_IP6 '=' DISABLED ';'
+ {
+ rgw_servers.auth_serv.ip6_disabled = 1;
+ }
+ | AUTH_IP6 '=' IP ';'
+ {
+ if (((struct sockaddr *)($3)) -> sa_family != AF_INET6) {
+ yyerror (&yylloc, conffile, "Invalid address specification !");
+ YYERROR;
+ }
+ memcpy( & rgw_servers.auth_serv.ip6_endpoint, &((struct sockaddr_in6 *)($3))->sin6_addr, sizeof(struct in6_addr) );
+ free($3);
+ rgw_servers.auth_serv.ip6_disabled = 0;
+ }
+ ;
+
+/* -------------------------------------- */
+
+acctserv: ACCT_ENABLE '=' INTEGER ';'
+ {
+ if ($3 == 0) {
+ rgw_servers.acct_serv.disabled = 1;
+ } else {
+ rgw_servers.acct_serv.disabled = 0;
+ }
+ }
+ | ACCT_PORT '=' INTEGER ';'
+ {
+ if ($3 <= 0 || $3 > 65535) {
+ yyerror (&yylloc, conffile, "Invalid port number !");
+ YYERROR;
+ }
+
+ rgw_servers.acct_serv.port = htons($3);
+ }
+ | ACCT_IP4 '=' DISABLED ';'
+ {
+ rgw_servers.acct_serv.ip_disabled = 1;
+ }
+ | ACCT_IP4 '=' IP ';'
+ {
+ if (((struct sockaddr *)($3)) -> sa_family != AF_INET) {
+ yyerror (&yylloc, conffile, "Invalid address specification !");
+ YYERROR;
+ }
+ memcpy( & rgw_servers.auth_serv.ip_endpoint, &((struct sockaddr_in *)($3))->sin_addr, sizeof(struct in_addr) );
+ free($3);
+ rgw_servers.acct_serv.ip_disabled = 0;
+ }
+ | ACCT_IP6 '=' DISABLED ';'
+ {
+ rgw_servers.acct_serv.ip6_disabled = 1;
+ }
+ | ACCT_IP6 '=' IP ';'
+ {
+ if (((struct sockaddr *)($3)) -> sa_family != AF_INET6) {
+ yyerror (&yylloc, conffile, "Invalid address specification !");
+ YYERROR;
+ }
+ memcpy( & rgw_servers.auth_serv.ip6_endpoint, &((struct sockaddr_in6 *)($3))->sin6_addr, sizeof(struct in6_addr) );
+ free($3);
+ rgw_servers.acct_serv.ip6_disabled = 0;
+ }
+ ;
diff --git a/extensions/app_radgw/rgw_main.c b/extensions/app_radgw/rgw_main.c
new file mode 100644
index 0000000..9276ef1
--- /dev/null
+++ b/extensions/app_radgw/rgw_main.c
@@ -0,0 +1,76 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+/*
+ * Main file of the app_radgw extension.
+ */
+
+#include "rgw.h"
+
+/* Extension entry point called by freeDiameter */
+static int rgw_main(char * conffile)
+{
+ CHECK_FCT( rgw_clients_init() );
+
+ CHECK_FCT( rgw_servers_init() );
+
+ CHECK_FCT( rgw_conf_handle(conffile) );
+
+ LOG_D( "Extension RADIUS Gateway initialized with configuration: '%s'", conffile);
+ rgw_servers_dump();
+ rgw_clients_dump();
+ rgw_plg_dump();
+
+ /* Start making extension list accelerators */
+ rgw_plg_start_cache();
+
+ /* Start the worker threads */
+ CHECK_FCT( rgw_work_start() );
+
+ /* Start the servers */
+ CHECK_FCT( rgw_servers_start() );
+
+ return 0;
+}
+
+/* Unload */
+void fd_ext_fini(void)
+{
+ rgw_servers_fini();
+ rgw_work_fini();
+ rgw_plg_fini();
+ rgw_clients_fini();
+}
+
+EXTENSION_ENTRY("app_radgw", rgw_main);
diff --git a/extensions/app_radgw/rgw_msg_attrtype.c b/extensions/app_radgw/rgw_msg_attrtype.c
new file mode 100644
index 0000000..b14e54c
--- /dev/null
+++ b/extensions/app_radgw/rgw_msg_attrtype.c
@@ -0,0 +1,300 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2011, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+#include "rgw_common.h"
+
+/* The content of this file was semi-automatically generated from the IANA registry. */
+
+/* Name of RADIUS attribute from its code */
+const char * rgw_msg_attrtype_str(unsigned char c) {
+ /* 1 User-Name */
+ if ( c == 1) return "User-Name ";
+ /* 2 User-Password */
+ if ( c == 2) return "User-Password ";
+ /* 3 CHAP-Password */
+ if ( c == 3) return "CHAP-Password ";
+ /* 4 NAS-IP-Address */
+ if ( c == 4) return "NAS-IP-Address ";
+ /* 5 NAS-Port */
+ if ( c == 5) return "NAS-Port ";
+ /* 6 Service-Type */
+ if ( c == 6) return "Service-Type ";
+ /* 7 Framed-Protocol */
+ if ( c == 7) return "Framed-Protocol ";
+ /* 8 Framed-IP-Address */
+ if ( c == 8) return "Framed-IP-Address ";
+ /* 9 Framed-IP-Netmask */
+ if ( c == 9) return "Framed-IP-Netmask ";
+ /* 10 Framed-Routing */
+ if ( c == 10) return "Framed-Routing ";
+ /* 11 Filter-Id */
+ if ( c == 11) return "Filter-Id ";
+ /* 12 Framed-MTU */
+ if ( c == 12) return "Framed-MTU ";
+ /* 13 Framed-Compression */
+ if ( c == 13) return "Framed-Compression ";
+ /* 14 Login-IP-Host */
+ if ( c == 14) return "Login-IP-Host ";
+ /* 15 Login-Service */
+ if ( c == 15) return "Login-Service ";
+ /* 16 Login-TCP-Port */
+ if ( c == 16) return "Login-TCP-Port ";
+ /* 17 Unassigned */
+ if ( c == 17) return "Unassigned ";
+ /* 18 Reply-Message */
+ if ( c == 18) return "Reply-Message ";
+ /* 19 Callback-Number */
+ if ( c == 19) return "Callback-Number ";
+ /* 20 Callback-Id */
+ if ( c == 20) return "Callback-Id ";
+ /* 21 Unassigned */
+ if ( c == 21) return "Unassigned ";
+ /* 22 Framed-Route */
+ if ( c == 22) return "Framed-Route ";
+ /* 23 Framed-IPX-Network */
+ if ( c == 23) return "Framed-IPX-Network ";
+ /* 24 State */
+ if ( c == 24) return "State ";
+ /* 25 Class */
+ if ( c == 25) return "Class ";
+ /* 26 Vendor-Specific */
+ if ( c == 26) return "Vendor-Specific ";
+ /* 27 Session-Timeout */
+ if ( c == 27) return "Session-Timeout ";
+ /* 28 Idle-Timeout */
+ if ( c == 28) return "Idle-Timeout ";
+ /* 29 Termination-Action */
+ if ( c == 29) return "Termination-Action ";
+ /* 30 Called-Station-Id */
+ if ( c == 30) return "Called-Station-Id ";
+ /* 31 Calling-Station-Id */
+ if ( c == 31) return "Calling-Station-Id ";
+ /* 32 NAS-Identifier */
+ if ( c == 32) return "NAS-Identifier ";
+ /* 33 Proxy-State */
+ if ( c == 33) return "Proxy-State ";
+ /* 34 Login-LAT-Service */
+ if ( c == 34) return "Login-LAT-Service ";
+ /* 35 Login-LAT-Node */
+ if ( c == 35) return "Login-LAT-Node ";
+ /* 36 Login-LAT-Group */
+ if ( c == 36) return "Login-LAT-Group ";
+ /* 37 Framed-AppleTalk-Link */
+ if ( c == 37) return "Framed-AppleTalk-Link ";
+ /* 38 Framed-AppleTalk-Network */
+ if ( c == 38) return "Framed-AppleTalk-Network ";
+ /* 39 Framed-AppleTalk-Zone */
+ if ( c == 39) return "Framed-AppleTalk-Zone ";
+ /* 40 Acct-Status-Type [RFC2866] */
+ if ( c == 40) return "Acct-Status-Type [RFC2866]";
+ /* 41 Acct-Delay-Time [RFC2866] */
+ if ( c == 41) return "Acct-Delay-Time [RFC2866]";
+ /* 42 Acct-Input-Octets [RFC2866] */
+ if ( c == 42) return "Acct-Input-Octets [RFC2866]";
+ /* 43 Acct-Output-Octets [RFC2866] */
+ if ( c == 43) return "Acct-Output-Octets [RFC2866]";
+ /* 44 Acct-Session-Id [RFC2866] */
+ if ( c == 44) return "Acct-Session-Id [RFC2866]";
+ /* 45 Acct-Authentic [RFC2866] */
+ if ( c == 45) return "Acct-Authentic [RFC2866]";
+ /* 46 Acct-Session-Time [RFC2866] */
+ if ( c == 46) return "Acct-Session-Time [RFC2866]";
+ /* 47 Acct-Input-Packets [RFC2866] */
+ if ( c == 47) return "Acct-Input-Packets [RFC2866]";
+ /* 48 Acct-Output-Packets [RFC2866] */
+ if ( c == 48) return "Acct-Output-Packets [RFC2866]";
+ /* 49 Acct-Terminate-Cause [RFC2866] */
+ if ( c == 49) return "Acct-Terminate-Cause [RFC2866]";
+ /* 50 Acct-Multi-Session-Id [RFC2866] */
+ if ( c == 50) return "Acct-Multi-Session-Id [RFC2866]";
+ /* 51 Acct-Link-Count [RFC2866] */
+ if ( c == 51) return "Acct-Link-Count [RFC2866]";
+ /* 52 Acct-Input-Gigawords [RFC2869] */
+ if ( c == 52) return "Acct-Input-Gigawords [RFC2869]";
+ /* 53 Acct-Output-Gigawords [RFC2869] */
+ if ( c == 53) return "Acct-Output-Gigawords [RFC2869]";
+ /* 54 Unassigned */
+ if ( c == 54) return "Unassigned ";
+ /* 55 Event-Timestamp [RFC2869] */
+ if ( c == 55) return "Event-Timestamp [RFC2869]";
+ /* 56 Egress-VLANID [RFC4675] */
+ if ( c == 56) return "Egress-VLANID [RFC4675]";
+ /* 57 Ingress-Filters [RFC4675] */
+ if ( c == 57) return "Ingress-Filters [RFC4675]";
+ /* 58 Egress-VLAN-Name [RFC4675] */
+ if ( c == 58) return "Egress-VLAN-Name [RFC4675]";
+ /* 59 User-Priority-Table [RFC4675] */
+ if ( c == 59) return "User-Priority-Table [RFC4675]";
+ /* 60 CHAP-Challenge */
+ if ( c == 60) return "CHAP-Challenge ";
+ /* 61 NAS-Port-Type */
+ if ( c == 61) return "NAS-Port-Type ";
+ /* 62 Port-Limit */
+ if ( c == 62) return "Port-Limit ";
+ /* 63 Login-LAT-Port */
+ if ( c == 63) return "Login-LAT-Port ";
+ /* 64 Tunnel-Type [RFC2868] */
+ if ( c == 64) return "Tunnel-Type [RFC2868]";
+ /* 65 Tunnel-Medium-Type [RFC2868] */
+ if ( c == 65) return "Tunnel-Medium-Type [RFC2868]";
+ /* 66 Tunnel-Client-Endpoint [RFC2868] */
+ if ( c == 66) return "Tunnel-Client-Endpoint [RFC2868]";
+ /* 67 Tunnel-Server-Endpoint [RFC2868] */
+ if ( c == 67) return "Tunnel-Server-Endpoint [RFC2868]";
+ /* 68 Acct-Tunnel-Connection [RFC2867] */
+ if ( c == 68) return "Acct-Tunnel-Connection [RFC2867]";
+ /* 69 Tunnel-Password [RFC2868] */
+ if ( c == 69) return "Tunnel-Password [RFC2868]";
+ /* 70 ARAP-Password [RFC2869] */
+ if ( c == 70) return "ARAP-Password [RFC2869]";
+ /* 71 ARAP-Features [RFC2869] */
+ if ( c == 71) return "ARAP-Features [RFC2869]";
+ /* 72 ARAP-Zone-Access [RFC2869] */
+ if ( c == 72) return "ARAP-Zone-Access [RFC2869]";
+ /* 73 ARAP-Security [RFC2869] */
+ if ( c == 73) return "ARAP-Security [RFC2869]";
+ /* 74 ARAP-Security-Data [RFC2869] */
+ if ( c == 74) return "ARAP-Security-Data [RFC2869]";
+ /* 75 Password-Retry [RFC2869] */
+ if ( c == 75) return "Password-Retry [RFC2869]";
+ /* 76 Prompt [RFC2869] */
+ if ( c == 76) return "Prompt [RFC2869]";
+ /* 77 Connect-Info [RFC2869] */
+ if ( c == 77) return "Connect-Info [RFC2869]";
+ /* 78 Configuration-Token [RFC2869] */
+ if ( c == 78) return "Configuration-Token [RFC2869]";
+ /* 79 EAP-Message [RFC2869] */
+ if ( c == 79) return "EAP-Message [RFC2869]";
+ /* 80 Message-Authenticator [RFC2869] */
+ if ( c == 80) return "Message-Authenticator [RFC2869]";
+ /* 81 Tunnel-Private-Group-ID [RFC2868] */
+ if ( c == 81) return "Tunnel-Private-Group-ID [RFC2868]";
+ /* 82 Tunnel-Assignment-ID [RFC2868] */
+ if ( c == 82) return "Tunnel-Assignment-ID [RFC2868]";
+ /* 83 Tunnel-Preference [RFC2868] */
+ if ( c == 83) return "Tunnel-Preference [RFC2868]";
+ /* 84 ARAP-Challenge-Response [RFC2869] */
+ if ( c == 84) return "ARAP-Challenge-Response [RFC2869]";
+ /* 85 Acct-Interim-Interval [RFC2869] */
+ if ( c == 85) return "Acct-Interim-Interval [RFC2869]";
+ /* 86 Acct-Tunnel-Packets-Lost [RFC2867] */
+ if ( c == 86) return "Acct-Tunnel-Packets-Lost [RFC2867]";
+ /* 87 NAS-Port-Id [RFC2869] */
+ if ( c == 87) return "NAS-Port-Id [RFC2869]";
+ /* 88 Framed-Pool [RFC2869] */
+ if ( c == 88) return "Framed-Pool [RFC2869]";
+ /* 89 CUI [RFC4372] */
+ if ( c == 89) return "CUI [RFC4372]";
+ /* 90 Tunnel-Client-Auth-ID [RFC2868] */
+ if ( c == 90) return "Tunnel-Client-Auth-ID [RFC2868]";
+ /* 91 Tunnel-Server-Auth-ID [RFC2868] */
+ if ( c == 91) return "Tunnel-Server-Auth-ID [RFC2868]";
+ /* 92 NAS-Filter-Rule [RFC4849] */
+ if ( c == 92) return "NAS-Filter-Rule [RFC4849]";
+ /* 93 Unassigned */
+ if ( c == 93) return "Unassigned ";
+ /* 94 Originating-Line-Info [RFC4005] */
+ if ( c == 94) return "Originating-Line-Info [RFC4005]";
+ /* 95 NAS-IPv6-Address [RFC3162] */
+ if ( c == 95) return "NAS-IPv6-Address [RFC3162]";
+ /* 96 Framed-Interface-Id [RFC3162] */
+ if ( c == 96) return "Framed-Interface-Id [RFC3162]";
+ /* 97 Framed-IPv6-Prefix [RFC3162] */
+ if ( c == 97) return "Framed-IPv6-Prefix [RFC3162]";
+ /* 98 Login-IPv6-Host [RFC3162] */
+ if ( c == 98) return "Login-IPv6-Host [RFC3162]";
+ /* 99 Framed-IPv6-Route [RFC3162] */
+ if ( c == 99) return "Framed-IPv6-Route [RFC3162]";
+ /* 100 Framed-IPv6-Pool [RFC3162] */
+ if ( c == 100) return "Framed-IPv6-Pool [RFC3162]";
+ /* 101 Error-Cause Attribute [RFC3576] */
+ if ( c == 101) return "Error-Cause Attribute[RFC3576]";
+ /* 102 EAP-Key-Name [RFC4072] */
+ if ( c == 102) return "EAP-Key-Name [RFC4072]";
+ /* 103 Digest-Response [RFC5090] */
+ if ( c == 103) return "Digest-Response [RFC5090]";
+ /* 104 Digest-Realm [RFC5090] */
+ if ( c == 104) return "Digest-Realm [RFC5090]";
+ /* 105 Digest-Nonce [RFC5090] */
+ if ( c == 105) return "Digest-Nonce [RFC5090]";
+ /* 106 Digest-Response-Auth [RFC5090] */
+ if ( c == 106) return "Digest-Response-Auth [RFC5090]";
+ /* 107 Digest-Nextnonce [RFC5090] */
+ if ( c == 107) return "Digest-Nextnonce [RFC5090]";
+ /* 108 Digest-Method [RFC5090] */
+ if ( c == 108) return "Digest-Method [RFC5090]";
+ /* 109 Digest-URI [RFC5090] */
+ if ( c == 109) return "Digest-URI [RFC5090]";
+ /* 110 Digest-Qop [RFC5090] */
+ if ( c == 110) return "Digest-Qop [RFC5090]";
+ /* 111 Digest-Algorithm [RFC5090] */
+ if ( c == 111) return "Digest-Algorithm [RFC5090]";
+ /* 112 Digest-Entity-Body-Hash [RFC5090] */
+ if ( c == 112) return "Digest-Entity-Body-Hash [RFC5090]";
+ /* 113 Digest-CNonce [RFC5090] */
+ if ( c == 113) return "Digest-CNonce [RFC5090]";
+ /* 114 Digest-Nonce-Count [RFC5090] */
+ if ( c == 114) return "Digest-Nonce-Count [RFC5090]";
+ /* 115 Digest-Username [RFC5090] */
+ if ( c == 115) return "Digest-Username [RFC5090]";
+ /* 116 Digest-Opaque [RFC5090] */
+ if ( c == 116) return "Digest-Opaque [RFC5090]";
+ /* 117 Digest-Auth-Param [RFC5090] */
+ if ( c == 117) return "Digest-Auth-Param [RFC5090]";
+ /* 118 Digest-AKA-Auts [RFC5090] */
+ if ( c == 118) return "Digest-AKA-Auts [RFC5090]";
+ /* 119 Digest-Domain [RFC5090] */
+ if ( c == 119) return "Digest-Domain [RFC5090]";
+ /* 120 Digest-Stale [RFC5090] */
+ if ( c == 120) return "Digest-Stale [RFC5090]";
+ /* 121 Digest-HA1 [RFC5090] */
+ if ( c == 121) return "Digest-HA1 [RFC5090]";
+ /* 122 SIP-AOR [RFC5090] */
+ if ( c == 122) return "SIP-AOR [RFC5090]";
+ /* 123 Delegated-IPv6-Prefix [RFC4818] */
+ if ( c == 123) return "Delegated-IPv6-Prefix [RFC4818]";
+ /* 124 MIP6-Feature-Vector [RFC5447] */
+ if ( c == 124) return "MIP6-Feature-Vector [RFC5447]";
+ /* 125 MIP6-Home-Link-Prefix [RFC5447] */
+ if ( c == 125) return "MIP6-Home-Link-Prefix [RFC5447]";
+ /* 126-191 Unassigned */
+ if ((c >= 126) && (c <= 191)) return "Unassigned ";
+ /* 192-223 Experimental Use [RFC3575] */
+ if ((c >= 192) && (c <= 223)) return "Experimental Use[RFC3575]";
+ /* 224-240 Implementation Specific [RFC3575] */
+ if ((c >= 224) && (c <= 240)) return "Implementation Specific[RFC3575]";
+ /* 241-255 Reserved [RFC3575] */
+ if ((c >= 241)) return "Reserved [RFC3575]";
+ /* fallback */ return "[Unknown]";
+}
diff --git a/extensions/app_radgw/rgw_msg_codes.c b/extensions/app_radgw/rgw_msg_codes.c
new file mode 100644
index 0000000..19b3e72
--- /dev/null
+++ b/extensions/app_radgw/rgw_msg_codes.c
@@ -0,0 +1,120 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2011, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+#include "rgw_common.h"
+
+/* The content of this file was semi-automatically generated from the IANA registry. */
+
+/* Name of RADIUS command from its command code */
+const char * rgw_msg_code_str(unsigned char c) {
+ /* 1 Access-Request [RFC2865] */
+ if ( c == 1) return "Access-Request [RFC2865]";
+ /* 2 Access-Accept [RFC2865] */
+ if ( c == 2) return "Access-Accept [RFC2865]";
+ /* 3 Access-Reject [RFC2865] */
+ if ( c == 3) return "Access-Reject [RFC2865]";
+ /* 4 Accounting-Request [RFC2865] */
+ if ( c == 4) return "Accounting-Request [RFC2865]";
+ /* 5 Accounting-Response [RFC2865] */
+ if ( c == 5) return "Accounting-Response [RFC2865]";
+ /* 6 Accounting-Status [RFC3575] */
+ if ( c == 6) return "Accounting-Status [RFC3575]";
+ /* 7 Password-Request [RFC3575] */
+ if ( c == 7) return "Password-Request [RFC3575]";
+ /* 8 Password-Ack [RFC3575] */
+ if ( c == 8) return "Password-Ack [RFC3575]";
+ /* 9 Password-Reject [RFC3575] */
+ if ( c == 9) return "Password-Reject [RFC3575]";
+ /* 10 Accounting-Message [RFC3575] */
+ if ( c == 10) return "Accounting-Message [RFC3575]";
+ /* 11 Access-Challenge [RFC2865] */
+ if ( c == 11) return "Access-Challenge [RFC2865]";
+ /* 12 Status-Server (experimental) [RFC2865] */
+ if ( c == 12) return "Status-Server (experimental)[RFC2865]";
+ /* 13 Status-Client (experimental) [RFC2865] */
+ if ( c == 13) return "Status-Client (experimental)[RFC2865]";
+ /* 21 Resource-Free-Request [RFC3575] */
+ if ( c == 21) return "Resource-Free-Request [RFC3575]";
+ /* 22 Resource-Free-Response [RFC3575] */
+ if ( c == 22) return "Resource-Free-Response [RFC3575]";
+ /* 23 Resource-Query-Request [RFC3575] */
+ if ( c == 23) return "Resource-Query-Request [RFC3575]";
+ /* 24 Resource-Query-Response [RFC3575] */
+ if ( c == 24) return "Resource-Query-Response [RFC3575]";
+ /* 25 Alternate-Resource-Reclaim-Request [RFC3575] */
+ if ( c == 25) return "Alternate-Resource-Reclaim-Request [RFC3575]";
+ /* 26 NAS-Reboot-Request [RFC3575] */
+ if ( c == 26) return "NAS-Reboot-Request [RFC3575]";
+ /* 27 NAS-Reboot-Response [RFC3575] */
+ if ( c == 27) return "NAS-Reboot-Response [RFC3575]";
+ /* 28 Reserved */
+ if ( c == 28) return "Reserved ";
+ /* 29 Next-Passcode [RFC3575] */
+ if ( c == 29) return "Next-Passcode [RFC3575]";
+ /* 30 New-Pin [RFC3575] */
+ if ( c == 30) return "New-Pin [RFC3575]";
+ /* 31 Terminate-Session [RFC3575] */
+ if ( c == 31) return "Terminate-Session [RFC3575]";
+ /* 32 Password-Expired [RFC3575] */
+ if ( c == 32) return "Password-Expired [RFC3575]";
+ /* 33 Event-Request [RFC3575] */
+ if ( c == 33) return "Event-Request [RFC3575]";
+ /* 34 Event-Response [RFC3575] */
+ if ( c == 34) return "Event-Response [RFC3575]";
+ /* 40 Disconnect-Request [RFC3575][RFC5176] */
+ if ( c == 40) return "Disconnect-Request [RFC3575][RFC5176]";
+ /* 41 Disconnect-ACK [RFC3575][RFC5176] */
+ if ( c == 41) return "Disconnect-ACK [RFC3575][RFC5176]";
+ /* 42 Disconnect-NAK [RFC3575][RFC5176] */
+ if ( c == 42) return "Disconnect-NAK [RFC3575][RFC5176]";
+ /* 43 CoA-Request [RFC3575][RFC5176] */
+ if ( c == 43) return "CoA-Request [RFC3575][RFC5176]";
+ /* 44 CoA-ACK [RFC3575][RFC5176] */
+ if ( c == 44) return "CoA-ACK [RFC3575][RFC5176]";
+ /* 45 CoA-NAK [RFC3575][RFC5176] */
+ if ( c == 45) return "CoA-NAK [RFC3575][RFC5176]";
+ /* 50 IP-Address-Allocate [RFC3575] */
+ if ( c == 50) return "IP-Address-Allocate [RFC3575]";
+ /* 51 IP-Address-Release [RFC3575] */
+ if ( c == 51) return "IP-Address-Release [RFC3575]";
+ /* 52-249 Unassigned */
+ if ((c >= 52) && (c <= 249)) return "Unassigned ";
+ /* 250-253 Experimental Use [RFC3575] */
+ if ((c >= 250) && (c <= 253)) return "Experimental Use[RFC3575]";
+ /* 254 Reserved [RFC3575] */
+ if ( c == 254) return "Reserved [RFC3575]";
+ /* 255 Reserved [RFC3575] */
+ if ( c == 255) return "Reserved [RFC3575]";
+ /* fallback */ return "[Unknown]";
+}
diff --git a/extensions/app_radgw/rgw_plugins.c b/extensions/app_radgw/rgw_plugins.c
new file mode 100644
index 0000000..3bd4526
--- /dev/null
+++ b/extensions/app_radgw/rgw_plugins.c
@@ -0,0 +1,491 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Manage the list of plugins that provide handlers for RADIUS messages and attributes */
+
+#include "rgw.h"
+#include <dlfcn.h>
+#include <libgen.h>
+
+/* List of plugins, in the order they appear in the configuration file. */
+static struct fd_list plg_list = FD_LIST_INITIALIZER(plg_list);
+
+/* A plugin entry */
+struct plg_descr {
+ struct fd_list chain; /* chaining in plg_list */
+
+ void *dlo; /* pointer returned by dlopen for the extension, to use with dlclose later */
+ struct rgw_api *descriptor; /* Points to the resolved plugin's rgwp_descriptor */
+ struct rgwp_config *cs; /* the (private) state returned by rgwp_conf_parse */
+
+ int type; /* this extension is called for messages received on this(these) server port(s) only */
+ unsigned char *cc; /* array of command codes, or NULL for all cc */
+ size_t cc_len; /* size of the previous array */
+};
+
+/* Accelerators for each command code (one for each port). These accelerators are built on-demand, as a cache, after start_cache function has been called. */
+static struct fd_list plg_accel_auth = FD_LIST_INITIALIZER(plg_accel_auth);
+static struct fd_list plg_accel_acct = FD_LIST_INITIALIZER(plg_accel_acct);
+
+/* Accelerator list item, one per command code value (only the ones actually used) */
+struct plg_accel {
+ struct fd_list chain; /* link in the plg_accel_* list. List ordered by ccode. */
+ unsigned char ccode; /* the command code for this accelerator. We don't handle extended CC yet */
+ struct fd_list plugins;/* head for the list of plg_accel_item, corresponding to the extensions to be called for this command code. */
+};
+
+/* Accelerator item */
+struct plg_accel_item {
+ struct fd_list chain; /* link in the plg_accel "plugins" list */
+ struct plg_descr * plg; /* pointer to the plugin */
+ /* Note: we can further optimize by caching the location of plg->descriptor->rgwp_rad_req etc... at this level. */
+};
+
+/* RWlock to protect all the previous lists */
+static pthread_rwlock_t plg_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* Has start_cache been called? */
+static int cache_started = 0;
+
+
+/* The read lock must be held before calling this function */
+static int get_accelerator(struct fd_list ** list, unsigned char ccode, int type)
+{
+ struct fd_list *refer, *search;
+ struct plg_accel * accel = NULL;
+ struct plg_accel_item * item = NULL;
+ int upgraded = 0;
+
+ TRACE_ENTRY("%p %hhu %i", list, ccode, type);
+
+ CHECK_PARAMS( cache_started && list && ((type == RGW_PLG_TYPE_AUTH) || (type == RGW_PLG_TYPE_ACCT)) );
+
+ if (type == RGW_PLG_TYPE_AUTH)
+ refer = &plg_accel_auth;
+ else
+ refer = &plg_accel_acct;
+restart:
+ /* Check if we have already an accelerator for this ccode */
+ for (search = refer->next; search != refer; search = search->next) {
+ struct plg_accel * loc = (struct plg_accel *)search;
+
+ if (loc->ccode < ccode)
+ continue;
+
+ if (loc->ccode > ccode)
+ break; /* we don't have an accelerator for this value yet */
+
+ /* We found the matching accelerator, just return this list */
+ *list = &loc->plugins;
+ return 0;
+ }
+
+ /* We must create the accelerator list, then save it just before "search" */
+
+ /* First , upgrade the lock to write lock, and restart the search. This is the only robust solution to avoid deadlocks */
+ if (! upgraded) {
+ CHECK_POSIX( pthread_rwlock_unlock(&plg_lock) );
+ CHECK_POSIX( pthread_rwlock_wrlock(&plg_lock) );
+ upgraded = 1;
+ goto restart;
+ }
+
+ /* Now create the new element */
+ CHECK_MALLOC( accel = malloc(sizeof(struct plg_accel)) );
+ memset(accel, 0, sizeof(struct plg_accel) );
+ fd_list_init(&accel->chain, NULL);
+ fd_list_init(&accel->plugins, accel);
+ accel->ccode = ccode;
+
+ /* Check each extension from the global list for this port and ccode */
+ for (refer = plg_list.next; refer != &plg_list; refer = refer->next) {
+ struct plg_descr * loc = (struct plg_descr *)refer;
+
+ /* Skip if this extension is not registered for this port */
+ if (! (loc->type & type) )
+ continue;
+
+ /* Check if the ccode is there */
+ if (loc->cc) {
+ int i;
+ int match = 0;
+ for (i=0; i< loc->cc_len; i++) {
+ if (loc->cc[i] < ccode)
+ continue;
+ if (loc->cc[i] == ccode)
+ match = 1;
+ break;
+ }
+ if (!match)
+ continue;
+ }
+
+ /* Ok, this extension must be called for this port / ccode, add to the accelerator */
+ CHECK_MALLOC( item = malloc(sizeof(struct plg_accel_item)) );
+ memset(item, 0, sizeof(struct plg_accel_item));
+ fd_list_init(&item->chain, NULL);
+ item->plg = loc;
+ /* Add as last element of the accelerator */
+ fd_list_insert_before(&accel->plugins, &item->chain);
+ }
+
+ /* Now, save this accelerator entry in the global list */
+ fd_list_insert_before(search, &accel->chain);
+ *list = &accel->plugins;
+
+ return 0;
+}
+
+
+int rgw_plg_add( char * plgfile, char * conffile, int type, unsigned char ** codes_array, size_t codes_sz )
+{
+ struct plg_descr * new;
+
+ TRACE_ENTRY("%p %p %d %p %zi", plgfile, conffile, type, codes_array, codes_sz);
+
+ CHECK_PARAMS( plgfile && type && codes_array && (cache_started == 0) );
+
+ CHECK_MALLOC( new = malloc(sizeof(struct plg_descr)) );
+ memset(new, 0, sizeof(struct plg_descr));
+
+ fd_list_init(&new->chain, new);
+
+ /* Try and load the plugin */
+ TRACE_DEBUG(FULL, "Loading plugin: %s", plgfile);
+ new->dlo = dlopen(plgfile, RTLD_NOW | RTLD_GLOBAL);
+ if (new->dlo == NULL) {
+ /* An error occured */
+ fd_log_debug("Loading of plugin '%s' failed: %s", plgfile, dlerror());
+ goto error;
+ }
+
+ /* Resolve the descriptor */
+ new->descriptor = dlsym( new->dlo, "rgwp_descriptor" );
+ if (new->descriptor == NULL) {
+ /* An error occured */
+ fd_log_debug("Unable to resolve 'rgwp_descriptor' in plugin '%s': %s", plgfile, dlerror());
+ goto error;
+ }
+
+ TRACE_DEBUG(FULL, "Plugin '%s' found in file '%s'", new->descriptor->rgwp_name, plgfile);
+
+ /* Now parse the configuration file, this will initialize all plugin states and store it in the "cs" pointer (the plugin must be re-entrant, so no global state) */
+ if (new->descriptor->rgwp_conf_parse) {
+ CHECK_FCT_DO( (*(new->descriptor->rgwp_conf_parse))(conffile, &new->cs),
+ {
+ fd_log_debug("An error occurred while parsing configuration file '%s' in plugin '%s', aborting...", conffile, plgfile);
+ goto error;
+ } );
+ }
+
+ /* Now sort the array (very simple algorithm, but this list is usually small) of command codes and save */
+ if (*codes_array && codes_sz) {
+ int i;
+
+ new->cc = *codes_array;
+ *codes_array = NULL;
+
+ for (i = 0; i < codes_sz - 1; i++) {
+ int j, idx = i, min = new->cc[i];
+
+ /* find the smallest remaining element */
+ for (j = i + 1; j < codes_sz; j++) {
+ if (min > new->cc[j]) {
+ min = new->cc[j];
+ idx = j;
+ }
+ }
+
+ /* swap if needed */
+ if (idx != i) {
+ int tmp = new->cc[i];
+ new->cc[i] = new->cc[idx];
+ new->cc[idx] = tmp;
+ }
+ }
+ new->cc_len = codes_sz;
+ }
+
+ new->type = type;
+
+ /* And save this new extension in the list. We don't need to lock at this point because we are single threaded. */
+ fd_list_insert_before(&plg_list, &new->chain);
+
+ return 0;
+
+
+error:
+ if (new && new->dlo)
+ dlclose(new->dlo);
+ if (new)
+ free(new);
+ return EINVAL;
+}
+
+void rgw_plg_dump(void)
+{
+ struct plg_descr * plg;
+ struct fd_list * ptr, *ptraccel;
+
+ if ( ! TRACE_BOOL(FULL) )
+ return;
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
+
+ if ( ! FD_IS_LIST_EMPTY( &plg_list ) )
+ fd_log_debug("[app_radgw] --- List of registered plugins:");
+ for (ptr = plg_list.next; ptr != &plg_list; ptr = ptr->next) {
+ char buf[1024];
+ plg = (struct plg_descr *)ptr;
+
+ snprintf(buf, sizeof(buf), " %-25s ( %p ) - types: %s%s, codes: ",
+ plg->descriptor->rgwp_name,
+ plg->cs,
+ plg->type & RGW_PLG_TYPE_AUTH ? "Au" : " ",
+ plg->type & RGW_PLG_TYPE_ACCT ? "Ac" : " ");
+
+ if (plg->cc) {
+ int i;
+
+ for (i = 0; i < plg->cc_len; i++) {
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx ", plg->cc[i]);
+ }
+ fd_log_debug("%s", buf);
+ } else {
+ fd_log_debug("%s*", buf);
+ }
+ }
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
+
+ /* Dump the list of accelerators */
+ if ( ! TRACE_BOOL(FULL + 1) )
+ return;
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
+ if ( !FD_IS_LIST_EMPTY( &plg_accel_auth ) || !FD_IS_LIST_EMPTY( &plg_accel_acct ))
+ fd_log_debug(" --- Accelerators:");
+
+ for (ptraccel = plg_accel_auth.next; ptraccel != &plg_accel_auth; ptraccel = ptraccel->next) {
+ struct plg_accel * accel = (struct plg_accel *)ptraccel;
+ fd_log_debug(" auth, code %02hhu:", accel->ccode);
+
+ for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
+ struct plg_accel_item * item = (struct plg_accel_item *)ptr;
+ fd_log_debug(" %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
+ }
+ }
+ for (ptraccel = plg_accel_acct.next; ptraccel != &plg_accel_acct; ptraccel = ptraccel->next) {
+ struct plg_accel * accel = (struct plg_accel *)ptraccel;
+ fd_log_debug(" acct, code %02hhu:", accel->ccode);
+
+ for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
+ struct plg_accel_item * item = (struct plg_accel_item *)ptr;
+ fd_log_debug(" %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
+ }
+ }
+
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
+
+}
+
+void rgw_plg_start_cache(void)
+{
+ cache_started++;
+}
+
+int rgw_plg_loop_req(struct rgw_radius_msg_meta **rad, struct msg **diam_msg, struct rgw_client * cli)
+{
+ int ret = 0;
+ struct fd_list * head = NULL, *li;
+ struct radius_msg * rad_ans = NULL;
+
+ TRACE_ENTRY("%p %p %p", rad, diam_msg, cli);
+ CHECK_PARAMS( rad && *rad && diam_msg && *diam_msg && cli);
+
+ /* First, get the list of extensions for this message */
+ CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
+ CHECK_FCT_DO( ret = get_accelerator(&head, (*rad)->radius.hdr->code, (*rad)->serv_type),
+ { CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
+
+ /* Loop in the list of extensions */
+ for (li = head->next; li != head; li = li->next) {
+ struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
+
+ if (plg->descriptor->rgwp_rad_req) {
+ TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
+ ret = (*plg->descriptor->rgwp_rad_req)(plg->cs, &(*rad)->radius, &rad_ans, diam_msg, cli);
+ if (ret)
+ break;
+ } else {
+ TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
+ }
+ }
+
+ CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
+
+ /* If no error encountered, we're done here */
+ if (ret == 0)
+ return 0;
+
+ /* Destroy the Diameter temp message, if any */
+ if (*diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(*diam_msg), );
+ *diam_msg = NULL;
+ }
+
+ /* Send the radius message back if required */
+ if ((ret == -2) && rad_ans && rad) {
+ CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, *rad, cli), /* It failed, it can't be helped... */);
+ }
+
+ if (ret > 0) {
+ /* Critical error, log and exit */
+ TRACE_DEBUG(NONE, "An error occurred while handling a RADIUS message from '%s': %s", rgw_clients_id(cli), strerror(ret));
+ return ret;
+ }
+
+ /* Now, discard the message and return */
+ rgw_msg_free(rad);
+ return 0;
+}
+
+/* Loop in the extension list (same as req) to convert data from diam_ans to rad_ans */
+int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli)
+{
+ int ret = 0;
+ struct fd_list * head = NULL, *li;
+
+ TRACE_ENTRY("%p %p %p %p", req, diam_ans, rad_ans, cli);
+ CHECK_PARAMS( req && diam_ans && *diam_ans && rad_ans && *rad_ans && cli);
+
+ /* Get the list of extensions of the RADIUS request */
+ CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
+ CHECK_FCT_DO( ret = get_accelerator(&head, req->radius.hdr->code, req->serv_type),
+ { CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
+
+ /* Loop in the list of extensions */
+ for (li = head->next; li != head; li = li->next) {
+ struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
+
+ if (plg->descriptor->rgwp_diam_ans) {
+ TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
+ ret = (*plg->descriptor->rgwp_diam_ans)(plg->cs, diam_ans, rad_ans, (void *)cli);
+ if (ret)
+ break;
+ } else {
+ TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
+ }
+ }
+
+ CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
+
+ /* If no error encountered, we're done here */
+ if (ret == 0)
+ return 0;
+
+ /* Destroy the temporary RADIUS answer */
+ if (*rad_ans) {
+ radius_msg_free(*rad_ans);
+ free(*rad_ans);
+ *rad_ans = NULL;
+ }
+
+ if (ret > 0) {
+ /* Critical error, log and exit */
+ fd_log_debug("[app_radgw] An error occurred while handling a DIAMETER answer to a converted RADIUS request, turn on DEBUG for details: %s", strerror(ret));
+ return ret;
+ }
+
+ /* We might define other return values with special meaning here (ret == -1, ...) for example create a new Diameter request */
+
+ /* -1: just abord the translation with no more processing. */
+
+ return 0;
+}
+
+void rgw_plg_fini(void)
+{
+ struct fd_list * item, *subitem;
+
+ TRACE_ENTRY();
+
+ CHECK_POSIX_DO( pthread_rwlock_rdlock( &plg_lock), /* continue anyway */ );
+
+ /* Remove all elements from all accelerators */
+ while ( ! FD_IS_LIST_EMPTY(&plg_accel_auth) ) {
+ item = plg_accel_auth.next;
+ fd_list_unlink(item);
+ {
+ struct plg_accel * accel = (struct plg_accel *)item;
+ while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
+ subitem = accel->plugins.next;
+ fd_list_unlink(subitem);
+ free(subitem);
+ }
+ }
+ free(item);
+ }
+ while ( ! FD_IS_LIST_EMPTY(&plg_accel_acct) ) {
+ item = plg_accel_acct.next;
+ fd_list_unlink(item);
+ {
+ struct plg_accel * accel = (struct plg_accel *)item;
+ while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
+ subitem = accel->plugins.next;
+ fd_list_unlink(subitem);
+ free(subitem);
+ }
+ }
+ free(item);
+ }
+
+ /* Now destroy all plugins information */
+ while ( ! FD_IS_LIST_EMPTY(&plg_list) ) {
+ struct plg_descr * plg = (struct plg_descr *) plg_list.next;
+ fd_list_unlink(&plg->chain);
+ free(plg->cc);
+ if (plg->descriptor && plg->descriptor->rgwp_conf_free ) {
+ TRACE_DEBUG(INFO, "RADIUS/Diameter gateway plugin '%s' cleaning up...", plg->descriptor->rgwp_name);
+ (*plg->descriptor->rgwp_conf_free)(plg->cs);
+ }
+ if (plg->dlo)
+ dlclose(plg->dlo);
+ free(plg);
+ }
+
+ CHECK_POSIX_DO( pthread_rwlock_unlock( &plg_lock), );
+}
diff --git a/extensions/app_radgw/rgw_servers.c b/extensions/app_radgw/rgw_servers.c
new file mode 100644
index 0000000..d524f20
--- /dev/null
+++ b/extensions/app_radgw/rgw_servers.c
@@ -0,0 +1,304 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Manage the RADIUS server(s): opening sockets, receiving messages, ... */
+
+#include "rgw.h"
+
+#define RADIUS_MAX_MSG_LEN 3000
+#define RADIUS_AUTH_PORT 1812
+#define RADIUS_ACCT_PORT 1813
+
+/* Declare the rgw_servers */
+struct rgw_servs rgw_servers;
+
+void rgw_servers_dump(void)
+{
+ char ipstr[INET6_ADDRSTRLEN];
+
+ LOG_D(" auth server:");
+ LOG_D(" disabled..... : %s", rgw_servers.auth_serv.disabled ? "TRUE":"false");
+ LOG_D(" IP disabled.. : %s", rgw_servers.auth_serv.ip_disabled ? "TRUE":"false");
+ LOG_D(" IPv6 disabled : %s", rgw_servers.auth_serv.ip6_disabled ? "TRUE":"false");
+ LOG_D(" port......... : %hu", ntohs(rgw_servers.auth_serv.port));
+ inet_ntop(AF_INET, &rgw_servers.auth_serv.ip_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IP bind...... : %s", ipstr);
+ inet_ntop(AF_INET6, &rgw_servers.auth_serv.ip6_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IPv6 bind.... : %s", ipstr);
+
+ LOG_D(" acct server:");
+ LOG_D(" disabled..... : %s", rgw_servers.acct_serv.disabled ? "TRUE":"false");
+ LOG_D(" IP disabled.. : %s", rgw_servers.acct_serv.ip_disabled ? "TRUE":"false");
+ LOG_D(" IPv6 disabled : %s", rgw_servers.acct_serv.ip6_disabled ? "TRUE":"false");
+ LOG_D(" port......... : %hu", ntohs(rgw_servers.acct_serv.port));
+ inet_ntop(AF_INET, &rgw_servers.acct_serv.ip_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IP bind...... : %s", ipstr);
+ inet_ntop(AF_INET6, &rgw_servers.acct_serv.ip6_endpoint,ipstr,sizeof(ipstr));
+ LOG_D(" IPv6 bind.... : %s", ipstr);
+
+}
+
+static struct servers_data {
+ int type; /* auth or acct */
+ int family; /* AF_INET or AF_INET6 */
+ int sock; /* the socket number */
+ pthread_t th; /* the running server thread, or NULL */
+ char name[10];
+} SERVERS[4];
+
+int rgw_servers_init(void)
+{
+ memset(&rgw_servers, 0, sizeof(rgw_servers));
+ memset(&SERVERS[0], 0, sizeof(SERVERS));
+
+ rgw_servers.auth_serv.port = htons(RADIUS_AUTH_PORT);
+ rgw_servers.acct_serv.port = htons(RADIUS_ACCT_PORT);
+
+ return 0;
+}
+
+static void * server_thread(void * param)
+{
+ struct servers_data * me = (struct servers_data *)param;
+
+ TRACE_ENTRY("%p", param);
+
+ CHECK_PARAMS_DO(param, return NULL);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "radgw/%s serv", me->name);
+ fd_log_threadname ( buf );
+ }
+
+ /* Now loop on this socket, parse and queue each message received, until thread is cancelled. */
+ while (1) {
+ struct sockaddr_storage from;
+ char sa_buf[sSA_DUMP_STRLEN];
+ socklen_t fromlen = sizeof(from);
+ int len;
+ struct rgw_client * nas_info = NULL;
+ uint16_t port = 0;
+ unsigned char buf[RADIUS_MAX_MSG_LEN];
+ struct rgw_radius_msg_meta *msg = NULL;
+
+ pthread_testcancel();
+
+ /* receive the next message */
+ CHECK_SYS_DO( len = recvfrom( me->sock, &buf[0], sizeof(buf), 0, (struct sockaddr *) &from, &fromlen), break );
+
+ /* Get the port */
+ port = sSAport(&from);
+ if (!port) {
+ LOG_E("Invalid port (family: %d), discarding received %d bytes...", from.ss_family, len);
+ continue;
+ }
+
+ fd_sa_sdump_numeric(sa_buf, (sSA*)&from);
+ LOG_D("RADIUS: RCV %dB from %s", len, sa_buf);
+
+ /* Search the associated client definition, if any */
+ CHECK_FCT_DO( rgw_clients_search((struct sockaddr *) &from, &nas_info),
+ {
+ LOG_E("Discarding %d bytes received from unknown IP: %s", len, sa_buf);
+ continue;
+ } );
+
+
+ /* parse the message, loop if message is invalid */
+ CHECK_FCT_DO( rgw_msg_parse(&buf[0], len, &msg),
+ {
+ DiamId_t cliname = NULL;
+ size_t clisz;
+ CHECK_FCT_DO( rgw_clients_get_origin(nas_info, &cliname, &clisz, NULL, NULL), );
+ LOG_E( "Discarding invalid RADIUS message from '%s'", cliname);
+ rgw_clients_dispose(&nas_info);
+ continue;
+ } );
+
+ msg->serv_type = me->type;
+ msg->port = port;
+
+ rgw_msg_dump(msg, 1);
+
+ /* queue the message for a worker thread */
+ CHECK_FCT_DO( rgw_work_add(msg, nas_info), break );
+
+ /* Then wait for next incoming message */
+ }
+
+ TRACE_DEBUG(INFO, "Server thread terminated.");
+ return NULL;
+}
+
+/* Set the socket options for UDP sockets, before bind is called */
+static int _udp_setsockopt(int family, int sk)
+{
+ int ret = 0;
+ int opt;
+
+ /* In case of v6 address, force the v6only option, we use a different socket for v4 */
+ #ifdef IPV6_V6ONLY
+ if (family == AF_INET6) {
+ opt = 1;
+ ret = setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
+ if (ret != 0) {
+ ret = errno;
+ TRACE_DEBUG(INFO, "Unable to set the socket IPV6_V6ONLY option: %s", strerror(ret));
+ return ret;
+ }
+ }
+ #endif /* IPV6_V6ONLY */
+
+ return 0;
+}
+
+/* We reuse the same logic for all 4 possible servers (IP / IPv6, Auth / Acct ports) */
+#define UDPSERV( _type_, _portval_, _family_ ) { \
+ /* Check that this type / family are not disabled by configuration */ \
+ if ( (! rgw_servers. _type_ ## _serv.disabled) \
+ && ( ! rgw_servers. _type_ ## _serv.ip ## _family_ ## _disabled ) ) { \
+ struct sockaddr_in ## _family_ sin ## _family_ ; \
+ /* Create the socket */ \
+ CHECK_SYS( SERVERS[idx].sock = socket(AF_INET ## _family_, SOCK_DGRAM, 0) ); \
+ /* Set the parameters for bind into "sin" or "sin6" */ \
+ memset(& sin ## _family_, 0, sizeof(struct sockaddr_in ## _family_)); \
+ sin ## _family_ . sin ## _family_ ## _family = AF_INET ## _family_; \
+ sin ## _family_ . sin ## _family_ ## _port = rgw_servers. _type_ ## _serv . port; \
+ memcpy( &sin ## _family_ .sin ## _family_ ## _addr, \
+ &rgw_servers. _type_ ## _serv . ip ## _family_ ## _endpoint, \
+ sizeof(struct in ## _family_ ## _addr) ); \
+ /* This sockopt must be set before binding */ \
+ TRACE_DEBUG(ANNOYING, "Setting socket options..."); \
+ CHECK_FCT( _udp_setsockopt(AF_INET ## _family_, SERVERS[idx].sock) ); \
+ /* OK, now, bind */ \
+ TRACE_DEBUG(ANNOYING, "Binding " #_type_ " ip" #_family_ " server..."); \
+ CHECK_SYS( bind( SERVERS[idx].sock, \
+ (struct sockaddr *)&sin ## _family_, \
+ sizeof(struct sockaddr_in ## _family_) ) ); \
+ /* Save the server information in SERVERS structure */ \
+ SERVERS[idx].type = _portval_; \
+ SERVERS[idx].family = AF_INET ## _family_; \
+ snprintf(&SERVERS[idx].name[0], sizeof(SERVERS[idx].name), # _type_ "/ip" #_family_); \
+ /* Create the server thread */ \
+ CHECK_POSIX( pthread_create(&SERVERS[idx].th, NULL, server_thread, &SERVERS[idx]) ); \
+ idx++; \
+ } \
+}
+
+int rgw_servers_start(void)
+{
+ int idx = 0;
+
+ TRACE_ENTRY();
+
+ UDPSERV( auth, RGW_PLG_TYPE_AUTH, );
+ UDPSERV( auth, RGW_PLG_TYPE_AUTH, 6 );
+ UDPSERV( acct, RGW_PLG_TYPE_ACCT, );
+ UDPSERV( acct, RGW_PLG_TYPE_ACCT, 6 );
+
+ TRACE_DEBUG(FULL, "%d UDP servers started succesfully.", idx);
+ return 0;
+}
+
+/* Send a RADIUS message */
+int rgw_servers_send(int type, unsigned char *buf, size_t buflen, struct sockaddr *to, uint16_t to_port)
+{
+ int idx = 0;
+ int ret = 0;
+ struct sockaddr_storage sto;
+ char sa_buf[sSA_DUMP_STRLEN];
+
+ /* Find the appropriate socket to use (not sure if it is important) */
+ for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
+ if ( SERVERS[idx].sock && (type == SERVERS[idx].type) && (to->sa_family == SERVERS[idx].family) ) {
+ ret = 1;
+ break;
+ }
+ }
+
+ if (!ret) {
+ LOG_E( "Trying to send a message from a disabled server: %s / %s",
+ (type == RGW_PLG_TYPE_AUTH) ? "Auth" : "Acct",
+ (to->sa_family == AF_INET) ? "IP (v4)" : "IPv6");
+ return EINVAL;
+ }
+
+ /* Prepare the destination info */
+ memset(&sto, 0, sizeof(sto));
+ if (to->sa_family == AF_INET) {
+ memcpy(&sto, to, sizeof(struct sockaddr_in));
+ ((struct sockaddr_in *)&sto)->sin_port = to_port;
+ } else {
+ memcpy(&sto, to, sizeof(struct sockaddr_in6));
+ ((struct sockaddr_in6 *)&sto)->sin6_port = to_port;
+ }
+
+ fd_sa_sdump_numeric(sa_buf, (sSA*)&sto);
+ LOG_D("RADIUS: SND %zdB to %s", buflen, sa_buf);
+
+ /* Send */
+ ret = sendto(SERVERS[idx].sock, buf, buflen, 0, (struct sockaddr *)&sto, sSAlen(&sto));
+ if (ret < 0) {
+ ret = errno;
+ TRACE_DEBUG(INFO, "An error prevented sending of a RADIUS message: %s", strerror(ret));
+ return ret;
+ }
+ if (ret != buflen) {
+ TRACE_DEBUG(INFO, "Incomplete send: %d bytes / %zd", ret, buflen);
+ return EAGAIN;
+ }
+
+ /* Done :) */
+ return 0;
+}
+
+void rgw_servers_fini(void)
+{
+ int idx = 0;
+
+ for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
+ if (SERVERS[idx].sock == 0)
+ break;
+
+ CHECK_FCT_DO( fd_thr_term(&SERVERS[idx].th), /* continue */ );
+ close(SERVERS[idx].sock);
+ SERVERS[idx].sock = 0;
+ }
+
+}
+
+
diff --git a/extensions/app_radgw/rgw_worker.c b/extensions/app_radgw/rgw_worker.c
new file mode 100644
index 0000000..25cb146
--- /dev/null
+++ b/extensions/app_radgw/rgw_worker.c
@@ -0,0 +1,346 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Manage incoming RADIUS messages. */
+
+#include "rgw.h"
+
+/* How many threads to handle messages in parallel ? */
+#define NB_WORKERS 2
+
+static pthread_t workers[NB_WORKERS];
+static struct fifo * work_stack = NULL;
+
+/* Data that is stacked */
+struct work_item {
+ struct rgw_radius_msg_meta * msg;
+ struct rgw_client * cli;
+};
+
+/* Data stored in freeDiameter while pending Diameter answer */
+struct pending_answer {
+ struct rgw_radius_msg_meta * rad; /* the RADIUS message that was received and translated */
+ struct rgw_client * cli; /* the client it was received from */
+};
+
+/* Callback when a Diameter answer is received */
+static void receive_diam_answer(void * paback, struct msg **ans);
+
+/* Worker thread, processing incoming RADIUS messages (after parsing) */
+static void * work_th(void * arg)
+{
+ TRACE_ENTRY("%p", arg);
+
+ /* Set the thread name */
+ {
+ char buf[48];
+ snprintf(buf, sizeof(buf), "radgw/worker #%ld", (long)arg);
+ fd_log_threadname ( buf );
+ }
+
+ while (1) { /* The thread will be cancelled */
+
+ struct rgw_radius_msg_meta * msg;
+ struct rgw_client * cli;
+ struct msg * diam_msg;
+ int pb, a;
+ struct pending_answer * pa;
+
+ /* Get the next incoming RADIUS message */
+ {
+ struct work_item * wi = NULL;
+
+ CHECK_FCT_DO( fd_fifo_get(work_stack, &wi), break );
+
+ msg = wi->msg;
+ cli = wi->cli;
+ free(wi);
+ }
+
+ TRACE_DEBUG(ANNOYING, "Processing next RADIUS message: %p received on client: %p", msg, cli);
+
+ /* process the data */
+
+ /* Check authenticator, if any */
+ CHECK_FCT_DO( rgw_clients_auth_check(msg, cli, NULL),
+ {
+ /* An error occurred, discard message */
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+ continue;
+ } );
+
+ /* Check duplicate */
+ CHECK_FCT_DO( rgw_clients_check_dup(&msg, cli),
+ {
+ /* An error occurred, discard message */
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+ continue;
+ } );
+ if (msg == NULL) {
+ rgw_clients_dispose(&cli);
+ continue; /* the message was a duplicate */
+ }
+
+ diam_msg = NULL;
+ /* Note: after this point, the radius message buffer may not be consistent with the array of attributes anymore. */
+
+ /* Check that IP is coherent with the identity in the message, and create an empty message with only Origin information */
+ CHECK_FCT_DO( rgw_clients_create_origin(msg, cli, &diam_msg),
+ {
+ /* An error occurred, discard message */
+ if (diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(diam_msg), );
+ }
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+ continue;
+ } );
+
+ /* Pass the message to the list of registered plugins */
+ CHECK_FCT_DO( rgw_plg_loop_req(&msg, &diam_msg, cli),
+ {
+ /* An error occurred, discard message */
+ if (diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(diam_msg), );
+ diam_msg = NULL;
+ }
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+ continue;
+ } );
+ if (msg == NULL) { /* Error or RADIUS answer locally generated */
+ rgw_clients_dispose(&cli);
+ if (diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(diam_msg), );
+ diam_msg = NULL;
+ }
+ continue; /* the message was handled already */
+ }
+
+ pb = 0;
+
+ /* Check the created Diameter message -- it will be invalid if no callback has handled the RADIUS message */
+ if ((diam_msg == NULL) || ( fd_msg_parse_rules(diam_msg, fd_g_config->cnf_dict, NULL) ) ) {
+ fd_log_debug("[radgw] No or invalid Diameter message was generated after processing the RADIUS command %hhd (%s)."
+ " It may indicate a gateway configuration problem, or implementation issue in a plugin.",
+ msg->radius.hdr->code, rgw_msg_code_str(msg->radius.hdr->code));
+ /* We should also dump the conflicting rule here to help debug? */
+ pb++;
+ }
+
+ /* Check if the full content of the RADIUS message was handled */
+ for (a = 0; a < msg->radius.attr_used; a++) {
+ struct radius_attr_hdr *attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[a]);
+ pb++;
+ fd_log_debug("[radgw] No plugin available to handle attribute %hhd (%s) in command %hhd (%s)! Translation aborted.",
+ attr->type, rgw_msg_attrtype_str(attr->type),
+ msg->radius.hdr->code, rgw_msg_code_str(msg->radius.hdr->code));
+ }
+
+ if (pb) {
+ /* Something went wrong during the conversion */
+ if (diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(diam_msg), );
+ diam_msg = NULL;
+ }
+
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+
+ TRACE_DEBUG(INFO, "%d problem(s) occurred while translating a RADIUS message, data discarded.", pb);
+ continue;
+ }
+
+ /* Send the Diameter message and register for receiving the answer */
+ CHECK_MALLOC_DO( pa = malloc(sizeof(struct pending_answer)), break );
+ memset(pa, 0, sizeof(*pa));
+ pa->rad = msg;
+ pa->cli = cli;
+
+ CHECK_FCT_DO( fd_msg_send( &diam_msg, receive_diam_answer, pa),
+ {
+ /* If an error occurs, log and destroy the data */
+ fd_log_debug("An error occurred while sending Diameter message, please turn Debug on for detail.");
+
+ if (diam_msg) {
+ CHECK_FCT_DO( fd_msg_free(diam_msg), );
+ diam_msg = NULL;
+ }
+
+ rgw_msg_free(&msg);
+ rgw_clients_dispose(&cli);
+
+ free(pa);
+
+ continue;
+ } );
+
+ /* Done! */
+ }
+
+ TRACE_DEBUG(INFO, "Thread terminated!");
+ return NULL;
+}
+
+static void receive_diam_answer(void * paback, struct msg **ans)
+{
+ struct pending_answer * pa = (struct pending_answer *)paback;
+ struct radius_msg * rad_ans;
+ struct avp *avp;
+ struct avp_hdr *ahdr;
+ int pb = 0;
+
+ TRACE_ENTRY("%p %p", pa, ans);
+ CHECK_PARAMS_DO( pa && ans, return );
+
+ /* Create an empty RADIUS answer message */
+ CHECK_MALLOC_DO( rad_ans = radius_msg_new(0, pa->rad->radius.hdr->identifier), goto out );
+
+ /* Pass the Diameter answer to the same extensions as the request */
+ CHECK_FCT_DO( rgw_plg_loop_ans(pa->rad, ans, &rad_ans, pa->cli), goto out );
+
+ if (*ans != NULL) {
+
+ /* Now check what AVPs remain in the diameter answer. If AVPs with the 'M' flag are here, we have a problem... */
+ CHECK_FCT_DO( fd_msg_browse(*ans, MSG_BRW_FIRST_CHILD, &avp, NULL), { avp = NULL; pb++; } );
+ while (avp) {
+ CHECK_FCT_DO( fd_msg_avp_hdr ( avp, &ahdr ), { pb++; continue; } );
+ if (ahdr->avp_flags & AVP_FLAG_MANDATORY) {
+ if (ahdr->avp_flags & AVP_FLAG_VENDOR) {
+ TRACE_DEBUG(FULL, "Remaining Mandatory Vendor AVP, code %d", ahdr->avp_code);
+ pb++;
+ } else {
+ switch (ahdr->avp_code) {
+ /* A few AVPs can be safely ignored here: */
+ case DIAM_ATTR_SESSION_ID:
+ case DIAM_ATTR_ROUTE_RECORD:
+ case DIAM_ATTR_PROXY_INFO:
+
+
+ /* just ignore */
+ break;
+
+ default:
+ LOG_D("Remaining Mandatory AVP, code %d", ahdr->avp_code);
+ pb++;
+ }
+ }
+ }
+ CHECK_FCT_DO( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL), { pb++; break; } );
+ }
+
+ if (pb) {
+ TRACE_DEBUG(INFO, "[radgw] WARNING: %d mandatory AVP in the Diameter answer have not been translated to RADIUS! Please use debug.rgwx for more information.", pb);
+ }
+ }
+
+
+out:
+ /* Now try and send the RADIUS answer */
+ if (rad_ans) {
+ CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, pa->rad, pa->cli), );
+ } else {
+ /* Remove the request from the duplicate cache */
+ CHECK_FCT_DO( rgw_client_finish_nosend(pa->rad, pa->cli), );
+ }
+
+ /* Clear the Diameter message */
+ if (*ans) {
+ CHECK_FCT_DO( fd_msg_free(*ans), );
+ *ans = NULL;
+ }
+
+ /* Clear the RADIUS request */
+ if (pa->rad) {
+ rgw_msg_free(&pa->rad);
+ }
+
+ /* Release reference on the client */
+ rgw_clients_dispose(&pa->cli);
+
+ /* Clear the answer data */
+ free(pa);
+
+ /* Finished */
+ return;
+}
+
+int rgw_work_start(void)
+{
+ long i;
+ TRACE_ENTRY();
+
+ memset(workers, 0, sizeof(workers));
+
+ CHECK_FCT( fd_fifo_new ( &work_stack, 30 ) );
+
+ /* Create the worker thread(s) */
+ for (i = 0; i < NB_WORKERS; i++) {
+ CHECK_POSIX( pthread_create(&workers[i], NULL, work_th, (void *)i) );
+ }
+
+ return 0;
+}
+
+int rgw_work_add(struct rgw_radius_msg_meta * msg, struct rgw_client * client)
+{
+ struct work_item * new;
+
+ CHECK_MALLOC( new = malloc(sizeof(struct work_item)) );
+ memset(new, 0, sizeof(struct work_item));
+
+ new->msg = msg;
+ new->cli = client;
+
+ CHECK_FCT( fd_fifo_post(work_stack, &new) );
+
+ return 0;
+}
+
+void rgw_work_fini(void)
+{
+ int i;
+ TRACE_ENTRY();
+
+ for (i = 0; i < NB_WORKERS; i++) {
+ fd_thr_term(&workers[i]);
+ }
+
+ TODO("Empty the stack, what to do about the RADIUS messages?");
+
+ return;
+}
diff --git a/extensions/app_radgw/rgwx_acct.c b/extensions/app_radgw/rgwx_acct.c
new file mode 100644
index 0000000..f9f1480
--- /dev/null
+++ b/extensions/app_radgw/rgwx_acct.c
@@ -0,0 +1,1463 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* RADIUS Accounting-Request messages translation plugin */
+
+#include "rgw_common.h"
+
+
+/* Other constants we use */
+#define AI_ACCT 3 /* Diameter Base Accounting application */
+#define CC_AC 271 /* ACR/ACA */
+#define ACV_ART_START_RECORD 2 /* START_RECORD */
+#define ACV_ART_INTERIM_RECORD 3 /* INTERIM_RECORD */
+#define ACV_ART_STOP_RECORD 4 /* STOP_RECORD */
+#define ACV_ART_AUTHORIZE_AUTHENTICATE 3 /* AUTHORIZE_AUTHENTICATE */
+
+
+/* The state we keep for this plugin */
+struct rgwp_config {
+ struct {
+ struct dict_object * Accounting_Record_Number; /* Accounting-Record-Number */
+ struct dict_object * Accounting_Record_Type; /* Accounting-Record-Type */
+ struct dict_object * Acct_Application_Id; /* Acct-Application-Id */
+ struct dict_object * Acct_Delay_Time; /* Acct-Delay-Time */
+ struct dict_object * Accounting_Input_Octets; /* Accounting-Input-Octets */
+ struct dict_object * Accounting_Output_Octets; /* Accounting-Output-Octets */
+ struct dict_object * Accounting_Input_Packets; /* Accounting-Input-Packets */
+ struct dict_object * Accounting_Output_Packets; /* Accounting-Output-Packets */
+ struct dict_object * Acct_Link_Count; /* Acct-Link-Count */
+ struct dict_object * Acct_Authentic; /* Acct-Authentic */
+ struct dict_object * Acct_Multi_Session_Id; /* Acct-Multi-Session-Id */
+ struct dict_object * Acct_Session_Id; /* Acct-Session-Id */
+ struct dict_object * Acct_Session_Time; /* Acct-Session-Time */
+
+ struct dict_object * ARAP_Password; /* ARAP-Password */
+ struct dict_object * ARAP_Security; /* ARAP-Security */
+ struct dict_object * ARAP_Security_Data; /* ARAP-Security-Data */
+ struct dict_object * Auth_Application_Id; /* Auth-Application-Id */
+ struct dict_object * Auth_Request_Type; /* Auth-Request-Type */
+ struct dict_object * Authorization_Lifetime; /* Authorization-Lifetime */
+ struct dict_object * Callback_Number; /* Callback-Number */
+ struct dict_object * Callback_Id; /* Callback-Id */
+ struct dict_object * Called_Station_Id; /* Called-Station-Id */
+ struct dict_object * Calling_Station_Id; /* Calling-Station-Id */
+ struct dict_object * Class; /* Class */
+ struct dict_object * CHAP_Algorithm; /* CHAP-Algorithm */
+ struct dict_object * CHAP_Auth; /* CHAP-Auth */
+ struct dict_object * CHAP_Challenge; /* CHAP-Challenge */
+ struct dict_object * CHAP_Ident; /* CHAP-Ident */
+ struct dict_object * CHAP_Response; /* CHAP-Response */
+ struct dict_object * Connect_Info; /* Connect-Info */
+ struct dict_object * Destination_Host; /* Destination-Host */
+ struct dict_object * Destination_Realm; /* Destination-Realm */
+ struct dict_object * EAP_Payload; /* EAP-Payload */
+ struct dict_object * Error_Message; /* Error-Message */
+ struct dict_object * Error_Reporting_Host; /* Error-Reporting-Host */
+ struct dict_object * Event_Timestamp; /* Event-Timestamp */
+ struct dict_object * Failed_AVP; /* Failed-AVP */
+ struct dict_object * Framed_AppleTalk_Link; /* Framed-AppleTalk-Link */
+ struct dict_object * Framed_AppleTalk_Network; /* Framed-AppleTalk-Network */
+ struct dict_object * Framed_AppleTalk_Zone; /* Framed-AppleTalk-Zone */
+ struct dict_object * Framed_Compression; /* Framed-Compression */
+ struct dict_object * Framed_IP_Address; /* Framed-IP-Address */
+ struct dict_object * Framed_IP_Netmask; /* Framed-IP-Netmask */
+ struct dict_object * Framed_Interface_Id; /* Framed-Interface-Id */
+ struct dict_object * Framed_IPv6_Prefix; /* Framed-IPv6-Prefix */
+ struct dict_object * Framed_IPX_Network; /* Framed-IPX-Network */
+ struct dict_object * Framed_MTU; /* Framed-MTU */
+ struct dict_object * Framed_Protocol; /* Framed-Protocol */
+ struct dict_object * Framed_Pool; /* Framed-Pool */
+ struct dict_object * Framed_IPv6_Route; /* Framed-IPv6-Route */
+ struct dict_object * Framed_IPv6_Pool; /* Framed-IPv6-Pool */
+ struct dict_object * Framed_Route; /* Framed-Route */
+ struct dict_object * Framed_Routing; /* Framed-Routing */
+ struct dict_object * Filter_Id; /* Filter-Id */
+ struct dict_object * Idle_Timeout; /* Idle-Timeout */
+ struct dict_object * Login_IP_Host; /* Login-IP-Host */
+ struct dict_object * Login_IPv6_Host; /* Login-IPv6-Host */
+ struct dict_object * Login_LAT_Group; /* Login-LAT-Group */
+ struct dict_object * Login_LAT_Node; /* Login-LAT-Node */
+ struct dict_object * Login_LAT_Port; /* Login-LAT-Port */
+ struct dict_object * Login_LAT_Service; /* Login-LAT-Service */
+ struct dict_object * Login_Service; /* Login-Service */
+ struct dict_object * Login_TCP_Port; /* Login-TCP-Port */
+ struct dict_object * NAS_Identifier; /* NAS-Identifier */
+ struct dict_object * NAS_IP_Address; /* NAS-IP-Address */
+ struct dict_object * NAS_IPv6_Address; /* NAS-IPv6-Address */
+ struct dict_object * NAS_Port; /* NAS-Port */
+ struct dict_object * NAS_Port_Id; /* NAS-Port-Id */
+ struct dict_object * NAS_Port_Type; /* NAS-Port-Type */
+ struct dict_object * Origin_AAA_Protocol; /* Origin-AAA-Protocol */
+ struct dict_object * Origin_Host; /* Origin-Host */
+ struct dict_object * Origin_Realm; /* Origin-Realm */
+ struct dict_object * Originating_Line_Info; /* Originating-Line-Info */
+ struct dict_object * Port_Limit; /* Port-Limit */
+ struct dict_object * Re_Auth_Request_Type; /* Re-Auth-Request-Type */
+ struct dict_object * Result_Code; /* Result-Code */
+ struct dict_object * Service_Type; /* Service-Type */
+ struct dict_object * Session_Id; /* Session-Id */
+ struct dict_object * Session_Timeout; /* Session-Timeout */
+ struct dict_object * State; /* State */
+ struct dict_object * Termination_Cause; /* Termination-Cause */
+ struct dict_object * Tunneling; /* Tunneling */
+ struct dict_object * Tunnel_Type; /* Tunnel-Type */
+ struct dict_object * Tunnel_Assignment_Id; /* Tunnel-Assignment-Id */
+ struct dict_object * Tunnel_Medium_Type; /* Tunnel-Medium-Type */
+ struct dict_object * Tunnel_Client_Endpoint; /* Tunnel-Client-Endpoint */
+ struct dict_object * Tunnel_Server_Endpoint; /* Tunnel-Server-Endpoint */
+ struct dict_object * Tunnel_Private_Group_Id; /* Tunnel-Private-Group-Id */
+ struct dict_object * Tunnel_Preference; /* Tunnel-Preference */
+ struct dict_object * Tunnel_Client_Auth_Id; /* Tunnel-Client-Auth-Id */
+ struct dict_object * Tunnel_Server_Auth_Id; /* Tunnel-Server-Auth-Id */
+ struct dict_object * User_Name; /* User-Name */
+
+ struct dict_object * Session_Termination_Request;/* STR */
+ } dict; /* cache of the dictionary objects we use */
+ struct session_handler * sess_hdl; /* We store RADIUS request authenticator information in the session */
+ char * confstr;
+
+ int ignore_nai;
+};
+
+/* The state we store in the session */
+struct sess_state {
+ application_id_t auth_appl; /* Auth-Application-Id used for this session, if available (stored in a Class attribute) */
+ int send_str; /* If not 0, we must send a STR when the ACA is received. */
+ uint32_t term_cause; /* If not 0, the Termination-Cause to put in the STR. */
+};
+
+static DECLARE_FD_DUMP_PROTOTYPE(acct_conf_session_state_dump, struct sess_state * st)
+{
+ return fd_dump_extend( FD_DUMP_STD_PARAMS, "[rgwx sess_state](@%p): aai:%x str:%d TC:%u", st, st->auth_appl, st->send_str, st->term_cause);
+}
+
+/* Initialize the plugin */
+static int acct_conf_parse(char * conffile, struct rgwp_config ** state)
+{
+ struct rgwp_config * new;
+ struct dict_object * app;
+
+ TRACE_ENTRY("%p %p", conffile, state);
+ CHECK_PARAMS( state );
+
+ CHECK_MALLOC( new = malloc(sizeof(struct rgwp_config)) );
+ memset(new, 0, sizeof(struct rgwp_config));
+
+ CHECK_FCT( fd_sess_handler_create( &new->sess_hdl, (void *)free, acct_conf_session_state_dump, NULL ) );
+ new->confstr = conffile;
+
+ if (conffile && strstr(conffile, "nonai"))
+ new->ignore_nai = 1;
+
+ /* Resolve all dictionary objects we use */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &new->dict.Accounting_Record_Number, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &new->dict.Accounting_Record_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Application-Id", &new->dict.Acct_Application_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Delay-Time", &new->dict.Acct_Delay_Time, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Input-Octets", &new->dict.Accounting_Input_Octets, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Output-Octets", &new->dict.Accounting_Output_Octets, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Input-Packets", &new->dict.Accounting_Input_Packets, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Output-Packets", &new->dict.Accounting_Output_Packets, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Authentic", &new->dict.Acct_Authentic, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Link-Count", &new->dict.Acct_Link_Count, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Multi-Session-Id", &new->dict.Acct_Multi_Session_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Session-Id", &new->dict.Acct_Session_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Acct-Session-Time", &new->dict.Acct_Session_Time, ENOENT) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Password", &new->dict.ARAP_Password, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Security", &new->dict.ARAP_Security, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Security-Data", &new->dict.ARAP_Security_Data, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Application-Id", &new->dict.Auth_Application_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Request-Type", &new->dict.Auth_Request_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Authorization-Lifetime", &new->dict.Authorization_Lifetime, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Callback-Number", &new->dict.Callback_Number, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Callback-Id", &new->dict.Callback_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Called-Station-Id", &new->dict.Called_Station_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Calling-Station-Id", &new->dict.Calling_Station_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Class", &new->dict.Class, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Connect-Info", &new->dict.Connect_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &new->dict.Destination_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &new->dict.Destination_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "EAP-Payload", &new->dict.EAP_Payload, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Message", &new->dict.Error_Message, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Reporting-Host", &new->dict.Error_Reporting_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Event-Timestamp", &new->dict.Event_Timestamp, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Failed-AVP", &new->dict.Failed_AVP, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-AppleTalk-Link", &new->dict.Framed_AppleTalk_Link, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-AppleTalk-Network", &new->dict.Framed_AppleTalk_Network, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-AppleTalk-Zone", &new->dict.Framed_AppleTalk_Zone, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Compression", &new->dict.Framed_Compression, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IP-Address", &new->dict.Framed_IP_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IP-Netmask", &new->dict.Framed_IP_Netmask, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Interface-Id", &new->dict.Framed_Interface_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IPv6-Prefix", &new->dict.Framed_IPv6_Prefix, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IPX-Network", &new->dict.Framed_IPX_Network, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-MTU", &new->dict.Framed_MTU, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Protocol", &new->dict.Framed_Protocol, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Pool", &new->dict.Framed_Pool, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Route", &new->dict.Framed_Route, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IPv6-Route", &new->dict.Framed_IPv6_Route, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IPv6-Pool", &new->dict.Framed_IPv6_Pool, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Routing", &new->dict.Framed_Routing, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Filter-Id", &new->dict.Filter_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Idle-Timeout", &new->dict.Idle_Timeout, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-IP-Host", &new->dict.Login_IP_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-IPv6-Host", &new->dict.Login_IPv6_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Group", &new->dict.Login_LAT_Group, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Node", &new->dict.Login_LAT_Node, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Port", &new->dict.Login_LAT_Port, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Service", &new->dict.Login_LAT_Service, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-Service", &new->dict.Login_Service, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-TCP-Port", &new->dict.Login_TCP_Port, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Identifier", &new->dict.NAS_Identifier, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-IP-Address", &new->dict.NAS_IP_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-IPv6-Address", &new->dict.NAS_IPv6_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port", &new->dict.NAS_Port, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port-Id", &new->dict.NAS_Port_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port-Type", &new->dict.NAS_Port_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-AAA-Protocol", &new->dict.Origin_AAA_Protocol, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &new->dict.Origin_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &new->dict.Origin_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Originating-Line-Info", &new->dict.Originating_Line_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Port-Limit", &new->dict.Port_Limit, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Re-Auth-Request-Type", &new->dict.Re_Auth_Request_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Result-Code", &new->dict.Result_Code, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Service-Type", &new->dict.Service_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &new->dict.Session_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Timeout", &new->dict.Session_Timeout, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "State", &new->dict.State, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Termination-Cause", &new->dict.Termination_Cause, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunneling", &new->dict.Tunneling, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Assignment-Id", &new->dict.Tunnel_Assignment_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Type", &new->dict.Tunnel_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Medium-Type", &new->dict.Tunnel_Medium_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Client-Endpoint", &new->dict.Tunnel_Client_Endpoint, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Server-Endpoint", &new->dict.Tunnel_Server_Endpoint, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Private-Group-Id", &new->dict.Tunnel_Private_Group_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Preference", &new->dict.Tunnel_Preference, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Client-Auth-Id", &new->dict.Tunnel_Client_Auth_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Server-Auth-Id", &new->dict.Tunnel_Server_Auth_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &new->dict.User_Name, ENOENT) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Session-Termination-Request", &new->dict.Session_Termination_Request, ENOENT) );
+
+ /* This plugin provides the following Diameter authentication applications support: */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Base Accounting", &app, ENOENT) );
+ CHECK_FCT( fd_disp_app_support ( app, NULL, 0, 1 ) );
+
+ *state = new;
+ return 0;
+}
+
+/* deinitialize */
+static void acct_conf_free(struct rgwp_config * state)
+{
+ TRACE_ENTRY("%p", state);
+ CHECK_PARAMS_DO( state, return );
+ CHECK_FCT_DO( fd_sess_handler_destroy( &state->sess_hdl, NULL ), );
+ free(state);
+ return;
+}
+
+/* Incoming RADIUS request */
+static int acct_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ int idx;
+ int send_str=0;
+ uint32_t str_cause=0;
+ uint32_t e2eid = 0;
+ application_id_t auth_appl=0;
+ int got_id = 0;
+ uint32_t status_type;
+ uint32_t termination_action = 0;
+ uint32_t gigawords_in=0, gigawords_out=0;
+ size_t nattr_used = 0;
+ union avp_value value;
+ struct avp ** avp_tun = NULL, *avp = NULL;
+ struct session * sess;
+
+ const char * prefix = "Diameter/";
+ size_t pref_len;
+ os0_t si = NULL;
+ size_t si_len = 0;
+ os0_t un = NULL;
+ size_t un_len = 0;
+
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+ CHECK_PARAMS(rad_req && (rad_req->hdr->code == RADIUS_CODE_ACCOUNTING_REQUEST) && rad_ans && diam_fw && *diam_fw);
+
+ pref_len = strlen(prefix);
+
+ /*
+ Either NAS-IP-Address or NAS-Identifier MUST be present in a
+ RADIUS Accounting-Request. It SHOULD contain a NAS-Port or NAS-
+ Port-Type attribute or both unless the service does not involve a
+ port or the NAS does not distinguish among its ports.
+ */
+ /* We also enforce that the message contains a CLASS attribute with Diameter/ prefix containing the Session-Id. */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+ uint8_t * v = (uint8_t *)(attr + 1);
+ size_t attr_len = attr->length - sizeof(struct radius_attr_hdr);
+
+ switch (attr->type) {
+ case RADIUS_ATTR_NAS_IP_ADDRESS:
+ case RADIUS_ATTR_NAS_IDENTIFIER:
+ case RADIUS_ATTR_NAS_IPV6_ADDRESS:
+ got_id = 1;
+ break;
+
+ case RADIUS_ATTR_TERMINATION_ACTION:
+ CHECK_PARAMS( attr->length == 6 );
+ termination_action = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ break;
+
+ case RADIUS_ATTR_ACCT_INPUT_GIGAWORDS:
+ CHECK_PARAMS( attr->length == 6 );
+ gigawords_in = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ break;
+
+ case RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS:
+ CHECK_PARAMS( attr->length == 6 );
+ gigawords_out = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ break;
+
+ case RADIUS_ATTR_CLASS:
+ if ((attr_len > pref_len ) && ! strncmp((char *)v, prefix, pref_len)) {
+ int i;
+ si = v + pref_len;
+ si_len = attr_len - pref_len;
+ TRACE_DEBUG(ANNOYING, "Found Class attribute with '%s' prefix (attr #%d), SI:'%.*s'.", prefix, idx, (int)si_len, si);
+ /* Remove from the message */
+ for (i = idx + 1; i < rad_req->attr_used; i++)
+ rad_req->attr_pos[i - 1] = rad_req->attr_pos[i];
+ rad_req->attr_used -= 1;
+ }
+ break;
+
+ case RADIUS_ATTR_USER_NAME:
+ if (attr_len) {
+ un = v;
+ un_len = attr_len;
+ TRACE_DEBUG(ANNOYING, "Found a User-Name attribute: '%.*s'", (int)un_len, un);
+ }
+ break;
+
+ }
+ }
+
+ /* Check basic information is there */
+ if (!got_id || radius_msg_get_attr_int32(rad_req, RADIUS_ATTR_ACCT_STATUS_TYPE, &status_type)) {
+ TRACE_DEBUG(INFO, "[acct.rgwx] RADIUS Account-Request from %s did not contain a NAS ip/identifier or Acct-Status-Type attribute, reject.", rgw_clients_id(cli));
+ return EINVAL;
+ }
+
+ /*
+ -- RFC2866:
+ In Accounting-Request Packets, the Authenticator value is a 16
+ octet MD5 [5] checksum, called the Request Authenticator.
+
+ The NAS and RADIUS accounting server share a secret. The Request
+ Authenticator field in Accounting-Request packets contains a one-
+ way MD5 hash calculated over a stream of octets consisting of the
+ Code + Identifier + Length + 16 zero octets + request attributes +
+ shared secret (where + indicates concatenation). The 16 octet MD5
+ hash value is stored in the Authenticator field of the
+ Accounting-Request packet.
+
+ Note that the Request Authenticator of an Accounting-Request can
+ not be done the same way as the Request Authenticator of a RADIUS
+ Access-Request, because there is no User-Password attribute in an
+ Accounting-Request.
+
+
+ -- RFC5080:
+ The Request Authenticator field MUST contain the correct data, as
+ given by the above calculation. Invalid packets are silently
+ discarded. Note that some early implementations always set the
+ Request Authenticator to all zeros. New implementations of RADIUS
+ clients MUST use the above algorithm to calculate the Request
+ Authenticator field. New RADIUS server implementations MUST
+ silently discard invalid packets.
+
+ */
+ {
+ uint8_t save[MD5_MAC_LEN];
+ uint8_t * secret;
+ size_t secret_len;
+
+ /* Get the shared secret */
+ CHECK_FCT(rgw_clients_getkey(cli, &secret, &secret_len));
+
+ /* Copy the received Request Authenticator */
+ memcpy(&save[0], &rad_req->hdr->authenticator[0], MD5_MAC_LEN);
+
+ /* Compute the same authenticator */
+ radius_msg_finish_acct(rad_req, secret, secret_len);
+
+ /* And now compare with the received value */
+ if (memcmp(&save[0], &rad_req->hdr->authenticator[0], MD5_MAC_LEN)) {
+ /* Invalid authenticator */
+ TRACE_BUFFER(FD_LOG_DEBUG, FULL+1, "Received ReqAuth: ", &save[0], MD5_MAC_LEN, "" );
+ TRACE_BUFFER(FD_LOG_DEBUG, FULL+1, "Expected ReqAuth: ", &rad_req->hdr->authenticator[0], MD5_MAC_LEN, "" );
+ TRACE_DEBUG(INFO, "[acct.rgwx] Invalid Request Authenticator in Account-Request from %s, discarding the message.", rgw_clients_id(cli));
+ return EINVAL;
+ }
+ }
+
+
+ /* Handle the Accounting-On case: nothing to do, just reply OK */
+ if (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON) {
+ TRACE_DEBUG(FULL, "[acct.rgwx] Received Accounting-On Acct-Status-Type attribute, replying without translation to Diameter.");
+ CHECK_MALLOC( *rad_ans = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, rad_req->hdr->identifier) );
+ return -2;
+ }
+
+ if (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF) {
+ TRACE_DEBUG(FULL, "[acct.rgwx] Received Accounting-Off Acct-Status-Type attribute, we must terminate all active sessions.");
+ TODO("RADIUS side is rebooting, send STR on all sessions???");
+ return ENOTSUP;
+ }
+
+ /* Check if we got a valid session information, otherwise the server will not be able to handle the data... */
+ if (!si) {
+ TRACE_DEBUG(INFO, "[acct.rgwx] RADIUS Account-Request from %s did not contain a CLASS attribute with Diameter session information, reject.", rgw_clients_id(cli));
+ return EINVAL;
+ }
+
+ /* Add the Destination-Realm */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Realm, 0, &avp ) );
+ idx = 0;
+ if (un && ! cs->ignore_nai) {
+ /* Is there an '@' in the user name? We don't care for decorated NAI here */
+ for (idx = un_len - 2; idx > 0; idx--) {
+ if (un[idx] == '@') {
+ idx++;
+ break;
+ }
+ }
+ }
+ if (idx == 0) {
+ /* Not found in the User-Name => we use the local domain of this gateway */
+ value.os.data = (uint8_t *)fd_g_config->cnf_diamrlm;
+ value.os.len = fd_g_config->cnf_diamrlm_len;
+ } else {
+ value.os.data = un + idx;
+ value.os.len = un_len - idx;
+ }
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+
+ /* Create the Session-Id AVP */
+ {
+ CHECK_FCT( fd_sess_fromsid_msg ( si, si_len, &sess, NULL) );
+
+ TRACE_DEBUG(FULL, "[acct.rgwx] Translating new accounting message for session '%.*s'...", (int)si_len, si);
+
+ /* Add the Session-Id AVP as first AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) );
+ value.os.data = (unsigned char *)si;
+ value.os.len = si_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+ CHECK_FCT( fd_msg_sess_set(*diam_fw, sess) );
+ }
+
+
+ /* Add the command code */
+ {
+ struct msg_hdr * header = NULL;
+ CHECK_FCT( fd_msg_hdr ( *diam_fw, &header ) );
+ header->msg_flags = CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE;
+ header->msg_code = CC_AC;
+ header->msg_appl = AI_ACCT;
+
+ /* Add the Acct-Application-Id */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Acct_Application_Id, 0, &avp ) );
+ value.i32 = header->msg_appl;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+
+ /* save the end to end id */
+ e2eid = header->msg_eteid;
+ }
+
+ /* Convert the RADIUS attributes, as they appear in the message */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+
+ switch (attr->type) {
+ /*
+ Any attribute valid in a RADIUS Access-Request or Access-Accept
+ packet is valid in a RADIUS Accounting-Request packet, except that
+ the following attributes MUST NOT be present in an Accounting-
+ Request: User-Password, CHAP-Password, Reply-Message, State.
+ */
+ case RADIUS_ATTR_USER_PASSWORD:
+ case RADIUS_ATTR_CHAP_PASSWORD:
+ case RADIUS_ATTR_REPLY_MESSAGE:
+ case RADIUS_ATTR_STATE:
+ case RADIUS_ATTR_MESSAGE_AUTHENTICATOR:
+ case RADIUS_ATTR_EAP_MESSAGE:
+ TRACE_DEBUG(INFO, "[acct.rgwx] RADIUS Account-Request contains a forbidden attribute (%hhd), reject.", attr->type);
+ return EINVAL;
+
+
+ /* This macro converts a RADIUS attribute to a Diameter AVP of type OctetString */
+ #define CONV2DIAM_STR( _dictobj_ ) \
+ CHECK_PARAMS( attr->length >= 2 ); \
+ /* Create the AVP with the specified dictionary model */ \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - 2; \
+ value.os.data = (unsigned char *)(attr + 1); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ /* Add the AVP in the Diameter message. */ \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+ /* Same thing, for scalar AVPs of 32 bits */
+ #define CONV2DIAM_32B( _dictobj_ ) \
+ CHECK_PARAMS( attr->length == 6 ); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u32 = (v[0] << 24) \
+ | (v[1] << 16) \
+ | (v[2] << 8) \
+ | v[3] ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+ /* And the 64b version */
+ #define CONV2DIAM_64B( _dictobj_ ) \
+ CHECK_PARAMS( attr->length == 10); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u64 = ((uint64_t)(v[0]) << 56) \
+ | ((uint64_t)(v[1]) << 48) \
+ | ((uint64_t)(v[2]) << 40) \
+ | ((uint64_t)(v[3]) << 32) \
+ | ((uint64_t)(v[4]) << 24) \
+ | ((uint64_t)(v[5]) << 16) \
+ | ((uint64_t)(v[6]) << 8) \
+ | (uint64_t)(v[7]) ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+
+ /* Attributes as listed in RFC2866, section 5.13 and RFC4005, section 10.2.1 */
+ case RADIUS_ATTR_USER_NAME:
+ CONV2DIAM_STR( User_Name );
+ break;
+
+ case RADIUS_ATTR_NAS_IP_ADDRESS:
+ CONV2DIAM_STR( NAS_IP_Address );
+ break;
+
+ case RADIUS_ATTR_NAS_PORT:
+ CONV2DIAM_32B( NAS_Port );
+ break;
+
+ case RADIUS_ATTR_SERVICE_TYPE:
+ CONV2DIAM_32B( Service_Type );
+ break;
+
+ case RADIUS_ATTR_FRAMED_PROTOCOL:
+ CONV2DIAM_32B( Framed_Protocol );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IP_ADDRESS:
+ CONV2DIAM_STR( Framed_IP_Address );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IP_NETMASK:
+ CONV2DIAM_STR( Framed_IP_Netmask );
+ break;
+
+ case RADIUS_ATTR_FRAMED_ROUTING:
+ CONV2DIAM_32B( Framed_Routing );
+ break;
+
+ case RADIUS_ATTR_FILTER_ID:
+ CONV2DIAM_STR( Filter_Id );
+ break;
+
+ case RADIUS_ATTR_FRAMED_MTU:
+ CONV2DIAM_32B( Framed_MTU );
+ break;
+
+ case RADIUS_ATTR_FRAMED_COMPRESSION:
+ CONV2DIAM_32B( Framed_Compression );
+ break;
+
+ case RADIUS_ATTR_LOGIN_IP_HOST:
+ CONV2DIAM_STR( Login_IP_Host );
+ break;
+
+ case RADIUS_ATTR_LOGIN_SERVICE:
+ CONV2DIAM_32B( Login_Service );
+ break;
+
+ case RADIUS_ATTR_LOGIN_TCP_PORT:
+ CONV2DIAM_32B( Login_TCP_Port );
+ break;
+
+ case RADIUS_ATTR_CALLBACK_NUMBER:
+ CONV2DIAM_STR( Callback_Number );
+ break;
+
+ case RADIUS_ATTR_CALLBACK_ID:
+ CONV2DIAM_STR( Callback_Id );
+ break;
+
+ case RADIUS_ATTR_FRAMED_ROUTE:
+ CONV2DIAM_STR( Framed_Route );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IPX_NETWORK:
+ CONV2DIAM_32B( Framed_IPX_Network );
+ break;
+
+ case RADIUS_ATTR_CLASS:
+ CONV2DIAM_STR( Class );
+ /* In addition, save the data in the session if it is "our" CLASS_AAI_PREFIX Class attribute */
+ {
+ char buf[32];
+ char * attr_val, *auth_val;
+ attr_val = (char *)(attr + 1);
+ auth_val = attr_val + CONSTSTRLEN(CLASS_AAI_PREFIX);
+ if ( (attr->length > sizeof(struct radius_attr_hdr) + CONSTSTRLEN(CLASS_AAI_PREFIX) )
+ && (attr->length < sizeof(struct radius_attr_hdr) + CONSTSTRLEN(CLASS_AAI_PREFIX) + sizeof(buf))
+ && ! strncmp(attr_val, CLASS_AAI_PREFIX, CONSTSTRLEN(CLASS_AAI_PREFIX))) {
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, auth_val, attr->length - sizeof(struct radius_attr_hdr) - CONSTSTRLEN(CLASS_AAI_PREFIX));
+ if (sscanf(buf, "%u", &auth_appl) == 1) {
+ TRACE_DEBUG(ANNOYING, "Found Class attribute with '%s' prefix (attr #%d), AAI:%u.", CLASS_AAI_PREFIX, idx, auth_appl);
+ }
+ }
+ }
+ break;
+
+ case RADIUS_ATTR_VENDOR_SPECIFIC:
+ if (attr->length >= 6) {
+ uint32_t vendor_id;
+ uint8_t * c = (uint8_t *)(attr + 1);
+
+ vendor_id = c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3];
+ c += 4;
+
+ switch (vendor_id) {
+
+ /* For the vendors we KNOW they follow the VSA recommended format, we convert following the rules of RFC4005 (9.6.2) */
+ case RADIUS_VENDOR_ID_MICROSOFT : /* RFC 2548 */
+ /* other vendors ? */
+ {
+ size_t left;
+ struct radius_attr_vendor *vtlv;
+
+ left = attr->length - 6;
+ vtlv = (struct radius_attr_vendor *)c;
+
+ while ((left >= 2) && (vtlv->vendor_length <= left)) {
+ /* Search our dictionary for corresponding Vendor's AVP */
+ struct dict_avp_request req;
+ struct dict_object * avp_model = NULL;
+ memset(&req, 0, sizeof(struct dict_avp_request));
+ req.avp_vendor = vendor_id;
+ req.avp_code = vtlv->vendor_type;
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_CODE_AND_VENDOR, &req, &avp_model, 0) );
+ if (!avp_model) {
+ TRACE_DEBUG(FULL, "Unknown attribute (vendor 0x%x, code 0x%x) ignored.", req.avp_vendor, req.avp_code);
+ } else {
+ CHECK_FCT( fd_msg_avp_new ( avp_model, 0, &avp ) );
+ value.os.len = vtlv->vendor_length - 2;
+ value.os.data = (unsigned char *)(vtlv + 1);
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ c += vtlv->vendor_length;
+ left -= vtlv->vendor_length;
+ vtlv = (struct radius_attr_vendor *)c;
+ }
+ }
+ break;
+
+ /* Other vendors we KNOw how to convert the attributes would be added here... */
+ /* case RADIUS_VENDOR_ID_CISCO :
+ break; */
+ /* case RADIUS_VENDOR_ID_IETF : (extended RADIUS attributes)
+ break; */
+
+ /* When we don't know, just discard the attribute... VSA are optional with regards to RADIUS anyway */
+ default:
+ /* do nothing */
+ TRACE_DEBUG(FULL, "VSA attribute from vendor %d discarded", vendor_id);
+
+ }
+ }
+ break;
+
+ case RADIUS_ATTR_SESSION_TIMEOUT:
+ /* Translation depends on Termination-Action : rfc4005#section-9.2.1 */
+ if (termination_action != RADIUS_TERMINATION_ACTION_RADIUS_REQUEST) {
+ CONV2DIAM_32B( Session_Timeout );
+ } else {
+ CONV2DIAM_32B( Authorization_Lifetime );
+ /* And add this additional AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Re_Auth_Request_Type, 0, &avp ) );
+ value.u32 = ACV_ART_AUTHORIZE_AUTHENTICATE;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ break;
+
+ case RADIUS_ATTR_IDLE_TIMEOUT:
+ CONV2DIAM_32B( Idle_Timeout );
+ break;
+
+ case RADIUS_ATTR_TERMINATION_ACTION:
+ /* Just remove */
+ break;
+
+ case RADIUS_ATTR_CALLED_STATION_ID:
+ CONV2DIAM_STR( Called_Station_Id );
+ break;
+
+ case RADIUS_ATTR_CALLING_STATION_ID:
+ CONV2DIAM_STR( Calling_Station_Id );
+ break;
+
+ case RADIUS_ATTR_NAS_IDENTIFIER:
+ CONV2DIAM_STR( NAS_Identifier );
+ break;
+
+ /* Proxy-State is handled by echo_drop.rgwx plugin, we ignore it here */
+
+ case RADIUS_ATTR_LOGIN_LAT_SERVICE:
+ CONV2DIAM_STR( Login_LAT_Service );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_NODE:
+ CONV2DIAM_STR( Login_LAT_Node );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_GROUP:
+ CONV2DIAM_STR( Login_LAT_Group );
+ break;
+
+ case RADIUS_ATTR_FRAMED_APPLETALK_LINK:
+ CONV2DIAM_32B( Framed_AppleTalk_Link );
+ break;
+
+ case RADIUS_ATTR_FRAMED_APPLETALK_NETWORK:
+ CONV2DIAM_32B( Framed_AppleTalk_Network );
+ break;
+
+ case RADIUS_ATTR_FRAMED_APPLETALK_ZONE:
+ CONV2DIAM_STR( Framed_AppleTalk_Zone );
+ break;
+
+ case RADIUS_ATTR_ACCT_STATUS_TYPE:
+ /*
+ - If the RADIUS message received is an Accounting-Request, the
+ Acct-Status-Type attribute value must be converted to a
+ Accounting-Record-Type AVP value. If the Acct-Status-Type
+ attribute value is STOP, the local server MUST issue a
+ Session-Termination-Request message once the Diameter
+ Accounting-Answer message has been received.
+ */
+ switch (status_type) {
+ case RADIUS_ACCT_STATUS_TYPE_START:
+ value.u32 = ACV_ART_START_RECORD;
+ break;
+ case RADIUS_ACCT_STATUS_TYPE_STOP:
+ value.u32 = ACV_ART_STOP_RECORD;
+ send_str = 1; /* Register this info in the session */
+ break;
+ case RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE:
+ value.u32 = ACV_ART_INTERIM_RECORD;
+ break;
+ default:
+ TRACE_DEBUG(INFO, "Unknown RADIUS_ATTR_ACCT_STATUS_TYPE value %d, aborting...", status_type);
+ return ENOTSUP;
+ }
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Record_Type, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+
+ /* While here, we also add the Accouting-Record-Number AVP.
+ The Accounting-Record-Number AVP (AVP Code 485) is of type Unsigned32
+ and identifies this record within one session. As Session-Id AVPs
+ are globally unique, the combination of Session-Id and Accounting-
+ Record-Number AVPs is also globally unique, and can be used in
+ matching accounting records with confirmations. An easy way to
+ produce unique numbers is to set the value to 0 for records of type
+ EVENT_RECORD and START_RECORD, and set the value to 1 for the first
+ INTERIM_RECORD, 2 for the second, and so on until the value for
+ STOP_RECORD is one more than for the last INTERIM_RECORD.
+
+ -- we actually use the end-to-end id of the message here, which remains constant
+ if we send a duplicate, so it has the same properties as the suggested algorithm.
+ Anyway, it assumes that we are not converting twice the same RADIUS message.
+ . */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Record_Number, 0, &avp ) );
+ value.u32 = e2eid;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+
+ break;
+
+ case RADIUS_ATTR_ACCT_DELAY_TIME:
+ CONV2DIAM_32B( Acct_Delay_Time );
+ break;
+
+ /*
+ - If the RADIUS message contains the Accounting-Input-Octets,
+ Accounting-Input-Packets, Accounting-Output-Octets, or
+ Accounting-Output-Packets, these attributes must be converted
+ to the Diameter equivalents. Further, if the Acct-Input-
+ Gigawords or Acct-Output-Gigawords attributes are present,
+ these must be used to properly compute the Diameter accounting
+ AVPs.
+ */
+ case RADIUS_ATTR_ACCT_INPUT_OCTETS:
+ memset(&value, 0, sizeof(value));
+ {
+ uint8_t * v = (uint8_t *)(attr + 1);
+ value.u64 = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ }
+ value.u64 += ((uint64_t)gigawords_in << 32);
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Input_Octets, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ break;
+
+ case RADIUS_ATTR_ACCT_OUTPUT_OCTETS:
+ memset(&value, 0, sizeof(value));
+ {
+ uint8_t * v = (uint8_t *)(attr + 1);
+ value.u64 = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ }
+ value.u64 += ((uint64_t)gigawords_out << 32);
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Output_Octets, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ break;
+
+ case RADIUS_ATTR_ACCT_SESSION_ID:
+ CONV2DIAM_STR( Acct_Session_Id );
+ break;
+
+ case RADIUS_ATTR_ACCT_AUTHENTIC:
+ CONV2DIAM_32B( Acct_Authentic );
+ break;
+
+ case RADIUS_ATTR_ACCT_SESSION_TIME:
+ CONV2DIAM_32B( Acct_Session_Time );
+ break;
+
+ case RADIUS_ATTR_ACCT_INPUT_PACKETS:
+ memset(&value, 0, sizeof(value));
+ {
+ uint8_t * v = (uint8_t *)(attr + 1);
+ value.u64 = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ }
+ /* value.u64 += (gigawords_in << 32); */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Input_Packets, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ break;
+
+ case RADIUS_ATTR_ACCT_OUTPUT_PACKETS:
+ memset(&value, 0, sizeof(value));
+ {
+ uint8_t * v = (uint8_t *)(attr + 1);
+ value.u64 = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ }
+ /* value.u64 += (gigawords_out << 32); */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Accounting_Output_Packets, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ break;
+
+ /*
+ - If the Accounting message contains an Acct-Termination-Cause
+ attribute, it should be translated to the equivalent
+ Termination-Cause AVP value.
+ */
+ case RADIUS_ATTR_ACCT_TERMINATE_CAUSE:
+ /* rfc4005#section-9.3.5 */
+ {
+ uint8_t * v = (uint8_t *)(attr + 1);
+ str_cause = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+ }
+ str_cause += 10; /* This seems to be the rule, we can modify later if needed */
+ break;
+
+ case RADIUS_ATTR_ACCT_MULTI_SESSION_ID:
+ CONV2DIAM_STR( Acct_Multi_Session_Id );
+ break;
+
+ case RADIUS_ATTR_ACCT_LINK_COUNT:
+ CONV2DIAM_32B( Acct_Link_Count );
+ break;
+
+ /* CHAP-Challenge is not present in Accounting-Request */
+
+ case RADIUS_ATTR_NAS_PORT_TYPE:
+ CONV2DIAM_32B( NAS_Port_Type );
+ break;
+
+ case RADIUS_ATTR_PORT_LIMIT:
+ CONV2DIAM_32B( Port_Limit );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_PORT:
+ CONV2DIAM_STR( Login_LAT_Port );
+ break;
+
+ /* RFC 3162 */
+ case RADIUS_ATTR_NAS_IPV6_ADDRESS:
+ CONV2DIAM_STR( NAS_IPv6_Address );
+ break;
+
+ case RADIUS_ATTR_FRAMED_INTERFACE_ID:
+ CONV2DIAM_64B( Framed_Interface_Id );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IPV6_PREFIX:
+ CONV2DIAM_STR( Framed_IPv6_Prefix );
+ break;
+
+ case RADIUS_ATTR_LOGIN_IPV6_HOST:
+ CONV2DIAM_STR( Login_IPv6_Host );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IPV6_ROUTE:
+ CONV2DIAM_STR( Framed_IPv6_Route );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IPV6_POOL:
+ CONV2DIAM_STR( Framed_IPv6_Pool );
+ break;
+
+ /* RFC 2868 */
+ /* Prepare the top-level Tunneling AVP for each tag values, as needed, and add to the Diameter message.
+ This macro is called when an AVP is added inside the group, so we will not have empty grouped AVPs */
+ #define AVP_TUN_PREPARE() { \
+ if (avp_tun == NULL) { \
+ CHECK_MALLOC( avp_tun = calloc(sizeof(struct avp *), 32 ) ); \
+ } \
+ tag = *(uint8_t *)(attr + 1); \
+ if (tag > 0x1F) tag = 0; \
+ if (avp_tun[tag] == NULL) { \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Tunneling, 0, &avp_tun[tag] ) ); \
+ CHECK_FCT( fd_msg_avp_add (*diam_fw, MSG_BRW_LAST_CHILD, avp_tun[tag]));\
+ } \
+ }
+
+ /* Convert an attribute to an OctetString AVP and add inside the Tunneling AVP corresponding to the tag */
+ #define CONV2DIAM_TUN_STR( _dictobj_ ) { \
+ uint8_t tag; \
+ CHECK_PARAMS( attr->length >= 3); \
+ AVP_TUN_PREPARE(); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - (tag ? 3 : 2); \
+ value.os.data = ((unsigned char *)(attr + 1)) + (tag ? 1 : 0); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( avp_tun[tag], MSG_BRW_LAST_CHILD, avp) ); \
+ }
+
+ /* Convert an attribute to a scalar AVP and add inside the Tunneling AVP corresponding to the tag */
+ #define CONV2DIAM_TUN_24B( _dictobj_ ) { \
+ uint8_t tag; \
+ CHECK_PARAMS( attr->length == 6); \
+ AVP_TUN_PREPARE(); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u32 = (v[1] << 16) | (v[2] <<8) | v[3] ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( avp_tun[tag], MSG_BRW_LAST_CHILD, avp) ); \
+ }
+
+ /*
+ - If the RADIUS message contains Tunnel information [RADTunnels],
+ the attributes or tagged groups should each be converted to a
+ Diameter Tunneling Grouped AVP set. If the tunnel information
+ contains a Tunnel-Password attribute, the RADIUS encryption
+ must be resolved, and the password forwarded, by using Diameter
+ security methods.
+ -> If the RADIUS message does not use properly the Tag info, result is unpredictable here..
+ */
+ case RADIUS_ATTR_TUNNEL_TYPE:
+ CONV2DIAM_TUN_24B( Tunnel_Type );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE:
+ CONV2DIAM_TUN_24B( Tunnel_Medium_Type );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_CLIENT_ENDPOINT:
+ CONV2DIAM_TUN_STR( Tunnel_Client_Endpoint );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_SERVER_ENDPOINT:
+ CONV2DIAM_TUN_STR( Tunnel_Server_Endpoint );
+ break;
+
+ /* Tunnel-Password never present in an Accounting-Request */
+
+ case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Private_Group_Id );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_ASSIGNMENT_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Assignment_Id );
+ break;
+
+ /* Tunnel-Reference never present in an Accounting-Request */
+
+ case RADIUS_ATTR_TUNNEL_CLIENT_AUTH_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Client_Auth_Id );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_SERVER_AUTH_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Server_Auth_Id );
+ break;
+
+ /* RFC 2869 */
+ /*
+ Acct-Input-Gigawords, Acct-Output-
+ Gigawords, Event-Timestamp, and NAS-Port-Id may have 0-1 instances in
+ an Accounting-Request packet. Connect-Info may have 0+ instances in
+ an Accounting-Request packet. The other attributes added in this
+ document must not be present in an Accounting-Request.
+ */
+ case RADIUS_ATTR_ACCT_INPUT_GIGAWORDS:
+ break; /* we already saved the value in gigawords_in */
+
+ case RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS:
+ break; /* we already saved the value in gigawords_out */
+
+ case RADIUS_ATTR_EVENT_TIMESTAMP:
+ /* RADIUS:
+ The Value field is four octets encoding an unsigned integer with
+ the number of seconds since January 1, 1970 00:00 UTC.
+ Diameter:
+ The Time format is derived from the OctetString AVP Base Format.
+ The string MUST contain four octets, in the same format as the
+ first four bytes are in the NTP timestamp format. The NTP
+ Timestamp format is defined in Chapter 3 of [RFC4330].
+
+ This represents the number of seconds since 0h on 1 January 1900
+ with respect to the Coordinated Universal Time (UTC).
+
+ -- RFC4330:
+ NTP timestamps are represented as a 64-bit unsigned
+ fixed-point number, in seconds relative to 0h on 1 January 1900. The
+ integer part is in the first 32 bits, and the fraction part in the
+ last 32 bits. In the fraction part, the non-significant low-order
+ bits are not specified and are ordinarily set to 0.
+ */
+ {
+ uint32_t ts;
+
+ uint8_t * v = (uint8_t *)(attr + 1);
+ /* Read the RADIUS attribute value */
+ ts = (v[0] << 24)
+ | (v[1] << 16)
+ | (v[2] << 8)
+ | v[3] ;
+
+ /* Add the 70 missing years */
+ ts += 2208988800U; /* 60 * 60 * 24 * ( 365 * 70 + 17 ) */
+
+ /* Convert to network byte order */
+ ts = htonl(ts);
+
+ /* Diameter Time datatype is derived from OctetString */
+ value.os.data = (void *) &ts;
+ value.os.len = sizeof(uint32_t);
+
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Event_Timestamp, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ break;
+
+ case RADIUS_ATTR_NAS_PORT_ID:
+ CONV2DIAM_STR( NAS_Port_Id );
+ break;
+
+ case RADIUS_ATTR_CONNECT_INFO:
+ CONV2DIAM_STR( Connect_Info );
+ break;
+
+ case RADIUS_ATTR_FRAMED_POOL: /* To follow the IPv6 version */
+ CONV2DIAM_STR( Framed_Pool );
+ break;
+
+
+ /* RFC 3579 */
+ /*
+ The EAP-Message and Message-Authenticator attributes specified in
+ this document MUST NOT be present in an Accounting-Request.
+ */
+ case RADIUS_ATTR_ORIGINATING_LINE_INFO:
+ CONV2DIAM_STR( Originating_Line_Info );
+ break;
+
+ /* Default */
+ default: /* unknown attribute */
+ /* We just keep the attribute in the RADIUS message */
+ rad_req->attr_pos[nattr_used++] = rad_req->attr_pos[idx];
+ }
+ }
+
+ /* Update the radius message to remove all handled attributes */
+ rad_req->attr_used = nattr_used;
+
+ /* Store useful information in the session */
+ {
+ struct sess_state * st;
+
+ CHECK_MALLOC( st = malloc(sizeof(struct sess_state)) );
+ memset(st, 0, sizeof(struct sess_state));
+ st->auth_appl = auth_appl;
+ if (auth_appl) { /* We use the value 0 for servers which indicated NO STATE MAINTAINED, hence have no need for STR */
+ st->send_str = send_str;
+ }
+ st->term_cause = str_cause;
+ CHECK_FCT( fd_sess_state_store( cs->sess_hdl, sess, &st ) );
+ }
+
+ return 0;
+}
+
+/* Callback when an STA is received after having sent an STR. */
+static void handle_sta(void * data, struct msg ** answer)
+{
+ struct rgwp_config * cs = data;
+ struct avp *avp;
+ struct avp_hdr *ahdr;
+
+ CHECK_PARAMS_DO( data && answer && *answer, goto out );
+
+ /* Check the Diameter error code */
+ CHECK_FCT_DO( fd_msg_search_avp (*answer, cs->dict.Result_Code, &avp), goto out );
+ CHECK_PARAMS_DO( avp, goto out );
+ CHECK_FCT_DO( fd_msg_avp_hdr ( avp, &ahdr ), goto out );
+ if (ahdr->avp_value->u32 != ER_DIAMETER_SUCCESS)
+ goto out;
+
+ /* OK, discard the message without complaining */
+ fd_msg_free(*answer);
+ *answer = NULL;
+
+out:
+ if (answer && *answer) {
+ char * buf = NULL; size_t buflen;
+ CHECK_MALLOC_DO( fd_msg_dump_treeview(&buf, &buflen, NULL, *answer, NULL, 0, 1), );
+ TRACE_DEBUG(INFO, "Received the following problematic STA message, discarding anyway: %s", buf ?: "<error>");
+ free(buf);
+
+ fd_msg_free(*answer);
+ *answer = NULL;
+ }
+ return;
+}
+
+static int acct_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+ struct session * sess;
+ struct sess_state * st = NULL, stloc;
+ struct avp *avp, *next;
+ struct avp_hdr *ahdr, *oh, *or;
+ os0_t sid = NULL;
+ size_t sidlen;
+
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+ CHECK_PARAMS(cs);
+
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_ans, &sess, NULL) );
+ if (sess) {
+ CHECK_FCT( fd_sess_state_retrieve( cs->sess_hdl, sess, &st ) );
+ CHECK_FCT( fd_sess_getsid(sess, &sid, &sidlen) );
+ }
+
+ if (!st) {
+ TRACE_DEBUG(INFO, "Received an ACA without corresponding session information, cannot translate to RADIUS");
+ return EINVAL;
+ }
+
+ /* Free the state */
+ memcpy(&stloc, st, sizeof(struct sess_state));
+ free(st);
+ st = &stloc;
+
+ /* Search these AVPs first */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Origin_Host, &avp) );
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &oh ) );
+
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Origin_Realm, &avp) );
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &or ) );
+
+ /* Check the Diameter error code */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Result_Code, &avp) );
+ ASSERT( avp ); /* otherwise the message should have been discarded a lot earlier because of ABNF */
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ switch (ahdr->avp_value->u32) {
+ case ER_DIAMETER_SUCCESS:
+ case ER_DIAMETER_LIMITED_SUCCESS:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCOUNTING_RESPONSE;
+ break;
+
+ default:
+ fd_log_debug("[acct.rgwx] Received Diameter answer with error code '%d' from server '%.*s', session %.*s, not translating into Accounting-Response",
+ ahdr->avp_value->u32,
+ (int)oh->avp_value->os.len, oh->avp_value->os.data,
+ (int)sidlen, sid);
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Error_Message, &avp) );
+ if (avp) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ fd_log_debug("[acct.rgwx] Error-Message content: '%.*s'",
+ (int)ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+ }
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Error_Reporting_Host, &avp) );
+ if (avp) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ fd_log_debug("[acct.rgwx] Error-Reporting-Host: '%.*s'",
+ (int)ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+ }
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Failed_AVP, &avp) );
+ if (avp) {
+ fd_log_debug("[acct.rgwx] Failed-AVP was included in the message.");
+ /* Dump its content ? */
+ }
+
+ /* Now, destroy the Diameter message, since we know it is not converted to RADIUS */
+ CHECK_FCT( fd_msg_free(*diam_ans) );
+ *diam_ans = NULL;
+
+ return -1;
+ }
+ /* Remove this Result-Code avp */
+ CHECK_FCT( fd_msg_free( avp ) );
+
+ /* If it was a response to a STOP record, we must send an STR for this session */
+ if (st->send_str) {
+ struct msg * str = NULL;
+ struct msg_hdr * hdr = NULL;
+ DiamId_t fqdn;
+ size_t fqdn_len;
+ DiamId_t realm;
+ size_t realm_len;
+ union avp_value avp_val;
+
+ /* Create a new STR message */
+ CHECK_FCT( fd_msg_new ( cs->dict.Session_Termination_Request, MSGFL_ALLOC_ETEID, &str ) );
+
+ /* Set the application-id to the auth application if available, accounting otherwise (not sure what is actually expected...) */
+ CHECK_FCT( fd_msg_hdr ( str, &hdr ) );
+ hdr->msg_appl = st->auth_appl ?: AI_ACCT;
+
+ /* Add the Session-Id AVP as first AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) );
+ avp_val.os.data = sid;
+ avp_val.os.len = sidlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_FIRST_CHILD, avp) );
+ CHECK_FCT( fd_sess_ref_msg(sess) );
+
+ /* Add the Destination-Realm as next AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Realm, 0, &avp ) );
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, or->avp_value ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Get information on the NAS */
+ CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &fqdn_len, &realm, &realm_len) );
+
+ /* Add the Origin-Host as next AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Origin_Host, 0, &avp ) );
+ memset(&avp_val, 0, sizeof(avp_val));
+ avp_val.os.data = (unsigned char *)fqdn;
+ avp_val.os.len = fqdn_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Add the Origin-Realm as next AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Origin_Realm, 0, &avp ) );
+ memset(&avp_val, 0, sizeof(avp_val));
+ avp_val.os.data = (unsigned char *)realm;
+ avp_val.os.len = realm_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Auth-Application-Id -- if we did not get it from our Class attribute, we just set "0" */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Auth_Application_Id, 0, &avp ) );
+ avp_val.u32 = st->auth_appl;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Termination-Cause */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Termination_Cause, 0, &avp ) );
+ avp_val.u32 = st->term_cause;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) );
+ CHECK_FCT( fd_msg_avp_add ( str, MSG_BRW_LAST_CHILD, avp) );
+
+ /* Send this message */
+ CHECK_FCT( fd_msg_send ( &str, handle_sta, cs ) );
+ }
+
+ /*
+ No attributes should be found in
+ Accounting-Response packets except Proxy-State and possibly Vendor-
+ Specific.
+ */
+
+ /* Now loop in the list of AVPs and convert those that we know how */
+ CHECK_FCT( fd_msg_browse(*diam_ans, MSG_BRW_FIRST_CHILD, &next, NULL) );
+
+ while (next) {
+ int handled = 1;
+ avp = next;
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &next, NULL) );
+
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+
+ if (!(ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+ /* Based on RFC4005, sec 3.10 */
+ case DIAM_ATTR_SESSION_ID:
+ case DIAM_ATTR_ORIGIN_HOST:
+ case DIAM_ATTR_ORIGIN_REALM:
+ case DIAM_ATTR_ACCOUNTING_RECORD_TYPE:
+ case DIAM_ATTR_ACCOUNTING_RECORD_NUMBER:
+ case DIAM_ATTR_ACCT_APPLICATION_ID:
+ case DIAM_ATTR_VENDOR_SPECIFIC_APPLICATION_ID:
+ case DIAM_ATTR_USER_NAME:
+ case DIAM_ATTR_ACCOUNTING_SUB_SESSION_ID:
+ case DIAM_ATTR_ACCT_SESSION_ID:
+ case DIAM_ATTR_ACCT_MULTI_SESSION_ID:
+ case DIAM_ATTR_EVENT_TIMESTAMP:
+ case DIAM_ATTR_ORIGIN_AAA_PROTOCOL:
+ case DIAM_ATTR_ORIGIN_STATE_ID:
+ case DIAM_ATTR_NAS_IDENTIFIER:
+ case DIAM_ATTR_NAS_IP_ADDRESS:
+ case DIAM_ATTR_NAS_IPV6_ADDRESS:
+ case DIAM_ATTR_NAS_PORT:
+ case DIAM_ATTR_NAS_PORT_ID:
+ case DIAM_ATTR_NAS_PORT_TYPE:
+ case DIAM_ATTR_SERVICE_TYPE:
+ case DIAM_ATTR_TERMINATION_CAUSE:
+ case DIAM_ATTR_ACCOUNTING_REALTIME_REQUIRED:
+ case DIAM_ATTR_ACCT_INTERIM_INTERVAL:
+ case DIAM_ATTR_CLASS:
+ /* We just remove these AVP, they are not expected in RADIUS client */
+ break;
+
+ default:
+ /* Leave the AVP in the message for further treatment */
+ handled = 0;
+ }
+ } else {
+ /* Vendor-specific AVPs */
+ switch (ahdr->avp_vendor) {
+
+ default: /* unknown vendor */
+ handled = 0;
+ }
+ }
+
+ if (handled) {
+ CHECK_FCT( fd_msg_free( avp ) );
+ }
+ }
+
+ /*
+ The Authenticator field in an Accounting-Response packet is called
+ the Response Authenticator, and contains a one-way MD5 hash
+ calculated over a stream of octets consisting of the Accounting-
+ Response Code, Identifier, Length, the Request Authenticator field
+ from the Accounting-Request packet being replied to, and the
+ response attributes if any, followed by the shared secret. The
+ resulting 16 octet MD5 hash value is stored in the Authenticator
+ field of the Accounting-Response packet.
+
+ -- done in radius_msg_finish_srv
+ */
+
+ return 0;
+}
+
+/* The exported symbol */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "acct",
+ .rgwp_conf_parse = acct_conf_parse,
+ .rgwp_conf_free = acct_conf_free,
+ .rgwp_rad_req = acct_rad_req,
+ .rgwp_diam_ans = acct_diam_ans
+};
diff --git a/extensions/app_radgw/rgwx_auth.c b/extensions/app_radgw/rgwx_auth.c
new file mode 100644
index 0000000..f78b97e
--- /dev/null
+++ b/extensions/app_radgw/rgwx_auth.c
@@ -0,0 +1,1961 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* RADIUS Access-Request messages translation plugin */
+
+#include "rgw_common.h"
+
+/* Attributes missing from radius.h */
+#define RADIUS_ATTR_CHAP_PASSWORD 3
+#define RADIUS_ATTR_ARAP_PASSWORD 70
+
+/* Other constants we use */
+#define AI_NASREQ 1 /* Diameter NASREQ */
+#define AI_EAP 5 /* Diameter EAP application */
+#define CC_AA 265 /* AAR */
+#define CC_DIAMETER_EAP 268 /* DER */
+#define ACV_ART_AUTHORIZE_AUTHENTICATE 3 /* AUTHORIZE_AUTHENTICATE */
+#define ACV_OAP_RADIUS 1 /* RADIUS */
+#define ACV_ASS_STATE_MAINTAINED 0 /* STATE_MAINTAINED */
+#define ACV_ASS_NO_STATE_MAINTAINED 1 /* NO_STATE_MAINTAINED */
+
+/* The state we keep for this plugin */
+struct rgwp_config {
+ struct {
+ struct dict_object * ARAP_Password; /* ARAP-Password */
+ struct dict_object * ARAP_Security; /* ARAP-Security */
+ struct dict_object * ARAP_Security_Data; /* ARAP-Security-Data */
+ struct dict_object * Auth_Application_Id; /* Auth-Application-Id */
+ struct dict_object * Auth_Request_Type; /* Auth-Request-Type */
+ struct dict_object * Authorization_Lifetime; /* Authorization-Lifetime */
+ struct dict_object * Callback_Number; /* Callback-Number */
+ struct dict_object * Called_Station_Id; /* Called-Station-Id */
+ struct dict_object * Calling_Station_Id; /* Calling-Station-Id */
+ struct dict_object * CHAP_Algorithm; /* CHAP-Algorithm */
+ struct dict_object * CHAP_Auth; /* CHAP-Auth */
+ struct dict_object * CHAP_Challenge; /* CHAP-Challenge */
+ struct dict_object * CHAP_Ident; /* CHAP-Ident */
+ struct dict_object * CHAP_Response; /* CHAP-Response */
+ struct dict_object * Destination_Host; /* Destination-Host */
+ struct dict_object * Destination_Realm; /* Destination-Realm */
+ struct dict_object * Connect_Info; /* Connect-Info */
+ struct dict_object * EAP_Payload; /* EAP-Payload */
+ struct dict_object * Error_Message; /* Error-Message */
+ struct dict_object * Error_Reporting_Host; /* Error-Reporting-Host */
+ struct dict_object * Failed_AVP; /* Failed-AVP */
+ struct dict_object * Framed_Compression; /* Framed-Compression */
+ struct dict_object * Framed_IP_Address; /* Framed-IP-Address */
+ struct dict_object * Framed_IP_Netmask; /* Framed-IP-Netmask */
+ struct dict_object * Framed_Interface_Id; /* Framed-Interface-Id */
+ struct dict_object * Framed_IPv6_Prefix; /* Framed-IPv6-Prefix */
+ struct dict_object * Framed_MTU; /* Framed-MTU */
+ struct dict_object * Framed_Protocol; /* Framed-Protocol */
+ struct dict_object * Login_IP_Host; /* Login-IP-Host */
+ struct dict_object * Login_IPv6_Host; /* Login-IPv6-Host */
+ struct dict_object * Login_LAT_Group; /* Login-LAT-Group */
+ struct dict_object * Login_LAT_Node; /* Login-LAT-Node */
+ struct dict_object * Login_LAT_Port; /* Login-LAT-Port */
+ struct dict_object * Login_LAT_Service; /* Login-LAT-Service */
+ struct dict_object * NAS_Identifier; /* NAS-Identifier */
+ struct dict_object * NAS_IP_Address; /* NAS-IP-Address */
+ struct dict_object * NAS_IPv6_Address; /* NAS-IPv6-Address */
+ struct dict_object * NAS_Port; /* NAS-Port */
+ struct dict_object * NAS_Port_Id; /* NAS-Port-Id */
+ struct dict_object * NAS_Port_Type; /* NAS-Port-Type */
+ struct dict_object * Origin_AAA_Protocol; /* Origin-AAA-Protocol */
+ struct dict_object * Origin_Host; /* Origin-Host */
+ struct dict_object * Origin_Realm; /* Origin-Realm */
+ struct dict_object * Originating_Line_Info; /* Originating-Line-Info */
+ struct dict_object * Port_Limit; /* Port-Limit */
+ struct dict_object * Re_Auth_Request_Type; /* Re-Auth-Request-Type */
+ struct dict_object * Result_Code; /* Result-Code */
+ struct dict_object * Service_Type; /* Service-Type */
+ struct dict_object * Session_Id; /* Session-Id */
+ struct dict_object * Session_Timeout; /* Session-Timeout */
+ struct dict_object * State; /* State */
+ struct dict_object * Tunneling; /* Tunneling */
+ struct dict_object * Tunnel_Type; /* Tunnel-Type */
+ struct dict_object * Tunnel_Medium_Type; /* Tunnel-Medium-Type */
+ struct dict_object * Tunnel_Client_Endpoint; /* Tunnel-Client-Endpoint */
+ struct dict_object * Tunnel_Server_Endpoint; /* Tunnel-Server-Endpoint */
+ struct dict_object * Tunnel_Private_Group_Id; /* Tunnel-Private-Group-Id */
+ struct dict_object * Tunnel_Preference; /* Tunnel-Preference */
+ struct dict_object * Tunnel_Client_Auth_Id; /* Tunnel-Client-Auth-Id */
+ struct dict_object * Tunnel_Server_Auth_Id; /* Tunnel-Server-Auth-Id */
+ struct dict_object * User_Name; /* User-Name */
+ struct dict_object * User_Password; /* User-Password */
+
+ } dict; /* cache of the dictionary objects we use */
+ struct session_handler * sess_hdl; /* We store RADIUS request authenticator information in the session */
+ char * confstr;
+
+ int ignore_nai;
+};
+
+struct sess_state {
+ uint8_t req_auth[16];
+};
+
+
+/* Initialize the plugin */
+static int auth_conf_parse(char * confstr, struct rgwp_config ** state)
+{
+ struct rgwp_config * new;
+ struct dict_object * app;
+
+ TRACE_ENTRY("%p %p", confstr, state);
+ CHECK_PARAMS( state );
+
+ CHECK_MALLOC( new = malloc(sizeof(struct rgwp_config)) );
+ memset(new, 0, sizeof(struct rgwp_config));
+
+ CHECK_FCT( fd_sess_handler_create( &new->sess_hdl, (void *)free, NULL, NULL ) );
+ new->confstr = confstr;
+
+ if (confstr && strstr(confstr, "nonai"))
+ new->ignore_nai = 1;
+
+ /* Resolve all dictionary objects we use */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Password", &new->dict.ARAP_Password, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Security", &new->dict.ARAP_Security, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "ARAP-Security-Data", &new->dict.ARAP_Security_Data, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Application-Id", &new->dict.Auth_Application_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Request-Type", &new->dict.Auth_Request_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Authorization-Lifetime", &new->dict.Authorization_Lifetime, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Callback-Number", &new->dict.Callback_Number, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Called-Station-Id", &new->dict.Called_Station_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Calling-Station-Id", &new->dict.Calling_Station_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Algorithm", &new->dict.CHAP_Algorithm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Auth", &new->dict.CHAP_Auth, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Challenge", &new->dict.CHAP_Challenge, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Ident", &new->dict.CHAP_Ident, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Response", &new->dict.CHAP_Response, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Connect-Info", &new->dict.Connect_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &new->dict.Destination_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &new->dict.Destination_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "EAP-Payload", &new->dict.EAP_Payload, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Message", &new->dict.Error_Message, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Reporting-Host", &new->dict.Error_Reporting_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Failed-AVP", &new->dict.Failed_AVP, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Compression", &new->dict.Framed_Compression, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IP-Address", &new->dict.Framed_IP_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IP-Netmask", &new->dict.Framed_IP_Netmask, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Interface-Id", &new->dict.Framed_Interface_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-IPv6-Prefix", &new->dict.Framed_IPv6_Prefix, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-MTU", &new->dict.Framed_MTU, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Framed-Protocol", &new->dict.Framed_Protocol, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-IP-Host", &new->dict.Login_IP_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-IPv6-Host", &new->dict.Login_IPv6_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Group", &new->dict.Login_LAT_Group, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Node", &new->dict.Login_LAT_Node, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Port", &new->dict.Login_LAT_Port, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Login-LAT-Service", &new->dict.Login_LAT_Service, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Identifier", &new->dict.NAS_Identifier, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-IP-Address", &new->dict.NAS_IP_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-IPv6-Address", &new->dict.NAS_IPv6_Address, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port", &new->dict.NAS_Port, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port-Id", &new->dict.NAS_Port_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "NAS-Port-Type", &new->dict.NAS_Port_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-AAA-Protocol", &new->dict.Origin_AAA_Protocol, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &new->dict.Origin_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &new->dict.Origin_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Originating-Line-Info", &new->dict.Originating_Line_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Port-Limit", &new->dict.Port_Limit, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Re-Auth-Request-Type", &new->dict.Re_Auth_Request_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Result-Code", &new->dict.Result_Code, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Service-Type", &new->dict.Service_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &new->dict.Session_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Timeout", &new->dict.Session_Timeout, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "State", &new->dict.State, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunneling", &new->dict.Tunneling, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Type", &new->dict.Tunnel_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Medium-Type", &new->dict.Tunnel_Medium_Type, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Client-Endpoint", &new->dict.Tunnel_Client_Endpoint, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Server-Endpoint", &new->dict.Tunnel_Server_Endpoint, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Private-Group-Id", &new->dict.Tunnel_Private_Group_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Preference", &new->dict.Tunnel_Preference, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Client-Auth-Id", &new->dict.Tunnel_Client_Auth_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Tunnel-Server-Auth-Id", &new->dict.Tunnel_Server_Auth_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &new->dict.User_Name, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Password", &new->dict.User_Password, ENOENT) );
+
+ /* This plugin provides the following Diameter authentication applications support: */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Network Access Server Application", &app, ENOENT) );
+ CHECK_FCT( fd_disp_app_support ( app, NULL, 1, 0 ) );
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Extensible Authentication Protocol (EAP) Application", &app, ENOENT) );
+ CHECK_FCT( fd_disp_app_support ( app, NULL, 1, 0 ) );
+
+ *state = new;
+ return 0;
+}
+
+/* deinitialize */
+static void auth_conf_free(struct rgwp_config * state)
+{
+ TRACE_ENTRY("%p", state);
+ CHECK_PARAMS_DO( state, return );
+ CHECK_FCT_DO( fd_sess_handler_destroy( &state->sess_hdl, NULL ), );
+ free(state);
+ return;
+}
+
+/* Handle an incoming RADIUS request */
+static int auth_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ int idx;
+ int got_id = 0;
+ int got_mac = 0;
+ int got_passwd = 0;
+ int got_eap = 0;
+ int got_empty_eap = 0;
+ const char * prefix = "Diameter/";
+ size_t pref_len;
+ os0_t dh = NULL;
+ size_t dh_len = 0;
+ os0_t dr = NULL;
+ size_t dr_len = 0;
+ os0_t si = NULL;
+ size_t si_len = 0;
+ os0_t un = NULL;
+ size_t un_len = 0;
+ size_t nattr_used = 0;
+ struct avp ** avp_tun = NULL, *avp = NULL;
+ union avp_value value;
+ struct session * sess;
+
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+ CHECK_PARAMS(cs && rad_req && (rad_req->hdr->code == RADIUS_CODE_ACCESS_REQUEST) && rad_ans && diam_fw && *diam_fw);
+
+ pref_len = strlen(prefix);
+
+ /*
+ Guidelines:
+ http://tools.ietf.org/html/rfc4005#section-9.1
+ http://tools.ietf.org/html/rfc4072#section-6.1
+
+ When a Translation Agent receives a RADIUS message, the following
+ steps should be taken:
+
+ - If a Message-Authenticator attribute is present, the value MUST
+ be checked but not included in the Diameter message. If it is
+ incorrect, the RADIUS message should be silently discarded.
+ The gateway system SHOULD generate and include a Message-
+ Authenticator in returned RADIUS responses.
+ -> done in rgw_msg_auth_check
+
+ - The transport address of the sender MUST be checked against the
+ NAS identifying attributes. See the description of NAS-
+ Identifier and NAS-IP-Address below.
+ -> done in rgw_clients_check_origin
+
+ - The Translation Agent must maintain transaction state
+ information relevant to the RADIUS request, such as the
+ Identifier field in the RADIUS header, any existing RADIUS
+ Proxy-State attribute, and the source IP address and port
+ number of the UDP packet. These may be maintained locally in a
+ state table or saved in a Proxy-Info AVP group. A Diameter
+ Session-Id AVP value must be created using a session state
+ mapping mechanism.
+ -> Identifier, source and port are saved along with the request,
+ and associated with the session state.
+ -> sub_echo_drop should handle the Proxy-State attribute (conf issue)
+
+ - The Diameter Origin-Host and Origin-Realm AVPs MUST be created
+ and added by using the information from an FQDN corresponding
+ to the NAS-IP-Address attribute (preferred if available),
+ and/or to the NAS-Identifier attribute. (Note that the RADIUS
+ NAS-Identifier is not required to be an FQDN.)
+ -> done in rgw_clients_create_origin.
+
+ - The response MUST have an Origin-AAA-Protocol AVP added,
+ indicating the protocol of origin of the message.
+ -> what "response" ??? Added to the AAR / DER in this function.
+
+ - The Proxy-Info group SHOULD be added, with the local server's
+ identity specified in the Proxy-Host AVP. This should ensure
+ that the response is returned to this system.
+ -> We don't need this, answer is always routed here anyway.
+
+ For EAP:
+
+ o RADIUS EAP-Message attribute(s) are translated to a Diameter
+ EAP-Payload AVP. If multiple RADIUS EAP-Message attributes are
+ present, they are concatenated and translated to a single Diameter
+ EAP-Payload AVP.
+ -> concatenation done by radius_msg_get_eap
+
+ -> the remaining is specific conversion rules
+ */
+
+ /* Check basic information is there, and also retrieve some attribute information */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+ uint8_t * attr_val = (uint8_t *)(attr + 1);
+ size_t attr_len = attr->length - sizeof(struct radius_attr_hdr);
+
+ switch (attr->type) {
+ case RADIUS_ATTR_NAS_IP_ADDRESS:
+ case RADIUS_ATTR_NAS_IDENTIFIER:
+ case RADIUS_ATTR_NAS_IPV6_ADDRESS:
+ got_id = 1;
+ break;
+ case RADIUS_ATTR_MESSAGE_AUTHENTICATOR:
+ got_mac = 1;
+ break;
+ case RADIUS_ATTR_EAP_MESSAGE:
+ got_eap = 1;
+ if (attr->length == 2)
+ got_empty_eap = 1;
+ break;
+ case RADIUS_ATTR_USER_PASSWORD:
+ case RADIUS_ATTR_CHAP_PASSWORD:
+ case RADIUS_ATTR_ARAP_PASSWORD:
+ got_passwd += 1;
+ break;
+
+ /* Is there a State attribute with prefix "Diameter/" in the message? (in that case: Diameter/Destination-Host/Destination-Realm/Session-Id) */
+ /* NOTE: RFC4005 says "Origin-Host" here, but it's not coherent with the rules for answers. Destination-Host makes more sense */
+ case RADIUS_ATTR_STATE:
+ if ((attr_len > pref_len + 5 /* for the '/'s and non empty strings */ )
+ && ! memcmp(attr_val, prefix, pref_len)) {
+ int i, start;
+
+ TRACE_DEBUG(ANNOYING, "Found a State attribute with '%s' prefix (attr #%d).", prefix, idx);
+
+ /* Now parse the value and check its content is valid. Unfortunately we cannot use strchr here since strings are not \0-terminated */
+
+ i = start = pref_len;
+ dh = attr_val + i;
+ for (; (i < attr_len - 2) && (attr_val[i] != '/'); i++) /* loop */;
+ if ( i >= attr_len - 2 ) continue; /* the attribute format is not good */
+ dh_len = i - start;
+
+ start = ++i;
+ dr = attr_val + i;
+ for (; (i < attr_len - 1) && (attr_val[i] != '/'); i++) /* loop */;
+ if ( i >= attr_len - 1 ) continue; /* the attribute format is not good */
+ dr_len = i - start;
+
+ i++;
+ si = attr_val + i;
+ si_len = attr_len - i;
+
+ TRACE_DEBUG(ANNOYING, "Attribute parsed successfully: DH:'%.*s' DR:'%.*s' SI:'%.*s'.", (int)dh_len, dh, (int)dr_len, dr, (int)si_len, si);
+ /* Remove from the message */
+ for (i = idx + 1; i < rad_req->attr_used; i++)
+ rad_req->attr_pos[i - 1] = rad_req->attr_pos[i];
+ rad_req->attr_used -= 1;
+ idx--;
+ }
+ break;
+
+ case RADIUS_ATTR_USER_NAME:
+ TRACE_DEBUG(ANNOYING, "Found a User-Name attribute: '%.*s'", (int)attr_len, attr_len ? (char *)attr_val : "");
+ un = attr_val;
+ un_len = attr_len;
+ break;
+
+ }
+ }
+ if (!got_id) {
+ TRACE_DEBUG(INFO, "RADIUS Access-Request did not contain a NAS IP or Identifier attribute, reject.");
+ return EINVAL;
+ }
+ /* [Note 1] An Access-Request that contains either a User-Password or
+ CHAP-Password or ARAP-Password or one or more EAP-Message attributes
+ MUST NOT contain more than one type of those four attributes. If it
+ does not contain any of those four attributes, it SHOULD contain a
+ Message-Authenticator. If any packet type contains an EAP-Message
+ attribute it MUST also contain a Message-Authenticator. A RADIUS
+ server receiving an Access-Request not containing any of those four
+ attributes and also not containing a Message-Authenticator attribute
+ SHOULD silently discard it. */
+ if (((got_eap + got_passwd) > 1) || (got_eap && !got_mac) || (!got_eap && !got_passwd && !got_mac)) {
+ TRACE_DEBUG(INFO, "RADIUS Access-Request not conform to RFC3579 sec 3.3 note 1, discard.");
+ return EINVAL;
+ }
+
+
+
+ /*
+ - If the RADIUS request contained a State attribute and the
+ prefix of the data is "Diameter/", the data following the
+ prefix contains the Diameter Origin-Host/Origin-Realm/Session-
+ Id. If no such attributes are present and the RADIUS command
+ is an Access-Request, a new Session-Id is created. The
+ Session-Id is included in the Session-Id AVP.
+ */
+
+ /* Add the Destination-Realm AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Realm, 0, &avp ) );
+ if (dr) {
+ value.os.data = (unsigned char *)dr;
+ value.os.len = dr_len;
+ } else {
+ int i = 0;
+ if (un && ! cs->ignore_nai) {
+ /* Is there an '@' in the user name? We don't care for decorated NAI here */
+ for (i = un_len - 2; i > 0; i--) {
+ if (un[i] == '@') {
+ i++;
+ break;
+ }
+ }
+ }
+ if (i <= 0) {
+ /* Not found in the User-Name => we use the local domain of this gateway */
+ value.os.data = (uint8_t *)fd_g_config->cnf_diamrlm;
+ value.os.len = fd_g_config->cnf_diamrlm_len;
+ } else {
+ value.os.data = un + i;
+ value.os.len = un_len - i;
+ }
+ }
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+
+ /* Add the Destination-Host if found */
+ if (dh) {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Host, 0, &avp ) );
+ value.os.data = (unsigned char *)dh;
+ value.os.len = dh_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+ }
+
+ /* Create the session if it is not already done */
+ {
+ os0_t sess_str = NULL;
+ size_t sess_strlen;
+
+ if (si_len) {
+ /* We already have the Session-Id, just use it */
+ CHECK_FCT( fd_sess_fromsid_msg ( si, si_len, &sess, NULL) );
+ } else {
+ /* Create a new Session-Id string */
+
+ DiamId_t fqdn;
+ size_t fqdnlen;
+ DiamId_t realm;
+ size_t realmlen;
+
+ /* Get information on the RADIUS client */
+ CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &fqdnlen, &realm, &realmlen) );
+
+ /* If we have a user name, create the new session with it */
+ if (un) {
+ int len;
+ /* If not found, create a new Session-Id. Our format is: {fqdn;hi32;lo32;username;diamid} */
+ CHECK_MALLOC( sess_str = malloc(un_len + 1 /* ';' */ + fd_g_config->cnf_diamid_len + 1 /* '\0' */) );
+ len = sprintf((char *)sess_str, "%.*s;%s", (int)un_len, un, fd_g_config->cnf_diamid);
+ CHECK_FCT( fd_sess_new(&sess, fqdn, fqdnlen, sess_str, len) );
+ free(sess_str);
+ } else {
+ /* We don't have enough information to create the Session-Id, the RADIUS message is probably invalid */
+ TRACE_DEBUG(INFO, "RADIUS Access-Request does not contain a User-Name attribute, rejecting.");
+ return EINVAL;
+ }
+ }
+
+ /* Now, add the Session-Id AVP at beginning of Diameter message */
+ CHECK_FCT( fd_sess_getsid(sess, &sess_str, &sess_strlen) );
+ TRACE_DEBUG(FULL, "[auth.rgwx] Translating new message for session '%s'...", sess_str);
+
+ /* Now add this session in the message */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) );
+ value.os.data = sess_str;
+ value.os.len = sess_strlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+ CHECK_FCT( fd_msg_sess_set(*diam_fw, sess) );
+ }
+
+
+ /* Add the appropriate command code & Auth-Application-Id */
+ {
+ struct msg_hdr * header = NULL;
+ CHECK_FCT( fd_msg_hdr ( *diam_fw, &header ) );
+ header->msg_flags = CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE;
+ if (got_eap) {
+ header->msg_code = CC_DIAMETER_EAP;
+ header->msg_appl = AI_EAP;
+ } else {
+ header->msg_code = CC_AA;
+ header->msg_appl = AI_NASREQ;
+ }
+
+ /* Add the Auth-Application-Id */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Auth_Application_Id, 0, &avp ) );
+ value.i32 = header->msg_appl;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ }
+
+ /* The type of request is identified through the Auth-Request-Type AVP
+ [BASE]. The recommended value for most RADIUS interoperabily
+ situations is AUTHORIZE_AUTHENTICATE. */
+
+ /* Add Auth-Request-Type AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Auth_Request_Type, 0, &avp ) );
+ value.i32 = ACV_ART_AUTHORIZE_AUTHENTICATE;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+ /* Add Origin-AAA-Protocol AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Origin_AAA_Protocol, 0, &avp ) );
+ value.i32 = ACV_OAP_RADIUS;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+ /* Convert the EAP payload (concat RADIUS attributes) */
+ if (got_eap) {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.EAP_Payload, 0, &avp ) );
+
+ /* o An empty RADIUS EAP-Message attribute (with length 2) signifies
+ EAP-Start, and it is translated to an empty EAP-Payload AVP. */
+ if (got_empty_eap) {
+ value.os.len = 0;
+ value.os.data = (uint8_t *)"";
+ } else {
+ CHECK_MALLOC( value.os.data = radius_msg_get_eap(rad_req, &value.os.len) );
+ }
+
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+ /* Tunnel AVPs need some preparation */
+ /* Convert the attributes one by one */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+
+ switch (attr->type) {
+
+ /* This macro converts a RADIUS attribute to a Diameter AVP of type OctetString */
+ #define CONV2DIAM_STR( _dictobj_ ) \
+ CHECK_PARAMS( attr->length >= sizeof(struct radius_attr_hdr) ); \
+ /* Create the AVP with the specified dictionary model */ \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - sizeof(struct radius_attr_hdr); \
+ value.os.data = (os0_t)(attr + 1); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ /* Add the AVP in the Diameter message. */ \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+ /* Same thing, for scalar AVPs of 32 bits */
+ #define CONV2DIAM_32B( _dictobj_ ) \
+ CHECK_PARAMS( attr->length == sizeof(struct radius_attr_hdr)+sizeof(uint32_t) );\
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u32 = (v[0] << 24) \
+ | (v[1] << 16) \
+ | (v[2] << 8) \
+ | v[3] ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+ /* And the 64b version */
+ #define CONV2DIAM_64B( _dictobj_ ) \
+ CHECK_PARAMS( attr->length == sizeof(struct radius_attr_hdr)+sizeof(uint64_t) );\
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u64 = ((uint64_t)(v[0]) << 56) \
+ | ((uint64_t)(v[1]) << 48) \
+ | ((uint64_t)(v[2]) << 40) \
+ | ((uint64_t)(v[3]) << 32) \
+ | ((uint64_t)(v[4]) << 24) \
+ | ((uint64_t)(v[5]) << 16) \
+ | ((uint64_t)(v[6]) << 8) \
+ | (uint64_t)(v[7]) ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+ /* RFC 2865 */
+ /*
+ - The Destination-Realm AVP is created from the information found
+ in the RADIUS User-Name attribute.
+ -> done in rgw_clients_create_origin
+ */
+ case RADIUS_ATTR_USER_NAME:
+ CONV2DIAM_STR( User_Name );
+ break;
+
+ /*
+ - If the RADIUS User-Password attribute is present, the password
+ must be unencrypted by using the link's RADIUS shared secret.
+ The unencrypted value must be forwarded in a User-Password AVP
+ using Diameter security.
+ */
+ case RADIUS_ATTR_USER_PASSWORD:
+ if ((attr->length - 2) % 16) {
+ TRACE_DEBUG(INFO, "Invalid length of User-Password attribute: %hhd", attr->length);
+ return EINVAL;
+ }
+ {
+ /* Decipher following this logic (refers to rfc2865#section-5.2 )
+ b1 = MD5(S + RA) p1 = c(1) xor b1
+ b2 = MD5(S + c(1)) p2 = c(2) xor b2
+ ...
+ */
+
+ uint8_t *ciph = (uint8_t *)(attr+1); /* c(i) */
+ size_t ciph_len = attr->length - 2;
+ uint8_t deciph[128]; /* pi */
+ size_t deciph_len = 0;
+ uint8_t * secret; /* S */
+ size_t secret_len;
+ uint8_t hash[16]; /* b(i) */
+ const uint8_t *addr[2];
+ size_t len[2];
+
+ /* Retrieve the shared secret */
+ CHECK_FCT(rgw_clients_getkey(cli, &secret, &secret_len));
+
+ /* Initial b1 = MD5(S + RA) */
+ addr[0] = secret;
+ len[0] = secret_len;
+ addr[1] = rad_req->hdr->authenticator;
+ len[1] = 16;
+ md5_vector(2, addr, len, hash);
+
+ /* loop */
+ while (deciph_len < ciph_len) {
+ int i;
+ /* pi = c(i) xor bi */
+ for (i = 0; i < 16; i++)
+ deciph[deciph_len + i] = ciph[deciph_len + i] ^ hash[i];
+ /* do we have to remove the padding '\0's ? */
+
+ /* b(i+1) = MD5(S + c(i) */
+ addr[1] = ciph + deciph_len;
+ md5_vector(2, addr, len, hash);
+
+ deciph_len += 16;
+ }
+
+ /* Now save this value in the AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.User_Password, 0, &avp ) );
+ value.os.data = &deciph[0];
+ value.os.len = deciph_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ break;
+
+
+ /*
+ - If the RADIUS CHAP-Password attribute is present, the Ident and
+ Data portion of the attribute are used to create the CHAP-Auth
+ grouped AVP.
+ */
+ case RADIUS_ATTR_CHAP_PASSWORD:
+ CHECK_PARAMS( attr->length == 19 /* RFC 2865 */);
+ {
+ uint8_t * c = (uint8_t *)(attr + 1);
+ struct avp * chap_auth;
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.CHAP_Auth, 0, &chap_auth ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, chap_auth) );
+
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.CHAP_Algorithm, 0, &avp ) );
+ value.u32 = 5; /* The only value defined currently... */
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( chap_auth, MSG_BRW_LAST_CHILD, avp) );
+
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.CHAP_Ident, 0, &avp ) );
+ value.os.data = c;
+ value.os.len = 1;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( chap_auth, MSG_BRW_LAST_CHILD, avp) );
+
+ c++;
+
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.CHAP_Response, 0, &avp ) );
+ value.os.data = c;
+ value.os.len = attr->length - 3;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( chap_auth, MSG_BRW_LAST_CHILD, avp) );
+ }
+ break;
+
+ case RADIUS_ATTR_NAS_IP_ADDRESS:
+ CONV2DIAM_STR( NAS_IP_Address );
+ break;
+
+ case RADIUS_ATTR_NAS_PORT:
+ CONV2DIAM_32B( NAS_Port );
+ break;
+
+ case RADIUS_ATTR_SERVICE_TYPE:
+ CONV2DIAM_32B( Service_Type );
+ break;
+
+ case RADIUS_ATTR_FRAMED_PROTOCOL:
+ CONV2DIAM_32B( Framed_Protocol );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IP_ADDRESS:
+ CONV2DIAM_STR( Framed_IP_Address );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IP_NETMASK:
+ CONV2DIAM_STR( Framed_IP_Netmask );
+ break;
+
+ /* Framed-Routing never present in an Access-Request */
+ /* Filter-Id never present in an Access-Request */
+
+ case RADIUS_ATTR_FRAMED_MTU:
+ CONV2DIAM_32B( Framed_MTU );
+ break;
+
+ case RADIUS_ATTR_FRAMED_COMPRESSION:
+ CONV2DIAM_32B( Framed_Compression );
+ break;
+
+ case RADIUS_ATTR_LOGIN_IP_HOST:
+ CONV2DIAM_STR( Login_IP_Host );
+ break;
+
+ /* Login-Service never present in an Access-Request */
+ /* Login-TCP-Port never present in an Access-Request */
+ /* Reply-Message never present in an Access-Request */
+
+ case RADIUS_ATTR_CALLBACK_NUMBER:
+ CONV2DIAM_STR( Callback_Number );
+ break;
+
+ /* Callback-Id never present in an Access-Request */
+ /* Framed-Route never present in an Access-Request */
+ /* Framed-IPX-Network never present in an Access-Request */
+
+ case RADIUS_ATTR_STATE:
+ CONV2DIAM_STR( State );
+ break;
+
+ /* Class never present in an Access-Request */
+
+ case RADIUS_ATTR_VENDOR_SPECIFIC:
+ /* RFC 4005, Section 9.6 :
+ Systems that don't have vendor format knowledge MAY discard such
+ attributes without knowing a suitable translation.
+
+ [conversion rule in 9.6.2]
+ */
+ if (attr->length >= 6) {
+ uint32_t vendor_id;
+ uint8_t * c = (uint8_t *)(attr + 1);
+
+ vendor_id = c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3];
+ c += 4;
+
+ switch (vendor_id) {
+
+ /* For the vendors we KNOW they follow the VSA recommended format, we convert following the rules of RFC4005 (9.6.2) */
+ case RADIUS_VENDOR_ID_MICROSOFT : /* RFC 2548 */
+ /* other vendors ? */
+ {
+ size_t left;
+ struct radius_attr_vendor *vtlv;
+
+ left = attr->length - 6;
+ vtlv = (struct radius_attr_vendor *)c;
+
+ while ((left >= 2) && (vtlv->vendor_length <= left)) {
+ /* Search our dictionary for corresponding Vendor's AVP */
+ struct dict_avp_request req;
+ struct dict_object * avp_model = NULL;
+ memset(&req, 0, sizeof(struct dict_avp_request));
+ req.avp_vendor = vendor_id;
+ req.avp_code = vtlv->vendor_type;
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_CODE_AND_VENDOR, &req, &avp_model, 0) );
+ if (!avp_model) {
+ TRACE_DEBUG(FULL, "Unknown attribute (vendor 0x%x, code 0x%x) ignored.", req.avp_vendor, req.avp_code);
+ } else {
+ CHECK_FCT( fd_msg_avp_new ( avp_model, 0, &avp ) );
+ value.os.len = vtlv->vendor_length - 2;
+ value.os.data = (unsigned char *)(vtlv + 1);
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ c += vtlv->vendor_length;
+ left -= vtlv->vendor_length;
+ vtlv = (struct radius_attr_vendor *)c;
+ }
+ }
+ break;
+
+ /* Other vendors we KNOw how to convert the attributes would be added here... */
+ /* case RADIUS_VENDOR_ID_CISCO :
+ break; */
+ /* case RADIUS_VENDOR_ID_IETF : (extended RADIUS attributes)
+ break; */
+
+ /* When we don't know, just discard the attribute... VSA are optional with regards to RADIUS anyway */
+ default:
+ /* do nothing */
+ TRACE_DEBUG(FULL, "VSA attribute from vendor %d discarded", vendor_id);
+
+ }
+ }
+ break;
+
+ /* Session-Timeout never present in an Access-Request */
+ /* Idle-Timeout never present in an Access-Request */
+ /* Termination-Action never present in an Access-Request */
+
+ case RADIUS_ATTR_CALLED_STATION_ID:
+ CONV2DIAM_STR( Called_Station_Id );
+ break;
+
+ case RADIUS_ATTR_CALLING_STATION_ID:
+ CONV2DIAM_STR( Calling_Station_Id );
+ break;
+
+ case RADIUS_ATTR_NAS_IDENTIFIER:
+ CONV2DIAM_STR( NAS_Identifier );
+ break;
+
+ /* Proxy-State is handled by echo_drop.rgwx plugin, we ignore it here */
+
+ case RADIUS_ATTR_LOGIN_LAT_SERVICE:
+ CONV2DIAM_STR( Login_LAT_Service );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_NODE:
+ CONV2DIAM_STR( Login_LAT_Node );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_GROUP:
+ CONV2DIAM_STR( Login_LAT_Group );
+ break;
+
+ /* Framed-AppleTalk-Link never present in an Access-Request */
+ /* Framed-AppleTalk-Network never present in an Access-Request */
+ /* Framed-AppleTalk-Zone never present in an Access-Request */
+
+ case RADIUS_ATTR_CHAP_CHALLENGE:
+ CONV2DIAM_STR( CHAP_Challenge );
+ break;
+
+ case RADIUS_ATTR_NAS_PORT_TYPE:
+ CONV2DIAM_32B( NAS_Port_Type );
+ break;
+
+ case RADIUS_ATTR_PORT_LIMIT:
+ CONV2DIAM_32B( Port_Limit );
+ break;
+
+ case RADIUS_ATTR_LOGIN_LAT_PORT:
+ CONV2DIAM_STR( Login_LAT_Port );
+ break;
+
+
+ /* RFC 3162 */
+ case RADIUS_ATTR_NAS_IPV6_ADDRESS:
+ CONV2DIAM_STR( NAS_IPv6_Address );
+ break;
+
+ case RADIUS_ATTR_FRAMED_INTERFACE_ID:
+ CONV2DIAM_64B( Framed_Interface_Id );
+ break;
+
+ case RADIUS_ATTR_FRAMED_IPV6_PREFIX:
+ CONV2DIAM_STR( Framed_IPv6_Prefix );
+ break;
+
+ case RADIUS_ATTR_LOGIN_IPV6_HOST:
+ CONV2DIAM_STR( Login_IPv6_Host );
+ break;
+
+ /* Framed-IPv6-Route never present in an Access-Request */
+ /* Framed-IPv6-Pool never present in an Access-Request */
+
+
+ /* RFC 2868 */
+ /* Prepare the top-level Tunneling AVP for each tag values, as needed, and add to the Diameter message.
+ This macro is called when an AVP is added inside the group, so we will not have empty grouped AVPs */
+ #define AVP_TUN_PREPARE() { \
+ if (avp_tun == NULL) { \
+ CHECK_MALLOC( avp_tun = calloc(sizeof(struct avp *), 32 ) ); \
+ } \
+ tag = *(uint8_t *)(attr + 1); \
+ if (tag > 0x1F) tag = 0; \
+ if (avp_tun[tag] == NULL) { \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Tunneling, 0, &avp_tun[tag] ) ); \
+ CHECK_FCT( fd_msg_avp_add (*diam_fw, MSG_BRW_LAST_CHILD, avp_tun[tag]));\
+ } \
+ }
+
+ /* Convert an attribute to an OctetString AVP and add inside the Tunneling AVP corresponding to the tag */
+ #define CONV2DIAM_TUN_STR( _dictobj_ ) { \
+ uint8_t tag; \
+ CHECK_PARAMS( attr->length >= 3); \
+ AVP_TUN_PREPARE(); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - (tag ? 3 : 2); \
+ value.os.data = ((unsigned char *)(attr + 1)) + (tag ? 1 : 0); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( avp_tun[tag], MSG_BRW_LAST_CHILD, avp) ); \
+ }
+
+ /* Convert an attribute to a scalar AVP and add inside the Tunneling AVP corresponding to the tag */
+ #define CONV2DIAM_TUN_24B( _dictobj_ ) { \
+ uint8_t tag; \
+ CHECK_PARAMS( attr->length == 6); \
+ AVP_TUN_PREPARE(); \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u32 = (v[1] << 16) | (v[2] <<8) | v[3] ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( avp_tun[tag], MSG_BRW_LAST_CHILD, avp) ); \
+ }
+
+ /*
+ - If the RADIUS message contains Tunnel information [RADTunnels],
+ the attributes or tagged groups should each be converted to a
+ Diameter Tunneling Grouped AVP set. If the tunnel information
+ contains a Tunnel-Password attribute, the RADIUS encryption
+ must be resolved, and the password forwarded, by using Diameter
+ security methods.
+ -> If the RADIUS message does not use properly the Tag info, result is unpredictable here..
+ */
+ case RADIUS_ATTR_TUNNEL_TYPE:
+ CONV2DIAM_TUN_24B( Tunnel_Type );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_MEDIUM_TYPE:
+ CONV2DIAM_TUN_24B( Tunnel_Medium_Type );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_CLIENT_ENDPOINT:
+ CONV2DIAM_TUN_STR( Tunnel_Client_Endpoint );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_SERVER_ENDPOINT:
+ CONV2DIAM_TUN_STR( Tunnel_Server_Endpoint );
+ break;
+
+ /* Tunnel-Password never present in an Access-Request */
+
+ case RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Private_Group_Id );
+ break;
+
+ /* Tunnel-Assignment-ID never present in an Access-Request */
+
+ case RADIUS_ATTR_TUNNEL_PREFERENCE:
+ CONV2DIAM_TUN_24B( Tunnel_Preference );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_CLIENT_AUTH_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Client_Auth_Id );
+ break;
+
+ case RADIUS_ATTR_TUNNEL_SERVER_AUTH_ID:
+ CONV2DIAM_TUN_STR( Tunnel_Server_Auth_Id );
+ break;
+
+
+ /* RFC 2869 */
+ case RADIUS_ATTR_ARAP_PASSWORD:
+ CONV2DIAM_STR( ARAP_Password );
+ break;
+
+ /* ARAP-Features never present in an Access-Request */
+ /* ARAP-Zone-Access never present in an Access-Request */
+
+ case RADIUS_ATTR_ARAP_SECURITY:
+ CONV2DIAM_32B( ARAP_Security );
+ break;
+
+ case RADIUS_ATTR_ARAP_SECURITY_DATA:
+ CONV2DIAM_STR( ARAP_Security_Data );
+ break;
+
+ /* Password-Retry never present in an Access-Request */
+ /* Prompt never present in an Access-Request */
+
+ case RADIUS_ATTR_CONNECT_INFO:
+ CONV2DIAM_STR( Connect_Info );
+ break;
+
+ /* Configuration-Token never present in an Access-Request */
+ /* ARAP-Challenge-Response never present in an Access-Request */
+ /* Acct-Interim-Interval never present in an Access-Request */
+
+ case RADIUS_ATTR_NAS_PORT_ID:
+ CONV2DIAM_STR( NAS_Port_Id );
+ break;
+
+ /* Framed-Pool never present in an Access-Request */
+
+
+ /* RFC 2869 / 3579 */
+ case RADIUS_ATTR_ORIGINATING_LINE_INFO:
+ CONV2DIAM_STR( Originating_Line_Info );
+ break;
+
+ case RADIUS_ATTR_MESSAGE_AUTHENTICATOR:
+ case RADIUS_ATTR_EAP_MESSAGE:
+ /* It was already handled, just remove the attribute */
+ break;
+
+ /* Default */
+ default: /* unknown attribute */
+ /* We just keep the attribute in the RADIUS message */
+ rad_req->attr_pos[nattr_used++] = rad_req->attr_pos[idx];
+ }
+ }
+
+ /* Destroy tunnel pointers (if we used it) */
+ free(avp_tun);
+
+ /* Update the radius message to remove all handled attributes */
+ rad_req->attr_used = nattr_used;
+
+ /* Store the request identifier in the session (if provided) */
+ {
+ struct sess_state *st;
+ CHECK_MALLOC(st = malloc(sizeof(struct sess_state)));
+ memcpy(st->req_auth, &rad_req->hdr->authenticator[0], 16);
+
+ CHECK_FCT( fd_sess_state_store( cs->sess_hdl, sess, &st ) );
+ }
+
+ return 0;
+}
+
+static int auth_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+ struct msg_hdr * hdr;
+ struct avp *avp, *next, *avp_x, *avp_y, *aoh;
+ struct avp_hdr *ahdr, *oh;
+ uint8_t buf[254]; /* to store some attributes values (with final '\0') */
+ size_t sz;
+ int ta_set = 0;
+ int no_str = 0; /* indicate if an STR is required for this server */
+ uint8_t tuntag = 0;
+ struct sess_state *st;
+ int error_cause = 0;
+ struct session * sess;
+ os0_t sid = NULL;
+ size_t sidlen;
+
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+ CHECK_PARAMS(cs && diam_ans && *diam_ans && rad_fw && *rad_fw);
+
+ /* Retrieve the request identified which was stored in the session */
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_ans, &sess, NULL) );
+ if (sess) {
+ CHECK_FCT( fd_sess_state_retrieve( cs->sess_hdl, sess, &st ) );
+ CHECK_FCT( fd_sess_getsid(sess, &sid, &sidlen) );
+ } /* else ? */
+
+ /*
+ - If the Diameter Command-Code is set to AA-Answer and the
+ Result-Code AVP is set to DIAMETER_MULTI_ROUND_AUTH, the
+ gateway must send a RADIUS Access-Challenge. This must have
+ the Origin-Host, Origin-Realm, and Diameter Session-Id AVPs
+ encapsulated in the RADIUS State attribute, with the prefix
+ "Diameter/", concatenated in the above order separated with "/"
+ characters, in UTF-8 [UTF-8]. This is necessary to ensure that
+ the Translation Agent receiving the subsequent RADIUS Access-
+ Request will have access to the Session Identifier and be able
+ to set the Destination-Host to the correct value.
+ -> done here below
+
+ - If the Command-Code is set to AA-Answer, the Diameter Session-
+ Id AVP is saved in a new RADIUS Class attribute whose format
+ consists of the string "Diameter/" followed by the Diameter
+ Session Identifier. This will ensure that the subsequent
+ Accounting messages, which could be received by any Translation
+ Agent, would have access to the original Diameter Session
+ Identifier.
+ -> done here but only for Access-Accept messages (Result-Code = success)
+ */
+
+ /* MACROS to help in the process: convert AVP data to RADIUS attributes. */
+ /* Control large attributes: _trunc_ = 0 => error; _trunc_ = 1 => truncate; _trunc = 2 => create several attributes */
+ #define CONV2RAD_STR( _attr_, _data_, _len_, _trunc_) { \
+ size_t __l = (size_t)(_len_); \
+ size_t __off = 0; \
+ TRACE_DEBUG(FULL, "Converting AVP to "#_attr_); \
+ if ((_trunc_) == 0) { \
+ CHECK_PARAMS( __l <= 253 ); \
+ } \
+ if ((__l > 253) && (_trunc_ == 1)) { \
+ TRACE_DEBUG(INFO, "[auth.rgwx] AVP truncated in "#_attr_); \
+ __l = 253; \
+ } \
+ do { \
+ size_t __w = (__l > 253) ? 253 : __l; \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (_data_) + __off, __w)); \
+ __off += __w; \
+ __l -= __w; \
+ } while (__l); \
+ }
+
+ #define CONV2RAD_32B( _attr_, _data_) { \
+ uint32_t __v = htonl((uint32_t)(_data_)); \
+ TRACE_DEBUG(FULL, "Converting AVP to "#_attr_); \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \
+ }
+
+ #define CONV2RAD_64B( _attr_, _data_) { \
+ uint64_t __v = htonll((uint64_t)(_data_)); \
+ TRACE_DEBUG(FULL, "Converting AVP to "#_attr_); \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \
+ }
+
+ /* Search the different AVPs we handle here */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Origin_Host, &aoh) );
+ CHECK_FCT( fd_msg_avp_hdr ( aoh, &oh ) );
+
+ /* Check the Diameter error code */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Result_Code, &avp) );
+ ASSERT( avp ); /* otherwise the message should have been discarded a lot earlier because of ABNF */
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ switch (ahdr->avp_value->u32) {
+ case ER_DIAMETER_MULTI_ROUND_AUTH:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+ case ER_DIAMETER_SUCCESS:
+ case ER_DIAMETER_LIMITED_SUCCESS:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_ACCEPT;
+ break;
+
+ default:
+ /* Can we convert the value to a natural Error-Cause ? */
+ switch (ahdr->avp_value->u32) {
+ case ER_DIAMETER_AVP_UNSUPPORTED:
+ error_cause = 401; /* Unsupported Attribute */
+ break;
+
+ case ER_DIAMETER_MISSING_AVP:
+ error_cause = 402; /* Missing Attribute */
+ break;
+
+ case ER_DIAMETER_UNABLE_TO_COMPLY:
+ error_cause = 404; /* Invalid Request */
+ break;
+
+ case ER_DIAMETER_APPLICATION_UNSUPPORTED:
+ error_cause = 405; /* Unsupported Service */
+ break;
+
+ case ER_DIAMETER_COMMAND_UNSUPPORTED:
+ error_cause = 406; /* Unsupported Extension */
+ break;
+
+ case ER_DIAMETER_INVALID_AVP_VALUE:
+ error_cause = 407; /* Invalid Attribute Value */
+ break;
+
+ case ER_DIAMETER_AVP_NOT_ALLOWED:
+ error_cause = 501; /* Administratively Prohibited */
+ break;
+
+ case ER_DIAMETER_REALM_NOT_SERVED:
+ case ER_DIAMETER_LOOP_DETECTED:
+ case ER_DIAMETER_UNKNOWN_PEER:
+ case ER_DIAMETER_UNABLE_TO_DELIVER:
+ error_cause = 502; /* Request Not Routable (Proxy) */
+ break;
+
+ case ER_DIAMETER_UNKNOWN_SESSION_ID:
+ error_cause = 503; /* Session Context Not Found */
+ break;
+
+ case ER_DIAMETER_TOO_BUSY:
+ case ER_DIAMETER_OUT_OF_SPACE:
+ error_cause = 506; /* Resources Unavailable */
+ break;
+
+#if 0
+ /* remaining Diameter Result-Code & RADIUS Error-Cause */
+ case ER_DIAMETER_REDIRECT_INDICATION:
+ case ER_DIAMETER_INVALID_HDR_BITS:
+ case ER_DIAMETER_INVALID_AVP_BITS:
+ case ER_DIAMETER_AUTHENTICATION_REJECTED:
+ case ER_ELECTION_LOST:
+ case ER_DIAMETER_AUTHORIZATION_REJECTED:
+ case ER_DIAMETER_RESOURCES_EXCEEDED:
+ case ER_DIAMETER_CONTRADICTING_AVPS:
+ case ER_DIAMETER_AVP_OCCURS_TOO_MANY_TIMES
+ case ER_DIAMETER_NO_COMMON_APPLICATION:
+ case ER_DIAMETER_UNSUPPORTED_VERSION:
+ case ER_DIAMETER_INVALID_BIT_IN_HEADER:
+ case ER_DIAMETER_INVALID_AVP_LENGTH:
+ case ER_DIAMETER_INVALID_MESSAGE_LENGTH:
+ case ER_DIAMETER_INVALID_AVP_BIT_COMBO:
+ case ER_DIAMETER_NO_COMMON_SECURITY:
+ error_cause = 403; /* NAS Identification Mismatch */
+ error_cause = 504; /* Session Context Not Removable */
+ error_cause = 505; /* Other Proxy Processing Error */
+ error_cause = 507; /* Request Initiated */
+ error_cause = 508; /* Multiple Session Selection Unsupported */
+#endif /* 0 */
+ }
+ /* In any case, the following is processed: */
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_REJECT;
+ fd_log_debug("[auth.rgwx] Received Diameter answer with error code '%d' from server '%.*s', session %.*s, translating into Access-Reject",
+ ahdr->avp_value->u32,
+ (int)oh->avp_value->os.len, oh->avp_value->os.data,
+ (int)sidlen, sid);
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Error_Message, &avp_x) );
+ if (avp_x) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp_x, &ahdr ) );
+ fd_log_debug("[auth.rgwx] Error-Message content: '%.*s'",
+ (int)ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+ }
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Error_Reporting_Host, &avp_x) );
+ if (avp_x) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp_x, &ahdr ) );
+ fd_log_debug("[auth.rgwx] Error-Reporting-Host: '%.*s'",
+ (int)ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+ }
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Failed_AVP, &avp_x) );
+ if (avp_x) {
+ fd_log_debug("[auth.rgwx] Failed-AVP was included in the message.");
+ /* Dump its content ? */
+ }
+ }
+ /* Remove this Result-Code avp */
+ CHECK_FCT( fd_msg_free( avp ) );
+
+ /* Creation of the State or Class attribute with session information */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Origin_Realm, &avp) );
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+
+ /* Now, save the session-id and eventually server info in a STATE or CLASS attribute */
+ if ((*rad_fw)->hdr->code == RADIUS_CODE_ACCESS_CHALLENGE) {
+ if (sizeof(buf) < (sz = snprintf((char *)buf, sizeof(buf), "Diameter/%.*s/%.*s/%.*s",
+ (int)oh->avp_value->os.len, (char *)oh->avp_value->os.data,
+ (int)ahdr->avp_value->os.len, (char *)ahdr->avp_value->os.data,
+ (int)sidlen, (char *)sid))) {
+ TRACE_DEBUG(INFO, "Data truncated in State attribute: %s", buf);
+ }
+ CONV2RAD_STR(RADIUS_ATTR_STATE, buf, sz, 0);
+ }
+
+ if ((*rad_fw)->hdr->code == RADIUS_CODE_ACCESS_ACCEPT) {
+ /* Add the Session-Id */
+ if (sizeof(buf) < (sz = snprintf((char *)buf, sizeof(buf), "Diameter/%.*s",
+ (int)sidlen, sid))) {
+ TRACE_DEBUG(INFO, "Data truncated in Class attribute: %s", buf);
+ }
+ CONV2RAD_STR(RADIUS_ATTR_CLASS, buf, sz, 0);
+ }
+
+ /* Unlink the Origin-Realm now; the others are unlinked at the end of this function */
+ CHECK_FCT( fd_msg_free( avp ) );
+
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Session_Timeout, &avp) );
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Authorization_Lifetime, &avp_x) );
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Re_Auth_Request_Type, &avp_y) );
+ /*
+ When translating a Diameter AA-Answer (with successful result code)
+ to RADIUS Access-Accept that contains a Session-Timeout or
+ Authorization-Lifetime AVP, take the following steps:
+
+ - If the Diameter message contains a Session-Timeout AVP but no
+ Authorization-Lifetime AVP, translate it to a Session-Timeout
+ attribute (not a Termination-Action).
+ */
+ if ((avp != NULL) && (avp_x == NULL)) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ CONV2RAD_32B( RADIUS_ATTR_SESSION_TIMEOUT, ahdr->avp_value->u32 );
+ }
+
+ /*
+ - If the Diameter message contains an Authorization-Lifetime AVP
+ but no Session-Timeout AVP, translate it to a Session-Timeout
+ attribute and a Termination-Action set to AA-REQUEST. (Remove
+ Authorization-Lifetime and Re-Auth-Request-Type.)
+ */
+ if ((avp == NULL) && (avp_x != NULL)) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp_x, &ahdr ) );
+ CONV2RAD_32B( RADIUS_ATTR_SESSION_TIMEOUT, ahdr->avp_value->u32 );
+ CONV2RAD_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST );
+ ta_set = 1;
+ }
+
+ /*
+ - If the Diameter message has both, the Session-Timeout must be
+ greater than or equal to the Authorization-Lifetime (required
+ by [BASE]). Translate it to a Session-Timeout value (with
+ value from Authorization-Lifetime AVP, the smaller one) and
+ with the Termination-Action set to AA-REQUEST. (Remove the
+ Authorization-Lifetime and Re-Auth-Request-Type.)
+ */
+ if ((avp != NULL) && (avp_x != NULL)) {
+ CHECK_FCT( fd_msg_avp_hdr ( avp_x, &ahdr ) );
+ CONV2RAD_32B( RADIUS_ATTR_SESSION_TIMEOUT, ahdr->avp_value->u32 );
+ CONV2RAD_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST );
+ ta_set = 1;
+ }
+
+ /* -> Not too sure about Auth-Grace-Period... we'll just discard it for now */
+
+ if (avp) {
+ CHECK_FCT( fd_msg_free( avp ) );
+ }
+ if (avp_x) {
+ CHECK_FCT( fd_msg_free( avp_x ) );
+ }
+ if (avp_y) {
+ CHECK_FCT( fd_msg_free( avp_y ) );
+ }
+
+
+ /*
+ - If a Proxy-State attribute was present in the RADIUS request,
+ the same attribute is added in the response. This information
+ may be found in the Proxy-Info AVP group, or in a local state
+ table.
+ -> handled by sub_echo_drop
+
+ - If state information regarding the RADIUS request was saved in
+ a Proxy-Info AVP or local state table, the RADIUS Identifier
+ and UDP IP Address and port number are extracted and used in
+ issuing the RADIUS reply.
+ -> was saved with the full request
+ */
+
+
+ /* Now loop in the list of AVPs and convert those that we know how */
+ CHECK_FCT( fd_msg_browse(*diam_ans, MSG_BRW_FIRST_CHILD, &next, NULL) );
+
+ while (next) {
+ int handled = 1;
+ avp = next;
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &next, NULL) );
+
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+
+ if (!(ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+ /* In case of Diameter error, include the Reply-Message attribute */
+ case DIAM_ATTR_ERROR_MESSAGE:
+ CONV2RAD_STR(RADIUS_ATTR_REPLY_MESSAGE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_ERROR_REPORTING_HOST:
+ {
+ char buf[254];
+ int bsz = snprintf(buf, sizeof(buf), "Error-Reporting-Host: %*s", (int)(ahdr->avp_value->os.len), ahdr->avp_value->os.data);
+ CONV2RAD_STR(RADIUS_ATTR_REPLY_MESSAGE, (uint8_t *)buf, bsz, 2);
+ }
+ break;
+
+ case DIAM_ATTR_FAILED_AVP:
+ {
+ struct avp * favp;
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &favp, NULL) );
+ if (favp) {
+ char buf[254];
+ int bsz;
+ struct dict_object * favp_model;
+
+ CHECK_FCT( fd_msg_model(favp, &favp_model) );
+ if (favp_model) {
+ struct dict_avp_data fadata;
+ CHECK_FCT( fd_dict_getval(favp_model, &fadata) );
+ bsz = snprintf(buf, sizeof(buf), "Failed-AVP: %s", fadata.avp_name);
+ } else {
+ struct avp_hdr * favp_hdr;
+ CHECK_FCT( fd_msg_avp_hdr ( favp, &favp_hdr ) );
+ bsz = snprintf(buf, sizeof(buf), "Failed-AVP: code %u, vendor %u", favp_hdr->avp_code, favp_hdr->avp_vendor);
+ }
+ CONV2RAD_STR(RADIUS_ATTR_REPLY_MESSAGE, (uint8_t *)buf, bsz, 2);
+ }
+ }
+ break;
+
+ /* RFC 4005 (AVP in the order of the AA-Request/Answer AVP Table) */
+ case DIAM_ATTR_ACCT_INTERIM_INTERVAL:
+ CONV2RAD_32B(RADIUS_ATTR_ACCT_INTERIM_INTERVAL, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_ARAP_CHALLENGE_RESPONSE:
+ CONV2RAD_STR(RADIUS_ATTR_ARAP_CHALLENGE_RESPONSE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_ARAP_FEATURES:
+ CONV2RAD_STR(RADIUS_ATTR_ARAP_FEATURES, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ /* ARAP-Password is not present in answers */
+
+ case DIAM_ATTR_ARAP_SECURITY:
+ CONV2RAD_32B(RADIUS_ATTR_ARAP_SECURITY, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_ARAP_SECURITY_DATA:
+ CONV2RAD_STR(RADIUS_ATTR_ARAP_SECURITY_DATA, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_ARAP_ZONE_ACCESS:
+ CONV2RAD_32B(RADIUS_ATTR_ARAP_ZONE_ACCESS, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_AUTH_APPLICATION_ID:
+ /* We just remove this AVP */
+ break;
+
+ case DIAM_ATTR_AUTH_GRACE_PERIOD:
+ /* We just remove this AVP (?) */
+ break;
+
+ case DIAM_ATTR_AUTH_REQUEST_TYPE:
+ /* We only check the value */
+ if (ahdr->avp_value->u32 != 3) {
+ fd_log_debug("[auth.rgwx] Received Diameter answer with Auth-Request-Type set to %d (%s) from server %.*s, session %.*s."
+ " This may cause interoperability problems with RADIUS.",
+ ahdr->avp_value->u32,
+ (ahdr->avp_value->u32 == 1) ? "AUTHENTICATE_ONLY" :
+ ((ahdr->avp_value->u32 == 2) ? "AUTHORIZE_ONLY" : "???"),
+ (int)oh->avp_value->os.len, oh->avp_value->os.data,
+ (int)sidlen, sid);
+ }
+ break;
+
+ case DIAM_ATTR_AUTH_SESSION_STATE:
+ if ((!ta_set) && (ahdr->avp_value->u32 == ACV_ASS_STATE_MAINTAINED)) {
+ CONV2RAD_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST );
+ }
+
+ if (ahdr->avp_value->u32 == ACV_ASS_NO_STATE_MAINTAINED) {
+ no_str = 1;
+ }
+ break;
+
+ /* Authorization-Lifetime already handled */
+
+ case DIAM_ATTR_CALLBACK_ID:
+ CONV2RAD_STR(RADIUS_ATTR_CALLBACK_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_CALLBACK_NUMBER:
+ CONV2RAD_STR(RADIUS_ATTR_CALLBACK_NUMBER, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ /* Called-Station-Id is not present in answers */
+ /* Calling-Station-Id is not present in answers */
+ /* CHAP-Auth is not present in answers */
+ /* CHAP-Challenge is not present in answers */
+
+ case DIAM_ATTR_CLASS:
+ CONV2RAD_STR(RADIUS_ATTR_CLASS, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_CONFIGURATION_TOKEN:
+ /* We might as well remove it since it's not supposed to be sent to the NAS... */
+ CONV2RAD_STR(RADIUS_ATTR_CONFIGURATION_TOKEN, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ /* Connect-Info is not present in answers */
+
+ case DIAM_ATTR_FILTER_ID:
+ CONV2RAD_STR(RADIUS_ATTR_FILTER_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_FRAMED_APPLETALK_LINK:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_APPLETALK_LINK, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_APPLETALK_NETWORK:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_APPLETALK_NETWORK, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_APPLETALK_ZONE:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_APPLETALK_ZONE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_FRAMED_COMPRESSION:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_COMPRESSION, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_INTERFACE_ID:
+ CONV2RAD_64B(RADIUS_ATTR_FRAMED_INTERFACE_ID, ahdr->avp_value->u64);
+ break;
+
+ case DIAM_ATTR_FRAMED_IP_ADDRESS:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_IP_ADDRESS, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_FRAMED_IP_NETMASK:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_IP_NETMASK, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_FRAMED_IPV6_PREFIX:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_IPV6_PREFIX, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_FRAMED_IPV6_POOL:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_IPV6_POOL, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_FRAMED_IPV6_ROUTE:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_IPV6_ROUTE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_FRAMED_IPX_NETWORK:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_IPX_NETWORK, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_MTU:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_MTU, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_POOL:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_POOL, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_FRAMED_PROTOCOL:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_PROTOCOL, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_FRAMED_ROUTE:
+ CONV2RAD_STR(RADIUS_ATTR_FRAMED_ROUTE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_FRAMED_ROUTING:
+ CONV2RAD_32B(RADIUS_ATTR_FRAMED_ROUTING, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_IDLE_TIMEOUT:
+ CONV2RAD_32B(RADIUS_ATTR_IDLE_TIMEOUT, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_LOGIN_IP_HOST:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_IP_HOST, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_LOGIN_IPV6_HOST:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_IPV6_HOST, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+
+ case DIAM_ATTR_LOGIN_LAT_GROUP:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_LAT_GROUP, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_LOGIN_LAT_NODE:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_LAT_NODE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_LOGIN_LAT_PORT:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_LAT_PORT, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_LOGIN_LAT_SERVICE:
+ CONV2RAD_STR(RADIUS_ATTR_LOGIN_LAT_SERVICE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_LOGIN_SERVICE:
+ CONV2RAD_32B(RADIUS_ATTR_LOGIN_SERVICE, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_LOGIN_TCP_PORT:
+ CONV2RAD_32B(RADIUS_ATTR_LOGIN_TCP_PORT, ahdr->avp_value->u32);
+ break;
+
+ /*
+ - If the
+ Multi-Round-Time-Out AVP is present, the value of the AVP MUST
+ be inserted in the RADIUS Session-Timeout AVP.
+
+ o As described in [NASREQ], if the Result-Code AVP set to
+ DIAMETER_MULTI_ROUND_AUTH and the Multi-Round-Time-Out AVP is
+ present, it is translated to the RADIUS Session-Timeout attribute.
+ */
+ case DIAM_ATTR_MULTI_ROUND_TIMEOUT:
+ CONV2RAD_32B(RADIUS_ATTR_SESSION_TIMEOUT, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_NAS_FILTER_RULE:
+ /* This is not translatable to RADIUS */
+ fd_log_debug("[auth.rgwx] Received Diameter answer with non-translatable NAS-Filter-Rule AVP from '%.*s' (session: '%.*s'), ignoring.",
+ (int)oh->avp_value->os.len, oh->avp_value->os.data,
+ (int)sidlen, sid);
+ handled = 0;
+ break;
+
+ /* NAS-Identifier is not present in answers */
+ /* NAS-IP-Address is not present in answers */
+ /* NAS-IPv6-Address is not present in answers */
+ /* NAS-Port is not present in answers */
+ /* NAS-Port-Id is not present in answers */
+ /* NAS-Port-Type is not present in answers */
+
+ case DIAM_ATTR_ORIGIN_AAA_PROTOCOL:
+ /* We just remove this AVP */
+ break;
+
+ /* Originating-Line-Info is not present in answers */
+
+ case DIAM_ATTR_PASSWORD_RETRY:
+ CONV2RAD_32B(RADIUS_ATTR_PASSWORD_RETRY, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_PORT_LIMIT:
+ CONV2RAD_32B(RADIUS_ATTR_PORT_LIMIT, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_PROMPT:
+ CONV2RAD_32B(RADIUS_ATTR_PROMPT, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_QOS_FILTER_RULE:
+ /* This is not translatable to RADIUS */
+ fd_log_debug("[auth.rgwx] Received Diameter answer with non-translatable QoS-Filter-Rule AVP from '%.*s' (session: '%.*s'), ignoring.",
+ (int)oh->avp_value->os.len, oh->avp_value->os.data,
+ (int)sidlen, sid);
+ handled = 0;
+ break;
+
+ /* Re-Auth-Request-Type already handled */
+
+ case DIAM_ATTR_REPLY_MESSAGE:
+ CONV2RAD_STR(RADIUS_ATTR_REPLY_MESSAGE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_SERVICE_TYPE:
+ CONV2RAD_32B(RADIUS_ATTR_SERVICE_TYPE, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_STATE:
+ CONV2RAD_STR(RADIUS_ATTR_STATE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 2);
+ break;
+
+ case DIAM_ATTR_TUNNELING:
+ {
+#define CONV2RAD_TUN_STR( _attr_, _data_, _len_, _trunc_) { \
+ size_t __l = (size_t)(_len_); \
+ size_t __w = (__l > 252) ? 252 : __l; \
+ size_t __off = 0; \
+ if ((_trunc_) == 0) { \
+ CHECK_PARAMS( __l <= 252 ); \
+ } \
+ if ((__l > 252) && (_trunc_ == 1)) { \
+ TRACE_DEBUG(FULL, "Attribute truncated!"); \
+ __l = 252; \
+ } \
+ buf[0] = tuntag; \
+ memcpy(&buf[1], (_data_), __w); \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), &buf[0], __w + 1)); \
+ while (__l -= __w) { \
+ __off += __w; \
+ __w = (__l > 253) ? 253 : __l; \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (_data_) + __off, __w)); \
+ } \
+}
+
+#define CONV2RAD_TUN_32B( _attr_, _data_) { \
+ uint32_t __v = htonl((uint32_t)(_data_) | (tuntag << 24)); \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \
+}
+ struct avp *inavp, *innext;
+ tuntag++;
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &innext, NULL) );
+ while (innext) {
+ inavp = innext;
+ CHECK_FCT( fd_msg_browse(inavp, MSG_BRW_NEXT, &innext, NULL) );
+ CHECK_FCT( fd_msg_avp_hdr ( inavp, &ahdr ) );
+
+ if ( ! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+ case DIAM_ATTR_TUNNEL_TYPE:
+ CONV2RAD_TUN_32B( RADIUS_ATTR_TUNNEL_TYPE, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_TUNNEL_MEDIUM_TYPE:
+ CONV2RAD_TUN_32B( RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_TUNNEL_CLIENT_ENDPOINT:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_CLIENT_ENDPOINT, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_TUNNEL_SERVER_ENDPOINT:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_SERVER_ENDPOINT, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_TUNNEL_PREFERENCE:
+ CONV2RAD_TUN_32B( RADIUS_ATTR_TUNNEL_PREFERENCE, ahdr->avp_value->u32);
+ break;
+
+ case DIAM_ATTR_TUNNEL_CLIENT_AUTH_ID:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_CLIENT_AUTH_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_TUNNEL_SERVER_AUTH_ID:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_SERVER_AUTH_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_TUNNEL_ASSIGNMENT_ID:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_ASSIGNMENT_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ case DIAM_ATTR_TUNNEL_PASSWORD:
+ {
+ /* This AVP must be encoded for RADIUS (similar to radius_msg_add_attr_user_password)
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Tag | Salt
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ Salt (cont) | String ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ size_t pos;
+ int i;
+ uint8_t * secret; /* S */
+ size_t secret_len;
+ uint8_t hash[16]; /* b(i) */
+ const uint8_t *addr[3];
+ size_t len[3];
+
+ /* We need the request authenticator */
+ CHECK_PARAMS(st);
+
+ /* Retrieve the shared secret */
+ CHECK_FCT(rgw_clients_getkey(cli, &secret, &secret_len));
+
+ /* Beginning of the buffer */
+ buf[0] = tuntag;
+ buf[1] = (uint8_t)(lrand48()); /* A (hi bits) */
+ buf[2] = (uint8_t)(lrand48()); /* A (low bits) */
+
+ /* The plain text string P */
+ CHECK_PARAMS(ahdr->avp_value->os.len < 240);
+ buf[3] = ahdr->avp_value->os.len;
+ memcpy(&buf[4], ahdr->avp_value->os.data, ahdr->avp_value->os.len);
+ memset(&buf[4 + ahdr->avp_value->os.len], 0, sizeof(buf) - 4 - ahdr->avp_value->os.len);
+
+ /* Initial b1 = MD5(S + R + A) */
+ addr[0] = secret;
+ len[0] = secret_len;
+ addr[1] = st->req_auth;
+ len[1] = 16;
+ addr[2] = &buf[1];
+ len[2] = 2;
+ md5_vector(3, addr, len, hash);
+
+ /* Initial c(1) = p(1) xor b(1) */
+ for (i = 0; i < 16; i++) {
+ buf[i + 3] ^= hash[i];
+ }
+ pos = 16;
+
+ /* loop */
+ while (pos < ahdr->avp_value->os.len + 1) {
+ addr[0] = secret;
+ len[0] = secret_len;
+ addr[1] = &buf[pos - 13];
+ len[1] = 16;
+ /* b(i) = MD5( S + c(i-1) */
+ md5_vector(2, addr, len, hash);
+
+ /* c(i) = p(i) xor b(i) */
+ for (i = 0; i < 16; i++)
+ buf[pos + i + 3] ^= hash[i];
+
+ pos += 16;
+ }
+
+ CONV2RAD_STR(RADIUS_ATTR_TUNNEL_PASSWORD, &buf[0], pos + 3, 0);
+ }
+ break;
+
+ case DIAM_ATTR_TUNNEL_PRIVATE_GROUP_ID:
+ CONV2RAD_TUN_STR(RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ default:
+ TRACE_DEBUG(FULL, "Ignored unknown AVP inside Tunneling AVP (%d)", ahdr->avp_code);
+ }
+ } else {
+ TRACE_DEBUG(FULL, "Ignored unknown Vendor AVP inside Tunneling AVP (%d, %d)", ahdr->avp_vendor, ahdr->avp_code);
+ }
+ }
+ }
+ break;
+
+ case DIAM_ATTR_USER_NAME:
+ CONV2RAD_STR(RADIUS_ATTR_USER_NAME, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ /* User-Password never present in answers */
+
+ /* RFC 4072 (AVP in the order of the EAP Command AVP Table) */
+ /*
+ o Diameter Accounting-EAP-Auth-Method AVPs, if present, are
+ discarded.
+ */
+ case DIAM_ATTR_ACCOUNTING_EAP_AUTH_METHOD:
+ break;
+
+ /*
+ o Diameter EAP-Master-Session-Key AVP can be translated to the
+ vendor-specific RADIUS MS-MPPE-Recv-Key and MS-MPPE-Send-Key
+ attributes [RFC2548]. The first up to 32 octets of the key is
+ stored into MS-MPPE-Recv-Key, and the next up to 32 octets (if
+ present) are stored into MS-MPPE-Send-Key. The encryption of this
+ attribute is described in [RFC2548].
+ */
+ case DIAM_ATTR_EAP_MASTER_SESSION_KEY:
+ {
+ uint8_t * secret; /* S */
+ size_t secret_len;
+ size_t recv_len, send_len;
+
+ /* We need the request authenticator */
+ CHECK_PARAMS(st);
+
+ /* Retrieve the shared secret */
+ CHECK_FCT(rgw_clients_getkey(cli, &secret, &secret_len));
+
+ if (ahdr->avp_value->os.len != 64) {
+ TRACE_DEBUG(INFO, "Received EAP-Master-Session-Key attribute with length %zd != 64.", ahdr->avp_value->os.len)
+ }
+
+ CHECK_PARAMS(ahdr->avp_value->os.len <= 64);
+ recv_len = ahdr->avp_value->os.len >= 32 ? 32 : ahdr->avp_value->os.len;
+ send_len = ahdr->avp_value->os.len - recv_len;
+
+ if ( ! radius_msg_add_mppe_keys(*rad_fw, st->req_auth, secret, secret_len,
+ ahdr->avp_value->os.data + recv_len, send_len,
+ ahdr->avp_value->os.data, recv_len) ) {
+ TRACE_DEBUG(INFO, "Error while converting EAP-Master-Session-Key to RADIUS message");
+ return ENOMEM;
+ }
+ }
+ break;
+
+ case DIAM_ATTR_EAP_KEY_NAME:
+ CONV2RAD_STR(RADIUS_ATTR_EAP_KEY_NAME, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+
+ /*
+ o Diameter EAP-Payload AVP is translated to RADIUS EAP-Message
+ attribute(s). If necessary, the value is split into multiple
+ RADIUS EAP-Message attributes.
+ */
+ case DIAM_ATTR_EAP_PAYLOAD:
+ if ( ! radius_msg_add_eap(*rad_fw, ahdr->avp_value->os.data, ahdr->avp_value->os.len) ) {
+ TRACE_DEBUG(INFO, "Error while converting EAP payload to RADIUS message");
+ return ENOMEM;
+ }
+ break;
+
+ /*
+ o Diameter EAP-Reissued-Payload AVP is translated to a message that
+ contains RADIUS EAP-Message attribute(s), and a RADIUS Error-Cause
+ attribute [RFC3576] with value 202 (decimal), "Invalid EAP Packet
+ (Ignored)" [RFC3579].
+ */
+ case DIAM_ATTR_EAP_REISSUED_PAYLOAD:
+ if ( ! radius_msg_add_eap(*rad_fw, ahdr->avp_value->os.data, ahdr->avp_value->os.len) ) {
+ TRACE_DEBUG(INFO, "Error while converting EAP reissued payload to RADIUS message");
+ return ENOMEM;
+ }
+
+ error_cause = 202; /* Invalid EAP Packet */
+ break;
+
+ default:
+ /* Leave the AVP in the message for further treatment */
+ handled = 0;
+ }
+ } else {
+ /* Vendor-specific AVPs */
+ switch (ahdr->avp_vendor) {
+
+ default: /* unknown vendor */
+ handled = 0;
+ }
+ }
+
+ if (handled) {
+ CHECK_FCT( fd_msg_free( avp ) );
+ }
+ }
+
+ CHECK_FCT( fd_msg_free( aoh ) );
+ free(st);
+
+ if (error_cause) {
+ if ( ! radius_msg_add_attr_int32(*rad_fw, RADIUS_ATTR_ERROR_CAUSE, error_cause) ) {
+ TRACE_DEBUG(INFO, "Error while adding Error-Cause attribute in RADIUS message");
+ return ENOMEM;
+ }
+ }
+
+ if ((*rad_fw)->hdr->code == RADIUS_CODE_ACCESS_ACCEPT) {
+ /* Add the auth-application-id required for STR, or 0 if no STR is required */
+ CHECK_FCT( fd_msg_hdr( *diam_ans, &hdr ) );
+ if (sizeof(buf) < (sz = snprintf((char *)buf, sizeof(buf), CLASS_AAI_PREFIX "%u",
+ no_str ? 0 : hdr->msg_appl))) {
+ TRACE_DEBUG(INFO, "Data truncated in Class attribute: %s", buf);
+ }
+ CONV2RAD_STR(RADIUS_ATTR_CLASS, buf, sz, 0);
+ }
+
+ return 0;
+}
+
+/* The exported symbol */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "auth",
+ .rgwp_conf_parse = auth_conf_parse,
+ .rgwp_conf_free = auth_conf_free,
+ .rgwp_rad_req = auth_rad_req,
+ .rgwp_diam_ans = auth_diam_ans
+};
diff --git a/extensions/app_radgw/rgwx_debug.c b/extensions/app_radgw/rgwx_debug.c
new file mode 100644
index 0000000..282b028
--- /dev/null
+++ b/extensions/app_radgw/rgwx_debug.c
@@ -0,0 +1,143 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Debug plugin for app_radgw */
+
+#include "rgw_common.h"
+
+/* Store the configuration string in the state */
+static int debug_conf_parse ( char * conf_file, struct rgwp_config ** state )
+{
+ TRACE_ENTRY("%p %p", conf_file, state);
+ CHECK_PARAMS(state);
+
+ *state = (void *)conf_file;
+
+ return 0;
+}
+
+
+/* Function to display the content of a RADIUS message (more friendly way than radius_msg_dump) */
+static void debug_dump_radius(struct radius_msg *msg)
+{
+ unsigned char *auth;
+ size_t i;
+
+ auth = &(msg->hdr->authenticator[0]);
+ fd_log_debug(" id : 0x%02hhx, code: %hhd (%s)", msg->hdr->identifier, msg->hdr->code, rgw_msg_code_str(msg->hdr->code));
+ fd_log_debug(" auth: %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
+ auth[0], auth[1], auth[2], auth[3],
+ auth[4], auth[5], auth[6], auth[7]);
+ fd_log_debug(" %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
+ auth[8], auth[9], auth[10], auth[11],
+ auth[12], auth[13], auth[14], auth[15]);
+ for (i = 0; i < msg->attr_used; i++) {
+ struct radius_attr_hdr *attr = (struct radius_attr_hdr *)(msg->buf + msg->attr_pos[i]);
+ fd_log_debug(" - len:%3hhu, type:0x%02hhx (%s)", attr->length, attr->type, rgw_msg_attrtype_str(attr->type));
+ /* If we need to dump the value, it's better to call directly radius_msg_dump instead... */
+ }
+}
+
+/* Function called when a new RADIUS message is being converted to Diameter */
+static int debug_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+
+ fd_log_debug("------------- RADIUS/Diameter Request Debug%s%s%s -------------", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : "");
+
+ if (!rad_req) {
+ fd_log_debug(" RADIUS request: NULL pointer");
+ } else {
+ fd_log_debug(" RADIUS request (%p) DUMP:", rad_req);
+ debug_dump_radius(rad_req);
+ }
+
+ if (!rad_ans || ! *rad_ans) {
+ fd_log_debug(" RADIUS answer: NULL pointer");
+ } else {
+ fd_log_debug(" RADIUS answer (%p) DUMP:", *rad_ans);
+ debug_dump_radius(*rad_ans);
+ }
+
+ if (!diam_fw || ! *diam_fw) {
+ fd_log_debug(" Diameter message: NULL pointer");
+ } else {
+ char * buf = NULL; size_t buflen;
+ CHECK_MALLOC( fd_msg_dump_treeview(&buf, &buflen, NULL, *diam_fw, NULL, 0, 1) );
+ fd_log_debug(" Diameter message (%p) DUMP: %s", *diam_fw, buf);
+ free(buf);
+ }
+
+ fd_log_debug("=========== Debug%s%s%s complete =============", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : "");
+
+ return 0;
+}
+
+/* This one, when Diameter answer is converted to RADIUS */
+static int debug_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+
+ fd_log_debug("------------- RADIUS/Diameter Answer Debug%s%s%s -------------", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : "");
+
+ if (!diam_ans || ! *diam_ans) {
+ fd_log_debug(" Diameter message: NULL pointer");
+ } else {
+ char * buf = NULL; size_t buflen;
+ CHECK_MALLOC( fd_msg_dump_treeview(&buf, &buflen, NULL, *diam_ans, NULL, 0, 1) );
+ fd_log_debug(" Diameter message (%p) DUMP: %s", *diam_ans, buf);
+ free(buf);
+ }
+
+ if (!rad_fw || ! *rad_fw) {
+ fd_log_debug(" RADIUS answer: NULL pointer");
+ } else {
+ fd_log_debug(" RADIUS answer (%p) DUMP:", *rad_fw);
+ debug_dump_radius(*rad_fw);
+ }
+
+ fd_log_debug("=========== Debug%s%s%s complete =============", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : "");
+ return 0;
+}
+
+
+/* The exported symbol */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "debug",
+ .rgwp_conf_parse = debug_conf_parse,
+ .rgwp_conf_free = NULL,
+ .rgwp_rad_req = debug_rad_req,
+ .rgwp_diam_ans = debug_diam_ans
+};
diff --git a/extensions/app_radgw/rgwx_echodrop.c b/extensions/app_radgw/rgwx_echodrop.c
new file mode 100644
index 0000000..c8f4333
--- /dev/null
+++ b/extensions/app_radgw/rgwx_echodrop.c
@@ -0,0 +1,309 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* See rgwx_echodrop.h for details */
+
+#include "rgwx_echodrop.h"
+
+struct sess_state {
+ struct fd_list sentinel;
+};
+
+/* If a session is destroyed, empty the list of ed_saved_attribute */
+static void state_delete(struct sess_state * arg, os0_t sid, void * opaque) {
+ while (!FD_IS_LIST_EMPTY(&arg->sentinel)) {
+ struct ed_saved_attribute * esa = (struct ed_saved_attribute *)(arg->sentinel.next);
+ fd_list_unlink(&esa->chain);
+ free(esa);
+ }
+ free(arg);
+}
+
+static DECLARE_FD_DUMP_PROTOTYPE(ed_session_state_dump, struct sess_state * st)
+{
+ struct fd_list * li;
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "[rgwx sess_state](@%p):\n", st), return NULL);
+ for (li = st->sentinel.next; li != &st->sentinel; li = li->next) {
+ struct ed_saved_attribute * esa = (struct ed_saved_attribute *)(li);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "[rgwx sess_state {esa}] t:%2hhx l:%2hhx d:", esa->attr.type, esa->attr.length), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend_hexdump(FD_DUMP_STD_PARAMS, (&esa->attr.length) + 1, esa->attr.length - 2, 0,0), return NULL);
+ CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n"), return NULL);
+ }
+ return *buf;
+}
+
+/* Initialize the plugin and parse the configuration. */
+static int ed_conf_parse(char * conffile, struct rgwp_config ** state)
+{
+ struct rgwp_config * new;
+
+ TRACE_ENTRY("%p %p", conffile, state);
+ CHECK_PARAMS( state );
+ CHECK_PARAMS_DO( conffile, { fd_log_debug("[echodrop.rgwx] The configuration file is not optional for this plugin."); return EINVAL; } );
+
+ CHECK_MALLOC( new = malloc(sizeof(struct rgwp_config)) );
+ memset(new, 0, sizeof(struct rgwp_config));
+
+ /* Initialize the list of attributes to handle */
+ fd_list_init(&new->attributes, NULL);
+
+ /* Create the session handler */
+ CHECK_FCT( fd_sess_handler_create( &new->sess_hdl, state_delete, ed_session_state_dump, NULL ) );
+
+ /* Parse the configuration file */
+ CHECK_FCT( ed_conffile_parse(conffile, new) );
+
+ if (TRACE_BOOL(FULL)) {
+ TRACE_DEBUG(INFO, "Echo/Drop plugin configuration ('%s'):", conffile);
+ struct fd_list * li;
+
+ for (li = new->attributes.next; li != &new->attributes; li = li->next) {
+ struct ed_conf_attribute * eca = (struct ed_conf_attribute *)li;
+ char * act = (eca->action == ACT_ECHO) ? "ECHO" : "DROP";
+ if (eca->ext) {
+ fd_log_debug(" %s Code: %hhu, Vendor: %u, Ext-Type: %hu", act, eca->code, eca->vendor_id, eca->extype);
+ continue;
+ }
+ if (eca->tlv) {
+ fd_log_debug(" %s Code: %hhu, Vendor: %u, Type: %hhu", act, eca->code, eca->vendor_id, eca->type);
+ continue;
+ }
+ if (eca->vsa) {
+ fd_log_debug(" %s Code: %hhu, Vendor: %u", act, eca->code, eca->vendor_id);
+ continue;
+ }
+ fd_log_debug(" %s Code: %hhu", act, eca->code);
+ }
+ }
+
+ /* OK, we are done */
+ *state = new;
+ return 0;
+}
+
+/* Destroy the state */
+static void ed_conf_free(struct rgwp_config * state)
+{
+ TRACE_ENTRY("%p", state);
+ CHECK_PARAMS_DO( state, return );
+ CHECK_FCT_DO( fd_sess_handler_destroy( &state->sess_hdl, NULL ), );
+ while (! FD_IS_LIST_EMPTY(&state->attributes) ) {
+ struct fd_list * li = state->attributes.next;
+ fd_list_unlink(li);
+ free(li);
+ }
+ free(state);
+ return;
+}
+
+
+/* Handle attributes from a RADIUS request as specified in the configuration */
+static int ed_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ size_t nattr_used = 0;
+ int idx;
+ struct fd_list echo_list = FD_LIST_INITIALIZER(echo_list);
+ struct fd_list *li;
+
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+ CHECK_PARAMS(cs && rad_req);
+
+ /* For each attribute in the original message */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ int action = 0;
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+
+ /* Look if we have a matching attribute in our configuration */
+ for (li = cs->attributes.next; li != &cs->attributes; li = li->next) {
+ struct ed_conf_attribute * eca = (struct ed_conf_attribute *)li;
+ uint32_t vid;
+ unsigned char * ptr;
+
+ if (eca->code < attr->type)
+ continue;
+ if (eca->code > attr->type)
+ break;
+
+ /* the code matches one in our configuration, check additional data if needed */
+
+ if (! eca->vsa) {
+ action = eca->action;
+ break;
+ }
+
+ if (attr->length < 8)
+ continue;
+
+ ptr = (unsigned char *)(attr + 1);
+ /* since attr is not aligned, we may not access *(attr+1) directly */
+ memcpy(&vid, ptr, sizeof(uint32_t));
+
+ if (eca->vendor_id < ntohl(vid))
+ continue;
+ if (eca->vendor_id > ntohl(vid))
+ break;
+
+ /* The vendor matches our configured line... */
+
+ if ( ! eca->tlv && ! eca->ext ) {
+ action = eca->action;
+ break;
+ }
+
+ if (attr->length < 10)
+ continue;
+
+ if (eca->tlv) {
+ struct radius_attr_vendor * tl = (struct radius_attr_vendor *)(ptr + sizeof(uint32_t));
+ if (tl->vendor_type == eca->type) {
+ action = eca->action;
+ break;
+ }
+ continue;
+ }
+
+ if (eca->ext) {
+ /* To be done */
+ fd_log_debug("Extended attributes are not implemented yet!");
+ ASSERT(0);
+ continue;
+ }
+ }
+
+ switch (action) {
+ case ACT_DROP:
+ TRACE_DEBUG(FULL, "Dropping attribute with code %hhd", attr->type);
+ break;
+
+ case ACT_ECHO:
+ {
+ struct ed_saved_attribute * esa = NULL;
+ TRACE_DEBUG(FULL, "Saving attribute with code %hhd", attr->type);
+ CHECK_MALLOC( esa = malloc(sizeof(struct ed_saved_attribute) + attr->length - sizeof(struct radius_attr_hdr)) );
+ fd_list_init(&esa->chain, NULL);
+ memcpy(&esa->attr, attr, attr->length);
+ fd_list_insert_before(&echo_list, &esa->chain);
+ }
+ break;
+
+ default: /* Attribute was not specified in the configuration */
+ /* We just keep the attribute in the RADIUS message */
+ rad_req->attr_pos[nattr_used++] = rad_req->attr_pos[idx];
+ }
+ }
+ rad_req->attr_used = nattr_used;
+
+ /* Save the echoed values in the session, if any */
+ if (!FD_IS_LIST_EMPTY(&echo_list)) {
+ struct session * sess;
+ struct sess_state * st;
+
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_fw, &sess, NULL) );
+
+ CHECK_PARAMS_DO(sess,
+ {
+ fd_log_debug( "[echodrop.rgwx] The extension is configured to echo some attributes from this message, but no session object has been created for it (yet)."
+ " Please check your configuration file and include a session-generating extension BEFORE calling echodrop.rgwx to echo attributes."
+ " Please use debug.rgwx to retrieve more information." );
+ return EINVAL;
+ } );
+
+ /* Move the values in a dynamically allocated list */
+ CHECK_MALLOC( st = malloc(sizeof(struct sess_state)) );
+ fd_list_init(&st->sentinel, NULL);
+ fd_list_move_end(&st->sentinel, &echo_list);
+
+ /* Save the list in the session */
+ CHECK_FCT( fd_sess_state_store( cs->sess_hdl, sess, &st ) );
+ }
+
+ return 0;
+}
+
+/* Process an answer: add the ECHO attributes back, if any */
+static int ed_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+ struct session * sess;
+ struct sess_state * st;
+
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+ CHECK_PARAMS(cs);
+
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_ans, &sess, NULL) );
+
+ /* If there is no session associated, just give up */
+ if (! sess ) {
+ TRACE_DEBUG(FULL, "No session associated with the message, nothing to do here...");
+ return 0;
+ }
+
+ /* Now try and retrieve any data from the session */
+ CHECK_FCT( fd_sess_state_retrieve( cs->sess_hdl, sess, &st ) );
+ if (st == NULL) {
+ /* No attribute saved in the session, just return */
+ return 0;
+ }
+
+ /* From this point on, we have a list of attributes to add to the radius message */
+
+ CHECK_PARAMS( rad_fw && *rad_fw);
+
+ while (! FD_IS_LIST_EMPTY(&st->sentinel) ) {
+ struct ed_saved_attribute * esa = (struct ed_saved_attribute *)(st->sentinel.next);
+
+ fd_list_unlink(&esa->chain);
+
+ TRACE_DEBUG(FULL, "Echo attribute in the RADIUS answer: type %hhu, len: %hhu", esa->attr.type, esa->attr.length);
+
+ /* Add this attribute in the RADIUS message */
+ CHECK_MALLOC( radius_msg_add_attr(*rad_fw, esa->attr.type, (unsigned char *)(esa + 1), esa->attr.length - sizeof(struct radius_attr_hdr)) );
+
+ free(esa);
+ }
+ free(st);
+
+ return 0;
+}
+
+
+
+/* The exported symbol */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "echo/drop",
+ .rgwp_conf_parse = ed_conf_parse,
+ .rgwp_conf_free = ed_conf_free,
+ .rgwp_rad_req = ed_rad_req,
+ .rgwp_diam_ans = ed_diam_ans
+};
diff --git a/extensions/app_radgw/rgwx_echodrop.h b/extensions/app_radgw/rgwx_echodrop.h
new file mode 100644
index 0000000..db84fbd
--- /dev/null
+++ b/extensions/app_radgw/rgwx_echodrop.h
@@ -0,0 +1,84 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2011, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* RADIUS translation plugin, to handle specific RADIUS attributes by either caching them and
+adding them to a corresponding RADIUS reply, or just dropping the attributes (no conversion to Diameter) */
+/* This extension is a kind of swiss army-knife for interoperability, must be used with care.
+ All attribute behaviors are specified through the configuration file of the extension
+ */
+
+#include "rgw_common.h"
+
+/* Action associated with an attribute */
+#define ACT_ECHO 1
+#define ACT_DROP 2
+
+/* Result of configuration parsing */
+struct rgwp_config {
+ /* list of attributes and associated actions */
+ struct fd_list attributes;
+
+ /* Handler to store the echo'ed attributes values */
+ struct session_handler * sess_hdl;
+};
+
+/* An item of the attribute list */
+struct ed_conf_attribute {
+ struct fd_list chain; /* link in the list */
+
+ struct {
+ unsigned action :2; /* ACT_ECHO or ACT_DROP */
+ unsigned vsa :1; /* Interpret as Vendor-Specific, and match the vendor id in addition to the code */
+ unsigned tlv :1; /* Interpret as Vendor-Specific with TLV format, and match the type also */
+ unsigned ext :1; /* Interpret as radius extended attribute, and match the ext-type also */
+ };
+
+ uint32_t vendor_id; /* vendor id to match when vsa = 1 */
+ uint16_t extype; /* Ext-Type value to match if ext = 1 */
+ uint8_t type; /* Type value to match if tlv = 1; */
+
+ uint8_t code; /* The attribute code, the list is ordered by this value */
+};
+
+/* For ECHO items, we save a list of these in the session */
+struct ed_saved_attribute {
+ struct fd_list chain;
+ struct radius_attr_hdr attr; /* copy of the attribute */
+ /* The data of the attribute follows ... */
+};
+
+
+/* The yacc parser */
+int ed_conffile_parse(char * conffile, struct rgwp_config *cs);
diff --git a/extensions/app_radgw/rgwx_echodrop.l b/extensions/app_radgw/rgwx_echodrop.l
new file mode 100644
index 0000000..aab2841
--- /dev/null
+++ b/extensions/app_radgw/rgwx_echodrop.l
@@ -0,0 +1,99 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Tokenizer for echo/drop plugin */
+
+%{
+#include "rgwx_echodrop.h"
+#include "rgwx_echodrop.tab.h"
+
+/* Update the column information */
+#define YY_USER_ACTION { \
+ yylloc->first_column = yylloc->last_column + 1; \
+ yylloc->last_column = yylloc->first_column + yyleng - 1; \
+}
+
+/* %option noinput ? */
+#define YY_NO_INPUT
+%}
+
+%option bison-bridge bison-locations
+%option noyywrap
+%option nounput
+
+%%
+
+<*>\n {
+ /* Update the line count */
+ yylloc->first_line++;
+ yylloc->last_line++;
+ yylloc->last_column=0;
+ }
+
+<*>([[:space:]]{-}[\n])+ ; /* Eat all spaces, not new lines */
+<*>#.*$ ; /* Eat all comments */
+
+[[:digit:]]+ {
+ /* Match an integer (not hexa) */
+ int ret = sscanf(yytext, "%d", &yylval->integer);
+ if (ret != 1) {
+ /* No matching: an error occurred */
+ fd_log_debug("[echodrop.rgwx] Unable to convert the value '%s' to a valid number: %s", yytext, strerror(errno));
+ return LEX_ERROR; /* trig an error in yacc parser */
+ /* Maybe we could REJECT instead of failing here? */
+ }
+ return INTEGER;
+ }
+
+(?i:"echo") { return TOK_ECHO; }
+(?i:"drop") { return TOK_DROP; }
+(?i:"code") { return TOK_CODE; }
+(?i:"vendor") { return TOK_VENDOR; }
+(?i:"tlv") { return TOK_TLV; }
+(?i:"ext") { return TOK_EXT; }
+
+ /* Valid single characters for yyparse */
+<*>[;] { return yytext[0]; }
+
+
+ /* Unrecognized token */
+<*>[[:alnum:]]+ | /* This rule is only useful to print a complete token in error messages */
+ /* Unrecognized character */
+<*>. {
+ fd_log_debug("[echodrop.rgwx] Unrecognized text on line %d col %d: '%s'.", yylloc->first_line, yylloc->first_column, yytext);
+ return LEX_ERROR;
+ }
+
+%%
diff --git a/extensions/app_radgw/rgwx_echodrop.y b/extensions/app_radgw/rgwx_echodrop.y
new file mode 100644
index 0000000..fccaf06
--- /dev/null
+++ b/extensions/app_radgw/rgwx_echodrop.y
@@ -0,0 +1,255 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Yacc parser for echo/drop plugin.
+See doc/echodrop.rgwx.conf.sample for description of the parsed format. */
+
+
+/* For development only : */
+%debug
+%error-verbose
+
+/* The parser receives the configuration file filename and the conf structure as parameter */
+%parse-param {char * conffile}
+%parse-param {struct rgwp_config *cs}
+
+/* Keep track of location */
+%locations
+%pure-parser
+
+%{
+#include "rgwx_echodrop.h"
+#include "rgwx_echodrop.tab.h" /* bison is not smart enough to define the YYLTYPE before including this code, so... */
+
+
+/* Forward declaration */
+int yyparse(char * conffile, struct rgwp_config *cs);
+
+/* The Lex parser prototype */
+int rgwx_echodroplex(YYSTYPE *lvalp, YYLTYPE *llocp);
+
+
+/* Parse the configuration file */
+int ed_conffile_parse(char * conffile, struct rgwp_config *cs)
+{
+ extern FILE * rgwx_echodropin;
+ int ret;
+
+ rgwx_echodropin = fopen(conffile, "r");
+ if ((rgwx_echodropin == NULL) && (*conffile != '/')) { /* We received a relative path, try adding DEFAULT_CONF_PATH prefix */
+ char * fullpath;
+ CHECK_MALLOC( fullpath = malloc( strlen(conffile) + strlen(DEFAULT_CONF_PATH) + 2 ) );
+ sprintf( fullpath, DEFAULT_CONF_PATH "/%s", conffile );
+ rgwx_echodropin = fopen(fullpath, "r");
+ free(fullpath);
+ }
+ if (rgwx_echodropin == NULL) {
+ ret = errno;
+ fd_log_debug("[echodrop.rgwx] Unable to open plugin configuration file %s for reading: %s", conffile, strerror(ret));
+ return ret;
+ }
+
+ ret = rgwx_echodropparse(conffile, cs);
+
+ fclose(rgwx_echodropin);
+
+ if (ret != 0) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/* Function to report the errors */
+void yyerror (YYLTYPE *ploc, char * conffile, struct rgwp_config *cs, char const *s)
+{
+ if (ploc->first_line != ploc->last_line)
+ fd_log_debug("%s:%d.%d-%d.%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_line, ploc->last_column, s);
+ else if (ploc->first_column != ploc->last_column)
+ fd_log_debug("%s:%d.%d-%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_column, s);
+ else
+ fd_log_debug("%s:%d.%d : %s", conffile, ploc->first_line, ploc->first_column, s);
+}
+
+static struct {
+ struct {
+ unsigned vendor :1;
+ unsigned tlv :1;
+ unsigned ext :1;
+ };
+ uint8_t type;
+ uint16_t extype;
+ uint32_t vendor_id;
+} attrinfo;
+
+
+%}
+
+/* Values returned by lex for tokens */
+%union {
+ unsigned integer; /* Value */
+}
+
+/* typed data */
+%token <integer> INTEGER
+%type <integer> action
+
+/* simple tokens */
+%token TOK_ECHO
+%token TOK_DROP
+%token TOK_CODE
+%token TOK_VENDOR
+%token TOK_TLV
+%token TOK_EXT
+
+/* In case of error in the lexical analysis */
+%token LEX_ERROR
+
+
+/* -------------------------------------- */
+%%
+
+ /* The grammar definition */
+conffile: /* empty grammar is OK */
+ | conffile attrdef
+ ;
+
+ /* An attribute line */
+attrdef: {
+ memset(&attrinfo, 0, sizeof(attrinfo));
+ }
+ action TOK_CODE INTEGER vendordef ';'
+ {
+ struct ed_conf_attribute * new;
+ struct fd_list * li;
+
+ if ($4 >= 256) {
+ yyerror (&yylloc, conffile, cs, "Too big value for attribute CODE");
+ YYERROR;
+ }
+
+ /* Create a new list item */
+ CHECK_MALLOC_DO( new = malloc(sizeof(struct ed_conf_attribute)),
+ {
+ yyerror (&yylloc, conffile, cs, "Memory allocation error");
+ YYERROR;
+ } );
+ memset(new, 0, sizeof(struct ed_conf_attribute));
+
+ fd_list_init(&new->chain, NULL);
+
+ new->action = $2;
+ new->vsa = attrinfo.vendor;
+ new->tlv = attrinfo.tlv;
+ new->ext = attrinfo.ext;
+
+ if (new->vsa)
+ new->vendor_id = attrinfo.vendor_id;
+ if (new->tlv)
+ new->type = attrinfo.type;
+ if (new->ext)
+ new->extype = attrinfo.extype;
+
+ new->code = $4;
+
+ /* Now place this attribute in the list */
+ for (li = cs->attributes.next; li != &cs->attributes; li = li->next) {
+ struct ed_conf_attribute *eca = (struct ed_conf_attribute *)li;
+ /* Order first by attribute code */
+ if (eca->code > new->code)
+ break;
+ if (eca->code < new->code)
+ continue;
+
+ /* Then by VSA flag */
+ if (! new->vsa)
+ break;
+ if (! eca->vsa)
+ continue;
+
+ /* Then by vendor value */
+ if (eca->vendor_id >= new->vendor_id)
+ break;
+ }
+
+ fd_list_insert_before(li, &new->chain);
+ }
+ ;
+
+ /* What to do with the specified attribute */
+action: TOK_ECHO
+ {
+ $$ = ACT_ECHO;
+ }
+ |
+ TOK_DROP
+ {
+ $$ = ACT_DROP;
+ }
+ ;
+
+ /* Vendor specifics, if any */
+vendordef: /* empty OK */
+ | TOK_VENDOR INTEGER specif
+ {
+ attrinfo.vendor_id = $2;
+ attrinfo.vendor = 1;
+ }
+ ;
+
+ /* Any additional specification ? */
+specif: /* empty OK */
+ | TOK_TLV INTEGER
+ {
+ if ($2 >= (1 << 8)) {
+ yyerror (&yylloc, conffile, cs, "Too big value for TLV type");
+ YYERROR;
+ }
+ attrinfo.type = $2;
+ attrinfo.tlv = 1;
+ }
+ | TOK_EXT INTEGER
+ {
+ if ($2 >= (1 << 16)) {
+ yyerror (&yylloc, conffile, cs, "Too big value for Ext-Type");
+ YYERROR;
+ }
+ attrinfo.extype = $2;
+ attrinfo.ext = 1;
+ yyerror (&yylloc, conffile, cs, "The EXT option is not supported in this version.");
+ YYERROR;
+ }
+ ;
+
diff --git a/extensions/app_radgw/rgwx_sample.c b/extensions/app_radgw/rgwx_sample.c
new file mode 100644
index 0000000..4e2cd03
--- /dev/null
+++ b/extensions/app_radgw/rgwx_sample.c
@@ -0,0 +1,97 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2013, WIDE Project and NICT *
+* All rights reserved. *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the WIDE Project or NICT nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of WIDE Project and *
+* NICT. *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+/* Sample radius/diameter gateway plugin, for developers to see the structure of a plugin. */
+
+#include "rgw_common.h"
+
+/* The state of this extension */
+struct rgwp_config {
+ /* In a real extension, we would store the parsed configuration file, and the states of the extension */
+ int init;
+
+ /* If needed to store information between sent Diameter request and received answer, the session is probably the best place.
+ See rgwx_echodrop for an example. */
+};
+
+/* The function called at plugin initialization */
+static int sample_conf_parse ( char * conf_file, struct rgwp_config ** state )
+{
+ TRACE_ENTRY("%p %p", conf_file, state);
+ CHECK_PARAMS(state);
+
+ CHECK_MALLOC( *state = malloc(sizeof(struct rgwp_config)) );
+
+ (*state)->init = 1;
+
+ return 0;
+}
+
+/* This function is called when the plugin is unloaded, to cleanup all the states */
+static void sample_conf_free(struct rgwp_config * state)
+{
+ TRACE_ENTRY("%p", state);
+ CHECK_PARAMS_DO( state, );
+ free(state);
+ return;
+}
+
+/* This function is called on incoming RADIUS messages. It should handle (some) RADIUS data and store into the Diameter message. */
+static int sample_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+ CHECK_PARAMS(cs);
+ TRACE_DEBUG(INFO, "RADIUS/Diameter Sample plugin received a new RADIUS message.");
+ return 0;
+}
+
+/* This function is called when a Diameter answer is coming back. It should remove the AVPs and add the attributes in the RADIUS message. */
+static int sample_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+ CHECK_PARAMS(cs);
+ TRACE_DEBUG(INFO, "RADIUS/Diameter Sample plugin received a new Diameter answer.");
+ return 0;
+}
+
+
+/* Finally, we declare the structure that will be loaded by main RADIUS/Diameter gateway extension */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "sample",
+ .rgwp_conf_parse = sample_conf_parse,
+ .rgwp_conf_free = sample_conf_free,
+ .rgwp_rad_req = sample_rad_req,
+ .rgwp_diam_ans = sample_diam_ans
+};
diff --git a/extensions/app_radgw/rgwx_sip.c b/extensions/app_radgw/rgwx_sip.c
new file mode 100644
index 0000000..b4fd91e
--- /dev/null
+++ b/extensions/app_radgw/rgwx_sip.c
@@ -0,0 +1,855 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Alexandre Westfahl <awestfahl@freediameter.net> *
+* * *
+* Copyright (c) 2013, WIDE Project and NICT *
+* Copyright (c) 2010, Alexandre Westfahl, Teraoka Laboratory (Keio University), and the WIDE Project. *
+* *
+* All rights reserved. *
+* Based on rgwx_auth plugin (Sebastien Decugis <sdecugis@freediameter.net>) *
+* *
+* Redistribution and use of this software in source and binary forms, with or without modification, are *
+* permitted provided that the following conditions are met: *
+* *
+* * Redistributions of source code must retain the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer. *
+* *
+* * Redistributions in binary form must reproduce the above *
+* copyright notice, this list of conditions and the *
+* following disclaimer in the documentation and/or other *
+* materials provided with the distribution. *
+* *
+* * Neither the name of the Teraoka Laboratory nor the *
+* names of its contributors may be used to endorse or *
+* promote products derived from this software without *
+* specific prior written permission of Teraoka Laboratory *
+* *
+* *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
+*********************************************************************************************************/
+
+
+/* RADIUS Access-Request messages translation plugin */
+
+#include "rgw_common.h"
+#include <string.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Other constants we use */
+#define AI_SIP 6 /* Diameter SIP application */
+#define CC_MULTIMEDIA_AUTH_REQUEST 286 /* MAR */
+#define CC_MULTIMEDIA_AUTH_ANSWER 286 /* MAA */
+#define ACV_ASS_STATE_MAINTAINED 0 /* STATE_MAINTAINED */
+#define ACV_ASS_NO_STATE_MAINTAINED 1 /* NO_STATE_MAINTAINED */
+#define ER_DIAMETER_SUCCESS_AUTH_SENT_SERVER_NOT_STORED 2008
+#define ER_DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED 2006
+
+
+
+/* This macro converts a RADIUS attribute to a Diameter AVP of type OctetString */
+#define CONV2DIAM_STR( _dictobj_ ) \
+ CHECK_PARAMS( attr->length >= sizeof(struct radius_attr_hdr) ); \
+ /* Create the AVP with the specified dictionary model */ \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - sizeof(struct radius_attr_hdr); \
+ value.os.data = (os0_t)(attr + 1); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ /* Add the AVP in the Diameter message. */ \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+#define CONV2DIAM_STR_AUTH( _dictobj_ ) \
+ CHECK_PARAMS( attr->length >= sizeof(struct radius_attr_hdr) ); \
+ /* Create the AVP with the specified dictionary model */ \
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ value.os.len = attr->length - sizeof(struct radius_attr_hdr); \
+ value.os.data = (os0_t)(attr + 1); \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ /* Add the AVP in the Diameter message. */ \
+ CHECK_FCT( fd_msg_avp_add ( auth, MSG_BRW_LAST_CHILD, avp) ); \
+
+/* Same thing, for scalar AVPs of 32 bits */
+#define CONV2DIAM_32B( _dictobj_ ) \
+ CHECK_PARAMS( attr->length == sizeof(struct radius_attr_hdr)+sizeof(uint32_t) );\
+ CHECK_FCT( fd_msg_avp_new ( cs->dict._dictobj_, 0, &avp ) ); \
+ { \
+ uint8_t * v = (uint8_t *)(attr + 1); \
+ value.u32 = (v[0] << 24) \
+ | (v[1] << 16) \
+ | (v[2] << 8) \
+ | v[3] ; \
+ } \
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); \
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); \
+
+
+
+
+
+/* The state we keep for this plugin */
+struct rgwp_config {
+ struct {
+ struct dict_object * Session_Id;
+ struct dict_object * Auth_Application_Id;
+ struct dict_object * Auth_Session_State;
+ struct dict_object * Origin_Host;
+ struct dict_object * Origin_Realm;
+ struct dict_object * Destination_Realm;
+ struct dict_object * SIP_AOR;
+ struct dict_object * SIP_Method;
+ struct dict_object * Destination_Host;
+ struct dict_object * User_Name;
+ struct dict_object * SIP_Server_URI;
+ struct dict_object * SIP_Number_Auth_Items;
+ struct dict_object * SIP_Authorization;
+ struct dict_object * SIP_Authentication_Scheme;
+ struct dict_object * SIP_Authentication_Info;
+ struct dict_object * SIP_Auth_Data_Item;
+ struct dict_object * Proxy_Info;
+ struct dict_object * Route_Record;
+ struct dict_object * Service_Type;
+ struct dict_object * Result_Code;
+ struct dict_object * Digest_URI;
+ struct dict_object * Digest_Nonce;
+ struct dict_object * Digest_CNonce;
+ struct dict_object * Digest_Nonce_Count;
+ struct dict_object * Digest_Realm;
+ struct dict_object * Digest_Response;
+ struct dict_object * Digest_Method;
+ struct dict_object * Digest_Response_Auth;
+ struct dict_object * Digest_Username;
+ struct dict_object * Digest_Algorithm;
+ struct dict_object * Digest_QOP;
+
+
+
+ } dict; /* cache of the dictionary objects we use */
+ char * confstr;
+ //Chained list of nonce
+ struct fd_list listnonce;
+ //This will be used to lock access to chained list
+ pthread_mutex_t nonce_mutex;
+};
+
+typedef struct noncechain noncechain;
+struct noncechain
+{
+ struct fd_list chain;
+ os0_t sid;
+ size_t sidlen;
+ os0_t nonce;
+ size_t noncelen;
+
+};
+
+static int nonce_add_element(os0_t nonce, size_t noncelen, os0_t sid, size_t sidlen, struct rgwp_config * state)
+{
+ CHECK_PARAMS(nonce && state && sid && sidlen && noncelen);
+
+ noncechain *newelt;
+ CHECK_MALLOC(newelt=malloc(sizeof(noncechain)));
+
+ CHECK_MALLOC(newelt->nonce= os0dup(nonce, noncelen));
+ newelt->noncelen=noncelen;
+
+ CHECK_MALLOC(newelt->sid=os0dup(sid, sidlen));
+ newelt->sidlen=sidlen;
+
+ fd_list_init(&newelt->chain,NULL);
+
+ CHECK_POSIX(pthread_mutex_lock(&state->nonce_mutex));
+ fd_list_insert_before(&state->listnonce,&newelt->chain);
+ CHECK_POSIX(pthread_mutex_unlock(&state->nonce_mutex));
+
+ return 0;
+}
+/*
+static void nonce_del_element(char * nonce, struct rgwp_config *state)
+{
+ struct fd_list * li;
+
+ CHECK_PARAMS_DO(nonce && state, return);
+
+ for(li=state->listnonce.next;li!=&state->listnonce;li=li->next)
+ {
+ noncechain *temp=(noncechain *)li;
+
+ if(strcmp(temp->nonce,nonce)==0)
+ {
+ fd_list_unlink (li);
+ free(temp->sid);
+ free(temp->nonce);
+ free(temp);
+ break;
+ }
+ }
+}
+*/
+//Retrieve sid from nonce
+static os0_t nonce_get_sid(os0_t nonce, size_t noncelen, size_t * sidlen, struct rgwp_config *state)
+{
+ struct fd_list * li;
+ os0_t sid=NULL;
+
+ CHECK_PARAMS_DO(nonce && state && noncelen && sidlen, return NULL);
+ *sidlen=0;
+
+ // **Start mutex
+ CHECK_POSIX_DO(pthread_mutex_lock(&state->nonce_mutex),);
+ for(li=state->listnonce.next;li!=&state->listnonce;li=li->next)
+ {
+ noncechain *temp=(noncechain *)li;
+
+ if (!fd_os_cmp(temp->nonce, temp->noncelen, nonce, noncelen))
+ {
+ fd_list_unlink (li);
+ sid=temp->sid;
+ *sidlen=temp->sidlen;
+ free(temp->nonce);
+ free(temp);
+ break;
+ }
+
+ }
+ CHECK_POSIX_DO(pthread_mutex_unlock(&state->nonce_mutex),);
+ // ***Stop mutex
+ return sid;
+}
+
+static void nonce_deletelistnonce(struct rgwp_config *state)
+{
+ // **Start mutex
+ CHECK_POSIX_DO(pthread_mutex_lock(&state->nonce_mutex),);
+ while(!(FD_IS_LIST_EMPTY(&state->listnonce)) )
+ {
+ noncechain *temp=(noncechain *)state->listnonce.next;
+
+ fd_list_unlink (&temp->chain);
+ free(temp->sid);
+ free(temp->nonce);
+ free(temp);
+
+ }
+ CHECK_POSIX_DO(pthread_mutex_unlock(&state->nonce_mutex),);
+ // ***Stop mutex
+}
+
+/* Initialize the plugin */
+static int sip_conf_parse(char * conffile, struct rgwp_config ** state)
+{
+ struct rgwp_config * new;
+ struct dict_object * app;
+
+
+ TRACE_ENTRY("%p %p", conffile, state);
+ CHECK_PARAMS( state );
+
+ CHECK_MALLOC( new = malloc(sizeof(struct rgwp_config)) );
+ memset(new, 0, sizeof(struct rgwp_config));
+
+ new->confstr = conffile;
+
+ /* Resolve all dictionary objects we use */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &new->dict.Session_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Application-Id", &new->dict.Auth_Application_Id, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Auth-Session-State", &new->dict.Auth_Session_State, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &new->dict.Origin_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &new->dict.Origin_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &new->dict.Destination_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-AOR", &new->dict.SIP_AOR, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Method", &new->dict.SIP_Method, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &new->dict.Destination_Host, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &new->dict.User_Name, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Server-URI", &new->dict.SIP_Server_URI, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Number-Auth-Items", &new->dict.SIP_Number_Auth_Items, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Authorization", &new->dict.SIP_Authorization, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Auth-Data-Item", &new->dict.SIP_Auth_Data_Item, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Authentication-Scheme", &new->dict.SIP_Authentication_Scheme, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "SIP-Authentication-Info", &new->dict.SIP_Authentication_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Proxy-Info", &new->dict.Proxy_Info, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &new->dict.Route_Record, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Result-Code", &new->dict.Result_Code, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-URI", &new->dict.Digest_URI, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Nonce", &new->dict.Digest_Nonce, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Method", &new->dict.Digest_Method, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-CNonce", &new->dict.Digest_CNonce, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Nonce-Count", &new->dict.Digest_Nonce_Count, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Realm", &new->dict.Digest_Realm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Response", &new->dict.Digest_Response, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Response-Auth", &new->dict.Digest_Response_Auth, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Username", &new->dict.Digest_Username, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-Algorithm", &new->dict.Digest_Algorithm, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Digest-QoP", &new->dict.Digest_QOP, ENOENT) );
+
+
+
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Session Initiation Protocol (SIP) Application", &app, ENOENT) );
+ CHECK_FCT( fd_disp_app_support ( app, NULL, 1, 0 ) );
+
+ //chained list
+ fd_list_init(&new->listnonce,NULL);
+ CHECK_POSIX(pthread_mutex_init(&new->nonce_mutex,NULL));
+
+ *state = new;
+ return 0;
+}
+
+/* deinitialize */
+static void sip_conf_free(struct rgwp_config * state)
+{
+ TRACE_ENTRY("%p", state);
+ CHECK_PARAMS_DO( state, return );
+
+ nonce_deletelistnonce(state);
+ CHECK_POSIX_DO(pthread_mutex_destroy(&state->nonce_mutex), /*continue*/);
+
+ free(state);
+ return;
+}
+
+/* Handle an incoming RADIUS request */
+static int sip_rad_req( struct rgwp_config * cs, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli )
+{
+ int idx;
+ int got_AOR = 0;
+ int got_Dusername = 0;
+ int got_Drealm = 0;
+ int got_Duri = 0;
+ int got_Dmethod = 0;
+ int got_Dnonce = 0;
+ int got_Dresponse = 0;
+ int got_Dalgorithm = 0;
+ os0_t sid = NULL;
+ size_t sidlen;
+ os0_t un=NULL;
+ size_t un_len;
+ size_t nattr_used = 0;
+ struct avp *auth_data=NULL, *auth=NULL, *avp = NULL;
+ union avp_value value;
+ struct session * sess;
+
+ TRACE_ENTRY("%p %p %p %p %p", cs, rad_req, rad_ans, diam_fw, cli);
+
+ CHECK_PARAMS(rad_req && (rad_req->hdr->code == RADIUS_CODE_ACCESS_REQUEST) && rad_ans && diam_fw && *diam_fw);
+
+ /*
+ RFC5090 RADIUS Extension Digest Application
+ */
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_fw, &sess, NULL) );
+ if (sess != NULL) {
+ TRACE_DEBUG(INFO,"INTERNAL ERROR: We are not supposed to receive a session in radSIP plugin.");
+ return EINVAL;
+ }
+
+ /* Check basic information is there */
+ for (idx = 0; idx < rad_req->attr_used; idx++) {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+
+
+ switch (attr->type) {
+
+ case RADIUS_ATTR_USER_NAME:
+ if (attr->length>sizeof(struct radius_attr_hdr))
+ {
+ TRACE_DEBUG(ANNOYING, "Found a User-Name attribute: '%.*s'", (int)(attr->length- sizeof(struct radius_attr_hdr)), (char *)(attr+1));
+ un = (os0_t)(attr + 1);
+ un_len =attr->length - sizeof(struct radius_attr_hdr);
+ }
+ break;
+ case RADIUS_ATTR_DIGEST_USERNAME:
+ got_Dusername = 1;
+ break;
+ case RADIUS_ATTR_DIGEST_REALM:
+ got_Drealm = 1;
+ break;
+ case RADIUS_ATTR_DIGEST_URI:
+ got_Duri = 1;
+ break;
+ case RADIUS_ATTR_DIGEST_METHOD:
+ got_Dmethod = 1;
+ break;
+ // case RADIUS_ATTR_DIGEST_QOP:
+ // got_Dqop = 1;
+ // break;
+ // case RADIUS_ATTR_DIGEST_NONCE_COUNT:
+ // got_Dnonce_count = 1;
+ // break;
+ case RADIUS_ATTR_DIGEST_NONCE:
+ got_Dnonce = 1;
+
+ sid=nonce_get_sid((os0_t)(attr+1), attr->length - sizeof(struct radius_attr_hdr), &sidlen, cs);
+ if(!sid)
+ {
+ TRACE_DEBUG(INFO,"We haven't found the session.'");
+ return EINVAL;
+ }
+ CHECK_FCT(fd_sess_fromsid_msg (sid, sidlen, &sess, NULL));
+ free(sid);
+
+
+ break;
+ // case RADIUS_ATTR_DIGEST_CNONCE:
+ // got_Dcnonce = 1;
+ // break;
+ case RADIUS_ATTR_DIGEST_RESPONSE:
+ got_Dresponse = 1;
+ break;
+ case RADIUS_ATTR_DIGEST_ALGORITHM:
+ got_Dalgorithm = 1;
+ break;
+ case RADIUS_ATTR_SIP_AOR:
+ got_AOR = 1;
+ break;
+ }
+ }
+ if(!un)
+ {
+ TRACE_DEBUG(INFO,"No Username in request");
+ return EINVAL;
+ }
+
+ /* Create the session if it is not already done */
+ if (!sess) {
+
+ DiamId_t fqdn;
+ size_t fqdn_len;
+ DiamId_t realm;
+ size_t realm_len;
+
+ /* Get information on the RADIUS client */
+ CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &fqdn_len, &realm, &realm_len) );
+
+ /* Create a new Session-Id. The format is: {fqdn;hi32;lo32;username;diamid} */
+ CHECK_MALLOC( sid = malloc(un_len + 1 /* ';' */ + fd_g_config->cnf_diamid_len + 1 /* '\0' */) );
+ sidlen = sprintf((char *)sid, "%.*s;%s", (int)un_len, un, fd_g_config->cnf_diamid);
+ CHECK_FCT( fd_sess_new(&sess, fqdn, fqdn_len, sid, sidlen) );
+ free(sid);
+ }
+
+ /* Now, add the Session-Id AVP at beginning of Diameter message */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) );
+ value.os.data = sid;
+ value.os.len = sidlen;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) );
+
+ TRACE_DEBUG(FULL, "[sip.rgwx] Translating new message for session '%s'...", sid);
+
+ /* Now add this session in the message */
+ CHECK_FCT( fd_msg_sess_set(*diam_fw, sess) );
+
+ /* Add the Destination-Realm AVP */
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Realm, 0, &avp ) );
+
+ int i = 0;
+
+ /* Is there an '@' in the user name? We don't care for decorated NAI here */
+ for (i = un_len - 2; i > 0; i--) {
+ if (un[i] == '@') {
+ i++;
+ break;
+ }
+ }
+
+ if (i == 0) {
+ /* Not found in the User-Name => we use the local domain of this gateway */
+ value.os.data = (os0_t)fd_g_config->cnf_diamrlm;
+ value.os.len = fd_g_config->cnf_diamrlm_len;
+ } else {
+ value.os.data = un + i;
+ value.os.len = un_len - i;
+ }
+
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+
+ /*
+ If the RADIUS Access-Request message does not
+ contain any Digest-* attribute, then the RADIUS client does not want
+ to apply HTTP Digest authentication, in which case, actions at the
+ gateway are outside the scope of this document.
+ */
+
+ if(!(got_Dmethod && got_Duri))
+ {
+ TRACE_DEBUG(INFO,"No Digest attributes in request, we drop it...");
+ return 1;
+ }
+
+ /* Add the appropriate command code & Auth-Application-Id */
+ {
+ struct msg_hdr * header = NULL;
+ CHECK_FCT( fd_msg_hdr ( *diam_fw, &header ) );
+ header->msg_flags = CMD_FLAG_REQUEST | CMD_FLAG_PROXIABLE;
+ header->msg_code = CC_MULTIMEDIA_AUTH_REQUEST;
+ header->msg_appl = AI_SIP;
+
+
+ /* Add the Auth-Application-Id */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Auth_Application_Id, 0, &avp ) );
+ value.i32 = header->msg_appl;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+ }
+ /*Add Auth_Session_State AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Auth_Session_State, 0, &avp ) );
+ value.i32 = ACV_ASS_NO_STATE_MAINTAINED;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+
+ /*Add SIP_Number_Auth_Items AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Number_Auth_Items, 0, &avp ) );
+ value.i32 = 1; //We just treat one auth per request in gateway
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ }
+
+ /* Add SIP_Auth_Data_Item AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Auth_Data_Item, 0, &auth_data ) );
+ }
+ /* Add SIP_Authentication_Scheme AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Authentication_Scheme, 0, &avp ) );
+ value.i32=0; //There is only Digest Auth in RFC for now
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( auth_data, MSG_BRW_LAST_CHILD, avp) );
+
+ }
+
+
+ /* Add SIP_Authorization AVP */
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Authorization, 0, &auth ) );
+ CHECK_FCT( fd_msg_avp_add ( auth_data, MSG_BRW_LAST_CHILD, auth) );
+ }
+
+ for (idx = 0; idx < rad_req->attr_used; idx++)
+ {
+ struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]);
+
+ switch (attr->type) {
+
+ case RADIUS_ATTR_USER_NAME:
+ CONV2DIAM_STR( User_Name );
+
+ if(!got_Dusername)
+ {
+ CONV2DIAM_STR_AUTH(Digest_Username);
+ got_Dusername=1;
+ }
+
+ break;
+
+ case RADIUS_ATTR_DIGEST_URI:
+
+ CONV2DIAM_STR_AUTH(Digest_URI);
+
+ //All of these attributes are required by Diameter but not defined in RFC5090 so we provide FAKE values (only in first exchange)
+ if(!got_AOR)
+ {
+ CONV2DIAM_STR( SIP_AOR );
+ got_AOR=1;
+ }
+ /*
+ We must provide a fake nonce because of RFC4740 problem
+ TODO: remove when RFC is updated
+ ==START of FAKE
+ */
+ if(!got_Dresponse)
+ {
+ CONV2DIAM_STR_AUTH(Digest_Response);
+ got_Dresponse=1;
+ }
+ /*
+ ==END of FAKE
+ */
+ if(!got_Drealm)
+ {
+ //We extract Realm from Digest_URI
+ DiamId_t realm=NULL;
+ size_t realm_len = 0;
+ os0_t temp;
+
+ temp = (os0_t)(attr + 1);
+
+ for (i=attr->length - sizeof(struct radius_attr_hdr) - 1; i>=0; i--) {
+ if (temp[i] == '@') {
+ realm = (DiamId_t)temp + i + 1;
+ CHECK_FCT_DO( fd_os_validate_DiameterIdentity(&realm, &realm_len, 1),
+ realm = NULL );
+ break;
+ }
+ }
+
+ if(realm!=NULL)
+ {
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Digest_Realm, 0, &avp ) );
+ value.os.data=(os0_t)realm;
+ value.os.len=realm_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( auth, MSG_BRW_LAST_CHILD, avp) );
+
+ //We add SIP-Server-URI AVP because SIP server is registrar (through gateway)
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Server_URI, 0, &avp ) );
+ value.os.data=(os0_t)realm;
+ value.os.len=realm_len;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+
+ free(realm);
+ }
+ else
+ {
+ TRACE_DEBUG(INFO, "Can't extract domain from URI, droping request...");
+ return 1;
+ }
+ got_Drealm=1;
+ }
+ break;
+
+ case RADIUS_ATTR_DIGEST_METHOD:
+ CONV2DIAM_STR(SIP_Method);
+ CONV2DIAM_STR_AUTH(Digest_Method);
+ break;
+ case RADIUS_ATTR_DIGEST_REALM:
+ CONV2DIAM_STR_AUTH(Digest_Realm);
+
+ //We add SIP-Server-URI AVP because SIP server is registrar (through gateway)
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.SIP_Server_URI, 0, &avp ) );
+ os0_t temp;
+ #define SIP_PREFIX "sip:"
+ size_t temp_len = attr->length - sizeof(struct radius_attr_hdr) + CONSTSTRLEN(SIP_PREFIX) + 1;
+ CHECK_MALLOC( temp = malloc(temp_len) );
+ temp_len = snprintf((char *)temp, temp_len, SIP_PREFIX "%.*s", (int)(attr->length - sizeof(struct radius_attr_hdr)), (char *)(attr + 1));
+
+ value.os.data=temp;
+ value.os.len=temp_len;
+
+ free(temp);
+
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) );
+ break;
+
+ case RADIUS_ATTR_DIGEST_USERNAME:
+ CONV2DIAM_STR_AUTH(Digest_Username);
+ break;
+
+ case RADIUS_ATTR_DIGEST_QOP:
+ CONV2DIAM_STR_AUTH( Digest_QOP );
+ break;
+ case RADIUS_ATTR_DIGEST_ALGORITHM:
+ CONV2DIAM_STR_AUTH( Digest_Algorithm );
+ break;
+ case RADIUS_ATTR_DIGEST_CNONCE:
+ CONV2DIAM_STR_AUTH( Digest_CNonce );
+ break;
+ case RADIUS_ATTR_DIGEST_NONCE:
+ CONV2DIAM_STR_AUTH( Digest_Nonce );
+ break;
+ case RADIUS_ATTR_DIGEST_NONCE_COUNT:
+ CONV2DIAM_STR_AUTH( Digest_Nonce_Count );
+ break;
+ case RADIUS_ATTR_DIGEST_RESPONSE:
+ CONV2DIAM_STR_AUTH( Digest_Response );
+ break;
+ case RADIUS_ATTR_SIP_AOR:
+ CONV2DIAM_STR( SIP_AOR );
+ break;
+
+ default:
+ if(!got_Dalgorithm)
+ {
+ //[Note 3] If Digest-Algorithm is missing, 'MD5' is assumed.
+ #define DIGEST_ALGO_MD5 "MD5"
+
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Digest_Algorithm, 0, &avp ) );
+
+ value.os.data = (os0_t)DIGEST_ALGO_MD5;
+ value.os.len = CONSTSTRLEN(DIGEST_ALGO_MD5) - 1;
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( auth, MSG_BRW_LAST_CHILD, avp) );
+ got_Dalgorithm=1;
+ }
+
+ if(!got_Dnonce)
+ {
+ //We give a fake nonce because it will be calculated at the server.
+ CHECK_FCT( fd_msg_avp_new ( cs->dict.Digest_Nonce, 0, &avp ) );
+ value.os.data=(unsigned char *)"nonce";
+ value.os.len=strlen((const char *)value.os.data);
+ CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) );
+ CHECK_FCT( fd_msg_avp_add ( auth, MSG_BRW_LAST_CHILD, avp) );
+ got_Dnonce=1;
+ }
+ break;
+
+ }
+ }
+
+
+ CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, auth_data) );
+
+ /* Update the radius message to remove all handled attributes */
+ rad_req->attr_used = nattr_used;
+
+ //fd_msg_dump_walk(1,*diam_fw);
+
+
+ return 0;
+}
+
+static int sip_diam_ans( struct rgwp_config * cs, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli )
+{
+
+
+ struct avp *avp, *next;
+ struct avp_hdr *ahdr;
+ //char buf[254]; /* to store some attributes values (with final '\0') */
+ struct session * sess;
+ os0_t sid = NULL;
+ size_t sidlen;
+
+ TRACE_ENTRY("%p %p %p %p", cs, diam_ans, rad_fw, cli);
+ CHECK_PARAMS(cs && diam_ans && *diam_ans && rad_fw && *rad_fw);
+
+
+ /* MACROS to help in the process: convert AVP data to RADIUS attributes. */
+ /* Control large attributes: _trunc_ = 0 => error; _trunc_ = 1 => truncate; _trunc = 2 => create several attributes */
+ #define CONV2RAD_STR( _attr_, _data_, _len_, _trunc_) { \
+ size_t __l = (size_t)(_len_); \
+ size_t __off = 0; \
+ TRACE_DEBUG(FULL, "Converting AVP to "#_attr_); \
+ if ((_trunc_) == 0) { \
+ CHECK_PARAMS( __l <= 253 ); \
+ } \
+ if ((__l > 253) && (_trunc_ == 1)) { \
+ TRACE_DEBUG(INFO, "[authSIP.rgwx] AVP truncated in "#_attr_); \
+ __l = 253; \
+ } \
+ do { \
+ size_t __w = (__l > 253) ? 253 : __l; \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (_data_) + __off, __w)); \
+ __off += __w; \
+ __l -= __w; \
+ } while (__l); \
+ }
+
+ #define CONV2RAD_32B( _attr_, _data_) { \
+ uint32_t __v = htonl((uint32_t)(_data_)); \
+ TRACE_DEBUG(FULL, "Converting AVP to "#_attr_); \
+ CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \
+ }
+
+ /* Search the different AVPs we handle here */
+ CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, *diam_ans, &sess, NULL) );
+ if (sess) {
+ CHECK_FCT( fd_sess_getsid(sess, &sid, &sidlen) );
+ }
+
+
+ /* Check the Diameter error code */
+ CHECK_FCT( fd_msg_search_avp (*diam_ans, cs->dict.Result_Code, &avp) );
+ ASSERT( avp ); /* otherwise the message should have been discarded a lot earlier because of ABNF */
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+ switch (ahdr->avp_value->u32) {
+ case ER_DIAMETER_MULTI_ROUND_AUTH:
+ case ER_DIAMETER_SUCCESS_AUTH_SENT_SERVER_NOT_STORED:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_CHALLENGE;
+ //struct timespec nowts;
+ //CHECK_SYS(clock_gettime(CLOCK_REALTIME, &nowts));
+ //nowts.tv_sec+=600;
+ //CHECK_FCT(fd_sess_settimeout(session, &nowts ));
+ break;
+ case ER_DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED:
+ case ER_DIAMETER_SUCCESS:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_ACCEPT;
+ // in_success=1;
+ break;
+
+ default:
+ (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_REJECT;
+ fd_log_debug("[sip.rgwx] Received Diameter answer with error code '%d', session %.*s, translating into Access-Reject",
+ ahdr->avp_value->u32, (int)sidlen, sid);
+ return 0;
+ }
+ /* Remove this Result-Code avp */
+ CHECK_FCT( fd_msg_free( avp ) );
+
+ /* Now loop in the list of AVPs and convert those that we know how */
+ CHECK_FCT( fd_msg_browse(*diam_ans, MSG_BRW_FIRST_CHILD, &next, NULL) );
+
+ while (next) {
+ int handled = 1;
+ avp = next;
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_WALK, &next, NULL) );
+
+ CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) );
+
+ if (!(ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+ switch (ahdr->avp_code) {
+
+
+ case DIAM_ATTR_DIGEST_NONCE:
+ CONV2RAD_STR(DIAM_ATTR_DIGEST_NONCE, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ nonce_add_element(ahdr->avp_value->os.data, ahdr->avp_value->os.len, sid, sidlen, cs);
+ break;
+ case DIAM_ATTR_DIGEST_REALM:
+ CONV2RAD_STR(DIAM_ATTR_DIGEST_REALM, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+ case DIAM_ATTR_DIGEST_QOP:
+ CONV2RAD_STR(DIAM_ATTR_DIGEST_QOP, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+ case DIAM_ATTR_DIGEST_ALGORITHM:
+ CONV2RAD_STR(DIAM_ATTR_DIGEST_ALGORITHM, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 1);
+ break;
+ case DIAM_ATTR_DIGEST_RESPONSE_AUTH:
+ CONV2RAD_STR(DIAM_ATTR_DIGEST_RESPONSE_AUTH, ahdr->avp_value->os.data, ahdr->avp_value->os.len, 0);
+ break;
+ default:
+ handled=0;
+ break;
+ }
+ }
+ else
+ {
+ /* Vendor-specific AVPs */
+ switch (ahdr->avp_vendor) {
+
+ default: /* unknown vendor */
+ handled = 0;
+ }
+ }
+ if (handled) {
+ CHECK_FCT( fd_msg_free( avp ) );
+ }
+ }
+
+ return 0;
+}
+
+/* The exported symbol */
+struct rgw_api rgwp_descriptor = {
+ .rgwp_name = "sip",
+ .rgwp_conf_parse = sip_conf_parse,
+ .rgwp_conf_free = sip_conf_free,
+ .rgwp_rad_req = sip_rad_req,
+ .rgwp_diam_ans = sip_diam_ans
+};
+