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

Generated on Wed Oct 28 11:50:56 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6