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