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