constification, remove include
[asterisk/asterisk.git] / cdr / cdr_csv.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * Includes code and algorithms from the Zapata library.
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief Comma Separated Value CDR records.
24  *
25  * \author Mark Spencer <markster@digium.com>
26  *
27  * \arg See also \ref AstCDR
28  * \ingroup cdr_drivers
29  */
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/paths.h"     /* use ast_config_AST_LOG_DIR */
36 #include "asterisk/config.h"
37 #include "asterisk/channel.h"
38 #include "asterisk/cdr.h"
39 #include "asterisk/module.h"
40 #include "asterisk/utils.h"
41 #include "asterisk/lock.h"
42
43 #define CSV_LOG_DIR "/cdr-csv"
44 #define CSV_MASTER  "/Master.csv"
45
46 #define DATE_FORMAT "%Y-%m-%d %T"
47
48 static int usegmtime = 0;
49 static int loguniqueid = 0;
50 static int loguserfield = 0;
51 static int loaded = 0;
52 static char *config = "cdr.conf";
53
54 /* #define CSV_LOGUNIQUEID 1 */
55 /* #define CSV_LOGUSERFIELD 1 */
56
57 /*----------------------------------------------------
58   The values are as follows:
59
60
61   "accountcode",        accountcode is the account name of detail records, Master.csv contains all records *
62                         Detail records are configured on a channel basis, IAX and SIP are determined by user *
63                         DAHDI is determined by channel in dahdi.conf
64   "source",
65   "destination",
66   "destination context",
67   "callerid",
68   "channel",
69   "destination channel",        (if applicable)
70   "last application",   Last application run on the channel
71   "last app argument",  argument to the last channel
72   "start time",
73   "answer time",
74   "end time",
75   duration,             Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
76                         "end time" minus "start time"
77   billable seconds,     the duration that a call was up after other end answered which will be <= to duration
78                         "end time" minus "answer time"
79   "disposition",        ANSWERED, NO ANSWER, BUSY
80   "amaflags",           DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
81   "uniqueid",           unique call identifier
82   "userfield"           user field set via SetCDRUserField
83 ----------------------------------------------------------*/
84
85 static char *name = "csv";
86
87 AST_MUTEX_DEFINE_STATIC(mf_lock);
88 AST_MUTEX_DEFINE_STATIC(acf_lock);
89
90 static int load_config(int reload)
91 {
92         struct ast_config *cfg;
93         struct ast_variable *var;
94         const char *tmp;
95         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
96
97         if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
98                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
99                 return 0;
100         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
101                 return 1;
102
103         usegmtime = 0;
104         loguniqueid = 0;
105         loguserfield = 0;
106
107         if (!(var = ast_variable_browse(cfg, "csv"))) {
108                 ast_config_destroy(cfg);
109                 return 0;
110         }
111
112         if ((tmp = ast_variable_retrieve(cfg, "csv", "usegmtime"))) {
113                 usegmtime = ast_true(tmp);
114                 if (usegmtime)
115                         ast_debug(1, "logging time in GMT\n");
116         }
117
118         if ((tmp = ast_variable_retrieve(cfg, "csv", "loguniqueid"))) {
119                 loguniqueid = ast_true(tmp);
120                 if (loguniqueid)
121                         ast_debug(1, "logging CDR field UNIQUEID\n");
122         }
123
124         if ((tmp = ast_variable_retrieve(cfg, "csv", "loguserfield"))) {
125                 loguserfield = ast_true(tmp);
126                 if (loguserfield)
127                         ast_debug(1, "logging CDR user-defined field\n");
128         }
129
130         ast_config_destroy(cfg);
131         return 1;
132 }
133
134 static int append_string(char *buf, const char *s, size_t bufsize)
135 {
136         int pos = strlen(buf), spos = 0, error = -1;
137
138         if (pos >= bufsize - 4)
139                 return -1;
140
141         buf[pos++] = '\"';
142
143         while(pos < bufsize - 3) {
144                 if (!s[spos]) {
145                         error = 0;
146                         break;
147                 }
148                 if (s[spos] == '\"')
149                         buf[pos++] = '\"';
150                 buf[pos++] = s[spos];
151                 spos++;
152         }
153
154         buf[pos++] = '\"';
155         buf[pos++] = ',';
156         buf[pos++] = '\0';
157
158         return error;
159 }
160
161 static int append_int(char *buf, int s, size_t bufsize)
162 {
163         char tmp[32];
164         int pos = strlen(buf);
165
166         snprintf(tmp, sizeof(tmp), "%d", s);
167
168         if (pos + strlen(tmp) > bufsize - 3)
169                 return -1;
170
171         strncat(buf, tmp, bufsize - strlen(buf) - 1);
172         pos = strlen(buf);
173         buf[pos++] = ',';
174         buf[pos++] = '\0';
175
176         return 0;
177 }
178
179 static int append_date(char *buf, struct timeval when, size_t bufsize)
180 {
181         char tmp[80] = "";
182         struct ast_tm tm;
183
184         if (strlen(buf) > bufsize - 3)
185                 return -1;
186
187         if (ast_tvzero(when)) {
188                 strncat(buf, ",", bufsize - strlen(buf) - 1);
189                 return 0;
190         }
191
192         ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
193         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
194
195         return append_string(buf, tmp, bufsize);
196 }
197
198 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
199 {
200
201         buf[0] = '\0';
202         /* Account code */
203         append_string(buf, cdr->accountcode, bufsize);
204         /* Source */
205         append_string(buf, cdr->src, bufsize);
206         /* Destination */
207         append_string(buf, cdr->dst, bufsize);
208         /* Destination context */
209         append_string(buf, cdr->dcontext, bufsize);
210         /* Caller*ID */
211         append_string(buf, cdr->clid, bufsize);
212         /* Channel */
213         append_string(buf, cdr->channel, bufsize);
214         /* Destination Channel */
215         append_string(buf, cdr->dstchannel, bufsize);
216         /* Last Application */
217         append_string(buf, cdr->lastapp, bufsize);
218         /* Last Data */
219         append_string(buf, cdr->lastdata, bufsize);
220         /* Start Time */
221         append_date(buf, cdr->start, bufsize);
222         /* Answer Time */
223         append_date(buf, cdr->answer, bufsize);
224         /* End Time */
225         append_date(buf, cdr->end, bufsize);
226         /* Duration */
227         append_int(buf, cdr->duration, bufsize);
228         /* Billable seconds */
229         append_int(buf, cdr->billsec, bufsize);
230         /* Disposition */
231         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
232         /* AMA Flags */
233         append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
234         /* Unique ID */
235         if (loguniqueid)
236                 append_string(buf, cdr->uniqueid, bufsize);
237         /* append the user field */
238         if(loguserfield)
239                 append_string(buf, cdr->userfield,bufsize);
240         /* If we hit the end of our buffer, log an error */
241         if (strlen(buf) < bufsize - 5) {
242                 /* Trim off trailing comma */
243                 buf[strlen(buf) - 1] = '\0';
244                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
245                 return 0;
246         }
247         return -1;
248 }
249
250 static int writefile(char *s, char *acc)
251 {
252         char tmp[PATH_MAX];
253         FILE *f;
254
255         if (strchr(acc, '/') || (acc[0] == '.')) {
256                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
257                 return -1;
258         }
259
260         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
261
262         ast_mutex_lock(&acf_lock);
263         if (!(f = fopen(tmp, "a"))) {
264                 ast_mutex_unlock(&acf_lock);
265                 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
266                 return -1;
267         }
268         fputs(s, f);
269         fflush(f);
270         fclose(f);
271         ast_mutex_unlock(&acf_lock);
272
273         return 0;
274 }
275
276
277 static int csv_log(struct ast_cdr *cdr)
278 {
279         FILE *mf = NULL;
280         /* Make sure we have a big enough buf */
281         char buf[1024];
282         char csvmaster[PATH_MAX];
283         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
284 #if 0
285         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);
286 #endif
287         if (build_csv_record(buf, sizeof(buf), cdr)) {
288                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
289                 return 0;
290         }
291
292         /* because of the absolutely unconditional need for the
293            highest reliability possible in writing billing records,
294            we open write and close the log file each time */
295         ast_mutex_lock(&mf_lock);
296         if ((mf = fopen(csvmaster, "a"))) {
297                 fputs(buf, mf);
298                 fflush(mf); /* be particularly anal here */
299                 fclose(mf);
300                 mf = NULL;
301                 ast_mutex_unlock(&mf_lock);
302         } else {
303                 ast_mutex_unlock(&mf_lock);
304                 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
305         }
306
307         if (!ast_strlen_zero(cdr->accountcode)) {
308                 if (writefile(buf, cdr->accountcode))
309                         ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
310         }
311
312         return 0;
313 }
314
315 static int unload_module(void)
316 {
317         ast_cdr_unregister(name);
318         loaded = 0;
319         return 0;
320 }
321
322 static int load_module(void)
323 {
324         int res;
325
326         if(!load_config(0))
327                 return AST_MODULE_LOAD_DECLINE;
328
329         if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
330                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
331         } else {
332                 loaded = 1;
333         }
334         return res;
335 }
336
337 static int reload(void)
338 {
339         if (load_config(1)) {
340                 loaded = 1;
341         } else {
342                 loaded = 0;
343                 ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
344                 ast_cdr_unregister(name);
345         }
346
347         return 0;
348 }
349
350 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Comma Separated Values CDR Backend",
351                 .load = load_module,
352                 .unload = unload_module,
353                 .reload = reload,
354                );