Sat Apr 26 2014 22:01:29

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