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