document usage of 'transfer' configuration option for ISDN PRI switch-side transfers
[asterisk-bristuff.git] / cdr / cdr_csv.c
blob00b8de32084be89ac337a480fd228435cd7b4eae
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * Includes code and algorithms from the Zapata library.
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.
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.
21 /*! \file
23 * \brief Comma Separated Value CDR records.
25 * \author Mark Spencer <markster@digium.com>
27 * \arg See also \ref AstCDR
28 * \ingroup cdr_drivers
31 #include "asterisk.h"
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <errno.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <time.h>
44 #include "asterisk/config.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/cdr.h"
47 #include "asterisk/module.h"
48 #include "asterisk/logger.h"
49 #include "asterisk/utils.h"
50 #include "asterisk/lock.h"
52 #define CSV_LOG_DIR "/cdr-csv"
53 #define CSV_MASTER "/Master.csv"
55 #define DATE_FORMAT "%Y-%m-%d %T"
57 static int usegmtime = 0;
58 static int loguniqueid = 0;
59 static int loguserfield = 0;
60 static char *config = "cdr.conf";
62 /* #define CSV_LOGUNIQUEID 1 */
63 /* #define CSV_LOGUSERFIELD 1 */
65 /*----------------------------------------------------
66 The values are as follows:
69 "accountcode", accountcode is the account name of detail records, Master.csv contains all records *
70 Detail records are configured on a channel basis, IAX and SIP are determined by user *
71 Zap is determined by channel in zaptel.conf
72 "source",
73 "destination",
74 "destination context",
75 "callerid",
76 "channel",
77 "destination channel", (if applicable)
78 "last application", Last application run on the channel
79 "last app argument", argument to the last channel
80 "start time",
81 "answer time",
82 "end time",
83 duration, Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
84 "end time" minus "start time"
85 billable seconds, the duration that a call was up after other end answered which will be <= to duration
86 "end time" minus "answer time"
87 "disposition", ANSWERED, NO ANSWER, BUSY
88 "amaflags", DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
89 "uniqueid", unique call identifier
90 "userfield" user field set via SetCDRUserField
91 ----------------------------------------------------------*/
93 static char *name = "csv";
95 AST_MUTEX_DEFINE_STATIC(mf_lock);
96 AST_MUTEX_DEFINE_STATIC(acf_lock);
98 static int load_config(void)
100 struct ast_config *cfg;
101 struct ast_variable *var;
102 const char *tmp;
104 usegmtime = 0;
105 loguniqueid = 0;
106 loguserfield = 0;
108 cfg = ast_config_load(config);
110 if (!cfg) {
111 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
112 return 0;
115 var = ast_variable_browse(cfg, "csv");
116 if (!var) {
117 ast_config_destroy(cfg);
118 return 0;
121 tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
122 if (tmp) {
123 usegmtime = ast_true(tmp);
124 if (usegmtime) {
125 ast_log(LOG_DEBUG, "logging time in GMT\n");
129 tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
130 if (tmp) {
131 loguniqueid = ast_true(tmp);
132 if (loguniqueid) {
133 ast_log(LOG_DEBUG, "logging CDR field UNIQUEID\n");
137 tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
138 if (tmp) {
139 loguserfield = ast_true(tmp);
140 if (loguserfield) {
141 ast_log(LOG_DEBUG, "logging CDR user-defined field\n");
145 ast_config_destroy(cfg);
146 return 1;
149 static int append_string(char *buf, char *s, size_t bufsize)
151 int pos = strlen(buf);
152 int spos = 0;
153 int error = 0;
154 if (pos >= bufsize - 4)
155 return -1;
156 buf[pos++] = '\"';
157 error = -1;
158 while(pos < bufsize - 3) {
159 if (!s[spos]) {
160 error = 0;
161 break;
163 if (s[spos] == '\"')
164 buf[pos++] = '\"';
165 buf[pos++] = s[spos];
166 spos++;
168 buf[pos++] = '\"';
169 buf[pos++] = ',';
170 buf[pos++] = '\0';
171 return error;
174 static int append_int(char *buf, int s, size_t bufsize)
176 char tmp[32];
177 int pos = strlen(buf);
178 snprintf(tmp, sizeof(tmp), "%d", s);
179 if (pos + strlen(tmp) > bufsize - 3)
180 return -1;
181 strncat(buf, tmp, bufsize - strlen(buf) - 1);
182 pos = strlen(buf);
183 buf[pos++] = ',';
184 buf[pos++] = '\0';
185 return 0;
188 static int append_date(char *buf, struct timeval tv, size_t bufsize)
190 char tmp[80] = "";
191 struct tm tm;
192 time_t t;
193 t = tv.tv_sec;
194 if (strlen(buf) > bufsize - 3)
195 return -1;
196 if (ast_tvzero(tv)) {
197 strncat(buf, ",", bufsize - strlen(buf) - 1);
198 return 0;
200 if (usegmtime) {
201 gmtime_r(&t,&tm);
202 } else {
203 ast_localtime(&t, &tm, NULL);
205 strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
206 return append_string(buf, tmp, bufsize);
209 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
212 buf[0] = '\0';
213 /* Account code */
214 append_string(buf, cdr->accountcode, bufsize);
215 /* Source */
216 append_string(buf, cdr->src, bufsize);
217 /* Destination */
218 append_string(buf, cdr->dst, bufsize);
219 /* Destination context */
220 append_string(buf, cdr->dcontext, bufsize);
221 /* Caller*ID */
222 append_string(buf, cdr->clid, bufsize);
223 /* Channel */
224 append_string(buf, cdr->channel, bufsize);
225 /* Destination Channel */
226 append_string(buf, cdr->dstchannel, bufsize);
227 /* Last Application */
228 append_string(buf, cdr->lastapp, bufsize);
229 /* Last Data */
230 append_string(buf, cdr->lastdata, bufsize);
231 /* Start Time */
232 append_date(buf, cdr->start, bufsize);
233 /* Answer Time */
234 append_date(buf, cdr->answer, bufsize);
235 /* End Time */
236 append_date(buf, cdr->end, bufsize);
237 /* Duration */
238 append_int(buf, cdr->duration, bufsize);
239 /* Billable seconds */
240 append_int(buf, cdr->billsec, bufsize);
241 /* Disposition */
242 append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
243 /* AMA Flags */
244 append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
245 /* Unique ID */
246 if (loguniqueid)
247 append_string(buf, cdr->uniqueid, bufsize);
248 /* append the user field */
249 if(loguserfield)
250 append_string(buf, cdr->userfield,bufsize);
251 /* If we hit the end of our buffer, log an error */
252 if (strlen(buf) < bufsize - 5) {
253 /* Trim off trailing comma */
254 buf[strlen(buf) - 1] = '\0';
255 strncat(buf, "\n", bufsize - strlen(buf) - 1);
256 return 0;
258 return -1;
261 static int writefile(char *s, char *acc)
263 char tmp[PATH_MAX];
264 FILE *f;
265 if (strchr(acc, '/') || (acc[0] == '.')) {
266 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
267 return -1;
269 snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
271 ast_mutex_lock(&acf_lock);
272 f = fopen(tmp, "a");
273 if (!f) {
274 ast_mutex_unlock(&acf_lock);
275 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
276 return -1;
278 fputs(s, f);
279 fflush(f);
280 fclose(f);
281 ast_mutex_unlock(&acf_lock);
283 return 0;
287 static int csv_log(struct ast_cdr *cdr)
289 FILE *mf = NULL;
290 /* Make sure we have a big enough buf */
291 char buf[1024];
292 char csvmaster[PATH_MAX];
293 snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
294 #if 0
295 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);
296 #endif
297 if (build_csv_record(buf, sizeof(buf), cdr)) {
298 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf));
299 } else {
300 /* because of the absolutely unconditional need for the
301 highest reliability possible in writing billing records,
302 we open write and close the log file each time */
303 ast_mutex_lock(&mf_lock);
304 mf = fopen(csvmaster, "a");
305 if (mf) {
306 fputs(buf, mf);
307 fflush(mf); /* be particularly anal here */
308 fclose(mf);
309 mf = NULL;
310 ast_mutex_unlock(&mf_lock);
311 } else {
312 ast_mutex_unlock(&mf_lock);
313 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
316 if (!ast_strlen_zero(cdr->accountcode)) {
317 if (writefile(buf, cdr->accountcode))
318 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
321 return 0;
324 static int unload_module(void)
326 ast_cdr_unregister(name);
327 return 0;
330 static int load_module(void)
332 int res;
334 if(!load_config())
335 return AST_MODULE_LOAD_DECLINE;
337 res = ast_cdr_register(name, ast_module_info->description, csv_log);
338 if (res) {
339 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
341 return res;
344 static int reload(void)
346 load_config();
348 return 0;
351 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
352 .load = load_module,
353 .unload = unload_module,
354 .reload = reload,