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