Add CLI command "cdr show pgsql status" based on "cdr mysql status"
[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 /*** MODULEINFO
32         <support_level>extended</support_level>
33  ***/
34
35 #include "asterisk.h"
36
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38
39 #include "asterisk/paths.h"     /* use ast_config_AST_LOG_DIR */
40 #include "asterisk/config.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/cdr.h"
43 #include "asterisk/module.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/lock.h"
46
47 #define CSV_LOG_DIR "/cdr-csv"
48 #define CSV_MASTER  "/Master.csv"
49
50 #define DATE_FORMAT "%Y-%m-%d %T"
51
52 static int usegmtime = 0;
53 static int accountlogs = 1;
54 static int loguniqueid = 0;
55 static int loguserfield = 0;
56 static int loaded = 0;
57 static const char config[] = "cdr.conf";
58
59 /* #define CSV_LOGUNIQUEID 1 */
60 /* #define CSV_LOGUSERFIELD 1 */
61
62 /*----------------------------------------------------
63   The values are as follows:
64
65
66   "accountcode",        accountcode is the account name of detail records, Master.csv contains all records *
67                         Detail records are configured on a channel basis, IAX and SIP are determined by user *
68                         DAHDI is determined by channel in dahdi.conf
69   "source",
70   "destination",
71   "destination context",
72   "callerid",
73   "channel",
74   "destination channel",        (if applicable)
75   "last application",   Last application run on the channel
76   "last app argument",  argument to the last channel
77   "start time",
78   "answer time",
79   "end time",
80   duration,             Duration is the whole length that the entire call lasted. ie. call rx'd to hangup
81                         "end time" minus "start time"
82   billable seconds,     the duration that a call was up after other end answered which will be <= to duration
83                         "end time" minus "answer time"
84   "disposition",        ANSWERED, NO ANSWER, BUSY
85   "amaflags",           DOCUMENTATION, BILL, IGNORE etc, specified on a per channel basis like accountcode.
86   "uniqueid",           unique call identifier
87   "userfield"           user field set via SetCDRUserField
88 ----------------------------------------------------------*/
89
90 static char *name = "csv";
91
92 AST_MUTEX_DEFINE_STATIC(mf_lock);
93 AST_MUTEX_DEFINE_STATIC(acf_lock);
94
95 static int load_config(int reload)
96 {
97         struct ast_config *cfg;
98         struct ast_variable *v;
99         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
100
101         if (!(cfg = ast_config_load(config, config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
102                 ast_log(LOG_WARNING, "unable to load config: %s\n", config);
103                 return 0;
104         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
105                 return 1;
106         }
107
108         accountlogs = 1;
109         usegmtime = 0;
110         loguniqueid = 0;
111         loguserfield = 0;
112
113         if (!(v = ast_variable_browse(cfg, "csv"))) {
114                 ast_config_destroy(cfg);
115                 return 0;
116         }
117
118         for (; v; v = v->next) {
119                 if (!strcasecmp(v->name, "usegmtime")) {
120                         usegmtime = ast_true(v->value);
121                 } else if (!strcasecmp(v->name, "accountlogs")) {
122                         /* Turn on/off separate files per accountcode. Default is on (as before) */
123                         accountlogs = ast_true(v->value);
124                 } else if (!strcasecmp(v->name, "loguniqueid")) {
125                         loguniqueid = ast_true(v->value);
126                 } else if (!strcasecmp(v->name, "loguserfield")) {
127                         loguserfield = ast_true(v->value);
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 (accountlogs && !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
330         if ((res = ast_cdr_register(name, ast_module_info->description, csv_log))) {
331                 ast_log(LOG_ERROR, "Unable to register CSV CDR handling\n");
332         } else {
333                 loaded = 1;
334         }
335         return res;
336 }
337
338 static int reload(void)
339 {
340         if (load_config(1)) {
341                 loaded = 1;
342         } else {
343                 loaded = 0;
344                 ast_log(LOG_WARNING, "No [csv] section in cdr.conf.  Unregistering backend.\n");
345                 ast_cdr_unregister(name);
346         }
347
348         return 0;
349 }
350
351 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Comma Separated Values CDR Backend",
352                 .load = load_module,
353                 .unload = unload_module,
354                 .reload = reload,
355                 .load_pri = AST_MODPRI_CDR_DRIVER,
356                );