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