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