Merged revisions 80360 via svnmerge from
[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(int reload)
99 {
100         struct ast_config *cfg;
101         struct ast_variable *var;
102         const char *tmp;
103         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
104
105         usegmtime = 0;
106         loguniqueid = 0;
107         loguserfield = 0;
108         
109         cfg = ast_config_load(config, config_flags);
110         
111         if (!cfg) {
112                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
113                 return 0;
114         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
115                 return 0;
116         
117         var = ast_variable_browse(cfg, "csv");
118         if (!var) {
119                 ast_config_destroy(cfg);
120                 return 0;
121         }
122         
123         tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
124         if (tmp) {
125                 usegmtime = ast_true(tmp);
126                 if (usegmtime) {
127                         ast_debug(1, "logging time in GMT\n");
128                 }
129         }
130
131         tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
132         if (tmp) {
133                 loguniqueid = ast_true(tmp);
134                 if (loguniqueid) {
135                         ast_debug(1, "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                         ast_debug(1, "logging CDR user-defined field\n");
144                 }
145         }
146
147         ast_config_destroy(cfg);
148         return 1;
149 }
150
151 static int append_string(char *buf, char *s, size_t bufsize)
152 {
153         int pos = strlen(buf);
154         int spos = 0;
155         int error = 0;
156         if (pos >= bufsize - 4)
157                 return -1;
158         buf[pos++] = '\"';
159         error = -1;
160         while(pos < bufsize - 3) {
161                 if (!s[spos]) {
162                         error = 0;
163                         break;
164                 }
165                 if (s[spos] == '\"')
166                         buf[pos++] = '\"';
167                 buf[pos++] = s[spos];
168                 spos++;
169         }
170         buf[pos++] = '\"';
171         buf[pos++] = ',';
172         buf[pos++] = '\0';
173         return error;
174 }
175
176 static int append_int(char *buf, int s, size_t bufsize)
177 {
178         char tmp[32];
179         int pos = strlen(buf);
180         snprintf(tmp, sizeof(tmp), "%d", s);
181         if (pos + strlen(tmp) > bufsize - 3)
182                 return -1;
183         strncat(buf, tmp, bufsize - strlen(buf) - 1);
184         pos = strlen(buf);
185         buf[pos++] = ',';
186         buf[pos++] = '\0';
187         return 0;
188 }
189
190 static int append_date(char *buf, struct timeval tv, size_t bufsize)
191 {
192         char tmp[80] = "";
193         struct ast_tm tm;
194         if (strlen(buf) > bufsize - 3)
195                 return -1;
196         if (ast_tvzero(tv)) {
197                 strncat(buf, ",", bufsize - strlen(buf) - 1);
198                 return 0;
199         }
200         ast_localtime(&tv, &tm, usegmtime ? "GMT" : NULL);
201         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
202         return append_string(buf, tmp, bufsize);
203 }
204
205 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
206 {
207
208         buf[0] = '\0';
209         /* Account code */
210         append_string(buf, cdr->accountcode, bufsize);
211         /* Source */
212         append_string(buf, cdr->src, bufsize);
213         /* Destination */
214         append_string(buf, cdr->dst, bufsize);
215         /* Destination context */
216         append_string(buf, cdr->dcontext, bufsize);
217         /* Caller*ID */
218         append_string(buf, cdr->clid, bufsize);
219         /* Channel */
220         append_string(buf, cdr->channel, bufsize);
221         /* Destination Channel */
222         append_string(buf, cdr->dstchannel, bufsize);
223         /* Last Application */
224         append_string(buf, cdr->lastapp, bufsize);
225         /* Last Data */
226         append_string(buf, cdr->lastdata, bufsize);
227         /* Start Time */
228         append_date(buf, cdr->start, bufsize);
229         /* Answer Time */
230         append_date(buf, cdr->answer, bufsize);
231         /* End Time */
232         append_date(buf, cdr->end, bufsize);
233         /* Duration */
234         append_int(buf, cdr->duration, bufsize);
235         /* Billable seconds */
236         append_int(buf, cdr->billsec, bufsize);
237         /* Disposition */
238         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
239         /* AMA Flags */
240         append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
241         /* Unique ID */
242         if (loguniqueid)
243                 append_string(buf, cdr->uniqueid, bufsize);
244         /* append the user field */
245         if(loguserfield)
246                 append_string(buf, cdr->userfield,bufsize);     
247         /* If we hit the end of our buffer, log an error */
248         if (strlen(buf) < bufsize - 5) {
249                 /* Trim off trailing comma */
250                 buf[strlen(buf) - 1] = '\0';
251                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
252                 return 0;
253         }
254         return -1;
255 }
256
257 static int writefile(char *s, char *acc)
258 {
259         char tmp[PATH_MAX];
260         FILE *f;
261         if (strchr(acc, '/') || (acc[0] == '.')) {
262                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
263                 return -1;
264         }
265         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
266         f = fopen(tmp, "a");
267         if (!f)
268                 return -1;
269         fputs(s, f);
270         fflush(f);
271         fclose(f);
272         return 0;
273 }
274
275
276 static int csv_log(struct ast_cdr *cdr)
277 {
278         /* Make sure we have a big enough buf */
279         char buf[1024];
280         char csvmaster[PATH_MAX];
281         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
282 #if 0
283         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);
284 #endif
285         if (build_csv_record(buf, sizeof(buf), cdr)) {
286                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
287         } else {
288                 /* because of the absolutely unconditional need for the
289                    highest reliability possible in writing billing records,
290                    we open write and close the log file each time */
291                 mf = fopen(csvmaster, "a");
292                 if (!mf) {
293                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
294                 }
295                 if (mf) {
296                         fputs(buf, mf);
297                         fflush(mf); /* be particularly anal here */
298                         fclose(mf);
299                         mf = NULL;
300                 }
301                 if (!ast_strlen_zero(cdr->accountcode)) {
302                         if (writefile(buf, cdr->accountcode))
303                                 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
304                 }
305         }
306         return 0;
307 }
308
309 static int unload_module(void)
310 {
311         if (mf)
312                 fclose(mf);
313         ast_cdr_unregister(name);
314         return 0;
315 }
316
317 static int load_module(void)
318 {
319         int res;
320         
321         if(!load_config(0))
322                 return AST_MODULE_LOAD_DECLINE;
323
324         res = ast_cdr_register(name, ast_module_info->description, csv_log);
325         if (res) {
326                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
327                 if (mf)
328                         fclose(mf);
329         }
330         return res;
331 }
332
333 static int reload(void)
334 {
335         load_config(1);
336
337         return 0;
338 }
339
340 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
341                 .load = load_module,
342                 .unload = unload_module,
343                 .reload = reload,
344                );