Selaa lähdekoodia

modules_k/acc: Implement CDR-based logging.

Full credits to Sven Knoblich, [email protected] .
Timo Reimann 14 vuotta sitten
vanhempi
commit
bbb6df5dff

+ 1 - 0
modules_k/acc/acc.h

@@ -69,6 +69,7 @@
 #define A_STATUS_LEN (sizeof(A_STATUS)-1)
 
 #define A_SEPARATOR_CHR ';'
+#define A_SEPARATOR_CHR_2 ' '
 #define A_EQ_CHR '='
 
 #define MAX_SYSLOG_SIZE  65536

+ 692 - 0
modules_k/acc/acc_cdr.c

@@ -0,0 +1,692 @@
+/*
+ * Accounting module
+ *
+ * Copyright (C) 2011 - Sven Knoblich 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/*! \file
+ * \ingroup acc
+ * \brief Acc:: File to handle CRD generation by the help of the dialog-module
+ *
+ * - Module: \ref acc
+ */
+
+/*! \defgroup acc ACC :: The Kamailio accounting Module
+ *
+ * The ACC module is used to account transactions information to
+ *  different backends like syslog, SQL, RADIUS and DIAMETER (beta
+ *  version).
+ *
+ */
+#include "../../modules/tm/tm_load.h"
+#include "../dialog/dlg_load.h"
+
+#include "acc_api.h"
+#include "acc_cdr.h"
+#include "acc_mod.h"
+#include "acc_extra.h"
+#include "acc.h"
+
+#include <sys/timeb.h>
+
+struct dlg_binds dlgb;
+struct acc_extra* cdr_extra = NULL;
+int cdr_facility = LOG_DAEMON;
+
+static const str start_id = { "sz", 2};
+static const str end_id = { "ez", 2};
+static const str duration_id = { "d", 1};
+static const str zero_duration = { "0", 1};
+static const struct timeb time_error = {0,0,0,0};
+static const char time_separator = {'.'};
+static const int milliseconds_max = 1000;
+static const unsigned int time_buffer_length = 256;
+static const str empty_string = { "", 0};
+
+// buffers which are used to collect the crd data for writing
+static str cdr_attrs[ MAX_CDR_CORE + MAX_CDR_EXTRA];
+static str cdr_value_array[ MAX_CDR_CORE + MAX_CDR_EXTRA];
+static int cdr_int_arr[ MAX_CDR_CORE + MAX_CDR_EXTRA];
+static char cdr_type_array[ MAX_CDR_CORE + MAX_CDR_EXTRA];
+
+extern struct tm_binds tmb;
+
+/* compare two times */
+static int is_time_equal( struct timeb first_time,
+                          struct timeb second_time)
+{
+    if( first_time.time == second_time.time &&
+        first_time.millitm == second_time.millitm &&
+        first_time.timezone == second_time.timezone &&
+        first_time.dstflag == second_time.dstflag )
+    {
+        return 1;
+    }
+
+    return 0;
+}
+
+/* write all basic information to buffers(e.g. start-time ...) */
+static int cdr_core2strar( struct dlg_cell* dlg,
+                           str* values,
+                           int* unused,
+                           char* types)
+{
+    str* start = NULL;
+    str* end = NULL;
+    str* duration = NULL;
+
+    if( !dlg || !values || !types)
+    {
+        LM_ERR( "invalid input parameter!\n");
+        return 0;
+    }
+
+    start = dlgb.get_dlg_var( dlg, (str*)&start_id);
+    end = dlgb.get_dlg_var( dlg, (str*)&end_id);
+    duration = dlgb.get_dlg_var( dlg, (str*)&duration_id);
+
+    values[0] = ( start != NULL ? *start : empty_string);
+    types[0] = ( start != NULL ? TYPE_STR : TYPE_NULL);
+
+    values[1] = ( end != NULL ? *end : empty_string);
+    types[1] = ( end != NULL ? TYPE_STR : TYPE_NULL);
+
+    values[2] = ( duration != NULL ? *duration : empty_string);
+    types[2] = ( duration != NULL ? TYPE_STR : TYPE_NULL);
+
+    return MAX_CDR_CORE;
+}
+
+/* collect all crd data and write it to a syslog */
+static int write_cdr( struct dlg_cell* dialog,
+                      struct sip_msg* message)
+{
+    static char cdr_message[ MAX_SYSLOG_SIZE];
+    static char* const cdr_message_end = cdr_message +
+                                         MAX_SYSLOG_SIZE -
+                                         2;// -2 because of the string ending '\n\0'
+    char* message_position = NULL;
+    int message_index = 0;
+    int counter = 0;
+
+    if( !dialog || !message)
+    {
+        LM_ERR( "dialog and/or message is/are empty!");
+        return -1;
+    }
+
+    /* get default values */
+    message_index = cdr_core2strar( dialog,
+                                    cdr_value_array,
+                                    cdr_int_arr,
+                                    cdr_type_array);
+
+    /* get extra values */
+    message_index += extra2strar( cdr_extra,
+                                  message,
+                                  cdr_value_array + message_index,
+                                  cdr_int_arr + message_index,
+                                  cdr_type_array + message_index);
+
+    for( counter = 0, message_position = cdr_message;
+         counter < message_index ;
+         counter++ )
+    {
+        const char* const next_message_end = message_position +
+                                             2 + // ', ' -> two letters
+                                             cdr_attrs[ counter].len +
+                                             1 + // '=' -> one letter
+                                             cdr_value_array[ counter].len;
+
+        if( next_message_end >= cdr_message_end ||
+            next_message_end < message_position)
+        {
+            LM_WARN("cdr message too long, truncating..\n");
+            message_position = cdr_message_end;
+            break;
+        }
+
+        if( counter > 0)
+        {
+            *(message_position++) = A_SEPARATOR_CHR;
+            *(message_position++) = A_SEPARATOR_CHR_2;
+        }
+
+        memcpy( message_position,
+                cdr_attrs[ counter].s,
+                cdr_attrs[ counter].len);
+
+        message_position += cdr_attrs[ counter].len;
+
+        *( message_position++) = A_EQ_CHR;
+
+        memcpy( message_position,
+                cdr_value_array[ counter].s,
+                cdr_value_array[ counter].len);
+
+        message_position += cdr_value_array[ counter].len;
+    }
+
+    /* terminating line */
+    *(message_position++) = '\n';
+    *(message_position++) = '\0';
+
+    LM_GEN2( cdr_facility, log_level, "%s", cdr_message);
+
+    return 0;
+}
+
+/* convert a string into a timeb struct */
+static struct timeb time_from_string( str* time_value)
+{
+    char* point_adresse = NULL;
+    int point_position = -1;
+
+    if( !time_value)
+    {
+        LM_ERR( "time_value is empty!");
+        return time_error;
+    }
+
+    point_adresse = strchr( time_value->s, time_separator);
+
+    if( !point_adresse)
+    {
+        LM_ERR( "failed to find separator('%c') in '%s'!\n",
+                time_separator,
+                time_value->s);
+        return time_error;
+    }
+
+    point_position = point_adresse-time_value->s + 1;
+
+    if( point_position >= strlen(time_value->s) ||
+        strchr(point_adresse + 1, time_separator))
+    {
+        LM_ERR( "invalid time-string '%s'\n", time_value->s);
+        return time_error;
+    }
+
+    return (struct timeb) { atoi( time_value->s),
+                            atoi( point_adresse + 1),
+                            0,
+                            0};
+}
+
+/* set the duration in the dialog struct */
+static int set_duration( struct dlg_cell* dialog)
+{
+    struct timeb start_timeb = time_error;
+    struct timeb end_timeb = time_error;
+    int milliseconds = -1;
+    int seconds = -1;
+    char buffer[ time_buffer_length];
+    int buffer_length = -1;
+    str duration_time = empty_string;
+
+    if( !dialog)
+    {
+        LM_ERR("dialog is empty!\n");
+        return -1;
+    }
+
+    start_timeb = time_from_string( dlgb.get_dlg_var( dialog, (str*)&start_id));
+    end_timeb  = time_from_string( dlgb.get_dlg_var( dialog, (str*)&end_id));
+
+    if( is_time_equal( start_timeb, time_error) ||
+        is_time_equal( end_timeb, time_error))
+    {
+        LM_ERR( "failed to extract time from start or/and end-time\n");
+        return -1;
+    }
+
+    if( start_timeb.millitm >= milliseconds_max ||
+        end_timeb.millitm >= milliseconds_max)
+    {
+        LM_ERR( "start-(%d) or/and end-time(%d) is out of the maximum of %d\n",
+                start_timeb.millitm,
+                end_timeb.millitm,
+                milliseconds_max);
+        return -1;
+    }
+
+    milliseconds = end_timeb.millitm < start_timeb.millitm ?
+                                ( milliseconds_max +
+                                  end_timeb.millitm -
+                                  start_timeb.millitm) :
+                                ( end_timeb.millitm - start_timeb.millitm);
+
+    seconds = end_timeb.time -
+              start_timeb.time -
+              ( end_timeb.millitm < start_timeb.millitm ? 1 : 0);
+
+    if( seconds < 0)
+    {
+        LM_ERR( "negativ seconds(%d) for duration calculated.\n", seconds);
+        return -1;
+    }
+
+    if( milliseconds < 0 || milliseconds >= milliseconds_max)
+    {
+        LM_ERR( "milliseconds %d are out of range 0 < x < %d.\n",
+                milliseconds,
+                milliseconds_max);
+        return -1;
+    }
+
+    buffer_length = snprintf( buffer,
+                              time_buffer_length,
+                              "%d%c%03d",
+                              seconds,
+                              time_separator,
+                              milliseconds);
+
+    if( buffer_length < 0)
+    {
+        LM_ERR( "failed to write to buffer.\n");
+        return -1;
+    }
+
+    duration_time = ( str){ buffer, buffer_length};
+
+    if( dlgb.set_dlg_var( dialog,
+                          (str*)&duration_id,
+                          (str*)&duration_time) != 0)
+    {
+        LM_ERR( "failed to set duration time");
+        return -1;
+    }
+
+    return 0;
+}
+
+/* set the current time as start-time in the dialog struct */
+static int set_start_time( struct dlg_cell* dialog)
+{
+    char buffer[ time_buffer_length];
+    struct timeb current_time = time_error;
+    int buffer_length = -1;
+    str start_time = empty_string;
+
+    if( !dialog)
+    {
+        LM_ERR("dialog is empty!\n");
+        return -1;
+    }
+
+    if( ftime( &current_time) < 0)
+    {
+        LM_ERR( "failed to get current time!\n");
+        return -1;
+    }
+
+    buffer_length = snprintf( buffer,
+                              time_buffer_length,
+                              "%d%c%03d",
+                              (int)current_time.time,
+                              time_separator,
+                              (int)current_time.millitm);
+
+    if( buffer_length < 0)
+    {
+        LM_ERR( "reach buffer size\n");
+        return -1;
+    }
+
+    start_time = (str){ buffer, buffer_length};
+
+    if( dlgb.set_dlg_var( dialog,
+                          (str*)&start_id,
+                          (str*)&start_time) != 0)
+    {
+        LM_ERR( "failed to set start time\n");
+        return -1;
+    }
+
+    if( dlgb.set_dlg_var( dialog,
+                          (str*)&end_id,
+                          (str*)&start_time) != 0)
+    {
+        LM_ERR( "failed to set initiation end time\n");
+        return -1;
+    }
+
+    if( dlgb.set_dlg_var( dialog,
+                          (str*)&duration_id,
+                          (str*)&zero_duration) != 0)
+    {
+        LM_ERR( "failed to set initiation duration time\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+/* set the current time as end-time in the dialog struct */
+static int set_end_time( struct dlg_cell* dialog)
+{
+    char buffer[ time_buffer_length];
+    struct timeb current_time = time_error;
+    int buffer_length = -1;
+    str end_time = empty_string;
+
+    if( !dialog)
+    {
+        LM_ERR("dialog is empty!\n");
+        return -1;
+    }
+
+    if( ftime( &current_time) < 0)
+    {
+        LM_ERR( "failed to set time!\n");
+        return -1;
+    }
+
+    buffer_length = snprintf( buffer,
+                              time_buffer_length,
+                              "%d%c%03d",
+                              (int)current_time.time,
+                              time_separator,
+                              (int)current_time.millitm);
+
+    if( buffer_length < 0)
+    {
+        LM_ERR( "failed to write buffer\n");
+        return -1;
+    }
+
+    end_time = ( str){ buffer, buffer_length};
+
+    if( dlgb.set_dlg_var( dialog,
+                          (str*)&end_id,
+                          (str*)&end_time) != 0)
+    {
+        LM_ERR( "failed to set start time");
+        return -1;
+    }
+
+    return 0;
+}
+
+/* callback for a confirmed (INVITE) dialog. */
+static void cdr_on_start( struct dlg_cell* dialog,
+                          int type,
+                          struct dlg_cb_params* params)
+{
+    if( !dialog || !params)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    if( cdr_start_on_confirmed == 0)
+    {
+        return;
+    }
+
+    if( set_start_time( dialog) != 0)
+    {
+        LM_ERR( "failed to set start time!\n");
+        return;
+    }
+}
+
+/* callback for a failure during a dialog. */
+static void cdr_on_failed( struct dlg_cell* dialog,
+                           int type,
+                           struct dlg_cb_params* params)
+{
+    struct sip_msg* msg = 0;
+
+    if( !dialog || !params)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    if( params->rpl && params->rpl != FAKED_REPLY)
+    {
+        msg = params->rpl;
+    }
+    else if( params->req)
+    {
+        msg = params->req;
+    }
+    else
+    {
+        LM_ERR( "request and response are invalid!");
+        return;
+    }
+
+    if( write_cdr( dialog, msg) != 0)
+    {
+        LM_ERR( "failed to write cdr!\n");
+        return;
+    }
+}
+
+/* callback for the finish of a dialog (reply to BYE). */
+void cdr_on_end_confirmed( struct dlg_cell* dialog,
+                        int type,
+                        struct dlg_cb_params* params)
+{
+    if( !dialog || !params || !params->req)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    if( write_cdr( dialog, params->req) != 0)
+    {
+        LM_ERR( "failed to write cdr!\n");
+        return;
+    }
+}
+
+/* callback for the end of a dialog (BYE). */
+static void cdr_on_end( struct dlg_cell* dialog,
+                        int type,
+                        struct dlg_cb_params* params)
+{
+    if( !dialog || !params || !params->req)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    if( set_end_time( dialog) != 0)
+    {
+        LM_ERR( "failed to set end time!\n");
+        return;
+    }
+
+    if( set_duration( dialog) != 0)
+    {
+        LM_ERR( "failed to set duration!\n");
+        return;
+    }
+}
+
+/* callback for a expired dialog. */
+static void cdr_on_expired( struct dlg_cell* dialog,
+                            int type,
+                            struct dlg_cb_params* params)
+{
+    if( !dialog || !params)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    LM_DBG("dialog '%p' expired!\n", dialog);
+}
+
+/* callback for the cleanup of a dialog. */
+static void cdr_on_destroy( struct dlg_cell* dialog,
+                            int type,
+                            struct dlg_cb_params* params)
+{
+    if( !dialog || !params)
+    {
+        LM_ERR("invalid values\n!");
+        return;
+    }
+
+    LM_DBG("dialog '%p' destroyed!\n", dialog);
+}
+
+/* callback for the creation of a dialog. */
+static void cdr_on_create( struct dlg_cell* dialog,
+                           int type,
+                           struct dlg_cb_params* params)
+{
+    if( !dialog || !params || !params->req)
+    {
+        LM_ERR( "invalid values\n!");
+        return;
+    }
+
+    if( cdr_enable == 0)
+    {
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_CONFIRMED, cdr_on_start, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog CONFIRM callback\n");
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_FAILED, cdr_on_failed, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog FAILED callback\n");
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_TERMINATED, cdr_on_end, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog TERMINATED callback\n");
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_TERMINATED_CONFIRMED, cdr_on_end_confirmed, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog TERMINATED CONFIRMED callback\n");
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_EXPIRED, cdr_on_expired, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog EXPIRED callback\n");
+        return;
+    }
+
+    if( dlgb.register_dlgcb( dialog, DLGCB_DESTROY, cdr_on_destroy, 0, 0) != 0)
+    {
+        LM_ERR("can't register create dialog DESTROY callback\n");
+        return;
+    }
+
+    LM_DBG("dialog '%p' created!", dialog);
+
+    if( set_start_time( dialog) != 0)
+    {
+        LM_ERR( "failed to set start time");
+        return;
+    }
+}
+/* convert the extra-data string into a list and store it */
+int set_cdr_extra( char* cdr_extra_value)
+{
+    struct acc_extra* extra = 0;
+    int counter = 0;
+
+    if( cdr_extra_value && ( cdr_extra = parse_acc_extra( cdr_extra_value))==0)
+    {
+        LM_ERR("failed to parse crd_extra param\n");
+        return -1;
+    }
+
+    /* fixed core attributes */
+    cdr_attrs[ counter++] = start_id;
+    cdr_attrs[ counter++] = end_id;
+    cdr_attrs[ counter++] = duration_id;
+
+    for(extra=cdr_extra; extra ; extra=extra->next)
+    {
+        cdr_attrs[ counter++] = extra->name;
+    }
+
+    return 0;
+}
+
+/* convert the facility-name string into a id and store it */
+int set_cdr_facility( char* cdr_facility_str)
+{
+    int facility_id = -1;
+
+    if( !cdr_facility_str)
+    {
+        LM_ERR( "facility is empty\n");
+        return -1;
+    }
+
+    facility_id = str2facility( cdr_facility_str);
+
+    if( facility_id == -1)
+    {
+        LM_ERR("invalid cdr facility configured\n");
+        return -1;
+    }
+
+    cdr_facility = facility_id;
+
+    return 0;
+}
+
+/* initialization of all necessary callbacks to track a dialog */
+int init_cdr_generation( void)
+{
+    if( load_dlg_api( &dlgb) != 0)
+    {
+        LM_ERR("can't load dialog API\n");
+        return -1;
+    }
+
+    if( dlgb.register_dlgcb( 0, DLGCB_CREATED, cdr_on_create, 0, 0) != 0)
+    {
+        LM_ERR("can't register create callback\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+/* convert the facility-name string into a id and store it */
+void destroy_cdr_generation( void)
+{
+    if( !cdr_extra)
+    {
+        return;
+    }
+
+    destroy_extras( cdr_extra);
+}

+ 49 - 0
modules_k/acc/acc_cdr.h

@@ -0,0 +1,49 @@
+/*
+ * Accounting module
+ *
+ * Copyright (C) 2011 - Sven Knoblich 1&1 Internet AG
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/*! \file
+ * \ingroup acc
+ * \brief Acc:: File to handle CRD generation by the help of the dialog-module
+ *
+ * - Module: \ref acc
+ */
+
+/*! \defgroup acc ACC :: The Kamailio accounting Module
+ *
+ * The ACC module is used to account transactions information to
+ *  different backends like syslog, SQL, RADIUS and DIAMETER (beta
+ *  version).
+ *
+ */
+
+#define MAX_CDR_CORE 3
+#define MAX_CDR_EXTRA 64
+
+
+int set_cdr_extra( char* cdr_extra_value);
+int set_cdr_facility( char* cdr_facility);
+int init_cdr_generation( void);
+void destroy_cdr_generation( void);
+
+
+

+ 46 - 1
modules_k/acc/acc_mod.c

@@ -73,6 +73,7 @@
 #include "acc_mod.h"
 #include "acc_extra.h"
 #include "acc_logic.h"
+#include "acc_cdr.h"
 
 #ifdef RAD_ACC
 #include "../../lib/kcore/radius.h"
@@ -110,7 +111,7 @@ struct acc_extra *leg_info = 0;
 
 
 /* ----- SYSLOG acc variables ----------- */
-/*! \name AccSyslogVariables  Syslog Variables */     
+/*! \name AccSyslogVariables  Syslog Variables */
 /*@{*/
 
 int log_flag = -1;
@@ -123,6 +124,16 @@ struct acc_extra *log_extra = 0; /*!< Log extra attributes */
 
 /*@}*/
 
+/* ----- CDR generation variables ------- */
+/*! \name AccCdrVariables  CDR Variables */
+/*@{*/
+
+int cdr_enable  = 0;
+int cdr_start_on_confirmed = 0;
+static char* cdr_facility_str = 0;
+static char* cdr_log_extra_str = 0;
+/*@{*/
+
 /* ----- RADIUS acc variables ----------- */
 /*! \name AccRadiusVariables  Radius Variables */     
 /*@{*/
@@ -230,6 +241,11 @@ static param_export_t params[] = {
 	{"log_level",            INT_PARAM, &log_level            },
 	{"log_facility",         STR_PARAM, &log_facility_str     },
 	{"log_extra",            STR_PARAM, &log_extra_str        },
+    /* cdr specific */
+    {"cdr_enable",           INT_PARAM, &cdr_enable                     },
+    {"cdr_start_on_confirmed", INT_PARAM, &cdr_start_on_confirmed   },
+    {"cdr_facility",         STR_PARAM, &cdr_facility_str                },
+    {"cdr_extra",            STR_PARAM, &cdr_log_extra_str              },
 #ifdef RAD_ACC
 	{"radius_config",        STR_PARAM, &radius_config        },
 	{"radius_flag",          INT_PARAM, &radius_flag          },
@@ -503,6 +519,35 @@ static int mod_init( void )
 
 	acc_log_init();
 
+    /* ----------- INIT CDR GENERATION ----------- */
+
+    if( cdr_enable < 0 || cdr_enable > 1)
+    {
+        LM_ERR("cdr_enable is out of rage\n");
+        return -1;
+    }
+
+    if( cdr_enable)
+    {
+        if( cdr_log_extra_str && set_cdr_extra( cdr_log_extra_str) != 0)
+        {
+            LM_ERR( "failed to set cdr extra '%s'\n", cdr_log_extra_str);
+            return -1;
+        }
+
+        if( cdr_facility_str && set_cdr_facility( cdr_facility_str) != 0)
+        {
+            LM_ERR( "failed to set cdr facility '%s'\n", cdr_facility_str);
+            return -1;
+        }
+
+        if( init_cdr_generation() != 0)
+        {
+            LM_ERR("failed to init cdr generation\n");
+            return -1;
+        }
+    }
+
 	/* ------------ SQL INIT SECTION ----------- */
 
 #ifdef SQL_ACC

+ 3 - 0
modules_k/acc/acc_mod.h

@@ -55,6 +55,9 @@ extern int log_level;
 extern int log_flag;
 extern int log_missed_flag;
 
+extern int cdr_enable;
+extern int cdr_start_on_confirmed;
+extern int cdr_log_facility;
 
 #ifdef RAD_ACC
 extern int radius_flag;

+ 16 - 0
modules_k/acc/doc/acc.xml

@@ -37,6 +37,12 @@
 		<affiliation><orgname>Voice Sistem SRL</orgname></affiliation>
 		<email>[email protected]</email>
 		</editor>
+		<editor>
+		<firstname>Sven</firstname>
+		<surname>Knoblich</surname>
+		<affiliation><orgname>1&amp;1 Internet AG</orgname></affiliation>
+		<email>[email protected]</email>
+		</editor>
 	</authorgroup>
 	<copyright>
 		<year>2002</year>
@@ -48,6 +54,16 @@
 		<year>2006</year>
 		<holder>Voice Sistem SRL</holder>
 	</copyright>
+	<copyright>
+		<year>2011</year>
+		<holder>1&amp;1 Internet AG</holder>
+	</copyright>
+	<revhistory>
+		<revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+		</revision>
+	</revhistory>
 	</bookinfo>
 	<toc></toc>
 

+ 192 - 11
modules_k/acc/doc/acc_admin.xml

@@ -84,15 +84,6 @@
 			types of routes.
 			</para>
 		</listitem>
-		<listitem>
-			<para>
-			There is no session/dialog accounting (yet) -- &kamailio; maintains
-			no sessions. If one needs to correlate INVITEs with BYEs for 
-			generating proper CDRs for example for purpose of billing, then 
-			it is better done in the entity which processes accounting 
-			information.
-			</para>
-		</listitem>
 		<listitem>
 			<para>
 			If a UA fails in middle of conversation, a proxy will never 
@@ -173,7 +164,7 @@ if (uri=~"sip:+40") /* calls to Romania */ {
 			the request, etc).
 			</para>
 		</section>
-		<section>
+		<section id="acc-def-syn">
 			<title>Definitions and syntax</title>
 			<para>
 			Selection of extra information is done via 
@@ -326,8 +317,127 @@ if (uri=~"sip:+40") /* calls to Romania */ {
 			</itemizedlist>
 		</section>
 	</section>
+	<section>
+		<title>Call Data Record generation</title>
+        <section>
+            <title>Overview</title>
+                <para>
+                It is possible to generate and log Call Data Records (CDRs) directly from &kamailio;
+                in addition to transaction-based logging. Apart from a basic set of CDR fields which
+                are always included (covering start time, end time, and duration), the approach allows
+                flexible specification of additional fields that should be taken into account using
+                the configuration script. This is very similar to how transaction-based logging may
+                be customized with the exception that CDRs rely on dialogs instead of transactions
+                to store relevant information during a call.
+                </para>
+
+                <para>
+                In order to set up CDR generation, you must enable the CDR switch and load the dialog
+                module. You probably also want to specify a set of pseudo-variables that define more
+                relevant CDR fields. Pseudo-variables may be assigned arbitrarily during script
+                execution, and the module will make sure that the variable content will be transformed
+                into a CDR by the end of the dialog.
+                </para>
+
+                <para>
+                To use CDR logging in a correct manner, you should only use the dialog-based
+                pseudo-variables (dlg_var) from the dialog module. This allows you to save values
+                right from the beginning through all requests and replies until termination of the
+                call. While not recommended, it is still possible to use other pseudo-variables as
+                well. Except for pseudo-variables valid in the call-final transaction, however,
+                information given will not be stored in the CDR as they cannot be accessed by the
+                end of the call when the CDR is logged.
+                </para>
+        </section>
+		<section id="cdr-extra-id">
+			<title>CDR Extra</title>
+					This section is similar to the <quote>LOG accounting</quote> part of
+					<xref linkend="ACC-extra-id"/>.
+			<section>
+				<title>Definitions and syntax</title>
+					<para>
+					Selection of extra information is done similar to the transaction extra
+					<xref linkend="acc-def-syn"/>.
+					</para>
+					<itemizedlist>
+						<listitem><para><emphasis>
+						cdr_log_extra = cdr_extra_definition (';'cdr_extra_definition)*
+						</emphasis></para></listitem>
+						<listitem><para><emphasis>
+						cdr_extra_definition = cdr_log_name '=' pseudo_variable
+						</emphasis></para></listitem>
+					</itemizedlist>
+					See also <xref linkend="cdr_log_extra"/>.
+					<para>
+					The full list of supported pseudo-variables in Sip-Router is
+					available at:
+					<ulink url="http://sip-router.org/wiki/cookbooks/pseudo-variables/devel">
+					http://sip-router.org/wiki/cookbooks/pseudo-variables/devel</ulink>
+					</para>
+			</section>
+		</section>
+		<section id="multi-cdr-call-legs">
+			<title>CDR with Multi Call-Legs</title>
+			<section>
+				<title>Overview</title>
+				<para>
+				As mentioned in <xref linkend="multi-call-legs"/>, a leg represents a parallel
+				or forwarded call. In contrast to the normal accounting the cdr logging uses dialogs
+				instead of transaction to log data. This may reduce the amount of information 
+				but it also make it possible to combine all important data in one cdr at 
+				once. A second mechanism to process multiple data-sets into one cdr is not further
+				necessary.
+				</para>
+			</section>
+			<section>
+				<title>Configuration</title>
+				<para>
+				When you route messages multiple times through your proxy (e.g. to
+				handle <quote>call-forwardings</quote>) you have to use detect_spirals
+				from the dialog modules. Otherwise the proxy can't identify and reuse existing
+				dialogs.
+				</para>
+				<para>
+				To get the correct call-forwarding-chain you have to store each cf* with the
+				corresponding caller and callee in a dialog based pseudo-variable (dlg_var)
+				(e.g. chain=B;cfa;C|C;cfnr;D). Additionally it is necessary to store the
+				caller and callee for each leg. All this helps to identify the involved
+				phone parners and forwarding chain. When you route such calls multiple times
+				to the same Proxy, you could store the caller and callee within an transaction
+				based avp and write it into the dialog based dlg_var pv during a 200 INVITE.
+				</para>
+				<section>
+					<title>Example for a spiraled Proxy</title>
+					<programlisting format="linespecific">
+# A calls B (transaction 1)
+$avp(caller)='A'
+$avp(callee)='B';
+$dlg_var(chain)='';
 
+# B cfa C (transaction 2)
+$avp(caller)='B'
+$avp(callee)='C';
+$dlg_var(chain)='B;cfu;C';
 
+# C cfnr D (transaction 3)
+$avp(caller)='C'
+$avp(callee)='D';
+$dlg_var(chain)=$dlg_var(chain) + "|" + "C;cfnr;D";
+
+# C confirms call (200 reply of transaction 2)
+$dlg_var(caller) = $avp(caller); #caller='B'
+$dlg_var(callee) = $avp(callee); #callee='C'
+					</programlisting>
+				</section>
+			</section>
+			<section>
+				<title>Logged data</title>
+				For each call, all dialog corresponding variables will be logged. After a call
+				is finished, the generated call data record information will be logged as string
+				(VAR1=xxx,VAR2=xxxx,...) to the syslog.
+			</section>
+		</section>
+	</section>
 	<section>
 		<title>Dependencies</title>
 		<section>
@@ -348,6 +458,11 @@ if (uri=~"sip:+40") /* calls to Romania */ {
 				<quote>detect_direction</quote> module parameter is enabled.
 				</para>
 				</listitem>
+				<listitem>
+				<para><emphasis>dialog</emphasis> -- Dialog, if
+				<quote>cdr_enable</quote> module parameter is enabled.
+				</para>
+				</listitem>
 			</itemizedlist>
 			</para>
 		</section>
@@ -572,7 +687,6 @@ modparam("acc", "log_facility", "LOG_DAEMON")
 </programlisting>
 		</example>
 	</section>
-
 	<section>
 		<title><varname>log_extra</varname> (string)</title>
 		<para>
@@ -962,6 +1076,73 @@ modparam("acc", "diameter_extra", "7846=$hdr(Content-type);7847=$avp(s:email)")
 </programlisting>
 		</example>
 	</section>
+	<section>
+		<title><varname>cdr_enable</varname> (integer)</title>
+		<para>
+		Should CDR-based logging be enabled?
+		</para>
+		<para>
+		0 - off (default)
+		1 - on
+		</para>
+		<example>
+		<title>cdr_enable example</title>
+		<programlisting format="linespecific">
+modparam("acc", "cdr_enable", 1)
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>cdr_start_when_confirmed</varname> (integer)</title>
+		<para>
+		Should the start time be taken from the time when the dialog is created,
+        or when the dialog is confirmed?
+		</para>
+		<para>
+		0 - use time of dialog creation (default).
+		1 - use time of dialog confirmation.
+		</para>
+		<example>
+		<title>cdr_start_when_confirmed example</title>
+		<programlisting format="linespecific">
+modparam("acc", "cdr_start_when_confirmed", 1)
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>cdr_log_facility</varname> (integer)</title>
+		<para>
+		Log facility to which CDR messages are issued to syslog.
+		This allows to easily seperate CDR-specific logging from
+		the other log messages.
+		</para>
+		<para>
+		Default value is LOG_DAEMON.
+		</para>
+		<example>
+		<title>cdr_log_facility example</title>
+		<programlisting format="linespecific">
+modparam("acc", "cdr_log_facility", "LOG_DAEMON")
+</programlisting>
+		</example>
+	</section>
+	<section id="cdr_log_extra">
+		<title><varname>cdr_log_extra</varname> (string)</title>
+		<para>
+		Set of pseudo-variables defining custom CDR fields. See
+        <xref linkend="cdr-extra-id"/> for more details.
+		</para>
+		<para>
+		Default value is NULL.
+		</para>
+		<example>
+		<title>cdr_log_extra example</title>
+		<programlisting format="linespecific">
+modparam("acc", "cdr_log_extra", "c1=$dlg_var(caller);c2=$dlg_var(callee)"
+</programlisting>
+		</example>
+	</section>
+
 	</section>
 
 	<section>