/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005-2012, Anthony Minessale II * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Anthony Minessale II * * mod_httapi.c -- HT-TAPI Hypertext Telephony API * */ #include #include SWITCH_MODULE_LOAD_FUNCTION(mod_httapi_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_httapi_shutdown); SWITCH_MODULE_DEFINITION(mod_httapi, mod_httapi_load, mod_httapi_shutdown, NULL); typedef struct profile_perms_s { switch_byte_t set_params; switch_byte_t set_vars; switch_byte_t get_vars; switch_byte_t extended_data; switch_byte_t execute_apps; switch_byte_t expand_vars; struct { switch_byte_t enabled; switch_byte_t set_context; switch_byte_t set_dp; switch_byte_t set_cid_name; switch_byte_t set_cid_number; switch_byte_t full_originate; } dial; struct { switch_byte_t enabled; switch_byte_t set_profile; } conference; } profile_perms_t; struct client_s; typedef struct action_binding_s { char *realm; char *input; char *action; char *error_file; char *match_digits; char *strip; struct client_s *client; struct action_binding_s *parent; } action_binding_t; typedef struct client_profile_s { char *name; char *method; char *url; char *cred; char *bind_local; int disable100continue; uint32_t enable_cacert_check; char *ssl_cert_file; char *ssl_key_file; char *ssl_key_password; char *ssl_version; char *ssl_cacert_file; uint32_t enable_ssl_verifyhost; char *cookie_file; switch_hash_t *vars_map; int auth_scheme; int timeout; profile_perms_t perms; char *ua; struct { char *use_profile; } conference_params; struct { char *context; char *dp; switch_event_t *app_list; int default_allow; } dial_params; struct { switch_event_t *expand_var_list; switch_event_t *set_var_list; switch_event_t *get_var_list; switch_event_t *api_list; int default_allow; } var_params; } client_profile_t; #define HTTAPI_MAX_API_BYTES 1024 * 1024 #define HTTAPI_MAX_FILE_BYTES 1024 * 1024 * 100 typedef struct client_s { switch_memory_pool_t *pool; int fd; switch_buffer_t *buffer; switch_size_t bytes; switch_size_t max_bytes; switch_event_t *headers; switch_event_t *params; switch_event_t *one_time_params; client_profile_t *profile; switch_core_session_t *session; switch_channel_t *channel; action_binding_t *matching_action_binding; action_binding_t *no_matching_action_binding; struct { char *action; char *name; char *file; } record; int err; long code; } client_t; typedef struct hash_node { switch_hash_t *hash; struct hash_node *next; } hash_node_t; static struct { switch_memory_pool_t *pool; hash_node_t *hash_root; hash_node_t *hash_tail; switch_hash_t *profile_hash; switch_hash_t *parse_hash; char cache_path[128]; int debug; int not_found_expires; int cache_ttl; } globals; /* for apr_pstrcat */ #define DEFAULT_PREBUFFER_SIZE 1024 * 64 struct http_file_source; struct http_file_context { int samples; switch_file_handle_t fh; char *cache_file; char *meta_file; char *lock_file; char *metadata; time_t expires; switch_file_t *lock_fd; switch_memory_pool_t *pool; int del_on_close; char *dest_url; char *ua; switch_event_t *url_params; struct { char *ext; } read; struct { char *file_name; char *profile_name; char *file; char *method; char *name; char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; } write; }; typedef struct http_file_context http_file_context_t; typedef switch_status_t (*tag_parse_t)(const char *tag_name, client_t *client, switch_xml_t tag, const char *body); static void bind_parser(const char *tag_name, tag_parse_t handler) { switch_core_hash_insert(globals.parse_hash, tag_name, (void *)(intptr_t)handler); } #define HTTAPI_SYNTAX "[debug_on|debug_off]" SWITCH_STANDARD_API(httapi_api_function) { if (session) { return SWITCH_STATUS_FALSE; } if (zstr(cmd)) { goto usage; } if (!strcasecmp(cmd, "debug_on")) { globals.debug = 1; } else if (!strcasecmp(cmd, "debug_off")) { globals.debug = 0; } else { goto usage; } stream->write_function(stream, "OK\n"); return SWITCH_STATUS_SUCCESS; usage: stream->write_function(stream, "USAGE: %s\n", HTTAPI_SYNTAX); return SWITCH_STATUS_SUCCESS; } static switch_status_t digit_action_callback(switch_ivr_dmachine_match_t *match) { action_binding_t *action_binding = (action_binding_t *) match->user_data; action_binding->client->matching_action_binding = action_binding; action_binding->match_digits = switch_core_strdup(action_binding->client->pool, match->match_digits); return SWITCH_STATUS_SUCCESS; } static switch_status_t digit_nomatch_action_callback(switch_ivr_dmachine_match_t *match) { action_binding_t *action_binding = (action_binding_t *) match->user_data; action_binding->client->no_matching_action_binding = action_binding; return SWITCH_STATUS_BREAK; } static switch_status_t parse_voicemail(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *check = switch_xml_attr(tag, "check"); const char *auth = switch_xml_attr(tag, "auth-only"); const char *profile = switch_xml_attr(tag, "profile"); const char *domain = switch_xml_attr(tag, "domain"); const char *id = switch_xml_attr_soft(tag, "id"); char *ddom = NULL; char *str; switch_status_t status; if (zstr(profile)) profile = "default"; if (zstr(domain)) { if ((ddom = switch_core_get_variable_dup("domain"))) { domain = ddom; } } if (switch_true(check)) { check = "check"; } else { check = ""; } if (switch_true(auth)) { auth = "auth_only"; check = "check"; } else { auth = ""; } str = switch_core_session_sprintf(client->session, "%s %s %s %s %s", check, auth, profile, domain, id); while(*str == ' ') str++; status = switch_core_session_execute_application(client->session, "voicemail", str); switch_safe_free(ddom); return status; } static switch_status_t parse_break(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { return SWITCH_STATUS_FALSE; } static void console_log(const char *level_str, const char *msg) { switch_log_level_t level = SWITCH_LOG_DEBUG; if (level_str) { level = switch_log_str2level(level_str); if (level == SWITCH_LOG_INVALID) { level = SWITCH_LOG_DEBUG; } } switch_log_printf(SWITCH_CHANNEL_LOG, level, "%s", switch_str_nil(msg)); } static void console_clean_log(const char *level_str, const char *msg) { switch_log_level_t level = SWITCH_LOG_DEBUG; if (level_str) { level = switch_log_str2level(level_str); if (level == SWITCH_LOG_INVALID) { level = SWITCH_LOG_DEBUG; } } switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, level, "%s", switch_str_nil(msg)); } static switch_status_t parse_get_var(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *var = switch_xml_attr(tag, "name"); const char *perm = switch_xml_attr(tag, "permanent"); if (zstr(var)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing name attribute!"); return SWITCH_STATUS_SUCCESS; } if (client->profile->perms.get_vars && (!client->profile->var_params.get_var_list || switch_event_check_permission_list(client->profile->var_params.get_var_list, var))) { const char *vval = switch_channel_get_variable(client->channel, var); if (vval) { switch_event_add_header_string(switch_true(perm) ? client->params : client->one_time_params, SWITCH_STACK_BOTTOM, var, vval); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "variable %s permission denied!\n", var); return SWITCH_STATUS_FALSE; } return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_continue(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_log(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *level = switch_xml_attr(tag, "level"); const char *clean = switch_xml_attr(tag, "clean"); if (switch_true(clean)) { console_clean_log(level, body); } else { console_log(level, body); } return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_playback(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *file = switch_xml_attr(tag, "file"); const char *name = switch_xml_attr(tag, "name"); const char *error_file = switch_xml_attr(tag, "error-file"); const char *action = switch_xml_attr(tag, "action"); const char *digit_timeout_ = switch_xml_attr(tag, "digit-timeout"); const char *input_timeout_ = switch_xml_attr(tag, "input-timeout"); const char *tts_engine = NULL; const char *tts_voice = NULL; char *loops_ = (char *) switch_xml_attr(tag, "loops"); int loops = 0; switch_status_t status = SWITCH_STATUS_FALSE; switch_ivr_dmachine_t *dmachine = NULL; switch_input_args_t *args = NULL, myargs = { 0 }, nullargs = { 0 }; long digit_timeout = 1500; long input_timeout = 5000; long tmp; switch_xml_t bind = NULL; int submit = 0; int input = 0; int speak = 0, say = 0, pause = 0, play = 0, speech = 0; char *sub_action = NULL; action_binding_t *top_action_binding = NULL; const char *say_lang = NULL; const char *say_type = NULL; const char *say_method = NULL; const char *say_gender = NULL; const char *sp_engine = NULL; const char *sp_grammar = NULL; char *free_string = NULL; if (!strcasecmp(tag_name, "say")) { say_lang = switch_xml_attr(tag, "language"); say_type = switch_xml_attr(tag, "type"); say_method = switch_xml_attr(tag, "method"); say_gender = switch_xml_attr(tag, "gender"); if (zstr(say_lang) || zstr(say_type) || zstr(say_method) || zstr(body)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speak: missing required attributes or text! (language) (type) (method) \n"); return SWITCH_STATUS_FALSE; } say = 1; } else if (!strcasecmp(tag_name, "speak")) { tts_engine = switch_xml_attr(tag, "engine"); tts_voice = switch_xml_attr(tag, "voice"); if (zstr(tts_engine)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speak: missing engine attribute!\n"); return SWITCH_STATUS_FALSE; } speak = 1; } else if (!strcasecmp(tag_name, "pause")) { const char *ms_ = switch_xml_attr(tag, "milliseconds"); pause = atoi(ms_); if (pause < 0) pause = 1000; } else if (!strcasecmp(tag_name, "playback")) { sp_engine = switch_xml_attr(tag, "asr-engine"); sp_grammar = switch_xml_attr(tag, "asr-grammar"); if (sp_grammar && sp_engine) { speech = 1; } else { play = 1; } } else if (!strcasecmp(tag_name, "vmname")) { const char *id = switch_xml_attr(tag, "id"); char *cmd, *resp; switch_stream_handle_t stream = { 0 }; char *p; SWITCH_STANDARD_STREAM(stream); cmd = switch_mprintf("%s|name_path", id); switch_api_execute("vm_prefs", cmd, NULL, &stream); resp = (char *) stream.data; if (!zstr(resp)) { if (switch_stristr("://", resp) || switch_file_exists(resp, NULL) == SWITCH_STATUS_SUCCESS) { play = 1; file = free_string = resp; resp = NULL; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid file! [%s]\n", resp); switch_safe_free(resp); return SWITCH_STATUS_FALSE; } } else { switch_safe_free(resp); say_lang = switch_xml_attr(tag, "language"); say_gender = switch_xml_attr(tag, "gender"); say_type = "name_spelled"; say_method = "pronounced"; free_string = strdup(id); if ((p = strchr(free_string, '@'))) { *p = '\0'; } say = 1; body = free_string; switch_ivr_play_file(client->session, NULL, "voicemail/vm-person.wav", &nullargs); } switch_safe_free(resp); } if (zstr(name)) name = "dialed_digits"; if (loops_) { loops = atoi(loops_); if (loops < 0) { loops = -1; } } if (digit_timeout_) { tmp = atol(digit_timeout_); if (tmp > 0) { digit_timeout = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid digit timeout [%s]\n", digit_timeout_); } } if (input_timeout_) { tmp = atol(input_timeout_); if (tmp > 0) { input_timeout = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid input timeout [%s]\n", input_timeout_); } } if ((bind = switch_xml_child(tag, "bind"))) { action_binding_t *action_binding; const char *realm = "default"; input++; top_action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding)); top_action_binding->client = client; top_action_binding->action = (char *) action; top_action_binding->error_file = (char *)error_file; switch_ivr_dmachine_create(&dmachine, "HTTAPI", NULL, digit_timeout, 0, NULL, digit_nomatch_action_callback, top_action_binding); while(bind) { action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding)); action_binding->realm = (char *) realm; action_binding->input = bind->txt; action_binding->client = client; action_binding->action = (char *) switch_xml_attr(bind, "action"); action_binding->strip = (char *) switch_xml_attr(bind, "strip"); action_binding->error_file = (char *) error_file; action_binding->parent = top_action_binding; switch_ivr_dmachine_bind(dmachine, action_binding->realm, action_binding->input, 0, digit_action_callback, action_binding); bind = bind->next; } switch_ivr_dmachine_set_realm(dmachine, realm); myargs.dmachine = dmachine; args = &myargs; } if (zstr(file) && !input) { file = body; } if (zstr(file) && !(speak || say || pause)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "missing file attribute!\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } do { if (speak) { status = switch_ivr_speak_text(client->session, tts_engine, tts_voice, (char *)body, args); } else if (say) { status = switch_ivr_say(client->session, body, say_lang, say_type, say_method, say_gender, args); } else if (play) { status = switch_ivr_play_file(client->session, NULL, file, args); } else if (speech) { char *result = NULL; status = switch_ivr_play_and_detect_speech(client->session, file, sp_engine, sp_grammar, &result, input_timeout, args); if (!zstr(result)) { switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, name, result); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "input_type", "detected_speech"); submit = 1; break; } input_timeout = 0; } else if (pause) { input_timeout = pause; status = SWITCH_STATUS_SUCCESS; } if (!switch_channel_ready(client->channel)) { break; } if (!input && !pause) { status = switch_channel_ready(client->channel) ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; submit = 1; break; } if (input_timeout && status == SWITCH_STATUS_SUCCESS) { if ((status = switch_ivr_sleep(client->session, input_timeout, SWITCH_TRUE, args)) == SWITCH_STATUS_SUCCESS) { status = (input || !pause) ? SWITCH_STATUS_BREAK : SWITCH_STATUS_SUCCESS; } } if (status == SWITCH_STATUS_BREAK) { if (error_file) { switch_ivr_play_file(client->session, NULL, error_file, &nullargs); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, name, "invalid"); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "input_type", "invalid"); status = SWITCH_STATUS_SUCCESS; } } else if (status == SWITCH_STATUS_FOUND) { status = SWITCH_STATUS_SUCCESS; submit = 1; break; } else if (status != SWITCH_STATUS_SUCCESS) { break; } } while (loops-- > 0); if (submit) { if (client->matching_action_binding) { if (client->matching_action_binding->match_digits) { if (client->matching_action_binding->strip) { char *pp, *p; for(pp = client->matching_action_binding->strip; pp && *pp; pp++) { if ((p = strchr(client->matching_action_binding->match_digits, *pp))) { *p = '\0'; } } } switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, name, client->matching_action_binding->match_digits); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "input_type", "dtmf"); } if (client->matching_action_binding->action) { sub_action = client->matching_action_binding->action; } else if (client->matching_action_binding->parent && client->matching_action_binding->parent->action) { sub_action = client->matching_action_binding->parent->action; } } if (!sub_action && top_action_binding && top_action_binding->action) { sub_action = top_action_binding->action; } if (sub_action && client->matching_action_binding && client->matching_action_binding->match_digits) { if (!strncasecmp(sub_action, "dial:", 5)) { char *context = NULL; char *dp = NULL; if (client->profile->perms.dial.set_context) { context = switch_core_session_strdup(client->session, sub_action + 5); if ((dp = strchr(context, ':'))) { *dp++ = '\0'; if (!client->profile->perms.dial.set_dp) { dp = NULL; } } } switch_ivr_session_transfer(client->session, client->matching_action_binding->match_digits, dp, context); status = SWITCH_STATUS_FALSE; } else { switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", sub_action); } } } if (dmachine) { switch_ivr_dmachine_destroy(&dmachine); } switch_safe_free(free_string); return status; } static switch_status_t parse_conference(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { char *str; char *dup, *p; const char *profile_name = switch_xml_attr(tag, "profile"); if (!client->profile->perms.conference.enabled) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } dup = switch_core_session_strdup(client->session, body); if ((p = strchr(dup, '@'))) { *p = '\0'; } if (zstr(profile_name) || !client->profile->perms.conference.set_profile) { profile_name = client->profile->conference_params.use_profile; } str = switch_core_session_sprintf(client->session, "%s@%s", dup, profile_name); switch_core_session_execute_application(client->session, "conference", str); return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_dial(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *context = NULL; const char *dp = NULL; const char *cid_name = NULL; const char *cid_number = NULL; if (!client->profile->perms.dial.enabled) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } if (client->profile->perms.dial.set_context) { context = switch_xml_attr(tag, "context"); } if (client->profile->perms.dial.set_dp) { dp = switch_xml_attr(tag, "dialplan"); } if (client->profile->perms.dial.set_cid_name) { cid_name = switch_xml_attr(tag, "caller-id-name"); } if (client->profile->perms.dial.set_cid_number) { cid_number = switch_xml_attr(tag, "caller-id-number"); } if (client->profile->perms.dial.full_originate && strchr(body, '/')) { char *str; if (!cid_name) { cid_name = switch_channel_get_variable(client->channel, "caller_id_name"); } if (!cid_number) { cid_number = switch_channel_get_variable(client->channel, "caller_id_number"); } str = switch_core_session_sprintf(client->session, "{origination_caller_id_name='%s',origination_caller_id_number='%s'}%s", cid_name, cid_number, body); switch_core_session_execute_application(client->session, "bridge", str); } else { switch_ivr_session_transfer(client->session, body, dp, context); } return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_sms(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { switch_event_t *event; const char *from_proto = "httapi"; const char *to_proto = "sip"; const char *to = switch_xml_attr(tag, "to"); if (to && switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", from_proto); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to_proto", to_proto); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", switch_channel_get_variable(client->channel, "caller_id_number")); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to", to); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "text/plain"); if (body) { switch_event_add_body(event, "%s", body); } switch_core_chat_send(to_proto, event); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing 'to' Attribute!\n"); return SWITCH_STATUS_FALSE; } return SWITCH_STATUS_SUCCESS; } static int check_app_perm(client_t *client, const char *app_name) { if (!client->profile->perms.execute_apps) { return 0; } return switch_event_check_permission_list(client->profile->dial_params.app_list, app_name); } static switch_status_t parse_execute(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *app_name = switch_xml_attr(tag, "application"); const char *data = switch_xml_attr(tag, "data"); if (zstr(data)) data = body; if (zstr(app_name)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid app\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } if (!check_app_perm(client, app_name)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } if (!client->profile->perms.expand_vars) { const char *p; for(p = data; p && *p; p++) { if (*p == '$') { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Expand Variables: Permission Denied!\n"); switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return SWITCH_STATUS_FALSE; } } } switch_core_session_execute_application(client->session, app_name, data); return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_hangup(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *cause_str = switch_xml_attr(tag, "cause"); switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING; if (zstr(cause_str)) { cause_str = body; } if (!zstr(cause_str)) { cause = switch_channel_str2cause(cause_str); } switch_channel_hangup(client->channel, cause); return SWITCH_STATUS_FALSE; } static switch_status_t parse_record_call(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *limit_ = switch_xml_attr(tag, "limit"); const char *name = switch_xml_attr(tag, "name"); const char *action = switch_xml_attr(tag, "action"); const char *record_file; int limit = 0; if (client->record.file) { return SWITCH_STATUS_SUCCESS; } if (zstr(name)) name = "recorded_file"; if (!strncasecmp(name, "http://", 7)) { record_file = name; } else { client->record.action = (char *) action; client->record.name = (char *)name; client->record.file = switch_core_session_sprintf(client->session, "%s%s%s.wav", SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, switch_core_session_get_uuid(client->session)); record_file = client->record.file; } if (limit_) { limit = atoi(limit_); if (limit < 0) limit = 0; } switch_ivr_record_session(client->session, (char *)record_file, limit, NULL); return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_record(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *file = switch_xml_attr(tag, "file"); const char *name = switch_xml_attr(tag, "name"); const char *error_file = switch_xml_attr(tag, "error-file"); const char *beep_file = switch_xml_attr(tag, "beep-file"); const char *action = switch_xml_attr(tag, "action"); const char *sub_action = NULL; const char *digit_timeout_ = switch_xml_attr(tag, "digit-timeout"); char *loops_ = (char *) switch_xml_attr(tag, "loops"); int loops = 0; switch_status_t status = SWITCH_STATUS_FALSE; switch_ivr_dmachine_t *dmachine = NULL; switch_input_args_t *args = NULL, myargs = { 0 }; long digit_timeout = 1500; long tmp; int thresh = 200; int silence_hits = 2; int record_limit = 0; char *tmp_record_path = NULL; const char *v; int rtmp; char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; char *fname = NULL; char *p, *ext = "wav"; switch_xml_t bind; action_binding_t *top_action_binding = NULL; int http = 0; switch_uuid_str(uuid_str, sizeof(uuid_str)); if (zstr(name)) name = "attached_file"; if (zstr(file)) { return SWITCH_STATUS_FALSE; } fname = switch_core_strdup(client->pool, file); for(p = fname; p && *p; p++) { if (*p == '/' || *p == '\\' || *p == '`') { *p = '_'; } } if ((p = strrchr(fname, '.'))) { *p++ = '\0'; ext = p; } for(p = fname; p && *p; p++) { if (*p == '.') { *p = '_'; } } if (!strncasecmp(fname, "http://", 7)) { tmp_record_path = fname; http = 1; } else { tmp_record_path = switch_core_sprintf(client->pool, "%s%s%s_%s.%s", SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, uuid_str, fname, ext); } if ((v = switch_xml_attr(tag, "limit"))) { if ((rtmp = atoi(v)) > -1) { record_limit = rtmp; } } if ((v = switch_xml_attr(tag, "silence-hits"))) { if ((rtmp = atoi(v)) > -1) { silence_hits = rtmp; } } if ((v = switch_xml_attr(tag, "threshold"))) { if ((rtmp = atoi(v)) > -1) { thresh = rtmp; } } if (loops_) { loops = atoi(loops_); if (loops < 0) { loops = -1; } } if (digit_timeout_) { tmp = atol(digit_timeout_); if (tmp > 0) { digit_timeout = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid digit timeout [%s]\n", digit_timeout_); } } if ((bind = switch_xml_child(tag, "bind"))) { action_binding_t *action_binding; const char *realm = "default"; top_action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding)); top_action_binding->client = client; top_action_binding->action = (char *) action; top_action_binding->error_file = (char *)error_file; switch_ivr_dmachine_create(&dmachine, "HTTAPI", NULL, digit_timeout, 0, NULL, digit_nomatch_action_callback, top_action_binding); while(bind) { action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding)); action_binding->realm = (char *) realm; action_binding->input = bind->txt; action_binding->client = client; action_binding->action = (char *) switch_xml_attr(bind, "action"); action_binding->error_file = (char *) error_file; action_binding->parent = top_action_binding; switch_ivr_dmachine_bind(dmachine, action_binding->realm, action_binding->input, 0, digit_action_callback, action_binding); bind = bind->next; } switch_ivr_dmachine_set_realm(dmachine, realm); myargs.dmachine = dmachine; args = &myargs; } if (beep_file) { status = switch_ivr_play_file(client->session, NULL, beep_file, args); } if (!switch_channel_ready(client->channel)) { goto end; } if (status == SWITCH_STATUS_SUCCESS) { switch_file_handle_t fh = { 0 }; fh.thresh = thresh; fh.silence_hits = silence_hits; status = switch_ivr_record_file(client->session, &fh, tmp_record_path, args, record_limit); } if (client->matching_action_binding) { if (client->matching_action_binding->action) { sub_action = client->matching_action_binding->action; } else if (client->matching_action_binding->parent && client->matching_action_binding->parent->action) { sub_action = client->matching_action_binding->parent->action; } } if (!sub_action && top_action_binding && top_action_binding->action) { sub_action = top_action_binding->action; } if (sub_action) { switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", sub_action); } if (!http && !zstr(tmp_record_path) && switch_file_exists(tmp_record_path, client->pool) == SWITCH_STATUS_SUCCESS) { char *key = switch_core_sprintf(client->pool, "attach_file:%s:%s.%s", name, fname, ext); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, key, tmp_record_path); } end: if (dmachine) { switch_ivr_dmachine_destroy(&dmachine); } return status; } static switch_status_t parse_common(const char *tag_name, client_t *client, switch_xml_t tag, const char *body) { const char *action = switch_xml_attr(tag, "action"); const char *tmp_action = switch_xml_attr(tag, "temp-action"); if (action) { switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", action); } if (tmp_action) { switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "url", tmp_action); } return SWITCH_STATUS_SUCCESS; } static switch_status_t parse_xml(client_t *client) { switch_status_t status = SWITCH_STATUS_FALSE; const void *bdata; switch_size_t len; int runs = 0; if ((len = switch_buffer_peek_zerocopy(client->buffer, &bdata)) && switch_buffer_len(client->buffer) > len) { switch_xml_t xml, tag, category; if (globals.debug) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Debugging Return Data:\n%s\n", (char *)bdata); } if ((xml = switch_xml_parse_str((char *)bdata, len))) { if (client->profile->perms.set_params) { if ((category = switch_xml_child(xml, "params"))) { tag = category->child; while(tag) { if (!zstr(tag->name)) { char *val = tag->txt; if (zstr(val)) { val = NULL; } switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, tag->name, val); } tag = tag->ordered; } } } if (client->profile->perms.set_vars) { if ((category = switch_xml_child(xml, "variables"))) { tag = category->child; while(tag) { if (!zstr(tag->name)) { char *val = tag->txt; if (zstr(val)) { val = NULL; } if (client->profile->perms.set_vars && (!client->profile->var_params.set_var_list || switch_event_check_permission_list(client->profile->var_params.set_var_list, tag->name))) { switch_channel_set_variable(client->channel, tag->name, val); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "variable %s permission denied!\n", tag->name); } } tag = tag->ordered; } } } if ((category = switch_xml_child(xml, "work"))) { tag = category->child; status = SWITCH_STATUS_SUCCESS; while(status == SWITCH_STATUS_SUCCESS && tag) { if (!zstr(tag->name)) { tag_parse_t handler; if ((handler = (tag_parse_t)(intptr_t) switch_core_hash_find(globals.parse_hash, tag->name))) { char *expanded = tag->txt; switch_event_t *templ_data; if (tag->txt && client->profile->perms.expand_vars) { switch_channel_get_variables(client->channel, &templ_data); switch_event_merge(templ_data, client->params); expanded = switch_event_expand_headers_check(templ_data, tag->txt, client->profile->var_params.expand_var_list, client->profile->var_params.api_list, 0); switch_event_destroy(&templ_data); } runs++; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Process Tag: [%s]\n", tag->name); parse_common(tag->name, client, tag, expanded); status = handler(tag->name, client, tag, expanded); if (expanded && expanded != tag->txt) { free(expanded); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported Tag: [%s]\n", tag->name); status = SWITCH_STATUS_FALSE; } } tag = tag->ordered; } } if (!runs) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No instructions found in result!\n"); status = SWITCH_STATUS_FALSE; } switch_xml_free(xml); } } return status; } static size_t get_header_callback(void *ptr, size_t size, size_t nmemb, void *userdata) { size_t realsize = (size * nmemb); char *val, *header = NULL; client_t *client = userdata; /* validate length... Apache and IIS won't send a header larger than 16 KB */ if (realsize == 0 || realsize > 1024 * 16) { return realsize; } /* get the header, adding NULL terminator if there isn't one */ switch_zmalloc(header, realsize + 1); strncpy(header, (char *)ptr, realsize); if ((val = strchr(header, ':'))) { char *cr; *val++ = '\0'; while(*val == ' ') val++; if ((cr = strchr(val, '\r')) || (cr = strchr(val, '\r'))) { *cr = '\0'; } switch_event_add_header_string(client->headers, SWITCH_STACK_BOTTOM, header, val); } switch_safe_free(header); return realsize; } static size_t file_callback(void *ptr, size_t size, size_t nmemb, void *data) { register unsigned int realsize = (unsigned int) (size * nmemb); client_t *client = data; client->bytes += realsize; if (client->bytes > client->max_bytes) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Oversized file detected [%d bytes]\n", (int) client->bytes); client->err = 1; return 0; } switch_buffer_write(client->buffer, ptr, realsize); return realsize; } static void client_destroy(client_t **client) { if (client && *client) { switch_memory_pool_t *pool; switch_event_destroy(&(*client)->headers); switch_event_destroy(&(*client)->params); switch_event_destroy(&(*client)->one_time_params); switch_buffer_destroy(&(*client)->buffer); pool = (*client)->pool; switch_core_destroy_memory_pool(&pool); } } static void client_reset(client_t *client) { if (client->headers) { switch_event_destroy(&client->headers); } switch_event_destroy(&client->one_time_params); switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE); client->one_time_params->flags |= EF_UNIQ_HEADERS; switch_event_create(&client->headers, SWITCH_EVENT_CLONE); switch_buffer_zero(client->buffer); client->code = 0; client->err = 0; client->matching_action_binding = NULL; client->no_matching_action_binding = NULL; } static client_t *client_create(switch_core_session_t *session, const char *profile_name, switch_event_t **params) { client_t *client; switch_memory_pool_t *pool; client_profile_t *profile; if (zstr(profile_name)) { profile_name = "default"; } profile = (client_profile_t *) switch_core_hash_find(globals.profile_hash, profile_name); if (!profile) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find profile [%s]\n", profile_name); return NULL; } switch_core_new_memory_pool(&pool); client = switch_core_alloc(pool, sizeof(*client)); client->pool = pool; switch_event_create(&client->headers, SWITCH_EVENT_CLONE); if (session) { client->session = session; client->channel = switch_core_session_get_channel(session); } client->profile = profile; client->max_bytes = HTTAPI_MAX_API_BYTES; switch_buffer_create_dynamic(&client->buffer, 1024, 1024, 0); if (params && *params) { client->params = *params; *params = NULL; } else { switch_event_create(&client->params, SWITCH_EVENT_CLONE); client->params->flags |= EF_UNIQ_HEADERS; } switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE); client->one_time_params->flags |= EF_UNIQ_HEADERS; switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "hostname", switch_core_get_switchname()); return client; } static void cleanup_attachments(client_t *client) { switch_event_header_t *hp; for (hp = client->params->headers; hp; hp = hp->next) { if (!strncasecmp(hp->name, "attach_file:", 12)) { if (switch_file_exists(hp->value, client->pool) == SWITCH_STATUS_SUCCESS) { printf("DELETE %s\n", hp->value); unlink(hp->value); } } } } size_t put_file_read( void *ptr, size_t size, size_t nmemb, void *userdata) { return fread(ptr, size, nmemb, (FILE *) userdata); } static switch_status_t httapi_sync(client_t *client) { switch_CURL *curl_handle = NULL; char *data = NULL; switch_curl_slist_t *headers = NULL; char *url = NULL; char *dynamic_url = NULL, *use_url = NULL; const char *session_id = NULL; switch_status_t status = SWITCH_STATUS_FALSE; int get_style_method = 0; char *method = NULL; struct curl_httppost *formpost=NULL; switch_event_t *save_params = NULL; const char *put_file; FILE *fd = NULL; char *creds, *dup_creds = NULL, *ua = NULL; if (client->one_time_params && client->one_time_params->headers) { save_params = client->params; switch_event_dup(&client->params, save_params); switch_event_merge(client->params, client->one_time_params); switch_event_destroy(&client->one_time_params); switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE); client->one_time_params->flags |= EF_UNIQ_HEADERS; } ua = switch_event_get_header(client->params, "user_agent"); if (zstr(ua)) { ua = client->profile->ua; } if (!(session_id = switch_event_get_header(client->params, "HTTAPI_SESSION_ID"))) { if (client->channel && !(session_id = switch_channel_get_variable(client->channel, "HTTAPI_SESSION_ID"))) { session_id = switch_core_session_get_uuid(client->session); } } if (client->code || client->err) { client_reset(client); } if (!(method = switch_event_get_header(client->params, "method"))) { method = client->profile->method; } if (!(url = switch_event_get_header(client->params, "url"))) { url = client->profile->url; switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", url); } get_style_method = method ? strcasecmp(method, "post") : 1; switch_event_add_header_string(client->params, SWITCH_STACK_TOP, "session_id", session_id); dynamic_url = switch_event_expand_headers(client->params, url); if ((put_file = switch_event_get_header(client->params, "put_file"))) { if (!(fd = fopen(put_file, "rb"))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Can't open [%s]\n", put_file); put_file = NULL; } } if (!put_file) { switch_curl_process_form_post_params(client->params, curl_handle, &formpost); get_style_method = 1; } if (formpost) { get_style_method = 1; } else { data = switch_event_build_param_string(client->params, NULL, client->profile->vars_map); switch_assert(data); if (get_style_method) { char *tmp = switch_mprintf("%s%c%s", dynamic_url, strchr(dynamic_url, '?') != NULL ? '&' : '?', data); if (dynamic_url != url) { free(dynamic_url); } dynamic_url = tmp; } } if ((use_url = strchr(dynamic_url, '@'))) { char *r, *q, *p = strstr(dynamic_url, "://"); use_url++; dup_creds = strdup(p+3); if ((q = strchr(dup_creds, '@'))) *q = '\0'; q = strdup(url); r = strchr(q, '@'); r++; if ((p = strstr(q, "://"))) { *(p+3) = '\0'; } p = switch_mprintf("%s%s", q, r); free(dynamic_url); dynamic_url = p; free(q); } if ((use_url = strchr(dynamic_url, '@'))) { char *q, *p = strstr(dynamic_url, "://"); use_url++; dup_creds = strdup(p+3); *p = '\0'; if ((q = strchr(dup_creds, '@'))) *q = '\0'; creds = dup_creds; p = switch_mprintf("%s%s", p, use_url); free(dynamic_url); dynamic_url = p; } else { creds = client->profile->cred; } curl_handle = switch_curl_easy_init(); if (session_id) { char *hval = switch_mprintf("HTTAPI_SESSION_ID=%s", session_id); switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIE, hval); free(hval); } if (!strncasecmp(dynamic_url, "https", 5)) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); } if (!zstr(creds)) { switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, client->profile->auth_scheme); switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, creds); } if (client->profile->disable100continue) { headers = switch_curl_slist_append(headers, "Expect:"); } if (client->profile->enable_cacert_check) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE); } switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers); if (!zstr(method)) { switch_curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, method); } if (put_file) { curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl_handle, CURLOPT_READDATA, fd); curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, put_file_read); } else if (formpost) { curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost); } else { switch_curl_easy_setopt(curl_handle, CURLOPT_POST, !get_style_method); } switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10); if (!get_style_method && !formpost) { switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data); } switch_curl_easy_setopt(curl_handle, CURLOPT_URL, dynamic_url); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, file_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, get_header_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) client); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *) client); switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, ua); if (client->profile->timeout) { switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, client->profile->timeout); switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); } if (client->profile->ssl_cert_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, client->profile->ssl_cert_file); } if (client->profile->ssl_key_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, client->profile->ssl_key_file); } if (client->profile->ssl_key_password) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEYPASSWD, client->profile->ssl_key_password); } if (client->profile->ssl_version) { if (!strcasecmp(client->profile->ssl_version, "SSLv3")) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); } else if (!strcasecmp(client->profile->ssl_version, "TLSv1")) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); } } if (client->profile->ssl_cacert_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, client->profile->ssl_cacert_file); } if (client->profile->enable_ssl_verifyhost) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2); } if (client->profile->cookie_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, client->profile->cookie_file); switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, client->profile->cookie_file); } if (client->profile->bind_local) { curl_easy_setopt(curl_handle, CURLOPT_INTERFACE, client->profile->bind_local); } switch_curl_easy_perform(curl_handle); switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &client->code); switch_curl_easy_cleanup(curl_handle); switch_curl_slist_free_all(headers); if (formpost) { curl_formfree(formpost); } if (client->err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error encountered! [%s]\ndata: [%s]\n", client->profile->url, data); status = SWITCH_STATUS_FALSE; } else { status = SWITCH_STATUS_SUCCESS; } switch_safe_free(data); switch_safe_free(dup_creds); if (dynamic_url != url) { switch_safe_free(dynamic_url); } cleanup_attachments(client); if (save_params) { switch_event_destroy(&client->params); client->params = save_params; save_params = NULL; } if (fd) { fclose(fd); } return status; } #define ENABLE_PARAM_VALUE "enabled" static switch_status_t do_config(void) { char *cf = "httapi.conf"; switch_xml_t cfg, xml, profiles_tag, profile_tag, param, settings, tag; client_profile_t *profile = NULL; int x = 0; int need_vars_map = 0; switch_hash_t *vars_map = NULL; if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf); return SWITCH_STATUS_TERM; } if ((settings = switch_xml_child(cfg, "settings"))) { for (param = switch_xml_child(settings, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "debug")) { globals.debug = switch_true(val); } else if (!strcasecmp(var, "file-cache-ttl")) { int tmp = atoi(val); if (tmp > -1) { globals.cache_ttl = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid value [%s]for file-cache-ttl\n", val); } } else if (!strcasecmp(var, "file-not-found-expires")) { globals.not_found_expires = atoi(val); if (globals.not_found_expires < 0) { globals.not_found_expires = -1; } } } } if (!(profiles_tag = switch_xml_child(cfg, "profiles"))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing tag!\n"); goto done; } for (profile_tag = switch_xml_child(profiles_tag, "profile"); profile_tag; profile_tag = profile_tag->next) { char *bname = (char *) switch_xml_attr_soft(profile_tag, "name"); char *url = NULL; char *bind_local = NULL; char *bind_cred = NULL; char *method = NULL; int disable100continue = 1; int timeout = 0; uint32_t enable_cacert_check = 0; char *ssl_cert_file = NULL; char *ssl_key_file = NULL; char *ssl_key_password = NULL; char *ssl_version = NULL; char *ssl_cacert_file = NULL; uint32_t enable_ssl_verifyhost = 0; char *cookie_file = NULL; char *ua = "mod_httapi/1.0"; hash_node_t *hash_node; int auth_scheme = CURLAUTH_BASIC; need_vars_map = 0; vars_map = NULL; if (zstr(bname)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "profile tag missing name field!\n"); continue; } if (switch_core_hash_find(globals.profile_hash, bname)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile %s already exists\n", (char *)bname); continue; } if ((tag = switch_xml_child(profile_tag, "params"))) { for (param = switch_xml_child(tag, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "gateway-url")) { if (val) { url = val; } } else if (!strcasecmp(var, "user-agent")) { ua = val; } else if (!strcasecmp(var, "gateway-credentials")) { bind_cred = val; } else if (!strcasecmp(var, "auth-scheme")) { if (*val == '=') { auth_scheme = 0; val++; } if (!strcasecmp(val, "basic")) { auth_scheme |= CURLAUTH_BASIC; } else if (!strcasecmp(val, "digest")) { auth_scheme |= CURLAUTH_DIGEST; } else if (!strcasecmp(val, "NTLM")) { auth_scheme |= CURLAUTH_NTLM; } else if (!strcasecmp(val, "GSS-NEGOTIATE")) { auth_scheme |= CURLAUTH_GSSNEGOTIATE; } else if (!strcasecmp(val, "any")) { auth_scheme = CURLAUTH_ANY; } } else if (!strcasecmp(var, "disable-100-continue") && !switch_true(val)) { disable100continue = 0; } else if (!strcasecmp(var, "method")) { method = val; } else if (!strcasecmp(var, "timeout")) { int tmp = atoi(val); if (tmp >= 0) { timeout = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't set a negative timeout!\n"); } } else if (!strcasecmp(var, "enable-cacert-check") && switch_true(val)) { enable_cacert_check = 1; } else if (!strcasecmp(var, "ssl-cert-path")) { ssl_cert_file = val; } else if (!strcasecmp(var, "ssl-key-path")) { ssl_key_file = val; } else if (!strcasecmp(var, "ssl-key-password")) { ssl_key_password = val; } else if (!strcasecmp(var, "ssl-version")) { ssl_version = val; } else if (!strcasecmp(var, "ssl-cacert-file")) { ssl_cacert_file = val; } else if (!strcasecmp(var, "enable-ssl-verifyhost") && switch_true(val)) { enable_ssl_verifyhost = 1; } else if (!strcasecmp(var, "cookie-file")) { cookie_file = val; } else if (!strcasecmp(var, "enable-post-var")) { if (!vars_map && need_vars_map == 0) { if (switch_core_hash_init(&vars_map, globals.pool) != SWITCH_STATUS_SUCCESS) { need_vars_map = -1; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't init params hash!\n"); continue; } need_vars_map = 1; } if (vars_map && val) { if (switch_core_hash_insert(vars_map, val, ENABLE_PARAM_VALUE) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't add %s to params hash!\n", val); } } } else if (!strcasecmp(var, "bind-local")) { bind_local = val; } } } if (!url) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile has no url!\n"); if (vars_map) switch_core_hash_destroy(&vars_map); continue; } if (!(profile = switch_core_alloc(globals.pool, sizeof(*profile)))) { if (vars_map) switch_core_hash_destroy(&vars_map); goto done; } memset(profile, 0, sizeof(*profile)); /* Defaults */ profile->ua = ua; profile->conference_params.use_profile = "default"; profile->perms.set_params = 1; profile->perms.conference.enabled = 1; profile->perms.dial.enabled = 1; if ((tag = switch_xml_child(profile_tag, "conference"))) { for (param = switch_xml_child(tag, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "default-profile")) { profile->conference_params.use_profile = switch_core_strdup(globals.pool, val); } } } if ((tag = switch_xml_child(profile_tag, "dial"))) { for (param = switch_xml_child(tag, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "context")) { profile->dial_params.context = switch_core_strdup(globals.pool, val); } else if (!strcasecmp(var, "dialplan")) { profile->dial_params.dp = switch_core_strdup(globals.pool, val); } } } if ((tag = switch_xml_child(profile_tag, "permissions"))) { for (param = switch_xml_child(tag, "permission"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "all")) { switch_byte_t all = switch_true(val); memset(&profile->perms, all, sizeof(profile->perms)); } else if (!strcasecmp(var, "none")) { switch_byte_t none = switch_true(val); memset(&profile->perms, none, sizeof(profile->perms)); } else if (!strcasecmp(var, "set-params")) { profile->perms.set_params = switch_true(val); } else if (!strcasecmp(var, "set-vars")) { profile->perms.set_vars = switch_true(val); if (profile->perms.set_vars) { switch_xml_t x_list, x_var; if ((x_list = switch_xml_child(param, "variable-list"))) { char *var = (char *) switch_xml_attr_soft(param, "default"); profile->var_params.default_allow = (var && !strcasecmp(var, "allow")); switch_event_create(&profile->var_params.set_var_list, SWITCH_EVENT_CLONE); profile->var_params.set_var_list->flags |= EF_UNIQ_HEADERS; if (profile->var_params.default_allow) { profile->var_params.set_var_list->flags |= EF_DEFAULT_ALLOW; } for (x_var = switch_xml_child(x_list, "variable"); x_var; x_var = x_var->next) { const char *name = switch_xml_attr(x_var, "name"); const char *type = switch_xml_attr(x_var, "type"); if (zstr(type)) type = profile->var_params.default_allow ? "deny" : "allow"; if (name) { switch_event_add_header_string(profile->var_params.set_var_list, SWITCH_STACK_BOTTOM, name, type); } } } } } else if (!strcasecmp(var, "get-vars")) { profile->perms.get_vars = switch_true(val); if (profile->perms.get_vars) { switch_xml_t x_list, x_var; if ((x_list = switch_xml_child(param, "variable-list"))) { char *var = (char *) switch_xml_attr_soft(param, "default"); profile->var_params.default_allow = (var && !strcasecmp(var, "allow")); switch_event_create(&profile->var_params.get_var_list, SWITCH_EVENT_CLONE); profile->var_params.get_var_list->flags |= EF_UNIQ_HEADERS; if (profile->var_params.default_allow) { profile->var_params.get_var_list->flags |= EF_DEFAULT_ALLOW; } for (x_var = switch_xml_child(x_list, "variable"); x_var; x_var = x_var->next) { const char *name = switch_xml_attr(x_var, "name"); const char *type = switch_xml_attr(x_var, "type"); if (zstr(type)) type = profile->var_params.default_allow ? "deny" : "allow"; if (name) { switch_event_add_header_string(profile->var_params.get_var_list, SWITCH_STACK_BOTTOM, name, type); } } } } } else if (!strcasecmp(var, "extended-data")) { profile->perms.extended_data = switch_true(val); } else if (!strcasecmp(var, "execute-apps")) { profile->perms.execute_apps = switch_true(val); if (profile->perms.execute_apps) { switch_xml_t x_list, x_app; if ((x_list = switch_xml_child(param, "application-list"))) { char *var = (char *) switch_xml_attr_soft(param, "default"); profile->dial_params.default_allow = (var && !strcasecmp(var, "allow")); switch_event_create(&profile->dial_params.app_list, SWITCH_EVENT_CLONE); profile->dial_params.app_list->flags |= EF_UNIQ_HEADERS; if (profile->dial_params.default_allow) { profile->dial_params.app_list->flags |= EF_DEFAULT_ALLOW; } for (x_app = switch_xml_child(x_list, "application"); x_app; x_app = x_app->next) { const char *name = switch_xml_attr(x_app, "name"); const char *type = switch_xml_attr(x_app, "type"); if (zstr(type)) type = profile->dial_params.default_allow ? "deny" : "allow"; if (name) { switch_event_add_header_string(profile->dial_params.app_list, SWITCH_STACK_BOTTOM, name, type); } } } } } else if (!strcasecmp(var, "expand-vars")) { profile->perms.expand_vars = switch_true(val); if (profile->perms.expand_vars) { switch_xml_t x_list, x_var, x_api; if ((x_list = switch_xml_child(param, "variable-list"))) { char *var = (char *) switch_xml_attr_soft(param, "default"); profile->var_params.default_allow = (var && !strcasecmp(var, "allow")); switch_event_create(&profile->var_params.expand_var_list, SWITCH_EVENT_CLONE); profile->var_params.expand_var_list->flags |= EF_UNIQ_HEADERS; if (profile->var_params.default_allow) { profile->var_params.expand_var_list->flags |= EF_DEFAULT_ALLOW; } for (x_var = switch_xml_child(x_list, "variable"); x_var; x_var = x_var->next) { const char *name = switch_xml_attr(x_var, "name"); const char *type = switch_xml_attr(x_var, "type"); if (zstr(type)) type = profile->var_params.default_allow ? "deny" : "allow"; if (name) { switch_event_add_header_string(profile->var_params.expand_var_list, SWITCH_STACK_BOTTOM, name, type); } } } if ((x_list = switch_xml_child(param, "api-list"))) { char *api = (char *) switch_xml_attr_soft(param, "default"); profile->var_params.default_allow = (api && !strcasecmp(api, "allow")); switch_event_create(&profile->var_params.api_list, SWITCH_EVENT_CLONE); profile->var_params.api_list->flags |= EF_UNIQ_HEADERS; if (profile->var_params.default_allow) { profile->var_params.api_list->flags |= EF_DEFAULT_ALLOW; } for (x_api = switch_xml_child(x_list, "api"); x_api; x_api = x_api->next) { const char *name = switch_xml_attr(x_api, "name"); const char *type = switch_xml_attr(x_api, "type"); if (zstr(type)) type = profile->var_params.default_allow ? "deny" : "allow"; if (name) { switch_event_add_header_string(profile->var_params.api_list, SWITCH_STACK_BOTTOM, name, type); } } } } } else if (!strcasecmp(var, "dial")) { profile->perms.dial.enabled = switch_true(val); } else if (!strcasecmp(var, "dial-set-context")) { profile->perms.dial.enabled = switch_true(val); profile->perms.dial.set_context = switch_true(val); } else if (!strcasecmp(var, "dial-set-dialplan")) { profile->perms.dial.enabled = switch_true(val); profile->perms.dial.set_dp = switch_true(val); } else if (!strcasecmp(var, "dial-set-cid-name")) { profile->perms.dial.enabled = switch_true(val); profile->perms.dial.set_cid_name = switch_true(val); } else if (!strcasecmp(var, "dial-set-cid-number")) { profile->perms.dial.enabled = switch_true(val); profile->perms.dial.set_cid_number = switch_true(val); } else if (!strcasecmp(var, "dial-full-originate")) { profile->perms.dial.enabled = switch_true(val); profile->perms.dial.full_originate = switch_true(val); } else if (!strcasecmp(var, "conference")) { profile->perms.conference.enabled = switch_true(val); } else if (!strcasecmp(var, "conference-set-profile")) { profile->perms.conference.enabled = switch_true(val); profile->perms.conference.set_profile = switch_true(val); } } } profile->auth_scheme = auth_scheme; profile->timeout = timeout; profile->url = strdup(url); switch_assert(profile->url); if (bind_local != NULL) { profile->bind_local = strdup(bind_local); } if (method != NULL) { profile->method = strdup(method); } else { profile->method = NULL; } if (bind_cred) { profile->cred = strdup(bind_cred); } profile->disable100continue = disable100continue; profile->enable_cacert_check = enable_cacert_check; if (ssl_cert_file) { profile->ssl_cert_file = strdup(ssl_cert_file); } if (ssl_key_file) { profile->ssl_key_file = strdup(ssl_key_file); } if (ssl_key_password) { profile->ssl_key_password = strdup(ssl_key_password); } if (ssl_version) { profile->ssl_version = strdup(ssl_version); } if (ssl_cacert_file) { profile->ssl_cacert_file = strdup(ssl_cacert_file); } profile->enable_ssl_verifyhost = enable_ssl_verifyhost; if (cookie_file) { profile->cookie_file = strdup(cookie_file); } profile->vars_map = vars_map; if (vars_map) { switch_zmalloc(hash_node, sizeof(hash_node_t)); hash_node->hash = vars_map; hash_node->next = NULL; if (!globals.hash_root) { globals.hash_root = hash_node; globals.hash_tail = globals.hash_root; } else { globals.hash_tail->next = hash_node; globals.hash_tail = globals.hash_tail->next; } } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Profile [%s] JSON Function [%s]\n", zstr(bname) ? "N/A" : bname, profile->url); profile->name = strdup(bname); switch_core_hash_insert(globals.profile_hash, bname, profile); x++; profile = NULL; } done: switch_xml_free(xml); return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; } static switch_status_t my_on_reporting(switch_core_session_t *session) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_status_t status = SWITCH_STATUS_SUCCESS; client_t *client; const char *var; if (!(client = (client_t *) switch_channel_get_private(channel, "_HTTAPI_CLIENT_"))) { return status; } switch_channel_set_private(channel, "_HTTAPI_CLIENT_", NULL); if (client->profile->perms.extended_data) { switch_channel_event_set_extended_data(channel, client->one_time_params); } switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "exiting", "true"); if (client->record.file) { char *key = switch_core_sprintf(client->pool, "attach_file:%s:%s.wav", client->record.name, switch_core_session_get_uuid(session)); switch_ivr_stop_record_session(client->session, client->record.file); switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, key, client->record.file); } var = switch_event_get_header(client->params, "url"); if (client->record.action) { if (strcmp(var, client->record.action)) { switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "url", client->record.action); httapi_sync(client); if (client->profile->perms.extended_data) { switch_channel_event_set_extended_data(channel, client->one_time_params); } switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "exiting", "true"); } } httapi_sync(client); client_destroy(&client); return status; } static switch_state_handler_table_t state_handlers = { /*.on_init */ NULL, /*.on_routing */ NULL, /*.on_execute */ NULL, /*.on_hangup */ NULL, /*.on_exchange_media */ NULL, /*.on_soft_execute */ NULL, /*.on_consume_media */ NULL, /*.on_hibernate */ NULL, /*.on_reset */ NULL, /*.on_park */ NULL, /*.on_reporting */ my_on_reporting, /*.on_destroy */ NULL, SSH_FLAG_STICKY }; SWITCH_STANDARD_APP(httapi_function) { switch_channel_t *channel = switch_core_session_get_channel(session); char *parsed = NULL; const char *profile_name = NULL, *url = NULL; client_t *client; switch_event_t *params = NULL; uint32_t loops = 0, all_extended = 0; switch_caller_profile_t *caller_profile; if (!zstr(data)) { switch_event_create_brackets((char *)data, '{', '}', ',', ¶ms, &parsed, SWITCH_TRUE); if (params) { url = parsed; } else { url = data; } if (!zstr(url) && switch_stristr("://", url)) { if (!params) { switch_event_create(¶ms, SWITCH_EVENT_CLONE); } switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "url", url); } } if ((client = (client_t *) switch_channel_get_private(channel, "_HTTAPI_CLIENT_"))) { if (params) { switch_event_merge(client->params, params); switch_event_destroy(¶ms); } } else { if (params) { profile_name = switch_event_get_header(params, "httapi_profile"); } if (zstr(profile_name) && !(profile_name = switch_channel_get_variable(channel, "httapi_profile"))) { profile_name = "default"; } if ((client = client_create(session, profile_name, ¶ms))) { switch_channel_set_private(channel, "_HTTAPI_CLIENT_", client); switch_channel_add_state_handler(channel, &state_handlers); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find suitable profile\n"); switch_event_destroy(¶ms); return; } } if (client->profile->perms.extended_data) { all_extended = switch_true(switch_event_get_header(client->params, "full_channel_data_on_every_req")); } if ((caller_profile = switch_channel_get_caller_profile(channel))) { switch_caller_profile_event_set_data(caller_profile, "Caller", client->params); } while(switch_channel_ready(channel)) { switch_status_t status = SWITCH_STATUS_FALSE; if (client->profile->perms.extended_data && (!loops++ || all_extended)) { switch_channel_event_set_extended_data(channel, client->one_time_params); } if ((status = httapi_sync(client)) == SWITCH_STATUS_SUCCESS) { if (client->code == 200) { const char *ct = switch_event_get_header(client->headers, "content-type"); if (switch_stristr("text/xml", ct)) { status = parse_xml(client); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received unsupported content-type %s\n", ct); break; } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received HTTP response: %ld.\n", client->code); break; } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error %d!\n", client->err); } if (status == SWITCH_STATUS_TERM) { httapi_sync(client); } if (status != SWITCH_STATUS_SUCCESS) { break; } } switch_safe_free(parsed); } /* HTTP FILE INTERFACE */ static char *load_cache_data(http_file_context_t *context, const char *url) { char *ext = NULL; char digest[SWITCH_MD5_DIGEST_STRING_SIZE] = { 0 }; char meta_buffer[1024] = ""; int fd; switch_ssize_t bytes; switch_md5_string(digest, (void *) url, strlen(url)); if (context->url_params) { ext = switch_event_get_header(context->url_params, "ext"); } if (zstr(ext)) { if ((ext = strrchr(url, '.'))) { ext++; } else { ext = "wav"; } } context->cache_file = switch_core_sprintf(context->pool, "%s%s%s.%s", globals.cache_path, SWITCH_PATH_SEPARATOR, digest, ext); context->meta_file = switch_core_sprintf(context->pool, "%s.meta", context->cache_file); context->lock_file = switch_core_sprintf(context->pool, "%s.lock", context->cache_file); if (switch_file_exists(context->meta_file, context->pool) == SWITCH_STATUS_SUCCESS && ((fd = open(context->meta_file, O_RDONLY, 0)) > -1)) { if ((bytes = read(fd, meta_buffer, sizeof(meta_buffer))) > 0) { char *p; if ((p = strchr(meta_buffer, ':'))) { *p++ = '\0'; context->expires = (time_t) atol(meta_buffer); context->metadata = switch_core_strdup(context->pool, p); } } close(fd); } return context->cache_file; } static size_t save_file_callback(void *ptr, size_t size, size_t nmemb, void *data) { register unsigned int realsize = (unsigned int) (size * nmemb); client_t *client = data; int x; client->bytes += realsize; if (client->bytes > client->max_bytes) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Oversized file detected [%d bytes]\n", (int) client->bytes); client->err = 1; return 0; } x = write(client->fd, ptr, realsize); if (x != (int) realsize) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write! %d out of %d\n", x, realsize); } return x; } static switch_status_t fetch_cache_data(http_file_context_t *context, const char *url, switch_event_t **headers, const char *save_path) { switch_CURL *curl_handle = NULL; client_t client = { 0 }; long code; switch_status_t status = SWITCH_STATUS_FALSE; char *dup_creds = NULL, *dynamic_url = NULL, *use_url; char *ua = NULL; client.fd = -1; if (save_path) { if ((client.fd = open(save_path, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) { return SWITCH_STATUS_FALSE; } } if (context->url_params) { ua = switch_event_get_header(context->url_params, "user_agent"); } if (zstr(ua)) { ua = "mod_httapi/1.0"; } if ((use_url = strchr(url, '@'))) { char *r, *q, *p = strstr(url, "://"); use_url++; dup_creds = strdup(p+3); if ((q = strchr(dup_creds, '@'))) *q = '\0'; q = strdup(url); r = strchr(q, '@'); r++; if ((p = strstr(q, "://"))) { *(p+3) = '\0'; } p = switch_mprintf("%s%s", q, r); dynamic_url = p; free(q); url = dynamic_url; } curl_handle = switch_curl_easy_init(); switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); if (!strncasecmp(url, "https", 5)) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); } client.max_bytes = HTTAPI_MAX_FILE_BYTES; switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10); switch_curl_easy_setopt(curl_handle, CURLOPT_URL, url); if (save_path) { switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, save_file_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &client); } else { switch_curl_easy_setopt(curl_handle, CURLOPT_HEADER, 1); switch_curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1); } if (headers) { switch_event_create(&client.headers, SWITCH_EVENT_CLONE); if (save_path) { switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, get_header_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *) &client); } else { switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, get_header_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &client); } } if (!zstr(dup_creds)) { switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, dup_creds); } switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, ua); switch_curl_easy_perform(curl_handle); switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &code); switch_curl_easy_cleanup(curl_handle); if (client.fd > -1) { close(client.fd); } if (headers && client.headers) { switch_event_add_header(client.headers, SWITCH_STACK_BOTTOM, "http-response-code", "%ld", code); *headers = client.headers; } switch(code) { case 200: if (save_path) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "caching: url:%s to %s (%" SWITCH_SIZE_T_FMT " bytes)\n", url, save_path, client.bytes); } status = SWITCH_STATUS_SUCCESS; break; case 404: status = SWITCH_STATUS_NOTFOUND; break; default: status = SWITCH_STATUS_FALSE; break; } switch_safe_free(dynamic_url); switch_safe_free(dup_creds); return status; } static switch_status_t write_meta_file(http_file_context_t *context, const char *data, switch_event_t *headers) { int fd; switch_status_t status = SWITCH_STATUS_SUCCESS; char write_data[1024]; if ((fd = open(context->meta_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) { return SWITCH_STATUS_FALSE; } if (!zstr(data)) { int ttl = globals.cache_ttl; const char *cc; const char *p; if (headers && (cc = switch_event_get_header(headers, "Cache-Control"))) { if ((p = switch_stristr("max-age=", cc))) { p += 8; if (!zstr(p)) { ttl = atoi(p); if (ttl < 0) ttl = globals.cache_ttl; } } if (switch_stristr("no-cache", cc) || switch_stristr("no-store", cc)) { context->del_on_close = 1; } } switch_snprintf(write_data, sizeof(write_data), "%" SWITCH_TIME_T_FMT ":%s", switch_epoch_time_now(NULL) + ttl, data); status = write(fd, write_data, strlen(write_data) + 1) > 0 ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; } close(fd); return status; } static switch_status_t lock_file(http_file_context_t *context, switch_bool_t lock) { switch_status_t status = SWITCH_STATUS_SUCCESS; if (lock) { if (switch_file_open(&context->lock_fd, context->lock_file, SWITCH_FOPEN_WRITE | SWITCH_FOPEN_CREATE | SWITCH_FOPEN_TRUNCATE, SWITCH_FPROT_UREAD | SWITCH_FPROT_UWRITE, context->pool) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } if (switch_file_lock(context->lock_fd, SWITCH_FLOCK_EXCLUSIVE) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } } else { if (context->lock_fd){ switch_file_close(context->lock_fd); status = SWITCH_STATUS_SUCCESS; } unlink(context->lock_file); } return status; } static switch_status_t locate_url_file(http_file_context_t *context, const char *url) { switch_event_t *headers = NULL; int unreachable = 0; switch_status_t status = SWITCH_STATUS_FALSE; time_t now = switch_epoch_time_now(NULL); char *metadata; load_cache_data(context, url); if (context->expires && now < context->expires) { return SWITCH_STATUS_SUCCESS; } lock_file(context, SWITCH_TRUE); if ((status = fetch_cache_data(context, url, &headers, NULL)) != SWITCH_STATUS_SUCCESS) { if (status == SWITCH_STATUS_NOTFOUND) { unreachable = 2; if (now - context->expires < globals.not_found_expires) { switch_goto_status(SWITCH_STATUS_SUCCESS, end); } } else { unreachable = 1; } } if (switch_file_exists(context->cache_file, context->pool) != SWITCH_STATUS_SUCCESS && unreachable) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "File at url [%s] is unreachable!\n", url); goto end; } if (!unreachable && !zstr(context->metadata)) { metadata = switch_core_sprintf(context->pool, "%s:%s:%s:%s", url, switch_event_get_header_nil(headers, "last-modified"), switch_event_get_header_nil(headers, "etag"), switch_event_get_header_nil(headers, "content-length") ); if (!strcmp(metadata, context->metadata)) { write_meta_file(context, metadata, headers); switch_goto_status(SWITCH_STATUS_SUCCESS, end); } } switch_event_destroy(&headers); fetch_cache_data(context, url, &headers, context->cache_file); metadata = switch_core_sprintf(context->pool, "%s:%s:%s:%s", url, switch_event_get_header_nil(headers, "last-modified"), switch_event_get_header_nil(headers, "etag"), switch_event_get_header_nil(headers, "content-length") ); write_meta_file(context, metadata, headers); if (switch_file_exists(context->cache_file, context->pool) == SWITCH_STATUS_SUCCESS) { status = SWITCH_STATUS_SUCCESS; } end: if (status != SWITCH_STATUS_SUCCESS) { unlink(context->meta_file); unlink(context->cache_file); } lock_file(context, SWITCH_FALSE); switch_event_destroy(&headers); return status; } static switch_status_t http_file_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence) { http_file_context_t *context = handle->private_info; if (!handle->seekable) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "File is not seekable\n"); return SWITCH_STATUS_NOTIMPL; } return switch_core_file_seek(&context->fh, cur_sample, samples, whence); } static switch_status_t http_file_file_open(switch_file_handle_t *handle, const char *path) { http_file_context_t *context; char *parsed = NULL, *pdup = NULL; switch_status_t status; context = switch_core_alloc(handle->memory_pool, sizeof(*context)); context->pool = handle->memory_pool; pdup = switch_core_strdup(context->pool, path); switch_event_create_brackets(pdup, '(', ')', ',', &context->url_params, &parsed, SWITCH_FALSE); if (context->url_params) { context->ua = switch_event_get_header(context->url_params, "ua"); } if (parsed) path = parsed; context->dest_url = switch_core_sprintf(context->pool, "http://%s", path); if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) { char *ext; context->fh.channels = handle->channels; context->fh.native_rate = handle->native_rate; context->fh.samples = handle->samples; context->fh.samplerate = handle->samplerate; context->fh.prefix = handle->prefix; if (context->url_params) { context->write.file_name = switch_event_get_header(context->url_params, "file"); context->write.profile_name = switch_event_get_header(context->url_params, "profile"); context->write.method = switch_event_get_header(context->url_params, "method"); context->write.name = switch_event_get_header(context->url_params, "name"); } if (!context->write.file_name) { char *p; if ((p = strrchr(context->dest_url, '/'))) { p++; context->write.file_name = switch_core_strdup(context->pool, p); } } if ((ext = strrchr(context->write.file_name, '.'))) { ext++; } else { ext = "wav"; } if (!context->write.profile_name) context->write.profile_name = "default"; if (!context->write.method) context->write.method = !strcasecmp(ext, "cgi") ? "post" : "put"; if (!context->write.name) context->write.name = "recorded_file"; switch_uuid_str(context->write.uuid_str, sizeof(context->write.uuid_str)); context->write.file = switch_core_sprintf(context->pool, "%s%s%s_%s", SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, context->write.uuid_str, context->write.file_name); if (switch_core_file_open(&context->fh, context->write.file, handle->channels, handle->samplerate, handle->flags, NULL) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_GENERR; } } else { if (context->url_params) { context->read.ext = switch_event_get_header(context->url_params, "ext"); } if ((status = locate_url_file(context, context->dest_url)) != SWITCH_STATUS_SUCCESS) { return status; } if ((status = switch_core_file_open(&context->fh, context->cache_file, handle->channels, handle->samplerate, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL)) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid cache file %s opening url %s Discarding file.\n", context->cache_file, path); unlink(context->cache_file); unlink(context->meta_file); unlink(context->lock_file); return status; } } handle->private_info = context; handle->samples = context->fh.samples; handle->format = context->fh.format; handle->sections = context->fh.sections; handle->seekable = context->fh.seekable; handle->speed = context->fh.speed; handle->interval = context->fh.interval; if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) { switch_set_flag(handle, SWITCH_FILE_NATIVE); } else { switch_clear_flag(handle, SWITCH_FILE_NATIVE); } return SWITCH_STATUS_SUCCESS; } static switch_status_t http_file_file_close(switch_file_handle_t *handle) { http_file_context_t *context = handle->private_info; if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) { switch_core_file_close(&context->fh); } if (context->write.file) { client_t *client; switch_event_t *params; char *key; switch_event_create(¶ms, SWITCH_EVENT_CLONE); params->flags |= EF_UNIQ_HEADERS; if (!strcasecmp(context->write.method, "put")) { switch_event_add_header(params, SWITCH_STACK_BOTTOM, "put_file", "%s", context->write.file); } else { key = switch_core_sprintf(context->pool, "attach_file:%s:%s", context->write.name, context->write.file_name); switch_event_add_header(params, SWITCH_STACK_BOTTOM, key, "%s", context->write.file); } switch_event_add_header(params, SWITCH_STACK_BOTTOM, "url", "%s", context->dest_url); switch_event_add_header(params, SWITCH_STACK_BOTTOM, "file_driver", "true"); switch_event_add_header(params, SWITCH_STACK_BOTTOM, "HTTAPI_SESSION_ID", "%s", context->write.uuid_str); if ((client = client_create(NULL, context->write.profile_name, ¶ms))) { httapi_sync(client); client_destroy(&client); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find suitable profile\n"); switch_event_destroy(¶ms); } unlink(context->write.file); return SWITCH_STATUS_SUCCESS; } if (context->del_on_close) { if (context->cache_file) { unlink(context->cache_file); unlink(context->meta_file); unlink(context->lock_file); } } if (context->url_params) { switch_event_destroy(&context->url_params); } return SWITCH_STATUS_SUCCESS; } static switch_status_t http_file_write(switch_file_handle_t *handle, void *data, size_t *len) { http_file_context_t *context = handle->private_info; return switch_core_file_write(&context->fh, data, len); } static switch_status_t http_file_file_read(switch_file_handle_t *handle, void *data, size_t *len) { http_file_context_t *context = handle->private_info; switch_status_t status; if (context->samples > 0) { if (*len > (size_t) context->samples) { *len = context->samples; } context->samples -= *len; memset(data, 255, *len *2); status = SWITCH_STATUS_SUCCESS; } else { status = switch_core_file_read(&context->fh, data, len); } return status; } /* Registration */ static char *http_file_supported_formats[SWITCH_MAX_CODECS] = { 0 }; /* /HTTP FILE INTERFACE */ SWITCH_MODULE_LOAD_FUNCTION(mod_httapi_load) { switch_api_interface_t *httapi_api_interface; switch_application_interface_t *app_interface; switch_file_interface_t *file_interface; /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); memset(&globals, 0, sizeof(globals)); globals.pool = pool; globals.hash_root = NULL; globals.hash_tail = NULL; globals.cache_ttl = 300; globals.not_found_expires = 300; http_file_supported_formats[0] = "http"; file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE); file_interface->interface_name = modname; file_interface->extens = http_file_supported_formats; file_interface->file_open = http_file_file_open; file_interface->file_close = http_file_file_close; file_interface->file_read = http_file_file_read; file_interface->file_write = http_file_write; file_interface->file_seek = http_file_file_seek; switch_snprintf(globals.cache_path, sizeof(globals.cache_path), "%s%shttp_file_cache", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR); switch_dir_make_recursive(globals.cache_path, SWITCH_DEFAULT_DIR_PERMS, pool); switch_core_hash_init(&globals.profile_hash, globals.pool); switch_core_hash_init_case(&globals.parse_hash, globals.pool, SWITCH_FALSE); bind_parser("execute", parse_execute); bind_parser("sms", parse_sms); bind_parser("dial", parse_dial); bind_parser("pause", parse_playback); bind_parser("hangup", parse_hangup); bind_parser("record", parse_record); bind_parser("recordCall", parse_record_call); bind_parser("playback", parse_playback); bind_parser("vmName", parse_playback); bind_parser("speak", parse_playback); bind_parser("say", parse_playback); bind_parser("conference", parse_conference); bind_parser("break", parse_break); bind_parser("log", parse_log); bind_parser("continue", parse_continue); bind_parser("getVariable", parse_get_var); bind_parser("voicemail", parse_voicemail); if (do_config() != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } SWITCH_ADD_API(httapi_api_interface, "httapi", "HT-TAPI Hypertext Telephony API", httapi_api_function, HTTAPI_SYNTAX); SWITCH_ADD_APP(app_interface, "httapi", "HT-TAPI Hypertext Telephony API", "HT-TAPI Hypertext Telephony API", httapi_function, "{=}", SAF_SUPPORT_NOMEDIA); switch_console_set_complete("add httapi debug_on"); switch_console_set_complete("add httapi debug_off"); /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; } SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_httapi_shutdown) { hash_node_t *ptr = NULL; client_profile_t *profile; switch_hash_index_t *hi; void *val; const void *vvar; for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, &vvar, NULL, &val); profile = (client_profile_t *) val; switch_event_destroy(&profile->dial_params.app_list); switch_event_destroy(&profile->var_params.expand_var_list); switch_event_destroy(&profile->var_params.set_var_list); switch_event_destroy(&profile->var_params.get_var_list); } switch_core_hash_destroy(&globals.profile_hash); switch_core_hash_destroy(&globals.parse_hash); while (globals.hash_root) { ptr = globals.hash_root; switch_core_hash_destroy(&ptr->hash); globals.hash_root = ptr->next; switch_safe_free(ptr); } return SWITCH_STATUS_SUCCESS; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4: */