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