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