Initial commit
Change-Id: I6a4444e3c193dae437cd7929f4c39aba7b749efa
diff --git a/extensions/app_acct/CMakeLists.txt b/extensions/app_acct/CMakeLists.txt
new file mode 100644
index 0000000..433ca5d
--- /dev/null
+++ b/extensions/app_acct/CMakeLists.txt
@@ -0,0 +1,38 @@
+# The app_acct extension
+PROJECT("Simple Accounting server" C)
+
+########################
+# Search for libpg (postgresql package)
+FIND_PACKAGE(PostgreSQL REQUIRED)
+INCLUDE_DIRECTORIES(${POSTGRESQL_INCLUDE_DIR})
+
+########################
+# Parser files
+BISON_FILE(acct_conf.y)
+FLEX_FILE(acct_conf.l)
+SET_SOURCE_FILES_PROPERTIES(lex.acct_conf.c acct_conf.tab.c PROPERTIES COMPILE_FLAGS "-I ${CMAKE_CURRENT_SOURCE_DIR}")
+
+# List of source files
+SET( APP_ACCT_SRC
+ app_acct.h
+ app_acct.c
+ acct_db.c
+ acct_records.c
+)
+SET( APP_ACCT_SRC_GEN
+ lex.acct_conf.c
+ acct_conf.tab.c
+ acct_conf.tab.h
+)
+
+# Compile as a module
+FD_ADD_EXTENSION(app_acct ${APP_ACCT_SRC} ${APP_ACCT_SRC_GEN})
+TARGET_LINK_LIBRARIES(app_acct ${POSTGRESQL_LIBRARIES})
+
+
+####
+## INSTALL section ##
+
+INSTALL(TARGETS app_acct
+ LIBRARY DESTINATION ${INSTALL_EXTENSIONS_SUFFIX}
+ COMPONENT freeDiameter-accounting-server)
diff --git a/extensions/app_acct/acct_conf.l b/extensions/app_acct/acct_conf.l
new file mode 100644
index 0000000..c25de1a
--- /dev/null
+++ b/extensions/app_acct/acct_conf.l
@@ -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. *
+*********************************************************************************************************/
+
+/* Lex extension's configuration parser.
+ *
+ */
+
+%{
+#include "app_acct.h"
+#include "acct_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; \
+}
+
+/* Avoid warning with newer flex */
+#define YY_NO_INPUT
+
+%}
+
+%option bison-bridge bison-locations
+%option noyywrap
+%option nounput
+
+/* Quoted string. Multilines do not match. */
+qstring \"[^\"\n]*\"
+
+%%
+
+ /* Update the line count */
+\n {
+ yylloc->first_line++;
+ yylloc->last_line++;
+ yylloc->last_column=0;
+ }
+
+ /* Eat all spaces but not new lines */
+([[:space:]]{-}[\n])+ ;
+ /* Eat all comments */
+#.*$ ;
+
+ /* Recognize any integer */
+[-]?[[:digit:]]+ {
+ /* Convert this to an integer value */
+ int ret=0;
+ ret = sscanf(yytext, "%i", &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;
+ }
+
+ /* Recognize quoted strings -- we do not support escaped \" in the string currently. */
+{qstring} {
+ /* Match a quoted string. Let's be very permissive. */
+ yylval->string = strdup(yytext+1);
+ if (!yylval->string) {
+ fd_log_debug("Unable to copy the string '%s': %s", yytext, strerror(errno));
+ TRACE_DEBUG(INFO, "strdup failed");
+ return LEX_ERROR; /* trig an error in yacc parser */
+ }
+ yylval->string[strlen(yytext) - 2] = '\0';
+ return QSTRING;
+ }
+
+ /* Recognize the tokens */
+(?i:"ConnInfo") {
+ return CONNINFO;
+ }
+
+(?i:"Table") {
+ return TABLE;
+ }
+
+(?i:"Timestamp_field") {
+ return TSFIELD;
+ }
+
+(?i:"Server_name_field") {
+ return SRVNFIELD;
+ }
+
+(?i:"field") {
+ return FIELD;
+ }
+
+(?i:"required") {
+ return REQUIRED;
+ }
+
+(?i:"multi") {
+ return MULTI;
+ }
+
+
+
+ /* Valid single characters for yyparse */
+[=;{}] { return yytext[0]; }
+
+ /* Unrecognized sequence, if it did not match any previous pattern */
+[^[:space:]"*=>;\n]+ {
+ 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_acct/acct_conf.y b/extensions/app_acct/acct_conf.y
new file mode 100644
index 0000000..f79f1fa
--- /dev/null
+++ b/extensions/app_acct/acct_conf.y
@@ -0,0 +1,336 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License) *
+* Author: Sebastien Decugis <sdecugis@freediameter.net> *
+* *
+* Copyright (c) 2015, 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_acct.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 "app_acct.h"
+#include "acct_conf.tab.h"
+
+#include <string.h>
+#include <errno.h>
+
+/* Forward declaration */
+int yyparse(char * conffile);
+
+/* The Lex parser prototype */
+int acct_conflex(YYSTYPE *lvalp, YYLTYPE *llocp);
+
+/* the global configuration */
+struct acct_conf * acct_config = NULL;
+
+/* Initialize the blank configuration structure */
+int acct_conf_init(void)
+{
+ TRACE_ENTRY();
+
+ CHECK_MALLOC( acct_config = malloc(sizeof(struct acct_conf)) );
+ memset(acct_config, 0, sizeof(struct acct_conf) );
+ fd_list_init(&acct_config->avps, NULL);
+
+ return 0;
+}
+
+/* Validate and eventually display the content of the configuration file for debug */
+int acct_conf_check(char * conffile)
+{
+ CHECK_PARAMS(acct_config);
+
+ if ( ! acct_config->conninfo) {
+ fd_log_debug("[app_acct] ERROR: 'ConnInfo' is missing in file '%s'."
+ " You can specify 'ConnInfo=\"\";' to use default parameters.", conffile);
+ return EINVAL;
+ }
+ if ( ! acct_config->tablename) {
+ fd_log_debug("[app_acct] ERROR: 'Table' value is missing in file '%s'.", conffile);
+ return EINVAL;
+ }
+
+ if (!TRACE_BOOL(FULL))
+ return 0;
+
+ struct fd_list * li;
+
+ fd_log_debug("[app_acct] Configuration dump:");
+ fd_log_debug(" Database:");
+ fd_log_debug(" ConnInfo ...... : '%s'", acct_config->conninfo ?: "<null>");
+ fd_log_debug(" Table name .... : '%s'", acct_config->tablename ?: "<null>");
+ fd_log_debug(" Timestamp field : '%s'", acct_config->tsfield ?: "<null>");
+ fd_log_debug(" Server name fld : '%s'", acct_config->srvnfield ?: "<null>");
+ fd_log_debug(" AVPs that will be saved to the database:");
+ for (li = acct_config->avps.next; li != &acct_config->avps; li = li->next) {
+ struct acct_conf_avp * a = (struct acct_conf_avp *)li;
+ fd_log_debug(" %-*s AVP%s saved in ", 30, a->avpname, a->required ? " [required]":"" );
+ if (a->multi) {
+ fd_log_debug("fields '%s[1..%d]' ", a->field?:a->avpname, a->multi);
+ } else {
+ fd_log_debug("field '%s' ", a->field?:a->avpname);
+ }
+ fd_log_debug("as ::%s", diam2db_types_mapping[a->avptype]);
+ }
+ fd_log_debug("[app_acct] Complete.");
+ return 0;
+}
+
+void acct_conf_free(void)
+{
+ TRACE_ENTRY();
+
+ if (!acct_config)
+ return;
+
+ /* Destroy the list */
+ while (!FD_IS_LIST_EMPTY(&acct_config->avps)) {
+ struct acct_conf_avp * a = (struct acct_conf_avp *)(acct_config->avps.next);
+ fd_list_unlink(&a->chain);
+ free(a->avpname);
+ free(a->field);
+ free(a);
+ }
+
+ /* destroy other data */
+ free(acct_config->conninfo);
+ free(acct_config->tablename);
+ free(acct_config->tsfield);
+ free(acct_config->srvnfield);
+
+ /* Done */
+ free(acct_config);
+ acct_config = NULL;
+}
+
+/* Parse the configuration file */
+int acct_conf_parse(char * conffile)
+{
+ extern FILE * acct_confin;
+ int ret;
+
+ TRACE_ENTRY("%p", conffile);
+
+ TRACE_DEBUG (FULL, "Parsing configuration file: %s...", conffile);
+
+ acct_confin = fopen(conffile, "r");
+ if (acct_confin == NULL) {
+ ret = errno;
+ fd_log_debug("Unable to open extension configuration file %s for reading: %s", conffile, strerror(ret));
+ return ret;
+ }
+
+ ret = yyparse(conffile);
+
+ fclose(acct_confin);
+
+ if (ret != 0) {
+ TRACE_DEBUG (INFO, "Unable to parse the configuration file.");
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+/* Function to report the errors */
+void yyerror (YYLTYPE *ploc, char * conffile, char const *s)
+{
+ LOG_E( "Error in configuration parsing");
+
+ if (ploc->first_line != ploc->last_line)
+ LOG_E("%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)
+ LOG_E("%s:%d.%d-%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_column, s);
+ else
+ LOG_E("%s:%d.%d : %s", conffile, ploc->first_line, ploc->first_column, s);
+}
+
+static struct acct_conf_avp avpdata;
+
+%}
+
+/* Values returned by lex for token */
+%union {
+ char *string; /* The string is allocated by strdup in lex.*/
+ int integer; /* Store integer values */
+}
+
+/* In case of error in the lexical analysis */
+%token LEX_ERROR
+
+/* Keywords */
+%token FIELD
+%token REQUIRED
+%token MULTI
+%token CONNINFO
+%token TABLE
+%token TSFIELD
+%token SRVNFIELD
+
+/* Tokens and types */
+/* A (de)quoted string (malloc'd in lex parser; it must be freed after use) */
+%token <string> QSTRING
+
+/* An integer value */
+%token <integer> INTEGER
+
+
+
+/* -------------------------------------- */
+%%
+
+ /* The grammar definition */
+conffile: /* empty grammar is OK for the parser (will be validated afterwards) */
+ | conffile avpline
+ | conffile conninfoline
+ | conffile tableline
+ | conffile tsfieldline
+ | conffile srvnfieldline
+ | conffile errors
+ {
+ yyerror(&yylloc, conffile, "An error occurred while parsing the configuration file.");
+ return EINVAL;
+ }
+ ;
+
+ /* Catch lexical and syntax errors */
+errors: LEX_ERROR
+ | error
+ ;
+
+ /* The tokens */
+avpline: {
+ memset(&avpdata, 0, sizeof(struct acct_conf_avp));
+ }
+ QSTRING avpcontents ';'
+ {
+ struct acct_conf_avp *new;
+ struct dict_object * dict;
+ struct dict_avp_data dictdata;
+
+ /* Validate the avp name first */
+ CHECK_FCT_DO( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME_ALL_VENDORS, $2, &dict, ENOENT),
+ { yyerror (&yylloc, conffile, "AVP definition not found in the dictionary. Was the appropriate dict_*.fdx extension loaded?"); YYERROR; } );
+ CHECK_FCT( fd_dict_getval( dict, &dictdata ));
+
+ /* Create a new entry */
+ CHECK_MALLOC_DO( new = malloc(sizeof(struct acct_conf_avp)),
+ { yyerror (&yylloc, conffile, "Out of memory"); YYERROR; } );
+
+ /* Retrieve all the data from avpcontents parsing (field, required, multi) */
+ memcpy(new, &avpdata, sizeof(struct acct_conf_avp));
+
+ /* Initialize the other data */
+ fd_list_init(&new->chain, NULL);
+ new->avpname = $2;
+ new->avpobj = dict;
+ new->avptype = dictdata.avp_basetype;
+
+ /* Add this new entry at the end of the list */
+ fd_list_insert_before( &acct_config->avps, &new->chain );
+ }
+ ;
+
+avpcontents: /* Empty content is fine */
+ | '=' '{' avpflagline '}'
+ ;
+
+avpflagline: /* Empty flags is also fine */
+ | avpflagline FIELD '=' QSTRING ';'
+ {
+ if (avpdata.field) {
+ yyerror (&yylloc, conffile, "Duplicate entry");
+ YYERROR;
+ }
+ avpdata.field = $4;
+ }
+ | avpflagline REQUIRED ';'
+ {
+ avpdata.required = 1;
+ }
+ | avpflagline MULTI '=' INTEGER ';'
+ {
+ avpdata.multi = (unsigned) $4;
+ }
+ ;
+
+conninfoline: CONNINFO '=' QSTRING ';'
+ {
+ if (acct_config->conninfo) {
+ yyerror (&yylloc, conffile, "Duplicate entry");
+ YYERROR;
+ }
+ acct_config->conninfo = $3;
+ }
+ ;
+
+tableline: TABLE '=' QSTRING ';'
+ {
+ if (acct_config->tablename) {
+ yyerror (&yylloc, conffile, "Duplicate entry");
+ YYERROR;
+ }
+ acct_config->tablename = $3;
+ }
+ ;
+
+tsfieldline: TSFIELD '=' QSTRING ';'
+ {
+ if (acct_config->tsfield) {
+ yyerror (&yylloc, conffile, "Duplicate entry");
+ YYERROR;
+ }
+ acct_config->tsfield = $3;
+ }
+ ;
+
+srvnfieldline: SRVNFIELD '=' QSTRING ';'
+ {
+ if (acct_config->srvnfield) {
+ yyerror (&yylloc, conffile, "Duplicate entry");
+ YYERROR;
+ }
+ acct_config->srvnfield = $3;
+ }
+ ;
diff --git a/extensions/app_acct/acct_db.c b/extensions/app_acct/acct_db.c
new file mode 100644
index 0000000..a444c94
--- /dev/null
+++ b/extensions/app_acct/acct_db.c
@@ -0,0 +1,341 @@
+/*********************************************************************************************************
+* 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. *
+*********************************************************************************************************/
+
+/* Database interface module */
+
+/* There is one connection to the db per thread.
+The connection is stored in the pthread_key_t variable */
+
+
+#include "app_acct.h"
+#include <libpq-fe.h>
+
+const char * diam2db_types_mapping[AVP_TYPE_MAX + 1] = {
+ "" /* AVP_TYPE_GROUPED */,
+ "bytea" /* AVP_TYPE_OCTETSTRING */,
+ "integer" /* AVP_TYPE_INTEGER32 */,
+ "bigint" /* AVP_TYPE_INTEGER64 */,
+ "integer" /* AVP_TYPE_UNSIGNED32 + cast */,
+ "bigint" /* AVP_TYPE_UNSIGNED64 + cast */,
+ "real" /* AVP_TYPE_FLOAT32 */,
+ "double precision" /* AVP_TYPE_FLOAT64 */
+};
+
+static const char * stmt = "acct_db_stmt";
+#ifndef TEST_DEBUG
+static
+#endif /* TEST_DEBUG */
+pthread_key_t connk;
+static char * sql = NULL; /* The buffer that will contain the SQL query */
+static int nbrecords = 0;
+
+
+/* Initialize the database context: connection to the DB, prepared statement to insert new records */
+int acct_db_init(void)
+{
+ struct acct_record_list emptyrecords;
+ struct fd_list * li;
+ size_t sql_allocd = 0; /* The malloc'd size of the buffer */
+ size_t sql_offset = 0; /* The actual data already written in this buffer */
+ int idx = 0;
+ PGresult * res;
+ PGconn *conn;
+ #define REALLOC_SIZE 1024 /* We extend the buffer by this amount */
+
+ TRACE_ENTRY();
+ CHECK_PARAMS( acct_config && acct_config->conninfo && acct_config->tablename );
+
+ CHECK_PARAMS_DO( PQisthreadsafe() == 1, {
+ fd_log_debug("You PostGreSQL installation is not thread-safe!");
+ return EINVAL;
+ } );
+
+ /* Use the information from acct_config to create the connection and prepare the query */
+ conn = PQconnectdb(acct_config->conninfo);
+
+ /* Check to see that the backend connection was successfully made */
+ if (PQstatus(conn) != CONNECTION_OK) {
+ fd_log_debug("Connection to database failed: %s", PQerrorMessage(conn));
+ acct_db_free();
+ return EINVAL;
+ }
+ if (PQprotocolVersion(conn) < 3) {
+ fd_log_debug("Database protocol version is too old, version 3 is required for prepared statements.");
+ acct_db_free();
+ return EINVAL;
+ }
+
+ TRACE_DEBUG(FULL, "Connection to database successful, server version %d.", PQserverVersion(conn));
+
+ /* Now, prepare the request object */
+
+ /* First, we build the list of AVP we will insert in the database */
+ CHECK_FCT( acct_rec_prepare(&emptyrecords) );
+
+ /* Now, prepare the text of the request */
+ CHECK_MALLOC(sql = malloc(REALLOC_SIZE));
+ sql_allocd = REALLOC_SIZE;
+
+ /* This macro hides the details of extending the buffer on each sprintf... */
+ #define ADD_EXTEND(args...) { \
+ size_t p; \
+ int loop = 0; \
+ do { \
+ p = snprintf(sql + sql_offset, sql_allocd - sql_offset, ##args); \
+ if (p >= sql_allocd - sql_offset) { \
+ /* Too short, extend the buffer and start again */ \
+ CHECK_MALLOC( sql = realloc(sql, sql_allocd + REALLOC_SIZE) ); \
+ sql_allocd += REALLOC_SIZE; \
+ loop++; \
+ ASSERT(loop < 100); /* detect infinite loops */ \
+ continue; \
+ } \
+ sql_offset += p; \
+ break; \
+ } while (1); \
+ }
+
+ /* This macro allows to add a value in the SQL buffer while escaping in properly */
+ #define ADD_ESCAPE(str) { \
+ char * __s = (char *)str; \
+ /* Check we have at least twice the size available +1 */ \
+ size_t p = strlen(__s); \
+ \
+ while (sql_allocd - sql_offset < 2 * p + 1) { \
+ /* Too short, extend the buffer */ \
+ CHECK_MALLOC( sql = realloc(sql, sql_allocd + REALLOC_SIZE) ); \
+ sql_allocd += REALLOC_SIZE; \
+ } \
+ \
+ /* Now add the escaped string */ \
+ p = PQescapeStringConn(conn, sql+sql_offset, __s, p, NULL); \
+ sql_offset += p; \
+ }
+
+ /* INSERT INTO table (tsfield, field1, field2, ...) VALUES (now, $1::bytea, $2::integer, ...) */
+ ADD_EXTEND("INSERT INTO %s (", acct_config->tablename);
+
+ if (acct_config->tsfield) {
+ ADD_EXTEND("\"");
+ ADD_ESCAPE(acct_config->tsfield);
+ ADD_EXTEND("\", ");
+ }
+
+ if (acct_config->srvnfield) {
+ ADD_EXTEND("\"");
+ ADD_ESCAPE(acct_config->srvnfield);
+ ADD_EXTEND("\", ");
+ }
+
+ for (li = emptyrecords.all.next; li != &emptyrecords.all; li = li->next) {
+ struct acct_record_item * i = (struct acct_record_item *)(li->o);
+ ADD_EXTEND("\"");
+ ADD_ESCAPE(i->param->field?:i->param->avpname);
+ if (i->index) {
+ ADD_EXTEND("%d", i->index);
+ }
+ if (li->next != &emptyrecords.all) {
+ ADD_EXTEND("\", ");
+ }
+ }
+
+ ADD_EXTEND("\") VALUES (");
+
+ if (acct_config->tsfield) {
+ ++idx;
+ ADD_EXTEND("$%d, ", idx);
+ }
+ if (acct_config->srvnfield) {
+ ADD_EXTEND("'");
+ ADD_ESCAPE(fd_g_config->cnf_diamid);
+ ADD_EXTEND("', ");
+ }
+
+ for (li = emptyrecords.all.next; li != &emptyrecords.all; li = li->next) {
+ struct acct_record_item * i = (struct acct_record_item *)(li->o);
+ ++idx;
+ ADD_EXTEND("$%d::%s", idx, diam2db_types_mapping[i->param->avptype]);
+
+ if (li->next != &emptyrecords.all) {
+ ADD_EXTEND(", ");
+ }
+ }
+
+ ADD_EXTEND(");");
+
+ TRACE_DEBUG(FULL, "Preparing the following SQL statement: '%s'", sql);
+ res = PQprepare(conn, stmt, sql, emptyrecords.nball, NULL);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+ TRACE_DEBUG(INFO, "Preparing statement '%s' failed: %s",
+ sql, PQerrorMessage(conn));
+ PQclear(res);
+ return EINVAL;
+ }
+ PQclear(res);
+ nbrecords = emptyrecords.nball;
+
+ acct_rec_empty(&emptyrecords);
+
+ CHECK_POSIX( pthread_key_create(&connk, (void (*)(void*))PQfinish) );
+ CHECK_POSIX( pthread_setspecific(connk, conn) );
+
+ /* Ok, ready */
+ return 0;
+}
+
+/* Terminate the connection to the DB */
+void acct_db_free(void)
+{
+ CHECK_POSIX_DO(pthread_key_delete(connk) , );
+ free(sql);
+}
+
+/* When a new message has been received, insert the content of the parsed mapping into the DB (using prepared statement) */
+int acct_db_insert(struct acct_record_list * records)
+{
+ char **val;
+ int *val_len;
+ int *val_isbin;
+ int idx = 0;
+ int size = 0;
+ PGresult *res;
+ struct fd_list *li;
+ PGconn *conn;
+ int new = 0;
+
+ TRACE_ENTRY("%p", records);
+ CHECK_PARAMS( records );
+
+ conn = pthread_getspecific(connk);
+ if (!conn) {
+ conn = PQconnectdb(acct_config->conninfo);
+ CHECK_POSIX( pthread_setspecific(connk, conn) );
+
+ new = 1;
+ }
+
+ /* First, check if the connection with the DB has not staled, and eventually try to fix it */
+ if (PQstatus(conn) != CONNECTION_OK) {
+ /* Attempt a reset */
+ PQreset(conn);
+ if (PQstatus(conn) != CONNECTION_OK) {
+ TRACE_DEBUG(INFO, "Lost connection to the database server, and attempt to reestablish it failed");
+ TODO("Terminate the freeDiameter instance completely?");
+ return ENOTCONN;
+ }
+ }
+
+ if (new) {
+ /* Create the prepared statement for this ocnnection, it is not shared */
+ res = PQprepare(conn, stmt, sql, nbrecords, NULL);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+ TRACE_DEBUG(INFO, "Preparing statement '%s' failed: %s",
+ sql, PQerrorMessage(conn));
+ PQclear(res);
+ return EINVAL;
+ }
+ PQclear(res);
+ }
+
+ size = acct_config->tsfield ? records->nball + 1 : records->nball;
+
+ /* Alloc the arrays of parameters */
+ CHECK_MALLOC( val = calloc(size, sizeof(const char *)) );
+ CHECK_MALLOC( val_len = calloc(size, sizeof(const int)) );
+ CHECK_MALLOC( val_isbin = calloc(size, sizeof(const int)) );
+
+ if (acct_config->tsfield) {
+ val[idx] = "now";
+ val_len[idx] = 3;
+ val_isbin[idx] = 0;
+ idx++;
+ }
+
+ /* Now write all the map'd records in these arrays */
+ for (li = records->all.next; li != &records->all; li = li->next) {
+ struct acct_record_item * r = (struct acct_record_item *)(li->o);
+ if (r->value) {
+ val_isbin[idx] = 1; /* We always pass binary parameters */
+ switch (r->param->avptype) {
+ case AVP_TYPE_OCTETSTRING:
+ val[idx] = (void *)(r->value->os.data);
+ val_len[idx] = r->value->os.len;
+ break;
+
+ case AVP_TYPE_INTEGER32:
+ case AVP_TYPE_UNSIGNED32:
+ case AVP_TYPE_FLOAT32:
+ r->scalar.v32 = htonl(r->value->u32);
+ val[idx] = &r->scalar.c;
+ val_len[idx] = sizeof(uint32_t);
+ break;
+
+ case AVP_TYPE_INTEGER64:
+ case AVP_TYPE_UNSIGNED64:
+ case AVP_TYPE_FLOAT64:
+ r->scalar.v64 = htonll(r->value->u64);
+ val[idx] = &r->scalar.c;
+ val_len[idx] = sizeof(uint64_t);
+ break;
+
+ default:
+ ASSERT(0); /* detect bugs */
+ }
+ }
+
+ idx++;
+ }
+
+ /* OK, now execute the SQL statement */
+ res = PQexecPrepared(conn, stmt, size, (const char * const *)val, val_len, val_isbin, 1 /* We actually don't care here */);
+
+ /* Done with the parameters */
+ free(val);
+ free(val_len);
+ free(val_isbin);
+
+ /* Now check the result code */
+ if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+ TRACE_DEBUG(INFO, "An error occurred while INSERTing in the database: %s", PQerrorMessage(conn));
+ PQclear(res);
+ return EINVAL; /* It was probably a mistake in configuration file... */
+ }
+ PQclear(res);
+
+ /* Ok, we are done */
+ return 0;
+}
+
+
diff --git a/extensions/app_acct/acct_records.c b/extensions/app_acct/acct_records.c
new file mode 100644
index 0000000..4f1b340
--- /dev/null
+++ b/extensions/app_acct/acct_records.c
@@ -0,0 +1,161 @@
+/*********************************************************************************************************
+* 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. *
+*********************************************************************************************************/
+
+/* Functions to create a list of AVPs according to the configuration file */
+
+#include "app_acct.h"
+
+/* Prepare a list of acct_record entries from the configuration, without mapping any value at this time */
+int acct_rec_prepare(struct acct_record_list * records)
+{
+ struct fd_list * li;
+ TRACE_ENTRY("%p", records);
+ CHECK_PARAMS( records && acct_config );
+
+ /* Prepare the records structure */
+ memset(records, 0, sizeof(struct acct_record_list));
+ fd_list_init(&records->all, records);
+ fd_list_init(&records->unmaped, records);
+
+ /* for each entry in the configuration */
+ for (li = acct_config->avps.next; li != &acct_config->avps; li = li->next) {
+ struct acct_conf_avp * a = (struct acct_conf_avp *)li;
+ struct acct_record_item * new;
+ int i = a->multi ? 1 : 0;
+ /* Create as many records as the 'multi' parameter requires */
+ do {
+ CHECK_MALLOC( new = malloc(sizeof(struct acct_record_item)) );
+ memset(new, 0, sizeof(struct acct_record_item));
+ fd_list_init(&new->chain, new);
+ fd_list_init(&new->unmapd, new);
+ new->param = a;
+ new->index = i;
+ fd_list_insert_before(&records->all, &new->chain);
+ fd_list_insert_before(&records->unmaped, &new->unmapd);
+ records->nball++;
+ records->nbunmap++;
+ i++;
+ } while (i <= a->multi);
+ }
+
+ return 0;
+}
+
+/* Find the AVPs from configuration inside a received message */
+int acct_rec_map(struct acct_record_list * records, struct msg * msg)
+{
+ struct avp * avp;
+
+ TRACE_ENTRY("%p %p", records, msg);
+
+ /* For each AVP in the message, search if we have a corresponding unmap'd record */
+ CHECK_FCT( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL) );
+ while (avp) {
+ struct fd_list * li;
+ struct dict_object * model;
+
+ CHECK_FCT( fd_msg_model(avp, &model) );
+ if (model != NULL) { /* we ignore the AVPs we don't recognize */
+
+ /* Search this model in the list */
+ for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
+ struct acct_record_item * r = (struct acct_record_item *)(li->o);
+ if (r->param->avpobj == model) {
+ /* It matches: save the AVP value and unlink this record from the unmap'd list */
+ struct avp_hdr * h;
+ CHECK_FCT( fd_msg_avp_hdr( avp, &h ) );
+ r->value = h->avp_value;
+ fd_list_unlink(&r->unmapd);
+ records->nbunmap -= 1;
+ break;
+ }
+ }
+
+ /* Continue only while there are some AVPs to map */
+ if (FD_IS_LIST_EMPTY(&records->unmaped))
+ break;
+ }
+
+ /* Go to next AVP in the message */
+ CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+ }
+
+ /* Done */
+ return 0;
+}
+
+/* Check that a mapped list is not empty and no required AVP is missing. Free the record list in case of error */
+int acct_rec_validate(struct acct_record_list * records)
+{
+ struct fd_list * li;
+ TRACE_ENTRY("%p", records);
+ CHECK_PARAMS( records );
+
+ /* Check at least one AVP was mapped */
+ if (records->nball == records->nbunmap) {
+ fd_log_debug("The received ACR does not contain any AVP from the configuration file."
+ " This is an invalid situation. Please fix your configuration file."
+ " One way to ensure this does not happen is to include Session-Id in the database.");
+ acct_rec_empty(records);
+ return EINVAL;
+ }
+
+ /* Now, check there is no required AVP unmap'd */
+ for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
+ struct acct_record_item * r = (struct acct_record_item *)(li->o);
+ if (r->param->required && (r->index <= 1)) {
+ fd_log_debug("The received ACR does not contain the required AVP '%s'.", r->param->avpname);
+ acct_rec_empty(records);
+ return EINVAL;
+ }
+ }
+
+ /* The record list is OK */
+ return 0;
+}
+
+/* Free all the items in an acct_record_list returned by acct_rec_prepare */
+void acct_rec_empty(struct acct_record_list * records)
+{
+ TRACE_ENTRY("%p", records);
+ CHECK_PARAMS_DO( records, return );
+
+ while (!FD_IS_LIST_EMPTY(&records->all)) {
+ struct acct_record_item * r = (struct acct_record_item *)(records->all.next);
+ fd_list_unlink( &r->chain );
+ fd_list_unlink( &r->unmapd );
+ free(r);
+ }
+}
diff --git a/extensions/app_acct/app_acct.c b/extensions/app_acct/app_acct.c
new file mode 100644
index 0000000..9465b75
--- /dev/null
+++ b/extensions/app_acct/app_acct.c
@@ -0,0 +1,155 @@
+/*********************************************************************************************************
+* 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. *
+*********************************************************************************************************/
+
+/* The simple Accounting server for freeDiameter */
+
+#include "app_acct.h"
+
+/* Mandatory AVPs for the Accounting-Answer (any value in adding all the other AVPs?) */
+static struct {
+ struct dict_object * Accounting_Record_Number;
+ struct dict_object * Accounting_Record_Type;
+} acct_dict;
+
+
+/* Callback for incoming Base Accounting Accounting-Request messages */
+static int acct_cb( struct msg ** msg, struct avp * avp, struct session * sess, void * opaque, enum disp_action * act)
+{
+ struct msg * m;
+ struct avp * a = NULL;
+ struct avp_hdr * art=NULL, *arn=NULL; /* We keep a pointer on the Accounting-Record-{Type, Number} AVPs from the query */
+ struct acct_record_list rl;
+
+ TRACE_ENTRY("%p %p %p %p", msg, avp, sess, act);
+ if (msg == NULL)
+ return EINVAL;
+
+ m = *msg;
+
+ /* Prepare a new record list */
+ CHECK_FCT( acct_rec_prepare( &rl ) );
+
+ /* Maps the AVPs from the query with this record list */
+ CHECK_FCT( acct_rec_map( &rl, m ) );
+
+ /* Check that at least one AVP was mapped */
+ CHECK_FCT( acct_rec_validate( &rl ) );
+
+ /* Now, save these mapped AVPs in the database */
+ CHECK_FCT( acct_db_insert( &rl ) );
+
+ acct_rec_empty( &rl );
+
+ /* OK, we can send a positive reply now */
+
+ /* Get Accounting-Record-{Number,Type} values */
+ CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Type, &a) );
+ if (a) {
+ CHECK_FCT( fd_msg_avp_hdr( a, &art ) );
+ }
+ CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Number, &a) );
+ if (a) {
+ CHECK_FCT( fd_msg_avp_hdr( a, &arn ) );
+ }
+
+ /* Create the answer message */
+ CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
+ m = *msg;
+
+ /* Set the Origin-Host, Origin-Realm, Result-Code AVPs */
+ CHECK_FCT( fd_msg_rescode_set( m, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
+
+ /* Add the mandatory AVPs in the ACA */
+ if (art) {
+ CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Type, 0, &a ) );
+ CHECK_FCT( fd_msg_avp_setvalue( a, art->avp_value ) );
+ CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
+ }
+ if (arn) {
+ CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Number, 0, &a ) );
+ CHECK_FCT( fd_msg_avp_setvalue( a, arn->avp_value ) );
+ CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
+ }
+
+ /* Send the answer */
+ *act = DISP_ACT_SEND;
+ return 0;
+}
+
+
+/* entry point */
+static int acct_entry(char * conffile)
+{
+ struct disp_when data;
+
+ TRACE_ENTRY("%p", conffile);
+
+#ifndef TEST_DEBUG /* We do this differently in the test scenario */
+ /* Initialize the configuration and parse the file */
+ CHECK_FCT( acct_conf_init() );
+ CHECK_FCT( acct_conf_parse(conffile) );
+ CHECK_FCT( acct_conf_check(conffile) );
+#endif /* TEST_DEBUG */
+
+ /* Now initialize the database module */
+ CHECK_FCT( acct_db_init() );
+
+ /* Search the AVPs we will need in this file */
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &acct_dict.Accounting_Record_Number, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &acct_dict.Accounting_Record_Type, ENOENT) );
+
+ /* Register the dispatch callbacks */
+ memset(&data, 0, sizeof(data));
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Base Accounting", &data.app, ENOENT) );
+ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Accounting-Request", &data.command, ENOENT) );
+ CHECK_FCT( fd_disp_register( acct_cb, DISP_HOW_CC, &data, NULL, NULL ) );
+
+ /* Advertise the support for the Diameter Base Accounting application in the peer */
+ CHECK_FCT( fd_disp_app_support ( data.app, NULL, 0, 1 ) );
+
+ return 0;
+}
+
+/* Unload */
+void fd_ext_fini(void)
+{
+ /* Close the db connection */
+ acct_db_free();
+
+ /* Destroy the configuration */
+ acct_conf_free();
+}
+
+EXTENSION_ENTRY("app_acct", acct_entry);
diff --git a/extensions/app_acct/app_acct.h b/extensions/app_acct/app_acct.h
new file mode 100644
index 0000000..a98a335
--- /dev/null
+++ b/extensions/app_acct/app_acct.h
@@ -0,0 +1,121 @@
+/*********************************************************************************************************
+* 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. *
+*********************************************************************************************************/
+
+/* Header file for the app_acct extension.
+ *
+ * This extension is a simple Diameter Accounting server.
+ *
+ * It receives the Diameter Accounting-Request message, and sends its content in a
+ * "buffer" postgreSQL database (see configuration sample file app_acct.conf.sample).
+ *
+ * The intent is that another application will then pick the records from this "buffer" database
+ * and process it accordingly to the requirements of the end application.
+ */
+
+#include <freeDiameter/extension.h>
+
+/* The structure corresponding to an AVP entry in the configuration file */
+struct acct_conf_avp {
+ /* Chain */
+ struct fd_list chain; /* link in the global list */
+
+ /* Raw information from the configuration file */
+ char *avpname; /* the name of the AVP, as appear in the configuration file */
+ char *field; /* the field name in the database, or NULL if it is the same as avpname */
+ int required; /* set to true if this AVP has to be in the message */
+ unsigned multi; /* the number of occurrences of this AVP we convert, or 0 if it was not specified */
+
+ /* Parsed information */
+ struct dict_object *avpobj; /* the dictionary object corresponding to this AVP */
+ enum dict_avp_basetype avptype; /* this info is extracted from avpobj. GROUPED avps are not allowed yet */
+};
+
+/* This is described only inside acct_db.c */
+struct acct_db;
+
+/* The complete configuration */
+struct acct_conf {
+ /* AVPs */
+ struct fd_list avps; /* the list of acct_conf_avp entries */
+
+ /* Raw information */
+ char *conninfo; /* the connection string to the database, that is passed as is to the database library */
+ char *tablename; /* the name of the table we are working with */
+ char *tsfield; /* the name of the timestamp field, or NULL if not required */
+ char *srvnfield; /* the name of the server name field, or NULL if not required */
+};
+
+/* A successfully parsed Accounting-Request produces a list of these: */
+struct acct_record_item {
+ struct fd_list chain; /* link with all others */
+ struct fd_list unmapd;/* link with only unmap'd records */
+ struct acct_conf_avp *param; /* the AVP entry this refers to. */
+ unsigned index; /* in case of multi */
+ union avp_value *value; /* If the AVP was found in the message, this points to its value. Otherwise, NULL */
+ union {
+ uint32_t v32 /* Storage area for network byte-order copy of the AVP value */;
+ uint64_t v64;
+ char c; /* pointer that is passed to the database */
+ } scalar;/* for scalar AVP (all types except OCTETSTRING) we copy in this area the value in network byte order */
+};
+
+/* The sentinel for a list of acct_record_items */
+struct acct_record_list {
+ struct fd_list all; /* The list of records */
+ int nball; /* The number of records in all */
+ struct fd_list unmaped;/* Only the records without a value */
+ int nbunmap;/* The number of unmap'd records */
+};
+
+/* Mapping of the data types between Diameter AVP and PQ types: */
+extern const char * diam2db_types_mapping[];
+
+/* In acct_conf.y */
+extern struct acct_conf * acct_config; /* the global configuration */
+int acct_conf_init(void);
+int acct_conf_parse(char * conffile);
+int acct_conf_check(char * conffile);
+void acct_conf_free(void);
+
+/* In acct_db.c */
+int acct_db_init(void);
+int acct_db_insert(struct acct_record_list * records);
+void acct_db_free(void);
+
+/* In acct_records.c */
+int acct_rec_prepare(struct acct_record_list * records);
+int acct_rec_map(struct acct_record_list * records, struct msg * msg);
+int acct_rec_validate(struct acct_record_list * records);
+void acct_rec_empty(struct acct_record_list * records);