| File: | src/mod/xml_int/mod_xml_cdr/mod_xml_cdr.c |
| Location: | line 402, column 5 |
| Description: | Value stored to 'fd' is never read |
| 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_xml_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_URLS20 20 |
| 37 | |
| 38 | #define ENCODING_NONE0 0 |
| 39 | #define ENCODING_DEFAULT1 1 |
| 40 | #define ENCODING_BASE642 2 |
| 41 | #define ENCODING_TEXTXML3 3 |
| 42 | |
| 43 | static struct { |
| 44 | char *cred; |
| 45 | char *urls[MAX_URLS20 + 1]; |
| 46 | int url_count; |
| 47 | int url_index; |
| 48 | switch_thread_rwlock_t *log_path_lock; |
| 49 | char *base_log_dir; |
| 50 | char *base_err_log_dir; |
| 51 | char *log_dir; |
| 52 | char *err_log_dir; |
| 53 | uint32_t delay; |
| 54 | uint32_t retries; |
| 55 | uint32_t shutdown; |
| 56 | uint32_t enable_cacert_check; |
| 57 | char *ssl_cert_file; |
| 58 | char *ssl_key_file; |
| 59 | char *ssl_key_password; |
| 60 | char *ssl_version; |
| 61 | char *ssl_cacert_file; |
| 62 | uint32_t enable_ssl_verifyhost; |
| 63 | int encode; |
| 64 | int log_http_and_disk; |
| 65 | int log_b; |
| 66 | int prefix_a; |
| 67 | int disable100continue; |
| 68 | int rotate; |
| 69 | long auth_scheme; |
| 70 | int timeout; |
| 71 | switch_memory_pool_t *pool; |
| 72 | switch_event_node_t *node; |
| 73 | } globals; |
| 74 | |
| 75 | SWITCH_MODULE_LOAD_FUNCTION(mod_xml_cdr_load)switch_status_t mod_xml_cdr_load (switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool); |
| 76 | SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_xml_cdr_shutdown)switch_status_t mod_xml_cdr_shutdown (void); |
| 77 | SWITCH_MODULE_DEFINITION(mod_xml_cdr, mod_xml_cdr_load, mod_xml_cdr_shutdown, NULL)static const char modname[] = "mod_xml_cdr" ; __attribute__(( visibility("default"))) switch_loadable_module_function_table_t mod_xml_cdr_module_interface = { 5, mod_xml_cdr_load, mod_xml_cdr_shutdown , ((void*)0), SMODF_NONE }; |
| 78 | |
| 79 | /* this function would have access to the HTML returned by the webserver, we don't need it |
| 80 | * and the default curl activity is to print to stdout, something not as desirable |
| 81 | * so we have a dummy function here |
| 82 | */ |
| 83 | static size_t httpCallBack(char *buffer, size_t size, size_t nitems, void *outstream) |
| 84 | { |
| 85 | return size * nitems; |
| 86 | } |
| 87 | |
| 88 | static switch_status_t set_xml_cdr_log_dirs() |
| 89 | { |
| 90 | switch_time_exp_t tm; |
| 91 | char *path = NULL((void*)0); |
| 92 | char date[80] = ""; |
| 93 | switch_size_t retsize; |
| 94 | switch_status_t status = SWITCH_STATUS_SUCCESS, dir_status; |
| 95 | |
| 96 | switch_time_exp_lt(&tm, switch_micro_time_now()); |
| 97 | switch_strftime_nocheck(date, &retsize, sizeof(date), "%Y-%m-%d-%H-%M-%S", &tm); |
| 98 | |
| 99 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 99, ((void*)0), SWITCH_LOG_NOTICE, "Rotating log file paths\n"); |
| 100 | |
| 101 | if (!zstr(globals.base_log_dir)_zstr(globals.base_log_dir)) { |
| 102 | if (globals.rotate) { |
| 103 | if ((path = switch_mprintf("%s%s%s", globals.base_log_dir, SWITCH_PATH_SEPARATOR"/", date))) { |
| 104 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 104, ((void*)0), SWITCH_LOG_NOTICE, "Rotating log file path to %s\n", path); |
| 105 | |
| 106 | dir_status = SWITCH_STATUS_SUCCESS; |
| 107 | if (switch_directory_exists(path, globals.pool) != SWITCH_STATUS_SUCCESS) { |
| 108 | dir_status = switch_dir_make(path, SWITCH_FPROT_OS_DEFAULT0x0FFF, globals.pool); |
| 109 | } |
| 110 | |
| 111 | if (dir_status == SWITCH_STATUS_SUCCESS) { |
| 112 | switch_thread_rwlock_wrlock(globals.log_path_lock); |
| 113 | switch_safe_free(globals.log_dir)if (globals.log_dir) {free(globals.log_dir);globals.log_dir=( (void*)0);}; |
| 114 | globals.log_dir = path; |
| 115 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 116 | } else { |
| 117 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 117, ((void*)0), SWITCH_LOG_ERROR, "Failed to create new mod_xml_cdr log_dir path\n"); |
| 118 | switch_safe_free(path)if (path) {free(path);path=((void*)0);}; |
| 119 | status = SWITCH_STATUS_FALSE; |
| 120 | } |
| 121 | } else { |
| 122 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 122, ((void*)0), SWITCH_LOG_ERROR, "Failed to generate new mod_xml_cdr log_dir path\n"); |
| 123 | status = SWITCH_STATUS_FALSE; |
| 124 | } |
| 125 | } else { |
| 126 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 126, ((void*)0), SWITCH_LOG_NOTICE, "Setting log file path to %s\n", globals.base_log_dir); |
| 127 | if ((path = switch_safe_strdup(globals.base_log_dir))) { |
| 128 | switch_thread_rwlock_wrlock(globals.log_path_lock); |
| 129 | switch_safe_free(globals.log_dir)if (globals.log_dir) {free(globals.log_dir);globals.log_dir=( (void*)0);}; |
| 130 | switch_dir_make_recursive(path, SWITCH_DEFAULT_DIR_PERMS0x0400 | 0x0200 | 0x0100 | 0x0040 | 0x0010, globals.pool); |
| 131 | globals.log_dir = path; |
| 132 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 133 | } else { |
| 134 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 134, ((void*)0), SWITCH_LOG_ERROR, "Failed to set log_dir path\n"); |
| 135 | status = SWITCH_STATUS_FALSE; |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | if (!zstr(globals.base_err_log_dir)_zstr(globals.base_err_log_dir)) { |
| 141 | if (globals.rotate) { |
| 142 | if ((path = switch_mprintf("%s%s%s", globals.base_err_log_dir, SWITCH_PATH_SEPARATOR"/", date))) { |
| 143 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 143, ((void*)0), SWITCH_LOG_NOTICE, "Rotating err log file path to %s\n", path); |
| 144 | |
| 145 | dir_status = SWITCH_STATUS_SUCCESS; |
| 146 | if (switch_directory_exists(path, globals.pool) != SWITCH_STATUS_SUCCESS) { |
| 147 | dir_status = switch_dir_make(path, SWITCH_FPROT_OS_DEFAULT0x0FFF, globals.pool); |
| 148 | } |
| 149 | |
| 150 | if (dir_status == SWITCH_STATUS_SUCCESS) { |
| 151 | switch_thread_rwlock_wrlock(globals.log_path_lock); |
| 152 | switch_safe_free(globals.err_log_dir)if (globals.err_log_dir) {free(globals.err_log_dir);globals.err_log_dir =((void*)0);}; |
| 153 | globals.err_log_dir = path; |
| 154 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 155 | } else { |
| 156 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 156, ((void*)0), SWITCH_LOG_ERROR, "Failed to create new mod_xml_cdr err_log_dir path\n"); |
| 157 | switch_safe_free(path)if (path) {free(path);path=((void*)0);}; |
| 158 | status = SWITCH_STATUS_FALSE; |
| 159 | } |
| 160 | } else { |
| 161 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 161, ((void*)0), SWITCH_LOG_ERROR, "Failed to generate new mod_xml_cdr err_log_dir path\n"); |
| 162 | status = SWITCH_STATUS_FALSE; |
| 163 | } |
| 164 | } else { |
| 165 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 165, ((void*)0), SWITCH_LOG_NOTICE, "Setting err log file path to %s\n", globals.base_err_log_dir); |
| 166 | if ((path = switch_safe_strdup(globals.base_err_log_dir))) { |
| 167 | switch_thread_rwlock_wrlock(globals.log_path_lock); |
| 168 | switch_safe_free(globals.err_log_dir)if (globals.err_log_dir) {free(globals.err_log_dir);globals.err_log_dir =((void*)0);}; |
| 169 | switch_dir_make_recursive(path, SWITCH_DEFAULT_DIR_PERMS0x0400 | 0x0200 | 0x0100 | 0x0040 | 0x0010, globals.pool); |
| 170 | globals.err_log_dir = path; |
| 171 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 172 | } else { |
| 173 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 173, ((void*)0), SWITCH_LOG_ERROR, "Failed to set err_log_dir path\n"); |
| 174 | status = SWITCH_STATUS_FALSE; |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return status; |
| 180 | } |
| 181 | |
| 182 | static switch_status_t my_on_reporting(switch_core_session_t *session) |
| 183 | { |
| 184 | switch_xml_t cdr = NULL((void*)0); |
| 185 | char *xml_text = NULL((void*)0); |
| 186 | char *path = NULL((void*)0); |
| 187 | char *curl_xml_text = NULL((void*)0); |
| 188 | const char *logdir = NULL((void*)0); |
| 189 | char *xml_text_escaped = NULL((void*)0); |
| 190 | int fd = -1; |
| 191 | uint32_t cur_try; |
| 192 | long httpRes; |
| 193 | switch_CURL *curl_handle = NULL((void*)0); |
| 194 | switch_curl_slist_t *headers = NULL((void*)0); |
| 195 | switch_curl_slist_t *slist = NULL((void*)0); |
| 196 | switch_channel_t *channel = switch_core_session_get_channel(session); |
| 197 | switch_status_t status = SWITCH_STATUS_FALSE; |
| 198 | int is_b; |
| 199 | const char *a_prefix = ""; |
| 200 | char url_joiner = '?'; |
| 201 | |
| 202 | if (globals.shutdown) { |
| 203 | return SWITCH_STATUS_SUCCESS; |
| 204 | } |
| 205 | |
| 206 | is_b = channel && switch_channel_get_originator_caller_profile(channel); |
| 207 | if (!globals.log_b && is_b) { |
| 208 | const char *force_cdr = switch_channel_get_variable(channel, SWITCH_FORCE_PROCESS_CDR_VARIABLE)switch_channel_get_variable_dup(channel, "force_process_cdr", SWITCH_TRUE, -1); |
| 209 | if (!switch_true(force_cdr)) { |
| 210 | return SWITCH_STATUS_SUCCESS; |
| 211 | } |
| 212 | } |
| 213 | if (!is_b && globals.prefix_a) |
| 214 | a_prefix = "a_"; |
| 215 | |
| 216 | if (switch_ivr_generate_xml_cdr(session, &cdr) != SWITCH_STATUS_SUCCESS) { |
| 217 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 217, ((void*)0), SWITCH_LOG_ERROR, "Error Generating Data!\n"); |
| 218 | return SWITCH_STATUS_FALSE; |
| 219 | } |
| 220 | |
| 221 | /* build the XML */ |
| 222 | xml_text = switch_xml_toxml(cdr, SWITCH_TRUE); |
| 223 | if (!xml_text) { |
| 224 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 224, ((void*)0), SWITCH_LOG_CRIT, "Memory Error!\n"); |
| 225 | goto error; |
| 226 | } |
| 227 | |
| 228 | switch_thread_rwlock_rdlock(globals.log_path_lock); |
| 229 | |
| 230 | if (!(logdir = switch_channel_get_variable(channel, "xml_cdr_base")switch_channel_get_variable_dup(channel, "xml_cdr_base", SWITCH_TRUE , -1))) { |
| 231 | logdir = globals.log_dir; |
| 232 | } |
| 233 | |
| 234 | if (!zstr(logdir)_zstr(logdir) && (globals.log_http_and_disk || !globals.url_count)) { |
| 235 | path = switch_mprintf("%s%s%s%s.cdr.xml", logdir, SWITCH_PATH_SEPARATOR"/", a_prefix, switch_core_session_get_uuid(session)); |
| 236 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 237 | if (path) { |
| 238 | #ifdef _MSC_VER |
| 239 | if ((fd = open(path, O_WRONLY01 | O_CREAT0100 | O_TRUNC01000, S_IRUSR0400 | S_IWUSR0200)) > -1) { |
| 240 | #else |
| 241 | if ((fd = open(path, O_WRONLY01 | O_CREAT0100 | O_TRUNC01000, S_IRUSR0400 | S_IWUSR0200 | S_IRGRP(0400 >> 3) | S_IWGRP(0200 >> 3) | S_IROTH((0400 >> 3) >> 3) | S_IWOTH((0200 >> 3) >> 3))) > -1) { |
| 242 | #endif |
| 243 | int wrote; |
| 244 | wrote = write(fd, xml_text, (unsigned) strlen(xml_text)); |
| 245 | wrote++; |
| 246 | close(fd); |
| 247 | fd = -1; |
| 248 | } else { |
| 249 | char ebuf[512] = { 0 }; |
| 250 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 250, ((void*)0), SWITCH_LOG_ERROR, "Error writing [%s][%s]\n", |
| 251 | path, switch_strerror_r(errno(*__errno_location ()), ebuf, sizeof(ebuf))); |
| 252 | } |
| 253 | switch_safe_free(path)if (path) {free(path);path=((void*)0);}; |
| 254 | } |
| 255 | } else { |
| 256 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 257 | } |
| 258 | |
| 259 | /* try to post it to the web server */ |
| 260 | if (globals.url_count) { |
| 261 | char *destUrl = NULL((void*)0); |
| 262 | curl_handle = switch_curl_easy_init(); |
| 263 | |
| 264 | if (globals.encode == ENCODING_TEXTXML3) { |
| 265 | headers = switch_curl_slist_append(headers, "Content-Type: text/xml"); |
| 266 | } else if (globals.encode) { |
| 267 | switch_size_t need_bytes = strlen(xml_text) * 3 + 1; |
| 268 | |
| 269 | xml_text_escaped = malloc(need_bytes); |
| 270 | switch_assert(xml_text_escaped)((xml_text_escaped) ? (void) (0) : __assert_fail ("xml_text_escaped" , "mod_xml_cdr.c", 270, __PRETTY_FUNCTION__)); |
| 271 | memset(xml_text_escaped, 0, need_bytes); |
| 272 | if (globals.encode == ENCODING_DEFAULT1) { |
| 273 | headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); |
| 274 | switch_url_encode(xml_text, xml_text_escaped, need_bytes); |
| 275 | } else { |
| 276 | headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-base64-encoded"); |
| 277 | switch_b64_encode((unsigned char *) xml_text, need_bytes / 3, (unsigned char *) xml_text_escaped, need_bytes); |
| 278 | } |
| 279 | switch_safe_free(xml_text)if (xml_text) {free(xml_text);xml_text=((void*)0);}; |
| 280 | xml_text = xml_text_escaped; |
| 281 | } else { |
| 282 | headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-plaintext"); |
| 283 | } |
| 284 | |
| 285 | if (globals.encode == ENCODING_TEXTXML3) { |
| 286 | curl_xml_text = xml_text; |
| 287 | } else if (!(curl_xml_text = switch_mprintf("cdr=%s", xml_text))) { |
| 288 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 288, ((void*)0), SWITCH_LOG_CRIT, "Memory Error!\n"); |
| 289 | goto error; |
| 290 | } |
| 291 | |
| 292 | if (!zstr(globals.cred)_zstr(globals.cred)) { |
| 293 | switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, globals.auth_scheme)curl_easy_setopt(curl_handle,CURLOPT_HTTPAUTH,globals.auth_scheme ); |
| 294 | switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, globals.cred)curl_easy_setopt(curl_handle,CURLOPT_USERPWD,globals.cred); |
| 295 | } |
| 296 | |
| 297 | switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers)curl_easy_setopt(curl_handle,CURLOPT_HTTPHEADER,headers); |
| 298 | switch_curl_easy_setopt(curl_handle, CURLOPT_POST, 1)curl_easy_setopt(curl_handle,CURLOPT_POST,1); |
| 299 | switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1)curl_easy_setopt(curl_handle,CURLOPT_NOSIGNAL,1); |
| 300 | switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, curl_xml_text)curl_easy_setopt(curl_handle,CURLOPT_POSTFIELDS,curl_xml_text ); |
| 301 | switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-xml/1.0")curl_easy_setopt(curl_handle,CURLOPT_USERAGENT,"freeswitch-xml/1.0" ); |
| 302 | switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, httpCallBack)curl_easy_setopt(curl_handle,CURLOPT_WRITEFUNCTION,httpCallBack ); |
| 303 | |
| 304 | if (globals.disable100continue) { |
| 305 | slist = switch_curl_slist_append(slist, "Expect:"); |
| 306 | switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, slist)curl_easy_setopt(curl_handle,CURLOPT_HTTPHEADER,slist); |
| 307 | } |
| 308 | |
| 309 | if (globals.ssl_cert_file) { |
| 310 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, globals.ssl_cert_file)curl_easy_setopt(curl_handle,CURLOPT_SSLCERT,globals.ssl_cert_file ); |
| 311 | } |
| 312 | |
| 313 | if (globals.ssl_key_file) { |
| 314 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, globals.ssl_key_file)curl_easy_setopt(curl_handle,CURLOPT_SSLKEY,globals.ssl_key_file ); |
| 315 | } |
| 316 | |
| 317 | if (globals.ssl_key_password) { |
| 318 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEYPASSWD, globals.ssl_key_password)curl_easy_setopt(curl_handle,CURLOPT_KEYPASSWD,globals.ssl_key_password ); |
| 319 | } |
| 320 | |
| 321 | if (globals.ssl_version) { |
| 322 | if (!strcasecmp(globals.ssl_version, "SSLv3")) { |
| 323 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3)curl_easy_setopt(curl_handle,CURLOPT_SSLVERSION,CURL_SSLVERSION_SSLv3 ); |
| 324 | } else if (!strcasecmp(globals.ssl_version, "TLSv1")) { |
| 325 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1)curl_easy_setopt(curl_handle,CURLOPT_SSLVERSION,CURL_SSLVERSION_TLSv1 ); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | if (globals.ssl_cacert_file) { |
| 330 | switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, globals.ssl_cacert_file)curl_easy_setopt(curl_handle,CURLOPT_CAINFO,globals.ssl_cacert_file ); |
| 331 | } |
| 332 | |
| 333 | switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, globals.timeout)curl_easy_setopt(curl_handle,CURLOPT_TIMEOUT,globals.timeout); |
| 334 | |
| 335 | /* these were used for testing, optionally they may be enabled if someone desires |
| 336 | switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); // 302 recursion level |
| 337 | */ |
| 338 | |
| 339 | for (cur_try = 0; cur_try < globals.retries; cur_try++) { |
| 340 | if (cur_try > 0) { |
| 341 | switch_yield(globals.delay * 1000000)switch_sleep(globals.delay * 1000000);; |
| 342 | } |
| 343 | |
| 344 | if( strchr(globals.urls[globals.url_index], '?')(__extension__ (__builtin_constant_p ('?') && !__builtin_constant_p (globals.urls[globals.url_index]) && ('?') == '\0' ? (char *) __rawmemchr (globals.urls[globals.url_index], '?') : __builtin_strchr (globals.urls[globals.url_index], '?'))) != NULL((void*)0) ) { |
| 345 | url_joiner = '&'; |
| 346 | } |
| 347 | destUrl = switch_mprintf("%s%cuuid=%s%s", globals.urls[globals.url_index], url_joiner, a_prefix, switch_core_session_get_uuid(session)); |
| 348 | switch_curl_easy_setopt(curl_handle, CURLOPT_URL, destUrl)curl_easy_setopt(curl_handle,CURLOPT_URL,destUrl); |
| 349 | |
| 350 | if (!strncasecmp(destUrl, "https", 5)) { |
| 351 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0)curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYPEER,0); |
| 352 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0)curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYHOST,0); |
| 353 | } |
| 354 | |
| 355 | if (globals.enable_cacert_check) { |
| 356 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE)curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYPEER,(!0)); |
| 357 | } |
| 358 | |
| 359 | if (globals.enable_ssl_verifyhost) { |
| 360 | switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2)curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYHOST,2); |
| 361 | } |
| 362 | |
| 363 | switch_curl_easy_perform(curl_handle); |
| 364 | switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpRes); |
| 365 | switch_safe_free(destUrl)if (destUrl) {free(destUrl);destUrl=((void*)0);}; |
| 366 | if (httpRes >= 200 && httpRes <= 299) { |
| 367 | goto success; |
| 368 | } else { |
| 369 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 369, ((void*)0), SWITCH_LOG_ERROR, "Got error [%ld] posting to web server [%s]\n", |
| 370 | httpRes, globals.urls[globals.url_index]); |
| 371 | globals.url_index++; |
| 372 | switch_assert(globals.url_count <= MAX_URLS)((globals.url_count <= 20) ? (void) (0) : __assert_fail ("globals.url_count <= 20" , "mod_xml_cdr.c", 372, __PRETTY_FUNCTION__)); |
| 373 | if (globals.url_index >= globals.url_count) { |
| 374 | globals.url_index = 0; |
| 375 | } |
| 376 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 376, ((void*)0), SWITCH_LOG_ERROR, "Retry will be with url [%s]\n", globals.urls[globals.url_index]); |
| 377 | } |
| 378 | } |
| 379 | switch_curl_easy_cleanup(curl_handle); |
| 380 | switch_curl_slist_free_all(headers); |
| 381 | switch_curl_slist_free_all(slist); |
| 382 | slist = NULL((void*)0); |
| 383 | headers = NULL((void*)0); |
| 384 | curl_handle = NULL((void*)0); |
| 385 | |
| 386 | /* if we are here the web post failed for some reason */ |
| 387 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 387, ((void*)0), SWITCH_LOG_ERROR, "Unable to post to web server, writing to file\n"); |
| 388 | |
| 389 | switch_thread_rwlock_rdlock(globals.log_path_lock); |
| 390 | path = switch_mprintf("%s%s%s%s.cdr.xml", globals.err_log_dir, SWITCH_PATH_SEPARATOR"/", a_prefix, switch_core_session_get_uuid(session)); |
| 391 | switch_thread_rwlock_unlock(globals.log_path_lock); |
| 392 | if (path) { |
| 393 | #ifdef _MSC_VER |
| 394 | if ((fd = open(path, O_WRONLY01 | O_CREAT0100 | O_TRUNC01000, S_IRUSR0400 | S_IWUSR0200)) > -1) { |
| 395 | #else |
| 396 | if ((fd = open(path, O_WRONLY01 | O_CREAT0100 | O_TRUNC01000, S_IRUSR0400 | S_IWUSR0200 | S_IRGRP(0400 >> 3) | S_IWGRP(0200 >> 3) | S_IROTH((0400 >> 3) >> 3) | S_IWOTH((0200 >> 3) >> 3))) > -1) { |
| 397 | #endif |
| 398 | int wrote; |
| 399 | wrote = write(fd, xml_text, (unsigned) strlen(xml_text)); |
| 400 | wrote++; |
| 401 | close(fd); |
| 402 | fd = -1; |
Value stored to 'fd' is never read | |
| 403 | } else { |
| 404 | char ebuf[512] = { 0 }; |
| 405 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 405, ((void*)0), SWITCH_LOG_ERROR, "Error![%s]\n", |
| 406 | switch_strerror_r(errno(*__errno_location ()), ebuf, sizeof(ebuf))); |
| 407 | } |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | success: |
| 412 | status = SWITCH_STATUS_SUCCESS; |
| 413 | |
| 414 | error: |
| 415 | if (curl_handle) { |
| 416 | switch_curl_easy_cleanup(curl_handle); |
| 417 | } |
| 418 | if (headers) { |
| 419 | switch_curl_slist_free_all(headers); |
| 420 | } |
| 421 | if (slist) { |
| 422 | switch_curl_slist_free_all(slist); |
| 423 | } |
| 424 | if (curl_xml_text != xml_text) { |
| 425 | switch_safe_free(curl_xml_text)if (curl_xml_text) {free(curl_xml_text);curl_xml_text=((void* )0);}; |
| 426 | } |
| 427 | switch_safe_free(xml_text)if (xml_text) {free(xml_text);xml_text=((void*)0);}; |
| 428 | switch_safe_free(path)if (path) {free(path);path=((void*)0);}; |
| 429 | switch_xml_free(cdr); |
| 430 | |
| 431 | return status; |
| 432 | } |
| 433 | |
| 434 | static void event_handler(switch_event_t *event) |
| 435 | { |
| 436 | const char *sig = switch_event_get_header(event, "Trapped-Signal")switch_event_get_header_idx(event, "Trapped-Signal", -1); |
| 437 | |
| 438 | if (sig && !strcmp(sig, "HUP")__extension__ ({ size_t __s1_len, __s2_len; (__builtin_constant_p (sig) && __builtin_constant_p ("HUP") && (__s1_len = __builtin_strlen (sig), __s2_len = __builtin_strlen ("HUP" ), (!((size_t)(const void *)((sig) + 1) - (size_t)(const void *)(sig) == 1) || __s1_len >= 4) && (!((size_t)(const void *)(("HUP") + 1) - (size_t)(const void *)("HUP") == 1) || __s2_len >= 4)) ? __builtin_strcmp (sig, "HUP") : (__builtin_constant_p (sig) && ((size_t)(const void *)((sig) + 1) - (size_t )(const void *)(sig) == 1) && (__s1_len = __builtin_strlen (sig), __s1_len < 4) ? (__builtin_constant_p ("HUP") && ((size_t)(const void *)(("HUP") + 1) - (size_t)(const void * )("HUP") == 1) ? __builtin_strcmp (sig, "HUP") : (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) ("HUP"); int __result = (((const unsigned char *) (const char *) (sig))[0] - __s2[0]); if (__s1_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) (sig))[1] - __s2[1]); if (__s1_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) (sig))[2] - __s2[2]); if (__s1_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) (sig))[3] - __s2[3]); } } __result; }))) : (__builtin_constant_p ("HUP") && ((size_t)(const void *)(("HUP") + 1) - (size_t )(const void *)("HUP") == 1) && (__s2_len = __builtin_strlen ("HUP"), __s2_len < 4) ? (__builtin_constant_p (sig) && ((size_t)(const void *)((sig) + 1) - (size_t)(const void *)( sig) == 1) ? __builtin_strcmp (sig, "HUP") : (- (__extension__ ({ const unsigned char *__s2 = (const unsigned char *) (const char *) (sig); int __result = (((const unsigned char *) (const char *) ("HUP"))[0] - __s2[0]); if (__s2_len > 0 && __result == 0) { __result = (((const unsigned char *) (const char *) ("HUP"))[1] - __s2[1]); if (__s2_len > 1 && __result == 0) { __result = (((const unsigned char *) (const char *) ("HUP"))[2] - __s2[2]); if (__s2_len > 2 && __result == 0) __result = (((const unsigned char *) (const char *) ("HUP"))[3] - __s2[3]); } } __result; })))) : __builtin_strcmp (sig, "HUP")))); })) { |
| 439 | if (globals.rotate) { |
| 440 | set_xml_cdr_log_dirs(); |
| 441 | } |
| 442 | } |
| 443 | } |
| 444 | |
| 445 | static switch_state_handler_table_t state_handlers = { |
| 446 | /*.on_init */ NULL((void*)0), |
| 447 | /*.on_routing */ NULL((void*)0), |
| 448 | /*.on_execute */ NULL((void*)0), |
| 449 | /*.on_hangup */ NULL((void*)0), |
| 450 | /*.on_exchange_media */ NULL((void*)0), |
| 451 | /*.on_soft_execute */ NULL((void*)0), |
| 452 | /*.on_consume_media */ NULL((void*)0), |
| 453 | /*.on_hibernate */ NULL((void*)0), |
| 454 | /*.on_reset */ NULL((void*)0), |
| 455 | /*.on_park */ NULL((void*)0), |
| 456 | /*.on_reporting */ my_on_reporting |
| 457 | }; |
| 458 | |
| 459 | SWITCH_MODULE_LOAD_FUNCTION(mod_xml_cdr_load)switch_status_t mod_xml_cdr_load (switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) |
| 460 | { |
| 461 | char *cf = "xml_cdr.conf"; |
| 462 | switch_xml_t cfg, xml, settings, param; |
| 463 | switch_status_t status = SWITCH_STATUS_SUCCESS; |
| 464 | |
| 465 | /* test global state handlers */ |
| 466 | switch_core_add_state_handler(&state_handlers); |
| 467 | |
| 468 | *module_interface = switch_loadable_module_create_module_interface(pool, modname); |
| 469 | |
| 470 | memset(&globals, 0, sizeof(globals)); |
| 471 | |
| 472 | if (switch_event_bind_removable(modname, SWITCH_EVENT_TRAP, SWITCH_EVENT_SUBCLASS_ANY((void*)0), event_handler, NULL((void*)0), &globals.node) != SWITCH_STATUS_SUCCESS) { |
| 473 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 473, ((void*)0), SWITCH_LOG_ERROR, "Couldn't bind!\n"); |
| 474 | return SWITCH_STATUS_GENERR; |
| 475 | } |
| 476 | |
| 477 | globals.log_http_and_disk = 0; |
| 478 | globals.log_b = 1; |
| 479 | globals.disable100continue = 0; |
| 480 | globals.pool = pool; |
| 481 | globals.auth_scheme = CURLAUTH_BASIC(((unsigned long)1)<<0); |
| 482 | |
| 483 | switch_thread_rwlock_create(&globals.log_path_lock, pool); |
| 484 | |
| 485 | /* parse the config */ |
| 486 | if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL((void*)0)))) { |
| 487 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 487, ((void*)0), SWITCH_LOG_ERROR, "Open of %s failed\n", cf); |
| 488 | return SWITCH_STATUS_FALSE; |
| 489 | } |
| 490 | |
| 491 | if ((settings = switch_xml_child(cfg, "settings"))) { |
| 492 | for (param = switch_xml_child(settings, "param"); param; param = param->next) { |
| 493 | char *var = (char *) switch_xml_attr_soft(param, "name"); |
| 494 | char *val = (char *) switch_xml_attr_soft(param, "value"); |
| 495 | |
| 496 | if (!strcasecmp(var, "cred") && !zstr(val)_zstr(val)) { |
| 497 | globals.cred = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 497); |
| 498 | } else if (!strcasecmp(var, "url") && !zstr(val)_zstr(val)) { |
| 499 | if (globals.url_count >= MAX_URLS20) { |
| 500 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 500, ((void*)0), SWITCH_LOG_ERROR, "maximum urls configured!\n"); |
| 501 | } else { |
| 502 | globals.urls[globals.url_count++] = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 502); |
| 503 | } |
| 504 | } else if (!strcasecmp(var, "log-http-and-disk")) { |
| 505 | globals.log_http_and_disk = switch_true(val); |
| 506 | } else if (!strcasecmp(var, "timeout")) { |
| 507 | int tmp = atoi(val); |
| 508 | if (tmp >= 0) { |
| 509 | globals.timeout = tmp; |
| 510 | } else { |
| 511 | globals.timeout = 0; |
| 512 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 512, ((void*)0), SWITCH_LOG_ERROR, "Can't set a negative timeout!\n"); |
| 513 | } |
| 514 | } else if (!strcasecmp(var, "delay") && !zstr(val)_zstr(val)) { |
| 515 | globals.delay = switch_atoui(val); |
| 516 | } else if (!strcasecmp(var, "log-b-leg")) { |
| 517 | globals.log_b = switch_true(val); |
| 518 | } else if (!strcasecmp(var, "prefix-a-leg")) { |
| 519 | globals.prefix_a = switch_true(val); |
| 520 | } else if (!strcasecmp(var, "disable-100-continue") && switch_true(val)) { |
| 521 | globals.disable100continue = 1; |
| 522 | } else if (!strcasecmp(var, "encode") && !zstr(val)_zstr(val)) { |
| 523 | if (!strcasecmp(val, "base64")) { |
| 524 | globals.encode = ENCODING_BASE642; |
| 525 | } else if (!strcasecmp(val, "textxml")) { |
| 526 | globals.encode = ENCODING_TEXTXML3; |
| 527 | } else { |
| 528 | globals.encode = switch_true(val) ? ENCODING_DEFAULT1 : ENCODING_NONE0; |
| 529 | } |
| 530 | } else if (!strcasecmp(var, "retries") && !zstr(val)_zstr(val)) { |
| 531 | globals.retries = switch_atoui(val); |
| 532 | } else if (!strcasecmp(var, "rotate") && !zstr(val)_zstr(val)) { |
| 533 | globals.rotate = switch_true(val); |
| 534 | } else if (!strcasecmp(var, "log-dir")) { |
| 535 | if (zstr(val)_zstr(val)) { |
| 536 | globals.base_log_dir = switch_core_sprintf(globals.pool, "%s%sxml_cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR"/"); |
| 537 | } else { |
| 538 | if (switch_is_file_path(val)) { |
| 539 | globals.base_log_dir = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 539); |
| 540 | } else { |
| 541 | globals.base_log_dir = switch_core_sprintf(globals.pool, "%s%s%s", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR"/", val); |
| 542 | } |
| 543 | } |
| 544 | } else if (!strcasecmp(var, "err-log-dir")) { |
| 545 | if (zstr(val)_zstr(val)) { |
| 546 | globals.base_err_log_dir = switch_core_sprintf(globals.pool, "%s%sxml_cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR"/"); |
| 547 | } else { |
| 548 | if (switch_is_file_path(val)) { |
| 549 | globals.base_err_log_dir = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 549); |
| 550 | } else { |
| 551 | globals.base_err_log_dir = switch_core_sprintf(globals.pool, "%s%s%s", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR"/", val); |
| 552 | } |
| 553 | } |
| 554 | } else if (!strcasecmp(var, "enable-cacert-check") && switch_true(val)) { |
| 555 | globals.enable_cacert_check = 1; |
| 556 | } else if (!strcasecmp(var, "ssl-cert-path")) { |
| 557 | globals.ssl_cert_file = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 557); |
| 558 | } else if (!strcasecmp(var, "ssl-key-path")) { |
| 559 | globals.ssl_key_file = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 559); |
| 560 | } else if (!strcasecmp(var, "ssl-key-password")) { |
| 561 | globals.ssl_key_password = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 561); |
| 562 | } else if (!strcasecmp(var, "ssl-version")) { |
| 563 | globals.ssl_version = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 563); |
| 564 | } else if (!strcasecmp(var, "ssl-cacert-file")) { |
| 565 | globals.ssl_cacert_file = switch_core_strdup(globals.pool, val)switch_core_perform_strdup(globals.pool, val, "mod_xml_cdr.c" , (const char *)__func__, 565); |
| 566 | } else if (!strcasecmp(var, "enable-ssl-verifyhost") && switch_true(val)) { |
| 567 | globals.enable_ssl_verifyhost = 1; |
| 568 | } else if (!strcasecmp(var, "auth-scheme")) { |
| 569 | if (*val == '=') { |
| 570 | globals.auth_scheme = 0; |
| 571 | val++; |
| 572 | } |
| 573 | |
| 574 | if (!strcasecmp(val, "basic")) { |
| 575 | globals.auth_scheme |= CURLAUTH_BASIC(((unsigned long)1)<<0); |
| 576 | } else if (!strcasecmp(val, "digest")) { |
| 577 | globals.auth_scheme |= CURLAUTH_DIGEST(((unsigned long)1)<<1); |
| 578 | } else if (!strcasecmp(val, "NTLM")) { |
| 579 | globals.auth_scheme |= CURLAUTH_NTLM(((unsigned long)1)<<3); |
| 580 | } else if (!strcasecmp(val, "GSS-NEGOTIATE")) { |
| 581 | globals.auth_scheme |= CURLAUTH_GSSNEGOTIATE(((unsigned long)1)<<2); |
| 582 | } else if (!strcasecmp(val, "any")) { |
| 583 | globals.auth_scheme = (long)CURLAUTH_ANY(~(((unsigned long)1)<<4)); |
| 584 | } |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | if (zstr(globals.base_err_log_dir)_zstr(globals.base_err_log_dir)) { |
| 589 | if (!zstr(globals.base_log_dir)_zstr(globals.base_log_dir)) { |
| 590 | globals.base_err_log_dir = switch_core_strdup(globals.pool, globals.base_log_dir)switch_core_perform_strdup(globals.pool, globals.base_log_dir , "mod_xml_cdr.c", (const char *)__func__, 590); |
| 591 | } else { |
| 592 | globals.base_err_log_dir = switch_core_sprintf(globals.pool, "%s%sxml_cdr", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR"/"); |
| 593 | } |
| 594 | } |
| 595 | |
| 596 | } |
| 597 | |
| 598 | if (globals.retries && globals.delay == 0) { |
| 599 | switch_log_printf(SWITCH_CHANNEL_LOGSWITCH_CHANNEL_ID_LOG, "mod_xml_cdr.c", (const char *)__func__ , 599, ((void*)0), SWITCH_LOG_ERROR, "Retries set but delay 0 setting to 5 seconds\n"); |
| 600 | globals.delay = 5; |
| 601 | } |
| 602 | |
| 603 | globals.retries++; |
| 604 | |
| 605 | set_xml_cdr_log_dirs(); |
| 606 | |
| 607 | switch_xml_free(xml); |
| 608 | |
| 609 | return status; |
| 610 | } |
| 611 | |
| 612 | SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_xml_cdr_shutdown)switch_status_t mod_xml_cdr_shutdown (void) |
| 613 | { |
| 614 | |
| 615 | globals.shutdown = 1; |
| 616 | |
| 617 | switch_safe_free(globals.log_dir)if (globals.log_dir) {free(globals.log_dir);globals.log_dir=( (void*)0);}; |
| 618 | switch_safe_free(globals.err_log_dir)if (globals.err_log_dir) {free(globals.err_log_dir);globals.err_log_dir =((void*)0);}; |
| 619 | |
| 620 | switch_event_unbind(&globals.node); |
| 621 | switch_core_remove_state_handler(&state_handlers); |
| 622 | |
| 623 | switch_thread_rwlock_destroy(globals.log_path_lock); |
| 624 | |
| 625 | return SWITCH_STATUS_SUCCESS; |
| 626 | } |
| 627 | |
| 628 | /* For Emacs: |
| 629 | * Local Variables: |
| 630 | * mode:c |
| 631 | * indent-tabs-mode:t |
| 632 | * tab-width:4 |
| 633 | * c-basic-offset:4 |
| 634 | * End: |
| 635 | * For VIM: |
| 636 | * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: |
| 637 | */ |