bug #8076 check option_debug before printing to debug channel.
[asterisk/asterisk.git] / cdr / cdr_csv.c
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 /*! \file
22  *
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 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <errno.h>
39
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <time.h>
43
44 #include "asterisk/config.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/cdr.h"
47 #include "asterisk/module.h"
48 #include "asterisk/options.h"
49 #include "asterisk/logger.h"
50 #include "asterisk/utils.h"
51
52 #define CSV_LOG_DIR "/cdr-csv"
53 #define CSV_MASTER  "/Master.csv"
54
55 #define DATE_FORMAT "%Y-%m-%d %T"
56
57 static int usegmtime = 0;
58 static int loguniqueid = 0;
59 static int loguserfield = 0;
60 static char *config = "cdr.conf";
61
62 /* #define CSV_LOGUNIQUEID 1 */
63 /* #define CSV_LOGUSERFIELD 1 */
64
65 /*----------------------------------------------------
66   The values are as follows:
67
68
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 ----------------------------------------------------------*/
92
93 static char *name = "csv";
94
95 static FILE *mf = NULL;
96
97
98 static int load_config(void)
99 {
100         struct ast_config *cfg;
101         struct ast_variable *var;
102         const char *tmp;
103
104         usegmtime = 0;
105         loguniqueid = 0;
106         loguserfield = 0;
107         
108         cfg = ast_config_load(config);
109         
110         if (!cfg) {
111                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
112                 return 0;
113         } 
114         
115         var = ast_variable_browse(cfg, "csv");
116         if (!var) {
117                 ast_config_destroy(cfg);
118                 return 0;
119         }
120         
121         tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
122         if (tmp) {
123                 usegmtime = ast_true(tmp);
124                 if (usegmtime) {
125                         if (option_debug)
126                                 ast_log(LOG_DEBUG, "logging time in GMT\n");
127                 }
128         }
129
130         tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
131         if (tmp) {
132                 loguniqueid = ast_true(tmp);
133                 if (loguniqueid) {
134                         if (option_debug)
135                                 ast_log(LOG_DEBUG, "logging CDR field UNIQUEID\n");
136                 }
137         }
138
139         tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
140         if (tmp) {
141                 loguserfield = ast_true(tmp);
142                 if (loguserfield) {
143                         if (option_debug)
144                                 ast_log(LOG_DEBUG, "logging CDR user-defined field\n");
145                 }
146         }
147
148         ast_config_destroy(cfg);
149         return 1;
150 }
151
152 static int append_string(char *buf, char *s, size_t bufsize)
153 {
154         int pos = strlen(buf);
155         int spos = 0;
156         int error = 0;
157         if (pos >= bufsize - 4)
158                 return -1;
159         buf[pos++] = '\"';
160         error = -1;
161         while(pos < bufsize - 3) {
162                 if (!s[spos]) {
163                         error = 0;
164                         break;
165                 }
166                 if (s[spos] == '\"')
167                         buf[pos++] = '\"';
168                 buf[pos++] = s[spos];
169                 spos++;
170         }
171         buf[pos++] = '\"';
172         buf[pos++] = ',';
173         buf[pos++] = '\0';
174         return error;
175 }
176
177 static int append_int(char *buf, int s, size_t bufsize)
178 {
179         char tmp[32];
180         int pos = strlen(buf);
181         snprintf(tmp, sizeof(tmp), "%d", s);
182         if (pos + strlen(tmp) > bufsize - 3)
183                 return -1;
184         strncat(buf, tmp, bufsize - strlen(buf) - 1);
185         pos = strlen(buf);
186         buf[pos++] = ',';
187         buf[pos++] = '\0';
188         return 0;
189 }
190
191 static int append_date(char *buf, struct timeval tv, size_t bufsize)
192 {
193         char tmp[80] = "";
194         struct tm tm;
195         time_t t;
196         t = tv.tv_sec;
197         if (strlen(buf) > bufsize - 3)
198                 return -1;
199         if (ast_tvzero(tv)) {
200                 strncat(buf, ",", bufsize - strlen(buf) - 1);
201                 return 0;
202         }
203         if (usegmtime) {
204                 gmtime_r(&t,&tm);
205         } else {
206                 localtime_r(&t,&tm);
207         }
208         strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
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         if (strchr(acc, '/') || (acc[0] == '.')) {
269                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
270                 return -1;
271         }
272         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
273         f = fopen(tmp, "a");
274         if (!f)
275                 return -1;
276         fputs(s, f);
277         fflush(f);
278         fclose(f);
279         return 0;
280 }
281
282
283 static int csv_log(struct ast_cdr *cdr)
284 {
285         /* Make sure we have a big enough buf */
286         char buf[1024];
287         char csvmaster[PATH_MAX];
288         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
289 #if 0
290         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);
291 #endif
292         if (build_csv_record(buf, sizeof(buf), cdr)) {
293                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
294         } else {
295                 /* because of the absolutely unconditional need for the
296                    highest reliability possible in writing billing records,
297                    we open write and close the log file each time */
298                 mf = fopen(csvmaster, "a");
299                 if (!mf) {
300                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
301                 }
302                 if (mf) {
303                         fputs(buf, mf);
304                         fflush(mf); /* be particularly anal here */
305                         fclose(mf);
306                         mf = NULL;
307                 }
308                 if (!ast_strlen_zero(cdr->accountcode)) {
309                         if (writefile(buf, cdr->accountcode))
310                                 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
311                 }
312         }
313         return 0;
314 }
315
316 static int unload_module(void)
317 {
318         if (mf)
319                 fclose(mf);
320         ast_cdr_unregister(name);
321         return 0;
322 }
323
324 static int load_module(void)
325 {
326         int res;
327         
328         if(!load_config())
329                 return AST_MODULE_LOAD_DECLINE;
330
331         res = ast_cdr_register(name, ast_module_info->description, csv_log);
332         if (res) {
333                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
334                 if (mf)
335                         fclose(mf);
336         }
337         return res;
338 }
339
340 static int reload(void)
341 {
342         load_config();
343
344         return 0;
345 }
346
347 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
348                 .load = load_module,
349                 .unload = unload_module,
350                 .reload = reload,
351                );