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