/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005-2014, 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_verto.c -- HTML5 Verto interface * */ #include #include /* Prototypes */ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_verto_shutdown); SWITCH_MODULE_LOAD_FUNCTION(mod_verto_load); SWITCH_MODULE_RUNTIME_FUNCTION(mod_verto_runtime); SWITCH_MODULE_DEFINITION(mod_verto, mod_verto_load, mod_verto_shutdown, mod_verto_runtime); #define EP_NAME "verto.rtc" #define WSS_STANDALONE 1 #include "ws.h" ////////////////////////// #include #include #include #include #include #include #include #define die(...) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, __VA_ARGS__); goto error struct globals_s globals; static struct { switch_mutex_t *store_mutex; switch_hash_t *store_hash; } json_GLOBALS; const char json_sql[] = "create table json_store (\n" " name varchar(255) not null,\n" " data text\n" ");\n"; typedef enum { CMD_ADD, CMD_DEL, CMD_DUMP, CMD_COMMIT, CMD_RETRIEVE } store_cmd_t; typedef struct { switch_mutex_t *mutex; cJSON *JSON_STORE; } json_store_t; static void json_cleanup(void) { switch_hash_index_t *hi = NULL; void *val; const void *var; cJSON *json; if (!json_GLOBALS.store_hash) { return; } switch_mutex_lock(json_GLOBALS.store_mutex); top: for (hi = switch_core_hash_first_iter(json_GLOBALS.store_hash, hi); hi; hi = switch_core_hash_next(&hi)) { switch_core_hash_this(hi, &var, NULL, &val); json = (cJSON *) val; cJSON_Delete(json); switch_core_hash_delete(json_GLOBALS.store_hash, var); goto top; } switch_safe_free(hi); switch_mutex_unlock(json_GLOBALS.store_mutex); } static switch_bool_t check_name(const char *name) { const char *p; for(p = name; p && *p; p++) { if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || *p == '-' || *p == '_') continue; return SWITCH_FALSE; } return SWITCH_TRUE; } static verto_profile_t *find_profile(const char *name); static jsock_t *get_jsock(const char *uuid); static void verto_deinit_ssl(verto_profile_t *profile) { if (profile->ssl_ctx) { SSL_CTX_free(profile->ssl_ctx); profile->ssl_ctx = NULL; } } static int ssl_init = 0; static void verto_init_ssl(verto_profile_t *profile) { if (!ssl_init) { SSL_library_init(); ssl_init = 1; } profile->ssl_method = SSLv23_server_method(); /* create server instance */ profile->ssl_ctx = SSL_CTX_new(profile->ssl_method); /* create context */ profile->ssl_ready = 1; assert(profile->ssl_ctx); /* set the local certificate from CertFile */ if (!zstr(profile->chain)) { SSL_CTX_use_certificate_chain_file(profile->ssl_ctx, profile->chain); } SSL_CTX_use_certificate_file(profile->ssl_ctx, profile->cert, SSL_FILETYPE_PEM); /* set the private key from KeyFile */ SSL_CTX_use_PrivateKey_file(profile->ssl_ctx, profile->key, SSL_FILETYPE_PEM); /* verify private key */ if ( !SSL_CTX_check_private_key(profile->ssl_ctx) ) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "SSL NOT AVAILABLE\n"); profile->ssl_ready = 0; verto_deinit_ssl(profile); } else { SSL_CTX_set_cipher_list(profile->ssl_ctx, "HIGH:!DSS:!aNULL@STRENGTH"); } } struct jsock_sub_node_head_s; typedef struct jsock_sub_node_s { jsock_t *jsock; uint32_t serno; struct jsock_sub_node_head_s *head; struct jsock_sub_node_s *next; } jsock_sub_node_t; typedef struct jsock_sub_node_head_s { jsock_sub_node_t *node; jsock_sub_node_t *tail; char *event_channel; } jsock_sub_node_head_t; static uint32_t jsock_unsub_head(jsock_t *jsock, jsock_sub_node_head_t *head) { uint32_t x = 0; jsock_sub_node_t *thisnp = NULL, *np, *last = NULL; np = head->tail = head->node; while (np) { thisnp = np; np = np->next; if (!jsock || thisnp->jsock == jsock) { x++; if (last) { last->next = np; } else { head->node = np; } if (thisnp->jsock->profile->debug || globals.debug) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "UNSUBBING %s [%s]\n", thisnp->jsock->name, thisnp->head->event_channel); } thisnp->jsock = NULL; free(thisnp); } else { last = thisnp; head->tail = last; } } return x; } static void unsub_all_jsock(void) { switch_hash_index_t *hi; void *val; jsock_sub_node_head_t *head; switch_thread_rwlock_wrlock(globals.event_channel_rwlock); top: head = NULL; for (hi = switch_core_hash_first(globals.event_channel_hash); hi; hi = switch_core_hash_next(&hi)) { switch_core_hash_this(hi, NULL, NULL, &val); head = (jsock_sub_node_head_t *) val; jsock_unsub_head(NULL, head); switch_core_hash_delete(globals.event_channel_hash, head->event_channel); free(head->event_channel); free(head); switch_safe_free(hi); goto top; } switch_thread_rwlock_unlock(globals.event_channel_rwlock); } static uint32_t jsock_unsub_channel(jsock_t *jsock, const char *event_channel) { jsock_sub_node_head_t *head; uint32_t x = 0; switch_thread_rwlock_wrlock(globals.event_channel_rwlock); if (!event_channel) { switch_hash_index_t *hi; void *val; for (hi = switch_core_hash_first(globals.event_channel_hash); hi; hi = switch_core_hash_next(&hi)) { switch_core_hash_this(hi, NULL, NULL, &val); if (val) { head = (jsock_sub_node_head_t *) val; x += jsock_unsub_head(jsock, head); } } } else { if ((head = switch_core_hash_find(globals.event_channel_hash, event_channel))) { x += jsock_unsub_head(jsock, head); } } switch_thread_rwlock_unlock(globals.event_channel_rwlock); return x; } static void presence_ping(const char *event_channel) { switch_console_callback_match_t *matches; const char *val = event_channel; if (val) { if (!strcasecmp(val, "presence")) { val = NULL; } else { char *p; if ((p = strchr(val, '.'))) { val = (p+1); } } } if ((matches = switch_core_session_findall_matching_var("presence_id", val))) { switch_console_callback_match_node_t *m; switch_core_session_t *session; for (m = matches->head; m; m = m->next) { if ((session = switch_core_session_locate(m->val))) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_event_t *event; if (switch_event_create(&event, SWITCH_EVENT_CHANNEL_CALLSTATE) == SWITCH_STATUS_SUCCESS) { switch_channel_callstate_t callstate = switch_channel_get_callstate(channel); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Original-Channel-Call-State", switch_channel_callstate2str(callstate)); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Channel-Call-State-Number", "%d", callstate); switch_channel_event_set_data(channel, event); switch_event_fire(&event); } switch_core_session_rwunlock(session); } } switch_console_free_matches(&matches); } } static switch_status_t jsock_sub_channel(jsock_t *jsock, const char *event_channel) { jsock_sub_node_t *node, *np; jsock_sub_node_head_t *head; switch_status_t status = SWITCH_STATUS_FALSE; switch_thread_rwlock_wrlock(globals.event_channel_rwlock); if (!(head = switch_core_hash_find(globals.event_channel_hash, event_channel))) { switch_zmalloc(head, sizeof(*head)); head->event_channel = strdup(event_channel); switch_core_hash_insert(globals.event_channel_hash, event_channel, head); switch_zmalloc(node, sizeof(*node)); node->jsock = jsock; node->head = head; head->node = node; head->tail = node; status = SWITCH_STATUS_SUCCESS; } else { int exist = 0; for (np = head->node; np; np = np->next) { if (np->jsock == jsock) { exist = 1; break; } } if (!exist) { switch_zmalloc(node, sizeof(*node)); node->jsock = jsock; node->head = head; if (!head->node) { head->node = node; head->tail = node; } else { head->tail->next = node; head->tail = head->tail->next; } status = SWITCH_STATUS_SUCCESS; } } switch_thread_rwlock_unlock(globals.event_channel_rwlock); if (status == SWITCH_STATUS_SUCCESS && !strncasecmp(event_channel, "presence", 8)) { presence_ping(event_channel); } return status; } static uint32_t ID = 1; static void close_file(int *sock) { if (*sock > -1) { close(*sock); *sock = -1; } } static void close_socket(int *sock) { if (*sock > -1) { shutdown(*sock, 2); close_file(sock); } } static void del_jsock(jsock_t *jsock) { jsock_t *p, *last = NULL; jsock_unsub_channel(jsock, NULL); switch_event_channel_permission_clear(jsock->uuid_str); switch_mutex_lock(jsock->profile->mutex); for(p = jsock->profile->jsock_head; p; p = p->next) { if (p == jsock) { if (last) { last->next = p->next; } else { jsock->profile->jsock_head = p->next; } jsock->profile->jsock_count--; break; } last = p; } switch_mutex_unlock(jsock->profile->mutex); } static void add_jsock(jsock_t *jsock) { switch_mutex_lock(jsock->profile->mutex); jsock->next = jsock->profile->jsock_head; jsock->profile->jsock_head = jsock; jsock->profile->jsock_count++; switch_mutex_unlock(jsock->profile->mutex); } static uint32_t next_id(void) { uint32_t id; switch_mutex_lock(globals.mutex); id = ID++; switch_mutex_unlock(globals.mutex); return id; } static cJSON *jrpc_new(uint32_t id) { cJSON *obj = cJSON_CreateObject(); cJSON_AddItemToObject(obj, "jsonrpc", cJSON_CreateString("2.0")); if (id) { cJSON_AddItemToObject(obj, "id", cJSON_CreateNumber(id)); } return obj; } static cJSON *jrpc_new_req(const char *method, const char *call_id, cJSON **paramsP) { cJSON *msg, *params = NULL; uint32_t id = next_id(); msg = jrpc_new(id); if (paramsP && *paramsP) { params = *paramsP; } if (!params) { params = cJSON_CreateObject(); } cJSON_AddItemToObject(msg, "method", cJSON_CreateString(method)); cJSON_AddItemToObject(msg, "params", params); if (call_id) { cJSON_AddItemToObject(params, "callID", cJSON_CreateString(call_id)); } if (paramsP) { *paramsP = params; } return msg; } static void jrpc_add_id(cJSON *obj, cJSON *jid, const char *idstr, int id) { if (jid) { cJSON_AddItemToObject(obj, "id", cJSON_Duplicate(jid, 1)); } else if (idstr) { cJSON_AddItemToObject(obj, "id", zstr(idstr) ? cJSON_CreateNull() : cJSON_CreateString(idstr)); } else { cJSON_AddItemToObject(obj, "id", cJSON_CreateNumber(id)); } } static void jrpc_add_error(cJSON *obj, int code, const char *message, cJSON *jid) { cJSON *error = cJSON_CreateObject(); cJSON_AddItemToObject(obj, "error", error); cJSON_AddItemToObject(error, "code", cJSON_CreateNumber(code)); cJSON_AddItemToObject(error, "message", cJSON_CreateString(message)); if (!cJSON_GetObjectItem(obj, "id")) { jrpc_add_id(obj, jid, "", 0); } } static void jrpc_add_result(cJSON *obj, cJSON *result) { if (result) { cJSON_AddItemToObject(obj, "result", result); } } static switch_ssize_t ws_write_json(jsock_t *jsock, cJSON **json, switch_bool_t destroy) { char *json_text; switch_ssize_t r = -1; switch_assert(json); if (!*json) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "WRITE NULL JS ERROR %" SWITCH_SIZE_T_FMT "\n", r); return r; } if (!zstr(jsock->uuid_str)) { cJSON *result = cJSON_GetObjectItem(*json, "result"); if (result) { cJSON_AddItemToObject(result, "sessid", cJSON_CreateString(jsock->uuid_str)); } } if ((json_text = cJSON_PrintUnformatted(*json))) { if (jsock->profile->debug || globals.debug) { char *log_text = cJSON_Print(*json); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "WRITE %s [%s]\n", jsock->name, log_text); free(log_text); } switch_mutex_lock(jsock->write_mutex); r = ws_write_frame(&jsock->ws, WSOC_TEXT, json_text, strlen(json_text)); switch_mutex_unlock(jsock->write_mutex); switch_safe_free(json_text); } if (destroy) { cJSON_Delete(*json); *json = NULL; } if (r <= 0) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "WRITE RETURNED ERROR %" SWITCH_SIZE_T_FMT " \n", r); jsock->drop = 1; } return r; } static switch_status_t jsock_queue_event(jsock_t *jsock, cJSON **json) { switch_status_t status = SWITCH_STATUS_FALSE; if (switch_queue_trypush(jsock->event_queue, *json) == SWITCH_STATUS_SUCCESS) { status = SWITCH_STATUS_SUCCESS; if (jsock->lost_events) { int le = jsock->lost_events; jsock->lost_events = 0; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Lost %d json events!\n", le); } } else { if (++jsock->lost_events > MAX_MISSED) { jsock->drop++; } cJSON_Delete(*json); } *json = NULL; return status; } static void write_event(const char *event_channel, jsock_t *use_jsock, cJSON *event) { jsock_sub_node_head_t *head; if ((head = switch_core_hash_find(globals.event_channel_hash, event_channel))) { jsock_sub_node_t *np; for(np = head->node; np; np = np->next) { cJSON *msg = NULL, *params; if (!use_jsock || use_jsock == np->jsock) { params = cJSON_Duplicate(event, 1); cJSON_AddItemToObject(params, "eventSerno", cJSON_CreateNumber(np->serno++)); msg = jrpc_new_req("verto.event", NULL, ¶ms); //ws_write_json(np->jsock, &msg, SWITCH_TRUE); jsock_queue_event(np->jsock, &msg); } } } } static void jsock_send_event(cJSON *event) { const char *event_channel, *session_uuid = NULL; jsock_t *use_jsock = NULL; switch_core_session_t *session = NULL; if (!(event_channel = cJSON_GetObjectCstr(event, "eventChannel"))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "NO EVENT CHANNEL SPECIFIED\n"); return; } if ((session = switch_core_session_locate(event_channel))) { switch_channel_t *channel = switch_core_session_get_channel(session); const char *jsock_uuid_str = switch_channel_get_variable(channel, "jsock_uuid_str"); if (jsock_uuid_str) { use_jsock = get_jsock(jsock_uuid_str); } switch_core_session_rwunlock(session); } if (use_jsock || (use_jsock = get_jsock(event_channel))) { /* implicit subscription to channel identical to the connection uuid or session uuid */ cJSON *msg = NULL, *params; params = cJSON_Duplicate(event, 1); msg = jrpc_new_req("verto.event", NULL, ¶ms); //ws_write_json(use_jsock, &msg, SWITCH_TRUE); jsock_queue_event(use_jsock, &msg); switch_thread_rwlock_unlock(use_jsock->rwlock); use_jsock = NULL; return; } if ((session_uuid = cJSON_GetObjectCstr(event, "sessid"))) { if (!(use_jsock = get_jsock(session_uuid))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Socket %s not connected\n", session_uuid); return; } } switch_thread_rwlock_rdlock(globals.event_channel_rwlock); write_event(event_channel, use_jsock, event); if (strchr(event_channel, '.')) { char *main_channel = strdup(event_channel); char *p = strchr(main_channel, '.'); if (p) *p = '\0'; write_event(main_channel, use_jsock, event); free(main_channel); } switch_thread_rwlock_unlock(globals.event_channel_rwlock); if (use_jsock) { switch_thread_rwlock_unlock(use_jsock->rwlock); use_jsock = NULL; } } static jrpc_func_t jrpc_get_func(jsock_t *jsock, const char *method) { jrpc_func_t func = NULL; char *main_method = NULL; switch_assert(method); if (jsock->allowed_methods) { if (strchr(method, '.')) { char *p; main_method = strdup(method); if ((p = strchr(main_method, '.'))) { *p = '\0'; } } if (!(switch_event_get_header(jsock->allowed_methods, method) || (main_method && switch_event_get_header(jsock->allowed_methods, main_method)))) { goto end; } } switch_mutex_lock(globals.method_mutex); func = (jrpc_func_t) (intptr_t) switch_core_hash_find(globals.method_hash, method); switch_mutex_unlock(globals.method_mutex); end: switch_safe_free(main_method); return func; } static void jrpc_add_func(const char *method, jrpc_func_t func) { switch_assert(method); switch_assert(func); switch_mutex_lock(globals.method_mutex); switch_core_hash_insert(globals.method_hash, method, (void *) (intptr_t) func); switch_mutex_unlock(globals.method_mutex); } static char *MARKER = "X"; static void set_perm(const char *str, switch_event_t **event) { char delim = ','; char *cur, *next; int count = 0; char *edup; if (!zstr(str)) { if (!strcasecmp(str, "__ANY__")) { return; } } switch_event_create(event, SWITCH_EVENT_REQUEST_PARAMS); if (!zstr(str)) { edup = strdup(str); cur = edup; if (strchr(edup, ' ')) { delim = ' '; } for (cur = edup; cur; count++) { if ((next = strchr(cur, delim))) { *next++ = '\0'; } switch_event_add_header_string(*event, SWITCH_STACK_BOTTOM, cur, MARKER); cur = next; } switch_safe_free(edup); } } static void check_permissions(jsock_t *jsock, switch_xml_t x_user, cJSON *params) { switch_xml_t x_param, x_params; const char *allowed_methods = NULL, *allowed_jsapi = NULL, *allowed_fsapi = NULL, *allowed_event_channels = NULL; if ((x_params = switch_xml_child(x_user, "params"))) { for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { const char *var = switch_xml_attr(x_param, "name"); const char *val = switch_xml_attr(x_param, "value"); if (zstr(val) || zstr(var)) { continue; } if (!strcasecmp(var, "jsonrpc-allowed-methods")) { allowed_methods = val; } if (!strcasecmp(var, "jsonrpc-allowed-jsapi")) { allowed_jsapi = val; } if (!strcasecmp(var, "jsonrpc-allowed-fsapi")) { allowed_fsapi = val; } if (!strcasecmp(var, "jsonrpc-allowed-event-channels")) { allowed_event_channels = val; } } } set_perm(allowed_methods, &jsock->allowed_methods); set_perm(allowed_jsapi, &jsock->allowed_jsapi); set_perm(allowed_fsapi, &jsock->allowed_fsapi); set_perm(allowed_event_channels, &jsock->allowed_event_channels); switch_event_add_header_string(jsock->allowed_methods, SWITCH_STACK_BOTTOM, "login", MARKER); } static switch_bool_t check_auth(jsock_t *jsock, cJSON *params, int *code, char *message, switch_size_t mlen) { switch_bool_t r = SWITCH_FALSE; const char *passwd = NULL; const char *login = NULL; if (!params) { *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Missing params"); goto end; } login = cJSON_GetObjectCstr(params, "login"); passwd = cJSON_GetObjectCstr(params, "passwd"); if (zstr(login)) { goto end; } if (zstr(passwd)) { *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Missing passwd"); goto end; } if (!strcmp(login, "root")) { if (!(r = !strcmp(passwd, jsock->profile->root_passwd))) { *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Authentication Failure"); } } else if (!zstr(jsock->profile->userauth)) { switch_xml_t x_user = NULL; char *id = NULL, *domain = NULL; switch_event_t *req_params; if (*jsock->profile->userauth == '@') { domain = jsock->profile->userauth + 1; id = (char *) login; } else if (switch_true(jsock->profile->userauth)) { id = switch_core_strdup(jsock->pool, login); if ((domain = strchr(id, '@'))) { *domain++ = '\0'; } } if (!(id && domain)) { *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Missing or improper credentials"); goto end; } switch_event_create(&req_params, SWITCH_EVENT_REQUEST_PARAMS); switch_assert(req_params); switch_event_add_header_string(req_params, SWITCH_STACK_BOTTOM, "action", "jsonrpc-authenticate"); if (switch_xml_locate_user_merged("id", id, domain, NULL, &x_user, req_params) != SWITCH_STATUS_SUCCESS) { *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Login Incorrect"); } else { switch_xml_t x_param, x_params; const char *use_passwd = NULL, *verto_context = NULL, *verto_dialplan = NULL; jsock->id = switch_core_strdup(jsock->pool, id); jsock->domain = switch_core_strdup(jsock->pool, domain); jsock->uid = switch_core_sprintf(jsock->pool, "%s@%s", id, domain); if ((x_params = switch_xml_child(x_user, "params"))) { for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { const char *var = switch_xml_attr_soft(x_param, "name"); const char *val = switch_xml_attr_soft(x_param, "value"); if (!use_passwd && !strcasecmp(var, "password")) { use_passwd = val; } else if (!strcasecmp(var, "jsonrpc-password")) { use_passwd = val; } else if (!strcasecmp(var, "verto-context")) { verto_context = val; } else if (!strcasecmp(var, "verto-dialplan")) { verto_dialplan = val; } switch_event_add_header_string(jsock->params, SWITCH_STACK_BOTTOM, var, val); } } if ((x_params = switch_xml_child(x_user, "variables"))) { for (x_param = switch_xml_child(x_params, "variable"); x_param; x_param = x_param->next) { const char *var = switch_xml_attr_soft(x_param, "name"); const char *val = switch_xml_attr_soft(x_param, "value"); switch_event_add_header_string(jsock->vars, SWITCH_STACK_BOTTOM, var, val); } } if (!zstr(verto_dialplan)) { jsock->dialplan = switch_core_strdup(jsock->pool, verto_dialplan); } if (!zstr(verto_context)) { jsock->context = switch_core_strdup(jsock->pool, verto_context); } if (zstr(use_passwd) || strcmp(passwd, use_passwd)) { r = SWITCH_FALSE; *code = CODE_AUTH_FAILED; switch_snprintf(message, mlen, "Authentication Failure"); } else { r = SWITCH_TRUE; check_permissions(jsock, x_user, params); } switch_xml_free(x_user); } switch_event_destroy(&req_params); } end: return r; } static void set_call_params(cJSON *params, verto_pvt_t *tech_pvt) { const char *caller_id_name = NULL; const char *caller_id_number = NULL; if (switch_channel_outbound_display(tech_pvt->channel)) { caller_id_name = switch_channel_get_variable(tech_pvt->channel, "caller_id_name"); caller_id_number = switch_channel_get_variable(tech_pvt->channel, "caller_id_number"); } else { caller_id_name = switch_channel_get_variable(tech_pvt->channel, "callee_id_name"); caller_id_number = switch_channel_get_variable(tech_pvt->channel, "callee_id_number"); } if (zstr(caller_id_name)) { caller_id_name = "Outbound Call"; } if (zstr(caller_id_number)) { caller_id_number = switch_channel_get_variable(tech_pvt->channel, "destination_number"); } cJSON_AddItemToObject(params, "caller_id_name", cJSON_CreateString(caller_id_name)); cJSON_AddItemToObject(params, "caller_id_number", cJSON_CreateString(caller_id_number)); } static jsock_t *get_jsock(const char *uuid) { jsock_t *jsock = NULL; switch_mutex_lock(globals.jsock_mutex); if ((jsock = switch_core_hash_find(globals.jsock_hash, uuid))) { if (switch_thread_rwlock_tryrdlock(jsock->rwlock) != SWITCH_STATUS_SUCCESS) { jsock = NULL; } } switch_mutex_unlock(globals.jsock_mutex); return jsock; } static void attach_jsock(jsock_t *jsock) { switch_mutex_lock(globals.jsock_mutex); switch_core_hash_insert(globals.jsock_hash, jsock->uuid_str, jsock); switch_mutex_unlock(globals.jsock_mutex); } static void detach_jsock(jsock_t *jsock) { switch_mutex_lock(globals.jsock_mutex); switch_core_hash_delete(globals.jsock_hash, jsock->uuid_str); switch_mutex_unlock(globals.jsock_mutex); } static int attach_wake(void) { switch_status_t status; int tries = 0; top: status = switch_mutex_trylock(globals.detach_mutex); if (status == SWITCH_STATUS_SUCCESS) { switch_thread_cond_signal(globals.detach_cond); switch_mutex_unlock(globals.detach_mutex); return 1; } else { if (switch_mutex_trylock(globals.detach2_mutex) == SWITCH_STATUS_SUCCESS) { switch_mutex_unlock(globals.detach2_mutex); } else { if (++tries < 10) { switch_cond_next(); goto top; } } } return 0; } static void tech_reattach(verto_pvt_t *tech_pvt, jsock_t *jsock) { cJSON *params = NULL; cJSON *msg = NULL; tech_pvt->detach_time = 0; globals.detached--; attach_wake(); switch_set_flag(tech_pvt, TFLAG_ATTACH_REQ); msg = jrpc_new_req("verto.attach", tech_pvt->call_id, ¶ms); cJSON_AddItemToObject(params, "sdp", cJSON_CreateString(tech_pvt->mparams->local_sdp_str)); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Local attach SDP %s:\n%s\n", switch_channel_get_name(tech_pvt->channel), tech_pvt->mparams->local_sdp_str); set_call_params(params, tech_pvt); ws_write_json(jsock, &msg, SWITCH_TRUE); } static void drop_detached(void) { verto_pvt_t *tech_pvt; switch_time_t now = switch_epoch_time_now(NULL); switch_thread_rwlock_rdlock(globals.tech_rwlock); for(tech_pvt = globals.tech_head; tech_pvt; tech_pvt = tech_pvt->next) { if (!switch_channel_up_nosig(tech_pvt->channel)) { continue; } if (tech_pvt->detach_time && (now - tech_pvt->detach_time) > globals.detach_timeout) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_RECOVERY_ON_TIMER_EXPIRE); } } switch_thread_rwlock_unlock(globals.tech_rwlock); } static void attach_calls(jsock_t *jsock) { verto_pvt_t *tech_pvt; switch_thread_rwlock_rdlock(globals.tech_rwlock); for(tech_pvt = globals.tech_head; tech_pvt; tech_pvt = tech_pvt->next) { if (tech_pvt->detach_time && !strcmp(tech_pvt->jsock_uuid, jsock->uuid_str)) { if (!switch_channel_up_nosig(tech_pvt->channel)) { continue; } tech_reattach(tech_pvt, jsock); } } switch_thread_rwlock_unlock(globals.tech_rwlock); } static void detach_calls(jsock_t *jsock) { verto_pvt_t *tech_pvt; switch_thread_rwlock_rdlock(globals.tech_rwlock); for(tech_pvt = globals.tech_head; tech_pvt; tech_pvt = tech_pvt->next) { if (!strcmp(tech_pvt->jsock_uuid, jsock->uuid_str)) { if (!switch_channel_up_nosig(tech_pvt->channel)) { continue; } tech_pvt->detach_time = switch_epoch_time_now(NULL); globals.detached++; attach_wake(); } } switch_thread_rwlock_unlock(globals.tech_rwlock); } static void process_jrpc_response(jsock_t *jsock, cJSON *json) { } static void set_session_id(jsock_t *jsock, const char *uuid) { //cJSON *params, *msg = jrpc_new(0); if (!zstr(uuid)) { switch_set_string(jsock->uuid_str, uuid); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s re-connecting session %s\n", jsock->name, jsock->uuid_str); } else { switch_uuid_str(jsock->uuid_str, sizeof(jsock->uuid_str)); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s new RPC session %s\n", jsock->name, jsock->uuid_str); } attach_jsock(jsock); } static cJSON *process_jrpc(jsock_t *jsock, cJSON *json) { cJSON *reply = NULL, *echo = NULL, *id = NULL, *params = NULL, *response = NULL, *result; const char *method = NULL, *version = NULL, *sessid = NULL; jrpc_func_t func = NULL; switch_assert(json); method = cJSON_GetObjectCstr(json, "method"); result = cJSON_GetObjectItem(json, "result"); version = cJSON_GetObjectCstr(json, "jsonrpc"); id = cJSON_GetObjectItem(json, "id"); if ((params = cJSON_GetObjectItem(json, "params"))) { sessid = cJSON_GetObjectCstr(params, "sessid"); } if (!switch_test_flag(jsock, JPFLAG_INIT)) { set_session_id(jsock, sessid); switch_set_flag(jsock, JPFLAG_INIT); } if (zstr(version) || strcmp(version, "2.0")) { reply = jrpc_new(0); jrpc_add_error(reply, CODE_INVALID, "Invalid message", id); goto end; } if (result) { process_jrpc_response(jsock, json); return NULL; } reply = jrpc_new(0); jrpc_add_id(reply, id, "", 0); if (!switch_test_flag(jsock, JPFLAG_AUTHED) && (jsock->profile->userauth || jsock->profile->root_passwd)) { int code = CODE_AUTH_REQUIRED; char message[128] = "Authentication Required"; if (!check_auth(jsock, params, &code, message, sizeof(message))) { jrpc_add_error(reply, code, message, id); goto end; } switch_set_flag(jsock, JPFLAG_AUTHED); } if (!method || !(func = jrpc_get_func(jsock, method))) { jrpc_add_error(reply, -32601, "Invalid Method, Missing Method or Permission Denied", id); } else { if (func(method, params, jsock, &response) == SWITCH_TRUE) { if (params) { echo = cJSON_GetObjectItem(params, "echoParams"); } if (echo) { if ((echo->type == cJSON_True || (echo->type == cJSON_String && switch_true(echo->valuestring)))) { cJSON_AddItemToObject(response, "requestParams", cJSON_Duplicate(params, 1)); } else { cJSON_AddItemToObject(response, "requestParams", cJSON_Duplicate(echo, 1)); } } jrpc_add_result(reply, response); } else { if (response) { cJSON_AddItemToObject(reply, "error", response); } else { jrpc_add_error(reply, -32602, "Permission Denied", id); } } } end: return reply; } static switch_status_t process_input(jsock_t *jsock, uint8_t *data, switch_ssize_t bytes) { cJSON *json = NULL, *reply = NULL; char *ascii = (char *) data; switch_status_t status = SWITCH_STATUS_SUCCESS; if (ascii) { json = cJSON_Parse(ascii); } if (json) { if (jsock->profile->debug || globals.debug) { char *log_text = cJSON_Print(json); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "READ %s [%s]\n", jsock->name, log_text); free(log_text); } if (json->type == cJSON_Array) { /* batch mode */ int i, len = cJSON_GetArraySize(json); reply = cJSON_CreateArray(); for(i = 0; i < len; i++) { cJSON *obj, *item = cJSON_GetArrayItem(json, i); if ((obj = process_jrpc(jsock, item))) { cJSON_AddItemToArray(reply, obj); } } } else { reply = process_jrpc(jsock, json); } } else { reply = jrpc_new(0); jrpc_add_error(reply, -32600, "Invalid Request", NULL); } if (reply) { ws_write_json(jsock, &reply, SWITCH_TRUE); } if (json) { cJSON_Delete(json); } return status; } static void jsock_check_event_queue(jsock_t *jsock) { void *pop; int this_pass = switch_queue_size(jsock->event_queue); switch_mutex_lock(jsock->write_mutex); while(this_pass-- > 0 && switch_queue_trypop(jsock->event_queue, &pop) == SWITCH_STATUS_SUCCESS) { cJSON *json = (cJSON *) pop; ws_write_json(jsock, &json, SWITCH_TRUE); } switch_mutex_unlock(jsock->write_mutex); } static void client_run(jsock_t *jsock) { jsock->local_addr.sin_family = AF_INET; jsock->local_addr.sin_addr.s_addr = htonl(INADDR_ANY); jsock->local_addr.sin_port = 0; if (ws_init(&jsock->ws, jsock->client_socket, (jsock->ptype & PTYPE_CLIENT_SSL) ? jsock->profile->ssl_ctx : NULL, 0, 1) < 0) { die("%s WS SETUP FAILED", jsock->name); } while(jsock->profile->running) { int pflags = switch_wait_sock(jsock->client_socket, 50, SWITCH_POLL_READ | SWITCH_POLL_ERROR | SWITCH_POLL_HUP); if (jsock->drop) { die("%s Dropping Connection\n", jsock->name); } if (pflags < 0) { if (errno != EINTR) { die("%s POLL FAILED\n", jsock->name); } } if (pflags & SWITCH_POLL_ERROR) { die("%s POLL ERROR\n", jsock->name); } if (pflags & SWITCH_POLL_HUP) { die("%s POLL HANGUP DETECTED\n", jsock->name); } if (pflags & SWITCH_POLL_INVALID) { die("%s POLL INVALID SOCKET\n", jsock->name); } if (pflags & SWITCH_POLL_READ) { switch_ssize_t bytes; ws_opcode_t oc; uint8_t *data; bytes = ws_read_frame(&jsock->ws, &oc, &data); if (bytes < 0) { die("BAD READ %" SWITCH_SIZE_T_FMT "\n", bytes); break; } if (bytes) { if (process_input(jsock, data, bytes) != SWITCH_STATUS_SUCCESS) { die("Input Error\n"); } if (!switch_test_flag(jsock, JPFLAG_CHECK_ATTACH) && switch_test_flag(jsock, JPFLAG_AUTHED)) { attach_calls(jsock); switch_set_flag(jsock, JPFLAG_CHECK_ATTACH); } } } else { jsock_check_event_queue(jsock); } } error: detach_jsock(jsock); ws_destroy(&jsock->ws); return; } static void jsock_flush(jsock_t *jsock) { void *pop; switch_mutex_lock(jsock->write_mutex); while(switch_queue_trypop(jsock->event_queue, &pop) == SWITCH_STATUS_SUCCESS) { cJSON *json = (cJSON *) pop; cJSON_Delete(json); } switch_mutex_unlock(jsock->write_mutex); } static void *SWITCH_THREAD_FUNC client_thread(switch_thread_t *thread, void *obj) { jsock_t *jsock = (jsock_t *) obj; switch_event_create(&jsock->params, SWITCH_EVENT_CHANNEL_DATA); switch_event_create(&jsock->vars, SWITCH_EVENT_CHANNEL_DATA); add_jsock(jsock); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Starting client thread.\n", jsock->name); if ((jsock->ptype & PTYPE_CLIENT) || (jsock->ptype & PTYPE_CLIENT_SSL)) { client_run(jsock); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s Ending client thread.\n", jsock->name); } detach_calls(jsock); del_jsock(jsock); switch_event_destroy(&jsock->params); switch_event_destroy(&jsock->vars); if (jsock->client_socket > -1) { close_socket(&jsock->client_socket); } switch_event_destroy(&jsock->allowed_methods); switch_event_destroy(&jsock->allowed_fsapi); switch_event_destroy(&jsock->allowed_jsapi); switch_event_destroy(&jsock->allowed_event_channels); jsock_flush(jsock); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Ending client thread.\n", jsock->name); switch_thread_rwlock_wrlock(jsock->rwlock); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Thread ended\n", jsock->name); switch_thread_rwlock_unlock(jsock->rwlock); return NULL; } static switch_bool_t auth_api_command(jsock_t *jsock, const char *api_cmd, const char *arg) { const char *check_cmd = api_cmd; char *sneaky_commands[] = { "bgapi", "sched_api", "eval", "expand", "xml_wrap", NULL }; int x = 0; char *dup_arg = NULL; char *next = NULL; switch_bool_t ok = SWITCH_TRUE; top: if (!jsock->allowed_fsapi) { ok = SWITCH_FALSE; goto end; } if (!switch_event_get_header(jsock->allowed_fsapi, check_cmd)) { ok = SWITCH_FALSE; goto end; } while (check_cmd) { for (x = 0; sneaky_commands[x]; x++) { if (!strcasecmp(sneaky_commands[x], check_cmd)) { if (check_cmd == api_cmd) { if (arg) { switch_safe_free(dup_arg); dup_arg = strdup(arg); check_cmd = dup_arg; if ((next = strchr(check_cmd, ' '))) { *next++ = '\0'; } } else { break; } } else { if (next) { check_cmd = next; } else { check_cmd = dup_arg; } if ((next = strchr(check_cmd, ' '))) { *next++ = '\0'; } } goto top; } } break; } end: switch_safe_free(dup_arg); return ok; } //// VERTO static void track_pvt(verto_pvt_t *tech_pvt) { switch_thread_rwlock_wrlock(globals.tech_rwlock); tech_pvt->next = globals.tech_head; globals.tech_head = tech_pvt; switch_thread_rwlock_unlock(globals.tech_rwlock); } static void untrack_pvt(verto_pvt_t *tech_pvt) { verto_pvt_t *p, *last = NULL; switch_thread_rwlock_wrlock(globals.tech_rwlock); if (tech_pvt->detach_time) { globals.detached--; tech_pvt->detach_time = 0; attach_wake(); } for(p = globals.tech_head; p; p = p->next) { if (p == tech_pvt) { if (last) { last->next = p->next; } else { globals.tech_head = p->next; } break; } last = p; } switch_thread_rwlock_unlock(globals.tech_rwlock); } static switch_status_t verto_on_hangup(switch_core_session_t *session) { jsock_t *jsock = NULL; verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); untrack_pvt(tech_pvt); // get the jsock and send hangup notice if (!tech_pvt->remote_hangup_cause && (jsock = get_jsock(tech_pvt->jsock_uuid))) { cJSON *params = NULL; cJSON *msg = jrpc_new_req("verto.bye", tech_pvt->call_id, ¶ms); switch_call_cause_t cause = switch_channel_get_cause(tech_pvt->channel); cJSON_AddItemToObject(params, "causeCode", cJSON_CreateNumber(cause)); cJSON_AddItemToObject(params, "cause", cJSON_CreateString(switch_channel_cause2str(cause))); ws_write_json(jsock, &msg, SWITCH_TRUE); switch_thread_rwlock_unlock(jsock->rwlock); } return SWITCH_STATUS_SUCCESS; } static void verto_set_media_options(verto_pvt_t *tech_pvt, verto_profile_t *profile); static switch_status_t verto_connect(switch_core_session_t *session, const char *method) { switch_status_t status = SWITCH_STATUS_SUCCESS; jsock_t *jsock = NULL; verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); if (!(jsock = get_jsock(tech_pvt->jsock_uuid))) { status = SWITCH_STATUS_BREAK; } else { cJSON *params = NULL; cJSON *msg = NULL; const char *var = NULL; switch_caller_profile_t *caller_profile = switch_channel_get_caller_profile(tech_pvt->channel); switch_channel_set_variable(tech_pvt->channel, "verto_user", jsock->uid); switch_channel_set_variable(tech_pvt->channel, "verto_host", jsock->domain); if ((var = switch_event_get_header(jsock->params, "caller-id-name"))) { caller_profile->callee_id_name = switch_core_strdup(caller_profile->pool, var); } if ((var = switch_event_get_header(jsock->params, "caller-id-number"))) { caller_profile->callee_id_number = switch_core_strdup(caller_profile->pool, var); } if (switch_channel_test_flag(tech_pvt->channel, CF_PROXY_MODE)) { switch_core_media_absorb_sdp(session); } else { switch_channel_set_variable(tech_pvt->channel, "media_webrtc", "true"); switch_core_session_set_ice(tech_pvt->session); verto_set_media_options(tech_pvt, jsock->profile); switch_channel_set_variable(tech_pvt->channel, "verto_profile_name", jsock->profile->name); if (!switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING)) { switch_core_media_prepare_codecs(tech_pvt->session, SWITCH_TRUE); if ((status = switch_core_media_choose_ports(tech_pvt->session, SWITCH_TRUE, SWITCH_TRUE)) != SWITCH_STATUS_SUCCESS) { //if ((status = switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0)) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_thread_rwlock_unlock(jsock->rwlock); return status; } } switch_core_media_gen_local_sdp(session, SDP_TYPE_REQUEST, NULL, 0, NULL, 0); } msg = jrpc_new_req(method, tech_pvt->call_id, ¶ms); if (tech_pvt->mparams->local_sdp_str) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Local %s SDP %s:\n%s\n", method, switch_channel_get_name(tech_pvt->channel), tech_pvt->mparams->local_sdp_str); cJSON_AddItemToObject(params, "sdp", cJSON_CreateString(tech_pvt->mparams->local_sdp_str)); set_call_params(params, tech_pvt); ws_write_json(jsock, &msg, SWITCH_TRUE); } else { status = SWITCH_STATUS_FALSE; } switch_thread_rwlock_unlock(jsock->rwlock); } return status; } switch_status_t verto_tech_media(verto_pvt_t *tech_pvt, const char *r_sdp, switch_sdp_type_t sdp_type) { uint8_t match = 0, p = 0; switch_assert(tech_pvt != NULL); switch_assert(r_sdp != NULL); if (zstr(r_sdp)) { return SWITCH_STATUS_FALSE; } if ((match = switch_core_media_negotiate_sdp(tech_pvt->session, r_sdp, &p, sdp_type))) { if (switch_core_media_choose_ports(tech_pvt->session, SWITCH_TRUE, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS) { //if (switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } if (switch_core_media_activate_rtp(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } //if (!switch_channel_test_flag(tech_pvt->channel, CF_ANSWERED)) { // switch_channel_set_variable(tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "EARLY MEDIA"); // switch_channel_mark_pre_answered(tech_pvt->channel); //} return SWITCH_STATUS_SUCCESS; } return SWITCH_STATUS_FALSE; } static switch_status_t verto_on_init(switch_core_session_t *session) { switch_status_t status = SWITCH_STATUS_SUCCESS; verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); if (switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING_BRIDGE) || switch_channel_test_flag(tech_pvt->channel, CF_RECOVERING)) { int tries = 120; switch_core_session_clear_crypto(session); while(--tries > 0) { status = verto_connect(session, "verto.attach"); if (status == SWITCH_STATUS_SUCCESS) { switch_set_flag(tech_pvt, TFLAG_ATTACH_REQ); break; } else if (status == SWITCH_STATUS_BREAK) { switch_yield(1000000); continue; } else { tries = 0; break; } } if (!tries) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); status = SWITCH_STATUS_FALSE; } switch_channel_set_flag(tech_pvt->channel, CF_VIDEO_BREAK); switch_core_session_kill_channel(tech_pvt->session, SWITCH_SIG_BREAK); tries = 500; while(--tries > 0 && switch_test_flag(tech_pvt, TFLAG_ATTACH_REQ)) { switch_yield(10000); } switch_core_session_refresh_video(session); switch_channel_set_flag(tech_pvt->channel, CF_VIDEO_BREAK); switch_core_session_kill_channel(tech_pvt->session, SWITCH_SIG_BREAK); return status; } if (switch_channel_direction(tech_pvt->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) { if ((status = verto_connect(tech_pvt->session, "verto.invite")) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } } return status; } static switch_state_handler_table_t verto_state_handlers = { /*.on_init */ verto_on_init, /*.on_routing */ NULL, /*.on_execute */ NULL, /*.on_hangup */ verto_on_hangup, /*.on_exchange_media */ NULL, /*.on_soft_execute */ NULL, /*.on_consume_media */ NULL, /*.on_hibernate */ NULL, /*.on_reset */ NULL, /*.on_park */ NULL, /*.on_reporting */ NULL, /*.on_destroy */ NULL, SSH_FLAG_STICKY }; static void verto_set_media_options(verto_pvt_t *tech_pvt, verto_profile_t *profile) { int i; tech_pvt->mparams->rtpip = switch_core_session_strdup(tech_pvt->session, profile->rtpip[profile->rtpip_cur++]); if (profile->rtpip_cur == profile->rtpip_index) { profile->rtpip_cur = 0; } tech_pvt->mparams->extrtpip = profile->extrtpip; //tech_pvt->mparams->dtmf_type = tech_pvt->profile->dtmf_type; switch_channel_set_flag(tech_pvt->channel, CF_TRACKABLE); switch_channel_set_variable(tech_pvt->channel, "secondary_recovery_module", modname); switch_core_media_check_dtmf_type(tech_pvt->session); //switch_channel_set_cap(tech_pvt->channel, CC_MEDIA_ACK); switch_channel_set_cap(tech_pvt->channel, CC_BYPASS_MEDIA); //switch_channel_set_cap(tech_pvt->channel, CC_PROXY_MEDIA); switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER); switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP); //switch_channel_set_cap(tech_pvt->channel, CC_QUEUEABLE_DTMF_DELAY); //tech_pvt->mparams->ndlb = tech_pvt->profile->mndlb; tech_pvt->mparams->inbound_codec_string = switch_core_session_strdup(tech_pvt->session, profile->inbound_codec_string); tech_pvt->mparams->outbound_codec_string = switch_core_session_strdup(tech_pvt->session, profile->outbound_codec_string); tech_pvt->mparams->jb_msec = "-3"; switch_media_handle_set_media_flag(tech_pvt->smh, SCMF_SUPPRESS_CNG); switch_media_handle_set_media_flag(tech_pvt->smh, SCMF_RENEG_ON_REINVITE); //tech_pvt->mparams->auto_rtp_bugs = profile->auto_rtp_bugs; tech_pvt->mparams->timer_name = profile->timer_name; //tech_pvt->mparams->vflags = profile->vflags; //tech_pvt->mparams->manual_rtp_bugs = profile->manual_rtp_bugs; //tech_pvt->mparams->manual_video_rtp_bugs = profile->manual_video_rtp_bugs; tech_pvt->mparams->local_network = switch_core_session_strdup(tech_pvt->session, profile->local_network); //tech_pvt->mparams->rtcp_audio_interval_msec = profile->rtpp_audio_interval_msec; //tech_pvt->mparams->rtcp_video_interval_msec = profile->rtpp_video_interval_msec; //tech_pvt->mparams->sdp_username = profile->sdp_username; //tech_pvt->mparams->cng_pt = tech_pvt->cng_pt; //tech_pvt->mparams->rtc_timeout_sec = profile->rtp_timeout_sec; //tech_pvt->mparams->rtc_hold_timeout_sec = profile->rtp_hold_timeout_sec; //switch_media_handle_set_media_flags(tech_pvt->media_handle, tech_pvt->profile->media_flags); for(i = 0; i < profile->cand_acl_count; i++) { switch_core_media_add_ice_acl(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, profile->cand_acl[i]); switch_core_media_add_ice_acl(tech_pvt->session, SWITCH_MEDIA_TYPE_VIDEO, profile->cand_acl[i]); } } static switch_status_t verto_media(switch_core_session_t *session) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); switch_status_t status = SWITCH_STATUS_SUCCESS; switch_core_media_prepare_codecs(tech_pvt->session, SWITCH_TRUE); if (tech_pvt->r_sdp) { if (verto_tech_media(tech_pvt, tech_pvt->r_sdp, SDP_TYPE_REQUEST) != SWITCH_STATUS_SUCCESS) { switch_channel_set_variable(tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "CODEC NEGOTIATION ERROR"); return SWITCH_STATUS_FALSE; } } if ((status = switch_core_media_choose_ports(tech_pvt->session, SWITCH_TRUE, SWITCH_FALSE)) != SWITCH_STATUS_SUCCESS) { //if ((status = switch_core_media_choose_port(tech_pvt->session, SWITCH_MEDIA_TYPE_AUDIO, 0)) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); return status; } switch_core_media_gen_local_sdp(session, SDP_TYPE_RESPONSE, NULL, 0, NULL, 0); if (switch_core_media_activate_rtp(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } if (tech_pvt->mparams->local_sdp_str) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Local SDP %s:\n%s\n", switch_channel_get_name(tech_pvt->channel), tech_pvt->mparams->local_sdp_str); } else { status = SWITCH_STATUS_FALSE; } return status; } static switch_status_t verto_send_media_indication(switch_core_session_t *session, const char *method) { switch_status_t status = SWITCH_STATUS_FALSE; verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); const char *proxy_sdp = NULL; if (switch_test_flag(tech_pvt, TFLAG_SENT_MEDIA)) { status = SWITCH_STATUS_SUCCESS; } if (switch_channel_test_flag(tech_pvt->channel, CF_PROXY_MODE)) { if ((proxy_sdp = switch_channel_get_variable(tech_pvt->channel, SWITCH_B_SDP_VARIABLE))) { status = SWITCH_STATUS_SUCCESS; switch_core_media_set_local_sdp(session, proxy_sdp, SWITCH_TRUE); } } if (status == SWITCH_STATUS_SUCCESS || (status = verto_media(session)) == SWITCH_STATUS_SUCCESS) { jsock_t *jsock = NULL; if (!(jsock = get_jsock(tech_pvt->jsock_uuid))) { status = SWITCH_STATUS_FALSE; } else { cJSON *params = NULL; cJSON *msg = jrpc_new_req(method, tech_pvt->call_id, ¶ms); if (!switch_test_flag(tech_pvt, TFLAG_SENT_MEDIA)) { cJSON_AddItemToObject(params, "sdp", cJSON_CreateString(tech_pvt->mparams->local_sdp_str)); } switch_set_flag(tech_pvt, TFLAG_SENT_MEDIA); if (ws_write_json(jsock, &msg, SWITCH_TRUE) <= 0) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); } switch_thread_rwlock_unlock(jsock->rwlock); } } return status; } static switch_status_t messagehook (switch_core_session_t *session, switch_core_session_message_t *msg) { switch_status_t r = SWITCH_STATUS_SUCCESS; verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); switch(msg->message_id) { case SWITCH_MESSAGE_INDICATE_DISPLAY: { const char *name, *number; cJSON *jmsg = NULL, *params = NULL; jsock_t *jsock = NULL; if ((jsock = get_jsock(tech_pvt->jsock_uuid))) { name = msg->string_array_arg[0]; number = msg->string_array_arg[1]; if (name || number) { jmsg = jrpc_new_req("verto.display", tech_pvt->call_id, ¶ms); cJSON_AddItemToObject(params, "display_name", cJSON_CreateString(name)); cJSON_AddItemToObject(params, "display_number", cJSON_CreateString(number)); //ws_write_json(jsock, &jmsg, SWITCH_TRUE); jsock_queue_event(jsock, &jmsg); } switch_thread_rwlock_unlock(jsock->rwlock); } } break; case SWITCH_MESSAGE_INDICATE_ANSWER: r = verto_send_media_indication(session, "verto.answer"); break; case SWITCH_MESSAGE_INDICATE_PROGRESS: r = verto_send_media_indication(session, "verto.media"); break; default: break; } return r; } static int verto_recover_callback(switch_core_session_t *session) { int r = 0; char name[512]; verto_pvt_t *tech_pvt = NULL; verto_profile_t *profile = NULL; const char *profile_name = NULL, *jsock_uuid_str = NULL; switch_channel_t *channel = switch_core_session_get_channel(session); profile_name = switch_channel_get_variable(channel, "verto_profile_name"); jsock_uuid_str = switch_channel_get_variable(channel, "jsock_uuid_str"); if (!(profile_name && jsock_uuid_str && (profile = find_profile(profile_name)))) { return 0; } tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt)); tech_pvt->session = session; tech_pvt->channel = channel; tech_pvt->jsock_uuid = (char *) jsock_uuid_str; switch_core_session_set_private_class(session, tech_pvt, SWITCH_PVT_SECONDARY); tech_pvt->call_id = switch_core_session_strdup(session, switch_core_session_get_uuid(session)); if ((tech_pvt->smh = switch_core_session_get_media_handle(session))) { tech_pvt->mparams = switch_core_media_get_mparams(tech_pvt->smh); verto_set_media_options(tech_pvt, profile); } switch_snprintf(name, sizeof(name), "verto.rtc/%s", tech_pvt->jsock_uuid); switch_channel_set_name(channel, name); switch_channel_add_state_handler(channel, &verto_state_handlers); switch_core_event_hook_add_receive_message(session, messagehook); track_pvt(tech_pvt); //switch_channel_clear_flag(tech_pvt->channel, CF_ANSWERED); //switch_channel_clear_flag(tech_pvt->channel, CF_EARLY_MEDIA); switch_thread_rwlock_unlock(profile->rwlock); r++; return r; } static void pass_sdp(verto_pvt_t *tech_pvt) { switch_core_session_t *other_session = NULL; switch_channel_t *other_channel = NULL; if (switch_core_session_get_partner(tech_pvt->session, &other_session) == SWITCH_STATUS_SUCCESS) { other_channel = switch_core_session_get_channel(other_session); switch_channel_set_variable(other_channel, SWITCH_B_SDP_VARIABLE, tech_pvt->r_sdp); switch_channel_set_flag(other_channel, CF_PROXY_MODE); switch_core_session_queue_indication(other_session, SWITCH_MESSAGE_INDICATE_ANSWER); switch_core_session_rwunlock(other_session); } } //// METHODS #define switch_either(_A, _B) zstr(_A) ? _B : _A static switch_bool_t verto__answer_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(); switch_core_session_t *session; cJSON *dialog = NULL; const char *call_id = NULL, *sdp = NULL; int err = 0; *response = obj; if (!(dialog = cJSON_GetObjectItem(params, "dialogParams"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Dialog data missing")); err = 1; goto cleanup; } if (!(call_id = cJSON_GetObjectCstr(dialog, "callID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CallID missing")); err = 1; goto cleanup; } if (!(sdp = cJSON_GetObjectCstr(params, "sdp"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("SDP missing")); err = 1; goto cleanup; } if ((session = switch_core_session_locate(call_id))) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); tech_pvt->r_sdp = switch_core_session_strdup(session, sdp); switch_channel_set_variable(tech_pvt->channel, SWITCH_R_SDP_VARIABLE, sdp); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote SDP %s:\n%s\n", switch_channel_get_name(tech_pvt->channel), sdp); switch_core_media_set_sdp_codec_string(session, sdp, SDP_TYPE_RESPONSE); if (switch_channel_test_flag(tech_pvt->channel, CF_PROXY_MODE)) { pass_sdp(tech_pvt); } else { if (verto_tech_media(tech_pvt, tech_pvt->r_sdp, SDP_TYPE_RESPONSE) != SWITCH_STATUS_SUCCESS) { switch_channel_set_variable(tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "CODEC NEGOTIATION ERROR"); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CODEC ERROR")); err = 1; } if (!err && switch_core_media_activate_rtp(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("MEDIA ERROR")); err = 1; } } if (!err) { switch_channel_mark_answered(tech_pvt->channel); } switch_core_session_rwunlock(session); } cleanup: if (!err) return SWITCH_TRUE; cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL DOES NOT EXIST")); cJSON_AddItemToObject(obj, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); return SWITCH_FALSE; } static switch_bool_t verto__bye_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(); switch_core_session_t *session; cJSON *dialog = NULL; const char *call_id = NULL, *cause_str = NULL; int err = 0; switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING; *response = obj; if (!(dialog = cJSON_GetObjectItem(params, "dialogParams"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Dialog data missing")); err = 1; goto cleanup; } if (!(call_id = cJSON_GetObjectCstr(dialog, "callID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CallID missing")); err = 1; goto cleanup; } if ((cause_str = cJSON_GetObjectCstr(params, "cause"))) { switch_call_cause_t check = switch_channel_str2cause(cause_str); if (check != SWITCH_CAUSE_NONE) { cause = check; } } cJSON_AddItemToObject(obj, "callID", cJSON_CreateString(call_id)); if ((session = switch_core_session_locate(call_id))) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); tech_pvt->remote_hangup_cause = cause; switch_channel_hangup(tech_pvt->channel, cause); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL ENDED")); cJSON_AddItemToObject(obj, "causeCode", cJSON_CreateNumber(cause)); cJSON_AddItemToObject(obj, "cause", cJSON_CreateString(switch_channel_cause2str(cause))); switch_core_session_rwunlock(session); } else { err = 1; } cleanup: if (!err) return SWITCH_TRUE; cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL DOES NOT EXIST")); cJSON_AddItemToObject(obj, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); return SWITCH_FALSE; } static switch_status_t xfer_hanguphook(switch_core_session_t *session) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_channel_state_t state = switch_channel_get_state(channel); if (state == CS_HANGUP) { switch_core_session_t *ksession; const char *uuid = switch_channel_get_variable(channel, "att_xfer_kill_uuid"); if (uuid && (ksession = switch_core_session_force_locate(uuid))) { switch_channel_t *kchannel = switch_core_session_get_channel(ksession); switch_channel_clear_flag(kchannel, CF_XFER_ZOMBIE); switch_channel_clear_flag(kchannel, CF_TRANSFER); if (switch_channel_up(kchannel)) { switch_channel_hangup(kchannel, SWITCH_CAUSE_NORMAL_CLEARING); } switch_core_session_rwunlock(ksession); } switch_core_event_hook_remove_state_change(session, xfer_hanguphook); } return SWITCH_STATUS_SUCCESS; } static void mark_transfer_record(switch_core_session_t *session, const char *br_a, const char *br_b) { switch_core_session_t *br_b_session, *br_a_session; switch_channel_t *channel; const char *uvar1, *dvar1, *uvar2, *dvar2; channel = switch_core_session_get_channel(session); uvar1 = "verto_user"; dvar1 = "verto_host"; if ((br_b_session = switch_core_session_locate(br_b)) ) { switch_channel_t *br_b_channel = switch_core_session_get_channel(br_b_session); switch_caller_profile_t *cp = switch_channel_get_caller_profile(br_b_channel); if (switch_channel_direction(br_b_channel) == SWITCH_CALL_DIRECTION_INBOUND) { uvar2 = "sip_from_user"; dvar2 = "sip_from_host"; } else { uvar2 = "sip_to_user"; dvar2 = "sip_to_host"; } cp->transfer_source = switch_core_sprintf(cp->pool, "%ld:%s:att_xfer:%s@%s/%s@%s", (long) switch_epoch_time_now(NULL), cp->uuid_str, switch_channel_get_variable(channel, uvar1), switch_channel_get_variable(channel, dvar1), switch_channel_get_variable(br_b_channel, uvar2), switch_channel_get_variable(br_b_channel, dvar2)); switch_channel_add_variable_var_check(br_b_channel, SWITCH_TRANSFER_HISTORY_VARIABLE, cp->transfer_source, SWITCH_FALSE, SWITCH_STACK_PUSH); switch_channel_set_variable(br_b_channel, SWITCH_TRANSFER_SOURCE_VARIABLE, cp->transfer_source); switch_core_session_rwunlock(br_b_session); } if ((br_a_session = switch_core_session_locate(br_a)) ) { switch_channel_t *br_a_channel = switch_core_session_get_channel(br_a_session); switch_caller_profile_t *cp = switch_channel_get_caller_profile(br_a_channel); if (switch_channel_direction(br_a_channel) == SWITCH_CALL_DIRECTION_INBOUND) { uvar2 = "sip_from_user"; dvar2 = "sip_from_host"; } else { uvar2 = "sip_to_user"; dvar2 = "sip_to_host"; } cp->transfer_source = switch_core_sprintf(cp->pool, "%ld:%s:att_xfer:%s@%s/%s@%s", (long) switch_epoch_time_now(NULL), cp->uuid_str, switch_channel_get_variable(channel, uvar1), switch_channel_get_variable(channel, dvar1), switch_channel_get_variable(br_a_channel, uvar2), switch_channel_get_variable(br_a_channel, dvar2)); switch_channel_add_variable_var_check(br_a_channel, SWITCH_TRANSFER_HISTORY_VARIABLE, cp->transfer_source, SWITCH_FALSE, SWITCH_STACK_PUSH); switch_channel_set_variable(br_a_channel, SWITCH_TRANSFER_SOURCE_VARIABLE, cp->transfer_source); switch_core_session_rwunlock(br_a_session); } } static switch_bool_t attended_transfer(switch_core_session_t *session, switch_core_session_t *b_session) { verto_pvt_t *tech_pvt = NULL, *b_tech_pvt = NULL; switch_bool_t result = SWITCH_FALSE; const char *br_a = NULL, *br_b = NULL; tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); b_tech_pvt = switch_core_session_get_private_class(b_session, SWITCH_PVT_SECONDARY); switch_assert(b_tech_pvt); switch_assert(tech_pvt); switch_channel_set_variable(tech_pvt->channel, "refer_uuid", switch_core_session_get_uuid(b_tech_pvt->session)); switch_channel_set_variable(b_tech_pvt->channel, "transfer_disposition", "replaced"); br_a = switch_channel_get_partner_uuid(tech_pvt->channel); br_b = switch_channel_get_partner_uuid(b_tech_pvt->channel); if (!switch_ivr_uuid_exists(br_a)) { br_a = NULL; } if (!switch_ivr_uuid_exists(br_b)) { br_b = NULL; } if (switch_channel_test_flag(b_tech_pvt->channel, CF_ORIGINATOR)) { switch_core_session_t *a_session; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Attended Transfer on originating session %s\n", switch_core_session_get_uuid(b_session)); switch_channel_set_variable_printf(b_tech_pvt->channel, "transfer_to", "satt:%s", br_a); switch_channel_set_variable(b_tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "ATTENDED_TRANSFER"); switch_channel_clear_flag(b_tech_pvt->channel, CF_LEG_HOLDING); switch_channel_set_variable(b_tech_pvt->channel, SWITCH_HOLDING_UUID_VARIABLE, br_a); switch_channel_set_flag(b_tech_pvt->channel, CF_XFER_ZOMBIE); switch_channel_set_flag(b_tech_pvt->channel, CF_TRANSFER); if ((a_session = switch_core_session_locate(br_a))) { const char *moh = "local_stream://moh"; switch_channel_t *a_channel = switch_core_session_get_channel(a_session); switch_caller_profile_t *prof = switch_channel_get_caller_profile(b_tech_pvt->channel); const char *tmp; switch_core_event_hook_add_state_change(a_session, xfer_hanguphook); switch_channel_set_variable(a_channel, "att_xfer_kill_uuid", switch_core_session_get_uuid(b_session)); switch_channel_set_variable(a_channel, "att_xfer_destination_number", prof->destination_number); switch_channel_set_variable(a_channel, "att_xfer_callee_id_name", prof->callee_id_name); switch_channel_set_variable(a_channel, "att_xfer_callee_id_number", prof->callee_id_number); if ((tmp = switch_channel_get_hold_music(a_channel))) { moh = tmp; } if (!zstr(moh) && !strcasecmp(moh, "silence")) { moh = NULL; } if (moh) { char *xdest; xdest = switch_core_session_sprintf(a_session, "endless_playback:%s,park", moh); switch_ivr_session_transfer(a_session, xdest, "inline", NULL); } else { switch_ivr_session_transfer(a_session, "park", "inline", NULL); } switch_core_session_rwunlock(a_session); result = SWITCH_TRUE; switch_channel_hangup(b_tech_pvt->channel, SWITCH_CAUSE_NORMAL_CLEARING); } else { result = SWITCH_FALSE; } } else if (br_a && br_b) { switch_core_session_t *tmp = NULL; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Attended Transfer [%s][%s]\n", switch_str_nil(br_a), switch_str_nil(br_b)); if ((tmp = switch_core_session_locate(br_b))) { switch_channel_t *tchannel = switch_core_session_get_channel(tmp); switch_channel_set_variable(tchannel, "transfer_disposition", "bridge"); switch_channel_set_flag(tchannel, CF_ATTENDED_TRANSFER); switch_core_session_rwunlock(tmp); } if (switch_true(switch_channel_get_variable(tech_pvt->channel, "recording_follow_transfer")) && (tmp = switch_core_session_locate(br_a))) { switch_channel_set_variable(switch_core_session_get_channel(tmp), "transfer_disposition", "bridge"); switch_core_media_bug_transfer_recordings(session, tmp); switch_core_session_rwunlock(tmp); } if (switch_true(switch_channel_get_variable(b_tech_pvt->channel, "recording_follow_transfer")) && (tmp = switch_core_session_locate(br_b))) { switch_core_media_bug_transfer_recordings(b_session, tmp); switch_core_session_rwunlock(tmp); } switch_channel_set_variable_printf(tech_pvt->channel, "transfer_to", "att:%s", br_b); mark_transfer_record(session, br_a, br_b); switch_ivr_uuid_bridge(br_a, br_b); switch_channel_set_variable(b_tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "ATTENDED_TRANSFER"); result = SWITCH_TRUE; switch_channel_clear_flag(b_tech_pvt->channel, CF_LEG_HOLDING); switch_channel_set_variable(b_tech_pvt->channel, "park_timeout", "2:attended_transfer"); switch_channel_set_state(b_tech_pvt->channel, CS_PARK); } else { if (!br_a && !br_b) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot transfer channels that are not in a bridge.\n"); result = SWITCH_FALSE; } else { switch_core_session_t *t_session, *hup_session; switch_channel_t *hup_channel; const char *ext; if (br_a && !br_b) { t_session = switch_core_session_locate(br_a); hup_channel = b_tech_pvt->channel; hup_session = b_session; } else { verto_pvt_t *h_tech_pvt = (verto_pvt_t *) switch_core_session_get_private_class(b_session, SWITCH_PVT_SECONDARY); t_session = switch_core_session_locate(br_b); hup_channel = tech_pvt->channel; hup_session = session; switch_channel_clear_flag(h_tech_pvt->channel, CF_LEG_HOLDING); switch_channel_hangup(b_tech_pvt->channel, SWITCH_CAUSE_ATTENDED_TRANSFER); } if (t_session) { //switch_channel_t *t_channel = switch_core_session_get_channel(t_session); const char *idest = switch_channel_get_variable(hup_channel, "inline_destination"); ext = switch_channel_get_variable(hup_channel, "destination_number"); if (switch_true(switch_channel_get_variable(hup_channel, "recording_follow_transfer"))) { switch_core_media_bug_transfer_recordings(hup_session, t_session); } if (idest) { switch_ivr_session_transfer(t_session, idest, "inline", NULL); } else { switch_ivr_session_transfer(t_session, ext, NULL, NULL); } result = SWITCH_TRUE; switch_channel_hangup(hup_channel, SWITCH_CAUSE_ATTENDED_TRANSFER); } else { result = SWITCH_FALSE; } } } if (b_session) { switch_core_session_rwunlock(b_session); } return result; } static switch_bool_t verto__modify_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(); switch_core_session_t *session; cJSON *dialog = NULL; const char *call_id = NULL, *destination = NULL, *action = NULL; int err = 0; *response = obj; if (!(dialog = cJSON_GetObjectItem(params, "dialogParams"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Dialog data missing")); err = 1; goto cleanup; } if (!(call_id = cJSON_GetObjectCstr(dialog, "callID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CallID missing")); err = 1; goto cleanup; } if (!(action = cJSON_GetObjectCstr(params, "action"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("action missing")); err = 1; goto cleanup; } cJSON_AddItemToObject(obj, "callID", cJSON_CreateString(call_id)); cJSON_AddItemToObject(obj, "action", cJSON_CreateString(action)); if ((session = switch_core_session_locate(call_id))) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); if (!strcasecmp(action, "transfer")) { switch_core_session_t *other_session = NULL; if (!(destination = cJSON_GetObjectCstr(params, "destination"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("destination missing")); err = 1; goto rwunlock; } if (switch_core_session_get_partner(tech_pvt->session, &other_session) == SWITCH_STATUS_SUCCESS) { switch_ivr_session_transfer(other_session, destination, NULL, NULL); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL TRANSFERRED")); switch_core_session_rwunlock(other_session); } else { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("call is not bridged")); err = 1; goto rwunlock; } } else if (!strcasecmp(action, "replace")) { const char *replace_call_id; switch_core_session_t *b_session = NULL; if (!(replace_call_id = cJSON_GetObjectCstr(params, "replaceCallID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("replaceCallID missing")); err = 1; goto rwunlock; } if ((b_session = switch_core_session_locate(replace_call_id))) { err = (int) attended_transfer(session, b_session); if (err) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("transfer failed")); } switch_core_session_rwunlock(b_session); } else { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("invalid transfer leg")); err = 1; goto rwunlock; } } else if (!strcasecmp(action, "hold")) { switch_core_media_toggle_hold(session, 1); } else if (!strcasecmp(action, "unhold")) { switch_core_media_toggle_hold(session, 0); } else if (!strcasecmp(action, "toggleHold")) { switch_core_media_toggle_hold(session, !!!switch_channel_test_flag(tech_pvt->channel, CF_PROTO_HOLD)); } cJSON_AddItemToObject(obj, "holdState", cJSON_CreateString(switch_channel_test_flag(tech_pvt->channel, CF_PROTO_HOLD) ? "held" : "active")); rwunlock: switch_core_session_rwunlock(session); } else { err = 1; } cleanup: if (!err) return SWITCH_TRUE; cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL DOES NOT EXIST")); cJSON_AddItemToObject(obj, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); return SWITCH_FALSE; } static switch_bool_t verto__attach_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(); switch_core_session_t *session = NULL; int err = 0; cJSON *dialog; verto_pvt_t *tech_pvt = NULL; const char *call_id = NULL, *sdp = NULL; uint8_t match = 0, p = 0; *response = obj; if (!(dialog = cJSON_GetObjectItem(params, "dialogParams"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Dialog data missing")); err = 1; goto cleanup; } if (!(sdp = cJSON_GetObjectCstr(params, "sdp"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("SDP missing")); err = 1; goto cleanup; } if (!(call_id = cJSON_GetObjectCstr(dialog, "callID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CallID missing")); err = 1; goto cleanup; } if (!(session = switch_core_session_locate(call_id))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Call does not exist")); err = 1; goto cleanup; } tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); tech_pvt->r_sdp = switch_core_session_strdup(session, sdp); switch_channel_set_variable(tech_pvt->channel, SWITCH_R_SDP_VARIABLE, tech_pvt->r_sdp); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote SDP %s:\n%s\n", switch_channel_get_name(tech_pvt->channel), tech_pvt->r_sdp); switch_core_media_clear_ice(tech_pvt->session); switch_channel_set_flag(tech_pvt->channel, CF_REINVITE); //switch_channel_audio_sync(tech_pvt->channel); //switch_channel_set_flag(tech_pvt->channel, CF_VIDEO_BREAK); //switch_core_session_kill_channel(tech_pvt->session, SWITCH_SIG_BREAK); if ((match = switch_core_media_negotiate_sdp(tech_pvt->session, tech_pvt->r_sdp, &p, SDP_TYPE_RESPONSE))) { if (switch_core_media_activate_rtp(tech_pvt->session) != SWITCH_STATUS_SUCCESS) { switch_channel_set_variable(tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "MEDIA ERROR"); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("MEDIA ERROR")); err = 1; goto cleanup; } } else { switch_channel_set_variable(tech_pvt->channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "CODEC NEGOTIATION ERROR"); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CODEC NEGOTIATION ERROR")); err = 1; goto cleanup; } cleanup: if (tech_pvt) { switch_channel_clear_flag(tech_pvt->channel, CF_REINVITE); switch_clear_flag(tech_pvt, TFLAG_ATTACH_REQ); if (switch_channel_test_flag(tech_pvt->channel, CF_CONFERENCE)) { switch_channel_set_flag(tech_pvt->channel, CF_CONFERENCE_ADV); } } if (session) { switch_core_session_rwunlock(session); } if (!err) { return SWITCH_TRUE; } if (tech_pvt && tech_pvt->channel) { switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_BEARERCAPABILITY_NOTAVAIL); } cJSON_AddItemToObject(obj, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); return SWITCH_FALSE; } static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *msg = NULL, *dialog = NULL; const char *call_id = NULL, *dtmf = NULL; switch_bool_t r = SWITCH_TRUE; char *proto = VERTO_CHAT_PROTO; char *pproto = NULL; *response = cJSON_CreateObject(); if ((dialog = cJSON_GetObjectItem(params, "dialogParams")) && (call_id = cJSON_GetObjectCstr(dialog, "callID"))) { switch_core_session_t *session = NULL; if ((session = switch_core_session_locate(call_id))) { if ((dtmf = cJSON_GetObjectCstr(params, "dtmf"))) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); char *send = switch_mprintf("~%s", dtmf); switch_channel_queue_dtmf_string(tech_pvt->channel, send); free(send); cJSON_AddItemToObject(*response, "message", cJSON_CreateString("SENT")); } switch_core_session_rwunlock(session); } } if ((msg = cJSON_GetObjectItem(params, "msg"))) { switch_event_t *event; char *to = (char *) cJSON_GetObjectCstr(msg, "to"); cJSON *indialog = cJSON_GetObjectItem(msg, "inDialog"); const char *body = cJSON_GetObjectCstr(msg, "body"); if (!zstr(to)) { if (strchr(to, '+')) { pproto = strdup(to); if ((to = strchr(pproto, '+'))) { *to++ = '\0'; } proto = pproto; } } if (!zstr(to) && !zstr(body) && switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", VERTO_CHAT_PROTO); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", jsock->uid); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_user", jsock->id); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_host", jsock->domain); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to", to); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "text/plain"); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from_full", jsock->id); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "verto_profile", jsock->profile->name); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "verto_jsock_uuid", jsock->uuid_str); if (indialog && (indialog->type == cJSON_True || (indialog->type == cJSON_String && switch_true(indialog->valuestring))) && call_id) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call_id", call_id); } switch_event_add_body(event, "%s", body); if (strcasecmp(proto, VERTO_CHAT_PROTO)) { switch_core_chat_send(proto, event); } switch_core_chat_send("GLOBAL", event); switch_event_destroy(&event); } else { r = SWITCH_FALSE; cJSON_AddItemToObject(*response, "message", cJSON_CreateString("INVALID MESSAGE to and body params required")); } switch_safe_free(pproto); } return r; } static switch_bool_t verto__invite_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *obj = cJSON_CreateObject(); switch_core_session_t *session = NULL; switch_channel_t *channel; switch_event_t *var_event; switch_call_cause_t reason = SWITCH_CAUSE_INVALID_MSG_UNSPECIFIED, cancel_cause = 0; switch_caller_profile_t *caller_profile; int err = 0; cJSON *dialog; verto_pvt_t *tech_pvt; char name[512]; const char *var, *destination_number, *call_id = NULL, *sdp = NULL, *caller_id_name = NULL, *caller_id_number = NULL, *context = NULL; *response = obj; switch_event_create_plain(&var_event, SWITCH_EVENT_CHANNEL_DATA); if (!(dialog = cJSON_GetObjectItem(params, "dialogParams"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Dialog data missing")); err = 1; goto cleanup; } if (!(call_id = cJSON_GetObjectCstr(dialog, "callID"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CallID missing")); err = 1; goto cleanup; } if (!(sdp = cJSON_GetObjectCstr(params, "sdp"))) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("SDP missing")); err = 1; goto cleanup; } switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "origination_uuid", call_id); if ((reason = switch_core_session_outgoing_channel(NULL, var_event, "rtc", NULL, &session, NULL, SOF_NONE, &cancel_cause)) != SWITCH_CAUSE_SUCCESS) { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Cannot create channel")); err = 1; goto cleanup; } channel = switch_core_session_get_channel(session); switch_channel_set_direction(channel, SWITCH_CALL_DIRECTION_INBOUND); tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt)); tech_pvt->session = session; tech_pvt->channel = channel; tech_pvt->jsock_uuid = switch_core_session_strdup(session, jsock->uuid_str); tech_pvt->r_sdp = switch_core_session_strdup(session, sdp); switch_core_media_set_sdp_codec_string(session, sdp, SDP_TYPE_REQUEST); switch_core_session_set_private_class(session, tech_pvt, SWITCH_PVT_SECONDARY); tech_pvt->call_id = switch_core_session_strdup(session, call_id); if ((tech_pvt->smh = switch_core_session_get_media_handle(session))) { tech_pvt->mparams = switch_core_media_get_mparams(tech_pvt->smh); verto_set_media_options(tech_pvt, jsock->profile); } else { cJSON_AddItemToObject(obj, "message", cJSON_CreateString("Cannot create media handle")); err = 1; goto cleanup; } if (!(destination_number = cJSON_GetObjectCstr(dialog, "destination_number"))) { destination_number = "service"; } switch_snprintf(name, sizeof(name), "verto.rtc/%s", destination_number); switch_channel_set_name(channel, name); switch_channel_set_variable(channel, "jsock_uuid_str", jsock->uuid_str); switch_channel_set_variable(channel, "verto_user", jsock->uid); switch_channel_set_variable(channel, "verto_host", jsock->domain); switch_channel_set_variable(channel, "event_channel_cookie", tech_pvt->jsock_uuid); switch_channel_set_variable(channel, "verto_profile_name", jsock->profile->name); caller_id_name = cJSON_GetObjectCstr(dialog, "caller_id_name"); caller_id_number = cJSON_GetObjectCstr(dialog, "caller_id_number"); if (zstr(caller_id_name)) { if ((var = switch_event_get_header(jsock->params, "caller-id-name"))) { caller_id_name = var; } } if (zstr(caller_id_number)) { if ((var = switch_event_get_header(jsock->params, "caller-id-number"))) { caller_id_number = var; } } if (!(context = switch_event_get_header(jsock->vars, "user_context"))) { context = switch_either(jsock->context, jsock->profile->context); } if ((caller_profile = switch_caller_profile_new(switch_core_session_get_pool(session), jsock->uid, switch_either(jsock->dialplan, jsock->profile->dialplan), caller_id_name, caller_id_number, inet_ntoa(jsock->remote_addr.sin_addr), cJSON_GetObjectCstr(dialog, "ani"), cJSON_GetObjectCstr(dialog, "aniii"), cJSON_GetObjectCstr(dialog, "rdnis"), modname, context, destination_number))) { switch_channel_set_caller_profile(channel, caller_profile); } switch_channel_set_variable(channel, SWITCH_R_SDP_VARIABLE, sdp); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote SDP %s:\n%s\n", switch_channel_get_name(tech_pvt->channel), sdp); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL CREATED")); cJSON_AddItemToObject(obj, "callID", cJSON_CreateString(tech_pvt->call_id)); switch_channel_add_state_handler(channel, &verto_state_handlers); switch_core_event_hook_add_receive_message(session, messagehook); switch_channel_set_state(channel, CS_INIT); track_pvt(tech_pvt); switch_core_session_thread_launch(session); cleanup: switch_event_destroy(&var_event); if (!err) { return SWITCH_TRUE; } if (session) { switch_core_session_destroy(&session); } cJSON_AddItemToObject(obj, "causeCode", cJSON_CreateNumber(reason)); cJSON_AddItemToObject(obj, "cause", cJSON_CreateString(switch_channel_cause2str(reason))); cJSON_AddItemToObject(obj, "message", cJSON_CreateString("CALL ERROR")); cJSON_AddItemToObject(obj, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); return SWITCH_FALSE; } static switch_bool_t event_channel_check_auth(jsock_t *jsock, const char *event_channel) { char *main_event_channel = NULL; switch_bool_t ok = SWITCH_TRUE, pre_ok = SWITCH_FALSE; switch_core_session_t *session = NULL; switch_assert(event_channel); pre_ok = switch_event_channel_permission_verify(jsock->uuid_str, event_channel); if (!pre_ok && (session = switch_core_session_locate(event_channel))) { switch_channel_t *channel = switch_core_session_get_channel(session); const char *jsock_uuid_str = switch_channel_get_variable(channel, "jsock_uuid_str"); if (jsock_uuid_str && !strcmp(jsock_uuid_str, jsock->uuid_str)) { pre_ok = SWITCH_TRUE; } switch_core_session_rwunlock(session); } if (pre_ok) { return pre_ok; } if (jsock->allowed_event_channels) { if (strchr(event_channel, '.')) { char *p; main_event_channel = strdup(event_channel); if ((p = strchr(main_event_channel, '.'))) { *p = '\0'; } } if ((!globals.enable_fs_events && (!strcasecmp(event_channel, "FSevent") || (main_event_channel && !strcasecmp(main_event_channel, "FSevent")))) || !(switch_event_get_header(jsock->allowed_event_channels, event_channel) || (main_event_channel && switch_event_get_header(jsock->allowed_event_channels, main_event_channel)))) { ok = SWITCH_FALSE; } } switch_safe_free(main_event_channel); return ok; } static switch_bool_t parse_subs(jsock_t *jsock, const char *event_channel, cJSON **sub_list, cJSON **err_list, cJSON **exist_list) { switch_bool_t r = SWITCH_FALSE; if (event_channel_check_auth(jsock, event_channel)) { if (!*sub_list) { *sub_list = cJSON_CreateArray(); } if (jsock_sub_channel(jsock, event_channel) == SWITCH_STATUS_SUCCESS) { cJSON_AddItemToArray(*sub_list, cJSON_CreateString(event_channel)); } else { if (!*exist_list) { *exist_list = cJSON_CreateArray(); } cJSON_AddItemToArray(*exist_list, cJSON_CreateString(event_channel)); } r = SWITCH_TRUE; } else { if (!*err_list) { *err_list = cJSON_CreateArray(); } cJSON_AddItemToArray(*err_list, cJSON_CreateString(event_channel)); } return r; } static switch_bool_t verto__subscribe_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { switch_bool_t r = SWITCH_TRUE; cJSON *subs = NULL, *errs = NULL, *exist = NULL; *response = cJSON_CreateObject(); if (params) { cJSON *jchannel = cJSON_GetObjectItem(params, "eventChannel"); if (jchannel) { if (jchannel->type == cJSON_String) { parse_subs(jsock, jchannel->valuestring, &subs, &errs, &exist); } else if (jchannel->type == cJSON_Array) { int i, len = cJSON_GetArraySize(jchannel); for(i = 0; i < len; i++) { cJSON *str = cJSON_GetArrayItem(jchannel, i); if (str->type == cJSON_String) { parse_subs(jsock, str->valuestring, &subs, &errs, &exist); } } } } } if (subs) { cJSON_AddItemToObject(*response, "subscribedChannels", subs); } if (errs) { cJSON_AddItemToObject(*response, "unauthorizedChannels", errs); } if (exist) { cJSON_AddItemToObject(*response, "alreadySubscribedChannels", exist); } if (!subs) { r = SWITCH_FALSE; } return r; } static void do_unsub(jsock_t *jsock, const char *event_channel, cJSON **subs, cJSON **errs) { if (jsock_unsub_channel(jsock, event_channel)) { if (!*subs) { *subs = cJSON_CreateArray(); } cJSON_AddItemToArray(*subs, cJSON_CreateString(event_channel)); } else { if (!*errs) { *errs = cJSON_CreateArray(); } cJSON_AddItemToArray(*errs, cJSON_CreateString(event_channel)); } } static switch_bool_t verto__unsubscribe_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { switch_bool_t r = SWITCH_TRUE; cJSON *subs = NULL, *errs = NULL; *response = cJSON_CreateObject(); if (params) { cJSON *jchannel = cJSON_GetObjectItem(params, "eventChannel"); if (jchannel) { if (jchannel->type == cJSON_String) { do_unsub(jsock, jchannel->valuestring, &subs, &errs); } else if (jchannel->type == cJSON_Array) { int i, len = cJSON_GetArraySize(jchannel); for(i = 0; i < len; i++) { cJSON *str = cJSON_GetArrayItem(jchannel, i); if (str->type == cJSON_String) { do_unsub(jsock, str->valuestring, &subs, &errs); } } } } } if (subs) { cJSON_AddItemToObject(*response, "unsubscribedChannels", subs); } if (errs) { cJSON_AddItemToObject(*response, "notSubscribedChannels", errs); } if (errs && !subs) { r = SWITCH_FALSE; } return r; } static switch_bool_t verto__broadcast_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { char *json_text = NULL; switch_bool_t r = SWITCH_FALSE; const char *event_channel = cJSON_GetObjectCstr(params, "eventChannel"); cJSON *jevent; *response = cJSON_CreateObject(); if (!event_channel) { cJSON_AddItemToObject(*response, "message", cJSON_CreateString("eventChannel not specified.")); cJSON_AddItemToObject(*response, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); goto end; } if (!event_channel_check_auth(jsock, event_channel)) { cJSON_AddItemToObject(*response, "message", cJSON_CreateString("Permission Denied.")); cJSON_AddItemToObject(*response, "code", cJSON_CreateNumber(CODE_SESSION_ERROR)); goto end; } cJSON_AddItemToObject(params, "userid", cJSON_CreateString(jsock->uid)); jevent = cJSON_Duplicate(params, 1); switch_event_channel_broadcast(event_channel, &jevent, modname, globals.event_channel_id); if (jsock->profile->mcast_pub.sock > -1) { if ((json_text = cJSON_PrintUnformatted(params))) { if ( mcast_socket_send(&jsock->profile->mcast_pub, json_text, strlen(json_text) + 1) < 0 ) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "multicast socket send error!\n"); } free(json_text); json_text = NULL; r = SWITCH_TRUE; cJSON_AddItemToObject(*response, "message", cJSON_CreateString("MCAST Data Sent")); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "JSON ERROR!\n"); } } end: return r; } static switch_bool_t login_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { *response = cJSON_CreateObject(); cJSON_AddItemToObject(*response, "message", cJSON_CreateString("logged in")); return SWITCH_TRUE; } static switch_bool_t echo_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { *response = cJSON_Duplicate(params, 1); return SWITCH_TRUE; } static switch_bool_t jsapi_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { if (jsock->allowed_jsapi) { const char *function; if (params) { if ((function = cJSON_GetObjectCstr(params, "command"))) { if (!switch_event_get_header(jsock->allowed_jsapi, function)) { return SWITCH_FALSE; } if (jsock->allowed_fsapi && !strcmp(function, "fsapi")) { cJSON *cmd = cJSON_GetObjectItem(params, "cmd"); cJSON *arg = cJSON_GetObjectItem(params, "arg"); if (cmd->type == cJSON_String && cmd->valuestring && !auth_api_command(jsock, cmd->valuestring, arg ? arg->valuestring : NULL)) { return SWITCH_FALSE; } } } } } switch_json_api_execute(params, NULL, response); return *response ? SWITCH_TRUE : SWITCH_FALSE; } static switch_bool_t fsapi_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response) { cJSON *cmd, *arg, *reply; switch_stream_handle_t stream = { 0 }; switch_status_t status = SWITCH_STATUS_SUCCESS; cmd = cJSON_GetObjectItem(params, "cmd"); arg = cJSON_GetObjectItem(params, "arg"); if (jsock->allowed_fsapi) { if (cmd->type == cJSON_String && cmd->valuestring && !auth_api_command(jsock, cmd->valuestring, arg ? arg->valuestring : NULL)) { return SWITCH_FALSE; } } if (cmd && !cmd->valuestring) { cmd = NULL; } if (arg && !arg->valuestring) { arg = NULL; } reply = cJSON_CreateObject(); SWITCH_STANDARD_STREAM(stream); if (cmd && (status = switch_api_execute(cmd->valuestring, arg ? arg->valuestring : NULL, NULL, &stream)) == SWITCH_STATUS_SUCCESS) { cJSON_AddItemToObject(reply, "message", cJSON_CreateString((char *) stream.data)); } else { cJSON_AddItemToObject(reply, "message", cJSON_CreateString("INVALID CALL")); } switch_safe_free(stream.data); if (reply) { *response = reply; return SWITCH_TRUE; } return SWITCH_FALSE; } //// static void jrpc_init(void) { jrpc_add_func("echo", echo_func); jrpc_add_func("jsapi", jsapi_func); jrpc_add_func("fsapi", fsapi_func); jrpc_add_func("login", login_func); jrpc_add_func("verto.invite", verto__invite_func); jrpc_add_func("verto.info", verto__info_func); jrpc_add_func("verto.attach", verto__attach_func); jrpc_add_func("verto.bye", verto__bye_func); jrpc_add_func("verto.answer", verto__answer_func); jrpc_add_func("verto.subscribe", verto__subscribe_func); jrpc_add_func("verto.unsubscribe", verto__unsubscribe_func); jrpc_add_func("verto.broadcast", verto__broadcast_func); jrpc_add_func("verto.modify", verto__modify_func); } static int start_jsock(verto_profile_t *profile, int sock) { jsock_t *jsock = NULL; int flag = 1; int i; unsigned int len; jsock_type_t ptype = PTYPE_CLIENT; switch_thread_data_t *td; switch_memory_pool_t *pool; switch_core_new_memory_pool(&pool); jsock = (jsock_t *) switch_core_alloc(pool, sizeof(*jsock)); jsock->pool = pool; len = sizeof(jsock->remote_addr); if ((jsock->client_socket = accept(sock, (struct sockaddr *) &jsock->remote_addr, &len)) < 0) { die("ACCEPT FAILED\n"); } for (i = 0; i < profile->i; i++) { if ( profile->server_socket[i] == sock ) { if (profile->ip[i].secure) { ptype = PTYPE_CLIENT_SSL; } break; } } jsock->local_sock = sock; jsock->profile = profile; if (zstr(jsock->name)) { jsock->name = switch_core_sprintf(pool, "%s:%d", inet_ntoa(jsock->remote_addr.sin_addr), ntohs(jsock->remote_addr.sin_port)); } jsock->ptype = ptype; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Client Connect.\n", jsock->name); /* no nagle please */ setsockopt(jsock->client_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); td = switch_core_alloc(jsock->pool, sizeof(*td)); td->alloc = 0; td->func = client_thread; td->obj = jsock; td->pool = pool; switch_mutex_init(&jsock->write_mutex, SWITCH_MUTEX_NESTED, jsock->pool); switch_mutex_init(&jsock->filter_mutex, SWITCH_MUTEX_NESTED, jsock->pool); switch_queue_create(&jsock->event_queue, MAX_QUEUE_LEN, jsock->pool); switch_thread_rwlock_create(&jsock->rwlock, jsock->pool); switch_thread_pool_launch_thread(&td); return 0; error: if (jsock) { if (jsock->client_socket > -1) { close_socket(&jsock->client_socket); } switch_core_destroy_memory_pool(&pool); } return -1; } static int prepare_socket(int ip, int port) { int sock = -1; int reuse_addr = 1; struct sockaddr_in addr; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { die("Socket Error!\n"); } if ( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr)) < 0 ) { die("Socket setsockopt Error!\n"); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = ip; addr.sin_port = htons(port); if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { die("Bind Error!\n"); return -1; } if (listen(sock, MAXPENDING) < 0) { die("Listen error\n"); return -1; } return sock; error: close_file(&sock); return -1; } static void handle_mcast_sub(verto_profile_t *profile) { int bytes = mcast_socket_recv(&profile->mcast_sub, NULL, 0, 0); if (bytes > 0) { cJSON *json; profile->mcast_sub.buffer[bytes] = '\0'; if ((json = cJSON_Parse((char *)profile->mcast_sub.buffer))) { jsock_send_event(json); cJSON_Delete(json); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MCAST JSON PARSE ERR: %s\n", (char *)profile->mcast_sub.buffer); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MCAST INVALID READ %d\n", bytes); } } static int runtime(verto_profile_t *profile) { int max = 2; int i; for (i = 0; i < profile->i; i++) { if ((profile->server_socket[i] = prepare_socket(profile->ip[i].local_ip_addr, profile->ip[i].local_port)) < 0) { die("Client Socket Error!\n"); } } if (profile->mcast_ip) { if (mcast_socket_create(profile->mcast_ip, profile->mcast_port, &profile->mcast_sub, MCAST_RECV | MCAST_TTL_HOST) < 0) { die("mcast recv socket create"); } if (mcast_socket_create(profile->mcast_ip, profile->mcast_port + 1, &profile->mcast_pub, MCAST_SEND | MCAST_TTL_HOST) > 0) { mcast_socket_close(&profile->mcast_sub); die("mcast send socket create"); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "MCAST Bound to %s:%d/%d\n", profile->mcast_ip, profile->mcast_port, profile->mcast_port + 1); } while(profile->running) { struct pollfd pfds[MAX_BIND+4]; int res, x = 0; int i = 0; memset(&pfds[0], 0, sizeof(pfds[0]) * MAX_BIND+2); for (i = 0; i < profile->i; i++) { pfds[i].fd = profile->server_socket[i]; pfds[i].events = POLLIN|POLLERR; } if (profile->mcast_ip) { pfds[i].fd = profile->mcast_sub.sock; pfds[i++].events = POLLIN|POLLERR; } max = i; if ((res = poll(pfds, max, 1000)) < 0) { if (errno != EINTR) { die("POLL FAILED\n"); } } if (res == 0) { continue; } for (x = 0; x < max; x++) { if (pfds[x].revents & POLLERR) { die("POLL ERROR\n"); } if (pfds[x].revents & POLLHUP) { die("POLL HUP\n"); } if (pfds[x].revents & POLLIN) { if (pfds[x].fd == profile->mcast_sub.sock) { handle_mcast_sub(profile); } else { start_jsock(profile, pfds[x].fd); } } } } if (profile->mcast_sub.sock > -1) { mcast_socket_close(&profile->mcast_sub); } if (profile->mcast_pub.sock > -1) { mcast_socket_close(&profile->mcast_pub); } return 0; error: return -1; } static void kill_profile(verto_profile_t *profile) { jsock_t *p; int i; profile->running = 0; //if (switch_thread_rwlock_tryrdlock(profile->rwlock) != SWITCH_STATUS_SUCCESS) { // return; //} switch_mutex_lock(profile->mutex); for (i = 0; i < profile->i; i++) { close_socket(&profile->server_socket[i]); } for(p = profile->jsock_head; p; p = p->next) { close_socket(&p->client_socket); } switch_mutex_unlock(profile->mutex); //switch_thread_rwlock_unlock(profile->rwlock); } static void kill_profiles(void) { verto_profile_t *pp; int sanity = 50; switch_mutex_lock(globals.mutex); for(pp = globals.profile_head; pp; pp = pp->next) { kill_profile(pp); } switch_mutex_unlock(globals.mutex); while(--sanity > 0 && globals.profile_threads > 0) { usleep(100000); } } static void do_shutdown(void) { globals.running = 0; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Shutting down (SIG %d)\n", globals.sig); kill_profiles(); unsub_all_jsock(); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Done\n"); } static void parse_ip(char *host, int *port, in_addr_t *addr, char *input) { char *p; struct hostent *hent; strncpy(host, input, 255); if ((p = strchr(host, ':')) != NULL) { *p++ = '\0'; *port = atoi(p); } if ( host[0] < '0' || host[0] > '9' ) { // Non-numeric host (at least it doesn't start with one). Convert it to ip addr first if ((hent = gethostbyname(host)) != NULL) { if (hent->h_addrtype == AF_INET) { memcpy(addr, hent->h_addr_list[0], 4); } } } else { *addr = inet_addr(host); } } static verto_profile_t *find_profile(const char *name) { verto_profile_t *p, *r = NULL; switch_mutex_lock(globals.mutex); for(p = globals.profile_head; p; p = p->next) { if (!strcmp(name, p->name)) { r = p; break; } } if (r && (!r->in_thread || !r->running)) { r = NULL; } if (r && switch_thread_rwlock_tryrdlock(r->rwlock) != SWITCH_STATUS_SUCCESS) { r = NULL; } switch_mutex_unlock(globals.mutex); return r; } static switch_bool_t profile_exists(const char *name) { switch_bool_t r = SWITCH_FALSE; verto_profile_t *p; switch_mutex_lock(globals.mutex); for(p = globals.profile_head; p; p = p->next) { if (!strcmp(p->name, name)) { r = SWITCH_TRUE; break; } } switch_mutex_unlock(globals.mutex); return r; } static void del_profile(verto_profile_t *profile) { verto_profile_t *p, *last = NULL; switch_mutex_lock(globals.mutex); for(p = globals.profile_head; p; p = p->next) { if (p == profile) { if (last) { last->next = p->next; } else { globals.profile_head = p->next; } globals.profile_count--; break; } last = p; } switch_mutex_unlock(globals.mutex); } static switch_status_t add_profile(verto_profile_t *profile) { switch_status_t status = SWITCH_STATUS_FALSE; switch_mutex_lock(globals.mutex); if (!profile_exists(profile->name)) { status = SWITCH_STATUS_SUCCESS; } if (status == SWITCH_STATUS_SUCCESS) { profile->next = globals.profile_head; globals.profile_head = profile; globals.profile_count++; } switch_mutex_unlock(globals.mutex); return status; } static switch_status_t parse_config(const char *cf) { switch_xml_t cfg, xml, settings, param, xprofile, xprofiles; switch_status_t status = SWITCH_STATUS_SUCCESS; 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 ((xprofiles = switch_xml_child(cfg, "profiles"))) { for (xprofile = switch_xml_child(xprofiles, "profile"); xprofile; xprofile = xprofile->next) { verto_profile_t *profile; switch_memory_pool_t *pool; const char *name = switch_xml_attr(xprofile, "name"); if (zstr(name)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Required field name missing\n"); continue; } if (profile_exists(name)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile %s already exists\n", name); continue; } switch_core_new_memory_pool(&pool); profile = switch_core_alloc(pool, sizeof(*profile)); profile->pool = pool; profile->name = switch_core_strdup(profile->pool, name); switch_mutex_init(&profile->mutex, SWITCH_MUTEX_NESTED, profile->pool); switch_thread_rwlock_create(&profile->rwlock, profile->pool); add_profile(profile); profile->local_network = "localnet.auto"; for (param = switch_xml_child(xprofile, "param"); param; param = param->next) { char *var = NULL; char *val = NULL; int i = 0; var = (char *) switch_xml_attr_soft(param, "name"); val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "bind-local")) { const char *secure = switch_xml_attr_soft(param, "secure"); if (i < MAX_BIND) { parse_ip(profile->ip[profile->i].local_ip, &profile->ip[profile->i].local_port, &profile->ip[profile->i].local_ip_addr, val); if (switch_true(secure)) { profile->ip[profile->i].secure = 1; } profile->i++; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Max Bindings Reached!\n"); } } else if (!strcasecmp(var, "secure-combined")) { set_string(profile->cert, val); set_string(profile->key, val); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Secure key and cert specified\n"); } else if (!strcasecmp(var, "secure-cert")) { set_string(profile->cert, val); } else if (!strcasecmp(var, "secure-key")) { set_string(profile->key, val); } else if (!strcasecmp(var, "secure-chain")) { set_string(profile->chain, val); } else if (!strcasecmp(var, "inbound-codec-string") && !zstr(val)) { profile->inbound_codec_string = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "outbound-codec-string") && !zstr(val)) { profile->outbound_codec_string = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "userauth") && !zstr(val)) { profile->userauth = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "root-password") && !zstr(val)) { profile->root_passwd = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "context") && !zstr(val)) { profile->context = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "dialplan") && !zstr(val)) { profile->dialplan = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "mcast-ip") && val) { profile->mcast_ip = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "mcast-port") && val) { profile->mcast_port = (switch_port_t) atoi(val); } else if (!strcasecmp(var, "timer-name") && !zstr(var)) { profile->timer_name = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "local-network") && !zstr(val)) { profile->local_network = switch_core_strdup(profile->pool, val); } else if (!strcasecmp(var, "apply-candidate-acl")) { if (profile->cand_acl_count < SWITCH_MAX_CAND_ACL) { profile->cand_acl[profile->cand_acl_count++] = switch_core_strdup(profile->pool, val); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Max acl records of %d reached\n", SWITCH_MAX_CAND_ACL); } } else if (!strcasecmp(var, "rtp-ip")) { if (zstr(val)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid RTP IP.\n"); } else { if (profile->rtpip_index < MAX_RTPIP -1) { profile->rtpip[profile->rtpip_index++] = switch_core_strdup(profile->pool, val); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Too many RTP IP.\n"); } } } else if (!strcasecmp(var, "ext-rtp-ip")) { if (zstr(val)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid External RTP IP.\n"); } else { profile->extrtpip = switch_core_strdup(profile->pool, val); } } else if (!strcasecmp(var, "debug")) { if (val) { profile->debug = atoi(val); } } } if (zstr(profile->outbound_codec_string)) { profile->outbound_codec_string = "opus,vp8"; } if (zstr(profile->inbound_codec_string)) { profile->inbound_codec_string = profile->outbound_codec_string; } if (zstr(profile->timer_name)) { profile->timer_name = "soft"; } if (zstr(profile->dialplan)) { profile->dialplan = "XML"; } if (zstr(profile->context)) { profile->context = "default"; } if (zstr(profile->ip[0].local_ip) ) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: local_ip bad\n", profile->name); if (profile->ip[0].local_port <= 0 ) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: local_port bad\n", profile->name); if (zstr(profile->ip[0].local_ip) || profile->ip[0].local_port <= 0) { del_profile(profile); switch_core_destroy_memory_pool(&pool); } else { int i; for (i = 0; i < profile->i; i++) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Bound to %s:%d\n", profile->name, profile->ip[i].local_ip, profile->ip[i].local_port); } } } } if ((settings = switch_xml_child(cfg, "settings"))) { for (param = switch_xml_child(settings, "param"); param; param = param->next) { char *var = NULL; char *val = NULL; var = (char *) switch_xml_attr_soft(param, "name"); val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "debug")) { if (val) { globals.debug = atoi(val); } } else if (!strcasecmp(var, "enable-presence") && val) { globals.enable_presence = switch_true(val); } else if (!strcasecmp(var, "enable-fs-events") && val) { globals.enable_fs_events = switch_true(val); } else if (!strcasecmp(var, "detach-timeout-sec") && val) { int tmp = atoi(val); if (tmp > 0) { globals.detach_timeout = tmp; } } } } switch_xml_free(xml); return status; } static int init(void) { verto_profile_t *p; parse_config("verto.conf"); switch_mutex_lock(globals.mutex); for(p = globals.profile_head; p; p = p->next) { verto_init_ssl(p); } switch_mutex_unlock(globals.mutex); globals.running = 1; return 0; } #if 0 static void print_status(verto_profile_t *profile, switch_stream_handle_t *stream) { jsock_t *p; stream->write_function(stream, "REMOTE\t\t\tLOCAL\n"); for(p = profile->jsock_head; p; p = p->next) { if (p->ptype & PTYPE_CLIENT) { int i; for (i = 0; i < profile->i; i++) { if (profile->server_socket[i] == p->local_sock) { stream->write_function(stream, "%s\t%s:%d\n", p->name, profile->ip[i].local_ip, profile->ip[i].local_port); } } } } } #endif SWITCH_STANDARD_API(verto_function) { int argc = 0; char *argv[5] = { 0 }; char *mydata = NULL; if (cmd) { mydata = strdup(cmd); argc = switch_split(mydata, ' ', argv); } if (argc > 0) { if (!strcasecmp(argv[0], "connections")) { //print_status(profile, stream); } } switch_safe_free(mydata); return SWITCH_STATUS_SUCCESS; } static void *SWITCH_THREAD_FUNC profile_thread(switch_thread_t *thread, void *obj) { verto_profile_t *profile = (verto_profile_t *) obj; int sanity = 50; switch_mutex_lock(globals.mutex); globals.profile_threads++; switch_mutex_unlock(globals.mutex); profile->in_thread = 1; profile->running = 1; runtime(profile); profile->running = 0; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "profile %s shutdown, Waiting for %d threads\n", profile->name, profile->jsock_count); while(--sanity > 0 && profile->jsock_count > 0) { usleep(100000); } verto_deinit_ssl(profile); del_profile(profile); switch_thread_rwlock_wrlock(profile->rwlock); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s Thread ending\n", profile->name); switch_thread_rwlock_unlock(profile->rwlock); profile->in_thread = 0; switch_mutex_lock(globals.mutex); globals.profile_threads--; switch_mutex_unlock(globals.mutex); return NULL; } static void run_profile_thread(verto_profile_t *profile) { switch_thread_data_t *td; td = switch_core_alloc(profile->pool, sizeof(*td)); td->alloc = 0; td->func = profile_thread; td->obj = profile; td->pool = profile->pool; switch_thread_pool_launch_thread(&td); } static void run_profiles(void) { verto_profile_t *p; switch_mutex_lock(globals.mutex); for(p = globals.profile_head; p; p = p->next) { if (!p->in_thread) { run_profile_thread(p); } } switch_mutex_unlock(globals.mutex); } //// ENDPOINT switch_endpoint_interface_t *verto_endpoint_interface; static switch_call_cause_t verto_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, switch_caller_profile_t *outbound_profile, switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags, switch_call_cause_t *cancel_cause); switch_io_routines_t verto_io_routines = { /*.outgoing_channel */ verto_outgoing_channel }; static char *verto_get_dial_string(const char *uid, switch_stream_handle_t *rstream) { jsock_t *jsock; verto_profile_t *profile; switch_stream_handle_t *use_stream = NULL, stream = { 0 }; char *gen_uid = NULL; int hits = 0; if (!strchr(uid, '@')) { gen_uid = switch_mprintf("%s@%s", uid, switch_core_get_domain(SWITCH_FALSE)); uid = gen_uid; } if (rstream) { use_stream = rstream; } else { SWITCH_STANDARD_STREAM(stream); use_stream = &stream; } switch_mutex_lock(globals.mutex); for(profile = globals.profile_head; profile; profile = profile->next) { switch_mutex_lock(profile->mutex); for(jsock = profile->jsock_head; jsock; jsock = jsock->next) { if (!zstr(jsock->uid) && !zstr(uid) && !strcmp(uid, jsock->uid)) { use_stream->write_function(use_stream, "%s/u:%s,", EP_NAME, jsock->uuid_str); hits++; } } switch_mutex_unlock(profile->mutex); } switch_mutex_unlock(globals.mutex); switch_safe_free(gen_uid); if (!hits) { use_stream->write_function(use_stream, "error/user_not_registered"); } if (use_stream->data) { char *p = use_stream->data; if (end_of(p) == ',') { end_of(p) = '\0'; } } return use_stream->data; } SWITCH_STANDARD_API(verto_contact_function) { char *uid = (char *) cmd; if (!zstr(uid)) { verto_get_dial_string(uid, stream); } return SWITCH_STATUS_SUCCESS; } static switch_call_cause_t verto_outgoing_channel(switch_core_session_t *session, switch_event_t *var_event, switch_caller_profile_t *outbound_profile, switch_core_session_t **new_session, switch_memory_pool_t **pool, switch_originate_flag_t flags, switch_call_cause_t *cancel_cause) { switch_call_cause_t cause = SWITCH_CAUSE_CHANNEL_UNACCEPTABLE; char *dest = NULL; if (!zstr(outbound_profile->destination_number)) { dest = strdup(outbound_profile->destination_number); } if (zstr(dest)) { goto end; } if (!switch_stristr("u:", dest)) { char *dial_str = verto_get_dial_string(dest, NULL); cause = SWITCH_CAUSE_USER_NOT_REGISTERED; if (dial_str) { switch_originate_flag_t myflags = SOF_NONE; if ((flags & SOF_NO_LIMITS)) { myflags |= SOF_NO_LIMITS; } if ((flags & SOF_FORKED_DIAL)) { myflags |= SOF_NOBLOCK; } if (switch_ivr_originate(session, new_session, &cause, dial_str, 0, NULL, NULL, NULL, outbound_profile, var_event, myflags, cancel_cause) == SWITCH_STATUS_SUCCESS) { switch_core_session_rwunlock(*new_session); } free(dial_str); } return cause; } if ((cause = switch_core_session_outgoing_channel(session, var_event, "rtc", outbound_profile, new_session, NULL, SOF_NONE, cancel_cause)) == SWITCH_CAUSE_SUCCESS) { switch_channel_t *channel = switch_core_session_get_channel(*new_session); char *jsock_uuid_str = outbound_profile->destination_number + 2; switch_caller_profile_t *caller_profile; verto_pvt_t *tech_pvt = NULL; char name[512]; tech_pvt = switch_core_session_alloc(*new_session, sizeof(*tech_pvt)); tech_pvt->session = *new_session; tech_pvt->channel = channel; tech_pvt->jsock_uuid = switch_core_session_strdup(*new_session, jsock_uuid_str); switch_core_session_set_private_class(*new_session, tech_pvt, SWITCH_PVT_SECONDARY); if (session) { switch_channel_t *ochannel = switch_core_session_get_channel(session); const char *ep_codec; if (switch_true(switch_channel_get_variable(ochannel, SWITCH_BYPASS_MEDIA_VARIABLE))) { switch_channel_set_flag(channel, CF_PROXY_MODE); switch_channel_set_flag(ochannel, CF_PROXY_MODE); switch_channel_set_cap(channel, CC_BYPASS_MEDIA); } if ((ep_codec = switch_channel_get_variable(ochannel, "ep_codec_string"))) { switch_channel_set_variable(tech_pvt->channel, "codec_string", ep_codec); } } tech_pvt->call_id = switch_core_session_strdup(*new_session, switch_core_session_get_uuid(*new_session)); if ((tech_pvt->smh = switch_core_session_get_media_handle(*new_session))) { tech_pvt->mparams = switch_core_media_get_mparams(tech_pvt->smh); } switch_snprintf(name, sizeof(name), "verto.rtc/%s", tech_pvt->jsock_uuid); switch_channel_set_name(channel, name); switch_channel_set_variable(channel, "jsock_uuid_str", tech_pvt->jsock_uuid); switch_channel_set_variable(channel, "event_channel_cookie", tech_pvt->jsock_uuid); if ((caller_profile = switch_caller_profile_dup(switch_core_session_get_pool(*new_session), outbound_profile))) { switch_channel_set_caller_profile(channel, caller_profile); } switch_channel_add_state_handler(channel, &verto_state_handlers); switch_core_event_hook_add_receive_message(*new_session, messagehook); switch_channel_set_state(channel, CS_INIT); track_pvt(tech_pvt); } end: switch_safe_free(dest); return cause; } void verto_broadcast(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id) { if (globals.debug > 9) { char *json_text; if ((json_text = cJSON_Print(json))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "EVENT BROADCAST %s %s\n", event_channel, json_text); free(json_text); } } jsock_send_event(json); } static int verto_send_chat(const char *uid, const char *call_id, cJSON *msg) { jsock_t *jsock; verto_profile_t *profile; int hits = 0; int done = 0; if (!strchr(uid, '@')) { return 0; } if (call_id) { switch_core_session_t *session; if ((session = switch_core_session_locate(call_id))) { verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY); jsock_t *jsock; if ((jsock = get_jsock(tech_pvt->jsock_uuid))) { jsock_queue_event(jsock, &msg); //if (ws_write_json(jsock, &msg, SWITCH_FALSE) <= 0) { // switch_channel_hangup(switch_core_session_get_channel(session), SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); //} switch_thread_rwlock_unlock(jsock->rwlock); done = 1; } switch_core_session_rwunlock(session); } } if (done) { return 1; } switch_mutex_lock(globals.mutex); for(profile = globals.profile_head; profile; profile = profile->next) { switch_mutex_lock(profile->mutex); for(jsock = profile->jsock_head; jsock; jsock = jsock->next) { if (!strcmp(uid, jsock->uid)) { //ws_write_json(jsock, &msg, SWITCH_FALSE); jsock_queue_event(jsock, &msg); hits++; } } switch_mutex_unlock(profile->mutex); } switch_mutex_unlock(globals.mutex); return hits; } static switch_status_t chat_send(switch_event_t *message_event) { switch_status_t status = SWITCH_STATUS_SUCCESS; const char *to = switch_event_get_header(message_event, "to"); const char *from = switch_event_get_header(message_event, "from"); const char *body = switch_event_get_body(message_event); const char *call_id = switch_event_get_header(message_event, "call_id"); DUMP_EVENT(message_event); if (!zstr(to) && !zstr(body) && !zstr(from)) { cJSON *obj = NULL, *msg = NULL, *params = NULL; obj = jrpc_new_req("verto.info", call_id, ¶ms); msg = json_add_child_obj(params, "msg", NULL); cJSON_AddItemToObject(msg, "from", cJSON_CreateString(from)); cJSON_AddItemToObject(msg, "to", cJSON_CreateString(to)); cJSON_AddItemToObject(msg, "body", cJSON_CreateString(body)); verto_send_chat(to, call_id, obj); cJSON_Delete(obj); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "INVALID EVENT\n"); status = SWITCH_STATUS_FALSE; } return status; } static switch_cache_db_handle_t *json_get_db_handle(void) { switch_cache_db_handle_t *dbh = NULL; const char *dsn; if (!(dsn = switch_core_get_variable("json_db_handle"))) { dsn = "json"; } if (switch_cache_db_get_db_handle_dsn(&dbh, dsn) != SWITCH_STATUS_SUCCESS) { dbh = NULL; } return dbh; } static int jcallback(void *pArg, int argc, char **argv, char **columnNames) { char **data = (char **) pArg; if (argv[0] && !*data) { *data = strdup(argv[0]); } return 0; } static cJSON *json_retrieve(const char *name, switch_mutex_t *mutex) { char *sql, *errmsg; switch_cache_db_handle_t *dbh; char *ascii = NULL; cJSON *json = NULL; if (!check_name(name)) { return NULL; } sql = switch_mprintf("select data from json_store where name='%q'", name); dbh = json_get_db_handle(); if (mutex) switch_mutex_lock(mutex); switch_cache_db_execute_sql_callback(dbh, sql, jcallback, &ascii, &errmsg); switch_cache_db_release_db_handle(&dbh); if (errmsg) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); free(errmsg); } else { if (ascii) { json = cJSON_Parse(ascii); } } if (mutex) switch_mutex_unlock(mutex); switch_safe_free(ascii); return json; } static switch_bool_t json_commit(cJSON *json, const char *name, switch_mutex_t *mutex) { char *ascii; char *sql; char del_sql[128] = ""; switch_cache_db_handle_t *dbh; char *err; if (!check_name(name)) { return SWITCH_FALSE; } if (!(ascii = cJSON_PrintUnformatted(json))) { return SWITCH_FALSE; } sql = switch_mprintf("insert into json_store (name,data) values('%q','%q')", name, ascii); switch_snprintf(del_sql, sizeof(del_sql), "delete from json_store where name='%q'", name); dbh = json_get_db_handle(); if (mutex) switch_mutex_lock(mutex); switch_cache_db_execute_sql(dbh, del_sql, &err); if (err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "sql err [%s]\n", err); free(err); } else { switch_cache_db_execute_sql(dbh, sql, &err); if (err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "sql err [%s]\n", err); free(err); } } if (mutex) switch_mutex_unlock(mutex); switch_safe_free(sql); switch_safe_free(ascii); switch_cache_db_release_db_handle(&dbh); return SWITCH_TRUE; } static switch_status_t json_hanguphook(switch_core_session_t *session) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_channel_state_t state = switch_channel_get_state(channel); json_store_t *session_store = NULL; char *ascii = NULL; if (state == CS_HANGUP) { if ((session_store = (json_store_t *) switch_channel_get_private(channel, "_json_store_"))) { if ((ascii = cJSON_PrintUnformatted(session_store->JSON_STORE))) { switch_channel_set_variable(channel, "json_store_data", ascii); free(ascii); } cJSON_Delete(session_store->JSON_STORE); session_store->JSON_STORE = NULL; switch_channel_set_private(channel, "_json_store_", NULL); } switch_core_event_hook_remove_state_change(session, json_hanguphook); } return SWITCH_STATUS_SUCCESS; } SWITCH_STANDARD_JSON_API(json_store_function) { cJSON *JSON_STORE, *reply = NULL, *data = cJSON_GetObjectItem(json, "data"); switch_status_t status = SWITCH_STATUS_FALSE; const char *cmd_attr = cJSON_GetObjectCstr(data, "cmd"); const char *uuid = cJSON_GetObjectCstr(data, "uuid"); const char *error = NULL, *message = NULL; store_cmd_t cmd; const char *key = cJSON_GetObjectCstr(data, "key"); const char *verbose = cJSON_GetObjectCstr(data, "verbose"); const char *commit = cJSON_GetObjectCstr(data, "commit"); const char *file = cJSON_GetObjectCstr(data, "file"); const char *storename = cJSON_GetObjectCstr(data, "storeName"); cJSON *obj, **use_store = NULL; switch_core_session_t *tsession = NULL; switch_channel_t *tchannel = NULL; json_store_t *session_store = NULL; reply = cJSON_CreateObject(); if (uuid) { if ((tsession = switch_core_session_locate(uuid))) { tchannel = switch_core_session_get_channel(tsession); } else { error = "Invalid INPUT, Missing UUID"; goto end; } } else { if (zstr(storename)) { storename = "global"; } } if (zstr(cmd_attr)) { error = "INVALID INPUT, Command not supplied"; goto end; } if (!strcasecmp(cmd_attr, "add")) { cmd = CMD_ADD; } else if (!strcasecmp(cmd_attr, "del")) { cmd = CMD_DEL; } else if (!strcasecmp(cmd_attr, "dump")) { cmd = CMD_DUMP; } else if (!strcasecmp(cmd_attr, "commit")) { cmd = CMD_COMMIT; } else if (!strcasecmp(cmd_attr, "retrieve")) { cmd = CMD_RETRIEVE; } else { error = "INVALID INPUT, Unknown Command"; goto end; } if (cmd == CMD_ADD) { if (zstr(key)) { error = "INVALID INPUT, No key supplied"; goto end; } } if (cmd == CMD_RETRIEVE || cmd == CMD_COMMIT) { if (zstr(file)) { error = "INVALID INPUT, No file specified"; goto end; } } switch_mutex_lock(json_GLOBALS.store_mutex); if (tsession) { if (!(session_store = (json_store_t *) switch_channel_get_private(tchannel, "_json_store_"))) { session_store = switch_core_session_alloc(tsession, sizeof(*session_store)); switch_mutex_init(&session_store->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(tsession)); session_store->JSON_STORE = cJSON_CreateObject(); switch_channel_set_private(tchannel, "_json_store_", session_store); switch_core_event_hook_add_state_change(tsession, json_hanguphook); } use_store = &session_store->JSON_STORE; switch_mutex_lock(session_store->mutex); switch_mutex_unlock(json_GLOBALS.store_mutex); } else { JSON_STORE = switch_core_hash_find(json_GLOBALS.store_hash, storename); if (!JSON_STORE) { JSON_STORE = cJSON_CreateObject(); switch_core_hash_insert(json_GLOBALS.store_hash, storename, JSON_STORE); } use_store = &JSON_STORE; } switch(cmd) { case CMD_RETRIEVE: obj = json_retrieve(file, NULL); if (!obj) { error = "CANNOT LOAD DATA"; if (session_store) { switch_mutex_unlock(session_store->mutex); } else { switch_mutex_unlock(json_GLOBALS.store_mutex); } goto end; } cJSON_Delete(*use_store); *use_store = obj; message = "Store Loaded"; break; case CMD_ADD: if (!(obj = cJSON_GetObjectItem(data, key))) { error = "INVALID INPUT"; if (session_store) { switch_mutex_unlock(session_store->mutex); } else { switch_mutex_unlock(json_GLOBALS.store_mutex); } goto end; } cJSON_DeleteItemFromObject(*use_store, key); obj = cJSON_Duplicate(obj, 1); cJSON_AddItemToObject(*use_store, key, obj); message = "Item Added"; break; case CMD_DEL: if (!key) { cJSON_Delete(*use_store); *use_store = cJSON_CreateObject(); message = "Store Deleted"; } else { cJSON_DeleteItemFromObject(*use_store, key); message = "Item Deleted"; } break; default: break; } if (switch_true(verbose) || cmd == CMD_DUMP) { cJSON *dump; if (key) { dump = cJSON_GetObjectItem(*use_store, key); } else { dump = *use_store; } if (dump) { dump = cJSON_Duplicate(dump, 1); cJSON_AddItemToObject(reply, "data", dump); message = "Data Dumped"; } else { error = "Key not found"; } } if (session_store) { switch_mutex_unlock(session_store->mutex); } else { switch_mutex_unlock(json_GLOBALS.store_mutex); } if (cmd == CMD_COMMIT || commit) { switch_bool_t ok; if (commit && zstr(file)) { file = commit; } if (session_store) { ok = json_commit(session_store->JSON_STORE, file, session_store->mutex); } else { ok = json_commit(JSON_STORE, file, json_GLOBALS.store_mutex); } cJSON_AddItemToObject(reply, "commitStatus", cJSON_CreateString(ok ? "success" : "fail")); if (!message) { message = "Message Comitted"; } status = SWITCH_STATUS_SUCCESS; } end: if (!zstr(error)) { cJSON_AddItemToObject(reply, "errorMessage", cJSON_CreateString(error)); } if (!zstr(message)) { cJSON_AddItemToObject(reply, "message", cJSON_CreateString(message)); status = SWITCH_STATUS_SUCCESS; } *json_reply = reply; if (tsession) { switch_core_session_rwunlock(tsession); } return status; } #define add_it(_name, _ename) if ((tmp = switch_event_get_header(event, _ename))) { cJSON_AddItemToObject(data, _name, cJSON_CreateString(tmp));} static void presence_event_handler(switch_event_t *event) { cJSON *msg = NULL, *data = NULL; const char *tmp; switch_event_header_t *hp; char *event_channel; const char *presence_id = switch_event_get_header(event, "channel-presence-id"); if (!globals.running) { return; } if (!globals.enable_presence || zstr(presence_id)) { return; } msg = cJSON_CreateObject(); data = json_add_child_obj(msg, "data", NULL); event_channel = switch_mprintf("presence.%s", presence_id); cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel)); add_it("channelCallState", "channel-call-state"); add_it("originalChannelCallState", "original-channel-call-state"); add_it("channelState", "channel-state"); add_it("callerUserName", "caller-username"); add_it("callerIDName", "caller-caller-id-name"); add_it("callerIDNumber", "caller-caller-id-number"); add_it("calleeIDName", "caller-callee-id-name"); add_it("calleeIDNumber", "caller-callee-id-number"); add_it("channelUUID", "unique-id"); add_it("presenceCallDirection", "presence-call-direction"); add_it("channelPresenceID", "channel-presence-id"); add_it("channelPresenceData", "channel-presence-data"); for(hp = event->headers; hp; hp = hp->next) { if (!strncasecmp(hp->name, "PD-", 3)) { add_it(hp->name, hp->name); } } switch_event_channel_broadcast(event_channel, &msg, __FILE__, NO_EVENT_CHANNEL_ID); free(event_channel); } static void event_handler(switch_event_t *event) { cJSON *msg = NULL, *data = NULL; char *event_channel; if (!globals.enable_fs_events) { return; } switch_event_serialize_json_obj(event, &data); msg = cJSON_CreateObject(); if (event->event_id == SWITCH_EVENT_CUSTOM) { const char *subclass = switch_event_get_header(event, "Event-Subclass"); event_channel = switch_mprintf("FSevent.%s::%s", switch_event_name(event->event_id), subclass); switch_tolower_max(event_channel + 8); } else { event_channel = switch_mprintf("FSevent.%s", switch_event_name(event->event_id)); switch_tolower_max(event_channel + 8); } cJSON_AddItemToObject(msg, "eventChannel", cJSON_CreateString(event_channel)); cJSON_AddItemToObject(msg, "data", data); /* comment broadcasting globally and change to only within the module cos FS events are heavy */ //switch_event_channel_broadcast(event_channel, &msg, __FILE__, NO_EVENT_CHANNEL_ID); verto_broadcast(event_channel, msg, __FILE__, NO_EVENT_CHANNEL_ID);cJSON_Delete(msg); free(event_channel); } /* Macro expands to: switch_status_t mod_verto_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ SWITCH_MODULE_LOAD_FUNCTION(mod_verto_load) { switch_api_interface_t *api_interface = NULL; switch_chat_interface_t *chat_interface = NULL; switch_json_api_interface_t *json_api_interface = NULL; int r; switch_cache_db_handle_t *dbh; //switch_application_interface_t *app_interface = NULL; memset(&globals, 0, sizeof(globals)); globals.pool = pool; globals.ready = SIGUSR1; globals.enable_presence = SWITCH_TRUE; globals.enable_fs_events = SWITCH_FALSE; switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_mutex_init(&globals.method_mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_core_hash_init(&globals.method_hash); switch_thread_rwlock_create(&globals.event_channel_rwlock, globals.pool); switch_core_hash_init(&globals.event_channel_hash); switch_mutex_init(&globals.jsock_mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_core_hash_init(&globals.jsock_hash); switch_thread_rwlock_create(&globals.tech_rwlock, globals.pool); switch_mutex_init(&globals.detach_mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_mutex_init(&globals.detach2_mutex, SWITCH_MUTEX_NESTED, globals.pool); switch_thread_cond_create(&globals.detach_cond, globals.pool); globals.detach_timeout = 120; memset(&json_GLOBALS, 0, sizeof(json_GLOBALS)); switch_mutex_init(&json_GLOBALS.store_mutex, SWITCH_MUTEX_NESTED, pool); switch_core_hash_init(&json_GLOBALS.store_hash); dbh = json_get_db_handle(); switch_cache_db_test_reactive(dbh, "select name from json_store where name=''", "drop table json_store", json_sql); switch_cache_db_release_db_handle(&dbh); switch_event_channel_bind(SWITCH_EVENT_CHANNEL_GLOBAL, verto_broadcast, &globals.event_channel_id); r = init(); if (r) return SWITCH_STATUS_TERM; jrpc_init(); /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); SWITCH_ADD_API(api_interface, "verto", "Verto API", verto_function, "syntax"); SWITCH_ADD_API(api_interface, "verto_contact", "Generate a verto endpoint dialstring", verto_contact_function, "user@domain"); SWITCH_ADD_JSON_API(json_api_interface, "store", "JSON store", json_store_function, ""); verto_endpoint_interface = (switch_endpoint_interface_t *) switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE); verto_endpoint_interface->interface_name = EP_NAME; verto_endpoint_interface->io_routines = &verto_io_routines; SWITCH_ADD_CHAT(chat_interface, VERTO_CHAT_PROTO, chat_send); switch_core_register_secondary_recover_callback(modname, verto_recover_callback); if (globals.enable_presence) { switch_event_bind(modname, SWITCH_EVENT_CHANNEL_CALLSTATE, SWITCH_EVENT_SUBCLASS_ANY, presence_event_handler, NULL); } if (globals.enable_fs_events) { if (switch_event_bind(modname, SWITCH_EVENT_ALL, SWITCH_EVENT_SUBCLASS_ANY, event_handler, NULL) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind!\n"); return SWITCH_STATUS_GENERR; } } run_profiles(); /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; } /* Called when the system shuts down Macro expands to: switch_status_t mod_verto_shutdown() */ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_verto_shutdown) { json_cleanup(); switch_core_hash_destroy(&json_GLOBALS.store_hash); switch_event_channel_unbind(NULL, verto_broadcast); switch_event_unbind_callback(presence_event_handler); switch_event_unbind_callback(event_handler); switch_core_unregister_secondary_recover_callback(modname); do_shutdown(); attach_wake(); attach_wake(); switch_core_hash_destroy(&globals.method_hash); switch_core_hash_destroy(&globals.event_channel_hash); switch_core_hash_destroy(&globals.jsock_hash); return SWITCH_STATUS_SUCCESS; } SWITCH_MODULE_RUNTIME_FUNCTION(mod_verto_runtime) { switch_mutex_lock(globals.detach_mutex); while(globals.running) { if (globals.detached) { drop_detached(); switch_yield(1000000); } else { switch_mutex_lock(globals.detach2_mutex); if (globals.running) { switch_thread_cond_wait(globals.detach_cond, globals.detach_mutex); } switch_mutex_unlock(globals.detach2_mutex); } } switch_mutex_unlock(globals.detach_mutex); return SWITCH_STATUS_TERM; } /* 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 noet */