Mon May 14 04:42:52 2007

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

Generated on Mon May 14 04:42:52 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1