Asterisk - The Open Source Telephony Project  GIT-master-a24979a
cdr_csv.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Includes code and algorithms from the Zapata library.
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20 
21 /*!
22  * \file
23  * \brief Comma Separated Value CDR records.
24  *
25  * \author Mark Spencer <markster@digium.com>
26  *
27  * \arg See also \ref AstCDR
28  * \ingroup cdr_drivers
29  */
30 
31 /*! \li \ref cdr_csv.c uses the configuration file \ref cdr.conf
32  * \addtogroup configuration_file Configuration Files
33  */
34 
35 /*** MODULEINFO
36  <support_level>extended</support_level>
37  ***/
38 
39 #include "asterisk.h"
40 
41 #include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
42 #include "asterisk/config.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/cdr.h"
45 #include "asterisk/module.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/lock.h"
48 
49 #define CSV_LOG_DIR "/cdr-csv"
50 #define CSV_MASTER "/Master.csv"
51 
52 #define DATE_FORMAT "%Y-%m-%d %T"
53 
54 static int usegmtime = 0;
55 static int accountlogs = 1;
56 static int loguniqueid = 0;
57 static int loguserfield = 0;
58 static int loaded = 0;
59 static int newcdrcolumns = 0;
60 static const char config[] = "cdr.conf";
62 
63 /* #define CSV_LOGUNIQUEID 1 */
64 /* #define CSV_LOGUSERFIELD 1 */
65 
66 /*----------------------------------------------------
67  The values are as follows:
68 
69 
70  "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
71  Detail records are configured on a channel basis, IAX and SIP are determined by user *
72  DAHDI is determined by channel in dahdi.conf
73  "source",
74  "destination",
75  "destination context",
76  "callerid",
77  "channel",
78  "destination channel", (if applicable)
79  "last application", Last application run on the channel
80  "last app argument", argument to the last channel
81  "start time",
82  "answer time",
83  "end time",
84  duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
85  "end time" minus "start time"
86  billable seconds, the duration that a call was up after other end answered which will be <= to duration
87  "end time" minus "answer time"
88  "disposition", ANSWERED, NO ANSWER, BUSY
89  "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
90  "uniqueid", unique call identifier
91  "userfield" user field set via SetCDRUserField
92 ----------------------------------------------------------*/
93 
94 static char *name = "csv";
95 
97 
98 static int load_config(int reload)
99 {
100  struct ast_config *cfg;
101  struct ast_variable *v;
102  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
103 
104  if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
105  ast_log(LOG_WARNING, "unable to load config: %s\n", config);
106  return 0;
107  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
108  return 1;
109  }
110 
111  accountlogs = 1;
112  usegmtime = 0;
113  loguniqueid = 0;
114  loguserfield = 0;
115  newcdrcolumns = 0;
116 
117  if (!(v = ast_variable_browse(cfg, "csv"))) {
118  ast_config_destroy(cfg);
119  return 0;
120  }
121 
122  /* compute the location of the csv master file */
124  snprintf(file_csv_master, sizeof(file_csv_master),
127 
128  for (; v; v = v->next) {
129  if (!strcasecmp(v->name, "usegmtime")) {
130  usegmtime = ast_true(v->value);
131  } else if (!strcasecmp(v->name, "accountlogs")) {
132  /* Turn on/off separate files per accountcode. Default is on (as before) */
133  accountlogs = ast_true(v->value);
134  } else if (!strcasecmp(v->name, "loguniqueid")) {
135  loguniqueid = ast_true(v->value);
136  } else if (!strcasecmp(v->name, "loguserfield")) {
138  } else if (!strcasecmp(v->name, "newcdrcolumns")) {
140  }
141 
142  }
143  ast_config_destroy(cfg);
144  return 1;
145 }
146 
147 static int append_string(char *buf, const char *s, size_t bufsize)
148 {
149  int pos = strlen(buf), spos = 0, error = -1;
150 
151  if (pos >= bufsize - 4)
152  return -1;
153 
154  buf[pos++] = '\"';
155 
156  while(pos < bufsize - 3) {
157  if (!s[spos]) {
158  error = 0;
159  break;
160  }
161  if (s[spos] == '\"')
162  buf[pos++] = '\"';
163  buf[pos++] = s[spos];
164  spos++;
165  }
166 
167  buf[pos++] = '\"';
168  buf[pos++] = ',';
169  buf[pos++] = '\0';
170 
171  return error;
172 }
173 
174 static int append_int(char *buf, int s, size_t bufsize)
175 {
176  char tmp[32];
177  int pos = strlen(buf);
178 
179  snprintf(tmp, sizeof(tmp), "%d", s);
180 
181  if (pos + strlen(tmp) > bufsize - 3)
182  return -1;
183 
184  strncat(buf, tmp, bufsize - strlen(buf) - 1);
185  pos = strlen(buf);
186  buf[pos++] = ',';
187  buf[pos++] = '\0';
188 
189  return 0;
190 }
191 
192 static int append_date(char *buf, struct timeval when, size_t bufsize)
193 {
194  char tmp[80] = "";
195  struct ast_tm tm;
196 
197  if (strlen(buf) > bufsize - 3)
198  return -1;
199 
200  if (ast_tvzero(when)) {
201  strncat(buf, ",", bufsize - strlen(buf) - 1);
202  return 0;
203  }
204 
205  ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
206  ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
207 
208  return append_string(buf, tmp, bufsize);
209 }
210 
211 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
212 {
213 
214  buf[0] = '\0';
215  /* Account code */
216  append_string(buf, cdr->accountcode, bufsize);
217  /* Source */
218  append_string(buf, cdr->src, bufsize);
219  /* Destination */
220  append_string(buf, cdr->dst, bufsize);
221  /* Destination context */
222  append_string(buf, cdr->dcontext, bufsize);
223  /* Caller*ID */
224  append_string(buf, cdr->clid, bufsize);
225  /* Channel */
226  append_string(buf, cdr->channel, bufsize);
227  /* Destination Channel */
228  append_string(buf, cdr->dstchannel, bufsize);
229  /* Last Application */
230  append_string(buf, cdr->lastapp, bufsize);
231  /* Last Data */
232  append_string(buf, cdr->lastdata, bufsize);
233  /* Start Time */
234  append_date(buf, cdr->start, bufsize);
235  /* Answer Time */
236  append_date(buf, cdr->answer, bufsize);
237  /* End Time */
238  append_date(buf, cdr->end, bufsize);
239  /* Duration */
240  append_int(buf, cdr->duration, bufsize);
241  /* Billable seconds */
242  append_int(buf, cdr->billsec, bufsize);
243  /* Disposition */
245  /* AMA Flags */
247  /* Unique ID */
248  if (loguniqueid)
249  append_string(buf, cdr->uniqueid, bufsize);
250  /* append the user field */
251  if(loguserfield)
252  append_string(buf, cdr->userfield, bufsize);
253  if (newcdrcolumns) {
254  append_string(buf, cdr->peeraccount, bufsize);
255  append_string(buf, cdr->linkedid, bufsize);
256  append_int(buf, cdr->sequence, bufsize);
257  }
258  /* If we hit the end of our buffer, log an error */
259  if (strlen(buf) < bufsize - 5) {
260  /* Trim off trailing comma */
261  buf[strlen(buf) - 1] = '\0';
262  strncat(buf, "\n", bufsize - strlen(buf) - 1);
263  return 0;
264  }
265  return -1;
266 }
267 
268 static int writefile(char *s, char *file_path)
269 {
270  FILE *f;
271  /* because of the absolutely unconditional need for the
272  highest reliability possible in writing billing records,
273  we open write and close the log file each time */
274  if (!(f = fopen(file_path, "a"))) {
275  ast_log(LOG_ERROR, "Unable to open file %s : %s\n", file_path, strerror(errno));
276  return -1;
277  }
278  fputs(s, f);
279  fflush(f); /* be particularly anal here */
280  fclose(f);
281 
282  return 0;
283 }
284 
285 
286 static int writefile_account(char *s, char *acc)
287 {
288  char file_account[PATH_MAX];
289  if (strchr(acc, '/') || (acc[0] == '.')) {
290  ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
291  return -1;
292  }
293  snprintf(file_account, sizeof(file_account), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
294  return writefile(s, file_account);
295 }
296 
297 static int csv_log(struct ast_cdr *cdr)
298 {
299  /* Make sure we have a big enough buf */
300  char buf[1024];
301  if (build_csv_record(buf, sizeof(buf), cdr)) {
302  ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
303  return 0;
304  }
305 
308  ast_log(LOG_WARNING, "Unable to write CSV record to master '%s' : %s\n", file_csv_master, strerror(errno));
309 
310  if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
311  if (writefile_account(buf, cdr->accountcode))
312  ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
313  }
315  return 0;
316 }
317 
318 static int unload_module(void)
319 {
320  if (ast_cdr_unregister(name)) {
321  return -1;
322  }
323 
324  loaded = 0;
325  return 0;
326 }
327 
328 static int load_module(void)
329 {
330  int res;
331 
332  if (!load_config(0)) {
334  }
335 
337  ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
338  } else {
339  loaded = 1;
340  }
341  return res;
342 }
343 
344 static int reload(void)
345 {
346  if (load_config(1)) {
347  loaded = 1;
348  } else {
349  loaded = 0;
350  ast_log(LOG_WARNING, "No [csv] section in cdr.conf. Unregistering backend.\n");
352  }
353 
354  return 0;
355 }
356 
357 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
358  .support_level = AST_MODULE_SUPPORT_EXTENDED,
359  .load = load_module,
360  .unload = unload_module,
361  .reload = reload,
362  .load_pri = AST_MODPRI_CDR_DRIVER,
363  .requires = "cdr",
364 );
Asterisk main include file. File version handling, generic pbx functions.
#define PATH_MAX
Definition: asterisk.h:40
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
Call Detail Record API.
const char * ast_cdr_disp2str(int disposition)
Disposition to a string.
Definition: cdr.c:3452
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:3010
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2965
static int append_date(char *buf, struct timeval when, size_t bufsize)
Definition: cdr_csv.c:192
static int loguserfield
Definition: cdr_csv.c:57
#define CSV_MASTER
Definition: cdr_csv.c:50
static int csv_log(struct ast_cdr *cdr)
Definition: cdr_csv.c:297
static int loaded
Definition: cdr_csv.c:58
static char * name
Definition: cdr_csv.c:94
static int writefile_account(char *s, char *acc)
Definition: cdr_csv.c:286
static int loguniqueid
Definition: cdr_csv.c:56
static int writefile(char *s, char *file_path)
Definition: cdr_csv.c:268
static int accountlogs
Definition: cdr_csv.c:55
static ast_mutex_t f_lock
Definition: cdr_csv.c:96
static int append_int(char *buf, int s, size_t bufsize)
Definition: cdr_csv.c:174
#define CSV_LOG_DIR
Definition: cdr_csv.c:49
static const char config[]
Definition: cdr_csv.c:60
static int newcdrcolumns
Definition: cdr_csv.c:59
static int append_string(char *buf, const char *s, size_t bufsize)
Definition: cdr_csv.c:147
static int usegmtime
Definition: cdr_csv.c:54
static int load_module(void)
Definition: cdr_csv.c:328
static char file_csv_master[PATH_MAX]
Definition: cdr_csv.c:61
static int unload_module(void)
Definition: cdr_csv.c:318
static int load_config(int reload)
Definition: cdr_csv.c:98
static int reload(void)
Definition: cdr_csv.c:344
#define DATE_FORMAT
Definition: cdr_csv.c:52
static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
Definition: cdr_csv.c:211
General Asterisk PBX channel definitions.
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
Definition: channel.c:4408
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
@ CONFIG_FLAG_FILEUNCHANGED
#define CONFIG_STATUS_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define LOG_ERROR
#define LOG_WARNING
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
Asterisk locking-related definitions:
#define ast_mutex_unlock(a)
Definition: lock.h:188
#define ast_mutex_lock(a)
Definition: lock.h:187
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_CDR_DRIVER
Definition: module.h:331
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_LOG_DIR
Definition: options.c:159
#define NULL
Definition: resample.c:96
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: main/utils.c:2097
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Responsible for call detail data.
Definition: cdr.h:277
char dstchannel[AST_MAX_EXTENSION]
Definition: cdr.h:289
long int disposition
Definition: cdr.h:305
char lastdata[AST_MAX_EXTENSION]
Definition: cdr.h:293
char linkedid[AST_MAX_UNIQUEID]
Definition: cdr.h:317
char userfield[AST_MAX_USER_FIELD]
Definition: cdr.h:319
long int billsec
Definition: cdr.h:303
struct timeval answer
Definition: cdr.h:297
char channel[AST_MAX_EXTENSION]
Definition: cdr.h:287
char peeraccount[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:311
long int duration
Definition: cdr.h:301
long int amaflags
Definition: cdr.h:307
char src[AST_MAX_EXTENSION]
Definition: cdr.h:281
char dst[AST_MAX_EXTENSION]
Definition: cdr.h:283
char clid[AST_MAX_EXTENSION]
Definition: cdr.h:279
char uniqueid[AST_MAX_UNIQUEID]
Definition: cdr.h:315
int sequence
Definition: cdr.h:321
struct timeval start
Definition: cdr.h:295
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:309
char lastapp[AST_MAX_EXTENSION]
Definition: cdr.h:291
char dcontext[AST_MAX_EXTENSION]
Definition: cdr.h:285
struct timeval end
Definition: cdr.h:299
Structure used to handle boolean flags.
Definition: utils.h:199
const char * description
Definition: module.h:352
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:115
int error(const char *format,...)
Definition: utils/frame.c:999
Utility functions.