Mon Aug 31 12:30:02 2015

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

Generated on 31 Aug 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1