Wed Jan 8 2020 09:49:42

Asterisk developer's documentation


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 /*** MODULEINFO
32  <support_level>extended</support_level>
33  ***/
34 
35 #include "asterisk.h"
36 
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 328209 $")
38 
39 #include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
40 #include "asterisk/config.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/cdr.h"
43 #include "asterisk/module.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/lock.h"
46 
47 #define CSV_LOG_DIR "/cdr-csv"
48 #define CSV_MASTER "/Master.csv"
49 
50 #define DATE_FORMAT "%Y-%m-%d %T"
51 
52 static int usegmtime = 0;
53 static int accountlogs;
54 static int loguniqueid = 0;
55 static int loguserfield = 0;
56 static int loaded = 0;
57 static const char config[] = "cdr.conf";
58 
59 /* #define CSV_LOGUNIQUEID 1 */
60 /* #define CSV_LOGUSERFIELD 1 */
61 
62 /*----------------------------------------------------
63  The values are as follows:
64 
65 
66  "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
67  Detail records are configured on a channel basis, IAX and SIP are determined by user *
68  DAHDI is determined by channel in dahdi.conf
69  "source",
70  "destination",
71  "destination context",
72  "callerid",
73  "channel",
74  "destination channel", (if applicable)
75  "last application", Last application run on the channel
76  "last app argument", argument to the last channel
77  "start time",
78  "answer time",
79  "end time",
80  duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
81  "end time" minus "start time"
82  billable seconds, the duration that a call was up after other end answered which will be <= to duration
83  "end time" minus "answer time"
84  "disposition", ANSWERED, NO ANSWER, BUSY
85  "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
86  "uniqueid", unique call identifier
87  "userfield" user field set via SetCDRUserField
88 ----------------------------------------------------------*/
89 
90 static char *name = "csv";
91 
94 
95 static int load_config(int reload)
96 {
97  struct ast_config *cfg;
98  struct ast_variable *var;
99  const char *tmp;
100  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
101 
102  if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
103  ast_log(LOG_WARNING, "unable to load config: %s\n", config);
104  return 0;
105  } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
106  return 1;
107 
108  accountlogs = 1;
109  usegmtime = 0;
110  loguniqueid = 0;
111  loguserfield = 0;
112 
113  if (!(var = ast_variable_browse(cfg, "csv"))) {
114  ast_config_destroy(cfg);
115  return 0;
116  }
117 
118  if ((tmp = ast_variable_retrieve(cfg, "csv", "usegmtime"))) {
119  usegmtime = ast_true(tmp);
120  if (usegmtime)
121  ast_debug(1, "logging time in GMT\n");
122  }
123 
124  /* Turn on/off separate files per accountcode. Default is on (as before) */
125  if ((tmp = ast_variable_retrieve(cfg, "csv", "accountlogs"))) {
126  accountlogs = ast_true(tmp);
127  if (accountlogs) {
128  ast_debug(1, "logging in separate files per accountcode\n");
129  }
130  }
131 
132  if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
133  loguniqueid = ast_true(tmp);
134  if (loguniqueid)
135  ast_debug(1, "logging CDR field UNIQUEID\n");
136  }
137 
138  if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
139  loguserfield = ast_true(tmp);
140  if (loguserfield)
141  ast_debug(1, "logging CDR user-defined field\n");
142  }
143 
144  ast_config_destroy(cfg);
145  return 1;
146 }
147 
148 static int append_string(char *buf, const char *s, size_t bufsize)
149 {
150  int pos = strlen(buf), spos = 0, error = -1;
151 
152  if (pos >= bufsize - 4)
153  return -1;
154 
155  buf[pos++] = '\"';
156 
157  while(pos < bufsize - 3) {
158  if (!s[spos]) {
159  error = 0;
160  break;
161  }
162  if (s[spos] == '\"')
163  buf[pos++] = '\"';
164  buf[pos++] = s[spos];
165  spos++;
166  }
167 
168  buf[pos++] = '\"';
169  buf[pos++] = ',';
170  buf[pos++] = '\0';
171 
172  return error;
173 }
174 
175 static int append_int(char *buf, int s, size_t bufsize)
176 {
177  char tmp[32];
178  int pos = strlen(buf);
179 
180  snprintf(tmp, sizeof(tmp), "%d", s);
181 
182  if (pos + strlen(tmp) > bufsize - 3)
183  return -1;
184 
185  strncat(buf, tmp, bufsize - strlen(buf) - 1);
186  pos = strlen(buf);
187  buf[pos++] = ',';
188  buf[pos++] = '\0';
189 
190  return 0;
191 }
192 
193 static int append_date(char *buf, struct timeval when, size_t bufsize)
194 {
195  char tmp[80] = "";
196  struct ast_tm tm;
197 
198  if (strlen(buf) > bufsize - 3)
199  return -1;
200 
201  if (ast_tvzero(when)) {
202  strncat(buf, ",", bufsize - strlen(buf) - 1);
203  return 0;
204  }
205 
206  ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
207  ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
208 
209  return append_string(buf, tmp, bufsize);
210 }
211 
212 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
213 {
214 
215  buf[0] = '\0';
216  /* Account code */
217  append_string(buf, cdr->accountcode, bufsize);
218  /* Source */
219  append_string(buf, cdr->src, bufsize);
220  /* Destination */
221  append_string(buf, cdr->dst, bufsize);
222  /* Destination context */
223  append_string(buf, cdr->dcontext, bufsize);
224  /* Caller*ID */
225  append_string(buf, cdr->clid, bufsize);
226  /* Channel */
227  append_string(buf, cdr->channel, bufsize);
228  /* Destination Channel */
229  append_string(buf, cdr->dstchannel, bufsize);
230  /* Last Application */
231  append_string(buf, cdr->lastapp, bufsize);
232  /* Last Data */
233  append_string(buf, cdr->lastdata, bufsize);
234  /* Start Time */
235  append_date(buf, cdr->start, bufsize);
236  /* Answer Time */
237  append_date(buf, cdr->answer, bufsize);
238  /* End Time */
239  append_date(buf, cdr->end, bufsize);
240  /* Duration */
241  append_int(buf, cdr->duration, bufsize);
242  /* Billable seconds */
243  append_int(buf, cdr->billsec, bufsize);
244  /* Disposition */
245  append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
246  /* AMA Flags */
247  append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
248  /* Unique ID */
249  if (loguniqueid)
250  append_string(buf, cdr->uniqueid, bufsize);
251  /* append the user field */
252  if(loguserfield)
253  append_string(buf, cdr->userfield,bufsize);
254  /* If we hit the end of our buffer, log an error */
255  if (strlen(buf) < bufsize - 5) {
256  /* Trim off trailing comma */
257  buf[strlen(buf) - 1] = '\0';
258  strncat(buf, "\n", bufsize - strlen(buf) - 1);
259  return 0;
260  }
261  return -1;
262 }
263 
264 static int writefile(char *s, char *acc)
265 {
266  char tmp[PATH_MAX];
267  FILE *f;
268 
269  if (strchr(acc, '/') || (acc[0] == '.')) {
270  ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
271  return -1;
272  }
273 
274  snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
275 
277  if (!(f = fopen(tmp, "a"))) {
279  ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
280  return -1;
281  }
282  fputs(s, f);
283  fflush(f);
284  fclose(f);
286 
287  return 0;
288 }
289 
290 
291 static int csv_log(struct ast_cdr *cdr)
292 {
293  FILE *mf = NULL;
294  /* Make sure we have a big enough buf */
295  char buf[1024];
296  char csvmaster[PATH_MAX];
297  snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
298 #if 0
299  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);
300 #endif
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  /* because of the absolutely unconditional need for the
307  highest reliability possible in writing billing records,
308  we open write and close the log file each time */
310  if ((mf = fopen(csvmaster, "a"))) {
311  fputs(buf, mf);
312  fflush(mf); /* be particularly anal here */
313  fclose(mf);
314  mf = NULL;
316  } else {
318  ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
319  }
320 
321  if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
322  if (writefile(buf, cdr->accountcode))
323  ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
324  }
325 
326  return 0;
327 }
328 
329 static int unload_module(void)
330 {
331  ast_cdr_unregister(name);
332  loaded = 0;
333  return 0;
334 }
335 
336 static int load_module(void)
337 {
338  int res;
339 
340  if(!load_config(0))
342 
343  if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
344  ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
345  } else {
346  loaded = 1;
347  }
348  return res;
349 }
350 
351 static int reload(void)
352 {
353  if (load_config(1)) {
354  loaded = 1;
355  } else {
356  loaded = 0;
357  ast_log(LOG_WARNING, "No [csv] section in cdr.conf. Unregistering backend.\n");
358  ast_cdr_unregister(name);
359  }
360 
361  return 0;
362 }
363 
364 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
365  .load = load_module,
366  .unload = unload_module,
367  .reload = reload,
368  .load_pri = AST_MODPRI_CDR_DRIVER,
369  );
const char * description
Definition: module.h:234
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: cdr.h:114
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
const char * ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
Gets a variable.
Definition: config.c:625
static const char config[]
Definition: cdr_csv.c:57
static int loguniqueid
Definition: cdr_csv.c:54
char dstchannel[AST_MAX_EXTENSION]
Definition: cdr.h:94
long int billsec
Definition: cdr.h:108
#define LOG_WARNING
Definition: logger.h:144
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category)
Goes through variables.
Definition: config.c:597
char dcontext[AST_MAX_EXTENSION]
Definition: cdr.h:90
static int load_config(int reload)
Definition: cdr_csv.c:95
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:1570
Structure for variables, used for configurations and for channel variables.
Definition: config.h:75
#define var
Definition: ast_expr2f.c:606
static int loaded
Definition: cdr_csv.c:56
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:100
char uniqueid[150]
Definition: cdr.h:121
#define CSV_MASTER
Definition: cdr_csv.c:48
Configuration File Parser.
#define ast_mutex_lock(a)
Definition: lock.h:155
static ast_mutex_t acf_lock
Definition: cdr_csv.c:93
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:374
char * ast_cdr_flags2str(int flags)
Definition: cdr.c:977
void ast_config_destroy(struct ast_config *config)
Destroys a config.
Definition: config.c:1037
Utility functions.
Call Detail Record API.
char lastdata[AST_MAX_EXTENSION]
Definition: cdr.h:98
long int amaflags
Definition: cdr.h:112
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
static int accountlogs
Definition: cdr_csv.c:53
int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
Register a CDR handling engine.
Definition: cdr.c:130
General Asterisk PBX channel definitions.
static ast_mutex_t mf_lock
Definition: cdr_csv.c:92
static int load_module(void)
Definition: cdr_csv.c:336
#define ast_config_load(filename, flags)
Load a config file.
Definition: config.h:170
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
char dst[AST_MAX_EXTENSION]
Definition: cdr.h:88
char channel[AST_MAX_EXTENSION]
Definition: cdr.h:92
static int reload(void)
Definition: cdr_csv.c:351
struct timeval answer
Definition: cdr.h:102
Responsible for call detail data.
Definition: cdr.h:82
char lastapp[AST_MAX_EXTENSION]
Definition: cdr.h:96
#define LOG_ERROR
Definition: logger.h:155
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is &quot;true&quot;. This function checks to see whether a string passed to it is an indication of an &quot;true&quot; value. It checks to see if the string is &quot;yes&quot;, &quot;true&quot;, &quot;y&quot;, &quot;t&quot;, &quot;on&quot; or &quot;1&quot;.
Definition: utils.c:1533
static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
Definition: cdr_csv.c:212
static int csv_log(struct ast_cdr *cdr)
Definition: cdr_csv.c:291
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
const char * ast_config_AST_LOG_DIR
Definition: asterisk.c:263
struct timeval start
Definition: cdr.h:100
int errno
static const char name[]
static int writefile(char *s, char *acc)
Definition: cdr_csv.c:264
static struct ast_format f[]
Definition: format_g726.c:181
long int duration
Definition: cdr.h:106
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:2351
Structure used to handle boolean flags.
Definition: utils.h:200
char src[AST_MAX_EXTENSION]
Definition: cdr.h:86
#define DATE_FORMAT
Definition: cdr_csv.c:50
struct timeval end
Definition: cdr.h:104
#define CSV_LOG_DIR
Definition: cdr_csv.c:47
static int unload_module(void)
Definition: cdr_csv.c:329
static int append_string(char *buf, const char *s, size_t bufsize)
Definition: cdr_csv.c:148
static int append_int(char *buf, int s, size_t bufsize)
Definition: cdr_csv.c:175
long int disposition
Definition: cdr.h:110
char clid[AST_MAX_EXTENSION]
Definition: cdr.h:84
#define CONFIG_STATUS_FILEINVALID
Definition: config.h:52
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
Asterisk module definitions.
static int usegmtime
Definition: cdr_csv.c:52
char * ast_cdr_disp2str(int disposition)
Disposition to a string.
Definition: cdr.c:959
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:526
void ast_cdr_unregister(const char *name)
Unregister a CDR handling engine.
Definition: cdr.c:165
static int loguserfield
Definition: cdr_csv.c:55
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
char userfield[AST_MAX_USER_FIELD]
Definition: cdr.h:125
#define CONFIG_STATUS_FILEUNCHANGED
Definition: config.h:51
#define ast_mutex_unlock(a)
Definition: lock.h:156
static int append_date(char *buf, struct timeval when, size_t bufsize)
Definition: cdr_csv.c:193