046713a8666e4a7b7d67ea3799075312405f23e7
[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_FILE_VERSION(__FILE__, "$Revision$")
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 const char config[] = "cdr.conf";
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(mf_lock);
97 AST_MUTEX_DEFINE_STATIC(acf_lock);
98
99 static int load_config(int reload)
100 {
101         struct ast_config *cfg;
102         struct ast_variable *v;
103         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
104
105         if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
106                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
107                 return 0;
108         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
109                 return 1;
110         }
111
112         accountlogs = 1;
113         usegmtime = 0;
114         loguniqueid = 0;
115         loguserfield = 0;
116
117         if (!(v = ast_variable_browse(cfg, "csv"))) {
118                 ast_config_destroy(cfg);
119                 return 0;
120         }
121
122         for (; v; v = v->next) {
123                 if (!strcasecmp(v->name, "usegmtime")) {
124                         usegmtime = ast_true(v->value);
125                 } else if (!strcasecmp(v->name, "accountlogs")) {
126                         /* Turn on/off separate files per accountcode. Default is on (as before) */
127                         accountlogs = ast_true(v->value);
128                 } else if (!strcasecmp(v->name, "loguniqueid")) {
129                         loguniqueid = ast_true(v->value);
130                 } else if (!strcasecmp(v->name, "loguserfield")) {
131                         loguserfield = ast_true(v->value);
132                 }
133         }
134         ast_config_destroy(cfg);
135         return 1;
136 }
137
138 static int append_string(char *buf, const char *s, size_t bufsize)
139 {
140         int pos = strlen(buf), spos = 0, error = -1;
141
142         if (pos >= bufsize - 4)
143                 return -1;
144
145         buf[pos++] = '\"';
146
147         while(pos < bufsize - 3) {
148                 if (!s[spos]) {
149                         error = 0;
150                         break;
151                 }
152                 if (s[spos] == '\"')
153                         buf[pos++] = '\"';
154                 buf[pos++] = s[spos];
155                 spos++;
156         }
157
158         buf[pos++] = '\"';
159         buf[pos++] = ',';
160         buf[pos++] = '\0';
161
162         return error;
163 }
164
165 static int append_int(char *buf, int s, size_t bufsize)
166 {
167         char tmp[32];
168         int pos = strlen(buf);
169
170         snprintf(tmp, sizeof(tmp), "%d", s);
171
172         if (pos + strlen(tmp) > bufsize - 3)
173                 return -1;
174
175         strncat(buf, tmp, bufsize - strlen(buf) - 1);
176         pos = strlen(buf);
177         buf[pos++] = ',';
178         buf[pos++] = '\0';
179
180         return 0;
181 }
182
183 static int append_date(char *buf, struct timeval when, size_t bufsize)
184 {
185         char tmp[80] = "";
186         struct ast_tm tm;
187
188         if (strlen(buf) > bufsize - 3)
189                 return -1;
190
191         if (ast_tvzero(when)) {
192                 strncat(buf, ",", bufsize - strlen(buf) - 1);
193                 return 0;
194         }
195
196         ast_localtime(&when, &tm, usegmtime ? "GMT" : NULL);
197         ast_strftime(tmp, sizeof(tmp), DATE_FORMAT, &tm);
198
199         return append_string(buf, tmp, bufsize);
200 }
201
202 static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
203 {
204
205         buf[0] = '\0';
206         /* Account code */
207         append_string(buf, cdr->accountcode, bufsize);
208         /* Source */
209         append_string(buf, cdr->src, bufsize);
210         /* Destination */
211         append_string(buf, cdr->dst, bufsize);
212         /* Destination context */
213         append_string(buf, cdr->dcontext, bufsize);
214         /* Caller*ID */
215         append_string(buf, cdr->clid, bufsize);
216         /* Channel */
217         append_string(buf, cdr->channel, bufsize);
218         /* Destination Channel */
219         append_string(buf, cdr->dstchannel, bufsize);
220         /* Last Application */
221         append_string(buf, cdr->lastapp, bufsize);
222         /* Last Data */
223         append_string(buf, cdr->lastdata, bufsize);
224         /* Start Time */
225         append_date(buf, cdr->start, bufsize);
226         /* Answer Time */
227         append_date(buf, cdr->answer, bufsize);
228         /* End Time */
229         append_date(buf, cdr->end, bufsize);
230         /* Duration */
231         append_int(buf, cdr->duration, bufsize);
232         /* Billable seconds */
233         append_int(buf, cdr->billsec, bufsize);
234         /* Disposition */
235         append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
236         /* AMA Flags */
237         append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize);
238         /* Unique ID */
239         if (loguniqueid)
240                 append_string(buf, cdr->uniqueid, bufsize);
241         /* append the user field */
242         if(loguserfield)
243                 append_string(buf, cdr->userfield,bufsize);
244         /* If we hit the end of our buffer, log an error */
245         if (strlen(buf) < bufsize - 5) {
246                 /* Trim off trailing comma */
247                 buf[strlen(buf) - 1] = '\0';
248                 strncat(buf, "\n", bufsize - strlen(buf) - 1);
249                 return 0;
250         }
251         return -1;
252 }
253
254 static int writefile(char *s, char *acc)
255 {
256         char tmp[PATH_MAX];
257         FILE *f;
258
259         if (strchr(acc, '/') || (acc[0] == '.')) {
260                 ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", acc);
261                 return -1;
262         }
263
264         snprintf(tmp, sizeof(tmp), "%s/%s/%s.csv", ast_config_AST_LOG_DIR,CSV_LOG_DIR, acc);
265
266         ast_mutex_lock(&acf_lock);
267         if (!(f = fopen(tmp, "a"))) {
268                 ast_mutex_unlock(&acf_lock);
269                 ast_log(LOG_ERROR, "Unable to open file %s : %s\n", tmp, strerror(errno));
270                 return -1;
271         }
272         fputs(s, f);
273         fflush(f);
274         fclose(f);
275         ast_mutex_unlock(&acf_lock);
276
277         return 0;
278 }
279
280
281 static int csv_log(struct ast_cdr *cdr)
282 {
283         FILE *mf = NULL;
284         /* Make sure we have a big enough buf */
285         char buf[1024];
286         char csvmaster[PATH_MAX];
287         snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
288         if (build_csv_record(buf, sizeof(buf), cdr)) {
289                 ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
290                 return 0;
291         }
292
293         /* because of the absolutely unconditional need for the
294            highest reliability possible in writing billing records,
295            we open write and close the log file each time */
296         ast_mutex_lock(&mf_lock);
297         if ((mf = fopen(csvmaster, "a"))) {
298                 fputs(buf, mf);
299                 fflush(mf); /* be particularly anal here */
300                 fclose(mf);
301                 mf = NULL;
302                 ast_mutex_unlock(&mf_lock);
303         } else {
304                 ast_mutex_unlock(&mf_lock);
305                 ast_log(LOG_ERROR, "Unable to re-open master file %s : %s\n", csvmaster, strerror(errno));
306         }
307
308         if (accountlogs && !ast_strlen_zero(cdr->accountcode)) {
309                 if (writefile(buf, cdr->accountcode))
310                         ast_log(LOG_WARNING, "Unable to write CSV record to account file '%s' : %s\n", cdr->accountcode, strerror(errno));
311         }
312
313         return 0;
314 }
315
316 static int unload_module(void)
317 {
318         if (ast_cdr_unregister(name)) {
319                 return -1;
320         }
321
322         loaded = 0;
323         return 0;
324 }
325
326 static int load_module(void)
327 {
328         int res;
329
330         if (!load_config(0)) {
331                 return AST_MODULE_LOAD_DECLINE;
332         }
333
334         if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
335                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
336         } else {
337                 loaded = 1;
338         }
339         return res;
340 }
341
342 static int reload(void)
343 {
344         if (load_config(1)) {
345                 loaded = 1;
346         } else {
347                 loaded = 0;
348                 ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
349                 ast_cdr_unregister(name);
350         }
351
352         return 0;
353 }
354
355 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
356                 .support_level = AST_MODULE_SUPPORT_EXTENDED,
357                 .load = load_module,
358                 .unload = unload_module,
359                 .reload = reload,
360                 .load_pri = AST_MODPRI_CDR_DRIVER,
361                );