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