Sat Aug 6 00:39:23 2011

Asterisk developer's documentation


cdr_csv.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * Includes code and algorithms from the Zapata library.
00009  *
00010  * See http://www.asterisk.org for more information about
00011  * the Asterisk project. Please do not directly contact
00012  * any of the maintainers of this project for assistance;
00013  * the project provides a web site, mailing lists and IRC
00014  * channels for your use.
00015  *
00016  * This program is free software, distributed under the terms of
00017  * the GNU General Public License Version 2. See the LICENSE file
00018  * at the top of the source tree.
00019  */
00020 
00021 /*! \file
00022  *
00023  * \brief Comma Separated Value CDR records.
00024  *
00025  * \author Mark Spencer <markster@digium.com>
00026  * 
00027  * \arg See also \ref AstCDR
00028  * \ingroup cdr_drivers
00029  */
00030 
00031 #include "asterisk.h"
00032 
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 130298 $")
00034 
00035 #include <sys/types.h>
00036 #include <stdio.h>
00037 #include <string.h>
00038 #include <errno.h>
00039 
00040 #include <stdlib.h>
00041 #include <unistd.h>
00042 #include <time.h>
00043 
00044 #include "asterisk/config.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/cdr.h"
00047 #include "asterisk/module.h"
00048 #include "asterisk/logger.h"
00049 #include "asterisk/utils.h"
00050 #include "asterisk/lock.h"
00051 
00052 #define CSV_LOG_DIR "/cdr-csv"
00053 #define CSV_MASTER  "/Master.csv"
00054 
00055 #define DATE_FORMAT "%Y-%m-%d %T"
00056 
00057 static int usegmtime = 0;
00058 static int loguniqueid = 0;
00059 static int loguserfield = 0;
00060 static int loaded = 0;
00061 static char *config = "cdr.conf";
00062 
00063 /* #define CSV_LOGUNIQUEID 1 */
00064 /* #define CSV_LOGUSERFIELD 1 */
00065 
00066 /*----------------------------------------------------
00067   The values are as follows:
00068 
00069 
00070   "accountcode",  accountcode is the account name of detail records, Master.csv contains all records *
00071          Detail records are configured on a channel basis, IAX and SIP are determined by user *
00072          DAHDI is determined by channel in chan_dahdi.conf 
00073   "source",
00074   "destination",
00075   "destination context", 
00076   "callerid",
00077   "channel",
00078   "destination channel",   (if applicable)
00079   "last application",   Last application run on the channel 
00080   "last app argument",  argument to the last channel 
00081   "start time", 
00082   "answer time", 
00083   "end time", 
00084   duration,       Duration is the whole length that the entire call lasted. ie. call rx'd to hangup  
00085          "end time" minus "start time" 
00086   billable seconds,  the duration that a call was up after other end answered which will be <= to duration  
00087          "end time" minus "answer time" 
00088   "disposition",     ANSWERED, NO ANSWER, BUSY 
00089   "amaflags",        DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode. 
00090   "uniqueid",           unique call identifier 
00091   "userfield"     user field set via SetCDRUserField 
00092 ----------------------------------------------------------*/
00093 
00094 static char *name = "csv";
00095 
00096 AST_MUTEX_DEFINE_STATIC(mf_lock);
00097 AST_MUTEX_DEFINE_STATIC(acf_lock);
00098 
00099 static int load_config(void)
00100 {
00101    struct ast_config *cfg;
00102    struct ast_variable *var;
00103    const char *tmp;
00104 
00105    usegmtime = 0;
00106    loguniqueid = 0;
00107    loguserfield = 0;
00108    
00109    cfg = ast_config_load(config);
00110    
00111    if (!cfg) {
00112       ast_log(LOG_WARNING, "unable to load config: %s\n", config);
00113       return 0;
00114    } 
00115    
00116    var = ast_variable_browse(cfg, "csv");
00117    if (!var) {
00118       ast_config_destroy(cfg);
00119       return 0;
00120    }
00121    
00122    tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
00123    if (tmp) {
00124       usegmtime = ast_true(tmp);
00125       if (usegmtime) {
00126          ast_log(LOG_DEBUG, "logging time in GMT\n");
00127       }
00128    }
00129 
00130    tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
00131    if (tmp) {
00132       loguniqueid = ast_true(tmp);
00133       if (loguniqueid) {
00134          ast_log(LOG_DEBUG, "logging CDR field UNIQUEID\n");
00135       }
00136    }
00137 
00138    tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
00139    if (tmp) {
00140       loguserfield = ast_true(tmp);
00141       if (loguserfield) {
00142          ast_log(LOG_DEBUG, "logging CDR user-defined field\n");
00143       }
00144    }
00145 
00146    ast_config_destroy(cfg);
00147    return 1;
00148 }
00149 
00150 static int append_string(char *buf, char *s, size_t bufsize)
00151 {
00152    int pos = strlen(buf);
00153    int spos = 0;
00154    int error = 0;
00155    if (pos >= bufsize - 4)
00156       return -1;
00157    buf[pos++] = '\"';
00158    error = -1;
00159    while(pos < bufsize - 3) {
00160       if (!s[spos]) {
00161          error = 0;
00162          break;
00163       }
00164       if (s[spos] == '\"')
00165          buf[pos++] = '\"';
00166       buf[pos++] = s[spos];
00167       spos++;
00168    }
00169    buf[pos++] = '\"';
00170    buf[pos++] = ',';
00171    buf[pos++] = '\0';
00172    return error;
00173 }
00174 
00175 static int append_int(char *buf, int s, size_t bufsize)
00176 {
00177    char tmp[32];
00178    int pos = strlen(buf);
00179    snprintf(tmp, sizeof(tmp), "%d", s);
00180    if (pos + strlen(tmp) > bufsize - 3)
00181       return -1;
00182    strncat(buf, tmp, bufsize - strlen(buf) - 1);
00183    pos = strlen(buf);
00184    buf[pos++] = ',';
00185    buf[pos++] = '\0';
00186    return 0;
00187 }
00188 
00189 static int append_date(char *buf, struct timeval tv, size_t bufsize)
00190 {
00191    char tmp[80] = "";
00192    struct tm tm;
00193    time_t t;
00194    t = tv.tv_sec;
00195    if (strlen(buf) > bufsize - 3)
00196       return -1;
00197    if (ast_tvzero(tv)) {
00198       strncat(buf, ",", bufsize - strlen(buf) - 1);
00199       return 0;
00200    }
00201    if (usegmtime) {
00202       gmtime_r(&t,&tm);
00203    } else {
00204       ast_localtime(&t, &tm, NULL);
00205    }
00206    strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
00207    return append_string(buf, tmp, bufsize);
00208 }
00209 
00210 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
00211 {
00212 
00213    buf[0] = '\0';
00214    /* Account code */
00215    append_string(buf, cdr->accountcode, bufsize);
00216    /* Source */
00217    append_string(buf, cdr->src, bufsize);
00218    /* Destination */
00219    append_string(buf, cdr->dst, bufsize);
00220    /* Destination context */
00221    append_string(buf, cdr->dcontext, bufsize);
00222    /* Caller*ID */
00223    append_string(buf, cdr->clid, bufsize);
00224    /* Channel */
00225    append_string(buf, cdr->channel, bufsize);
00226    /* Destination Channel */
00227    append_string(buf, cdr->dstchannel, bufsize);
00228    /* Last Application */
00229    append_string(buf, cdr->lastapp, bufsize);
00230    /* Last Data */
00231    append_string(buf, cdr->lastdata, bufsize);
00232    /* Start Time */
00233    append_date(buf, cdr->start, bufsize);
00234    /* Answer Time */
00235    append_date(buf, cdr->answer, bufsize);
00236    /* End Time */
00237    append_date(buf, cdr->end, bufsize);
00238    /* Duration */
00239    append_int(buf, cdr->duration, bufsize);
00240    /* Billable seconds */
00241    append_int(buf, cdr->billsec, bufsize);
00242    /* Disposition */
00243    append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
00244    /* AMA Flags */
00245    append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
00246    /* Unique ID */
00247    if (loguniqueid)
00248       append_string(buf, cdr->uniqueid, bufsize);
00249    /* append the user field */
00250    if(loguserfield)
00251       append_string(buf, cdr->userfield,bufsize);  
00252    /* If we hit the end of our buffer, log an error */
00253    if (strlen(buf) < bufsize - 5) {
00254       /* Trim off trailing comma */
00255       buf[strlen(buf) - 1] = '\0';
00256       strncat(buf, "\n", bufsize - strlen(buf) - 1);
00257       return 0;
00258    }
00259    return -1;
00260 }
00261 
00262 static int writefile(char *s, char *acc)
00263 {
00264    char tmp[PATH_MAX];
00265    FILE *f;
00266    if (strchr(acc, '/') || (acc[0] == '.')) {
00267       ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
00268       return -1;
00269    }
00270    snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
00271 
00272    ast_mutex_lock(&acf_lock);
00273    f = fopen(tmp, "a");
00274    if (!f) {
00275       ast_mutex_unlock(&acf_lock);
00276       ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
00277       return -1;
00278    }
00279    fputs(s, f);
00280    fflush(f);
00281    fclose(f);
00282    ast_mutex_unlock(&acf_lock);
00283 
00284    return 0;
00285 }
00286 
00287 
00288 static int csv_log(struct ast_cdr *cdr)
00289 {
00290    FILE *mf = NULL;
00291    /* Make sure we have a big enough buf */
00292    char buf[1024];
00293    char csvmaster[PATH_MAX];
00294    snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
00295 #if 0
00296    printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);
00297 #endif
00298    if (build_csv_record(buf, sizeof(buf), cdr)) {
00299       ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
00300    } else {
00301       /* because of the absolutely unconditional need for the
00302          highest reliability possible in writing billing records,
00303          we open write and close the log file each time */
00304       ast_mutex_lock(&mf_lock);
00305       mf = fopen(csvmaster, "a");
00306       if (mf) {
00307          fputs(buf, mf);
00308          fflush(mf); /* be particularly anal here */
00309          fclose(mf);
00310          mf = NULL;
00311          ast_mutex_unlock(&mf_lock);
00312       } else {
00313          ast_mutex_unlock(&mf_lock);
00314          ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
00315       }
00316 
00317       if (!ast_strlen_zero(cdr->accountcode)) {
00318          if (writefile(buf, cdr->accountcode))
00319             ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
00320       }
00321    }
00322    return 0;
00323 }
00324 
00325 static int unload_module(void)
00326 {
00327    ast_cdr_unregister(name);
00328    loaded = 0;
00329    return 0;
00330 }
00331 
00332 static int load_module(void)
00333 {
00334    int res;
00335    
00336    if(!load_config())
00337       return AST_MODULE_LOAD_DECLINE;
00338 
00339    res = ast_cdr_register(name, ast_module_info->description, csv_log);
00340    if (res) {
00341       ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
00342    } else {
00343       loaded = 1;
00344    }
00345    return res;
00346 }
00347 
00348 static int reload(void)
00349 {
00350    if (load_config()) {
00351       loaded = 1;
00352    } else {
00353       loaded = 0;
00354       ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
00355       ast_cdr_unregister(name);
00356    }
00357 
00358    return 0;
00359 }
00360 
00361 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
00362       .load = load_module,
00363       .unload = unload_module,
00364       .reload = reload,
00365           );

Generated on Sat Aug 6 00:39:23 2011 for Asterisk - the Open Source PBX by  doxygen 1.4.7