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

Generated on Thu Apr 16 06:27:17 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6