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