install_prereq: Add SUSE.
[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 /*!
22  * \file
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 /*! \li \ref cdr_csv.c uses the configuration file \ref cdr.conf
32  * \addtogroup configuration_file Configuration Files
33  */
34
35 /*** MODULEINFO
36         <support_level>extended</support_level>
37  ***/
38
39 #include "asterisk.h"
40
41 #include "asterisk/paths.h"     /* use ast_config_AST_LOG_DIR */
42 #include "asterisk/config.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/cdr.h"
45 #include "asterisk/module.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/lock.h"
48
49 #define CSV_LOG_DIR "/cdr-csv"
50 #define CSV_MASTER  "/Master.csv"
51
52 #define DATE_FORMAT "%Y-%m-%d %T"
53
54 static int usegmtime = 0;
55 static int accountlogs = 1;
56 static int loguniqueid = 0;
57 static int loguserfield = 0;
58 static int loaded = 0;
59 static int newcdrcolumns = 0;
60 static const char config[] = "cdr.conf";
61 static char file_csv_master[PATH_MAX];
62
63 /* #define CSV_LOGUNIQUEID 1 */
64 /* #define CSV_LOGUSERFIELD 1 */
65
66 /*----------------------------------------------------
67   The values are as follows:
68
69
70   "accountcode",        accountcode is the account name of detail records, Master.csv contains all records *
71                         Detail records are configured on a channel basis, IAX and SIP are determined by user *
72                         DAHDI is determined by channel in dahdi.conf
73   "source",
74   "destination",
75   "destination context",
76   "callerid",
77   "channel",
78   "destination channel",        (if applicable)
79   "last application",   Last application run on the channel
80   "last app argument",  argument to the last channel
81   "start time",
82   "answer time",
83   "end time",
84   duration,             Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
85                         "end time" minus "start time"
86   billable seconds,     the duration that a call was up after other end answered which will be <= to duration
87                         "end time" minus "answer time"
88   "disposition",        ANSWERED, NO ANSWER, BUSY
89   "amaflags",           DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
90   "uniqueid",           unique call identifier
91   "userfield"           user field set via SetCDRUserField
92 ----------------------------------------------------------*/
93
94 static char *name = "csv";
95
96 AST_MUTEX_DEFINE_STATIC(f_lock);
97
98 static int load_config(int reload)
99 {
100         struct ast_config *cfg;
101         struct ast_variable *v;
102         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
103
104         if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
105                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
106                 return 0;
107         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
108                 return 1;
109         }
110
111         accountlogs = 1;
112         usegmtime = 0;
113         loguniqueid = 0;
114         loguserfield = 0;
115         newcdrcolumns = 0;
116
117         if (!(v = ast_variable_browse(cfg, "csv"))) {
118                 ast_config_destroy(cfg);
119                 return 0;
120         }
121
122         /* compute the location of the csv master file */
123         ast_mutex_lock(&f_lock);
124         snprintf(file_csv_master, sizeof(file_csv_master),
125                 "%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
126         ast_mutex_unlock(&f_lock);
127
128         for (; v; v = v->next) {
129                 if (!strcasecmp(v->name, "usegmtime")) {
130                         usegmtime = ast_true(v->value);
131                 } else if (!strcasecmp(v->name, "accountlogs")) {
132                         /* Turn on/off separate files per accountcode. Default is on (as before) */
133                         accountlogs = ast_true(v->value);
134                 } else if (!strcasecmp(v->name, "loguniqueid")) {
135                         loguniqueid = ast_true(v->value);
136                 } else if (!strcasecmp(v->name, "loguserfield")) {
137                         loguserfield = ast_true(v->value);
138                 } else if (!strcasecmp(v->name, "newcdrcolumns")) {
139                         newcdrcolumns = ast_true(v->value);
140                 }
141
142         }
143         ast_config_destroy(cfg);
144         return 1;
145 }
146
147 static int append_string(char *buf, const char *s, size_t bufsize)
148 {
149         int pos = strlen(buf), spos = 0, error = -1;
150
151         if (pos >= bufsize - 4)
152                 return -1;
153
154         buf[pos++] = '\"';
155
156         while(pos < bufsize - 3) {
157                 if (!s[spos]) {
158                         error = 0;
159                         break;
160                 }
161                 if (s[spos] == '\"')
162                         buf[pos++] = '\"';
163                 buf[pos++] = s[spos];
164                 spos++;
165         }
166
167         buf[pos++] = '\"';
168         buf[pos++] = ',';
169         buf[pos++] = '\0';
170
171         return error;
172 }
173
174 static int append_int(char *buf, int s, size_t bufsize)
175 {
176         char tmp[32];
177         int pos = strlen(buf);
178
179         snprintf(tmp, sizeof(tmp), "%d", s);
180
181         if (pos + strlen(tmp) > bufsize - 3)
182                 return -1;
183
184         strncat(buf, tmp, bufsize - strlen(buf) - 1);
185         pos = strlen(buf);
186         buf[pos++] = ',';
187         buf[pos++] = '\0';
188
189         return 0;
190 }
191
192 static int append_date(char *buf, struct timeval when, size_t bufsize)
193 {
194         char tmp[80] = "";
195         struct ast_tm tm;
196
197         if (strlen(buf) > bufsize - 3)
198                 return -1;
199
200         if (ast_tvzero(when)) {
201                 strncat(buf, ",", bufsize - strlen(buf) - 1);
202                 return 0;
203         }
204
205         ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
206         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
207
208         return append_string(buf, tmp, bufsize);
209 }
210
211 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
212 {
213
214         buf[0] = '\0';
215         /* Account code */
216         append_string(buf, cdr->accountcode, bufsize);
217         /* Source */
218         append_string(buf, cdr->src, bufsize);
219         /* Destination */
220         append_string(buf, cdr->dst, bufsize);
221         /* Destination context */
222         append_string(buf, cdr->dcontext, bufsize);
223         /* Caller*ID */
224         append_string(buf, cdr->clid, bufsize);
225         /* Channel */
226         append_string(buf, cdr->channel, bufsize);
227         /* Destination Channel */
228         append_string(buf, cdr->dstchannel, bufsize);
229         /* Last Application */
230         append_string(buf, cdr->lastapp, bufsize);
231         /* Last Data */
232         append_string(buf, cdr->lastdata, bufsize);
233         /* Start Time */
234         append_date(buf, cdr->start, bufsize);
235         /* Answer Time */
236         append_date(buf, cdr->answer, bufsize);
237         /* End Time */
238         append_date(buf, cdr->end, bufsize);
239         /* Duration */
240         append_int(buf, cdr->duration, bufsize);
241         /* Billable seconds */
242         append_int(buf, cdr->billsec, bufsize);
243         /* Disposition */
244         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
245         /* AMA Flags */
246         append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize);
247         /* Unique ID */
248         if (loguniqueid)
249                 append_string(buf, cdr->uniqueid, bufsize);
250         /* append the user field */
251         if(loguserfield)
252                 append_string(buf, cdr->userfield, bufsize);
253         if (newcdrcolumns) {
254                 append_string(buf, cdr->peeraccount, bufsize);
255                 append_string(buf, cdr->linkedid, bufsize);
256                 append_int(buf, cdr->sequence, bufsize);
257         }
258         /* If we hit the end of our buffer, log an error */
259         if (strlen(buf) < bufsize - 5) {
260                 /* Trim off trailing comma */
261                 buf[strlen(buf) - 1] = '\0';
262                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
263                 return 0;
264         }
265         return -1;
266 }
267
268 static int writefile(char *s, char *file_path)
269 {
270         FILE *f;
271         /* because of the absolutely unconditional need for the
272            highest reliability possible in writing billing records,
273            we open write and close the log file each time */
274         if (!(f = fopen(file_path, "a"))) {
275                 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", file_path, strerror(errno));
276                 return -1;
277         }
278         fputs(s, f);
279         fflush(f); /* be particularly anal here */
280         fclose(f);
281
282         return 0;
283 }
284
285
286 static int writefile_account(char *s, char *acc)
287 {
288         char file_account[PATH_MAX];
289         if (strchr(acc, '/') || (acc[0] == '.')) {
290                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
291                 return -1;
292         }
293         snprintf(file_account, sizeof(file_account), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
294         return writefile(s, file_account);
295 }
296
297 static int csv_log(struct ast_cdr *cdr)
298 {
299         /* Make sure we have a big enough buf */
300         char buf[1024];
301         if (build_csv_record(buf, sizeof(buf), cdr)) {
302                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
303                 return 0;
304         }
305
306         ast_mutex_lock(&f_lock);
307         if (writefile(buf, file_csv_master))
308                 ast_log(LOG_WARNING, "Unable to write CSV record to master '%s' : %s\n", file_csv_master, strerror(errno));
309
310         if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
311                 if (writefile_account(buf, cdr->accountcode))
312                         ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
313         }
314         ast_mutex_unlock(&f_lock);
315         return 0;
316 }
317
318 static int unload_module(void)
319 {
320         if (ast_cdr_unregister(name)) {
321                 return -1;
322         }
323
324         loaded = 0;
325         return 0;
326 }
327
328 static int load_module(void)
329 {
330         int res;
331
332         if (!load_config(0)) {
333                 return AST_MODULE_LOAD_DECLINE;
334         }
335
336         if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
337                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
338         } else {
339                 loaded = 1;
340         }
341         return res;
342 }
343
344 static int reload(void)
345 {
346         if (load_config(1)) {
347                 loaded = 1;
348         } else {
349                 loaded = 0;
350                 ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
351                 ast_cdr_unregister(name);
352         }
353
354         return 0;
355 }
356
357 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
358         .support_level = AST_MODULE_SUPPORT_EXTENDED,
359         .load = load_module,
360         .unload = unload_module,
361         .reload = reload,
362         .load_pri = AST_MODPRI_CDR_DRIVER,
363 );