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