Fri Sep 25 19:28:06 2009

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: 82344 $")
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 char *config = "cdr.conf";
00061 
00062 /* #define CSV_LOGUNIQUEID 1 */
00063 /* #define CSV_LOGUSERFIELD 1 */
00064 
00065 /*----------------------------------------------------
00066   The values are as follows:
00067 
00068 
00069   "accountcode",  accountcode is the account name of detail records, Master.csv contains all records *
00070          Detail records are configured on a channel basis, IAX and SIP are determined by user *
00071          Zap is determined by channel in zaptel.conf 
00072   "source",
00073   "destination",
00074   "destination context", 
00075   "callerid",
00076   "channel",
00077   "destination channel",   (if applicable)
00078   "last application",   Last application run on the channel 
00079   "last app argument",  argument to the last channel 
00080   "start time", 
00081   "answer time", 
00082   "end time", 
00083   duration,       Duration is the whole length that the entire call lasted. ie. call rx'd to hangup  
00084          "end time" minus "start time" 
00085   billable seconds,  the duration that a call was up after other end answered which will be <= to duration  
00086          "end time" minus "answer time" 
00087   "disposition",     ANSWERED, NO ANSWER, BUSY 
00088   "amaflags",        DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode. 
00089   "uniqueid",           unique call identifier 
00090   "userfield"     user field set via SetCDRUserField 
00091 ----------------------------------------------------------*/
00092 
00093 static char *name = "csv";
00094 
00095 AST_MUTEX_DEFINE_STATIC(mf_lock);
00096 AST_MUTEX_DEFINE_STATIC(acf_lock);
00097 
00098 static int load_config(void)
00099 {
00100    struct ast_config *cfg;
00101    struct ast_variable *var;
00102    const char *tmp;
00103 
00104    usegmtime = 0;
00105    loguniqueid = 0;
00106    loguserfield = 0;
00107    
00108    cfg = ast_config_load(config);
00109    
00110    if (!cfg) {
00111       ast_log(LOG_WARNING, "unable to load config: %s\n", config);
00112       return 0;
00113    } 
00114    
00115    var = ast_variable_browse(cfg, "csv");
00116    if (!var) {
00117       ast_config_destroy(cfg);
00118       return 0;
00119    }
00120    
00121    tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
00122    if (tmp) {
00123       usegmtime = ast_true(tmp);
00124       if (usegmtime) {
00125          ast_log(LOG_DEBUG, "logging time in GMT\n");
00126       }
00127    }
00128 
00129    tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
00130    if (tmp) {
00131       loguniqueid = ast_true(tmp);
00132       if (loguniqueid) {
00133          ast_log(LOG_DEBUG, "logging CDR field UNIQUEID\n");
00134       }
00135    }
00136 
00137    tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
00138    if (tmp) {
00139       loguserfield = ast_true(tmp);
00140       if (loguserfield) {
00141          ast_log(LOG_DEBUG, "logging CDR user-defined field\n");
00142       }
00143    }
00144 
00145    ast_config_destroy(cfg);
00146    return 1;
00147 }
00148 
00149 static int append_string(char *buf, char *s, size_t bufsize)
00150 {
00151    int pos = strlen(buf);
00152    int spos = 0;
00153    int error = 0;
00154    if (pos >= bufsize - 4)
00155       return -1;
00156    buf[pos++] = '\"';
00157    error = -1;
00158    while(pos < bufsize - 3) {
00159       if (!s[spos]) {
00160          error = 0;
00161          break;
00162       }
00163       if (s[spos] == '\"')
00164          buf[pos++] = '\"';
00165       buf[pos++] = s[spos];
00166       spos++;
00167    }
00168    buf[pos++] = '\"';
00169    buf[pos++] = ',';
00170    buf[pos++] = '\0';
00171    return error;
00172 }
00173 
00174 static int append_int(char *buf, int s, size_t bufsize)
00175 {
00176    char tmp[32];
00177    int pos = strlen(buf);
00178    snprintf(tmp, sizeof(tmp), "%d", s);
00179    if (pos + strlen(tmp) > bufsize - 3)
00180       return -1;
00181    strncat(buf, tmp, bufsize - strlen(buf) - 1);
00182    pos = strlen(buf);
00183    buf[pos++] = ',';
00184    buf[pos++] = '\0';
00185    return 0;
00186 }
00187 
00188 static int append_date(char *buf, struct timeval tv, size_t bufsize)
00189 {
00190    char tmp[80] = "";
00191    struct tm tm;
00192    time_t t;
00193    t = tv.tv_sec;
00194    if (strlen(buf) > bufsize - 3)
00195       return -1;
00196    if (ast_tvzero(tv)) {
00197       strncat(buf, ",", bufsize - strlen(buf) - 1);
00198       return 0;
00199    }
00200    if (usegmtime) {
00201       gmtime_r(&t,&tm);
00202    } else {
00203       ast_localtime(&t, &tm, NULL);
00204    }
00205    strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
00206    return append_string(buf, tmp, bufsize);
00207 }
00208 
00209 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
00210 {
00211 
00212    buf[0] = '\0';
00213    /* Account code */
00214    append_string(buf, cdr->accountcode, bufsize);
00215    /* Source */
00216    append_string(buf, cdr->src, bufsize);
00217    /* Destination */
00218    append_string(buf, cdr->dst, bufsize);
00219    /* Destination context */
00220    append_string(buf, cdr->dcontext, bufsize);
00221    /* Caller*ID */
00222    append_string(buf, cdr->clid, bufsize);
00223    /* Channel */
00224    append_string(buf, cdr->channel, bufsize);
00225    /* Destination Channel */
00226    append_string(buf, cdr->dstchannel, bufsize);
00227    /* Last Application */
00228    append_string(buf, cdr->lastapp, bufsize);
00229    /* Last Data */
00230    append_string(buf, cdr->lastdata, bufsize);
00231    /* Start Time */
00232    append_date(buf, cdr->start, bufsize);
00233    /* Answer Time */
00234    append_date(buf, cdr->answer, bufsize);
00235    /* End Time */
00236    append_date(buf, cdr->end, bufsize);
00237    /* Duration */
00238    append_int(buf, cdr->duration, bufsize);
00239    /* Billable seconds */
00240    append_int(buf, cdr->billsec, bufsize);
00241    /* Disposition */
00242    append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
00243    /* AMA Flags */
00244    append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
00245    /* Unique ID */
00246    if (loguniqueid)
00247       append_string(buf, cdr->uniqueid, bufsize);
00248    /* append the user field */
00249    if(loguserfield)
00250       append_string(buf, cdr->userfield,bufsize);  
00251    /* If we hit the end of our buffer, log an error */
00252    if (strlen(buf) < bufsize - 5) {
00253       /* Trim off trailing comma */
00254       buf[strlen(buf) - 1] = '\0';
00255       strncat(buf, "\n", bufsize - strlen(buf) - 1);
00256       return 0;
00257    }
00258    return -1;
00259 }
00260 
00261 static int writefile(char *s, char *acc)
00262 {
00263    char tmp[PATH_MAX];
00264    FILE *f;
00265    if (strchr(acc, '/') || (acc[0] == '.')) {
00266       ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
00267       return -1;
00268    }
00269    snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
00270 
00271    ast_mutex_lock(&acf_lock);
00272    f = fopen(tmp, "a");
00273    if (!f) {
00274       ast_mutex_unlock(&acf_lock);
00275       ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
00276       return -1;
00277    }
00278    fputs(s, f);
00279    fflush(f);
00280    fclose(f);
00281    ast_mutex_unlock(&acf_lock);
00282 
00283    return 0;
00284 }
00285 
00286 
00287 static int csv_log(struct ast_cdr *cdr)
00288 {
00289    FILE *mf = NULL;
00290    /* Make sure we have a big enough buf */
00291    char buf[1024];
00292    char csvmaster[PATH_MAX];
00293    snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
00294 #if 0
00295    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);
00296 #endif
00297    if (build_csv_record(buf, sizeof(buf), cdr)) {
00298       ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
00299    } else {
00300       /* because of the absolutely unconditional need for the
00301          highest reliability possible in writing billing records,
00302          we open write and close the log file each time */
00303       ast_mutex_lock(&mf_lock);
00304       mf = fopen(csvmaster, "a");
00305       if (mf) {
00306          fputs(buf, mf);
00307          fflush(mf); /* be particularly anal here */
00308          fclose(mf);
00309          mf = NULL;
00310          ast_mutex_unlock(&mf_lock);
00311       } else {
00312          ast_mutex_unlock(&mf_lock);
00313          ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
00314       }
00315 
00316       if (!ast_strlen_zero(cdr->accountcode)) {
00317          if (writefile(buf, cdr->accountcode))
00318             ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
00319       }
00320    }
00321    return 0;
00322 }
00323 
00324 static int unload_module(void)
00325 {
00326    ast_cdr_unregister(name);
00327    return 0;
00328 }
00329 
00330 static int load_module(void)
00331 {
00332    int res;
00333    
00334    if(!load_config())
00335       return AST_MODULE_LOAD_DECLINE;
00336 
00337    res = ast_cdr_register(name, ast_module_info->description, csv_log);
00338    if (res) {
00339       ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
00340    }
00341    return res;
00342 }
00343 
00344 static int reload(void)
00345 {
00346    load_config();
00347 
00348    return 0;
00349 }
00350 
00351 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
00352       .load = load_module,
00353       .unload = unload_module,
00354       .reload = reload,
00355           );

Generated on Fri Sep 25 19:28:06 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.5