f40f555a8d58e18b099a75cd9b54a2fb128276d7
[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                         ast_debug(1, "logging time in GMT\n");
126                 }
127         }
128
129         tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
130         if (tmp) {
131                 loguniqueid = ast_true(tmp);
132                 if (loguniqueid) {
133                         ast_debug(1, "logging CDR field UNIQUEID\n");
134                 }
135         }
136
137         tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
138         if (tmp) {
139                 loguserfield = ast_true(tmp);
140                 if (loguserfield) {
141                         ast_debug(1, "logging CDR user-defined field\n");
142                 }
143         }
144
145         ast_config_destroy(cfg);
146         return 1;
147 }
148
149 static int append_string(char *buf, char *s, size_t bufsize)
150 {
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;
162                 }
163                 if (s[spos] == '\"')
164                         buf[pos++] = '\"';
165                 buf[pos++] = s[spos];
166                 spos++;
167         }
168         buf[pos++] = '\"';
169         buf[pos++] = ',';
170         buf[pos++] = '\0';
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         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;
186 }
187
188 static int append_date(char *buf, struct timeval tv, size_t bufsize)
189 {
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;
199         }
200         if (usegmtime) {
201                 gmtime_r(&t,&tm);
202         } else {
203                 ast_localtime(&t, &tm, NULL);
204         }
205         strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
206         return append_string(buf, tmp, bufsize);
207 }
208
209 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
210 {
211
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;
257         }
258         return -1;
259 }
260
261 static int writefile(char *s, char *acc)
262 {
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;
268         }
269         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
270         f = fopen(tmp, "a");
271         if (!f)
272                 return -1;
273         fputs(s, f);
274         fflush(f);
275         fclose(f);
276         return 0;
277 }
278
279
280 static int csv_log(struct ast_cdr *cdr)
281 {
282         /* Make sure we have a big enough buf */
283         char buf[1024];
284         char csvmaster[PATH_MAX];
285         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
286 #if 0
287         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);
288 #endif
289         if (build_csv_record(buf, sizeof(buf), cdr)) {
290                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
291         } else {
292                 /* because of the absolutely unconditional need for the
293                    highest reliability possible in writing billing records,
294                    we open write and close the log file each time */
295                 mf = fopen(csvmaster, "a");
296                 if (!mf) {
297                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
298                 }
299                 if (mf) {
300                         fputs(buf, mf);
301                         fflush(mf); /* be particularly anal here */
302                         fclose(mf);
303                         mf = NULL;
304                 }
305                 if (!ast_strlen_zero(cdr->accountcode)) {
306                         if (writefile(buf, cdr->accountcode))
307                                 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
308                 }
309         }
310         return 0;
311 }
312
313 static int unload_module(void)
314 {
315         if (mf)
316                 fclose(mf);
317         ast_cdr_unregister(name);
318         return 0;
319 }
320
321 static int load_module(void)
322 {
323         int res;
324         
325         if(!load_config())
326                 return AST_MODULE_LOAD_DECLINE;
327
328         res = ast_cdr_register(name, ast_module_info->description, csv_log);
329         if (res) {
330                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
331                 if (mf)
332                         fclose(mf);
333         }
334         return res;
335 }
336
337 static int reload(void)
338 {
339         load_config();
340
341         return 0;
342 }
343
344 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
345                 .load = load_module,
346                 .unload = unload_module,
347                 .reload = reload,
348                );