Merged revisions 82344 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 #include "asterisk/lock.h"
52
53 #define CSV_LOG_DIR "/cdr-csv"
54 #define CSV_MASTER  "/Master.csv"
55
56 #define DATE_FORMAT "%Y-%m-%d %T"
57
58 static int usegmtime = 0;
59 static int loguniqueid = 0;
60 static int loguserfield = 0;
61 static char *config = "cdr.conf";
62
63 /* #define CSV_LOGUNIQUEID 1 */
64 /* #define CSV_LOGUSERFIELD 1 */
65
66 /*----------------------------------------------------
67   The values are as follows:
68
69
70   "accountcode",        accountcode is the account name of detail records, Master.csv contains all records *
71                         Detail records are configured on a channel basis, IAX and SIP are determined by user *
72                         Zap is determined by channel in zaptel.conf 
73   "source",
74   "destination",
75   "destination context", 
76   "callerid",
77   "channel",
78   "destination channel",        (if applicable)
79   "last application",   Last application run on the channel 
80   "last app argument",  argument to the last channel 
81   "start time", 
82   "answer time", 
83   "end time", 
84   duration,             Duration is the whole length that the entire call lasted. ie. call rx'd to hangup  
85                         "end time" minus "start time" 
86   billable seconds,     the duration that a call was up after other end answered which will be <= to duration  
87                         "end time" minus "answer time" 
88   "disposition",        ANSWERED, NO ANSWER, BUSY 
89   "amaflags",           DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode. 
90   "uniqueid",           unique call identifier 
91   "userfield"           user field set via SetCDRUserField 
92 ----------------------------------------------------------*/
93
94 static char *name = "csv";
95
96 AST_MUTEX_DEFINE_STATIC(mf_lock);
97 AST_MUTEX_DEFINE_STATIC(acf_lock);
98
99 static int load_config(int reload)
100 {
101         struct ast_config *cfg;
102         struct ast_variable *var;
103         const char *tmp;
104         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
105
106         usegmtime = 0;
107         loguniqueid = 0;
108         loguserfield = 0;
109         
110         cfg = ast_config_load(config, config_flags);
111         
112         if (!cfg) {
113                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
114                 return 0;
115         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
116                 return 0;
117         
118         var = ast_variable_browse(cfg, "csv");
119         if (!var) {
120                 ast_config_destroy(cfg);
121                 return 0;
122         }
123         
124         tmp = ast_variable_retrieve(cfg, "csv", "usegmtime");
125         if (tmp) {
126                 usegmtime = ast_true(tmp);
127                 if (usegmtime) {
128                         ast_debug(1, "logging time in GMT\n");
129                 }
130         }
131
132         tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid");
133         if (tmp) {
134                 loguniqueid = ast_true(tmp);
135                 if (loguniqueid) {
136                         ast_debug(1, "logging CDR field UNIQUEID\n");
137                 }
138         }
139
140         tmp = ast_variable_retrieve(cfg, "csv", "loguserfield");
141         if (tmp) {
142                 loguserfield = ast_true(tmp);
143                 if (loguserfield) {
144                         ast_debug(1, "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 ast_tm tm;
195         if (strlen(buf) > bufsize - 3)
196                 return -1;
197         if (ast_tvzero(tv)) {
198                 strncat(buf, ",", bufsize - strlen(buf) - 1);
199                 return 0;
200         }
201         ast_localtime(&tv, &tm, usegmtime ? "GMT" : NULL);
202         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
203         return append_string(buf, tmp, bufsize);
204 }
205
206 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
207 {
208
209         buf[0] = '\0';
210         /* Account code */
211         append_string(buf, cdr->accountcode, bufsize);
212         /* Source */
213         append_string(buf, cdr->src, bufsize);
214         /* Destination */
215         append_string(buf, cdr->dst, bufsize);
216         /* Destination context */
217         append_string(buf, cdr->dcontext, bufsize);
218         /* Caller*ID */
219         append_string(buf, cdr->clid, bufsize);
220         /* Channel */
221         append_string(buf, cdr->channel, bufsize);
222         /* Destination Channel */
223         append_string(buf, cdr->dstchannel, bufsize);
224         /* Last Application */
225         append_string(buf, cdr->lastapp, bufsize);
226         /* Last Data */
227         append_string(buf, cdr->lastdata, bufsize);
228         /* Start Time */
229         append_date(buf, cdr->start, bufsize);
230         /* Answer Time */
231         append_date(buf, cdr->answer, bufsize);
232         /* End Time */
233         append_date(buf, cdr->end, bufsize);
234         /* Duration */
235         append_int(buf, cdr->duration, bufsize);
236         /* Billable seconds */
237         append_int(buf, cdr->billsec, bufsize);
238         /* Disposition */
239         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
240         /* AMA Flags */
241         append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
242         /* Unique ID */
243         if (loguniqueid)
244                 append_string(buf, cdr->uniqueid, bufsize);
245         /* append the user field */
246         if(loguserfield)
247                 append_string(buf, cdr->userfield,bufsize);     
248         /* If we hit the end of our buffer, log an error */
249         if (strlen(buf) < bufsize - 5) {
250                 /* Trim off trailing comma */
251                 buf[strlen(buf) - 1] = '\0';
252                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
253                 return 0;
254         }
255         return -1;
256 }
257
258 static int writefile(char *s, char *acc)
259 {
260         char tmp[PATH_MAX];
261         FILE *f;
262         if (strchr(acc, '/') || (acc[0] == '.')) {
263                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
264                 return -1;
265         }
266         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", (char *)ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
267
268         ast_mutex_lock(&acf_lock);
269         f = fopen(tmp, "a");
270         if (!f) {
271                 ast_mutex_unlock(&acf_lock);
272                 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
273                 return -1;
274         }
275         fputs(s, f);
276         fflush(f);
277         fclose(f);
278         ast_mutex_unlock(&acf_lock);
279
280         return 0;
281 }
282
283
284 static int csv_log(struct ast_cdr *cdr)
285 {
286         FILE *mf = NULL;
287         /* Make sure we have a big enough buf */
288         char buf[1024];
289         char csvmaster[PATH_MAX];
290         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
291 #if 0
292         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);
293 #endif
294         if (build_csv_record(buf, sizeof(buf), cdr)) {
295                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
296         } else {
297                 /* because of the absolutely unconditional need for the
298                    highest reliability possible in writing billing records,
299                    we open write and close the log file each time */
300                 ast_mutex_lock(&mf_lock);
301                 mf = fopen(csvmaster, "a");
302                 if (mf) {
303                         fputs(buf, mf);
304                         fflush(mf); /* be particularly anal here */
305                         fclose(mf);
306                         mf = NULL;
307                         ast_mutex_unlock(&mf_lock);
308                 } else {
309                         ast_mutex_unlock(&mf_lock);
310                         ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
311                 }
312
313                 if (!ast_strlen_zero(cdr->accountcode)) {
314                         if (writefile(buf, cdr->accountcode))
315                                 ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
316                 }
317         }
318         return 0;
319 }
320
321 static int unload_module(void)
322 {
323         ast_cdr_unregister(name);
324         return 0;
325 }
326
327 static int load_module(void)
328 {
329         int res;
330         
331         if(!load_config(0))
332                 return AST_MODULE_LOAD_DECLINE;
333
334         res = ast_cdr_register(name, ast_module_info->description, csv_log);
335         if (res) {
336                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
337         }
338         return res;
339 }
340
341 static int reload(void)
342 {
343         load_config(1);
344
345         return 0;
346 }
347
348 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
349                 .load = load_module,
350                 .unload = unload_module,
351                 .reload = reload,
352                );