Asterisk - The Open Source Telephony Project  18.5.0
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 <[email protected]>
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 <[email protected]>
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 */
244  append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
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 
307  if (writefile(buf, file_csv_master))
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 );
const char * description
Definition: module.h:352
static ast_mutex_t f_lock
Definition: cdr_csv.c:96
struct ast_variable * next
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:308
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
int ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:2988
static const char config[]
Definition: cdr_csv.c:60
static int loguniqueid
Definition: cdr_csv.c:56
static int writefile_account(char *s, char *acc)
Definition: cdr_csv.c:286
char dstchannel[AST_MAX_EXTENSION]
Definition: cdr.h:288
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1216
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
long int billsec
Definition: cdr.h:302
#define LOG_WARNING
Definition: logger.h:274
#define CONFIG_STATUS_FILEINVALID
char dcontext[AST_MAX_EXTENSION]
Definition: cdr.h:284
static int tmp()
Definition: bt_open.c:389
static int load_config(int reload)
Definition: cdr_csv.c:98
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
Structure for variables, used for configurations and for channel variables.
static int newcdrcolumns
Definition: cdr_csv.c:59
static int loaded
Definition: cdr_csv.c:58
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:108
#define CSV_MASTER
Definition: cdr_csv.c:50
int sequence
Definition: cdr.h:320
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
Definition: channel.c:4418
#define ast_mutex_lock(a)
Definition: lock.h:187
#define NULL
Definition: resample.c:96
static char file_csv_master[PATH_MAX]
Definition: cdr_csv.c:61
Utility functions.
#define ast_strlen_zero(foo)
Definition: strings.h:52
Call Detail Record API.
static int writefile(char *s, char *file_path)
Definition: cdr_csv.c:268
char lastdata[AST_MAX_EXTENSION]
Definition: cdr.h:292
Configuration File Parser.
long int amaflags
Definition: cdr.h:306
#define ast_log
Definition: astobj2.c:42
#define ast_config_load(filename, flags)
Load a config file.
static int accountlogs
Definition: cdr_csv.c:55
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:2943
General Asterisk PBX channel definitions.
Asterisk file paths, configured in asterisk.conf.
static int load_module(void)
Definition: cdr_csv.c:328
char linkedid[AST_MAX_UNIQUEID]
Definition: cdr.h:316
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: extconf.c:1290
char uniqueid[AST_MAX_UNIQUEID]
Definition: cdr.h:314
char dst[AST_MAX_EXTENSION]
Definition: cdr.h:282
char channel[AST_MAX_EXTENSION]
Definition: cdr.h:286
static char * name
Definition: cdr_csv.c:94
static int reload(void)
Definition: cdr_csv.c:344
#define CONFIG_STATUS_FILEUNCHANGED
struct timeval answer
Definition: cdr.h:296
Responsible for call detail data.
Definition: cdr.h:276
char lastapp[AST_MAX_EXTENSION]
Definition: cdr.h:290
const char * ast_cdr_disp2str(int disposition)
Disposition to a string.
Definition: cdr.c:3430
#define LOG_ERROR
Definition: logger.h:285
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: main/utils.c:1951
static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
Definition: cdr_csv.c:211
int errno
static int csv_log(struct ast_cdr *cdr)
Definition: cdr_csv.c:297
const char * ast_config_AST_LOG_DIR
Definition: options.c:159
struct timeval start
Definition: cdr.h:294
long int duration
Definition: cdr.h:300
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
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
Structure used to handle boolean flags.
Definition: utils.h:199
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
char src[AST_MAX_EXTENSION]
Definition: cdr.h:280
char peeraccount[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:310
#define DATE_FORMAT
Definition: cdr_csv.c:52
struct timeval end
Definition: cdr.h:298
#define CSV_LOG_DIR
Definition: cdr_csv.c:49
static int unload_module(void)
Definition: cdr_csv.c:318
static int append_string(char *buf, const char *s, size_t bufsize)
Definition: cdr_csv.c:147
int error(const char *format,...)
Definition: utils/frame.c:999
#define PATH_MAX
Definition: asterisk.h:40
static int append_int(char *buf, int s, size_t bufsize)
Definition: cdr_csv.c:174
long int disposition
Definition: cdr.h:304
char clid[AST_MAX_EXTENSION]
Definition: cdr.h:278
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
static int usegmtime
Definition: cdr_csv.c:54
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
static int loguserfield
Definition: cdr_csv.c:57
char userfield[AST_MAX_USER_FIELD]
Definition: cdr.h:318
#define ast_mutex_unlock(a)
Definition: lock.h:188
static int append_date(char *buf, struct timeval when, size_t bufsize)
Definition: cdr_csv.c:192