1 /*
2 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Anthony Minessale II <anthm@freeswitch.org>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Brian West <brian@freeswitch.org>
27 * Bret McDanel <trixter AT 0xdecafbad.com>
28 * Justin Cassidy <xachenant@hotmail.com>
29 *
30 * mod_format_cdr.c -- XML CDR Module to files or curl
31 *
32 */
33 #include <switch.h>
34 #include <sys/stat.h>
35 #include <switch_curl.h>
36 #define MAX_URLS 20
37 #define MAX_ERR_DIRS 20
38
39 #define ENCODING_NONE 0
40 #define ENCODING_DEFAULT 1
41 #define ENCODING_BASE64 2
42 #define ENCODING_TEXTXML 3
43 #define ENCODING_APPLJSON 4
44
45 static struct {
46 switch_hash_t *profile_hash;
47 switch_memory_pool_t *pool;
48 switch_event_node_t *node;
49 switch_mutex_t *mutex;
50 uint32_t shutdown;
51 } globals;
52
53 struct cdr_profile {
54 char *name;
55 char *format;
56 char *cred;
57 char *urls[MAX_URLS + 1];
58 int url_count;
59 int url_index;
60 switch_thread_rwlock_t *log_path_lock;
61 char *base_log_dir;
62 char *base_err_log_dir[MAX_ERR_DIRS];
63 char *log_dir;
64 char *err_log_dir[MAX_ERR_DIRS];
65 int err_dir_count;
66 char *log_file;
67 uint32_t delay;
68 uint32_t retries;
69 uint32_t enable_cacert_check;
70 char *ssl_cert_file;
71 char *ssl_key_file;
72 char *ssl_key_password;
73 char *ssl_version;
74 char *ssl_cacert_file;
75 uint32_t enable_ssl_verifyhost;
76 int encode;
77 int encode_values;
78 int log_http_and_disk;
79 int log_b;
80 int prefix_a;
81 int disable100continue;
82 int rotate;
83 long auth_scheme;
84 int timeout;
85 switch_memory_pool_t *pool;
86 };
87 typedef struct cdr_profile cdr_profile_t;
88
89 SWITCH_MODULE_LOAD_FUNCTION(mod_format_cdr_load);
90 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_format_cdr_shutdown);
91 SWITCH_MODULE_DEFINITION(mod_format_cdr, mod_format_cdr_load, mod_format_cdr_shutdown, NULL);
92
93 /* this function would have access to the HTML returned by the webserver, we don't need it
94 * and the default curl activity is to print to stdout, something not as desirable
95 * so we have a dummy function here
96 */
httpCallBack(char * buffer,size_t size,size_t nitems,void * outstream)97 static size_t httpCallBack(char *buffer, size_t size, size_t nitems, void *outstream)
98 {
99 return size * nitems;
100 }
101
set_format_cdr_log_dirs(cdr_profile_t * profile)102 static switch_status_t set_format_cdr_log_dirs(cdr_profile_t *profile)
103 {
104 switch_time_exp_t tm;
105 char *path = NULL;
106 char date[80] = "";
107 switch_size_t retsize;
108 switch_status_t status = SWITCH_STATUS_SUCCESS, dir_status;
109 int err_dir_index;
110
111 switch_time_exp_lt(&tm, switch_micro_time_now());
112 switch_strftime_nocheck(date, &retsize, sizeof(date), "%Y-%m-%d-%H-%M-%S", &tm);
113
114 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Rotating log file paths\n");
115
116 if (!zstr(profile->base_log_dir)) {
117 if (profile->rotate) {
118 if ((path = switch_mprintf("%s%s%s", profile->base_log_dir, SWITCH_PATH_SEPARATOR, date))) {
119 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Rotating log file path to %s\n", path);
120
121 dir_status = SWITCH_STATUS_SUCCESS;
122 if (switch_directory_exists(path, profile->pool) != SWITCH_STATUS_SUCCESS) {
123 dir_status = switch_dir_make(path, SWITCH_FPROT_OS_DEFAULT, profile->pool);
124 }
125
126 if (dir_status == SWITCH_STATUS_SUCCESS) {
127 switch_thread_rwlock_wrlock(profile->log_path_lock);
128 switch_safe_free(profile->log_dir);
129 profile->log_dir = path;
130 switch_thread_rwlock_unlock(profile->log_path_lock);
131 } else {
132 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new mod_format_cdr log_dir path\n");
133 switch_safe_free(path);
134 status = SWITCH_STATUS_FALSE;
135 }
136 } else {
137 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to generate new mod_format_cdr log_dir path\n");
138 status = SWITCH_STATUS_FALSE;
139 }
140 } else {
141 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Setting log file path to %s\n", profile->base_log_dir);
142 if ((path = switch_safe_strdup(profile->base_log_dir))) {
143 switch_thread_rwlock_wrlock(profile->log_path_lock);
144 switch_safe_free(profile->log_dir);
145 switch_dir_make_recursive(path, SWITCH_DEFAULT_DIR_PERMS, profile->pool);
146 profile->log_dir = path;
147 switch_thread_rwlock_unlock(profile->log_path_lock);
148 } else {
149 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to set log_dir path\n");
150 status = SWITCH_STATUS_FALSE;
151 }
152 }
153 }
154
155 for (err_dir_index = 0; err_dir_index < profile->err_dir_count; err_dir_index++) {
156 if (profile->rotate) {
157 if ((path = switch_mprintf("%s%s%s", profile->base_err_log_dir[err_dir_index], SWITCH_PATH_SEPARATOR, date))) {
158 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Rotating err log file path to %s\n", path);
159
160 dir_status = SWITCH_STATUS_SUCCESS;
161 if (switch_directory_exists(path, profile->pool) != SWITCH_STATUS_SUCCESS) {
162 dir_status = switch_dir_make(path, SWITCH_FPROT_OS_DEFAULT, profile->pool);
163 }
164
165 if (dir_status == SWITCH_STATUS_SUCCESS) {
166 switch_thread_rwlock_wrlock(profile->log_path_lock);
167 switch_safe_free(profile->err_log_dir[err_dir_index]);
168 profile->err_log_dir[err_dir_index] = path;
169 switch_thread_rwlock_unlock(profile->log_path_lock);
170 } else {
171 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create new mod_format_cdr err_log_dir path\n");
172 switch_safe_free(path);
173 status = SWITCH_STATUS_FALSE;
174 }
175 } else {
176 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to generate new mod_format_cdr err_log_dir path\n");
177 status = SWITCH_STATUS_FALSE;
178 }
179 } else {
180 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Setting err log file path to %s\n", profile->base_err_log_dir[err_dir_index]);
181 if ((path = switch_safe_strdup(profile->base_err_log_dir[err_dir_index]))) {
182 switch_thread_rwlock_wrlock(profile->log_path_lock);
183 switch_safe_free(profile->err_log_dir[err_dir_index]);
184 switch_dir_make_recursive(path, SWITCH_DEFAULT_DIR_PERMS, profile->pool);
185 profile->err_log_dir[err_dir_index] = path;
186 switch_thread_rwlock_unlock(profile->log_path_lock);
187 } else {
188 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to set err_log_dir path\n");
189 status = SWITCH_STATUS_FALSE;
190 }
191 }
192 }
193
194 return status;
195 }
196
my_on_reporting_cb(switch_core_session_t * session,cdr_profile_t * profile)197 static switch_status_t my_on_reporting_cb(switch_core_session_t *session, cdr_profile_t *profile)
198 {
199 switch_xml_t xml_cdr = NULL;
200 cJSON *json_cdr = NULL;
201 char *cdr_text = NULL;
202 char *lfile = NULL;
203 char *dpath = NULL;
204 char *path = NULL;
205 char *curl_cdr_text = NULL;
206 const char *logdir = NULL;
207 char *cdr_text_escaped = NULL;
208 int fd = -1;
209 uint32_t cur_try;
210 long httpRes;
211 switch_CURL *curl_handle = NULL;
212 switch_curl_slist_t *headers = NULL;
213 switch_curl_slist_t *slist = NULL;
214 switch_channel_t *channel = switch_core_session_get_channel(session);
215 switch_status_t status = SWITCH_STATUS_FALSE;
216 int is_b;
217 const char *a_prefix = "";
218
219 if (globals.shutdown) {
220 return SWITCH_STATUS_SUCCESS;
221 }
222
223 is_b = channel && switch_channel_get_originator_caller_profile(channel);
224 if (!profile->log_b && is_b) {
225 const char *force_cdr = switch_channel_get_variable(channel, SWITCH_FORCE_PROCESS_CDR_VARIABLE);
226 if (!switch_true(force_cdr)) {
227 return SWITCH_STATUS_SUCCESS;
228 }
229 }
230 if (!is_b && profile->prefix_a)
231 a_prefix = "a_";
232
233 if ( ! strcasecmp(profile->format, "json") ) {
234 if (switch_ivr_generate_json_cdr(session, &json_cdr, profile->encode_values == ENCODING_DEFAULT) != SWITCH_STATUS_SUCCESS) {
235 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Generating JSON Data!\n");
236 return SWITCH_STATUS_FALSE;
237 }
238
239 /* build the JSON */
240 cdr_text = cJSON_PrintUnformatted(json_cdr);
241
242 if (!cdr_text) {
243 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error generating JSON!\n");
244 goto error;
245 }
246 } else if ( ! strcasecmp(profile->format, "xml") ) {
247 if (switch_ivr_generate_xml_cdr(session, &xml_cdr) != SWITCH_STATUS_SUCCESS) {
248 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Generating XML Data!\n");
249 return SWITCH_STATUS_FALSE;
250 }
251
252 /* build the XML */
253 cdr_text = switch_xml_toxml(xml_cdr, SWITCH_TRUE);
254 if (!cdr_text) {
255 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error generating XML!\n");
256 goto error;
257 }
258 } else {
259 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unhandled format for mod_format_cdr!\n");
260 goto error;
261 }
262
263 switch_thread_rwlock_rdlock(profile->log_path_lock);
264
265 if (!(logdir = switch_channel_get_variable(channel, "format_cdr_base"))) {
266 logdir = profile->log_dir;
267 }
268
269 if (!zstr(logdir) && (profile->log_http_and_disk || !profile->url_count)) {
270 if (profile->log_file) {
271 lfile = switch_channel_expand_variables(channel, profile->log_file);
272 } else {
273 lfile = switch_mprintf("%s%s.cdr.%s", a_prefix, switch_core_session_get_uuid(session), profile->format);
274 }
275 dpath = switch_mprintf("%s%s%s", logdir, SWITCH_PATH_SEPARATOR, a_prefix);
276 path = switch_mprintf("%s%s%s", logdir, SWITCH_PATH_SEPARATOR, lfile);
277 if (lfile != profile->log_file) switch_safe_free(lfile);
278 switch_thread_rwlock_unlock(profile->log_path_lock);
279 if (path) {
280 if (switch_directory_exists(dpath, profile->pool) != SWITCH_STATUS_SUCCESS) {
281 switch_dir_make_recursive(dpath, SWITCH_FPROT_OS_DEFAULT, profile->pool);
282 }
283 #ifdef _MSC_VER
284 if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
285 #else
286 if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) > -1) {
287 #endif
288 int wrote;
289 wrote = write(fd, cdr_text, (unsigned) strlen(cdr_text));
290 if (!strcasecmp(profile->format, "json")) {
291 wrote += write(fd, "\n", 1);
292 }
293 wrote++;
294 close(fd);
295 } else {
296 char ebuf[512] = { 0 };
297 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error writing [%s][%s]\n",
298 path, switch_strerror_r(errno, ebuf, sizeof(ebuf)));
299 }
300 switch_safe_free(path);
301 switch_safe_free(dpath);
302 }
303 } else {
304 switch_thread_rwlock_unlock(profile->log_path_lock);
305 }
306
307 /* try to post it to the web server */
308 if (profile->url_count) {
309 char *destUrl = NULL;
310 curl_handle = switch_curl_easy_init();
311
312 if (profile->encode == ENCODING_TEXTXML) {
313 headers = switch_curl_slist_append(headers, "Content-Type: text/xml");
314 } else if (profile->encode == ENCODING_APPLJSON) {
315 headers = switch_curl_slist_append(headers, "Content-Type: application/json");
316 } else if (profile->encode) {
317 switch_size_t need_bytes = strlen(cdr_text) * 3 + 1;
318
319 cdr_text_escaped = malloc(need_bytes);
320 switch_assert(cdr_text_escaped);
321 memset(cdr_text_escaped, 0, need_bytes);
322 if (profile->encode == ENCODING_DEFAULT) {
323 headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
324 switch_url_encode_opt(cdr_text, cdr_text_escaped, need_bytes, SWITCH_TRUE);
325 } else {
326 headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-base64-encoded");
327 switch_b64_encode((unsigned char *) cdr_text, need_bytes / 3, (unsigned char *) cdr_text_escaped, need_bytes);
328 }
329 switch_safe_free(cdr_text);
330 cdr_text = cdr_text_escaped;
331 } else {
332 headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-plaintext");
333 }
334
335 if (profile->encode == ENCODING_TEXTXML) {
336 curl_cdr_text = cdr_text;
337 } else if (profile->encode == ENCODING_APPLJSON) {
338 curl_cdr_text = cdr_text;
339 } else if (!(curl_cdr_text = switch_mprintf("cdr=%s", cdr_text))) {
340 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");
341 goto error;
342 }
343
344 if (!zstr(profile->cred)) {
345 switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, profile->auth_scheme);
346 switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, profile->cred);
347 }
348
349 switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
350 switch_curl_easy_setopt(curl_handle, CURLOPT_POST, 1);
351 switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
352 switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, curl_cdr_text);
353 switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-format-cdr/1.0");
354 switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, httpCallBack);
355
356 if (profile->disable100continue) {
357 slist = switch_curl_slist_append(slist, "Expect:");
358 switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, slist);
359 }
360
361 if (!zstr(profile->ssl_cert_file)) {
362 switch_curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, profile->ssl_cert_file);
363 }
364
365 if (!zstr(profile->ssl_key_file)) {
366 switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, profile->ssl_key_file);
367 }
368
369 if (!zstr(profile->ssl_key_password)) {
370 switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEYPASSWD, profile->ssl_key_password);
371 }
372
373 if (!zstr(profile->ssl_version)) {
374 if (!strcasecmp(profile->ssl_version, "SSLv3")) {
375 switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
376 } else if (!strcasecmp(profile->ssl_version, "TLSv1")) {
377 switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
378 }
379 }
380
381 if (!zstr(profile->ssl_cacert_file)) {
382 switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, profile->ssl_cacert_file);
383 }
384
385 switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, profile->timeout);
386
387 /* these were used for testing, optionally they may be enabled if someone desires
388 switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); // 302 recursion level
389 */
390
391 for (cur_try = 0; cur_try < profile->retries; cur_try++) {
392 if (cur_try > 0) {
393 switch_yield(profile->delay * 1000000);
394 }
395
396 destUrl = switch_mprintf("%s?uuid=%s%s", profile->urls[profile->url_index], a_prefix, switch_core_session_get_uuid(session));
397 switch_curl_easy_setopt(curl_handle, CURLOPT_URL, destUrl);
398
399 if (!strncasecmp(destUrl, "https", 5)) {
400 switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
401 switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
402 }
403
404 if (profile->enable_cacert_check) {
405 switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE);
406 }
407
408 if (profile->enable_ssl_verifyhost) {
409 switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
410 }
411
412 switch_curl_easy_perform(curl_handle);
413 switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpRes);
414 switch_safe_free(destUrl);
415 if (httpRes >= 200 && httpRes <= 299) {
416 goto success;
417 } else {
418 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Got error [%ld] posting to web server [%s]\n",
419 httpRes, profile->urls[profile->url_index]);
420 profile->url_index++;
421 switch_assert(profile->url_count <= MAX_URLS);
422 if (profile->url_index >= profile->url_count) {
423 profile->url_index = 0;
424 }
425 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Retry will be with url [%s]\n", profile->urls[profile->url_index]);
426 }
427 }
428 switch_curl_easy_cleanup(curl_handle);
429 switch_curl_slist_free_all(headers);
430 switch_curl_slist_free_all(slist);
431 slist = NULL;
432 headers = NULL;
433 curl_handle = NULL;
434
435 /* if we are here the web post failed for some reason */
436 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to post to web server, writing to file\n");
437
438 switch_thread_rwlock_rdlock(profile->log_path_lock);
439 if (profile->log_file) {
440 lfile = switch_channel_expand_variables(channel, profile->log_file);
441 } else {
442 lfile = switch_mprintf("%s%s.cdr.%s", a_prefix, switch_core_session_get_uuid(session), profile->format);
443 }
444 dpath = switch_mprintf("%s%s%s", logdir, SWITCH_PATH_SEPARATOR, a_prefix);
445 path = switch_mprintf("%s%s%s", logdir, SWITCH_PATH_SEPARATOR, lfile);
446 if (lfile != profile->log_file) switch_safe_free(lfile);
447 switch_thread_rwlock_unlock(profile->log_path_lock);
448 if (path) {
449 if (switch_directory_exists(dpath, profile->pool) != SWITCH_STATUS_SUCCESS) {
450 switch_dir_make_recursive(dpath, SWITCH_FPROT_OS_DEFAULT, profile->pool);
451 }
452 #ifdef _MSC_VER
453 if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
454 #else
455 if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) > -1) {
456 #endif
457 int wrote;
458 wrote = write(fd, cdr_text, (unsigned) strlen(cdr_text));
459 if (!strcasecmp(profile->format, "json")) {
460 wrote += write(fd, "\n", 1);
461 }
462 wrote++;
463 close(fd);
464 } else {
465 char ebuf[512] = { 0 };
466 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error![%s]\n",
467 switch_strerror_r(errno, ebuf, sizeof(ebuf)));
468 }
469 switch_safe_free(path);
470 switch_safe_free(dpath);
471 }
472 }
473
474 success:
475 status = SWITCH_STATUS_SUCCESS;
476
477 error:
478 if (curl_handle) {
479 switch_curl_easy_cleanup(curl_handle);
480 }
481 if (headers) {
482 switch_curl_slist_free_all(headers);
483 }
484 if (slist) {
485 switch_curl_slist_free_all(slist);
486 }
487 if (curl_cdr_text != cdr_text) {
488 switch_safe_free(curl_cdr_text);
489 }
490 switch_safe_free(cdr_text);
491 switch_safe_free(path);
492 switch_safe_free(dpath);
493 if ( xml_cdr )
494 {
495 switch_xml_free(xml_cdr);
496 }
497
498 if ( json_cdr )
499 {
500 cJSON_Delete(json_cdr);
501 }
502
503 return status;
504 }
505
506 static switch_status_t my_on_reporting(switch_core_session_t *session)
507 {
508 switch_hash_index_t *hi;
509 void *val;
510 switch_status_t status, tmpstatus;
511
512 status = SWITCH_STATUS_SUCCESS;
513
514 for (hi = switch_core_hash_first(globals.profile_hash); hi; hi = switch_core_hash_next(&hi)) {
515 cdr_profile_t *profile;
516 switch_core_hash_this(hi, NULL, NULL, &val);
517 profile = (cdr_profile_t *) val;
518
519 tmpstatus = my_on_reporting_cb(session, profile);
520 if ( tmpstatus != SWITCH_STATUS_SUCCESS ) {
521 status = tmpstatus;
522 }
523 }
524
525 return status;
526 }
527
528
529 static void event_handler(switch_event_t *event)
530 {
531 switch_hash_index_t *hi;
532 void *val;
533
534 const char *sig = switch_event_get_header(event, "Trapped-Signal");
535
536 if (sig && !strcmp(sig, "HUP")) {
537 for (hi = switch_core_hash_first(globals.profile_hash); hi; hi = switch_core_hash_next(&hi)) {
538 cdr_profile_t *profile;
539 switch_core_hash_this(hi, NULL, NULL, &val);
540 profile = (cdr_profile_t *) val;
541
542 if (profile->rotate) {
543 set_format_cdr_log_dirs(profile);
544 }
545 }
546 }
547 }
548
549 static switch_state_handler_table_t state_handlers = {
550 /*.on_init */ NULL,
551 /*.on_routing */ NULL,
552 /*.on_execute */ NULL,
553 /*.on_hangup */ NULL,
554 /*.on_exchange_media */ NULL,
555 /*.on_soft_execute */ NULL,
556 /*.on_consume_media */ NULL,
557 /*.on_hibernate */ NULL,
558 /*.on_reset */ NULL,
559 /*.on_park */ NULL,
560 /*.on_reporting */ my_on_reporting
561 };
562
563 switch_status_t mod_format_cdr_load_profile_xml(switch_xml_t xprofile)
564 {
565 switch_memory_pool_t *pool = NULL;
566 cdr_profile_t *profile = NULL;
567 switch_xml_t settings, param;
568 char *profile_name = (char *) switch_xml_attr_soft(xprofile, "name");
569
570 if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
571 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n");
572 return SWITCH_STATUS_TERM;
573 }
574
575 profile = switch_core_alloc(pool, sizeof(cdr_profile_t));
576 memset(profile, 0, sizeof(cdr_profile_t));
577
578 profile->pool = pool;
579 profile->name = switch_core_strdup(profile->pool, profile_name);
580
581 profile->log_http_and_disk = 0;
582 profile->log_b = 1;
583 profile->disable100continue = 0;
584 profile->auth_scheme = CURLAUTH_BASIC;
585
586 switch_thread_rwlock_create(&profile->log_path_lock, pool);
587
588 if ((settings = switch_xml_child(xprofile, "settings"))) {
589 for (param = switch_xml_child(settings, "param"); param; param = param->next) {
590 char *var = (char *) switch_xml_attr_soft(param, "name");
591 char *val = (char *) switch_xml_attr_soft(param, "value");
592
593 if (!strcasecmp(var, "cred") && !zstr(val)) {
594 profile->cred = switch_core_strdup(profile->pool, val);
595 } else if (!strcasecmp(var, "format") && !zstr(val)) {
596 profile->format = switch_core_strdup(profile->pool, val);
597 } else if (!strcasecmp(var, "url") && !zstr(val)) {
598 if (profile->url_count >= MAX_URLS) {
599 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "maximum urls configured!\n");
600 } else {
601 profile->urls[profile->url_count++] = switch_core_strdup(profile->pool, val);
602 }
603 } else if (!strcasecmp(var, "log-http-and-disk")) {
604 profile->log_http_and_disk = switch_true(val);
605 } else if (!strcasecmp(var, "timeout")) {
606 int tmp = atoi(val);
607 if (tmp >= 0) {
608 profile->timeout = tmp;
609 } else {
610 profile->timeout = 0;
611 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't set a negative timeout!\n");
612 }
613 } else if (!strcasecmp(var, "delay") && !zstr(val)) {
614 profile->delay = switch_atoui(val);
615 } else if (!strcasecmp(var, "log-b-leg")) {
616 profile->log_b = switch_true(val);
617 } else if (!strcasecmp(var, "prefix-a-leg")) {
618 profile->prefix_a = switch_true(val);
619 } else if (!strcasecmp(var, "disable-100-continue") && switch_true(val)) {
620 profile->disable100continue = 1;
621 } else if (!strcasecmp(var, "encode") && !zstr(val)) {
622 if (!strcasecmp(val, "base64")) {
623 profile->encode = ENCODING_BASE64;
624 } else if (!strcasecmp(val, "textxml")) {
625 profile->encode = ENCODING_TEXTXML;
626 } else if (!strcasecmp(val, "appljson")) {
627 profile->encode = ENCODING_APPLJSON;
628 } else {
629 profile->encode = switch_true(val) ? ENCODING_DEFAULT : ENCODING_NONE;
630 }
631 } else if (!strcasecmp(var, "retries") && !zstr(val)) {
632 profile->retries = switch_atoui(val);
633 } else if (!strcasecmp(var, "rotate") && !zstr(val)) {
634 profile->rotate = switch_true(val);
635 } else if (!strcasecmp(var, "log-file") && !zstr(val)) {
636 profile->log_file = switch_core_strdup(profile->pool, val);
637 } else if (!strcasecmp(var, "log-dir")) {
638 if (zstr(val)) {
639 profile->base_log_dir = switch_core_sprintf(profile->pool, "%s%sformat_cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR);
640 } else {
641 if (switch_is_file_path(val)) {
642 profile->base_log_dir = switch_core_strdup(profile->pool, val);
643 } else {
644 profile->base_log_dir = switch_core_sprintf(profile->pool, "%s%s%s", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR, val);
645 }
646 }
647 } else if (!strcasecmp(var, "err-log-dir")) {
648 if (profile->err_dir_count >= MAX_ERR_DIRS) {
649 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "maximum error directories configured!\n");
650 } else {
651
652 if (zstr(val)) {
653 profile->base_err_log_dir[profile->err_dir_count++] = switch_core_sprintf(profile->pool, "%s%sformat_cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR);
654 } else {
655 if (switch_is_file_path(val)) {
656 profile->base_err_log_dir[profile->err_dir_count++] = switch_core_strdup(profile->pool, val);
657 } else {
658 profile->base_err_log_dir[profile->err_dir_count++] = switch_core_sprintf(profile->pool, "%s%s%s", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR, val);
659 }
660 }
661
662 }
663 } else if (!strcasecmp(var, "enable-cacert-check") && switch_true(val)) {
664 profile->enable_cacert_check = 1;
665 } else if (!strcasecmp(var, "ssl-cert-path")) {
666 profile->ssl_cert_file = switch_core_strdup(profile->pool, val);
667 } else if (!strcasecmp(var, "ssl-key-path")) {
668 profile->ssl_key_file = switch_core_strdup(profile->pool, val);
669 } else if (!strcasecmp(var, "ssl-key-password")) {
670 profile->ssl_key_password = switch_core_strdup(profile->pool, val);
671 } else if (!strcasecmp(var, "ssl-version")) {
672 profile->ssl_version = switch_core_strdup(profile->pool, val);
673 } else if (!strcasecmp(var, "ssl-cacert-file")) {
674 profile->ssl_cacert_file = switch_core_strdup(profile->pool, val);
675 } else if (!strcasecmp(var, "enable-ssl-verifyhost") && switch_true(val)) {
676 profile->enable_ssl_verifyhost = 1;
677 } else if (!strcasecmp(var, "auth-scheme")) {
678 if (*val == '=') {
679 profile->auth_scheme = 0;
680 val++;
681 }
682
683 if (!strcasecmp(val, "basic")) {
684 profile->auth_scheme |= CURLAUTH_BASIC;
685 } else if (!strcasecmp(val, "digest")) {
686 profile->auth_scheme |= CURLAUTH_DIGEST;
687 } else if (!strcasecmp(val, "NTLM")) {
688 profile->auth_scheme |= CURLAUTH_NTLM;
689 } else if (!strcasecmp(val, "GSS-NEGOTIATE")) {
690 profile->auth_scheme |= CURLAUTH_GSSNEGOTIATE;
691 } else if (!strcasecmp(val, "any")) {
692 profile->auth_scheme = (long)CURLAUTH_ANY;
693 }
694 } else if (!strcasecmp(var, "encode-values") && !zstr(val)) {
695 profile->encode_values = switch_true(val) ? ENCODING_DEFAULT : ENCODING_NONE;
696 }
697 }
698
699 if (!profile->err_dir_count) {
700 if (!zstr(profile->base_log_dir)) {
701 profile->base_err_log_dir[profile->err_dir_count++] = switch_core_strdup(profile->pool, profile->base_log_dir);
702 } else {
703 profile->base_err_log_dir[profile->err_dir_count++] = switch_core_sprintf(profile->pool, "%s%sformat_cdr",
704 SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR);
705 }
706 }
707 }
708
709 if (profile->retries && profile->delay == 0) {
710 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Retries set but delay 0 setting to 5 seconds\n");
711 profile->delay = 5;
712 }
713
714 if ( ! profile->format || (strcasecmp(profile->format,"json") && strcasecmp(profile->format,"xml")) )
715 {
716 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No valid format_cdr format specified, defaulting to xml.\n");
717 profile->format = switch_core_strdup(profile->pool,"xml");
718 }
719
720 profile->retries++;
721
722 switch_mutex_lock(globals.mutex);
723 switch_core_hash_insert(globals.profile_hash, profile->name, profile);
724 switch_mutex_unlock(globals.mutex);
725
726 set_format_cdr_log_dirs(profile);
727
728 return SWITCH_STATUS_SUCCESS;
729 }
730
731 SWITCH_MODULE_LOAD_FUNCTION(mod_format_cdr_load)
732 {
733 char *cf = "format_cdr.conf";
734 switch_xml_t cfg, xml, xprofiles, xprofile;
735 switch_status_t status = SWITCH_STATUS_SUCCESS;
736
737 /* test global state handlers */
738 switch_core_add_state_handler(&state_handlers);
739
740 *module_interface = switch_loadable_module_create_module_interface(pool, modname);
741
742 memset(&globals, 0, sizeof(globals));
743
744 if (switch_event_bind_removable(modname, SWITCH_EVENT_TRAP, SWITCH_EVENT_SUBCLASS_ANY,
745 event_handler, NULL, &globals.node) != SWITCH_STATUS_SUCCESS) {
746 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind!\n");
747 return SWITCH_STATUS_GENERR;
748 }
749
750 globals.pool = pool;
751
752 switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool);
753 switch_core_hash_init(&globals.profile_hash);
754
755 /* parse the config */
756 if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
757 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", cf);
758 return SWITCH_STATUS_TERM;
759 }
760
761 switch_mutex_lock(globals.mutex);
762 if ((xprofiles = switch_xml_child(cfg, "profiles"))) {
763 for (xprofile = switch_xml_child(xprofiles, "profile"); xprofile; xprofile = xprofile->next) {
764 char *profile_name = (char *) switch_xml_attr_soft(xprofile, "name");
765
766 if (zstr(profile_name)) {
767 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
768 "<profile> is missing name attribute\n");
769 continue;
770 }
771
772 mod_format_cdr_load_profile_xml(xprofile);
773 }
774 }
775 switch_xml_free(xml);
776 switch_mutex_unlock(globals.mutex);
777
778 return status;
779 }
780
781 void mod_format_cdr_profile_shutdown(cdr_profile_t *profile)
782 {
783 int err_dir_index = 0;
784
785 for (err_dir_index = 0; err_dir_index < profile->err_dir_count; err_dir_index++) {
786 switch_safe_free(profile->err_log_dir[err_dir_index]);
787 }
788
789 switch_safe_free(profile->log_dir);
790
791 switch_thread_rwlock_destroy(profile->log_path_lock);
792
793 switch_core_destroy_memory_pool(&profile->pool);
794 }
795
796 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_format_cdr_shutdown)
797 {
798 switch_hash_index_t *hi;
799 void *val;
800
801 globals.shutdown = 1;
802
803 switch_event_unbind(&globals.node);
804 switch_core_remove_state_handler(&state_handlers);
805
806 for (hi = switch_core_hash_first(globals.profile_hash); hi; hi = switch_core_hash_next(&hi)) {
807 cdr_profile_t *profile;
808 switch_core_hash_this(hi, NULL, NULL, &val);
809 profile = (cdr_profile_t *) val;
810
811 if ( profile ) {
812 mod_format_cdr_profile_shutdown(profile);
813 }
814 }
815
816 switch_core_hash_destroy(&globals.profile_hash);
817
818 return SWITCH_STATUS_SUCCESS;
819 }
820
821 /* For Emacs:
822 * Local Variables:
823 * mode:c
824 * indent-tabs-mode:t
825 * tab-width:4
826 * c-basic-offset:4
827 * End:
828 * For VIM:
829 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
830 */
831