00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include "asterisk.h"
00032
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 278132 $")
00034
00035 #include "asterisk/paths.h"
00036 #include "asterisk/config.h"
00037 #include "asterisk/channel.h"
00038 #include "asterisk/cdr.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/utils.h"
00041 #include "asterisk/lock.h"
00042
00043 #define CSV_LOG_DIR "/cdr-csv"
00044 #define CSV_MASTER "/Master.csv"
00045
00046 #define DATE_FORMAT "%Y-%m-%d %T"
00047
00048 static int usegmtime = 0;
00049 static int accountlogs;
00050 static int loguniqueid = 0;
00051 static int loguserfield = 0;
00052 static int loaded = 0;
00053 static const char config[] = "cdr.conf";
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086 static char *name = "csv";
00087
00088 AST_MUTEX_DEFINE_STATIC(mf_lock);
00089 AST_MUTEX_DEFINE_STATIC(acf_lock);
00090
00091 static int load_config(int reload)
00092 {
00093 struct ast_config *cfg;
00094 struct ast_variable *var;
00095 const char *tmp;
00096 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00097
00098 if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
00099 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
00100 return 0;
00101 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00102 return 1;
00103
00104 accountlogs = 1;
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
00121 if ((tmp = ast_variable_retrieve(cfg, "csv", "accountlogs"))) {
00122 accountlogs = ast_true(tmp);
00123 if (accountlogs) {
00124 ast_debug(1, "logging in separate files per accountcode\n");
00125 }
00126 }
00127
00128 if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
00129 loguniqueid = ast_true(tmp);
00130 if (loguniqueid)
00131 ast_debug(1, "logging CDR field UNIQUEID\n");
00132 }
00133
00134 if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
00135 loguserfield = ast_true(tmp);
00136 if (loguserfield)
00137 ast_debug(1, "logging CDR user-defined field\n");
00138 }
00139
00140 ast_config_destroy(cfg);
00141 return 1;
00142 }
00143
00144 static int append_string(char *buf, const char *s, size_t bufsize)
00145 {
00146 int pos = strlen(buf), spos = 0, error = -1;
00147
00148 if (pos >= bufsize - 4)
00149 return -1;
00150
00151 buf[pos++] = '\"';
00152
00153 while(pos < bufsize - 3) {
00154 if (!s[spos]) {
00155 error = 0;
00156 break;
00157 }
00158 if (s[spos] == '\"')
00159 buf[pos++] = '\"';
00160 buf[pos++] = s[spos];
00161 spos++;
00162 }
00163
00164 buf[pos++] = '\"';
00165 buf[pos++] = ',';
00166 buf[pos++] = '\0';
00167
00168 return error;
00169 }
00170
00171 static int append_int(char *buf, int s, size_t bufsize)
00172 {
00173 char tmp[32];
00174 int pos = strlen(buf);
00175
00176 snprintf(tmp, sizeof(tmp), "%d", s);
00177
00178 if (pos + strlen(tmp) > bufsize - 3)
00179 return -1;
00180
00181 strncat(buf, tmp, bufsize - strlen(buf) - 1);
00182 pos = strlen(buf);
00183 buf[pos++] = ',';
00184 buf[pos++] = '\0';
00185
00186 return 0;
00187 }
00188
00189 static int append_date(char *buf, struct timeval when, size_t bufsize)
00190 {
00191 char tmp[80] = "";
00192 struct ast_tm tm;
00193
00194 if (strlen(buf) > bufsize - 3)
00195 return -1;
00196
00197 if (ast_tvzero(when)) {
00198 strncat(buf, ",", bufsize - strlen(buf) - 1);
00199 return 0;
00200 }
00201
00202 ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
00203 ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
00204
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
00213 append_string(buf, cdr->accountcode, bufsize);
00214
00215 append_string(buf, cdr->src, bufsize);
00216
00217 append_string(buf, cdr->dst, bufsize);
00218
00219 append_string(buf, cdr->dcontext, bufsize);
00220
00221 append_string(buf, cdr->clid, bufsize);
00222
00223 append_string(buf, cdr->channel, bufsize);
00224
00225 append_string(buf, cdr->dstchannel, bufsize);
00226
00227 append_string(buf, cdr->lastapp, bufsize);
00228
00229 append_string(buf, cdr->lastdata, bufsize);
00230
00231 append_date(buf, cdr->start, bufsize);
00232
00233 append_date(buf, cdr->answer, bufsize);
00234
00235 append_date(buf, cdr->end, bufsize);
00236
00237 append_int(buf, cdr->duration, bufsize);
00238
00239 append_int(buf, cdr->billsec, bufsize);
00240
00241 append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
00242
00243 append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
00244
00245 if (loguniqueid)
00246 append_string(buf, cdr->uniqueid, bufsize);
00247
00248 if(loguserfield)
00249 append_string(buf, cdr->userfield,bufsize);
00250
00251 if (strlen(buf) < bufsize - 5) {
00252
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
00265 if (strchr(acc, '/') || (acc[0] == '.')) {
00266 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
00267 return -1;
00268 }
00269
00270 snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
00271
00272 ast_mutex_lock(&acf_lock);
00273 if (!(f = fopen(tmp, "a"))) {
00274 ast_mutex_unlock(&acf_lock);
00275 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
00276 return -1;
00277 }
00278 fputs(s, f);
00279 fflush(f);
00280 fclose(f);
00281 ast_mutex_unlock(&acf_lock);
00282
00283 return 0;
00284 }
00285
00286
00287 static int csv_log(struct ast_cdr *cdr)
00288 {
00289 FILE *mf = NULL;
00290
00291 char buf[1024];
00292 char csvmaster[PATH_MAX];
00293 snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
00294 #if 0
00295 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);
00296 #endif
00297 if (build_csv_record(buf, sizeof(buf), cdr)) {
00298 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
00299 return 0;
00300 }
00301
00302
00303
00304
00305 ast_mutex_lock(&mf_lock);
00306 if ((mf = fopen(csvmaster, "a"))) {
00307 fputs(buf, mf);
00308 fflush(mf);
00309 fclose(mf);
00310 mf = NULL;
00311 ast_mutex_unlock(&mf_lock);
00312 } else {
00313 ast_mutex_unlock(&mf_lock);
00314 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
00315 }
00316
00317 if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
00318 if (writefile(buf, cdr->accountcode))
00319 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
00320 }
00321
00322 return 0;
00323 }
00324
00325 static int unload_module(void)
00326 {
00327 ast_cdr_unregister(name);
00328 loaded = 0;
00329 return 0;
00330 }
00331
00332 static int load_module(void)
00333 {
00334 int res;
00335
00336 if(!load_config(0))
00337 return AST_MODULE_LOAD_DECLINE;
00338
00339 if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
00340 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
00341 } else {
00342 loaded = 1;
00343 }
00344 return res;
00345 }
00346
00347 static int reload(void)
00348 {
00349 if (load_config(1)) {
00350 loaded = 1;
00351 } else {
00352 loaded = 0;
00353 ast_log(LOG_WARNING, "No [csv] section in cdr.conf. Unregistering backend.\n");
00354 ast_cdr_unregister(name);
00355 }
00356
00357 return 0;
00358 }
00359
00360 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
00361 .load = load_module,
00362 .unload = unload_module,
00363 .reload = reload,
00364 .load_pri = AST_MODPRI_CDR_DRIVER,
00365 );