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