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