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