Merge in ast_strftime branch, which changes timestamps to be accurate to the microsec...
[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 ast_tm tm;
192         if (strlen(buf) > bufsize - 3)
193                 return -1;
194         if (ast_tvzero(tv)) {
195                 strncat(buf, ",", bufsize - strlen(buf) - 1);
196                 return 0;
197         }
198         ast_localtime(&tv, &tm, usegmtime ? "GMT" : NULL);
199         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
200         return append_string(buf, tmp, bufsize);
201 }
202
203 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
204 {
205
206         buf[0] = '\0';
207         /* Account code */
208         append_string(buf, cdr->accountcode, bufsize);
209         /* Source */
210         append_string(buf, cdr->src, bufsize);
211         /* Destination */
212         append_string(buf, cdr->dst, bufsize);
213         /* Destination context */
214         append_string(buf, cdr->dcontext, bufsize);
215         /* Caller*ID */
216         append_string(buf, cdr->clid, bufsize);
217         /* Channel */
218         append_string(buf, cdr->channel, bufsize);
219         /* Destination Channel */
220         append_string(buf, cdr->dstchannel, bufsize);
221         /* Last Application */
222         append_string(buf, cdr->lastapp, bufsize);
223         /* Last Data */
224         append_string(buf, cdr->lastdata, bufsize);
225         /* Start Time */
226         append_date(buf, cdr->start, bufsize);
227         /* Answer Time */
228         append_date(buf, cdr->answer, bufsize);
229         /* End Time */
230         append_date(buf, cdr->end, bufsize);
231         /* Duration */
232         append_int(buf, cdr->duration, bufsize);
233         /* Billable seconds */
234         append_int(buf, cdr->billsec, bufsize);
235         /* Disposition */
236         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
237         /* AMA Flags */
238         append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
239         /* Unique ID */
240         if (loguniqueid)
241                 append_string(buf, cdr->uniqueid, bufsize);
242         /* append the user field */
243         if(loguserfield)
244                 append_string(buf, cdr->userfield,bufsize);     
245         /* If we hit the end of our buffer, log an error */
246         if (strlen(buf) < bufsize - 5) {
247                 /* Trim off trailing comma */
248                 buf[strlen(buf) - 1] = '\0';
249                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
250                 return 0;
251         }
252         return -1;
253 }
254
255 static int writefile(char *s, char *acc)
256 {
257         char tmp[PATH_MAX];
258         FILE *f;
259         if (strchr(acc, '/') || (acc[0] == '.')) {
260                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
261                 return -1;
262         }
263         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
264         f = fopen(tmp, "a");
265         if (!f)
266                 return -1;
267         fputs(s, f);
268         fflush(f);
269         fclose(f);
270         return 0;
271 }
272
273
274 static int csv_log(struct ast_cdr *cdr)
275 {
276         /* Make sure we have a big enough buf */
277         char buf[1024];
278         char csvmaster[PATH_MAX];
279         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
280 #if 0
281         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);
282 #endif
283         if (build_csv_record(buf, sizeof(buf), cdr)) {
284                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
285         } else {
286                 /* because of the absolutely unconditional need for the
287                    highest reliability possible in writing billing records,
288                    we open write and close the log file each time */
289                 mf = fopen(csvmaster, "a");
290                 if (!mf) {
291                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
292                 }
293                 if (mf) {
294                         fputs(buf, mf);
295                         fflush(mf); /* be particularly anal here */
296                         fclose(mf);
297                         mf = NULL;
298                 }
299                 if (!ast_strlen_zero(cdr->accountcode)) {
300                         if (writefile(buf, cdr->accountcode))
301                                 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
302                 }
303         }
304         return 0;
305 }
306
307 static int unload_module(void)
308 {
309         if (mf)
310                 fclose(mf);
311         ast_cdr_unregister(name);
312         return 0;
313 }
314
315 static int load_module(void)
316 {
317         int res;
318         
319         if(!load_config())
320                 return AST_MODULE_LOAD_DECLINE;
321
322         res = ast_cdr_register(name, ast_module_info->description, csv_log);
323         if (res) {
324                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
325                 if (mf)
326                         fclose(mf);
327         }
328         return res;
329 }
330
331 static int reload(void)
332 {
333         load_config();
334
335         return 0;
336 }
337
338 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
339                 .load = load_module,
340                 .unload = unload_module,
341                 .reload = reload,
342                );