Asterisk - The Open Source Telephony Project  21.4.1
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";
61 static char file_csv_master[PATH_MAX];
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 
96 AST_MUTEX_DEFINE_STATIC(f_lock);
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 */
123  ast_mutex_lock(&f_lock);
124  snprintf(file_csv_master, sizeof(file_csv_master),
125  "%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
126  ast_mutex_unlock(&f_lock);
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")) {
137  loguserfield = ast_true(v->value);
138  } else if (!strcasecmp(v->name, "newcdrcolumns")) {
139  newcdrcolumns = ast_true(v->value);
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 */
246  append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize);
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 
306  ast_mutex_lock(&f_lock);
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  }
314  ast_mutex_unlock(&f_lock);
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 
336  if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
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");
351  ast_cdr_unregister(name);
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:366
struct ast_variable * next
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:311
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:3050
char dstchannel[AST_MAX_EXTENSION]
Definition: cdr.h:291
long int billsec
Definition: cdr.h:305
char dcontext[AST_MAX_EXTENSION]
Definition: cdr.h:287
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.
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:117
int sequence
Definition: cdr.h:323
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
Definition: channel.c:4373
Utility functions.
Call Detail Record API.
char lastdata[AST_MAX_EXTENSION]
Definition: cdr.h:295
Configuration File Parser.
long int amaflags
Definition: cdr.h:309
#define ast_config_load(filename, flags)
Load a config file.
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:3005
General Asterisk PBX channel definitions.
Asterisk file paths, configured in asterisk.conf.
char linkedid[AST_MAX_UNIQUEID]
Definition: cdr.h:319
char uniqueid[AST_MAX_UNIQUEID]
Definition: cdr.h:317
char dst[AST_MAX_EXTENSION]
Definition: cdr.h:285
Responsible for call detail data.
Definition: cdr.h:279
char lastapp[AST_MAX_EXTENSION]
Definition: cdr.h:293
const char * ast_cdr_disp2str(int disposition)
Disposition to a string.
Definition: cdr.c:3492
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: utils.c:2199
long int duration
Definition: cdr.h:303
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
char src[AST_MAX_EXTENSION]
Definition: cdr.h:283
char peeraccount[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:313
long int disposition
Definition: cdr.h:307
char clid[AST_MAX_EXTENSION]
Definition: cdr.h:281
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
char userfield[AST_MAX_USER_FIELD]
Definition: cdr.h:321